clawculator 2.7.0 → 2.8.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/package.json +1 -1
- package/skills/clawculator/snapshotCard.js +324 -0
- package/src/snapshotCard.js +324 -0
package/package.json
CHANGED
|
@@ -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 };
|