@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 +198 -86
- package/dist/claude-auth.d.ts +7 -0
- package/dist/claude-auth.js +12 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -0
- package/dist/daemon.js +32 -15
- package/dist/db.d.ts +18 -0
- package/dist/db.js +50 -0
- package/dist/doctor.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +164 -76
- package/dist/insights.d.ts +26 -0
- package/dist/insights.js +172 -20
- package/dist/mcp-server.d.ts +1 -1
- package/dist/mcp-server.js +148 -21
- package/dist/notifier.js +24 -6
- package/dist/pattern-analyzer.d.ts +1 -0
- package/dist/pattern-analyzer.js +13 -0
- package/dist/quota-tracker.d.ts +6 -0
- package/dist/quota-tracker.js +79 -32
- package/dist/roast.js +129 -40
- package/dist/routes/events.js +1 -1
- package/package.json +1 -1
package/dist/roast.js
CHANGED
|
@@ -2,54 +2,38 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runRoast = runRoast;
|
|
4
4
|
const db_js_1 = require("./db.js");
|
|
5
|
-
function formatMinutes(totalMinutes) {
|
|
6
|
-
if (totalMinutes < 60)
|
|
7
|
-
return `${Math.round(totalMinutes)} minutes`;
|
|
8
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
9
|
-
if (hours < 24)
|
|
10
|
-
return `${hours} hours`;
|
|
11
|
-
const days = Math.floor(hours / 24);
|
|
12
|
-
return `${days} days`;
|
|
13
|
-
}
|
|
14
5
|
function getRoastRating(avgEfficiency) {
|
|
15
6
|
if (avgEfficiency >= 90)
|
|
16
|
-
return "You're a machine. Or maybe you're just not using Claude enough.
|
|
7
|
+
return "You're a machine. Or maybe you're just not using Claude enough.";
|
|
17
8
|
if (avgEfficiency >= 70)
|
|
18
9
|
return "Solid. Not great, not terrible. The AI equivalent of a C+ student.";
|
|
19
10
|
if (avgEfficiency >= 50)
|
|
20
|
-
return "Room for growth, champ.
|
|
21
|
-
return "Oof. That's a lot of money down the drain. Are you okay?
|
|
11
|
+
return "Room for growth, champ.";
|
|
12
|
+
return "Oof. That's a lot of money down the drain. Are you okay?";
|
|
22
13
|
}
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
lines.push('π₯ Your Claude Code Roast');
|
|
26
|
-
lines.push('');
|
|
14
|
+
function getRoastCards(data) {
|
|
15
|
+
const cards = [];
|
|
27
16
|
if (data.totalBashCalls > 0) {
|
|
28
17
|
const minutesPerCall = (data.days * 24 * 60) / data.totalBashCalls;
|
|
29
18
|
if (minutesPerCall < 60) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
lines.push('');
|
|
19
|
+
cards.push(` π₯οΈ BASH OVERLOAD\n` +
|
|
20
|
+
` ${data.totalBashCalls} calls in ${data.days}d β once every ${minutesPerCall.toFixed(1)} min\n` +
|
|
21
|
+
` Are you okay?`);
|
|
34
22
|
}
|
|
35
23
|
}
|
|
36
24
|
if (data.contextHits > 0) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
cards.push(` π§ CONTEXT AMNESIA\n` +
|
|
26
|
+
` ${data.contextHits} sessions at 90%+ context\n` +
|
|
27
|
+
` Claude was writing with amnesia half the time.`);
|
|
40
28
|
}
|
|
41
29
|
if (data.totalLoops > 0) {
|
|
42
30
|
const loopCost = data.totalCost * 0.15;
|
|
43
|
-
lines.push(` You spent $${loopCost.toFixed(2)} on loops you never noticed.`);
|
|
44
31
|
const coffees = Math.floor(loopCost / 0.3);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
32
|
+
cards.push(` π LOOP MONEY PIT\n` +
|
|
33
|
+
` $${loopCost.toFixed(2)} wasted on loops${coffees > 0 ? ` β that's ${coffees} coffees` : ''}\n` +
|
|
34
|
+
` Just saying.`);
|
|
49
35
|
}
|
|
50
|
-
|
|
51
|
-
lines.push(` ${getRoastRating(data.avgEfficiency)}`);
|
|
52
|
-
return lines.join('\n');
|
|
36
|
+
return cards;
|
|
53
37
|
}
|
|
54
38
|
async function runRoast(opts) {
|
|
55
39
|
const days = opts.months ?? 30;
|
|
@@ -58,8 +42,10 @@ async function runRoast(opts) {
|
|
|
58
42
|
let totalBashCalls = 0;
|
|
59
43
|
let totalLoops = 0;
|
|
60
44
|
let contextHits = 0;
|
|
45
|
+
let totalTokens = 0;
|
|
61
46
|
for (const session of sessions) {
|
|
62
47
|
totalLoops += session.loops_detected || 0;
|
|
48
|
+
totalTokens += (session.total_input_tokens || 0) + (session.total_output_tokens || 0) + (session.total_cache_read || 0);
|
|
63
49
|
if ((session.total_input_tokens || 0) + (session.total_output_tokens || 0) > 150000) {
|
|
64
50
|
contextHits++;
|
|
65
51
|
}
|
|
@@ -71,6 +57,10 @@ async function runRoast(opts) {
|
|
|
71
57
|
const avgEfficiency = sessions.length > 0
|
|
72
58
|
? sessions.reduce((a, s) => a + (s.efficiency_score || 0), 0) / sessions.length
|
|
73
59
|
: 100;
|
|
60
|
+
const avgCostPerSession = sessions.length > 0 ? totalCost / sessions.length : 0;
|
|
61
|
+
const topTools = db_js_1.dbOps.getTopTools(days, 'cost', 1);
|
|
62
|
+
const topTool = topTools[0]?.tool_name ?? 'Unknown';
|
|
63
|
+
const topToolPct = totalCost > 0 ? Math.round((topTools[0]?.total_cost_usd ?? 0) / totalCost * 100) : 0;
|
|
74
64
|
const data = {
|
|
75
65
|
totalCost,
|
|
76
66
|
totalSessions: sessions.length,
|
|
@@ -79,17 +69,116 @@ async function runRoast(opts) {
|
|
|
79
69
|
avgEfficiency,
|
|
80
70
|
contextHits,
|
|
81
71
|
days,
|
|
72
|
+
totalTokens,
|
|
73
|
+
avgCostPerSession,
|
|
74
|
+
topTool,
|
|
75
|
+
topToolPct,
|
|
76
|
+
};
|
|
77
|
+
const R = '\x1b[0m';
|
|
78
|
+
const B = '\x1b[1m';
|
|
79
|
+
const D = '\x1b[2m';
|
|
80
|
+
const G = '\x1b[32m';
|
|
81
|
+
const Y = '\x1b[33m';
|
|
82
|
+
const C = '\x1b[36m';
|
|
83
|
+
const M = '\x1b[35m';
|
|
84
|
+
const bar = (pct, width = 20) => {
|
|
85
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * width);
|
|
86
|
+
const color = pct >= 90 ? '\x1b[31m' : pct >= 70 ? '\x1b[33m' : '\x1b[32m';
|
|
87
|
+
return `${color}${'β'.repeat(filled)}${R}${D}${'β'.repeat(width - filled)}${R}`;
|
|
88
|
+
};
|
|
89
|
+
const fmtTok = (n) => {
|
|
90
|
+
if (n >= 1000000)
|
|
91
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
92
|
+
if (n >= 1000)
|
|
93
|
+
return `${Math.round(n / 1000)}K`;
|
|
94
|
+
return n.toString();
|
|
82
95
|
};
|
|
83
96
|
if (opts.stats) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
const effPct = Math.round(avgEfficiency);
|
|
98
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
99
|
+
const padVisual = (s, target) => {
|
|
100
|
+
const visible = stripAnsi(s).length;
|
|
101
|
+
return s + ' '.repeat(Math.max(0, target - visible));
|
|
102
|
+
};
|
|
103
|
+
const rows = [
|
|
104
|
+
{ label: 'Sessions', value: `${B}${sessions.length}${R}`, barPct: Math.min(sessions.length / 50 * 100, 100) },
|
|
105
|
+
{ label: 'Total cost', value: `${B}$${totalCost.toFixed(2)}${R}`, barPct: Math.min(totalCost / 200 * 100, 100) },
|
|
106
|
+
{ label: 'Bash calls', value: `${B}${totalBashCalls}${R}`, barPct: Math.min(totalBashCalls / 500 * 100, 100) },
|
|
107
|
+
{ label: 'Loops', value: `${B}${totalLoops}${R}`, barPct: Math.min(totalLoops / 200 * 100, 100) },
|
|
108
|
+
{ label: 'Efficiency', value: `${B}${effPct}/100${R}`, barPct: effPct },
|
|
109
|
+
];
|
|
110
|
+
const lines = [];
|
|
111
|
+
lines.push(`\n${B}π Claude Code Stats${R} ${D}(${days} days)${R}`);
|
|
112
|
+
lines.push('β'.repeat(42));
|
|
113
|
+
lines.push('');
|
|
114
|
+
for (let i = 0; i < rows.length; i++) {
|
|
115
|
+
const r = rows[i];
|
|
116
|
+
const labelCol = ` ${r.label}`.padEnd(14);
|
|
117
|
+
const valueCol = padVisual(r.value, 12);
|
|
118
|
+
lines.push(`${labelCol}${valueCol}${bar(r.barPct)}`);
|
|
119
|
+
if (i < rows.length - 1)
|
|
120
|
+
lines.push('');
|
|
121
|
+
}
|
|
122
|
+
lines.push('');
|
|
123
|
+
console.log(lines.join('\n'));
|
|
90
124
|
return;
|
|
91
125
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
const rating = Math.round(avgEfficiency);
|
|
127
|
+
const stars = rating >= 90 ? 'β
β
β
β
β
' : rating >= 70 ? 'β
β
β
β
β' : rating >= 50 ? 'β
β
β
ββ' : 'β
β
βββ';
|
|
128
|
+
const lines = [];
|
|
129
|
+
lines.push(`\n${B}π₯ Your Claude Code Roast${R} ${D}(${days} days)${R}`);
|
|
130
|
+
lines.push('β'.repeat(44));
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push(` ${B}Score${R} ${bar(rating)} ${B}${rating}/100${R} ${Y}${stars}${R}`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push(` ${B}Scorecard${R}`);
|
|
135
|
+
lines.push(` βββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ`);
|
|
136
|
+
lines.push(` β ${D}Metric${R} β ${D}Value${R} β ${D}Rating${R} β`);
|
|
137
|
+
lines.push(` βββββββββββββββββββΌβββββββββββββββΌβββββββββββββββ€`);
|
|
138
|
+
const sessionLabel = `${data.totalSessions}`;
|
|
139
|
+
const sessionRating = data.totalSessions > 30 ? `${Y}prolific${R}` : `${G}normal${R}`;
|
|
140
|
+
lines.push(` β Sessions β ${sessionLabel.padEnd(12)} β ${sessionRating}`.padEnd(46) + 'β');
|
|
141
|
+
const costLabel = `$${totalCost.toFixed(2)}`;
|
|
142
|
+
const costRating = totalCost > 100 ? `${Y}πΈ burning${R}` : totalCost > 20 ? `${G}reasonable${R}` : `${G}frugal${R}`;
|
|
143
|
+
lines.push(` β Total cost β ${costLabel.padEnd(12)} β ${costRating}`.padEnd(46) + 'β');
|
|
144
|
+
const avgLabel = `$${avgCostPerSession.toFixed(2)}/session`;
|
|
145
|
+
const avgRating = avgCostPerSession > 10 ? `${Y}expensive${R}` : `${G}efficient${R}`;
|
|
146
|
+
lines.push(` β Avg/session β ${avgLabel.padEnd(12)} β ${avgRating}`.padEnd(46) + 'β');
|
|
147
|
+
const bashLabel = `${totalBashCalls}`;
|
|
148
|
+
const bashRating = totalBashCalls > 200 ? `${Y}π¨ overload${R}` : `${G}normal${R}`;
|
|
149
|
+
lines.push(` β Bash calls β ${bashLabel.padEnd(12)} β ${bashRating}`.padEnd(46) + 'β');
|
|
150
|
+
const loopLabel = `${totalLoops}`;
|
|
151
|
+
const loopRating = totalLoops > 50 ? `${Y}π looping${R}` : `${G}clean${R}`;
|
|
152
|
+
lines.push(` β Loops β ${loopLabel.padEnd(12)} β ${loopRating}`.padEnd(46) + 'β');
|
|
153
|
+
const effLabel = `${Math.round(avgEfficiency)}/100`;
|
|
154
|
+
const effRating = avgEfficiency >= 90 ? `${G}π elite${R}` : avgEfficiency >= 70 ? `${Y}decent${R}` : `${Y}needs work${R}`;
|
|
155
|
+
lines.push(` β Efficiency β ${effLabel.padEnd(12)} β ${effRating}`.padEnd(46) + 'β');
|
|
156
|
+
const tokLabel = fmtTok(totalTokens);
|
|
157
|
+
lines.push(` β Tokens β ${tokLabel.padEnd(12)} β ${D}β${R}`.padEnd(46) + 'β');
|
|
158
|
+
const toolLabel = `${topTool} ${topToolPct}%`;
|
|
159
|
+
lines.push(` β Top tool β ${toolLabel.padEnd(12)} β ${D}β${R}`.padEnd(46) + 'β');
|
|
160
|
+
lines.push(` βββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
const cards = getRoastCards(data);
|
|
163
|
+
if (cards.length > 0) {
|
|
164
|
+
lines.push(` ${B}Roast Cards${R}`);
|
|
165
|
+
lines.push('');
|
|
166
|
+
for (const card of cards) {
|
|
167
|
+
const cardLines = card.split('\n');
|
|
168
|
+
const maxLen = Math.max(...cardLines.map(l => l.length));
|
|
169
|
+
lines.push(` β${'β'.repeat(maxLen + 2)}β`);
|
|
170
|
+
for (const cl of cardLines) {
|
|
171
|
+
lines.push(` β ${cl.padEnd(maxLen)} β`);
|
|
172
|
+
}
|
|
173
|
+
lines.push(` β${'β'.repeat(maxLen + 2)}β`);
|
|
174
|
+
lines.push('');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
lines.push(` ${B}Verdict${R}`);
|
|
178
|
+
lines.push(` ${getRoastRating(avgEfficiency)}`);
|
|
179
|
+
lines.push('');
|
|
180
|
+
lines.push('β'.repeat(44));
|
|
181
|
+
lines.push(` ${D}github.com/DeibyGS/claudestat${R}`);
|
|
182
|
+
lines.push('');
|
|
183
|
+
console.log(lines.join('\n'));
|
|
95
184
|
}
|
package/dist/routes/events.js
CHANGED
|
@@ -267,6 +267,6 @@ exports.onCostUpdate = onCostUpdate;
|
|
|
267
267
|
// βββ Callback de auto-compact ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
268
268
|
const onCompactDetected = (sessionId) => {
|
|
269
269
|
(0, stream_1.broadcast)({ type: 'compact_detected', payload: { session_id: sessionId, ts: Date.now() } });
|
|
270
|
-
console.log(`[daemon] Auto-compact
|
|
270
|
+
console.log(`[daemon] Auto-compact detected for session ${sessionId.slice(0, 8)}`);
|
|
271
271
|
};
|
|
272
272
|
exports.onCompactDetected = onCompactDetected;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statforge/claudestat",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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",
|