/** * pi-focus v1.1 — Focus timer with productivity metrics * * Not just a timer — tracks tokens/min during focus vs break. * Shows productivity score. Data-driven focus management. * * /focus start [min] — start focus (default 25min) * /focus break — take a break (5min) * /focus stop — stop timer * /focus stats — productivity metrics * /focus — show status */ import type { ExtensionAPI } from '@anthropic-ai/claude-code' interface FocusSession { start: number; end: number; tokens: number; messages: number; mode: 'focus' | 'break' } let timer: ReturnType | null = null let isActive = false, onBreak = false let focusStart = 0, focusDuration = 25, breakDuration = 5 let tokensThisPeriod = 0, messagesThisPeriod = 0 const sessions: FocusSession[] = [] let sessionsCompleted = 0 function endPeriod(mode: 'focus' | 'break') { sessions.push({ start: focusStart, end: Date.now(), tokens: tokensThisPeriod, messages: messagesThisPeriod, mode }) if (sessions.length > 50) sessions.shift() tokensThisPeriod = 0 messagesThisPeriod = 0 } function formatStats(): string { if (sessions.length === 0) return 'No completed sessions. Start with `/focus start`.' const focusSessions = sessions.filter(s => s.mode === 'focus') const breakSessions = sessions.filter(s => s.mode === 'break') const focusTokPerMin = focusSessions.length > 0 ? focusSessions.reduce((sum, s) => { const mins = Math.max(1, (s.end - s.start) / 60000) return sum + s.tokens / mins }, 0) / focusSessions.length : 0 const breakTokPerMin = breakSessions.length > 0 ? breakSessions.reduce((sum, s) => { const mins = Math.max(1, (s.end - s.start) / 60000) return sum + s.tokens / mins }, 0) / breakSessions.length : 0 const focusMsgPerMin = focusSessions.length > 0 ? focusSessions.reduce((sum, s) => { const mins = Math.max(1, (s.end - s.start) / 60000) return sum + s.messages / mins }, 0) / focusSessions.length : 0 const totalFocusMin = focusSessions.reduce((sum, s) => sum + (s.end - s.start) / 60000, 0) const totalBreakMin = breakSessions.reduce((sum, s) => sum + (s.end - s.start) / 60000, 0) const ratio = totalBreakMin > 0 ? (totalFocusMin / totalBreakMin).toFixed(1) : '∞' // Productivity score: higher tok/min during focus = better const productivity = Math.min(100, Math.round(focusTokPerMin / 10)) return [ `## Focus Productivity`, '', `| Metric | Value |`, `|--------|-------|`, `| Sessions | ${sessionsCompleted} completed |`, `| Focus time | ${Math.round(totalFocusMin)}m |`, `| Break time | ${Math.round(totalBreakMin)}m |`, `| Focus:Break | ${ratio} |`, `| **Tok/min (focus)** | **${Math.round(focusTokPerMin)}** |`, `| Tok/min (break) | ${Math.round(breakTokPerMin)} |`, `| Msg/min (focus) | ${focusMsgPerMin.toFixed(1)} |`, `| **Productivity** | **${productivity}/100** |`, '', focusTokPerMin > breakTokPerMin * 1.5 ? '📈 You\'re significantly more productive during focus periods!' : focusTokPerMin > breakTokPerMin ? '📊 Focus mode is working — slightly more productive than breaks.' : '🤔 No significant difference between focus and break productivity.', ].join('\n') } export default function init(pi: ExtensionAPI) { pi.on('message_end', (event: any) => { const msg = event.message as any const tokens = (msg?.usage?.input_tokens || 0) + (msg?.usage?.output_tokens || 0) tokensThisPeriod += tokens messagesThisPeriod++ }) function startFocus(mins: number) { if (isActive) endPeriod(onBreak ? 'break' : 'focus') focusStart = Date.now(); focusDuration = mins; isActive = true; onBreak = false tokensThisPeriod = 0; messagesThisPeriod = 0 if (timer) clearTimeout(timer) timer = setTimeout(() => { endPeriod('focus'); sessionsCompleted++; isActive = false pi.sendMessage({ content: `🍅 **Focus done!** ${mins}min. Sessions: ${sessionsCompleted}. Run \`/focus stats\` for metrics.`, display: true }, { triggerTurn: false }) }, mins * 60000) } function startBreak() { if (isActive) endPeriod('focus') focusStart = Date.now(); onBreak = true; isActive = false tokensThisPeriod = 0; messagesThisPeriod = 0 if (timer) clearTimeout(timer) timer = setTimeout(() => { endPeriod('break'); onBreak = false pi.sendMessage({ content: '☕ **Break over!** `/focus start` for next session.', display: true }, { triggerTurn: false }) }, breakDuration * 60000) } pi.addCommand({ name: 'focus', description: 'Focus timer with productivity metrics', handler: async (args) => { const parts = args.trim().split(/\s+/) const sub = parts[0]?.toLowerCase() if (sub === 'start') { startFocus(parseInt(parts[1], 10) || 25); pi.sendMessage({ content: `🍅 **Focus!** ${focusDuration}min.`, display: true }, { triggerTurn: false }); return } if (sub === 'break') { startBreak(); pi.sendMessage({ content: `☕ **Break!** ${breakDuration}min.`, display: true }, { triggerTurn: false }); return } if (sub === 'stop') { if (isActive) endPeriod(onBreak ? 'break' : 'focus'); if (timer) clearTimeout(timer); isActive = false; onBreak = false; pi.sendMessage({ content: 'Timer stopped.', display: true }, { triggerTurn: false }); return } if (sub === 'stats') { pi.sendMessage({ content: formatStats(), display: true }, { triggerTurn: false }); return } // Status const elapsed = isActive ? Math.round((Date.now() - focusStart) / 60000) : 0 const remaining = isActive ? focusDuration - elapsed : 0 pi.sendMessage({ content: [ '## Focus', isActive ? `🍅 **Focusing** — ${remaining}min left (${tokensThisPeriod} tok this period)` : onBreak ? `☕ **Break** (${tokensThisPeriod} tok)` : '⏸️ Not active', `Sessions: ${sessionsCompleted}`, '', '/focus start|break|stop|stats' ].join('\n'), display: true }, { triggerTurn: false }) } }) pi.addTool({ name: 'focus_stats', description: 'Get focus productivity metrics — tok/min during focus vs break.', parameters: { type: 'object', properties: {} }, handler: async () => formatStats() }) }