letmecode 0.1.4 → 0.1.6
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 +1 -0
- package/ink-app/dist/index.js +263 -133
- package/ink-app/dist/providers/antigravity.js +290 -0
- package/ink-app/dist/providers/claude.js +539 -56
- package/ink-app/dist/providers/codex.js +111 -18
- package/ink-app/dist/providers/contract.js +14 -46
- package/ink-app/dist/providers/copilot.js +31 -46
- package/ink-app/dist/providers/index.js +14 -1
- package/ink-app/dist/providers/limits.js +1 -1
- package/ink-app/dist/providers/pricing.js +18 -0
- package/ink-app/dist/reporting.js +115 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ npx -y letmecode
|
|
|
20
20
|
| `1` | Select the Copilot VS Code setup action |
|
|
21
21
|
| `h` / `l` | Select a Copilot setup action |
|
|
22
22
|
| `q` or `Esc` | Quit |
|
|
23
|
+
| Mouse click | Click a provider tab or a section tab to switch to it. Hold `Shift` while dragging to select/copy text anywhere else. |
|
|
23
24
|
|
|
24
25
|
## Copilot
|
|
25
26
|
|
package/ink-app/dist/index.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { Box, Text, measureElement, useApp, useInput, useStdout, render } from "ink";
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, Text, measureElement, useApp, useInput, useStdin, useStdout, render } from "ink";
|
|
4
4
|
import { configureCopilotVsCodeLogging, createProviders } from "./providers/index.js";
|
|
5
|
+
import { reportAnonymousUsage } from "./reporting.js";
|
|
6
|
+
const ESC = String.fromCharCode(0x1b);
|
|
7
|
+
// Normal mouse tracking (button press/release only) + SGR extended coordinates.
|
|
8
|
+
// This makes the tabs clickable while leaving Shift+drag native text selection
|
|
9
|
+
// available everywhere else, exactly like Midnight Commander.
|
|
10
|
+
const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
|
|
11
|
+
const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
|
|
12
|
+
// SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
|
|
13
|
+
const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
|
|
5
14
|
const VERTICAL_TABS = [
|
|
6
15
|
{ id: "limit-windows", label: "Limits" },
|
|
7
16
|
{ id: "summary", label: "Summary" },
|
|
@@ -17,39 +26,26 @@ const LIMIT_WINDOW_COLUMNS = {
|
|
|
17
26
|
date: 17,
|
|
18
27
|
value: 10
|
|
19
28
|
};
|
|
20
|
-
const
|
|
29
|
+
const MODEL_USAGE_COLUMNS = {
|
|
21
30
|
model: 17,
|
|
22
|
-
input:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
value: 12
|
|
27
|
-
};
|
|
28
|
-
const ANTHROPIC_MODEL_USAGE_COLUMNS = {
|
|
29
|
-
model: 17,
|
|
30
|
-
input: 10,
|
|
31
|
+
input: 11,
|
|
32
|
+
output: 10,
|
|
33
|
+
cacheRead: 11,
|
|
34
|
+
cacheWrite: 11,
|
|
31
35
|
cacheWrite5m: 10,
|
|
32
36
|
cacheWrite1h: 10,
|
|
33
|
-
cacheRead: 10,
|
|
34
|
-
output: 10,
|
|
35
37
|
credits: 12,
|
|
36
38
|
value: 12
|
|
37
39
|
};
|
|
38
|
-
const
|
|
40
|
+
const DAY_USAGE_COLUMNS = {
|
|
39
41
|
day: 11,
|
|
40
42
|
events: 6,
|
|
41
43
|
input: 11,
|
|
42
44
|
output: 10,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const ANTHROPIC_DAY_USAGE_COLUMNS = {
|
|
46
|
-
day: 11,
|
|
47
|
-
events: 6,
|
|
48
|
-
input: 10,
|
|
45
|
+
cacheRead: 11,
|
|
46
|
+
cacheWrite: 11,
|
|
49
47
|
cacheWrite5m: 10,
|
|
50
48
|
cacheWrite1h: 10,
|
|
51
|
-
cacheRead: 10,
|
|
52
|
-
output: 10,
|
|
53
49
|
value: 10
|
|
54
50
|
};
|
|
55
51
|
const COPILOT_ACTIONS = [
|
|
@@ -63,6 +59,7 @@ function App(props) {
|
|
|
63
59
|
const { exit } = useApp();
|
|
64
60
|
const viewportHeight = useViewportHeight();
|
|
65
61
|
const { ref: contentPanelRef, height: contentPanelHeight } = useMeasuredElementSize();
|
|
62
|
+
const { getRegionRef, resolveClick } = useClickRegions();
|
|
66
63
|
const providers = React.useState(() => createProviders())[0];
|
|
67
64
|
const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
|
|
68
65
|
const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
|
|
@@ -73,6 +70,7 @@ function App(props) {
|
|
|
73
70
|
const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
|
|
74
71
|
const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
|
|
75
72
|
const [copilotActionMessage, setCopilotActionMessage] = useState();
|
|
73
|
+
const hasReportedAnonymousUsageRef = useRef(false);
|
|
76
74
|
const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
|
|
77
75
|
const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
|
|
78
76
|
const selectedProvider = sortedProviderStates[selectedProviderIndex];
|
|
@@ -123,7 +121,38 @@ function App(props) {
|
|
|
123
121
|
}
|
|
124
122
|
setSelectedProviderId(topProvider.provider.id);
|
|
125
123
|
}, [hasUserSelectedProvider, providerStates, sortedProviderStates]);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (hasReportedAnonymousUsageRef.current || providerStates.some((state) => state.status === "loading")) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
hasReportedAnonymousUsageRef.current = true;
|
|
129
|
+
const readyStats = providerStates
|
|
130
|
+
.filter((state) => state.status === "ready")
|
|
131
|
+
.map((state) => state.stats);
|
|
132
|
+
void reportAnonymousUsage(readyStats).catch(() => {
|
|
133
|
+
// Anonymous usage reporting is best-effort and must never disturb the TUI.
|
|
134
|
+
});
|
|
135
|
+
}, [providerStates]);
|
|
136
|
+
useMouseClick((click) => {
|
|
137
|
+
const regionId = resolveClick(click);
|
|
138
|
+
if (!regionId) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (regionId.startsWith("provider:")) {
|
|
142
|
+
setSelectedProviderId(regionId.slice("provider:".length));
|
|
143
|
+
setHasUserSelectedProvider(true);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (regionId.startsWith("vtab:")) {
|
|
147
|
+
setSelectedVerticalTabIndex(Number(regionId.slice("vtab:".length)));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
126
150
|
useInput((input, key) => {
|
|
151
|
+
// Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
|
|
152
|
+
// Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
|
|
153
|
+
if (input.startsWith("[<")) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
127
156
|
if (input === "q" || key.escape) {
|
|
128
157
|
exit();
|
|
129
158
|
return;
|
|
@@ -191,7 +220,7 @@ function App(props) {
|
|
|
191
220
|
setSelectedVerticalTabIndex((current) => (current - 1 + VERTICAL_TABS.length) % VERTICAL_TABS.length);
|
|
192
221
|
}
|
|
193
222
|
});
|
|
194
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", 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, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexDirection: "column", width: VERTICAL_TAB_WIDTH, marginRight: 2, overflow: "hidden", children: VERTICAL_TABS.map((tab, index) => (_jsx(VerticalTab, { label: tab.label, active: index === selectedVerticalTabIndex }, tab.id))) }), _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) })] }), _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", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] })] }));
|
|
223
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", 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, click tabs to switch, 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, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id))) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexDirection: "column", width: VERTICAL_TAB_WIDTH, marginRight: 2, overflow: "hidden", children: VERTICAL_TABS.map((tab, index) => (_jsx(VerticalTab, { label: tab.label, active: index === selectedVerticalTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id))) }), _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedVerticalTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) })] }), _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", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] })] }));
|
|
195
224
|
}
|
|
196
225
|
function CopilotActionsPanel(props) {
|
|
197
226
|
if (props.providerState.provider.id !== "copilot") {
|
|
@@ -228,14 +257,15 @@ function formatCopilotLoggingResult(result) {
|
|
|
228
257
|
function ProviderTab(props) {
|
|
229
258
|
const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
|
|
230
259
|
const tabLabel = props.active ? ` ${props.label} ` : `[${props.label}]`;
|
|
231
|
-
return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
|
|
260
|
+
return (_jsx(Box, { marginRight: 1, ref: props.regionRef, children: _jsx(Text, { inverse: props.active, color: statusColor, children: tabLabel }) }));
|
|
232
261
|
}
|
|
233
262
|
function VerticalTab(props) {
|
|
234
|
-
return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
|
|
263
|
+
return (_jsx(Box, { width: VERTICAL_TAB_WIDTH, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", inverse: props.active, children: props.active ? ` ${props.label} ` : ` ${props.label}` }) }));
|
|
235
264
|
}
|
|
236
265
|
function SummaryPanel(props) {
|
|
237
266
|
const { summary } = props.stats;
|
|
238
|
-
|
|
267
|
+
const isCodexProvider = props.stats.providerId === "codex";
|
|
268
|
+
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 }), isCodexProvider ? (_jsxs(Text, { children: ["Burned tokens for: ", formatUsageUsd(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"] })] }));
|
|
239
269
|
}
|
|
240
270
|
function ContentPanel(props) {
|
|
241
271
|
if (props.providerState.status === "loading") {
|
|
@@ -256,10 +286,13 @@ function ContentPanel(props) {
|
|
|
256
286
|
return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
|
|
257
287
|
}
|
|
258
288
|
function LimitWindowsPanel(props) {
|
|
289
|
+
const hasPrimaryWindows = props.stats.primaryLimitWindows.length > 0;
|
|
259
290
|
const bodyLines = [
|
|
260
291
|
{ key: "primary-title", text: "Primary Limit Windows", bold: true },
|
|
261
292
|
...buildLimitWindowSectionLines("primary", props.stats.primaryLimitWindows, props.selectedRowKey),
|
|
262
|
-
|
|
293
|
+
...(hasPrimaryWindows
|
|
294
|
+
? [{ key: "section-separator", text: buildLimitSectionSeparatorLine(), color: "gray" }]
|
|
295
|
+
: [{ key: "section-gap", text: "" }]),
|
|
263
296
|
{ key: "secondary-title", text: "Secondary Limit Windows", bold: true },
|
|
264
297
|
...buildLimitWindowSectionLines("secondary", props.stats.secondaryLimitWindows, props.selectedRowKey)
|
|
265
298
|
];
|
|
@@ -270,55 +303,24 @@ function UsageByModelPanel(props) {
|
|
|
270
303
|
return _jsx(Text, { color: "gray", children: "No model usage found." });
|
|
271
304
|
}
|
|
272
305
|
const totals = props.stats.summary.totals;
|
|
273
|
-
|
|
274
|
-
return (_jsx(ScrollableLineViewport, { headerLines: [
|
|
275
|
-
{
|
|
276
|
-
key: "anthropic-model-header",
|
|
277
|
-
text: `${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`,
|
|
278
|
-
color: "gray"
|
|
279
|
-
}
|
|
280
|
-
], bodyLines: props.stats.modelUsage.flatMap((row) => {
|
|
281
|
-
if (row.totals.tokenBreakdown.schema !== "anthropic") {
|
|
282
|
-
return [];
|
|
283
|
-
}
|
|
284
|
-
return [
|
|
285
|
-
{
|
|
286
|
-
key: `model-row:${row.modelId}`,
|
|
287
|
-
text: `${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, row.modelId), ANTHROPIC_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(row.totals, row.modelId), ANTHROPIC_MODEL_USAGE_COLUMNS.value)}`,
|
|
288
|
-
inverse: props.selectedModelId === row.modelId,
|
|
289
|
-
color: props.selectedModelId === row.modelId ? "cyan" : undefined
|
|
290
|
-
}
|
|
291
|
-
];
|
|
292
|
-
}), footerLines: [
|
|
293
|
-
{
|
|
294
|
-
key: "anthropic-model-total",
|
|
295
|
-
text: `${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)}`,
|
|
296
|
-
color: "cyan"
|
|
297
|
-
}
|
|
298
|
-
], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
|
|
299
|
-
}
|
|
306
|
+
const isCodexProvider = props.stats.providerId === "codex";
|
|
300
307
|
return (_jsx(ScrollableLineViewport, { headerLines: [
|
|
301
308
|
{
|
|
302
|
-
key: "
|
|
303
|
-
text:
|
|
309
|
+
key: "model-header",
|
|
310
|
+
text: isCodexProvider
|
|
311
|
+
? `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} value`
|
|
312
|
+
: `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad("credits", MODEL_USAGE_COLUMNS.credits)} value`,
|
|
304
313
|
color: "gray"
|
|
305
314
|
}
|
|
306
|
-
], bodyLines: props.stats.modelUsage.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
key: `model-row:${row.modelId}`,
|
|
313
|
-
text: `${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, row.modelId), OPENAI_MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(row.totals, row.modelId), OPENAI_MODEL_USAGE_COLUMNS.value)}`,
|
|
314
|
-
inverse: props.selectedModelId === row.modelId,
|
|
315
|
-
color: props.selectedModelId === row.modelId ? "cyan" : undefined
|
|
316
|
-
}
|
|
317
|
-
];
|
|
318
|
-
}), footerLines: [
|
|
315
|
+
], bodyLines: props.stats.modelUsage.map((row) => ({
|
|
316
|
+
key: `model-row:${row.modelId}`,
|
|
317
|
+
text: formatModelUsageRow(row.modelId, row.totals, isCodexProvider),
|
|
318
|
+
inverse: props.selectedModelId === row.modelId,
|
|
319
|
+
color: props.selectedModelId === row.modelId ? "cyan" : undefined
|
|
320
|
+
})), footerLines: [
|
|
319
321
|
{
|
|
320
|
-
key: "
|
|
321
|
-
text:
|
|
322
|
+
key: "model-total",
|
|
323
|
+
text: formatModelUsageRow("TOTAL", totals, isCodexProvider),
|
|
322
324
|
color: "cyan"
|
|
323
325
|
}
|
|
324
326
|
], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
|
|
@@ -328,46 +330,18 @@ function DayToDayPanel(props) {
|
|
|
328
330
|
return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
|
|
329
331
|
}
|
|
330
332
|
const totals = props.stats.summary.totals;
|
|
331
|
-
if (totals.tokenBreakdown.schema === "anthropic") {
|
|
332
|
-
return (_jsx(ScrollableLineViewport, { headerLines: [
|
|
333
|
-
{
|
|
334
|
-
key: "anthropic-day-header",
|
|
335
|
-
text: `${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`,
|
|
336
|
-
color: "gray"
|
|
337
|
-
}
|
|
338
|
-
], bodyLines: props.stats.dayUsage.flatMap((row) => {
|
|
339
|
-
if (row.totals.tokenBreakdown.schema !== "anthropic") {
|
|
340
|
-
return [];
|
|
341
|
-
}
|
|
342
|
-
return [
|
|
343
|
-
{
|
|
344
|
-
key: `day-row:${row.dayKey}`,
|
|
345
|
-
text: `${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)}`,
|
|
346
|
-
inverse: props.selectedDayKey === row.dayKey,
|
|
347
|
-
color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
|
|
348
|
-
}
|
|
349
|
-
];
|
|
350
|
-
}), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
|
|
351
|
-
}
|
|
352
333
|
return (_jsx(ScrollableLineViewport, { headerLines: [
|
|
353
334
|
{
|
|
354
|
-
key: "
|
|
355
|
-
text: `${pad("day",
|
|
335
|
+
key: "day-header",
|
|
336
|
+
text: `${pad("day", DAY_USAGE_COLUMNS.day)} ${pad("events", DAY_USAGE_COLUMNS.events)} ${pad("input", DAY_USAGE_COLUMNS.input)} ${pad("output", DAY_USAGE_COLUMNS.output)} ${pad("cacheRead", DAY_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", DAY_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", DAY_USAGE_COLUMNS.cacheWrite1h)} value`,
|
|
356
337
|
color: "gray"
|
|
357
338
|
}
|
|
358
|
-
], bodyLines: props.stats.dayUsage.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
key: `day-row:${row.dayKey}`,
|
|
365
|
-
text: `${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)}`,
|
|
366
|
-
inverse: props.selectedDayKey === row.dayKey,
|
|
367
|
-
color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
|
|
368
|
-
}
|
|
369
|
-
];
|
|
370
|
-
}), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
|
|
339
|
+
], bodyLines: props.stats.dayUsage.map((row) => ({
|
|
340
|
+
key: `day-row:${row.dayKey}`,
|
|
341
|
+
text: `${pad(formatUtcDay(row.dayKey), DAY_USAGE_COLUMNS.day)} ${pad(formatCompactTokenCount(row.totals.eventCount), DAY_USAGE_COLUMNS.events)} ${pad(formatCompactTokenCount(row.totals.inputTokens), DAY_USAGE_COLUMNS.input)} ${pad(formatCompactTokenCount(row.totals.outputTokens), DAY_USAGE_COLUMNS.output)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens), DAY_USAGE_COLUMNS.cacheRead)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens), DAY_USAGE_COLUMNS.cacheWrite)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite5mInputTokens), DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite1hInputTokens), DAY_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatUsageUsd(row.totals), DAY_USAGE_COLUMNS.value)}`,
|
|
342
|
+
inverse: props.selectedDayKey === row.dayKey,
|
|
343
|
+
color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
|
|
344
|
+
})), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
|
|
371
345
|
}
|
|
372
346
|
function ScrollableLineViewport(props) {
|
|
373
347
|
const headerLines = props.headerLines ?? [];
|
|
@@ -400,7 +374,7 @@ function buildLimitWindowSectionLines(scope, windows, selectedRowKey) {
|
|
|
400
374
|
...windows.map((window) => {
|
|
401
375
|
const lineKey = getLimitRowKey(window);
|
|
402
376
|
const windowLabel = formatWindowMinutes(window.windowMinutes);
|
|
403
|
-
const usedLabel =
|
|
377
|
+
const usedLabel = formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent);
|
|
404
378
|
const isSelected = selectedRowKey === lineKey;
|
|
405
379
|
return {
|
|
406
380
|
key: `limit-row:${lineKey}`,
|
|
@@ -417,7 +391,7 @@ function SelectionDetailsPanel(props) {
|
|
|
417
391
|
}
|
|
418
392
|
if (props.tabId === "limit-windows" && props.selectedLimitRow) {
|
|
419
393
|
const row = props.selectedLimitRow;
|
|
420
|
-
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,
|
|
394
|
+
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: ", formatUsedPercentRange(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 })] }));
|
|
421
395
|
}
|
|
422
396
|
if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
|
|
423
397
|
const row = props.selectedDayRow;
|
|
@@ -435,6 +409,26 @@ function UsageTotalsDetails(props) {
|
|
|
435
409
|
function formatInteger(value) {
|
|
436
410
|
return Math.round(value).toLocaleString("en-US");
|
|
437
411
|
}
|
|
412
|
+
function formatCompactTokenCount(value) {
|
|
413
|
+
const roundedValue = Math.round(value);
|
|
414
|
+
if (roundedValue < 1000) {
|
|
415
|
+
return formatInteger(roundedValue);
|
|
416
|
+
}
|
|
417
|
+
if (roundedValue < 100000) {
|
|
418
|
+
return `${formatCompactNumber(roundedValue / 1000)}K`;
|
|
419
|
+
}
|
|
420
|
+
if (roundedValue < 1000000) {
|
|
421
|
+
return `${formatInteger(Math.round(roundedValue / 1000))}K`;
|
|
422
|
+
}
|
|
423
|
+
return `${formatCompactNumber(roundedValue / 1000000)}M`;
|
|
424
|
+
}
|
|
425
|
+
function formatCompactNumber(value) {
|
|
426
|
+
const maximumFractionDigits = value < 10 ? 2 : value < 100 ? 1 : 0;
|
|
427
|
+
return value.toLocaleString("en-US", {
|
|
428
|
+
maximumFractionDigits,
|
|
429
|
+
minimumFractionDigits: 0
|
|
430
|
+
});
|
|
431
|
+
}
|
|
438
432
|
function formatCredits(value) {
|
|
439
433
|
if (value > 0 && value < 0.01) {
|
|
440
434
|
return "<0.01";
|
|
@@ -448,30 +442,41 @@ function formatUsageCredits(totals, modelId) {
|
|
|
448
442
|
if (isInternalUsageModel(modelId)) {
|
|
449
443
|
return "N/A";
|
|
450
444
|
}
|
|
451
|
-
return totals.estimatedCreditsStatus === "unavailable" ? "
|
|
445
|
+
return totals.estimatedCreditsStatus === "unavailable" ? "-" : formatCredits(totals.estimatedCredits);
|
|
452
446
|
}
|
|
453
447
|
function formatUsageUsd(totals, modelId) {
|
|
454
448
|
if (isInternalUsageModel(modelId)) {
|
|
455
449
|
return "N/A";
|
|
456
450
|
}
|
|
457
451
|
return totals.estimatedCreditsStatus === "unavailable"
|
|
458
|
-
? "
|
|
452
|
+
? "-"
|
|
459
453
|
: formatUsd(totals.estimatedCredits * CODEX_CREDIT_COST_USD);
|
|
460
454
|
}
|
|
461
455
|
function isInternalUsageModel(modelId) {
|
|
462
456
|
return modelId === "codex-auto-review" || modelId === "<synthetic>";
|
|
463
457
|
}
|
|
464
458
|
function formatUsd(value) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
return value.toLocaleString("en-US", {
|
|
459
|
+
const roundedUpValue = value > 0 ? Math.ceil(value * 100) / 100 : value;
|
|
460
|
+
return roundedUpValue.toLocaleString("en-US", {
|
|
469
461
|
currency: "USD",
|
|
470
462
|
style: "currency",
|
|
471
463
|
minimumFractionDigits: 2,
|
|
472
|
-
maximumFractionDigits:
|
|
464
|
+
maximumFractionDigits: 2
|
|
473
465
|
});
|
|
474
466
|
}
|
|
467
|
+
function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
|
|
468
|
+
return minUsedPercent === maxUsedPercent
|
|
469
|
+
? `${minUsedPercent}%`
|
|
470
|
+
: `${minUsedPercent}%->${maxUsedPercent}%`;
|
|
471
|
+
}
|
|
472
|
+
function buildLimitSectionSeparatorLine() {
|
|
473
|
+
return "─".repeat(LIMIT_WINDOW_COLUMNS.plan +
|
|
474
|
+
LIMIT_WINDOW_COLUMNS.window +
|
|
475
|
+
LIMIT_WINDOW_COLUMNS.used +
|
|
476
|
+
LIMIT_WINDOW_COLUMNS.date * 2 +
|
|
477
|
+
LIMIT_WINDOW_COLUMNS.value +
|
|
478
|
+
5);
|
|
479
|
+
}
|
|
475
480
|
function formatWindowMinutes(value) {
|
|
476
481
|
const hours = value / 60;
|
|
477
482
|
if (hours >= 24) {
|
|
@@ -494,7 +499,7 @@ function formatLocalDateTime(value) {
|
|
|
494
499
|
}
|
|
495
500
|
function formatUtcDay(value) {
|
|
496
501
|
if (value === "unknown") {
|
|
497
|
-
return "
|
|
502
|
+
return "-";
|
|
498
503
|
}
|
|
499
504
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
500
505
|
month: "short",
|
|
@@ -507,37 +512,50 @@ function formatUtcDay(value) {
|
|
|
507
512
|
}
|
|
508
513
|
function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
|
|
509
514
|
if (!firstEventUtcIso || !lastEventUtcIso) {
|
|
510
|
-
return "
|
|
515
|
+
return "-";
|
|
511
516
|
}
|
|
512
517
|
return `${formatLocalDateTime(firstEventUtcIso)} -> ${formatLocalDateTime(lastEventUtcIso)}`;
|
|
513
518
|
}
|
|
514
519
|
function pad(value, length) {
|
|
515
520
|
return value.length >= length ? value.slice(0, length) : value.padEnd(length);
|
|
516
521
|
}
|
|
522
|
+
function formatModelUsageRow(modelId, totals, isCodexProvider) {
|
|
523
|
+
const displayModelId = modelId === "unknown" ? "-" : modelId;
|
|
524
|
+
const prefix = `${pad(displayModelId, MODEL_USAGE_COLUMNS.model)} ${pad(formatCompactTokenCount(totals.inputTokens), MODEL_USAGE_COLUMNS.input)} ${pad(formatCompactTokenCount(totals.outputTokens), MODEL_USAGE_COLUMNS.output)} ${pad(formatCompactCacheTokens(totals, totals.cacheReadInputTokens), MODEL_USAGE_COLUMNS.cacheRead)} ${pad(formatCompactCacheTokens(totals, totals.cacheWriteInputTokens), MODEL_USAGE_COLUMNS.cacheWrite)} ${pad(formatOptionalCompactTokens(totals.cacheWrite5mInputTokens), MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatOptionalCompactTokens(totals.cacheWrite1hInputTokens), MODEL_USAGE_COLUMNS.cacheWrite1h)}`;
|
|
525
|
+
return isCodexProvider
|
|
526
|
+
? `${prefix} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`
|
|
527
|
+
: `${prefix} ${pad(formatUsageCredits(totals, modelId), MODEL_USAGE_COLUMNS.credits)} ${pad(formatUsageUsd(totals, modelId), MODEL_USAGE_COLUMNS.value)}`;
|
|
528
|
+
}
|
|
517
529
|
function UsageBreakdownLines(props) {
|
|
518
530
|
const { totals } = props;
|
|
519
|
-
|
|
520
|
-
|
|
531
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["input: ", formatInteger(totals.inputTokens), " output: ", formatInteger(totals.outputTokens), " cacheRead: ", formatCacheTokens(totals, totals.cacheReadInputTokens)] }), _jsxs(Text, { children: ["cacheWrite: ", formatCacheTokens(totals, totals.cacheWriteInputTokens), " cacheW5m: ", formatOptionalTokens(totals.cacheWrite5mInputTokens), " cacheW1h: ", formatOptionalTokens(totals.cacheWrite1hInputTokens), " reasoning: ", formatInteger(totals.reasoningOutputTokens), " total: ", formatInteger(totals.totalTokens)] })] }));
|
|
532
|
+
}
|
|
533
|
+
function formatCacheTokens(totals, value) {
|
|
534
|
+
if (totals.cacheStatus === "unavailable") {
|
|
535
|
+
return "-";
|
|
521
536
|
}
|
|
522
|
-
return (
|
|
537
|
+
return formatOptionalTokens(value);
|
|
523
538
|
}
|
|
524
|
-
function
|
|
525
|
-
if (totals.
|
|
526
|
-
return "
|
|
539
|
+
function formatCompactCacheTokens(totals, value) {
|
|
540
|
+
if (totals.cacheStatus === "unavailable") {
|
|
541
|
+
return "-";
|
|
527
542
|
}
|
|
528
|
-
return
|
|
543
|
+
return formatOptionalCompactTokens(value);
|
|
544
|
+
}
|
|
545
|
+
function formatOptionalTokens(value) {
|
|
546
|
+
return value > 0 ? formatInteger(value) : "-";
|
|
547
|
+
}
|
|
548
|
+
function formatOptionalCompactTokens(value) {
|
|
549
|
+
return value > 0 ? formatCompactTokenCount(value) : "-";
|
|
529
550
|
}
|
|
530
551
|
function formatInputPerOutput(totals) {
|
|
531
552
|
if (totals.outputTokens <= 0) {
|
|
532
|
-
return
|
|
533
|
-
}
|
|
534
|
-
if (totals.tokenBreakdown.schema === "anthropic") {
|
|
535
|
-
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`;
|
|
553
|
+
return "input:cacheRead:cacheWrite:output = 0:0:0:0";
|
|
536
554
|
}
|
|
537
555
|
if (totals.cacheStatus === "unavailable") {
|
|
538
|
-
return
|
|
556
|
+
return `input:cacheRead:cacheWrite:output = ${formatInteger(Math.round(totals.inputTokens / totals.outputTokens))}:-:-:1`;
|
|
539
557
|
}
|
|
540
|
-
return `
|
|
558
|
+
return `input:cacheRead:cacheWrite:output = ${formatInteger(Math.round(totals.inputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.cacheReadInputTokens / totals.outputTokens))}:${formatInteger(Math.round(totals.cacheWriteInputTokens / totals.outputTokens))}:1`;
|
|
541
559
|
}
|
|
542
560
|
function useMeasuredElementSize() {
|
|
543
561
|
const ref = useRef(null);
|
|
@@ -555,6 +573,101 @@ function useMeasuredElementSize() {
|
|
|
555
573
|
height: size.height
|
|
556
574
|
};
|
|
557
575
|
}
|
|
576
|
+
// Walks the Yoga layout tree to get a node's absolute position/size (in terminal
|
|
577
|
+
// cells). Reading it live at click time keeps hit-testing correct across resizes,
|
|
578
|
+
// wrapping, and re-layouts without tracking coordinates by hand.
|
|
579
|
+
function getAbsoluteRect(node) {
|
|
580
|
+
const yogaNode = node?.yogaNode;
|
|
581
|
+
if (!node || !yogaNode) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
let left = 0;
|
|
585
|
+
let top = 0;
|
|
586
|
+
let current = node;
|
|
587
|
+
while (current?.yogaNode) {
|
|
588
|
+
left += current.yogaNode.getComputedLeft();
|
|
589
|
+
top += current.yogaNode.getComputedTop();
|
|
590
|
+
current = current.parentNode;
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
left,
|
|
594
|
+
top,
|
|
595
|
+
width: yogaNode.getComputedWidth(),
|
|
596
|
+
height: yogaNode.getComputedHeight()
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function parseMouseClicks(chunk) {
|
|
600
|
+
const clicks = [];
|
|
601
|
+
for (const match of chunk.matchAll(SGR_MOUSE_SEQUENCE)) {
|
|
602
|
+
const buttonCode = Number(match[1]);
|
|
603
|
+
const isPress = match[4] === "M";
|
|
604
|
+
// Only react to a plain left-button press (button 0, no modifier bits). Any
|
|
605
|
+
// press carrying Shift/Ctrl/Alt or motion/wheel bits is left alone so the
|
|
606
|
+
// terminal can still use it for native text selection.
|
|
607
|
+
if (!isPress || buttonCode !== 0) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
clicks.push({ column: Number(match[2]), row: Number(match[3]) });
|
|
611
|
+
}
|
|
612
|
+
return clicks;
|
|
613
|
+
}
|
|
614
|
+
// Tracks the on-screen rectangle of named clickable regions (the tabs) via Ink
|
|
615
|
+
// refs and resolves a click coordinate back to a region id.
|
|
616
|
+
function useClickRegions() {
|
|
617
|
+
const nodesRef = useRef(new Map());
|
|
618
|
+
const refCallbacksRef = useRef(new Map());
|
|
619
|
+
const getRegionRef = useCallback((id) => {
|
|
620
|
+
const cached = refCallbacksRef.current.get(id);
|
|
621
|
+
if (cached) {
|
|
622
|
+
return cached;
|
|
623
|
+
}
|
|
624
|
+
const callback = (node) => {
|
|
625
|
+
if (node) {
|
|
626
|
+
nodesRef.current.set(id, node);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
nodesRef.current.delete(id);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
refCallbacksRef.current.set(id, callback);
|
|
633
|
+
return callback;
|
|
634
|
+
}, []);
|
|
635
|
+
const resolveClick = useCallback((click) => {
|
|
636
|
+
// SGR mouse coordinates are 1-based; Ink layout coordinates are 0-based.
|
|
637
|
+
const column = click.column - 1;
|
|
638
|
+
const row = click.row - 1;
|
|
639
|
+
for (const [id, node] of nodesRef.current) {
|
|
640
|
+
const rect = getAbsoluteRect(node);
|
|
641
|
+
if (rect &&
|
|
642
|
+
column >= rect.left &&
|
|
643
|
+
column < rect.left + rect.width &&
|
|
644
|
+
row >= rect.top &&
|
|
645
|
+
row < rect.top + rect.height) {
|
|
646
|
+
return id;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return undefined;
|
|
650
|
+
}, []);
|
|
651
|
+
return { getRegionRef, resolveClick };
|
|
652
|
+
}
|
|
653
|
+
// Parses left-clicks out of Ink's raw input stream and forwards them to onClick.
|
|
654
|
+
// Keyboard handling stays in useInput; this only consumes mouse reports.
|
|
655
|
+
function useMouseClick(onClick) {
|
|
656
|
+
const { internal_eventEmitter } = useStdin();
|
|
657
|
+
const handlerRef = useRef(onClick);
|
|
658
|
+
handlerRef.current = onClick;
|
|
659
|
+
useEffect(() => {
|
|
660
|
+
const handleInput = (chunk) => {
|
|
661
|
+
for (const click of parseMouseClicks(chunk)) {
|
|
662
|
+
handlerRef.current(click);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
internal_eventEmitter.on("input", handleInput);
|
|
666
|
+
return () => {
|
|
667
|
+
internal_eventEmitter.off("input", handleInput);
|
|
668
|
+
};
|
|
669
|
+
}, [internal_eventEmitter]);
|
|
670
|
+
}
|
|
558
671
|
function useAutoScrollOffset(selectedIndex, rowCount, viewportSize) {
|
|
559
672
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
560
673
|
useEffect(() => {
|
|
@@ -633,7 +746,7 @@ function providerUsageScore(state) {
|
|
|
633
746
|
return 0;
|
|
634
747
|
}
|
|
635
748
|
const totals = state.stats.summary.totals;
|
|
636
|
-
return totals.
|
|
749
|
+
return totals.inputTokens + totals.outputTokens + totals.cacheReadInputTokens + totals.cacheWriteInputTokens;
|
|
637
750
|
}
|
|
638
751
|
function getLimitRows(providerState) {
|
|
639
752
|
if (providerState.status !== "ready") {
|
|
@@ -687,7 +800,9 @@ function resolveViewportHeight(rows) {
|
|
|
687
800
|
}
|
|
688
801
|
export function main(argv = process.argv.slice(2)) {
|
|
689
802
|
const restoreFullscreen = enterFullscreenMode(process.stdout);
|
|
803
|
+
const disableMouse = enableMouseReporting(process.stdout);
|
|
690
804
|
const exitHandler = () => {
|
|
805
|
+
disableMouse();
|
|
691
806
|
restoreFullscreen();
|
|
692
807
|
};
|
|
693
808
|
process.once("exit", exitHandler);
|
|
@@ -699,6 +814,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
699
814
|
void instance.waitUntilExit().finally(() => {
|
|
700
815
|
process.off("exit", exitHandler);
|
|
701
816
|
instance.cleanup();
|
|
817
|
+
disableMouse();
|
|
702
818
|
restoreFullscreen();
|
|
703
819
|
});
|
|
704
820
|
}
|
|
@@ -716,4 +832,18 @@ function enterFullscreenMode(stdout) {
|
|
|
716
832
|
stdout.write(EXIT_FULLSCREEN_MODE);
|
|
717
833
|
};
|
|
718
834
|
}
|
|
835
|
+
function enableMouseReporting(stdout) {
|
|
836
|
+
if (!stdout.isTTY) {
|
|
837
|
+
return () => { };
|
|
838
|
+
}
|
|
839
|
+
let disabled = false;
|
|
840
|
+
stdout.write(ENABLE_MOUSE_TRACKING);
|
|
841
|
+
return () => {
|
|
842
|
+
if (disabled) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
disabled = true;
|
|
846
|
+
stdout.write(DISABLE_MOUSE_TRACKING);
|
|
847
|
+
};
|
|
848
|
+
}
|
|
719
849
|
main();
|