clawculator 2.8.3 → 2.9.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/bin/clawculator.js +2 -2
- package/package.json +3 -3
- package/skills/clawculator/snapshotCard.js +152 -5
- package/src/snapshotCard.js +152 -5
package/bin/clawculator.js
CHANGED
|
@@ -172,10 +172,10 @@ 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
|
-
|
|
175
|
+
const openFile = result.pngPath || result.htmlPath;
|
|
176
|
+
exec(`open "${openFile}" 2>/dev/null || xdg-open "${openFile}" 2>/dev/null`, () => {
|
|
176
177
|
process.exit(0);
|
|
177
178
|
});
|
|
178
|
-
// Don't exit immediately — give exec time to open browser
|
|
179
179
|
setTimeout(() => process.exit(0), 2000);
|
|
180
180
|
return;
|
|
181
181
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawculator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
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": {},
|
|
34
33
|
"optionalDependencies": {
|
|
35
|
-
"better-sqlite3": "^11.0.0"
|
|
34
|
+
"better-sqlite3": "^11.0.0",
|
|
35
|
+
"canvas": "^3.2.1"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -22,7 +22,7 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
22
22
|
// Exclude: historical session costs, untracked sessions, cache totals, cost gaps
|
|
23
23
|
// These are noise for "what does it cost to run this setup?"
|
|
24
24
|
const configFindings = (analysis.findings || []).filter(f => {
|
|
25
|
-
const t = (f.title || '').toLowerCase();
|
|
25
|
+
const t = (f.title || f.message || '').toLowerCase();
|
|
26
26
|
const src = (f.source || '').toLowerCase();
|
|
27
27
|
// Skip historical/session findings
|
|
28
28
|
if (t.includes('orphaned session')) return false;
|
|
@@ -349,6 +349,149 @@ 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
|
+
|
|
352
495
|
// ── Terminal card (screenshot-ready) ───────────────────
|
|
353
496
|
const C = '\x1b[36m'; // cyan
|
|
354
497
|
const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
|
|
@@ -392,11 +535,15 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
392
535
|
|
|
393
536
|
console.log(lines.join('\n'));
|
|
394
537
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
+
}
|
|
398
545
|
|
|
399
|
-
return { htmlPath, grade, costRange };
|
|
546
|
+
return { htmlPath, pngPath, grade, costRange };
|
|
400
547
|
}
|
|
401
548
|
|
|
402
549
|
module.exports = { generateSnapshotCard };
|
package/src/snapshotCard.js
CHANGED
|
@@ -22,7 +22,7 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
22
22
|
// Exclude: historical session costs, untracked sessions, cache totals, cost gaps
|
|
23
23
|
// These are noise for "what does it cost to run this setup?"
|
|
24
24
|
const configFindings = (analysis.findings || []).filter(f => {
|
|
25
|
-
const t = (f.title || '').toLowerCase();
|
|
25
|
+
const t = (f.title || f.message || '').toLowerCase();
|
|
26
26
|
const src = (f.source || '').toLowerCase();
|
|
27
27
|
// Skip historical/session findings
|
|
28
28
|
if (t.includes('orphaned session')) return false;
|
|
@@ -349,6 +349,149 @@ 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
|
+
|
|
352
495
|
// ── Terminal card (screenshot-ready) ───────────────────
|
|
353
496
|
const C = '\x1b[36m'; // cyan
|
|
354
497
|
const G = gradeColor === '#22c55e' ? '\x1b[32m' : gradeColor === '#f59e0b' ? '\x1b[33m' : gradeColor === '#f97316' ? '\x1b[33m' : '\x1b[31m';
|
|
@@ -392,11 +535,15 @@ function generateSnapshotCard(analysis, outputDir) {
|
|
|
392
535
|
|
|
393
536
|
console.log(lines.join('\n'));
|
|
394
537
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
+
}
|
|
398
545
|
|
|
399
|
-
return { htmlPath, grade, costRange };
|
|
546
|
+
return { htmlPath, pngPath, grade, costRange };
|
|
400
547
|
}
|
|
401
548
|
|
|
402
549
|
module.exports = { generateSnapshotCard };
|