claude-roi 0.8.7 → 0.8.9
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 +1 -1
- package/src/dashboard.html +127 -17
- package/src/metrics.js +23 -8
package/package.json
CHANGED
package/src/dashboard.html
CHANGED
|
@@ -2245,6 +2245,8 @@ let sortCol = 'startTime';
|
|
|
2245
2245
|
let sortOrder = -1;
|
|
2246
2246
|
let timelineChart = null;
|
|
2247
2247
|
let timelineLogScale = false;
|
|
2248
|
+
let tokenBurnChart = null;
|
|
2249
|
+
let tokenBurnPercentMode = false;
|
|
2248
2250
|
|
|
2249
2251
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
2250
2252
|
initTheme();
|
|
@@ -2308,6 +2310,7 @@ function render() {
|
|
|
2308
2310
|
<div class="chart-card full-width">
|
|
2309
2311
|
<div class="chart-header">
|
|
2310
2312
|
<h3>Token Burn Rate <i class="info-tip" data-tip="Daily token consumption stacked by type (input/output/cache). Dashed orange line shows cumulative total — watch it climb.">i</i></h3>
|
|
2313
|
+
<button class="scale-toggle" onclick="toggleTokenBurnPercent()">Split view</button>
|
|
2311
2314
|
</div>
|
|
2312
2315
|
<div class="chart-container"><canvas id="chart-token-burn"></canvas></div>
|
|
2313
2316
|
</div>
|
|
@@ -2327,8 +2330,8 @@ function render() {
|
|
|
2327
2330
|
<div class="chart-container"><canvas id="chart-tools"></canvas></div>
|
|
2328
2331
|
</div>
|
|
2329
2332
|
<div class="chart-card">
|
|
2330
|
-
<h3>
|
|
2331
|
-
<div class="chart-container"><canvas id="chart-
|
|
2333
|
+
<h3>Cost per Commit by Model <i class="info-tip" data-tip="Compare how much each model costs per commit. Lower blue bars = better value. Purple bars show commit volume for confidence. Use this to decide which model to use for different tasks.">i</i></h3>
|
|
2334
|
+
<div class="chart-container"><canvas id="chart-model-efficiency"></canvas></div>
|
|
2332
2335
|
</div>
|
|
2333
2336
|
<div class="chart-card">
|
|
2334
2337
|
<h3>Productivity Heatmap <i class="info-tip" data-tip="Each cell = commits produced during that hour and day of week. Darker green = more commits. Find your peak productivity windows.">i</i></h3>
|
|
@@ -2891,7 +2894,7 @@ function initCharts() {
|
|
|
2891
2894
|
let cumulative = 0;
|
|
2892
2895
|
const cumulativeData = dailyTokens.map(d => { cumulative += (d.totalTokens || 0); return cumulative; });
|
|
2893
2896
|
|
|
2894
|
-
new Chart(document.getElementById('chart-token-burn'), {
|
|
2897
|
+
tokenBurnChart = new Chart(document.getElementById('chart-token-burn'), {
|
|
2895
2898
|
type: 'bar',
|
|
2896
2899
|
data: {
|
|
2897
2900
|
labels: dailyTokens.map(d => new Date(d.date + 'T12:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric' })),
|
|
@@ -3123,25 +3126,29 @@ function initCharts() {
|
|
|
3123
3126
|
},
|
|
3124
3127
|
});
|
|
3125
3128
|
|
|
3126
|
-
//
|
|
3127
|
-
const
|
|
3128
|
-
const
|
|
3129
|
-
|
|
3129
|
+
// Cost per Commit by Model
|
|
3130
|
+
const modelEff = DATA.modelBreakdown;
|
|
3131
|
+
const modelEffLabels = Object.keys(modelEff).filter(k => modelEff[k].commits > 0);
|
|
3132
|
+
const modelEffColors = { opus: '#a855f7', sonnet: '#3b82f6', haiku: '#22d3a8', unknown: '#94a3b8' };
|
|
3133
|
+
new Chart(document.getElementById('chart-model-efficiency'), {
|
|
3130
3134
|
type: 'bar',
|
|
3131
3135
|
data: {
|
|
3132
|
-
labels:
|
|
3136
|
+
labels: modelEffLabels.map(m => m.charAt(0).toUpperCase() + m.slice(1) + ' ($' + modelEff[m].cost.toFixed(0) + ' total)'),
|
|
3133
3137
|
datasets: [
|
|
3134
3138
|
{
|
|
3135
3139
|
label: 'Avg $/Commit',
|
|
3136
|
-
data:
|
|
3137
|
-
backgroundColor: '#
|
|
3140
|
+
data: modelEffLabels.map(m => modelEff[m].avgCostPerCommit),
|
|
3141
|
+
backgroundColor: modelEffLabels.map(m => modelEffColors[m] || '#94a3b8'),
|
|
3138
3142
|
borderRadius: 4,
|
|
3139
3143
|
yAxisID: 'y',
|
|
3140
3144
|
},
|
|
3141
3145
|
{
|
|
3142
|
-
label: '
|
|
3143
|
-
data:
|
|
3144
|
-
backgroundColor:
|
|
3146
|
+
label: 'Commits',
|
|
3147
|
+
data: modelEffLabels.map(m => Math.round(modelEff[m].commits)),
|
|
3148
|
+
backgroundColor: modelEffLabels.map(m => {
|
|
3149
|
+
const c = modelEffColors[m] || '#94a3b8';
|
|
3150
|
+
return c + '40';
|
|
3151
|
+
}),
|
|
3145
3152
|
borderRadius: 4,
|
|
3146
3153
|
yAxisID: 'y1',
|
|
3147
3154
|
},
|
|
@@ -3150,10 +3157,30 @@ function initCharts() {
|
|
|
3150
3157
|
options: {
|
|
3151
3158
|
responsive: true,
|
|
3152
3159
|
maintainAspectRatio: false,
|
|
3153
|
-
plugins: {
|
|
3160
|
+
plugins: {
|
|
3161
|
+
legend: { position: 'top' },
|
|
3162
|
+
tooltip: {
|
|
3163
|
+
callbacks: {
|
|
3164
|
+
label: ctx => {
|
|
3165
|
+
if (ctx.datasetIndex === 0) return ` Avg $/Commit: $${ctx.parsed.y.toFixed(2)}`;
|
|
3166
|
+
return ` Commits: ${ctx.parsed.y}`;
|
|
3167
|
+
},
|
|
3168
|
+
afterBody: (items) => {
|
|
3169
|
+
const idx = items[0].dataIndex;
|
|
3170
|
+
const m = modelEffLabels[idx];
|
|
3171
|
+
const d = modelEff[m];
|
|
3172
|
+
const lines = [];
|
|
3173
|
+
lines.push(`Sessions: ${d.sessions}`);
|
|
3174
|
+
lines.push(`Total: $${d.cost.toFixed(2)} | ${formatTokens(d.tokens)} tokens`);
|
|
3175
|
+
if (d.tokensPerCommit) lines.push(`${formatTokens(d.tokensPerCommit)} tokens/commit`);
|
|
3176
|
+
return lines;
|
|
3177
|
+
},
|
|
3178
|
+
},
|
|
3179
|
+
},
|
|
3180
|
+
},
|
|
3154
3181
|
scales: {
|
|
3155
|
-
y: { type: 'linear', position: 'left', title: { display: true, text: 'Avg $/Commit' } },
|
|
3156
|
-
y1: { type: 'linear', position: 'right', title: { display: true, text: '
|
|
3182
|
+
y: { type: 'linear', position: 'left', title: { display: true, text: 'Avg $/Commit' }, ticks: { callback: v => '$' + v.toFixed(1) } },
|
|
3183
|
+
y1: { type: 'linear', position: 'right', title: { display: true, text: 'Commits' }, grid: { drawOnChartArea: false } },
|
|
3157
3184
|
},
|
|
3158
3185
|
},
|
|
3159
3186
|
});
|
|
@@ -3263,10 +3290,93 @@ window.toggleTimelineScale = function() {
|
|
|
3263
3290
|
timelineChart.options.scales.y.type = scaleType;
|
|
3264
3291
|
timelineChart.options.scales.y1.type = scaleType;
|
|
3265
3292
|
timelineChart.update();
|
|
3266
|
-
const btn = document.querySelector('.scale-toggle');
|
|
3293
|
+
const btn = document.querySelector('#chart-timeline').closest('.chart-card').querySelector('.scale-toggle');
|
|
3267
3294
|
if (btn) btn.textContent = timelineLogScale ? 'Linear scale' : 'Log scale';
|
|
3268
3295
|
};
|
|
3269
3296
|
|
|
3297
|
+
window.toggleTokenBurnPercent = function() {
|
|
3298
|
+
if (!tokenBurnChart) return;
|
|
3299
|
+
tokenBurnPercentMode = !tokenBurnPercentMode;
|
|
3300
|
+
const dailyTokens = DATA.daily;
|
|
3301
|
+
if (tokenBurnPercentMode) {
|
|
3302
|
+
// Switch to split line view — each token type gets its own line
|
|
3303
|
+
tokenBurnChart.data.datasets[0].type = 'line';
|
|
3304
|
+
tokenBurnChart.data.datasets[0].borderColor = 'rgba(59, 130, 246, 1)';
|
|
3305
|
+
tokenBurnChart.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.1)';
|
|
3306
|
+
tokenBurnChart.data.datasets[0].fill = true;
|
|
3307
|
+
tokenBurnChart.data.datasets[0].stack = undefined;
|
|
3308
|
+
tokenBurnChart.data.datasets[0].borderWidth = 2;
|
|
3309
|
+
tokenBurnChart.data.datasets[0].pointRadius = 2;
|
|
3310
|
+
tokenBurnChart.data.datasets[0].tension = 0.3;
|
|
3311
|
+
|
|
3312
|
+
tokenBurnChart.data.datasets[1].type = 'line';
|
|
3313
|
+
tokenBurnChart.data.datasets[1].borderColor = 'rgba(168, 85, 247, 1)';
|
|
3314
|
+
tokenBurnChart.data.datasets[1].backgroundColor = 'rgba(168, 85, 247, 0.1)';
|
|
3315
|
+
tokenBurnChart.data.datasets[1].fill = true;
|
|
3316
|
+
tokenBurnChart.data.datasets[1].stack = undefined;
|
|
3317
|
+
tokenBurnChart.data.datasets[1].borderWidth = 2;
|
|
3318
|
+
tokenBurnChart.data.datasets[1].pointRadius = 2;
|
|
3319
|
+
tokenBurnChart.data.datasets[1].tension = 0.3;
|
|
3320
|
+
|
|
3321
|
+
tokenBurnChart.data.datasets[2].type = 'line';
|
|
3322
|
+
tokenBurnChart.data.datasets[2].borderColor = 'rgba(6, 182, 212, 1)';
|
|
3323
|
+
tokenBurnChart.data.datasets[2].backgroundColor = 'rgba(6, 182, 212, 0.1)';
|
|
3324
|
+
tokenBurnChart.data.datasets[2].fill = true;
|
|
3325
|
+
tokenBurnChart.data.datasets[2].stack = undefined;
|
|
3326
|
+
tokenBurnChart.data.datasets[2].borderWidth = 2;
|
|
3327
|
+
tokenBurnChart.data.datasets[2].pointRadius = 2;
|
|
3328
|
+
tokenBurnChart.data.datasets[2].tension = 0.3;
|
|
3329
|
+
|
|
3330
|
+
tokenBurnChart.data.datasets[3].hidden = true;
|
|
3331
|
+
tokenBurnChart.options.scales.x.stacked = false;
|
|
3332
|
+
tokenBurnChart.options.scales.y.stacked = false;
|
|
3333
|
+
tokenBurnChart.options.scales.y.type = 'logarithmic';
|
|
3334
|
+
tokenBurnChart.options.scales.y.title.text = 'Tokens / Day (log)';
|
|
3335
|
+
tokenBurnChart.options.scales.y1.display = false;
|
|
3336
|
+
} else {
|
|
3337
|
+
// Restore stacked bar view
|
|
3338
|
+
tokenBurnChart.data.datasets[0].type = 'bar';
|
|
3339
|
+
tokenBurnChart.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.8)';
|
|
3340
|
+
tokenBurnChart.data.datasets[0].stack = 'tokens';
|
|
3341
|
+
tokenBurnChart.data.datasets[0].borderRadius = 2;
|
|
3342
|
+
delete tokenBurnChart.data.datasets[0].borderColor;
|
|
3343
|
+
delete tokenBurnChart.data.datasets[0].fill;
|
|
3344
|
+
delete tokenBurnChart.data.datasets[0].borderWidth;
|
|
3345
|
+
delete tokenBurnChart.data.datasets[0].pointRadius;
|
|
3346
|
+
delete tokenBurnChart.data.datasets[0].tension;
|
|
3347
|
+
|
|
3348
|
+
tokenBurnChart.data.datasets[1].type = 'bar';
|
|
3349
|
+
tokenBurnChart.data.datasets[1].backgroundColor = 'rgba(168, 85, 247, 0.8)';
|
|
3350
|
+
tokenBurnChart.data.datasets[1].stack = 'tokens';
|
|
3351
|
+
tokenBurnChart.data.datasets[1].borderRadius = 2;
|
|
3352
|
+
delete tokenBurnChart.data.datasets[1].borderColor;
|
|
3353
|
+
delete tokenBurnChart.data.datasets[1].fill;
|
|
3354
|
+
delete tokenBurnChart.data.datasets[1].borderWidth;
|
|
3355
|
+
delete tokenBurnChart.data.datasets[1].pointRadius;
|
|
3356
|
+
delete tokenBurnChart.data.datasets[1].tension;
|
|
3357
|
+
|
|
3358
|
+
tokenBurnChart.data.datasets[2].type = 'bar';
|
|
3359
|
+
tokenBurnChart.data.datasets[2].backgroundColor = 'rgba(6, 182, 212, 0.6)';
|
|
3360
|
+
tokenBurnChart.data.datasets[2].stack = 'tokens';
|
|
3361
|
+
tokenBurnChart.data.datasets[2].borderRadius = 2;
|
|
3362
|
+
delete tokenBurnChart.data.datasets[2].borderColor;
|
|
3363
|
+
delete tokenBurnChart.data.datasets[2].fill;
|
|
3364
|
+
delete tokenBurnChart.data.datasets[2].borderWidth;
|
|
3365
|
+
delete tokenBurnChart.data.datasets[2].pointRadius;
|
|
3366
|
+
delete tokenBurnChart.data.datasets[2].tension;
|
|
3367
|
+
|
|
3368
|
+
tokenBurnChart.data.datasets[3].hidden = false;
|
|
3369
|
+
tokenBurnChart.options.scales.x.stacked = true;
|
|
3370
|
+
tokenBurnChart.options.scales.y.stacked = true;
|
|
3371
|
+
tokenBurnChart.options.scales.y.type = 'linear';
|
|
3372
|
+
tokenBurnChart.options.scales.y.title.text = 'Tokens / Day';
|
|
3373
|
+
tokenBurnChart.options.scales.y1.display = true;
|
|
3374
|
+
}
|
|
3375
|
+
tokenBurnChart.update();
|
|
3376
|
+
const btn = document.querySelector('#chart-token-burn').closest('.chart-card').querySelector('.scale-toggle');
|
|
3377
|
+
if (btn) btn.textContent = tokenBurnPercentMode ? 'Stacked' : 'Split view';
|
|
3378
|
+
};
|
|
3379
|
+
|
|
3270
3380
|
/* ── Share Report Card ─────────────────────────── */
|
|
3271
3381
|
|
|
3272
3382
|
const GRADE_HEX = { A: '#22d3a8', B: '#3b82f6', C: '#f59e0b', D: '#f0883e', F: '#ef4444' };
|
package/src/metrics.js
CHANGED
|
@@ -581,8 +581,10 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
581
581
|
day.totalTokens += dayData.inputTokens + dayData.outputTokens + dayData.cacheReadTokens + (dayData.cacheCreationTokens || 0);
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
// Session count attributed to
|
|
585
|
-
|
|
584
|
+
// Session count attributed to each day it had activity
|
|
585
|
+
for (const date of Object.keys(usage)) {
|
|
586
|
+
ensureDay(date).sessions++;
|
|
587
|
+
}
|
|
586
588
|
|
|
587
589
|
// Commits attributed to their own timestamps
|
|
588
590
|
for (const commit of session.commits) {
|
|
@@ -740,12 +742,25 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
740
742
|
if (dateStr === todayStr) { costByPeriod.today.cost += dayData.cost; costByPeriod.today.tokens += dTok; }
|
|
741
743
|
}
|
|
742
744
|
|
|
743
|
-
// Sessions count
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
745
|
+
// Sessions count if they had any activity in the period (via dailyUsage dates)
|
|
746
|
+
const usageDates = Object.keys(usage);
|
|
747
|
+
const hasActivityToday = usageDates.includes(todayStr);
|
|
748
|
+
const hasActivityThisWeek = usageDates.some(d => new Date(d + 'T12:00:00') >= startOfWeek);
|
|
749
|
+
const hasActivityThisMonth = usageDates.some(d => new Date(d + 'T12:00:00') >= startOfMonth);
|
|
750
|
+
costByPeriod.allTime.sessions++;
|
|
751
|
+
if (hasActivityThisMonth) costByPeriod.month.sessions++;
|
|
752
|
+
if (hasActivityThisWeek) costByPeriod.week.sessions++;
|
|
753
|
+
if (hasActivityToday) costByPeriod.today.sessions++;
|
|
754
|
+
|
|
755
|
+
// Commits count by their actual commit date, not session start date
|
|
756
|
+
for (const commit of (session.commits || [])) {
|
|
757
|
+
const commitDateStr = toDateStr(commit.timestamp);
|
|
758
|
+
const commitDate = new Date(commitDateStr + 'T12:00:00');
|
|
759
|
+
costByPeriod.allTime.commits++;
|
|
760
|
+
if (commitDate >= startOfMonth) costByPeriod.month.commits++;
|
|
761
|
+
if (commitDate >= startOfWeek) costByPeriod.week.commits++;
|
|
762
|
+
if (commitDateStr === todayStr) costByPeriod.today.commits++;
|
|
763
|
+
}
|
|
749
764
|
}
|
|
750
765
|
|
|
751
766
|
const summary = {
|