bashstats 0.1.0 → 0.2.0

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.
@@ -0,0 +1 @@
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"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/notification.ts
7
7
  handleHookEvent("Notification").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/permission-request.ts
7
7
  handleHookEvent("PermissionRequest").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/post-tool-failure.ts
7
7
  handleHookEvent("PostToolUseFailure").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/post-tool-use.ts
7
7
  handleHookEvent("PostToolUse").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/pre-compact.ts
7
7
  handleHookEvent("PreCompact").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/pre-tool-use.ts
7
7
  handleHookEvent("PreToolUse").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/session-start.ts
7
7
  handleHookEvent("SessionStart").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/setup.ts
7
7
  handleHookEvent("Setup").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/stop.ts
7
7
  handleHookEvent("Stop").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/subagent-start.ts
7
7
  handleHookEvent("SubagentStart").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/subagent-stop.ts
7
7
  handleHookEvent("SubagentStop").catch(() => process.exit(0));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleHookEvent
4
- } from "./chunk-EFVDQUHM.js";
4
+ } from "./chunk-7K77JJRD.js";
5
5
 
6
6
  // src/hooks/scripts/user-prompt-submit.ts
7
7
  handleHookEvent("UserPromptSubmit").catch(() => process.exit(0));
package/dist/index.d.ts CHANGED
@@ -54,6 +54,12 @@ interface SetupInput extends HookInput {
54
54
  trigger: 'init' | 'maintenance';
55
55
  CLAUDE_ENV_FILE: string;
56
56
  }
57
+ interface TokenUsage {
58
+ input_tokens: number;
59
+ output_tokens: number;
60
+ cache_creation_input_tokens: number;
61
+ cache_read_input_tokens: number;
62
+ }
57
63
  interface EventRow {
58
64
  id: number;
59
65
  session_id: string;
@@ -78,6 +84,10 @@ interface SessionRow {
78
84
  error_count: number;
79
85
  project: string | null;
80
86
  duration_seconds: number | null;
87
+ input_tokens: number | null;
88
+ output_tokens: number | null;
89
+ cache_creation_input_tokens: number | null;
90
+ cache_read_input_tokens: number | null;
81
91
  }
82
92
  interface PromptRow {
83
93
  id: number;
@@ -94,6 +104,10 @@ interface DailyActivityRow {
94
104
  tool_calls: number;
95
105
  errors: number;
96
106
  duration_seconds: number;
107
+ input_tokens: number;
108
+ output_tokens: number;
109
+ cache_creation_input_tokens: number;
110
+ cache_read_input_tokens: number;
97
111
  }
98
112
  interface AchievementUnlockRow {
99
113
  badge_id: string;
@@ -118,6 +132,11 @@ interface LifetimeStats {
118
132
  totalCompactions: number;
119
133
  totalErrors: number;
120
134
  totalRateLimits: number;
135
+ totalInputTokens: number;
136
+ totalOutputTokens: number;
137
+ totalCacheCreationTokens: number;
138
+ totalCacheReadTokens: number;
139
+ totalTokens: number;
121
140
  }
122
141
  interface ToolBreakdown {
123
142
  [toolName: string]: number;
@@ -142,6 +161,8 @@ interface SessionRecords {
142
161
  avgDurationSeconds: number;
143
162
  avgPromptsPerSession: number;
144
163
  avgToolsPerSession: number;
164
+ mostTokensInSession: number;
165
+ avgTokensPerSession: number;
145
166
  }
146
167
  interface ProjectStats {
147
168
  uniqueProjects: number;
@@ -221,6 +242,7 @@ declare class BashStatsDB {
221
242
  }): void;
222
243
  getSession(id: string): SessionRow | null;
223
244
  updateSession(id: string, updates: Partial<Pick<SessionRow, 'ended_at' | 'stop_reason' | 'duration_seconds'>>): void;
245
+ updateSessionTokens(id: string, tokens: TokenUsage): void;
224
246
  incrementSessionCounters(id: string, counters: {
225
247
  prompts?: number;
226
248
  tools?: number;
@@ -234,6 +256,10 @@ declare class BashStatsDB {
234
256
  tool_calls?: number;
235
257
  errors?: number;
236
258
  duration_seconds?: number;
259
+ input_tokens?: number;
260
+ output_tokens?: number;
261
+ cache_creation_input_tokens?: number;
262
+ cache_read_input_tokens?: number;
237
263
  }): void;
238
264
  getDailyActivity(date: string): DailyActivityRow | null;
239
265
  getAllDailyActivity(days?: number): DailyActivityRow[];
@@ -255,7 +281,7 @@ declare class BashStatsWriter {
255
281
  recordSessionStart(sessionId: string, cwd: string, source: string, agent?: string): void;
256
282
  recordPrompt(sessionId: string, content: string): void;
257
283
  recordToolUse(sessionId: string, hookType: string, toolName: string, toolInput: Record<string, unknown>, toolOutput: Record<string, unknown>, exitCode: number | null, cwd: string): void;
258
- recordSessionEnd(sessionId: string, stopReason: string): void;
284
+ recordSessionEnd(sessionId: string, stopReason: string, tokens?: TokenUsage | null): void;
259
285
  recordNotification(sessionId: string, message: string, notificationType: string): void;
260
286
  recordSubagent(sessionId: string, hookType: string, agentId: string, agentType?: string): void;
261
287
  recordCompaction(sessionId: string, trigger: string): void;
@@ -352,4 +378,4 @@ declare const DATA_DIR = ".bashstats";
352
378
  declare const DB_FILENAME = "bashstats.db";
353
379
  declare const DEFAULT_PORT = 17900;
354
380
 
355
- export { AGENT_DISPLAY_NAMES, AchievementEngine, type AchievementUnlockRow, type AchievementsPayload, type AgentType, type AllStats, BADGE_DEFINITIONS, type BadgeDefinition, type BadgeResult, type BadgeTier, BashStatsDB, BashStatsWriter, DATA_DIR, DB_FILENAME, DEFAULT_PORT, type DailyActivityRow, type EventRow, type HookInput, type LifetimeStats, type NotificationInput, type PermissionRequestInput, type PostToolUseInput, type PreCompactInput, type PreToolUseInput, type ProjectStats, type PromptRow, RANK_THRESHOLDS, type SessionRecords, type SessionRow, type SessionStartInput, type SetupInput, StatsEngine, type StopInput, type SubagentStartInput, type SubagentStopInput, TIER_NAMES, TIER_XP, type TimeStats, type ToolBreakdown, type UserPromptInput, type XPResult, detectAgent, handleHookEvent, install, isInstalled, parseHookEvent, uninstall };
381
+ export { AGENT_DISPLAY_NAMES, AchievementEngine, type AchievementUnlockRow, type AchievementsPayload, type AgentType, type AllStats, BADGE_DEFINITIONS, type BadgeDefinition, type BadgeResult, type BadgeTier, BashStatsDB, BashStatsWriter, DATA_DIR, DB_FILENAME, DEFAULT_PORT, type DailyActivityRow, type EventRow, type HookInput, type LifetimeStats, type NotificationInput, type PermissionRequestInput, type PostToolUseInput, type PreCompactInput, type PreToolUseInput, type ProjectStats, type PromptRow, RANK_THRESHOLDS, type SessionRecords, type SessionRow, type SessionStartInput, type SetupInput, StatsEngine, type StopInput, type SubagentStartInput, type SubagentStopInput, TIER_NAMES, TIER_XP, type TimeStats, type TokenUsage, type ToolBreakdown, type UserPromptInput, type XPResult, detectAgent, handleHookEvent, install, isInstalled, parseHookEvent, uninstall };
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  isInstalled,
19
19
  parseHookEvent,
20
20
  uninstall
21
- } from "./chunk-2KXMOTBO.js";
21
+ } from "./chunk-OYLQHCOY.js";
22
22
  export {
23
23
  AGENT_DISPLAY_NAMES,
24
24
  AchievementEngine,
@@ -802,7 +802,7 @@
802
802
 
803
803
  .session-item {
804
804
  display: grid;
805
- grid-template-columns: 140px 1fr 100px 100px 100px;
805
+ grid-template-columns: 140px 1fr 100px 100px 100px 100px;
806
806
  gap: 12px;
807
807
  align-items: center;
808
808
  padding: 10px 14px;
@@ -1246,6 +1246,14 @@
1246
1246
  return (seconds / 3600).toFixed(1);
1247
1247
  }
1248
1248
 
1249
+ function formatTokens(n) {
1250
+ if (n == null || n === 0) return '0';
1251
+ if (n >= 1000000000) return (n / 1000000000).toFixed(1) + 'B';
1252
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
1253
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
1254
+ return n.toLocaleString();
1255
+ }
1256
+
1249
1257
  function formatDate(dateStr) {
1250
1258
  const d = new Date(dateStr);
1251
1259
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
@@ -1407,7 +1415,7 @@
1407
1415
  { label: 'Prompts', value: formatNumber(lt.totalPrompts || 0), sub: 'messages sent' },
1408
1416
  { label: 'Tool Calls', value: formatNumber(lt.totalToolCalls || 0), sub: 'total invocations' },
1409
1417
  { label: 'Hours', value: formatHours(lt.totalDurationSeconds || 0), sub: 'coding time' },
1410
- { label: 'Files Read', value: formatNumber(lt.totalFilesRead || 0), sub: 'files accessed' },
1418
+ { label: 'Tokens Used', value: formatTokens(lt.totalTokens || 0), sub: 'input + output' },
1411
1419
  { label: 'Bash Cmds', value: formatNumber(lt.totalBashCommands || lt.totalToolCalls || 0), sub: 'commands run' },
1412
1420
  ];
1413
1421
 
@@ -1440,7 +1448,7 @@
1440
1448
  <div class="stat-card"><div class="stat-card-label">Prompts</div><div class="stat-card-number mono">0</div></div>
1441
1449
  <div class="stat-card"><div class="stat-card-label">Tool Calls</div><div class="stat-card-number mono">0</div></div>
1442
1450
  <div class="stat-card"><div class="stat-card-label">Hours</div><div class="stat-card-number mono">0</div></div>
1443
- <div class="stat-card"><div class="stat-card-label">Files Read</div><div class="stat-card-number mono">0</div></div>
1451
+ <div class="stat-card"><div class="stat-card-label">Tokens Used</div><div class="stat-card-number mono">0</div></div>
1444
1452
  <div class="stat-card"><div class="stat-card-label">Bash Cmds</div><div class="stat-card-number mono">0</div></div>
1445
1453
  `;
1446
1454
  }
@@ -1610,17 +1618,20 @@
1610
1618
  const recent = sessions.slice(-20).reverse();
1611
1619
  el.innerHTML = `
1612
1620
  <div class="session-item" style="font-weight:600;background:var(--bg-accent);font-size:12px;">
1613
- <div>Date</div><div>Project</div><div style="text-align:right">Duration</div><div style="text-align:right">Prompts</div><div style="text-align:right">Tools</div>
1621
+ <div>Date</div><div>Project</div><div style="text-align:right">Duration</div><div style="text-align:right">Prompts</div><div style="text-align:right">Tools</div><div style="text-align:right">Tokens</div>
1614
1622
  </div>
1615
- ${recent.map(s => `
1623
+ ${recent.map(s => {
1624
+ const sessionTokens = (s.input_tokens || 0) + (s.output_tokens || 0);
1625
+ return `
1616
1626
  <div class="session-item">
1617
1627
  <div class="session-date">${escapeHtml(formatDateTime(s.start_time || s.startTime || s.started_at || ''))}</div>
1618
1628
  <div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}</div>
1619
1629
  <div class="session-stat">${formatDuration(s.duration_seconds || s.durationSeconds || 0)}</div>
1620
1630
  <div class="session-stat">${formatNumber(s.prompt_count || s.prompts || s.promptCount || 0)}</div>
1621
1631
  <div class="session-stat">${formatNumber(s.tool_count || s.tool_calls || s.toolCalls || 0)}</div>
1632
+ <div class="session-stat">${formatTokens(sessionTokens)}</div>
1622
1633
  </div>
1623
- `).join('')}
1634
+ `}).join('')}
1624
1635
  `;
1625
1636
  }
1626
1637
 
@@ -1658,6 +1669,16 @@
1658
1669
  ['Files Read', formatNumber(lt.totalFilesRead)],
1659
1670
  ]);
1660
1671
 
1672
+ // Token Usage
1673
+ html += buildStatsSection('Token Usage', [
1674
+ ['Total Tokens', formatTokens((lt.totalInputTokens || 0) + (lt.totalOutputTokens || 0))],
1675
+ ['Input Tokens', formatTokens(lt.totalInputTokens)],
1676
+ ['Output Tokens', formatTokens(lt.totalOutputTokens)],
1677
+ ['Cache Read Tokens', formatTokens(lt.totalCacheReadTokens)],
1678
+ ['Cache Creation Tokens', formatTokens(lt.totalCacheCreationTokens)],
1679
+ ['Avg Tokens / Session', formatTokens(lt.totalSessions ? Math.round(((lt.totalInputTokens || 0) + (lt.totalOutputTokens || 0)) / lt.totalSessions) : 0)],
1680
+ ]);
1681
+
1661
1682
  // Tool Breakdown
1662
1683
  const toolEntries = Object.entries(tools).sort((a, b) => b[1] - a[1]);
1663
1684
  if (toolEntries.length > 0) {
@@ -1823,9 +1844,11 @@
1823
1844
  { label: 'Longest Session', value: formatDuration(sess.longestSessionSeconds), sub: 'personal best' },
1824
1845
  { label: 'Most Tools (Session)', value: formatNumber(sess.mostToolsInSession), sub: 'single session' },
1825
1846
  { label: 'Most Prompts (Session)', value: formatNumber(sess.mostPromptsInSession), sub: 'single session' },
1847
+ { label: 'Most Tokens (Session)', value: formatTokens(sess.mostTokensInSession), sub: 'single session' },
1826
1848
  { label: 'Shortest Session', value: formatDuration(sess.shortestSessionSeconds), sub: 'speed run' },
1827
1849
  { label: 'Average Prompts', value: formatNumber(avgPrompts), sub: 'per session' },
1828
1850
  { label: 'Average Tools', value: formatNumber(avgTools), sub: 'per session' },
1851
+ { label: 'Avg Tokens / Session', value: formatTokens(sess.avgTokensPerSession), sub: 'per session' },
1829
1852
  { label: 'Average Duration', value: formatDuration(avgDuration), sub: 'per session' },
1830
1853
  { label: 'Longest Streak', value: (time.longestStreak || 0) + ' days', sub: 'consecutive days' },
1831
1854
  { label: 'Peak Hour', value: time.peakHour != null ? time.peakHour + ':00' : 'N/A', sub: 'most active hour' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bashstats",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Obsessive stat tracking, achievements, and badges for Claude Code users",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/debug-hook.cjs DELETED
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs')
3
- const path = require('path')
4
- const os = require('os')
5
-
6
- const logFile = path.join(os.homedir(), '.bashstats', 'hook-debug.log')
7
-
8
- function log(msg) {
9
- fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${msg}\n`)
10
- }
11
-
12
- log(`--- Hook invoked ---`)
13
- log(`argv: ${JSON.stringify(process.argv)}`)
14
- log(`CLAUDE_HOOK_EVENT env: ${process.env.CLAUDE_HOOK_EVENT ? 'SET (' + process.env.CLAUDE_HOOK_EVENT.length + ' chars)' : 'NOT SET'}`)
15
-
16
- let data = ''
17
- process.stdin.setEncoding('utf-8')
18
- process.stdin.on('data', (chunk) => {
19
- data += chunk
20
- })
21
- process.stdin.on('end', () => {
22
- log(`stdin length: ${data.length}`)
23
- log(`stdin preview: ${data.substring(0, 500)}`)
24
- try {
25
- const parsed = JSON.parse(data)
26
- log(`parsed hook_event_name: ${parsed.hook_event_name}`)
27
- log(`parsed session_id: ${parsed.session_id}`)
28
- if (parsed.prompt) log(`parsed prompt: ${parsed.prompt.substring(0, 100)}`)
29
- } catch (e) {
30
- log(`parse error: ${e.message}`)
31
- }
32
- log(`--- Done ---\n`)
33
- })
34
-
35
- setTimeout(() => {
36
- log(`TIMEOUT - stdin never ended. data so far: ${data.length} chars`)
37
- process.exit(0)
38
- }, 3000)