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 +35 -9
- package/ink-app/dist/index.js +253 -32
- package/ink-app/dist/providers/claude.js +145 -44
- package/ink-app/dist/providers/codex.js +24 -15
- package/ink-app/dist/providers/contract.js +54 -9
- package/ink-app/dist/providers/copilot.js +388 -0
- package/ink-app/dist/providers/daily.js +64 -0
- package/ink-app/dist/providers/index.js +3 -1
- package/ink-app/dist/providers/limits.js +5 -5
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
|
-
# letmecode
|
|
1
|
+
# letmecode - Discover your detailed agent ussage Codex | Claude
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Local development
|
|
3
|
+
Ussage:
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
|
-
|
|
9
|
-
pnpm start
|
|
6
|
+
npx -y letmecode
|
|
10
7
|
```
|
|
11
8
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
pnpm install
|
|
43
|
+
pnpm start
|
|
18
44
|
```
|
package/ink-app/dist/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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 (
|
|
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 (
|
|
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 ((
|
|
96
|
-
|
|
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 ((
|
|
100
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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();
|