clawculator 2.7.0 → 2.8.1
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/package.json
CHANGED
|
@@ -38,7 +38,16 @@ node {baseDir}/run.js --md
|
|
|
38
38
|
|
|
39
39
|
Return the full markdown report to the user inline.
|
|
40
40
|
|
|
41
|
+
When the user types `snapshot`, `share my grade`, `cost grade`, `clawculator snapshot`, or `what's my grade`, run:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node {baseDir}/run.js --snapshot
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Return the terminal card output to the user. This shows their grade (A+ to D), cost range, setup complexity, and findings — designed to be screenshot and shared.
|
|
48
|
+
|
|
41
49
|
**Flags**
|
|
50
|
+
- `--snapshot` — shareable grade card (terminal output, screenshot-ready)
|
|
42
51
|
- `--md` — write markdown report and print to stdout
|
|
43
52
|
- `--json` — machine-readable JSON to stdout
|
|
44
53
|
- `--out=PATH` — custom output path for `--md`
|
|
@@ -10,11 +10,12 @@ const fs = require('fs');
|
|
|
10
10
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
12
|
const flags = {
|
|
13
|
-
json:
|
|
14
|
-
md:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
json: args.includes('--json'),
|
|
14
|
+
md: args.includes('--md'),
|
|
15
|
+
snapshot: args.includes('--snapshot'),
|
|
16
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
17
|
+
config: args.find(a => a.startsWith('--config='))?.split('=')[1],
|
|
18
|
+
out: args.find(a => a.startsWith('--out='))?.split('=')[1],
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const BANNER = `
|
|
@@ -81,6 +82,13 @@ async function main() {
|
|
|
81
82
|
process.exit(0);
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
if (flags.snapshot) {
|
|
86
|
+
const { generateSnapshotCard } = require('./snapshotCard');
|
|
87
|
+
const outDir = flags.out || process.cwd();
|
|
88
|
+
generateSnapshotCard(analysis, outDir);
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
if (flags.md) {
|
|
85
93
|
const outPath = flags.out || path.join(process.cwd(), 'clawculator-report.md');
|
|
86
94
|
fs.writeFileSync(outPath, generateMarkdownReport(analysis), 'utf8');
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a shareable cost snapshot card.
|
|
8
|
+
* Square format — works on Twitter, Instagram, TikTok, Discord.
|
|
9
|
+
* Shows grade, cost range, setup complexity — no exact dollars or session names.
|
|
10
|
+
*/
|
|
11
|
+
function generateSnapshotCard(analysis, outputDir) {
|
|
12
|
+
const s = analysis.summary;
|
|
13
|
+
|
|
14
|
+
// ── Compute grade ──────────────────────────────────────
|
|
15
|
+
const criticals = s.critical || 0;
|
|
16
|
+
const highs = s.high || 0;
|
|
17
|
+
const mediums = s.medium || 0;
|
|
18
|
+
const totalFindings = criticals + highs + mediums;
|
|
19
|
+
|
|
20
|
+
let grade, gradeColor, gradeGlow, gradeEmoji;
|
|
21
|
+
if (criticals === 0 && highs === 0 && mediums === 0) {
|
|
22
|
+
grade = 'A+'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.3)'; gradeEmoji = '🏆';
|
|
23
|
+
} else if (criticals === 0 && highs <= 1) {
|
|
24
|
+
grade = 'A'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.2)'; gradeEmoji = '✅';
|
|
25
|
+
} else if (criticals <= 1 && highs <= 2) {
|
|
26
|
+
grade = 'B'; gradeColor = '#f59e0b'; gradeGlow = 'rgba(245,158,11,0.2)'; gradeEmoji = '👍';
|
|
27
|
+
} else if (criticals <= 2) {
|
|
28
|
+
grade = 'C'; gradeColor = '#f97316'; gradeGlow = 'rgba(249,115,22,0.2)'; gradeEmoji = '⚠️';
|
|
29
|
+
} else {
|
|
30
|
+
grade = 'D'; gradeColor = '#ef4444'; gradeGlow = 'rgba(239,68,68,0.2)'; gradeEmoji = '🔥';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Cost range (bucketed, not exact) ───────────────────
|
|
34
|
+
const todayCost = s.todayCost || 0;
|
|
35
|
+
let costRange, costEmoji;
|
|
36
|
+
if (todayCost === 0) { costRange = 'Free tier'; costEmoji = '🆓'; }
|
|
37
|
+
else if (todayCost < 0.50) { costRange = 'Under $0.50/day'; costEmoji = '💲'; }
|
|
38
|
+
else if (todayCost < 1) { costRange = 'Under $1/day'; costEmoji = '💲'; }
|
|
39
|
+
else if (todayCost < 5) { costRange = '$1–5/day'; costEmoji = '💵'; }
|
|
40
|
+
else if (todayCost < 10) { costRange = '$5–10/day'; costEmoji = '💰'; }
|
|
41
|
+
else if (todayCost < 25) { costRange = '$10–25/day'; costEmoji = '💰'; }
|
|
42
|
+
else if (todayCost < 50) { costRange = '$25–50/day'; costEmoji = '🔥'; }
|
|
43
|
+
else { costRange = '$50+/day'; costEmoji = '🚨'; }
|
|
44
|
+
|
|
45
|
+
// ── Setup complexity from config ───────────────────────
|
|
46
|
+
const config = analysis.config || {};
|
|
47
|
+
|
|
48
|
+
const channels = [];
|
|
49
|
+
if (config.channels?.whatsapp) channels.push('WhatsApp');
|
|
50
|
+
if (config.channels?.telegram) channels.push('Telegram');
|
|
51
|
+
if (config.channels?.discord) channels.push('Discord');
|
|
52
|
+
if (config.channels?.signal) channels.push('Signal');
|
|
53
|
+
if (config.channels?.slack) channels.push('Slack');
|
|
54
|
+
if (config.webchat || config.channels?.webchat) channels.push('Webchat');
|
|
55
|
+
|
|
56
|
+
const skillCount = config.skills ? Object.keys(config.skills).length : 0;
|
|
57
|
+
const cronCount = config.cron ? Object.keys(config.cron).length : 0;
|
|
58
|
+
const hookCount = config.hooks ? Object.keys(config.hooks).length : 0;
|
|
59
|
+
const agentCount = config.agents?.list ? Object.keys(config.agents.list).length : 1;
|
|
60
|
+
const sessionCount = s.sessionsAnalyzed || 0;
|
|
61
|
+
|
|
62
|
+
const modelSet = new Set();
|
|
63
|
+
for (const sess of (analysis.sessions || [])) {
|
|
64
|
+
if (sess.model || sess.modelLabel) modelSet.add(sess.modelLabel || sess.model);
|
|
65
|
+
}
|
|
66
|
+
const modelCount = modelSet.size || 1;
|
|
67
|
+
|
|
68
|
+
const totalTokens = s.totalTokensFound || 1;
|
|
69
|
+
const cacheEfficiency = Math.round((s.totalCacheRead || 0) / totalTokens * 100);
|
|
70
|
+
|
|
71
|
+
// ── Build stat pills ──────────────────────────────────
|
|
72
|
+
const pills = [];
|
|
73
|
+
if (channels.length > 0) pills.push({ icon: '📱', label: `${channels.length} channel${channels.length>1?'s':''}` });
|
|
74
|
+
if (skillCount > 0) pills.push({ icon: '🔧', label: `${skillCount} skill${skillCount>1?'s':''}` });
|
|
75
|
+
if (cronCount > 0) pills.push({ icon: '⏰', label: `${cronCount} cron${cronCount>1?'s':''}` });
|
|
76
|
+
if (hookCount > 0) pills.push({ icon: '🪝', label: `${hookCount} hook${hookCount>1?'s':''}` });
|
|
77
|
+
if (agentCount > 1) pills.push({ icon: '🤖', label: `${agentCount} agents` });
|
|
78
|
+
if (sessionCount > 0) pills.push({ icon: '💬', label: `${sessionCount} sessions` });
|
|
79
|
+
pills.push({ icon: '🧠', label: `${modelCount} model${modelCount>1?'s':''}` });
|
|
80
|
+
if (cacheEfficiency > 0) pills.push({ icon: '⚡', label: `${cacheEfficiency}% cache` });
|
|
81
|
+
|
|
82
|
+
// ── Findings summary ──────────────────────────────────
|
|
83
|
+
const findingSummary = [];
|
|
84
|
+
if (criticals > 0) findingSummary.push(`🔴 ${criticals} critical`);
|
|
85
|
+
if (highs > 0) findingSummary.push(`🟠 ${highs} high`);
|
|
86
|
+
if (mediums > 0) findingSummary.push(`🟡 ${mediums} medium`);
|
|
87
|
+
|
|
88
|
+
const html = `<!DOCTYPE html>
|
|
89
|
+
<html lang="en">
|
|
90
|
+
<head>
|
|
91
|
+
<meta charset="UTF-8">
|
|
92
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
93
|
+
<title>Clawculator Snapshot</title>
|
|
94
|
+
<style>
|
|
95
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&family=Outfit:wght@400;500;600;700;800;900&display=swap');
|
|
96
|
+
|
|
97
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
98
|
+
|
|
99
|
+
html, body {
|
|
100
|
+
min-height: 100vh;
|
|
101
|
+
font-family: 'Outfit', sans-serif;
|
|
102
|
+
background: #020408;
|
|
103
|
+
color: #e2e8f0;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.card {
|
|
110
|
+
width: 480px; max-width: 96vw;
|
|
111
|
+
background: #05080f;
|
|
112
|
+
border-radius: 24px;
|
|
113
|
+
border: 1px solid #1a2744;
|
|
114
|
+
padding: 32px 28px 24px;
|
|
115
|
+
display: flex; flex-direction: column;
|
|
116
|
+
align-items: center;
|
|
117
|
+
position: relative;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
box-shadow: 0 0 80px rgba(34,211,238,0.04), 0 20px 60px rgba(0,0,0,0.5);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Grid bg */
|
|
123
|
+
.card::before {
|
|
124
|
+
content: '';
|
|
125
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
126
|
+
background:
|
|
127
|
+
linear-gradient(rgba(34,211,238,0.02) 1px, transparent 1px),
|
|
128
|
+
linear-gradient(90deg, rgba(34,211,238,0.02) 1px, transparent 1px);
|
|
129
|
+
background-size: 32px 32px;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.card > * { position: relative; z-index: 1; }
|
|
134
|
+
|
|
135
|
+
/* Header */
|
|
136
|
+
.header {
|
|
137
|
+
display: flex; align-items: center; gap: 10px;
|
|
138
|
+
margin-bottom: 20px;
|
|
139
|
+
}
|
|
140
|
+
.logo-claw { font-size: 28px; }
|
|
141
|
+
.logo-text {
|
|
142
|
+
font-family: 'JetBrains Mono', monospace;
|
|
143
|
+
font-weight: 800; font-size: 18px; letter-spacing: -0.5px;
|
|
144
|
+
background: linear-gradient(135deg, #22d3ee, #818cf8);
|
|
145
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Grade */
|
|
149
|
+
.grade-container {
|
|
150
|
+
display: flex; flex-direction: column; align-items: center;
|
|
151
|
+
margin-bottom: 16px;
|
|
152
|
+
}
|
|
153
|
+
.grade-ring {
|
|
154
|
+
width: 120px; height: 120px;
|
|
155
|
+
border-radius: 50%;
|
|
156
|
+
border: 3px solid ${gradeColor}44;
|
|
157
|
+
display: flex; align-items: center; justify-content: center;
|
|
158
|
+
box-shadow: 0 0 40px ${gradeGlow}, inset 0 0 20px ${gradeGlow};
|
|
159
|
+
position: relative;
|
|
160
|
+
}
|
|
161
|
+
.grade-ring::before {
|
|
162
|
+
content: '';
|
|
163
|
+
position: absolute; inset: -2px;
|
|
164
|
+
border-radius: 50%;
|
|
165
|
+
border: 2px solid ${gradeColor};
|
|
166
|
+
opacity: 0.5;
|
|
167
|
+
}
|
|
168
|
+
.grade-letter {
|
|
169
|
+
font-family: 'JetBrains Mono', monospace;
|
|
170
|
+
font-weight: 900; font-size: 52px;
|
|
171
|
+
color: ${gradeColor};
|
|
172
|
+
text-shadow: 0 0 20px ${gradeGlow};
|
|
173
|
+
line-height: 1;
|
|
174
|
+
}
|
|
175
|
+
.grade-label {
|
|
176
|
+
font-size: 11px; font-weight: 600; color: #64748b;
|
|
177
|
+
text-transform: uppercase; letter-spacing: 1.5px;
|
|
178
|
+
margin-top: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Cost */
|
|
182
|
+
.cost-range {
|
|
183
|
+
font-family: 'JetBrains Mono', monospace;
|
|
184
|
+
font-weight: 700; font-size: 22px;
|
|
185
|
+
color: #e2e8f0;
|
|
186
|
+
margin-bottom: 14px;
|
|
187
|
+
text-align: center;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Divider */
|
|
191
|
+
.divider {
|
|
192
|
+
width: 60px; height: 1px;
|
|
193
|
+
background: linear-gradient(90deg, transparent, #1e3a5f, transparent);
|
|
194
|
+
margin-bottom: 14px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Pills */
|
|
198
|
+
.pills {
|
|
199
|
+
display: flex; flex-wrap: wrap; justify-content: center;
|
|
200
|
+
gap: 6px;
|
|
201
|
+
margin-bottom: 14px;
|
|
202
|
+
max-width: 400px;
|
|
203
|
+
}
|
|
204
|
+
.pill {
|
|
205
|
+
display: flex; align-items: center; gap: 4px;
|
|
206
|
+
background: #0b1120;
|
|
207
|
+
border: 1px solid #1a2744;
|
|
208
|
+
border-radius: 14px;
|
|
209
|
+
padding: 5px 12px;
|
|
210
|
+
font-size: 12px; font-weight: 500;
|
|
211
|
+
color: #94a3b8;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Findings */
|
|
215
|
+
.findings {
|
|
216
|
+
text-align: center;
|
|
217
|
+
margin-bottom: 14px;
|
|
218
|
+
}
|
|
219
|
+
.findings-badges {
|
|
220
|
+
display: flex; justify-content: center; gap: 8px;
|
|
221
|
+
flex-wrap: wrap;
|
|
222
|
+
}
|
|
223
|
+
.findings-badge {
|
|
224
|
+
font-family: 'JetBrains Mono', monospace;
|
|
225
|
+
font-size: 12px; font-weight: 600;
|
|
226
|
+
padding: 4px 10px; border-radius: 6px;
|
|
227
|
+
}
|
|
228
|
+
.findings-clean {
|
|
229
|
+
font-size: 14px; color: #22c55e; font-weight: 600;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* CTA */
|
|
233
|
+
.cta {
|
|
234
|
+
margin-top: auto;
|
|
235
|
+
text-align: center;
|
|
236
|
+
width: 100%;
|
|
237
|
+
}
|
|
238
|
+
.cta-divider {
|
|
239
|
+
width: 100%; height: 1px;
|
|
240
|
+
background: linear-gradient(90deg, transparent, #1a2744, transparent);
|
|
241
|
+
margin-bottom: 14px;
|
|
242
|
+
}
|
|
243
|
+
.cta-prompt {
|
|
244
|
+
font-size: 13px; color: #64748b;
|
|
245
|
+
margin-bottom: 8px;
|
|
246
|
+
}
|
|
247
|
+
.cta-command {
|
|
248
|
+
font-family: 'JetBrains Mono', monospace;
|
|
249
|
+
font-size: 14px; font-weight: 700;
|
|
250
|
+
color: #22d3ee;
|
|
251
|
+
background: #0b1120;
|
|
252
|
+
border: 1px solid #1a2744;
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
padding: 10px 18px;
|
|
255
|
+
display: inline-block;
|
|
256
|
+
margin-bottom: 6px;
|
|
257
|
+
}
|
|
258
|
+
.cta-sub {
|
|
259
|
+
font-size: 10px; color: #334155;
|
|
260
|
+
}
|
|
261
|
+
</style>
|
|
262
|
+
</head>
|
|
263
|
+
<body>
|
|
264
|
+
<div class="card">
|
|
265
|
+
|
|
266
|
+
<div class="header">
|
|
267
|
+
<div class="logo-claw">🦞</div>
|
|
268
|
+
<div class="logo-text">CLAWCULATOR</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="grade-container">
|
|
272
|
+
<div class="grade-ring">
|
|
273
|
+
<div class="grade-letter">${grade}</div>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="grade-label">${gradeEmoji} cost health</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div class="cost-range">${costEmoji} ${costRange}</div>
|
|
279
|
+
|
|
280
|
+
<div class="divider"></div>
|
|
281
|
+
|
|
282
|
+
<div class="pills">
|
|
283
|
+
${pills.map(p => `<div class="pill"><span>${p.icon}</span>${p.label}</div>`).join('\n ')}
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div class="findings">
|
|
287
|
+
${findingSummary.length > 0 ?
|
|
288
|
+
`<div class="findings-badges">${findingSummary.map(f => {
|
|
289
|
+
const c = f.startsWith('🔴') ? '#ef4444' : f.startsWith('🟠') ? '#f97316' : '#eab308';
|
|
290
|
+
return `<div class="findings-badge" style="background:${c}18;color:${c}">${f}</div>`;
|
|
291
|
+
}).join('')}</div>` :
|
|
292
|
+
`<div class="findings-clean">✅ No issues found</div>`
|
|
293
|
+
}
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div class="cta">
|
|
297
|
+
<div class="cta-divider"></div>
|
|
298
|
+
<div class="cta-prompt">Get your OpenClaw cost grade</div>
|
|
299
|
+
<div class="cta-command">npx clawculator --snapshot</div>
|
|
300
|
+
<div class="cta-sub">100% offline · your data never leaves your machine</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
</div>
|
|
304
|
+
</body>
|
|
305
|
+
</html>`;
|
|
306
|
+
|
|
307
|
+
const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
|
|
308
|
+
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
309
|
+
|
|
310
|
+
// Terminal summary
|
|
311
|
+
console.log(`\n ${gradeEmoji} Grade: \x1b[1m${grade}\x1b[0m`);
|
|
312
|
+
console.log(` ${costEmoji} Cost: ${costRange}`);
|
|
313
|
+
console.log(` 📦 Setup: ${pills.map(p => p.label).join(' · ')}`);
|
|
314
|
+
if (findingSummary.length > 0) {
|
|
315
|
+
console.log(` 🔍 ${findingSummary.join(' · ')}`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` ✅ Clean — no issues`);
|
|
318
|
+
}
|
|
319
|
+
console.log('');
|
|
320
|
+
|
|
321
|
+
return { htmlPath, grade, costRange };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { generateSnapshotCard };
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a shareable cost snapshot card.
|
|
8
|
+
* Square format — works on Twitter, Instagram, TikTok, Discord.
|
|
9
|
+
* Shows grade, cost range, setup complexity — no exact dollars or session names.
|
|
10
|
+
*/
|
|
11
|
+
function generateSnapshotCard(analysis, outputDir) {
|
|
12
|
+
const s = analysis.summary;
|
|
13
|
+
|
|
14
|
+
// ── Compute grade ──────────────────────────────────────
|
|
15
|
+
const criticals = s.critical || 0;
|
|
16
|
+
const highs = s.high || 0;
|
|
17
|
+
const mediums = s.medium || 0;
|
|
18
|
+
const totalFindings = criticals + highs + mediums;
|
|
19
|
+
|
|
20
|
+
let grade, gradeColor, gradeGlow, gradeEmoji;
|
|
21
|
+
if (criticals === 0 && highs === 0 && mediums === 0) {
|
|
22
|
+
grade = 'A+'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.3)'; gradeEmoji = '🏆';
|
|
23
|
+
} else if (criticals === 0 && highs <= 1) {
|
|
24
|
+
grade = 'A'; gradeColor = '#22c55e'; gradeGlow = 'rgba(34,197,94,0.2)'; gradeEmoji = '✅';
|
|
25
|
+
} else if (criticals <= 1 && highs <= 2) {
|
|
26
|
+
grade = 'B'; gradeColor = '#f59e0b'; gradeGlow = 'rgba(245,158,11,0.2)'; gradeEmoji = '👍';
|
|
27
|
+
} else if (criticals <= 2) {
|
|
28
|
+
grade = 'C'; gradeColor = '#f97316'; gradeGlow = 'rgba(249,115,22,0.2)'; gradeEmoji = '⚠️';
|
|
29
|
+
} else {
|
|
30
|
+
grade = 'D'; gradeColor = '#ef4444'; gradeGlow = 'rgba(239,68,68,0.2)'; gradeEmoji = '🔥';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Cost range (bucketed, not exact) ───────────────────
|
|
34
|
+
const todayCost = s.todayCost || 0;
|
|
35
|
+
let costRange, costEmoji;
|
|
36
|
+
if (todayCost === 0) { costRange = 'Free tier'; costEmoji = '🆓'; }
|
|
37
|
+
else if (todayCost < 0.50) { costRange = 'Under $0.50/day'; costEmoji = '💲'; }
|
|
38
|
+
else if (todayCost < 1) { costRange = 'Under $1/day'; costEmoji = '💲'; }
|
|
39
|
+
else if (todayCost < 5) { costRange = '$1–5/day'; costEmoji = '💵'; }
|
|
40
|
+
else if (todayCost < 10) { costRange = '$5–10/day'; costEmoji = '💰'; }
|
|
41
|
+
else if (todayCost < 25) { costRange = '$10–25/day'; costEmoji = '💰'; }
|
|
42
|
+
else if (todayCost < 50) { costRange = '$25–50/day'; costEmoji = '🔥'; }
|
|
43
|
+
else { costRange = '$50+/day'; costEmoji = '🚨'; }
|
|
44
|
+
|
|
45
|
+
// ── Setup complexity from config ───────────────────────
|
|
46
|
+
const config = analysis.config || {};
|
|
47
|
+
|
|
48
|
+
const channels = [];
|
|
49
|
+
if (config.channels?.whatsapp) channels.push('WhatsApp');
|
|
50
|
+
if (config.channels?.telegram) channels.push('Telegram');
|
|
51
|
+
if (config.channels?.discord) channels.push('Discord');
|
|
52
|
+
if (config.channels?.signal) channels.push('Signal');
|
|
53
|
+
if (config.channels?.slack) channels.push('Slack');
|
|
54
|
+
if (config.webchat || config.channels?.webchat) channels.push('Webchat');
|
|
55
|
+
|
|
56
|
+
const skillCount = config.skills ? Object.keys(config.skills).length : 0;
|
|
57
|
+
const cronCount = config.cron ? Object.keys(config.cron).length : 0;
|
|
58
|
+
const hookCount = config.hooks ? Object.keys(config.hooks).length : 0;
|
|
59
|
+
const agentCount = config.agents?.list ? Object.keys(config.agents.list).length : 1;
|
|
60
|
+
const sessionCount = s.sessionsAnalyzed || 0;
|
|
61
|
+
|
|
62
|
+
const modelSet = new Set();
|
|
63
|
+
for (const sess of (analysis.sessions || [])) {
|
|
64
|
+
if (sess.model || sess.modelLabel) modelSet.add(sess.modelLabel || sess.model);
|
|
65
|
+
}
|
|
66
|
+
const modelCount = modelSet.size || 1;
|
|
67
|
+
|
|
68
|
+
const totalTokens = s.totalTokensFound || 1;
|
|
69
|
+
const cacheEfficiency = Math.round((s.totalCacheRead || 0) / totalTokens * 100);
|
|
70
|
+
|
|
71
|
+
// ── Build stat pills ──────────────────────────────────
|
|
72
|
+
const pills = [];
|
|
73
|
+
if (channels.length > 0) pills.push({ icon: '📱', label: `${channels.length} channel${channels.length>1?'s':''}` });
|
|
74
|
+
if (skillCount > 0) pills.push({ icon: '🔧', label: `${skillCount} skill${skillCount>1?'s':''}` });
|
|
75
|
+
if (cronCount > 0) pills.push({ icon: '⏰', label: `${cronCount} cron${cronCount>1?'s':''}` });
|
|
76
|
+
if (hookCount > 0) pills.push({ icon: '🪝', label: `${hookCount} hook${hookCount>1?'s':''}` });
|
|
77
|
+
if (agentCount > 1) pills.push({ icon: '🤖', label: `${agentCount} agents` });
|
|
78
|
+
if (sessionCount > 0) pills.push({ icon: '💬', label: `${sessionCount} sessions` });
|
|
79
|
+
pills.push({ icon: '🧠', label: `${modelCount} model${modelCount>1?'s':''}` });
|
|
80
|
+
if (cacheEfficiency > 0) pills.push({ icon: '⚡', label: `${cacheEfficiency}% cache` });
|
|
81
|
+
|
|
82
|
+
// ── Findings summary ──────────────────────────────────
|
|
83
|
+
const findingSummary = [];
|
|
84
|
+
if (criticals > 0) findingSummary.push(`🔴 ${criticals} critical`);
|
|
85
|
+
if (highs > 0) findingSummary.push(`🟠 ${highs} high`);
|
|
86
|
+
if (mediums > 0) findingSummary.push(`🟡 ${mediums} medium`);
|
|
87
|
+
|
|
88
|
+
const html = `<!DOCTYPE html>
|
|
89
|
+
<html lang="en">
|
|
90
|
+
<head>
|
|
91
|
+
<meta charset="UTF-8">
|
|
92
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
93
|
+
<title>Clawculator Snapshot</title>
|
|
94
|
+
<style>
|
|
95
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&family=Outfit:wght@400;500;600;700;800;900&display=swap');
|
|
96
|
+
|
|
97
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
98
|
+
|
|
99
|
+
html, body {
|
|
100
|
+
min-height: 100vh;
|
|
101
|
+
font-family: 'Outfit', sans-serif;
|
|
102
|
+
background: #020408;
|
|
103
|
+
color: #e2e8f0;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.card {
|
|
110
|
+
width: 480px; max-width: 96vw;
|
|
111
|
+
background: #05080f;
|
|
112
|
+
border-radius: 24px;
|
|
113
|
+
border: 1px solid #1a2744;
|
|
114
|
+
padding: 32px 28px 24px;
|
|
115
|
+
display: flex; flex-direction: column;
|
|
116
|
+
align-items: center;
|
|
117
|
+
position: relative;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
box-shadow: 0 0 80px rgba(34,211,238,0.04), 0 20px 60px rgba(0,0,0,0.5);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Grid bg */
|
|
123
|
+
.card::before {
|
|
124
|
+
content: '';
|
|
125
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
126
|
+
background:
|
|
127
|
+
linear-gradient(rgba(34,211,238,0.02) 1px, transparent 1px),
|
|
128
|
+
linear-gradient(90deg, rgba(34,211,238,0.02) 1px, transparent 1px);
|
|
129
|
+
background-size: 32px 32px;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.card > * { position: relative; z-index: 1; }
|
|
134
|
+
|
|
135
|
+
/* Header */
|
|
136
|
+
.header {
|
|
137
|
+
display: flex; align-items: center; gap: 10px;
|
|
138
|
+
margin-bottom: 20px;
|
|
139
|
+
}
|
|
140
|
+
.logo-claw { font-size: 28px; }
|
|
141
|
+
.logo-text {
|
|
142
|
+
font-family: 'JetBrains Mono', monospace;
|
|
143
|
+
font-weight: 800; font-size: 18px; letter-spacing: -0.5px;
|
|
144
|
+
background: linear-gradient(135deg, #22d3ee, #818cf8);
|
|
145
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Grade */
|
|
149
|
+
.grade-container {
|
|
150
|
+
display: flex; flex-direction: column; align-items: center;
|
|
151
|
+
margin-bottom: 16px;
|
|
152
|
+
}
|
|
153
|
+
.grade-ring {
|
|
154
|
+
width: 120px; height: 120px;
|
|
155
|
+
border-radius: 50%;
|
|
156
|
+
border: 3px solid ${gradeColor}44;
|
|
157
|
+
display: flex; align-items: center; justify-content: center;
|
|
158
|
+
box-shadow: 0 0 40px ${gradeGlow}, inset 0 0 20px ${gradeGlow};
|
|
159
|
+
position: relative;
|
|
160
|
+
}
|
|
161
|
+
.grade-ring::before {
|
|
162
|
+
content: '';
|
|
163
|
+
position: absolute; inset: -2px;
|
|
164
|
+
border-radius: 50%;
|
|
165
|
+
border: 2px solid ${gradeColor};
|
|
166
|
+
opacity: 0.5;
|
|
167
|
+
}
|
|
168
|
+
.grade-letter {
|
|
169
|
+
font-family: 'JetBrains Mono', monospace;
|
|
170
|
+
font-weight: 900; font-size: 52px;
|
|
171
|
+
color: ${gradeColor};
|
|
172
|
+
text-shadow: 0 0 20px ${gradeGlow};
|
|
173
|
+
line-height: 1;
|
|
174
|
+
}
|
|
175
|
+
.grade-label {
|
|
176
|
+
font-size: 11px; font-weight: 600; color: #64748b;
|
|
177
|
+
text-transform: uppercase; letter-spacing: 1.5px;
|
|
178
|
+
margin-top: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Cost */
|
|
182
|
+
.cost-range {
|
|
183
|
+
font-family: 'JetBrains Mono', monospace;
|
|
184
|
+
font-weight: 700; font-size: 22px;
|
|
185
|
+
color: #e2e8f0;
|
|
186
|
+
margin-bottom: 14px;
|
|
187
|
+
text-align: center;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Divider */
|
|
191
|
+
.divider {
|
|
192
|
+
width: 60px; height: 1px;
|
|
193
|
+
background: linear-gradient(90deg, transparent, #1e3a5f, transparent);
|
|
194
|
+
margin-bottom: 14px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Pills */
|
|
198
|
+
.pills {
|
|
199
|
+
display: flex; flex-wrap: wrap; justify-content: center;
|
|
200
|
+
gap: 6px;
|
|
201
|
+
margin-bottom: 14px;
|
|
202
|
+
max-width: 400px;
|
|
203
|
+
}
|
|
204
|
+
.pill {
|
|
205
|
+
display: flex; align-items: center; gap: 4px;
|
|
206
|
+
background: #0b1120;
|
|
207
|
+
border: 1px solid #1a2744;
|
|
208
|
+
border-radius: 14px;
|
|
209
|
+
padding: 5px 12px;
|
|
210
|
+
font-size: 12px; font-weight: 500;
|
|
211
|
+
color: #94a3b8;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Findings */
|
|
215
|
+
.findings {
|
|
216
|
+
text-align: center;
|
|
217
|
+
margin-bottom: 14px;
|
|
218
|
+
}
|
|
219
|
+
.findings-badges {
|
|
220
|
+
display: flex; justify-content: center; gap: 8px;
|
|
221
|
+
flex-wrap: wrap;
|
|
222
|
+
}
|
|
223
|
+
.findings-badge {
|
|
224
|
+
font-family: 'JetBrains Mono', monospace;
|
|
225
|
+
font-size: 12px; font-weight: 600;
|
|
226
|
+
padding: 4px 10px; border-radius: 6px;
|
|
227
|
+
}
|
|
228
|
+
.findings-clean {
|
|
229
|
+
font-size: 14px; color: #22c55e; font-weight: 600;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* CTA */
|
|
233
|
+
.cta {
|
|
234
|
+
margin-top: auto;
|
|
235
|
+
text-align: center;
|
|
236
|
+
width: 100%;
|
|
237
|
+
}
|
|
238
|
+
.cta-divider {
|
|
239
|
+
width: 100%; height: 1px;
|
|
240
|
+
background: linear-gradient(90deg, transparent, #1a2744, transparent);
|
|
241
|
+
margin-bottom: 14px;
|
|
242
|
+
}
|
|
243
|
+
.cta-prompt {
|
|
244
|
+
font-size: 13px; color: #64748b;
|
|
245
|
+
margin-bottom: 8px;
|
|
246
|
+
}
|
|
247
|
+
.cta-command {
|
|
248
|
+
font-family: 'JetBrains Mono', monospace;
|
|
249
|
+
font-size: 14px; font-weight: 700;
|
|
250
|
+
color: #22d3ee;
|
|
251
|
+
background: #0b1120;
|
|
252
|
+
border: 1px solid #1a2744;
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
padding: 10px 18px;
|
|
255
|
+
display: inline-block;
|
|
256
|
+
margin-bottom: 6px;
|
|
257
|
+
}
|
|
258
|
+
.cta-sub {
|
|
259
|
+
font-size: 10px; color: #334155;
|
|
260
|
+
}
|
|
261
|
+
</style>
|
|
262
|
+
</head>
|
|
263
|
+
<body>
|
|
264
|
+
<div class="card">
|
|
265
|
+
|
|
266
|
+
<div class="header">
|
|
267
|
+
<div class="logo-claw">🦞</div>
|
|
268
|
+
<div class="logo-text">CLAWCULATOR</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="grade-container">
|
|
272
|
+
<div class="grade-ring">
|
|
273
|
+
<div class="grade-letter">${grade}</div>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="grade-label">${gradeEmoji} cost health</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div class="cost-range">${costEmoji} ${costRange}</div>
|
|
279
|
+
|
|
280
|
+
<div class="divider"></div>
|
|
281
|
+
|
|
282
|
+
<div class="pills">
|
|
283
|
+
${pills.map(p => `<div class="pill"><span>${p.icon}</span>${p.label}</div>`).join('\n ')}
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div class="findings">
|
|
287
|
+
${findingSummary.length > 0 ?
|
|
288
|
+
`<div class="findings-badges">${findingSummary.map(f => {
|
|
289
|
+
const c = f.startsWith('🔴') ? '#ef4444' : f.startsWith('🟠') ? '#f97316' : '#eab308';
|
|
290
|
+
return `<div class="findings-badge" style="background:${c}18;color:${c}">${f}</div>`;
|
|
291
|
+
}).join('')}</div>` :
|
|
292
|
+
`<div class="findings-clean">✅ No issues found</div>`
|
|
293
|
+
}
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div class="cta">
|
|
297
|
+
<div class="cta-divider"></div>
|
|
298
|
+
<div class="cta-prompt">Get your OpenClaw cost grade</div>
|
|
299
|
+
<div class="cta-command">npx clawculator --snapshot</div>
|
|
300
|
+
<div class="cta-sub">100% offline · your data never leaves your machine</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
</div>
|
|
304
|
+
</body>
|
|
305
|
+
</html>`;
|
|
306
|
+
|
|
307
|
+
const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
|
|
308
|
+
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
309
|
+
|
|
310
|
+
// Terminal summary
|
|
311
|
+
console.log(`\n ${gradeEmoji} Grade: \x1b[1m${grade}\x1b[0m`);
|
|
312
|
+
console.log(` ${costEmoji} Cost: ${costRange}`);
|
|
313
|
+
console.log(` 📦 Setup: ${pills.map(p => p.label).join(' · ')}`);
|
|
314
|
+
if (findingSummary.length > 0) {
|
|
315
|
+
console.log(` 🔍 ${findingSummary.join(' · ')}`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` ✅ Clean — no issues`);
|
|
318
|
+
}
|
|
319
|
+
console.log('');
|
|
320
|
+
|
|
321
|
+
return { htmlPath, grade, costRange };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { generateSnapshotCard };
|