clawculator 2.9.0 → 2.9.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.
@@ -172,8 +172,7 @@ async function main() {
172
172
  const { generateSnapshotCard } = require('../src/snapshotCard');
173
173
  const result = generateSnapshotCard(analysis, outDir);
174
174
  const { exec } = require('child_process');
175
- const openFile = result.pngPath || result.htmlPath;
176
- exec(`open "${openFile}" 2>/dev/null || xdg-open "${openFile}" 2>/dev/null`, () => {
175
+ exec(`open "${result.htmlPath}" 2>/dev/null || xdg-open "${result.htmlPath}" 2>/dev/null`, () => {
177
176
  process.exit(0);
178
177
  });
179
178
  setTimeout(() => process.exit(0), 2000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawculator",
3
- "version": "2.9.0",
3
+ "version": "2.9.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": {
@@ -30,8 +30,8 @@
30
30
  "engines": {
31
31
  "node": ">=18.0.0"
32
32
  },
33
+ "dependencies": {},
33
34
  "optionalDependencies": {
34
- "better-sqlite3": "^11.0.0",
35
- "canvas": "^3.2.1"
35
+ "better-sqlite3": "^11.0.0"
36
36
  }
37
37
  }
@@ -349,149 +349,6 @@ function generateSnapshotCard(analysis, outputDir) {
349
349
  const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
350
350
  fs.writeFileSync(htmlPath, html, 'utf8');
351
351
 
352
- // ── Generate PNG card ──────────────────────────────────
353
- let pngPath = null;
354
- try {
355
- const { createCanvas } = require('canvas');
356
- const W = 1080, H = 1080;
357
- const canvas = createCanvas(W, H);
358
- const ctx = canvas.getContext('2d');
359
-
360
- // Background
361
- ctx.fillStyle = '#05080f';
362
- ctx.fillRect(0, 0, W, H);
363
-
364
- // Grid lines
365
- ctx.strokeStyle = 'rgba(34,211,238,0.03)';
366
- ctx.lineWidth = 1;
367
- for (let x = 0; x < W; x += 54) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
368
- for (let y = 0; y < H; y += 54) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
369
-
370
- // Border
371
- ctx.strokeStyle = '#1a2744';
372
- ctx.lineWidth = 2;
373
- ctx.roundRect(24, 24, W - 48, H - 48, 24);
374
- ctx.stroke();
375
-
376
- // ── Header
377
- ctx.font = 'bold 36px sans-serif';
378
- ctx.fillStyle = '#22d3ee';
379
- ctx.textAlign = 'center';
380
- ctx.fillText('CLAWCULATOR', W / 2 + 20, 100);
381
-
382
- // Lobster emoji substitute
383
- ctx.font = '44px sans-serif';
384
- ctx.fillText('🦞', W / 2 - 160, 102);
385
-
386
- // ── Grade circle
387
- const cx = W / 2, cy = 280, r = 100;
388
- // Outer glow
389
- ctx.beginPath(); ctx.arc(cx, cy, r + 8, 0, Math.PI * 2);
390
- ctx.fillStyle = gradeGlow; ctx.fill();
391
- // Ring
392
- ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2);
393
- ctx.strokeStyle = gradeColor + '66'; ctx.lineWidth = 4; ctx.stroke();
394
- ctx.beginPath(); ctx.arc(cx, cy, r - 3, 0, Math.PI * 2);
395
- ctx.strokeStyle = gradeColor + 'aa'; ctx.lineWidth = 2; ctx.stroke();
396
- // Grade letter
397
- ctx.font = 'bold 72px sans-serif';
398
- ctx.fillStyle = gradeColor;
399
- ctx.textAlign = 'center';
400
- ctx.textBaseline = 'middle';
401
- ctx.fillText(grade, cx, cy);
402
- ctx.textBaseline = 'alphabetic';
403
- // Label
404
- ctx.font = '16px sans-serif';
405
- ctx.fillStyle = '#64748b';
406
- ctx.fillText('COST HEALTH GRADE', cx, cy + r + 30);
407
-
408
- // ── Cost range
409
- ctx.font = 'bold 36px sans-serif';
410
- ctx.fillStyle = '#e2e8f0';
411
- ctx.fillText(costEmoji + ' ' + costRange, cx, 460);
412
-
413
- // ── Divider
414
- ctx.beginPath();
415
- ctx.moveTo(W / 2 - 60, 500);
416
- ctx.lineTo(W / 2 + 60, 500);
417
- ctx.strokeStyle = '#1e3a5f'; ctx.lineWidth = 1; ctx.stroke();
418
-
419
- // ── Pills
420
- ctx.font = '20px sans-serif';
421
- ctx.fillStyle = '#94a3b8';
422
- const pillLines = [];
423
- let currentLine = '';
424
- for (const p of pills) {
425
- const item = p.icon + ' ' + p.label;
426
- const test = currentLine ? currentLine + ' ' + item : item;
427
- if (test.length > 45 && currentLine) {
428
- pillLines.push(currentLine);
429
- currentLine = item;
430
- } else {
431
- currentLine = test;
432
- }
433
- }
434
- if (currentLine) pillLines.push(currentLine);
435
-
436
- let pillY = 545;
437
- for (const line of pillLines) {
438
- ctx.fillText(line, cx, pillY);
439
- pillY += 32;
440
- }
441
-
442
- // ── Findings
443
- const findY = pillY + 30;
444
- if (findingSummary.length > 0) {
445
- ctx.font = 'bold 22px sans-serif';
446
- findingSummary.forEach((f, i) => {
447
- const c = f.startsWith('🔴') ? '#ef4444' : f.startsWith('🟠') ? '#f97316' : '#eab308';
448
- ctx.fillStyle = c;
449
- ctx.fillText(f, cx, findY + i * 32);
450
- });
451
- } else {
452
- ctx.font = 'bold 22px sans-serif';
453
- ctx.fillStyle = '#22c55e';
454
- ctx.fillText('✅ No issues found', cx, findY);
455
- }
456
-
457
- // ── Divider 2
458
- const divY2 = findY + (findingSummary.length > 0 ? findingSummary.length * 32 + 20 : 50);
459
- ctx.beginPath();
460
- ctx.moveTo(80, divY2);
461
- ctx.lineTo(W - 80, divY2);
462
- ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 1; ctx.stroke();
463
-
464
- // ── CTA
465
- ctx.font = '18px sans-serif';
466
- ctx.fillStyle = '#64748b';
467
- ctx.fillText('Get your OpenClaw cost grade', cx, divY2 + 40);
468
-
469
- // Command box
470
- const cmdY = divY2 + 70;
471
- const cmdW = 380, cmdH = 44;
472
- ctx.fillStyle = '#0b1120';
473
- ctx.strokeStyle = '#1a2744';
474
- ctx.lineWidth = 1;
475
- ctx.roundRect(cx - cmdW / 2, cmdY - cmdH / 2, cmdW, cmdH, 8);
476
- ctx.fill(); ctx.stroke();
477
-
478
- ctx.font = 'bold 20px monospace';
479
- ctx.fillStyle = '#22d3ee';
480
- ctx.fillText('npx clawculator --snapshot', cx, cmdY + 6);
481
-
482
- // Subtitle
483
- ctx.font = '13px sans-serif';
484
- ctx.fillStyle = '#334155';
485
- ctx.fillText('100% offline · your data never leaves your machine', cx, cmdY + 38);
486
-
487
- // Write PNG
488
- pngPath = path.join(outputDir, 'clawculator-snapshot.png');
489
- fs.writeFileSync(pngPath, canvas.toBuffer('image/png'));
490
- } catch (e) {
491
- // canvas not installed — PNG generation skipped, HTML still works
492
- pngPath = null;
493
- }
494
-
495
352
  // ── Terminal card (screenshot-ready) ───────────────────
496
353
  const C = '\x1b[36m'; // cyan
497
354
  const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
@@ -535,15 +392,10 @@ function generateSnapshotCard(analysis, outputDir) {
535
392
 
536
393
  console.log(lines.join('\n'));
537
394
 
538
- if (pngPath) {
539
- console.log(` \x1b[32m✓ PNG card saved:\x1b[0m ${pngPath}`);
540
- console.log(` \x1b[90mDrag into Twitter, Discord, or any social feed!\x1b[0m\n`);
541
- } else {
542
- console.log(` \x1b[90mHTML card saved: ${htmlPath}\x1b[0m`);
543
- console.log(` \x1b[90mTip: npm install canvas — to auto-generate a PNG for sharing\x1b[0m\n`);
544
- }
395
+ console.log(` \x1b[32m✓\x1b[0m Card saved: ${htmlPath}`);
396
+ console.log(` \x1b[90mOpen it screenshot → drag into any tweet or post\x1b[0m\n`);
545
397
 
546
- return { htmlPath, pngPath, grade, costRange };
398
+ return { htmlPath, grade, costRange };
547
399
  }
548
400
 
549
401
  module.exports = { generateSnapshotCard };
@@ -349,149 +349,6 @@ function generateSnapshotCard(analysis, outputDir) {
349
349
  const htmlPath = path.join(outputDir, 'clawculator-snapshot.html');
350
350
  fs.writeFileSync(htmlPath, html, 'utf8');
351
351
 
352
- // ── Generate PNG card ──────────────────────────────────
353
- let pngPath = null;
354
- try {
355
- const { createCanvas } = require('canvas');
356
- const W = 1080, H = 1080;
357
- const canvas = createCanvas(W, H);
358
- const ctx = canvas.getContext('2d');
359
-
360
- // Background
361
- ctx.fillStyle = '#05080f';
362
- ctx.fillRect(0, 0, W, H);
363
-
364
- // Grid lines
365
- ctx.strokeStyle = 'rgba(34,211,238,0.03)';
366
- ctx.lineWidth = 1;
367
- for (let x = 0; x < W; x += 54) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
368
- for (let y = 0; y < H; y += 54) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
369
-
370
- // Border
371
- ctx.strokeStyle = '#1a2744';
372
- ctx.lineWidth = 2;
373
- ctx.roundRect(24, 24, W - 48, H - 48, 24);
374
- ctx.stroke();
375
-
376
- // ── Header
377
- ctx.font = 'bold 36px sans-serif';
378
- ctx.fillStyle = '#22d3ee';
379
- ctx.textAlign = 'center';
380
- ctx.fillText('CLAWCULATOR', W / 2 + 20, 100);
381
-
382
- // Lobster emoji substitute
383
- ctx.font = '44px sans-serif';
384
- ctx.fillText('🦞', W / 2 - 160, 102);
385
-
386
- // ── Grade circle
387
- const cx = W / 2, cy = 280, r = 100;
388
- // Outer glow
389
- ctx.beginPath(); ctx.arc(cx, cy, r + 8, 0, Math.PI * 2);
390
- ctx.fillStyle = gradeGlow; ctx.fill();
391
- // Ring
392
- ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2);
393
- ctx.strokeStyle = gradeColor + '66'; ctx.lineWidth = 4; ctx.stroke();
394
- ctx.beginPath(); ctx.arc(cx, cy, r - 3, 0, Math.PI * 2);
395
- ctx.strokeStyle = gradeColor + 'aa'; ctx.lineWidth = 2; ctx.stroke();
396
- // Grade letter
397
- ctx.font = 'bold 72px sans-serif';
398
- ctx.fillStyle = gradeColor;
399
- ctx.textAlign = 'center';
400
- ctx.textBaseline = 'middle';
401
- ctx.fillText(grade, cx, cy);
402
- ctx.textBaseline = 'alphabetic';
403
- // Label
404
- ctx.font = '16px sans-serif';
405
- ctx.fillStyle = '#64748b';
406
- ctx.fillText('COST HEALTH GRADE', cx, cy + r + 30);
407
-
408
- // ── Cost range
409
- ctx.font = 'bold 36px sans-serif';
410
- ctx.fillStyle = '#e2e8f0';
411
- ctx.fillText(costEmoji + ' ' + costRange, cx, 460);
412
-
413
- // ── Divider
414
- ctx.beginPath();
415
- ctx.moveTo(W / 2 - 60, 500);
416
- ctx.lineTo(W / 2 + 60, 500);
417
- ctx.strokeStyle = '#1e3a5f'; ctx.lineWidth = 1; ctx.stroke();
418
-
419
- // ── Pills
420
- ctx.font = '20px sans-serif';
421
- ctx.fillStyle = '#94a3b8';
422
- const pillLines = [];
423
- let currentLine = '';
424
- for (const p of pills) {
425
- const item = p.icon + ' ' + p.label;
426
- const test = currentLine ? currentLine + ' ' + item : item;
427
- if (test.length > 45 && currentLine) {
428
- pillLines.push(currentLine);
429
- currentLine = item;
430
- } else {
431
- currentLine = test;
432
- }
433
- }
434
- if (currentLine) pillLines.push(currentLine);
435
-
436
- let pillY = 545;
437
- for (const line of pillLines) {
438
- ctx.fillText(line, cx, pillY);
439
- pillY += 32;
440
- }
441
-
442
- // ── Findings
443
- const findY = pillY + 30;
444
- if (findingSummary.length > 0) {
445
- ctx.font = 'bold 22px sans-serif';
446
- findingSummary.forEach((f, i) => {
447
- const c = f.startsWith('🔴') ? '#ef4444' : f.startsWith('🟠') ? '#f97316' : '#eab308';
448
- ctx.fillStyle = c;
449
- ctx.fillText(f, cx, findY + i * 32);
450
- });
451
- } else {
452
- ctx.font = 'bold 22px sans-serif';
453
- ctx.fillStyle = '#22c55e';
454
- ctx.fillText('✅ No issues found', cx, findY);
455
- }
456
-
457
- // ── Divider 2
458
- const divY2 = findY + (findingSummary.length > 0 ? findingSummary.length * 32 + 20 : 50);
459
- ctx.beginPath();
460
- ctx.moveTo(80, divY2);
461
- ctx.lineTo(W - 80, divY2);
462
- ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 1; ctx.stroke();
463
-
464
- // ── CTA
465
- ctx.font = '18px sans-serif';
466
- ctx.fillStyle = '#64748b';
467
- ctx.fillText('Get your OpenClaw cost grade', cx, divY2 + 40);
468
-
469
- // Command box
470
- const cmdY = divY2 + 70;
471
- const cmdW = 380, cmdH = 44;
472
- ctx.fillStyle = '#0b1120';
473
- ctx.strokeStyle = '#1a2744';
474
- ctx.lineWidth = 1;
475
- ctx.roundRect(cx - cmdW / 2, cmdY - cmdH / 2, cmdW, cmdH, 8);
476
- ctx.fill(); ctx.stroke();
477
-
478
- ctx.font = 'bold 20px monospace';
479
- ctx.fillStyle = '#22d3ee';
480
- ctx.fillText('npx clawculator --snapshot', cx, cmdY + 6);
481
-
482
- // Subtitle
483
- ctx.font = '13px sans-serif';
484
- ctx.fillStyle = '#334155';
485
- ctx.fillText('100% offline · your data never leaves your machine', cx, cmdY + 38);
486
-
487
- // Write PNG
488
- pngPath = path.join(outputDir, 'clawculator-snapshot.png');
489
- fs.writeFileSync(pngPath, canvas.toBuffer('image/png'));
490
- } catch (e) {
491
- // canvas not installed — PNG generation skipped, HTML still works
492
- pngPath = null;
493
- }
494
-
495
352
  // ── Terminal card (screenshot-ready) ───────────────────
496
353
  const C = '\x1b[36m'; // cyan
497
354
  const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
@@ -535,15 +392,10 @@ function generateSnapshotCard(analysis, outputDir) {
535
392
 
536
393
  console.log(lines.join('\n'));
537
394
 
538
- if (pngPath) {
539
- console.log(` \x1b[32m✓ PNG card saved:\x1b[0m ${pngPath}`);
540
- console.log(` \x1b[90mDrag into Twitter, Discord, or any social feed!\x1b[0m\n`);
541
- } else {
542
- console.log(` \x1b[90mHTML card saved: ${htmlPath}\x1b[0m`);
543
- console.log(` \x1b[90mTip: npm install canvas — to auto-generate a PNG for sharing\x1b[0m\n`);
544
- }
395
+ console.log(` \x1b[32m✓\x1b[0m Card saved: ${htmlPath}`);
396
+ console.log(` \x1b[90mOpen it screenshot → drag into any tweet or post\x1b[0m\n`);
545
397
 
546
- return { htmlPath, pngPath, grade, costRange };
398
+ return { htmlPath, grade, costRange };
547
399
  }
548
400
 
549
401
  module.exports = { generateSnapshotCard };