letmecode 0.1.24 → 0.1.26
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/ink-app/dist/cli-options.js +6 -6
- package/ink-app/dist/index.js +148 -86
- package/ink-app/dist/providers/codex.js +46 -3
- package/ink-app/dist/providers/limits.js +55 -1
- package/package.json +13 -11
|
@@ -58,12 +58,12 @@ export function buildHelpText() {
|
|
|
58
58
|
" --no-usage Disable anonymous usage reporting",
|
|
59
59
|
"",
|
|
60
60
|
"Controls:",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
" 1,
|
|
61
|
+
" Up / Down Select Provider, View, or table rows",
|
|
62
|
+
" Left / Right Change the selected Provider or View",
|
|
63
|
+
" Left / Right Do nothing when table rows are selected",
|
|
64
|
+
" Tab / Shift+Tab Switch providers without leaving the table",
|
|
65
|
+
" [ / ] Switch views without leaving the table",
|
|
66
|
+
" 1, Enter Run Copilot setup actions",
|
|
67
67
|
" q or Esc Quit",
|
|
68
68
|
"",
|
|
69
69
|
"Trace logging:",
|
package/ink-app/dist/index.js
CHANGED
|
@@ -4,23 +4,25 @@ import { Box, Text, measureElement, useApp, useInput, useStdin, useStdout, rende
|
|
|
4
4
|
import { buildHelpText, buildProviderStatsOptions, parseCliOptions } from "./cli-options.js";
|
|
5
5
|
import { configureCopilotVsCodeLogging, createProviders } from "./providers/index.js";
|
|
6
6
|
import { reportAnonymousUsage } from "./reporting.js";
|
|
7
|
-
import { estimateLimitFullValue } from "./providers/limits.js";
|
|
7
|
+
import { estimateLimitFullValue, selectLatestActiveLimitWindows } from "./providers/limits.js";
|
|
8
8
|
const ESC = String.fromCharCode(0x1b);
|
|
9
9
|
// Normal mouse tracking (button press/release only) + SGR extended coordinates.
|
|
10
|
-
// This makes the
|
|
10
|
+
// This makes the controls clickable while leaving Shift+drag native text selection
|
|
11
11
|
// available everywhere else, exactly like Midnight Commander.
|
|
12
12
|
const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
|
|
13
13
|
const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
|
|
14
14
|
// SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
|
|
15
15
|
const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
|
|
16
|
-
const
|
|
16
|
+
const DETAIL_VIEWS = [
|
|
17
17
|
{ id: "limit-windows", label: "Limits" },
|
|
18
18
|
{ id: "summary", label: "Summary" },
|
|
19
19
|
{ id: "day-to-day-analyses", label: "Daily" },
|
|
20
20
|
{ id: "usage-by-model", label: "Models" }
|
|
21
21
|
];
|
|
22
|
+
const CONTROL_SECTIONS = ["provider", "view", "table"];
|
|
22
23
|
const CODEX_CREDIT_COST_USD = 0.01;
|
|
23
|
-
const
|
|
24
|
+
const ACTIVE_LIMIT_WINDOW_COLOR = "#FFA500";
|
|
25
|
+
const LIMIT_TABLE_HEADERS = ["Scope", "Plan", "Models", "Window", "Used", "Tokens", "Start", "End", "API eq.", "Full"];
|
|
24
26
|
const DAILY_TABLE_HEADERS = ["Day", "Ev", "Input", "Output", "C read", "C write", "API eq."];
|
|
25
27
|
const MODEL_TABLE_HEADERS = ["Model", "Input", "Output", "C read", "C write", "API eq."];
|
|
26
28
|
const COPILOT_ACTIONS = [
|
|
@@ -39,26 +41,29 @@ function App(props) {
|
|
|
39
41
|
const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
|
|
40
42
|
const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
|
|
41
43
|
const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
|
|
42
|
-
const [
|
|
43
|
-
const [
|
|
44
|
-
const [
|
|
45
|
-
const [
|
|
44
|
+
const [selectedDetailViewIndex, setSelectedDetailViewIndex] = useState(0);
|
|
45
|
+
const [selectedControlSectionIndex, setSelectedControlSectionIndex] = useState(0);
|
|
46
|
+
const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState();
|
|
47
|
+
const [selectedDayRowIndex, setSelectedDayRowIndex] = useState();
|
|
48
|
+
const [selectedModelRowIndex, setSelectedModelRowIndex] = useState();
|
|
46
49
|
const [selectedCopilotActionIndex, setSelectedCopilotActionIndex] = useState(0);
|
|
47
50
|
const [copilotActionMessage, setCopilotActionMessage] = useState();
|
|
48
51
|
const hasReportedAnonymousUsageRef = useRef(false);
|
|
49
52
|
const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
|
|
50
53
|
const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
|
|
51
54
|
const selectedProvider = sortedProviderStates[selectedProviderIndex];
|
|
52
|
-
const
|
|
55
|
+
const selectedDetailView = DETAIL_VIEWS[selectedDetailViewIndex];
|
|
56
|
+
const selectedControlSection = CONTROL_SECTIONS[selectedControlSectionIndex];
|
|
57
|
+
const isTableControlSelected = selectedControlSection === "table";
|
|
53
58
|
const limitRows = getLimitRows(selectedProvider);
|
|
54
59
|
const dayRows = getDayRows(selectedProvider);
|
|
55
60
|
const modelRows = getModelRows(selectedProvider);
|
|
56
|
-
const activeLimitRowIndex =
|
|
57
|
-
const activeDayRowIndex =
|
|
58
|
-
const activeModelRowIndex =
|
|
59
|
-
const selectedLimitRow = activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
|
|
60
|
-
const selectedDayRow = activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
|
|
61
|
-
const selectedModelRow = activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
|
|
61
|
+
const activeLimitRowIndex = clampOptionalSelectionIndex(selectedLimitRowIndex, limitRows.length);
|
|
62
|
+
const activeDayRowIndex = clampOptionalSelectionIndex(selectedDayRowIndex, dayRows.length);
|
|
63
|
+
const activeModelRowIndex = clampOptionalSelectionIndex(selectedModelRowIndex, modelRows.length);
|
|
64
|
+
const selectedLimitRow = isTableControlSelected && activeLimitRowIndex >= 0 ? limitRows[activeLimitRowIndex] : undefined;
|
|
65
|
+
const selectedDayRow = isTableControlSelected && activeDayRowIndex >= 0 ? dayRows[activeDayRowIndex] : undefined;
|
|
66
|
+
const selectedModelRow = isTableControlSelected && activeModelRowIndex >= 0 ? modelRows[activeModelRowIndex] : undefined;
|
|
62
67
|
useEffect(() => {
|
|
63
68
|
let cancelled = false;
|
|
64
69
|
for (const provider of providers) {
|
|
@@ -110,6 +115,11 @@ function App(props) {
|
|
|
110
115
|
// Anonymous usage reporting is best-effort and must never disturb the TUI.
|
|
111
116
|
});
|
|
112
117
|
}, [props.usageReportingEnabled, providerStates]);
|
|
118
|
+
const clearSelectedTableRows = useCallback(() => {
|
|
119
|
+
setSelectedLimitRowIndex(undefined);
|
|
120
|
+
setSelectedDayRowIndex(undefined);
|
|
121
|
+
setSelectedModelRowIndex(undefined);
|
|
122
|
+
}, []);
|
|
113
123
|
useMouseClick((click) => {
|
|
114
124
|
const regionId = resolveClick(click);
|
|
115
125
|
if (!regionId) {
|
|
@@ -118,23 +128,36 @@ function App(props) {
|
|
|
118
128
|
if (regionId.startsWith("provider:")) {
|
|
119
129
|
setSelectedProviderId(regionId.slice("provider:".length));
|
|
120
130
|
setHasUserSelectedProvider(true);
|
|
131
|
+
setSelectedControlSectionIndex(CONTROL_SECTIONS.indexOf("provider"));
|
|
132
|
+
clearSelectedTableRows();
|
|
121
133
|
return;
|
|
122
134
|
}
|
|
123
|
-
if (regionId.startsWith("
|
|
124
|
-
|
|
135
|
+
if (regionId.startsWith("view:")) {
|
|
136
|
+
setSelectedDetailViewIndex(Number(regionId.slice("view:".length)));
|
|
137
|
+
setSelectedControlSectionIndex(CONTROL_SECTIONS.indexOf("view"));
|
|
138
|
+
clearSelectedTableRows();
|
|
125
139
|
}
|
|
126
140
|
});
|
|
127
141
|
const moveSelectedTableRow = useCallback((delta) => {
|
|
128
|
-
if (
|
|
129
|
-
|
|
142
|
+
if (selectedDetailView.id === "limit-windows") {
|
|
143
|
+
if (limitRows.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex < 0 ? 0 : activeLimitRowIndex + delta, limitRows.length));
|
|
130
147
|
return;
|
|
131
148
|
}
|
|
132
|
-
if (
|
|
133
|
-
|
|
149
|
+
if (selectedDetailView.id === "usage-by-model") {
|
|
150
|
+
if (modelRows.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex < 0 ? 0 : activeModelRowIndex + delta, modelRows.length));
|
|
134
154
|
return;
|
|
135
155
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
156
|
+
if (selectedDetailView.id === "day-to-day-analyses") {
|
|
157
|
+
if (dayRows.length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex < 0 ? 0 : activeDayRowIndex + delta, dayRows.length));
|
|
138
161
|
}
|
|
139
162
|
}, [
|
|
140
163
|
activeDayRowIndex,
|
|
@@ -143,8 +166,59 @@ function App(props) {
|
|
|
143
166
|
dayRows.length,
|
|
144
167
|
limitRows.length,
|
|
145
168
|
modelRows.length,
|
|
146
|
-
|
|
169
|
+
selectedDetailView.id
|
|
147
170
|
]);
|
|
171
|
+
const canMoveTableRowUp = useCallback(() => {
|
|
172
|
+
if (selectedDetailView.id === "limit-windows") {
|
|
173
|
+
return activeLimitRowIndex > 0;
|
|
174
|
+
}
|
|
175
|
+
if (selectedDetailView.id === "usage-by-model") {
|
|
176
|
+
return activeModelRowIndex > 0;
|
|
177
|
+
}
|
|
178
|
+
if (selectedDetailView.id === "day-to-day-analyses") {
|
|
179
|
+
return activeDayRowIndex > 0;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}, [activeDayRowIndex, activeLimitRowIndex, activeModelRowIndex, selectedDetailView.id]);
|
|
183
|
+
const moveSelectionDown = useCallback(() => {
|
|
184
|
+
if (selectedControlSection === "table") {
|
|
185
|
+
moveSelectedTableRow(1);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setSelectedControlSectionIndex((current) => {
|
|
189
|
+
const next = Math.min(current + 1, CONTROL_SECTIONS.length - 1);
|
|
190
|
+
if (CONTROL_SECTIONS[next] === "table") {
|
|
191
|
+
moveSelectedTableRow(1);
|
|
192
|
+
}
|
|
193
|
+
return next;
|
|
194
|
+
});
|
|
195
|
+
}, [moveSelectedTableRow, selectedControlSection]);
|
|
196
|
+
const moveSelectionUp = useCallback(() => {
|
|
197
|
+
if (selectedControlSection === "table" && canMoveTableRowUp()) {
|
|
198
|
+
moveSelectedTableRow(-1);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (selectedControlSection === "table") {
|
|
202
|
+
clearSelectedTableRows();
|
|
203
|
+
}
|
|
204
|
+
setSelectedControlSectionIndex((current) => Math.max(current - 1, 0));
|
|
205
|
+
}, [canMoveTableRowUp, clearSelectedTableRows, moveSelectedTableRow, selectedControlSection]);
|
|
206
|
+
const changeSelectedProvider = useCallback((delta) => {
|
|
207
|
+
setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + delta + sortedProviderStates.length) % sortedProviderStates.length].provider.id);
|
|
208
|
+
setHasUserSelectedProvider(true);
|
|
209
|
+
}, [selectedProviderIndex, sortedProviderStates]);
|
|
210
|
+
const changeSelectedDetailView = useCallback((delta) => {
|
|
211
|
+
setSelectedDetailViewIndex((current) => (current + delta + DETAIL_VIEWS.length) % DETAIL_VIEWS.length);
|
|
212
|
+
}, []);
|
|
213
|
+
const changeSelectedSectionValue = useCallback((delta) => {
|
|
214
|
+
if (selectedControlSection === "provider") {
|
|
215
|
+
changeSelectedProvider(delta);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (selectedControlSection === "view") {
|
|
219
|
+
changeSelectedDetailView(delta);
|
|
220
|
+
}
|
|
221
|
+
}, [changeSelectedDetailView, changeSelectedProvider, selectedControlSection]);
|
|
148
222
|
useInput((input, key) => {
|
|
149
223
|
// Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
|
|
150
224
|
// Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
|
|
@@ -160,45 +234,42 @@ function App(props) {
|
|
|
160
234
|
return;
|
|
161
235
|
}
|
|
162
236
|
if (selectedProvider.provider.id === "copilot" && key.return) {
|
|
163
|
-
runCopilotAction(
|
|
237
|
+
runCopilotAction(setCopilotActionMessage);
|
|
164
238
|
return;
|
|
165
239
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
240
|
+
if (key.tab && !key.shift) {
|
|
241
|
+
changeSelectedProvider(1);
|
|
168
242
|
return;
|
|
169
243
|
}
|
|
170
|
-
if (
|
|
171
|
-
|
|
244
|
+
if (key.tab && key.shift) {
|
|
245
|
+
changeSelectedProvider(-1);
|
|
172
246
|
return;
|
|
173
247
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
248
|
+
if (input === "]") {
|
|
249
|
+
changeSelectedDetailView(1);
|
|
176
250
|
return;
|
|
177
251
|
}
|
|
178
|
-
if (
|
|
179
|
-
|
|
252
|
+
if (input === "[") {
|
|
253
|
+
changeSelectedDetailView(-1);
|
|
180
254
|
return;
|
|
181
255
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
setHasUserSelectedProvider(true);
|
|
256
|
+
if (key.rightArrow) {
|
|
257
|
+
changeSelectedSectionValue(1);
|
|
185
258
|
return;
|
|
186
259
|
}
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
.provider.id);
|
|
190
|
-
setHasUserSelectedProvider(true);
|
|
260
|
+
if (key.leftArrow) {
|
|
261
|
+
changeSelectedSectionValue(-1);
|
|
191
262
|
return;
|
|
192
263
|
}
|
|
193
|
-
if (key.downArrow
|
|
194
|
-
|
|
264
|
+
if (key.downArrow) {
|
|
265
|
+
moveSelectionDown();
|
|
195
266
|
return;
|
|
196
267
|
}
|
|
197
|
-
if (key.upArrow
|
|
198
|
-
|
|
268
|
+
if (key.upArrow) {
|
|
269
|
+
moveSelectionUp();
|
|
199
270
|
}
|
|
200
271
|
});
|
|
201
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "white", backgroundColor: "green", children: " beta " })] }), _jsxs(Box, { children: [_jsx(
|
|
272
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "white", backgroundColor: "green", children: " beta " })] }), _jsxs(Box, { children: [_jsx(ControlSectionLabel, { label: "Provider", active: selectedControlSection === "provider" }), sortedProviderStates.map((state) => (_jsx(ProviderOption, { 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, { children: [_jsx(ControlSectionLabel, { label: "View", active: selectedControlSection === "view" }), DETAIL_VIEWS.map((view, index) => (_jsx(ViewOption, { label: view.label, active: index === selectedDetailViewIndex, regionRef: getRegionRef(`view:${index}`) }, view.id)))] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, overflow: "hidden", children: _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, viewId: selectedDetailView.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, viewId: selectedDetailView.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, { 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] }), _jsx(Text, { color: "gray", children: "\u2191/\u2193 move \u00B7 \u2190/\u2192 selected \u00B7 Tab provider \u00B7 [/] view \u00B7 q quit" })] }));
|
|
202
273
|
}
|
|
203
274
|
function CopilotActionsPanel(props) {
|
|
204
275
|
if (props.providerState.provider.id !== "copilot") {
|
|
@@ -206,9 +277,9 @@ function CopilotActionsPanel(props) {
|
|
|
206
277
|
}
|
|
207
278
|
const hasNoUsage = props.providerState.status === "ready" && props.providerState.stats.summary.tokenEvents === 0;
|
|
208
279
|
const accentColor = hasNoUsage ? "red" : "cyan";
|
|
209
|
-
return (_jsxs(Box, { 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
|
|
280
|
+
return (_jsxs(Box, { 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 to select an action, enter to run selected." }), props.actionMessage ? _jsx(Text, { children: props.actionMessage }) : null] }));
|
|
210
281
|
}
|
|
211
|
-
function runCopilotAction(
|
|
282
|
+
function runCopilotAction(setCopilotActionMessage) {
|
|
212
283
|
setCopilotActionMessage("Updating VS Code settings...");
|
|
213
284
|
void configureCopilotVsCodeLogging()
|
|
214
285
|
.then((result) => {
|
|
@@ -232,14 +303,17 @@ function formatCopilotLoggingResult(result) {
|
|
|
232
303
|
`"github.copilot.chat.otel.outfile": "${result.outfile}"`
|
|
233
304
|
].join("\n");
|
|
234
305
|
}
|
|
235
|
-
function
|
|
306
|
+
function ProviderOption(props) {
|
|
236
307
|
const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
|
|
237
|
-
const
|
|
238
|
-
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children:
|
|
308
|
+
const optionLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
|
|
309
|
+
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: optionLabel }) }));
|
|
310
|
+
}
|
|
311
|
+
function ControlSectionLabel(props) {
|
|
312
|
+
return (_jsxs(Text, { color: props.active ? "cyan" : "gray", bold: props.active, children: [props.active ? "› " : " ", pad(props.label, 10)] }));
|
|
239
313
|
}
|
|
240
|
-
function
|
|
241
|
-
const
|
|
242
|
-
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children:
|
|
314
|
+
function ViewOption(props) {
|
|
315
|
+
const optionLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
|
|
316
|
+
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: optionLabel }) }));
|
|
243
317
|
}
|
|
244
318
|
function SummaryPanel(props) {
|
|
245
319
|
const { summary } = props.stats;
|
|
@@ -262,13 +336,13 @@ function ContentPanel(props) {
|
|
|
262
336
|
if (props.providerState.status === "error") {
|
|
263
337
|
return _jsxs(Text, { color: "red", children: ["Provider error: ", props.providerState.errorMessage] });
|
|
264
338
|
}
|
|
265
|
-
if (props.
|
|
339
|
+
if (props.viewId === "limit-windows") {
|
|
266
340
|
return (_jsx(LimitWindowsPanel, { stats: props.providerState.stats, selectedRowKey: props.selectedLimitRowKey, availableHeight: props.availableHeight }));
|
|
267
341
|
}
|
|
268
|
-
if (props.
|
|
342
|
+
if (props.viewId === "summary") {
|
|
269
343
|
return _jsx(SummaryPanel, { stats: props.providerState.stats });
|
|
270
344
|
}
|
|
271
|
-
if (props.
|
|
345
|
+
if (props.viewId === "day-to-day-analyses") {
|
|
272
346
|
return (_jsx(DayToDayPanel, { stats: props.providerState.stats, selectedDayKey: props.selectedDayKey, availableHeight: props.availableHeight }));
|
|
273
347
|
}
|
|
274
348
|
return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
|
|
@@ -357,7 +431,7 @@ function buildTextTableLines(options) {
|
|
|
357
431
|
key: row.key,
|
|
358
432
|
text: buildTableRow(table, row.cells),
|
|
359
433
|
inverse: isSelected,
|
|
360
|
-
color: isSelected ? "cyan" :
|
|
434
|
+
color: isSelected ? "cyan" : row.color
|
|
361
435
|
}];
|
|
362
436
|
if (options.separatorAfterRowKeys?.has(row.key)) {
|
|
363
437
|
lines.push({
|
|
@@ -392,8 +466,10 @@ function buildTextTableLines(options) {
|
|
|
392
466
|
};
|
|
393
467
|
}
|
|
394
468
|
function buildLimitWindowTableLines(stats, selectedRowKey) {
|
|
395
|
-
const
|
|
396
|
-
const
|
|
469
|
+
const allWindows = [...stats.primaryLimitWindows, ...stats.secondaryLimitWindows];
|
|
470
|
+
const activeWindows = selectLatestActiveLimitWindows(allWindows);
|
|
471
|
+
const primaryRows = stats.primaryLimitWindows.map((window) => buildLimitWindowTableRow(window, activeWindows.has(window)));
|
|
472
|
+
const secondaryRows = stats.secondaryLimitWindows.map((window) => buildLimitWindowTableRow(window, activeWindows.has(window)));
|
|
397
473
|
const separatorAfterRowKeys = primaryRows.length > 0 && secondaryRows.length > 0
|
|
398
474
|
? new Set([primaryRows[primaryRows.length - 1].key])
|
|
399
475
|
: undefined;
|
|
@@ -405,15 +481,17 @@ function buildLimitWindowTableLines(stats, selectedRowKey) {
|
|
|
405
481
|
separatorAfterRowKeys
|
|
406
482
|
});
|
|
407
483
|
}
|
|
408
|
-
function buildLimitWindowTableRow(window) {
|
|
484
|
+
function buildLimitWindowTableRow(window, isActive) {
|
|
409
485
|
return {
|
|
410
486
|
key: `limit-row:${getLimitRowKey(window)}`,
|
|
487
|
+
color: isActive ? ACTIVE_LIMIT_WINDOW_COLOR : undefined,
|
|
411
488
|
cells: [
|
|
412
489
|
window.scope,
|
|
413
490
|
window.planType,
|
|
414
491
|
formatLimitWindowModels(window),
|
|
415
492
|
formatCompactWindowMinutes(window.windowMinutes),
|
|
416
493
|
formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent),
|
|
494
|
+
formatCompactTokenCount(window.totals.totalTokens),
|
|
417
495
|
formatCompactLocalDateTime(window.startTimeUtcIso),
|
|
418
496
|
formatCompactLocalDateTime(window.endTimeUtcIso),
|
|
419
497
|
// Status-aware: shows "-" when the API-equivalent cost is unknown rather
|
|
@@ -463,15 +541,15 @@ function SelectionDetailsPanel(props) {
|
|
|
463
541
|
if (props.providerState.status !== "ready") {
|
|
464
542
|
return null;
|
|
465
543
|
}
|
|
466
|
-
if (props.
|
|
544
|
+
if (props.viewId === "limit-windows" && props.selectedLimitRow) {
|
|
467
545
|
const row = props.selectedLimitRow;
|
|
468
546
|
return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 25, children: [_jsx(DetailRow, { label: "Plan", value: row.planType }), _jsx(DetailRow, { label: "Models", value: formatLimitWindowModels(row) }), _jsx(DetailRow, { label: "Window", value: formatCompactWindowMinutes(row.windowMinutes) }), _jsx(DetailRow, { label: "Usage", value: formatUsedPercentRange(row.minUsedPercent, row.maxUsedPercent) }), _jsx(DetailRow, { label: "Events", value: formatInteger(row.eventCount) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailRow, { label: "Period", value: `${formatCompactLocalDateTime(row.startTimeUtcIso)} → ${formatCompactLocalDateTime(row.endTimeUtcIso)}` }), _jsx(DetailRow, { label: "Input", value: formatInteger(row.totals.inputTokens) }), _jsx(DetailRow, { label: "Cache read", value: formatCacheTokens(row.totals.cacheReadStatus, row.totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Cache write", value: formatCacheTokens(row.totals.cacheWriteStatus, row.totals.cacheWriteInputTokens) }), _jsx(DetailRow, { label: "Output", value: formatInteger(row.totals.outputTokens) }), _jsx(DetailRow, { label: "Total", value: formatInteger(row.totals.totalTokens) })] })] }), _jsx(DetailRow, { label: "API eq.", value: formatUsageUsd(row.totals), note: "API equivalent cost" }), _jsx(DetailRow, { label: "Full value", value: formatLimitFullValueRange(row.totals, row.maxUsedPercent), note: "API equivalent cost if 100% will be used" })] }));
|
|
469
547
|
}
|
|
470
|
-
if (props.
|
|
548
|
+
if (props.viewId === "day-to-day-analyses" && props.selectedDayRow) {
|
|
471
549
|
const row = props.selectedDayRow;
|
|
472
550
|
return (_jsxs(DetailsPanelFrame, { children: [_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 })] }));
|
|
473
551
|
}
|
|
474
|
-
if (props.
|
|
552
|
+
if (props.viewId === "usage-by-model" && props.selectedModelRow) {
|
|
475
553
|
return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Text, { children: ["model: ", props.selectedModelRow.modelId, " events: ", formatInteger(props.selectedModelRow.totals.eventCount)] }), _jsx(UsageTotalsDetails, { totals: props.selectedModelRow.totals, modelId: props.selectedModelRow.modelId })] }));
|
|
476
554
|
}
|
|
477
555
|
return null;
|
|
@@ -534,21 +612,6 @@ function formatCompactNumber(value) {
|
|
|
534
612
|
minimumFractionDigits: 0
|
|
535
613
|
});
|
|
536
614
|
}
|
|
537
|
-
function formatCredits(value) {
|
|
538
|
-
if (value > 0 && value < 0.01) {
|
|
539
|
-
return "<0.01";
|
|
540
|
-
}
|
|
541
|
-
return value.toLocaleString("en-US", {
|
|
542
|
-
minimumFractionDigits: 2,
|
|
543
|
-
maximumFractionDigits: 2
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
function formatUsageCredits(totals, modelId) {
|
|
547
|
-
if (isInternalUsageModel(modelId)) {
|
|
548
|
-
return "N/A";
|
|
549
|
-
}
|
|
550
|
-
return totals.estimatedCreditsStatus === "unavailable" ? "-" : formatCredits(totals.estimatedCredits);
|
|
551
|
-
}
|
|
552
615
|
function formatUsageUsd(totals, modelId) {
|
|
553
616
|
if (isInternalUsageModel(modelId)) {
|
|
554
617
|
return "N/A";
|
|
@@ -664,13 +727,6 @@ function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
|
|
|
664
727
|
? fmt(minUsedPercent)
|
|
665
728
|
: `${fmt(minUsedPercent)}–${fmt(maxUsedPercent)}`;
|
|
666
729
|
}
|
|
667
|
-
function formatWindowMinutes(value) {
|
|
668
|
-
const hours = value / 60;
|
|
669
|
-
if (hours >= 24) {
|
|
670
|
-
return `${(hours / 24).toFixed(2)}d`;
|
|
671
|
-
}
|
|
672
|
-
return `${hours.toFixed(2)}h`;
|
|
673
|
-
}
|
|
674
730
|
function formatCompactWindowMinutes(value) {
|
|
675
731
|
const hours = value / 60;
|
|
676
732
|
if (hours >= 24) {
|
|
@@ -843,7 +899,7 @@ function parseMouseClicks(chunk) {
|
|
|
843
899
|
}
|
|
844
900
|
return clicks;
|
|
845
901
|
}
|
|
846
|
-
// Tracks the on-screen rectangle of named clickable regions
|
|
902
|
+
// Tracks the on-screen rectangle of named clickable regions via Ink
|
|
847
903
|
// refs and resolves a click coordinate back to a region id.
|
|
848
904
|
function useClickRegions() {
|
|
849
905
|
const nodesRef = useRef(new Map());
|
|
@@ -966,6 +1022,12 @@ function clampSelectionIndex(value, rowCount) {
|
|
|
966
1022
|
}
|
|
967
1023
|
return Math.max(0, Math.min(value, rowCount - 1));
|
|
968
1024
|
}
|
|
1025
|
+
function clampOptionalSelectionIndex(value, rowCount) {
|
|
1026
|
+
if (value === undefined) {
|
|
1027
|
+
return -1;
|
|
1028
|
+
}
|
|
1029
|
+
return clampSelectionIndex(value, rowCount);
|
|
1030
|
+
}
|
|
969
1031
|
function sortProviderStatesByUsage(states) {
|
|
970
1032
|
return states
|
|
971
1033
|
.map((state, index) => ({ state, index }))
|
|
@@ -13,7 +13,44 @@ import { resolveUsageRate } from "./pricing.js";
|
|
|
13
13
|
// provider. These are the real gpt-5.* API prices, not the (4x cheaper) Codex
|
|
14
14
|
// subscription credit prices.
|
|
15
15
|
const USD_TO_CREDITS = 100;
|
|
16
|
+
const GPT_5_6_SOL_RATE = {
|
|
17
|
+
input: 5,
|
|
18
|
+
cacheRead: 0.5,
|
|
19
|
+
cacheWrite: 6.25,
|
|
20
|
+
cacheWrite5m: 6.25,
|
|
21
|
+
cacheWrite1h: 6.25,
|
|
22
|
+
output: 30,
|
|
23
|
+
longContext: {
|
|
24
|
+
thresholdTokens: 272000,
|
|
25
|
+
rate: { input: 10, cacheRead: 1, cacheWrite: 12.5, cacheWrite5m: 12.5, cacheWrite1h: 12.5, output: 45 }
|
|
26
|
+
}
|
|
27
|
+
};
|
|
16
28
|
const RATE_CARD = {
|
|
29
|
+
"gpt-5.6-sol": GPT_5_6_SOL_RATE,
|
|
30
|
+
"gpt-5.6-terra": {
|
|
31
|
+
input: 2.5,
|
|
32
|
+
cacheRead: 0.25,
|
|
33
|
+
cacheWrite: 3.125,
|
|
34
|
+
cacheWrite5m: 3.125,
|
|
35
|
+
cacheWrite1h: 3.125,
|
|
36
|
+
output: 15,
|
|
37
|
+
longContext: {
|
|
38
|
+
thresholdTokens: 272000,
|
|
39
|
+
rate: { input: 5, cacheRead: 0.5, cacheWrite: 6.25, cacheWrite5m: 6.25, cacheWrite1h: 6.25, output: 22.5 }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"gpt-5.6-luna": {
|
|
43
|
+
input: 1,
|
|
44
|
+
cacheRead: 0.1,
|
|
45
|
+
cacheWrite: 1.25,
|
|
46
|
+
cacheWrite5m: 1.25,
|
|
47
|
+
cacheWrite1h: 1.25,
|
|
48
|
+
output: 6,
|
|
49
|
+
longContext: {
|
|
50
|
+
thresholdTokens: 272000,
|
|
51
|
+
rate: { input: 2, cacheRead: 0.2, cacheWrite: 2.5, cacheWrite5m: 2.5, cacheWrite1h: 2.5, output: 9 }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
17
54
|
"gpt-5.5": { input: 5, cacheRead: 0.5, cacheWrite: 5, cacheWrite5m: 5, cacheWrite1h: 5, output: 30 },
|
|
18
55
|
"gpt-5.4": { input: 2.5, cacheRead: 0.25, cacheWrite: 2.5, cacheWrite5m: 2.5, cacheWrite1h: 2.5, output: 15 },
|
|
19
56
|
"gpt-5.4-mini": { input: 0.75, cacheRead: 0.075, cacheWrite: 0.75, cacheWrite5m: 0.75, cacheWrite1h: 0.75, output: 4.5 }
|
|
@@ -53,7 +90,7 @@ export class CodexUsageProvider extends UsageProviderBase {
|
|
|
53
90
|
.sort((left, right) => right.totals.estimatedCredits - left.totals.estimatedCredits);
|
|
54
91
|
const unknownPricedModels = modelUsage
|
|
55
92
|
.map((row) => row.modelId)
|
|
56
|
-
.filter((modelId) => !
|
|
93
|
+
.filter((modelId) => !rateForCodexModel(modelId) && !isAssumedZeroRatedCodexModel(modelId, knownModels));
|
|
57
94
|
if (unknownPricedModels.length > 0) {
|
|
58
95
|
warnings.push(`No credit rate configured for: ${unknownPricedModels.join(", ")}.`);
|
|
59
96
|
}
|
|
@@ -235,7 +272,7 @@ function subtractRawUsage(current, previous) {
|
|
|
235
272
|
};
|
|
236
273
|
}
|
|
237
274
|
function creditsFor(modelId, usage) {
|
|
238
|
-
const rate =
|
|
275
|
+
const rate = rateForCodexModel(modelId, usage.inputTokens);
|
|
239
276
|
if (!rate) {
|
|
240
277
|
return 0;
|
|
241
278
|
}
|
|
@@ -246,6 +283,12 @@ function creditsFor(modelId, usage) {
|
|
|
246
283
|
(usage.outputTokens / 1000000) * rate.output) *
|
|
247
284
|
USD_TO_CREDITS);
|
|
248
285
|
}
|
|
286
|
+
function rateForCodexModel(modelId, inputTokens = 0) {
|
|
287
|
+
// The unsuffixed API alias routes to Sol. Normalize only the exact alias so
|
|
288
|
+
// an unknown future gpt-5.6-* tier is not accidentally charged at Sol rates.
|
|
289
|
+
const pricedModelId = modelId === "gpt-5.6" ? "gpt-5.6-sol" : modelId;
|
|
290
|
+
return resolveUsageRate(RATE_CARD, pricedModelId, inputTokens, { prefixMatch: true });
|
|
291
|
+
}
|
|
249
292
|
function rawUsageToTotals(usage) {
|
|
250
293
|
const cacheReadInputTokens = Math.min(usage.cachedInputTokens, usage.inputTokens);
|
|
251
294
|
const inputTokens = Math.max(0, usage.inputTokens - cacheReadInputTokens);
|
|
@@ -267,7 +310,7 @@ function createUsageTotalsForModel(modelId, usage, knownModels) {
|
|
|
267
310
|
const deltaTotals = rawUsageToTotals(usage);
|
|
268
311
|
deltaTotals.estimatedCredits = creditsFor(resolvedModelId, usage);
|
|
269
312
|
deltaTotals.eventCount = 1;
|
|
270
|
-
if (!
|
|
313
|
+
if (!rateForCodexModel(resolvedModelId, usage.inputTokens) && !isAssumedZeroRatedCodexModel(resolvedModelId, knownModels)) {
|
|
271
314
|
deltaTotals.estimatedCreditsStatus = "unavailable";
|
|
272
315
|
}
|
|
273
316
|
return deltaTotals;
|
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
import { addUsageTotals, cloneUsageTotals, createEmptyUsageTotals } from "./contract.js";
|
|
2
|
+
// Recent Codex monthly windows can report a reset timestamp that jitters by a
|
|
3
|
+
// few minutes while the logical quota cycle remains unchanged. Short and weekly
|
|
4
|
+
// windows retain exact reset identity so their established behavior is stable.
|
|
5
|
+
const MONTHLY_WINDOW_MINUTES = 30 * 24 * 60;
|
|
6
|
+
const MONTHLY_RESET_JITTER_SECONDS = 15 * 60;
|
|
2
7
|
export function createLimitWindowAggregates() {
|
|
3
8
|
return new Map();
|
|
4
9
|
}
|
|
10
|
+
export function isLimitWindowActive(window, nowMs = Date.now()) {
|
|
11
|
+
const startMs = Date.parse(window.startTimeUtcIso);
|
|
12
|
+
const endMs = Date.parse(window.endTimeUtcIso);
|
|
13
|
+
return Number.isFinite(startMs) && Number.isFinite(endMs) && startMs <= nowMs && nowMs <= endMs;
|
|
14
|
+
}
|
|
15
|
+
export function selectLatestActiveLimitWindows(windows, nowMs = Date.now()) {
|
|
16
|
+
const latestByPlanAndWindow = new Map();
|
|
17
|
+
for (const window of windows) {
|
|
18
|
+
if (!isLimitWindowActive(window, nowMs)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const startMs = Date.parse(window.startTimeUtcIso);
|
|
22
|
+
const groupKey = JSON.stringify([window.planType, window.windowMinutes]);
|
|
23
|
+
const current = latestByPlanAndWindow.get(groupKey);
|
|
24
|
+
if (!current || startMs > current.startMs) {
|
|
25
|
+
latestByPlanAndWindow.set(groupKey, { window, startMs });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return new Set([...latestByPlanAndWindow.values()].map((entry) => entry.window));
|
|
29
|
+
}
|
|
5
30
|
export function numberOrZero(value) {
|
|
6
31
|
if (typeof value === "number") {
|
|
7
32
|
return Number.isFinite(value) ? value : 0;
|
|
@@ -92,6 +117,35 @@ function makeWindowKey(scope, rateLimits, window) {
|
|
|
92
117
|
numberOrZero(window.resets_at)
|
|
93
118
|
].join("|");
|
|
94
119
|
}
|
|
120
|
+
function findMatchingWindowKey(windows, scope, rateLimits, windowMinutes, resetsAt) {
|
|
121
|
+
const exactKey = makeWindowKey(scope, rateLimits, { window_minutes: windowMinutes, resets_at: resetsAt });
|
|
122
|
+
if (windows.has(exactKey) || windowMinutes < MONTHLY_WINDOW_MINUTES) {
|
|
123
|
+
return exactKey;
|
|
124
|
+
}
|
|
125
|
+
const limitId = String(rateLimits.limit_id ?? "unknown");
|
|
126
|
+
const planType = String(rateLimits.plan_type ?? "unknown");
|
|
127
|
+
let closestKey = exactKey;
|
|
128
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
129
|
+
for (const [key, candidate] of windows) {
|
|
130
|
+
if (candidate.scope !== scope ||
|
|
131
|
+
candidate.limitId !== limitId ||
|
|
132
|
+
candidate.planType !== planType ||
|
|
133
|
+
candidate.windowMinutes !== windowMinutes) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const minResetsAt = candidate.minStartsAt + candidate.windowMinutes * 60;
|
|
137
|
+
const distance = resetsAt < minResetsAt
|
|
138
|
+
? minResetsAt - resetsAt
|
|
139
|
+
: resetsAt > candidate.maxResetsAt
|
|
140
|
+
? resetsAt - candidate.maxResetsAt
|
|
141
|
+
: 0;
|
|
142
|
+
if (distance <= MONTHLY_RESET_JITTER_SECONDS && distance < closestDistance) {
|
|
143
|
+
closestKey = key;
|
|
144
|
+
closestDistance = distance;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return closestKey;
|
|
148
|
+
}
|
|
95
149
|
function collapseNearbyWindows(rows) {
|
|
96
150
|
const collapsed = new Map();
|
|
97
151
|
for (const row of rows) {
|
|
@@ -163,7 +217,7 @@ function upsertWindow(windows, scope, rateLimits, window, eventTimeMs, modelId,
|
|
|
163
217
|
}
|
|
164
218
|
const startsAt = resetsAt - windowMinutes * 60;
|
|
165
219
|
const usedPercent = numberOrZero(window.used_percent);
|
|
166
|
-
const key =
|
|
220
|
+
const key = findMatchingWindowKey(windows, scope, rateLimits, windowMinutes, resetsAt);
|
|
167
221
|
const existing = windows.get(key);
|
|
168
222
|
if (!existing) {
|
|
169
223
|
windows.set(key, {
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letmecode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "Terminal AI usage dashboard for Codex, Claude, Copilot, and Antigravity.",
|
|
5
5
|
"author": "Devforth (https://devforth.io)",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"packageManager": "pnpm@10.28.2",
|
|
7
8
|
"type": "commonjs",
|
|
8
9
|
"main": "./dist/index.js",
|
|
9
10
|
"bin": {
|
|
@@ -29,6 +30,16 @@
|
|
|
29
30
|
"publishConfig": {
|
|
30
31
|
"access": "public"
|
|
31
32
|
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('ink-app/dist', { recursive: true, force: true });\"",
|
|
35
|
+
"build": "npm run clean && tsc -p tsconfig.json && tsc -p ink-app/tsconfig.json",
|
|
36
|
+
"prepack": "npm run build",
|
|
37
|
+
"prestart": "npm run build",
|
|
38
|
+
"start": "node ./bin/letmecode.js",
|
|
39
|
+
"pretest": "npm run build",
|
|
40
|
+
"smoke": "node ./bin/letmecode.js",
|
|
41
|
+
"test": "node --test ink-app/test/*.test.mjs"
|
|
42
|
+
},
|
|
32
43
|
"keywords": [
|
|
33
44
|
"ai",
|
|
34
45
|
"agents",
|
|
@@ -53,14 +64,5 @@
|
|
|
53
64
|
"@types/node": "^24.0.7",
|
|
54
65
|
"@types/react": "^18.3.24",
|
|
55
66
|
"typescript": "^5.8.3"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('ink-app/dist', { recursive: true, force: true });\"",
|
|
59
|
-
"build": "npm run clean && tsc -p tsconfig.json && tsc -p ink-app/tsconfig.json",
|
|
60
|
-
"prestart": "npm run build",
|
|
61
|
-
"start": "node ./bin/letmecode.js",
|
|
62
|
-
"pretest": "npm run build",
|
|
63
|
-
"smoke": "node ./bin/letmecode.js",
|
|
64
|
-
"test": "node --test ink-app/test/*.test.mjs"
|
|
65
67
|
}
|
|
66
|
-
}
|
|
68
|
+
}
|