@glrs-dev/cli 2.3.0 → 2.4.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
  3. package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
  4. package/dist/chunk-2VMFXAJH.js +795 -0
  5. package/dist/chunk-5ZVUFNCP.js +140 -0
  6. package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
  7. package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
  8. package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
  9. package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
  10. package/dist/{chunk-MIWZLETC.js → chunk-MCM47HH4.js} +1 -1
  11. package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
  12. package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
  13. package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
  14. package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
  15. package/dist/cli.js +31 -20
  16. package/dist/commands/autopilot-interactive.d.ts +89 -0
  17. package/dist/commands/autopilot-interactive.js +248 -0
  18. package/dist/commands/autopilot-raw.d.ts +1 -0
  19. package/dist/commands/autopilot-raw.js +368 -0
  20. package/dist/commands/autopilot-tui.d.ts +7 -0
  21. package/dist/commands/autopilot-tui.js +7 -0
  22. package/dist/commands/autopilot.d.ts +39 -0
  23. package/dist/commands/autopilot.js +395 -0
  24. package/dist/commands/cleanup.js +3 -3
  25. package/dist/commands/create.js +4 -4
  26. package/dist/commands/dashboard.d.ts +3 -0
  27. package/dist/commands/dashboard.js +1549 -0
  28. package/dist/commands/debrief.d.ts +57 -0
  29. package/dist/commands/debrief.js +9 -0
  30. package/dist/commands/delete.js +3 -3
  31. package/dist/commands/go.js +2 -2
  32. package/dist/commands/list.js +3 -3
  33. package/dist/commands/loop.d.ts +42 -0
  34. package/dist/commands/loop.js +133 -0
  35. package/dist/commands/plan-picker.d.ts +15 -0
  36. package/dist/commands/plan-picker.js +76 -0
  37. package/dist/commands/scoper.d.ts +54 -0
  38. package/dist/{vendor/harness-opencode/dist/scoper-S77SOK7X.js → commands/scoper.js} +30 -15
  39. package/dist/commands/switch.js +3 -3
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/lib/auto-update.js +1 -1
  43. package/dist/lib/config.d.ts +3 -2
  44. package/dist/lib/config.js +1 -1
  45. package/dist/lib/registry.d.ts +2 -0
  46. package/dist/lib/registry.js +1 -1
  47. package/dist/lib/worktree.js +3 -3
  48. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.d.ts +261 -0
  49. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.js +488 -0
  50. package/dist/node_modules/@glrs-dev/adapter-opencode/package.json +8 -0
  51. package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-LCT6LIH7.js +7 -0
  52. package/dist/node_modules/@glrs-dev/autopilot/dist/changeset-generator-DG3MVWVV.js +15 -0
  53. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-7OSEI5TF.js +249 -0
  54. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-E7PWTRFO.js +91 -0
  55. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-M2ZVBPWL.js +101 -0
  56. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-Q4ULU6ER.js +68 -0
  57. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-VITL2Z45.js +2772 -0
  58. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-ZNJWARTM.js +449 -0
  59. package/dist/node_modules/@glrs-dev/autopilot/dist/index.d.ts +1765 -0
  60. package/dist/node_modules/@glrs-dev/autopilot/dist/index.js +688 -0
  61. package/dist/node_modules/@glrs-dev/autopilot/dist/logger-UITJGIZE.js +8 -0
  62. package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-XKL3NHUA.js +8 -0
  63. package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-D3RPJR2J.js +14 -0
  64. package/dist/node_modules/@glrs-dev/autopilot/package.json +8 -0
  65. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
  66. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  67. package/dist/vendor/harness-opencode/dist/cli.js +335 -639
  68. package/dist/vendor/harness-opencode/dist/index.js +35 -8
  69. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  70. package/dist/vendor/harness-opencode/package.json +1 -1
  71. package/package.json +14 -6
  72. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
  73. package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
  74. package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
  75. package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
  76. package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
  77. package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
  78. package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
  79. package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +0 -117
@@ -0,0 +1,795 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-3RG5ZIWI.js";
4
+
5
+ // src/commands/autopilot-tui.ts
6
+ import React3 from "react";
7
+ import { render } from "ink";
8
+
9
+ // src/tui/components/AutopilotPicker.tsx
10
+ import { useState, useMemo } from "react";
11
+ import { Box, Text, useInput } from "ink";
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ function shortenHome(p) {
16
+ const home = process.env.HOME ?? "";
17
+ if (home && p.startsWith(home)) return "~" + p.slice(home.length);
18
+ return p;
19
+ }
20
+ function isPlanDir(dirPath) {
21
+ if (fs.existsSync(path.join(dirPath, "main.md"))) return "md";
22
+ if (fs.existsSync(path.join(dirPath, "spec", "main.yaml"))) return "yaml";
23
+ return false;
24
+ }
25
+ function readDir(dir) {
26
+ try {
27
+ const raw = fs.readdirSync(dir, { withFileTypes: true });
28
+ const dirs = [];
29
+ const files = [];
30
+ for (const entry of raw) {
31
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === "target") continue;
32
+ if (entry.name.startsWith(".") && entry.name !== ".glrs" && entry.name !== ".opencode" && entry.name !== ".agent" && entry.name !== ".claude") continue;
33
+ const fullPath = path.join(dir, entry.name);
34
+ if (entry.isDirectory()) {
35
+ const planType = isPlanDir(fullPath);
36
+ dirs.push({
37
+ name: planType ? `${entry.name}/` : `${entry.name}/`,
38
+ isDir: true,
39
+ path: fullPath,
40
+ isPlan: !!planType
41
+ });
42
+ } else if (entry.name.endsWith(".md") || entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) {
43
+ files.push({ name: entry.name, isDir: false, path: fullPath });
44
+ }
45
+ }
46
+ dirs.sort((a, b) => {
47
+ if (a.isPlan && !b.isPlan) return -1;
48
+ if (!a.isPlan && b.isPlan) return 1;
49
+ return a.name.localeCompare(b.name);
50
+ });
51
+ files.sort((a, b) => a.name.localeCompare(b.name));
52
+ return [...dirs, ...files];
53
+ } catch {
54
+ return [];
55
+ }
56
+ }
57
+ var VIEWPORT = 15;
58
+ function AutopilotPicker({ startDir, onSelect, onCancel }) {
59
+ const [cwd, setCwd] = useState(startDir);
60
+ const [selectedIndex, setSelectedIndex] = useState(0);
61
+ const entries = useMemo(() => readDir(cwd), [cwd]);
62
+ useInput((_input, key) => {
63
+ if (key.escape) {
64
+ if (cwd === startDir) {
65
+ onCancel();
66
+ } else {
67
+ setCwd(path.dirname(cwd));
68
+ setSelectedIndex(0);
69
+ }
70
+ return;
71
+ }
72
+ if (key.return && entries[selectedIndex]) {
73
+ const entry = entries[selectedIndex];
74
+ if (entry.isPlan) {
75
+ onSelect(entry.path);
76
+ } else if (entry.isDir) {
77
+ setCwd(entry.path);
78
+ setSelectedIndex(0);
79
+ } else {
80
+ onSelect(entry.path);
81
+ }
82
+ return;
83
+ }
84
+ if (key.upArrow) {
85
+ setSelectedIndex((i) => Math.max(0, i - 1));
86
+ }
87
+ if (key.downArrow) {
88
+ setSelectedIndex((i) => Math.min(entries.length - 1, i + 1));
89
+ }
90
+ });
91
+ const halfWindow = Math.floor(VIEWPORT / 2);
92
+ let windowStart = Math.max(0, selectedIndex - halfWindow);
93
+ const windowEnd = Math.min(entries.length, windowStart + VIEWPORT);
94
+ if (windowEnd - windowStart < VIEWPORT) {
95
+ windowStart = Math.max(0, windowEnd - VIEWPORT);
96
+ }
97
+ const visible = entries.slice(windowStart, windowEnd);
98
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
99
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
100
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Select a plan" }),
101
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
102
+ " \u2014 ",
103
+ shortenHome(cwd)
104
+ ] })
105
+ ] }),
106
+ entries.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Empty directory." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
107
+ windowStart > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
108
+ " \u2191 ",
109
+ windowStart,
110
+ " more"
111
+ ] }),
112
+ visible.map((entry, i) => {
113
+ const globalIndex = windowStart + i;
114
+ const isSelected = globalIndex === selectedIndex;
115
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: isSelected ? "blue" : void 0, children: [
116
+ isSelected ? "\u25B6 " : " ",
117
+ entry.isPlan ? /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: entry.name }) : entry.isDir ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: entry.name }) : /* @__PURE__ */ jsx(Text, { children: entry.name }),
118
+ entry.isPlan && /* @__PURE__ */ jsx(Text, { color: "green", children: " \u2190 plan" })
119
+ ] }) }, `${entry.path}-${globalIndex}`);
120
+ }),
121
+ windowEnd < entries.length && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
122
+ " \u2193 ",
123
+ entries.length - windowEnd,
124
+ " more"
125
+ ] })
126
+ ] }),
127
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
128
+ "\u2191\u2193 navigate \xB7 Enter ",
129
+ entries[selectedIndex]?.isPlan ? "select plan" : entries[selectedIndex]?.isDir ? "open" : "select",
130
+ " \xB7 Esc ",
131
+ cwd === startDir ? "quit" : "back"
132
+ ] }) })
133
+ ] });
134
+ }
135
+
136
+ // src/tui/components/AutopilotExecution.tsx
137
+ import { useState as useState2, useEffect } from "react";
138
+ import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "ink";
139
+ import { Spinner } from "@inkjs/ui";
140
+ import * as path2 from "path";
141
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
142
+ var MAX_LOG_ENTRIES = 5e3;
143
+ function initialState() {
144
+ return {
145
+ planPath: "",
146
+ executionMode: "unknown",
147
+ startedAt: Date.now(),
148
+ totalCost: 0,
149
+ _lastReportedCost: 0,
150
+ tokensIn: 0,
151
+ tokensOut: 0,
152
+ _lastMsgTokensIn: 0,
153
+ _lastMsgTokensOut: 0,
154
+ cacheRead: 0,
155
+ cacheWrite: 0,
156
+ phases: [],
157
+ currentPhaseIndex: -1,
158
+ currentIteration: 0,
159
+ maxIterations: 0,
160
+ totalTurns: 0,
161
+ retryCount: 0,
162
+ enriching: false,
163
+ enrichedFiles: 0,
164
+ totalEnrichFiles: 0,
165
+ enrichDone: false,
166
+ logEntries: [],
167
+ done: false
168
+ };
169
+ }
170
+ function formatCost(usd) {
171
+ if (usd === 0) return "$0.00";
172
+ return `$${usd.toFixed(2)}`;
173
+ }
174
+ function formatElapsed(ms) {
175
+ const totalSec = Math.floor(ms / 1e3);
176
+ const m = Math.floor(totalSec / 60);
177
+ const s = totalSec % 60;
178
+ if (m === 0) return `${s}s`;
179
+ return `${m}m ${s.toString().padStart(2, "0")}s`;
180
+ }
181
+ function formatTokens(n) {
182
+ if (n === 0) return "\u2014";
183
+ if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
184
+ return String(n);
185
+ }
186
+ function formatDuration(ms) {
187
+ const s = Math.round(ms / 1e3);
188
+ if (s < 60) return `${s}s`;
189
+ return `${Math.floor(s / 60)}m${(s % 60).toString().padStart(2, "0")}s`;
190
+ }
191
+ function planName(planPath) {
192
+ if (!planPath) return "";
193
+ return path2.basename(planPath);
194
+ }
195
+ function truncateArg(s, max = 40) {
196
+ if (!s) return "";
197
+ if (s.length <= max) return s;
198
+ return `\u2026${s.slice(-(max - 1))}`;
199
+ }
200
+ function nowHHMMSS() {
201
+ const d = /* @__PURE__ */ new Date();
202
+ const hh = d.getHours().toString().padStart(2, "0");
203
+ const mm = d.getMinutes().toString().padStart(2, "0");
204
+ const ss = d.getSeconds().toString().padStart(2, "0");
205
+ return `${hh}:${mm}:${ss}`;
206
+ }
207
+ function appendLog(entries, text, color) {
208
+ const entry = { time: nowHHMMSS(), text, color };
209
+ return [...entries, entry].slice(-MAX_LOG_ENTRIES);
210
+ }
211
+ function AutopilotExecution({ emitter }) {
212
+ const [state, setState] = useState2(initialState);
213
+ const [elapsedMs, setElapsedMs] = useState2(0);
214
+ const { stdout } = useStdout();
215
+ const [, setResizeTick] = useState2(0);
216
+ useEffect(() => {
217
+ const onResize = () => setResizeTick((n) => n + 1);
218
+ stdout.on("resize", onResize);
219
+ return () => {
220
+ stdout.off("resize", onResize);
221
+ };
222
+ }, [stdout]);
223
+ useEffect(() => {
224
+ const handler = (event) => {
225
+ setState((prev) => applyEvent(prev, event));
226
+ };
227
+ emitter.on("event", handler);
228
+ return () => {
229
+ emitter.off("event", handler);
230
+ };
231
+ }, [emitter]);
232
+ useEffect(() => {
233
+ const interval = setInterval(() => {
234
+ setState((prev) => {
235
+ setElapsedMs(Date.now() - prev.startedAt);
236
+ return prev;
237
+ });
238
+ }, 1e3);
239
+ return () => clearInterval(interval);
240
+ }, []);
241
+ const [scrollOffset, setScrollOffset] = useState2(null);
242
+ useInput2((input, key) => {
243
+ if (input === "c" && key.ctrl) {
244
+ process.kill(process.pid, "SIGINT");
245
+ return;
246
+ }
247
+ const totalEntries = state.logEntries.length;
248
+ const termH = stdout.rows ?? 24;
249
+ const viewHeight = Math.max(4, termH - 8);
250
+ if (key.upArrow || input === "k") {
251
+ setScrollOffset((prev) => {
252
+ const current = prev ?? Math.max(0, totalEntries - viewHeight);
253
+ return Math.max(0, current - 1);
254
+ });
255
+ } else if (key.downArrow || input === "j") {
256
+ setScrollOffset((prev) => {
257
+ const current = prev ?? Math.max(0, totalEntries - viewHeight);
258
+ const maxOffset = Math.max(0, totalEntries - viewHeight);
259
+ const next = Math.min(maxOffset, current + 1);
260
+ return next >= maxOffset ? null : next;
261
+ });
262
+ } else if (input === "G") {
263
+ setScrollOffset(null);
264
+ } else if (input === "g") {
265
+ setScrollOffset(0);
266
+ } else if (input === "c") {
267
+ const text = state.logEntries.map((e) => `${e.time} ${e.text}`).join("\n");
268
+ const proc = __require("child_process").spawn(
269
+ process.platform === "darwin" ? "pbcopy" : "xclip",
270
+ process.platform === "darwin" ? [] : ["-selection", "clipboard"],
271
+ { stdio: ["pipe", "ignore", "ignore"] }
272
+ );
273
+ proc.stdin.write(text);
274
+ proc.stdin.end();
275
+ }
276
+ });
277
+ const termWidth = stdout.columns ?? 80;
278
+ const {
279
+ planPath,
280
+ totalCost,
281
+ tokensIn,
282
+ tokensOut,
283
+ executionMode,
284
+ phases,
285
+ currentPhaseIndex,
286
+ currentIteration,
287
+ maxIterations,
288
+ totalTurns,
289
+ retryCount,
290
+ enriching,
291
+ enrichedFiles,
292
+ totalEnrichFiles,
293
+ enrichDone,
294
+ logEntries,
295
+ lastError,
296
+ done,
297
+ exitReason
298
+ } = state;
299
+ const elapsed = formatElapsed(elapsedMs);
300
+ const costStr = formatCost(totalCost);
301
+ const tokInStr = formatTokens(tokensIn);
302
+ const tokOutStr = formatTokens(tokensOut);
303
+ const name = planName(planPath);
304
+ const termHeight = stdout.rows ?? 24;
305
+ const logHeight = Math.max(4, termHeight - 8);
306
+ const visibleLogs = scrollOffset === null ? logEntries.slice(-logHeight) : logEntries.slice(scrollOffset, scrollOffset + logHeight);
307
+ const isPinned = scrollOffset === null;
308
+ const scrollIndicator = !isPinned ? ` \u2191${scrollOffset}/${logEntries.length}` : "";
309
+ const phaseBar = phases.map((p, i) => {
310
+ const icon = p.status === "complete" ? "\u2713" : p.status === "failed" ? "\u2717" : p.status === "running" ? "\u25CF" : p.status === "skipped" ? "\u2014" : "\u25CB";
311
+ const color = p.status === "complete" ? "green" : p.status === "failed" ? "red" : p.status === "running" ? "yellow" : void 0;
312
+ return /* @__PURE__ */ jsx2(Text2, { color, dimColor: !color, children: icon }, `phase-${i}`);
313
+ });
314
+ const currentPhaseName = currentPhaseIndex >= 0 && phases[currentPhaseIndex] ? phases[currentPhaseIndex].name : "";
315
+ const statusIcon = done ? exitReason === "sentinel" ? /* @__PURE__ */ jsx2(Text2, { color: "green", bold: true, children: "\u2713" }) : /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: "\u2717" }) : enriching ? /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "\u25D0" }) : /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\u25B6" });
316
+ const enrichStr = enriching ? `enrich ${enrichedFiles}/${totalEnrichFiles}` : enrichDone ? `enrich \u2713` : "";
317
+ const innerWidth = Math.max(20, termWidth - 4);
318
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: termWidth, children: [
319
+ /* @__PURE__ */ jsxs2(
320
+ Box2,
321
+ {
322
+ borderStyle: "single",
323
+ borderTop: true,
324
+ borderLeft: true,
325
+ borderRight: true,
326
+ borderBottom: false,
327
+ borderColor: "gray",
328
+ paddingX: 1,
329
+ children: [
330
+ /* @__PURE__ */ jsxs2(Box2, { flexGrow: 1, children: [
331
+ statusIcon,
332
+ /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
333
+ " ",
334
+ name || "autopilot"
335
+ ] }),
336
+ executionMode !== "unknown" && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
337
+ " ",
338
+ executionMode === "fast" ? "fast" : "deep"
339
+ ] })
340
+ ] }),
341
+ /* @__PURE__ */ jsx2(Text2, { children: elapsed })
342
+ ]
343
+ }
344
+ ),
345
+ /* @__PURE__ */ jsxs2(
346
+ Box2,
347
+ {
348
+ borderStyle: "single",
349
+ borderTop: false,
350
+ borderLeft: true,
351
+ borderRight: true,
352
+ borderBottom: true,
353
+ borderColor: "gray",
354
+ paddingX: 1,
355
+ gap: 2,
356
+ children: [
357
+ phases.length > 0 && /* @__PURE__ */ jsxs2(Box2, { gap: 0, children: [
358
+ phaseBar,
359
+ currentPhaseName && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
360
+ " ",
361
+ currentPhaseName
362
+ ] })
363
+ ] }),
364
+ phases.length === 0 && !enriching && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "starting\u2026" }),
365
+ enrichStr && /* @__PURE__ */ jsx2(Text2, { color: enriching ? "yellow" : "green", children: enrichStr }),
366
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
367
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: costStr }),
368
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
369
+ tokInStr,
370
+ "\u2191 ",
371
+ tokOutStr,
372
+ "\u2193"
373
+ ] })
374
+ ]
375
+ }
376
+ ),
377
+ /* @__PURE__ */ jsxs2(
378
+ Box2,
379
+ {
380
+ flexDirection: "column",
381
+ borderStyle: "single",
382
+ borderTop: false,
383
+ borderLeft: true,
384
+ borderRight: true,
385
+ borderBottom: false,
386
+ borderColor: "gray",
387
+ paddingX: 1,
388
+ height: logHeight + 2,
389
+ children: [
390
+ visibleLogs.length === 0 && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "waiting for events\u2026" }),
391
+ visibleLogs.map((entry, i) => /* @__PURE__ */ jsx2(LogRow, { entry, maxWidth: innerWidth }, `${entry.time}-${i}`))
392
+ ]
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsxs2(
396
+ Box2,
397
+ {
398
+ borderStyle: "single",
399
+ borderColor: "gray",
400
+ paddingX: 1,
401
+ justifyContent: "space-between",
402
+ children: [
403
+ /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
404
+ lastError && /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
405
+ "\u2717 ",
406
+ lastError.length > innerWidth - 20 ? lastError.slice(0, innerWidth - 23) + "\u2026" : lastError
407
+ ] }),
408
+ done && !lastError && (exitReason === "sentinel" ? /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
409
+ "\u2713 complete \xB7 ",
410
+ costStr,
411
+ " \xB7 ",
412
+ totalTurns,
413
+ " tool calls"
414
+ ] }) : /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
415
+ "\u2717 ",
416
+ exitReason ?? "stopped"
417
+ ] })),
418
+ !done && !lastError && /* @__PURE__ */ jsxs2(Fragment2, { children: [
419
+ currentPhaseName && /* @__PURE__ */ jsx2(Text2, { bold: true, children: currentPhaseName }),
420
+ maxIterations > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
421
+ "iter ",
422
+ currentIteration,
423
+ "/",
424
+ maxIterations
425
+ ] }),
426
+ totalTurns > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
427
+ "turns ",
428
+ totalTurns
429
+ ] }),
430
+ retryCount > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
431
+ "retries ",
432
+ retryCount
433
+ ] }),
434
+ enriching && /* @__PURE__ */ jsx2(Spinner, {})
435
+ ] })
436
+ ] }),
437
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
438
+ scrollIndicator ? scrollIndicator + " " : "",
439
+ "c:copy \u2191\u2193:scroll Ctrl+C"
440
+ ] })
441
+ ]
442
+ }
443
+ )
444
+ ] });
445
+ }
446
+ function LogRow({ entry, maxWidth }) {
447
+ const textMax = Math.max(10, maxWidth - 9);
448
+ const text = entry.text.length > textMax ? entry.text.slice(0, textMax - 1) + "\u2026" : entry.text;
449
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
450
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
451
+ entry.time,
452
+ " "
453
+ ] }),
454
+ /* @__PURE__ */ jsx2(Text2, { color: entry.color, children: text })
455
+ ] });
456
+ }
457
+ function applyEvent(prev, event) {
458
+ switch (event.type) {
459
+ case "session:start":
460
+ return {
461
+ ...prev,
462
+ planPath: event.planPath,
463
+ startedAt: Date.now(),
464
+ executionMode: event.fast ? "fast" : "deep",
465
+ logEntries: appendLog(prev.logEntries, `session started (enrich: ${event.enrichModel ?? "prime"} \xB7 execute: ${event.executeModel ?? "build"})`)
466
+ };
467
+ case "enrich:start":
468
+ return {
469
+ ...prev,
470
+ enriching: true,
471
+ totalEnrichFiles: event.fileCount,
472
+ enrichedFiles: 0,
473
+ logEntries: appendLog(
474
+ prev.logEntries,
475
+ `enrichment started (${event.fileCount} files)`,
476
+ "yellow"
477
+ )
478
+ };
479
+ case "enrich:file:start":
480
+ return {
481
+ ...prev,
482
+ logEntries: appendLog(prev.logEntries, `enriching ${event.file}\u2026`)
483
+ };
484
+ case "enrich:file:done": {
485
+ const specRef = event.specFile ? ` \u2192 ${event.specFile}` : "";
486
+ return {
487
+ ...prev,
488
+ enrichedFiles: prev.enrichedFiles + 1,
489
+ logEntries: appendLog(
490
+ prev.logEntries,
491
+ `\u2713 ${event.file}${specRef}`,
492
+ "green"
493
+ )
494
+ };
495
+ }
496
+ case "enrich:file:skip":
497
+ return {
498
+ ...prev,
499
+ // Count skipped files toward the enrichment progress so the
500
+ // counter reflects total files processed, not just enriched.
501
+ enrichedFiles: prev.enrichedFiles + 1,
502
+ logEntries: appendLog(
503
+ prev.logEntries,
504
+ `${event.file} skipped: ${event.reason}`,
505
+ "yellow"
506
+ )
507
+ };
508
+ case "enrich:file:error":
509
+ return {
510
+ ...prev,
511
+ enrichedFiles: prev.enrichedFiles + 1,
512
+ logEntries: appendLog(
513
+ prev.logEntries,
514
+ `\u2717 ${event.file}: ${event.error}`,
515
+ "red"
516
+ )
517
+ };
518
+ case "enrich:done":
519
+ return {
520
+ ...prev,
521
+ enriching: false,
522
+ enrichDone: true,
523
+ logEntries: appendLog(prev.logEntries, "enrichment complete", "green")
524
+ };
525
+ case "phase:start": {
526
+ if (event.current === 0) {
527
+ const phases2 = Array.from({ length: event.total }, () => ({
528
+ name: "",
529
+ status: "pending",
530
+ iterations: 0,
531
+ cost: 0
532
+ }));
533
+ return {
534
+ ...prev,
535
+ phases: phases2,
536
+ logEntries: appendLog(
537
+ prev.logEntries,
538
+ `plan loaded: ${event.total} phases`
539
+ )
540
+ };
541
+ }
542
+ const phaseIdx = event.current - 1;
543
+ let phases = prev.phases.map((p, i) => {
544
+ if (i === phaseIdx) {
545
+ return { ...p, name: event.phase, status: "running" };
546
+ }
547
+ return p;
548
+ });
549
+ if (phaseIdx >= phases.length) {
550
+ const grown = [...prev.phases];
551
+ while (grown.length <= phaseIdx) {
552
+ grown.push({ name: "", status: "pending", iterations: 0, cost: 0 });
553
+ }
554
+ grown[phaseIdx] = { name: event.phase, status: "running", iterations: 0, cost: 0 };
555
+ phases = grown;
556
+ }
557
+ return {
558
+ ...prev,
559
+ phases,
560
+ currentPhaseIndex: phaseIdx,
561
+ logEntries: appendLog(
562
+ prev.logEntries,
563
+ `phase:start ${event.phase} ${event.current}/${event.total}`
564
+ )
565
+ };
566
+ }
567
+ case "phase:done": {
568
+ const phases = prev.phases.map((p) => {
569
+ if (p.name === event.phase) {
570
+ return {
571
+ ...p,
572
+ status: event.completed ? "complete" : "failed",
573
+ iterations: event.iterations,
574
+ cost: event.costUsd
575
+ };
576
+ }
577
+ return p;
578
+ });
579
+ const statusIcon = event.completed ? "\u2713" : "\u2717";
580
+ return {
581
+ ...prev,
582
+ phases,
583
+ logEntries: appendLog(
584
+ prev.logEntries,
585
+ `phase:done ${event.phase} ${statusIcon} (${event.iterations} iter, ${formatCost(event.costUsd)})`,
586
+ event.completed ? "green" : "red"
587
+ )
588
+ };
589
+ }
590
+ case "iteration:start":
591
+ return {
592
+ ...prev,
593
+ currentIteration: event.iteration,
594
+ maxIterations: event.maxIterations,
595
+ logEntries: appendLog(
596
+ prev.logEntries,
597
+ `iter ${event.iteration}/${event.maxIterations} start`
598
+ )
599
+ };
600
+ case "iteration:done": {
601
+ const dur = formatDuration(event.durationMs);
602
+ const costPart = event.costUsd != null ? ` ${formatCost(event.costUsd)}` : "";
603
+ const filesPart = event.filesChanged != null ? ` ${event.filesChanged} files` : "";
604
+ return {
605
+ ...prev,
606
+ logEntries: appendLog(
607
+ prev.logEntries,
608
+ `iter ${event.iteration} done ${dur}${costPart}${filesPart}`
609
+ )
610
+ };
611
+ }
612
+ case "tool:call": {
613
+ const argStr = event.firstArg ? ` ${truncateArg(event.firstArg)}` : "";
614
+ return {
615
+ ...prev,
616
+ totalTurns: prev.totalTurns + 1,
617
+ logEntries: appendLog(
618
+ prev.logEntries,
619
+ `\u2699 ${event.toolName}${argStr}`
620
+ )
621
+ };
622
+ }
623
+ case "thinking": {
624
+ const label = event.elapsedSec < 60 ? `\u{1F4AD} thinking\u2026 ${event.elapsedSec}s \xB7 ${event.chars} chars` : `\u{1F4AD} thinking\u2026 ${Math.floor(event.elapsedSec / 60)}m${(event.elapsedSec % 60).toString().padStart(2, "0")}s \xB7 ${event.chars} chars`;
625
+ const entries = [...prev.logEntries];
626
+ const lastIdx = entries.length - 1;
627
+ if (lastIdx >= 0 && entries[lastIdx].text.startsWith("\u{1F4AD} thinking")) {
628
+ entries[lastIdx] = { ...entries[lastIdx], text: label };
629
+ } else {
630
+ const now = nowHHMMSS();
631
+ entries.push({ time: now, text: label });
632
+ if (entries.length > MAX_LOG_ENTRIES) entries.shift();
633
+ }
634
+ return { ...prev, logEntries: entries };
635
+ }
636
+ case "cost:update": {
637
+ const reportedCost = event.cumulativeCostUsd;
638
+ let newCost = prev.totalCost;
639
+ if (reportedCost < prev._lastReportedCost) {
640
+ newCost = prev.totalCost + reportedCost;
641
+ } else {
642
+ newCost = prev.totalCost - prev._lastReportedCost + reportedCost;
643
+ }
644
+ let newIn = prev.tokensIn;
645
+ let newOut = prev.tokensOut;
646
+ if (event.tokensIn != null) {
647
+ if (event.tokensIn < prev._lastMsgTokensIn) {
648
+ newIn = prev.tokensIn + event.tokensIn;
649
+ } else {
650
+ newIn = prev.tokensIn - prev._lastMsgTokensIn + event.tokensIn;
651
+ }
652
+ }
653
+ if (event.tokensOut != null) {
654
+ if (event.tokensOut < prev._lastMsgTokensOut) {
655
+ newOut = prev.tokensOut + event.tokensOut;
656
+ } else {
657
+ newOut = prev.tokensOut - prev._lastMsgTokensOut + event.tokensOut;
658
+ }
659
+ }
660
+ return {
661
+ ...prev,
662
+ totalCost: newCost,
663
+ _lastReportedCost: reportedCost,
664
+ tokensIn: newIn,
665
+ tokensOut: newOut,
666
+ _lastMsgTokensIn: event.tokensIn ?? prev._lastMsgTokensIn,
667
+ _lastMsgTokensOut: event.tokensOut ?? prev._lastMsgTokensOut
668
+ };
669
+ }
670
+ case "error":
671
+ return {
672
+ ...prev,
673
+ lastError: event.message,
674
+ logEntries: appendLog(prev.logEntries, `\u2717 ${event.message}`, "red")
675
+ };
676
+ case "credential:expired":
677
+ return {
678
+ ...prev,
679
+ lastError: `Credentials expired (${event.provider}). Run gs-assume to refresh.`,
680
+ logEntries: appendLog(
681
+ prev.logEntries,
682
+ `\u2717 credentials expired (${event.provider})`,
683
+ "red"
684
+ )
685
+ };
686
+ case "verify:start":
687
+ return {
688
+ ...prev,
689
+ logEntries: appendLog(
690
+ prev.logEntries,
691
+ `verify:start ${event.itemCount} commands`
692
+ )
693
+ };
694
+ case "verify:result":
695
+ return {
696
+ ...prev,
697
+ logEntries: appendLog(
698
+ prev.logEntries,
699
+ `verify ${event.passed ? "\u2713" : "\u2717"} ${event.command}`,
700
+ event.passed ? "green" : "red"
701
+ )
702
+ };
703
+ case "verify:done":
704
+ return {
705
+ ...prev,
706
+ logEntries: appendLog(
707
+ prev.logEntries,
708
+ `verify done: ${event.passed}/${event.passed + event.failed}`,
709
+ event.failed === 0 ? "green" : "red"
710
+ )
711
+ };
712
+ case "session:done":
713
+ return {
714
+ ...prev,
715
+ done: true,
716
+ exitReason: event.exitReason,
717
+ totalCost: event.cumulativeCostUsd ?? prev.totalCost,
718
+ logEntries: appendLog(
719
+ prev.logEntries,
720
+ `session done: ${event.exitReason}`,
721
+ event.exitReason === "sentinel" ? "green" : "yellow"
722
+ )
723
+ };
724
+ default:
725
+ return prev;
726
+ }
727
+ }
728
+
729
+ // src/commands/autopilot-tui.ts
730
+ import { SessionRunner } from "@glrs-dev/autopilot";
731
+ import { OpenCodeAdapter } from "@glrs-dev/adapter-opencode";
732
+ async function runAutopilot() {
733
+ const cwd = process.cwd();
734
+ if (!process.stderr.isTTY) {
735
+ process.stderr.write("glrs autopilot requires a TTY.\n");
736
+ process.exit(1);
737
+ }
738
+ const planPath = await pickPlan(cwd);
739
+ if (!planPath) {
740
+ return;
741
+ }
742
+ const adapter = new OpenCodeAdapter();
743
+ const runner = new SessionRunner({
744
+ planPath,
745
+ cwd,
746
+ fast: true,
747
+ adapter
748
+ });
749
+ const app = render(
750
+ React3.createElement(AutopilotExecution, { emitter: runner.events }),
751
+ { stdout: process.stderr, exitOnCtrlC: false }
752
+ );
753
+ const sigintHandler = () => {
754
+ runner.abort();
755
+ };
756
+ process.on("SIGINT", sigintHandler);
757
+ await new Promise((resolve) => setTimeout(resolve, 50));
758
+ try {
759
+ await runner.run();
760
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
761
+ } catch (err) {
762
+ const message = err instanceof Error ? err.message : String(err);
763
+ process.stderr.write(`
764
+ \x1B[31m\u2717 Fatal error: ${message}\x1B[0m
765
+ `);
766
+ } finally {
767
+ process.removeListener("SIGINT", sigintHandler);
768
+ app.unmount();
769
+ app.clear();
770
+ }
771
+ }
772
+ function pickPlan(startDir) {
773
+ return new Promise((resolve) => {
774
+ const app = render(
775
+ React3.createElement(AutopilotPicker, {
776
+ startDir,
777
+ onSelect: (planPath) => {
778
+ app.unmount();
779
+ app.clear();
780
+ resolve(planPath);
781
+ },
782
+ onCancel: () => {
783
+ app.unmount();
784
+ app.clear();
785
+ resolve(null);
786
+ }
787
+ }),
788
+ { stdout: process.stderr, exitOnCtrlC: true }
789
+ );
790
+ });
791
+ }
792
+
793
+ export {
794
+ runAutopilot
795
+ };