letmecode 0.1.1 → 0.1.3

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,13 +1,15 @@
1
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
6
  { id: "limit-windows", label: "Limits" },
7
7
  { id: "summary", label: "Summary" },
8
+ { id: "day-to-day-analyses", label: "day to day" },
8
9
  { id: "usage-by-model", label: "by model" }
9
10
  ];
10
11
  const CODEX_CREDIT_COST_USD = 0.01;
12
+ const VERTICAL_TAB_WIDTH = 12;
11
13
  const LIMIT_WINDOW_COLUMNS = {
12
14
  plan: 8,
13
15
  window: 8,
@@ -15,36 +17,74 @@ const LIMIT_WINDOW_COLUMNS = {
15
17
  date: 17,
16
18
  value: 10
17
19
  };
18
- const MODEL_USAGE_COLUMNS = {
20
+ const OPENAI_MODEL_USAGE_COLUMNS = {
19
21
  model: 17,
20
22
  input: 12,
21
23
  cached: 12,
22
- nonCached: 12,
23
24
  output: 11,
24
25
  credits: 12,
25
26
  value: 12
26
27
  };
27
- function App() {
28
+ const ANTHROPIC_MODEL_USAGE_COLUMNS = {
29
+ model: 17,
30
+ input: 10,
31
+ cacheWrite5m: 10,
32
+ cacheWrite1h: 10,
33
+ cacheRead: 10,
34
+ output: 10,
35
+ credits: 12,
36
+ value: 12
37
+ };
38
+ const OPENAI_DAY_USAGE_COLUMNS = {
39
+ day: 11,
40
+ events: 6,
41
+ input: 11,
42
+ output: 10,
43
+ value: 10
44
+ };
45
+ const ANTHROPIC_DAY_USAGE_COLUMNS = {
46
+ day: 11,
47
+ events: 6,
48
+ input: 10,
49
+ cacheWrite5m: 10,
50
+ cacheWrite1h: 10,
51
+ cacheRead: 10,
52
+ output: 10,
53
+ value: 10
54
+ };
55
+ const COPILOT_ACTIONS = [
56
+ { id: "vscode", label: "Start logging VS Code", enabled: true }
57
+ ];
58
+ function App(props) {
28
59
  const { exit } = useApp();
29
60
  const providers = React.useState(() => createProviders())[0];
30
61
  const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
31
- const [selectedProviderIndex, setSelectedProviderIndex] = useState(0);
62
+ const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
63
+ const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
32
64
  const [selectedVerticalTabIndex, setSelectedVerticalTabIndex] = useState(0);
33
65
  const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
66
+ const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
34
67
  const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
35
- const selectedProvider = providerStates[selectedProviderIndex];
68
+ const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
69
+ const [copilotActionMessage, setCopilotActionMessage] = useState();
70
+ const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
71
+ const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
72
+ const selectedProvider = sortedProviderStates[selectedProviderIndex];
36
73
  const selectedVerticalTab = VERTICAL_TABS[selectedVerticalTabIndex];
37
74
  const limitRows = getLimitRows(selectedProvider);
75
+ const dayRows = getDayRows(selectedProvider);
38
76
  const modelRows = getModelRows(selectedProvider);
39
77
  const activeLimitRowIndex = clampSelectionIndex(selectedLimitRowIndex, limitRows.length);
78
+ const activeDayRowIndex = clampSelectionIndex(selectedDayRowIndex, dayRows.length);
40
79
  const activeModelRowIndex = clampSelectionIndex(selectedModelRowIndex, modelRows.length);
41
80
  const selectedLimitRow = activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
81
+ const selectedDayRow = activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
42
82
  const selectedModelRow = activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
43
83
  useEffect(() => {
44
84
  let cancelled = false;
45
85
  for (const provider of providers) {
46
86
  void provider
47
- .getStats()
87
+ .getStats(props.statsOptions)
48
88
  .then((stats) => {
49
89
  if (cancelled) {
50
90
  return;
@@ -66,13 +106,39 @@ function App() {
66
106
  return () => {
67
107
  cancelled = true;
68
108
  };
69
- }, [providers]);
109
+ }, [props.statsOptions, providers]);
110
+ useEffect(() => {
111
+ if (hasUserSelectedProvider || providerStates.some((state) => state.status === "loading")) {
112
+ return;
113
+ }
114
+ const topProvider = sortedProviderStates[0];
115
+ if (providerUsageScore(topProvider) <= 0) {
116
+ return;
117
+ }
118
+ setSelectedProviderId(topProvider.provider.id);
119
+ }, [hasUserSelectedProvider, providerStates, sortedProviderStates]);
70
120
  useInput((input, key) => {
71
121
  if (input === "q" || key.escape) {
72
122
  exit();
73
123
  return;
74
124
  }
75
- if ((key.ctrl || key.shift) && key.downArrow) {
125
+ if (selectedProvider.provider.id === "copilot" && input >= "1" && input <= String(COPILOT_ACTIONS.length)) {
126
+ setSelectedCopilotActionIndex(Number(input) - 1);
127
+ return;
128
+ }
129
+ if (selectedProvider.provider.id === "copilot" && key.return) {
130
+ runCopilotAction(COPILOT_ACTIONS[selectedCopilotActionIndex].id, setCopilotActionMessage);
131
+ return;
132
+ }
133
+ if (selectedProvider.provider.id === "copilot" && input === "l") {
134
+ setSelectedCopilotActionIndex((current) => (current + 1) % COPILOT_ACTIONS.length);
135
+ return;
136
+ }
137
+ if (selectedProvider.provider.id === "copilot" && input === "h") {
138
+ setSelectedCopilotActionIndex((current) => (current - 1 + COPILOT_ACTIONS.length) % COPILOT_ACTIONS.length);
139
+ return;
140
+ }
141
+ if (key.rightArrow) {
76
142
  if (selectedVerticalTab.id === "limit-windows") {
77
143
  setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + 1, limitRows.length));
78
144
  return;
@@ -81,8 +147,12 @@ function App() {
81
147
  setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + 1, modelRows.length));
82
148
  return;
83
149
  }
150
+ if (selectedVerticalTab.id === "day-to-day-analyses") {
151
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + 1, dayRows.length));
152
+ return;
153
+ }
84
154
  }
85
- if ((key.ctrl || key.shift) && key.upArrow) {
155
+ if (key.leftArrow) {
86
156
  if (selectedVerticalTab.id === "limit-windows") {
87
157
  setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex - 1, limitRows.length));
88
158
  return;
@@ -91,13 +161,20 @@ function App() {
91
161
  setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex - 1, modelRows.length));
92
162
  return;
93
163
  }
164
+ if (selectedVerticalTab.id === "day-to-day-analyses") {
165
+ setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex - 1, dayRows.length));
166
+ return;
167
+ }
94
168
  }
95
- if ((input === "\t" && !key.shift) || key.rightArrow) {
96
- setSelectedProviderIndex((current) => (current + 1) % providerStates.length);
169
+ if ((key.tab && !key.shift) || input === "]") {
170
+ setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
171
+ setHasUserSelectedProvider(true);
97
172
  return;
98
173
  }
99
- if ((input === "\t" && key.shift) || key.leftArrow) {
100
- setSelectedProviderIndex((current) => (current - 1 + providerStates.length) % providerStates.length);
174
+ if ((key.tab && key.shift) || input === "[") {
175
+ setSelectedProviderId(sortedProviderStates[(selectedProviderIndex - 1 + sortedProviderStates.length) % sortedProviderStates.length]
176
+ .provider.id);
177
+ setHasUserSelectedProvider(true);
101
178
  return;
102
179
  }
103
180
  if (key.downArrow || input === "j") {
@@ -108,7 +185,39 @@ function App() {
108
185
  setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
109
186
  }
110
187
  });
111
- 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, ctrl+up/down or shift+up/down to select a row, 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))) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Box, { flexDirection: "column", width: 12, 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, selectedModelId: selectedModelRow?.modelId }) })] }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRow: selectedLimitRow, selectedModelRow: selectedModelRow }), 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] }));
188
+ 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] }));
189
+ }
190
+ function CopilotActionsPanel(props) {
191
+ if (props.providerState.provider.id !== "copilot") {
192
+ return null;
193
+ }
194
+ const hasNoUsage = props.providerState.status === "ready" && props.providerState.stats.summary.tokenEvents === 0;
195
+ const accentColor = hasNoUsage ? "red" : "cyan";
196
+ 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] }));
197
+ }
198
+ function runCopilotAction(actionId, setCopilotActionMessage) {
199
+ setCopilotActionMessage("Updating VS Code settings...");
200
+ void configureCopilotVsCodeLogging()
201
+ .then((result) => {
202
+ setCopilotActionMessage(formatCopilotLoggingResult(result));
203
+ })
204
+ .catch((error) => {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ setCopilotActionMessage(`Failed to update VS Code settings: ${message}`);
207
+ });
208
+ }
209
+ function formatCopilotLoggingResult(result) {
210
+ const status = result.changed ? "VS Code logging enabled" : "VS Code logging already enabled";
211
+ return [
212
+ `${status}: ${result.outfile}`,
213
+ `Settings written to: ${result.settingsPath}`,
214
+ 'Open "Preferences: Open User Settings (JSON)" in VS Code and verify that this is the active file.',
215
+ "Expected settings:",
216
+ '"github.copilot.chat.otel.enabled": true',
217
+ '"github.copilot.chat.otel.exporterType": "file"',
218
+ '"github.copilot.chat.otel.captureContent": false',
219
+ `"github.copilot.chat.otel.outfile": "${result.outfile}"`
220
+ ].join("\n");
112
221
  }
113
222
  function ProviderTab(props) {
114
223
  const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
@@ -116,12 +225,11 @@ function ProviderTab(props) {
116
225
  return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
117
226
  }
118
227
  function VerticalTab(props) {
119
- return (_jsx(Text, { inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }));
228
+ return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
120
229
  }
121
230
  function SummaryPanel(props) {
122
231
  const { summary } = props.stats;
123
- const inputPerOutput = formatInputPerOutput(summary.totals);
124
- 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: ", 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)] }), _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"] })] }));
232
+ 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"] })] }));
125
233
  }
126
234
  function ContentPanel(props) {
127
235
  if (props.providerState.status === "loading") {
@@ -136,10 +244,13 @@ function ContentPanel(props) {
136
244
  if (props.tabId === "summary") {
137
245
  return _jsx(SummaryPanel, { stats: props.providerState.stats });
138
246
  }
247
+ if (props.tabId === "day-to-day-analyses") {
248
+ return _jsx(DayToDayPanel, { stats: props.providerState.stats, selectedDayKey: props.selectedDayKey });
249
+ }
139
250
  return _jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId });
140
251
  }
141
252
  function LimitWindowsPanel(props) {
142
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Primary" }), _jsx(LimitWindowSection, { windows: props.stats.primaryLimitWindows, selectedRowKey: props.selectedRowKey }), _jsx(Box, { marginTop: 1 }), _jsx(Text, { bold: true, children: "Secondary" }), _jsx(LimitWindowSection, { windows: props.stats.secondaryLimitWindows, selectedRowKey: props.selectedRowKey })] }));
253
+ 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 })] }));
143
254
  }
144
255
  function LimitWindowSection(props) {
145
256
  if (props.windows.length === 0) {
@@ -157,10 +268,44 @@ function UsageByModelPanel(props) {
157
268
  return _jsx(Text, { color: "gray", children: "No model usage found." });
158
269
  }
159
270
  const totals = props.stats.summary.totals;
160
- 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) => {
271
+ if (totals.tokenBreakdown.schema === "anthropic") {
272
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [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"] }), props.stats.modelUsage.map((row) => {
273
+ const isSelected = props.selectedModelId === row.modelId;
274
+ if (row.totals.tokenBreakdown.schema !== "anthropic") {
275
+ return null;
276
+ }
277
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [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), ANTHROPIC_MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsageUsd(row.totals), ANTHROPIC_MODEL_USAGE_COLUMNS.value)] }, row.modelId));
278
+ }), _jsxs(Text, { color: "cyan", children: [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)] })] }));
279
+ }
280
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [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"] }), props.stats.modelUsage.map((row) => {
161
281
  const isSelected = props.selectedModelId === row.modelId;
162
- 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(formatInteger(row.totals.cachedInputTokens), MODEL_USAGE_COLUMNS.cached), " ", pad(formatInteger(row.totals.nonCachedInputTokens), MODEL_USAGE_COLUMNS.nonCached), " ", pad(formatInteger(row.totals.outputTokens), MODEL_USAGE_COLUMNS.output), " ", pad(formatCredits(row.totals.estimatedCredits), MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsd(row.totals.estimatedCredits * CODEX_CREDIT_COST_USD), MODEL_USAGE_COLUMNS.value)] }, row.modelId));
163
- }), _jsxs(Text, { color: "cyan", children: [pad("TOTAL", MODEL_USAGE_COLUMNS.model), " ", pad(formatInteger(totals.inputTokens), MODEL_USAGE_COLUMNS.input), " ", pad(formatInteger(totals.cachedInputTokens), MODEL_USAGE_COLUMNS.cached), " ", pad(formatInteger(totals.nonCachedInputTokens), MODEL_USAGE_COLUMNS.nonCached), " ", pad(formatInteger(totals.outputTokens), MODEL_USAGE_COLUMNS.output), " ", pad(formatCredits(totals.estimatedCredits), MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD), MODEL_USAGE_COLUMNS.value)] })] }));
282
+ if (row.totals.tokenBreakdown.schema !== "openai") {
283
+ return null;
284
+ }
285
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [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), OPENAI_MODEL_USAGE_COLUMNS.credits), " ", pad(formatUsageUsd(row.totals), OPENAI_MODEL_USAGE_COLUMNS.value)] }, row.modelId));
286
+ }), _jsxs(Text, { color: "cyan", children: [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)] })] }));
287
+ }
288
+ function DayToDayPanel(props) {
289
+ if (props.stats.dayUsage.length === 0) {
290
+ return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
291
+ }
292
+ const totals = props.stats.summary.totals;
293
+ if (totals.tokenBreakdown.schema === "anthropic") {
294
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [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"] }), props.stats.dayUsage.map((row) => {
295
+ const isSelected = props.selectedDayKey === row.dayKey;
296
+ if (row.totals.tokenBreakdown.schema !== "anthropic") {
297
+ return null;
298
+ }
299
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [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)] }, row.dayKey));
300
+ })] }));
301
+ }
302
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: [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"] }), props.stats.dayUsage.map((row) => {
303
+ const isSelected = props.selectedDayKey === row.dayKey;
304
+ if (row.totals.tokenBreakdown.schema !== "openai") {
305
+ return null;
306
+ }
307
+ return (_jsxs(Text, { inverse: isSelected, color: isSelected ? "cyan" : undefined, children: [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)] }, row.dayKey));
308
+ })] }));
164
309
  }
165
310
  function SelectionDetailsPanel(props) {
166
311
  if (props.providerState.status !== "ready") {
@@ -170,6 +315,10 @@ function SelectionDetailsPanel(props) {
170
315
  const row = props.selectedLimitRow;
171
316
  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 })] }));
172
317
  }
318
+ if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
319
+ const row = props.selectedDayRow;
320
+ return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Day details" }), _jsxs(Text, { children: ["day: ", formatUtcDay(row.dayKey), " events: ", formatInteger(row.totals.eventCount), " models: ", formatInteger(row.distinctModels.length), " plans: ", formatInteger(row.distinctPlanTypes.length)] }), _jsxs(Text, { children: ["range: ", formatEventRange(row.firstEventUtcIso, row.lastEventUtcIso)] }), _jsxs(Text, { children: ["models: ", row.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", row.distinctPlanTypes.join(", ") || "none"] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
321
+ }
173
322
  if (props.tabId === "usage-by-model" && props.selectedModelRow) {
174
323
  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 })] }));
175
324
  }
@@ -177,19 +326,32 @@ function SelectionDetailsPanel(props) {
177
326
  }
178
327
  function UsageTotalsDetails(props) {
179
328
  const { totals } = props;
180
- const inputPerOutput = formatInputPerOutput(totals);
181
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Total credits burned: ", formatCredits(totals.estimatedCredits)] }), _jsxs(Text, { children: ["Credits Value (@ $0.01/credit): ", formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD)] }), _jsxs(Text, { children: ["IpO: ", inputPerOutput.cached, ":", inputPerOutput.nonCached, ":", inputPerOutput.output] })] }));
329
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(UsageBreakdownLines, { totals: totals }), _jsxs(Text, { children: ["Total credits burned: ", formatUsageCredits(totals)] }), _jsxs(Text, { children: ["Credits Value (@ $0.01/credit): ", formatUsageUsd(totals)] }), _jsxs(Text, { children: ["IpO: ", formatInputPerOutput(totals)] })] }));
182
330
  }
183
331
  function formatInteger(value) {
184
332
  return Math.round(value).toLocaleString("en-US");
185
333
  }
186
334
  function formatCredits(value) {
335
+ if (value > 0 && value < 0.01) {
336
+ return "<0.01";
337
+ }
187
338
  return value.toLocaleString("en-US", {
188
339
  minimumFractionDigits: 2,
189
340
  maximumFractionDigits: 2
190
341
  });
191
342
  }
343
+ function formatUsageCredits(totals) {
344
+ return totals.estimatedCreditsStatus === "unavailable" ? "unknown" : formatCredits(totals.estimatedCredits);
345
+ }
346
+ function formatUsageUsd(totals) {
347
+ return totals.estimatedCreditsStatus === "unavailable"
348
+ ? "unknown"
349
+ : formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD);
350
+ }
192
351
  function formatUsd(value) {
352
+ if (value > 0 && value < 0.0001) {
353
+ return "<$0.0001";
354
+ }
193
355
  return value.toLocaleString("en-US", {
194
356
  currency: "USD",
195
357
  style: "currency",
@@ -217,18 +379,52 @@ function formatLocalDateTime(value) {
217
379
  const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
218
380
  return `${lookup.day} ${lookup.month} ${lookup.year} ${lookup.hour}:${lookup.minute}`;
219
381
  }
382
+ function formatUtcDay(value) {
383
+ if (value === "unknown") {
384
+ return "unknown";
385
+ }
386
+ const parts = new Intl.DateTimeFormat("en-US", {
387
+ month: "short",
388
+ day: "2-digit",
389
+ year: "2-digit",
390
+ timeZone: "UTC"
391
+ }).formatToParts(new Date(`${value}T00:00:00.000Z`));
392
+ const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
393
+ return `${lookup.day} ${lookup.month} ${lookup.year}`;
394
+ }
395
+ function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
396
+ if (!firstEventUtcIso || !lastEventUtcIso) {
397
+ return "unknown";
398
+ }
399
+ return `${formatLocalDateTime(firstEventUtcIso)} -> ${formatLocalDateTime(lastEventUtcIso)}`;
400
+ }
220
401
  function pad(value, length) {
221
402
  return value.length >= length ? value.slice(0, length) : value.padEnd(length);
222
403
  }
404
+ function UsageBreakdownLines(props) {
405
+ const { totals } = props;
406
+ if (totals.tokenBreakdown.schema === "anthropic") {
407
+ 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)] })] }));
408
+ }
409
+ 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)] })] }));
410
+ }
411
+ function formatOpenAiTokens(totals, kind) {
412
+ if (totals.tokenBreakdown.schema !== "openai" || totals.cacheStatus === "unavailable") {
413
+ return "unknown";
414
+ }
415
+ return formatInteger(kind === "non-cached" ? totals.tokenBreakdown.nonCachedInputTokens : totals.tokenBreakdown.cachedInputTokens);
416
+ }
223
417
  function formatInputPerOutput(totals) {
224
418
  if (totals.outputTokens <= 0) {
225
- return { cached: "0", nonCached: "0", output: "0" };
419
+ return totals.tokenBreakdown.schema === "anthropic" ? "input:cacheW5m:cacheW1h:cacheRead:output = 0:0:0:0:0" : "uncached:cached:output = 0:0:0";
226
420
  }
227
- return {
228
- cached: formatInteger(Math.round(totals.cachedInputTokens / totals.outputTokens)),
229
- nonCached: formatInteger(Math.round(totals.nonCachedInputTokens / totals.outputTokens)),
230
- output: "1"
231
- };
421
+ if (totals.tokenBreakdown.schema === "anthropic") {
422
+ 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`;
423
+ }
424
+ if (totals.cacheStatus === "unavailable") {
425
+ return "uncached:cached:output = unknown:unknown:1";
426
+ }
427
+ return `uncached:cached:output = ${formatInteger(Math.round(totals.tokenBreakdown.nonCachedInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.tokenBreakdown.cachedInputTokens / totals.outputTokens))}:1`;
232
428
  }
233
429
  function clampSelectionIndex(value, rowCount) {
234
430
  if (rowCount === 0) {
@@ -236,6 +432,20 @@ function clampSelectionIndex(value, rowCount) {
236
432
  }
237
433
  return Math.max(0, Math.min(value, rowCount - 1));
238
434
  }
435
+ function sortProviderStatesByUsage(states) {
436
+ return states
437
+ .map((state, index) => ({ state, index }))
438
+ .sort((left, right) => providerUsageScore(right.state) - providerUsageScore(left.state) ||
439
+ left.index - right.index)
440
+ .map((entry) => entry.state);
441
+ }
442
+ function providerUsageScore(state) {
443
+ if (state.status !== "ready") {
444
+ return 0;
445
+ }
446
+ const totals = state.stats.summary.totals;
447
+ return totals.inputTotalTokens + totals.outputTokens;
448
+ }
239
449
  function getLimitRows(providerState) {
240
450
  if (providerState.status !== "ready") {
241
451
  return [];
@@ -248,10 +458,21 @@ function getModelRows(providerState) {
248
458
  }
249
459
  return providerState.stats.modelUsage;
250
460
  }
461
+ function getDayRows(providerState) {
462
+ if (providerState.status !== "ready") {
463
+ return [];
464
+ }
465
+ return providerState.stats.dayUsage;
466
+ }
251
467
  function getLimitRowKey(row) {
252
468
  return `${row.scope}-${row.planType}-${row.limitId}-${row.startTimeUtcIso}-${row.endTimeUtcIso}`;
253
469
  }
254
- export function main() {
255
- render(_jsx(App, {}));
470
+ function parseStatsOptions(argv) {
471
+ return {
472
+ verbose: argv.includes("-v") || argv.includes("--verbose")
473
+ };
474
+ }
475
+ export function main(argv = process.argv.slice(2)) {
476
+ render(_jsx(App, { statsOptions: parseStatsOptions(argv) }));
256
477
  }
257
478
  main();