letmecode 0.1.12 → 0.1.13

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.
@@ -12,43 +12,38 @@ const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
12
12
  const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
13
13
  // SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
14
14
  const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
15
- const VERTICAL_TABS = [
15
+ const DETAIL_TABS = [
16
16
  { id: "limit-windows", label: "Limits" },
17
17
  { id: "summary", label: "Summary" },
18
- { id: "day-to-day-analyses", label: "day to day" },
19
- { id: "usage-by-model", label: "by model" }
18
+ { id: "day-to-day-analyses", label: "Daily" },
19
+ { id: "usage-by-model", label: "Models" }
20
20
  ];
21
21
  const CODEX_CREDIT_COST_USD = 0.01;
22
- const VERTICAL_TAB_WIDTH = 12;
23
- const LIMIT_WINDOW_COLUMNS = {
24
- plan: 8,
25
- window: 8,
26
- used: 10,
27
- date: 17,
28
- value: 10
29
- };
30
- const MODEL_USAGE_COLUMNS = {
31
- model: 17,
32
- input: 11,
33
- output: 10,
34
- cacheRead: 11,
35
- cacheWrite: 11,
36
- cacheWrite5m: 10,
37
- cacheWrite1h: 10,
38
- credits: 12,
39
- value: 12
40
- };
41
- const DAY_USAGE_COLUMNS = {
42
- day: 11,
43
- events: 6,
44
- input: 11,
45
- output: 10,
46
- cacheRead: 11,
47
- cacheWrite: 11,
48
- cacheWrite5m: 10,
49
- cacheWrite1h: 10,
50
- value: 10
51
- };
22
+ const LIMIT_TABLE_COLUMNS = [
23
+ { header: "Plan", width: 5 },
24
+ { header: "Window", width: 6 },
25
+ { header: "Used", width: 8 },
26
+ { header: "Start", width: 14 },
27
+ { header: "End", width: 14 },
28
+ { header: "API eq.", width: 8 }
29
+ ];
30
+ const DAILY_TABLE_COLUMNS = [
31
+ { header: "Day", width: 9 },
32
+ { header: "Ev", width: 5 },
33
+ { header: "Input", width: 8 },
34
+ { header: "Output", width: 7 },
35
+ { header: "C read", width: 8 },
36
+ { header: "C write", width: 7 },
37
+ { header: "API eq.", width: 7 }
38
+ ];
39
+ const MODEL_TABLE_COLUMNS = [
40
+ { header: "Model", width: 16 },
41
+ { header: "Input", width: 7 },
42
+ { header: "Output", width: 7 },
43
+ { header: "C read", width: 7 },
44
+ { header: "C write", width: 7 },
45
+ { header: "API eq.", width: 7 }
46
+ ];
52
47
  const COPILOT_ACTIONS = [
53
48
  { id: "vscode", label: "Start logging VS Code", enabled: true }
54
49
  ];
@@ -65,7 +60,7 @@ function App(props) {
65
60
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
66
61
  const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
67
62
  const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
68
- const [selectedVerticalTabIndex, setSelectedVerticalTabIndex] = useState(0);
63
+ const [selectedDetailTabIndex, setSelectedDetailTabIndex] = useState(0);
69
64
  const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
70
65
  const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
71
66
  const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
@@ -75,7 +70,7 @@ function App(props) {
75
70
  const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
76
71
  const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
77
72
  const selectedProvider = sortedProviderStates[selectedProviderIndex];
78
- const selectedVerticalTab = VERTICAL_TABS[selectedVerticalTabIndex];
73
+ const selectedDetailTab = DETAIL_TABS[selectedDetailTabIndex];
79
74
  const limitRows = getLimitRows(selectedProvider);
80
75
  const dayRows = getDayRows(selectedProvider);
81
76
  const modelRows = getModelRows(selectedProvider);
@@ -145,9 +140,30 @@ function App(props) {
145
140
  return;
146
141
  }
147
142
  if (regionId.startsWith("vtab:")) {
148
- setSelectedVerticalTabIndex(Number(regionId.slice("vtab:".length)));
143
+ setSelectedDetailTabIndex(Number(regionId.slice("vtab:".length)));
149
144
  }
150
145
  });
146
+ const moveSelectedTableRow = useCallback((delta) => {
147
+ if (selectedDetailTab.id === "limit-windows") {
148
+ setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + delta, limitRows.length));
149
+ return;
150
+ }
151
+ if (selectedDetailTab.id === "usage-by-model") {
152
+ setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + delta, modelRows.length));
153
+ return;
154
+ }
155
+ if (selectedDetailTab.id === "day-to-day-analyses") {
156
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + delta, dayRows.length));
157
+ }
158
+ }, [
159
+ activeDayRowIndex,
160
+ activeLimitRowIndex,
161
+ activeModelRowIndex,
162
+ dayRows.length,
163
+ limitRows.length,
164
+ modelRows.length,
165
+ selectedDetailTab.id
166
+ ]);
151
167
  useInput((input, key) => {
152
168
  // Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
153
169
  // Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
@@ -175,32 +191,12 @@ function App(props) {
175
191
  return;
176
192
  }
177
193
  if (key.rightArrow) {
178
- if (selectedVerticalTab.id === "limit-windows") {
179
- setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + 1, limitRows.length));
180
- return;
181
- }
182
- if (selectedVerticalTab.id === "usage-by-model") {
183
- setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + 1, modelRows.length));
184
- return;
185
- }
186
- if (selectedVerticalTab.id === "day-to-day-analyses") {
187
- setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + 1, dayRows.length));
188
- return;
189
- }
194
+ setSelectedDetailTabIndex((current) => (current + 1) % DETAIL_TABS.length);
195
+ return;
190
196
  }
191
197
  if (key.leftArrow) {
192
- if (selectedVerticalTab.id === "limit-windows") {
193
- setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex - 1, limitRows.length));
194
- return;
195
- }
196
- if (selectedVerticalTab.id === "usage-by-model") {
197
- setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex - 1, modelRows.length));
198
- return;
199
- }
200
- if (selectedVerticalTab.id === "day-to-day-analyses") {
201
- setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex - 1, dayRows.length));
202
- return;
203
- }
198
+ setSelectedDetailTabIndex((current) => (current - 1 + DETAIL_TABS.length) % DETAIL_TABS.length);
199
+ return;
204
200
  }
205
201
  if ((key.tab && !key.shift) || input === "]") {
206
202
  setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
@@ -214,14 +210,14 @@ function App(props) {
214
210
  return;
215
211
  }
216
212
  if (key.downArrow || input === "j") {
217
- setSelectedVerticalTabIndex((current) => (current + 1) % VERTICAL_TABS.length);
213
+ moveSelectedTableRow(1);
218
214
  return;
219
215
  }
220
216
  if (key.upArrow || input === "k") {
221
- setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
217
+ moveSelectedTableRow(-1);
222
218
  }
223
219
  });
224
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "cyan", children: "letmecode usage dashboard" }), _jsx(Text, { color: "gray", children: "[/]/tab to switch providers, j/k or up/down for details, left/right to select a row, enter for actions, click tabs to switch, q to quit" }), _jsx(Box, { marginTop: 1, children: sortedProviderStates.map((state) => (_jsx(ProviderTab, { label: state.provider.label, active: state.provider.id === selectedProvider.provider.id, status: state.status, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id))) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexDirection: "column", width: VERTICAL_TAB_WIDTH, marginRight: 2, overflow: "hidden", children: VERTICAL_TABS.map((tab, index) => (_jsx(VerticalTab, { label: tab.label, active: index === selectedVerticalTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id))) }), _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) })] }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRow: selectedLimitRow, selectedDayRow: selectedDayRow, selectedModelRow: selectedModelRow }), _jsx(CopilotActionsPanel, { providerState: selectedProvider, actionMessage: copilotActionMessage, selectedActionIndex: selectedCopilotActionIndex }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] })] }));
220
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "cyan", children: "letmecode usage dashboard" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Provider " }), sortedProviderStates.map((state) => (_jsx(ProviderTab, { label: state.provider.label, active: state.provider.id === selectedProvider.provider.id, status: state.status, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id)))] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "View " }), DETAIL_TABS.map((tab, index) => (_jsx(DetailTab, { label: tab.label, active: index === selectedDetailTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id)))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, overflow: "hidden", children: _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRow: selectedLimitRow, selectedDayRow: selectedDayRow, selectedModelRow: selectedModelRow }), _jsx(CopilotActionsPanel, { providerState: selectedProvider, actionMessage: copilotActionMessage, selectedActionIndex: selectedCopilotActionIndex }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }), _jsx(Text, { color: "gray", children: "Tab provider \u00B7 \u2190/\u2192 view \u00B7 \u2191/\u2193 row \u00B7 q quit" })] }));
225
221
  }
226
222
  function CopilotActionsPanel(props) {
227
223
  if (props.providerState.provider.id !== "copilot") {
@@ -257,16 +253,23 @@ function formatCopilotLoggingResult(result) {
257
253
  }
258
254
  function ProviderTab(props) {
259
255
  const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
260
- const tabLabel = props.active ? ` ${props.label} ` : `[${props.label}]`;
261
- return (_jsx(Box, { marginRight: 1, ref: props.regionRef, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
256
+ const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
257
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: tabLabel }) }));
262
258
  }
263
- function VerticalTab(props) {
264
- return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
259
+ function DetailTab(props) {
260
+ const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
261
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: tabLabel }) }));
265
262
  }
266
263
  function SummaryPanel(props) {
267
264
  const { summary } = props.stats;
268
- const isCodexProvider = props.stats.providerId === "codex";
269
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: props.stats.providerLabel }), _jsxs(Text, { children: ["root: ", summary.rootLabel, " (", summary.rootPath, ")"] }), _jsxs(Text, { children: ["files: ", formatInteger(summary.filesScanned), " lines: ", formatInteger(summary.linesRead), " token events: ", formatInteger(summary.tokenEvents)] }), _jsx(UsageBreakdownLines, { totals: summary.totals }), isCodexProvider ? (_jsxs(Text, { children: ["Burned tokens for: ", formatUsageUsd(summary.totals)] })) : (_jsxs(Text, { children: ["estimated credits: ", formatUsageCredits(summary.totals)] })), _jsxs(Text, { children: ["IpO: ", formatInputPerOutput(summary.totals)] }), _jsxs(Text, { children: ["models: ", summary.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", summary.distinctPlanTypes.join(", ") || "none"] })] }));
265
+ const totals = summary.totals;
266
+ const period = resolveSummaryPeriod(props.stats.dayUsage);
267
+ const cacheRatio = resolveCacheRatio(totals);
268
+ const averageTokensPerEvent = totals.eventCount > 0 ? totals.totalTokens / totals.eventCount : 0;
269
+ const costPerEvent = totals.eventCount > 0
270
+ ? (totals.estimatedCredits * CODEX_CREDIT_COST_USD) / totals.eventCount
271
+ : 0;
272
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [props.stats.providerLabel, " overview"] }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 45, children: [_jsx(DetailRow, { label: "Plan", value: summary.distinctPlanTypes.join(", ") || "none" }), _jsx(DetailRow, { label: "Models", value: summary.distinctModels.join(", ") || "none" }), _jsx(DetailRow, { label: "Period", value: period }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: "Usage" }), _jsx(DetailRow, { label: "Events", value: formatInteger(totals.eventCount) }), _jsx(DetailRow, { label: "Input", value: formatOverviewTokenCount(totals.inputTokens) }), _jsx(DetailRow, { label: "Output", value: formatOverviewTokenCount(totals.outputTokens) }), _jsx(DetailRow, { label: "Cache read", value: formatCacheOverviewTokenCount(totals, totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Reasoning", value: formatOverviewTokenCount(totals.reasoningOutputTokens) }), _jsx(DetailRow, { label: "Total", value: formatOverviewTokenCount(totals.totalTokens) }), _jsx(DetailRow, { label: "API equiv.", value: formatUsageUsd(totals) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Efficiency" }), _jsx(DetailRow, { label: "Cache ratio", value: formatPercent(cacheRatio) }), _jsx(DetailRow, { label: "Input/output", value: formatInputOutputRatio(totals) }), _jsx(DetailRow, { label: "Avg/event", value: `${formatOverviewTokenCount(averageTokensPerEvent)} tokens` }), _jsx(DetailRow, { label: "Cost/event", value: formatUnitUsd(costPerEvent) }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: "Data source" }), _jsx(DetailRow, { label: "Files", value: formatInteger(summary.filesScanned) }), _jsx(DetailRow, { label: "Lines", value: formatInteger(summary.linesRead) }), _jsx(DetailRow, { label: "Path", value: summary.rootPath })] })] })] }));
270
273
  }
271
274
  function ContentPanel(props) {
272
275
  if (props.providerState.status === "loading") {
@@ -287,62 +290,27 @@ function ContentPanel(props) {
287
290
  return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
288
291
  }
289
292
  function LimitWindowsPanel(props) {
290
- const hasPrimaryWindows = props.stats.primaryLimitWindows.length > 0;
291
293
  const bodyLines = [
292
- { key: "primary-title", text: "Primary Limit Windows", bold: true },
293
- ...buildLimitWindowSectionLines("primary", props.stats.primaryLimitWindows, props.selectedRowKey),
294
- ...(hasPrimaryWindows
295
- ? [{ key: "section-separator", text: buildLimitSectionSeparatorLine(), color: "gray" }]
296
- : [{ key: "section-gap", text: "" }]),
297
- { key: "secondary-title", text: "Secondary Limit Windows", bold: true },
298
- ...buildLimitWindowSectionLines("secondary", props.stats.secondaryLimitWindows, props.selectedRowKey)
294
+ ...buildLimitWindowTableLines("primary", "Primary limits", props.stats.primaryLimitWindows, props.selectedRowKey),
295
+ { key: "section-gap", text: "" },
296
+ ...buildLimitWindowTableLines("secondary", "Secondary limits", props.stats.secondaryLimitWindows, props.selectedRowKey)
299
297
  ];
300
- return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, selectedBodyLineKey: props.selectedRowKey ? `limit-row:${props.selectedRowKey}` : undefined, availableHeight: props.availableHeight }));
298
+ return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, availableHeight: props.availableHeight }));
301
299
  }
302
300
  function UsageByModelPanel(props) {
303
301
  if (props.stats.modelUsage.length === 0) {
304
302
  return _jsx(Text, { color: "gray", children: "No model usage found." });
305
303
  }
306
304
  const totals = props.stats.summary.totals;
307
- const isCodexProvider = props.stats.providerId === "codex";
308
- return (_jsx(ScrollableLineViewport, { headerLines: [
309
- {
310
- key: "model-header",
311
- text: isCodexProvider
312
- ? `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} value`
313
- : `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad("credits", MODEL_USAGE_COLUMNS.credits)} value`,
314
- color: "gray"
315
- }
316
- ], bodyLines: props.stats.modelUsage.map((row) => ({
317
- key: `model-row:${row.modelId}`,
318
- text: formatModelUsageRow(row.modelId, row.totals, isCodexProvider),
319
- inverse: props.selectedModelId === row.modelId,
320
- color: props.selectedModelId === row.modelId ? "cyan" : undefined
321
- })), footerLines: [
322
- {
323
- key: "model-total",
324
- text: formatModelUsageRow("TOTAL", totals, isCodexProvider),
325
- color: "cyan"
326
- }
327
- ], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
305
+ const bodyLines = buildModelUsageTableLines(props.stats.modelUsage, totals, props.selectedModelId);
306
+ return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
328
307
  }
329
308
  function DayToDayPanel(props) {
330
309
  if (props.stats.dayUsage.length === 0) {
331
310
  return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
332
311
  }
333
- const totals = props.stats.summary.totals;
334
- return (_jsx(ScrollableLineViewport, { headerLines: [
335
- {
336
- key: "day-header",
337
- text: `${pad("day", DAY_USAGE_COLUMNS.day)} ${pad("events", DAY_USAGE_COLUMNS.events)} ${pad("input", DAY_USAGE_COLUMNS.input)} ${pad("output", DAY_USAGE_COLUMNS.output)} ${pad("cacheRead", DAY_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", DAY_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", DAY_USAGE_COLUMNS.cacheWrite1h)} value`,
338
- color: "gray"
339
- }
340
- ], bodyLines: props.stats.dayUsage.map((row) => ({
341
- key: `day-row:${row.dayKey}`,
342
- text: `${pad(formatUtcDay(row.dayKey), DAY_USAGE_COLUMNS.day)} ${pad(formatCompactTokenCount(row.totals.eventCount), DAY_USAGE_COLUMNS.events)} ${pad(formatCompactTokenCount(row.totals.inputTokens), DAY_USAGE_COLUMNS.input)} ${pad(formatCompactTokenCount(row.totals.outputTokens), DAY_USAGE_COLUMNS.output)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens), DAY_USAGE_COLUMNS.cacheRead)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens), DAY_USAGE_COLUMNS.cacheWrite)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite5mInputTokens), DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite1hInputTokens), DAY_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatUsageUsd(row.totals), DAY_USAGE_COLUMNS.value)}`,
343
- inverse: props.selectedDayKey === row.dayKey,
344
- color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
345
- })), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
312
+ const bodyLines = buildDailyUsageTableLines(props.stats.dayUsage, props.selectedDayKey);
313
+ return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
346
314
  }
347
315
  function ScrollableLineViewport(props) {
348
316
  const headerLines = props.headerLines ?? [];
@@ -362,28 +330,146 @@ function ScrollableLineViewport(props) {
362
330
  function ScrollableViewportLine(props) {
363
331
  return (_jsx(Text, { bold: props.line.bold, color: props.line.color, inverse: props.line.inverse, wrap: "truncate-end", children: props.line.text }));
364
332
  }
365
- function buildLimitWindowSectionLines(scope, windows, selectedRowKey) {
333
+ function buildTableBorder(columns, left, middle, right) {
334
+ return `${left}${columns.map((column) => "─".repeat(column.width + 2)).join(middle)}${right}`;
335
+ }
336
+ function buildTableRow(columns, cells) {
337
+ return `│${columns.map((column, index) => ` ${pad(cells[index] ?? "", column.width)} `).join("│")}│`;
338
+ }
339
+ function buildLimitWindowTableLines(scope, title, windows, selectedRowKey) {
366
340
  if (windows.length === 0) {
367
- return [{ key: `${scope}-empty`, text: "No windows found.", color: "gray" }];
341
+ return [
342
+ { key: `${scope}-title`, text: title, bold: true },
343
+ { key: `${scope}-empty`, text: "No windows found.", color: "gray" }
344
+ ];
368
345
  }
369
346
  return [
347
+ { key: `${scope}-title`, text: title, bold: true },
348
+ {
349
+ key: `${scope}-top-border`,
350
+ text: buildTableBorder(LIMIT_TABLE_COLUMNS, "┌", "┬", "┐"),
351
+ color: "gray"
352
+ },
370
353
  {
371
354
  key: `${scope}-header`,
372
- text: `${pad("plan", LIMIT_WINDOW_COLUMNS.plan)} ${pad("window", LIMIT_WINDOW_COLUMNS.window)} ${pad("used", LIMIT_WINDOW_COLUMNS.used)} ${pad("start", LIMIT_WINDOW_COLUMNS.date)} ${pad("end", LIMIT_WINDOW_COLUMNS.date)} value`,
355
+ text: buildTableRow(LIMIT_TABLE_COLUMNS, LIMIT_TABLE_COLUMNS.map((column) => column.header)),
356
+ color: "gray"
357
+ },
358
+ {
359
+ key: `${scope}-header-border`,
360
+ text: buildTableBorder(LIMIT_TABLE_COLUMNS, "├", "┼", "┤"),
373
361
  color: "gray"
374
362
  },
375
363
  ...windows.map((window) => {
376
364
  const lineKey = getLimitRowKey(window);
377
- const windowLabel = formatWindowMinutes(window.windowMinutes);
365
+ const windowLabel = formatCompactWindowMinutes(window.windowMinutes);
378
366
  const usedLabel = formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent);
379
367
  const isSelected = selectedRowKey === lineKey;
380
368
  return {
381
369
  key: `limit-row:${lineKey}`,
382
- text: `${pad(window.planType, LIMIT_WINDOW_COLUMNS.plan)} ${pad(windowLabel, LIMIT_WINDOW_COLUMNS.window)} ${pad(usedLabel, LIMIT_WINDOW_COLUMNS.used)} ${pad(formatLocalDateTime(window.startTimeUtcIso), LIMIT_WINDOW_COLUMNS.date)} ${pad(formatLocalDateTime(window.endTimeUtcIso), LIMIT_WINDOW_COLUMNS.date)} ${pad(formatUsd(window.totals.estimatedCredits * CODEX_CREDIT_COST_USD), LIMIT_WINDOW_COLUMNS.value)}`,
370
+ text: buildTableRow(LIMIT_TABLE_COLUMNS, [
371
+ window.planType,
372
+ windowLabel,
373
+ usedLabel,
374
+ formatCompactLocalDateTime(window.startTimeUtcIso),
375
+ formatCompactLocalDateTime(window.endTimeUtcIso),
376
+ formatUsd(window.totals.estimatedCredits * CODEX_CREDIT_COST_USD)
377
+ ]),
378
+ inverse: isSelected,
379
+ color: isSelected ? "cyan" : undefined
380
+ };
381
+ }),
382
+ {
383
+ key: `${scope}-bottom-border`,
384
+ text: buildTableBorder(LIMIT_TABLE_COLUMNS, "└", "┴", "┘"),
385
+ color: "gray"
386
+ }
387
+ ];
388
+ }
389
+ function buildDailyUsageTableLines(rows, selectedDayKey) {
390
+ return [
391
+ { key: "daily-title", text: "Daily usage", bold: true },
392
+ {
393
+ key: "daily-top-border",
394
+ text: buildTableBorder(DAILY_TABLE_COLUMNS, "┌", "┬", "┐"),
395
+ color: "gray"
396
+ },
397
+ {
398
+ key: "daily-header",
399
+ text: buildTableRow(DAILY_TABLE_COLUMNS, DAILY_TABLE_COLUMNS.map((column) => column.header)),
400
+ color: "gray"
401
+ },
402
+ {
403
+ key: "daily-header-border",
404
+ text: buildTableBorder(DAILY_TABLE_COLUMNS, "├", "┼", "┤"),
405
+ color: "gray"
406
+ },
407
+ ...rows.map((row) => {
408
+ const isSelected = selectedDayKey === row.dayKey;
409
+ return {
410
+ key: `day-row:${row.dayKey}`,
411
+ text: buildTableRow(DAILY_TABLE_COLUMNS, [
412
+ formatUtcDay(row.dayKey),
413
+ formatCompactTokenCount(row.totals.eventCount),
414
+ formatCompactTokenCount(row.totals.inputTokens),
415
+ formatCompactTokenCount(row.totals.outputTokens),
416
+ formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens),
417
+ formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens),
418
+ formatUsageUsd(row.totals)
419
+ ]),
383
420
  inverse: isSelected,
384
421
  color: isSelected ? "cyan" : undefined
385
422
  };
386
- })
423
+ }),
424
+ {
425
+ key: "daily-bottom-border",
426
+ text: buildTableBorder(DAILY_TABLE_COLUMNS, "└", "┴", "┘"),
427
+ color: "gray"
428
+ }
429
+ ];
430
+ }
431
+ function buildModelUsageTableLines(rows, totals, selectedModelId) {
432
+ return [
433
+ { key: "model-title", text: "Model usage", bold: true },
434
+ {
435
+ key: "model-top-border",
436
+ text: buildTableBorder(MODEL_TABLE_COLUMNS, "┌", "┬", "┐"),
437
+ color: "gray"
438
+ },
439
+ {
440
+ key: "model-header",
441
+ text: buildTableRow(MODEL_TABLE_COLUMNS, MODEL_TABLE_COLUMNS.map((column) => column.header)),
442
+ color: "gray"
443
+ },
444
+ {
445
+ key: "model-header-border",
446
+ text: buildTableBorder(MODEL_TABLE_COLUMNS, "├", "┼", "┤"),
447
+ color: "gray"
448
+ },
449
+ ...rows.map((row) => {
450
+ const isSelected = selectedModelId === row.modelId;
451
+ return {
452
+ key: `model-row:${row.modelId}`,
453
+ text: formatModelUsageTableRow(row.modelId, row.totals),
454
+ inverse: isSelected,
455
+ color: isSelected ? "cyan" : undefined
456
+ };
457
+ }),
458
+ {
459
+ key: "model-total-border",
460
+ text: buildTableBorder(MODEL_TABLE_COLUMNS, "├", "┼", "┤"),
461
+ color: "gray"
462
+ },
463
+ {
464
+ key: "model-total",
465
+ text: formatModelUsageTableRow("TOTAL", totals),
466
+ color: "cyan"
467
+ },
468
+ {
469
+ key: "model-bottom-border",
470
+ text: buildTableBorder(MODEL_TABLE_COLUMNS, "└", "┴", "┘"),
471
+ color: "gray"
472
+ }
387
473
  ];
388
474
  }
389
475
  function SelectionDetailsPanel(props) {
@@ -392,17 +478,23 @@ function SelectionDetailsPanel(props) {
392
478
  }
393
479
  if (props.tabId === "limit-windows" && props.selectedLimitRow) {
394
480
  const row = props.selectedLimitRow;
395
- return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Limit details" }), _jsxs(Text, { children: [row.scope, " plan: ", row.planType, " window: ", formatWindowMinutes(row.windowMinutes), " used: ", formatUsedPercentRange(row.minUsedPercent, row.maxUsedPercent), " limit: ", row.limitId] }), _jsxs(Text, { children: ["range: ", formatLocalDateTime(row.startTimeUtcIso), " ", "->", " ", formatLocalDateTime(row.endTimeUtcIso), " events: ", formatInteger(row.eventCount)] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
481
+ return (_jsx(DetailsPanelFrame, { children: _jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 25, children: [_jsx(DetailRow, { label: "Plan", value: row.planType }), _jsx(DetailRow, { label: "Window", value: formatCompactWindowMinutes(row.windowMinutes) }), _jsx(DetailRow, { label: "Usage", value: formatUsedPercentRange(row.minUsedPercent, row.maxUsedPercent) }), _jsx(DetailRow, { label: "Events", value: formatInteger(row.eventCount) }), _jsx(DetailRow, { label: "API eq.", value: formatUsageUsd(row.totals) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailRow, { label: "Period", value: `${formatCompactLocalDateTime(row.startTimeUtcIso)} → ${formatCompactLocalDateTime(row.endTimeUtcIso)}` }), _jsx(DetailRow, { label: "Input", value: formatInteger(row.totals.inputTokens) }), _jsx(DetailRow, { label: "Cache read", value: formatCacheTokens(row.totals, row.totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Cache write", value: formatCacheTokens(row.totals, row.totals.cacheWriteInputTokens) }), _jsx(DetailRow, { label: "Output", value: formatInteger(row.totals.outputTokens) }), _jsx(DetailRow, { label: "Total", value: formatInteger(row.totals.totalTokens) })] })] }) }));
396
482
  }
397
483
  if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
398
484
  const row = props.selectedDayRow;
399
- return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Day details" }), _jsxs(Text, { children: ["day: ", formatUtcDay(row.dayKey), " events: ", formatInteger(row.totals.eventCount), " models: ", formatInteger(row.distinctModels.length), " plans: ", formatInteger(row.distinctPlanTypes.length)] }), _jsxs(Text, { children: ["range: ", formatEventRange(row.firstEventUtcIso, row.lastEventUtcIso)] }), _jsxs(Text, { children: ["models: ", row.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", row.distinctPlanTypes.join(", ") || "none"] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
485
+ return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Text, { children: ["day: ", formatUtcDay(row.dayKey), " events: ", formatInteger(row.totals.eventCount), " models: ", formatInteger(row.distinctModels.length), " plans: ", formatInteger(row.distinctPlanTypes.length)] }), _jsxs(Text, { children: ["range: ", formatEventRange(row.firstEventUtcIso, row.lastEventUtcIso)] }), _jsxs(Text, { children: ["models: ", row.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", row.distinctPlanTypes.join(", ") || "none"] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
400
486
  }
401
487
  if (props.tabId === "usage-by-model" && props.selectedModelRow) {
402
- return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Model details" }), _jsxs(Text, { children: ["model: ", props.selectedModelRow.modelId, " events: ", formatInteger(props.selectedModelRow.totals.eventCount)] }), _jsx(UsageTotalsDetails, { totals: props.selectedModelRow.totals, modelId: props.selectedModelRow.modelId })] }));
488
+ return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Text, { children: ["model: ", props.selectedModelRow.modelId, " events: ", formatInteger(props.selectedModelRow.totals.eventCount)] }), _jsx(UsageTotalsDetails, { totals: props.selectedModelRow.totals, modelId: props.selectedModelRow.modelId })] }));
403
489
  }
404
490
  return null;
405
491
  }
492
+ function DetailsPanelFrame(props) {
493
+ return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Details" }), props.children] }));
494
+ }
495
+ function DetailRow(props) {
496
+ return (_jsxs(Text, { children: [pad(props.label, 14), props.value] }));
497
+ }
406
498
  function UsageTotalsDetails(props) {
407
499
  const { totals } = props;
408
500
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(UsageBreakdownLines, { totals: totals }), _jsxs(Text, { children: ["Burned tokens for: ", formatUsageUsd(totals, props.modelId)] }), _jsxs(Text, { children: ["IpO: ", formatInputPerOutput(totals)] })] }));
@@ -410,6 +502,28 @@ function UsageTotalsDetails(props) {
410
502
  function formatInteger(value) {
411
503
  return Math.round(value).toLocaleString("en-US");
412
504
  }
505
+ function formatOverviewTokenCount(value) {
506
+ const roundedValue = Math.round(value);
507
+ if (roundedValue >= 1000000000) {
508
+ return `${formatFixedCompactNumber(roundedValue / 1000000000)}B`;
509
+ }
510
+ if (roundedValue >= 1000000) {
511
+ return `${formatFixedCompactNumber(roundedValue / 1000000)}M`;
512
+ }
513
+ if (roundedValue >= 1000) {
514
+ return `${formatFixedCompactNumber(roundedValue / 1000)}K`;
515
+ }
516
+ return formatInteger(roundedValue);
517
+ }
518
+ function formatCacheOverviewTokenCount(totals, value) {
519
+ return totals.cacheStatus === "unavailable" ? "-" : formatOverviewTokenCount(value);
520
+ }
521
+ function formatFixedCompactNumber(value) {
522
+ return value.toLocaleString("en-US", {
523
+ maximumFractionDigits: value < 10 ? 2 : 1,
524
+ minimumFractionDigits: 0
525
+ });
526
+ }
413
527
  function formatCompactTokenCount(value) {
414
528
  const roundedValue = Math.round(value);
415
529
  if (roundedValue < 1000) {
@@ -465,18 +579,51 @@ function formatUsd(value) {
465
579
  maximumFractionDigits: 2
466
580
  });
467
581
  }
582
+ function formatUnitUsd(value) {
583
+ if (!Number.isFinite(value)) {
584
+ return "-";
585
+ }
586
+ return value.toLocaleString("en-US", {
587
+ currency: "USD",
588
+ style: "currency",
589
+ minimumFractionDigits: value < 0.01 && value > 0 ? 4 : 3,
590
+ maximumFractionDigits: value < 0.01 && value > 0 ? 4 : 3
591
+ });
592
+ }
593
+ function formatPercent(value) {
594
+ if (!Number.isFinite(value)) {
595
+ return "-";
596
+ }
597
+ return `${value.toLocaleString("en-US", {
598
+ maximumFractionDigits: 1,
599
+ minimumFractionDigits: 0
600
+ })}%`;
601
+ }
602
+ function resolveCacheRatio(totals) {
603
+ if (totals.cacheStatus === "unavailable") {
604
+ return NaN;
605
+ }
606
+ const inputPool = totals.inputTokens +
607
+ totals.cacheReadInputTokens +
608
+ totals.cacheWriteInputTokens;
609
+ return inputPool > 0
610
+ ? (totals.cacheReadInputTokens / inputPool) * 100
611
+ : 0;
612
+ }
613
+ function formatInputOutputRatio(totals) {
614
+ if (totals.outputTokens <= 0) {
615
+ return "-";
616
+ }
617
+ return `${(totals.inputTokens / totals.outputTokens).toLocaleString("en-US", {
618
+ maximumFractionDigits: 1,
619
+ minimumFractionDigits: 0
620
+ })} : 1`;
621
+ }
468
622
  function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
623
+ const fmt = (v) => `${Math.round(v)}%`;
469
624
  return minUsedPercent === maxUsedPercent
470
- ? `${minUsedPercent}%`
471
- : `${minUsedPercent}%->${maxUsedPercent}%`;
472
- }
473
- function buildLimitSectionSeparatorLine() {
474
- return "─".repeat(LIMIT_WINDOW_COLUMNS.plan +
475
- LIMIT_WINDOW_COLUMNS.window +
476
- LIMIT_WINDOW_COLUMNS.used +
477
- LIMIT_WINDOW_COLUMNS.date * 2 +
478
- LIMIT_WINDOW_COLUMNS.value +
479
- 5);
625
+ ? fmt(minUsedPercent)
626
+ : `${fmt(minUsedPercent)}–${fmt(maxUsedPercent)}`;
480
627
  }
481
628
  function formatWindowMinutes(value) {
482
629
  const hours = value / 60;
@@ -485,6 +632,13 @@ function formatWindowMinutes(value) {
485
632
  }
486
633
  return `${hours.toFixed(2)}h`;
487
634
  }
635
+ function formatCompactWindowMinutes(value) {
636
+ const hours = value / 60;
637
+ if (hours >= 24) {
638
+ return `${formatCompactNumber(hours / 24)}d`;
639
+ }
640
+ return `${formatCompactNumber(hours)}h`;
641
+ }
488
642
  function formatLocalDateTime(value) {
489
643
  const parts = new Intl.DateTimeFormat("en-US", {
490
644
  month: "short",
@@ -498,6 +652,29 @@ function formatLocalDateTime(value) {
498
652
  const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
499
653
  return `${lookup.day} ${lookup.month} ${lookup.year} ${lookup.hour}:${lookup.minute}`;
500
654
  }
655
+ function formatCompactLocalDateTime(value) {
656
+ const parts = new Intl.DateTimeFormat("en-US", {
657
+ month: "short",
658
+ day: "2-digit",
659
+ hour: "2-digit",
660
+ minute: "2-digit",
661
+ hour12: false,
662
+ hourCycle: "h23"
663
+ }).formatToParts(new Date(value));
664
+ const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
665
+ return `${lookup.day} ${lookup.month} ${lookup.hour}:${lookup.minute}`;
666
+ }
667
+ function resolveSummaryPeriod(dayUsage) {
668
+ const timestamps = dayUsage.flatMap((row) => [
669
+ row.firstEventUtcIso,
670
+ row.lastEventUtcIso
671
+ ]).filter((value) => Boolean(value));
672
+ if (timestamps.length === 0) {
673
+ return "-";
674
+ }
675
+ const sortedTimestamps = timestamps.sort();
676
+ return `${formatCompactLocalDateTime(sortedTimestamps[0])} → ${formatCompactLocalDateTime(sortedTimestamps[sortedTimestamps.length - 1])}`;
677
+ }
501
678
  function formatUtcDay(value) {
502
679
  if (value === "unknown") {
503
680
  return "-";
@@ -520,12 +697,16 @@ function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
520
697
  function pad(value, length) {
521
698
  return value.length >= length ? value.slice(0, length) : value.padEnd(length);
522
699
  }
523
- function formatModelUsageRow(modelId, totals, isCodexProvider) {
700
+ function formatModelUsageTableRow(modelId, totals) {
524
701
  const displayModelId = modelId === "unknown" ? "-" : modelId;
525
- const prefix = `${pad(displayModelId, MODEL_USAGE_COLUMNS.model)} ${pad(formatCompactTokenCount(totals.inputTokens), MODEL_USAGE_COLUMNS.input)} ${pad(formatCompactTokenCount(totals.outputTokens), MODEL_USAGE_COLUMNS.output)} ${pad(formatCompactCacheTokens(totals, totals.cacheReadInputTokens), MODEL_USAGE_COLUMNS.cacheRead)} ${pad(formatCompactCacheTokens(totals, totals.cacheWriteInputTokens), MODEL_USAGE_COLUMNS.cacheWrite)} ${pad(formatOptionalCompactTokens(totals.cacheWrite5mInputTokens), MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatOptionalCompactTokens(totals.cacheWrite1hInputTokens), MODEL_USAGE_COLUMNS.cacheWrite1h)}`;
526
- return isCodexProvider
527
- ? `${prefix} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`
528
- : `${prefix} ${pad(formatUsageCredits(totals, modelId), MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`;
702
+ return buildTableRow(MODEL_TABLE_COLUMNS, [
703
+ displayModelId,
704
+ formatCompactTokenCount(totals.inputTokens),
705
+ formatCompactTokenCount(totals.outputTokens),
706
+ formatCompactCacheTokens(totals, totals.cacheReadInputTokens),
707
+ formatCompactCacheTokens(totals, totals.cacheWriteInputTokens),
708
+ formatUsageUsd(totals, modelId)
709
+ ]);
529
710
  }
530
711
  function UsageBreakdownLines(props) {
531
712
  const { totals } = props;