@wrongstack/tui 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -0
- package/dist/index.js +426 -54
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useReducer, useRef, useEffect, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
2
2
|
import { render, useApp, Box, useStdout, Static, Text, useInput } from 'ink';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
4
|
import * as fs2 from 'fs/promises';
|
|
@@ -187,15 +187,20 @@ function padCell(text, width, align) {
|
|
|
187
187
|
}
|
|
188
188
|
return text + " ".repeat(pad);
|
|
189
189
|
}
|
|
190
|
-
function History({ entries, streamingText }) {
|
|
190
|
+
function History({ entries, streamingText, toolStream }) {
|
|
191
191
|
const { stdout } = useStdout();
|
|
192
192
|
const termWidth = stdout?.columns ?? 80;
|
|
193
193
|
const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
|
|
194
|
+
const toolTail = toolStream && toolStream.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
|
|
194
195
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
195
196
|
/* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id) }),
|
|
196
197
|
tail ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
197
198
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
|
|
198
199
|
/* @__PURE__ */ jsx(Text, { children: tail })
|
|
200
|
+
] }) : null,
|
|
201
|
+
toolTail ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
202
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `\u25C6 ${toolStream.name} ` }),
|
|
203
|
+
/* @__PURE__ */ jsx(Text, { children: toolTail })
|
|
199
204
|
] }) : null
|
|
200
205
|
] });
|
|
201
206
|
}
|
|
@@ -212,18 +217,19 @@ function tailForDisplay(text, maxChars) {
|
|
|
212
217
|
function DiffBlock({ rows, hidden }) {
|
|
213
218
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, marginTop: 0, children: [
|
|
214
219
|
rows.map((row, i) => {
|
|
220
|
+
const key = i;
|
|
215
221
|
if (row.kind === "hunk") {
|
|
216
|
-
return /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: row.text },
|
|
222
|
+
return /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: row.text }, key);
|
|
217
223
|
}
|
|
218
224
|
if (row.kind === "meta") {
|
|
219
|
-
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.text },
|
|
225
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.text }, key);
|
|
220
226
|
}
|
|
221
227
|
if (row.kind === "ctx") {
|
|
222
|
-
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.text },
|
|
228
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.text }, key);
|
|
223
229
|
}
|
|
224
230
|
const bg = row.kind === "add" ? "green" : "red";
|
|
225
231
|
const fg = row.kind === "add" ? "black" : "white";
|
|
226
|
-
return /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: fg, children: row.text },
|
|
232
|
+
return /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: fg, children: row.text }, key);
|
|
227
233
|
}),
|
|
228
234
|
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
|
|
229
235
|
] });
|
|
@@ -254,15 +260,13 @@ function Entry({ entry, termWidth }) {
|
|
|
254
260
|
] }) : null,
|
|
255
261
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` })
|
|
256
262
|
] }),
|
|
257
|
-
outLines.map((line, i) =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
] }, i);
|
|
265
|
-
}),
|
|
263
|
+
outLines.map((line, i) => (
|
|
264
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
|
|
265
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
266
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
|
|
267
|
+
/* @__PURE__ */ jsx(Text, { color: !entry.ok || line.startsWith("!") ? "red" : void 0, dimColor: entry.ok && !line.startsWith("!"), children: line })
|
|
268
|
+
] }, i)
|
|
269
|
+
)),
|
|
266
270
|
diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
|
|
267
271
|
] });
|
|
268
272
|
}
|
|
@@ -274,6 +278,30 @@ function Entry({ entry, termWidth }) {
|
|
|
274
278
|
return /* @__PURE__ */ jsx(Text, { color: "red", children: entry.text });
|
|
275
279
|
case "turn-summary":
|
|
276
280
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
|
|
281
|
+
case "confirm":
|
|
282
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderTop: false, borderLeft: false, borderRight: false, borderBottom: false, paddingX: 1, children: [
|
|
283
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
284
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
285
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
286
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: entry.toolName })
|
|
287
|
+
] }),
|
|
288
|
+
entry.input && typeof entry.input === "object" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: Object.entries(entry.input).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${String(v).slice(0, 80)}`).join(" ") }) : null,
|
|
289
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
290
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
291
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
292
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
293
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
294
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
295
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
296
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
297
|
+
" always (",
|
|
298
|
+
entry.suggestedPattern,
|
|
299
|
+
") "
|
|
300
|
+
] }),
|
|
301
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
302
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
303
|
+
] }) })
|
|
304
|
+
] });
|
|
277
305
|
case "banner":
|
|
278
306
|
return /* @__PURE__ */ jsx(Banner, { entry });
|
|
279
307
|
}
|
|
@@ -306,6 +334,15 @@ function Banner({
|
|
|
306
334
|
entry.model
|
|
307
335
|
] })
|
|
308
336
|
] }),
|
|
337
|
+
entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
338
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
|
|
339
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
|
|
340
|
+
] }) : null,
|
|
341
|
+
entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
342
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
|
|
343
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
|
|
344
|
+
/* @__PURE__ */ jsx(Text, { children: entry.keyTail })
|
|
345
|
+
] }) : null,
|
|
309
346
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
310
347
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
|
|
311
348
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
|
|
@@ -888,7 +925,7 @@ function scanNumberedRange(text) {
|
|
|
888
925
|
let count = 0;
|
|
889
926
|
for (const line of text.split("\n")) {
|
|
890
927
|
const m = line.match(/^\s*(\d+)→/);
|
|
891
|
-
if (m
|
|
928
|
+
if (m?.[1]) {
|
|
892
929
|
const n = Number.parseInt(m[1], 10);
|
|
893
930
|
if (Number.isFinite(n)) {
|
|
894
931
|
if (first === void 0) first = n;
|
|
@@ -928,27 +965,21 @@ function Input({
|
|
|
928
965
|
const before = value.slice(0, cursor);
|
|
929
966
|
const at = value.slice(cursor, cursor + 1) || " ";
|
|
930
967
|
const after = value.slice(cursor + 1);
|
|
931
|
-
const
|
|
968
|
+
const promptColor = disabled ? "red" : "cyan";
|
|
932
969
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
933
|
-
placeholders.map((p, i) =>
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
before,
|
|
947
|
-
/* @__PURE__ */ jsx(Text, { inverse: true, children: at }),
|
|
948
|
-
after
|
|
949
|
-
] })
|
|
950
|
-
}
|
|
951
|
-
),
|
|
970
|
+
placeholders.map((p, i) => (
|
|
971
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: placeholders are append-only, index is stable
|
|
972
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
973
|
+
" \u21B3 ",
|
|
974
|
+
p
|
|
975
|
+
] }, i)
|
|
976
|
+
)),
|
|
977
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
978
|
+
/* @__PURE__ */ jsx(Text, { color: promptColor, children: prompt }),
|
|
979
|
+
before,
|
|
980
|
+
/* @__PURE__ */ jsx(Text, { inverse: true, children: at }),
|
|
981
|
+
after
|
|
982
|
+
] }),
|
|
952
983
|
hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
953
984
|
] });
|
|
954
985
|
}
|
|
@@ -1186,6 +1217,117 @@ function SlashMenu({ query, matches, selected }) {
|
|
|
1186
1217
|
matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
|
|
1187
1218
|
] });
|
|
1188
1219
|
}
|
|
1220
|
+
function ModelPicker({
|
|
1221
|
+
step,
|
|
1222
|
+
providerOptions,
|
|
1223
|
+
modelOptions,
|
|
1224
|
+
selected,
|
|
1225
|
+
pickedProviderId,
|
|
1226
|
+
hint
|
|
1227
|
+
}) {
|
|
1228
|
+
if (step === "provider") {
|
|
1229
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1230
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
|
|
1231
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
|
|
1232
|
+
providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : providerOptions.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1233
|
+
i === selected ? "\u203A " : " ",
|
|
1234
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
1235
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1236
|
+
" [",
|
|
1237
|
+
p.family,
|
|
1238
|
+
"]"
|
|
1239
|
+
] }),
|
|
1240
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1241
|
+
" ",
|
|
1242
|
+
p.models.length,
|
|
1243
|
+
" model",
|
|
1244
|
+
p.models.length === 1 ? "" : "s"
|
|
1245
|
+
] })
|
|
1246
|
+
] }, p.id)),
|
|
1247
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1248
|
+
] });
|
|
1249
|
+
}
|
|
1250
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1251
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
1252
|
+
"\u2501\u2501 Switch model \u2014 Step 2/2: Pick model",
|
|
1253
|
+
" ",
|
|
1254
|
+
"(",
|
|
1255
|
+
pickedProviderId,
|
|
1256
|
+
") \u2501\u2501"
|
|
1257
|
+
] }),
|
|
1258
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl-C cancel" }),
|
|
1259
|
+
modelOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no models known for this provider)" }) : modelOptions.map((id, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1260
|
+
i === selected ? "\u203A " : " ",
|
|
1261
|
+
id
|
|
1262
|
+
] }, id)),
|
|
1263
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1264
|
+
] });
|
|
1265
|
+
}
|
|
1266
|
+
function stringifyInput(input) {
|
|
1267
|
+
if (!input || typeof input !== "object") return "";
|
|
1268
|
+
const obj = input;
|
|
1269
|
+
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate(JSON.stringify(v), 80)}`).join(" ");
|
|
1270
|
+
}
|
|
1271
|
+
function truncate(s, max) {
|
|
1272
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1273
|
+
}
|
|
1274
|
+
function hasDiff(input) {
|
|
1275
|
+
return Boolean(
|
|
1276
|
+
input && typeof input === "object" && "diff" in input
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
function renderDiffLine(line) {
|
|
1280
|
+
const prefix = line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : line.startsWith("@@") ? "cyan" : void 0;
|
|
1281
|
+
return /* @__PURE__ */ jsxs(Text, { color: prefix, children: [
|
|
1282
|
+
line,
|
|
1283
|
+
"\n"
|
|
1284
|
+
] }, line);
|
|
1285
|
+
}
|
|
1286
|
+
function renderDiff(diff) {
|
|
1287
|
+
const lines = diff.split("\n").filter((l) => l.length > 0).slice(0, 20);
|
|
1288
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, children: lines.map((l) => renderDiffLine(l)) });
|
|
1289
|
+
}
|
|
1290
|
+
function ConfirmPrompt({ toolName, input, suggestedPattern, onDecision }) {
|
|
1291
|
+
useInput((_, key) => {
|
|
1292
|
+
if (key.return) {
|
|
1293
|
+
onDecision("yes");
|
|
1294
|
+
} else if (key.escape) {
|
|
1295
|
+
onDecision("no");
|
|
1296
|
+
} else if (key.ctrl && _.toLowerCase() === "a") {
|
|
1297
|
+
onDecision("always");
|
|
1298
|
+
} else if (key.ctrl && _.toLowerCase() === "d") {
|
|
1299
|
+
onDecision("deny");
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
const inputSummary = stringifyInput(input);
|
|
1303
|
+
const showDiff = hasDiff(input);
|
|
1304
|
+
const inp = input;
|
|
1305
|
+
const diff = typeof inp?.diff === "string" ? inp.diff : "";
|
|
1306
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderTop: false, borderLeft: false, borderRight: false, borderBottom: false, paddingX: 1, children: [
|
|
1307
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1308
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
1309
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1310
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: toolName })
|
|
1311
|
+
] }),
|
|
1312
|
+
inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
|
|
1313
|
+
showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
|
|
1314
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
1315
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1316
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
1317
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
1318
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
1319
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
1320
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
1321
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1322
|
+
" always (",
|
|
1323
|
+
suggestedPattern,
|
|
1324
|
+
") "
|
|
1325
|
+
] }),
|
|
1326
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
1327
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
1328
|
+
] }) })
|
|
1329
|
+
] });
|
|
1330
|
+
}
|
|
1189
1331
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1190
1332
|
"node_modules",
|
|
1191
1333
|
".git",
|
|
@@ -1344,7 +1486,9 @@ function runCmd(cmd, args) {
|
|
|
1344
1486
|
return new Promise((resolve) => {
|
|
1345
1487
|
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1346
1488
|
let out = "";
|
|
1347
|
-
child.stdout.on("data", (c) =>
|
|
1489
|
+
child.stdout.on("data", (c) => {
|
|
1490
|
+
out += String(c);
|
|
1491
|
+
});
|
|
1348
1492
|
child.on("error", () => resolve(null));
|
|
1349
1493
|
child.on("exit", (code) => resolve(code === 0 ? out : null));
|
|
1350
1494
|
});
|
|
@@ -1577,6 +1721,26 @@ function reducer(state, action) {
|
|
|
1577
1721
|
}
|
|
1578
1722
|
return state;
|
|
1579
1723
|
}
|
|
1724
|
+
case "toolStreamAppend": {
|
|
1725
|
+
const cur = state.toolStream;
|
|
1726
|
+
if (cur && cur.toolUseId === action.toolUseId) {
|
|
1727
|
+
return {
|
|
1728
|
+
...state,
|
|
1729
|
+
toolStream: { ...cur, text: cur.text + action.text }
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
...state,
|
|
1734
|
+
toolStream: { toolUseId: action.toolUseId, name: action.name, text: action.text }
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
case "toolStreamClear": {
|
|
1738
|
+
if (state.toolStream === null) return state;
|
|
1739
|
+
const t = state.toolStream;
|
|
1740
|
+
if (action.toolUseId !== void 0 && action.toolUseId !== t.toolUseId) return state;
|
|
1741
|
+
if (action.name !== void 0 && action.toolUseId === void 0 && action.name !== t.name) return state;
|
|
1742
|
+
return { ...state, toolStream: null };
|
|
1743
|
+
}
|
|
1580
1744
|
case "enqueue": {
|
|
1581
1745
|
const item = { ...action.item, id: state.nextQueueId };
|
|
1582
1746
|
return {
|
|
@@ -1632,6 +1796,72 @@ function reducer(state, action) {
|
|
|
1632
1796
|
const entry = next === 0 ? "" : state.inputHistory[next - 1] ?? "";
|
|
1633
1797
|
return { ...state, historyIndex: next, buffer: entry, cursor: entry.length };
|
|
1634
1798
|
}
|
|
1799
|
+
case "modelPickerOpen":
|
|
1800
|
+
return {
|
|
1801
|
+
...state,
|
|
1802
|
+
modelPicker: {
|
|
1803
|
+
open: true,
|
|
1804
|
+
step: "provider",
|
|
1805
|
+
providerOptions: action.providers,
|
|
1806
|
+
modelOptions: [],
|
|
1807
|
+
selected: 0,
|
|
1808
|
+
hint: void 0
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
case "modelPickerClose":
|
|
1812
|
+
return {
|
|
1813
|
+
...state,
|
|
1814
|
+
modelPicker: {
|
|
1815
|
+
open: false,
|
|
1816
|
+
step: "provider",
|
|
1817
|
+
providerOptions: [],
|
|
1818
|
+
modelOptions: [],
|
|
1819
|
+
selected: 0
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
case "modelPickerMove": {
|
|
1823
|
+
if (!state.modelPicker.open) return state;
|
|
1824
|
+
const len = state.modelPicker.step === "provider" ? state.modelPicker.providerOptions.length : state.modelPicker.modelOptions.length;
|
|
1825
|
+
if (len === 0) return state;
|
|
1826
|
+
const next = (state.modelPicker.selected + action.delta + len) % len;
|
|
1827
|
+
return {
|
|
1828
|
+
...state,
|
|
1829
|
+
modelPicker: { ...state.modelPicker, selected: next }
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
case "modelPickerPickProvider":
|
|
1833
|
+
return {
|
|
1834
|
+
...state,
|
|
1835
|
+
modelPicker: {
|
|
1836
|
+
...state.modelPicker,
|
|
1837
|
+
step: "model",
|
|
1838
|
+
modelOptions: action.models,
|
|
1839
|
+
selected: 0,
|
|
1840
|
+
pickedProviderId: action.providerId,
|
|
1841
|
+
hint: void 0
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
case "modelPickerBack":
|
|
1845
|
+
return {
|
|
1846
|
+
...state,
|
|
1847
|
+
modelPicker: {
|
|
1848
|
+
...state.modelPicker,
|
|
1849
|
+
step: "provider",
|
|
1850
|
+
modelOptions: [],
|
|
1851
|
+
selected: 0,
|
|
1852
|
+
pickedProviderId: void 0,
|
|
1853
|
+
hint: void 0
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
case "modelPickerHint":
|
|
1857
|
+
return {
|
|
1858
|
+
...state,
|
|
1859
|
+
modelPicker: { ...state.modelPicker, hint: action.text }
|
|
1860
|
+
};
|
|
1861
|
+
case "confirmOpen":
|
|
1862
|
+
return { ...state, confirm: action.info };
|
|
1863
|
+
case "confirmClose":
|
|
1864
|
+
return { ...state, confirm: null };
|
|
1635
1865
|
}
|
|
1636
1866
|
}
|
|
1637
1867
|
var PASTE_THRESHOLD_CHARS = 200;
|
|
@@ -1647,10 +1877,16 @@ function App({
|
|
|
1647
1877
|
yolo = false,
|
|
1648
1878
|
appVersion,
|
|
1649
1879
|
provider,
|
|
1880
|
+
family,
|
|
1881
|
+
keyTail,
|
|
1882
|
+
getPickableProviders,
|
|
1883
|
+
switchProviderAndModel,
|
|
1650
1884
|
effectiveMaxContext,
|
|
1651
1885
|
onExit
|
|
1652
1886
|
}) {
|
|
1653
1887
|
const { exit } = useApp();
|
|
1888
|
+
const [liveModel, setLiveModel] = useState(model);
|
|
1889
|
+
const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
|
|
1654
1890
|
const [state, dispatch] = useReducer(reducer, {
|
|
1655
1891
|
entries: banner ? [
|
|
1656
1892
|
{
|
|
@@ -1659,13 +1895,16 @@ function App({
|
|
|
1659
1895
|
version: appVersion ?? "dev",
|
|
1660
1896
|
provider: provider ?? "agent",
|
|
1661
1897
|
model,
|
|
1662
|
-
cwd: agent.ctx.cwd
|
|
1898
|
+
cwd: agent.ctx.cwd,
|
|
1899
|
+
family,
|
|
1900
|
+
keyTail
|
|
1663
1901
|
}
|
|
1664
1902
|
] : [],
|
|
1665
1903
|
buffer: "",
|
|
1666
1904
|
cursor: 0,
|
|
1667
1905
|
placeholders: [],
|
|
1668
1906
|
streamingText: "",
|
|
1907
|
+
toolStream: null,
|
|
1669
1908
|
status: "idle",
|
|
1670
1909
|
interrupts: 0,
|
|
1671
1910
|
hint: "",
|
|
@@ -1676,7 +1915,15 @@ function App({
|
|
|
1676
1915
|
queue: [],
|
|
1677
1916
|
nextQueueId: 1,
|
|
1678
1917
|
inputHistory: [],
|
|
1679
|
-
historyIndex: 0
|
|
1918
|
+
historyIndex: 0,
|
|
1919
|
+
modelPicker: {
|
|
1920
|
+
open: false,
|
|
1921
|
+
step: "provider",
|
|
1922
|
+
providerOptions: [],
|
|
1923
|
+
modelOptions: [],
|
|
1924
|
+
selected: 0
|
|
1925
|
+
},
|
|
1926
|
+
confirm: null
|
|
1680
1927
|
});
|
|
1681
1928
|
const builderRef = useRef(null);
|
|
1682
1929
|
if (builderRef.current === null) {
|
|
@@ -1890,6 +2137,23 @@ function App({
|
|
|
1890
2137
|
slashRegistry.unregister("queue");
|
|
1891
2138
|
};
|
|
1892
2139
|
}, [slashRegistry]);
|
|
2140
|
+
useEffect(() => {
|
|
2141
|
+
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
2142
|
+
const cmd = {
|
|
2143
|
+
name: "model",
|
|
2144
|
+
aliases: ["provider", "switch"],
|
|
2145
|
+
description: "Pick a provider + model interactively (two-step).",
|
|
2146
|
+
async run() {
|
|
2147
|
+
const providers = await getPickableProviders();
|
|
2148
|
+
dispatch({ type: "modelPickerOpen", providers });
|
|
2149
|
+
return { message: void 0 };
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
slashRegistry.register(cmd);
|
|
2153
|
+
return () => {
|
|
2154
|
+
slashRegistry.unregister("model");
|
|
2155
|
+
};
|
|
2156
|
+
}, [slashRegistry, getPickableProviders, switchProviderAndModel]);
|
|
1893
2157
|
useEffect(() => {
|
|
1894
2158
|
const FLUSH_MS = 100;
|
|
1895
2159
|
const flush = () => {
|
|
@@ -1908,6 +2172,15 @@ function App({
|
|
|
1908
2172
|
const offToolStart = events.on("tool.started", (e) => {
|
|
1909
2173
|
dispatch({ type: "toolStarted", id: e.id, name: e.name });
|
|
1910
2174
|
});
|
|
2175
|
+
const offToolProgress = events.on("tool.progress", (e) => {
|
|
2176
|
+
if (e.event.type !== "partial_output" || !e.event.text) return;
|
|
2177
|
+
dispatch({
|
|
2178
|
+
type: "toolStreamAppend",
|
|
2179
|
+
toolUseId: e.id,
|
|
2180
|
+
name: e.name,
|
|
2181
|
+
text: e.event.text
|
|
2182
|
+
});
|
|
2183
|
+
});
|
|
1911
2184
|
const offTool = events.on("tool.executed", (e) => {
|
|
1912
2185
|
dispatch({
|
|
1913
2186
|
type: "addEntry",
|
|
@@ -1921,6 +2194,7 @@ function App({
|
|
|
1921
2194
|
}
|
|
1922
2195
|
});
|
|
1923
2196
|
dispatch({ type: "toolEnded", name: e.name });
|
|
2197
|
+
dispatch({ type: "toolStreamClear", name: e.name });
|
|
1924
2198
|
});
|
|
1925
2199
|
const offRetry = events.on("provider.retry", (e) => {
|
|
1926
2200
|
const secs = (e.delayMs / 1e3).toFixed(e.delayMs >= 1e3 ? 1 : 2);
|
|
@@ -1935,12 +2209,35 @@ function App({
|
|
|
1935
2209
|
entry: { kind: "error", text: e.description }
|
|
1936
2210
|
});
|
|
1937
2211
|
});
|
|
2212
|
+
const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
|
|
2213
|
+
dispatch({
|
|
2214
|
+
type: "addEntry",
|
|
2215
|
+
entry: {
|
|
2216
|
+
kind: "confirm",
|
|
2217
|
+
toolName: e.tool.name,
|
|
2218
|
+
input: e.input,
|
|
2219
|
+
suggestedPattern: e.suggestedPattern
|
|
2220
|
+
}
|
|
2221
|
+
});
|
|
2222
|
+
dispatch({
|
|
2223
|
+
type: "confirmOpen",
|
|
2224
|
+
info: {
|
|
2225
|
+
toolUseId: e.toolUseId,
|
|
2226
|
+
toolName: e.tool.name,
|
|
2227
|
+
input: e.input,
|
|
2228
|
+
suggestedPattern: e.suggestedPattern,
|
|
2229
|
+
resolve: e.resolve
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
});
|
|
1938
2233
|
return () => {
|
|
1939
2234
|
offDelta();
|
|
1940
2235
|
offToolStart();
|
|
2236
|
+
offToolProgress();
|
|
1941
2237
|
offTool();
|
|
1942
2238
|
offRetry();
|
|
1943
2239
|
offProvErr();
|
|
2240
|
+
offConfirmNeeded();
|
|
1944
2241
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
1945
2242
|
};
|
|
1946
2243
|
}, [events]);
|
|
@@ -1985,6 +2282,53 @@ function App({
|
|
|
1985
2282
|
}, [state.interrupts, state.status, exit, onExit]);
|
|
1986
2283
|
const handleKey = async (input, key) => {
|
|
1987
2284
|
if (state.status === "aborting") return;
|
|
2285
|
+
if (state.modelPicker.open) {
|
|
2286
|
+
if (key.escape) {
|
|
2287
|
+
if (state.modelPicker.step === "model") {
|
|
2288
|
+
dispatch({ type: "modelPickerBack" });
|
|
2289
|
+
} else {
|
|
2290
|
+
dispatch({ type: "modelPickerClose" });
|
|
2291
|
+
}
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
if (key.upArrow) {
|
|
2295
|
+
dispatch({ type: "modelPickerMove", delta: -1 });
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (key.downArrow) {
|
|
2299
|
+
dispatch({ type: "modelPickerMove", delta: 1 });
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
if (key.return) {
|
|
2303
|
+
if (state.modelPicker.step === "provider") {
|
|
2304
|
+
const opt = state.modelPicker.providerOptions[state.modelPicker.selected];
|
|
2305
|
+
if (!opt) return;
|
|
2306
|
+
dispatch({
|
|
2307
|
+
type: "modelPickerPickProvider",
|
|
2308
|
+
providerId: opt.id,
|
|
2309
|
+
models: opt.models
|
|
2310
|
+
});
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
const providerId = state.modelPicker.pickedProviderId;
|
|
2314
|
+
const modelId = state.modelPicker.modelOptions[state.modelPicker.selected];
|
|
2315
|
+
if (!providerId || !modelId) return;
|
|
2316
|
+
const err = switchProviderAndModel?.(providerId, modelId);
|
|
2317
|
+
if (err) {
|
|
2318
|
+
dispatch({ type: "modelPickerHint", text: err });
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
setLiveProvider(providerId);
|
|
2322
|
+
setLiveModel(modelId);
|
|
2323
|
+
dispatch({
|
|
2324
|
+
type: "addEntry",
|
|
2325
|
+
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
2326
|
+
});
|
|
2327
|
+
dispatch({ type: "modelPickerClose" });
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
1988
2332
|
if (state.slashPicker.open) {
|
|
1989
2333
|
if (key.escape) {
|
|
1990
2334
|
dispatch({ type: "slashPickerClose" });
|
|
@@ -2118,25 +2462,26 @@ function App({
|
|
|
2118
2462
|
}
|
|
2119
2463
|
if (!input || key.ctrl || key.meta) return;
|
|
2120
2464
|
let bracketedPaste = false;
|
|
2465
|
+
let cleanInput = input;
|
|
2121
2466
|
if (input.includes("\x1B[200~") || input.includes("\x1B[201~")) {
|
|
2122
|
-
|
|
2467
|
+
cleanInput = input.replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
|
|
2123
2468
|
bracketedPaste = true;
|
|
2124
2469
|
}
|
|
2125
|
-
if (bracketedPaste ||
|
|
2470
|
+
if (bracketedPaste || cleanInput.length > PASTE_THRESHOLD_CHARS || cleanInput.includes("\n")) {
|
|
2126
2471
|
const builder = builderRef.current;
|
|
2127
2472
|
if (!builder) return;
|
|
2128
|
-
const ph = await builder.appendPaste(
|
|
2473
|
+
const ph = await builder.appendPaste(cleanInput);
|
|
2129
2474
|
if (ph) {
|
|
2130
|
-
const lineCount =
|
|
2475
|
+
const lineCount = cleanInput.split("\n").length;
|
|
2131
2476
|
dispatch({ type: "addPlaceholder", ph: `${ph} (${lineCount} lines)` });
|
|
2132
2477
|
} else {
|
|
2133
|
-
const next2 = state.buffer.slice(0, state.cursor) +
|
|
2134
|
-
dispatch({ type: "setBuffer", buffer: next2, cursor: state.cursor +
|
|
2478
|
+
const next2 = state.buffer.slice(0, state.cursor) + cleanInput + state.buffer.slice(state.cursor);
|
|
2479
|
+
dispatch({ type: "setBuffer", buffer: next2, cursor: state.cursor + cleanInput.length });
|
|
2135
2480
|
}
|
|
2136
2481
|
return;
|
|
2137
2482
|
}
|
|
2138
|
-
const next = state.buffer.slice(0, state.cursor) +
|
|
2139
|
-
dispatch({ type: "setBuffer", buffer: next, cursor: state.cursor +
|
|
2483
|
+
const next = state.buffer.slice(0, state.cursor) + cleanInput + state.buffer.slice(state.cursor);
|
|
2484
|
+
dispatch({ type: "setBuffer", buffer: next, cursor: state.cursor + cleanInput.length });
|
|
2140
2485
|
};
|
|
2141
2486
|
const runBlocks = async (blocks) => {
|
|
2142
2487
|
const ctrl = new AbortController();
|
|
@@ -2149,7 +2494,7 @@ function App({
|
|
|
2149
2494
|
const result = await agent.run(blocks, { signal: ctrl.signal });
|
|
2150
2495
|
const streamed = streamingTextRef.current;
|
|
2151
2496
|
const text = result.status === "done" && result.finalText ? result.finalText : streamed;
|
|
2152
|
-
if (text
|
|
2497
|
+
if (text?.trim()) {
|
|
2153
2498
|
dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
|
|
2154
2499
|
}
|
|
2155
2500
|
streamingTextRef.current = "";
|
|
@@ -2162,12 +2507,11 @@ function App({
|
|
|
2162
2507
|
if (result.status === "aborted") {
|
|
2163
2508
|
dispatch({ type: "addEntry", entry: { kind: "warn", text: "Aborted." } });
|
|
2164
2509
|
} else if (result.status === "failed") {
|
|
2510
|
+
const err = result.error;
|
|
2511
|
+
const text2 = err ? `Failed [${err.severity}${err.recoverable ? ", recoverable" : ""}]: ${err.describe()}` : "Failed.";
|
|
2165
2512
|
dispatch({
|
|
2166
2513
|
type: "addEntry",
|
|
2167
|
-
entry: {
|
|
2168
|
-
kind: "error",
|
|
2169
|
-
text: `Failed: ${result.error instanceof Error ? result.error.message : String(result.error)}`
|
|
2170
|
-
}
|
|
2514
|
+
entry: { kind: "error", text: text2 }
|
|
2171
2515
|
});
|
|
2172
2516
|
} else if (result.status === "max_iterations") {
|
|
2173
2517
|
dispatch({
|
|
@@ -2215,6 +2559,10 @@ function App({
|
|
|
2215
2559
|
if (res?.message) {
|
|
2216
2560
|
dispatch({ type: "addEntry", entry: { kind: "info", text: res.message } });
|
|
2217
2561
|
}
|
|
2562
|
+
const ctxModel = agent.ctx.model;
|
|
2563
|
+
if (ctxModel && ctxModel !== liveModel) setLiveModel(ctxModel);
|
|
2564
|
+
const ctxProviderId = agent.ctx.provider?.id;
|
|
2565
|
+
if (ctxProviderId && ctxProviderId !== liveProvider) setLiveProvider(ctxProviderId);
|
|
2218
2566
|
if (res?.exit) {
|
|
2219
2567
|
exit();
|
|
2220
2568
|
onExit(0);
|
|
@@ -2253,7 +2601,7 @@ function App({
|
|
|
2253
2601
|
return "";
|
|
2254
2602
|
}, [state.buffer, state.status, state.picker.open]);
|
|
2255
2603
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2256
|
-
/* @__PURE__ */ jsx(History, { entries: state.entries, streamingText: state.streamingText }),
|
|
2604
|
+
/* @__PURE__ */ jsx(History, { entries: state.entries, streamingText: state.streamingText, toolStream: state.toolStream }),
|
|
2257
2605
|
/* @__PURE__ */ jsx(
|
|
2258
2606
|
Input,
|
|
2259
2607
|
{
|
|
@@ -2281,10 +2629,30 @@ function App({
|
|
|
2281
2629
|
selected: state.slashPicker.selected
|
|
2282
2630
|
}
|
|
2283
2631
|
) : null,
|
|
2632
|
+
state.modelPicker.open ? /* @__PURE__ */ jsx(
|
|
2633
|
+
ModelPicker,
|
|
2634
|
+
{
|
|
2635
|
+
step: state.modelPicker.step,
|
|
2636
|
+
providerOptions: state.modelPicker.providerOptions,
|
|
2637
|
+
modelOptions: state.modelPicker.modelOptions,
|
|
2638
|
+
selected: state.modelPicker.selected,
|
|
2639
|
+
pickedProviderId: state.modelPicker.pickedProviderId,
|
|
2640
|
+
hint: state.modelPicker.hint
|
|
2641
|
+
}
|
|
2642
|
+
) : null,
|
|
2643
|
+
state.confirm ? /* @__PURE__ */ jsx(
|
|
2644
|
+
ConfirmPrompt,
|
|
2645
|
+
{
|
|
2646
|
+
toolName: state.confirm.toolName,
|
|
2647
|
+
input: state.confirm.input,
|
|
2648
|
+
suggestedPattern: state.confirm.suggestedPattern,
|
|
2649
|
+
onDecision: state.confirm.resolve
|
|
2650
|
+
}
|
|
2651
|
+
) : null,
|
|
2284
2652
|
/* @__PURE__ */ jsx(
|
|
2285
2653
|
StatusBar,
|
|
2286
2654
|
{
|
|
2287
|
-
model
|
|
2655
|
+
model: `${liveProvider}/${liveModel}`,
|
|
2288
2656
|
state: state.status,
|
|
2289
2657
|
tokenCounter,
|
|
2290
2658
|
hint: renderRunningTools(state.runningTools) || state.hint,
|
|
@@ -2403,6 +2771,10 @@ async function runTui(opts) {
|
|
|
2403
2771
|
yolo: opts.yolo,
|
|
2404
2772
|
appVersion: opts.appVersion,
|
|
2405
2773
|
provider: opts.provider,
|
|
2774
|
+
family: opts.family,
|
|
2775
|
+
keyTail: opts.keyTail,
|
|
2776
|
+
getPickableProviders: opts.getPickableProviders,
|
|
2777
|
+
switchProviderAndModel: opts.switchProviderAndModel,
|
|
2406
2778
|
effectiveMaxContext: opts.effectiveMaxContext,
|
|
2407
2779
|
onExit
|
|
2408
2780
|
}),
|