tokens-metric 0.4.6 → 0.4.7
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/dist/core/history.js +11 -0
- package/dist/tui/index.js +37 -1
- package/package.json +1 -1
package/dist/core/history.js
CHANGED
|
@@ -91,10 +91,16 @@ function aggregate(now) {
|
|
|
91
91
|
if (ts !== null && (oldest === null || ts < oldest))
|
|
92
92
|
oldest = ts;
|
|
93
93
|
}
|
|
94
|
+
// last7Days: the 7 most recent days with data, oldest→newest
|
|
95
|
+
const last7Days = Array.from({ length: 7 }, (_, i) => ({
|
|
96
|
+
dayStart: startToday - (6 - i) * 86_400_000,
|
|
97
|
+
byModel: { ...(merged.get(startToday - (6 - i) * 86_400_000)?.byModel ?? {}) },
|
|
98
|
+
}));
|
|
94
99
|
const snap = {
|
|
95
100
|
today: { byModel: {}, sessions: new Set() },
|
|
96
101
|
d7: { byModel: {}, sessions: new Set() },
|
|
97
102
|
d30: { byModel: {}, sessions: new Set() },
|
|
103
|
+
last7Days,
|
|
98
104
|
scannedFiles: cache.size,
|
|
99
105
|
generatedAt: now,
|
|
100
106
|
oldestMtimeMs: oldest,
|
|
@@ -150,10 +156,15 @@ export function bucketTopModel(b) {
|
|
|
150
156
|
return topModel;
|
|
151
157
|
}
|
|
152
158
|
function emptySnapshot(now, scannedFiles) {
|
|
159
|
+
const startToday = startOfDay(now);
|
|
153
160
|
return {
|
|
154
161
|
today: { byModel: {}, sessions: new Set() },
|
|
155
162
|
d7: { byModel: {}, sessions: new Set() },
|
|
156
163
|
d30: { byModel: {}, sessions: new Set() },
|
|
164
|
+
last7Days: Array.from({ length: 7 }, (_, i) => ({
|
|
165
|
+
dayStart: startToday - (6 - i) * 86_400_000,
|
|
166
|
+
byModel: {},
|
|
167
|
+
})),
|
|
157
168
|
scannedFiles,
|
|
158
169
|
generatedAt: now,
|
|
159
170
|
oldestMtimeMs: null,
|
package/dist/tui/index.js
CHANGED
|
@@ -327,9 +327,45 @@ function BarRow({ label, value, max, total, color, cost, }) {
|
|
|
327
327
|
}
|
|
328
328
|
// ── History panel ────────────────────────────────────────────────────────────
|
|
329
329
|
function HistoryPanel({ history }) {
|
|
330
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, flexDirection: "column", width: "100%", children: [_jsxs(Text, { bold: true, color: "blue", children: ['Usage history', _jsx(Text, { bold: false, color: "blue", children: " \u00B7 refreshes every 60s" })] }), !history ? (_jsx(Text, { dimColor: true, children: "Scanning
|
|
330
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, flexDirection: "column", width: "100%", children: [_jsxs(Text, { bold: true, color: "blue", children: ['Usage history', _jsx(Text, { bold: false, color: "blue", children: " \u00B7 refreshes every 60s" })] }), !history ? (_jsx(Text, { dimColor: true, children: "Scanning\u2026" })) : history.scannedFiles === 0 ? (_jsx(Text, { dimColor: true, children: "No transcripts found." })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(DualBarChart, { days: history.last7Days, now: history.generatedAt }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(HistoryRow, { label: "", today: "Today", d7: "7d", d30: "30d", dim: true }), _jsx(HistoryRow, { label: "Tokens ", today: fmtNumber(bucketTokens(history.today)), d7: fmtNumber(bucketTokens(history.d7)), d30: fmtNumber(bucketTokens(history.d30)) }), _jsx(HistoryRow, { label: "Cost~ ", today: fmtCost(bucketCostUSD(history.today)), d7: fmtCost(bucketCostUSD(history.d7)), d30: fmtCost(bucketCostUSD(history.d30)) }), _jsx(HistoryRow, { label: "Sessions", today: String(history.today.sessions.size), d7: String(history.d7.sessions.size), d30: String(history.d30.sessions.size) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [`scanned ${history.scannedFiles} transcripts`, history.oldestMtimeMs !== null &&
|
|
331
331
|
` · data since ${fmtDate(history.oldestMtimeMs)} (${daysAgo(history.oldestMtimeMs, history.generatedAt)} days)`] }) })] }))] }));
|
|
332
332
|
}
|
|
333
|
+
// ── Dual bar chart ────────────────────────────────────────────────────────────
|
|
334
|
+
const CHART_BAR_WIDTH = 28;
|
|
335
|
+
function claudeTokens(byModel) {
|
|
336
|
+
return Object.entries(byModel)
|
|
337
|
+
.filter(([m]) => m !== 'codex')
|
|
338
|
+
.reduce((s, [, u]) => s + totalTokens(u), 0);
|
|
339
|
+
}
|
|
340
|
+
function codexTokens(byModel) {
|
|
341
|
+
return totalTokens(byModel['codex'] ?? { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 });
|
|
342
|
+
}
|
|
343
|
+
function claudeCost(byModel) {
|
|
344
|
+
return Object.entries(byModel)
|
|
345
|
+
.filter(([m]) => m !== 'codex')
|
|
346
|
+
.reduce((s, [m, u]) => s + (estimateCostUSD(m, u) ?? 0), 0);
|
|
347
|
+
}
|
|
348
|
+
function DualBarChart({ days, now }) {
|
|
349
|
+
const maxTokens = Math.max(1, ...days.flatMap((d) => [claudeTokens(d.byModel), codexTokens(d.byModel)]));
|
|
350
|
+
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
351
|
+
const todayStart = (() => { const d = new Date(now); d.setHours(0, 0, 0, 0); return d.getTime(); })();
|
|
352
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: 'last 7 days' }), days.map((day) => {
|
|
353
|
+
const ct = claudeTokens(day.byModel);
|
|
354
|
+
const cxt = codexTokens(day.byModel);
|
|
355
|
+
const cc = claudeCost(day.byModel);
|
|
356
|
+
const cxc = Object.entries(day.byModel)
|
|
357
|
+
.filter(([m]) => m === 'codex')
|
|
358
|
+
.reduce((s, [m, u]) => s + (estimateCostUSD(m, u) ?? 0), 0);
|
|
359
|
+
const isToday = day.dayStart === todayStart;
|
|
360
|
+
const label = isToday ? 'today' : DAY_LABELS[new Date(day.dayStart).getDay()];
|
|
361
|
+
const hasData = ct > 0 || cxt > 0;
|
|
362
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: isToday, color: isToday ? 'white' : undefined, children: [label.padEnd(6), !hasData && _jsx(Text, { dimColor: true, children: " no data" })] }), ct > 0 && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' claude ' }), _jsx(Text, { color: "cyan", children: solidBar(ct / maxTokens, CHART_BAR_WIDTH) }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: true, children: fmtNumber(ct).padStart(7) }), cc > 0 && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(cc)}` })] })), cxt > 0 && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' codex ' }), _jsx(Text, { color: "magenta", children: solidBar(cxt / maxTokens, CHART_BAR_WIDTH) }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: true, children: fmtNumber(cxt).padStart(7) }), cxc > 0 && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(cxc)}` })] }))] }, day.dayStart));
|
|
363
|
+
})] }));
|
|
364
|
+
}
|
|
365
|
+
function solidBar(ratio, width) {
|
|
366
|
+
const filled = Math.round(Math.min(1, ratio) * width);
|
|
367
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
368
|
+
}
|
|
333
369
|
function fmtDate(ms) {
|
|
334
370
|
const d = new Date(ms);
|
|
335
371
|
const y = d.getFullYear();
|