letmecode 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -20,6 +20,7 @@ npx -y letmecode
20
20
  | `1` | Select the Copilot VS Code setup action |
21
21
  | `h` / `l` | Select a Copilot setup action |
22
22
  | `q` or `Esc` | Quit |
23
+ | Mouse click | Click a provider tab or a section tab to switch to it. Hold `Shift` while dragging to select/copy text anywhere else. |
23
24
 
24
25
  ## Copilot
25
26
 
@@ -1,7 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useRef, useState } from "react";
3
- import { Box, Text, measureElement, useApp, useInput, useStdout, render } from "ink";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
+ import { Box, Text, measureElement, useApp, useInput, useStdin, useStdout, render } from "ink";
4
4
  import { configureCopilotVsCodeLogging, createProviders } from "./providers/index.js";
5
+ import { reportAnonymousUsage } from "./reporting.js";
6
+ const ESC = String.fromCharCode(0x1b);
7
+ // Normal mouse tracking (button press/release only) + SGR extended coordinates.
8
+ // This makes the tabs clickable while leaving Shift+drag native text selection
9
+ // available everywhere else, exactly like Midnight Commander.
10
+ const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
11
+ const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
12
+ // SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
13
+ const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
5
14
  const VERTICAL_TABS = [
6
15
  { id: "limit-windows", label: "Limits" },
7
16
  { id: "summary", label: "Summary" },
@@ -17,39 +26,26 @@ const LIMIT_WINDOW_COLUMNS = {
17
26
  date: 17,
18
27
  value: 10
19
28
  };
20
- const OPENAI_MODEL_USAGE_COLUMNS = {
29
+ const MODEL_USAGE_COLUMNS = {
21
30
  model: 17,
22
- input: 12,
23
- cached: 12,
24
- output: 11,
25
- credits: 12,
26
- value: 12
27
- };
28
- const ANTHROPIC_MODEL_USAGE_COLUMNS = {
29
- model: 17,
30
- input: 10,
31
+ input: 11,
32
+ output: 10,
33
+ cacheRead: 11,
34
+ cacheWrite: 11,
31
35
  cacheWrite5m: 10,
32
36
  cacheWrite1h: 10,
33
- cacheRead: 10,
34
- output: 10,
35
37
  credits: 12,
36
38
  value: 12
37
39
  };
38
- const OPENAI_DAY_USAGE_COLUMNS = {
40
+ const DAY_USAGE_COLUMNS = {
39
41
  day: 11,
40
42
  events: 6,
41
43
  input: 11,
42
44
  output: 10,
43
- value: 10
44
- };
45
- const ANTHROPIC_DAY_USAGE_COLUMNS = {
46
- day: 11,
47
- events: 6,
48
- input: 10,
45
+ cacheRead: 11,
46
+ cacheWrite: 11,
49
47
  cacheWrite5m: 10,
50
48
  cacheWrite1h: 10,
51
- cacheRead: 10,
52
- output: 10,
53
49
  value: 10
54
50
  };
55
51
  const COPILOT_ACTIONS = [
@@ -63,6 +59,7 @@ function App(props) {
63
59
  const { exit } = useApp();
64
60
  const viewportHeight = useViewportHeight();
65
61
  const { ref: contentPanelRef, height: contentPanelHeight } = useMeasuredElementSize();
62
+ const { getRegionRef, resolveClick } = useClickRegions();
66
63
  const providers = React.useState(() => createProviders())[0];
67
64
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
68
65
  const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
@@ -73,6 +70,7 @@ function App(props) {
73
70
  const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
74
71
  const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
75
72
  const [copilotActionMessage, setCopilotActionMessage] = useState();
73
+ const hasReportedAnonymousUsageRef = useRef(false);
76
74
  const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
77
75
  const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
78
76
  const selectedProvider = sortedProviderStates[selectedProviderIndex];
@@ -123,7 +121,38 @@ function App(props) {
123
121
  }
124
122
  setSelectedProviderId(topProvider.provider.id);
125
123
  }, [hasUserSelectedProvider, providerStates, sortedProviderStates]);
124
+ useEffect(() => {
125
+ if (hasReportedAnonymousUsageRef.current || providerStates.some((state) => state.status === "loading")) {
126
+ return;
127
+ }
128
+ hasReportedAnonymousUsageRef.current = true;
129
+ const readyStats = providerStates
130
+ .filter((state) => state.status === "ready")
131
+ .map((state) => state.stats);
132
+ void reportAnonymousUsage(readyStats).catch(() => {
133
+ // Anonymous usage reporting is best-effort and must never disturb the TUI.
134
+ });
135
+ }, [providerStates]);
136
+ useMouseClick((click) => {
137
+ const regionId = resolveClick(click);
138
+ if (!regionId) {
139
+ return;
140
+ }
141
+ if (regionId.startsWith("provider:")) {
142
+ setSelectedProviderId(regionId.slice("provider:".length));
143
+ setHasUserSelectedProvider(true);
144
+ return;
145
+ }
146
+ if (regionId.startsWith("vtab:")) {
147
+ setSelectedVerticalTabIndex(Number(regionId.slice("vtab:".length)));
148
+ }
149
+ });
126
150
  useInput((input, key) => {
151
+ // Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
152
+ // Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
153
+ if (input.startsWith("[<")) {
154
+ return;
155
+ }
127
156
  if (input === "q" || key.escape) {
128
157
  exit();
129
158
  return;
@@ -191,7 +220,7 @@ function App(props) {
191
220
  setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
192
221
  }
193
222
  });
194
- 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, 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 }, 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 }, 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] })] }));
223
+ 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] })] }));
195
224
  }
196
225
  function CopilotActionsPanel(props) {
197
226
  if (props.providerState.provider.id !== "copilot") {
@@ -228,14 +257,15 @@ function formatCopilotLoggingResult(result) {
228
257
  function ProviderTab(props) {
229
258
  const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
230
259
  const tabLabel = props.active ? ` ${props.label} ` : `[${props.label}]`;
231
- return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
260
+ return (_jsx(Box, { marginRight: 1, ref: props.regionRef, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
232
261
  }
233
262
  function VerticalTab(props) {
234
- return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
263
+ 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}` }) }));
235
264
  }
236
265
  function SummaryPanel(props) {
237
266
  const { summary } = props.stats;
238
- 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 }), _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"] })] }));
267
+ const isCodexProvider = props.stats.providerId === "codex";
268
+ 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"] })] }));
239
269
  }
240
270
  function ContentPanel(props) {
241
271
  if (props.providerState.status === "loading") {
@@ -256,10 +286,13 @@ function ContentPanel(props) {
256
286
  return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
257
287
  }
258
288
  function LimitWindowsPanel(props) {
289
+ const hasPrimaryWindows = props.stats.primaryLimitWindows.length > 0;
259
290
  const bodyLines = [
260
291
  { key: "primary-title", text: "Primary Limit Windows", bold: true },
261
292
  ...buildLimitWindowSectionLines("primary", props.stats.primaryLimitWindows, props.selectedRowKey),
262
- { key: "section-gap", text: "" },
293
+ ...(hasPrimaryWindows
294
+ ? [{ key: "section-separator", text: buildLimitSectionSeparatorLine(), color: "gray" }]
295
+ : [{ key: "section-gap", text: "" }]),
263
296
  { key: "secondary-title", text: "Secondary Limit Windows", bold: true },
264
297
  ...buildLimitWindowSectionLines("secondary", props.stats.secondaryLimitWindows, props.selectedRowKey)
265
298
  ];
@@ -270,55 +303,24 @@ function UsageByModelPanel(props) {
270
303
  return _jsx(Text, { color: "gray", children: "No model usage found." });
271
304
  }
272
305
  const totals = props.stats.summary.totals;
273
- if (totals.tokenBreakdown.schema === "anthropic") {
274
- return (_jsx(ScrollableLineViewport, { headerLines: [
275
- {
276
- key: "anthropic-model-header",
277
- text: `${pad("model", ANTHROPIC_MODEL_USAGE_COLUMNS.model)} ${pad("input", ANTHROPIC_MODEL_USAGE_COLUMNS.input)} ${pad("cacheW5m", ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad("cacheRead", ANTHROPIC_MODEL_USAGE_COLUMNS.cacheRead)} ${pad("output", ANTHROPIC_MODEL_USAGE_COLUMNS.output)} ${pad("credits", ANTHROPIC_MODEL_USAGE_COLUMNS.credits)} value`,
278
- color: "gray"
279
- }
280
- ], bodyLines: props.stats.modelUsage.flatMap((row) => {
281
- if (row.totals.tokenBreakdown.schema !== "anthropic") {
282
- return [];
283
- }
284
- return [
285
- {
286
- key: `model-row:${row.modelId}`,
287
- text: `${pad(row.modelId, ANTHROPIC_MODEL_USAGE_COLUMNS.model)} ${pad(formatInteger(row.totals.tokenBreakdown.inputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.input)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheWrite5mInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheWrite1hInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheReadInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheRead)} ${pad(formatInteger(row.totals.outputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.output)} ${pad(formatUsageCredits(row.totals, row.modelId), ANTHROPIC_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(row.totals, row.modelId), ANTHROPIC_MODEL_USAGE_COLUMNS.value)}`,
288
- inverse: props.selectedModelId === row.modelId,
289
- color: props.selectedModelId === row.modelId ? "cyan" : undefined
290
- }
291
- ];
292
- }), footerLines: [
293
- {
294
- key: "anthropic-model-total",
295
- text: `${pad("TOTAL", ANTHROPIC_MODEL_USAGE_COLUMNS.model)} ${pad(formatInteger(totals.tokenBreakdown.inputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.input)} ${pad(formatInteger(totals.tokenBreakdown.cacheWrite5mInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatInteger(totals.tokenBreakdown.cacheWrite1hInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatInteger(totals.tokenBreakdown.cacheReadInputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.cacheRead)} ${pad(formatInteger(totals.outputTokens), ANTHROPIC_MODEL_USAGE_COLUMNS.output)} ${pad(formatUsageCredits(totals), ANTHROPIC_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(totals), ANTHROPIC_MODEL_USAGE_COLUMNS.value)}`,
296
- color: "cyan"
297
- }
298
- ], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
299
- }
306
+ const isCodexProvider = props.stats.providerId === "codex";
300
307
  return (_jsx(ScrollableLineViewport, { headerLines: [
301
308
  {
302
- key: "openai-model-header",
303
- text: `${pad("model", OPENAI_MODEL_USAGE_COLUMNS.model)} ${pad("uncached", OPENAI_MODEL_USAGE_COLUMNS.input)} ${pad("cached", OPENAI_MODEL_USAGE_COLUMNS.cached)} ${pad("output", OPENAI_MODEL_USAGE_COLUMNS.output)} ${pad("credits", OPENAI_MODEL_USAGE_COLUMNS.credits)} value`,
309
+ key: "model-header",
310
+ text: isCodexProvider
311
+ ? `${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`
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)} ${pad("credits", MODEL_USAGE_COLUMNS.credits)} value`,
304
313
  color: "gray"
305
314
  }
306
- ], bodyLines: props.stats.modelUsage.flatMap((row) => {
307
- if (row.totals.tokenBreakdown.schema !== "openai") {
308
- return [];
309
- }
310
- return [
311
- {
312
- key: `model-row:${row.modelId}`,
313
- text: `${pad(row.modelId, OPENAI_MODEL_USAGE_COLUMNS.model)} ${pad(formatOpenAiTokens(row.totals, "non-cached"), OPENAI_MODEL_USAGE_COLUMNS.input)} ${pad(formatOpenAiTokens(row.totals, "cached"), OPENAI_MODEL_USAGE_COLUMNS.cached)} ${pad(formatInteger(row.totals.outputTokens), OPENAI_MODEL_USAGE_COLUMNS.output)} ${pad(formatUsageCredits(row.totals, row.modelId), OPENAI_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(row.totals, row.modelId), OPENAI_MODEL_USAGE_COLUMNS.value)}`,
314
- inverse: props.selectedModelId === row.modelId,
315
- color: props.selectedModelId === row.modelId ? "cyan" : undefined
316
- }
317
- ];
318
- }), footerLines: [
315
+ ], bodyLines: props.stats.modelUsage.map((row) => ({
316
+ key: `model-row:${row.modelId}`,
317
+ text: formatModelUsageRow(row.modelId, row.totals, isCodexProvider),
318
+ inverse: props.selectedModelId === row.modelId,
319
+ color: props.selectedModelId === row.modelId ? "cyan" : undefined
320
+ })), footerLines: [
319
321
  {
320
- key: "openai-model-total",
321
- text: `${pad("TOTAL", OPENAI_MODEL_USAGE_COLUMNS.model)} ${pad(formatOpenAiTokens(totals, "non-cached"), OPENAI_MODEL_USAGE_COLUMNS.input)} ${pad(formatOpenAiTokens(totals, "cached"), OPENAI_MODEL_USAGE_COLUMNS.cached)} ${pad(formatInteger(totals.outputTokens), OPENAI_MODEL_USAGE_COLUMNS.output)} ${pad(formatUsageCredits(totals), OPENAI_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(totals), OPENAI_MODEL_USAGE_COLUMNS.value)}`,
322
+ key: "model-total",
323
+ text: formatModelUsageRow("TOTAL", totals, isCodexProvider),
322
324
  color: "cyan"
323
325
  }
324
326
  ], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
@@ -328,46 +330,18 @@ function DayToDayPanel(props) {
328
330
  return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
329
331
  }
330
332
  const totals = props.stats.summary.totals;
331
- if (totals.tokenBreakdown.schema === "anthropic") {
332
- return (_jsx(ScrollableLineViewport, { headerLines: [
333
- {
334
- key: "anthropic-day-header",
335
- text: `${pad("day", ANTHROPIC_DAY_USAGE_COLUMNS.day)} ${pad("events", ANTHROPIC_DAY_USAGE_COLUMNS.events)} ${pad("input", ANTHROPIC_DAY_USAGE_COLUMNS.input)} ${pad("cacheW5m", ANTHROPIC_DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", ANTHROPIC_DAY_USAGE_COLUMNS.cacheWrite1h)} ${pad("cacheRead", ANTHROPIC_DAY_USAGE_COLUMNS.cacheRead)} ${pad("output", ANTHROPIC_DAY_USAGE_COLUMNS.output)} value`,
336
- color: "gray"
337
- }
338
- ], bodyLines: props.stats.dayUsage.flatMap((row) => {
339
- if (row.totals.tokenBreakdown.schema !== "anthropic") {
340
- return [];
341
- }
342
- return [
343
- {
344
- key: `day-row:${row.dayKey}`,
345
- text: `${pad(formatUtcDay(row.dayKey), ANTHROPIC_DAY_USAGE_COLUMNS.day)} ${pad(formatInteger(row.totals.eventCount), ANTHROPIC_DAY_USAGE_COLUMNS.events)} ${pad(formatInteger(row.totals.tokenBreakdown.inputTokens), ANTHROPIC_DAY_USAGE_COLUMNS.input)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheWrite5mInputTokens), ANTHROPIC_DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheWrite1hInputTokens), ANTHROPIC_DAY_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatInteger(row.totals.tokenBreakdown.cacheReadInputTokens), ANTHROPIC_DAY_USAGE_COLUMNS.cacheRead)} ${pad(formatInteger(row.totals.outputTokens), ANTHROPIC_DAY_USAGE_COLUMNS.output)} ${pad(formatUsageUsd(row.totals), ANTHROPIC_DAY_USAGE_COLUMNS.value)}`,
346
- inverse: props.selectedDayKey === row.dayKey,
347
- color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
348
- }
349
- ];
350
- }), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
351
- }
352
333
  return (_jsx(ScrollableLineViewport, { headerLines: [
353
334
  {
354
- key: "openai-day-header",
355
- text: `${pad("day", OPENAI_DAY_USAGE_COLUMNS.day)} ${pad("events", OPENAI_DAY_USAGE_COLUMNS.events)} ${pad("input", OPENAI_DAY_USAGE_COLUMNS.input)} ${pad("output", OPENAI_DAY_USAGE_COLUMNS.output)} value`,
335
+ key: "day-header",
336
+ 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`,
356
337
  color: "gray"
357
338
  }
358
- ], bodyLines: props.stats.dayUsage.flatMap((row) => {
359
- if (row.totals.tokenBreakdown.schema !== "openai") {
360
- return [];
361
- }
362
- return [
363
- {
364
- key: `day-row:${row.dayKey}`,
365
- text: `${pad(formatUtcDay(row.dayKey), OPENAI_DAY_USAGE_COLUMNS.day)} ${pad(formatInteger(row.totals.eventCount), OPENAI_DAY_USAGE_COLUMNS.events)} ${pad(formatInteger(row.totals.inputTotalTokens), OPENAI_DAY_USAGE_COLUMNS.input)} ${pad(formatInteger(row.totals.outputTokens), OPENAI_DAY_USAGE_COLUMNS.output)} ${pad(formatUsageUsd(row.totals), OPENAI_DAY_USAGE_COLUMNS.value)}`,
366
- inverse: props.selectedDayKey === row.dayKey,
367
- color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
368
- }
369
- ];
370
- }), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
339
+ ], bodyLines: props.stats.dayUsage.map((row) => ({
340
+ key: `day-row:${row.dayKey}`,
341
+ 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)}`,
342
+ inverse: props.selectedDayKey === row.dayKey,
343
+ color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
344
+ })), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
371
345
  }
372
346
  function ScrollableLineViewport(props) {
373
347
  const headerLines = props.headerLines ?? [];
@@ -400,7 +374,7 @@ function buildLimitWindowSectionLines(scope, windows, selectedRowKey) {
400
374
  ...windows.map((window) => {
401
375
  const lineKey = getLimitRowKey(window);
402
376
  const windowLabel = formatWindowMinutes(window.windowMinutes);
403
- const usedLabel = `${window.minUsedPercent}%->${window.maxUsedPercent}%`;
377
+ const usedLabel = formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent);
404
378
  const isSelected = selectedRowKey === lineKey;
405
379
  return {
406
380
  key: `limit-row:${lineKey}`,
@@ -417,7 +391,7 @@ function SelectionDetailsPanel(props) {
417
391
  }
418
392
  if (props.tabId === "limit-windows" && props.selectedLimitRow) {
419
393
  const row = props.selectedLimitRow;
420
- 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: ", 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 })] }));
394
+ 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 })] }));
421
395
  }
422
396
  if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
423
397
  const row = props.selectedDayRow;
@@ -435,6 +409,26 @@ function UsageTotalsDetails(props) {
435
409
  function formatInteger(value) {
436
410
  return Math.round(value).toLocaleString("en-US");
437
411
  }
412
+ function formatCompactTokenCount(value) {
413
+ const roundedValue = Math.round(value);
414
+ if (roundedValue < 1000) {
415
+ return formatInteger(roundedValue);
416
+ }
417
+ if (roundedValue < 100000) {
418
+ return `${formatCompactNumber(roundedValue / 1000)}K`;
419
+ }
420
+ if (roundedValue < 1000000) {
421
+ return `${formatInteger(Math.round(roundedValue / 1000))}K`;
422
+ }
423
+ return `${formatCompactNumber(roundedValue / 1000000)}M`;
424
+ }
425
+ function formatCompactNumber(value) {
426
+ const maximumFractionDigits = value < 10 ? 2 : value < 100 ? 1 : 0;
427
+ return value.toLocaleString("en-US", {
428
+ maximumFractionDigits,
429
+ minimumFractionDigits: 0
430
+ });
431
+ }
438
432
  function formatCredits(value) {
439
433
  if (value > 0 && value < 0.01) {
440
434
  return "<0.01";
@@ -448,30 +442,41 @@ function formatUsageCredits(totals, modelId) {
448
442
  if (isInternalUsageModel(modelId)) {
449
443
  return "N/A";
450
444
  }
451
- return totals.estimatedCreditsStatus === "unavailable" ? "unknown" : formatCredits(totals.estimatedCredits);
445
+ return totals.estimatedCreditsStatus === "unavailable" ? "-" : formatCredits(totals.estimatedCredits);
452
446
  }
453
447
  function formatUsageUsd(totals, modelId) {
454
448
  if (isInternalUsageModel(modelId)) {
455
449
  return "N/A";
456
450
  }
457
451
  return totals.estimatedCreditsStatus === "unavailable"
458
- ? "unknown"
452
+ ? "-"
459
453
  : formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD);
460
454
  }
461
455
  function isInternalUsageModel(modelId) {
462
456
  return modelId === "codex-auto-review" || modelId === "<synthetic>";
463
457
  }
464
458
  function formatUsd(value) {
465
- if (value > 0 && value < 0.0001) {
466
- return "<$0.0001";
467
- }
468
- return value.toLocaleString("en-US", {
459
+ const roundedUpValue = value > 0 ? Math.ceil(value * 100) / 100 : value;
460
+ return roundedUpValue.toLocaleString("en-US", {
469
461
  currency: "USD",
470
462
  style: "currency",
471
463
  minimumFractionDigits: 2,
472
- maximumFractionDigits: 4
464
+ maximumFractionDigits: 2
473
465
  });
474
466
  }
467
+ function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
468
+ return minUsedPercent === maxUsedPercent
469
+ ? `${minUsedPercent}%`
470
+ : `${minUsedPercent}%->${maxUsedPercent}%`;
471
+ }
472
+ function buildLimitSectionSeparatorLine() {
473
+ return "─".repeat(LIMIT_WINDOW_COLUMNS.plan +
474
+ LIMIT_WINDOW_COLUMNS.window +
475
+ LIMIT_WINDOW_COLUMNS.used +
476
+ LIMIT_WINDOW_COLUMNS.date * 2 +
477
+ LIMIT_WINDOW_COLUMNS.value +
478
+ 5);
479
+ }
475
480
  function formatWindowMinutes(value) {
476
481
  const hours = value / 60;
477
482
  if (hours >= 24) {
@@ -494,7 +499,7 @@ function formatLocalDateTime(value) {
494
499
  }
495
500
  function formatUtcDay(value) {
496
501
  if (value === "unknown") {
497
- return "unknown";
502
+ return "-";
498
503
  }
499
504
  const parts = new Intl.DateTimeFormat("en-US", {
500
505
  month: "short",
@@ -507,37 +512,50 @@ function formatUtcDay(value) {
507
512
  }
508
513
  function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
509
514
  if (!firstEventUtcIso || !lastEventUtcIso) {
510
- return "unknown";
515
+ return "-";
511
516
  }
512
517
  return `${formatLocalDateTime(firstEventUtcIso)} -> ${formatLocalDateTime(lastEventUtcIso)}`;
513
518
  }
514
519
  function pad(value, length) {
515
520
  return value.length >= length ? value.slice(0, length) : value.padEnd(length);
516
521
  }
522
+ function formatModelUsageRow(modelId, totals, isCodexProvider) {
523
+ const displayModelId = modelId === "unknown" ? "-" : modelId;
524
+ 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)}`;
525
+ return isCodexProvider
526
+ ? `${prefix} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`
527
+ : `${prefix} ${pad(formatUsageCredits(totals, modelId), MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`;
528
+ }
517
529
  function UsageBreakdownLines(props) {
518
530
  const { totals } = props;
519
- if (totals.tokenBreakdown.schema === "anthropic") {
520
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["input total: ", formatInteger(totals.inputTotalTokens), " input: ", formatInteger(totals.tokenBreakdown.inputTokens), " cacheW5m: ", formatInteger(totals.tokenBreakdown.cacheWrite5mInputTokens)] }), _jsxs(Text, { children: ["cacheW1h: ", formatInteger(totals.tokenBreakdown.cacheWrite1hInputTokens), " cacheRead: ", formatInteger(totals.tokenBreakdown.cacheReadInputTokens), " output: ", formatInteger(totals.outputTokens), " reasoning: ", formatInteger(totals.reasoningOutputTokens), " total: ", formatInteger(totals.totalTokens)] })] }));
531
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["input: ", formatInteger(totals.inputTokens), " output: ", formatInteger(totals.outputTokens), " cacheRead: ", formatCacheTokens(totals, totals.cacheReadInputTokens)] }), _jsxs(Text, { children: ["cacheWrite: ", formatCacheTokens(totals, totals.cacheWriteInputTokens), " cacheW5m: ", formatOptionalTokens(totals.cacheWrite5mInputTokens), " cacheW1h: ", formatOptionalTokens(totals.cacheWrite1hInputTokens), " reasoning: ", formatInteger(totals.reasoningOutputTokens), " total: ", formatInteger(totals.totalTokens)] })] }));
532
+ }
533
+ function formatCacheTokens(totals, value) {
534
+ if (totals.cacheStatus === "unavailable") {
535
+ return "-";
521
536
  }
522
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["input total: ", formatInteger(totals.inputTotalTokens), " uncached: ", formatOpenAiTokens(totals, "non-cached"), " cached: ", formatOpenAiTokens(totals, "cached")] }), _jsxs(Text, { children: ["output: ", formatInteger(totals.outputTokens), " reasoning: ", formatInteger(totals.reasoningOutputTokens), " total: ", formatInteger(totals.totalTokens)] })] }));
537
+ return formatOptionalTokens(value);
523
538
  }
524
- function formatOpenAiTokens(totals, kind) {
525
- if (totals.tokenBreakdown.schema !== "openai" || totals.cacheStatus === "unavailable") {
526
- return "unknown";
539
+ function formatCompactCacheTokens(totals, value) {
540
+ if (totals.cacheStatus === "unavailable") {
541
+ return "-";
527
542
  }
528
- return formatInteger(kind === "non-cached" ? totals.tokenBreakdown.nonCachedInputTokens : totals.tokenBreakdown.cachedInputTokens);
543
+ return formatOptionalCompactTokens(value);
544
+ }
545
+ function formatOptionalTokens(value) {
546
+ return value > 0 ? formatInteger(value) : "-";
547
+ }
548
+ function formatOptionalCompactTokens(value) {
549
+ return value > 0 ? formatCompactTokenCount(value) : "-";
529
550
  }
530
551
  function formatInputPerOutput(totals) {
531
552
  if (totals.outputTokens <= 0) {
532
- return totals.tokenBreakdown.schema === "anthropic" ? "input:cacheW5m:cacheW1h:cacheRead:output = 0:0:0:0:0" : "uncached:cached:output = 0:0:0";
533
- }
534
- if (totals.tokenBreakdown.schema === "anthropic") {
535
- return `input:cacheW5m:cacheW1h:cacheRead:output = ${formatInteger(Math.round(totals.tokenBreakdown.inputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.tokenBreakdown.cacheWrite5mInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.tokenBreakdown.cacheWrite1hInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.tokenBreakdown.cacheReadInputTokens / totals.outputTokens))}:1`;
553
+ return "input:cacheRead:cacheWrite:output = 0:0:0:0";
536
554
  }
537
555
  if (totals.cacheStatus === "unavailable") {
538
- return "uncached:cached:output = unknown:unknown:1";
556
+ return `input:cacheRead:cacheWrite:output = ${formatInteger(Math.round(totals.inputTokens / totals.outputTokens))}:-:-:1`;
539
557
  }
540
- return `uncached:cached:output = ${formatInteger(Math.round(totals.tokenBreakdown.nonCachedInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.tokenBreakdown.cachedInputTokens / totals.outputTokens))}:1`;
558
+ return `input:cacheRead:cacheWrite:output = ${formatInteger(Math.round(totals.inputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.cacheReadInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.cacheWriteInputTokens / totals.outputTokens))}:1`;
541
559
  }
542
560
  function useMeasuredElementSize() {
543
561
  const ref = useRef(null);
@@ -555,6 +573,101 @@ function useMeasuredElementSize() {
555
573
  height: size.height
556
574
  };
557
575
  }
576
+ // Walks the Yoga layout tree to get a node's absolute position/size (in terminal
577
+ // cells). Reading it live at click time keeps hit-testing correct across resizes,
578
+ // wrapping, and re-layouts without tracking coordinates by hand.
579
+ function getAbsoluteRect(node) {
580
+ const yogaNode = node?.yogaNode;
581
+ if (!node || !yogaNode) {
582
+ return undefined;
583
+ }
584
+ let left = 0;
585
+ let top = 0;
586
+ let current = node;
587
+ while (current?.yogaNode) {
588
+ left += current.yogaNode.getComputedLeft();
589
+ top += current.yogaNode.getComputedTop();
590
+ current = current.parentNode;
591
+ }
592
+ return {
593
+ left,
594
+ top,
595
+ width: yogaNode.getComputedWidth(),
596
+ height: yogaNode.getComputedHeight()
597
+ };
598
+ }
599
+ function parseMouseClicks(chunk) {
600
+ const clicks = [];
601
+ for (const match of chunk.matchAll(SGR_MOUSE_SEQUENCE)) {
602
+ const buttonCode = Number(match[1]);
603
+ const isPress = match[4] === "M";
604
+ // Only react to a plain left-button press (button 0, no modifier bits). Any
605
+ // press carrying Shift/Ctrl/Alt or motion/wheel bits is left alone so the
606
+ // terminal can still use it for native text selection.
607
+ if (!isPress || buttonCode !== 0) {
608
+ continue;
609
+ }
610
+ clicks.push({ column: Number(match[2]), row: Number(match[3]) });
611
+ }
612
+ return clicks;
613
+ }
614
+ // Tracks the on-screen rectangle of named clickable regions (the tabs) via Ink
615
+ // refs and resolves a click coordinate back to a region id.
616
+ function useClickRegions() {
617
+ const nodesRef = useRef(new Map());
618
+ const refCallbacksRef = useRef(new Map());
619
+ const getRegionRef = useCallback((id) => {
620
+ const cached = refCallbacksRef.current.get(id);
621
+ if (cached) {
622
+ return cached;
623
+ }
624
+ const callback = (node) => {
625
+ if (node) {
626
+ nodesRef.current.set(id, node);
627
+ }
628
+ else {
629
+ nodesRef.current.delete(id);
630
+ }
631
+ };
632
+ refCallbacksRef.current.set(id, callback);
633
+ return callback;
634
+ }, []);
635
+ const resolveClick = useCallback((click) => {
636
+ // SGR mouse coordinates are 1-based; Ink layout coordinates are 0-based.
637
+ const column = click.column - 1;
638
+ const row = click.row - 1;
639
+ for (const [id, node] of nodesRef.current) {
640
+ const rect = getAbsoluteRect(node);
641
+ if (rect &&
642
+ column >= rect.left &&
643
+ column < rect.left + rect.width &&
644
+ row >= rect.top &&
645
+ row < rect.top + rect.height) {
646
+ return id;
647
+ }
648
+ }
649
+ return undefined;
650
+ }, []);
651
+ return { getRegionRef, resolveClick };
652
+ }
653
+ // Parses left-clicks out of Ink's raw input stream and forwards them to onClick.
654
+ // Keyboard handling stays in useInput; this only consumes mouse reports.
655
+ function useMouseClick(onClick) {
656
+ const { internal_eventEmitter } = useStdin();
657
+ const handlerRef = useRef(onClick);
658
+ handlerRef.current = onClick;
659
+ useEffect(() => {
660
+ const handleInput = (chunk) => {
661
+ for (const click of parseMouseClicks(chunk)) {
662
+ handlerRef.current(click);
663
+ }
664
+ };
665
+ internal_eventEmitter.on("input", handleInput);
666
+ return () => {
667
+ internal_eventEmitter.off("input", handleInput);
668
+ };
669
+ }, [internal_eventEmitter]);
670
+ }
558
671
  function useAutoScrollOffset(selectedIndex, rowCount, viewportSize) {
559
672
  const [scrollOffset, setScrollOffset] = useState(0);
560
673
  useEffect(() => {
@@ -633,7 +746,7 @@ function providerUsageScore(state) {
633
746
  return 0;
634
747
  }
635
748
  const totals = state.stats.summary.totals;
636
- return totals.inputTotalTokens + totals.outputTokens;
749
+ return totals.inputTokens + totals.outputTokens + totals.cacheReadInputTokens + totals.cacheWriteInputTokens;
637
750
  }
638
751
  function getLimitRows(providerState) {
639
752
  if (providerState.status !== "ready") {
@@ -687,7 +800,9 @@ function resolveViewportHeight(rows) {
687
800
  }
688
801
  export function main(argv = process.argv.slice(2)) {
689
802
  const restoreFullscreen = enterFullscreenMode(process.stdout);
803
+ const disableMouse = enableMouseReporting(process.stdout);
690
804
  const exitHandler = () => {
805
+ disableMouse();
691
806
  restoreFullscreen();
692
807
  };
693
808
  process.once("exit", exitHandler);
@@ -699,6 +814,7 @@ export function main(argv = process.argv.slice(2)) {
699
814
  void instance.waitUntilExit().finally(() => {
700
815
  process.off("exit", exitHandler);
701
816
  instance.cleanup();
817
+ disableMouse();
702
818
  restoreFullscreen();
703
819
  });
704
820
  }
@@ -716,4 +832,18 @@ function enterFullscreenMode(stdout) {
716
832
  stdout.write(EXIT_FULLSCREEN_MODE);
717
833
  };
718
834
  }
835
+ function enableMouseReporting(stdout) {
836
+ if (!stdout.isTTY) {
837
+ return () => { };
838
+ }
839
+ let disabled = false;
840
+ stdout.write(ENABLE_MOUSE_TRACKING);
841
+ return () => {
842
+ if (disabled) {
843
+ return;
844
+ }
845
+ disabled = true;
846
+ stdout.write(DISABLE_MOUSE_TRACKING);
847
+ };
848
+ }
719
849
  main();