llm-usage-metrics 0.3.7 → 0.4.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/README.md +9 -0
- package/dist/index.js +684 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1014,6 +1014,13 @@ function hasUsageSignal(usage) {
|
|
|
1014
1014
|
function deriveDeltaUsage(info, previousTotalUsage) {
|
|
1015
1015
|
const totalUsage = toUsage(info.total_token_usage);
|
|
1016
1016
|
const lastUsage = toUsage(info.last_token_usage);
|
|
1017
|
+
if (totalUsage && previousTotalUsage) {
|
|
1018
|
+
const deltaFromTotals = subtractUsage(totalUsage, previousTotalUsage);
|
|
1019
|
+
if (hasUsageSignal(deltaFromTotals)) {
|
|
1020
|
+
return { deltaUsage: deltaFromTotals, latestTotalUsage: totalUsage };
|
|
1021
|
+
}
|
|
1022
|
+
return { latestTotalUsage: totalUsage };
|
|
1023
|
+
}
|
|
1017
1024
|
if (lastUsage) {
|
|
1018
1025
|
return { deltaUsage: lastUsage, latestTotalUsage: totalUsage };
|
|
1019
1026
|
}
|
|
@@ -2452,6 +2459,214 @@ function createDefaultAdapters(options) {
|
|
|
2452
2459
|
return sourceRegistrations.map((source) => source.create(options, sourceDirectoryOverrides));
|
|
2453
2460
|
}
|
|
2454
2461
|
|
|
2462
|
+
// src/render/share-svg-theme.ts
|
|
2463
|
+
var shareTheme = {
|
|
2464
|
+
bg: "#0d1117",
|
|
2465
|
+
cardBg: "#161b22",
|
|
2466
|
+
cardBorder: "#30363d",
|
|
2467
|
+
textPrimary: "#e6edf3",
|
|
2468
|
+
textSecondary: "#8b949e",
|
|
2469
|
+
textMuted: "#484f58",
|
|
2470
|
+
gridLine: "#21262d",
|
|
2471
|
+
font: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif",
|
|
2472
|
+
mono: "ui-monospace, 'SF Mono', 'Fira Code', monospace"
|
|
2473
|
+
};
|
|
2474
|
+
var knownSourceColors = {
|
|
2475
|
+
pi: "#ec4899",
|
|
2476
|
+
codex: "#22c55e",
|
|
2477
|
+
gemini: "#eab308",
|
|
2478
|
+
droid: "#3b82f6",
|
|
2479
|
+
opencode: "#a855f7"
|
|
2480
|
+
};
|
|
2481
|
+
var fallbackColors = ["#f97316", "#06b6d4", "#ef4444", "#84cc16", "#f43f5e"];
|
|
2482
|
+
function getSourceColor(source, index) {
|
|
2483
|
+
return knownSourceColors[source] ?? fallbackColors[index % fallbackColors.length];
|
|
2484
|
+
}
|
|
2485
|
+
function escapeSvg(value) {
|
|
2486
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2487
|
+
}
|
|
2488
|
+
function formatCompact(n) {
|
|
2489
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1).replace(/\.0$/, "") + "B";
|
|
2490
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
2491
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|
|
2492
|
+
return String(n);
|
|
2493
|
+
}
|
|
2494
|
+
var intFmt = new Intl.NumberFormat("en-US");
|
|
2495
|
+
var decFmt = new Intl.NumberFormat("en-US", {
|
|
2496
|
+
minimumFractionDigits: 2,
|
|
2497
|
+
maximumFractionDigits: 2
|
|
2498
|
+
});
|
|
2499
|
+
var usdFmt = new Intl.NumberFormat("en-US", {
|
|
2500
|
+
style: "currency",
|
|
2501
|
+
currency: "USD",
|
|
2502
|
+
minimumFractionDigits: 2,
|
|
2503
|
+
maximumFractionDigits: 2
|
|
2504
|
+
});
|
|
2505
|
+
function formatInteger(n) {
|
|
2506
|
+
return intFmt.format(n);
|
|
2507
|
+
}
|
|
2508
|
+
function formatDecimal(n) {
|
|
2509
|
+
return n === void 0 ? "-" : decFmt.format(n);
|
|
2510
|
+
}
|
|
2511
|
+
function formatUsd(n) {
|
|
2512
|
+
return n === void 0 ? "-" : usdFmt.format(n);
|
|
2513
|
+
}
|
|
2514
|
+
function catmullRom(points, tension = 0.3, yFloor) {
|
|
2515
|
+
if (points.length < 2) return "";
|
|
2516
|
+
const clamp = (y) => yFloor !== void 0 ? Math.min(y, yFloor) : y;
|
|
2517
|
+
let d = `M${points[0].x.toFixed(2)},${points[0].y.toFixed(2)}`;
|
|
2518
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
2519
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
2520
|
+
const p1 = points[i];
|
|
2521
|
+
const p2 = points[i + 1];
|
|
2522
|
+
const p3 = points[Math.min(points.length - 1, i + 2)];
|
|
2523
|
+
const cp1x = p1.x + (p2.x - p0.x) * tension / 3;
|
|
2524
|
+
const cp1y = clamp(p1.y + (p2.y - p0.y) * tension / 3);
|
|
2525
|
+
const cp2x = p2.x - (p3.x - p1.x) * tension / 3;
|
|
2526
|
+
const cp2y = clamp(p2.y - (p3.y - p1.y) * tension / 3);
|
|
2527
|
+
d += ` C${cp1x.toFixed(2)},${cp1y.toFixed(2)} ${cp2x.toFixed(2)},${cp2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)}`;
|
|
2528
|
+
}
|
|
2529
|
+
return d;
|
|
2530
|
+
}
|
|
2531
|
+
function scaleY(value, max, top, bottom) {
|
|
2532
|
+
if (max <= 0) return bottom;
|
|
2533
|
+
return bottom - Math.max(0, value) / max * (bottom - top);
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/render/render-efficiency-share-svg.ts
|
|
2537
|
+
var W = 1500;
|
|
2538
|
+
var H = 640;
|
|
2539
|
+
var ACCENT_H = 4;
|
|
2540
|
+
var FOOTER_H = 36;
|
|
2541
|
+
var pad = { top: 160, right: 130, bottom: 70 + FOOTER_H, left: 110 };
|
|
2542
|
+
var chartColors = {
|
|
2543
|
+
commits: "#8b949e",
|
|
2544
|
+
usdPerCommit: "#f97316"
|
|
2545
|
+
};
|
|
2546
|
+
function toMonthlyRows(rows) {
|
|
2547
|
+
return rows.filter((row) => row.rowType === "period").sort((a, b) => a.periodKey.localeCompare(b.periodKey));
|
|
2548
|
+
}
|
|
2549
|
+
function toAllRow(rows) {
|
|
2550
|
+
return rows.find((row) => row.periodKey === "ALL");
|
|
2551
|
+
}
|
|
2552
|
+
function renderSummaryStats(allRow, vcenter) {
|
|
2553
|
+
const cost = formatUsd(allRow?.costUsd);
|
|
2554
|
+
const commits = formatInteger(allRow?.commitCount ?? 0);
|
|
2555
|
+
const usdPerCommit = formatUsd(allRow?.usdPerCommit);
|
|
2556
|
+
const tokPerCommit = formatDecimal(allRow?.tokensPerCommit);
|
|
2557
|
+
const y = vcenter;
|
|
2558
|
+
const items = [
|
|
2559
|
+
{ label: "Total Cost", value: cost, x: pad.left },
|
|
2560
|
+
{ label: "Commits", value: commits, x: 280 },
|
|
2561
|
+
{ label: "$/Commit", value: usdPerCommit, x: 480 },
|
|
2562
|
+
{ label: "Tokens/Commit", value: tokPerCommit, x: 680 }
|
|
2563
|
+
];
|
|
2564
|
+
return items.map(
|
|
2565
|
+
(item) => `<text x="${item.x}" y="${y}" font-size="14" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">${escapeSvg(item.label)}</text><text x="${item.x}" y="${y + 20}" font-size="18" font-weight="700" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}">${escapeSvg(item.value)}</text>`
|
|
2566
|
+
).join("\n");
|
|
2567
|
+
}
|
|
2568
|
+
function renderLegend(x, y) {
|
|
2569
|
+
const items = [
|
|
2570
|
+
{ label: "Commits", color: chartColors.commits, shape: "rect" },
|
|
2571
|
+
{ label: "$ / Commit", color: chartColors.usdPerCommit, shape: "line" }
|
|
2572
|
+
];
|
|
2573
|
+
return items.map((item, i) => {
|
|
2574
|
+
const ix = x + i * 180;
|
|
2575
|
+
const shape = item.shape === "rect" ? `<rect x="${ix}" y="${y - 6}" width="14" height="14" rx="3" fill="${item.color}" opacity="0.5"/>` : `<line x1="${ix}" y1="${y + 1}" x2="${ix + 14}" y2="${y + 1}" stroke="${item.color}" stroke-width="3" stroke-linecap="round"/>`;
|
|
2576
|
+
return `${shape}<text x="${ix + 20}" y="${y + 5}" font-size="13" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">${escapeSvg(item.label)}</text>`;
|
|
2577
|
+
}).join("\n");
|
|
2578
|
+
}
|
|
2579
|
+
function renderCommitBarLabels(monthlyRows, chartLeft, stepX, maxCommits, chartTop, chartBottom) {
|
|
2580
|
+
return monthlyRows.map((row, i) => {
|
|
2581
|
+
const x = chartLeft + i * stepX;
|
|
2582
|
+
const yTop = scaleY(row.commitCount, maxCommits, chartTop, chartBottom);
|
|
2583
|
+
return `<text x="${x.toFixed(2)}" y="${(yTop - 8).toFixed(0)}" text-anchor="middle" font-size="12" font-weight="600" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">${formatInteger(row.commitCount)}</text>`;
|
|
2584
|
+
}).join("\n");
|
|
2585
|
+
}
|
|
2586
|
+
function renderUsdDataLabels(monthlyRows, usdPoints) {
|
|
2587
|
+
return monthlyRows.map((row, i) => {
|
|
2588
|
+
const p = usdPoints[i];
|
|
2589
|
+
const val = row.usdPerCommit;
|
|
2590
|
+
if (val === void 0) return "";
|
|
2591
|
+
const labelY = p.y - 14;
|
|
2592
|
+
return `<text x="${p.x.toFixed(2)}" y="${labelY.toFixed(0)}" text-anchor="middle" font-size="12" font-weight="600" fill="${chartColors.usdPerCommit}" font-family="${shareTheme.font}">${escapeSvg(formatUsd(val))}</text>`;
|
|
2593
|
+
}).join("\n");
|
|
2594
|
+
}
|
|
2595
|
+
function renderEfficiencyMonthlyShareSvg(efficiencyData) {
|
|
2596
|
+
const monthlyRows = toMonthlyRows(efficiencyData.rows);
|
|
2597
|
+
const allRow = toAllRow(efficiencyData.rows);
|
|
2598
|
+
const chartLeft = pad.left;
|
|
2599
|
+
const chartTop = pad.top;
|
|
2600
|
+
const chartRight = W - pad.right;
|
|
2601
|
+
const chartBottom = H - pad.bottom;
|
|
2602
|
+
const chartW = chartRight - chartLeft;
|
|
2603
|
+
const count = Math.max(1, monthlyRows.length);
|
|
2604
|
+
const stepX = count === 1 ? 0 : chartW / (count - 1);
|
|
2605
|
+
const maxCommits = Math.max(1, ...monthlyRows.map((r) => r.commitCount));
|
|
2606
|
+
const actualMaxUsd = Math.max(0, ...monthlyRows.map((r) => Math.max(0, r.usdPerCommit ?? 0)));
|
|
2607
|
+
const scaleMaxUsd = Math.max(0.01, actualMaxUsd);
|
|
2608
|
+
const barWidth = Math.min(48, Math.max(18, chartW / (count * 2.2)));
|
|
2609
|
+
const commitBars = monthlyRows.map((row, i) => {
|
|
2610
|
+
const x = chartLeft + i * stepX;
|
|
2611
|
+
const yTop = scaleY(row.commitCount, maxCommits, chartTop, chartBottom);
|
|
2612
|
+
return `<rect x="${(x - barWidth / 2).toFixed(2)}" y="${yTop.toFixed(2)}" width="${barWidth.toFixed(2)}" height="${(chartBottom - yTop).toFixed(2)}" rx="4" fill="${chartColors.commits}" fill-opacity="0.35"/>`;
|
|
2613
|
+
}).join("\n");
|
|
2614
|
+
const usdPoints = monthlyRows.map((row, i) => ({
|
|
2615
|
+
x: chartLeft + i * stepX,
|
|
2616
|
+
y: scaleY(row.usdPerCommit ?? 0, scaleMaxUsd, chartTop, chartBottom)
|
|
2617
|
+
}));
|
|
2618
|
+
const usdLine = usdPoints.length >= 2 ? `<path d="${catmullRom(usdPoints, 0.3, chartBottom)}" fill="none" stroke="${chartColors.usdPerCommit}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>` : "";
|
|
2619
|
+
const dotRadius = count <= 4 ? 6 : 4.5;
|
|
2620
|
+
const usdDots = usdPoints.map(
|
|
2621
|
+
(p) => `<circle cx="${p.x.toFixed(2)}" cy="${p.y.toFixed(2)}" r="${dotRadius}" fill="${chartColors.usdPerCommit}"/>`
|
|
2622
|
+
).join("\n");
|
|
2623
|
+
const monthLabels = monthlyRows.map((row, i) => {
|
|
2624
|
+
const x = chartLeft + i * stepX;
|
|
2625
|
+
return `<text x="${x.toFixed(2)}" y="${(chartBottom + 28).toFixed(0)}" text-anchor="middle" font-size="13" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">${escapeSvg(row.periodKey)}</text>`;
|
|
2626
|
+
}).join("\n");
|
|
2627
|
+
const axisLabels = [
|
|
2628
|
+
`<text x="${(chartLeft - 12).toFixed(0)}" y="${(chartTop - 10).toFixed(0)}" text-anchor="end" font-size="12" font-weight="600" fill="${chartColors.commits}" font-family="${shareTheme.font}">Commits</text>`,
|
|
2629
|
+
`<text x="${(chartLeft - 12).toFixed(0)}" y="${(chartTop + 5).toFixed(0)}" text-anchor="end" font-size="11" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">${escapeSvg(formatInteger(maxCommits))}</text>`,
|
|
2630
|
+
`<text x="${(chartLeft - 12).toFixed(0)}" y="${(chartBottom + 5).toFixed(0)}" text-anchor="end" font-size="11" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">0</text>`,
|
|
2631
|
+
`<text x="${(chartRight + 12).toFixed(0)}" y="${(chartTop - 10).toFixed(0)}" font-size="12" font-weight="600" fill="${chartColors.usdPerCommit}" font-family="${shareTheme.font}">$/Commit</text>`,
|
|
2632
|
+
`<text x="${(chartRight + 12).toFixed(0)}" y="${(chartTop + 5).toFixed(0)}" font-size="11" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">${escapeSvg(formatUsd(actualMaxUsd))}</text>`,
|
|
2633
|
+
`<text x="${(chartRight + 12).toFixed(0)}" y="${(chartBottom + 5).toFixed(0)}" font-size="11" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">$0.00</text>`
|
|
2634
|
+
].join("\n");
|
|
2635
|
+
const noData = monthlyRows.length === 0 ? `<text x="${(W / 2).toFixed(0)}" y="${(H / 2).toFixed(0)}" text-anchor="middle" font-size="20" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">No monthly efficiency data available</text>` : "";
|
|
2636
|
+
const vcenter = 70;
|
|
2637
|
+
const commandText = "llm-usage efficiency monthly --share";
|
|
2638
|
+
const badgeW = commandText.length * 9.5 + 28;
|
|
2639
|
+
const badgeX = W - pad.right - badgeW;
|
|
2640
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
|
2641
|
+
<defs>
|
|
2642
|
+
<linearGradient id="accent-grad" x1="0" y1="0" x2="1" y2="0">
|
|
2643
|
+
<stop offset="0%" stop-color="#10b981"/>
|
|
2644
|
+
<stop offset="100%" stop-color="#06b6d4"/>
|
|
2645
|
+
</linearGradient>
|
|
2646
|
+
</defs>
|
|
2647
|
+
<rect width="${W}" height="${H}" fill="${shareTheme.bg}"/>
|
|
2648
|
+
<rect width="${W}" height="${ACCENT_H}" fill="url(#accent-grad)"/>
|
|
2649
|
+
<text x="${pad.left}" y="52" font-size="32" font-weight="700" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}">Monthly Efficiency</text>
|
|
2650
|
+
<rect x="${badgeX.toFixed(0)}" y="30" width="${badgeW.toFixed(0)}" height="34" rx="17" fill="none" stroke="${shareTheme.cardBorder}"/>
|
|
2651
|
+
<text x="${(badgeX + badgeW / 2).toFixed(0)}" y="52" text-anchor="middle" font-size="14" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}">${escapeSvg(commandText)}</text>
|
|
2652
|
+
${renderSummaryStats(allRow, vcenter)}
|
|
2653
|
+
${renderLegend(pad.left, vcenter + 50)}
|
|
2654
|
+
<line x1="${chartLeft}" y1="${chartBottom}" x2="${chartRight}" y2="${chartBottom}" stroke="${shareTheme.gridLine}" stroke-width="1"/>
|
|
2655
|
+
<line x1="${chartLeft}" y1="${chartTop}" x2="${chartLeft}" y2="${chartBottom}" stroke="${shareTheme.gridLine}" stroke-width="1"/>
|
|
2656
|
+
<line x1="${chartRight}" y1="${chartTop}" x2="${chartRight}" y2="${chartBottom}" stroke="${shareTheme.gridLine}" stroke-width="1"/>
|
|
2657
|
+
${axisLabels}
|
|
2658
|
+
${commitBars}
|
|
2659
|
+
${renderCommitBarLabels(monthlyRows, chartLeft, stepX, maxCommits, chartTop, chartBottom)}
|
|
2660
|
+
${usdLine}
|
|
2661
|
+
${usdDots}
|
|
2662
|
+
${renderUsdDataLabels(monthlyRows, usdPoints)}
|
|
2663
|
+
${monthLabels}
|
|
2664
|
+
${noData}
|
|
2665
|
+
<line x1="0" y1="${H - FOOTER_H + 1}" x2="${W}" y2="${H - FOOTER_H + 1}" stroke="${shareTheme.gridLine}" stroke-width="1"/>
|
|
2666
|
+
<text x="60" y="${H - FOOTER_H / 2 + 5}" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}" font-size="13">llm-usage-metrics</text>
|
|
2667
|
+
</svg>`;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2455
2670
|
// src/utils/time-buckets.ts
|
|
2456
2671
|
var formatterCache = /* @__PURE__ */ new Map();
|
|
2457
2672
|
function getDateFormatter(timezone) {
|
|
@@ -2776,8 +2991,7 @@ function computeDerivedMetrics(usage, outcomes) {
|
|
|
2776
2991
|
return {
|
|
2777
2992
|
usdPerCommit: costUsd !== void 0 && outcomes.commitCount > 0 ? costUsd / outcomes.commitCount : void 0,
|
|
2778
2993
|
usdPer1kLinesChanged: costUsd !== void 0 && outcomes.linesChanged > 0 ? costUsd / (outcomes.linesChanged / 1e3) : void 0,
|
|
2779
|
-
tokensPerCommit: outcomes.commitCount > 0 ?
|
|
2780
|
-
nonCacheTokensPerCommit: outcomes.commitCount > 0 ? nonCacheTotalTokens / outcomes.commitCount : void 0,
|
|
2994
|
+
tokensPerCommit: outcomes.commitCount > 0 ? nonCacheTotalTokens / outcomes.commitCount : void 0,
|
|
2781
2995
|
commitsPerUsd: costUsd !== void 0 && costUsd > 0 ? outcomes.commitCount / costUsd : void 0
|
|
2782
2996
|
};
|
|
2783
2997
|
}
|
|
@@ -3576,7 +3790,7 @@ function normalizeSkippedRowReasons(value) {
|
|
|
3576
3790
|
// src/cli/parse-file-cache.ts
|
|
3577
3791
|
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
3578
3792
|
import path11 from "path";
|
|
3579
|
-
var PARSE_FILE_CACHE_VERSION =
|
|
3793
|
+
var PARSE_FILE_CACHE_VERSION = 4;
|
|
3580
3794
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
3581
3795
|
function createCacheKey(source, filePath) {
|
|
3582
3796
|
return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
|
|
@@ -5122,6 +5336,15 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
|
|
|
5122
5336
|
}
|
|
5123
5337
|
}
|
|
5124
5338
|
|
|
5339
|
+
// src/cli/share-artifact.ts
|
|
5340
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
5341
|
+
import path13 from "path";
|
|
5342
|
+
async function writeShareSvgFile(fileName, svgContent) {
|
|
5343
|
+
const outputPath = path13.resolve(process.cwd(), fileName);
|
|
5344
|
+
await writeFile4(outputPath, svgContent, "utf8");
|
|
5345
|
+
return outputPath;
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5125
5348
|
// src/render/table-text-layout.ts
|
|
5126
5349
|
var ansiEscapePattern = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
|
|
5127
5350
|
var combiningMarkPattern = /\p{Mark}/u;
|
|
@@ -5372,8 +5595,7 @@ var efficiencyTableHeaders = [
|
|
|
5372
5595
|
"Cost",
|
|
5373
5596
|
"$/Commit",
|
|
5374
5597
|
"$/1k Lines",
|
|
5375
|
-
"
|
|
5376
|
-
"Non-Cache/Commit",
|
|
5598
|
+
"Tokens/Commit",
|
|
5377
5599
|
"Commits/$"
|
|
5378
5600
|
];
|
|
5379
5601
|
var integerFormatter = new Intl.NumberFormat("en-US");
|
|
@@ -5393,10 +5615,10 @@ var usdRateFormatter = new Intl.NumberFormat("en-US", {
|
|
|
5393
5615
|
minimumFractionDigits: 4,
|
|
5394
5616
|
maximumFractionDigits: 4
|
|
5395
5617
|
});
|
|
5396
|
-
function
|
|
5618
|
+
function formatInteger2(value) {
|
|
5397
5619
|
return integerFormatter.format(value);
|
|
5398
5620
|
}
|
|
5399
|
-
function
|
|
5621
|
+
function formatUsd2(value, options = {}) {
|
|
5400
5622
|
if (value === void 0) {
|
|
5401
5623
|
return "-";
|
|
5402
5624
|
}
|
|
@@ -5410,7 +5632,7 @@ function formatUsdRate(value, options = {}) {
|
|
|
5410
5632
|
const formatted = usdRateFormatter.format(value);
|
|
5411
5633
|
return options.approximate ? `~${formatted}` : formatted;
|
|
5412
5634
|
}
|
|
5413
|
-
function
|
|
5635
|
+
function formatDecimal2(value, options = {}) {
|
|
5414
5636
|
if (value === void 0) {
|
|
5415
5637
|
return "-";
|
|
5416
5638
|
}
|
|
@@ -5420,22 +5642,21 @@ function formatDecimal(value, options = {}) {
|
|
|
5420
5642
|
function toEfficiencyTableCells(rows) {
|
|
5421
5643
|
return rows.map((row) => [
|
|
5422
5644
|
row.periodKey,
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5645
|
+
formatInteger2(row.commitCount),
|
|
5646
|
+
formatInteger2(row.linesAdded),
|
|
5647
|
+
formatInteger2(row.linesDeleted),
|
|
5648
|
+
formatInteger2(row.linesChanged),
|
|
5649
|
+
formatInteger2(row.inputTokens),
|
|
5650
|
+
formatInteger2(row.outputTokens),
|
|
5651
|
+
formatInteger2(row.reasoningTokens),
|
|
5652
|
+
formatInteger2(row.cacheReadTokens),
|
|
5653
|
+
formatInteger2(row.cacheWriteTokens),
|
|
5654
|
+
formatInteger2(row.totalTokens),
|
|
5655
|
+
formatUsd2(row.costUsd, { approximate: row.costIncomplete }),
|
|
5434
5656
|
formatUsdRate(row.usdPerCommit, { approximate: row.costIncomplete }),
|
|
5435
5657
|
formatUsdRate(row.usdPer1kLinesChanged, { approximate: row.costIncomplete }),
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
formatDecimal(row.commitsPerUsd, { approximate: row.costIncomplete })
|
|
5658
|
+
formatDecimal2(row.tokensPerCommit),
|
|
5659
|
+
formatDecimal2(row.commitsPerUsd, { approximate: row.costIncomplete })
|
|
5439
5660
|
]);
|
|
5440
5661
|
}
|
|
5441
5662
|
|
|
@@ -5546,7 +5767,7 @@ function formatSource(row) {
|
|
|
5546
5767
|
function formatTokenCount(value) {
|
|
5547
5768
|
return integerFormatter2.format(value ?? 0);
|
|
5548
5769
|
}
|
|
5549
|
-
function
|
|
5770
|
+
function formatUsd3(value, options = {}) {
|
|
5550
5771
|
if (value === void 0) {
|
|
5551
5772
|
return "-";
|
|
5552
5773
|
}
|
|
@@ -5581,13 +5802,13 @@ function formatModelMetric(row, selector, formatter, layout) {
|
|
|
5581
5802
|
}
|
|
5582
5803
|
function formatModelCostMetric(row, layout) {
|
|
5583
5804
|
if (layout !== "per_model_columns" || row.modelBreakdown.length === 0) {
|
|
5584
|
-
return
|
|
5805
|
+
return formatUsd3(row.costUsd, { incomplete: row.costIncomplete });
|
|
5585
5806
|
}
|
|
5586
5807
|
const lines = row.modelBreakdown.map(
|
|
5587
|
-
(modelUsage) =>
|
|
5808
|
+
(modelUsage) => formatUsd3(modelUsage.costUsd, { incomplete: modelUsage.costIncomplete })
|
|
5588
5809
|
);
|
|
5589
5810
|
if (row.modelBreakdown.length > 1) {
|
|
5590
|
-
lines.push(
|
|
5811
|
+
lines.push(formatUsd3(row.costUsd, { incomplete: row.costIncomplete }));
|
|
5591
5812
|
}
|
|
5592
5813
|
return lines.join("\n");
|
|
5593
5814
|
}
|
|
@@ -6191,13 +6412,23 @@ function resolveReportFormat(options) {
|
|
|
6191
6412
|
}
|
|
6192
6413
|
return "terminal";
|
|
6193
6414
|
}
|
|
6415
|
+
function validateShareOption(granularity, options) {
|
|
6416
|
+
if (!options.share) {
|
|
6417
|
+
return;
|
|
6418
|
+
}
|
|
6419
|
+
if (granularity !== "monthly") {
|
|
6420
|
+
throw new Error("--share is only supported for efficiency monthly");
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6194
6423
|
async function prepareEfficiencyReport(granularity, options) {
|
|
6195
6424
|
validateOutputFormatOptions(options);
|
|
6425
|
+
validateShareOption(granularity, options);
|
|
6196
6426
|
const efficiencyData = await buildEfficiencyData(granularity, options);
|
|
6197
6427
|
const format = resolveReportFormat(options);
|
|
6198
6428
|
return {
|
|
6199
6429
|
format,
|
|
6200
6430
|
diagnostics: efficiencyData.diagnostics,
|
|
6431
|
+
shareSvg: options.share ? renderEfficiencyMonthlyShareSvg(efficiencyData) : void 0,
|
|
6201
6432
|
output: renderEfficiencyReport(efficiencyData, format, {
|
|
6202
6433
|
granularity
|
|
6203
6434
|
})
|
|
@@ -6222,9 +6453,141 @@ async function runEfficiencyReport(granularity, options) {
|
|
|
6222
6453
|
logger.warn(message);
|
|
6223
6454
|
});
|
|
6224
6455
|
}
|
|
6456
|
+
if (preparedReport.shareSvg) {
|
|
6457
|
+
const outputPath = await writeShareSvgFile(
|
|
6458
|
+
"efficiency-monthly-share.svg",
|
|
6459
|
+
preparedReport.shareSvg
|
|
6460
|
+
);
|
|
6461
|
+
logger.info(`Wrote efficiency share SVG: ${outputPath}`);
|
|
6462
|
+
}
|
|
6225
6463
|
console.log(preparedReport.output);
|
|
6226
6464
|
}
|
|
6227
6465
|
|
|
6466
|
+
// src/render/render-optimize-share-svg.ts
|
|
6467
|
+
var W2 = 1500;
|
|
6468
|
+
var H2 = 780;
|
|
6469
|
+
var ACCENT_H2 = 4;
|
|
6470
|
+
var FOOTER_H2 = 36;
|
|
6471
|
+
var pad2 = { top: 180, right: 70, bottom: 60 + FOOTER_H2, left: 260 };
|
|
6472
|
+
function formatPercent(value) {
|
|
6473
|
+
if (value === void 0) return "-";
|
|
6474
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
6475
|
+
}
|
|
6476
|
+
function cellFill(value) {
|
|
6477
|
+
if (value === void 0) return "rgba(139,148,158,0.12)";
|
|
6478
|
+
const mag = Math.min(1, Math.abs(value));
|
|
6479
|
+
const alpha = 0.22 + mag * 0.58;
|
|
6480
|
+
if (value > 0) return `rgba(34,197,94,${alpha.toFixed(3)})`;
|
|
6481
|
+
if (value < 0) return `rgba(239,68,68,${alpha.toFixed(3)})`;
|
|
6482
|
+
return "rgba(139,148,158,0.15)";
|
|
6483
|
+
}
|
|
6484
|
+
function cellTextFill(value) {
|
|
6485
|
+
if (value === void 0) return shareTheme.textMuted;
|
|
6486
|
+
if (Math.abs(value) >= 0.35) return "#ffffff";
|
|
6487
|
+
return shareTheme.textPrimary;
|
|
6488
|
+
}
|
|
6489
|
+
function toCandidateRows(data) {
|
|
6490
|
+
return data.rows.filter((r) => r.rowType === "candidate");
|
|
6491
|
+
}
|
|
6492
|
+
function sortPeriodKeys(keys) {
|
|
6493
|
+
return [...keys].sort(compareByCodePoint);
|
|
6494
|
+
}
|
|
6495
|
+
function renderOptimizeMonthlyShareSvg(optimizeData) {
|
|
6496
|
+
const candidateRows = toCandidateRows(optimizeData);
|
|
6497
|
+
const periodKeys = sortPeriodKeys(
|
|
6498
|
+
new Set(candidateRows.map((r) => r.periodKey).filter((k) => k !== "ALL"))
|
|
6499
|
+
);
|
|
6500
|
+
const candidateModels = [...new Set(candidateRows.map((r) => r.candidateModel))].sort(
|
|
6501
|
+
compareByCodePoint
|
|
6502
|
+
);
|
|
6503
|
+
const cellMap = /* @__PURE__ */ new Map();
|
|
6504
|
+
for (const row of candidateRows) {
|
|
6505
|
+
cellMap.set(`${row.candidateModel}__${row.periodKey}`, row);
|
|
6506
|
+
}
|
|
6507
|
+
const allByCandidate = /* @__PURE__ */ new Map();
|
|
6508
|
+
for (const row of candidateRows) {
|
|
6509
|
+
if (row.periodKey === "ALL") allByCandidate.set(row.candidateModel, row);
|
|
6510
|
+
}
|
|
6511
|
+
const chartLeft = pad2.left;
|
|
6512
|
+
const chartTop = pad2.top;
|
|
6513
|
+
const chartRight = W2 - pad2.right;
|
|
6514
|
+
const chartBottom = H2 - pad2.bottom;
|
|
6515
|
+
const chartW = chartRight - chartLeft;
|
|
6516
|
+
const chartH = chartBottom - chartTop;
|
|
6517
|
+
const rowCount = Math.max(1, candidateModels.length);
|
|
6518
|
+
const colCount = Math.max(1, periodKeys.length);
|
|
6519
|
+
const cellW = chartW / colCount;
|
|
6520
|
+
const cellH = chartH / rowCount;
|
|
6521
|
+
const gridCells = [];
|
|
6522
|
+
const colLabels = [];
|
|
6523
|
+
const rowLabels = [];
|
|
6524
|
+
for (let c = 0; c < periodKeys.length; c++) {
|
|
6525
|
+
const x = chartLeft + c * cellW + cellW / 2;
|
|
6526
|
+
colLabels.push(
|
|
6527
|
+
`<text x="${x.toFixed(2)}" y="${(chartTop - 14).toFixed(0)}" text-anchor="middle" font-size="14" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">${escapeSvg(periodKeys[c])}</text>`
|
|
6528
|
+
);
|
|
6529
|
+
}
|
|
6530
|
+
for (let r = 0; r < candidateModels.length; r++) {
|
|
6531
|
+
const model = candidateModels[r];
|
|
6532
|
+
const y = chartTop + r * cellH + cellH / 2 + 5;
|
|
6533
|
+
const allRow = allByCandidate.get(model);
|
|
6534
|
+
const allLabel = allRow?.savingsUsd === void 0 ? "-" : formatUsd(Math.abs(allRow.savingsUsd));
|
|
6535
|
+
const allColor = allRow?.savingsUsd === void 0 ? shareTheme.textMuted : allRow.savingsUsd > 0 ? "#22c55e" : allRow.savingsUsd < 0 ? "#ef4444" : shareTheme.textSecondary;
|
|
6536
|
+
const prefix = allRow?.savingsUsd === void 0 ? "" : allRow.savingsUsd > 0 ? "+" : allRow.savingsUsd < 0 ? "-" : "";
|
|
6537
|
+
rowLabels.push(
|
|
6538
|
+
`<text x="${(chartLeft - 16).toFixed(0)}" y="${y.toFixed(0)}" text-anchor="end" font-size="14" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}">${escapeSvg(model)}</text>`
|
|
6539
|
+
);
|
|
6540
|
+
rowLabels.push(
|
|
6541
|
+
`<text x="${(chartLeft - 16).toFixed(0)}" y="${(y + 16).toFixed(0)}" text-anchor="end" font-size="12" fill="${allColor}" font-family="${shareTheme.font}">ALL: ${escapeSvg(prefix + allLabel)}</text>`
|
|
6542
|
+
);
|
|
6543
|
+
for (let c = 0; c < periodKeys.length; c++) {
|
|
6544
|
+
const row = cellMap.get(`${model}__${periodKeys[c]}`);
|
|
6545
|
+
const x = chartLeft + c * cellW;
|
|
6546
|
+
const yTop = chartTop + r * cellH;
|
|
6547
|
+
const pct = row?.savingsPct;
|
|
6548
|
+
gridCells.push(
|
|
6549
|
+
`<rect x="${(x + 2).toFixed(2)}" y="${(yTop + 2).toFixed(2)}" width="${Math.max(0, cellW - 4).toFixed(2)}" height="${Math.max(0, cellH - 4).toFixed(2)}" fill="${cellFill(pct)}" rx="6"/>`
|
|
6550
|
+
);
|
|
6551
|
+
gridCells.push(
|
|
6552
|
+
`<text x="${(x + cellW / 2).toFixed(2)}" y="${(yTop + cellH / 2 + 5).toFixed(2)}" text-anchor="middle" font-size="13" font-weight="600" fill="${cellTextFill(pct)}" font-family="${shareTheme.font}">${escapeSvg(formatPercent(pct))}</text>`
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
const provider = optimizeData.diagnostics.provider;
|
|
6557
|
+
const missing = optimizeData.diagnostics.candidatesWithMissingPricing;
|
|
6558
|
+
const warning = optimizeData.diagnostics.warning ?? "";
|
|
6559
|
+
const commandText = "llm-usage optimize monthly --share";
|
|
6560
|
+
const badgeW = commandText.length * 9.5 + 28;
|
|
6561
|
+
const badgeX = W2 - pad2.right - badgeW;
|
|
6562
|
+
const noData = candidateModels.length === 0 || periodKeys.length === 0 ? `<text x="${(W2 / 2).toFixed(0)}" y="${(H2 / 2).toFixed(0)}" text-anchor="middle" font-size="20" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">No monthly optimize data available</text>` : "";
|
|
6563
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W2}" height="${H2}" viewBox="0 0 ${W2} ${H2}">
|
|
6564
|
+
<defs>
|
|
6565
|
+
<linearGradient id="accent-grad" x1="0" y1="0" x2="1" y2="0">
|
|
6566
|
+
<stop offset="0%" stop-color="#10b981"/>
|
|
6567
|
+
<stop offset="100%" stop-color="#06b6d4"/>
|
|
6568
|
+
</linearGradient>
|
|
6569
|
+
</defs>
|
|
6570
|
+
<rect width="${W2}" height="${H2}" fill="${shareTheme.bg}"/>
|
|
6571
|
+
<rect width="${W2}" height="${ACCENT_H2}" fill="url(#accent-grad)"/>
|
|
6572
|
+
<text x="${pad2.left}" y="52" font-size="32" font-weight="700" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}">Monthly Optimize</text>
|
|
6573
|
+
<text x="${pad2.left}" y="78" font-size="15" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">Savings % heatmap by candidate and month</text>
|
|
6574
|
+
<rect x="${badgeX.toFixed(0)}" y="30" width="${badgeW.toFixed(0)}" height="34" rx="17" fill="none" stroke="${shareTheme.cardBorder}"/>
|
|
6575
|
+
<text x="${(badgeX + badgeW / 2).toFixed(0)}" y="52" text-anchor="middle" font-size="14" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}">${escapeSvg(commandText)}</text>
|
|
6576
|
+
<text x="${pad2.left}" y="112" font-size="15" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}">Provider: <tspan font-weight="700">${escapeSvg(provider)}</tspan></text>
|
|
6577
|
+
<text x="${pad2.left + 280}" y="112" font-size="14" fill="#22c55e" font-family="${shareTheme.font}">\u25CF positive = savings</text>
|
|
6578
|
+
<text x="${pad2.left + 480}" y="112" font-size="14" fill="#ef4444" font-family="${shareTheme.font}">\u25CF negative = higher cost</text>
|
|
6579
|
+
${missing.length > 0 ? `<text x="${pad2.left}" y="136" font-size="13" fill="#eab308" font-family="${shareTheme.font}">Missing pricing: ${escapeSvg(missing.join(", "))}</text>` : ""}
|
|
6580
|
+
${warning ? `<text x="${pad2.left}" y="158" font-size="13" fill="#eab308" font-family="${shareTheme.font}">${escapeSvg(warning)}</text>` : ""}
|
|
6581
|
+
<rect x="${chartLeft}" y="${chartTop}" width="${chartW}" height="${chartH}" fill="${shareTheme.cardBg}" rx="10"/>
|
|
6582
|
+
${gridCells.join("\n")}
|
|
6583
|
+
${colLabels.join("\n")}
|
|
6584
|
+
${rowLabels.join("\n")}
|
|
6585
|
+
${noData}
|
|
6586
|
+
<line x1="0" y1="${H2 - FOOTER_H2 + 1}" x2="${W2}" y2="${H2 - FOOTER_H2 + 1}" stroke="${shareTheme.gridLine}" stroke-width="1"/>
|
|
6587
|
+
<text x="60" y="${H2 - FOOTER_H2 / 2 + 5}" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}" font-size="13">llm-usage-metrics</text>
|
|
6588
|
+
</svg>`;
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6228
6591
|
// src/render/render-optimize-report.ts
|
|
6229
6592
|
import { markdownTable as markdownTable2 } from "markdown-table";
|
|
6230
6593
|
import pc6 from "picocolors";
|
|
@@ -6261,14 +6624,14 @@ function getReportTitle2(granularity) {
|
|
|
6261
6624
|
return "Monthly Optimize Report";
|
|
6262
6625
|
}
|
|
6263
6626
|
}
|
|
6264
|
-
function
|
|
6627
|
+
function formatUsd4(value, options = {}) {
|
|
6265
6628
|
if (value === void 0) {
|
|
6266
6629
|
return "-";
|
|
6267
6630
|
}
|
|
6268
6631
|
const formatted = usdFormatter3.format(value);
|
|
6269
6632
|
return options.approximate ? `~${formatted}` : formatted;
|
|
6270
6633
|
}
|
|
6271
|
-
function
|
|
6634
|
+
function formatPercent2(value) {
|
|
6272
6635
|
if (value === void 0) {
|
|
6273
6636
|
return "-";
|
|
6274
6637
|
}
|
|
@@ -6322,7 +6685,7 @@ function resolveTerminalContextLines(optimizeData, options) {
|
|
|
6322
6685
|
lines.push(options.useColor ? pc6.cyan(providerLine) : providerLine);
|
|
6323
6686
|
if (allBaselineRow) {
|
|
6324
6687
|
lines.push(
|
|
6325
|
-
`ALL baseline cost: ${
|
|
6688
|
+
`ALL baseline cost: ${formatUsd4(allBaselineRow.baselineCostUsd, { approximate: allBaselineRow.baselineCostIncomplete })}`
|
|
6326
6689
|
);
|
|
6327
6690
|
}
|
|
6328
6691
|
if (allCandidateRows.length > 0) {
|
|
@@ -6334,11 +6697,11 @@ function resolveTerminalContextLines(optimizeData, options) {
|
|
|
6334
6697
|
lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
|
|
6335
6698
|
} else if (bestRow.savingsUsd > 0) {
|
|
6336
6699
|
lines.push(
|
|
6337
|
-
`ALL best candidate: ${bestRow.candidateModel} saves ${formatAbsoluteUsd(bestRow.savingsUsd)} (${
|
|
6700
|
+
`ALL best candidate: ${bestRow.candidateModel} saves ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent2(bestRow.savingsPct)})`
|
|
6338
6701
|
);
|
|
6339
6702
|
} else if (bestRow.savingsUsd < 0) {
|
|
6340
6703
|
lines.push(
|
|
6341
|
-
`ALL best candidate: ${bestRow.candidateModel} increases cost by ${formatAbsoluteUsd(bestRow.savingsUsd)} (${
|
|
6704
|
+
`ALL best candidate: ${bestRow.candidateModel} increases cost by ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent2(bestRow.savingsPct)})`
|
|
6342
6705
|
);
|
|
6343
6706
|
} else {
|
|
6344
6707
|
lines.push(`ALL best candidate: ${bestRow.candidateModel} matches baseline cost`);
|
|
@@ -6364,20 +6727,20 @@ function toTableCells(optimizeData, options) {
|
|
|
6364
6727
|
periodCell,
|
|
6365
6728
|
styleCandidateCell("BASELINE", "baseline", options.useColor),
|
|
6366
6729
|
"-",
|
|
6367
|
-
|
|
6730
|
+
formatUsd4(row.baselineCostUsd, { approximate: row.baselineCostIncomplete }),
|
|
6368
6731
|
"-",
|
|
6369
6732
|
"-"
|
|
6370
6733
|
];
|
|
6371
6734
|
return options.includeNotesColumn ? [...baselineCells, "-"] : baselineCells;
|
|
6372
6735
|
}
|
|
6373
|
-
const savingsCell =
|
|
6374
|
-
const savingsPctCell =
|
|
6736
|
+
const savingsCell = formatUsd4(row.savingsUsd);
|
|
6737
|
+
const savingsPctCell = formatPercent2(row.savingsPct);
|
|
6375
6738
|
const notesCell = formatNotes(row.notes);
|
|
6376
6739
|
const candidateCells = [
|
|
6377
6740
|
periodCell,
|
|
6378
6741
|
styleCandidateCell(row.candidateModel, "candidate", options.useColor),
|
|
6379
|
-
|
|
6380
|
-
|
|
6742
|
+
formatUsd4(row.hypotheticalCostUsd, { approximate: row.hypotheticalCostIncomplete }),
|
|
6743
|
+
formatUsd4(baselineRow?.baselineCostUsd, {
|
|
6381
6744
|
approximate: baselineRow?.baselineCostIncomplete === true
|
|
6382
6745
|
}),
|
|
6383
6746
|
styleDeltaCell2(row.savingsUsd, savingsCell, options.useColor),
|
|
@@ -6818,8 +7181,17 @@ function resolveReportFormat2(options) {
|
|
|
6818
7181
|
}
|
|
6819
7182
|
return "terminal";
|
|
6820
7183
|
}
|
|
7184
|
+
function validateShareOption2(granularity, options) {
|
|
7185
|
+
if (!options.share) {
|
|
7186
|
+
return;
|
|
7187
|
+
}
|
|
7188
|
+
if (granularity !== "monthly") {
|
|
7189
|
+
throw new Error("--share is only supported for optimize monthly");
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
6821
7192
|
async function prepareOptimizeReport(granularity, options) {
|
|
6822
7193
|
validateOutputFormatOptions2(options);
|
|
7194
|
+
validateShareOption2(granularity, options);
|
|
6823
7195
|
const optimizeData = await buildOptimizeData(granularity, options);
|
|
6824
7196
|
const format = resolveReportFormat2(options);
|
|
6825
7197
|
return {
|
|
@@ -6828,6 +7200,7 @@ async function prepareOptimizeReport(granularity, options) {
|
|
|
6828
7200
|
candidateCount: optimizeData.rows.filter(
|
|
6829
7201
|
(row) => row.rowType === "candidate" && row.periodKey === "ALL"
|
|
6830
7202
|
).length,
|
|
7203
|
+
shareSvg: options.share ? renderOptimizeMonthlyShareSvg(optimizeData) : void 0,
|
|
6831
7204
|
output: renderOptimizeReport(optimizeData, format, {
|
|
6832
7205
|
granularity
|
|
6833
7206
|
})
|
|
@@ -6853,6 +7226,13 @@ async function runOptimizeReport(granularity, options) {
|
|
|
6853
7226
|
logger.warn(message);
|
|
6854
7227
|
});
|
|
6855
7228
|
}
|
|
7229
|
+
if (preparedReport.shareSvg) {
|
|
7230
|
+
const outputPath = await writeShareSvgFile(
|
|
7231
|
+
"optimize-monthly-share.svg",
|
|
7232
|
+
preparedReport.shareSvg
|
|
7233
|
+
);
|
|
7234
|
+
logger.info(`Wrote optimize share SVG: ${outputPath}`);
|
|
7235
|
+
}
|
|
6856
7236
|
console.log(preparedReport.output);
|
|
6857
7237
|
}
|
|
6858
7238
|
|
|
@@ -6910,6 +7290,257 @@ function renderUsageReport(usageData, format, options) {
|
|
|
6910
7290
|
}
|
|
6911
7291
|
}
|
|
6912
7292
|
|
|
7293
|
+
// src/render/render-usage-share-svg.ts
|
|
7294
|
+
var W3 = 1500;
|
|
7295
|
+
var H3 = 560;
|
|
7296
|
+
var ACCENT_H3 = 4;
|
|
7297
|
+
var FOOTER_H3 = 36;
|
|
7298
|
+
var pad3 = { top: 140, right: 80, bottom: 60 + FOOTER_H3, left: 200 };
|
|
7299
|
+
function extractPeriodSourceRows(rows) {
|
|
7300
|
+
return rows.filter((r) => r.rowType === "period_source");
|
|
7301
|
+
}
|
|
7302
|
+
function extractGrandTotal(rows) {
|
|
7303
|
+
return rows.find((r) => r.rowType === "grand_total");
|
|
7304
|
+
}
|
|
7305
|
+
function buildSourceSeries(sourceRows, periods, sources) {
|
|
7306
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
7307
|
+
const sourceTotals = /* @__PURE__ */ new Map();
|
|
7308
|
+
for (const row of sourceRows) {
|
|
7309
|
+
const key = `${row.source}__${row.periodKey}`;
|
|
7310
|
+
lookup.set(key, (lookup.get(key) ?? 0) + row.totalTokens);
|
|
7311
|
+
sourceTotals.set(row.source, (sourceTotals.get(row.source) ?? 0) + row.totalTokens);
|
|
7312
|
+
}
|
|
7313
|
+
return sources.map((source, index) => ({
|
|
7314
|
+
source,
|
|
7315
|
+
color: getSourceColor(source, index),
|
|
7316
|
+
total: sourceTotals.get(source) ?? 0,
|
|
7317
|
+
values: periods.map((period) => lookup.get(`${source}__${period}`) ?? 0)
|
|
7318
|
+
}));
|
|
7319
|
+
}
|
|
7320
|
+
function buildStackedValues(series) {
|
|
7321
|
+
if (series.length === 0) return [];
|
|
7322
|
+
const periodCount = series[0].values.length;
|
|
7323
|
+
const stacked = [];
|
|
7324
|
+
for (let s = 0; s < series.length; s++) {
|
|
7325
|
+
stacked.push(
|
|
7326
|
+
Array.from({ length: periodCount }, (_, p) => {
|
|
7327
|
+
let sum = 0;
|
|
7328
|
+
for (let si = 0; si <= s; si++) sum += series[si].values[p];
|
|
7329
|
+
return sum;
|
|
7330
|
+
})
|
|
7331
|
+
);
|
|
7332
|
+
}
|
|
7333
|
+
return stacked;
|
|
7334
|
+
}
|
|
7335
|
+
function renderAccentBar() {
|
|
7336
|
+
return [
|
|
7337
|
+
`<defs><linearGradient id="accent-grad" x1="0" y1="0" x2="1" y2="0">`,
|
|
7338
|
+
` <stop offset="0%" stop-color="#10b981"/>`,
|
|
7339
|
+
` <stop offset="100%" stop-color="#06b6d4"/>`,
|
|
7340
|
+
`</linearGradient></defs>`,
|
|
7341
|
+
`<rect width="${W3}" height="${ACCENT_H3}" fill="url(#accent-grad)"/>`
|
|
7342
|
+
].join("\n");
|
|
7343
|
+
}
|
|
7344
|
+
function renderStatColumn(totalTokens, costUsd, sourceCount) {
|
|
7345
|
+
const x = 60;
|
|
7346
|
+
const baseY = ACCENT_H3 + 48;
|
|
7347
|
+
let svg = "";
|
|
7348
|
+
svg += `<text x="${x}" y="${baseY}" fill="${shareTheme.textPrimary}" font-family="${shareTheme.font}" font-size="52" font-weight="800">${escapeSvg(formatCompact(totalTokens))}</text>
|
|
7349
|
+
`;
|
|
7350
|
+
svg += `<text x="${x}" y="${baseY + 22}" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}" font-size="14" letter-spacing="3" font-weight="600">TOKENS</text>
|
|
7351
|
+
`;
|
|
7352
|
+
if (costUsd !== void 0) {
|
|
7353
|
+
svg += `<text x="${x}" y="${baseY + 50}" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}" font-size="22" font-weight="600">${escapeSvg(formatUsd(costUsd))}</text>
|
|
7354
|
+
`;
|
|
7355
|
+
}
|
|
7356
|
+
svg += `<text x="${x}" y="${baseY + 74}" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}" font-size="13">${sourceCount} source${sourceCount !== 1 ? "s" : ""}</text>
|
|
7357
|
+
`;
|
|
7358
|
+
return svg;
|
|
7359
|
+
}
|
|
7360
|
+
function renderSourcePills(series) {
|
|
7361
|
+
let svg = "";
|
|
7362
|
+
let cx = pad3.left + 10;
|
|
7363
|
+
const pillY = ACCENT_H3 + 30;
|
|
7364
|
+
for (const s of series) {
|
|
7365
|
+
const label = `${s.source} ${formatCompact(s.total)}`;
|
|
7366
|
+
const textW = label.length * 8.5;
|
|
7367
|
+
const pillW = textW + 28;
|
|
7368
|
+
const pillH = 30;
|
|
7369
|
+
svg += `<rect x="${cx}" y="${pillY}" width="${pillW.toFixed(0)}" height="${pillH}" rx="${pillH / 2}" fill="${s.color}" fill-opacity="0.15" stroke="${s.color}" stroke-opacity="0.4" stroke-width="1"/>
|
|
7370
|
+
`;
|
|
7371
|
+
svg += `<circle cx="${cx + 14}" cy="${pillY + pillH / 2}" r="4" fill="${s.color}"/>
|
|
7372
|
+
`;
|
|
7373
|
+
svg += `<text x="${cx + 24}" y="${pillY + pillH / 2 + 5}" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}" font-size="14">${escapeSvg(label)}</text>
|
|
7374
|
+
`;
|
|
7375
|
+
cx += pillW + 10;
|
|
7376
|
+
}
|
|
7377
|
+
return svg;
|
|
7378
|
+
}
|
|
7379
|
+
function renderCommandBadge(command) {
|
|
7380
|
+
const textW = command.length * 9;
|
|
7381
|
+
const badgeW = textW + 28;
|
|
7382
|
+
const badgeH = 30;
|
|
7383
|
+
const x = W3 - 60 - badgeW;
|
|
7384
|
+
const y = ACCENT_H3 + 30;
|
|
7385
|
+
return [
|
|
7386
|
+
`<rect x="${x}" y="${y}" width="${badgeW}" height="${badgeH}" rx="${badgeH / 2}" fill="none" stroke="${shareTheme.cardBorder}" stroke-width="1"/>`,
|
|
7387
|
+
`<text x="${x + badgeW / 2}" y="${y + badgeH / 2 + 5}" text-anchor="middle" font-size="13" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}">${escapeSvg(command)}</text>`
|
|
7388
|
+
].join("\n");
|
|
7389
|
+
}
|
|
7390
|
+
function renderGridLines(chartLeft, chartRight, chartTop, chartH, maxY) {
|
|
7391
|
+
const gridCount = 4;
|
|
7392
|
+
let svg = "";
|
|
7393
|
+
for (let i = 1; i <= gridCount; i++) {
|
|
7394
|
+
const val = maxY / gridCount * i;
|
|
7395
|
+
const y = chartTop + chartH - i / gridCount * chartH;
|
|
7396
|
+
svg += `<line x1="${chartLeft}" y1="${y.toFixed(2)}" x2="${chartRight}" y2="${y.toFixed(2)}" stroke="${shareTheme.gridLine}" stroke-width="1" stroke-dasharray="4 4"/>
|
|
7397
|
+
`;
|
|
7398
|
+
svg += `<text x="${(chartLeft - 12).toFixed(0)}" y="${(y + 4).toFixed(0)}" text-anchor="end" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}" font-size="11">${escapeSvg(formatCompact(val))}</text>
|
|
7399
|
+
`;
|
|
7400
|
+
}
|
|
7401
|
+
return svg;
|
|
7402
|
+
}
|
|
7403
|
+
function renderGradientDefs(series) {
|
|
7404
|
+
return series.map(
|
|
7405
|
+
(s, i) => `<linearGradient id="area-grad-${i}" x1="0" y1="0" x2="0" y2="1">
|
|
7406
|
+
<stop offset="0%" stop-color="${s.color}" stop-opacity="0.6"/>
|
|
7407
|
+
<stop offset="100%" stop-color="${s.color}" stop-opacity="0.15"/>
|
|
7408
|
+
</linearGradient>`
|
|
7409
|
+
).join("\n");
|
|
7410
|
+
}
|
|
7411
|
+
function renderStackedAreas(series, stacked, periodCount, toX, toChartY, chartBottom) {
|
|
7412
|
+
if (periodCount < 2 || series.length === 0) return "";
|
|
7413
|
+
let svg = "";
|
|
7414
|
+
for (let s = series.length - 1; s >= 0; s--) {
|
|
7415
|
+
const topPoints = Array.from({ length: periodCount }, (_, p) => ({
|
|
7416
|
+
x: toX(p),
|
|
7417
|
+
y: toChartY(stacked[s][p])
|
|
7418
|
+
}));
|
|
7419
|
+
const topPath = catmullRom(topPoints, 0.3, chartBottom);
|
|
7420
|
+
let botPath;
|
|
7421
|
+
if (s === 0) {
|
|
7422
|
+
botPath = `L${toX(periodCount - 1).toFixed(2)},${chartBottom} L${toX(0).toFixed(2)},${chartBottom}`;
|
|
7423
|
+
} else {
|
|
7424
|
+
const botPoints = Array.from({ length: periodCount }, (_, p) => ({
|
|
7425
|
+
x: toX(p),
|
|
7426
|
+
y: toChartY(stacked[s - 1][p])
|
|
7427
|
+
})).reverse();
|
|
7428
|
+
botPath = catmullRom(botPoints, 0.3, chartBottom).replace("M", "L");
|
|
7429
|
+
}
|
|
7430
|
+
svg += `<path d="${topPath} ${botPath} Z" fill="url(#area-grad-${s})" clip-path="url(#chart-clip)"/>
|
|
7431
|
+
`;
|
|
7432
|
+
}
|
|
7433
|
+
const totalPoints = Array.from({ length: periodCount }, (_, p) => ({
|
|
7434
|
+
x: toX(p),
|
|
7435
|
+
y: toChartY(stacked[stacked.length - 1][p])
|
|
7436
|
+
}));
|
|
7437
|
+
const topLinePath = catmullRom(totalPoints, 0.3, chartBottom);
|
|
7438
|
+
svg += `<path d="${topLinePath}" fill="none" stroke="${shareTheme.textPrimary}" stroke-width="2" stroke-opacity="0.5" stroke-linejoin="round" stroke-linecap="round" clip-path="url(#chart-clip)"/>
|
|
7439
|
+
`;
|
|
7440
|
+
for (const pt of totalPoints) {
|
|
7441
|
+
svg += `<circle cx="${pt.x.toFixed(2)}" cy="${pt.y.toFixed(2)}" r="3" fill="${shareTheme.textPrimary}" fill-opacity="0.6" clip-path="url(#chart-clip)"/>
|
|
7442
|
+
`;
|
|
7443
|
+
}
|
|
7444
|
+
return svg;
|
|
7445
|
+
}
|
|
7446
|
+
function renderSinglePeriodBars(series, stacked, toX, toChartY, chartBottom, chartW) {
|
|
7447
|
+
let svg = "";
|
|
7448
|
+
const barWidth = Math.min(120, chartW * 0.4);
|
|
7449
|
+
const xCenter = toX(0);
|
|
7450
|
+
for (let s = series.length - 1; s >= 0; s--) {
|
|
7451
|
+
const yTop = toChartY(stacked[s][0]);
|
|
7452
|
+
const yBot = s === 0 ? chartBottom : toChartY(stacked[s - 1][0]);
|
|
7453
|
+
if (yBot - yTop > 0) {
|
|
7454
|
+
svg += `<rect x="${(xCenter - barWidth / 2).toFixed(2)}" y="${yTop.toFixed(2)}" width="${barWidth.toFixed(2)}" height="${(yBot - yTop).toFixed(2)}" fill="url(#area-grad-${s})" rx="4"/>
|
|
7455
|
+
`;
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7458
|
+
return svg;
|
|
7459
|
+
}
|
|
7460
|
+
function renderPeriodLabels(periods, toX, chartBottom) {
|
|
7461
|
+
const periodCount = periods.length;
|
|
7462
|
+
const maxLabels = 12;
|
|
7463
|
+
const labelStep = periodCount <= maxLabels ? 1 : Math.ceil(periodCount / maxLabels);
|
|
7464
|
+
let svg = "";
|
|
7465
|
+
for (let p = 0; p < periodCount; p += labelStep) {
|
|
7466
|
+
svg += `<text x="${toX(p).toFixed(2)}" y="${(chartBottom + 24).toFixed(0)}" text-anchor="middle" font-size="13" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}">${escapeSvg(periods[p])}</text>
|
|
7467
|
+
`;
|
|
7468
|
+
}
|
|
7469
|
+
return svg;
|
|
7470
|
+
}
|
|
7471
|
+
function renderFooter(periods) {
|
|
7472
|
+
const y = H3 - FOOTER_H3;
|
|
7473
|
+
const lineY = y + 1;
|
|
7474
|
+
const textY = y + FOOTER_H3 / 2 + 5;
|
|
7475
|
+
const range = periods.length >= 2 ? `${periods[0]} \u2192 ${periods[periods.length - 1]}` : periods[0] ?? "";
|
|
7476
|
+
return [
|
|
7477
|
+
`<line x1="0" y1="${lineY}" x2="${W3}" y2="${lineY}" stroke="${shareTheme.gridLine}" stroke-width="1"/>`,
|
|
7478
|
+
`<text x="60" y="${textY}" fill="${shareTheme.textMuted}" font-family="${shareTheme.mono}" font-size="13">llm-usage-metrics</text>`,
|
|
7479
|
+
`<text x="${W3 - 60}" y="${textY}" text-anchor="end" fill="${shareTheme.textMuted}" font-family="${shareTheme.font}" font-size="13">${escapeSvg(range)}</text>`
|
|
7480
|
+
].join("\n");
|
|
7481
|
+
}
|
|
7482
|
+
function renderUsageShareSvg(usageData, granularity) {
|
|
7483
|
+
const sourceRows = extractPeriodSourceRows(usageData.rows);
|
|
7484
|
+
const grandTotal = extractGrandTotal(usageData.rows);
|
|
7485
|
+
const periods = [...new Set(sourceRows.map((r) => r.periodKey))].sort(compareByCodePoint);
|
|
7486
|
+
const sources = [...new Set(sourceRows.map((r) => r.source))].sort(compareByCodePoint);
|
|
7487
|
+
const allSeries = buildSourceSeries(sourceRows, periods, sources);
|
|
7488
|
+
const activeSeries = allSeries.filter((s) => s.total > 0);
|
|
7489
|
+
const totalTokens = grandTotal?.totalTokens ?? 0;
|
|
7490
|
+
const totalCost = grandTotal?.costUsd;
|
|
7491
|
+
const chartLeft = pad3.left;
|
|
7492
|
+
const chartTop = pad3.top;
|
|
7493
|
+
const chartRight = W3 - pad3.right;
|
|
7494
|
+
const chartBottom = H3 - pad3.bottom;
|
|
7495
|
+
const chartW = chartRight - chartLeft;
|
|
7496
|
+
const chartH = chartBottom - chartTop;
|
|
7497
|
+
const periodCount = periods.length;
|
|
7498
|
+
const stacked = buildStackedValues(activeSeries);
|
|
7499
|
+
const maxY = periodCount > 0 && stacked.length > 0 ? Math.max(1, ...stacked[stacked.length - 1]) * 1.08 : 1;
|
|
7500
|
+
const toX = (p) => chartLeft + (periodCount <= 1 ? chartW / 2 : p / (periodCount - 1) * chartW);
|
|
7501
|
+
const toChartY = (val) => scaleY(val, maxY, chartTop, chartBottom);
|
|
7502
|
+
const commandText = `llm-usage ${granularity} --share`;
|
|
7503
|
+
let chartContent;
|
|
7504
|
+
if (periodCount === 0) {
|
|
7505
|
+
chartContent = `<text x="${(W3 / 2).toFixed(0)}" y="${(H3 / 2).toFixed(0)}" text-anchor="middle" font-size="20" fill="${shareTheme.textSecondary}" font-family="${shareTheme.font}">No usage data available</text>`;
|
|
7506
|
+
} else if (periodCount === 1) {
|
|
7507
|
+
chartContent = renderSinglePeriodBars(
|
|
7508
|
+
activeSeries,
|
|
7509
|
+
stacked,
|
|
7510
|
+
toX,
|
|
7511
|
+
toChartY,
|
|
7512
|
+
chartBottom,
|
|
7513
|
+
chartW
|
|
7514
|
+
);
|
|
7515
|
+
} else {
|
|
7516
|
+
chartContent = renderStackedAreas(
|
|
7517
|
+
activeSeries,
|
|
7518
|
+
stacked,
|
|
7519
|
+
periodCount,
|
|
7520
|
+
toX,
|
|
7521
|
+
toChartY,
|
|
7522
|
+
chartBottom
|
|
7523
|
+
);
|
|
7524
|
+
}
|
|
7525
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W3}" height="${H3}" viewBox="0 0 ${W3} ${H3}">
|
|
7526
|
+
<defs>
|
|
7527
|
+
<clipPath id="chart-clip">
|
|
7528
|
+
<rect x="${chartLeft}" y="${chartTop - 4}" width="${chartW}" height="${chartH + 8}"/>
|
|
7529
|
+
</clipPath>
|
|
7530
|
+
${renderGradientDefs(activeSeries)}
|
|
7531
|
+
</defs>
|
|
7532
|
+
<rect width="${W3}" height="${H3}" fill="${shareTheme.bg}"/>
|
|
7533
|
+
${renderAccentBar()}
|
|
7534
|
+
${renderStatColumn(totalTokens, totalCost, activeSeries.length)}
|
|
7535
|
+
${renderSourcePills(activeSeries)}
|
|
7536
|
+
${renderCommandBadge(commandText)}
|
|
7537
|
+
${renderGridLines(chartLeft, chartRight, chartTop, chartH, maxY)}
|
|
7538
|
+
${chartContent}
|
|
7539
|
+
${renderPeriodLabels(periods, toX, chartBottom)}
|
|
7540
|
+
${renderFooter(periods)}
|
|
7541
|
+
</svg>`;
|
|
7542
|
+
}
|
|
7543
|
+
|
|
6913
7544
|
// src/cli/run-usage-report.ts
|
|
6914
7545
|
function validateOutputFormatOptions3(options) {
|
|
6915
7546
|
if (options.markdown && options.json) {
|
|
@@ -6928,6 +7559,9 @@ function resolveReportFormat3(options) {
|
|
|
6928
7559
|
function resolveTableLayout(options) {
|
|
6929
7560
|
return options.perModelColumns ? "per_model_columns" : "compact";
|
|
6930
7561
|
}
|
|
7562
|
+
function resolveShareFileName(granularity) {
|
|
7563
|
+
return `usage-${granularity}-share.svg`;
|
|
7564
|
+
}
|
|
6931
7565
|
async function prepareUsageReport(granularity, options) {
|
|
6932
7566
|
validateOutputFormatOptions3(options);
|
|
6933
7567
|
const usageData = await buildUsageData(granularity, options);
|
|
@@ -6935,6 +7569,7 @@ async function prepareUsageReport(granularity, options) {
|
|
|
6935
7569
|
return {
|
|
6936
7570
|
format,
|
|
6937
7571
|
diagnostics: usageData.diagnostics,
|
|
7572
|
+
shareSvg: options.share ? renderUsageShareSvg(usageData, granularity) : void 0,
|
|
6938
7573
|
output: renderUsageReport(usageData, format, {
|
|
6939
7574
|
granularity,
|
|
6940
7575
|
tableLayout: resolveTableLayout(options)
|
|
@@ -6950,6 +7585,13 @@ async function runUsageReport(granularity, options) {
|
|
|
6950
7585
|
logger.warn(message);
|
|
6951
7586
|
});
|
|
6952
7587
|
}
|
|
7588
|
+
if (preparedReport.shareSvg) {
|
|
7589
|
+
const outputPath = await writeShareSvgFile(
|
|
7590
|
+
resolveShareFileName(granularity),
|
|
7591
|
+
preparedReport.shareSvg
|
|
7592
|
+
);
|
|
7593
|
+
logger.info(`Wrote usage share SVG: ${outputPath}`);
|
|
7594
|
+
}
|
|
6953
7595
|
console.log(preparedReport.output);
|
|
6954
7596
|
}
|
|
6955
7597
|
|
|
@@ -6999,6 +7641,9 @@ function addSharedOptions(command, options = {}) {
|
|
|
6999
7641
|
"Render per-model metrics as multiline aligned table columns (terminal/markdown)"
|
|
7000
7642
|
);
|
|
7001
7643
|
}
|
|
7644
|
+
function addShareOption(command) {
|
|
7645
|
+
return command.option("--share", "Write a share SVG image to the current directory");
|
|
7646
|
+
}
|
|
7002
7647
|
function commandDescription(granularity) {
|
|
7003
7648
|
switch (granularity) {
|
|
7004
7649
|
case "daily":
|
|
@@ -7011,7 +7656,7 @@ function commandDescription(granularity) {
|
|
|
7011
7656
|
}
|
|
7012
7657
|
function createCommand(granularity) {
|
|
7013
7658
|
const command = new Command(granularity);
|
|
7014
|
-
addSharedOptions(command).description(commandDescription(granularity)).action(async (options) => {
|
|
7659
|
+
addShareOption(addSharedOptions(command)).description(commandDescription(granularity)).action(async (options) => {
|
|
7015
7660
|
await runUsageReport(granularity, options);
|
|
7016
7661
|
});
|
|
7017
7662
|
return command;
|
|
@@ -7025,14 +7670,14 @@ function parseGranularityArgument(value) {
|
|
|
7025
7670
|
}
|
|
7026
7671
|
function createEfficiencyCommand() {
|
|
7027
7672
|
const command = new Command("efficiency");
|
|
7028
|
-
addSharedOptions(command, { includePerModelColumns: false }).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option("--repo-dir <path>", "Path to repository for Git outcome metrics").option("--include-merge-commits", "Include merge commits in Git outcome metrics").description("Show efficiency report by correlating usage metrics with local Git outcomes").action(async (granularity, options) => {
|
|
7673
|
+
addShareOption(addSharedOptions(command, { includePerModelColumns: false })).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option("--repo-dir <path>", "Path to repository for Git outcome metrics").option("--include-merge-commits", "Include merge commits in Git outcome metrics").description("Show efficiency report by correlating usage metrics with local Git outcomes").action(async (granularity, options) => {
|
|
7029
7674
|
await runEfficiencyReport(granularity, options);
|
|
7030
7675
|
});
|
|
7031
7676
|
return command;
|
|
7032
7677
|
}
|
|
7033
7678
|
function createOptimizeCommand() {
|
|
7034
7679
|
const command = new Command("optimize");
|
|
7035
|
-
addSharedOptions(command, { includePerModelColumns: false }).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
|
|
7680
|
+
addShareOption(addSharedOptions(command, { includePerModelColumns: false })).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
|
|
7036
7681
|
"--candidate-model <name>",
|
|
7037
7682
|
"Candidate model for counterfactual pricing (repeatable or comma-separated)",
|
|
7038
7683
|
collectRepeatedOption,
|