letmecode 0.1.0 → 0.1.2

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
@@ -1,18 +1,44 @@
1
- # letmecode
1
+ # letmecode - Discover your detailed agent ussage Codex | Claude
2
2
 
3
- Minimal `npx`-first CLI package.
4
-
5
- ## Local development
3
+ Ussage:
6
4
 
7
5
  ```bash
8
- pnpm install
9
- pnpm start
6
+ npx -y letmecode
10
7
  ```
11
8
 
12
- The Ink app source lives in `ink-app/`.
9
+ <img width="2308" height="1491" alt="image" src="https://github.com/user-attachments/assets/f3f52d79-00e3-4ff5-bf2f-65f8be632aaa" />
10
+
11
+ ## Controls
12
+
13
+ | Key | Action |
14
+ | --- | --- |
15
+ | `[` / `]` | Switch providers |
16
+ | `Tab` / `Shift+Tab` | Switch providers when supported by the terminal |
17
+ | `Up` / `Down` or `k` / `j` | Switch dashboard sections |
18
+ | `Left` / `Right` | Select the previous or next table row |
19
+ | `Enter` | Run the selected provider action |
20
+ | `1` | Select the Copilot VS Code setup action |
21
+ | `h` / `l` | Select a Copilot setup action |
22
+ | `q` or `Esc` | Quit |
23
+
24
+ ## Copilot
13
25
 
14
- ## Usage
26
+ Copilot CLI usage is read from `~/.copilot/session-state`.
27
+
28
+ VS Code extension usage needs file OTEL logging first. Select the `Copilot` provider, choose `Start logging VS Code` with `1` or `h` / `l`, then press `Enter`; letmecode will update the current user's VS Code settings with:
29
+
30
+ ```json
31
+ {
32
+ "github.copilot.chat.otel.enabled": true,
33
+ "github.copilot.chat.otel.exporterType": "file",
34
+ "github.copilot.chat.otel.outfile": "~/.copilot/otel/vscode.jsonl",
35
+ "github.copilot.chat.otel.captureContent": false
36
+ }
37
+ ```
38
+
39
+ ## Local development
15
40
 
16
41
  ```bash
17
- npx letmecode
42
+ pnpm install
43
+ pnpm start
18
44
  ```
@@ -1,22 +1,71 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState } from "react";
3
3
  import { Box, Text, useApp, useInput, render } from "ink";
4
- import { createProviders } from "./providers/index.js";
4
+ import { configureCopilotVsCodeLogging, createProviders } from "./providers/index.js";
5
5
  const VERTICAL_TABS = [
6
- { id: "limit-windows", label: "Limit windows" },
7
- { id: "usage-by-model", label: "Usage by model" }
6
+ { id: "limit-windows", label: "Limits" },
7
+ { id: "summary", label: "Summary" },
8
+ { id: "day-to-day-analyses", label: "day to day" },
9
+ { id: "usage-by-model", label: "by model" }
8
10
  ];
9
- function App() {
11
+ const CODEX_CREDIT_COST_USD = 0.01;
12
+ const VERTICAL_TAB_WIDTH = 12;
13
+ const LIMIT_WINDOW_COLUMNS = {
14
+ plan: 8,
15
+ window: 8,
16
+ used: 10,
17
+ date: 17,
18
+ value: 10
19
+ };
20
+ const MODEL_USAGE_COLUMNS = {
21
+ model: 17,
22
+ input: 12,
23
+ cached: 12,
24
+ nonCached: 12,
25
+ output: 11,
26
+ credits: 12,
27
+ value: 12
28
+ };
29
+ const DAY_USAGE_COLUMNS = {
30
+ day: 11,
31
+ events: 6,
32
+ input: 11,
33
+ output: 10,
34
+ value: 10
35
+ };
36
+ const COPILOT_ACTIONS = [
37
+ { id: "vscode", label: "Start logging VS Code", enabled: true }
38
+ ];
39
+ function App(props) {
10
40
  const { exit } = useApp();
11
41
  const providers = React.useState(() => createProviders())[0];
12
42
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
13
- const [selectedProviderIndex, setSelectedProviderIndex] = useState(0);
43
+ const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
44
+ const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
14
45
  const [selectedVerticalTabIndex, setSelectedVerticalTabIndex] = useState(0);
46
+ const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
47
+ const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
48
+ const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
49
+ const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
50
+ const [copilotActionMessage, setCopilotActionMessage] = useState();
51
+ const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
52
+ const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
53
+ const selectedProvider = sortedProviderStates[selectedProviderIndex];
54
+ const selectedVerticalTab = VERTICAL_TABS[selectedVerticalTabIndex];
55
+ const limitRows = getLimitRows(selectedProvider);
56
+ const dayRows = getDayRows(selectedProvider);
57
+ const modelRows = getModelRows(selectedProvider);
58
+ const activeLimitRowIndex = clampSelectionIndex(selectedLimitRowIndex, limitRows.length);
59
+ const activeDayRowIndex = clampSelectionIndex(selectedDayRowIndex, dayRows.length);
60
+ const activeModelRowIndex = clampSelectionIndex(selectedModelRowIndex, modelRows.length);
61
+ const selectedLimitRow = activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
62
+ const selectedDayRow = activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
63
+ const selectedModelRow = activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
15
64
  useEffect(() => {
16
65
  let cancelled = false;
17
66
  for (const provider of providers) {
18
67
  void provider
19
- .getStats()
68
+ .getStats(props.statsOptions)
20
69
  .then((stats) => {
21
70
  if (cancelled) {
22
71
  return;
@@ -38,18 +87,75 @@ function App() {
38
87
  return () => {
39
88
  cancelled = true;
40
89
  };
41
- }, [providers]);
90
+ }, [props.statsOptions, providers]);
91
+ useEffect(() => {
92
+ if (hasUserSelectedProvider || providerStates.some((state) => state.status === "loading")) {
93
+ return;
94
+ }
95
+ const topProvider = sortedProviderStates[0];
96
+ if (providerUsageScore(topProvider) <= 0) {
97
+ return;
98
+ }
99
+ setSelectedProviderId(topProvider.provider.id);
100
+ }, [hasUserSelectedProvider, providerStates, sortedProviderStates]);
42
101
  useInput((input, key) => {
43
102
  if (input === "q" || key.escape) {
44
103
  exit();
45
104
  return;
46
105
  }
47
- if ((input === "\t" && !key.shift) || key.rightArrow) {
48
- setSelectedProviderIndex((current) => (current + 1) % providerStates.length);
106
+ if (selectedProvider.provider.id === "copilot" && input >= "1" && input <= String(COPILOT_ACTIONS.length)) {
107
+ setSelectedCopilotActionIndex(Number(input) - 1);
108
+ return;
109
+ }
110
+ if (selectedProvider.provider.id === "copilot" && key.return) {
111
+ runCopilotAction(COPILOT_ACTIONS[selectedCopilotActionIndex].id, setCopilotActionMessage);
112
+ return;
113
+ }
114
+ if (selectedProvider.provider.id === "copilot" && input === "l") {
115
+ setSelectedCopilotActionIndex((current) => (current + 1) % COPILOT_ACTIONS.length);
116
+ return;
117
+ }
118
+ if (selectedProvider.provider.id === "copilot" && input === "h") {
119
+ setSelectedCopilotActionIndex((current) => (current - 1 + COPILOT_ACTIONS.length) % COPILOT_ACTIONS.length);
120
+ return;
121
+ }
122
+ if (key.rightArrow) {
123
+ if (selectedVerticalTab.id === "limit-windows") {
124
+ setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + 1, limitRows.length));
125
+ return;
126
+ }
127
+ if (selectedVerticalTab.id === "usage-by-model") {
128
+ setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + 1, modelRows.length));
129
+ return;
130
+ }
131
+ if (selectedVerticalTab.id === "day-to-day-analyses") {
132
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + 1, dayRows.length));
133
+ return;
134
+ }
135
+ }
136
+ if (key.leftArrow) {
137
+ if (selectedVerticalTab.id === "limit-windows") {
138
+ setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex - 1, limitRows.length));
139
+ return;
140
+ }
141
+ if (selectedVerticalTab.id === "usage-by-model") {
142
+ setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex - 1, modelRows.length));
143
+ return;
144
+ }
145
+ if (selectedVerticalTab.id === "day-to-day-analyses") {
146
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex - 1, dayRows.length));
147
+ return;
148
+ }
149
+ }
150
+ if ((key.tab && !key.shift) || input === "]") {
151
+ setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
152
+ setHasUserSelectedProvider(true);
49
153
  return;
50
154
  }
51
- if ((input === "\t" && key.shift) || key.leftArrow) {
52
- setSelectedProviderIndex((current) => (current - 1 + providerStates.length) % providerStates.length);
155
+ if ((key.tab && key.shift) || input === "[") {
156
+ setSelectedProviderId(sortedProviderStates[(selectedProviderIndex - 1 + sortedProviderStates.length) % sortedProviderStates.length]
157
+ .provider.id);
158
+ setHasUserSelectedProvider(true);
53
159
  return;
54
160
  }
55
161
  if (key.downArrow || input === "j") {
@@ -60,9 +166,39 @@ function App() {
60
166
  setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
61
167
  }
62
168
  });
63
- const selectedProvider = providerStates[selectedProviderIndex];
64
- const selectedVerticalTab = VERTICAL_TABS[selectedVerticalTabIndex];
65
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "letmecode usage dashboard" }), _jsx(Text, { color: "gray", children: "tab/shift+tab or left/right to switch providers, j/k or up/down for details, q to quit" }), _jsx(Box, { marginTop: 1, children: providerStates.map((state, index) => (_jsx(ProviderTab, { label: state.provider.label, active: index === selectedProviderIndex, status: state.status }, state.provider.id))) }), _jsx(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, paddingY: 0, flexDirection: "column", children: _jsx(SummarySection, { providerState: selectedProvider }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Box, { flexDirection: "column", width: 22, marginRight: 2, children: VERTICAL_TABS.map((tab, index) => (_jsx(VerticalTab, { label: tab.label, active: index === selectedVerticalTabIndex }, tab.id))) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id }) })] }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }));
169
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, 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, children: [_jsx(Box, { flexDirection: "column", width: VERTICAL_TAB_WIDTH, marginRight: 2, children: VERTICAL_TABS.map((tab, index) => (_jsx(VerticalTab, { label: tab.label, active: index === selectedVerticalTabIndex }, tab.id))) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId }) })] }), _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", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }));
170
+ }
171
+ function CopilotActionsPanel(props) {
172
+ if (props.providerState.provider.id !== "copilot") {
173
+ return null;
174
+ }
175
+ const hasNoUsage = props.providerState.status === "ready" && props.providerState.stats.summary.tokenEvents === 0;
176
+ const accentColor = hasNoUsage ? "red" : "cyan";
177
+ return (_jsxs(Box, { marginTop: 1, 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] }));
178
+ }
179
+ function runCopilotAction(actionId, setCopilotActionMessage) {
180
+ setCopilotActionMessage("Updating VS Code settings...");
181
+ void configureCopilotVsCodeLogging()
182
+ .then((result) => {
183
+ setCopilotActionMessage(formatCopilotLoggingResult(result));
184
+ })
185
+ .catch((error) => {
186
+ const message = error instanceof Error ? error.message : String(error);
187
+ setCopilotActionMessage(`Failed to update VS Code settings: ${message}`);
188
+ });
189
+ }
190
+ function formatCopilotLoggingResult(result) {
191
+ const status = result.changed ? "VS Code logging enabled" : "VS Code logging already enabled";
192
+ return [
193
+ `${status}: ${result.outfile}`,
194
+ `Settings written to: ${result.settingsPath}`,
195
+ 'Open "Preferences: Open User Settings (JSON)" in VS Code and verify that this is the active file.',
196
+ "Expected settings:",
197
+ '"github.copilot.chat.otel.enabled": true',
198
+ '"github.copilot.chat.otel.exporterType": "file"',
199
+ '"github.copilot.chat.otel.captureContent": false',
200
+ `"github.copilot.chat.otel.outfile": "${result.outfile}"`
201
+ ].join("\n");
66
202
  }
67
203
  function ProviderTab(props) {
68
204
  const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
@@ -70,17 +206,12 @@ function ProviderTab(props) {
70
206
  return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
71
207
  }
72
208
  function VerticalTab(props) {
73
- return (_jsx(Text, { inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }));
209
+ return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
74
210
  }
75
- function SummarySection(props) {
76
- if (props.providerState.status === "loading") {
77
- return (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: props.providerState.provider.label }), _jsx(Text, { color: "yellow", children: "Loading stats from local sessions..." })] }));
78
- }
79
- if (props.providerState.status === "error") {
80
- return (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: props.providerState.provider.label }), _jsxs(Text, { color: "red", children: ["Failed to load provider stats: ", props.providerState.errorMessage] })] }));
81
- }
82
- const { summary } = props.providerState.stats;
83
- return (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: props.providerState.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)] }), _jsxs(Text, { children: ["input: ", formatInteger(summary.totals.inputTokens), " cached: ", formatInteger(summary.totals.cachedInputTokens), " non-cached: ", formatInteger(summary.totals.nonCachedInputTokens)] }), _jsxs(Text, { children: ["output: ", formatInteger(summary.totals.outputTokens), " reasoning: ", formatInteger(summary.totals.reasoningOutputTokens), " total: ", formatInteger(summary.totals.totalTokens)] }), _jsxs(Text, { children: ["estimated credits: ", formatCredits(summary.totals.estimatedCredits), " models: ", summary.distinctModels.join(", ") || "none", " plans: ", summary.distinctPlanTypes.join(", ") || "none"] })] }));
211
+ function SummaryPanel(props) {
212
+ const { summary } = props.stats;
213
+ const inputPerOutput = formatInputPerOutput(summary.totals);
214
+ 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)] }), _jsxs(Text, { children: ["input: ", formatInteger(summary.totals.inputTokens), " cached: ", formatCacheTokens(summary.totals, "cached"), " non-cached: ", formatCacheTokens(summary.totals, "non-cached")] }), _jsxs(Text, { children: ["output: ", formatInteger(summary.totals.outputTokens), " reasoning: ", formatInteger(summary.totals.reasoningOutputTokens), " total: ", formatInteger(summary.totals.totalTokens)] }), _jsxs(Text, { children: ["estimated credits: ", formatUsageCredits(summary.totals)] }), _jsxs(Text, { children: ["IpO: ", inputPerOutput.cached, ":", inputPerOutput.nonCached, ":", inputPerOutput.output] }), _jsxs(Text, { children: ["models: ", summary.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", summary.distinctPlanTypes.join(", ") || "none"] })] }));
84
215
  }
85
216
  function ContentPanel(props) {
86
217
  if (props.providerState.status === "loading") {
@@ -90,21 +221,28 @@ function ContentPanel(props) {
90
221
  return _jsxs(Text, { color: "red", children: ["Provider error: ", props.providerState.errorMessage] });
91
222
  }
92
223
  if (props.tabId === "limit-windows") {
93
- return _jsx(LimitWindowsPanel, { stats: props.providerState.stats });
224
+ return _jsx(LimitWindowsPanel, { stats: props.providerState.stats, selectedRowKey: props.selectedLimitRowKey });
225
+ }
226
+ if (props.tabId === "summary") {
227
+ return _jsx(SummaryPanel, { stats: props.providerState.stats });
94
228
  }
95
- return _jsx(UsageByModelPanel, { stats: props.providerState.stats });
229
+ if (props.tabId === "day-to-day-analyses") {
230
+ return _jsx(DayToDayPanel, { stats: props.providerState.stats, selectedDayKey: props.selectedDayKey });
231
+ }
232
+ return _jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId });
96
233
  }
97
234
  function LimitWindowsPanel(props) {
98
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Primary" }), _jsx(LimitWindowSection, { windows: props.stats.primaryLimitWindows }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { bold: true, children: "Secondary" }), _jsx(LimitWindowSection, { windows: props.stats.secondaryLimitWindows })] }));
235
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Primary Limit Windows" }), _jsx(LimitWindowSection, { windows: props.stats.primaryLimitWindows, selectedRowKey: props.selectedRowKey }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { bold: true, children: "Secondary Limit Windows" }), _jsx(LimitWindowSection, { windows: props.stats.secondaryLimitWindows, selectedRowKey: props.selectedRowKey })] }));
99
236
  }
100
237
  function LimitWindowSection(props) {
101
238
  if (props.windows.length === 0) {
102
239
  return _jsx(Text, { color: "gray", children: "No windows found." });
103
240
  }
104
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "plan limit window used start end events" }), props.windows.map((window) => {
241
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [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"] }), props.windows.map((window) => {
105
242
  const windowLabel = formatWindowMinutes(window.windowMinutes);
106
243
  const usedLabel = `${window.minUsedPercent}%->${window.maxUsedPercent}%`;
107
- return (_jsxs(Text, { children: [pad(window.planType, 10), " ", pad(window.limitId, 10), " ", pad(windowLabel, 8), " ", pad(usedLabel, 12), " ", pad(shortIso(window.startTimeIso), 20), " ", pad(shortIso(window.endTimeIso), 20), " ", formatInteger(window.eventCount)] }, `${window.scope}-${window.planType}-${window.limitId}-${window.endTimeIso}`));
244
+ const isSelected = props.selectedRowKey === getLimitRowKey(window);
245
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [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)] }, getLimitRowKey(window)));
108
246
  })] }));
109
247
  }
110
248
  function UsageByModelPanel(props) {
@@ -112,17 +250,79 @@ function UsageByModelPanel(props) {
112
250
  return _jsx(Text, { color: "gray", children: "No model usage found." });
113
251
  }
114
252
  const totals = props.stats.summary.totals;
115
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "model input cached non-cached output credits events" }), props.stats.modelUsage.map((row) => (_jsxs(Text, { children: [pad(row.modelId, 16), " ", pad(formatInteger(row.totals.inputTokens), 12), " ", pad(formatInteger(row.totals.cachedInputTokens), 12), " ", pad(formatInteger(row.totals.nonCachedInputTokens), 12), " ", pad(formatInteger(row.totals.outputTokens), 12), " ", pad(formatCredits(row.totals.estimatedCredits), 12), " ", pad(formatInteger(row.totals.eventCount), 8)] }, row.modelId))), _jsxs(Text, { color: "cyan", children: [pad("TOTAL", 16), " ", pad(formatInteger(totals.inputTokens), 12), " ", pad(formatInteger(totals.cachedInputTokens), 12), " ", pad(formatInteger(totals.nonCachedInputTokens), 12), " ", pad(formatInteger(totals.outputTokens), 12), " ", pad(formatCredits(totals.estimatedCredits), 12), " ", pad(formatInteger(totals.eventCount), 8)] })] }));
253
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [pad("model", MODEL_USAGE_COLUMNS.model), " ", pad("input", MODEL_USAGE_COLUMNS.input), " ", pad("cached", MODEL_USAGE_COLUMNS.cached), " ", pad("non-cached", MODEL_USAGE_COLUMNS.nonCached), " ", pad("output", MODEL_USAGE_COLUMNS.output), " ", pad("credits", MODEL_USAGE_COLUMNS.credits), " value"] }), props.stats.modelUsage.map((row) => {
254
+ const isSelected = props.selectedModelId === row.modelId;
255
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [pad(row.modelId, MODEL_USAGE_COLUMNS.model), " ", pad(formatInteger(row.totals.inputTokens), MODEL_USAGE_COLUMNS.input), " ", pad(formatCacheTokens(row.totals, "cached"), MODEL_USAGE_COLUMNS.cached), " ", pad(formatCacheTokens(row.totals, "non-cached"), MODEL_USAGE_COLUMNS.nonCached), " ", pad(formatInteger(row.totals.outputTokens), MODEL_USAGE_COLUMNS.output), " ", pad(formatUsageCredits(row.totals), MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsageUsd(row.totals), MODEL_USAGE_COLUMNS.value)] }, row.modelId));
256
+ }), _jsxs(Text, { color: "cyan", children: [pad("TOTAL", MODEL_USAGE_COLUMNS.model), " ", pad(formatInteger(totals.inputTokens), MODEL_USAGE_COLUMNS.input), " ", pad(formatCacheTokens(totals, "cached"), MODEL_USAGE_COLUMNS.cached), " ", pad(formatCacheTokens(totals, "non-cached"), MODEL_USAGE_COLUMNS.nonCached), " ", pad(formatInteger(totals.outputTokens), MODEL_USAGE_COLUMNS.output), " ", pad(formatUsageCredits(totals), MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsageUsd(totals), MODEL_USAGE_COLUMNS.value)] })] }));
257
+ }
258
+ function DayToDayPanel(props) {
259
+ if (props.stats.dayUsage.length === 0) {
260
+ return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
261
+ }
262
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [pad("day", DAY_USAGE_COLUMNS.day), " ", pad("events", DAY_USAGE_COLUMNS.events), " ", pad("input", DAY_USAGE_COLUMNS.input), " ", pad("output", DAY_USAGE_COLUMNS.output), " value"] }), props.stats.dayUsage.map((row) => {
263
+ const isSelected = props.selectedDayKey === row.dayKey;
264
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [pad(formatUtcDay(row.dayKey), DAY_USAGE_COLUMNS.day), " ", pad(formatInteger(row.totals.eventCount), DAY_USAGE_COLUMNS.events), " ", pad(formatInteger(row.totals.inputTokens), DAY_USAGE_COLUMNS.input), " ", pad(formatInteger(row.totals.outputTokens), DAY_USAGE_COLUMNS.output), " ", pad(formatUsageUsd(row.totals), DAY_USAGE_COLUMNS.value)] }, row.dayKey));
265
+ })] }));
266
+ }
267
+ function SelectionDetailsPanel(props) {
268
+ if (props.providerState.status !== "ready") {
269
+ return null;
270
+ }
271
+ if (props.tabId === "limit-windows" && props.selectedLimitRow) {
272
+ const row = props.selectedLimitRow;
273
+ 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 })] }));
274
+ }
275
+ if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
276
+ const row = props.selectedDayRow;
277
+ 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: ["input: ", formatInteger(row.totals.inputTokens), " cached: ", formatCacheTokens(row.totals, "cached")] }), _jsxs(Text, { children: ["non-cached: ", formatCacheTokens(row.totals, "non-cached"), " output: ", formatInteger(row.totals.outputTokens)] }), _jsxs(Text, { children: ["models: ", row.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", row.distinctPlanTypes.join(", ") || "none"] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
278
+ }
279
+ if (props.tabId === "usage-by-model" && props.selectedModelRow) {
280
+ 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 })] }));
281
+ }
282
+ return null;
283
+ }
284
+ function UsageTotalsDetails(props) {
285
+ const { totals } = props;
286
+ const inputPerOutput = formatInputPerOutput(totals);
287
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Total credits burned: ", formatUsageCredits(totals)] }), _jsxs(Text, { children: ["Credits Value (@ $0.01/credit): ", formatUsageUsd(totals)] }), _jsxs(Text, { children: ["IpO: ", inputPerOutput.cached, ":", inputPerOutput.nonCached, ":", inputPerOutput.output] })] }));
116
288
  }
117
289
  function formatInteger(value) {
118
290
  return Math.round(value).toLocaleString("en-US");
119
291
  }
120
292
  function formatCredits(value) {
293
+ if (value > 0 && value < 0.01) {
294
+ return "<0.01";
295
+ }
121
296
  return value.toLocaleString("en-US", {
122
297
  minimumFractionDigits: 2,
123
298
  maximumFractionDigits: 2
124
299
  });
125
300
  }
301
+ function formatUsageCredits(totals) {
302
+ return totals.estimatedCreditsStatus === "unavailable" ? "unknown" : formatCredits(totals.estimatedCredits);
303
+ }
304
+ function formatUsageUsd(totals) {
305
+ return totals.estimatedCreditsStatus === "unavailable"
306
+ ? "unknown"
307
+ : formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD);
308
+ }
309
+ function formatCacheTokens(totals, kind) {
310
+ if (totals.cacheStatus === "unavailable") {
311
+ return "unknown";
312
+ }
313
+ return formatInteger(kind === "cached" ? totals.cachedInputTokens : totals.nonCachedInputTokens);
314
+ }
315
+ function formatUsd(value) {
316
+ if (value > 0 && value < 0.0001) {
317
+ return "<$0.0001";
318
+ }
319
+ return value.toLocaleString("en-US", {
320
+ currency: "USD",
321
+ style: "currency",
322
+ minimumFractionDigits: 2,
323
+ maximumFractionDigits: 4
324
+ });
325
+ }
126
326
  function formatWindowMinutes(value) {
127
327
  const hours = value / 60;
128
328
  if (hours >= 24) {
@@ -130,13 +330,101 @@ function formatWindowMinutes(value) {
130
330
  }
131
331
  return `${hours.toFixed(2)}h`;
132
332
  }
133
- function shortIso(value) {
134
- return value.replace(".000Z", "Z").slice(0, 19) + "Z";
333
+ function formatLocalDateTime(value) {
334
+ const parts = new Intl.DateTimeFormat("en-US", {
335
+ month: "short",
336
+ day: "2-digit",
337
+ year: "2-digit",
338
+ hour: "2-digit",
339
+ minute: "2-digit",
340
+ hour12: false,
341
+ hourCycle: "h23"
342
+ }).formatToParts(new Date(value));
343
+ const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
344
+ return `${lookup.day} ${lookup.month} ${lookup.year} ${lookup.hour}:${lookup.minute}`;
345
+ }
346
+ function formatUtcDay(value) {
347
+ if (value === "unknown") {
348
+ return "unknown";
349
+ }
350
+ const parts = new Intl.DateTimeFormat("en-US", {
351
+ month: "short",
352
+ day: "2-digit",
353
+ year: "2-digit",
354
+ timeZone: "UTC"
355
+ }).formatToParts(new Date(`${value}T00:00:00.000Z`));
356
+ const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
357
+ return `${lookup.day} ${lookup.month} ${lookup.year}`;
358
+ }
359
+ function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
360
+ if (!firstEventUtcIso || !lastEventUtcIso) {
361
+ return "unknown";
362
+ }
363
+ return `${formatLocalDateTime(firstEventUtcIso)} -> ${formatLocalDateTime(lastEventUtcIso)}`;
135
364
  }
136
365
  function pad(value, length) {
137
366
  return value.length >= length ? value.slice(0, length) : value.padEnd(length);
138
367
  }
139
- export function main() {
140
- render(_jsx(App, {}));
368
+ function formatInputPerOutput(totals) {
369
+ if (totals.cacheStatus === "unavailable") {
370
+ return { cached: "unknown", nonCached: "unknown", output: "1" };
371
+ }
372
+ if (totals.outputTokens <= 0) {
373
+ return { cached: "0", nonCached: "0", output: "0" };
374
+ }
375
+ return {
376
+ cached: formatInteger(Math.round(totals.cachedInputTokens / totals.outputTokens)),
377
+ nonCached: formatInteger(Math.round(totals.nonCachedInputTokens / totals.outputTokens)),
378
+ output: "1"
379
+ };
380
+ }
381
+ function clampSelectionIndex(value, rowCount) {
382
+ if (rowCount === 0) {
383
+ return -1;
384
+ }
385
+ return Math.max(0, Math.min(value, rowCount - 1));
386
+ }
387
+ function sortProviderStatesByUsage(states) {
388
+ return states
389
+ .map((state, index) => ({ state, index }))
390
+ .sort((left, right) => providerUsageScore(right.state) - providerUsageScore(left.state) ||
391
+ left.index - right.index)
392
+ .map((entry) => entry.state);
393
+ }
394
+ function providerUsageScore(state) {
395
+ if (state.status !== "ready") {
396
+ return 0;
397
+ }
398
+ const totals = state.stats.summary.totals;
399
+ return totals.inputTokens + totals.cachedInputTokens + totals.outputTokens;
400
+ }
401
+ function getLimitRows(providerState) {
402
+ if (providerState.status !== "ready") {
403
+ return [];
404
+ }
405
+ return [...providerState.stats.primaryLimitWindows, ...providerState.stats.secondaryLimitWindows];
406
+ }
407
+ function getModelRows(providerState) {
408
+ if (providerState.status !== "ready") {
409
+ return [];
410
+ }
411
+ return providerState.stats.modelUsage;
412
+ }
413
+ function getDayRows(providerState) {
414
+ if (providerState.status !== "ready") {
415
+ return [];
416
+ }
417
+ return providerState.stats.dayUsage;
418
+ }
419
+ function getLimitRowKey(row) {
420
+ return `${row.scope}-${row.planType}-${row.limitId}-${row.startTimeUtcIso}-${row.endTimeUtcIso}`;
421
+ }
422
+ function parseStatsOptions(argv) {
423
+ return {
424
+ verbose: argv.includes("-v") || argv.includes("--verbose")
425
+ };
426
+ }
427
+ export function main(argv = process.argv.slice(2)) {
428
+ render(_jsx(App, { statsOptions: parseStatsOptions(argv) }));
141
429
  }
142
430
  main();