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 +35 -9
- package/ink-app/dist/index.js +323 -35
- package/ink-app/dist/providers/claude.js +311 -0
- package/ink-app/dist/providers/codex.js +23 -109
- package/ink-app/dist/providers/contract.js +23 -0
- package/ink-app/dist/providers/copilot.js +380 -0
- package/ink-app/dist/providers/daily.js +64 -0
- package/ink-app/dist/providers/index.js +5 -1
- package/ink-app/dist/providers/limits.js +146 -0
- package/package.json +2 -1
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,22 +1,71 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
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: "
|
|
7
|
-
{ id: "
|
|
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
|
-
|
|
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 [
|
|
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 (
|
|
48
|
-
|
|
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 ((
|
|
52
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
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: [
|
|
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
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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();
|