tokens-metric 0.4.8 → 0.4.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/dist/tui/index.js +44 -5
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -49,6 +49,7 @@ function App() {
|
|
|
49
49
|
// openTab: which panel is expanded (null = all collapsed)
|
|
50
50
|
const [focusedTab, setFocusedTab] = useState(1);
|
|
51
51
|
const [openTab, setOpenTab] = useState(null);
|
|
52
|
+
const [sessionCursor, setSessionCursor] = useState(0);
|
|
52
53
|
const startedAtRef = useRef(Date.now());
|
|
53
54
|
// useInput requires raw mode (interactive TTY). Skip it when stdin is piped
|
|
54
55
|
// or otherwise non-interactive, so `node dist/tui/index.js | cat` still
|
|
@@ -63,7 +64,18 @@ function App() {
|
|
|
63
64
|
setOpenTab(null);
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
|
-
//
|
|
67
|
+
// ↑↓ navigate sessions when Sessions tab is open
|
|
68
|
+
if (openTab === 3) {
|
|
69
|
+
if (key.upArrow) {
|
|
70
|
+
setSessionCursor((c) => Math.max(0, c - 1));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (key.downArrow) {
|
|
74
|
+
setSessionCursor((c) => c + 1);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Arrow keys move the tab cursor
|
|
67
79
|
if (key.leftArrow) {
|
|
68
80
|
setFocusedTab((t) => (t > 1 ? (t - 1) : t));
|
|
69
81
|
return;
|
|
@@ -238,7 +250,7 @@ function App() {
|
|
|
238
250
|
: claudeLastTailAt ?? codexLastTailAt;
|
|
239
251
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Header, { auth: auth, codexDetected: codexDetected, sessionsToday: today.sessions, projectsToday: today.projects, lastTailAt: lastTailAt, startedAt: startedAtRef.current, now: now, updateAvailable: updateAvailable }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [claudeStats
|
|
240
252
|
? _jsx(SessionStatusBar, { stats: claudeStats, ratePerSec: claudeRate, now: now, series: claudeSeries })
|
|
241
|
-
: !codexStats && _jsx(SessionStatusBar, { stats: null, ratePerSec: 0, now: now, series: claudeSeries }), codexStats && (_jsx(Box, { marginTop: claudeStats ? 1 : 0, children: _jsx(SessionStatusBar, { stats: codexStats, ratePerSec: codexRate, now: now, series: codexSeries }) }))] }), _jsx(Box, { marginTop: 1, children: _jsx(TabBar, { focusedTab: focusedTab, openTab: openTab }) }), openTab !== null && (_jsxs(Box, { marginTop: 1, children: [openTab === 1 && (_jsx(BreakdownPanel, { stats: primaryStats, series: primarySeries, ratePerSec: primaryRate })), openTab === 2 && _jsx(HistoryPanel, { history: history }), openTab === 3 && _jsx(TodaySessionsPanel, { sessions: todaySessions, now: now }), openTab === 4 && (_jsx(TranscriptsPanel, { transcripts: transcripts, activePath: claudePath, now: now }))] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "magenta", children: "q" }), " quit \u00B7", ' ', _jsx(Text, { color: "magenta", children: "\u2190\u2192" }), " move \u00B7", ' ', _jsx(Text, { color: "magenta", children: "enter" }), " open/close \u00B7", ' ', _jsx(Text, { color: "magenta", children: "1\u20134" }), " jump \u00B7 pricing is"
|
|
253
|
+
: !codexStats && _jsx(SessionStatusBar, { stats: null, ratePerSec: 0, now: now, series: claudeSeries }), codexStats && (_jsx(Box, { marginTop: claudeStats ? 1 : 0, children: _jsx(SessionStatusBar, { stats: codexStats, ratePerSec: codexRate, now: now, series: codexSeries }) }))] }), _jsx(Box, { marginTop: 1, children: _jsx(TabBar, { focusedTab: focusedTab, openTab: openTab }) }), openTab !== null && (_jsxs(Box, { marginTop: 1, children: [openTab === 1 && (_jsx(BreakdownPanel, { stats: primaryStats, series: primarySeries, ratePerSec: primaryRate })), openTab === 2 && _jsx(HistoryPanel, { history: history }), openTab === 3 && _jsx(TodaySessionsPanel, { sessions: todaySessions, now: now, cursor: Math.min(sessionCursor, Math.max(0, todaySessions.length - 1)) }), openTab === 4 && (_jsx(TranscriptsPanel, { transcripts: transcripts, activePath: claudePath, now: now }))] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "magenta", children: "q" }), " quit \u00B7", ' ', _jsx(Text, { color: "magenta", children: "\u2190\u2192" }), " move \u00B7", ' ', _jsx(Text, { color: "magenta", children: "enter" }), " open/close \u00B7", ' ', _jsx(Text, { color: "magenta", children: "1\u20134" }), " jump", openTab === 3 && (_jsxs(Text, { dimColor: true, children: [' · ', _jsx(Text, { color: "magenta", children: "\u2191\u2193" }), " select session"] })), _jsx(Text, { dimColor: true, children: " \u00B7 pricing is " }), _jsx(Text, { italic: true, dimColor: true, children: "API-equivalent" }), _jsx(Text, { dimColor: true, children: ", not your real bill on a subscription" })] }) })] }));
|
|
242
254
|
}
|
|
243
255
|
// ── Header ───────────────────────────────────────────────────────────────────
|
|
244
256
|
function Header({ auth, codexDetected, sessionsToday, projectsToday, lastTailAt, startedAt, now, updateAvailable, }) {
|
|
@@ -399,8 +411,9 @@ function fmtTopModel(b) {
|
|
|
399
411
|
return m ? shortModel(m) : '—';
|
|
400
412
|
}
|
|
401
413
|
// ── Today's sessions panel ───────────────────────────────────────────────────
|
|
402
|
-
function TodaySessionsPanel({ sessions, now }) {
|
|
403
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, flexDirection: "column", width: "100%", children: [_jsxs(Text, { bold: true, color: "blue", children: ["Today's sessions", _jsx(Text, { bold: false, color: "blue", children: ` · ${sessions.length} ${plural(sessions.length, 'session', 'sessions')} today` })] }), sessions.map((s) => {
|
|
414
|
+
function TodaySessionsPanel({ sessions, now, cursor, }) {
|
|
415
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, flexDirection: "column", width: "100%", children: [_jsxs(Text, { bold: true, color: "blue", children: ["Today's sessions", _jsx(Text, { bold: false, color: "blue", children: ` · ${sessions.length} ${plural(sessions.length, 'session', 'sessions')} today` })] }), sessions.map((s, idx) => {
|
|
416
|
+
const isSelected = idx === cursor;
|
|
404
417
|
const tokens = Object.values(s.byModel).reduce((acc, u) => acc + totalTokens(u), 0);
|
|
405
418
|
const cost = Object.entries(s.byModel).reduce((acc, [m, u]) => {
|
|
406
419
|
const c = estimateCostUSD(m, u);
|
|
@@ -414,9 +427,35 @@ function TodaySessionsPanel({ sessions, now }) {
|
|
|
414
427
|
const duration = s.startedAt !== null && s.endedAt !== null
|
|
415
428
|
? fmtDuration(s.endedAt - s.startedAt)
|
|
416
429
|
: null;
|
|
417
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: s.isActive ? 'green' : 'gray', children: s.isActive ? '▶ ' : ' ' }), _jsx(Text, { bold: s.isActive, children: s.startedAt ? fmtTime(s.startedAt) : '??:??' }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { wrap: "truncate-middle", dimColor: !s.isActive, children: OPTS.reveal ? (s.cwd ?? '—') : displayCwd(s.cwd) }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "cyan", children: topModel ? shortModel(topModel) : '—' }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { children: fmtNumber(tokens) }), cost !== null && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(cost)}` }), duration && _jsx(Text, { dimColor: true, children: ` ${duration}` }), s.isActive && _jsx(Text, { color: "green", children: " active" })] }, s.sessionId));
|
|
430
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: isSelected ? 'cyan' : s.isActive ? 'green' : 'gray', children: isSelected ? '› ' : s.isActive ? '▶ ' : ' ' }), _jsx(Text, { bold: isSelected || s.isActive, color: isSelected ? 'cyan' : undefined, children: s.startedAt ? fmtTime(s.startedAt) : '??:??' }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { wrap: "truncate-middle", dimColor: !isSelected && !s.isActive, color: isSelected ? 'cyan' : undefined, children: OPTS.reveal ? (s.cwd ?? '—') : displayCwd(s.cwd) }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "cyan", children: topModel ? shortModel(topModel) : '—' }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { bold: isSelected, children: fmtNumber(tokens) }), cost !== null && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(cost)}` }), duration && _jsx(Text, { dimColor: true, children: ` ${duration}` }), s.isActive && _jsx(Text, { color: "green", children: " active" })] }), isSelected && _jsx(SessionDetail, { session: s })] }, s.sessionId));
|
|
418
431
|
})] }));
|
|
419
432
|
}
|
|
433
|
+
function SessionDetail({ session: s }) {
|
|
434
|
+
const DETAIL_BAR = 16;
|
|
435
|
+
// Aggregate all models into one Usage for the bar chart
|
|
436
|
+
const totals = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
|
|
437
|
+
for (const u of Object.values(s.byModel)) {
|
|
438
|
+
totals.input_tokens += u.input_tokens;
|
|
439
|
+
totals.output_tokens += u.output_tokens;
|
|
440
|
+
totals.cache_creation_input_tokens += u.cache_creation_input_tokens;
|
|
441
|
+
totals.cache_read_input_tokens += u.cache_read_input_tokens;
|
|
442
|
+
}
|
|
443
|
+
const total = totalTokens(totals);
|
|
444
|
+
const max = Math.max(1, totals.input_tokens, totals.output_tokens, totals.cache_creation_input_tokens, totals.cache_read_input_tokens);
|
|
445
|
+
const allModels = Object.keys(s.byModel);
|
|
446
|
+
const primaryModel = allModels.reduce((best, m) => best === null || totalTokens(s.byModel[m]) > totalTokens(s.byModel[best]) ? m : best, null) ?? '';
|
|
447
|
+
const totalCost = Object.entries(s.byModel).reduce((acc, [m, u]) => {
|
|
448
|
+
const c = estimateCostUSD(m, u);
|
|
449
|
+
if (c === null)
|
|
450
|
+
return acc;
|
|
451
|
+
return (acc ?? 0) + c;
|
|
452
|
+
}, null);
|
|
453
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Input " }), _jsx(Text, { color: "cyan", children: bar(totals.input_tokens / max, DETAIL_BAR) }), _jsxs(Text, { children: [" ", fmtNumber(totals.input_tokens).padStart(7)] }), _jsx(Text, { dimColor: true, children: ` ${total > 0 ? ((totals.input_tokens / total) * 100).toFixed(1).padStart(5) : ' 0.0'}%` }), (() => { const c = categoryCostUSD(primaryModel, 'input', totals.input_tokens); return c !== null ? _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(c)}` }) : null; })()] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Output " }), _jsx(Text, { color: "green", children: bar(totals.output_tokens / max, DETAIL_BAR) }), _jsxs(Text, { children: [" ", fmtNumber(totals.output_tokens).padStart(7)] }), _jsx(Text, { dimColor: true, children: ` ${total > 0 ? ((totals.output_tokens / total) * 100).toFixed(1).padStart(5) : ' 0.0'}%` }), (() => { const c = categoryCostUSD(primaryModel, 'output', totals.output_tokens); return c !== null ? _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(c)}` }) : null; })()] }), totals.cache_creation_input_tokens > 0 && (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "C.write " }), _jsx(Text, { color: "yellow", children: bar(totals.cache_creation_input_tokens / max, DETAIL_BAR) }), _jsxs(Text, { children: [" ", fmtNumber(totals.cache_creation_input_tokens).padStart(7)] }), _jsx(Text, { dimColor: true, children: ` ${total > 0 ? ((totals.cache_creation_input_tokens / total) * 100).toFixed(1).padStart(5) : ' 0.0'}%` }), (() => { const c = categoryCostUSD(primaryModel, 'cacheWrite', totals.cache_creation_input_tokens); return c !== null ? _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(c)}` }) : null; })()] })), totals.cache_read_input_tokens > 0 && (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "C.read " }), _jsx(Text, { color: "magenta", children: bar(totals.cache_read_input_tokens / max, DETAIL_BAR) }), _jsxs(Text, { children: [" ", fmtNumber(totals.cache_read_input_tokens).padStart(7)] }), _jsx(Text, { dimColor: true, children: ` ${total > 0 ? ((totals.cache_read_input_tokens / total) * 100).toFixed(1).padStart(5) : ' 0.0'}%` }), (() => { const c = categoryCostUSD(primaryModel, 'cacheRead', totals.cache_read_input_tokens); return c !== null ? _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(c)}` }) : null; })()] }))] }), _jsx(Text, { dimColor: true, children: '─'.repeat(DETAIL_BAR + 32) }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: 'Total ' }), _jsx(Text, { bold: true, children: fmtNumber(total).padStart(DETAIL_BAR + 2 + 7) }), totalCost !== null && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(totalCost)}` })] }), allModels.length > 1 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "by model" }), allModels.map((m) => {
|
|
454
|
+
const mu = s.byModel[m];
|
|
455
|
+
const mc = estimateCostUSD(m, mu);
|
|
456
|
+
return (_jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [" ", shortModel(m)] }), _jsx(Text, { dimColor: true, children: " \u03A3 " }), _jsx(Text, { children: fmtNumber(totalTokens(mu)) }), mc !== null && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(mc)}` })] }, m));
|
|
457
|
+
})] }))] }) }));
|
|
458
|
+
}
|
|
420
459
|
function fmtDuration(ms) {
|
|
421
460
|
const totalSec = Math.floor(ms / 1000);
|
|
422
461
|
const h = Math.floor(totalSec / 3600);
|