@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 +51 -17
- package/dist/index.js +23 -2
- package/dist/mcp-server.js +28 -7
- package/dist/service.d.ts +2 -0
- package/dist/service.js +124 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# claudestat
|
|
4
4
|
|
|
5
|
-
**
|
|
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
|
[](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
|
-

|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
52
|
+
**What you get:**
|
|
41
53
|
|
|
42
|
-
- Live tool trace with duration and token cost
|
|
43
|
-
- Quota guard
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
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
|
+

|
|
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
|
|
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.')
|
package/dist/mcp-server.js
CHANGED
|
@@ -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
|
}
|
package/dist/service.js
ADDED
|
@@ -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.
|
|
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",
|