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.
- package/README.md +186 -0
- package/dist/{chunk-2KXMOTBO.js → chunk-OYLQHCOY.js} +135 -16
- package/dist/chunk-OYLQHCOY.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/hooks/{chunk-EFVDQUHM.js → chunk-7K77JJRD.js} +106 -15
- package/dist/hooks/chunk-7K77JJRD.js.map +1 -0
- package/dist/hooks/notification.js +1 -1
- package/dist/hooks/permission-request.js +1 -1
- package/dist/hooks/post-tool-failure.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-compact.js +1 -1
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/hooks/setup.js +1 -1
- package/dist/hooks/stop.js +1 -1
- package/dist/hooks/subagent-start.js +1 -1
- package/dist/hooks/subagent-stop.js +1 -1
- package/dist/hooks/user-prompt-submit.js +1 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +1 -1
- package/dist/static/index.html +29 -6
- package/package.json +1 -1
- package/debug-hook.cjs +0 -38
- package/dist/chunk-2KXMOTBO.js.map +0 -1
- package/dist/hooks/chunk-EFVDQUHM.js.map +0 -1
- package/nul +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# bashstats
|
|
2
|
+
<img width="1727" height="916" alt="bashstats2" src="https://github.com/user-attachments/assets/4029e711-f559-4771-9490-dedd4aeec1ee" />
|
|
3
|
+
|
|
4
|
+
Track every prompt, tool call, and late-night coding session. Earn badges. Build streaks. Watch your rank climb from Bronze to Obsidian.
|
|
5
|
+
bashstats hooks into Claude Code and quietly records everything — sessions, prompts, tool usage, errors, and streaks. It then turns it all into stats,
|
|
6
|
+
achievements, and a dashboard you'll check way too often.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g bashstats
|
|
12
|
+
bashstats init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`bashstats init` installs Claude Code hooks and creates the local database at `~/.bashstats/bashstats.db`. Stats begin recording immediately.
|
|
16
|
+
|
|
17
|
+
## CLI Commands
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `bashstats init` | Install hooks and set up database |
|
|
22
|
+
| `bashstats stats` | Quick stat summary in your terminal |
|
|
23
|
+
| `bashstats achievements` | List all badges with progress bars |
|
|
24
|
+
| `bashstats streak` | Show current and longest daily streak |
|
|
25
|
+
| `bashstats web` | Launch the browser dashboard |
|
|
26
|
+
| `bashstats export` | Export all data as JSON |
|
|
27
|
+
| `bashstats reset` | Wipe all data |
|
|
28
|
+
| `bashstats uninstall` | Remove hooks and data |
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bashstats web --port 8080 # Custom port (default: 17900)
|
|
34
|
+
bashstats web --no-open # Don't auto-open browser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Dashboard
|
|
38
|
+
|
|
39
|
+
The browser dashboard at `http://localhost:17900` includes:
|
|
40
|
+
|
|
41
|
+
- **Overview** - Recent badges, rank progress, stat cards, activity heatmap, and recent sessions at a glance
|
|
42
|
+
- **Stats** - Lifetime totals, tool breakdowns, time analysis, session records, and project stats in a 2x2 grid
|
|
43
|
+
- **Achievements** - All 53 badges with tier progress, organized by category
|
|
44
|
+
- **Timeline** - Activity heatmap and session history with sparkline charts
|
|
45
|
+
|
|
46
|
+
## What Gets Tracked
|
|
47
|
+
|
|
48
|
+
bashstats hooks into 12 Claude Code events:
|
|
49
|
+
|
|
50
|
+
| Event | What it records |
|
|
51
|
+
|---|---|
|
|
52
|
+
| SessionStart | Session creation, project, agent type |
|
|
53
|
+
| UserPromptSubmit | Prompt content, character/word counts |
|
|
54
|
+
| PreToolUse | Tool invocations (Bash, Read, Edit, etc.) |
|
|
55
|
+
| PostToolUse | Tool results and exit codes |
|
|
56
|
+
| PostToolUseFailure | Failed tool calls |
|
|
57
|
+
| Stop | Session end time and duration |
|
|
58
|
+
| Notification | Errors and rate limits |
|
|
59
|
+
| SubagentStart | Subagent spawns |
|
|
60
|
+
| SubagentStop | Subagent completions |
|
|
61
|
+
| PreCompact | Context compactions |
|
|
62
|
+
| PermissionRequest | Permission prompts |
|
|
63
|
+
| Setup | Initialization events |
|
|
64
|
+
|
|
65
|
+
## Achievements
|
|
66
|
+
<img width="1732" height="917" alt="bashstats" src="https://github.com/user-attachments/assets/63591b76-54ff-4659-b81a-e6310810e364" />
|
|
67
|
+
|
|
68
|
+
53 badges across 10 categories, each with 5 tiers: Bronze, Silver, Gold, Diamond, Obsidian.
|
|
69
|
+
|
|
70
|
+
### Volume
|
|
71
|
+
- **First Prompt** - Submit prompts to Claude
|
|
72
|
+
- **Tool Time** - Make tool calls
|
|
73
|
+
- **Marathon** - Spend hours in sessions
|
|
74
|
+
- **Wordsmith** - Type characters in prompts
|
|
75
|
+
- **Session Vet** - Complete sessions
|
|
76
|
+
|
|
77
|
+
### Tool Mastery
|
|
78
|
+
- **Shell Lord** - Execute Bash commands
|
|
79
|
+
- **Bookworm** - Read files
|
|
80
|
+
- **Editor-in-Chief** - Edit files
|
|
81
|
+
- **Architect** - Create files
|
|
82
|
+
- **Detective** - Search with Grep and Glob
|
|
83
|
+
- **Web Crawler** - Fetch web pages
|
|
84
|
+
- **Delegator** - Spawn subagents
|
|
85
|
+
|
|
86
|
+
### Time & Streaks
|
|
87
|
+
- **Iron Streak** - Maintain a daily streak
|
|
88
|
+
- **Night Owl** - Prompts between midnight and 5am
|
|
89
|
+
- **Early Bird** - Prompts between 5am and 8am
|
|
90
|
+
- **Weekend Warrior** - Weekend sessions
|
|
91
|
+
|
|
92
|
+
### Behavioral
|
|
93
|
+
- **Creature of Habit** - Repeat your most-used prompt
|
|
94
|
+
- **Explorer** - Use unique tool types
|
|
95
|
+
- **Planner** - Use plan mode
|
|
96
|
+
- **Novelist** - Write prompts over 1000 characters
|
|
97
|
+
- **Speed Demon** - Complete sessions in under 5 minutes
|
|
98
|
+
|
|
99
|
+
### Resilience
|
|
100
|
+
- **Clean Hands** - Longest error-free tool streak
|
|
101
|
+
- **Resilient** - Survive errors
|
|
102
|
+
- **Rate Limited** - Hit rate limits
|
|
103
|
+
|
|
104
|
+
### Shipping & Projects
|
|
105
|
+
- **Shipper** - Make commits via Claude
|
|
106
|
+
- **PR Machine** - Create pull requests
|
|
107
|
+
- **Empire** - Work on unique projects
|
|
108
|
+
- **Polyglot** - Use different programming languages
|
|
109
|
+
|
|
110
|
+
### Multi-Agent
|
|
111
|
+
- **Buddy System** - Use concurrent agents
|
|
112
|
+
- **Hive Mind** - Spawn subagents total
|
|
113
|
+
|
|
114
|
+
### Humor
|
|
115
|
+
- **Please and Thank You** - "You're polite to the AI. When they take over, you'll be spared."
|
|
116
|
+
- **Wall of Text** - "Claude read your entire novel and didn't even complain."
|
|
117
|
+
- **The Fixer** - "At this point just rewrite the whole thing."
|
|
118
|
+
- **What Day Is It?** - "Your chair is now a part of you."
|
|
119
|
+
- **Copy Pasta** - "Maybe if I ask again it'll work differently."
|
|
120
|
+
- **Error Magnet** - "At this point, the errors are a feature."
|
|
121
|
+
|
|
122
|
+
### Aspirational (Obsidian-only)
|
|
123
|
+
- **The Machine** - "You are no longer using the tool. You are the tool."
|
|
124
|
+
- **Year of Code** - "365 days. No breaks. Absolute unit."
|
|
125
|
+
- **Million Words** - "You've written more to Claude than most people write in a lifetime."
|
|
126
|
+
- **Lifer** - "At this point, Claude is your cofounder."
|
|
127
|
+
- **Transcendent** - "You've reached the peak. The view is nice up here."
|
|
128
|
+
- **Omniscient** - "You've mastered every tool. There is nothing left to teach you."
|
|
129
|
+
|
|
130
|
+
### Secret
|
|
131
|
+
10 hidden badges unlocked by specific behaviors. Discover them yourself.
|
|
132
|
+
|
|
133
|
+
## Rank System
|
|
134
|
+
|
|
135
|
+
XP is earned from badge tiers. Your rank progresses through:
|
|
136
|
+
|
|
137
|
+
| Rank | XP Required |
|
|
138
|
+
|---|---|
|
|
139
|
+
| Bronze | 0 |
|
|
140
|
+
| Silver | 1,000 |
|
|
141
|
+
| Gold | 5,000 |
|
|
142
|
+
| Diamond | 25,000 |
|
|
143
|
+
| Obsidian | 100,000 |
|
|
144
|
+
|
|
145
|
+
## Agent Support
|
|
146
|
+
|
|
147
|
+
bashstats detects which CLI agent is running:
|
|
148
|
+
|
|
149
|
+
- Claude Code (default)
|
|
150
|
+
- Gemini CLI
|
|
151
|
+
- Copilot CLI
|
|
152
|
+
- OpenCode
|
|
153
|
+
|
|
154
|
+
## Data Storage
|
|
155
|
+
|
|
156
|
+
All data is stored locally in `~/.bashstats/bashstats.db` (SQLite with WAL mode). Nothing is sent anywhere. Tables:
|
|
157
|
+
|
|
158
|
+
- `events` - Every hook event with full context
|
|
159
|
+
- `sessions` - Session lifecycle (start, end, duration, counts)
|
|
160
|
+
- `prompts` - Prompt content and word/char counts
|
|
161
|
+
- `daily_activity` - Aggregated daily stats
|
|
162
|
+
- `achievement_unlocks` - Badge tier unlock timestamps
|
|
163
|
+
|
|
164
|
+
## Tech Stack
|
|
165
|
+
|
|
166
|
+
- TypeScript + Node.js 18+
|
|
167
|
+
- SQLite via `better-sqlite3`
|
|
168
|
+
- Express for the dashboard server
|
|
169
|
+
- Commander for the CLI
|
|
170
|
+
- tsup for bundling
|
|
171
|
+
- vitest for tests
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/GhostPeony/bashstats.git
|
|
177
|
+
cd bashstats
|
|
178
|
+
npm install
|
|
179
|
+
npm run build
|
|
180
|
+
npm link
|
|
181
|
+
bashstats init
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -111,7 +111,11 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
111
111
|
tool_count INTEGER DEFAULT 0,
|
|
112
112
|
error_count INTEGER DEFAULT 0,
|
|
113
113
|
project TEXT,
|
|
114
|
-
duration_seconds INTEGER
|
|
114
|
+
duration_seconds INTEGER,
|
|
115
|
+
input_tokens INTEGER DEFAULT 0,
|
|
116
|
+
output_tokens INTEGER DEFAULT 0,
|
|
117
|
+
cache_creation_input_tokens INTEGER DEFAULT 0,
|
|
118
|
+
cache_read_input_tokens INTEGER DEFAULT 0
|
|
115
119
|
);
|
|
116
120
|
|
|
117
121
|
CREATE TABLE IF NOT EXISTS prompts (
|
|
@@ -130,7 +134,11 @@ CREATE TABLE IF NOT EXISTS daily_activity (
|
|
|
130
134
|
prompts INTEGER DEFAULT 0,
|
|
131
135
|
tool_calls INTEGER DEFAULT 0,
|
|
132
136
|
errors INTEGER DEFAULT 0,
|
|
133
|
-
duration_seconds INTEGER DEFAULT 0
|
|
137
|
+
duration_seconds INTEGER DEFAULT 0,
|
|
138
|
+
input_tokens INTEGER DEFAULT 0,
|
|
139
|
+
output_tokens INTEGER DEFAULT 0,
|
|
140
|
+
cache_creation_input_tokens INTEGER DEFAULT 0,
|
|
141
|
+
cache_read_input_tokens INTEGER DEFAULT 0
|
|
134
142
|
);
|
|
135
143
|
|
|
136
144
|
CREATE TABLE IF NOT EXISTS achievement_unlocks (
|
|
@@ -159,16 +167,30 @@ var BashStatsDB = class {
|
|
|
159
167
|
constructor(dbPath) {
|
|
160
168
|
this.db = new Database(dbPath);
|
|
161
169
|
this.db.pragma("journal_mode = WAL");
|
|
170
|
+
this.db.pragma("busy_timeout = 5000");
|
|
162
171
|
this.db.pragma("foreign_keys = ON");
|
|
163
172
|
this.db.exec(SCHEMA);
|
|
164
173
|
this.migrate();
|
|
165
174
|
}
|
|
166
175
|
migrate() {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
176
|
+
const sessionCols = this.db.pragma("table_info(sessions)");
|
|
177
|
+
const sessionColNames = new Set(sessionCols.map((c) => c.name));
|
|
178
|
+
if (!sessionColNames.has("agent")) {
|
|
170
179
|
this.db.exec("ALTER TABLE sessions ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude-code'");
|
|
171
180
|
}
|
|
181
|
+
const tokenCols = ["input_tokens", "output_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"];
|
|
182
|
+
for (const col of tokenCols) {
|
|
183
|
+
if (!sessionColNames.has(col)) {
|
|
184
|
+
this.db.exec(`ALTER TABLE sessions ADD COLUMN ${col} INTEGER DEFAULT 0`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const dailyCols = this.db.pragma("table_info(daily_activity)");
|
|
188
|
+
const dailyColNames = new Set(dailyCols.map((c) => c.name));
|
|
189
|
+
for (const col of tokenCols) {
|
|
190
|
+
if (!dailyColNames.has(col)) {
|
|
191
|
+
this.db.exec(`ALTER TABLE daily_activity ADD COLUMN ${col} INTEGER DEFAULT 0`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
172
194
|
}
|
|
173
195
|
close() {
|
|
174
196
|
this.db.close();
|
|
@@ -243,6 +265,11 @@ var BashStatsDB = class {
|
|
|
243
265
|
params.push(id);
|
|
244
266
|
this.db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
245
267
|
}
|
|
268
|
+
updateSessionTokens(id, tokens) {
|
|
269
|
+
this.db.prepare(`
|
|
270
|
+
UPDATE sessions SET input_tokens = ?, output_tokens = ?, cache_creation_input_tokens = ?, cache_read_input_tokens = ? WHERE id = ?
|
|
271
|
+
`).run(tokens.input_tokens, tokens.output_tokens, tokens.cache_creation_input_tokens, tokens.cache_read_input_tokens, id);
|
|
272
|
+
}
|
|
246
273
|
incrementSessionCounters(id, counters) {
|
|
247
274
|
const sets = [];
|
|
248
275
|
const params = [];
|
|
@@ -275,21 +302,29 @@ var BashStatsDB = class {
|
|
|
275
302
|
// === Daily Activity ===
|
|
276
303
|
incrementDailyActivity(date, increments) {
|
|
277
304
|
this.db.prepare(`
|
|
278
|
-
INSERT INTO daily_activity (date, sessions, prompts, tool_calls, errors, duration_seconds)
|
|
279
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
305
|
+
INSERT INTO daily_activity (date, sessions, prompts, tool_calls, errors, duration_seconds, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens)
|
|
306
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
280
307
|
ON CONFLICT(date) DO UPDATE SET
|
|
281
308
|
sessions = sessions + excluded.sessions,
|
|
282
309
|
prompts = prompts + excluded.prompts,
|
|
283
310
|
tool_calls = tool_calls + excluded.tool_calls,
|
|
284
311
|
errors = errors + excluded.errors,
|
|
285
|
-
duration_seconds = duration_seconds + excluded.duration_seconds
|
|
312
|
+
duration_seconds = duration_seconds + excluded.duration_seconds,
|
|
313
|
+
input_tokens = input_tokens + excluded.input_tokens,
|
|
314
|
+
output_tokens = output_tokens + excluded.output_tokens,
|
|
315
|
+
cache_creation_input_tokens = cache_creation_input_tokens + excluded.cache_creation_input_tokens,
|
|
316
|
+
cache_read_input_tokens = cache_read_input_tokens + excluded.cache_read_input_tokens
|
|
286
317
|
`).run(
|
|
287
318
|
date,
|
|
288
319
|
increments.sessions ?? 0,
|
|
289
320
|
increments.prompts ?? 0,
|
|
290
321
|
increments.tool_calls ?? 0,
|
|
291
322
|
increments.errors ?? 0,
|
|
292
|
-
increments.duration_seconds ?? 0
|
|
323
|
+
increments.duration_seconds ?? 0,
|
|
324
|
+
increments.input_tokens ?? 0,
|
|
325
|
+
increments.output_tokens ?? 0,
|
|
326
|
+
increments.cache_creation_input_tokens ?? 0,
|
|
327
|
+
increments.cache_read_input_tokens ?? 0
|
|
293
328
|
);
|
|
294
329
|
}
|
|
295
330
|
getDailyActivity(date) {
|
|
@@ -550,7 +585,7 @@ var BashStatsWriter = class {
|
|
|
550
585
|
errors: success === 0 ? 1 : 0
|
|
551
586
|
});
|
|
552
587
|
}
|
|
553
|
-
recordSessionEnd(sessionId, stopReason) {
|
|
588
|
+
recordSessionEnd(sessionId, stopReason, tokens) {
|
|
554
589
|
const timestamp = this.now();
|
|
555
590
|
const session = this.db.getSession(sessionId);
|
|
556
591
|
let durationSeconds;
|
|
@@ -564,6 +599,9 @@ var BashStatsWriter = class {
|
|
|
564
599
|
stop_reason: stopReason,
|
|
565
600
|
duration_seconds: durationSeconds
|
|
566
601
|
});
|
|
602
|
+
if (tokens) {
|
|
603
|
+
this.db.updateSessionTokens(sessionId, tokens);
|
|
604
|
+
}
|
|
567
605
|
this.db.insertEvent({
|
|
568
606
|
session_id: sessionId,
|
|
569
607
|
hook_type: "Stop",
|
|
@@ -576,8 +614,18 @@ var BashStatsWriter = class {
|
|
|
576
614
|
project: null,
|
|
577
615
|
timestamp
|
|
578
616
|
});
|
|
617
|
+
const dailyIncrements = {};
|
|
579
618
|
if (durationSeconds !== void 0) {
|
|
580
|
-
|
|
619
|
+
dailyIncrements.duration_seconds = durationSeconds;
|
|
620
|
+
}
|
|
621
|
+
if (tokens) {
|
|
622
|
+
dailyIncrements.input_tokens = tokens.input_tokens;
|
|
623
|
+
dailyIncrements.output_tokens = tokens.output_tokens;
|
|
624
|
+
dailyIncrements.cache_creation_input_tokens = tokens.cache_creation_input_tokens;
|
|
625
|
+
dailyIncrements.cache_read_input_tokens = tokens.cache_read_input_tokens;
|
|
626
|
+
}
|
|
627
|
+
if (Object.keys(dailyIncrements).length > 0) {
|
|
628
|
+
this.db.incrementDailyActivity(this.today(), dailyIncrements);
|
|
581
629
|
}
|
|
582
630
|
}
|
|
583
631
|
recordNotification(sessionId, message, notificationType) {
|
|
@@ -635,7 +683,49 @@ var BashStatsWriter = class {
|
|
|
635
683
|
// src/hooks/handler.ts
|
|
636
684
|
import path3 from "path";
|
|
637
685
|
import os2 from "os";
|
|
686
|
+
import fs3 from "fs";
|
|
687
|
+
|
|
688
|
+
// src/hooks/transcript.ts
|
|
638
689
|
import fs2 from "fs";
|
|
690
|
+
import readline from "readline";
|
|
691
|
+
async function extractTokenUsage(transcriptPath) {
|
|
692
|
+
try {
|
|
693
|
+
if (!fs2.existsSync(transcriptPath)) return null;
|
|
694
|
+
const stream = fs2.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
695
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
696
|
+
let inputTokens = 0;
|
|
697
|
+
let outputTokens = 0;
|
|
698
|
+
let cacheCreation = 0;
|
|
699
|
+
let cacheRead = 0;
|
|
700
|
+
let found = false;
|
|
701
|
+
for await (const line of rl) {
|
|
702
|
+
if (!line.trim()) continue;
|
|
703
|
+
try {
|
|
704
|
+
const entry = JSON.parse(line);
|
|
705
|
+
const usage = entry.usage ?? entry.response?.usage ?? entry.message?.usage;
|
|
706
|
+
if (usage && typeof usage === "object") {
|
|
707
|
+
inputTokens += usage.input_tokens ?? 0;
|
|
708
|
+
outputTokens += usage.output_tokens ?? 0;
|
|
709
|
+
cacheCreation += usage.cache_creation_input_tokens ?? 0;
|
|
710
|
+
cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
711
|
+
found = true;
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (!found) return null;
|
|
717
|
+
return {
|
|
718
|
+
input_tokens: inputTokens,
|
|
719
|
+
output_tokens: outputTokens,
|
|
720
|
+
cache_creation_input_tokens: cacheCreation,
|
|
721
|
+
cache_read_input_tokens: cacheRead
|
|
722
|
+
};
|
|
723
|
+
} catch {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/hooks/handler.ts
|
|
639
729
|
function detectAgent() {
|
|
640
730
|
if (process.env.GEMINI_CLI || process.env.GEMINI_API_KEY) return "gemini-cli";
|
|
641
731
|
if (process.env.GITHUB_COPILOT_CLI) return "copilot-cli";
|
|
@@ -676,7 +766,7 @@ async function handleHookEvent(hookType) {
|
|
|
676
766
|
const event = parseHookEvent(raw);
|
|
677
767
|
if (!event) return;
|
|
678
768
|
const dataDir = getDataDir();
|
|
679
|
-
|
|
769
|
+
fs3.mkdirSync(dataDir, { recursive: true });
|
|
680
770
|
const dbPath = getDbPath();
|
|
681
771
|
const db = new BashStatsDB(dbPath);
|
|
682
772
|
const writer = new BashStatsWriter(db);
|
|
@@ -717,7 +807,10 @@ async function handleHookEvent(hookType) {
|
|
|
717
807
|
break;
|
|
718
808
|
}
|
|
719
809
|
case "Stop": {
|
|
720
|
-
|
|
810
|
+
const rawPath = event.transcript_path ?? "";
|
|
811
|
+
const transcriptPath = rawPath && rawPath.endsWith(".jsonl") ? path3.resolve(rawPath) : "";
|
|
812
|
+
const tokens = transcriptPath ? await extractTokenUsage(transcriptPath) : null;
|
|
813
|
+
writer.recordSessionEnd(sessionId, "stopped", tokens);
|
|
721
814
|
break;
|
|
722
815
|
}
|
|
723
816
|
case "Notification": {
|
|
@@ -809,6 +902,19 @@ var StatsEngine = class {
|
|
|
809
902
|
const totalRateLimits = this.queryScalar(
|
|
810
903
|
"SELECT COUNT(*) as c FROM events WHERE hook_type = 'Notification' AND tool_input LIKE '%rate_limit%'"
|
|
811
904
|
);
|
|
905
|
+
const totalInputTokens = this.queryScalar(
|
|
906
|
+
"SELECT COALESCE(SUM(input_tokens), 0) as c FROM sessions"
|
|
907
|
+
);
|
|
908
|
+
const totalOutputTokens = this.queryScalar(
|
|
909
|
+
"SELECT COALESCE(SUM(output_tokens), 0) as c FROM sessions"
|
|
910
|
+
);
|
|
911
|
+
const totalCacheCreationTokens = this.queryScalar(
|
|
912
|
+
"SELECT COALESCE(SUM(cache_creation_input_tokens), 0) as c FROM sessions"
|
|
913
|
+
);
|
|
914
|
+
const totalCacheReadTokens = this.queryScalar(
|
|
915
|
+
"SELECT COALESCE(SUM(cache_read_input_tokens), 0) as c FROM sessions"
|
|
916
|
+
);
|
|
917
|
+
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
812
918
|
return {
|
|
813
919
|
totalSessions,
|
|
814
920
|
totalDurationSeconds,
|
|
@@ -825,7 +931,12 @@ var StatsEngine = class {
|
|
|
825
931
|
totalSubagents,
|
|
826
932
|
totalCompactions,
|
|
827
933
|
totalErrors,
|
|
828
|
-
totalRateLimits
|
|
934
|
+
totalRateLimits,
|
|
935
|
+
totalInputTokens,
|
|
936
|
+
totalOutputTokens,
|
|
937
|
+
totalCacheCreationTokens,
|
|
938
|
+
totalCacheReadTokens,
|
|
939
|
+
totalTokens
|
|
829
940
|
};
|
|
830
941
|
}
|
|
831
942
|
getToolBreakdown() {
|
|
@@ -939,6 +1050,12 @@ var StatsEngine = class {
|
|
|
939
1050
|
const avgToolsPerSession = this.queryScalar(
|
|
940
1051
|
"SELECT COALESCE(AVG(tool_count), 0) as c FROM sessions"
|
|
941
1052
|
);
|
|
1053
|
+
const mostTokensInSession = this.queryScalar(
|
|
1054
|
+
"SELECT COALESCE(MAX(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0) as c FROM sessions"
|
|
1055
|
+
);
|
|
1056
|
+
const avgTokensPerSession = this.queryScalar(
|
|
1057
|
+
"SELECT COALESCE(AVG(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0) as c FROM sessions"
|
|
1058
|
+
);
|
|
942
1059
|
return {
|
|
943
1060
|
longestSessionSeconds,
|
|
944
1061
|
mostToolsInSession,
|
|
@@ -946,7 +1063,9 @@ var StatsEngine = class {
|
|
|
946
1063
|
fastestSessionSeconds,
|
|
947
1064
|
avgDurationSeconds: Math.round(avgDurationSeconds),
|
|
948
1065
|
avgPromptsPerSession: Math.round(avgPromptsPerSession * 100) / 100,
|
|
949
|
-
avgToolsPerSession: Math.round(avgToolsPerSession * 100) / 100
|
|
1066
|
+
avgToolsPerSession: Math.round(avgToolsPerSession * 100) / 100,
|
|
1067
|
+
mostTokensInSession,
|
|
1068
|
+
avgTokensPerSession: Math.round(avgTokensPerSession)
|
|
950
1069
|
};
|
|
951
1070
|
}
|
|
952
1071
|
getProjectStats() {
|
|
@@ -1367,4 +1486,4 @@ export {
|
|
|
1367
1486
|
TIER_NAMES,
|
|
1368
1487
|
AchievementEngine
|
|
1369
1488
|
};
|
|
1370
|
-
//# sourceMappingURL=chunk-
|
|
1489
|
+
//# sourceMappingURL=chunk-OYLQHCOY.js.map
|