@noir-ai/cli 1.14.0 → 1.15.0-beta.1
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 +2 -2
- package/dist/bin.d.ts +10 -3
- package/dist/bin.js +1240 -365
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +39 -12
- package/dist/index.js +1258 -383
- package/dist/index.js.map +1 -1
- package/dist/{output-BNE9htEm.d.ts → output-2T2lGR7a.d.ts} +3 -3
- package/dist/tui/{chunk-6LO4LXEH.js → chunk-5KWETEMF.js} +1191 -352
- package/dist/tui/chunk-5KWETEMF.js.map +1 -0
- package/dist/tui/{chunk-ESZTOQEC.js → chunk-KETMM3BA.js} +25 -3
- package/dist/tui/chunk-KETMM3BA.js.map +1 -0
- package/dist/tui/{chunk-2RK6AFZQ.js → chunk-TPNJEN4B.js} +2 -2
- package/dist/tui/chunk-TPNJEN4B.js.map +1 -0
- package/dist/tui/{create-FD4E626Z.js → create-6KDIFPUL.js} +10 -6
- package/dist/tui/create-6KDIFPUL.js.map +1 -0
- package/dist/tui/index.js +994 -120
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/{install-YGTUSLRO.js → install-KA35ONQP.js} +2 -2
- package/dist/tui/{sync-N633SQSS.js → sync-AII5WCRO.js} +8 -4
- package/dist/tui/sync-AII5WCRO.js.map +1 -0
- package/dist/tui/{update-LET5BWOU.js → update-XGKJ4KBR.js} +3 -3
- package/package.json +11 -11
- package/dist/tui/chunk-2RK6AFZQ.js.map +0 -1
- package/dist/tui/chunk-6LO4LXEH.js.map +0 -1
- package/dist/tui/chunk-ESZTOQEC.js.map +0 -1
- package/dist/tui/create-FD4E626Z.js.map +0 -1
- package/dist/tui/sync-N633SQSS.js.map +0 -1
- /package/dist/tui/{install-YGTUSLRO.js.map → install-KA35ONQP.js.map} +0 -0
- /package/dist/tui/{update-LET5BWOU.js.map → update-XGKJ4KBR.js.map} +0 -0
package/dist/tui/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
INTERRUPT_GRACE_MS,
|
|
3
|
+
RunInterrupt,
|
|
4
|
+
UsageReducer,
|
|
2
5
|
buildPaletteCommandsForTui,
|
|
3
6
|
describeContext,
|
|
4
7
|
describeDaemon,
|
|
@@ -6,24 +9,37 @@ import {
|
|
|
6
9
|
describeStore,
|
|
7
10
|
describeWorkflow,
|
|
8
11
|
gatherStatusPayload,
|
|
12
|
+
humanizeElapsed,
|
|
13
|
+
interruptedNotice,
|
|
9
14
|
isDestructive,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
loadRunConfig,
|
|
16
|
+
mergeEnv,
|
|
17
|
+
normalizeStreamEvent,
|
|
18
|
+
parseStreamLine,
|
|
19
|
+
performPostRunAction,
|
|
20
|
+
postRunActions,
|
|
21
|
+
resolveRunProfile,
|
|
22
|
+
resolveSections,
|
|
23
|
+
runHost,
|
|
24
|
+
runPromptFrom,
|
|
25
|
+
writeTranscript
|
|
26
|
+
} from "./chunk-5KWETEMF.js";
|
|
27
|
+
import "./chunk-TPNJEN4B.js";
|
|
13
28
|
import {
|
|
14
29
|
c,
|
|
15
30
|
contentWidth,
|
|
16
31
|
divider,
|
|
17
32
|
useColor
|
|
18
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-KETMM3BA.js";
|
|
19
34
|
|
|
20
35
|
// src/tui/index.tsx
|
|
21
|
-
import {
|
|
36
|
+
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
37
|
+
import { loadProjectInfo, parseConfig } from "@noir-ai/core";
|
|
22
38
|
import { render } from "ink";
|
|
23
39
|
|
|
24
40
|
// src/tui/App.tsx
|
|
25
|
-
import { Box as
|
|
26
|
-
import { useEffect, useMemo, useState as
|
|
41
|
+
import { Box as Box8, Text as Text11, useApp, useInput as useInput3 } from "ink";
|
|
42
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
|
|
27
43
|
|
|
28
44
|
// src/tui/CommandInput.tsx
|
|
29
45
|
import { Box, Text } from "ink";
|
|
@@ -81,9 +97,13 @@ async function captureProcessOutput(fn) {
|
|
|
81
97
|
import { Text as Text2 } from "ink";
|
|
82
98
|
|
|
83
99
|
// src/tui/hints.ts
|
|
84
|
-
var FOOTER_HINT = "?/h/Ctrl+K palette \xB7 Ctrl+F find \xB7 \u2191/\u2193 scroll \xB7 Enter run \xB7 q/Esc quit \xB7 Ctrl+C exit";
|
|
100
|
+
var FOOTER_HINT = "?/h/Ctrl+K palette \xB7 Ctrl+F find \xB7 Ctrl+T transcripts \xB7 \u2191/\u2193 scroll \xB7 Enter run \xB7 q/Esc quit \xB7 Ctrl+C exit";
|
|
85
101
|
var RUNNING_HINT = "running\u2026 (Ctrl+C to force exit)";
|
|
86
102
|
var LIST_NAV_HINT = "\u2191/\u2193 navigate \xB7 Enter run \xB7 Esc close \xB7 Tab view";
|
|
103
|
+
var RUN_HINT = "streaming \u2014 Esc cancels the host";
|
|
104
|
+
var RUN_CANCEL_HINT = "cancelling\u2026";
|
|
105
|
+
var TRANSCRIPT_LIST_HINT = "\u2191/\u2193 choose \xB7 Enter open \xB7 Esc back";
|
|
106
|
+
var TRANSCRIPT_READ_HINT = "\u2191/\u2193 scroll \xB7 Esc back to list \xB7 read-only";
|
|
87
107
|
var HELP_ENTRIES = [
|
|
88
108
|
{ keys: "/command", desc: "run a Noir sub-command (e.g. /status, /sync, /task next)" },
|
|
89
109
|
{ keys: "Enter", desc: "run the typed /command" },
|
|
@@ -94,8 +114,10 @@ var HELP_ENTRIES = [
|
|
|
94
114
|
{ keys: "h", desc: "palette \u2014 commands (quick actions + all commands)" },
|
|
95
115
|
{ keys: "Ctrl+K", desc: "palette \u2014 all commands (fuzzy search)" },
|
|
96
116
|
{ keys: "Ctrl+F", desc: "palette \u2014 find in the dispatched output" },
|
|
117
|
+
{ keys: "Ctrl+T", desc: "transcripts \u2014 reopen a past run, read-only" },
|
|
97
118
|
{ keys: "Tab", desc: "switch between commands, output, and help" },
|
|
98
119
|
{ keys: "y / n", desc: "approve / decline a destructive command prompt" },
|
|
120
|
+
{ keys: "Esc (in a run)", desc: "cancel the host and return to the dashboard" },
|
|
99
121
|
{ keys: "Ctrl+C", desc: "force exit" }
|
|
100
122
|
];
|
|
101
123
|
|
|
@@ -168,6 +190,10 @@ function useInputBuffer() {
|
|
|
168
190
|
return { buffer, setBuffer, pushHistory, recall, clear, seed };
|
|
169
191
|
}
|
|
170
192
|
|
|
193
|
+
// src/tui/modes/run.tsx
|
|
194
|
+
import { Box as Box4, Text as Text7, useInput } from "ink";
|
|
195
|
+
import { useCallback as useCallback2, useEffect, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
196
|
+
|
|
171
197
|
// src/tui/OutputPane.tsx
|
|
172
198
|
import { Text as Text4 } from "ink";
|
|
173
199
|
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -186,14 +212,15 @@ function OutputPane({
|
|
|
186
212
|
lines,
|
|
187
213
|
scrollOffset,
|
|
188
214
|
height = 12,
|
|
189
|
-
title
|
|
215
|
+
title,
|
|
216
|
+
followTail = false
|
|
190
217
|
}) {
|
|
191
218
|
const width = contentWidth();
|
|
192
219
|
if (lines.length === 0) {
|
|
193
220
|
return /* @__PURE__ */ jsx4(Text4, { children: c.dim("(no output \u2014 type a /command, or ? for help)") });
|
|
194
221
|
}
|
|
195
222
|
const maxOffset = Math.max(0, lines.length - height);
|
|
196
|
-
const offset = Math.min(Math.max(0, scrollOffset), maxOffset);
|
|
223
|
+
const offset = followTail ? maxOffset : Math.min(Math.max(0, scrollOffset), maxOffset);
|
|
197
224
|
const rows = toRows(lines.slice(offset, offset + height));
|
|
198
225
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
199
226
|
title !== void 0 ? /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: c.dim(`\u2500\u2500 ${title} \u2500\u2500`) }) : null,
|
|
@@ -204,7 +231,7 @@ function OutputPane({
|
|
|
204
231
|
] });
|
|
205
232
|
}
|
|
206
233
|
|
|
207
|
-
// src/tui/overlays/
|
|
234
|
+
// src/tui/overlays/PostRunOverlay.tsx
|
|
208
235
|
import { Box as Box3, Text as Text5 } from "ink";
|
|
209
236
|
|
|
210
237
|
// src/tui/Panel.tsx
|
|
@@ -227,12 +254,665 @@ function Panel({ children, maxWidth }) {
|
|
|
227
254
|
);
|
|
228
255
|
}
|
|
229
256
|
|
|
230
|
-
// src/tui/overlays/
|
|
257
|
+
// src/tui/overlays/PostRunOverlay.tsx
|
|
231
258
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
259
|
+
var OVERLAY_WIDTH = 64;
|
|
260
|
+
function truncate(text, max) {
|
|
261
|
+
return text.length > max ? `${text.slice(0, Math.max(1, max - 1))}\u2026` : text;
|
|
262
|
+
}
|
|
263
|
+
function PostRunOverlay({
|
|
264
|
+
options,
|
|
265
|
+
active,
|
|
266
|
+
value,
|
|
267
|
+
working = false,
|
|
268
|
+
result
|
|
269
|
+
}) {
|
|
270
|
+
const listFocused = value === void 0 && !working && result === void 0;
|
|
271
|
+
const elements = [
|
|
272
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
273
|
+
c.bold("\u25B8 run finished "),
|
|
274
|
+
/* @__PURE__ */ jsx6(Text5, { children: c.dim("\xB7 what next?") })
|
|
275
|
+
] }) }, "header")
|
|
276
|
+
];
|
|
277
|
+
for (let i = 0; i < options.length; i++) {
|
|
278
|
+
const row = options[i];
|
|
279
|
+
if (!row) continue;
|
|
280
|
+
const focused = listFocused && i === active;
|
|
281
|
+
const body = focused ? `${truncate(row.label, 28).padEnd(30)}${truncate(row.hint, 30)}` : truncate(row.label, 60);
|
|
282
|
+
elements.push(
|
|
283
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: focused ? c.inverse(`\u25B8 ${body}`) : c.dim(listFocused ? ` ${body}` : `\u25B8 ${body}`) }) }, row.value)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (value !== void 0) {
|
|
287
|
+
elements.push(
|
|
288
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
289
|
+
c.accent("\u25B8 "),
|
|
290
|
+
value.value.length > 0 ? value.value : c.dim(value.placeholder),
|
|
291
|
+
/* @__PURE__ */ jsx6(Text5, { children: c.dim("\u258C") })
|
|
292
|
+
] }) }, "value"),
|
|
293
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: c.dim(`${value.label} \u2014 Enter to continue \xB7 Esc to go back`) }) }, "value-label")
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (value?.notice !== void 0) {
|
|
297
|
+
elements.push(
|
|
298
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: c.warn(value.notice) }) }, "value-notice")
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (working) {
|
|
302
|
+
elements.push(
|
|
303
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: c.dim("working\u2026") }) }, "working")
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (result !== void 0) {
|
|
307
|
+
elements.push(
|
|
308
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: result.ok ? c.ok(result.message) : c.warn(result.message) }) }, "result")
|
|
309
|
+
);
|
|
310
|
+
elements.push(
|
|
311
|
+
/* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { children: c.dim("Enter or Esc to close") }) }, "result-hint")
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
|
|
315
|
+
/* @__PURE__ */ jsx6(Panel, { maxWidth: OVERLAY_WIDTH, children: elements }),
|
|
316
|
+
/* @__PURE__ */ jsx6(Text5, { children: c.dim(
|
|
317
|
+
result !== void 0 ? "Enter close" : value !== void 0 ? "type the value \xB7 Enter continue \xB7 Esc back" : "\u2191/\u2193 choose \xB7 Enter run \xB7 Esc dismiss"
|
|
318
|
+
) })
|
|
319
|
+
] });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/tui/run-stream.ts
|
|
323
|
+
var TOOL_MARK = "\u25CF";
|
|
324
|
+
var RunStream = class {
|
|
325
|
+
reducer = new UsageReducer();
|
|
326
|
+
/** The pane, in arrival order. */
|
|
327
|
+
rows = [];
|
|
328
|
+
sessionId;
|
|
329
|
+
model;
|
|
330
|
+
/** Fold one normalized host event in. */
|
|
331
|
+
apply(event) {
|
|
332
|
+
switch (event.kind) {
|
|
333
|
+
case "init":
|
|
334
|
+
if (event.sessionId !== void 0) this.sessionId = event.sessionId;
|
|
335
|
+
if (event.model !== void 0) this.model = event.model;
|
|
336
|
+
break;
|
|
337
|
+
case "assistant":
|
|
338
|
+
this.commitMessage(event);
|
|
339
|
+
break;
|
|
340
|
+
case "tool":
|
|
341
|
+
this.recordTool(event.name, event.messageId !== void 0);
|
|
342
|
+
break;
|
|
343
|
+
case "delta":
|
|
344
|
+
this.appendText(event.text);
|
|
345
|
+
break;
|
|
346
|
+
default:
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
this.reducer.add(event);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Settle what is still provisional: called once the run has ended, so a run
|
|
353
|
+
* that stopped mid-block keeps the words it had already produced and the
|
|
354
|
+
* tools it had already started instead of losing them (and so a transcript
|
|
355
|
+
* read back from disk renders completely).
|
|
356
|
+
*/
|
|
357
|
+
finalize() {
|
|
358
|
+
for (const row of this.rows) row.pending = false;
|
|
359
|
+
}
|
|
360
|
+
/** Everything the run screen renders, as of now. */
|
|
361
|
+
snapshot() {
|
|
362
|
+
const usage = this.reducer.snapshot();
|
|
363
|
+
const lines = [];
|
|
364
|
+
const answer = [];
|
|
365
|
+
let toolCount = 0;
|
|
366
|
+
for (const row of this.rows) {
|
|
367
|
+
if (row.kind === "tool") {
|
|
368
|
+
toolCount += 1;
|
|
369
|
+
lines.push(`${TOOL_MARK} ${row.text}`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
answer.push(row.text);
|
|
373
|
+
lines.push(...splitRows(row.text));
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
lines,
|
|
377
|
+
// Provisional text counts: a cancelled run's answer is what it managed to
|
|
378
|
+
// say, and `finalize` will have committed it anyway.
|
|
379
|
+
answer: answer.join(""),
|
|
380
|
+
toolCount,
|
|
381
|
+
...this.sessionId === void 0 ? {} : { sessionId: this.sessionId },
|
|
382
|
+
...this.model === void 0 ? {} : { model: this.model },
|
|
383
|
+
inputTokens: usage.inputTokens,
|
|
384
|
+
outputTokens: usage.outputTokens
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Commit a completed assistant message.
|
|
389
|
+
*
|
|
390
|
+
* Its text supersedes the deltas that spelled it out — the host reports the
|
|
391
|
+
* same words twice — and its tool calls are the authoritative word on the
|
|
392
|
+
* calls the incremental feed opened, each taking over the row its own
|
|
393
|
+
* announcement opened. The text lands where its deltas were, which is above
|
|
394
|
+
* the calls it went on to make: a message that says something and then calls
|
|
395
|
+
* a tool reads in that order, rather than the call floating above the
|
|
396
|
+
* sentence that introduced it.
|
|
397
|
+
*/
|
|
398
|
+
commitMessage(event) {
|
|
399
|
+
const opened = this.rows.findIndex((row) => row.kind === "text" && row.pending);
|
|
400
|
+
const at = opened === -1 ? this.rows.length : opened;
|
|
401
|
+
for (let i = this.rows.length - 1; i >= 0; i--) {
|
|
402
|
+
const row = this.rows[i];
|
|
403
|
+
if (row !== void 0 && row.kind === "text" && row.pending) this.rows.splice(i, 1);
|
|
404
|
+
}
|
|
405
|
+
if (event.isError !== true && event.text !== void 0 && event.text.length > 0) {
|
|
406
|
+
this.rows.splice(at, 0, { kind: "text", text: event.text, pending: false });
|
|
407
|
+
}
|
|
408
|
+
for (const name of event.tools ?? []) this.recordTool(name, true);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Record a tool call, confirmed by a completed message or announced by the
|
|
412
|
+
* frame that opened its block. A confirmed call takes over the row the
|
|
413
|
+
* announcement left open; with nothing to take over — the first word on the
|
|
414
|
+
* call, which is all some hosts ever send — it opens a row of its own.
|
|
415
|
+
*/
|
|
416
|
+
recordTool(name, confirmed) {
|
|
417
|
+
if (confirmed) {
|
|
418
|
+
const opened = this.rows.findIndex(
|
|
419
|
+
(row2) => row2.kind === "tool" && row2.pending && row2.text === name
|
|
420
|
+
);
|
|
421
|
+
const row = this.rows[opened];
|
|
422
|
+
if (row !== void 0) {
|
|
423
|
+
row.pending = false;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
this.rows.push({ kind: "tool", text: name, pending: !confirmed });
|
|
428
|
+
}
|
|
429
|
+
/** Extend the stretch of text the host is writing, or start a new one. */
|
|
430
|
+
appendText(text) {
|
|
431
|
+
const last = this.rows[this.rows.length - 1];
|
|
432
|
+
if (last !== void 0 && last.kind === "text" && last.pending) {
|
|
433
|
+
this.rows[this.rows.length - 1] = { kind: "text", text: last.text + text, pending: true };
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
this.rows.push({ kind: "text", text, pending: true });
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
function splitRows(text) {
|
|
440
|
+
const rows = text.split("\n");
|
|
441
|
+
while (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
|
|
442
|
+
return rows;
|
|
443
|
+
}
|
|
444
|
+
function renderTranscript(rawLines) {
|
|
445
|
+
const stream = new RunStream();
|
|
446
|
+
for (const line of rawLines) {
|
|
447
|
+
const raw = parseStreamLine(line);
|
|
448
|
+
if (raw === null) continue;
|
|
449
|
+
const event = normalizeStreamEvent(raw);
|
|
450
|
+
if (event !== null) stream.apply(event);
|
|
451
|
+
}
|
|
452
|
+
stream.finalize();
|
|
453
|
+
return stream.snapshot().lines;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/tui/StatusBar.tsx
|
|
457
|
+
import { Text as Text6 } from "ink";
|
|
458
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
459
|
+
function Cell({
|
|
460
|
+
label,
|
|
461
|
+
value,
|
|
462
|
+
accent
|
|
463
|
+
}) {
|
|
464
|
+
return /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
465
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(`${label}:`) }),
|
|
466
|
+
" ",
|
|
467
|
+
/* @__PURE__ */ jsx7(Text6, { children: accent ? c.accent(value) : value })
|
|
468
|
+
] });
|
|
469
|
+
}
|
|
470
|
+
function StatusBar({ payload, loading, run }) {
|
|
471
|
+
if (run !== void 0) {
|
|
472
|
+
const tools = run.tools !== void 0 && run.tools > 0 ? ` \xB7 ${run.tools} tool${run.tools === 1 ? "" : "s"}` : "";
|
|
473
|
+
return /* @__PURE__ */ jsx7(Text6, { children: /* @__PURE__ */ jsx7(Cell, { label: "run", value: `${run.model} \xB7 ${run.elapsed} \xB7 ${run.tokens}${tools}`, accent: true }) });
|
|
474
|
+
}
|
|
475
|
+
const host = payload?.host ?? "\u2014";
|
|
476
|
+
const mode = payload?.workflow?.mode ?? "\u2014";
|
|
477
|
+
const phase = payload?.workflow?.phase ?? (loading ? "\u2026" : "idle");
|
|
478
|
+
const daemonRunning = payload?.daemon.running === true;
|
|
479
|
+
const daemon = payload === null && loading ? "\u2026" : daemonRunning ? "up" : "down";
|
|
480
|
+
return /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
481
|
+
/* @__PURE__ */ jsx7(Cell, { label: "host", value: host, accent: true }),
|
|
482
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(" \xB7 ") }),
|
|
483
|
+
/* @__PURE__ */ jsx7(Cell, { label: "mode", value: mode }),
|
|
484
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(" \xB7 ") }),
|
|
485
|
+
/* @__PURE__ */ jsx7(Cell, { label: "phase", value: phase }),
|
|
486
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(" \xB7 ") }),
|
|
487
|
+
/* @__PURE__ */ jsxs5(Text6, { children: [
|
|
488
|
+
c.dim("daemon:"),
|
|
489
|
+
" ",
|
|
490
|
+
daemonRunning ? c.ok(daemon) : c.warn(daemon)
|
|
491
|
+
] })
|
|
492
|
+
] });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/tui/modes/run.tsx
|
|
496
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
497
|
+
var EMPTY_STREAM = {
|
|
498
|
+
lines: [],
|
|
499
|
+
answer: "",
|
|
500
|
+
toolCount: 0,
|
|
501
|
+
inputTokens: 0,
|
|
502
|
+
outputTokens: 0
|
|
503
|
+
};
|
|
504
|
+
function tokenCell(input, output) {
|
|
505
|
+
return `\u2193${input.toLocaleString()}\u2191${output.toLocaleString()} tokens`;
|
|
506
|
+
}
|
|
507
|
+
function RunMode({
|
|
508
|
+
prompt,
|
|
509
|
+
deps,
|
|
510
|
+
onExit,
|
|
511
|
+
onQuit = onExit,
|
|
512
|
+
height = 14,
|
|
513
|
+
tickMs = 1e3,
|
|
514
|
+
now = Date.now,
|
|
515
|
+
graceMs = INTERRUPT_GRACE_MS
|
|
516
|
+
}) {
|
|
517
|
+
const [stream, setStream] = useState2(EMPTY_STREAM);
|
|
518
|
+
const [binary, setBinary] = useState2("host");
|
|
519
|
+
const [phase, setPhase] = useState2({ kind: "running" });
|
|
520
|
+
const [elapsedMs, setElapsedMs] = useState2(0);
|
|
521
|
+
const [turn, setTurn] = useState2({ prompt });
|
|
522
|
+
const [valueBuffer, setValueBuffer] = useState2("");
|
|
523
|
+
const [valueNotice, setValueNotice] = useState2(null);
|
|
524
|
+
const [postRun, setPostRun] = useState2(null);
|
|
525
|
+
const [statusHint, setStatusHint] = useState2(null);
|
|
526
|
+
const interruptRef = useRef2(null);
|
|
527
|
+
const rawLinesRef = useRef2([]);
|
|
528
|
+
const startedAtRef = useRef2(now());
|
|
529
|
+
const exitRef = useRef2(onExit);
|
|
530
|
+
useEffect(() => {
|
|
531
|
+
exitRef.current = onExit;
|
|
532
|
+
}, [onExit]);
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
const streamState = new RunStream();
|
|
535
|
+
const interrupt = new RunInterrupt({ graceMs });
|
|
536
|
+
interruptRef.current = interrupt;
|
|
537
|
+
rawLinesRef.current = [];
|
|
538
|
+
startedAtRef.current = now();
|
|
539
|
+
let live = true;
|
|
540
|
+
setStream(streamState.snapshot());
|
|
541
|
+
setElapsedMs(0);
|
|
542
|
+
setPostRun(null);
|
|
543
|
+
setPhase({ kind: "running" });
|
|
544
|
+
setStatusHint(null);
|
|
545
|
+
const tick = setInterval(() => {
|
|
546
|
+
if (live) setElapsedMs(now() - startedAtRef.current);
|
|
547
|
+
}, tickMs);
|
|
548
|
+
let session;
|
|
549
|
+
try {
|
|
550
|
+
session = deps.start(
|
|
551
|
+
turn.prompt,
|
|
552
|
+
{
|
|
553
|
+
onEvent: (event) => {
|
|
554
|
+
streamState.apply(event);
|
|
555
|
+
if (live) setStream(streamState.snapshot());
|
|
556
|
+
},
|
|
557
|
+
onLine: (line) => {
|
|
558
|
+
rawLinesRef.current.push(line);
|
|
559
|
+
},
|
|
560
|
+
signal: interrupt.signal,
|
|
561
|
+
onChild: (child) => interrupt.track(child)
|
|
562
|
+
},
|
|
563
|
+
turn.sessionId
|
|
564
|
+
);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
clearInterval(tick);
|
|
567
|
+
setPhase({
|
|
568
|
+
kind: "ended",
|
|
569
|
+
ok: false,
|
|
570
|
+
message: err instanceof Error ? err.message : String(err)
|
|
571
|
+
});
|
|
572
|
+
return () => {
|
|
573
|
+
live = false;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
setBinary(session.binary);
|
|
577
|
+
void session.done.then(async (result) => {
|
|
578
|
+
streamState.finalize();
|
|
579
|
+
const snapshot = streamState.snapshot();
|
|
580
|
+
const cancelled = interrupt.signal.aborted;
|
|
581
|
+
const transcript = await deps.transcripts?.write(rawLinesRef.current) ?? "(not persisted)";
|
|
582
|
+
if (!live) return;
|
|
583
|
+
setStream(snapshot);
|
|
584
|
+
if (cancelled) {
|
|
585
|
+
exitRef.current(interruptedNotice(transcript));
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (result.isError || result.exitCode !== 0) {
|
|
589
|
+
setPhase({ kind: "ended", ok: false, message: failureText(result, session.binary) });
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
setPostRun({
|
|
593
|
+
answer: snapshot.answer,
|
|
594
|
+
transcript,
|
|
595
|
+
host: session.binary,
|
|
596
|
+
...snapshot.sessionId === void 0 ? {} : { sessionId: snapshot.sessionId },
|
|
597
|
+
canContinue: snapshot.sessionId !== void 0
|
|
598
|
+
});
|
|
599
|
+
setPhase({ kind: "menu", active: 0 });
|
|
600
|
+
}).catch((err) => {
|
|
601
|
+
if (!live) return;
|
|
602
|
+
setPhase({
|
|
603
|
+
kind: "ended",
|
|
604
|
+
ok: false,
|
|
605
|
+
message: `could not run ${session.binary}: ${err instanceof Error ? err.message : String(err)}`
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
return () => {
|
|
609
|
+
live = false;
|
|
610
|
+
clearInterval(tick);
|
|
611
|
+
interrupt.terminate();
|
|
612
|
+
};
|
|
613
|
+
}, [turn, deps, now, tickMs, graceMs]);
|
|
614
|
+
const options = useMemo(() => postRun === null ? [] : deps.actions(postRun), [postRun, deps]);
|
|
615
|
+
const choose = useCallback2(
|
|
616
|
+
(choice, value) => {
|
|
617
|
+
if (postRun === null) return;
|
|
618
|
+
const label = options.find((o) => o.value === choice)?.label ?? "That action";
|
|
619
|
+
if (choice === "dismiss") {
|
|
620
|
+
onExit("run finished");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (choice === "resume") {
|
|
624
|
+
const sessionId = postRun.sessionId;
|
|
625
|
+
if (sessionId === void 0 || value === void 0 || value.trim().length === 0) {
|
|
626
|
+
setValueNotice("a follow-up prompt is required");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
setValueBuffer("");
|
|
630
|
+
setValueNotice(null);
|
|
631
|
+
setTurn({ prompt: value.trim(), sessionId });
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
setPhase({ kind: "working", message: label });
|
|
635
|
+
void (async () => {
|
|
636
|
+
let outcome = { ok: true, message: "" };
|
|
637
|
+
const captured = await captureProcessOutput(async () => {
|
|
638
|
+
outcome = await deps.perform(postRun, choice, value);
|
|
639
|
+
});
|
|
640
|
+
const written = `${captured.stdout}${captured.stderr}`.trim();
|
|
641
|
+
setPhase({
|
|
642
|
+
kind: "result",
|
|
643
|
+
ok: outcome.ok,
|
|
644
|
+
message: [outcome.message, outcome.detail, written].filter((part) => part !== void 0 && part.length > 0).join(" \u2014 ").replace(/\s+/g, " ")
|
|
645
|
+
});
|
|
646
|
+
})();
|
|
647
|
+
},
|
|
648
|
+
[deps, onExit, options, postRun]
|
|
649
|
+
);
|
|
650
|
+
const submitValue = useCallback2(
|
|
651
|
+
(option) => {
|
|
652
|
+
const value = valueBuffer;
|
|
653
|
+
if (option.needsValue !== void 0 && value.trim().length === 0 && option.value !== "save") {
|
|
654
|
+
setValueNotice(`a ${option.needsValue.label} is required`);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
setValueBuffer("");
|
|
658
|
+
setValueNotice(null);
|
|
659
|
+
choose(option.value, value);
|
|
660
|
+
},
|
|
661
|
+
[choose, valueBuffer]
|
|
662
|
+
);
|
|
663
|
+
useInput((input, key) => {
|
|
664
|
+
if (key.ctrl && input === "c") {
|
|
665
|
+
if (phase.kind === "running") {
|
|
666
|
+
const interrupt = interruptRef.current;
|
|
667
|
+
if (interrupt === null) return;
|
|
668
|
+
if (interrupt.signal.aborted) {
|
|
669
|
+
interrupt.forceNow();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
interrupt.terminate();
|
|
673
|
+
setStatusHint(RUN_CANCEL_HINT);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
onQuit();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (phase.kind === "running") {
|
|
680
|
+
if (key.escape) {
|
|
681
|
+
interruptRef.current?.terminate();
|
|
682
|
+
setStatusHint(RUN_CANCEL_HINT);
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (phase.kind === "ended") {
|
|
687
|
+
if (key.return || key.escape) onExit();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (phase.kind === "result") {
|
|
691
|
+
if (key.return || key.escape) setPhase({ kind: "menu", active: 0 });
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (phase.kind === "working") return;
|
|
695
|
+
if (phase.kind === "value") {
|
|
696
|
+
if (key.escape) {
|
|
697
|
+
setValueBuffer("");
|
|
698
|
+
setValueNotice(null);
|
|
699
|
+
setPhase({ kind: "menu", active: 0 });
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (key.return) {
|
|
703
|
+
submitValue(phase.option);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (key.backspace || key.delete) {
|
|
707
|
+
setValueBuffer((b) => b.slice(0, -1));
|
|
708
|
+
setValueNotice(null);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (key.ctrl || key.tab || key.upArrow || key.downArrow) return;
|
|
712
|
+
if (input.length > 0) {
|
|
713
|
+
setValueBuffer((b) => b + input);
|
|
714
|
+
setValueNotice(null);
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (key.escape) {
|
|
719
|
+
onExit("run finished");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (key.return) {
|
|
723
|
+
const option = options[phase.active];
|
|
724
|
+
if (option === void 0) return;
|
|
725
|
+
if (option.needsValue === void 0) {
|
|
726
|
+
choose(option.value);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
setValueBuffer("");
|
|
730
|
+
setValueNotice(null);
|
|
731
|
+
setPhase({ kind: "value", option });
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (key.upArrow) {
|
|
735
|
+
setPhase({ kind: "menu", active: Math.max(0, phase.active - 1) });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (key.downArrow) {
|
|
739
|
+
setPhase({
|
|
740
|
+
kind: "menu",
|
|
741
|
+
active: Math.min(Math.max(0, options.length - 1), phase.active + 1)
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
const valueStep = phase.kind === "value" && phase.option.needsValue !== void 0 ? {
|
|
746
|
+
label: phase.option.needsValue.label,
|
|
747
|
+
placeholder: phase.option.needsValue.placeholder,
|
|
748
|
+
value: valueBuffer,
|
|
749
|
+
...valueNotice === null ? {} : { notice: valueNotice }
|
|
750
|
+
} : void 0;
|
|
751
|
+
const overlay = phase.kind === "menu" || phase.kind === "value" || phase.kind === "working" || phase.kind === "result" ? /* @__PURE__ */ jsx8(
|
|
752
|
+
PostRunOverlay,
|
|
753
|
+
{
|
|
754
|
+
options,
|
|
755
|
+
active: phase.kind === "menu" ? phase.active : 0,
|
|
756
|
+
...valueStep === void 0 ? {} : { value: valueStep },
|
|
757
|
+
...phase.kind === "working" ? { working: true } : {},
|
|
758
|
+
...phase.kind === "result" ? { result: { ok: phase.ok, message: phase.message } } : {}
|
|
759
|
+
}
|
|
760
|
+
) : null;
|
|
761
|
+
return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
|
|
762
|
+
/* @__PURE__ */ jsx8(Header, { tagline: "run" }),
|
|
763
|
+
/* @__PURE__ */ jsxs6(Panel, { children: [
|
|
764
|
+
/* @__PURE__ */ jsx8(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx8(
|
|
765
|
+
StatusBar,
|
|
766
|
+
{
|
|
767
|
+
payload: null,
|
|
768
|
+
run: {
|
|
769
|
+
model: stream.model ?? binary,
|
|
770
|
+
elapsed: humanizeElapsed(elapsedMs),
|
|
771
|
+
tokens: tokenCell(stream.inputTokens, stream.outputTokens),
|
|
772
|
+
tools: stream.toolCount
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
) }),
|
|
776
|
+
/* @__PURE__ */ jsx8(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx8(Text7, { children: divider() }) }),
|
|
777
|
+
/* @__PURE__ */ jsx8(Box4, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsx8(
|
|
778
|
+
OutputPane,
|
|
779
|
+
{
|
|
780
|
+
lines: stream.lines,
|
|
781
|
+
scrollOffset: 0,
|
|
782
|
+
height,
|
|
783
|
+
title: prompt,
|
|
784
|
+
followTail: true
|
|
785
|
+
}
|
|
786
|
+
) })
|
|
787
|
+
] }),
|
|
788
|
+
phase.kind === "running" ? /* @__PURE__ */ jsx8(Text7, { children: c.dim(statusHint ?? RUN_HINT) }) : null,
|
|
789
|
+
phase.kind === "ended" ? /* @__PURE__ */ jsx8(Text7, { children: phase.ok ? c.ok(phase.message) : c.warn(phase.message) }) : null,
|
|
790
|
+
overlay
|
|
791
|
+
] });
|
|
792
|
+
}
|
|
793
|
+
function failureText(result, binary) {
|
|
794
|
+
const reason = result.errorText !== void 0 && result.errorText.trim().length > 0 ? result.errorText.trim() : `exit code ${result.exitCode}`;
|
|
795
|
+
return `${binary} failed: ${reason}`;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/tui/modes/transcripts.tsx
|
|
799
|
+
import { Box as Box5, Text as Text8, useInput as useInput2 } from "ink";
|
|
800
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
801
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
802
|
+
function TranscriptPicker({
|
|
803
|
+
transcripts,
|
|
804
|
+
onExit,
|
|
805
|
+
height = 14
|
|
806
|
+
}) {
|
|
807
|
+
const [entries, setEntries] = useState3([]);
|
|
808
|
+
const [loading, setLoading] = useState3(true);
|
|
809
|
+
const [active, setActive] = useState3(0);
|
|
810
|
+
const [view, setView] = useState3({ kind: "list" });
|
|
811
|
+
const [scroll, setScroll] = useState3(0);
|
|
812
|
+
useEffect2(() => {
|
|
813
|
+
let live = true;
|
|
814
|
+
void transcripts.list().then((found) => {
|
|
815
|
+
if (live) setEntries(found);
|
|
816
|
+
}).catch(() => {
|
|
817
|
+
if (live) setEntries([]);
|
|
818
|
+
}).finally(() => {
|
|
819
|
+
if (live) setLoading(false);
|
|
820
|
+
});
|
|
821
|
+
return () => {
|
|
822
|
+
live = false;
|
|
823
|
+
};
|
|
824
|
+
}, [transcripts]);
|
|
825
|
+
useInput2((_input, key) => {
|
|
826
|
+
if (loading) {
|
|
827
|
+
if (key.escape) onExit();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (view.kind === "list") {
|
|
831
|
+
if (key.escape) {
|
|
832
|
+
onExit();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (key.return) {
|
|
836
|
+
const entry = entries[active];
|
|
837
|
+
if (entry === void 0) return;
|
|
838
|
+
void transcripts.read(entry.path).then((lines) => {
|
|
839
|
+
setView({ kind: "open", name: entry.name, lines: renderTranscript(lines) });
|
|
840
|
+
setScroll(0);
|
|
841
|
+
}).catch(() => {
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (key.upArrow) {
|
|
846
|
+
setActive((a) => Math.max(0, a - 1));
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (key.downArrow) {
|
|
850
|
+
setActive((a) => Math.min(Math.max(0, entries.length - 1), a + 1));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (key.escape) {
|
|
856
|
+
setView({ kind: "list" });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (key.upArrow) setScroll((s) => Math.max(0, s - 1));
|
|
860
|
+
if (key.downArrow) setScroll((s) => s + 1);
|
|
861
|
+
});
|
|
862
|
+
if (loading) {
|
|
863
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
864
|
+
/* @__PURE__ */ jsx9(Header, { tagline: "transcripts" }),
|
|
865
|
+
/* @__PURE__ */ jsx9(Panel, { children: /* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { children: c.dim("loading transcripts\u2026") }) }) }),
|
|
866
|
+
/* @__PURE__ */ jsx9(Text8, { children: c.dim("Esc back") })
|
|
867
|
+
] });
|
|
868
|
+
}
|
|
869
|
+
if (view.kind === "open") {
|
|
870
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
871
|
+
/* @__PURE__ */ jsx9(Header, { tagline: `transcripts \xB7 ${view.name}` }),
|
|
872
|
+
/* @__PURE__ */ jsx9(Panel, { children: /* @__PURE__ */ jsx9(Box5, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsx9(
|
|
873
|
+
OutputPane,
|
|
874
|
+
{
|
|
875
|
+
lines: view.lines.length > 0 ? view.lines : ["(empty transcript)"],
|
|
876
|
+
scrollOffset: scroll,
|
|
877
|
+
height,
|
|
878
|
+
title: view.name
|
|
879
|
+
}
|
|
880
|
+
) }) }),
|
|
881
|
+
/* @__PURE__ */ jsx9(Text8, { children: c.dim(TRANSCRIPT_READ_HINT) })
|
|
882
|
+
] });
|
|
883
|
+
}
|
|
884
|
+
if (entries.length === 0) {
|
|
885
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
886
|
+
/* @__PURE__ */ jsx9(Header, { tagline: "transcripts" }),
|
|
887
|
+
/* @__PURE__ */ jsx9(Panel, { children: /* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { children: c.dim("no transcripts yet \u2014 a run leaves one here") }) }) }),
|
|
888
|
+
/* @__PURE__ */ jsx9(Text8, { children: c.dim("Esc back") })
|
|
889
|
+
] });
|
|
890
|
+
}
|
|
891
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
892
|
+
/* @__PURE__ */ jsx9(Header, { tagline: "transcripts" }),
|
|
893
|
+
/* @__PURE__ */ jsxs7(Panel, { children: [
|
|
894
|
+
/* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
895
|
+
c.bold("\u25B8 recent transcripts "),
|
|
896
|
+
/* @__PURE__ */ jsx9(Text8, { children: c.dim("\xB7 newest first") })
|
|
897
|
+
] }) }),
|
|
898
|
+
entries.map((entry, i) => {
|
|
899
|
+
const focused = i === active;
|
|
900
|
+
const when = new Date(entry.mtimeMs).toISOString().replace("T", " ").slice(0, 19);
|
|
901
|
+
const row = `${entry.name} \xB7 ${when} \xB7 ${entry.sizeBytes} bytes`;
|
|
902
|
+
return /* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { children: focused ? c.inverse(`\u25B8 ${row}`) : c.dim(` ${row}`) }) }, entry.path);
|
|
903
|
+
})
|
|
904
|
+
] }),
|
|
905
|
+
/* @__PURE__ */ jsx9(Text8, { children: c.dim(TRANSCRIPT_LIST_HINT) })
|
|
906
|
+
] });
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/tui/overlays/ConfirmOverlay.tsx
|
|
910
|
+
import { Box as Box6, Text as Text9 } from "ink";
|
|
911
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
232
912
|
function ConfirmOverlay({ argv }) {
|
|
233
|
-
return /* @__PURE__ */
|
|
913
|
+
return /* @__PURE__ */ jsx10(Panel, { children: /* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
234
914
|
c.warn(`! run /${argv.join(" ")}? (y/N)`),
|
|
235
|
-
/* @__PURE__ */
|
|
915
|
+
/* @__PURE__ */ jsx10(Text9, { children: c.dim(" [y approve \xB7 n/Esc back to palette]") })
|
|
236
916
|
] }) }) });
|
|
237
917
|
}
|
|
238
918
|
|
|
@@ -329,7 +1009,7 @@ var handRolledMatcher = {
|
|
|
329
1009
|
};
|
|
330
1010
|
|
|
331
1011
|
// src/tui/palette/Palette.tsx
|
|
332
|
-
import { Box as
|
|
1012
|
+
import { Box as Box7, Text as Text10 } from "ink";
|
|
333
1013
|
|
|
334
1014
|
// src/tui/palette/output.ts
|
|
335
1015
|
function computeMatches(lines, query) {
|
|
@@ -357,6 +1037,20 @@ function recentDeduped(recent, commands) {
|
|
|
357
1037
|
const known = new Set(commands.map((cmd) => cmd.id));
|
|
358
1038
|
return recent.filter((cmd) => known.has(cmd.id));
|
|
359
1039
|
}
|
|
1040
|
+
function argRequirements(commands, homeSections) {
|
|
1041
|
+
const out = /* @__PURE__ */ new Map();
|
|
1042
|
+
for (const cmd of commands) {
|
|
1043
|
+
if (cmd.needsArg !== void 0) out.set(cmd.argv.join(" "), cmd.needsArg);
|
|
1044
|
+
}
|
|
1045
|
+
for (const { action } of flattenHome(homeSections)) {
|
|
1046
|
+
const label = action.needsArg?.label;
|
|
1047
|
+
if (label !== void 0) out.set((action.dispatch ?? [action.id]).join(" "), label);
|
|
1048
|
+
}
|
|
1049
|
+
return out;
|
|
1050
|
+
}
|
|
1051
|
+
function requirementFor(requirements, argv, id) {
|
|
1052
|
+
return requirements.get(argv.join(" ")) ?? requirements.get(id);
|
|
1053
|
+
}
|
|
360
1054
|
function buildPaletteRows(input) {
|
|
361
1055
|
const { corpus, query, commands, matcher, recent, homeSections, outputLines } = input;
|
|
362
1056
|
if (corpus === "help") {
|
|
@@ -414,6 +1108,7 @@ function buildPaletteRows(input) {
|
|
|
414
1108
|
group: "matches"
|
|
415
1109
|
}));
|
|
416
1110
|
}
|
|
1111
|
+
const args = argRequirements(commands, homeSections);
|
|
417
1112
|
if (query.length === 0) {
|
|
418
1113
|
const rows = [];
|
|
419
1114
|
const recents = recentDeduped(recent, commands);
|
|
@@ -425,7 +1120,8 @@ function buildPaletteRows(input) {
|
|
|
425
1120
|
secondary: cmd.description,
|
|
426
1121
|
argv: cmd.argv,
|
|
427
1122
|
destructive: cmd.destructive,
|
|
428
|
-
group: "recent"
|
|
1123
|
+
group: "recent",
|
|
1124
|
+
needsArg: requirementFor(args, cmd.argv, cmd.id)
|
|
429
1125
|
});
|
|
430
1126
|
}
|
|
431
1127
|
const homeIds = /* @__PURE__ */ new Set();
|
|
@@ -439,7 +1135,8 @@ function buildPaletteRows(input) {
|
|
|
439
1135
|
secondary: action.hint,
|
|
440
1136
|
argv,
|
|
441
1137
|
destructive: action.destructive ?? false,
|
|
442
|
-
group
|
|
1138
|
+
group,
|
|
1139
|
+
needsArg: requirementFor(args, argv, action.id)
|
|
443
1140
|
});
|
|
444
1141
|
}
|
|
445
1142
|
for (const cmd of commands) {
|
|
@@ -450,7 +1147,8 @@ function buildPaletteRows(input) {
|
|
|
450
1147
|
secondary: cmd.description,
|
|
451
1148
|
argv: cmd.argv,
|
|
452
1149
|
destructive: cmd.destructive,
|
|
453
|
-
group: cmd.category
|
|
1150
|
+
group: cmd.category,
|
|
1151
|
+
needsArg: requirementFor(args, cmd.argv, cmd.id)
|
|
454
1152
|
});
|
|
455
1153
|
}
|
|
456
1154
|
return rows;
|
|
@@ -461,12 +1159,13 @@ function buildPaletteRows(input) {
|
|
|
461
1159
|
secondary: m.item.description,
|
|
462
1160
|
argv: m.item.argv,
|
|
463
1161
|
destructive: m.item.destructive,
|
|
464
|
-
group: null
|
|
1162
|
+
group: null,
|
|
1163
|
+
needsArg: requirementFor(args, m.item.argv, m.item.id)
|
|
465
1164
|
}));
|
|
466
1165
|
}
|
|
467
1166
|
|
|
468
1167
|
// src/tui/palette/Palette.tsx
|
|
469
|
-
import { jsx as
|
|
1168
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
470
1169
|
var PALETTE_WIDTH = 64;
|
|
471
1170
|
var ROW_TEXT_WIDTH = PALETTE_WIDTH - 6;
|
|
472
1171
|
var LABEL_WIDTH = 26;
|
|
@@ -495,22 +1194,31 @@ function placeholder(corpus) {
|
|
|
495
1194
|
if (corpus === "output") return "type to filter output";
|
|
496
1195
|
return "keybindings";
|
|
497
1196
|
}
|
|
498
|
-
function Palette({ corpus, query, active, rows }) {
|
|
1197
|
+
function Palette({ corpus, query, active, rows, arg }) {
|
|
499
1198
|
const total = rows.length;
|
|
500
1199
|
const scroll = Math.max(0, Math.min(active - (VISIBLE_ROWS - 1), total - VISIBLE_ROWS));
|
|
501
1200
|
const visible = rows.slice(scroll, scroll + VISIBLE_ROWS);
|
|
502
1201
|
const activeIndex = Math.min(Math.max(active - scroll, 0), Math.max(0, visible.length - 1));
|
|
1202
|
+
const input = arg === void 0 ? { marker: c.dim("> "), body: query.length > 0 ? query : c.dim(placeholder(corpus)) } : {
|
|
1203
|
+
marker: c.accent("\u25B8 "),
|
|
1204
|
+
body: arg.value.length > 0 ? arg.value : c.dim(arg.placeholder)
|
|
1205
|
+
};
|
|
503
1206
|
const elements = [
|
|
504
|
-
/* @__PURE__ */
|
|
1207
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
505
1208
|
c.bold("\u25B8 palette "),
|
|
506
|
-
/* @__PURE__ */
|
|
1209
|
+
/* @__PURE__ */ jsx11(Text10, { children: c.dim(`\xB7 ${corpus}`) })
|
|
507
1210
|
] }) }, "header"),
|
|
508
|
-
/* @__PURE__ */
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
/* @__PURE__ */
|
|
1211
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1212
|
+
input.marker,
|
|
1213
|
+
input.body,
|
|
1214
|
+
/* @__PURE__ */ jsx11(Text10, { children: c.dim("\u258C") })
|
|
512
1215
|
] }) }, "query")
|
|
513
1216
|
];
|
|
1217
|
+
if (arg?.hint !== void 0) {
|
|
1218
|
+
elements.push(
|
|
1219
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { children: c.warn(arg.hint) }) }, "arg-hint")
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
514
1222
|
let lastGroup = null;
|
|
515
1223
|
for (let i = 0; i < visible.length; i++) {
|
|
516
1224
|
const row = visible[i];
|
|
@@ -518,27 +1226,32 @@ function Palette({ corpus, query, active, rows }) {
|
|
|
518
1226
|
if (row.group !== null && row.group !== lastGroup) {
|
|
519
1227
|
if (lastGroup !== null) {
|
|
520
1228
|
elements.push(
|
|
521
|
-
/* @__PURE__ */
|
|
1229
|
+
/* @__PURE__ */ jsx11(Box7, { children: /* @__PURE__ */ jsx11(Text10, { children: " " }) }, `gap:${row.group}`)
|
|
522
1230
|
);
|
|
523
1231
|
}
|
|
524
1232
|
elements.push(
|
|
525
|
-
/* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { children: c.bold(`\u2500\u2500 ${row.group} \u2500\u2500`) }) }, `cat:${row.group}`)
|
|
526
1234
|
);
|
|
527
1235
|
lastGroup = row.group;
|
|
528
1236
|
}
|
|
529
1237
|
const isActive = i === activeIndex;
|
|
530
|
-
const
|
|
1238
|
+
const focused = isActive && arg === void 0;
|
|
1239
|
+
const prefix = focused ? "\u25B8 " : " ";
|
|
531
1240
|
const label = truncateLabel(row.primary);
|
|
532
1241
|
const labelCol = (prefix + label).padEnd(LABEL_WIDTH);
|
|
533
1242
|
const hintRaw = row.argv ? `/${row.argv.join(" ")}` : row.secondary;
|
|
534
1243
|
const hint = hintRaw.length > HINT_WIDTH ? `${hintRaw.slice(0, HINT_WIDTH - 1)}\u2026` : hintRaw;
|
|
535
|
-
if (
|
|
1244
|
+
if (focused) {
|
|
536
1245
|
elements.push(
|
|
537
|
-
/* @__PURE__ */
|
|
1246
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { children: c.inverse(`${labelCol}${hint}`) }) }, row.key)
|
|
1247
|
+
);
|
|
1248
|
+
} else if (arg !== void 0) {
|
|
1249
|
+
elements.push(
|
|
1250
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { children: c.dim(`${labelCol}${hint}`) }) }, row.key)
|
|
538
1251
|
);
|
|
539
1252
|
} else {
|
|
540
1253
|
elements.push(
|
|
541
|
-
/* @__PURE__ */
|
|
1254
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
542
1255
|
c.bold(labelCol),
|
|
543
1256
|
hint.length > 0 ? c.dim(hint) : null
|
|
544
1257
|
] }) }, row.key)
|
|
@@ -550,54 +1263,19 @@ function Palette({ corpus, query, active, rows }) {
|
|
|
550
1263
|
const line = detailLines[di] ?? "";
|
|
551
1264
|
const prefix2 = di === 0 ? " \u21B3 " : " ";
|
|
552
1265
|
elements.push(
|
|
553
|
-
/* @__PURE__ */
|
|
1266
|
+
/* @__PURE__ */ jsx11(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { children: c.dim(`${prefix2}${line}`) }) }, `${row.key}:d:${di}`)
|
|
554
1267
|
);
|
|
555
1268
|
}
|
|
556
1269
|
}
|
|
557
1270
|
}
|
|
558
|
-
return /* @__PURE__ */
|
|
559
|
-
/* @__PURE__ */
|
|
560
|
-
/* @__PURE__ */
|
|
561
|
-
] });
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// src/tui/StatusBar.tsx
|
|
565
|
-
import { Text as Text7 } from "ink";
|
|
566
|
-
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
567
|
-
function Cell({
|
|
568
|
-
label,
|
|
569
|
-
value,
|
|
570
|
-
accent
|
|
571
|
-
}) {
|
|
572
|
-
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
573
|
-
/* @__PURE__ */ jsx8(Text7, { children: c.dim(`${label}:`) }),
|
|
574
|
-
" ",
|
|
575
|
-
/* @__PURE__ */ jsx8(Text7, { children: accent ? c.accent(value) : value })
|
|
576
|
-
] });
|
|
577
|
-
}
|
|
578
|
-
function StatusBar({ payload, loading }) {
|
|
579
|
-
const host = payload?.host ?? "\u2014";
|
|
580
|
-
const mode = payload?.workflow?.mode ?? "\u2014";
|
|
581
|
-
const phase = payload?.workflow?.phase ?? (loading ? "\u2026" : "idle");
|
|
582
|
-
const daemonRunning = payload?.daemon.running === true;
|
|
583
|
-
const daemon = payload === null && loading ? "\u2026" : daemonRunning ? "up" : "down";
|
|
584
|
-
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
585
|
-
/* @__PURE__ */ jsx8(Cell, { label: "host", value: host, accent: true }),
|
|
586
|
-
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
587
|
-
/* @__PURE__ */ jsx8(Cell, { label: "mode", value: mode }),
|
|
588
|
-
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
589
|
-
/* @__PURE__ */ jsx8(Cell, { label: "phase", value: phase }),
|
|
590
|
-
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
591
|
-
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
592
|
-
c.dim("daemon:"),
|
|
593
|
-
" ",
|
|
594
|
-
daemonRunning ? c.ok(daemon) : c.warn(daemon)
|
|
595
|
-
] })
|
|
1271
|
+
return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
|
|
1272
|
+
/* @__PURE__ */ jsx11(Panel, { maxWidth: PALETTE_WIDTH, children: elements }),
|
|
1273
|
+
/* @__PURE__ */ jsx11(Text10, { children: c.dim(LIST_NAV_HINT) })
|
|
596
1274
|
] });
|
|
597
1275
|
}
|
|
598
1276
|
|
|
599
1277
|
// src/tui/App.tsx
|
|
600
|
-
import { jsx as
|
|
1278
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
601
1279
|
function App({
|
|
602
1280
|
deps,
|
|
603
1281
|
initialPayload = null,
|
|
@@ -606,20 +1284,22 @@ function App({
|
|
|
606
1284
|
}) {
|
|
607
1285
|
const { exit } = useApp();
|
|
608
1286
|
const { buffer, setBuffer, pushHistory, recall, clear, seed } = useInputBuffer();
|
|
609
|
-
const [mode, setMode] =
|
|
610
|
-
const [payload, setPayload] =
|
|
611
|
-
const [loading, setLoading] =
|
|
612
|
-
const [output, setOutput] =
|
|
613
|
-
const [searchLines, setSearchLines] =
|
|
614
|
-
const [scrollOffset, setScrollOffset] =
|
|
615
|
-
const [running, setRunning] =
|
|
616
|
-
const [notice, setNotice] =
|
|
617
|
-
const [pending, setPending] =
|
|
618
|
-
const [recent, setRecent] =
|
|
619
|
-
const [homeSections, setHomeSections] =
|
|
620
|
-
const [paletteQuery, setPaletteQuery] =
|
|
621
|
-
const [paletteActive, setPaletteActive] =
|
|
622
|
-
|
|
1287
|
+
const [mode, setMode] = useState4(initialMode);
|
|
1288
|
+
const [payload, setPayload] = useState4(initialPayload);
|
|
1289
|
+
const [loading, setLoading] = useState4(initialPayload === null);
|
|
1290
|
+
const [output, setOutput] = useState4(null);
|
|
1291
|
+
const [searchLines, setSearchLines] = useState4([]);
|
|
1292
|
+
const [scrollOffset, setScrollOffset] = useState4(0);
|
|
1293
|
+
const [running, setRunning] = useState4(false);
|
|
1294
|
+
const [notice, setNotice] = useState4(null);
|
|
1295
|
+
const [pending, setPending] = useState4(null);
|
|
1296
|
+
const [recent, setRecent] = useState4([]);
|
|
1297
|
+
const [homeSections, setHomeSections] = useState4([]);
|
|
1298
|
+
const [paletteQuery, setPaletteQuery] = useState4("");
|
|
1299
|
+
const [paletteActive, setPaletteActive] = useState4(0);
|
|
1300
|
+
const [argBuffer, setArgBuffer] = useState4("");
|
|
1301
|
+
const [argNotice, setArgNotice] = useState4(null);
|
|
1302
|
+
useEffect3(() => {
|
|
623
1303
|
if (!deps.loadRecent) return;
|
|
624
1304
|
const loader = deps.loadRecent;
|
|
625
1305
|
let cancelled = false;
|
|
@@ -634,7 +1314,7 @@ function App({
|
|
|
634
1314
|
cancelled = true;
|
|
635
1315
|
};
|
|
636
1316
|
}, [deps.loadRecent, seed]);
|
|
637
|
-
|
|
1317
|
+
useEffect3(() => {
|
|
638
1318
|
let cancelled = false;
|
|
639
1319
|
void resolveSections(deps.commands ?? []).then((sections) => {
|
|
640
1320
|
if (!cancelled) setHomeSections(sections);
|
|
@@ -643,7 +1323,7 @@ function App({
|
|
|
643
1323
|
cancelled = true;
|
|
644
1324
|
};
|
|
645
1325
|
}, [deps.commands]);
|
|
646
|
-
|
|
1326
|
+
useEffect3(() => {
|
|
647
1327
|
if (running || mode.kind !== "dashboard") return;
|
|
648
1328
|
let cancelled = false;
|
|
649
1329
|
let inFlight = false;
|
|
@@ -668,7 +1348,7 @@ function App({
|
|
|
668
1348
|
clearInterval(id);
|
|
669
1349
|
};
|
|
670
1350
|
}, [running, mode.kind, refreshMs, deps.fetchStatus]);
|
|
671
|
-
|
|
1351
|
+
useEffect3(() => {
|
|
672
1352
|
if (pending === null) return;
|
|
673
1353
|
const argv = pending;
|
|
674
1354
|
let active = true;
|
|
@@ -700,6 +1380,14 @@ function App({
|
|
|
700
1380
|
setMode({ kind: "confirm", argv: [...argv] });
|
|
701
1381
|
return;
|
|
702
1382
|
}
|
|
1383
|
+
enterRunOrDispatch(argv);
|
|
1384
|
+
}
|
|
1385
|
+
function enterRunOrDispatch(argv) {
|
|
1386
|
+
const prompt = deps.run === void 0 ? null : runPromptFrom(argv);
|
|
1387
|
+
if (prompt !== null) {
|
|
1388
|
+
setMode({ kind: "run", prompt });
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
703
1391
|
dispatchCmd([...argv]);
|
|
704
1392
|
void recordRuns([...argv]);
|
|
705
1393
|
pushHistory(`/${argv.join(" ")}`);
|
|
@@ -777,6 +1465,10 @@ function App({
|
|
|
777
1465
|
openOutputSearch();
|
|
778
1466
|
return;
|
|
779
1467
|
}
|
|
1468
|
+
if (input === "t" && deps.run?.transcripts !== void 0) {
|
|
1469
|
+
setMode({ kind: "transcripts" });
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
780
1472
|
return;
|
|
781
1473
|
}
|
|
782
1474
|
if (input.length === 1) {
|
|
@@ -807,6 +1499,10 @@ function App({
|
|
|
807
1499
|
}
|
|
808
1500
|
function handlePaletteInput(input, key) {
|
|
809
1501
|
if (mode.kind !== "palette") return;
|
|
1502
|
+
if (mode.collecting !== void 0) {
|
|
1503
|
+
handleArgInput(input, key, mode.collecting);
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
810
1506
|
if (key.escape) {
|
|
811
1507
|
setPaletteQuery("");
|
|
812
1508
|
setPaletteActive(0);
|
|
@@ -827,7 +1523,22 @@ function App({
|
|
|
827
1523
|
if (key.return) {
|
|
828
1524
|
if (mode.corpus === "commands") {
|
|
829
1525
|
const row = paletteRows[paletteActive];
|
|
830
|
-
if (row?.argv)
|
|
1526
|
+
if (!row?.argv) return;
|
|
1527
|
+
if (row.needsArg !== void 0) {
|
|
1528
|
+
setArgBuffer("");
|
|
1529
|
+
setArgNotice(null);
|
|
1530
|
+
setMode({
|
|
1531
|
+
kind: "palette",
|
|
1532
|
+
corpus: mode.corpus,
|
|
1533
|
+
collecting: {
|
|
1534
|
+
argv: [...row.argv],
|
|
1535
|
+
label: row.needsArg,
|
|
1536
|
+
destructive: row.destructive
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
handleRun(row.argv, row.destructive);
|
|
831
1542
|
} else {
|
|
832
1543
|
moveActive(1);
|
|
833
1544
|
}
|
|
@@ -852,6 +1563,37 @@ function App({
|
|
|
852
1563
|
setPaletteActive(0);
|
|
853
1564
|
}
|
|
854
1565
|
}
|
|
1566
|
+
function handleArgInput(input, key, collecting) {
|
|
1567
|
+
if (mode.kind !== "palette") return;
|
|
1568
|
+
const corpus = mode.corpus;
|
|
1569
|
+
if (key.escape) {
|
|
1570
|
+
setArgBuffer("");
|
|
1571
|
+
setArgNotice(null);
|
|
1572
|
+
setMode({ kind: "palette", corpus });
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (key.return) {
|
|
1576
|
+
const value = argBuffer.trim();
|
|
1577
|
+
if (value.length === 0) {
|
|
1578
|
+
setArgNotice(`a ${collecting.label} is required`);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
setArgBuffer("");
|
|
1582
|
+
setArgNotice(null);
|
|
1583
|
+
handleRun([...collecting.argv, value], collecting.destructive);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
if (key.backspace || key.delete) {
|
|
1587
|
+
setArgBuffer((b) => b.slice(0, -1));
|
|
1588
|
+
setArgNotice(null);
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
if (key.ctrl || key.tab) return;
|
|
1592
|
+
if (input.length > 0) {
|
|
1593
|
+
setArgBuffer((b) => b + input);
|
|
1594
|
+
setArgNotice(null);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
855
1597
|
function handleConfirmInput(input, key) {
|
|
856
1598
|
if (key.escape || input === "n" || input === "N") {
|
|
857
1599
|
setPaletteQuery("");
|
|
@@ -861,13 +1603,14 @@ function App({
|
|
|
861
1603
|
}
|
|
862
1604
|
if (input === "y" || input === "Y") {
|
|
863
1605
|
if (mode.kind !== "confirm") return;
|
|
864
|
-
|
|
865
|
-
void recordRuns([...mode.argv]);
|
|
866
|
-
pushHistory(`/${mode.argv.join(" ")}`);
|
|
867
|
-
setMode({ kind: "dashboard" });
|
|
1606
|
+
enterRunOrDispatch(mode.argv);
|
|
868
1607
|
}
|
|
869
1608
|
}
|
|
870
|
-
|
|
1609
|
+
useInput3((input, key) => {
|
|
1610
|
+
if (key.ctrl && input === "c") {
|
|
1611
|
+
if (mode.kind !== "run") exit();
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
871
1614
|
switch (mode.kind) {
|
|
872
1615
|
case "dashboard":
|
|
873
1616
|
handleDashboardInput(input, key);
|
|
@@ -878,6 +1621,9 @@ function App({
|
|
|
878
1621
|
case "confirm":
|
|
879
1622
|
handleConfirmInput(input, key);
|
|
880
1623
|
return;
|
|
1624
|
+
case "run":
|
|
1625
|
+
case "transcripts":
|
|
1626
|
+
return;
|
|
881
1627
|
}
|
|
882
1628
|
});
|
|
883
1629
|
function submit() {
|
|
@@ -901,7 +1647,7 @@ function App({
|
|
|
901
1647
|
dispatchCmd(argv);
|
|
902
1648
|
void recordRuns(argv);
|
|
903
1649
|
}
|
|
904
|
-
const paletteRows =
|
|
1650
|
+
const paletteRows = useMemo2(
|
|
905
1651
|
() => buildPaletteRows({
|
|
906
1652
|
corpus: mode.kind === "palette" ? mode.corpus : "commands",
|
|
907
1653
|
query: paletteQuery,
|
|
@@ -914,40 +1660,64 @@ function App({
|
|
|
914
1660
|
[mode, paletteQuery, deps.commands, deps.matcher, recent, homeSections, searchLines]
|
|
915
1661
|
);
|
|
916
1662
|
if (mode.kind === "palette") {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
/* @__PURE__ */
|
|
1663
|
+
const collecting = mode.collecting;
|
|
1664
|
+
return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
1665
|
+
/* @__PURE__ */ jsx12(Header, { tagline: `palette \xB7 ${mode.corpus}` }),
|
|
1666
|
+
/* @__PURE__ */ jsx12(
|
|
920
1667
|
Palette,
|
|
921
1668
|
{
|
|
922
1669
|
corpus: mode.corpus,
|
|
923
1670
|
query: paletteQuery,
|
|
924
1671
|
active: paletteActive,
|
|
925
|
-
rows: paletteRows
|
|
1672
|
+
rows: paletteRows,
|
|
1673
|
+
arg: collecting === void 0 ? void 0 : {
|
|
1674
|
+
value: argBuffer,
|
|
1675
|
+
placeholder: `${collecting.label} for ${collecting.argv.join(" ")}\u2026`,
|
|
1676
|
+
hint: argNotice ?? void 0
|
|
1677
|
+
}
|
|
926
1678
|
}
|
|
927
1679
|
),
|
|
928
|
-
/* @__PURE__ */
|
|
1680
|
+
/* @__PURE__ */ jsx12(Footer, { running: false })
|
|
929
1681
|
] });
|
|
930
1682
|
}
|
|
931
1683
|
if (mode.kind === "confirm") {
|
|
932
|
-
return /* @__PURE__ */
|
|
933
|
-
/* @__PURE__ */
|
|
934
|
-
/* @__PURE__ */
|
|
935
|
-
/* @__PURE__ */
|
|
1684
|
+
return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
1685
|
+
/* @__PURE__ */ jsx12(Header, { tagline: "confirm" }),
|
|
1686
|
+
/* @__PURE__ */ jsx12(ConfirmOverlay, { argv: mode.argv }),
|
|
1687
|
+
/* @__PURE__ */ jsx12(Footer, { running: false })
|
|
936
1688
|
] });
|
|
937
1689
|
}
|
|
1690
|
+
if (mode.kind === "run" && deps.run !== void 0) {
|
|
1691
|
+
return /* @__PURE__ */ jsx12(
|
|
1692
|
+
RunMode,
|
|
1693
|
+
{
|
|
1694
|
+
prompt: mode.prompt,
|
|
1695
|
+
deps: deps.run,
|
|
1696
|
+
onQuit: exit,
|
|
1697
|
+
onExit: (notice2) => {
|
|
1698
|
+
if (notice2 !== void 0) setNotice(notice2);
|
|
1699
|
+
setMode({ kind: "dashboard" });
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
const transcripts = deps.run?.transcripts;
|
|
1705
|
+
if (mode.kind === "transcripts" && transcripts !== void 0) {
|
|
1706
|
+
return /* @__PURE__ */ jsx12(TranscriptPicker, { transcripts, onExit: () => setMode({ kind: "dashboard" }) });
|
|
1707
|
+
}
|
|
938
1708
|
const snapshotLines = formatStatusPayload(payload);
|
|
939
1709
|
const paneLines = output?.lines ?? snapshotLines ?? [];
|
|
940
1710
|
const paneTitle = output?.title ?? "snapshot";
|
|
941
|
-
return /* @__PURE__ */
|
|
942
|
-
/* @__PURE__ */
|
|
943
|
-
/* @__PURE__ */
|
|
944
|
-
/* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
946
|
-
/* @__PURE__ */
|
|
1711
|
+
return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
1712
|
+
/* @__PURE__ */ jsx12(Header, { tagline: "dashboard" }),
|
|
1713
|
+
/* @__PURE__ */ jsxs10(Panel, { children: [
|
|
1714
|
+
/* @__PURE__ */ jsx12(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx12(StatusBar, { payload, loading }) }),
|
|
1715
|
+
/* @__PURE__ */ jsx12(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { children: divider() }) }),
|
|
1716
|
+
/* @__PURE__ */ jsx12(Box8, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsx12(OutputPane, { lines: paneLines, scrollOffset, title: paneTitle }) })
|
|
947
1717
|
] }),
|
|
948
|
-
/* @__PURE__ */
|
|
949
|
-
notice !== null ? /* @__PURE__ */
|
|
950
|
-
/* @__PURE__ */
|
|
1718
|
+
/* @__PURE__ */ jsx12(CommandInput, { buffer, running }),
|
|
1719
|
+
notice !== null ? /* @__PURE__ */ jsx12(Text11, { children: c.dim(notice) }) : null,
|
|
1720
|
+
/* @__PURE__ */ jsx12(Footer, { running })
|
|
951
1721
|
] });
|
|
952
1722
|
}
|
|
953
1723
|
|
|
@@ -1006,8 +1776,57 @@ function recordRecent(projectId, argv) {
|
|
|
1006
1776
|
}
|
|
1007
1777
|
}
|
|
1008
1778
|
|
|
1779
|
+
// src/tui/transcripts.ts
|
|
1780
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
1781
|
+
import { join as join2, resolve, sep } from "path";
|
|
1782
|
+
import { paths } from "@noir-ai/core";
|
|
1783
|
+
var TRANSCRIPT_LIST_LIMIT = 20;
|
|
1784
|
+
function transcriptDir(root = process.cwd()) {
|
|
1785
|
+
return paths.transcriptsDir(root);
|
|
1786
|
+
}
|
|
1787
|
+
function createTranscriptStore(opts) {
|
|
1788
|
+
const root = opts.root ?? process.cwd();
|
|
1789
|
+
const dir = transcriptDir(root);
|
|
1790
|
+
return {
|
|
1791
|
+
list: async (limit = TRANSCRIPT_LIST_LIMIT) => {
|
|
1792
|
+
let names;
|
|
1793
|
+
try {
|
|
1794
|
+
names = readdirSync(dir);
|
|
1795
|
+
} catch {
|
|
1796
|
+
return [];
|
|
1797
|
+
}
|
|
1798
|
+
const entries = [];
|
|
1799
|
+
for (const name of names) {
|
|
1800
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
1801
|
+
const path = join2(dir, name);
|
|
1802
|
+
try {
|
|
1803
|
+
const info = statSync(path);
|
|
1804
|
+
if (!info.isFile()) continue;
|
|
1805
|
+
entries.push({ path, name, sizeBytes: info.size, mtimeMs: info.mtimeMs });
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
entries.sort((a, b) => b.mtimeMs - a.mtimeMs || a.name.localeCompare(b.name));
|
|
1810
|
+
return entries.slice(0, Math.max(0, limit));
|
|
1811
|
+
},
|
|
1812
|
+
read: async (path) => {
|
|
1813
|
+
const resolved = resolve(path);
|
|
1814
|
+
if (resolved !== resolve(dir) && !resolved.startsWith(resolve(dir) + sep)) return [];
|
|
1815
|
+
try {
|
|
1816
|
+
return readFileSync2(resolved, "utf8").split("\n");
|
|
1817
|
+
} catch {
|
|
1818
|
+
return [];
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
write: async (lines) => {
|
|
1822
|
+
return writeTranscript(opts.host, lines, root);
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1009
1827
|
// src/tui/index.tsx
|
|
1010
|
-
import { jsx as
|
|
1828
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1829
|
+
var DEFAULT_RUN_HOST = "claude";
|
|
1011
1830
|
function defaultFetchStatus(opts) {
|
|
1012
1831
|
return async () => {
|
|
1013
1832
|
try {
|
|
@@ -1017,17 +1836,68 @@ function defaultFetchStatus(opts) {
|
|
|
1017
1836
|
}
|
|
1018
1837
|
};
|
|
1019
1838
|
}
|
|
1839
|
+
function startHostRun(prompt, handlers, resumeSessionId) {
|
|
1840
|
+
const host = DEFAULT_RUN_HOST;
|
|
1841
|
+
if (!SUPPORTED_HOSTS.includes(host)) {
|
|
1842
|
+
throw new Error(`unknown host '${host}' (supported: ${SUPPORTED_HOSTS.join(", ")})`);
|
|
1843
|
+
}
|
|
1844
|
+
const profile = resolveProfile();
|
|
1845
|
+
const extraArgs = resumeSessionId === void 0 ? profile.args : [...profile.args ?? [], "--resume", resumeSessionId];
|
|
1846
|
+
const env = profile.env === void 0 ? void 0 : mergeEnv(process.env, profile.env);
|
|
1847
|
+
const binary = profile.binary !== void 0 && profile.binary.length > 0 ? profile.binary : host;
|
|
1848
|
+
return {
|
|
1849
|
+
binary,
|
|
1850
|
+
done: runHost({
|
|
1851
|
+
host,
|
|
1852
|
+
prompt,
|
|
1853
|
+
...profile.binary === void 0 ? {} : { customBinary: profile.binary },
|
|
1854
|
+
...extraArgs === void 0 ? {} : { extraArgs },
|
|
1855
|
+
...env === void 0 ? {} : { env },
|
|
1856
|
+
onLine: handlers.onLine,
|
|
1857
|
+
onEvent: handlers.onEvent,
|
|
1858
|
+
signal: handlers.signal,
|
|
1859
|
+
...handlers.onChild === void 0 ? {} : { onChild: handlers.onChild }
|
|
1860
|
+
})
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function resolveProfile() {
|
|
1864
|
+
const config = loadRunConfig(process.cwd()) ?? parseConfig({});
|
|
1865
|
+
const resolved = resolveRunProfile(void 0, config, process.env);
|
|
1866
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
1867
|
+
return resolved.profile;
|
|
1868
|
+
}
|
|
1869
|
+
function defaultRunDeps(opts) {
|
|
1870
|
+
const toInput = (request) => ({
|
|
1871
|
+
answer: request.answer,
|
|
1872
|
+
transcript: request.transcript,
|
|
1873
|
+
host: request.host,
|
|
1874
|
+
opts,
|
|
1875
|
+
...request.sessionId === void 0 ? {} : { sessionId: request.sessionId },
|
|
1876
|
+
...request.canContinue === void 0 ? {} : { canContinue: request.canContinue }
|
|
1877
|
+
});
|
|
1878
|
+
return {
|
|
1879
|
+
start: (prompt, handlers, resumeSessionId) => startHostRun(prompt, handlers, resumeSessionId),
|
|
1880
|
+
actions: (request) => postRunActions(toInput(request)),
|
|
1881
|
+
perform: (request, choice, value) => performPostRunAction(choice, toInput(request), value),
|
|
1882
|
+
// Named by host, exactly as the CLI names its own transcripts, so a run
|
|
1883
|
+
// started from the dashboard lands beside one started from a shell.
|
|
1884
|
+
transcripts: createTranscriptStore({ host: DEFAULT_RUN_HOST })
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1020
1887
|
async function runTui(opts, dispatch) {
|
|
1021
1888
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
1022
1889
|
const deps = await buildTuiDeps(opts, dispatch);
|
|
1023
|
-
const instance = render(/* @__PURE__ */
|
|
1890
|
+
const instance = render(/* @__PURE__ */ jsx13(App, { deps }), { exitOnCtrlC: false });
|
|
1024
1891
|
await instance.waitUntilExit();
|
|
1025
1892
|
}
|
|
1026
1893
|
async function runPalette(opts, dispatch) {
|
|
1027
1894
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
1028
1895
|
const deps = await buildTuiDeps(opts, dispatch);
|
|
1029
1896
|
const instance = render(
|
|
1030
|
-
/* @__PURE__ */
|
|
1897
|
+
/* @__PURE__ */ jsx13(App, { deps, initialMode: { kind: "palette", corpus: "commands" } }),
|
|
1898
|
+
{
|
|
1899
|
+
exitOnCtrlC: false
|
|
1900
|
+
}
|
|
1031
1901
|
);
|
|
1032
1902
|
await instance.waitUntilExit();
|
|
1033
1903
|
}
|
|
@@ -1041,9 +1911,13 @@ async function buildTuiDeps(opts, dispatch) {
|
|
|
1041
1911
|
const deps = {
|
|
1042
1912
|
dispatch,
|
|
1043
1913
|
fetchStatus: defaultFetchStatus(opts),
|
|
1044
|
-
// The
|
|
1914
|
+
// The live host-run screen. Present here (not optional in practice) is what
|
|
1915
|
+
// routes a `run <prompt>` selection to the screen instead of dispatching it
|
|
1916
|
+
// through the captured-command path — see `enterRunOrDispatch` in App.tsx.
|
|
1917
|
+
run: defaultRunDeps(opts),
|
|
1918
|
+
// The palette source, derived from a fresh commander program at launch.
|
|
1045
1919
|
commands: buildPaletteCommandsForTui(),
|
|
1046
|
-
//
|
|
1920
|
+
// Persistent recent commands (projectId-keyed). recordRecent is async
|
|
1047
1921
|
// only to match the TuiDeps seam; it never rejects.
|
|
1048
1922
|
record: (argv) => {
|
|
1049
1923
|
if (projectId) recordRecent(projectId, argv);
|