clawculator 2.3.1 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawculator",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "AI cost forensics for OpenClaw and multi-model setups. Your friendly penny pincher. 100% offline. Zero AI. Pure deterministic logic.",
5
5
  "main": "src/analyzer.js",
6
6
  "bin": {
@@ -69,7 +69,8 @@ async function generateHTMLReport(analysis, outPath) {
69
69
  </div>
70
70
  <div style="font-weight:600; color:#111; margin-bottom:4px">${f.message}</div>
71
71
  ${f.detail ? `<div style="color:#555; font-size:14px; margin-bottom:6px; white-space:pre-line">${f.detail}</div>` : ''}
72
- ${f.recommendation ? `<div style="color:#16a34a; font-size:14px; margin-top:8px">→ Fix: ${f.recommendation}</div>` : ''}
72
+ ${f.fix ? `<div style="color:#16a34a; font-size:14px; margin-top:8px;">→ ${f.fix}</div>` : ''}
73
+ ${f.command ? `<div style="background:#0f172a; color:#38bdf8; font-family:monospace; font-size:13px; padding:10px 14px; border-radius:6px; margin-top:6px; white-space:pre-wrap; border:1px solid #1e293b;"><span style="color:#6b7280; user-select:none;">$ </span>${f.command}</div>` : ''}
73
74
  </div>
74
75
  `).join('');
75
76
 
@@ -243,7 +244,60 @@ async function generateHTMLReport(analysis, outPath) {
243
244
  ${untrackedHidden > 0 ? `<div style="margin-top:8px; font-size:12px; color:#64748b;">+ ${untrackedHidden} more not shown</div>` : ''}
244
245
  </div>` : ''}
245
246
 
246
- ${burnSummary} : ''}
247
+ ${burnSummary}
248
+
249
+ <div class="section">
250
+ <div class="section-title">Summary</div>
251
+ <div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:16px;">
252
+ <div>
253
+ <div style="font-size:13px; color:#94a3b8;">Severity Breakdown</div>
254
+ <div style="margin-top:4px;">🔴 ${summary.critical} critical · 🟠 ${summary.high} high · 🟡 ${summary.medium} medium · 🔵 ${summary.low||0} low · ✅ ${summary.info} ok</div>
255
+ </div>
256
+ <div>
257
+ <div style="font-size:13px; color:#94a3b8;">Sessions Analyzed</div>
258
+ <div style="margin-top:4px; font-size:18px; font-weight:700;">${summary.sessionsAnalyzed}</div>
259
+ </div>
260
+ <div>
261
+ <div style="font-size:13px; color:#94a3b8;">Total Tokens</div>
262
+ <div style="margin-top:4px; font-size:18px; font-weight:700;">${(summary.totalTokensFound||0).toLocaleString()}</div>
263
+ </div>
264
+ ${summary.todayCost > 0 ? `<div>
265
+ <div style="font-size:13px; color:#94a3b8;">Today's Spend</div>
266
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#ef4444;">$${summary.todayCost.toFixed(2)}</div>
267
+ </div>` : ''}
268
+ <div>
269
+ <div style="font-size:13px; color:#94a3b8;">All-Time Spend</div>
270
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#fbbf24;">$${summary.totalRealCost.toFixed(2)}</div>
271
+ </div>
272
+ ${summary.totalCacheRead > 0 ? `<div>
273
+ <div style="font-size:13px; color:#94a3b8;">Cache Tokens</div>
274
+ <div style="margin-top:4px; font-size:14px;">${(summary.totalCacheRead||0).toLocaleString()} read<br/>${(summary.totalCacheWrite||0).toLocaleString()} write</div>
275
+ </div>` : ''}
276
+ ${summary.totalEstimatedCost > 0 && summary.totalRealCost > summary.totalEstimatedCost * 1.1 ? `<div>
277
+ <div style="font-size:13px; color:#94a3b8;">sessions.json Gap</div>
278
+ <div style="margin-top:4px; font-size:14px; color:#f59e0b;">Estimate: $${summary.totalEstimatedCost.toFixed(4)}<br/>${(summary.totalRealCost / summary.totalEstimatedCost).toFixed(1)}x under-reported</div>
279
+ </div>` : ''}
280
+ ${bleed > 0 ? `<div>
281
+ <div style="font-size:13px; color:#94a3b8;">Monthly Bleed</div>
282
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#ef4444;">$${bleed.toFixed(2)}/mo</div>
283
+ </div>` : ''}
284
+ </div>
285
+ </div>
286
+
287
+ ${(() => {
288
+ const wins = findings.filter(f => f.fix && f.severity !== 'info');
289
+ if (!wins.length) return '';
290
+ return `
291
+ <div class="section" style="border:1px solid #22c55e;">
292
+ <div class="section-title" style="color:#22c55e;">⚡ Quick Wins</div>
293
+ ${wins.slice(0, 5).map((f, i) => `
294
+ <div style="margin-bottom:16px;">
295
+ <div style="color:#22c55e; font-weight:600; margin-bottom:4px;">${i+1}. ${f.fix}</div>
296
+ ${f.command ? `<div style="background:#0f172a; color:#38bdf8; font-family:monospace; font-size:13px; padding:10px 14px; border-radius:6px; white-space:pre-wrap; border:1px solid #1e293b;"><span style="color:#6b7280; user-select:none;">$ </span>${f.command}</div>` : ''}
297
+ </div>
298
+ `).join('')}
299
+ </div>`;
300
+ })()}
247
301
 
248
302
  </div>
249
303
  <div class="footer">
package/src/htmlReport.js CHANGED
@@ -69,7 +69,8 @@ async function generateHTMLReport(analysis, outPath) {
69
69
  </div>
70
70
  <div style="font-weight:600; color:#111; margin-bottom:4px">${f.message}</div>
71
71
  ${f.detail ? `<div style="color:#555; font-size:14px; margin-bottom:6px; white-space:pre-line">${f.detail}</div>` : ''}
72
- ${f.recommendation ? `<div style="color:#16a34a; font-size:14px; margin-top:8px">→ Fix: ${f.recommendation}</div>` : ''}
72
+ ${f.fix ? `<div style="color:#16a34a; font-size:14px; margin-top:8px;">→ ${f.fix}</div>` : ''}
73
+ ${f.command ? `<div style="background:#0f172a; color:#38bdf8; font-family:monospace; font-size:13px; padding:10px 14px; border-radius:6px; margin-top:6px; white-space:pre-wrap; border:1px solid #1e293b;"><span style="color:#6b7280; user-select:none;">$ </span>${f.command}</div>` : ''}
73
74
  </div>
74
75
  `).join('');
75
76
 
@@ -243,7 +244,60 @@ async function generateHTMLReport(analysis, outPath) {
243
244
  ${untrackedHidden > 0 ? `<div style="margin-top:8px; font-size:12px; color:#64748b;">+ ${untrackedHidden} more not shown</div>` : ''}
244
245
  </div>` : ''}
245
246
 
246
- ${burnSummary} : ''}
247
+ ${burnSummary}
248
+
249
+ <div class="section">
250
+ <div class="section-title">Summary</div>
251
+ <div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:16px;">
252
+ <div>
253
+ <div style="font-size:13px; color:#94a3b8;">Severity Breakdown</div>
254
+ <div style="margin-top:4px;">🔴 ${summary.critical} critical · 🟠 ${summary.high} high · 🟡 ${summary.medium} medium · 🔵 ${summary.low||0} low · ✅ ${summary.info} ok</div>
255
+ </div>
256
+ <div>
257
+ <div style="font-size:13px; color:#94a3b8;">Sessions Analyzed</div>
258
+ <div style="margin-top:4px; font-size:18px; font-weight:700;">${summary.sessionsAnalyzed}</div>
259
+ </div>
260
+ <div>
261
+ <div style="font-size:13px; color:#94a3b8;">Total Tokens</div>
262
+ <div style="margin-top:4px; font-size:18px; font-weight:700;">${(summary.totalTokensFound||0).toLocaleString()}</div>
263
+ </div>
264
+ ${summary.todayCost > 0 ? `<div>
265
+ <div style="font-size:13px; color:#94a3b8;">Today's Spend</div>
266
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#ef4444;">$${summary.todayCost.toFixed(2)}</div>
267
+ </div>` : ''}
268
+ <div>
269
+ <div style="font-size:13px; color:#94a3b8;">All-Time Spend</div>
270
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#fbbf24;">$${summary.totalRealCost.toFixed(2)}</div>
271
+ </div>
272
+ ${summary.totalCacheRead > 0 ? `<div>
273
+ <div style="font-size:13px; color:#94a3b8;">Cache Tokens</div>
274
+ <div style="margin-top:4px; font-size:14px;">${(summary.totalCacheRead||0).toLocaleString()} read<br/>${(summary.totalCacheWrite||0).toLocaleString()} write</div>
275
+ </div>` : ''}
276
+ ${summary.totalEstimatedCost > 0 && summary.totalRealCost > summary.totalEstimatedCost * 1.1 ? `<div>
277
+ <div style="font-size:13px; color:#94a3b8;">sessions.json Gap</div>
278
+ <div style="margin-top:4px; font-size:14px; color:#f59e0b;">Estimate: $${summary.totalEstimatedCost.toFixed(4)}<br/>${(summary.totalRealCost / summary.totalEstimatedCost).toFixed(1)}x under-reported</div>
279
+ </div>` : ''}
280
+ ${bleed > 0 ? `<div>
281
+ <div style="font-size:13px; color:#94a3b8;">Monthly Bleed</div>
282
+ <div style="margin-top:4px; font-size:18px; font-weight:700; color:#ef4444;">$${bleed.toFixed(2)}/mo</div>
283
+ </div>` : ''}
284
+ </div>
285
+ </div>
286
+
287
+ ${(() => {
288
+ const wins = findings.filter(f => f.fix && f.severity !== 'info');
289
+ if (!wins.length) return '';
290
+ return `
291
+ <div class="section" style="border:1px solid #22c55e;">
292
+ <div class="section-title" style="color:#22c55e;">⚡ Quick Wins</div>
293
+ ${wins.slice(0, 5).map((f, i) => `
294
+ <div style="margin-bottom:16px;">
295
+ <div style="color:#22c55e; font-weight:600; margin-bottom:4px;">${i+1}. ${f.fix}</div>
296
+ ${f.command ? `<div style="background:#0f172a; color:#38bdf8; font-family:monospace; font-size:13px; padding:10px 14px; border-radius:6px; white-space:pre-wrap; border:1px solid #1e293b;"><span style="color:#6b7280; user-select:none;">$ </span>${f.command}</div>` : ''}
297
+ </div>
298
+ `).join('')}
299
+ </div>`;
300
+ })()}
247
301
 
248
302
  </div>
249
303
  <div class="footer">