letmecode 0.1.24 → 0.1.25

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.
@@ -58,12 +58,12 @@ export function buildHelpText() {
58
58
  " --no-usage Disable anonymous usage reporting",
59
59
  "",
60
60
  "Controls:",
61
- " [ ] / Tab Switch providers",
62
- " Shift+Tab Switch providers backward",
63
- " j / k Switch dashboard sections",
64
- " Up / Down Switch dashboard sections",
65
- " Left / Right Select the previous or next row",
66
- " 1, h / l, Enter Run Copilot setup actions",
61
+ " Up / Down Select Provider, View, or table rows",
62
+ " Left / Right Change the selected Provider or View",
63
+ " Left / Right Do nothing when table rows are selected",
64
+ " Tab / Shift+Tab Switch providers without leaving the table",
65
+ " [ / ] Switch views without leaving the table",
66
+ " 1, Enter Run Copilot setup actions",
67
67
  " q or Esc Quit",
68
68
  "",
69
69
  "Trace logging:",
@@ -7,18 +7,19 @@ import { reportAnonymousUsage } from "./reporting.js";
7
7
  import { estimateLimitFullValue } from "./providers/limits.js";
8
8
  const ESC = String.fromCharCode(0x1b);
9
9
  // Normal mouse tracking (button press/release only) + SGR extended coordinates.
10
- // This makes the tabs clickable while leaving Shift+drag native text selection
10
+ // This makes the controls clickable while leaving Shift+drag native text selection
11
11
  // available everywhere else, exactly like Midnight Commander.
12
12
  const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
13
13
  const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
14
14
  // SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
15
15
  const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
16
- const DETAIL_TABS = [
16
+ const DETAIL_VIEWS = [
17
17
  { id: "limit-windows", label: "Limits" },
18
18
  { id: "summary", label: "Summary" },
19
19
  { id: "day-to-day-analyses", label: "Daily" },
20
20
  { id: "usage-by-model", label: "Models" }
21
21
  ];
22
+ const CONTROL_SECTIONS = ["provider", "view", "table"];
22
23
  const CODEX_CREDIT_COST_USD = 0.01;
23
24
  const LIMIT_TABLE_HEADERS = ["Scope", "Plan", "Models", "Window", "Used", "Start", "End", "API eq.", "Full"];
24
25
  const DAILY_TABLE_HEADERS = ["Day", "Ev", "Input", "Output", "C read", "C write", "API eq."];
@@ -39,26 +40,29 @@ function App(props) {
39
40
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
40
41
  const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
41
42
  const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
42
- const [selectedDetailTabIndex, setSelectedDetailTabIndex] = useState(0);
43
- const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
44
- const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
45
- const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
43
+ const [selectedDetailViewIndex, setSelectedDetailViewIndex] = useState(0);
44
+ const [selectedControlSectionIndex, setSelectedControlSectionIndex] = useState(0);
45
+ const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState();
46
+ const [selectedDayRowIndex, setSelectedDayRowIndex] = useState();
47
+ const [selectedModelRowIndex, setSelectedModelRowIndex] = useState();
46
48
  const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
47
49
  const [copilotActionMessage, setCopilotActionMessage] = useState();
48
50
  const hasReportedAnonymousUsageRef = useRef(false);
49
51
  const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
50
52
  const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
51
53
  const selectedProvider = sortedProviderStates[selectedProviderIndex];
52
- const selectedDetailTab = DETAIL_TABS[selectedDetailTabIndex];
54
+ const selectedDetailView = DETAIL_VIEWS[selectedDetailViewIndex];
55
+ const selectedControlSection = CONTROL_SECTIONS[selectedControlSectionIndex];
56
+ const isTableControlSelected = selectedControlSection === "table";
53
57
  const limitRows = getLimitRows(selectedProvider);
54
58
  const dayRows = getDayRows(selectedProvider);
55
59
  const modelRows = getModelRows(selectedProvider);
56
- const activeLimitRowIndex = clampSelectionIndex(selectedLimitRowIndex, limitRows.length);
57
- const activeDayRowIndex = clampSelectionIndex(selectedDayRowIndex, dayRows.length);
58
- const activeModelRowIndex = clampSelectionIndex(selectedModelRowIndex, modelRows.length);
59
- const selectedLimitRow = activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
60
- const selectedDayRow = activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
61
- const selectedModelRow = activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
60
+ const activeLimitRowIndex = clampOptionalSelectionIndex(selectedLimitRowIndex, limitRows.length);
61
+ const activeDayRowIndex = clampOptionalSelectionIndex(selectedDayRowIndex, dayRows.length);
62
+ const activeModelRowIndex = clampOptionalSelectionIndex(selectedModelRowIndex, modelRows.length);
63
+ const selectedLimitRow = isTableControlSelected && activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
64
+ const selectedDayRow = isTableControlSelected && activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
65
+ const selectedModelRow = isTableControlSelected && activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
62
66
  useEffect(() => {
63
67
  let cancelled = false;
64
68
  for (const provider of providers) {
@@ -110,6 +114,11 @@ function App(props) {
110
114
  // Anonymous usage reporting is best-effort and must never disturb the TUI.
111
115
  });
112
116
  }, [props.usageReportingEnabled, providerStates]);
117
+ const clearSelectedTableRows = useCallback(() => {
118
+ setSelectedLimitRowIndex(undefined);
119
+ setSelectedDayRowIndex(undefined);
120
+ setSelectedModelRowIndex(undefined);
121
+ }, []);
113
122
  useMouseClick((click) => {
114
123
  const regionId = resolveClick(click);
115
124
  if (!regionId) {
@@ -118,23 +127,36 @@ function App(props) {
118
127
  if (regionId.startsWith("provider:")) {
119
128
  setSelectedProviderId(regionId.slice("provider:".length));
120
129
  setHasUserSelectedProvider(true);
130
+ setSelectedControlSectionIndex(CONTROL_SECTIONS.indexOf("provider"));
131
+ clearSelectedTableRows();
121
132
  return;
122
133
  }
123
- if (regionId.startsWith("vtab:")) {
124
- setSelectedDetailTabIndex(Number(regionId.slice("vtab:".length)));
134
+ if (regionId.startsWith("view:")) {
135
+ setSelectedDetailViewIndex(Number(regionId.slice("view:".length)));
136
+ setSelectedControlSectionIndex(CONTROL_SECTIONS.indexOf("view"));
137
+ clearSelectedTableRows();
125
138
  }
126
139
  });
127
140
  const moveSelectedTableRow = useCallback((delta) => {
128
- if (selectedDetailTab.id === "limit-windows") {
129
- setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + delta, limitRows.length));
141
+ if (selectedDetailView.id === "limit-windows") {
142
+ if (limitRows.length === 0) {
143
+ return;
144
+ }
145
+ setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex < 0 ? 0 : activeLimitRowIndex + delta, limitRows.length));
130
146
  return;
131
147
  }
132
- if (selectedDetailTab.id === "usage-by-model") {
133
- setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + delta, modelRows.length));
148
+ if (selectedDetailView.id === "usage-by-model") {
149
+ if (modelRows.length === 0) {
150
+ return;
151
+ }
152
+ setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex < 0 ? 0 : activeModelRowIndex + delta, modelRows.length));
134
153
  return;
135
154
  }
136
- if (selectedDetailTab.id === "day-to-day-analyses") {
137
- setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + delta, dayRows.length));
155
+ if (selectedDetailView.id === "day-to-day-analyses") {
156
+ if (dayRows.length === 0) {
157
+ return;
158
+ }
159
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex < 0 ? 0 : activeDayRowIndex + delta, dayRows.length));
138
160
  }
139
161
  }, [
140
162
  activeDayRowIndex,
@@ -143,8 +165,59 @@ function App(props) {
143
165
  dayRows.length,
144
166
  limitRows.length,
145
167
  modelRows.length,
146
- selectedDetailTab.id
168
+ selectedDetailView.id
147
169
  ]);
170
+ const canMoveTableRowUp = useCallback(() => {
171
+ if (selectedDetailView.id === "limit-windows") {
172
+ return activeLimitRowIndex > 0;
173
+ }
174
+ if (selectedDetailView.id === "usage-by-model") {
175
+ return activeModelRowIndex > 0;
176
+ }
177
+ if (selectedDetailView.id === "day-to-day-analyses") {
178
+ return activeDayRowIndex > 0;
179
+ }
180
+ return false;
181
+ }, [activeDayRowIndex, activeLimitRowIndex, activeModelRowIndex, selectedDetailView.id]);
182
+ const moveSelectionDown = useCallback(() => {
183
+ if (selectedControlSection === "table") {
184
+ moveSelectedTableRow(1);
185
+ return;
186
+ }
187
+ setSelectedControlSectionIndex((current) => {
188
+ const next = Math.min(current + 1, CONTROL_SECTIONS.length - 1);
189
+ if (CONTROL_SECTIONS[next] === "table") {
190
+ moveSelectedTableRow(1);
191
+ }
192
+ return next;
193
+ });
194
+ }, [moveSelectedTableRow, selectedControlSection]);
195
+ const moveSelectionUp = useCallback(() => {
196
+ if (selectedControlSection === "table" && canMoveTableRowUp()) {
197
+ moveSelectedTableRow(-1);
198
+ return;
199
+ }
200
+ if (selectedControlSection === "table") {
201
+ clearSelectedTableRows();
202
+ }
203
+ setSelectedControlSectionIndex((current) => Math.max(current - 1, 0));
204
+ }, [canMoveTableRowUp, clearSelectedTableRows, moveSelectedTableRow, selectedControlSection]);
205
+ const changeSelectedProvider = useCallback((delta) => {
206
+ setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + delta + sortedProviderStates.length) % sortedProviderStates.length].provider.id);
207
+ setHasUserSelectedProvider(true);
208
+ }, [selectedProviderIndex, sortedProviderStates]);
209
+ const changeSelectedDetailView = useCallback((delta) => {
210
+ setSelectedDetailViewIndex((current) => (current + delta + DETAIL_VIEWS.length) % DETAIL_VIEWS.length);
211
+ }, []);
212
+ const changeSelectedSectionValue = useCallback((delta) => {
213
+ if (selectedControlSection === "provider") {
214
+ changeSelectedProvider(delta);
215
+ return;
216
+ }
217
+ if (selectedControlSection === "view") {
218
+ changeSelectedDetailView(delta);
219
+ }
220
+ }, [changeSelectedDetailView, changeSelectedProvider, selectedControlSection]);
148
221
  useInput((input, key) => {
149
222
  // Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
150
223
  // Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
@@ -160,45 +233,42 @@ function App(props) {
160
233
  return;
161
234
  }
162
235
  if (selectedProvider.provider.id === "copilot" && key.return) {
163
- runCopilotAction(COPILOT_ACTIONS[selectedCopilotActionIndex].id, setCopilotActionMessage);
236
+ runCopilotAction(setCopilotActionMessage);
164
237
  return;
165
238
  }
166
- if (selectedProvider.provider.id === "copilot" && input === "l") {
167
- setSelectedCopilotActionIndex((current) => (current + 1) % COPILOT_ACTIONS.length);
239
+ if (key.tab && !key.shift) {
240
+ changeSelectedProvider(1);
168
241
  return;
169
242
  }
170
- if (selectedProvider.provider.id === "copilot" && input === "h") {
171
- setSelectedCopilotActionIndex((current) => (current - 1 + COPILOT_ACTIONS.length) % COPILOT_ACTIONS.length);
243
+ if (key.tab && key.shift) {
244
+ changeSelectedProvider(-1);
172
245
  return;
173
246
  }
174
- if (key.rightArrow) {
175
- setSelectedDetailTabIndex((current) => (current + 1) % DETAIL_TABS.length);
247
+ if (input === "]") {
248
+ changeSelectedDetailView(1);
176
249
  return;
177
250
  }
178
- if (key.leftArrow) {
179
- setSelectedDetailTabIndex((current) => (current - 1 + DETAIL_TABS.length) % DETAIL_TABS.length);
251
+ if (input === "[") {
252
+ changeSelectedDetailView(-1);
180
253
  return;
181
254
  }
182
- if ((key.tab && !key.shift) || input === "]") {
183
- setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
184
- setHasUserSelectedProvider(true);
255
+ if (key.rightArrow) {
256
+ changeSelectedSectionValue(1);
185
257
  return;
186
258
  }
187
- if ((key.tab && key.shift) || input === "[") {
188
- setSelectedProviderId(sortedProviderStates[(selectedProviderIndex - 1 + sortedProviderStates.length) % sortedProviderStates.length]
189
- .provider.id);
190
- setHasUserSelectedProvider(true);
259
+ if (key.leftArrow) {
260
+ changeSelectedSectionValue(-1);
191
261
  return;
192
262
  }
193
- if (key.downArrow || input === "j") {
194
- moveSelectedTableRow(1);
263
+ if (key.downArrow) {
264
+ moveSelectionDown();
195
265
  return;
196
266
  }
197
- if (key.upArrow || input === "k") {
198
- moveSelectedTableRow(-1);
267
+ if (key.upArrow) {
268
+ moveSelectionUp();
199
269
  }
200
270
  });
201
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "white", backgroundColor: "green", children: " beta " })] }), _jsxs(Box, { 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, { 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, { 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" })] }));
271
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "white", backgroundColor: "green", children: " beta " })] }), _jsxs(Box, { children: [_jsx(ControlSectionLabel, { label: "Provider", active: selectedControlSection === "provider" }), sortedProviderStates.map((state) => (_jsx(ProviderOption, { 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(ControlSectionLabel, { label: "View", active: selectedControlSection === "view" }), DETAIL_VIEWS.map((view, index) => (_jsx(ViewOption, { label: view.label, active: index === selectedDetailViewIndex, regionRef: getRegionRef(`view:${index}`) }, view.id)))] }), _jsxs(Box, { 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, viewId: selectedDetailView.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, viewId: selectedDetailView.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, { 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: "\u2191/\u2193 move \u00B7 \u2190/\u2192 selected \u00B7 Tab provider \u00B7 [/] view \u00B7 q quit" })] }));
202
272
  }
203
273
  function CopilotActionsPanel(props) {
204
274
  if (props.providerState.provider.id !== "copilot") {
@@ -206,9 +276,9 @@ function CopilotActionsPanel(props) {
206
276
  }
207
277
  const hasNoUsage = props.providerState.status === "ready" && props.providerState.stats.summary.tokenEvents === 0;
208
278
  const accentColor = hasNoUsage ? "red" : "cyan";
209
- return (_jsxs(Box, { borderStyle: "round", borderColor: accentColor, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: accentColor, children: "Copilot setup" }), _jsx(Box, { children: COPILOT_ACTIONS.map((action, index) => (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: index === (props.selectedActionIndex ?? 0), bold: hasNoUsage && action.id === "vscode", color: action.enabled ? (hasNoUsage && action.id === "vscode" ? accentColor : undefined) : "gray", children: `${index + 1} ${action.label}` }) }, action.id))) }), _jsx(Text, { color: hasNoUsage ? accentColor : "gray", children: "Press 1 or h/l to select an action, enter to run selected." }), props.actionMessage ? _jsx(Text, { children: props.actionMessage }) : null] }));
279
+ return (_jsxs(Box, { borderStyle: "round", borderColor: accentColor, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: accentColor, children: "Copilot setup" }), _jsx(Box, { children: COPILOT_ACTIONS.map((action, index) => (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: index === (props.selectedActionIndex ?? 0), bold: hasNoUsage && action.id === "vscode", color: action.enabled ? (hasNoUsage && action.id === "vscode" ? accentColor : undefined) : "gray", children: `${index + 1} ${action.label}` }) }, action.id))) }), _jsx(Text, { color: hasNoUsage ? accentColor : "gray", children: "Press 1 to select an action, enter to run selected." }), props.actionMessage ? _jsx(Text, { children: props.actionMessage }) : null] }));
210
280
  }
211
- function runCopilotAction(actionId, setCopilotActionMessage) {
281
+ function runCopilotAction(setCopilotActionMessage) {
212
282
  setCopilotActionMessage("Updating VS Code settings...");
213
283
  void configureCopilotVsCodeLogging()
214
284
  .then((result) => {
@@ -232,14 +302,17 @@ function formatCopilotLoggingResult(result) {
232
302
  `"github.copilot.chat.otel.outfile": "${result.outfile}"`
233
303
  ].join("\n");
234
304
  }
235
- function ProviderTab(props) {
305
+ function ProviderOption(props) {
236
306
  const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
237
- const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
238
- return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: tabLabel }) }));
307
+ const optionLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
308
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: optionLabel }) }));
309
+ }
310
+ function ControlSectionLabel(props) {
311
+ return (_jsxs(Text, { color: props.active ? "cyan" : "gray", bold: props.active, children: [props.active ? "› " : " ", pad(props.label, 10)] }));
239
312
  }
240
- function DetailTab(props) {
241
- const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
242
- return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: tabLabel }) }));
313
+ function ViewOption(props) {
314
+ const optionLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
315
+ return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: optionLabel }) }));
243
316
  }
244
317
  function SummaryPanel(props) {
245
318
  const { summary } = props.stats;
@@ -262,13 +335,13 @@ function ContentPanel(props) {
262
335
  if (props.providerState.status === "error") {
263
336
  return _jsxs(Text, { color: "red", children: ["Provider error: ", props.providerState.errorMessage] });
264
337
  }
265
- if (props.tabId === "limit-windows") {
338
+ if (props.viewId === "limit-windows") {
266
339
  return (_jsx(LimitWindowsPanel, { stats: props.providerState.stats, selectedRowKey: props.selectedLimitRowKey, availableHeight: props.availableHeight }));
267
340
  }
268
- if (props.tabId === "summary") {
341
+ if (props.viewId === "summary") {
269
342
  return _jsx(SummaryPanel, { stats: props.providerState.stats });
270
343
  }
271
- if (props.tabId === "day-to-day-analyses") {
344
+ if (props.viewId === "day-to-day-analyses") {
272
345
  return (_jsx(DayToDayPanel, { stats: props.providerState.stats, selectedDayKey: props.selectedDayKey, availableHeight: props.availableHeight }));
273
346
  }
274
347
  return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
@@ -463,15 +536,15 @@ function SelectionDetailsPanel(props) {
463
536
  if (props.providerState.status !== "ready") {
464
537
  return null;
465
538
  }
466
- if (props.tabId === "limit-windows" && props.selectedLimitRow) {
539
+ if (props.viewId === "limit-windows" && props.selectedLimitRow) {
467
540
  const row = props.selectedLimitRow;
468
541
  return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 25, children: [_jsx(DetailRow, { label: "Plan", value: row.planType }), _jsx(DetailRow, { label: "Models", value: formatLimitWindowModels(row) }), _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) })] }), _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.cacheReadStatus, row.totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Cache write", value: formatCacheTokens(row.totals.cacheWriteStatus, row.totals.cacheWriteInputTokens) }), _jsx(DetailRow, { label: "Output", value: formatInteger(row.totals.outputTokens) }), _jsx(DetailRow, { label: "Total", value: formatInteger(row.totals.totalTokens) })] })] }), _jsx(DetailRow, { label: "API eq.", value: formatUsageUsd(row.totals), note: "API equivalent cost" }), _jsx(DetailRow, { label: "Full value", value: formatLimitFullValueRange(row.totals, row.maxUsedPercent), note: "API equivalent cost if 100% will be used" })] }));
469
542
  }
470
- if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
543
+ if (props.viewId === "day-to-day-analyses" && props.selectedDayRow) {
471
544
  const row = props.selectedDayRow;
472
545
  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 })] }));
473
546
  }
474
- if (props.tabId === "usage-by-model" && props.selectedModelRow) {
547
+ if (props.viewId === "usage-by-model" && props.selectedModelRow) {
475
548
  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 })] }));
476
549
  }
477
550
  return null;
@@ -534,21 +607,6 @@ function formatCompactNumber(value) {
534
607
  minimumFractionDigits: 0
535
608
  });
536
609
  }
537
- function formatCredits(value) {
538
- if (value > 0 && value < 0.01) {
539
- return "<0.01";
540
- }
541
- return value.toLocaleString("en-US", {
542
- minimumFractionDigits: 2,
543
- maximumFractionDigits: 2
544
- });
545
- }
546
- function formatUsageCredits(totals, modelId) {
547
- if (isInternalUsageModel(modelId)) {
548
- return "N/A";
549
- }
550
- return totals.estimatedCreditsStatus === "unavailable" ? "-" : formatCredits(totals.estimatedCredits);
551
- }
552
610
  function formatUsageUsd(totals, modelId) {
553
611
  if (isInternalUsageModel(modelId)) {
554
612
  return "N/A";
@@ -664,13 +722,6 @@ function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
664
722
  ? fmt(minUsedPercent)
665
723
  : `${fmt(minUsedPercent)}–${fmt(maxUsedPercent)}`;
666
724
  }
667
- function formatWindowMinutes(value) {
668
- const hours = value / 60;
669
- if (hours >= 24) {
670
- return `${(hours / 24).toFixed(2)}d`;
671
- }
672
- return `${hours.toFixed(2)}h`;
673
- }
674
725
  function formatCompactWindowMinutes(value) {
675
726
  const hours = value / 60;
676
727
  if (hours >= 24) {
@@ -843,7 +894,7 @@ function parseMouseClicks(chunk) {
843
894
  }
844
895
  return clicks;
845
896
  }
846
- // Tracks the on-screen rectangle of named clickable regions (the tabs) via Ink
897
+ // Tracks the on-screen rectangle of named clickable regions via Ink
847
898
  // refs and resolves a click coordinate back to a region id.
848
899
  function useClickRegions() {
849
900
  const nodesRef = useRef(new Map());
@@ -966,6 +1017,12 @@ function clampSelectionIndex(value, rowCount) {
966
1017
  }
967
1018
  return Math.max(0, Math.min(value, rowCount - 1));
968
1019
  }
1020
+ function clampOptionalSelectionIndex(value, rowCount) {
1021
+ if (value === undefined) {
1022
+ return -1;
1023
+ }
1024
+ return clampSelectionIndex(value, rowCount);
1025
+ }
969
1026
  function sortProviderStatesByUsage(states) {
970
1027
  return states
971
1028
  .map((state, index) => ({ state, index }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letmecode",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Terminal AI usage dashboard for Codex, Claude, Copilot, and Antigravity.",
5
5
  "author": "Devforth (https://devforth.io)",
6
6
  "license": "MIT",