letmecode 0.1.12 → 0.1.15

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,16 @@ 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_HEADERS = ["Scope", "Plan", "Window", "Used", "Start", "End", "API eq."];
23
+ const DAILY_TABLE_HEADERS = ["Day", "Ev", "Input", "Output", "C read", "C write", "API eq."];
24
+ const MODEL_TABLE_HEADERS = ["Model", "Input", "Output", "C read", "C write", "API eq."];
52
25
  const COPILOT_ACTIONS = [
53
26
  { id: "vscode", label: "Start logging VS Code", enabled: true }
54
27
  ];
@@ -65,7 +38,7 @@ function App(props) {
65
38
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
66
39
  const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
67
40
  const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
68
- const [selectedVerticalTabIndex, setSelectedVerticalTabIndex] = useState(0);
41
+ const [selectedDetailTabIndex, setSelectedDetailTabIndex] = useState(0);
69
42
  const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
70
43
  const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
71
44
  const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
@@ -75,7 +48,7 @@ function App(props) {
75
48
  const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
76
49
  const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
77
50
  const selectedProvider = sortedProviderStates[selectedProviderIndex];
78
- const selectedVerticalTab = VERTICAL_TABS[selectedVerticalTabIndex];
51
+ const selectedDetailTab = DETAIL_TABS[selectedDetailTabIndex];
79
52
  const limitRows = getLimitRows(selectedProvider);
80
53
  const dayRows = getDayRows(selectedProvider);
81
54
  const modelRows = getModelRows(selectedProvider);
@@ -145,9 +118,30 @@ function App(props) {
145
118
  return;
146
119
  }
147
120
  if (regionId.startsWith("vtab:")) {
148
- setSelectedVerticalTabIndex(Number(regionId.slice("vtab:".length)));
121
+ setSelectedDetailTabIndex(Number(regionId.slice("vtab:".length)));
149
122
  }
150
123
  });
124
+ const moveSelectedTableRow = useCallback((delta) => {
125
+ if (selectedDetailTab.id === "limit-windows") {
126
+ setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + delta, limitRows.length));
127
+ return;
128
+ }
129
+ if (selectedDetailTab.id === "usage-by-model") {
130
+ setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + delta, modelRows.length));
131
+ return;
132
+ }
133
+ if (selectedDetailTab.id === "day-to-day-analyses") {
134
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + delta, dayRows.length));
135
+ }
136
+ }, [
137
+ activeDayRowIndex,
138
+ activeLimitRowIndex,
139
+ activeModelRowIndex,
140
+ dayRows.length,
141
+ limitRows.length,
142
+ modelRows.length,
143
+ selectedDetailTab.id
144
+ ]);
151
145
  useInput((input, key) => {
152
146
  // Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
153
147
  // Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
@@ -175,32 +169,12 @@ function App(props) {
175
169
  return;
176
170
  }
177
171
  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
- }
172
+ setSelectedDetailTabIndex((current) => (current + 1) % DETAIL_TABS.length);
173
+ return;
190
174
  }
191
175
  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
- }
176
+ setSelectedDetailTabIndex((current) => (current - 1 + DETAIL_TABS.length) % DETAIL_TABS.length);
177
+ return;
204
178
  }
205
179
  if ((key.tab && !key.shift) || input === "]") {
206
180
  setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
@@ -214,14 +188,14 @@ function App(props) {
214
188
  return;
215
189
  }
216
190
  if (key.downArrow || input === "j") {
217
- setSelectedVerticalTabIndex((current) => (current + 1) % VERTICAL_TABS.length);
191
+ moveSelectedTableRow(1);
218
192
  return;
219
193
  }
220
194
  if (key.upArrow || input === "k") {
221
- setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
195
+ moveSelectedTableRow(-1);
222
196
  }
223
197
  });
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] })] }));
198
+ 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
199
  }
226
200
  function CopilotActionsPanel(props) {
227
201
  if (props.providerState.provider.id !== "copilot") {
@@ -257,16 +231,23 @@ function formatCopilotLoggingResult(result) {
257
231
  }
258
232
  function ProviderTab(props) {
259
233
  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 }) }));
234
+ const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
235
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: tabLabel }) }));
262
236
  }
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}` }) }));
237
+ function DetailTab(props) {
238
+ const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
239
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: tabLabel }) }));
265
240
  }
266
241
  function SummaryPanel(props) {
267
242
  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"] })] }));
243
+ const totals = summary.totals;
244
+ const period = resolveSummaryPeriod(props.stats.dayUsage);
245
+ const cacheRatio = resolveCacheRatio(totals);
246
+ const averageTokensPerEvent = totals.eventCount > 0 ? totals.totalTokens / totals.eventCount : 0;
247
+ const costPerEvent = totals.eventCount > 0
248
+ ? (totals.estimatedCredits * CODEX_CREDIT_COST_USD) / totals.eventCount
249
+ : 0;
250
+ 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
251
  }
271
252
  function ContentPanel(props) {
272
253
  if (props.providerState.status === "loading") {
@@ -287,62 +268,23 @@ function ContentPanel(props) {
287
268
  return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
288
269
  }
289
270
  function LimitWindowsPanel(props) {
290
- const hasPrimaryWindows = props.stats.primaryLimitWindows.length > 0;
291
- 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)
299
- ];
300
- return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, selectedBodyLineKey: props.selectedRowKey ? `limit-row:${props.selectedRowKey}` : undefined, availableHeight: props.availableHeight }));
271
+ const tableLines = buildLimitWindowTableLines(props.stats, props.selectedRowKey);
272
+ return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedRowKey ? `limit-row:${props.selectedRowKey}` : undefined, availableHeight: props.availableHeight }));
301
273
  }
302
274
  function UsageByModelPanel(props) {
303
275
  if (props.stats.modelUsage.length === 0) {
304
276
  return _jsx(Text, { color: "gray", children: "No model usage found." });
305
277
  }
306
278
  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 }));
279
+ const tableLines = buildModelUsageTableLines(props.stats.modelUsage, totals, props.selectedModelId);
280
+ return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
328
281
  }
329
282
  function DayToDayPanel(props) {
330
283
  if (props.stats.dayUsage.length === 0) {
331
284
  return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
332
285
  }
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 }));
286
+ const tableLines = buildDailyUsageTableLines(props.stats.dayUsage, props.selectedDayKey);
287
+ return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
346
288
  }
347
289
  function ScrollableLineViewport(props) {
348
290
  const headerLines = props.headerLines ?? [];
@@ -362,29 +304,153 @@ function ScrollableLineViewport(props) {
362
304
  function ScrollableViewportLine(props) {
363
305
  return (_jsx(Text, { bold: props.line.bold, color: props.line.color, inverse: props.line.inverse, wrap: "truncate-end", children: props.line.text }));
364
306
  }
365
- function buildLimitWindowSectionLines(scope, windows, selectedRowKey) {
366
- if (windows.length === 0) {
367
- return [{ key: `${scope}-empty`, text: "No windows found.", color: "gray" }];
307
+ function createTextTable(headers, rows) {
308
+ return {
309
+ headers: [...headers],
310
+ widths: headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)))
311
+ };
312
+ }
313
+ function buildTableBorder(table, left, middle, right) {
314
+ return `${left}${table.widths.map((width) => "─".repeat(width + 2)).join(middle)}${right}`;
315
+ }
316
+ function buildTableRow(table, cells) {
317
+ return `│${table.widths.map((width, index) => ` ${pad(cells[index] ?? "", width)} `).join("│")}│`;
318
+ }
319
+ function buildTextTableLines(options) {
320
+ if (options.rows.length === 0 && !options.totalRow) {
321
+ return {
322
+ bodyLines: [
323
+ { key: `${options.lineKeyPrefix}-title`, text: options.title, bold: true },
324
+ { key: `${options.lineKeyPrefix}-empty`, text: "No rows found.", color: "gray" }
325
+ ]
326
+ };
368
327
  }
369
- return [
328
+ const tableRows = options.totalRow
329
+ ? [...options.rows, options.totalRow]
330
+ : options.rows;
331
+ const table = createTextTable(options.headers, tableRows.map((row) => row.cells));
332
+ const headerLines = [
333
+ { key: `${options.lineKeyPrefix}-title`, text: options.title, bold: true },
370
334
  {
371
- 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`,
335
+ key: `${options.lineKeyPrefix}-top-border`,
336
+ text: buildTableBorder(table, "", "", ""),
373
337
  color: "gray"
374
338
  },
375
- ...windows.map((window) => {
376
- const lineKey = getLimitRowKey(window);
377
- const windowLabel = formatWindowMinutes(window.windowMinutes);
378
- const usedLabel = formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent);
379
- const isSelected = selectedRowKey === lineKey;
380
- return {
381
- 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)}`,
339
+ {
340
+ key: `${options.lineKeyPrefix}-header`,
341
+ text: buildTableRow(table, table.headers),
342
+ color: "gray"
343
+ },
344
+ {
345
+ key: `${options.lineKeyPrefix}-header-border`,
346
+ text: buildTableBorder(table, "├", "┼", "┤"),
347
+ color: "gray"
348
+ }
349
+ ];
350
+ const bodyLines = options.rows.flatMap((row) => {
351
+ const isSelected = options.selectedRowKey === row.key;
352
+ const lines = [{
353
+ key: row.key,
354
+ text: buildTableRow(table, row.cells),
383
355
  inverse: isSelected,
384
356
  color: isSelected ? "cyan" : undefined
385
- };
386
- })
387
- ];
357
+ }];
358
+ if (options.separatorAfterRowKeys?.has(row.key)) {
359
+ lines.push({
360
+ key: `${row.key}-separator`,
361
+ text: buildTableBorder(table, "├", "┼", "┤"),
362
+ color: "gray"
363
+ });
364
+ }
365
+ return lines;
366
+ });
367
+ const footerLines = [];
368
+ if (options.totalRow) {
369
+ footerLines.push({
370
+ key: `${options.lineKeyPrefix}-total-border`,
371
+ text: buildTableBorder(table, "├", "┼", "┤"),
372
+ color: "gray"
373
+ }, {
374
+ key: options.totalRow.key,
375
+ text: buildTableRow(table, options.totalRow.cells),
376
+ color: "cyan"
377
+ });
378
+ }
379
+ footerLines.push({
380
+ key: `${options.lineKeyPrefix}-bottom-border`,
381
+ text: buildTableBorder(table, "└", "┴", "┘"),
382
+ color: "gray"
383
+ });
384
+ return {
385
+ headerLines,
386
+ bodyLines,
387
+ footerLines
388
+ };
389
+ }
390
+ function buildLimitWindowTableLines(stats, selectedRowKey) {
391
+ const primaryRows = stats.primaryLimitWindows.map((window) => buildLimitWindowTableRow(window));
392
+ const secondaryRows = stats.secondaryLimitWindows.map((window) => buildLimitWindowTableRow(window));
393
+ const separatorAfterRowKeys = primaryRows.length > 0 && secondaryRows.length > 0
394
+ ? new Set([primaryRows[primaryRows.length - 1].key])
395
+ : undefined;
396
+ return buildTextTableLines({
397
+ title: "Limits",
398
+ lineKeyPrefix: "limits",
399
+ headers: LIMIT_TABLE_HEADERS,
400
+ rows: [...primaryRows, ...secondaryRows],
401
+ selectedRowKey: selectedRowKey ? `limit-row:${selectedRowKey}` : undefined,
402
+ separatorAfterRowKeys
403
+ });
404
+ }
405
+ function buildLimitWindowTableRow(window) {
406
+ return {
407
+ key: `limit-row:${getLimitRowKey(window)}`,
408
+ cells: [
409
+ window.scope,
410
+ window.planType,
411
+ formatCompactWindowMinutes(window.windowMinutes),
412
+ formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent),
413
+ formatCompactLocalDateTime(window.startTimeUtcIso),
414
+ formatCompactLocalDateTime(window.endTimeUtcIso),
415
+ formatUsd(window.totals.estimatedCredits * CODEX_CREDIT_COST_USD)
416
+ ]
417
+ };
418
+ }
419
+ function buildDailyUsageTableLines(rows, selectedDayKey) {
420
+ return buildTextTableLines({
421
+ title: "Daily usage",
422
+ lineKeyPrefix: "daily",
423
+ headers: DAILY_TABLE_HEADERS,
424
+ rows: rows.map((row) => ({
425
+ key: `day-row:${row.dayKey}`,
426
+ cells: [
427
+ formatUtcDay(row.dayKey),
428
+ formatCompactTokenCount(row.totals.eventCount),
429
+ formatCompactTokenCount(row.totals.inputTokens),
430
+ formatCompactTokenCount(row.totals.outputTokens),
431
+ formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens),
432
+ formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens),
433
+ formatUsageUsd(row.totals)
434
+ ]
435
+ })),
436
+ selectedRowKey: selectedDayKey ? `day-row:${selectedDayKey}` : undefined
437
+ });
438
+ }
439
+ function buildModelUsageTableLines(rows, totals, selectedModelId) {
440
+ return buildTextTableLines({
441
+ title: "Model usage",
442
+ lineKeyPrefix: "model",
443
+ headers: MODEL_TABLE_HEADERS,
444
+ rows: rows.map((row) => ({
445
+ key: `model-row:${row.modelId}`,
446
+ cells: formatModelUsageTableCells(row.modelId, row.totals)
447
+ })),
448
+ selectedRowKey: selectedModelId ? `model-row:${selectedModelId}` : undefined,
449
+ totalRow: {
450
+ key: "model-total",
451
+ cells: formatModelUsageTableCells("TOTAL", totals)
452
+ }
453
+ });
388
454
  }
389
455
  function SelectionDetailsPanel(props) {
390
456
  if (props.providerState.status !== "ready") {
@@ -392,17 +458,23 @@ function SelectionDetailsPanel(props) {
392
458
  }
393
459
  if (props.tabId === "limit-windows" && props.selectedLimitRow) {
394
460
  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 })] }));
461
+ 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
462
  }
397
463
  if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
398
464
  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 })] }));
465
+ 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
466
  }
401
467
  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 })] }));
468
+ 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
469
  }
404
470
  return null;
405
471
  }
472
+ function DetailsPanelFrame(props) {
473
+ return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Details" }), props.children] }));
474
+ }
475
+ function DetailRow(props) {
476
+ return (_jsxs(Text, { children: [pad(props.label, 14), props.value] }));
477
+ }
406
478
  function UsageTotalsDetails(props) {
407
479
  const { totals } = props;
408
480
  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 +482,28 @@ function UsageTotalsDetails(props) {
410
482
  function formatInteger(value) {
411
483
  return Math.round(value).toLocaleString("en-US");
412
484
  }
485
+ function formatOverviewTokenCount(value) {
486
+ const roundedValue = Math.round(value);
487
+ if (roundedValue >= 1000000000) {
488
+ return `${formatFixedCompactNumber(roundedValue / 1000000000)}B`;
489
+ }
490
+ if (roundedValue >= 1000000) {
491
+ return `${formatFixedCompactNumber(roundedValue / 1000000)}M`;
492
+ }
493
+ if (roundedValue >= 1000) {
494
+ return `${formatFixedCompactNumber(roundedValue / 1000)}K`;
495
+ }
496
+ return formatInteger(roundedValue);
497
+ }
498
+ function formatCacheOverviewTokenCount(totals, value) {
499
+ return totals.cacheStatus === "unavailable" ? "-" : formatOverviewTokenCount(value);
500
+ }
501
+ function formatFixedCompactNumber(value) {
502
+ return value.toLocaleString("en-US", {
503
+ maximumFractionDigits: value < 10 ? 2 : 1,
504
+ minimumFractionDigits: 0
505
+ });
506
+ }
413
507
  function formatCompactTokenCount(value) {
414
508
  const roundedValue = Math.round(value);
415
509
  if (roundedValue < 1000) {
@@ -465,18 +559,51 @@ function formatUsd(value) {
465
559
  maximumFractionDigits: 2
466
560
  });
467
561
  }
562
+ function formatUnitUsd(value) {
563
+ if (!Number.isFinite(value)) {
564
+ return "-";
565
+ }
566
+ return value.toLocaleString("en-US", {
567
+ currency: "USD",
568
+ style: "currency",
569
+ minimumFractionDigits: value < 0.01 && value > 0 ? 4 : 3,
570
+ maximumFractionDigits: value < 0.01 && value > 0 ? 4 : 3
571
+ });
572
+ }
573
+ function formatPercent(value) {
574
+ if (!Number.isFinite(value)) {
575
+ return "-";
576
+ }
577
+ return `${value.toLocaleString("en-US", {
578
+ maximumFractionDigits: 1,
579
+ minimumFractionDigits: 0
580
+ })}%`;
581
+ }
582
+ function resolveCacheRatio(totals) {
583
+ if (totals.cacheStatus === "unavailable") {
584
+ return NaN;
585
+ }
586
+ const inputPool = totals.inputTokens +
587
+ totals.cacheReadInputTokens +
588
+ totals.cacheWriteInputTokens;
589
+ return inputPool > 0
590
+ ? (totals.cacheReadInputTokens / inputPool) * 100
591
+ : 0;
592
+ }
593
+ function formatInputOutputRatio(totals) {
594
+ if (totals.outputTokens <= 0) {
595
+ return "-";
596
+ }
597
+ return `${(totals.inputTokens / totals.outputTokens).toLocaleString("en-US", {
598
+ maximumFractionDigits: 1,
599
+ minimumFractionDigits: 0
600
+ })} : 1`;
601
+ }
468
602
  function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
603
+ const fmt = (v) => `${Math.round(v)}%`;
469
604
  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);
605
+ ? fmt(minUsedPercent)
606
+ : `${fmt(minUsedPercent)}–${fmt(maxUsedPercent)}`;
480
607
  }
481
608
  function formatWindowMinutes(value) {
482
609
  const hours = value / 60;
@@ -485,6 +612,13 @@ function formatWindowMinutes(value) {
485
612
  }
486
613
  return `${hours.toFixed(2)}h`;
487
614
  }
615
+ function formatCompactWindowMinutes(value) {
616
+ const hours = value / 60;
617
+ if (hours >= 24) {
618
+ return `${formatCompactNumber(hours / 24)}d`;
619
+ }
620
+ return `${formatCompactNumber(hours)}h`;
621
+ }
488
622
  function formatLocalDateTime(value) {
489
623
  const parts = new Intl.DateTimeFormat("en-US", {
490
624
  month: "short",
@@ -498,6 +632,29 @@ function formatLocalDateTime(value) {
498
632
  const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
499
633
  return `${lookup.day} ${lookup.month} ${lookup.year} ${lookup.hour}:${lookup.minute}`;
500
634
  }
635
+ function formatCompactLocalDateTime(value) {
636
+ const parts = new Intl.DateTimeFormat("en-US", {
637
+ month: "short",
638
+ day: "2-digit",
639
+ hour: "2-digit",
640
+ minute: "2-digit",
641
+ hour12: false,
642
+ hourCycle: "h23"
643
+ }).formatToParts(new Date(value));
644
+ const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
645
+ return `${lookup.day} ${lookup.month} ${lookup.hour}:${lookup.minute}`;
646
+ }
647
+ function resolveSummaryPeriod(dayUsage) {
648
+ const timestamps = dayUsage.flatMap((row) => [
649
+ row.firstEventUtcIso,
650
+ row.lastEventUtcIso
651
+ ]).filter((value) => Boolean(value));
652
+ if (timestamps.length === 0) {
653
+ return "-";
654
+ }
655
+ const sortedTimestamps = timestamps.sort();
656
+ return `${formatCompactLocalDateTime(sortedTimestamps[0])} → ${formatCompactLocalDateTime(sortedTimestamps[sortedTimestamps.length - 1])}`;
657
+ }
501
658
  function formatUtcDay(value) {
502
659
  if (value === "unknown") {
503
660
  return "-";
@@ -520,12 +677,16 @@ function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
520
677
  function pad(value, length) {
521
678
  return value.length >= length ? value.slice(0, length) : value.padEnd(length);
522
679
  }
523
- function formatModelUsageRow(modelId, totals, isCodexProvider) {
680
+ function formatModelUsageTableCells(modelId, totals) {
524
681
  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)}`;
682
+ return [
683
+ displayModelId,
684
+ formatCompactTokenCount(totals.inputTokens),
685
+ formatCompactTokenCount(totals.outputTokens),
686
+ formatCompactCacheTokens(totals, totals.cacheReadInputTokens),
687
+ formatCompactCacheTokens(totals, totals.cacheWriteInputTokens),
688
+ formatUsageUsd(totals, modelId)
689
+ ];
529
690
  }
530
691
  function UsageBreakdownLines(props) {
531
692
  const { totals } = props;