bashstats 0.1.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.
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;
@@ -164,9 +185,10 @@ interface BadgeDefinition {
164
185
  name: string;
165
186
  icon: string;
166
187
  description: string;
167
- category: 'volume' | 'tool_mastery' | 'time' | 'behavioral' | 'resilience' | 'shipping' | 'multi_agent' | 'humor' | 'aspirational' | 'secret';
188
+ category: 'volume' | 'tool_mastery' | 'time' | 'behavioral' | 'resilience' | 'shipping' | 'multi_agent' | 'humor' | 'aspirational' | 'secret' | 'session_behavior' | 'prompt_patterns' | 'error_recovery' | 'tool_combos' | 'project_dedication' | 'token_usage';
168
189
  stat: string;
169
190
  tiers: [number, number, number, number, number];
191
+ trigger: string;
170
192
  secret?: boolean;
171
193
  humor?: boolean;
172
194
  aspirational?: boolean;
@@ -185,6 +207,7 @@ interface BadgeResult {
185
207
  nextThreshold: number;
186
208
  progress: number;
187
209
  maxed: boolean;
210
+ trigger: string;
188
211
  secret: boolean;
189
212
  unlocked: boolean;
190
213
  }
@@ -221,6 +244,7 @@ declare class BashStatsDB {
221
244
  }): void;
222
245
  getSession(id: string): SessionRow | null;
223
246
  updateSession(id: string, updates: Partial<Pick<SessionRow, 'ended_at' | 'stop_reason' | 'duration_seconds'>>): void;
247
+ updateSessionTokens(id: string, tokens: TokenUsage): void;
224
248
  incrementSessionCounters(id: string, counters: {
225
249
  prompts?: number;
226
250
  tools?: number;
@@ -234,6 +258,10 @@ declare class BashStatsDB {
234
258
  tool_calls?: number;
235
259
  errors?: number;
236
260
  duration_seconds?: number;
261
+ input_tokens?: number;
262
+ output_tokens?: number;
263
+ cache_creation_input_tokens?: number;
264
+ cache_read_input_tokens?: number;
237
265
  }): void;
238
266
  getDailyActivity(date: string): DailyActivityRow | null;
239
267
  getAllDailyActivity(days?: number): DailyActivityRow[];
@@ -255,7 +283,7 @@ declare class BashStatsWriter {
255
283
  recordSessionStart(sessionId: string, cwd: string, source: string, agent?: string): void;
256
284
  recordPrompt(sessionId: string, content: string): void;
257
285
  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;
286
+ recordSessionEnd(sessionId: string, stopReason: string, tokens?: TokenUsage | null): void;
259
287
  recordNotification(sessionId: string, message: string, notificationType: string): void;
260
288
  recordSubagent(sessionId: string, hookType: string, agentId: string, agentType?: string): void;
261
289
  recordCompaction(sessionId: string, trigger: string): void;
@@ -307,6 +335,53 @@ declare class AchievementEngine {
307
335
  private queryHolidayActivity;
308
336
  private querySpeedRunSession;
309
337
  private queryAllToolsInSession;
338
+ private queryWitchingHourPrompts;
339
+ private queryLunchBreakDays;
340
+ private queryMondaySessions;
341
+ private queryFridayCommits;
342
+ private queryMaxUniqueHoursInDay;
343
+ private queryUniqueQuarters;
344
+ private queryExtendedSessionCount;
345
+ private queryQuickDrawSessions;
346
+ private queryDiverseToolSessions;
347
+ private queryPermissionRequests;
348
+ private queryReturnerDays;
349
+ private queryShortPromptCount;
350
+ private queryQuestionPromptCount;
351
+ private querySorryPromptCount;
352
+ private queryCapsLockPromptCount;
353
+ private queryEmojiPromptCount;
354
+ private queryCodeDumpPromptCount;
355
+ private queryRubberDuckCount;
356
+ private queryThirdTimeCharmCount;
357
+ private queryUndoEditCount;
358
+ private queryCrashySessions;
359
+ private queryReadEditRunCount;
360
+ private queryMaxFilesCreatedInSession;
361
+ private queryMaxSameFileEditsLifetime;
362
+ private querySearchThenEditCount;
363
+ private queryMaxProjectSessions;
364
+ private queryMaxProjectsInDay;
365
+ private queryFinishedProjects;
366
+ private queryLegacyReturns;
367
+ private queryMaxConcurrentSubagents;
368
+ private queryQuickSubagentStops;
369
+ private queryMaxSubagentsInSession;
370
+ private queryDejaVuCount;
371
+ private queryTrustIssueCount;
372
+ private queryBackseatDriverCount;
373
+ private queryNegotiatorCount;
374
+ private queryMaxConsecutivePermissions;
375
+ private queryBashRetrySuccessCount;
376
+ private queryEasterEggActivity;
377
+ private queryFullMoonSession;
378
+ private queryBirthdaySession;
379
+ private queryGhostSessions;
380
+ private queryBullseyeSessions;
381
+ private queryHeavyTokenSessions;
382
+ private queryLightTokenSessions;
383
+ private queryMaxOutputInSession;
384
+ private queryHasTenMillionSession;
310
385
  }
311
386
 
312
387
  /**
@@ -352,4 +427,4 @@ declare const DATA_DIR = ".bashstats";
352
427
  declare const DB_FILENAME = "bashstats.db";
353
428
  declare const DEFAULT_PORT = 17900;
354
429
 
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 };
430
+ 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-4HVDBCTU.js";
22
22
  export {
23
23
  AGENT_DISPLAY_NAMES,
24
24
  AchievementEngine,
@@ -608,6 +608,17 @@
608
608
  line-height: 1.4;
609
609
  }
610
610
 
611
+ .badge-trigger {
612
+ font-size: 11px;
613
+ font-family: 'JetBrains Mono', monospace;
614
+ color: var(--accent);
615
+ line-height: 1.3;
616
+ padding: 3px 6px;
617
+ background: var(--bg-accent);
618
+ border-radius: 2px;
619
+ border-left: 2px solid var(--accent);
620
+ }
621
+
611
622
  .badge-progress-row {
612
623
  display: flex;
613
624
  align-items: center;
@@ -802,7 +813,7 @@
802
813
 
803
814
  .session-item {
804
815
  display: grid;
805
- grid-template-columns: 140px 1fr 100px 100px 100px;
816
+ grid-template-columns: 140px 1fr 100px 100px 100px 100px;
806
817
  gap: 12px;
807
818
  align-items: center;
808
819
  padding: 10px 14px;
@@ -1246,6 +1257,14 @@
1246
1257
  return (seconds / 3600).toFixed(1);
1247
1258
  }
1248
1259
 
1260
+ function formatTokens(n) {
1261
+ if (n == null || n === 0) return '0';
1262
+ if (n >= 1000000000) return (n / 1000000000).toFixed(1) + 'B';
1263
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
1264
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
1265
+ return n.toLocaleString();
1266
+ }
1267
+
1249
1268
  function formatDate(dateStr) {
1250
1269
  const d = new Date(dateStr);
1251
1270
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
@@ -1407,7 +1426,7 @@
1407
1426
  { label: 'Prompts', value: formatNumber(lt.totalPrompts || 0), sub: 'messages sent' },
1408
1427
  { label: 'Tool Calls', value: formatNumber(lt.totalToolCalls || 0), sub: 'total invocations' },
1409
1428
  { label: 'Hours', value: formatHours(lt.totalDurationSeconds || 0), sub: 'coding time' },
1410
- { label: 'Files Read', value: formatNumber(lt.totalFilesRead || 0), sub: 'files accessed' },
1429
+ { label: 'Tokens Used', value: formatTokens(lt.totalTokens || 0), sub: 'all token types' },
1411
1430
  { label: 'Bash Cmds', value: formatNumber(lt.totalBashCommands || lt.totalToolCalls || 0), sub: 'commands run' },
1412
1431
  ];
1413
1432
 
@@ -1440,7 +1459,7 @@
1440
1459
  <div class="stat-card"><div class="stat-card-label">Prompts</div><div class="stat-card-number mono">0</div></div>
1441
1460
  <div class="stat-card"><div class="stat-card-label">Tool Calls</div><div class="stat-card-number mono">0</div></div>
1442
1461
  <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>
1462
+ <div class="stat-card"><div class="stat-card-label">Tokens Used</div><div class="stat-card-number mono">0</div></div>
1444
1463
  <div class="stat-card"><div class="stat-card-label">Bash Cmds</div><div class="stat-card-number mono">0</div></div>
1445
1464
  `;
1446
1465
  }
@@ -1610,17 +1629,20 @@
1610
1629
  const recent = sessions.slice(-20).reverse();
1611
1630
  el.innerHTML = `
1612
1631
  <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>
1632
+ <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
1633
  </div>
1615
- ${recent.map(s => `
1634
+ ${recent.map(s => {
1635
+ const sessionTokens = (s.input_tokens || 0) + (s.output_tokens || 0) + (s.cache_creation_input_tokens || 0) + (s.cache_read_input_tokens || 0);
1636
+ return `
1616
1637
  <div class="session-item">
1617
1638
  <div class="session-date">${escapeHtml(formatDateTime(s.start_time || s.startTime || s.started_at || ''))}</div>
1618
1639
  <div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}</div>
1619
1640
  <div class="session-stat">${formatDuration(s.duration_seconds || s.durationSeconds || 0)}</div>
1620
1641
  <div class="session-stat">${formatNumber(s.prompt_count || s.prompts || s.promptCount || 0)}</div>
1621
1642
  <div class="session-stat">${formatNumber(s.tool_count || s.tool_calls || s.toolCalls || 0)}</div>
1643
+ <div class="session-stat">${formatTokens(sessionTokens)}</div>
1622
1644
  </div>
1623
- `).join('')}
1645
+ `}).join('')}
1624
1646
  `;
1625
1647
  }
1626
1648
 
@@ -1658,6 +1680,16 @@
1658
1680
  ['Files Read', formatNumber(lt.totalFilesRead)],
1659
1681
  ]);
1660
1682
 
1683
+ // Token Usage
1684
+ html += buildStatsSection('Token Usage', [
1685
+ ['Total Tokens', formatTokens(lt.totalTokens || 0)],
1686
+ ['Input Tokens', formatTokens(lt.totalInputTokens)],
1687
+ ['Output Tokens', formatTokens(lt.totalOutputTokens)],
1688
+ ['Cache Read Tokens', formatTokens(lt.totalCacheReadTokens)],
1689
+ ['Cache Creation Tokens', formatTokens(lt.totalCacheCreationTokens)],
1690
+ ['Avg Tokens / Session', formatTokens(lt.totalSessions ? Math.round((lt.totalTokens || 0) / lt.totalSessions) : 0)],
1691
+ ]);
1692
+
1661
1693
  // Tool Breakdown
1662
1694
  const toolEntries = Object.entries(tools).sort((a, b) => b[1] - a[1]);
1663
1695
  if (toolEntries.length > 0) {
@@ -1736,16 +1768,24 @@
1736
1768
  });
1737
1769
 
1738
1770
  let html = '';
1739
- const catOrder = ['volume', 'tools', 'streaks', 'time', 'exploration', 'humor', 'secret'];
1771
+ const catOrder = ['volume', 'token_usage', 'tool_mastery', 'time', 'session_behavior', 'behavioral', 'prompt_patterns', 'resilience', 'error_recovery', 'tool_combos', 'shipping', 'project_dedication', 'multi_agent', 'humor', 'aspirational', 'secret'];
1740
1772
  const catNames = {
1741
1773
  volume: 'Volume',
1742
- tools: 'Tools',
1743
- streaks: 'Streaks',
1744
- time: 'Time',
1745
- exploration: 'Exploration',
1746
- humor: 'Humor',
1774
+ token_usage: 'Token Usage',
1775
+ tool_mastery: 'Tool Mastery',
1776
+ time: 'Time & Patterns',
1777
+ session_behavior: 'Session Behavior',
1778
+ behavioral: 'Behavioral',
1779
+ prompt_patterns: 'Prompt Patterns',
1780
+ resilience: 'Resilience',
1781
+ error_recovery: 'Error & Recovery',
1782
+ tool_combos: 'Tool Combos',
1783
+ shipping: 'Shipping & Projects',
1784
+ project_dedication: 'Project Dedication',
1785
+ multi_agent: 'Multi-Agent',
1786
+ humor: 'Humor & Meta',
1787
+ aspirational: 'Aspirational',
1747
1788
  secret: 'Secret',
1748
- general: 'General',
1749
1789
  };
1750
1790
 
1751
1791
  // Sort: known categories first, then others
@@ -1765,6 +1805,7 @@
1765
1805
  const cardClass = isSecretLocked ? 'badge-card secret-locked' : (isLocked ? 'badge-card locked' : 'badge-card');
1766
1806
  const displayName = isSecretLocked ? '???' : b.name;
1767
1807
  const displayDesc = isSecretLocked ? 'Unlock this secret badge to reveal it.' : (b.description || '');
1808
+ const displayTrigger = isSecretLocked ? '' : (b.trigger || '');
1768
1809
  const pct = b.maxed ? 100 : Math.min(100, (b.progress || 0) * 100);
1769
1810
  const fillClass = b.maxed ? 'badge-progress-fill maxed' : 'badge-progress-fill';
1770
1811
 
@@ -1778,6 +1819,7 @@
1778
1819
  <span class="badge-tier-label" style="color:${tierColor(b.tier)};border-color:${tierColor(b.tier)}">${escapeHtml(b.tierName || TIER_NAMES[b.tier] || 'Locked')}</span>
1779
1820
  </div>
1780
1821
  ${displayDesc ? `<div class="badge-description">${escapeHtml(displayDesc)}</div>` : ''}
1822
+ ${displayTrigger ? `<div class="badge-trigger">${escapeHtml(displayTrigger)}</div>` : ''}
1781
1823
  <div class="badge-progress-row">
1782
1824
  <div class="badge-progress-track">
1783
1825
  <div class="${fillClass}" style="width:${pct}%"></div>
@@ -1823,9 +1865,11 @@
1823
1865
  { label: 'Longest Session', value: formatDuration(sess.longestSessionSeconds), sub: 'personal best' },
1824
1866
  { label: 'Most Tools (Session)', value: formatNumber(sess.mostToolsInSession), sub: 'single session' },
1825
1867
  { label: 'Most Prompts (Session)', value: formatNumber(sess.mostPromptsInSession), sub: 'single session' },
1868
+ { label: 'Most Tokens (Session)', value: formatTokens(sess.mostTokensInSession), sub: 'single session' },
1826
1869
  { label: 'Shortest Session', value: formatDuration(sess.shortestSessionSeconds), sub: 'speed run' },
1827
1870
  { label: 'Average Prompts', value: formatNumber(avgPrompts), sub: 'per session' },
1828
1871
  { label: 'Average Tools', value: formatNumber(avgTools), sub: 'per session' },
1872
+ { label: 'Avg Tokens / Session', value: formatTokens(sess.avgTokensPerSession), sub: 'per session' },
1829
1873
  { label: 'Average Duration', value: formatDuration(avgDuration), sub: 'per session' },
1830
1874
  { label: 'Longest Streak', value: (time.longestStreak || 0) + ' days', sub: 'consecutive days' },
1831
1875
  { 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.1",
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)