clawculator 2.1.0 → 2.1.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawculator",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
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": {
@@ -5,15 +5,15 @@ const path = require('path');
5
5
  const os = require('os');
6
6
 
7
7
  function severityColor(severity) {
8
- return { critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#38bdf8', info: '#22c55e' }[severity] || '#6b7280';
8
+ return { critical: '#ef4444', high: '#f97316', medium: '#eab308', info: '#22c55e' }[severity] || '#6b7280';
9
9
  }
10
10
 
11
11
  function severityBg(severity) {
12
- return { critical: '#fef2f2', high: '#fff7ed', medium: '#fefce8', low: '#f0f9ff', info: '#f0fdf4' }[severity] || '#f9fafb';
12
+ return { critical: '#fef2f2', high: '#fff7ed', medium: '#fefce8', info: '#f0fdf4' }[severity] || '#f9fafb';
13
13
  }
14
14
 
15
15
  function severityIcon(severity) {
16
- return { critical: '🔴', high: '🟠', medium: '🟡', low: '🔵', info: '✅' }[severity] || '⚪';
16
+ return { critical: '🔴', high: '🟠', medium: '🟡', info: '✅' }[severity] || '⚪';
17
17
  }
18
18
 
19
19
  function relativeAge(ageMs) {
@@ -50,8 +50,7 @@ async function generateHTMLReport(analysis, outPath) {
50
50
  </div>
51
51
  <div style="font-weight:600; color:#111; margin-bottom:4px">${f.message}</div>
52
52
  ${f.detail ? `<div style="color:#555; font-size:14px; margin-bottom:6px; white-space:pre-line">${f.detail}</div>` : ''}
53
- ${f.fix ? `<div style="color:#16a34a; font-size:14px; margin-top:8px">→ Fix: ${f.fix}</div>` : ''}
54
- ${f.command ? `<div style="background:#0f172a; color:#7dd3fc; font-family:monospace; font-size:12px; padding:8px 12px; border-radius:6px; margin-top:8px">${f.command}</div>` : ''}
53
+ ${f.recommendation ? `<div style="color:#16a34a; font-size:14px; margin-top:8px">→ Fix: ${f.recommendation}</div>` : ''}
55
54
  </div>
56
55
  `).join('');
57
56
 
@@ -59,19 +58,19 @@ async function generateHTMLReport(analysis, outPath) {
59
58
  .sort((a, b) => (b.inputTokens + b.outputTokens) - (a.inputTokens + a.outputTokens))
60
59
  .slice(0, 20)
61
60
  .map(s => {
62
- const keyDisplay = s.key.length > 18 ? s.key.slice(0, 16) + '…' : s.key;
61
+ const keyDisplay = s.key.length > 12 ? s.key.slice(0, 8) + '…' : s.key;
63
62
  const flag = s.isOrphaned ? ' ⚠️' : '';
64
63
  const age = s.ageMs ? relativeAge(s.ageMs) : 'unknown';
65
64
  const absDate = s.updatedAt ? new Date(s.updatedAt).toLocaleString() : '';
66
65
  const daily = s.dailyCost ? `$${s.dailyCost.toFixed(4)}/day` : '—';
67
66
  return `
68
- <tr style="${s.isOrphaned ? 'background:#fff7ed; color:#111;' : ''}">
69
- <td style="padding:8px 12px; font-family:monospace; font-size:13px; ${s.isOrphaned ? 'color:#111;' : ''}">${keyDisplay}${flag}</td>
70
- <td style="padding:8px 12px; ${s.isOrphaned ? 'color:#111;' : ''}">${s.modelLabel || s.model}</td>
71
- <td style="padding:8px 12px; text-align:right; ${s.isOrphaned ? 'color:#111;' : ''}">${(s.inputTokens + s.outputTokens).toLocaleString()}</td>
67
+ <tr style="${s.isOrphaned ? 'background:#fff7ed' : ''}">
68
+ <td style="padding:8px 12px; font-family:monospace; font-size:13px">${keyDisplay}${flag}</td>
69
+ <td style="padding:8px 12px">${s.modelLabel || s.model}</td>
70
+ <td style="padding:8px 12px; text-align:right">${(s.inputTokens + s.outputTokens).toLocaleString()}</td>
72
71
  <td style="padding:8px 12px; text-align:right; color:${s.cost > 0.01 ? '#ef4444' : '#22c55e'}">$${s.cost.toFixed(6)}</td>
73
72
  <td style="padding:8px 12px; text-align:right; color:#f59e0b">${daily}</td>
74
- <td style="padding:8px 12px; color:#94a3b8; font-size:13px; ${s.isOrphaned ? 'color:#6b7280;' : ''}" title="${absDate}">${age}</td>
73
+ <td style="padding:8px 12px; color:#6b7280; font-size:13px" title="${absDate}">${age}</td>
75
74
  </tr>
76
75
  `}).join('');
77
76
 
@@ -88,16 +87,14 @@ async function generateHTMLReport(analysis, outPath) {
88
87
  .logo { font-size: 42px; font-weight: 900; letter-spacing: -2px; background: linear-gradient(90deg, #38bdf8, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
89
88
  .tagline { color: #94a3b8; margin-top: 8px; font-size: 16px; }
90
89
  .container { max-width: 1000px; margin: 0 auto; padding: 32px 24px; }
91
- .cards { display: table; width: 100%; border-spacing: 16px; border-collapse: separate; margin-bottom: 16px; }
92
- .cards-row { display: table-row; }
93
- .card { display: table-cell; background: #1e293b; border-radius: 12px; padding: 20px; border: 1px solid #334155; width: 16.6%; }
90
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
91
+ .card { background: #1e293b; border-radius: 12px; padding: 20px; border: 1px solid #334155; }
94
92
  .card-value { font-size: 32px; font-weight: 800; }
95
93
  .card-label { font-size: 13px; color: #94a3b8; margin-top: 4px; }
96
94
  .section { background: #1e293b; border-radius: 12px; padding: 24px; margin-bottom: 24px; border: 1px solid #334155; }
97
95
  .section-title { font-size: 18px; font-weight: 700; margin-bottom: 16px; color: #f1f5f9; }
98
96
  table { width: 100%; border-collapse: collapse; }
99
- th { background: #0f172a; padding: 10px 12px; text-align: left; font-size: 13px; color: #cbd5e1; font-weight: 600; }
100
- td { color: #e2e8f0; }
97
+ th { background: #0f172a; padding: 10px 12px; text-align: left; font-size: 13px; color: #94a3b8; }
101
98
  tr:nth-child(even) { background: #0f172a33; }
102
99
  .footer { text-align: center; color: #475569; font-size: 13px; padding: 32px; }
103
100
  .bleed { background: linear-gradient(135deg, #7f1d1d, #991b1b); border-radius: 12px; padding: 20px 24px; margin-bottom: 24px; border: 1px solid #ef4444; }
@@ -122,7 +119,7 @@ async function generateHTMLReport(analysis, outPath) {
122
119
  <div style="color:#86efac; font-size:18px; font-weight:700">✅ No significant cost bleed detected</div>
123
120
  </div>`}
124
121
 
125
- <div class="cards"><div class="cards-row">
122
+ <div class="cards">
126
123
  <div class="card">
127
124
  <div class="card-value" style="color:#ef4444">${summary.critical}</div>
128
125
  <div class="card-label">🔴 Critical</div>
@@ -135,10 +132,6 @@ async function generateHTMLReport(analysis, outPath) {
135
132
  <div class="card-value" style="color:#eab308">${summary.medium}</div>
136
133
  <div class="card-label">🟡 Medium</div>
137
134
  </div>
138
- <div class="card">
139
- <div class="card-value" style="color:#38bdf8">${summary.low || 0}</div>
140
- <div class="card-label">🔵 Low</div>
141
- </div>
142
135
  <div class="card">
143
136
  <div class="card-value" style="color:#22c55e">${summary.info}</div>
144
137
  <div class="card-label">✅ OK</div>
@@ -151,7 +144,7 @@ async function generateHTMLReport(analysis, outPath) {
151
144
  <div class="card-value" style="color:#818cf8">${(summary.totalTokensFound || 0).toLocaleString()}</div>
152
145
  <div class="card-label">Total Tokens Found</div>
153
146
  </div>
154
- </div></div>
147
+ </div>
155
148
 
156
149
  <div class="section">
157
150
  <div class="section-title">Findings</div>
@@ -178,9 +171,11 @@ async function generateHTMLReport(analysis, outPath) {
178
171
  </body>
179
172
  </html>`;
180
173
 
181
- const finalPath = outPath || path.join(os.tmpdir(), `clawculator-report-${Date.now()}.html`);
182
- fs.writeFileSync(finalPath, html, 'utf8');
183
- return finalPath;
174
+ if (!outPath) {
175
+ outPath = path.join(os.tmpdir(), `clawculator-report-${Date.now()}.html`);
176
+ }
177
+ fs.writeFileSync(outPath, html, 'utf8');
178
+ return outPath;
184
179
  }
185
180
 
186
181
  module.exports = { generateHTMLReport };