clawculator 2.8.1 → 2.8.3
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 +44 -3
- package/bin/clawculator.js +16 -0
- package/package.json +1 -1
- package/skills/clawculator/snapshotCard.js +105 -27
- package/src/snapshotCard.js +105 -27
package/README.md
CHANGED
|
@@ -25,11 +25,10 @@ It could be any of these. Clawculator finds all of them — with zero AI, zero g
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npx clawculator --
|
|
28
|
+
npx clawculator --snapshot # Get your cost grade — screenshot & share
|
|
29
|
+
npx clawculator --web # Browser dashboard at localhost:3457
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
That's it. Pin the tab. Watch your costs in real-time while you work.
|
|
32
|
-
|
|
33
32
|
---
|
|
34
33
|
|
|
35
34
|
## [▶ Live Demo](https://echoudhry.github.io/clawculator)
|
|
@@ -48,6 +47,41 @@ The dashboard features a Pac-Man-style lobster claw that chomps across the heade
|
|
|
48
47
|
|
|
49
48
|
## Modes
|
|
50
49
|
|
|
50
|
+
### `--snapshot` — Shareable Grade Card (new in v2.8)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx clawculator --snapshot
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Grades your setup **A+ to D**, shows your daily cost range, and displays your setup complexity — all in a screenshot-ready terminal card. No exact dollar amounts or session names are exposed. Built for sharing.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
╔══════════════════════════════════════════════╗
|
|
60
|
+
║ ║
|
|
61
|
+
║ 🦞 C L A W C U L A T O R ║
|
|
62
|
+
║ ║
|
|
63
|
+
║ ┌─────────┐ ║
|
|
64
|
+
║ │ A+ │ ║
|
|
65
|
+
║ └─────────┘ ║
|
|
66
|
+
║ cost health grade ║
|
|
67
|
+
║ ║
|
|
68
|
+
║ 💲 Under $1/day ║
|
|
69
|
+
║ ║
|
|
70
|
+
║ 📱 2 channels 🔧 4 skills ⏰ 1 cron ║
|
|
71
|
+
║ 💬 6 sessions 🧠 2 models ⚡ 85% cache ║
|
|
72
|
+
║ ║
|
|
73
|
+
║ ✅ No issues found ║
|
|
74
|
+
║ ║
|
|
75
|
+
║ Get your OpenClaw cost grade ║
|
|
76
|
+
║ npx clawculator --snapshot ║
|
|
77
|
+
║ ║
|
|
78
|
+
╚══════════════════════════════════════════════╝
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Screenshot it. Post it. What's your grade?
|
|
82
|
+
|
|
83
|
+
Also works as an OpenClaw skill — type `snapshot` or `what's my grade` in any channel.
|
|
84
|
+
|
|
51
85
|
### `--web` — Browser Dashboard (new in v2.6)
|
|
52
86
|
|
|
53
87
|
```bash
|
|
@@ -151,12 +185,14 @@ The `--web` and `--live` modes watch these files in real-time, tailing new lines
|
|
|
151
185
|
|
|
152
186
|
```bash
|
|
153
187
|
npx clawculator # Terminal analysis (default)
|
|
188
|
+
npx clawculator --snapshot # Shareable grade card (screenshot & post!)
|
|
154
189
|
npx clawculator --web # Browser dashboard (localhost:3457)
|
|
155
190
|
npx clawculator --web --port=8080 # Custom port
|
|
156
191
|
npx clawculator --live # Real-time terminal dashboard
|
|
157
192
|
npx clawculator --report # Visual HTML report
|
|
158
193
|
npx clawculator --md # Markdown report
|
|
159
194
|
npx clawculator --json # JSON for piping
|
|
195
|
+
npx clawculator --prune # Clean up SQLite database
|
|
160
196
|
npx clawculator --md --out=~/cost.md # Custom output path
|
|
161
197
|
npx clawculator --config=/path/to/openclaw.json
|
|
162
198
|
npx clawculator --help
|
|
@@ -181,6 +217,11 @@ clawculator
|
|
|
181
217
|
|
|
182
218
|
Your agent runs the analysis and returns the full markdown report directly in chat.
|
|
183
219
|
|
|
220
|
+
For the shareable grade card, type:
|
|
221
|
+
```
|
|
222
|
+
snapshot
|
|
223
|
+
```
|
|
224
|
+
|
|
184
225
|
**Or install manually** into your workspace:
|
|
185
226
|
```bash
|
|
186
227
|
mkdir -p ~/clawd/skills/clawculator
|
package/bin/clawculator.js
CHANGED
|
@@ -16,6 +16,7 @@ const flags = {
|
|
|
16
16
|
live: args.includes('--live'),
|
|
17
17
|
web: args.includes('--web'),
|
|
18
18
|
prune: args.includes('--prune'),
|
|
19
|
+
snapshot: args.includes('--snapshot'),
|
|
19
20
|
help: args.includes('--help') || args.includes('-h'),
|
|
20
21
|
config: args.find(a => a.startsWith('--config='))?.split('=')[1],
|
|
21
22
|
out: args.find(a => a.startsWith('--out='))?.split('=')[1],
|
|
@@ -43,6 +44,7 @@ Options:
|
|
|
43
44
|
--live Real-time cost dashboard in terminal (great for tmux)
|
|
44
45
|
--web Browser dashboard at localhost:3457 (pin the tab!)
|
|
45
46
|
--report Generate HTML report and open in browser
|
|
47
|
+
--snapshot Generate a shareable cost snapshot card (screenshot & post!)
|
|
46
48
|
--md Save markdown report to ./clawculator-report.md
|
|
47
49
|
--json Output raw JSON
|
|
48
50
|
--out=PATH Custom output path for --md or --report
|
|
@@ -54,6 +56,7 @@ Options:
|
|
|
54
56
|
Examples:
|
|
55
57
|
npx clawculator # Terminal analysis
|
|
56
58
|
npx clawculator --web # Browser dashboard (localhost:3457)
|
|
59
|
+
npx clawculator --snapshot # Shareable cost card (screenshot & post!)
|
|
57
60
|
npx clawculator --live # Real-time terminal dashboard
|
|
58
61
|
npx clawculator --md # Markdown report (readable by your AI agent)
|
|
59
62
|
npx clawculator --report # Visual HTML dashboard
|
|
@@ -164,6 +167,19 @@ async function main() {
|
|
|
164
167
|
process.exit(0);
|
|
165
168
|
}
|
|
166
169
|
|
|
170
|
+
if (flags.snapshot) {
|
|
171
|
+
const outDir = flags.out || process.cwd();
|
|
172
|
+
const { generateSnapshotCard } = require('../src/snapshotCard');
|
|
173
|
+
const result = generateSnapshotCard(analysis, outDir);
|
|
174
|
+
const { exec } = require('child_process');
|
|
175
|
+
exec(`open "${result.htmlPath}" 2>/dev/null || xdg-open "${result.htmlPath}" 2>/dev/null`, () => {
|
|
176
|
+
process.exit(0);
|
|
177
|
+
});
|
|
178
|
+
// Don't exit immediately — give exec time to open browser
|
|
179
|
+
setTimeout(() => process.exit(0), 2000);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
167
183
|
generateTerminalReport(analysis);
|
|
168
184
|
console.log('\x1b[90m────────────────────────────────────────────────────\x1b[0m');
|
|
169
185
|
console.log('\x1b[36mClawculator\x1b[0m · github.com/echoudhry/clawculator · Your friendly penny pincher.');
|
package/package.json
CHANGED
|
@@ -17,18 +17,27 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
17
17
|
const mediums = s.medium || 0;
|
|
18
18
|
const totalFindings = criticals + highs + mediums;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
// ── Filter findings by type ────────────────────────────
|
|
21
|
+
// For the grade, only count CONFIG findings (things a new user would inherit)
|
|
22
|
+
// Exclude: historical session costs, untracked sessions, cache totals, cost gaps
|
|
23
|
+
// These are noise for "what does it cost to run this setup?"
|
|
24
|
+
const configFindings = (analysis.findings || []).filter(f => {
|
|
25
|
+
const t = (f.title || '').toLowerCase();
|
|
26
|
+
const src = (f.source || '').toLowerCase();
|
|
27
|
+
// Skip historical/session findings
|
|
28
|
+
if (t.includes('orphaned session')) return false;
|
|
29
|
+
if (t.includes('untracked session')) return false;
|
|
30
|
+
if (t.includes('prompt caching added')) return false;
|
|
31
|
+
if (t.includes('real cost') && t.includes('higher than')) return false;
|
|
32
|
+
if (t.includes('>50k tokens')) return false;
|
|
33
|
+
if (t.includes('pricing table')) return false;
|
|
34
|
+
// Keep config findings (heartbeat, whatsapp, hooks, crons, models, vision, memory)
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const cfgCriticals = configFindings.filter(f => f.severity === 'critical').length;
|
|
39
|
+
const cfgHighs = configFindings.filter(f => f.severity === 'high').length;
|
|
40
|
+
const cfgMediums = configFindings.filter(f => f.severity === 'medium').length;
|
|
32
41
|
|
|
33
42
|
// ── Cost range (bucketed, not exact) ───────────────────
|
|
34
43
|
const todayCost = s.todayCost || 0;
|
|
@@ -42,6 +51,38 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
42
51
|
else if (todayCost < 50) { costRange = '$25–50/day'; costEmoji = '🔥'; }
|
|
43
52
|
else { costRange = '$50+/day'; costEmoji = '🚨'; }
|
|
44
53
|
|
|
54
|
+
// ── Compute grade ──────────────────────────────────────
|
|
55
|
+
// Grade = "how well is this SETUP configured for cost efficiency?"
|
|
56
|
+
// Only counts config findings, not historical session data
|
|
57
|
+
let grade, gradeColor, gradeGlow, gradeEmoji;
|
|
58
|
+
|
|
59
|
+
let score = (cfgCriticals * 5) + (cfgHighs * 2) + (cfgMediums * 0.5);
|
|
60
|
+
|
|
61
|
+
// Cost bonus: low daily spend is the whole point
|
|
62
|
+
if (todayCost < 1) score -= 4;
|
|
63
|
+
else if (todayCost < 5) score -= 3;
|
|
64
|
+
else if (todayCost < 10) score -= 1;
|
|
65
|
+
else if (todayCost > 50) score += 3;
|
|
66
|
+
|
|
67
|
+
// No criticals bonus
|
|
68
|
+
if (cfgCriticals === 0) score -= 2;
|
|
69
|
+
|
|
70
|
+
if (score < 0) score = 0;
|
|
71
|
+
|
|
72
|
+
if (score === 0) {
|
|
73
|
+
grade = 'A+'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.3)'; gradeEmoji = '🏆';
|
|
74
|
+
} else if (score <= 2) {
|
|
75
|
+
grade = 'A'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.2)'; gradeEmoji = '✅';
|
|
76
|
+
} else if (score <= 5) {
|
|
77
|
+
grade = 'B+'; gradeColor = '#84cc16'; gradeGlow = 'rgba(132,204,22,0.2)'; gradeEmoji = '👍';
|
|
78
|
+
} else if (score <= 8) {
|
|
79
|
+
grade = 'B'; gradeColor = '#f59e0b'; gradeGlow = 'rgba(245,158,11,0.2)'; gradeEmoji = '👍';
|
|
80
|
+
} else if (score <= 12) {
|
|
81
|
+
grade = 'C'; gradeColor = '#f97316'; gradeGlow = 'rgba(249,115,22,0.2)'; gradeEmoji = '⚠️';
|
|
82
|
+
} else {
|
|
83
|
+
grade = 'D'; gradeColor = '#ef4444'; gradeGlow = 'rgba(239,68,68,0.2)'; gradeEmoji = '🔥';
|
|
84
|
+
}
|
|
85
|
+
|
|
45
86
|
// ── Setup complexity from config ───────────────────────
|
|
46
87
|
const config = analysis.config || {};
|
|
47
88
|
|
|
@@ -66,7 +107,8 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
66
107
|
const modelCount = modelSet.size || 1;
|
|
67
108
|
|
|
68
109
|
const totalTokens = s.totalTokensFound || 1;
|
|
69
|
-
const
|
|
110
|
+
const totalWithCache = totalTokens + (s.totalCacheRead || 0) + (s.totalCacheWrite || 0);
|
|
111
|
+
const cacheEfficiency = Math.min(99, Math.round((s.totalCacheRead || 0) / totalWithCache * 100));
|
|
70
112
|
|
|
71
113
|
// ── Build stat pills ──────────────────────────────────
|
|
72
114
|
const pills = [];
|
|
@@ -79,11 +121,11 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
79
121
|
pills.push({ icon: '🧠', label: `${modelCount} model${modelCount>1?'s':''}` });
|
|
80
122
|
if (cacheEfficiency > 0) pills.push({ icon: '⚡', label: `${cacheEfficiency}% cache` });
|
|
81
123
|
|
|
82
|
-
// ── Findings summary
|
|
124
|
+
// ── Findings summary (config only) ─────────────────────
|
|
83
125
|
const findingSummary = [];
|
|
84
|
-
if (
|
|
85
|
-
if (
|
|
86
|
-
if (
|
|
126
|
+
if (cfgCriticals > 0) findingSummary.push(`🔴 ${cfgCriticals} critical`);
|
|
127
|
+
if (cfgHighs > 0) findingSummary.push(`🟠 ${cfgHighs} high`);
|
|
128
|
+
if (cfgMediums > 0) findingSummary.push(`🟡 ${cfgMediums} medium`);
|
|
87
129
|
|
|
88
130
|
const html = `<!DOCTYPE html>
|
|
89
131
|
<html lang="en">
|
|
@@ -307,16 +349,52 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
307
349
|
const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
|
|
308
350
|
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
309
351
|
|
|
310
|
-
// Terminal
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
352
|
+
// ── Terminal card (screenshot-ready) ───────────────────
|
|
353
|
+
const C = '\x1b[36m'; // cyan
|
|
354
|
+
const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
|
|
355
|
+
const D = '\x1b[90m'; // dim
|
|
356
|
+
const B = '\x1b[1m'; // bold
|
|
357
|
+
const R = '\x1b[0m'; // reset
|
|
358
|
+
const W = '\x1b[37m'; // white
|
|
359
|
+
|
|
360
|
+
const pillStr = pills.map(p => `${p.icon} ${p.label}`).join(' ');
|
|
361
|
+
const findStr = findingSummary.length > 0
|
|
362
|
+
? findingSummary.join(' ')
|
|
363
|
+
: '✅ No issues found';
|
|
364
|
+
|
|
365
|
+
const lines = [
|
|
366
|
+
``,
|
|
367
|
+
`${D} ╔══════════════════════════════════════════════╗${R}`,
|
|
368
|
+
`${D} ║${R} ${D}║${R}`,
|
|
369
|
+
`${D} ║${R} ${C}🦞 C L A W C U L A T O R${R} ${D}║${R}`,
|
|
370
|
+
`${D} ║${R} ${D}║${R}`,
|
|
371
|
+
`${D} ║${R} ${G}${B}┌─────────┐${R} ${D}║${R}`,
|
|
372
|
+
`${D} ║${R} ${G}${B}│ ${grade.padStart(3)} │${R} ${D}║${R}`,
|
|
373
|
+
`${D} ║${R} ${G}${B}└─────────┘${R} ${D}║${R}`,
|
|
374
|
+
`${D} ║${R} ${D}cost health grade${R} ${D}║${R}`,
|
|
375
|
+
`${D} ║${R} ${D}║${R}`,
|
|
376
|
+
`${D} ║${R} ${W}${B}${costEmoji} ${costRange}${R}`,
|
|
377
|
+
`${D} ║${R} ${D}║${R}`,
|
|
378
|
+
`${D} ║${R} ${D}──────────────────────────────────────────${R} ${D}║${R}`,
|
|
379
|
+
`${D} ║${R} ${D}║${R}`,
|
|
380
|
+
`${D} ║${R} ${pillStr}`,
|
|
381
|
+
`${D} ║${R} ${D}║${R}`,
|
|
382
|
+
`${D} ║${R} ${findStr}`,
|
|
383
|
+
`${D} ║${R} ${D}║${R}`,
|
|
384
|
+
`${D} ║${R} ${D}──────────────────────────────────────────${R} ${D}║${R}`,
|
|
385
|
+
`${D} ║${R} ${D}║${R}`,
|
|
386
|
+
`${D} ║${R} ${D}Get your OpenClaw cost grade${R} ${D}║${R}`,
|
|
387
|
+
`${D} ║${R} ${C}${B}npx clawculator --snapshot${R} ${D}║${R}`,
|
|
388
|
+
`${D} ║${R} ${D}║${R}`,
|
|
389
|
+
`${D} ╚══════════════════════════════════════════════╝${R}`,
|
|
390
|
+
``,
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
console.log(lines.join('\n'));
|
|
394
|
+
|
|
395
|
+
// Also mention the HTML file
|
|
396
|
+
console.log(` ${D}HTML card saved: ${htmlPath}${R}`);
|
|
397
|
+
console.log(` ${D}Screenshot this terminal or open the HTML — both work!${R}\n`);
|
|
320
398
|
|
|
321
399
|
return { htmlPath, grade, costRange };
|
|
322
400
|
}
|
package/src/snapshotCard.js
CHANGED
|
@@ -17,18 +17,27 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
17
17
|
const mediums = s.medium || 0;
|
|
18
18
|
const totalFindings = criticals + highs + mediums;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
// ── Filter findings by type ────────────────────────────
|
|
21
|
+
// For the grade, only count CONFIG findings (things a new user would inherit)
|
|
22
|
+
// Exclude: historical session costs, untracked sessions, cache totals, cost gaps
|
|
23
|
+
// These are noise for "what does it cost to run this setup?"
|
|
24
|
+
const configFindings = (analysis.findings || []).filter(f => {
|
|
25
|
+
const t = (f.title || '').toLowerCase();
|
|
26
|
+
const src = (f.source || '').toLowerCase();
|
|
27
|
+
// Skip historical/session findings
|
|
28
|
+
if (t.includes('orphaned session')) return false;
|
|
29
|
+
if (t.includes('untracked session')) return false;
|
|
30
|
+
if (t.includes('prompt caching added')) return false;
|
|
31
|
+
if (t.includes('real cost') && t.includes('higher than')) return false;
|
|
32
|
+
if (t.includes('>50k tokens')) return false;
|
|
33
|
+
if (t.includes('pricing table')) return false;
|
|
34
|
+
// Keep config findings (heartbeat, whatsapp, hooks, crons, models, vision, memory)
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const cfgCriticals = configFindings.filter(f => f.severity === 'critical').length;
|
|
39
|
+
const cfgHighs = configFindings.filter(f => f.severity === 'high').length;
|
|
40
|
+
const cfgMediums = configFindings.filter(f => f.severity === 'medium').length;
|
|
32
41
|
|
|
33
42
|
// ── Cost range (bucketed, not exact) ───────────────────
|
|
34
43
|
const todayCost = s.todayCost || 0;
|
|
@@ -42,6 +51,38 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
42
51
|
else if (todayCost < 50) { costRange = '$25–50/day'; costEmoji = '🔥'; }
|
|
43
52
|
else { costRange = '$50+/day'; costEmoji = '🚨'; }
|
|
44
53
|
|
|
54
|
+
// ── Compute grade ──────────────────────────────────────
|
|
55
|
+
// Grade = "how well is this SETUP configured for cost efficiency?"
|
|
56
|
+
// Only counts config findings, not historical session data
|
|
57
|
+
let grade, gradeColor, gradeGlow, gradeEmoji;
|
|
58
|
+
|
|
59
|
+
let score = (cfgCriticals * 5) + (cfgHighs * 2) + (cfgMediums * 0.5);
|
|
60
|
+
|
|
61
|
+
// Cost bonus: low daily spend is the whole point
|
|
62
|
+
if (todayCost < 1) score -= 4;
|
|
63
|
+
else if (todayCost < 5) score -= 3;
|
|
64
|
+
else if (todayCost < 10) score -= 1;
|
|
65
|
+
else if (todayCost > 50) score += 3;
|
|
66
|
+
|
|
67
|
+
// No criticals bonus
|
|
68
|
+
if (cfgCriticals === 0) score -= 2;
|
|
69
|
+
|
|
70
|
+
if (score < 0) score = 0;
|
|
71
|
+
|
|
72
|
+
if (score === 0) {
|
|
73
|
+
grade = 'A+'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.3)'; gradeEmoji = '🏆';
|
|
74
|
+
} else if (score <= 2) {
|
|
75
|
+
grade = 'A'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.2)'; gradeEmoji = '✅';
|
|
76
|
+
} else if (score <= 5) {
|
|
77
|
+
grade = 'B+'; gradeColor = '#84cc16'; gradeGlow = 'rgba(132,204,22,0.2)'; gradeEmoji = '👍';
|
|
78
|
+
} else if (score <= 8) {
|
|
79
|
+
grade = 'B'; gradeColor = '#f59e0b'; gradeGlow = 'rgba(245,158,11,0.2)'; gradeEmoji = '👍';
|
|
80
|
+
} else if (score <= 12) {
|
|
81
|
+
grade = 'C'; gradeColor = '#f97316'; gradeGlow = 'rgba(249,115,22,0.2)'; gradeEmoji = '⚠️';
|
|
82
|
+
} else {
|
|
83
|
+
grade = 'D'; gradeColor = '#ef4444'; gradeGlow = 'rgba(239,68,68,0.2)'; gradeEmoji = '🔥';
|
|
84
|
+
}
|
|
85
|
+
|
|
45
86
|
// ── Setup complexity from config ───────────────────────
|
|
46
87
|
const config = analysis.config || {};
|
|
47
88
|
|
|
@@ -66,7 +107,8 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
66
107
|
const modelCount = modelSet.size || 1;
|
|
67
108
|
|
|
68
109
|
const totalTokens = s.totalTokensFound || 1;
|
|
69
|
-
const
|
|
110
|
+
const totalWithCache = totalTokens + (s.totalCacheRead || 0) + (s.totalCacheWrite || 0);
|
|
111
|
+
const cacheEfficiency = Math.min(99, Math.round((s.totalCacheRead || 0) / totalWithCache * 100));
|
|
70
112
|
|
|
71
113
|
// ── Build stat pills ──────────────────────────────────
|
|
72
114
|
const pills = [];
|
|
@@ -79,11 +121,11 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
79
121
|
pills.push({ icon: '🧠', label: `${modelCount} model${modelCount>1?'s':''}` });
|
|
80
122
|
if (cacheEfficiency > 0) pills.push({ icon: '⚡', label: `${cacheEfficiency}% cache` });
|
|
81
123
|
|
|
82
|
-
// ── Findings summary
|
|
124
|
+
// ── Findings summary (config only) ─────────────────────
|
|
83
125
|
const findingSummary = [];
|
|
84
|
-
if (
|
|
85
|
-
if (
|
|
86
|
-
if (
|
|
126
|
+
if (cfgCriticals > 0) findingSummary.push(`🔴 ${cfgCriticals} critical`);
|
|
127
|
+
if (cfgHighs > 0) findingSummary.push(`🟠 ${cfgHighs} high`);
|
|
128
|
+
if (cfgMediums > 0) findingSummary.push(`🟡 ${cfgMediums} medium`);
|
|
87
129
|
|
|
88
130
|
const html = `<!DOCTYPE html>
|
|
89
131
|
<html lang="en">
|
|
@@ -307,16 +349,52 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
307
349
|
const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
|
|
308
350
|
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
309
351
|
|
|
310
|
-
// Terminal
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
352
|
+
// ── Terminal card (screenshot-ready) ───────────────────
|
|
353
|
+
const C = '\x1b[36m'; // cyan
|
|
354
|
+
const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
|
|
355
|
+
const D = '\x1b[90m'; // dim
|
|
356
|
+
const B = '\x1b[1m'; // bold
|
|
357
|
+
const R = '\x1b[0m'; // reset
|
|
358
|
+
const W = '\x1b[37m'; // white
|
|
359
|
+
|
|
360
|
+
const pillStr = pills.map(p => `${p.icon} ${p.label}`).join(' ');
|
|
361
|
+
const findStr = findingSummary.length > 0
|
|
362
|
+
? findingSummary.join(' ')
|
|
363
|
+
: '✅ No issues found';
|
|
364
|
+
|
|
365
|
+
const lines = [
|
|
366
|
+
``,
|
|
367
|
+
`${D} ╔══════════════════════════════════════════════╗${R}`,
|
|
368
|
+
`${D} ║${R} ${D}║${R}`,
|
|
369
|
+
`${D} ║${R} ${C}🦞 C L A W C U L A T O R${R} ${D}║${R}`,
|
|
370
|
+
`${D} ║${R} ${D}║${R}`,
|
|
371
|
+
`${D} ║${R} ${G}${B}┌─────────┐${R} ${D}║${R}`,
|
|
372
|
+
`${D} ║${R} ${G}${B}│ ${grade.padStart(3)} │${R} ${D}║${R}`,
|
|
373
|
+
`${D} ║${R} ${G}${B}└─────────┘${R} ${D}║${R}`,
|
|
374
|
+
`${D} ║${R} ${D}cost health grade${R} ${D}║${R}`,
|
|
375
|
+
`${D} ║${R} ${D}║${R}`,
|
|
376
|
+
`${D} ║${R} ${W}${B}${costEmoji} ${costRange}${R}`,
|
|
377
|
+
`${D} ║${R} ${D}║${R}`,
|
|
378
|
+
`${D} ║${R} ${D}──────────────────────────────────────────${R} ${D}║${R}`,
|
|
379
|
+
`${D} ║${R} ${D}║${R}`,
|
|
380
|
+
`${D} ║${R} ${pillStr}`,
|
|
381
|
+
`${D} ║${R} ${D}║${R}`,
|
|
382
|
+
`${D} ║${R} ${findStr}`,
|
|
383
|
+
`${D} ║${R} ${D}║${R}`,
|
|
384
|
+
`${D} ║${R} ${D}──────────────────────────────────────────${R} ${D}║${R}`,
|
|
385
|
+
`${D} ║${R} ${D}║${R}`,
|
|
386
|
+
`${D} ║${R} ${D}Get your OpenClaw cost grade${R} ${D}║${R}`,
|
|
387
|
+
`${D} ║${R} ${C}${B}npx clawculator --snapshot${R} ${D}║${R}`,
|
|
388
|
+
`${D} ║${R} ${D}║${R}`,
|
|
389
|
+
`${D} ╚══════════════════════════════════════════════╝${R}`,
|
|
390
|
+
``,
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
console.log(lines.join('\n'));
|
|
394
|
+
|
|
395
|
+
// Also mention the HTML file
|
|
396
|
+
console.log(` ${D}HTML card saved: ${htmlPath}${R}`);
|
|
397
|
+
console.log(` ${D}Screenshot this terminal or open the HTML — both work!${R}\n`);
|
|
320
398
|
|
|
321
399
|
return { htmlPath, grade, costRange };
|
|
322
400
|
}
|