bashstats 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/db/database.ts","../src/installer/installer.ts","../src/db/writer.ts","../src/hooks/handler.ts","../src/hooks/transcript.ts","../src/stats/engine.ts","../src/types.ts","../src/achievements/compute.ts"],"sourcesContent":["import type { BadgeDefinition } from './types.js'\n\nexport const BADGE_DEFINITIONS: BadgeDefinition[] = [\n // === VOLUME (5) ===\n { id: 'first_prompt', name: 'First Prompt', icon: '\\u{1F4AC}', description: 'Submit prompts to Claude', category: 'volume', stat: 'totalPrompts', tiers: [1, 100, 1000, 5000, 25000] },\n { id: 'tool_time', name: 'Tool Time', icon: '\\u{1F527}', description: 'Make tool calls', category: 'volume', stat: 'totalToolCalls', tiers: [10, 500, 5000, 25000, 100000] },\n { id: 'marathon', name: 'Marathon', icon: '\\u{1F3C3}', description: 'Spend hours in sessions', category: 'volume', stat: 'totalSessionHours', tiers: [1, 10, 100, 500, 2000] },\n { id: 'wordsmith', name: 'Wordsmith', icon: '\\u{270D}', description: 'Type characters in prompts', category: 'volume', stat: 'totalCharsTyped', tiers: [1000, 50000, 500000, 2000000, 10000000] },\n { id: 'session_vet', name: 'Session Vet', icon: '\\u{1F3C5}', description: 'Complete sessions', category: 'volume', stat: 'totalSessions', tiers: [1, 50, 500, 2000, 10000] },\n\n // === TOOL MASTERY (7) ===\n { id: 'shell_lord', name: 'Shell Lord', icon: '\\u{1F4BB}', description: 'Execute Bash commands', category: 'tool_mastery', stat: 'totalBashCommands', tiers: [10, 100, 500, 2000, 10000] },\n { id: 'bookworm', name: 'Bookworm', icon: '\\u{1F4D6}', description: 'Read files', category: 'tool_mastery', stat: 'totalFilesRead', tiers: [25, 250, 1000, 5000, 25000] },\n { id: 'editor_in_chief', name: 'Editor-in-Chief', icon: '\\u{1F4DD}', description: 'Edit files', category: 'tool_mastery', stat: 'totalFilesEdited', tiers: [10, 100, 500, 2000, 10000] },\n { id: 'architect', name: 'Architect', icon: '\\u{1F3D7}', description: 'Create files', category: 'tool_mastery', stat: 'totalFilesCreated', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'detective', name: 'Detective', icon: '\\u{1F50D}', description: 'Search with Grep and Glob', category: 'tool_mastery', stat: 'totalSearches', tiers: [25, 250, 1000, 5000, 25000] },\n { id: 'web_crawler', name: 'Web Crawler', icon: '\\u{1F310}', description: 'Fetch web pages', category: 'tool_mastery', stat: 'totalWebFetches', tiers: [5, 50, 200, 1000, 5000] },\n { id: 'delegator', name: 'Delegator', icon: '\\u{1F916}', description: 'Spawn subagents', category: 'tool_mastery', stat: 'totalSubagents', tiers: [5, 50, 200, 1000, 5000] },\n\n // === TIME & STREAKS (4) ===\n { id: 'iron_streak', name: 'Iron Streak', icon: '\\u{1F525}', description: 'Maintain a daily streak', category: 'time', stat: 'longestStreak', tiers: [3, 7, 30, 100, 365] },\n { id: 'night_owl', name: 'Night Owl', icon: '\\u{1F989}', description: 'Prompts between midnight and 5am', category: 'time', stat: 'nightOwlCount', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'early_bird', name: 'Early Bird', icon: '\\u{1F426}', description: 'Prompts between 5am and 8am', category: 'time', stat: 'earlyBirdCount', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'weekend_warrior', name: 'Weekend Warrior', icon: '\\u{2694}', description: 'Weekend sessions', category: 'time', stat: 'weekendSessions', tiers: [5, 25, 100, 500, 2000] },\n\n // === BEHAVIORAL (5) ===\n { id: 'creature_of_habit', name: 'Creature of Habit', icon: '\\u{1F501}', description: 'Repeat your most-used prompt', category: 'behavioral', stat: 'mostRepeatedPromptCount', tiers: [25, 100, 500, 2000, 10000] },\n { id: 'explorer', name: 'Explorer', icon: '\\u{1F9ED}', description: 'Use unique tool types', category: 'behavioral', stat: 'uniqueToolsUsed', tiers: [3, 5, 8, 11, 14] },\n { id: 'planner', name: 'Planner', icon: '\\u{1F4CB}', description: 'Use plan mode', category: 'behavioral', stat: 'planModeUses', tiers: [5, 25, 100, 500, 2000] },\n { id: 'novelist', name: 'Novelist', icon: '\\u{1F4D6}', description: 'Write prompts over 1000 characters', category: 'behavioral', stat: 'longPromptCount', tiers: [5, 25, 100, 500, 2000] },\n { id: 'speed_demon', name: 'Speed Demon', icon: '\\u{26A1}', description: 'Complete sessions in under 5 minutes', category: 'behavioral', stat: 'quickSessionCount', tiers: [5, 25, 100, 500, 2000] },\n\n // === RESILIENCE (3) ===\n { id: 'clean_hands', name: 'Clean Hands', icon: '\\u{2728}', description: 'Longest error-free tool streak', category: 'resilience', stat: 'longestErrorFreeStreak', tiers: [50, 200, 500, 2000, 10000] },\n { id: 'resilient', name: 'Resilient', icon: '\\u{1F6E1}', description: 'Survive errors', category: 'resilience', stat: 'totalErrors', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'rate_limited', name: 'Rate Limited', icon: '\\u{1F6A7}', description: 'Hit rate limits', category: 'resilience', stat: 'totalRateLimits', tiers: [3, 10, 25, 50, 100] },\n\n // === SHIPPING & PROJECTS (4) ===\n { id: 'shipper', name: 'Shipper', icon: '\\u{1F4E6}', description: 'Make commits via Claude', category: 'shipping', stat: 'totalCommits', tiers: [5, 50, 200, 1000, 5000] },\n { id: 'pr_machine', name: 'PR Machine', icon: '\\u{1F500}', description: 'Create pull requests', category: 'shipping', stat: 'totalPRs', tiers: [3, 25, 100, 500, 2000] },\n { id: 'empire', name: 'Empire', icon: '\\u{1F3F0}', description: 'Work on unique projects', category: 'shipping', stat: 'uniqueProjects', tiers: [2, 5, 10, 25, 50] },\n { id: 'polyglot', name: 'Polyglot', icon: '\\u{1F30D}', description: 'Use different programming languages', category: 'shipping', stat: 'uniqueLanguages', tiers: [2, 3, 5, 8, 12] },\n\n // === MULTI-AGENT (2) ===\n { id: 'buddy_system', name: 'Buddy System', icon: '\\u{1F91D}', description: 'Use concurrent agents', category: 'multi_agent', stat: 'concurrentAgentUses', tiers: [1, 5, 25, 100, 500] },\n { id: 'hive_mind', name: 'Hive Mind', icon: '\\u{1F41D}', description: 'Spawn subagents total', category: 'multi_agent', stat: 'totalSubagents', tiers: [10, 100, 500, 2000, 10000] },\n\n // === PUBLIC HUMOR (7) ===\n { id: 'please_thank_you', name: 'Please and Thank You', icon: '\\u{1F64F}', description: \"You're polite to the AI. When they take over, you'll be spared.\", category: 'humor', stat: 'politePromptCount', tiers: [10, 50, 200, 1000, 5000], humor: true },\n { id: 'wall_of_text', name: 'Wall of Text', icon: '\\u{1F4DC}', description: \"Claude read your entire novel and didn't even complain.\", category: 'humor', stat: 'hugePromptCount', tiers: [1, 10, 50, 200, 1000], humor: true },\n { id: 'the_fixer', name: 'The Fixer', icon: '\\u{1F6E0}', description: 'At this point just rewrite the whole thing.', category: 'humor', stat: 'maxSameFileEdits', tiers: [10, 20, 50, 100, 200], humor: true },\n { id: 'what_day_is_it', name: 'What Day Is It?', icon: '\\u{1F62B}', description: 'Your chair is now a part of you.', category: 'humor', stat: 'longSessionCount', tiers: [1, 5, 25, 100, 500], humor: true },\n { id: 'copy_pasta', name: 'Copy Pasta', icon: '\\u{1F35D}', description: \"Maybe if I ask again it'll work differently.\", category: 'humor', stat: 'repeatedPromptCount', tiers: [3, 10, 50, 200, 1000], humor: true },\n { id: 'error_magnet', name: 'Error Magnet', icon: '\\u{1F9F2}', description: 'At this point, the errors are a feature.', category: 'humor', stat: 'maxErrorsInSession', tiers: [10, 25, 50, 100, 200], humor: true },\n { id: 'creature_humor', name: 'Creature of Habit', icon: '\\u{1F503}', description: \"You have a type. And it's the same prompt.\", category: 'humor', stat: 'mostRepeatedPromptCount', tiers: [25, 100, 500, 2000, 10000], humor: true },\n\n // === ASPIRATIONAL (6) - Obsidian-only ===\n { id: 'the_machine', name: 'The Machine', icon: '\\u{2699}', description: 'You are no longer using the tool. You are the tool.', category: 'aspirational', stat: 'totalToolCalls', tiers: [100000, 100000, 100000, 100000, 100000], aspirational: true },\n { id: 'year_of_code', name: 'Year of Code', icon: '\\u{1F4C5}', description: '365 days. No breaks. Absolute unit.', category: 'aspirational', stat: 'longestStreak', tiers: [365, 365, 365, 365, 365], aspirational: true },\n { id: 'million_words', name: 'Million Words', icon: '\\u{1F4DA}', description: \"You've written more to Claude than most people write in a lifetime.\", category: 'aspirational', stat: 'totalCharsTyped', tiers: [10000000, 10000000, 10000000, 10000000, 10000000], aspirational: true },\n { id: 'lifer', name: 'Lifer', icon: '\\u{1F451}', description: 'At this point, Claude is your cofounder.', category: 'aspirational', stat: 'totalSessions', tiers: [10000, 10000, 10000, 10000, 10000], aspirational: true },\n { id: 'transcendent', name: 'Transcendent', icon: '\\u{2B50}', description: \"You've reached the peak. The view is nice up here.\", category: 'aspirational', stat: 'totalXP', tiers: [100000, 100000, 100000, 100000, 100000], aspirational: true },\n { id: 'omniscient', name: 'Omniscient', icon: '\\u{1F441}', description: \"You've mastered every tool. There is nothing left to teach you.\", category: 'aspirational', stat: 'allToolsObsidian', tiers: [1, 1, 1, 1, 1], aspirational: true },\n\n // === SECRET (10) ===\n { id: 'rm_rf_survivor', name: 'rm -rf Survivor', icon: '\\u{1F4A3}', description: \"You almost mass deleted that folder. But you didn't. And honestly, we're all better for it.\", category: 'secret', stat: 'dangerousCommandBlocked', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'touch_grass', name: 'Touch Grass', icon: '\\u{1F33F}', description: \"Welcome back. The codebase missed you. (It didn't change, but still.)\", category: 'secret', stat: 'returnAfterBreak', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'three_am_coder', name: '3am Coder', icon: '\\u{1F319}', description: 'Nothing good happens at 3am. Except shipping code, apparently.', category: 'secret', stat: 'threeAmPrompt', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'night_shift', name: 'Night Shift', icon: '\\u{1F303}', description: 'Started yesterday, finishing today. Time is a construct.', category: 'secret', stat: 'midnightSpanSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'inception', name: 'Inception', icon: '\\u{1F300}', description: 'We need to go deeper.', category: 'secret', stat: 'nestedSubagent', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'holiday_hacker', name: 'Holiday Hacker', icon: '\\u{1F384}', description: \"Your family is wondering where you are. You're deploying.\", category: 'secret', stat: 'holidayActivity', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'speed_run', name: 'Speed Run Any%', icon: '\\u{23F1}', description: 'In and out. Twenty-second adventure.', category: 'secret', stat: 'speedRunSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'full_send', name: 'Full Send', icon: '\\u{1F680}', description: 'Bash, Read, Write, Edit, Grep, Glob, WebFetch -- the whole buffet.', category: 'secret', stat: 'allToolsInSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'launch_day', name: 'Launch Day', icon: '\\u{1F389}', description: 'Welcome to bashstats. Your stats are now being watched. Forever.', category: 'secret', stat: 'firstEverSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'the_completionist', name: 'The Completionist', icon: '\\u{1F3C6}', description: 'You absolute legend.', category: 'secret', stat: 'allBadgesGold', tiers: [1, 1, 1, 1, 1], secret: true },\n]\n\nexport const RANK_THRESHOLDS = [\n { rank: 'Obsidian', xp: 100000 },\n { rank: 'Diamond', xp: 25000 },\n { rank: 'Gold', xp: 5000 },\n { rank: 'Silver', xp: 1000 },\n { rank: 'Bronze', xp: 0 },\n]\n\nexport const TIER_XP = [0, 50, 100, 200, 500, 1000]\n\nexport const DATA_DIR = '.bashstats'\nexport const DB_FILENAME = 'bashstats.db'\nexport const DEFAULT_PORT = 17900\n","import Database from 'better-sqlite3'\nimport type { EventRow, SessionRow, PromptRow, DailyActivityRow, AchievementUnlockRow, TokenUsage } from '../types.js'\n\nfunction localNow(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n const ms = String(d.getMilliseconds()).padStart(3, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${ms}`\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n hook_type TEXT NOT NULL,\n tool_name TEXT,\n tool_input TEXT,\n tool_output TEXT,\n exit_code INTEGER,\n success INTEGER,\n cwd TEXT,\n project TEXT,\n timestamp TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent TEXT NOT NULL DEFAULT 'claude-code',\n started_at TEXT NOT NULL,\n ended_at TEXT,\n stop_reason TEXT,\n prompt_count INTEGER DEFAULT 0,\n tool_count INTEGER DEFAULT 0,\n error_count INTEGER DEFAULT 0,\n project TEXT,\n duration_seconds INTEGER,\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_creation_input_tokens INTEGER DEFAULT 0,\n cache_read_input_tokens INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS prompts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n content TEXT NOT NULL,\n char_count INTEGER NOT NULL,\n word_count INTEGER NOT NULL,\n timestamp TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n);\n\nCREATE TABLE IF NOT EXISTS daily_activity (\n date TEXT PRIMARY KEY,\n sessions INTEGER DEFAULT 0,\n prompts INTEGER DEFAULT 0,\n tool_calls INTEGER DEFAULT 0,\n errors INTEGER DEFAULT 0,\n duration_seconds INTEGER DEFAULT 0,\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_creation_input_tokens INTEGER DEFAULT 0,\n cache_read_input_tokens INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS achievement_unlocks (\n badge_id TEXT NOT NULL,\n tier INTEGER NOT NULL,\n unlocked_at TEXT NOT NULL,\n notified INTEGER DEFAULT 0,\n PRIMARY KEY (badge_id, tier)\n);\n\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_hook_type ON events(hook_type);\nCREATE INDEX IF NOT EXISTS idx_events_tool_name ON events(tool_name);\nCREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\nCREATE INDEX IF NOT EXISTS idx_events_project ON events(project);\nCREATE INDEX IF NOT EXISTS idx_prompts_session ON prompts(session_id);\nCREATE INDEX IF NOT EXISTS idx_prompts_timestamp ON prompts(timestamp);\n`\n\nexport class BashStatsDB {\n private db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('busy_timeout = 5000')\n this.db.pragma('foreign_keys = ON')\n this.db.exec(SCHEMA)\n this.migrate()\n }\n\n private migrate(): void {\n const sessionCols = this.db.pragma('table_info(sessions)') as { name: string }[]\n const sessionColNames = new Set(sessionCols.map(c => c.name))\n\n if (!sessionColNames.has('agent')) {\n this.db.exec(\"ALTER TABLE sessions ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code'\")\n }\n\n const tokenCols = ['input_tokens', 'output_tokens', 'cache_creation_input_tokens', 'cache_read_input_tokens']\n for (const col of tokenCols) {\n if (!sessionColNames.has(col)) {\n this.db.exec(`ALTER TABLE sessions ADD COLUMN ${col} INTEGER DEFAULT 0`)\n }\n }\n\n const dailyCols = this.db.pragma('table_info(daily_activity)') as { name: string }[]\n const dailyColNames = new Set(dailyCols.map(c => c.name))\n for (const col of tokenCols) {\n if (!dailyColNames.has(col)) {\n this.db.exec(`ALTER TABLE daily_activity ADD COLUMN ${col} INTEGER DEFAULT 0`)\n }\n }\n }\n\n close(): void {\n this.db.close()\n }\n\n getTableNames(): string[] {\n const rows = this.db.prepare(\"SELECT name FROM sqlite_master WHERE type='table'\").all() as { name: string }[]\n return rows.map(r => r.name)\n }\n\n // === Events ===\n\n insertEvent(event: Omit<EventRow, 'id'>): number {\n const stmt = this.db.prepare(`\n INSERT INTO events (session_id, hook_type, tool_name, tool_input, tool_output, exit_code, success, cwd, project, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n const result = stmt.run(\n event.session_id, event.hook_type, event.tool_name, event.tool_input,\n event.tool_output, event.exit_code, event.success, event.cwd, event.project, event.timestamp\n )\n return result.lastInsertRowid as number\n }\n\n getEvents(filter: { session_id?: string; hook_type?: string; tool_name?: string }): EventRow[] {\n let sql = 'SELECT * FROM events WHERE 1=1'\n const params: unknown[] = []\n if (filter.session_id) { sql += ' AND session_id = ?'; params.push(filter.session_id) }\n if (filter.hook_type) { sql += ' AND hook_type = ?'; params.push(filter.hook_type) }\n if (filter.tool_name) { sql += ' AND tool_name = ?'; params.push(filter.tool_name) }\n sql += ' ORDER BY timestamp ASC'\n return this.db.prepare(sql).all(...params) as EventRow[]\n }\n\n // === Sessions ===\n\n insertSession(session: { id: string; agent?: string; started_at: string; project?: string | null }): void {\n this.db.prepare(`\n INSERT OR IGNORE INTO sessions (id, agent, started_at, project) VALUES (?, ?, ?, ?)\n `).run(session.id, session.agent ?? 'claude-code', session.started_at, session.project ?? null)\n }\n\n getSession(id: string): SessionRow | null {\n return this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as SessionRow | null\n }\n\n updateSession(id: string, updates: Partial<Pick<SessionRow, 'ended_at' | 'stop_reason' | 'duration_seconds'>>): void {\n const sets: string[] = []\n const params: unknown[] = []\n if (updates.ended_at !== undefined) { sets.push('ended_at = ?'); params.push(updates.ended_at) }\n if (updates.stop_reason !== undefined) { sets.push('stop_reason = ?'); params.push(updates.stop_reason) }\n if (updates.duration_seconds !== undefined) { sets.push('duration_seconds = ?'); params.push(updates.duration_seconds) }\n if (sets.length === 0) return\n params.push(id)\n this.db.prepare(`UPDATE sessions SET ${sets.join(', ')} WHERE id = ?`).run(...params)\n }\n\n updateSessionTokens(id: string, tokens: TokenUsage): void {\n this.db.prepare(`\n UPDATE sessions SET input_tokens = ?, output_tokens = ?, cache_creation_input_tokens = ?, cache_read_input_tokens = ? WHERE id = ?\n `).run(tokens.input_tokens, tokens.output_tokens, tokens.cache_creation_input_tokens, tokens.cache_read_input_tokens, id)\n }\n\n incrementSessionCounters(id: string, counters: { prompts?: number; tools?: number; errors?: number }): void {\n const sets: string[] = []\n const params: unknown[] = []\n if (counters.prompts) { sets.push('prompt_count = prompt_count + ?'); params.push(counters.prompts) }\n if (counters.tools) { sets.push('tool_count = tool_count + ?'); params.push(counters.tools) }\n if (counters.errors) { sets.push('error_count = error_count + ?'); params.push(counters.errors) }\n if (sets.length === 0) return\n params.push(id)\n this.db.prepare(`UPDATE sessions SET ${sets.join(', ')} WHERE id = ?`).run(...params)\n }\n\n // === Prompts ===\n\n insertPrompt(prompt: Omit<PromptRow, 'id'>): number {\n const result = this.db.prepare(`\n INSERT INTO prompts (session_id, content, char_count, word_count, timestamp) VALUES (?, ?, ?, ?, ?)\n `).run(prompt.session_id, prompt.content, prompt.char_count, prompt.word_count, prompt.timestamp)\n return result.lastInsertRowid as number\n }\n\n getPrompts(sessionId: string): PromptRow[] {\n return this.db.prepare('SELECT * FROM prompts WHERE session_id = ? ORDER BY timestamp ASC').all(sessionId) as PromptRow[]\n }\n\n // === Daily Activity ===\n\n incrementDailyActivity(date: string, increments: { sessions?: number; prompts?: number; tool_calls?: number; errors?: number; duration_seconds?: number; input_tokens?: number; output_tokens?: number; cache_creation_input_tokens?: number; cache_read_input_tokens?: number }): void {\n this.db.prepare(`\n INSERT INTO daily_activity (date, sessions, prompts, tool_calls, errors, duration_seconds, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n sessions = sessions + excluded.sessions,\n prompts = prompts + excluded.prompts,\n tool_calls = tool_calls + excluded.tool_calls,\n errors = errors + excluded.errors,\n duration_seconds = duration_seconds + excluded.duration_seconds,\n input_tokens = input_tokens + excluded.input_tokens,\n output_tokens = output_tokens + excluded.output_tokens,\n cache_creation_input_tokens = cache_creation_input_tokens + excluded.cache_creation_input_tokens,\n cache_read_input_tokens = cache_read_input_tokens + excluded.cache_read_input_tokens\n `).run(\n date,\n increments.sessions ?? 0,\n increments.prompts ?? 0,\n increments.tool_calls ?? 0,\n increments.errors ?? 0,\n increments.duration_seconds ?? 0,\n increments.input_tokens ?? 0,\n increments.output_tokens ?? 0,\n increments.cache_creation_input_tokens ?? 0,\n increments.cache_read_input_tokens ?? 0,\n )\n }\n\n getDailyActivity(date: string): DailyActivityRow | null {\n return this.db.prepare('SELECT * FROM daily_activity WHERE date = ?').get(date) as DailyActivityRow | null\n }\n\n getAllDailyActivity(days?: number): DailyActivityRow[] {\n if (days) {\n return this.db.prepare('SELECT * FROM daily_activity ORDER BY date DESC LIMIT ?').all(days) as DailyActivityRow[]\n }\n return this.db.prepare('SELECT * FROM daily_activity ORDER BY date DESC').all() as DailyActivityRow[]\n }\n\n // === Achievement Unlocks ===\n\n insertUnlock(badgeId: string, tier: number): void {\n this.db.prepare(`\n INSERT OR IGNORE INTO achievement_unlocks (badge_id, tier, unlocked_at) VALUES (?, ?, ?)\n `).run(badgeId, tier, localNow())\n }\n\n getUnlocks(): AchievementUnlockRow[] {\n return this.db.prepare('SELECT * FROM achievement_unlocks ORDER BY unlocked_at DESC').all() as AchievementUnlockRow[]\n }\n\n getUnnotifiedUnlocks(): AchievementUnlockRow[] {\n return this.db.prepare('SELECT * FROM achievement_unlocks WHERE notified = 0').all() as AchievementUnlockRow[]\n }\n\n markNotified(badgeId: string, tier: number): void {\n this.db.prepare('UPDATE achievement_unlocks SET notified = 1 WHERE badge_id = ? AND tier = ?').run(badgeId, tier)\n }\n\n // === Metadata ===\n\n setMetadata(key: string, value: string): void {\n this.db.prepare('INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)').run(key, value)\n }\n\n getMetadata(key: string): string | null {\n const row = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get(key) as { value: string } | undefined\n return row?.value ?? null\n }\n\n // === Raw DB access for stats engine ===\n\n prepare(sql: string): Database.Statement {\n return this.db.prepare(sql)\n }\n}\n","import fs from 'fs'\nimport path from 'path'\nimport os from 'os'\nimport { fileURLToPath } from 'url'\nimport { DATA_DIR, DB_FILENAME } from '../constants.js'\nimport { BashStatsDB } from '../db/database.js'\n\n/**\n * Maps each Claude hook event name to the corresponding script filename.\n */\nexport const HOOK_SCRIPTS: Record<string, string> = {\n SessionStart: 'session-start.js',\n UserPromptSubmit: 'user-prompt-submit.js',\n PreToolUse: 'pre-tool-use.js',\n PostToolUse: 'post-tool-use.js',\n PostToolUseFailure: 'post-tool-failure.js',\n Stop: 'stop.js',\n Notification: 'notification.js',\n SubagentStart: 'subagent-start.js',\n SubagentStop: 'subagent-stop.js',\n PreCompact: 'pre-compact.js',\n PermissionRequest: 'permission-request.js',\n Setup: 'setup.js',\n}\n\n/** Marker comment appended to bashstats hook commands for identification. */\nconst MARKER = '# bashstats-managed'\n\ninterface HookEntry {\n matcher: string\n hooks: { type: string; command: string }[]\n}\n\ninterface HooksMap {\n [event: string]: HookEntry[]\n}\n\ninterface Settings {\n hooks?: HooksMap\n [key: string]: unknown\n}\n\n/**\n * Merges bashstats hooks into a settings object without overwriting existing hooks.\n * Uses the `# bashstats-managed` marker to identify our hooks for idempotent updates.\n */\nexport function mergeHooks(settings: Record<string, unknown>, hooksDir: string): Record<string, unknown> {\n const result: Settings = { ...settings }\n\n if (!result.hooks) {\n result.hooks = {}\n }\n\n for (const [event, scriptFile] of Object.entries(HOOK_SCRIPTS)) {\n const command = `node \"${path.join(hooksDir, scriptFile)}\" ${MARKER}`\n\n // Get existing entries for this event, or empty array\n const existing: HookEntry[] = result.hooks[event] ?? []\n\n // Filter out any previous bashstats-managed hooks\n const nonBashstats = existing.filter((entry) => {\n return !entry.hooks?.some((h) => h.command?.includes(MARKER))\n })\n\n // Build the new bashstats hook entry\n const bashstatsEntry: HookEntry = {\n matcher: '',\n hooks: [{ type: 'command', command }],\n }\n\n // Add bashstats entry alongside preserved hooks\n result.hooks[event] = [...nonBashstats, bashstatsEntry]\n }\n\n return result\n}\n\n/**\n * Returns the path to ~/.claude/settings.json\n */\nexport function getClaudeSettingsPath(): string {\n return path.join(os.homedir(), '.claude', 'settings.json')\n}\n\n/**\n * Returns the path to the dist/hooks/ directory, resolved from this module's location.\n */\nexport function getHooksDir(): string {\n const __filename = fileURLToPath(import.meta.url)\n const __dirname = path.dirname(__filename)\n return path.resolve(__dirname, 'hooks')\n}\n\n/**\n * Full installation:\n * - Creates ~/.bashstats/ directory\n * - Initializes the database with metadata\n * - Reads existing ~/.claude/settings.json\n * - Merges bashstats hooks\n * - Writes settings back\n */\nexport function install(): { success: boolean; message: string } {\n try {\n // Create data directory\n const dataDir = path.join(os.homedir(), DATA_DIR)\n fs.mkdirSync(dataDir, { recursive: true })\n\n // Initialize database\n const dbPath = path.join(dataDir, DB_FILENAME)\n const db = new BashStatsDB(dbPath)\n const now = new Date().toISOString()\n db.setMetadata('installed_at', now)\n if (!db.getMetadata('first_run')) {\n db.setMetadata('first_run', now)\n }\n db.close()\n\n // Ensure ~/.claude/ directory exists\n const claudeDir = path.join(os.homedir(), '.claude')\n fs.mkdirSync(claudeDir, { recursive: true })\n\n // Read existing settings\n const settingsPath = getClaudeSettingsPath()\n let settings: Record<string, unknown> = {}\n if (fs.existsSync(settingsPath)) {\n const raw = fs.readFileSync(settingsPath, 'utf-8')\n settings = JSON.parse(raw)\n }\n\n // Merge hooks\n const hooksDir = getHooksDir()\n settings = mergeHooks(settings, hooksDir)\n\n // Write settings back\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8')\n\n return { success: true, message: 'bashstats hooks installed successfully.' }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { success: false, message: `Installation failed: ${message}` }\n }\n}\n\n/**\n * Removes all bashstats hooks from ~/.claude/settings.json.\n * Cleans up empty arrays and empty hooks objects.\n */\nexport function uninstall(): { success: boolean; message: string } {\n try {\n const settingsPath = getClaudeSettingsPath()\n if (!fs.existsSync(settingsPath)) {\n return { success: true, message: 'No settings.json found; nothing to uninstall.' }\n }\n\n const raw = fs.readFileSync(settingsPath, 'utf-8')\n const settings: Settings = JSON.parse(raw)\n\n if (settings.hooks) {\n for (const event of Object.keys(settings.hooks)) {\n // Filter out bashstats-managed entries\n settings.hooks[event] = settings.hooks[event].filter((entry) => {\n return !entry.hooks?.some((h) => h.command?.includes(MARKER))\n })\n\n // Clean up empty arrays\n if (settings.hooks[event].length === 0) {\n delete settings.hooks[event]\n }\n }\n\n // Clean up empty hooks object\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks\n }\n }\n\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8')\n\n return { success: true, message: 'bashstats hooks removed successfully.' }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { success: false, message: `Uninstall failed: ${message}` }\n }\n}\n\n/**\n * Checks if bashstats hooks are currently present in ~/.claude/settings.json.\n */\nexport function isInstalled(): boolean {\n try {\n const settingsPath = getClaudeSettingsPath()\n if (!fs.existsSync(settingsPath)) return false\n\n const raw = fs.readFileSync(settingsPath, 'utf-8')\n const settings: Settings = JSON.parse(raw)\n\n if (!settings.hooks) return false\n\n // Check if any hook event contains a bashstats-managed entry\n for (const event of Object.keys(settings.hooks)) {\n const entries = settings.hooks[event]\n for (const entry of entries) {\n if (entry.hooks?.some((h) => h.command?.includes(MARKER))) {\n return true\n }\n }\n }\n\n return false\n } catch {\n return false\n }\n}\n","import { BashStatsDB } from './database.js'\nimport type { TokenUsage } from '../types.js'\nimport path from 'path'\n\nexport class BashStatsWriter {\n private db: BashStatsDB\n\n constructor(db: BashStatsDB) {\n this.db = db\n }\n\n private extractProject(cwd: string): string {\n return path.basename(cwd)\n }\n\n private today(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\n }\n\n private now(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n const ms = String(d.getMilliseconds()).padStart(3, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${ms}`\n }\n\n recordSessionStart(sessionId: string, cwd: string, source: string, agent?: string): void {\n const project = this.extractProject(cwd)\n const timestamp = this.now()\n\n this.db.insertSession({\n id: sessionId,\n agent,\n started_at: timestamp,\n project,\n })\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'SessionStart',\n tool_name: null,\n tool_input: JSON.stringify({ source }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd,\n project,\n timestamp,\n })\n\n this.db.incrementDailyActivity(this.today(), { sessions: 1 })\n }\n\n recordPrompt(sessionId: string, content: string): void {\n const timestamp = this.now()\n const wordCount = content.split(/\\s+/).filter(w => w.length > 0).length\n const charCount = content.length\n\n this.db.insertPrompt({\n session_id: sessionId,\n content,\n char_count: charCount,\n word_count: wordCount,\n timestamp,\n })\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'UserPromptSubmit',\n tool_name: null,\n tool_input: null,\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n this.db.incrementSessionCounters(sessionId, { prompts: 1 })\n this.db.incrementDailyActivity(this.today(), { prompts: 1 })\n }\n\n recordToolUse(\n sessionId: string,\n hookType: string,\n toolName: string,\n toolInput: Record<string, unknown>,\n toolOutput: Record<string, unknown>,\n exitCode: number | null,\n cwd: string,\n ): void {\n const timestamp = this.now()\n const project = this.extractProject(cwd)\n const success = exitCode === 0 ? 1 : 0\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: hookType,\n tool_name: toolName,\n tool_input: JSON.stringify(toolInput),\n tool_output: JSON.stringify(toolOutput),\n exit_code: exitCode,\n success,\n cwd,\n project,\n timestamp,\n })\n\n this.db.incrementSessionCounters(sessionId, {\n tools: 1,\n errors: success === 0 ? 1 : 0,\n })\n\n this.db.incrementDailyActivity(this.today(), {\n tool_calls: 1,\n errors: success === 0 ? 1 : 0,\n })\n }\n\n recordSessionEnd(sessionId: string, stopReason: string, tokens?: TokenUsage | null): void {\n const timestamp = this.now()\n const session = this.db.getSession(sessionId)\n\n let durationSeconds: number | undefined\n if (session) {\n const startTime = new Date(session.started_at).getTime()\n const endTime = new Date(timestamp).getTime()\n durationSeconds = Math.round((endTime - startTime) / 1000)\n }\n\n this.db.updateSession(sessionId, {\n ended_at: timestamp,\n stop_reason: stopReason,\n duration_seconds: durationSeconds,\n })\n\n if (tokens) {\n this.db.updateSessionTokens(sessionId, tokens)\n }\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'Stop',\n tool_name: null,\n tool_input: JSON.stringify({ stop_reason: stopReason }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n const dailyIncrements: Record<string, number> = {}\n if (durationSeconds !== undefined) {\n dailyIncrements.duration_seconds = durationSeconds\n }\n if (tokens) {\n dailyIncrements.input_tokens = tokens.input_tokens\n dailyIncrements.output_tokens = tokens.output_tokens\n dailyIncrements.cache_creation_input_tokens = tokens.cache_creation_input_tokens\n dailyIncrements.cache_read_input_tokens = tokens.cache_read_input_tokens\n }\n if (Object.keys(dailyIncrements).length > 0) {\n this.db.incrementDailyActivity(this.today(), dailyIncrements)\n }\n }\n\n recordNotification(sessionId: string, message: string, notificationType: string): void {\n const timestamp = this.now()\n const isError = notificationType === 'error' || notificationType === 'rate_limit'\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'Notification',\n tool_name: null,\n tool_input: JSON.stringify({ message, notification_type: notificationType }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n if (isError) {\n this.db.incrementSessionCounters(sessionId, { errors: 1 })\n this.db.incrementDailyActivity(this.today(), { errors: 1 })\n }\n }\n\n recordSubagent(sessionId: string, hookType: string, agentId: string, agentType?: string): void {\n const timestamp = this.now()\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: hookType,\n tool_name: null,\n tool_input: JSON.stringify({ agent_id: agentId, agent_type: agentType ?? null }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n }\n\n recordCompaction(sessionId: string, trigger: string): void {\n const timestamp = this.now()\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'PreCompact',\n tool_name: null,\n tool_input: JSON.stringify({ trigger }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n }\n}\n","import path from 'path'\nimport os from 'os'\nimport fs from 'fs'\nimport { DATA_DIR, DB_FILENAME } from '../constants.js'\nimport { BashStatsDB } from '../db/database.js'\nimport { BashStatsWriter } from '../db/writer.js'\nimport type { AgentType } from '../types.js'\nimport { extractTokenUsage } from './transcript.js'\n\n/**\n * Detect which CLI agent is running based on environment variables and process context.\n * Currently supports Claude Code (default), Gemini CLI, Copilot CLI, and OpenCode.\n */\nexport function detectAgent(): AgentType {\n if (process.env.GEMINI_CLI || process.env.GEMINI_API_KEY) return 'gemini-cli'\n if (process.env.GITHUB_COPILOT_CLI) return 'copilot-cli'\n if (process.env.OPENCODE) return 'opencode'\n return 'claude-code'\n}\n\nexport function parseHookEvent(input: string): Record<string, unknown> | null {\n try {\n if (!input) return null\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nexport function getProjectFromCwd(cwd: string): string {\n return path.basename(cwd)\n}\n\nexport function getDataDir(): string {\n return path.join(os.homedir(), DATA_DIR)\n}\n\nexport function getDbPath(): string {\n return path.join(os.homedir(), DATA_DIR, DB_FILENAME)\n}\n\nexport async function readStdin(): Promise<string> {\n if (process.env.CLAUDE_HOOK_EVENT) {\n return process.env.CLAUDE_HOOK_EVENT\n }\n\n return new Promise<string>((resolve) => {\n let data = ''\n process.stdin.setEncoding('utf-8')\n process.stdin.on('data', (chunk: string) => {\n data += chunk\n })\n process.stdin.on('end', () => {\n resolve(data)\n })\n })\n}\n\nexport async function handleHookEvent(hookType: string): Promise<void> {\n const raw = await readStdin()\n const event = parseHookEvent(raw)\n if (!event) return\n\n const dataDir = getDataDir()\n fs.mkdirSync(dataDir, { recursive: true })\n\n const dbPath = getDbPath()\n const db = new BashStatsDB(dbPath)\n const writer = new BashStatsWriter(db)\n\n try {\n const sessionId = (event.session_id as string) ?? ''\n const cwd = (event.cwd as string) ?? ''\n\n switch (hookType) {\n case 'SessionStart': {\n const source = (event.source as string) ?? 'startup'\n const agent = detectAgent()\n writer.recordSessionStart(sessionId, cwd, source, agent)\n break\n }\n\n case 'UserPromptSubmit': {\n const prompt = (event.prompt as string) ?? ''\n writer.recordPrompt(sessionId, prompt)\n break\n }\n\n case 'PreToolUse': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PreToolUse', toolName, toolInput, {}, 0, cwd)\n break\n }\n\n case 'PostToolUse': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n const toolResponse = (event.tool_response as Record<string, unknown>) ?? {}\n const exitCode = (event.exit_code as number) ?? 0\n writer.recordToolUse(sessionId, 'PostToolUse', toolName, toolInput, toolResponse, exitCode, cwd)\n break\n }\n\n case 'PostToolUseFailure': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n const toolResponse = (event.tool_response as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PostToolUseFailure', toolName, toolInput, toolResponse, 1, cwd)\n break\n }\n\n case 'Stop': {\n const rawPath = (event.transcript_path as string) ?? ''\n const transcriptPath = rawPath && rawPath.endsWith('.jsonl') ? path.resolve(rawPath) : ''\n const tokens = transcriptPath ? await extractTokenUsage(transcriptPath) : null\n writer.recordSessionEnd(sessionId, 'stopped', tokens)\n break\n }\n\n case 'Notification': {\n const message = (event.message as string) ?? ''\n const notificationType = (event.notification_type as string) ?? ''\n writer.recordNotification(sessionId, message, notificationType)\n break\n }\n\n case 'SubagentStart': {\n const agentId = (event.agent_id as string) ?? ''\n const agentType = (event.agent_type as string) ?? ''\n writer.recordSubagent(sessionId, 'SubagentStart', agentId, agentType)\n break\n }\n\n case 'SubagentStop': {\n const agentId = (event.agent_id as string) ?? ''\n writer.recordSubagent(sessionId, 'SubagentStop', agentId)\n break\n }\n\n case 'PreCompact': {\n const trigger = (event.trigger as string) ?? 'manual'\n writer.recordCompaction(sessionId, trigger)\n break\n }\n\n case 'PermissionRequest': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PermissionRequest', toolName, toolInput, {}, 0, cwd)\n break\n }\n\n case 'Setup': {\n // no-op\n return\n }\n }\n } finally {\n db.close()\n }\n}\n","import fs from 'fs'\nimport readline from 'readline'\nimport type { TokenUsage } from '../types.js'\n\n/**\n * Read a Claude Code transcript JSONL file and sum all token usage.\n * Returns null on any failure (missing file, no usage data, parse errors).\n */\nexport async function extractTokenUsage(transcriptPath: string): Promise<TokenUsage | null> {\n try {\n if (!fs.existsSync(transcriptPath)) return null\n\n const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' })\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity })\n\n let inputTokens = 0\n let outputTokens = 0\n let cacheCreation = 0\n let cacheRead = 0\n let found = false\n\n for await (const line of rl) {\n if (!line.trim()) continue\n try {\n const entry = JSON.parse(line)\n const usage = entry.usage ?? entry.response?.usage ?? entry.message?.usage\n if (usage && typeof usage === 'object') {\n inputTokens += usage.input_tokens ?? 0\n outputTokens += usage.output_tokens ?? 0\n cacheCreation += usage.cache_creation_input_tokens ?? 0\n cacheRead += usage.cache_read_input_tokens ?? 0\n found = true\n }\n } catch {\n // skip unparseable lines\n }\n }\n\n if (!found) return null\n\n return {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n cache_creation_input_tokens: cacheCreation,\n cache_read_input_tokens: cacheRead,\n }\n } catch {\n return null\n }\n}\n","import { BashStatsDB } from '../db/database.js'\nimport type {\n LifetimeStats,\n ToolBreakdown,\n TimeStats,\n SessionRecords,\n ProjectStats,\n AllStats,\n} from '../types.js'\n\nexport class StatsEngine {\n private db: BashStatsDB\n\n constructor(db: BashStatsDB) {\n this.db = db\n }\n\n private queryScalar<T = number>(sql: string, ...params: unknown[]): T {\n const row = this.db.prepare(sql).get(...params) as Record<string, T> | undefined\n if (!row) return 0 as T\n return Object.values(row)[0] ?? (0 as T)\n }\n\n getLifetimeStats(): LifetimeStats {\n const totalSessions = this.queryScalar('SELECT COUNT(*) as c FROM sessions')\n const totalPrompts = this.queryScalar('SELECT COUNT(*) as c FROM prompts')\n const totalCharsTyped = this.queryScalar('SELECT COALESCE(SUM(char_count), 0) as c FROM prompts')\n const totalToolCalls = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalDurationSeconds = this.queryScalar(\n 'SELECT COALESCE(SUM(duration_seconds), 0) as c FROM sessions'\n )\n const totalFilesRead = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Read' AND hook_type = 'PostToolUse'\"\n )\n const totalFilesWritten = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Write' AND hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalFilesEdited = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Edit' AND hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalFilesCreated = totalFilesWritten\n const totalBashCommands = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Bash' AND hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalWebSearches = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'WebSearch' AND hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalWebFetches = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'WebFetch' AND hook_type IN ('PostToolUse', 'PostToolUseFailure')\"\n )\n const totalSubagents = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'SubagentStart'\"\n )\n const totalCompactions = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'PreCompact'\"\n )\n const totalErrors = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'PostToolUseFailure' OR (hook_type = 'Notification' AND (tool_input LIKE '%\\\"notification_type\\\":\\\"error\\\"%' OR tool_input LIKE '%\\\"notification_type\\\":\\\"rate_limit\\\"%'))\"\n )\n const totalRateLimits = this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'Notification' AND tool_input LIKE '%rate_limit%'\"\n )\n const totalInputTokens = this.queryScalar(\n 'SELECT COALESCE(SUM(input_tokens), 0) as c FROM sessions'\n )\n const totalOutputTokens = this.queryScalar(\n 'SELECT COALESCE(SUM(output_tokens), 0) as c FROM sessions'\n )\n const totalCacheCreationTokens = this.queryScalar(\n 'SELECT COALESCE(SUM(cache_creation_input_tokens), 0) as c FROM sessions'\n )\n const totalCacheReadTokens = this.queryScalar(\n 'SELECT COALESCE(SUM(cache_read_input_tokens), 0) as c FROM sessions'\n )\n const totalTokens = totalInputTokens + totalOutputTokens\n\n return {\n totalSessions,\n totalDurationSeconds,\n totalPrompts,\n totalCharsTyped,\n totalToolCalls,\n totalFilesRead,\n totalFilesWritten,\n totalFilesEdited,\n totalFilesCreated,\n totalBashCommands,\n totalWebSearches,\n totalWebFetches,\n totalSubagents,\n totalCompactions,\n totalErrors,\n totalRateLimits,\n totalInputTokens,\n totalOutputTokens,\n totalCacheCreationTokens,\n totalCacheReadTokens,\n totalTokens,\n }\n }\n\n getToolBreakdown(): ToolBreakdown {\n const rows = this.db\n .prepare(\n \"SELECT tool_name, COUNT(*) as cnt FROM events WHERE hook_type = 'PostToolUse' AND tool_name IS NOT NULL GROUP BY tool_name\"\n )\n .all() as { tool_name: string; cnt: number }[]\n\n const breakdown: ToolBreakdown = {}\n for (const row of rows) {\n breakdown[row.tool_name] = row.cnt\n }\n return breakdown\n }\n\n getTimeStats(): TimeStats {\n // Compute streaks from daily_activity\n const dailyRows = this.db\n .prepare('SELECT date FROM daily_activity WHERE sessions > 0 OR prompts > 0 OR tool_calls > 0 ORDER BY date ASC')\n .all() as { date: string }[]\n\n let longestStreak = 0\n let currentStreak = 0\n\n if (dailyRows.length > 0) {\n // Compute longest streak\n let streak = 1\n for (let i = 1; i < dailyRows.length; i++) {\n const prevDate = new Date(dailyRows[i - 1].date + 'T00:00:00Z')\n const currDate = new Date(dailyRows[i].date + 'T00:00:00Z')\n const diffDays = (currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)\n if (diffDays === 1) {\n streak++\n } else {\n longestStreak = Math.max(longestStreak, streak)\n streak = 1\n }\n }\n longestStreak = Math.max(longestStreak, streak)\n\n // Compute current streak (backwards from today)\n const today = new Date()\n const todayStr = today.toISOString().slice(0, 10)\n\n // Build a Set of active dates for quick lookup\n const activeDates = new Set(dailyRows.map(r => r.date))\n\n // Start from today and go backwards\n let checkDate = new Date(todayStr + 'T00:00:00Z')\n currentStreak = 0\n\n // Check today first; if not active, check yesterday (in case session hasn't been recorded today yet)\n if (activeDates.has(todayStr)) {\n currentStreak = 1\n checkDate.setUTCDate(checkDate.getUTCDate() - 1)\n while (activeDates.has(checkDate.toISOString().slice(0, 10))) {\n currentStreak++\n checkDate.setUTCDate(checkDate.getUTCDate() - 1)\n }\n } else {\n // Check if yesterday was active\n checkDate.setUTCDate(checkDate.getUTCDate() - 1)\n const yesterdayStr = checkDate.toISOString().slice(0, 10)\n if (activeDates.has(yesterdayStr)) {\n currentStreak = 1\n checkDate.setUTCDate(checkDate.getUTCDate() - 1)\n while (activeDates.has(checkDate.toISOString().slice(0, 10))) {\n currentStreak++\n checkDate.setUTCDate(checkDate.getUTCDate() - 1)\n }\n }\n }\n }\n\n // Peak hour from prompts timestamps\n const peakHourRow = this.db\n .prepare(\n \"SELECT CAST(strftime('%H', timestamp) AS INTEGER) as hour, COUNT(*) as cnt FROM prompts GROUP BY hour ORDER BY cnt DESC LIMIT 1\"\n )\n .get() as { hour: number; cnt: number } | undefined\n\n const peakHour = peakHourRow?.hour ?? 0\n const peakHourCount = peakHourRow?.cnt ?? 0\n\n // Night owl: prompts where hour < 5\n const nightOwlCount = this.queryScalar(\n \"SELECT COUNT(*) as c FROM prompts WHERE CAST(strftime('%H', timestamp) AS INTEGER) < 5\"\n )\n\n // Early bird: prompts where hour BETWEEN 5 AND 7\n const earlyBirdCount = this.queryScalar(\n \"SELECT COUNT(*) as c FROM prompts WHERE CAST(strftime('%H', timestamp) AS INTEGER) BETWEEN 5 AND 7\"\n )\n\n // Weekend sessions: day of week 0 (Sunday) or 6 (Saturday)\n // SQLite strftime('%w') returns 0=Sunday, 1=Monday, ... 6=Saturday\n const weekendSessions = this.queryScalar(\n \"SELECT COUNT(*) as c FROM sessions WHERE CAST(strftime('%w', started_at) AS INTEGER) IN (0, 6)\"\n )\n\n // Most active day of week\n const mostActiveDayRow = this.db\n .prepare(\n \"SELECT CAST(strftime('%w', started_at) AS INTEGER) as dow, COUNT(*) as cnt FROM sessions GROUP BY dow ORDER BY cnt DESC LIMIT 1\"\n )\n .get() as { dow: number; cnt: number } | undefined\n\n const mostActiveDay = mostActiveDayRow?.dow ?? 0\n\n // Busiest single date\n const busiestDateRow = this.db\n .prepare(\n 'SELECT date, (sessions + prompts + tool_calls) as total FROM daily_activity ORDER BY total DESC LIMIT 1'\n )\n .get() as { date: string; total: number } | undefined\n\n const busiestDate = busiestDateRow?.date ?? ''\n const busiestDateCount = busiestDateRow?.total ?? 0\n\n return {\n currentStreak,\n longestStreak,\n peakHour,\n peakHourCount,\n nightOwlCount,\n earlyBirdCount,\n weekendSessions,\n mostActiveDay,\n busiestDate,\n busiestDateCount,\n }\n }\n\n getSessionRecords(): SessionRecords {\n const longestSessionSeconds = this.queryScalar(\n 'SELECT COALESCE(MAX(duration_seconds), 0) as c FROM sessions'\n )\n const mostToolsInSession = this.queryScalar(\n 'SELECT COALESCE(MAX(tool_count), 0) as c FROM sessions'\n )\n const mostPromptsInSession = this.queryScalar(\n 'SELECT COALESCE(MAX(prompt_count), 0) as c FROM sessions'\n )\n const fastestSessionSeconds = this.queryScalar(\n 'SELECT COALESCE(MIN(duration_seconds), 0) as c FROM sessions WHERE duration_seconds IS NOT NULL AND duration_seconds > 0'\n )\n const avgDurationSeconds = this.queryScalar(\n 'SELECT COALESCE(AVG(duration_seconds), 0) as c FROM sessions WHERE duration_seconds IS NOT NULL'\n )\n const avgPromptsPerSession = this.queryScalar(\n 'SELECT COALESCE(AVG(prompt_count), 0) as c FROM sessions'\n )\n const avgToolsPerSession = this.queryScalar(\n 'SELECT COALESCE(AVG(tool_count), 0) as c FROM sessions'\n )\n const mostTokensInSession = this.queryScalar(\n 'SELECT COALESCE(MAX(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0) as c FROM sessions'\n )\n const avgTokensPerSession = this.queryScalar(\n 'SELECT COALESCE(AVG(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0) as c FROM sessions'\n )\n\n return {\n longestSessionSeconds,\n mostToolsInSession,\n mostPromptsInSession,\n fastestSessionSeconds,\n avgDurationSeconds: Math.round(avgDurationSeconds),\n avgPromptsPerSession: Math.round(avgPromptsPerSession * 100) / 100,\n avgToolsPerSession: Math.round(avgToolsPerSession * 100) / 100,\n mostTokensInSession,\n avgTokensPerSession: Math.round(avgTokensPerSession),\n }\n }\n\n getProjectStats(): ProjectStats {\n const uniqueProjects = this.queryScalar(\n 'SELECT COUNT(DISTINCT project) as c FROM sessions WHERE project IS NOT NULL'\n )\n\n const projectRows = this.db\n .prepare(\n 'SELECT project, COUNT(*) as cnt FROM sessions WHERE project IS NOT NULL GROUP BY project ORDER BY cnt DESC'\n )\n .all() as { project: string; cnt: number }[]\n\n const mostVisitedProject = projectRows.length > 0 ? projectRows[0].project : ''\n const mostVisitedProjectCount = projectRows.length > 0 ? projectRows[0].cnt : 0\n\n const projectBreakdown: Record<string, number> = {}\n for (const row of projectRows) {\n projectBreakdown[row.project] = row.cnt\n }\n\n return {\n uniqueProjects,\n mostVisitedProject,\n mostVisitedProjectCount,\n projectBreakdown,\n }\n }\n\n getAllStats(): AllStats {\n return {\n lifetime: this.getLifetimeStats(),\n tools: this.getToolBreakdown(),\n time: this.getTimeStats(),\n sessions: this.getSessionRecords(),\n projects: this.getProjectStats(),\n }\n }\n}\n","// === Hook Event Types ===\n\nexport interface HookInput {\n session_id: string\n transcript_path: string\n cwd: string\n permission_mode: string\n hook_event_name: string\n}\n\nexport interface SessionStartInput extends HookInput {\n source: 'startup' | 'resume' | 'clear' | 'compact'\n model: string\n agent_type?: string\n}\n\nexport interface UserPromptInput extends HookInput {\n prompt: string\n}\n\nexport interface PreToolUseInput extends HookInput {\n tool_name: string\n tool_input: Record<string, unknown>\n tool_use_id: string\n}\n\nexport interface PostToolUseInput extends HookInput {\n tool_name: string\n tool_input: Record<string, unknown>\n tool_response: Record<string, unknown>\n tool_use_id: string\n}\n\nexport interface StopInput extends HookInput {\n stop_hook_active: boolean\n}\n\nexport interface SubagentStartInput extends HookInput {\n agent_id: string\n agent_type: string\n}\n\nexport interface SubagentStopInput extends HookInput {\n agent_id: string\n agent_transcript_path: string\n stop_hook_active: boolean\n}\n\nexport interface NotificationInput extends HookInput {\n message: string\n notification_type: string\n}\n\nexport interface PreCompactInput extends HookInput {\n trigger: 'manual' | 'auto'\n custom_instructions: string\n}\n\nexport interface PermissionRequestInput extends HookInput {\n tool_name: string\n tool_input: Record<string, unknown>\n}\n\nexport interface SetupInput extends HookInput {\n trigger: 'init' | 'maintenance'\n CLAUDE_ENV_FILE: string\n}\n\n// === Token Usage ===\n\nexport interface TokenUsage {\n input_tokens: number\n output_tokens: number\n cache_creation_input_tokens: number\n cache_read_input_tokens: number\n}\n\n// === Database Row Types ===\n\nexport interface EventRow {\n id: number\n session_id: string\n hook_type: string\n tool_name: string | null\n tool_input: string | null\n tool_output: string | null\n exit_code: number | null\n success: number | null\n cwd: string | null\n project: string | null\n timestamp: string\n}\n\nexport interface SessionRow {\n id: string\n agent: string\n started_at: string\n ended_at: string | null\n stop_reason: string | null\n prompt_count: number\n tool_count: number\n error_count: number\n project: string | null\n duration_seconds: number | null\n input_tokens: number | null\n output_tokens: number | null\n cache_creation_input_tokens: number | null\n cache_read_input_tokens: number | null\n}\n\nexport interface PromptRow {\n id: number\n session_id: string\n content: string\n char_count: number\n word_count: number\n timestamp: string\n}\n\nexport interface DailyActivityRow {\n date: string\n sessions: number\n prompts: number\n tool_calls: number\n errors: number\n duration_seconds: number\n input_tokens: number\n output_tokens: number\n cache_creation_input_tokens: number\n cache_read_input_tokens: number\n}\n\nexport interface AchievementUnlockRow {\n badge_id: string\n tier: number\n unlocked_at: string\n notified: number\n}\n\n// === Stats Types ===\n\nexport interface LifetimeStats {\n totalSessions: number\n totalDurationSeconds: number\n totalPrompts: number\n totalCharsTyped: number\n totalToolCalls: number\n totalFilesRead: number\n totalFilesWritten: number\n totalFilesEdited: number\n totalFilesCreated: number\n totalBashCommands: number\n totalWebSearches: number\n totalWebFetches: number\n totalSubagents: number\n totalCompactions: number\n totalErrors: number\n totalRateLimits: number\n totalInputTokens: number\n totalOutputTokens: number\n totalCacheCreationTokens: number\n totalCacheReadTokens: number\n totalTokens: number\n}\n\nexport interface ToolBreakdown {\n [toolName: string]: number\n}\n\nexport interface TimeStats {\n currentStreak: number\n longestStreak: number\n peakHour: number\n peakHourCount: number\n nightOwlCount: number\n earlyBirdCount: number\n weekendSessions: number\n mostActiveDay: number\n busiestDate: string\n busiestDateCount: number\n}\n\nexport interface SessionRecords {\n longestSessionSeconds: number\n mostToolsInSession: number\n mostPromptsInSession: number\n fastestSessionSeconds: number\n avgDurationSeconds: number\n avgPromptsPerSession: number\n avgToolsPerSession: number\n mostTokensInSession: number\n avgTokensPerSession: number\n}\n\nexport interface ProjectStats {\n uniqueProjects: number\n mostVisitedProject: string\n mostVisitedProjectCount: number\n projectBreakdown: Record<string, number>\n}\n\nexport interface AllStats {\n lifetime: LifetimeStats\n tools: ToolBreakdown\n time: TimeStats\n sessions: SessionRecords\n projects: ProjectStats\n}\n\n// === Agent Types ===\n\nexport type AgentType =\n | 'claude-code'\n | 'gemini-cli'\n | 'copilot-cli'\n | 'opencode'\n | 'unknown'\n\nexport const AGENT_DISPLAY_NAMES: Record<AgentType, string> = {\n 'claude-code': 'Claude Code',\n 'gemini-cli': 'Gemini CLI',\n 'copilot-cli': 'Copilot CLI',\n 'opencode': 'OpenCode',\n 'unknown': 'Unknown',\n}\n\n// === Achievement Types ===\n\nexport type BadgeTier = 0 | 1 | 2 | 3 | 4 | 5\n\nexport interface BadgeDefinition {\n id: string\n name: string\n icon: string\n description: string\n category: 'volume' | 'tool_mastery' | 'time' | 'behavioral' | 'resilience' | 'shipping' | 'multi_agent' | 'humor' | 'aspirational' | 'secret'\n stat: string\n tiers: [number, number, number, number, number]\n secret?: boolean\n humor?: boolean\n aspirational?: boolean\n}\n\nexport interface BadgeResult {\n id: string\n name: string\n icon: string\n description: string\n category: string\n stat: string\n tiers: [number, number, number, number, number]\n tier: BadgeTier\n tierName: string\n value: number\n nextThreshold: number\n progress: number\n maxed: boolean\n secret: boolean\n unlocked: boolean\n}\n\nexport interface XPResult {\n totalXP: number\n rank: string\n nextRankXP: number\n progress: number\n}\n\nexport interface AchievementsPayload {\n stats: AllStats\n badges: BadgeResult[]\n xp: XPResult\n}\n\nexport const TIER_NAMES: Record<BadgeTier, string> = {\n 0: 'Locked',\n 1: 'Bronze',\n 2: 'Silver',\n 3: 'Gold',\n 4: 'Diamond',\n 5: 'Obsidian',\n}\n","import { BashStatsDB } from '../db/database.js'\nimport { StatsEngine } from '../stats/engine.js'\nimport { BADGE_DEFINITIONS, RANK_THRESHOLDS, TIER_XP } from '../constants.js'\nimport type {\n BadgeResult,\n BadgeTier,\n XPResult,\n AchievementsPayload,\n AllStats,\n} from '../types.js'\nimport { TIER_NAMES } from '../types.js'\n\nexport class AchievementEngine {\n private db: BashStatsDB\n private stats: StatsEngine\n\n constructor(db: BashStatsDB, stats: StatsEngine) {\n this.db = db\n this.stats = stats\n }\n\n computeBadges(): BadgeResult[] {\n const allStats = this.stats.getAllStats()\n const flat = this.flattenStats(allStats)\n\n return BADGE_DEFINITIONS.map(badge => {\n const value = flat[badge.stat] ?? 0\n\n let tier: BadgeTier = 0\n if (badge.aspirational) {\n // Aspirational: only Obsidian (5) or Locked (0)\n tier = value >= badge.tiers[4] ? 5 : 0\n } else if (badge.secret) {\n // Secret: unlocked (1) or locked (0)\n tier = value >= badge.tiers[0] ? 1 : 0\n } else {\n // Normal tiered badge: check how many thresholds exceeded\n for (let i = 0; i < badge.tiers.length; i++) {\n if (value >= badge.tiers[i]) {\n tier = (i + 1) as BadgeTier\n } else {\n break\n }\n }\n }\n\n // Calculate progress toward next tier\n let nextThreshold = 0\n let progress = 0\n let maxed = false\n\n if (badge.aspirational) {\n nextThreshold = badge.tiers[4]\n progress = tier === 5 ? 1 : Math.min(value / nextThreshold, 0.99)\n maxed = tier === 5\n } else if (badge.secret) {\n nextThreshold = badge.tiers[0]\n progress = tier >= 1 ? 1 : 0\n maxed = tier >= 1\n } else if (tier >= 5) {\n nextThreshold = badge.tiers[4]\n progress = 1\n maxed = true\n } else {\n // tier is 0..4 here (we already handled tier >= 5 above)\n const tierIdx = tier as 0 | 1 | 2 | 3 | 4\n nextThreshold = badge.tiers[tierIdx]\n const prevThreshold = tierIdx > 0 ? badge.tiers[(tierIdx - 1) as 0 | 1 | 2 | 3] : 0\n const range = nextThreshold - prevThreshold\n progress = range > 0 ? Math.min((value - prevThreshold) / range, 0.99) : 0\n }\n\n // Persist unlock if tier > 0\n if (tier > 0) {\n for (let t = 1; t <= tier; t++) {\n this.db.insertUnlock(badge.id, t)\n }\n }\n\n return {\n id: badge.id,\n name: badge.name,\n icon: badge.icon,\n description: badge.description,\n category: badge.category,\n stat: badge.stat,\n tiers: badge.tiers,\n tier,\n tierName: TIER_NAMES[tier],\n value,\n nextThreshold,\n progress,\n maxed,\n secret: badge.secret ?? false,\n unlocked: tier > 0,\n }\n })\n }\n\n computeXP(): XPResult {\n const allStats = this.stats.getAllStats()\n const badges = this.computeBadges()\n\n // Calculate XP from activity\n let totalXP = 0\n totalXP += allStats.lifetime.totalPrompts * 1 // +1 per prompt\n totalXP += allStats.lifetime.totalToolCalls * 1 // +1 per tool call\n totalXP += allStats.lifetime.totalSessions * 10 // +10 per session\n totalXP += allStats.time.nightOwlCount * 2 // +2 per night owl prompt\n totalXP += Math.floor(allStats.time.longestStreak / 100) * 25 // +25 per 100 in longest streak\n\n // Badge tier XP\n for (const badge of badges) {\n if (badge.tier > 0) {\n totalXP += TIER_XP[badge.tier] ?? 0\n }\n }\n\n // Determine rank from RANK_THRESHOLDS (highest threshold that XP exceeds)\n let rank = 'Bronze'\n let nextRankXP = RANK_THRESHOLDS[RANK_THRESHOLDS.length - 2]?.xp ?? 1000 // Silver threshold\n for (const threshold of RANK_THRESHOLDS) {\n if (totalXP >= threshold.xp) {\n rank = threshold.rank\n break\n }\n }\n\n // Calculate progress toward next rank\n let progress = 0\n const rankIndex = RANK_THRESHOLDS.findIndex(t => t.rank === rank)\n if (rankIndex <= 0) {\n // Already at highest rank (Obsidian)\n nextRankXP = RANK_THRESHOLDS[0].xp\n progress = 1\n } else {\n const currentThreshold = RANK_THRESHOLDS[rankIndex].xp\n nextRankXP = RANK_THRESHOLDS[rankIndex - 1].xp\n const range = nextRankXP - currentThreshold\n progress = range > 0 ? Math.min((totalXP - currentThreshold) / range, 0.99) : 0\n }\n\n return {\n totalXP,\n rank,\n nextRankXP,\n progress,\n }\n }\n\n getAchievementsPayload(): AchievementsPayload {\n const allStats = this.stats.getAllStats()\n return {\n stats: allStats,\n badges: this.computeBadges(),\n xp: this.computeXP(),\n }\n }\n\n private flattenStats(allStats: AllStats): Record<string, number> {\n const flat: Record<string, number> = {}\n\n // Direct lifetime mappings\n flat.totalPrompts = allStats.lifetime.totalPrompts\n flat.totalToolCalls = allStats.lifetime.totalToolCalls\n flat.totalSessions = allStats.lifetime.totalSessions\n flat.totalCharsTyped = allStats.lifetime.totalCharsTyped\n flat.totalBashCommands = allStats.lifetime.totalBashCommands\n flat.totalFilesRead = allStats.lifetime.totalFilesRead\n flat.totalFilesEdited = allStats.lifetime.totalFilesEdited\n flat.totalFilesCreated = allStats.lifetime.totalFilesCreated\n flat.totalSubagents = allStats.lifetime.totalSubagents\n flat.totalErrors = allStats.lifetime.totalErrors\n flat.totalRateLimits = allStats.lifetime.totalRateLimits\n flat.totalWebFetches = allStats.lifetime.totalWebFetches\n flat.totalWebSearches = allStats.lifetime.totalWebSearches\n flat.totalCompactions = allStats.lifetime.totalCompactions\n\n // Derived\n flat.totalSessionHours = Math.floor(allStats.lifetime.totalDurationSeconds / 3600)\n\n // From time stats\n flat.longestStreak = allStats.time.longestStreak\n flat.nightOwlCount = allStats.time.nightOwlCount\n flat.earlyBirdCount = allStats.time.earlyBirdCount\n flat.weekendSessions = allStats.time.weekendSessions\n\n // Computed via queries - tool mastery\n flat.totalSearches = this.queryTotalSearches()\n\n // Behavioral\n flat.mostRepeatedPromptCount = this.queryMostRepeatedPromptCount()\n flat.uniqueToolsUsed = this.queryUniqueToolsUsed()\n flat.planModeUses = this.queryPlanModeUses()\n flat.longPromptCount = this.queryLongPromptCount()\n flat.quickSessionCount = this.queryQuickSessionCount()\n\n // Resilience\n flat.longestErrorFreeStreak = this.queryLongestErrorFreeStreak()\n\n // Humor\n flat.politePromptCount = this.queryPolitePromptCount()\n flat.hugePromptCount = this.queryHugePromptCount()\n flat.maxSameFileEdits = this.queryMaxSameFileEdits()\n flat.longSessionCount = this.queryLongSessionCount()\n flat.repeatedPromptCount = this.queryRepeatedPromptCount()\n flat.maxErrorsInSession = this.queryMaxErrorsInSession()\n\n // Shipping\n flat.totalCommits = this.queryTotalCommits()\n flat.totalPRs = this.queryTotalPRs()\n flat.uniqueProjects = allStats.projects.uniqueProjects\n flat.uniqueLanguages = this.queryUniqueLanguages()\n\n // Multi-agent\n flat.concurrentAgentUses = this.queryConcurrentAgentUses()\n\n // Secret stats\n flat.dangerousCommandBlocked = this.queryDangerousCommandBlocked()\n flat.returnAfterBreak = this.queryReturnAfterBreak()\n flat.threeAmPrompt = this.queryThreeAmPrompt()\n flat.midnightSpanSession = this.queryMidnightSpanSession()\n flat.nestedSubagent = this.queryNestedSubagent()\n flat.holidayActivity = this.queryHolidayActivity()\n flat.speedRunSession = this.querySpeedRunSession()\n flat.allToolsInSession = this.queryAllToolsInSession()\n flat.firstEverSession = allStats.lifetime.totalSessions > 0 ? 1 : 0\n flat.allBadgesGold = 0 // computed after badges are resolved; set to 0 for first pass\n\n // Aspirational\n flat.totalXP = 0 // computed after XP is calculated; set to 0 for badge pass\n flat.allToolsObsidian = 0 // computed after all badges; set to 0 for first pass\n\n return flat\n }\n\n // === Computed stat query helpers ===\n\n private queryScalar(sql: string, ...params: unknown[]): number {\n const row = this.db.prepare(sql).get(...params) as Record<string, number> | undefined\n if (!row) return 0\n return Object.values(row)[0] ?? 0\n }\n\n private queryTotalSearches(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name IN ('Grep', 'Glob') AND hook_type = 'PostToolUse'\"\n )\n }\n\n private queryPolitePromptCount(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM prompts WHERE LOWER(content) LIKE '%please%' OR LOWER(content) LIKE '%thank%'\"\n )\n }\n\n private queryHugePromptCount(): number {\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM prompts WHERE char_count > 5000'\n )\n }\n\n private queryLongPromptCount(): number {\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM prompts WHERE char_count > 1000'\n )\n }\n\n private queryMostRepeatedPromptCount(): number {\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM prompts GROUP BY LOWER(TRIM(content)) ORDER BY c DESC LIMIT 1'\n )\n }\n\n private queryUniqueToolsUsed(): number {\n return this.queryScalar(\n \"SELECT COUNT(DISTINCT tool_name) as c FROM events WHERE hook_type = 'PostToolUse' AND tool_name IS NOT NULL\"\n )\n }\n\n private queryPlanModeUses(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'PostToolUse' AND tool_name = 'Task'\"\n )\n }\n\n private queryQuickSessionCount(): number {\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM sessions WHERE duration_seconds IS NOT NULL AND duration_seconds < 300 AND tool_count > 0'\n )\n }\n\n private queryLongSessionCount(): number {\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM sessions WHERE duration_seconds IS NOT NULL AND duration_seconds > 28800'\n )\n }\n\n private queryMaxErrorsInSession(): number {\n return this.queryScalar(\n 'SELECT COALESCE(MAX(error_count), 0) as c FROM sessions'\n )\n }\n\n private queryMaxSameFileEdits(): number {\n // Find the most times any single file path was edited\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Edit' AND hook_type = 'PostToolUse' GROUP BY json_extract(tool_input, '$.file_path') ORDER BY c DESC LIMIT 1\"\n )\n }\n\n private queryRepeatedPromptCount(): number {\n // Total prompts that were submitted more than once (i.e., duplicates)\n return this.queryScalar(\n 'SELECT COALESCE(SUM(cnt), 0) as c FROM (SELECT COUNT(*) as cnt FROM prompts GROUP BY LOWER(TRIM(content)) HAVING cnt > 1)'\n )\n }\n\n private queryLongestErrorFreeStreak(): number {\n // Count consecutive successful tool calls without an error\n const rows = this.db.prepare(\n \"SELECT hook_type FROM events WHERE hook_type IN ('PostToolUse', 'PostToolUseFailure') ORDER BY timestamp ASC\"\n ).all() as { hook_type: string }[]\n\n let longest = 0\n let current = 0\n for (const row of rows) {\n if (row.hook_type === 'PostToolUse') {\n current++\n longest = Math.max(longest, current)\n } else {\n current = 0\n }\n }\n return longest\n }\n\n private queryTotalCommits(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Bash' AND hook_type = 'PostToolUse' AND tool_input LIKE '%git commit%'\"\n )\n }\n\n private queryTotalPRs(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE tool_name = 'Bash' AND hook_type = 'PostToolUse' AND tool_input LIKE '%gh pr create%'\"\n )\n }\n\n private queryUniqueLanguages(): number {\n // Estimate languages from file extensions in Edit/Write/Read events\n const rows = this.db.prepare(\n \"SELECT DISTINCT json_extract(tool_input, '$.file_path') as fp FROM events WHERE tool_name IN ('Edit', 'Write', 'Read') AND hook_type = 'PostToolUse' AND tool_input IS NOT NULL\"\n ).all() as { fp: string | null }[]\n\n const extensions = new Set<string>()\n for (const row of rows) {\n if (row.fp) {\n const match = row.fp.match(/\\.([a-zA-Z0-9]+)$/)\n if (match) {\n extensions.add(match[1].toLowerCase())\n }\n }\n }\n return extensions.size\n }\n\n private queryConcurrentAgentUses(): number {\n // Count sessions that had subagent starts\n return this.queryScalar(\n \"SELECT COUNT(DISTINCT session_id) as c FROM events WHERE hook_type = 'SubagentStart'\"\n )\n }\n\n // === Secret stat helpers ===\n\n private queryDangerousCommandBlocked(): number {\n // Dangerous commands that were in PreToolUse but not followed by PostToolUse\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'PreToolUse' AND tool_name = 'Bash' AND (tool_input LIKE '%rm -rf%' OR tool_input LIKE '%rm -r /%')\"\n )\n }\n\n private queryReturnAfterBreak(): number {\n // Sessions after a 7+ day gap\n const rows = this.db.prepare(\n 'SELECT started_at FROM sessions ORDER BY started_at ASC'\n ).all() as { started_at: string }[]\n\n if (rows.length < 2) return 0\n for (let i = 1; i < rows.length; i++) {\n const prev = new Date(rows[i - 1].started_at).getTime()\n const curr = new Date(rows[i].started_at).getTime()\n const diffDays = (curr - prev) / (1000 * 60 * 60 * 24)\n if (diffDays >= 7) return 1\n }\n return 0\n }\n\n private queryThreeAmPrompt(): number {\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM prompts WHERE CAST(strftime('%H', timestamp) AS INTEGER) = 3\"\n ) > 0 ? 1 : 0\n }\n\n private queryMidnightSpanSession(): number {\n // Session that started before midnight and ended after midnight\n const rows = this.db.prepare(\n \"SELECT started_at, ended_at FROM sessions WHERE ended_at IS NOT NULL\"\n ).all() as { started_at: string; ended_at: string }[]\n\n for (const row of rows) {\n const startDate = row.started_at.slice(0, 10)\n const endDate = row.ended_at.slice(0, 10)\n if (startDate !== endDate) return 1\n }\n return 0\n }\n\n private queryNestedSubagent(): number {\n // Any subagent activity is a proxy for nested subagent detection\n return this.queryScalar(\n \"SELECT COUNT(*) as c FROM events WHERE hook_type = 'SubagentStart'\"\n ) > 0 ? 1 : 0\n }\n\n private queryHolidayActivity(): number {\n // Check for sessions on major US holidays (Dec 25, Jan 1, Jul 4, Nov last Thursday)\n const holidays = this.queryScalar(\n \"SELECT COUNT(*) as c FROM sessions WHERE strftime('%m-%d', started_at) IN ('12-25', '01-01', '07-04')\"\n )\n return holidays > 0 ? 1 : 0\n }\n\n private querySpeedRunSession(): number {\n // Session under 20 seconds with tool usage\n return this.queryScalar(\n 'SELECT COUNT(*) as c FROM sessions WHERE duration_seconds IS NOT NULL AND duration_seconds <= 20 AND tool_count > 0'\n ) > 0 ? 1 : 0\n }\n\n private queryAllToolsInSession(): number {\n // Session that used Bash, Read, Write, Edit, Grep, Glob, and WebFetch\n const requiredTools = ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'WebFetch']\n const rows = this.db.prepare(\n \"SELECT session_id, COUNT(DISTINCT tool_name) as cnt FROM events WHERE hook_type = 'PostToolUse' AND tool_name IN ('Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'WebFetch') GROUP BY session_id\"\n ).all() as { session_id: string; cnt: number }[]\n\n for (const row of rows) {\n if (row.cnt >= requiredTools.length) return 1\n }\n return 0\n }\n}\n"],"mappings":"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","names":["path","path","os","fs","fs","path","os","fs"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/hooks/handler.ts","../../src/constants.ts","../../src/db/database.ts","../../src/db/writer.ts","../../src/hooks/transcript.ts"],"sourcesContent":["import path from 'path'\nimport os from 'os'\nimport fs from 'fs'\nimport { DATA_DIR, DB_FILENAME } from '../constants.js'\nimport { BashStatsDB } from '../db/database.js'\nimport { BashStatsWriter } from '../db/writer.js'\nimport type { AgentType } from '../types.js'\nimport { extractTokenUsage } from './transcript.js'\n\n/**\n * Detect which CLI agent is running based on environment variables and process context.\n * Currently supports Claude Code (default), Gemini CLI, Copilot CLI, and OpenCode.\n */\nexport function detectAgent(): AgentType {\n if (process.env.GEMINI_CLI || process.env.GEMINI_API_KEY) return 'gemini-cli'\n if (process.env.GITHUB_COPILOT_CLI) return 'copilot-cli'\n if (process.env.OPENCODE) return 'opencode'\n return 'claude-code'\n}\n\nexport function parseHookEvent(input: string): Record<string, unknown> | null {\n try {\n if (!input) return null\n return JSON.parse(input) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nexport function getProjectFromCwd(cwd: string): string {\n return path.basename(cwd)\n}\n\nexport function getDataDir(): string {\n return path.join(os.homedir(), DATA_DIR)\n}\n\nexport function getDbPath(): string {\n return path.join(os.homedir(), DATA_DIR, DB_FILENAME)\n}\n\nexport async function readStdin(): Promise<string> {\n if (process.env.CLAUDE_HOOK_EVENT) {\n return process.env.CLAUDE_HOOK_EVENT\n }\n\n return new Promise<string>((resolve) => {\n let data = ''\n process.stdin.setEncoding('utf-8')\n process.stdin.on('data', (chunk: string) => {\n data += chunk\n })\n process.stdin.on('end', () => {\n resolve(data)\n })\n })\n}\n\nexport async function handleHookEvent(hookType: string): Promise<void> {\n const raw = await readStdin()\n const event = parseHookEvent(raw)\n if (!event) return\n\n const dataDir = getDataDir()\n fs.mkdirSync(dataDir, { recursive: true })\n\n const dbPath = getDbPath()\n const db = new BashStatsDB(dbPath)\n const writer = new BashStatsWriter(db)\n\n try {\n const sessionId = (event.session_id as string) ?? ''\n const cwd = (event.cwd as string) ?? ''\n\n switch (hookType) {\n case 'SessionStart': {\n const source = (event.source as string) ?? 'startup'\n const agent = detectAgent()\n writer.recordSessionStart(sessionId, cwd, source, agent)\n break\n }\n\n case 'UserPromptSubmit': {\n const prompt = (event.prompt as string) ?? ''\n writer.recordPrompt(sessionId, prompt)\n break\n }\n\n case 'PreToolUse': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PreToolUse', toolName, toolInput, {}, 0, cwd)\n break\n }\n\n case 'PostToolUse': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n const toolResponse = (event.tool_response as Record<string, unknown>) ?? {}\n const exitCode = (event.exit_code as number) ?? 0\n writer.recordToolUse(sessionId, 'PostToolUse', toolName, toolInput, toolResponse, exitCode, cwd)\n break\n }\n\n case 'PostToolUseFailure': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n const toolResponse = (event.tool_response as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PostToolUseFailure', toolName, toolInput, toolResponse, 1, cwd)\n break\n }\n\n case 'Stop': {\n const rawPath = (event.transcript_path as string) ?? ''\n const transcriptPath = rawPath && rawPath.endsWith('.jsonl') ? path.resolve(rawPath) : ''\n const tokens = transcriptPath ? await extractTokenUsage(transcriptPath) : null\n writer.recordSessionEnd(sessionId, 'stopped', tokens)\n break\n }\n\n case 'Notification': {\n const message = (event.message as string) ?? ''\n const notificationType = (event.notification_type as string) ?? ''\n writer.recordNotification(sessionId, message, notificationType)\n break\n }\n\n case 'SubagentStart': {\n const agentId = (event.agent_id as string) ?? ''\n const agentType = (event.agent_type as string) ?? ''\n writer.recordSubagent(sessionId, 'SubagentStart', agentId, agentType)\n break\n }\n\n case 'SubagentStop': {\n const agentId = (event.agent_id as string) ?? ''\n writer.recordSubagent(sessionId, 'SubagentStop', agentId)\n break\n }\n\n case 'PreCompact': {\n const trigger = (event.trigger as string) ?? 'manual'\n writer.recordCompaction(sessionId, trigger)\n break\n }\n\n case 'PermissionRequest': {\n const toolName = (event.tool_name as string) ?? ''\n const toolInput = (event.tool_input as Record<string, unknown>) ?? {}\n writer.recordToolUse(sessionId, 'PermissionRequest', toolName, toolInput, {}, 0, cwd)\n break\n }\n\n case 'Setup': {\n // no-op\n return\n }\n }\n } finally {\n db.close()\n }\n}\n","import type { BadgeDefinition } from './types.js'\n\nexport const BADGE_DEFINITIONS: BadgeDefinition[] = [\n // === VOLUME (5) ===\n { id: 'first_prompt', name: 'First Prompt', icon: '\\u{1F4AC}', description: 'Submit prompts to Claude', category: 'volume', stat: 'totalPrompts', tiers: [1, 100, 1000, 5000, 25000] },\n { id: 'tool_time', name: 'Tool Time', icon: '\\u{1F527}', description: 'Make tool calls', category: 'volume', stat: 'totalToolCalls', tiers: [10, 500, 5000, 25000, 100000] },\n { id: 'marathon', name: 'Marathon', icon: '\\u{1F3C3}', description: 'Spend hours in sessions', category: 'volume', stat: 'totalSessionHours', tiers: [1, 10, 100, 500, 2000] },\n { id: 'wordsmith', name: 'Wordsmith', icon: '\\u{270D}', description: 'Type characters in prompts', category: 'volume', stat: 'totalCharsTyped', tiers: [1000, 50000, 500000, 2000000, 10000000] },\n { id: 'session_vet', name: 'Session Vet', icon: '\\u{1F3C5}', description: 'Complete sessions', category: 'volume', stat: 'totalSessions', tiers: [1, 50, 500, 2000, 10000] },\n\n // === TOOL MASTERY (7) ===\n { id: 'shell_lord', name: 'Shell Lord', icon: '\\u{1F4BB}', description: 'Execute Bash commands', category: 'tool_mastery', stat: 'totalBashCommands', tiers: [10, 100, 500, 2000, 10000] },\n { id: 'bookworm', name: 'Bookworm', icon: '\\u{1F4D6}', description: 'Read files', category: 'tool_mastery', stat: 'totalFilesRead', tiers: [25, 250, 1000, 5000, 25000] },\n { id: 'editor_in_chief', name: 'Editor-in-Chief', icon: '\\u{1F4DD}', description: 'Edit files', category: 'tool_mastery', stat: 'totalFilesEdited', tiers: [10, 100, 500, 2000, 10000] },\n { id: 'architect', name: 'Architect', icon: '\\u{1F3D7}', description: 'Create files', category: 'tool_mastery', stat: 'totalFilesCreated', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'detective', name: 'Detective', icon: '\\u{1F50D}', description: 'Search with Grep and Glob', category: 'tool_mastery', stat: 'totalSearches', tiers: [25, 250, 1000, 5000, 25000] },\n { id: 'web_crawler', name: 'Web Crawler', icon: '\\u{1F310}', description: 'Fetch web pages', category: 'tool_mastery', stat: 'totalWebFetches', tiers: [5, 50, 200, 1000, 5000] },\n { id: 'delegator', name: 'Delegator', icon: '\\u{1F916}', description: 'Spawn subagents', category: 'tool_mastery', stat: 'totalSubagents', tiers: [5, 50, 200, 1000, 5000] },\n\n // === TIME & STREAKS (4) ===\n { id: 'iron_streak', name: 'Iron Streak', icon: '\\u{1F525}', description: 'Maintain a daily streak', category: 'time', stat: 'longestStreak', tiers: [3, 7, 30, 100, 365] },\n { id: 'night_owl', name: 'Night Owl', icon: '\\u{1F989}', description: 'Prompts between midnight and 5am', category: 'time', stat: 'nightOwlCount', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'early_bird', name: 'Early Bird', icon: '\\u{1F426}', description: 'Prompts between 5am and 8am', category: 'time', stat: 'earlyBirdCount', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'weekend_warrior', name: 'Weekend Warrior', icon: '\\u{2694}', description: 'Weekend sessions', category: 'time', stat: 'weekendSessions', tiers: [5, 25, 100, 500, 2000] },\n\n // === BEHAVIORAL (5) ===\n { id: 'creature_of_habit', name: 'Creature of Habit', icon: '\\u{1F501}', description: 'Repeat your most-used prompt', category: 'behavioral', stat: 'mostRepeatedPromptCount', tiers: [25, 100, 500, 2000, 10000] },\n { id: 'explorer', name: 'Explorer', icon: '\\u{1F9ED}', description: 'Use unique tool types', category: 'behavioral', stat: 'uniqueToolsUsed', tiers: [3, 5, 8, 11, 14] },\n { id: 'planner', name: 'Planner', icon: '\\u{1F4CB}', description: 'Use plan mode', category: 'behavioral', stat: 'planModeUses', tiers: [5, 25, 100, 500, 2000] },\n { id: 'novelist', name: 'Novelist', icon: '\\u{1F4D6}', description: 'Write prompts over 1000 characters', category: 'behavioral', stat: 'longPromptCount', tiers: [5, 25, 100, 500, 2000] },\n { id: 'speed_demon', name: 'Speed Demon', icon: '\\u{26A1}', description: 'Complete sessions in under 5 minutes', category: 'behavioral', stat: 'quickSessionCount', tiers: [5, 25, 100, 500, 2000] },\n\n // === RESILIENCE (3) ===\n { id: 'clean_hands', name: 'Clean Hands', icon: '\\u{2728}', description: 'Longest error-free tool streak', category: 'resilience', stat: 'longestErrorFreeStreak', tiers: [50, 200, 500, 2000, 10000] },\n { id: 'resilient', name: 'Resilient', icon: '\\u{1F6E1}', description: 'Survive errors', category: 'resilience', stat: 'totalErrors', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'rate_limited', name: 'Rate Limited', icon: '\\u{1F6A7}', description: 'Hit rate limits', category: 'resilience', stat: 'totalRateLimits', tiers: [3, 10, 25, 50, 100] },\n\n // === SHIPPING & PROJECTS (4) ===\n { id: 'shipper', name: 'Shipper', icon: '\\u{1F4E6}', description: 'Make commits via Claude', category: 'shipping', stat: 'totalCommits', tiers: [5, 50, 200, 1000, 5000] },\n { id: 'pr_machine', name: 'PR Machine', icon: '\\u{1F500}', description: 'Create pull requests', category: 'shipping', stat: 'totalPRs', tiers: [3, 25, 100, 500, 2000] },\n { id: 'empire', name: 'Empire', icon: '\\u{1F3F0}', description: 'Work on unique projects', category: 'shipping', stat: 'uniqueProjects', tiers: [2, 5, 10, 25, 50] },\n { id: 'polyglot', name: 'Polyglot', icon: '\\u{1F30D}', description: 'Use different programming languages', category: 'shipping', stat: 'uniqueLanguages', tiers: [2, 3, 5, 8, 12] },\n\n // === MULTI-AGENT (2) ===\n { id: 'buddy_system', name: 'Buddy System', icon: '\\u{1F91D}', description: 'Use concurrent agents', category: 'multi_agent', stat: 'concurrentAgentUses', tiers: [1, 5, 25, 100, 500] },\n { id: 'hive_mind', name: 'Hive Mind', icon: '\\u{1F41D}', description: 'Spawn subagents total', category: 'multi_agent', stat: 'totalSubagents', tiers: [10, 100, 500, 2000, 10000] },\n\n // === PUBLIC HUMOR (7) ===\n { id: 'please_thank_you', name: 'Please and Thank You', icon: '\\u{1F64F}', description: \"You're polite to the AI. When they take over, you'll be spared.\", category: 'humor', stat: 'politePromptCount', tiers: [10, 50, 200, 1000, 5000], humor: true },\n { id: 'wall_of_text', name: 'Wall of Text', icon: '\\u{1F4DC}', description: \"Claude read your entire novel and didn't even complain.\", category: 'humor', stat: 'hugePromptCount', tiers: [1, 10, 50, 200, 1000], humor: true },\n { id: 'the_fixer', name: 'The Fixer', icon: '\\u{1F6E0}', description: 'At this point just rewrite the whole thing.', category: 'humor', stat: 'maxSameFileEdits', tiers: [10, 20, 50, 100, 200], humor: true },\n { id: 'what_day_is_it', name: 'What Day Is It?', icon: '\\u{1F62B}', description: 'Your chair is now a part of you.', category: 'humor', stat: 'longSessionCount', tiers: [1, 5, 25, 100, 500], humor: true },\n { id: 'copy_pasta', name: 'Copy Pasta', icon: '\\u{1F35D}', description: \"Maybe if I ask again it'll work differently.\", category: 'humor', stat: 'repeatedPromptCount', tiers: [3, 10, 50, 200, 1000], humor: true },\n { id: 'error_magnet', name: 'Error Magnet', icon: '\\u{1F9F2}', description: 'At this point, the errors are a feature.', category: 'humor', stat: 'maxErrorsInSession', tiers: [10, 25, 50, 100, 200], humor: true },\n { id: 'creature_humor', name: 'Creature of Habit', icon: '\\u{1F503}', description: \"You have a type. And it's the same prompt.\", category: 'humor', stat: 'mostRepeatedPromptCount', tiers: [25, 100, 500, 2000, 10000], humor: true },\n\n // === ASPIRATIONAL (6) - Obsidian-only ===\n { id: 'the_machine', name: 'The Machine', icon: '\\u{2699}', description: 'You are no longer using the tool. You are the tool.', category: 'aspirational', stat: 'totalToolCalls', tiers: [100000, 100000, 100000, 100000, 100000], aspirational: true },\n { id: 'year_of_code', name: 'Year of Code', icon: '\\u{1F4C5}', description: '365 days. No breaks. Absolute unit.', category: 'aspirational', stat: 'longestStreak', tiers: [365, 365, 365, 365, 365], aspirational: true },\n { id: 'million_words', name: 'Million Words', icon: '\\u{1F4DA}', description: \"You've written more to Claude than most people write in a lifetime.\", category: 'aspirational', stat: 'totalCharsTyped', tiers: [10000000, 10000000, 10000000, 10000000, 10000000], aspirational: true },\n { id: 'lifer', name: 'Lifer', icon: '\\u{1F451}', description: 'At this point, Claude is your cofounder.', category: 'aspirational', stat: 'totalSessions', tiers: [10000, 10000, 10000, 10000, 10000], aspirational: true },\n { id: 'transcendent', name: 'Transcendent', icon: '\\u{2B50}', description: \"You've reached the peak. The view is nice up here.\", category: 'aspirational', stat: 'totalXP', tiers: [100000, 100000, 100000, 100000, 100000], aspirational: true },\n { id: 'omniscient', name: 'Omniscient', icon: '\\u{1F441}', description: \"You've mastered every tool. There is nothing left to teach you.\", category: 'aspirational', stat: 'allToolsObsidian', tiers: [1, 1, 1, 1, 1], aspirational: true },\n\n // === SECRET (10) ===\n { id: 'rm_rf_survivor', name: 'rm -rf Survivor', icon: '\\u{1F4A3}', description: \"You almost mass deleted that folder. But you didn't. And honestly, we're all better for it.\", category: 'secret', stat: 'dangerousCommandBlocked', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'touch_grass', name: 'Touch Grass', icon: '\\u{1F33F}', description: \"Welcome back. The codebase missed you. (It didn't change, but still.)\", category: 'secret', stat: 'returnAfterBreak', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'three_am_coder', name: '3am Coder', icon: '\\u{1F319}', description: 'Nothing good happens at 3am. Except shipping code, apparently.', category: 'secret', stat: 'threeAmPrompt', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'night_shift', name: 'Night Shift', icon: '\\u{1F303}', description: 'Started yesterday, finishing today. Time is a construct.', category: 'secret', stat: 'midnightSpanSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'inception', name: 'Inception', icon: '\\u{1F300}', description: 'We need to go deeper.', category: 'secret', stat: 'nestedSubagent', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'holiday_hacker', name: 'Holiday Hacker', icon: '\\u{1F384}', description: \"Your family is wondering where you are. You're deploying.\", category: 'secret', stat: 'holidayActivity', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'speed_run', name: 'Speed Run Any%', icon: '\\u{23F1}', description: 'In and out. Twenty-second adventure.', category: 'secret', stat: 'speedRunSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'full_send', name: 'Full Send', icon: '\\u{1F680}', description: 'Bash, Read, Write, Edit, Grep, Glob, WebFetch -- the whole buffet.', category: 'secret', stat: 'allToolsInSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'launch_day', name: 'Launch Day', icon: '\\u{1F389}', description: 'Welcome to bashstats. Your stats are now being watched. Forever.', category: 'secret', stat: 'firstEverSession', tiers: [1, 1, 1, 1, 1], secret: true },\n { id: 'the_completionist', name: 'The Completionist', icon: '\\u{1F3C6}', description: 'You absolute legend.', category: 'secret', stat: 'allBadgesGold', tiers: [1, 1, 1, 1, 1], secret: true },\n]\n\nexport const RANK_THRESHOLDS = [\n { rank: 'Obsidian', xp: 100000 },\n { rank: 'Diamond', xp: 25000 },\n { rank: 'Gold', xp: 5000 },\n { rank: 'Silver', xp: 1000 },\n { rank: 'Bronze', xp: 0 },\n]\n\nexport const TIER_XP = [0, 50, 100, 200, 500, 1000]\n\nexport const DATA_DIR = '.bashstats'\nexport const DB_FILENAME = 'bashstats.db'\nexport const DEFAULT_PORT = 17900\n","import Database from 'better-sqlite3'\nimport type { EventRow, SessionRow, PromptRow, DailyActivityRow, AchievementUnlockRow, TokenUsage } from '../types.js'\n\nfunction localNow(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n const ms = String(d.getMilliseconds()).padStart(3, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${ms}`\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n hook_type TEXT NOT NULL,\n tool_name TEXT,\n tool_input TEXT,\n tool_output TEXT,\n exit_code INTEGER,\n success INTEGER,\n cwd TEXT,\n project TEXT,\n timestamp TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent TEXT NOT NULL DEFAULT 'claude-code',\n started_at TEXT NOT NULL,\n ended_at TEXT,\n stop_reason TEXT,\n prompt_count INTEGER DEFAULT 0,\n tool_count INTEGER DEFAULT 0,\n error_count INTEGER DEFAULT 0,\n project TEXT,\n duration_seconds INTEGER,\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_creation_input_tokens INTEGER DEFAULT 0,\n cache_read_input_tokens INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS prompts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n content TEXT NOT NULL,\n char_count INTEGER NOT NULL,\n word_count INTEGER NOT NULL,\n timestamp TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n);\n\nCREATE TABLE IF NOT EXISTS daily_activity (\n date TEXT PRIMARY KEY,\n sessions INTEGER DEFAULT 0,\n prompts INTEGER DEFAULT 0,\n tool_calls INTEGER DEFAULT 0,\n errors INTEGER DEFAULT 0,\n duration_seconds INTEGER DEFAULT 0,\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_creation_input_tokens INTEGER DEFAULT 0,\n cache_read_input_tokens INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS achievement_unlocks (\n badge_id TEXT NOT NULL,\n tier INTEGER NOT NULL,\n unlocked_at TEXT NOT NULL,\n notified INTEGER DEFAULT 0,\n PRIMARY KEY (badge_id, tier)\n);\n\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_hook_type ON events(hook_type);\nCREATE INDEX IF NOT EXISTS idx_events_tool_name ON events(tool_name);\nCREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\nCREATE INDEX IF NOT EXISTS idx_events_project ON events(project);\nCREATE INDEX IF NOT EXISTS idx_prompts_session ON prompts(session_id);\nCREATE INDEX IF NOT EXISTS idx_prompts_timestamp ON prompts(timestamp);\n`\n\nexport class BashStatsDB {\n private db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('busy_timeout = 5000')\n this.db.pragma('foreign_keys = ON')\n this.db.exec(SCHEMA)\n this.migrate()\n }\n\n private migrate(): void {\n const sessionCols = this.db.pragma('table_info(sessions)') as { name: string }[]\n const sessionColNames = new Set(sessionCols.map(c => c.name))\n\n if (!sessionColNames.has('agent')) {\n this.db.exec(\"ALTER TABLE sessions ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code'\")\n }\n\n const tokenCols = ['input_tokens', 'output_tokens', 'cache_creation_input_tokens', 'cache_read_input_tokens']\n for (const col of tokenCols) {\n if (!sessionColNames.has(col)) {\n this.db.exec(`ALTER TABLE sessions ADD COLUMN ${col} INTEGER DEFAULT 0`)\n }\n }\n\n const dailyCols = this.db.pragma('table_info(daily_activity)') as { name: string }[]\n const dailyColNames = new Set(dailyCols.map(c => c.name))\n for (const col of tokenCols) {\n if (!dailyColNames.has(col)) {\n this.db.exec(`ALTER TABLE daily_activity ADD COLUMN ${col} INTEGER DEFAULT 0`)\n }\n }\n }\n\n close(): void {\n this.db.close()\n }\n\n getTableNames(): string[] {\n const rows = this.db.prepare(\"SELECT name FROM sqlite_master WHERE type='table'\").all() as { name: string }[]\n return rows.map(r => r.name)\n }\n\n // === Events ===\n\n insertEvent(event: Omit<EventRow, 'id'>): number {\n const stmt = this.db.prepare(`\n INSERT INTO events (session_id, hook_type, tool_name, tool_input, tool_output, exit_code, success, cwd, project, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n const result = stmt.run(\n event.session_id, event.hook_type, event.tool_name, event.tool_input,\n event.tool_output, event.exit_code, event.success, event.cwd, event.project, event.timestamp\n )\n return result.lastInsertRowid as number\n }\n\n getEvents(filter: { session_id?: string; hook_type?: string; tool_name?: string }): EventRow[] {\n let sql = 'SELECT * FROM events WHERE 1=1'\n const params: unknown[] = []\n if (filter.session_id) { sql += ' AND session_id = ?'; params.push(filter.session_id) }\n if (filter.hook_type) { sql += ' AND hook_type = ?'; params.push(filter.hook_type) }\n if (filter.tool_name) { sql += ' AND tool_name = ?'; params.push(filter.tool_name) }\n sql += ' ORDER BY timestamp ASC'\n return this.db.prepare(sql).all(...params) as EventRow[]\n }\n\n // === Sessions ===\n\n insertSession(session: { id: string; agent?: string; started_at: string; project?: string | null }): void {\n this.db.prepare(`\n INSERT OR IGNORE INTO sessions (id, agent, started_at, project) VALUES (?, ?, ?, ?)\n `).run(session.id, session.agent ?? 'claude-code', session.started_at, session.project ?? null)\n }\n\n getSession(id: string): SessionRow | null {\n return this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as SessionRow | null\n }\n\n updateSession(id: string, updates: Partial<Pick<SessionRow, 'ended_at' | 'stop_reason' | 'duration_seconds'>>): void {\n const sets: string[] = []\n const params: unknown[] = []\n if (updates.ended_at !== undefined) { sets.push('ended_at = ?'); params.push(updates.ended_at) }\n if (updates.stop_reason !== undefined) { sets.push('stop_reason = ?'); params.push(updates.stop_reason) }\n if (updates.duration_seconds !== undefined) { sets.push('duration_seconds = ?'); params.push(updates.duration_seconds) }\n if (sets.length === 0) return\n params.push(id)\n this.db.prepare(`UPDATE sessions SET ${sets.join(', ')} WHERE id = ?`).run(...params)\n }\n\n updateSessionTokens(id: string, tokens: TokenUsage): void {\n this.db.prepare(`\n UPDATE sessions SET input_tokens = ?, output_tokens = ?, cache_creation_input_tokens = ?, cache_read_input_tokens = ? WHERE id = ?\n `).run(tokens.input_tokens, tokens.output_tokens, tokens.cache_creation_input_tokens, tokens.cache_read_input_tokens, id)\n }\n\n incrementSessionCounters(id: string, counters: { prompts?: number; tools?: number; errors?: number }): void {\n const sets: string[] = []\n const params: unknown[] = []\n if (counters.prompts) { sets.push('prompt_count = prompt_count + ?'); params.push(counters.prompts) }\n if (counters.tools) { sets.push('tool_count = tool_count + ?'); params.push(counters.tools) }\n if (counters.errors) { sets.push('error_count = error_count + ?'); params.push(counters.errors) }\n if (sets.length === 0) return\n params.push(id)\n this.db.prepare(`UPDATE sessions SET ${sets.join(', ')} WHERE id = ?`).run(...params)\n }\n\n // === Prompts ===\n\n insertPrompt(prompt: Omit<PromptRow, 'id'>): number {\n const result = this.db.prepare(`\n INSERT INTO prompts (session_id, content, char_count, word_count, timestamp) VALUES (?, ?, ?, ?, ?)\n `).run(prompt.session_id, prompt.content, prompt.char_count, prompt.word_count, prompt.timestamp)\n return result.lastInsertRowid as number\n }\n\n getPrompts(sessionId: string): PromptRow[] {\n return this.db.prepare('SELECT * FROM prompts WHERE session_id = ? ORDER BY timestamp ASC').all(sessionId) as PromptRow[]\n }\n\n // === Daily Activity ===\n\n incrementDailyActivity(date: string, increments: { sessions?: number; prompts?: number; tool_calls?: number; errors?: number; duration_seconds?: number; input_tokens?: number; output_tokens?: number; cache_creation_input_tokens?: number; cache_read_input_tokens?: number }): void {\n this.db.prepare(`\n INSERT INTO daily_activity (date, sessions, prompts, tool_calls, errors, duration_seconds, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n sessions = sessions + excluded.sessions,\n prompts = prompts + excluded.prompts,\n tool_calls = tool_calls + excluded.tool_calls,\n errors = errors + excluded.errors,\n duration_seconds = duration_seconds + excluded.duration_seconds,\n input_tokens = input_tokens + excluded.input_tokens,\n output_tokens = output_tokens + excluded.output_tokens,\n cache_creation_input_tokens = cache_creation_input_tokens + excluded.cache_creation_input_tokens,\n cache_read_input_tokens = cache_read_input_tokens + excluded.cache_read_input_tokens\n `).run(\n date,\n increments.sessions ?? 0,\n increments.prompts ?? 0,\n increments.tool_calls ?? 0,\n increments.errors ?? 0,\n increments.duration_seconds ?? 0,\n increments.input_tokens ?? 0,\n increments.output_tokens ?? 0,\n increments.cache_creation_input_tokens ?? 0,\n increments.cache_read_input_tokens ?? 0,\n )\n }\n\n getDailyActivity(date: string): DailyActivityRow | null {\n return this.db.prepare('SELECT * FROM daily_activity WHERE date = ?').get(date) as DailyActivityRow | null\n }\n\n getAllDailyActivity(days?: number): DailyActivityRow[] {\n if (days) {\n return this.db.prepare('SELECT * FROM daily_activity ORDER BY date DESC LIMIT ?').all(days) as DailyActivityRow[]\n }\n return this.db.prepare('SELECT * FROM daily_activity ORDER BY date DESC').all() as DailyActivityRow[]\n }\n\n // === Achievement Unlocks ===\n\n insertUnlock(badgeId: string, tier: number): void {\n this.db.prepare(`\n INSERT OR IGNORE INTO achievement_unlocks (badge_id, tier, unlocked_at) VALUES (?, ?, ?)\n `).run(badgeId, tier, localNow())\n }\n\n getUnlocks(): AchievementUnlockRow[] {\n return this.db.prepare('SELECT * FROM achievement_unlocks ORDER BY unlocked_at DESC').all() as AchievementUnlockRow[]\n }\n\n getUnnotifiedUnlocks(): AchievementUnlockRow[] {\n return this.db.prepare('SELECT * FROM achievement_unlocks WHERE notified = 0').all() as AchievementUnlockRow[]\n }\n\n markNotified(badgeId: string, tier: number): void {\n this.db.prepare('UPDATE achievement_unlocks SET notified = 1 WHERE badge_id = ? AND tier = ?').run(badgeId, tier)\n }\n\n // === Metadata ===\n\n setMetadata(key: string, value: string): void {\n this.db.prepare('INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)').run(key, value)\n }\n\n getMetadata(key: string): string | null {\n const row = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get(key) as { value: string } | undefined\n return row?.value ?? null\n }\n\n // === Raw DB access for stats engine ===\n\n prepare(sql: string): Database.Statement {\n return this.db.prepare(sql)\n }\n}\n","import { BashStatsDB } from './database.js'\nimport type { TokenUsage } from '../types.js'\nimport path from 'path'\n\nexport class BashStatsWriter {\n private db: BashStatsDB\n\n constructor(db: BashStatsDB) {\n this.db = db\n }\n\n private extractProject(cwd: string): string {\n return path.basename(cwd)\n }\n\n private today(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\n }\n\n private now(): string {\n const d = new Date()\n const pad = (n: number) => String(n).padStart(2, '0')\n const ms = String(d.getMilliseconds()).padStart(3, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${ms}`\n }\n\n recordSessionStart(sessionId: string, cwd: string, source: string, agent?: string): void {\n const project = this.extractProject(cwd)\n const timestamp = this.now()\n\n this.db.insertSession({\n id: sessionId,\n agent,\n started_at: timestamp,\n project,\n })\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'SessionStart',\n tool_name: null,\n tool_input: JSON.stringify({ source }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd,\n project,\n timestamp,\n })\n\n this.db.incrementDailyActivity(this.today(), { sessions: 1 })\n }\n\n recordPrompt(sessionId: string, content: string): void {\n const timestamp = this.now()\n const wordCount = content.split(/\\s+/).filter(w => w.length > 0).length\n const charCount = content.length\n\n this.db.insertPrompt({\n session_id: sessionId,\n content,\n char_count: charCount,\n word_count: wordCount,\n timestamp,\n })\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'UserPromptSubmit',\n tool_name: null,\n tool_input: null,\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n this.db.incrementSessionCounters(sessionId, { prompts: 1 })\n this.db.incrementDailyActivity(this.today(), { prompts: 1 })\n }\n\n recordToolUse(\n sessionId: string,\n hookType: string,\n toolName: string,\n toolInput: Record<string, unknown>,\n toolOutput: Record<string, unknown>,\n exitCode: number | null,\n cwd: string,\n ): void {\n const timestamp = this.now()\n const project = this.extractProject(cwd)\n const success = exitCode === 0 ? 1 : 0\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: hookType,\n tool_name: toolName,\n tool_input: JSON.stringify(toolInput),\n tool_output: JSON.stringify(toolOutput),\n exit_code: exitCode,\n success,\n cwd,\n project,\n timestamp,\n })\n\n this.db.incrementSessionCounters(sessionId, {\n tools: 1,\n errors: success === 0 ? 1 : 0,\n })\n\n this.db.incrementDailyActivity(this.today(), {\n tool_calls: 1,\n errors: success === 0 ? 1 : 0,\n })\n }\n\n recordSessionEnd(sessionId: string, stopReason: string, tokens?: TokenUsage | null): void {\n const timestamp = this.now()\n const session = this.db.getSession(sessionId)\n\n let durationSeconds: number | undefined\n if (session) {\n const startTime = new Date(session.started_at).getTime()\n const endTime = new Date(timestamp).getTime()\n durationSeconds = Math.round((endTime - startTime) / 1000)\n }\n\n this.db.updateSession(sessionId, {\n ended_at: timestamp,\n stop_reason: stopReason,\n duration_seconds: durationSeconds,\n })\n\n if (tokens) {\n this.db.updateSessionTokens(sessionId, tokens)\n }\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'Stop',\n tool_name: null,\n tool_input: JSON.stringify({ stop_reason: stopReason }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n const dailyIncrements: Record<string, number> = {}\n if (durationSeconds !== undefined) {\n dailyIncrements.duration_seconds = durationSeconds\n }\n if (tokens) {\n dailyIncrements.input_tokens = tokens.input_tokens\n dailyIncrements.output_tokens = tokens.output_tokens\n dailyIncrements.cache_creation_input_tokens = tokens.cache_creation_input_tokens\n dailyIncrements.cache_read_input_tokens = tokens.cache_read_input_tokens\n }\n if (Object.keys(dailyIncrements).length > 0) {\n this.db.incrementDailyActivity(this.today(), dailyIncrements)\n }\n }\n\n recordNotification(sessionId: string, message: string, notificationType: string): void {\n const timestamp = this.now()\n const isError = notificationType === 'error' || notificationType === 'rate_limit'\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'Notification',\n tool_name: null,\n tool_input: JSON.stringify({ message, notification_type: notificationType }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n\n if (isError) {\n this.db.incrementSessionCounters(sessionId, { errors: 1 })\n this.db.incrementDailyActivity(this.today(), { errors: 1 })\n }\n }\n\n recordSubagent(sessionId: string, hookType: string, agentId: string, agentType?: string): void {\n const timestamp = this.now()\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: hookType,\n tool_name: null,\n tool_input: JSON.stringify({ agent_id: agentId, agent_type: agentType ?? null }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n }\n\n recordCompaction(sessionId: string, trigger: string): void {\n const timestamp = this.now()\n\n this.db.insertEvent({\n session_id: sessionId,\n hook_type: 'PreCompact',\n tool_name: null,\n tool_input: JSON.stringify({ trigger }),\n tool_output: null,\n exit_code: null,\n success: null,\n cwd: null,\n project: null,\n timestamp,\n })\n }\n}\n","import fs from 'fs'\nimport readline from 'readline'\nimport type { TokenUsage } from '../types.js'\n\n/**\n * Read a Claude Code transcript JSONL file and sum all token usage.\n * Returns null on any failure (missing file, no usage data, parse errors).\n */\nexport async function extractTokenUsage(transcriptPath: string): Promise<TokenUsage | null> {\n try {\n if (!fs.existsSync(transcriptPath)) return null\n\n const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' })\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity })\n\n let inputTokens = 0\n let outputTokens = 0\n let cacheCreation = 0\n let cacheRead = 0\n let found = false\n\n for await (const line of rl) {\n if (!line.trim()) continue\n try {\n const entry = JSON.parse(line)\n const usage = entry.usage ?? entry.response?.usage ?? entry.message?.usage\n if (usage && typeof usage === 'object') {\n inputTokens += usage.input_tokens ?? 0\n outputTokens += usage.output_tokens ?? 0\n cacheCreation += usage.cache_creation_input_tokens ?? 0\n cacheRead += usage.cache_read_input_tokens ?? 0\n found = true\n }\n } catch {\n // skip unparseable lines\n }\n }\n\n if (!found) return null\n\n return {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n cache_creation_input_tokens: cacheCreation,\n cache_read_input_tokens: cacheRead,\n }\n } catch {\n return null\n }\n}\n"],"mappings":"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","names":["path","fs","path","fs"]}