@statforge/claudestat 1.2.0 → 1.2.2

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 CHANGED
@@ -12,7 +12,7 @@ Works with Claude Pro, Max 5, and Max 20. Zero cloud dependencies. Pure Node.js.
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
13
13
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
14
14
  [![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-brightgreen)]()
15
- [![Tests](https://img.shields.io/badge/tests-214%2F214-brightgreen)]()
15
+ [![Tests](https://img.shields.io/badge/tests-243%2F243-brightgreen)]()
16
16
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
17
17
 
18
18
  [Installation](#installation) • [Quick Start](#quick-start) • [Commands](#commands) • [Dashboard](#dashboard) • [Contributing](#contributing)
@@ -139,13 +139,14 @@ That's it. Start a Claude Code session and watch the events flow in.
139
139
  | `claudestat uninstall` | Remove hooks from Claude Code |
140
140
  | `claudestat watch` | Live terminal trace view |
141
141
  | `claudestat status` | Show quota, cost, and burn rate |
142
- | `claudestat status --compact` | One-line output for tmux status bar |
143
142
  | `claudestat config` | View or edit configuration |
144
143
  | `claudestat top` | Rank tools by cost, call count, or duration |
145
144
  | `claudestat weekly` | Weekly usage summary with actionable tips |
146
- | `claudestat export [format]` | Export session data to JSON or CSV |
147
- | `claudestat share [session-id]` | Generate shareable session card (ASCII/JSON) |
145
+ | `claudestat insights` | Deep usage insights: cost breakdown, cache savings, efficiency, peak hours, model breakdown |
148
146
  | `claudestat roast` | Sarcastic usage analysis with roast jokes |
147
+ | `claudestat roast --stats` | Raw stats with visual bars |
148
+ | `claudestat version` | Show version and check for npm updates |
149
+ | `claudestat export [format]` | Export session data to JSON or CSV |
149
150
  | `claudestat doctor` | Check installation health and diagnose issues |
150
151
 
151
152
  ### `claudestat watch`
@@ -173,15 +174,32 @@ Ranks your most-used tools by estimated cost, call count, or duration across all
173
174
  ```
174
175
  claudestat top
175
176
 
176
- 🏆 claudestat topby est. cost (last 30 days)
177
-
178
- # Tool Calls Duration Est. Cost %
179
- ── ───────────────── ──────── ───────────── ───────── ────
180
- 1 Bash 1,240 18.3m $1.24 38%
181
- 2 Read 890 4.1m $0.87 27%
182
- 3 Edit 430 2.8m $0.61 19%
183
- 4 Agent (haiku) 120 9.2m $0.38 12%
184
- 5 Write 210 1.1m $0.12 4%
177
+ 🏆 claudestat top by est. cost (last 30 days)
178
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
179
+
180
+ 1 Edit ████████████████░░░░ $146.47 21%
181
+ 2479 calls · 38.5m
182
+ 2 Bash ███████████████░░░░░ $140.66 20%
183
+ 2651 calls · 153.6m
184
+ 3 Read ██████████████░░░░░░ $126.08 18%
185
+ 2315 calls · 34.0m
186
+ 4 Grep ████░░░░░░░░░░░░░░░░ $39.93 6%
187
+ 699 calls · 9.3m
188
+ 5 ToolSearch ██░░░░░░░░░░░░░░░░░░ $21.83 3%
189
+ 469 calls · 7.4m
190
+ 6 Glob ██░░░░░░░░░░░░░░░░░░ $13.96 2%
191
+ 269 calls · 5.7m
192
+ 7 Write █░░░░░░░░░░░░░░░░░░░ $12.93 2%
193
+ 237 calls · 87.1m
194
+ 8 mcp__plugin_engr… █░░░░░░░░░░░░░░░░░░░ $8.10 1%
195
+ 149 calls · 2.6m
196
+ 9 Agent █░░░░░░░░░░░░░░░░░░░ $8.09 1%
197
+ 168 calls · 95.7m
198
+ 10 WebFetch █░░░░░░░░░░░░░░░░░░░ $5.86 1%
199
+ 106 calls · 9.9m
200
+ Other — $184.79 26%
201
+
202
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
185
203
  ```
186
204
 
187
205
  Options: `--by cost|count|duration` · `--days 7|30|90` · `--limit N`
@@ -193,88 +211,177 @@ Weekly usage summary with an actionable tip. Detects patterns like Bash overuse,
193
211
  ```
194
212
  claudestat weekly
195
213
 
196
- 📊 claudestat weekly insight (May 5 May 11)
197
- ──────────────────────────────────────────────
198
- Sessions: 42 · Cost: $146.21 · Loops: 93
199
- Top tool: Bash (21% of cost) · Efficiency: 93/100
200
- ⚡ Tip: Group bash commands to reduce tool calls — each call costs context
214
+ 📊 claudestat weekly May 8 May 13
215
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
216
+
217
+ 💰 $198.38 total · 40 sessions · 114 loops
218
+
219
+ 🔧 Top tool Bash 22% of cost
220
+
221
+ 📈 Efficiency ██████████████████░░ 91/100
222
+
223
+ 💾 Cache hit ████████████████████ 100%
224
+
225
+ 📦 Tokens 73K in + 1.2M out
226
+
227
+ ⚡ Tip: 114 loops detected — consider using /compact earlier to prevent context thrashing
228
+
229
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
201
230
  ```
202
231
 
203
232
  Options: `--json` for machine-readable output.
204
233
 
205
234
  ### `claudestat status`
206
235
 
236
+ Shows your current quota usage with visual progress bars, plan detection, and burn rate.
237
+
207
238
  ```
208
239
  claudestat status
209
240
 
210
- Quota 5h 45/50 prompts (90%) | reset in 22m
211
- Plan MAX5
212
- Weekly 3.5h / 40h (9%) this week
213
- Burn rate 1,240 tok/min
241
+ 📊 claudestat PRO plan
242
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
243
+
244
+ 5h ████████████████████ 100% resets 4:10 AM
245
+
246
+ Week ██████░░░░░░░░░░░░░░ 31% resets May 18
247
+
248
+ 🔥 490 tok/min · 101 prompts used
249
+
250
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
214
251
  ```
215
252
 
216
- ### `claudestat status --compact`
253
+ Options: `--json` for machine-readable output.
217
254
 
218
- One-line output for tmux status bar or scripting. Shows cycle quota and weekly usage with colored emoji.
255
+ ### `claudestat insights`
256
+
257
+ Deep usage insights: cost breakdown by project, cache savings, output/input ratio, efficiency trend, peak activity hours, and model breakdown.
219
258
 
220
- ```bash
221
- claudestat status --compact
222
- C:45%🟡 W:9%🟢 pro
223
259
  ```
260
+ claudestat insights
224
261
 
225
- ### `claudestat share`
262
+ 💡 claudestat insights last 7 days
263
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
226
264
 
227
- Generate a shareable session card — perfect for sharing on social media or in bug reports.
265
+ 💰 $4.96/session · 40 sessions · $198.38 total
228
266
 
229
- ```bash
230
- claudestat share
231
- ╔═══════════════════════════════════╗
232
- Session Report · claudestat
233
- ╠═══════════════════════════════════╣
234
- Project my-project ║
235
- ║ Duration 2h 14m ║
236
- Tools 847 calls ║
237
- ║ Cost $0.84 ║
238
- Cache hit 27% saved ($0.31) ║
239
- ║ Top tool Bash (38%) ║
240
- Efficiency 91 / 100 ║
241
- ╚═══════════════════════════════════╝
242
- github.com/DeibyGS/claudestat
267
+ 🗂 Top projects
268
+ no project █████████░░░░░░░░░░░ $93.69 47%
269
+
270
+ claudestat ████████░░░░░░░░░░░░ $74.60 38%
271
+
272
+ wodrival ███░░░░░░░░░░░░░░░░░ $24.95 13%
273
+
274
+ aprendiendo-in ░░░░░░░░░░░░░░░░░░░░ $3.32 2%
275
+
276
+ other ░░░░░░░░░░░░░░░░░░░░ $1.81 1%
277
+
278
+ ⚡ Cache ~$1029.43 saved · 100% hit rate
279
+
280
+ 📊 16× output/input · cache-heavy workload
281
+
282
+ 📈 Efficiency 91/100 ↓ -2 vs prev period · 114 loops
283
+
284
+ ⏰ Activity by time of day
285
+ 🌙 00:00–05:59 ████████████████████ 18 sessions
286
+
287
+ 🌅 06:00–11:59 ███████░░░░░░░░░░░░░ 6 sessions
288
+
289
+ ☀️ 12:00–17:59 ███░░░░░░░░░░░░░░░░░ 3 sessions
290
+
291
+ 🌆 18:00–23:59 ██████████████░░░░░░ 13 sessions
292
+
293
+ 🤖 Models
294
+ claude-sonnet-4-6 ████████████████████ $197.11 99% · 23 sessions
295
+
296
+ claude-haiku-4-5-20251001 ░░░░░░░░░░░░░░░░░░░░ $1.26 1% · 15 sessions
297
+
298
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
243
299
  ```
244
300
 
245
- Options:
246
- - `--format ascii|json` — output format (default: ascii)
247
- - `--copy` — copy to clipboard automatically (macOS only)
301
+ Options: `--days 7|14|30|90` · `--json` for machine-readable output.
248
302
 
249
- ### `claudestat roast`
303
+ ### `claudestat config`
250
304
 
251
- Get a sarcastic analysis of your Claude Code usage — humor with insights.
305
+ ```
306
+ claudestat config
307
+
308
+ ⚙️ claudestat config
309
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
310
+
311
+ Plan PRO
312
+ Alerts enabled
313
+
314
+ Kill switch OFF
315
+ ████████████████████
316
+
317
+ Cycle thresholds 70%, 85%, 95%
318
+ yellow ████████░░ orange █████████░ red ██████████
319
+
320
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
321
+ ```
252
322
 
253
323
  ```bash
254
- claudestat roast
324
+ # Enable kill switch at 90% quota
325
+ claudestat config --kill-switch true --threshold 90
326
+
327
+ # Force plan detection
328
+ claudestat config --plan max5 # pro | max5 | max20 | auto
255
329
 
256
- === Claude Code Stats (last 30 days) ===
257
- Sessions: 47
258
- Total cost: $12.40
259
- Bash calls: 1,240
260
- Loops: 8
261
- Efficiency: 72/100
330
+ # Toggle daemon rate limit alerts
331
+ claudestat config --alerts false
332
+ ```
262
333
 
263
- 🔥 Your Claude Code Roast
334
+ Config is stored at `~/.claudestat/config.json` (macOS/Linux) or `%USERPROFILE%\.claudestat\config.json` (Windows).
264
335
 
265
- You called Bash 1,240 times last month.
266
- That's once every 2.3 minutes.
267
- Are you okay?
336
+ ### `claudestat roast`
268
337
 
269
- You hit 90%+ context in 12 sessions.
270
- Claude was writing with amnesia half the time.
338
+ Get a sarcastic analysis of your Claude Code usage — humor with insights.
271
339
 
272
- You spent $4.20 on loops you never noticed.
273
- That's 14 coffees. Just saying.
340
+ ```bash
341
+ claudestat roast
274
342
 
275
- Efficiency: 72/100 room for growth, champ.
343
+ 🔥 Your Claude Code Roast (30 days)
344
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
345
+
346
+ Score ██████████████████░░ 92/100 ★★★★★
347
+
348
+ Scorecard
349
+ ┌─────────────────┬──────────────┬──────────────┐
350
+ │ Metric │ Value │ Rating │
351
+ ├─────────────────┼──────────────┼──────────────┤
352
+ │ Sessions │ 47 │ normal │
353
+ │ Total cost │ $12.40 │ frugal │
354
+ │ Avg/session │ $0.26/session│ efficient │
355
+ │ Bash calls │ 1240 │ 🔨 overload │
356
+ │ Loops │ 8 │ clean │
357
+ │ Efficiency │ 92/100 │ 🏆 elite │
358
+ │ Tokens │ 4.2M │ — │
359
+ │ Top tool │ Bash 38% │ — │
360
+ └─────────────────┴──────────────┴──────────────┘
361
+
362
+ Roast Cards
363
+
364
+ ┌──────────────────────────────────────────────────┐
365
+ │ 🖥️ BASH OVERLOAD │
366
+ │ 1240 calls in 30d — once every 2.3 min │
367
+ │ Are you okay? │
368
+ └──────────────────────────────────────────────────┘
369
+
370
+ ┌──────────────────────────────────────────────────┐
371
+ │ 🔄 LOOP MONEY PIT │
372
+ │ $4.20 wasted on loops — that's 14 coffees │
373
+ │ Just saying. │
374
+ └──────────────────────────────────────────────────┘
375
+
376
+ Verdict
377
+ You're a machine. Or maybe you're just not using Claude enough.
378
+
379
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
380
+ github.com/DeibyGS/claudestat
276
381
  ```
277
382
 
383
+ Options: `--stats` for raw stats with visual bars · `--months N` to look back N months.
384
+
278
385
  ### `claudestat doctor`
279
386
 
280
387
  Diagnoses common installation problems — useful if `claudestat start` fails or hooks are not firing.
@@ -291,6 +398,9 @@ claudestat doctor
291
398
  ✓ Hook script deployed (~/.claudestat/hooks/event.js)
292
399
  ✓ Daemon running (localhost:7337)
293
400
  ✓ Global CLI symlink valid
401
+ ✓ No duplicate claudestat binaries in PATH
402
+ ✓ Version match (installed: v1.2.2)
403
+ ✓ NVM prefix matches active binary
294
404
  ──────────────────────────────────────────────
295
405
  All checks passed — claudestat is healthy!
296
406
  ```
@@ -299,6 +409,19 @@ If a check fails, `doctor` prints the exact fix command to run.
299
409
 
300
410
  ---
301
411
 
412
+ ### `claudestat version`
413
+
414
+ Shows the current version and checks npm for updates.
415
+
416
+ ```bash
417
+ claudestat version
418
+
419
+ 1.2.2
420
+ latest ✓
421
+ ```
422
+
423
+ If a newer version is available, it shows: `latest: 1.3.0 — run npm update`.
424
+
302
425
  ### `claudestat export`
303
426
 
304
427
  Export session data to JSON or CSV. Supports date and project filters.
@@ -326,23 +449,6 @@ Each row includes: `id`, `started_at`, `cwd`, `project_path`, `total_cost_usd`,
326
449
 
327
450
  ---
328
451
 
329
- ### `claudestat config`
330
-
331
- ```bash
332
- # Enable kill switch — block new sessions when quota exceeds 95%
333
- claudestat config --kill-switch true --threshold 95
334
-
335
- # Force plan detection instead of auto
336
- claudestat config --plan max5 # pro | max5 | max20 | auto
337
-
338
- # Disable daemon rate limit alerts
339
- claudestat config --alerts false
340
- ```
341
-
342
- Config is stored at `~/.claudestat/config.json` (macOS/Linux) or `%USERPROFILE%\.claudestat\config.json` (Windows).
343
-
344
- ---
345
-
346
452
  ## MCP Server
347
453
 
348
454
  claudestat includes an MCP (Model Context Protocol) server that lets Claude Code query its own usage stats — Claude can tell you its quota, session cost, and top tools in real time.
@@ -351,10 +457,12 @@ claudestat includes an MCP (Model Context Protocol) server that lets Claude Code
351
457
 
352
458
  | Tool | Description |
353
459
  |------|------------|
354
- | `get_quota_status` | 5h cycle usage %, plan, weekly hours, burn rate |
460
+ | `get_quota_status` | 5h cycle usage %, plan, weekly hours, burn rate (with on-demand API refresh + disk cache) |
355
461
  | `get_current_session` | Latest session: cost, tokens, efficiency, loops |
356
462
  | `get_session_stats` | Aggregated stats for N days |
357
- | `get_top_tools` | Top 10 tools by cost/count/duration |
463
+ | `get_top_tools` | Top 10 tools by cost/count/duration (default 30 days) |
464
+ | `get_usage_insights` | Deep insights: cost per project, cache savings, efficiency trend, peak hours, model breakdown |
465
+ | `get_model_breakdown` | Cost and session count broken down by Claude model (Sonnet, Haiku, Opus) |
358
466
  | `get_weekly_insight` | Weekly summary with actionable tip |
359
467
 
360
468
  ### Register with Claude Code
@@ -367,8 +475,10 @@ Once registered, ask Claude things like:
367
475
  - *"What's my current quota status?"*
368
476
  - *"Show me my latest session cost"*
369
477
  - *"What are my top 5 tools by cost this week?"*
478
+ - *"Give me usage insights for the last 14 days"*
479
+ - *"Break down my usage by model"*
370
480
 
371
- Zero extra dependencies — stdio JSON-RPC, works without the daemon running.
481
+ Zero extra dependencies — stdio JSON-RPC, works without the daemon running. Uses on-demand API refresh with shared disk cache for accurate quota data.
372
482
 
373
483
  ---
374
484
 
@@ -525,7 +635,7 @@ Whether you want to fix a bug, improve a dashboard view, add a new pattern to th
525
635
  1. Fork the repository
526
636
  2. Create a branch: `git checkout -b feat/your-feature`
527
637
  3. Make your changes
528
- 4. Run the test suite: `npm test` (208 tests)
638
+ 4. Run the test suite: `node --require tsx/cjs tests/index.ts` (243 tests)
529
639
  5. Open a PR with a clear description of what you changed and why
530
640
 
531
641
  ### Good first areas
@@ -542,11 +652,13 @@ git clone https://github.com/YOUR_USERNAME/claudestat
542
652
  cd claudestat
543
653
  npm install
544
654
  npm run dev:full # starts daemon + dashboard hot-reload together
545
- npm test # run all tests
655
+ node --require tsx/cjs tests/index.ts # run all tests
546
656
  ```
547
657
 
548
658
  See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines.
549
659
 
660
+ This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).
661
+
550
662
  ---
551
663
 
552
664
  ## Contributors
@@ -18,7 +18,14 @@ export interface ClaudeAuthInfo {
18
18
  expiresAt: number;
19
19
  tokenValid: boolean;
20
20
  source: 'keychain' | 'file' | 'unknown';
21
+ accessToken?: string;
21
22
  }
23
+ /**
24
+ * Devuelve el accessToken OAuth para autenticar llamadas a la API de Anthropic.
25
+ * Usa el mismo caché de 5 minutos que readClaudeAuth().
26
+ * Retorna null si no hay credenciales disponibles (Linux sin configurar, token expirado).
27
+ */
28
+ export declare function getOAuthAccessToken(): string | null;
22
29
  /**
23
30
  * Lee las credenciales de autenticación de Claude Code.
24
31
  * Intenta: keychain (macOS) → archivo → unknown.
@@ -17,6 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.getOAuthAccessToken = getOAuthAccessToken;
20
21
  exports.readClaudeAuth = readClaudeAuth;
21
22
  exports.subscriptionTypeToPlan = subscriptionTypeToPlan;
22
23
  const child_process_1 = require("child_process");
@@ -50,6 +51,7 @@ function readFromKeychain() {
50
51
  expiresAt: oa.expiresAt ?? 0,
51
52
  tokenValid: Date.now() < (oa.expiresAt ?? 0),
52
53
  source: 'keychain',
54
+ accessToken: oa.accessToken,
53
55
  };
54
56
  }
55
57
  catch {
@@ -82,6 +84,7 @@ function readFromFile() {
82
84
  expiresAt: oa.expiresAt ?? 0,
83
85
  tokenValid: Date.now() < (oa.expiresAt ?? 0),
84
86
  source: 'file',
87
+ accessToken: oa.accessToken,
85
88
  };
86
89
  }
87
90
  catch {
@@ -90,6 +93,15 @@ function readFromFile() {
90
93
  }
91
94
  return null;
92
95
  }
96
+ // ─── Token OAuth para llamadas a la API ───────────────────────────────────────
97
+ /**
98
+ * Devuelve el accessToken OAuth para autenticar llamadas a la API de Anthropic.
99
+ * Usa el mismo caché de 5 minutos que readClaudeAuth().
100
+ * Retorna null si no hay credenciales disponibles (Linux sin configurar, token expirado).
101
+ */
102
+ function getOAuthAccessToken() {
103
+ return readClaudeAuth().accessToken ?? null;
104
+ }
93
105
  // ─── API pública ──────────────────────────────────────────────────────────────
94
106
  /**
95
107
  * Lee las credenciales de autenticación de Claude Code.
package/dist/config.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface ClaudestatConfig {
23
23
  killSwitchEnabled: boolean;
24
24
  killSwitchThreshold: number;
25
25
  warnThresholds: number[];
26
+ weeklyWarnThresholds: number[];
27
+ resetReminderMins: number;
26
28
  plan: ClaudePlan | null;
27
29
  reportsEnabled: boolean;
28
30
  reportFrequency: ReportFrequency;
package/dist/config.js CHANGED
@@ -34,6 +34,8 @@ const DEFAULTS = {
34
34
  killSwitchEnabled: false,
35
35
  killSwitchThreshold: 95,
36
36
  warnThresholds: [70, 85, 95],
37
+ weeklyWarnThresholds: [50, 75, 90],
38
+ resetReminderMins: 10,
37
39
  plan: null,
38
40
  reportsEnabled: false,
39
41
  reportFrequency: 'weekly',
@@ -80,6 +82,16 @@ function validateConfig(raw) {
80
82
  }
81
83
  if ('plan' in cfg && !VALID_PLANS.has(cfg.plan))
82
84
  return `plan debe ser uno de: free, pro, max5, max20 o null`;
85
+ if ('weeklyWarnThresholds' in cfg) {
86
+ const v = cfg.weeklyWarnThresholds;
87
+ if (!Array.isArray(v) || v.length !== 3 || v.some(n => typeof n !== 'number' || isNaN(n) || n < 1 || n > 100))
88
+ return 'weeklyWarnThresholds must be an array of 3 numbers between 1 and 100';
89
+ }
90
+ if ('resetReminderMins' in cfg) {
91
+ const v = cfg.resetReminderMins;
92
+ if (typeof v !== 'number' || isNaN(v) || v < 0 || v > 60)
93
+ return 'resetReminderMins must be a number between 0 and 60';
94
+ }
83
95
  if ('alertsEnabled' in cfg && typeof cfg.alertsEnabled !== 'boolean')
84
96
  return 'alertsEnabled debe ser boolean';
85
97
  if ('reportsEnabled' in cfg && typeof cfg.reportsEnabled !== 'boolean')
package/dist/daemon.js CHANGED
@@ -139,7 +139,7 @@ async function migrateSessionSummaries(limit = 5) {
139
139
  const summary = await (0, summarizer_1.summarizeSession)(events, s.total_cost_usd ?? 0, projectName);
140
140
  if (summary) {
141
141
  db_1.dbOps.updateSessionSummary(s.id, summary);
142
- console.log(`[daemon] Summary generado para sesión ${s.id.slice(0, 8)}: "${summary}"`);
142
+ console.log(`[daemon] Summary generated for session ${s.id.slice(0, 8)}: "${summary}"`);
143
143
  }
144
144
  }
145
145
  catch (err) {
@@ -175,7 +175,20 @@ const LEVEL_COLOR = {
175
175
  orange: '\x1b[33m',
176
176
  red: '\x1b[31m',
177
177
  };
178
- let _lastAlertLevel = null;
178
+ let _lastCycleAlertLevel = null;
179
+ let _lastWeeklyAlertLevel = null;
180
+ let _resetReminderFired = false;
181
+ function checkAlertLevel(level, lastLevel, logMsg, notifTitle, notifBody) {
182
+ if (!level)
183
+ return null;
184
+ const prevRank = lastLevel ? LEVEL_RANK[lastLevel] ?? 0 : 0;
185
+ const currRank = LEVEL_RANK[level];
186
+ if (currRank > prevRank) {
187
+ process.stderr.write(`${LEVEL_COLOR[level]}${logMsg}\x1b[0m\n`);
188
+ (0, notifier_1.sendDesktopNotification)(notifTitle, notifBody);
189
+ }
190
+ return currRank > prevRank ? level : lastLevel;
191
+ }
179
192
  function startAlertPolling() {
180
193
  alertInterval = setInterval(() => {
181
194
  try {
@@ -183,20 +196,23 @@ function startAlertPolling() {
183
196
  if (!cfg.alertsEnabled)
184
197
  return;
185
198
  const data = (0, quota_tracker_1.computeQuota)(cfg.plan ?? undefined);
186
- const level = (0, config_1.getWarnLevel)(data.cyclePct, cfg.warnThresholds);
187
- if (!level) {
188
- _lastAlertLevel = null;
189
- return;
190
- }
191
- const prevRank = _lastAlertLevel ? LEVEL_RANK[_lastAlertLevel] ?? 0 : 0;
192
- const currRank = LEVEL_RANK[level] ?? 0;
193
- if (currRank > prevRank) {
194
- const color = LEVEL_COLOR[level];
195
- process.stderr.write(`${color}[claudestat] ⚠️ Rate limit alert: ${data.cyclePct}% of quota used (${data.cyclePrompts}/${data.cycleLimit} prompts)\x1b[0m\n`);
196
- if (data.cyclePct >= cfg.killSwitchThreshold) {
197
- (0, notifier_1.sendDesktopNotification)('claudestat Rate limit warning', `${data.cyclePct}% of 5h quota used (${data.cyclePrompts}/${data.cycleLimit} prompts)`);
199
+ const resetMins = Math.ceil(data.cycleResetMs / 60000);
200
+ // ── Cycle 5h alerts ──────────────────────────────────────────────────────
201
+ _lastCycleAlertLevel = checkAlertLevel((0, config_1.getWarnLevel)(data.cyclePct, cfg.warnThresholds), _lastCycleAlertLevel, `[claudestat] ⚠️ 5h cycle at ${data.cyclePct}% (${data.cyclePrompts}/${data.cycleLimit} prompts)`, 'claudestat — 5h cycle alert', `${data.cyclePct}% of cycle used · resets in ${resetMins}m`);
202
+ // ── Weekly alerts ────────────────────────────────────────────────────────
203
+ _lastWeeklyAlertLevel = checkAlertLevel((0, config_1.getWarnLevel)(data.weeklyPctAll, cfg.weeklyWarnThresholds), _lastWeeklyAlertLevel, `[claudestat] ⚠️ Weekly usage at ${data.weeklyPctAll}%`, 'claudestat — Weekly usage alert', `${data.weeklyPctAll}% of weekly quota used`);
204
+ // ── Reset reminder ───────────────────────────────────────────────────────
205
+ const reminderMs = (cfg.resetReminderMins ?? 10) * 60000;
206
+ if (reminderMs > 0) {
207
+ if (data.cycleResetMs > reminderMs * 1.5) {
208
+ _resetReminderFired = false; // cycle reset happened arm reminder again
209
+ }
210
+ else if (data.cycleResetMs <= reminderMs && data.cycleResetMs > 0 && !_resetReminderFired) {
211
+ const mins = Math.ceil(data.cycleResetMs / 60000);
212
+ process.stderr.write(`\x1b[36m[claudestat] ⏰ Quota resets in ${mins}m — good time to wrap up\x1b[0m\n`);
213
+ (0, notifier_1.sendDesktopNotification)('claudestat — Quota reset soon', `Your 5h cycle resets in ${mins} min — good time to start a new task`);
214
+ _resetReminderFired = true;
198
215
  }
199
- _lastAlertLevel = level;
200
216
  }
201
217
  }
202
218
  catch {
@@ -281,6 +297,7 @@ function startDaemon() {
281
297
  }
282
298
  // Polling de alertas de rate limit cada 60s
283
299
  startAlertPolling();
300
+ // API quota data is refreshed on-demand by the CLI status command (disk cache shared)
284
301
  });
285
302
  // Manejo de error de puerto ocupado — fuera del callback para capturar EADDRINUSE
286
303
  _server.on('error', (err) => {
package/dist/db.d.ts CHANGED
@@ -144,4 +144,22 @@ export declare const dbOps: {
144
144
  earliest: number;
145
145
  latest: number;
146
146
  };
147
+ getProjectCosts(days?: number): {
148
+ project: string;
149
+ session_count: number;
150
+ total_cost: number;
151
+ }[];
152
+ getHourlyDistribution(days?: number): {
153
+ hour: number;
154
+ session_count: number;
155
+ }[];
156
+ getCacheReadByModel(days: number): {
157
+ model: string;
158
+ cache_read: number;
159
+ }[];
160
+ getModelBreakdown(days: number): {
161
+ model: string;
162
+ total_cost: number;
163
+ session_count: number;
164
+ }[];
147
165
  };
package/dist/db.js CHANGED
@@ -416,6 +416,26 @@ const stmts = {
416
416
  MAX(last_event_at) AS latest
417
417
  FROM sessions
418
418
  WHERE started_at >= ?
419
+ `),
420
+ getProjectCosts: db.prepare(`
421
+ SELECT
422
+ COALESCE(project_path, 'no project') AS project,
423
+ COUNT(*) AS session_count,
424
+ COALESCE(SUM(total_cost_usd), 0) AS total_cost
425
+ FROM sessions
426
+ WHERE started_at >= ? AND total_cost_usd > 0
427
+ GROUP BY project_path
428
+ ORDER BY total_cost DESC
429
+ LIMIT 5
430
+ `),
431
+ getHourlyDistribution: db.prepare(`
432
+ SELECT
433
+ CAST(strftime('%H', datetime(started_at/1000, 'unixepoch', 'localtime')) AS INTEGER) AS hour,
434
+ COUNT(*) AS session_count
435
+ FROM sessions
436
+ WHERE started_at >= ?
437
+ GROUP BY hour
438
+ ORDER BY hour ASC
419
439
  `),
420
440
  getUnattributedCost: db.prepare(`
421
441
  WITH period_cost AS (
@@ -582,4 +602,34 @@ exports.dbOps = {
582
602
  const since = Date.now() - days * 86400000;
583
603
  return stmts.getCostProjection.get(since);
584
604
  },
605
+ getProjectCosts(days = 7) {
606
+ const since = Date.now() - days * 86400000;
607
+ return stmts.getProjectCosts.all(since);
608
+ },
609
+ getHourlyDistribution(days = 7) {
610
+ const since = Date.now() - days * 86400000;
611
+ return stmts.getHourlyDistribution.all(since);
612
+ },
613
+ getCacheReadByModel(days) {
614
+ const since = Date.now() - days * 86400000;
615
+ return db.prepare(`
616
+ SELECT COALESCE(dominant_model, 'unknown') as model, SUM(total_cache_read) as cache_read
617
+ FROM sessions
618
+ WHERE started_at >= ?
619
+ GROUP BY dominant_model
620
+ `).all(since);
621
+ },
622
+ getModelBreakdown(days) {
623
+ const since = Date.now() - days * 86400000;
624
+ return db.prepare(`
625
+ SELECT
626
+ COALESCE(dominant_model, 'unknown') as model,
627
+ SUM(total_cost_usd) as total_cost,
628
+ COUNT(*) as session_count
629
+ FROM sessions
630
+ WHERE started_at >= ?
631
+ GROUP BY dominant_model
632
+ ORDER BY total_cost DESC
633
+ `).all(since);
634
+ },
585
635
  };
package/dist/doctor.js CHANGED
@@ -183,6 +183,7 @@ async function runDoctor() {
183
183
  const failed = checks.filter(c => !c.ok).length;
184
184
  if (failed === 0) {
185
185
  console.log(' \x1b[32mAll checks passed — claudestat is healthy!\x1b[0m\n');
186
+ process.exit(0);
186
187
  }
187
188
  else {
188
189
  console.log(` \x1b[31m${failed} check(s) failed — see fixes above\x1b[0m\n`);