@statforge/claudestat 1.2.3 → 1.3.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 CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  # claudestat
4
4
 
5
- **Real-time execution trace and cost intelligence for Claude Code**
5
+ **Live Claude Code monitor — real-time trace, quota guard, and MCP server**
6
+
7
+ Most tools read your logs after a session ends. claudestat hooks into every event as it fires.
8
+ See what Claude is spending right now, get alerted before you hit your quota, and ask Claude about its own usage — from inside the terminal.
6
9
 
7
- Hook into every tool call, token, and dollar — as it happens.
8
10
  Works with Claude Pro, Max 5, and Max 20. Zero cloud dependencies. Pure Node.js. Runs on macOS, Linux, and Windows.
9
11
 
10
12
  [![npm version](https://img.shields.io/npm/v/@statforge/claudestat?color=blue)](https://www.npmjs.com/package/@statforge/claudestat)
@@ -17,7 +19,7 @@ Works with Claude Pro, Max 5, and Max 20. Zero cloud dependencies. Pure Node.js.
17
19
 
18
20
  [Installation](#installation) • [Quick Start](#quick-start) • [Commands](#commands) • [Dashboard](#dashboard) • [Contributing](#contributing)
19
21
 
20
- ![ClaudeStat dashboard](https://res.cloudinary.com/dgscloudinary/image/upload/v1778225605/My%20portfolio%7D/ClaudeStat_vnfmup.png)
22
+ ![ClaudeStat banner](https://raw.githubusercontent.com/DeibyGS/claudestat/main/assets/banner.png)
21
23
 
22
24
  ---
23
25
 
@@ -31,26 +33,57 @@ Works with Claude Pro, Max 5, and Max 20. Zero cloud dependencies. Pure Node.js.
31
33
 
32
34
  ---
33
35
 
34
- ## Why?
36
+ ## Why claudestat?
37
+
38
+ Tools like ccusage are great for reviewing history. claudestat is for while you're coding.
35
39
 
36
- You're burning tokens right now and you have no idea how many, on what, or whether Claude is stuck in a loop.
40
+ It taps into Claude Code's hook system to capture every event the moment it fires, stores everything locally in SQLite, and gives you a live dashboard, quota alerts, and an MCP server not just a report.
37
41
 
38
- Claude Code is powerful but it's a black box while it runs. You can't see what it's spending, how deep the context is, whether it's looping, or if you're about to hit your quota limit.
42
+ | | claudestat | ccusage |
43
+ |---|:---:|:---:|
44
+ | Real-time event stream | ✅ | ❌ |
45
+ | Live terminal trace (`watch`) | ✅ | ❌ |
46
+ | Web dashboard | ✅ | ❌ |
47
+ | Quota alerts + kill switch | ✅ | ❌ |
48
+ | Loop detector | ✅ | ❌ |
49
+ | MCP server (ask Claude about itself) | ✅ | ❌ |
50
+ | Historical usage analysis | ✅ | ✅ |
39
51
 
40
- **claudestat fixes that.** It taps into Claude Code's hook system to capture every event, stores it locally in SQLite, and shows you everything in a live dashboard or terminal trace.
52
+ **What you get:**
41
53
 
42
- - Live tool trace with duration and token cost per call
43
- - Quota guard with configurable kill switch (block new sessions at X%)
44
- - Pattern analyzer: detects loops, Bash overuse, low cache reuse, and more
45
- - Per-session cost breakdown + cache savings + burn rate
46
- - Weekly usage insights with actionable tips
47
- - AI-generated weekly usage reports
48
- - MCP server: query quota, sessions, and tools from within Claude Code
54
+ - Live tool trace — every call with duration and token cost as it runs
55
+ - Quota guard alerts at 70%, 85%, 95%; optional kill switch blocks new sessions at X%
56
+ - Loop detector flags context thrashing with estimated waste cost
57
+ - Top tools know which tools eat most of your budget
58
+ - Web dashboard session history, analytics, model breakdown, charts
59
+ - MCP server — 7 tools so Claude can answer questions about its own usage
60
+ - Weekly insights pattern analysis with actionable tips
49
61
 
50
62
  > If claudestat is useful, give it a ⭐ — it helps other developers find it.
51
63
 
52
64
  ---
53
65
 
66
+ ## Ask Claude about itself
67
+
68
+ claudestat ships an MCP server. Once registered, you can ask Claude Code questions about its own usage — without leaving the terminal.
69
+
70
+ ```bash
71
+ claude mcp add claudestat -s user -- claudestat-mcp
72
+ ```
73
+
74
+ Then just ask:
75
+
76
+ ```
77
+ > What's my current quota status?
78
+ > How much did I spend this week?
79
+ > What are my top 5 tools by cost?
80
+ > Break down my usage by model
81
+ ```
82
+
83
+ Claude reads your local SQLite data through the MCP server and answers in real time. No cloud, no API key, no extra setup. [Full MCP reference →](#mcp-server)
84
+
85
+ ---
86
+
54
87
  ## How it works
55
88
 
56
89
  ```
@@ -478,6 +511,8 @@ Once registered, ask Claude things like:
478
511
  - *"Give me usage insights for the last 14 days"*
479
512
  - *"Break down my usage by model"*
480
513
 
514
+ ![claudestat MCP demo](https://raw.githubusercontent.com/DeibyGS/claudestat/main/assets/mcp-demo.gif)
515
+
481
516
  Zero extra dependencies — stdio JSON-RPC, works without the daemon running. Uses on-demand API refresh with shared disk cache for accurate quota data.
482
517
 
483
518
  ---
@@ -673,9 +708,8 @@ Want to appear here? Pick a [good-first-issue](https://github.com/DeibyGS/claude
673
708
 
674
709
  ## FAQ
675
710
 
676
- **What is claudestat?**
677
- claudestat is a real-time token monitoring and cost analytics tool for Claude Code.
678
- It captures every tool call, token usage, and API cost as it happens — locally, with zero cloud dependencies.
711
+ **What is claudestat? How is it different from ccusage?**
712
+ claudestat is a real-time monitor for Claude Code — not a log reader. It hooks into every tool call as it fires, tracks token usage and cost live, guards your quota with configurable alerts, and exposes an MCP server so Claude can answer questions about its own usage. ccusage reads JSONL history after sessions end; claudestat runs while you code.
679
713
 
680
714
  **How do I monitor Claude Code token usage?**
681
715
  Install with `npm install -g @statforge/claudestat`, run `claudestat start`, and open `http://localhost:7337` for the live dashboard.
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ const daemon_1 = require("./daemon");
24
24
  const watchdog_1 = require("./watchdog");
25
25
  const watch_1 = require("./watch");
26
26
  const install_1 = require("./install");
27
+ const service_1 = require("./service");
27
28
  const export_1 = require("./export");
28
29
  const config_1 = require("./config");
29
30
  const doctor_1 = require("./doctor");
@@ -186,14 +187,34 @@ program
186
187
  console.error('\n❌ Error:', err.message);
187
188
  process.exit(1);
188
189
  }));
190
+ program
191
+ .command('setup')
192
+ .description('One-command setup: install hooks + register daemon as system service (auto-starts on login)')
193
+ .option('--uninstall', 'Remove hooks and system service')
194
+ .action(async (opts) => {
195
+ if (opts.uninstall) {
196
+ console.log('Uninstalling claudestat...');
197
+ (0, service_1.uninstallService)();
198
+ (0, install_1.uninstallHooks)();
199
+ await stopDaemon().catch(() => { });
200
+ console.log('✅ claudestat fully removed');
201
+ process.exit(0);
202
+ }
203
+ console.log('Setting up claudestat...');
204
+ (0, install_1.installHooks)();
205
+ (0, service_1.installService)();
206
+ console.log('✅ claudestat is running and will start automatically on login');
207
+ console.log(' Dashboard → http://localhost:7337');
208
+ process.exit(0);
209
+ });
189
210
  program
190
211
  .command('install')
191
212
  .description('Install hooks into Claude Code settings')
192
- .action(install_1.runInstall);
213
+ .action(async () => { await (0, install_1.runInstall)(); process.exit(0); });
193
214
  program
194
215
  .command('uninstall')
195
216
  .description('Remove hooks from Claude Code')
196
- .action(install_1.uninstallHooks);
217
+ .action(() => { (0, install_1.uninstallHooks)(); process.exit(0); });
197
218
  program
198
219
  .command('export [format]')
199
220
  .description('Export session data (json | csv, default: json). Max 500 sessions.')
@@ -40,6 +40,9 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  return result;
41
41
  };
42
42
  })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
43
46
  Object.defineProperty(exports, "__esModule", { value: true });
44
47
  process.on('warning', (w) => {
45
48
  if (w.name === 'ExperimentalWarning' && w.message.includes('SQLite'))
@@ -47,10 +50,27 @@ process.on('warning', (w) => {
47
50
  process.stderr.write(`${w.name}: ${w.message}\n`);
48
51
  });
49
52
  const readline = __importStar(require("readline"));
53
+ const fs_1 = __importDefault(require("fs"));
50
54
  const db_1 = require("./db");
51
55
  const quota_tracker_1 = require("./quota-tracker");
52
56
  const insights_1 = require("./insights");
53
57
  const config_1 = require("./config");
58
+ const paths_1 = require("./paths");
59
+ function isDaemonRunning() {
60
+ try {
61
+ const pid = parseInt(fs_1.default.readFileSync((0, paths_1.getPidFile)(), 'utf8').trim(), 10);
62
+ process.kill(pid, 0);
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ const DAEMON_WARNING = `⚠️ claudestat daemon is not running — real-time monitoring is disabled.
70
+ Start it with: claudestat start
71
+
72
+ Data shown below is from the last recorded session.
73
+ ---`;
54
74
  const SERVER_NAME = 'claudestat';
55
75
  const SERVER_VERSION = '1.2.2';
56
76
  const PROTOCOL_VERSION = '2025-03-26';
@@ -364,17 +384,18 @@ function toolGetWeeklyInsight(days) {
364
384
  ].join('\n');
365
385
  }
366
386
  async function handleToolCall(name, args) {
387
+ const warning = isDaemonRunning() ? '' : DAEMON_WARNING + '\n';
367
388
  const sortBy = typeof args.sort_by === 'string' ? args.sort_by : 'cost';
368
389
  switch (name) {
369
390
  case 'get_quota_status':
370
391
  await (0, quota_tracker_1.refreshFromApi)();
371
- return toolGetQuotaStatus();
372
- case 'get_current_session': return toolGetCurrentSession();
373
- case 'get_session_stats': return toolGetSessionStats(typeof args.days === 'number' ? args.days : 7);
374
- case 'get_top_tools': return toolGetTopTools(typeof args.days === 'number' ? args.days : 30, sortBy);
375
- case 'get_usage_insights': return toolGetUsageInsights(typeof args.days === 'number' ? args.days : 7);
376
- case 'get_model_breakdown': return toolGetModelBreakdown(typeof args.days === 'number' ? args.days : 7);
377
- case 'get_weekly_insight': return toolGetWeeklyInsight(typeof args.days === 'number' ? args.days : 7);
392
+ return warning + toolGetQuotaStatus();
393
+ case 'get_current_session': return warning + toolGetCurrentSession();
394
+ case 'get_session_stats': return warning + toolGetSessionStats(typeof args.days === 'number' ? args.days : 7);
395
+ case 'get_top_tools': return warning + toolGetTopTools(typeof args.days === 'number' ? args.days : 30, sortBy);
396
+ case 'get_usage_insights': return warning + toolGetUsageInsights(typeof args.days === 'number' ? args.days : 7);
397
+ case 'get_model_breakdown': return warning + toolGetModelBreakdown(typeof args.days === 'number' ? args.days : 7);
398
+ case 'get_weekly_insight': return warning + toolGetWeeklyInsight(typeof args.days === 'number' ? args.days : 7);
378
399
  default: return `Unknown tool: ${name}`;
379
400
  }
380
401
  }
@@ -0,0 +1,2 @@
1
+ export declare function installService(): void;
2
+ export declare function uninstallService(): void;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installService = installService;
7
+ exports.uninstallService = uninstallService;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const child_process_1 = require("child_process");
11
+ const PLIST_LABEL = 'com.statforge.claudestat';
12
+ const PLIST_PATH = path_1.default.join(process.env.HOME ?? '~', 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);
13
+ const SYSTEMD_DIR = path_1.default.join(process.env.HOME ?? '~', '.config', 'systemd', 'user');
14
+ const SYSTEMD_PATH = path_1.default.join(SYSTEMD_DIR, 'claudestat.service');
15
+ function makePlist() {
16
+ const node = process.execPath;
17
+ const script = process.argv[1];
18
+ return `<?xml version="1.0" encoding="UTF-8"?>
19
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
20
+ <plist version="1.0">
21
+ <dict>
22
+ <key>Label</key>
23
+ <string>${PLIST_LABEL}</string>
24
+ <key>ProgramArguments</key>
25
+ <array>
26
+ <string>${node}</string>
27
+ <string>${script}</string>
28
+ <string>start</string>
29
+ </array>
30
+ <key>EnvironmentVariables</key>
31
+ <dict>
32
+ <key>CLAUDESTAT_DAEMON</key>
33
+ <string>1</string>
34
+ </dict>
35
+ <key>StandardOutPath</key>
36
+ <string>/tmp/claudestat-daemon.log</string>
37
+ <key>StandardErrorPath</key>
38
+ <string>/tmp/claudestat-daemon.err</string>
39
+ </dict>
40
+ </plist>`;
41
+ }
42
+ function makeUnit() {
43
+ const node = process.execPath;
44
+ const script = process.argv[1];
45
+ return `[Unit]
46
+ Description=ClaudeStat daemon — real-time Claude Code monitor
47
+ After=default.target
48
+
49
+ [Service]
50
+ Type=simple
51
+ ExecStart=${node} ${script} start
52
+ Restart=on-failure
53
+ RestartSec=5
54
+ Environment=CLAUDESTAT_DAEMON=1
55
+
56
+ [Install]
57
+ WantedBy=default.target`;
58
+ }
59
+ function installService() {
60
+ if (process.platform === 'darwin') {
61
+ fs_1.default.mkdirSync(path_1.default.dirname(PLIST_PATH), { recursive: true });
62
+ try {
63
+ (0, child_process_1.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
64
+ }
65
+ catch { }
66
+ fs_1.default.writeFileSync(PLIST_PATH, makePlist());
67
+ (0, child_process_1.execSync)(`launchctl load "${PLIST_PATH}"`);
68
+ console.log(` service → ${PLIST_PATH}`);
69
+ console.log(` node → ${process.execPath}`);
70
+ }
71
+ else if (process.platform === 'linux') {
72
+ const hasSystemd = (() => {
73
+ try {
74
+ (0, child_process_1.execSync)('which systemctl', { stdio: 'pipe' });
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ })();
81
+ if (!hasSystemd) {
82
+ console.log(' systemd not found — run `claudestat start` manually to start the daemon');
83
+ return;
84
+ }
85
+ fs_1.default.mkdirSync(SYSTEMD_DIR, { recursive: true });
86
+ fs_1.default.writeFileSync(SYSTEMD_PATH, makeUnit());
87
+ (0, child_process_1.execSync)('systemctl --user daemon-reload');
88
+ (0, child_process_1.execSync)('systemctl --user enable --now claudestat');
89
+ console.log(` service → ${SYSTEMD_PATH}`);
90
+ console.log(` node → ${process.execPath}`);
91
+ }
92
+ else {
93
+ console.log(' Auto-start on Windows coming soon. Run `claudestat start` manually.');
94
+ }
95
+ }
96
+ function uninstallService() {
97
+ if (process.platform === 'darwin') {
98
+ try {
99
+ (0, child_process_1.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
100
+ }
101
+ catch { }
102
+ try {
103
+ fs_1.default.unlinkSync(PLIST_PATH);
104
+ console.log(` removed → ${PLIST_PATH}`);
105
+ }
106
+ catch {
107
+ console.log(' service file not found (already removed)');
108
+ }
109
+ }
110
+ else if (process.platform === 'linux') {
111
+ try {
112
+ (0, child_process_1.execSync)('systemctl --user disable --now claudestat 2>/dev/null', { stdio: 'ignore' });
113
+ }
114
+ catch { }
115
+ try {
116
+ fs_1.default.unlinkSync(SYSTEMD_PATH);
117
+ (0, child_process_1.execSync)('systemctl --user daemon-reload');
118
+ console.log(` removed → ${SYSTEMD_PATH}`);
119
+ }
120
+ catch {
121
+ console.log(' service file not found (already removed)');
122
+ }
123
+ }
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statforge/claudestat",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Observability layer for Claude Code — live token tracking, cost analytics, quota guard, loop detection, and usage dashboard. The htop for Claude Code.",
5
5
  "keywords": [
6
6
  "claude-code",