@glrs-dev/cli 2.3.0 → 2.4.0

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 (62) hide show
  1. package/CHANGELOG.md +2 -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/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
  49. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  50. package/dist/vendor/harness-opencode/dist/cli.js +335 -639
  51. package/dist/vendor/harness-opencode/dist/index.js +35 -8
  52. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  53. package/dist/vendor/harness-opencode/package.json +1 -1
  54. package/package.json +8 -2
  55. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
  56. package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
  57. package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
  58. package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
  59. package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
  60. package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
  61. package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
  62. package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +0 -117
@@ -0,0 +1,248 @@
1
+ import {
2
+ __require
3
+ } from "../chunk-3RG5ZIWI.js";
4
+
5
+ // src/commands/autopilot-interactive.ts
6
+ import * as fs2 from "fs";
7
+ import * as path2 from "path";
8
+
9
+ // src/plan-paths.ts
10
+ import { execFile } from "child_process";
11
+ import * as fs from "fs/promises";
12
+ import * as os from "os";
13
+ import * as path from "path";
14
+ function execFileP(file, args, opts = {}) {
15
+ const { cwd, timeoutMs = 5e3 } = opts;
16
+ return new Promise((resolve3, reject) => {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19
+ execFile(
20
+ file,
21
+ args,
22
+ { signal: controller.signal, cwd, encoding: "utf8" },
23
+ (err, stdout) => {
24
+ clearTimeout(timer);
25
+ if (err) {
26
+ reject(err);
27
+ return;
28
+ }
29
+ resolve3(stdout ?? "");
30
+ }
31
+ );
32
+ });
33
+ }
34
+ function expandTilde(p) {
35
+ if (p === "~") return os.homedir();
36
+ if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
37
+ return p;
38
+ }
39
+ async function getRepoFolder(worktreeDir) {
40
+ let stdout;
41
+ try {
42
+ stdout = await execFileP(
43
+ "git",
44
+ ["rev-parse", "--git-common-dir"],
45
+ { cwd: worktreeDir }
46
+ );
47
+ } catch (err) {
48
+ const msg = err instanceof Error ? err.message : "unknown error running `git rev-parse --git-common-dir`";
49
+ throw new Error(
50
+ `getRepoFolder: failed to resolve git-common-dir for ${worktreeDir}: ${msg}`
51
+ );
52
+ }
53
+ const gitCommonDir = stdout.trim();
54
+ if (!gitCommonDir) {
55
+ throw new Error(
56
+ `getRepoFolder: \`git rev-parse --git-common-dir\` returned empty for ${worktreeDir}`
57
+ );
58
+ }
59
+ const absCommonDir = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(worktreeDir, gitCommonDir);
60
+ const repoRoot = path.dirname(absCommonDir);
61
+ return path.basename(repoRoot);
62
+ }
63
+ async function getPlanDir(worktreeDir) {
64
+ const override = process.env.GLRS_PLAN_DIR ?? process.env.GLORIOUS_PLAN_DIR;
65
+ const base = override ? expandTilde(override) : path.join(os.homedir(), ".glrs", "opencode");
66
+ const repoFolder = await getRepoFolder(worktreeDir);
67
+ const planDir = path.join(base, repoFolder, "plans");
68
+ await fs.mkdir(planDir, { recursive: true });
69
+ return planDir;
70
+ }
71
+
72
+ // src/commands/autopilot-interactive.ts
73
+ function defaultBanner(message) {
74
+ process.stdout.write(`
75
+ ${message}
76
+ `);
77
+ }
78
+ async function orchestrateAutopilot(opts, deps) {
79
+ const banner = deps.onBanner ?? defaultBanner;
80
+ const cwd = opts.cwd ?? process.cwd();
81
+ banner("\u2192 Phase 1/3: Scoping (interactive)...");
82
+ const scoperResult = await deps.runScoper({
83
+ planDir: opts.planDir,
84
+ slug: opts.slug,
85
+ initialGoal: opts.initialGoal,
86
+ ...opts.existingPlanContent ? { existingPlanContent: opts.existingPlanContent } : {}
87
+ });
88
+ banner(`\u2713 Scope captured at ${scoperResult.scopePath}`);
89
+ const actualSlug = path2.basename(path2.dirname(scoperResult.scopePath));
90
+ banner("\u2192 Phase 2/3: Planning (headless)...");
91
+ const planResult = await deps.runPlan({
92
+ scopePath: scoperResult.scopePath,
93
+ planDir: opts.planDir,
94
+ slug: actualSlug || opts.slug
95
+ });
96
+ banner(`\u2713 Plan written at ${planResult.planPath}`);
97
+ banner("\u2192 Phase 3/3: Executing (headless loop)...");
98
+ const loopResult = await deps.runLoop({
99
+ planPath: planResult.planPath,
100
+ cwd
101
+ });
102
+ return {
103
+ scopePath: scoperResult.scopePath,
104
+ planPath: planResult.planPath,
105
+ loopResult
106
+ };
107
+ }
108
+ function deriveSlug(goal) {
109
+ const slug = goal.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
110
+ return slug.length > 0 ? slug : `feature-${Date.now()}`;
111
+ }
112
+ function extractGoalFromPlan(content, filePath) {
113
+ const h1Match = content.match(/^#\s+(.+)$/m);
114
+ if (h1Match) return h1Match[1].trim();
115
+ const goalSectionMatch = content.match(/^##\s+Goal\s*\n+([^\n#][^\n]*)/m);
116
+ if (goalSectionMatch) return goalSectionMatch[1].trim();
117
+ const base = path2.basename(filePath, path2.extname(filePath));
118
+ return base.replace(/-/g, " ");
119
+ }
120
+ async function runInteractiveAutopilot(cwd, planPath, _deps, options) {
121
+ const _getPlanDir = _deps?.getPlanDir ?? getPlanDir;
122
+ const planDir = await _getPlanDir(cwd);
123
+ let goal;
124
+ let ticketRef;
125
+ let existingPlanContent;
126
+ if (planPath) {
127
+ const resolvedPath = path2.resolve(cwd, planPath);
128
+ const isDir = fs2.existsSync(resolvedPath) && fs2.statSync(resolvedPath).isDirectory();
129
+ const hasMainMd = isDir && fs2.existsSync(path2.join(resolvedPath, "main.md"));
130
+ const isMdFile = !isDir && resolvedPath.endsWith(".md") && fs2.existsSync(resolvedPath);
131
+ if (isDir && !hasMainMd) {
132
+ const { pickPlanFile } = await import("./plan-picker.js");
133
+ const picked = await pickPlanFile(resolvedPath);
134
+ if (!picked) {
135
+ return {
136
+ scopePath: "",
137
+ planPath: resolvedPath,
138
+ loopResult: { exitReason: "error", iterations: 0, message: "No plan selected." }
139
+ };
140
+ }
141
+ return runInteractiveAutopilot(cwd, picked, _deps, options);
142
+ }
143
+ if (hasMainMd || isMdFile) {
144
+ const { parsePlanState } = await import("@glrs-dev/autopilot");
145
+ const planState = parsePlanState(resolvedPath);
146
+ if (planState.totalItems > 0) {
147
+ const banner = _deps?.onBanner ?? ((msg) => process.stdout.write(`
148
+ ${msg}
149
+ `));
150
+ const unchecked = planState.totalItems - planState.checkedItems;
151
+ if (unchecked === 0) {
152
+ banner(`\u26A0 All ${planState.totalItems} items already checked \u2014 nothing to execute`);
153
+ return {
154
+ scopePath: "",
155
+ planPath: resolvedPath,
156
+ loopResult: { exitReason: "sentinel", iterations: 0, message: "All items already checked." }
157
+ };
158
+ }
159
+ banner(`\u2192 Plan validated: ${unchecked}/${planState.totalItems} items remaining`);
160
+ if (planState.type === "multi") {
161
+ banner(` ${planState.phaseCount} phases, ${planState.phaseCount - planState.phasesCompleted} remaining`);
162
+ }
163
+ banner(`\u2192 Executing against: ${resolvedPath}`);
164
+ const { runLoopSession: runLoopSession2 } = await import("@glrs-dev/autopilot");
165
+ const _runLoop = _deps?.runLoop ?? runLoopSession2;
166
+ const loopResult = await _runLoop({ planPath: resolvedPath, cwd, fast: options?.fast, resume: options?.resume, maxIterationsPerPhase: options?.maxIterationsPerPhase, parallel: options?.parallel, ship: options?.ship, logger: options?.logger });
167
+ return {
168
+ scopePath: "",
169
+ planPath: resolvedPath,
170
+ loopResult
171
+ };
172
+ }
173
+ if (isDir) {
174
+ const banner = _deps?.onBanner ?? ((msg) => process.stdout.write(`
175
+ ${msg}
176
+ `));
177
+ banner(`\u2192 Executing plan directory: ${resolvedPath}`);
178
+ const { runLoopSession: runLoopSession2 } = await import("@glrs-dev/autopilot");
179
+ const _runLoop = _deps?.runLoop ?? runLoopSession2;
180
+ const loopResult = await _runLoop({ planPath: resolvedPath, cwd, fast: options?.fast, resume: options?.resume, maxIterationsPerPhase: options?.maxIterationsPerPhase, parallel: options?.parallel, ship: options?.ship, logger: options?.logger });
181
+ return {
182
+ scopePath: "",
183
+ planPath: resolvedPath,
184
+ loopResult
185
+ };
186
+ }
187
+ }
188
+ const content = fs2.readFileSync(resolvedPath, "utf-8");
189
+ goal = extractGoalFromPlan(content, resolvedPath);
190
+ ticketRef = "";
191
+ existingPlanContent = content;
192
+ } else {
193
+ if (_deps?.promptGoal) {
194
+ goal = await _deps.promptGoal();
195
+ } else {
196
+ const { input } = await import("@inquirer/prompts");
197
+ goal = await input({
198
+ message: "What do you want to build? (one sentence, free-form)",
199
+ validate: (v) => v.trim().length > 0 ? true : "Please describe what you want to build."
200
+ });
201
+ }
202
+ if (_deps?.promptTicketRef) {
203
+ ticketRef = await _deps.promptTicketRef();
204
+ } else {
205
+ const { input } = await import("@inquirer/prompts");
206
+ ticketRef = await input({
207
+ message: "Optional ticket or issue ref (Linear ID, GitHub issue URL, etc.)",
208
+ default: ""
209
+ });
210
+ }
211
+ }
212
+ const slug = deriveSlug(goal);
213
+ const seedDir = path2.join(planDir, slug);
214
+ const seedPath = path2.join(seedDir, "scope-seed.md");
215
+ const _mkdirSync = _deps?.mkdirSync ?? ((p, o) => fs2.mkdirSync(p, o));
216
+ const _writeFileSync = _deps?.writeFileSync ?? fs2.writeFileSync;
217
+ _mkdirSync(seedDir, { recursive: true });
218
+ const seedContent = [
219
+ `# Scope Seed: ${slug}`,
220
+ "",
221
+ `## Goal`,
222
+ "",
223
+ goal,
224
+ "",
225
+ ...ticketRef.trim() ? [`## Ticket / Issue Ref`, "", ticketRef.trim(), ""] : []
226
+ ].join("\n");
227
+ _writeFileSync(seedPath, seedContent);
228
+ const { runScoperSession } = await import("./scoper.js");
229
+ const { runPlanSession } = await import("@glrs-dev/autopilot");
230
+ const { runLoopSession } = await import("@glrs-dev/autopilot");
231
+ return orchestrateAutopilot(
232
+ { slug, planDir, cwd, initialGoal: goal, existingPlanContent },
233
+ {
234
+ runScoper: _deps?.runScoper ?? runScoperSession,
235
+ runPlan: _deps?.runPlan ?? ((opts) => {
236
+ const { OpenCodeAdapter } = __require("@glrs-dev/adapter-opencode");
237
+ return runPlanSession({ ...opts, adapter: new OpenCodeAdapter() });
238
+ }),
239
+ runLoop: _deps?.runLoop ?? runLoopSession,
240
+ onBanner: _deps?.onBanner
241
+ }
242
+ );
243
+ }
244
+ export {
245
+ deriveSlug,
246
+ orchestrateAutopilot,
247
+ runInteractiveAutopilot
248
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // src/commands/autopilot-raw.ts
4
+ import { createOpencode } from "@opencode-ai/sdk";
5
+ import * as YAML from "yaml";
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ var planDirArg = process.argv[2];
9
+ if (!planDirArg) {
10
+ process.stderr.write("Usage: bun autopilot-raw.ts <plan-dir>\n");
11
+ process.exit(1);
12
+ }
13
+ var PLAN_DIR = path.resolve(planDirArg);
14
+ var SPEC_DIR = path.join(PLAN_DIR, "spec");
15
+ var MAIN_YAML = path.join(SPEC_DIR, "main.yaml");
16
+ var MAX_ITERATIONS = 10;
17
+ var POLL_INTERVAL_MS = 2e3;
18
+ if (!fs.existsSync(PLAN_DIR) || !fs.statSync(PLAN_DIR).isDirectory()) {
19
+ process.stderr.write(`Plan dir does not exist: ${PLAN_DIR}
20
+ `);
21
+ process.exit(1);
22
+ }
23
+ function log(level, msg) {
24
+ const ts = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
25
+ process.stderr.write(`${ts} [${level.padEnd(4)}] ${msg}
26
+ `);
27
+ }
28
+ function unwrap(res) {
29
+ if (res && typeof res === "object" && "data" in res) return res.data;
30
+ return res;
31
+ }
32
+ function listMarkdownFiles() {
33
+ return fs.readdirSync(PLAN_DIR).filter((f) => f.endsWith(".md") && (f === "main.md" || /^wave_/.test(f))).sort((a, b) => {
34
+ if (a === "main.md") return -1;
35
+ if (b === "main.md") return 1;
36
+ return a.localeCompare(b, void 0, { numeric: true });
37
+ });
38
+ }
39
+ function specPathFor(mdFile) {
40
+ return path.join(SPEC_DIR, mdFile.replace(/\.md$/, ".yaml"));
41
+ }
42
+ function specIsComplete(mdFile) {
43
+ const p = specPathFor(mdFile);
44
+ if (!fs.existsSync(p)) return false;
45
+ try {
46
+ const data = YAML.parse(fs.readFileSync(p, "utf8"));
47
+ if (mdFile === "main.md") return Array.isArray(data?.phases) && data.phases.length > 0;
48
+ if (!Array.isArray(data?.items) || data.items.length === 0) return false;
49
+ return data.items.every((it) => typeof it?.id === "string" && typeof it?.checked === "boolean");
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ function readPhaseItems(specPath) {
55
+ try {
56
+ const data = YAML.parse(fs.readFileSync(specPath, "utf8"));
57
+ return Array.isArray(data?.items) ? data.items : [];
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+ function markPhaseCompleted(phaseFile) {
63
+ try {
64
+ const data = YAML.parse(fs.readFileSync(MAIN_YAML, "utf8"));
65
+ for (const p of data?.phases ?? []) {
66
+ if (p.file === phaseFile) p.completed = true;
67
+ }
68
+ fs.writeFileSync(MAIN_YAML, YAML.stringify(data));
69
+ } catch (e) {
70
+ log("ERR", `Failed to mark ${phaseFile} completed: ${e}`);
71
+ }
72
+ }
73
+ var opencode = await createOpencode({ hostname: "127.0.0.1", port: 0, timeout: 3e4 });
74
+ var { client, server } = opencode;
75
+ log("INFO", `Server ready at ${server.url}`);
76
+ var shuttingDown = false;
77
+ async function shutdown(code = 0) {
78
+ if (shuttingDown) process.exit(code);
79
+ shuttingDown = true;
80
+ try {
81
+ await server.close();
82
+ } catch {
83
+ }
84
+ process.exit(code);
85
+ }
86
+ process.on("SIGINT", () => {
87
+ log("INFO", "SIGINT");
88
+ void shutdown(0);
89
+ });
90
+ process.on("SIGTERM", () => {
91
+ void shutdown(0);
92
+ });
93
+ var currentWait = null;
94
+ function settle(w, r) {
95
+ clearTimeout(w.timer);
96
+ if (currentWait === w) currentWait = null;
97
+ w.resolve(r);
98
+ }
99
+ function waitForSession(sessionId, timeoutMs = 10 * 60 * 1e3) {
100
+ return new Promise((resolve2) => {
101
+ const w = {
102
+ sessionId,
103
+ resolve: resolve2,
104
+ timer: setTimeout(() => {
105
+ log("ERR", "Timeout");
106
+ settle(w, "timeout");
107
+ }, timeoutMs)
108
+ };
109
+ currentWait = w;
110
+ });
111
+ }
112
+ (async function consumeEvents() {
113
+ try {
114
+ const subscribed = await client.event.subscribe();
115
+ const stream = subscribed?.stream ?? subscribed?.data?.stream;
116
+ if (!stream) {
117
+ log("ERR", "No SSE stream");
118
+ return;
119
+ }
120
+ for await (const evt of stream) {
121
+ const type = evt?.type ?? "";
122
+ const props = evt?.properties ?? {};
123
+ const w = currentWait;
124
+ if (type === "session.idle" && w && props.sessionID === w.sessionId) {
125
+ settle(w, "idle");
126
+ } else if (type === "session.error" && w && props.sessionID === w.sessionId) {
127
+ const err = props.error?.data?.message ?? props.error?.message ?? "unknown";
128
+ log("ERR", `session.error: ${err}`);
129
+ settle(w, "error");
130
+ }
131
+ }
132
+ } catch (e) {
133
+ log("ERR", `SSE crashed: ${e}`);
134
+ }
135
+ })();
136
+ function startPoller(sessionId) {
137
+ const seenParts = /* @__PURE__ */ new Set();
138
+ let running = true;
139
+ const poll = async () => {
140
+ while (running) {
141
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
142
+ if (!running) break;
143
+ try {
144
+ const msgs = unwrap(await client.session.messages({ path: { id: sessionId } }));
145
+ if (!msgs) continue;
146
+ for (const msg of msgs) {
147
+ if (msg?.info?.role !== "assistant") continue;
148
+ for (const part of msg?.parts ?? []) {
149
+ if (part?.type !== "tool") continue;
150
+ const key = `${part.callID ?? part.id}:${part.state?.status}`;
151
+ if (seenParts.has(key)) continue;
152
+ seenParts.add(key);
153
+ const tool = part.tool ?? "?";
154
+ const status = part.state?.status ?? "?";
155
+ const title = part.state?.title ?? "";
156
+ const input = part.state?.input;
157
+ let arg = "";
158
+ if (input && typeof input === "object") {
159
+ for (const k of ["filePath", "file_path", "path", "command", "pattern", "query"]) {
160
+ const v = input[k];
161
+ if (typeof v === "string" && v.length > 0) {
162
+ arg = v.split("\n")[0].slice(0, 80);
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ if (status === "completed") {
168
+ log("TOOL", `\u2713 ${tool} ${arg || title}`);
169
+ } else if (status === "error") {
170
+ const err = (part.state?.error ?? "").toString().slice(0, 100);
171
+ log("ERR", `\u2717 ${tool} ${arg || title} \u2192 ${err}`);
172
+ } else if (status === "running") {
173
+ log("TOOL", `\u25B6 ${tool} ${arg || title}`);
174
+ }
175
+ }
176
+ const cost = msg?.info?.cost;
177
+ const tokens = msg?.info?.tokens;
178
+ if (cost > 0 && !seenParts.has(`cost:${msg.info.id}`)) {
179
+ seenParts.add(`cost:${msg.info.id}`);
180
+ const parts = [`$${cost.toFixed(4)}`, `in=${tokens?.input ?? 0}`, `out=${tokens?.output ?? 0}`];
181
+ if (tokens?.cache?.read) parts.push(`cache=${tokens.cache.read}`);
182
+ log("COST", parts.join(" | "));
183
+ }
184
+ }
185
+ } catch {
186
+ }
187
+ }
188
+ };
189
+ poll();
190
+ return { stop: () => {
191
+ running = false;
192
+ } };
193
+ }
194
+ async function runPrompt(agent, text, model) {
195
+ const session = unwrap(await client.session.create({ body: {} }));
196
+ const sessionId = session?.id;
197
+ if (!sessionId) {
198
+ log("ERR", "No session ID");
199
+ return { sessionId: "", result: "error" };
200
+ }
201
+ log("INFO", `Session: ${sessionId} (${agent}${model ? ` \u2192 ${model.providerID}/${model.modelID}` : ""})`);
202
+ const waiter = waitForSession(sessionId);
203
+ const poller = startPoller(sessionId);
204
+ const body = { parts: [{ type: "text", text }] };
205
+ if (agent && agent !== "default") body.agent = agent;
206
+ if (model) body.model = model;
207
+ client.session.prompt({ path: { id: sessionId }, body }).catch((e) => {
208
+ log("ERR", `prompt rejected: ${e}`);
209
+ if (currentWait?.sessionId === sessionId) settle(currentWait, "error");
210
+ });
211
+ const result = await waiter;
212
+ await new Promise((r) => setTimeout(r, 500));
213
+ poller.stop();
214
+ return { sessionId, result };
215
+ }
216
+ function enrichmentPromptForMain(waveYamlFiles) {
217
+ const mainRel = path.relative(process.cwd(), path.join(PLAN_DIR, "main.md"));
218
+ const specRel = path.relative(process.cwd(), MAIN_YAML);
219
+ const phaseLines = waveYamlFiles.map((f) => ` - file: ${f}
220
+ completed: false`).join("\n");
221
+ return `Read the plan at \`${mainRel}\` and write a structured YAML spec to \`${specRel}\` using the file_edit tool.
222
+
223
+ The YAML must have this exact shape:
224
+
225
+ \`\`\`yaml
226
+ title: "<plan title>"
227
+ goal: "<goal text>"
228
+ phases:
229
+ ${phaseLines}
230
+ \`\`\`
231
+
232
+ Use the file_edit tool to create \`${specRel}\`. Output no prose.`;
233
+ }
234
+ function enrichmentPromptForWave(mdFile) {
235
+ const mdRel = path.relative(process.cwd(), path.join(PLAN_DIR, mdFile));
236
+ const specRel = path.relative(process.cwd(), specPathFor(mdFile));
237
+ return `Read the phase plan at \`${mdRel}\` and write a structured YAML spec to \`${specRel}\` using the file_edit tool.
238
+
239
+ First read the markdown. Then read relevant codebase files for context. Write a YAML file:
240
+
241
+ \`\`\`yaml
242
+ items:
243
+ - id: "1.1"
244
+ intent: "what this does"
245
+ checked: false
246
+ files:
247
+ - path: <relative path>
248
+ isNew: <bool>
249
+ verify: "<shell command>"
250
+ \`\`\`
251
+
252
+ Rules:
253
+ - Every item starts with checked: false.
254
+ - Cover every actionable item. Do not invent items.
255
+ - Use the file_edit tool to create \`${specRel}\`. Output no prose.`;
256
+ }
257
+ async function runEnrichment() {
258
+ log("INFO", "\u2550\u2550\u2550 ENRICHMENT \u2550\u2550\u2550");
259
+ const mdFiles = listMarkdownFiles();
260
+ fs.mkdirSync(SPEC_DIR, { recursive: true });
261
+ for (const mdFile of mdFiles) {
262
+ if (specIsComplete(mdFile)) {
263
+ log("DIM", `Skip ${mdFile}`);
264
+ continue;
265
+ }
266
+ log("INFO", `Enriching ${mdFile}`);
267
+ const prompt = mdFile === "main.md" ? enrichmentPromptForMain(mdFiles.filter((f) => f !== "main.md").map((f) => f.replace(/\.md$/, ".yaml"))) : enrichmentPromptForWave(mdFile);
268
+ const { result } = await runPrompt("prime", prompt);
269
+ if (result !== "idle") {
270
+ log("ERR", `Enrichment ${mdFile}: ${result}`);
271
+ continue;
272
+ }
273
+ log("INFO", specIsComplete(mdFile) ? `\u2713 ${mdFile}` : `\u26A0 ${mdFile} incomplete`);
274
+ }
275
+ }
276
+ function executionPrompt(phaseFile, yamlText) {
277
+ const specRel = path.relative(process.cwd(), path.join(SPEC_DIR, phaseFile));
278
+ return `You are completing one phase of a plan. The phase spec lives at \`${specRel}\`:
279
+
280
+ \`\`\`yaml
281
+ ${yamlText}
282
+ \`\`\`
283
+
284
+ Complete every item where checked: false. For each:
285
+ 1. Edit or create the files listed.
286
+ 2. Run the verify command if present.
287
+ 3. Use file_edit to change checked: false to checked: true in \`${specRel}\`.
288
+
289
+ Rules:
290
+ - Do not modify checked: true items.
291
+ - Work in id order.
292
+ - When all items are checked: true, you are done.`;
293
+ }
294
+ async function runExecution() {
295
+ log("INFO", "\u2550\u2550\u2550 EXECUTION \u2550\u2550\u2550");
296
+ if (!fs.existsSync(MAIN_YAML)) {
297
+ log("ERR", "No spec/main.yaml");
298
+ return;
299
+ }
300
+ let phases = [];
301
+ try {
302
+ const main = YAML.parse(fs.readFileSync(MAIN_YAML, "utf8"));
303
+ phases = main?.phases ?? [];
304
+ } catch (e) {
305
+ log("ERR", `Parse error: ${e}`);
306
+ return;
307
+ }
308
+ const remaining = phases.filter((p) => !p.completed);
309
+ log("INFO", `${phases.length} phases, ${remaining.length} remaining`);
310
+ if (remaining.length === 0) {
311
+ log("INFO", "All done.");
312
+ return;
313
+ }
314
+ for (let i = 0; i < remaining.length; i++) {
315
+ const phase = remaining[i];
316
+ log("INFO", `
317
+ \u2550\u2550\u2550 PHASE: ${phase.file} (${i + 1}/${remaining.length}) \u2550\u2550\u2550`);
318
+ const specPath = path.join(SPEC_DIR, phase.file);
319
+ if (!fs.existsSync(specPath)) {
320
+ log("ERR", `Missing: ${specPath}`);
321
+ continue;
322
+ }
323
+ for (let iter = 1; iter <= MAX_ITERATIONS; iter++) {
324
+ const items2 = readPhaseItems(specPath);
325
+ const unchecked = items2.filter((it) => !it.checked);
326
+ if (items2.length > 0 && unchecked.length === 0) {
327
+ log("INFO", `\u2713 ${phase.file} complete (${items2.length}/${items2.length})`);
328
+ markPhaseCompleted(phase.file);
329
+ break;
330
+ }
331
+ log("INFO", `Iter ${iter}/${MAX_ITERATIONS} \u2014 ${items2.length - unchecked.length}/${items2.length} done`);
332
+ const yamlText = fs.readFileSync(specPath, "utf8");
333
+ const { result } = await runPrompt("build", executionPrompt(phase.file, yamlText), {
334
+ providerID: "amazon-bedrock",
335
+ modelID: "zai.glm-5"
336
+ });
337
+ if (result !== "idle") {
338
+ log("ERR", `Iter ${iter}: ${result}`);
339
+ break;
340
+ }
341
+ }
342
+ const items = readPhaseItems(specPath);
343
+ if (items.length > 0 && items.every((it) => it.checked)) {
344
+ markPhaseCompleted(phase.file);
345
+ } else {
346
+ log("WARN", `${phase.file}: ${items.filter((it) => it.checked).length}/${items.length}`);
347
+ }
348
+ }
349
+ }
350
+ var phasesRemaining = (() => {
351
+ if (!fs.existsSync(MAIN_YAML)) return "?";
352
+ try {
353
+ const m = YAML.parse(fs.readFileSync(MAIN_YAML, "utf8"));
354
+ return (m?.phases ?? []).filter((p) => !p.completed).length;
355
+ } catch {
356
+ return "?";
357
+ }
358
+ })();
359
+ log("INFO", `Plan: ${PLAN_DIR} (${listMarkdownFiles().length} md, ${phasesRemaining} phases remaining)`);
360
+ try {
361
+ await runEnrichment();
362
+ await runExecution();
363
+ log("INFO", "\n\u2713 Done.");
364
+ } catch (e) {
365
+ log("ERR", `Fatal: ${e?.stack ?? e}`);
366
+ } finally {
367
+ await shutdown(0);
368
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `glrs autopilot` — TUI plan picker at cwd, then run the autopilot session
3
+ * with live event rendering to stderr.
4
+ */
5
+ declare function runAutopilot(): Promise<void>;
6
+
7
+ export { runAutopilot };
@@ -0,0 +1,7 @@
1
+ import {
2
+ runAutopilot
3
+ } from "../chunk-2VMFXAJH.js";
4
+ import "../chunk-3RG5ZIWI.js";
5
+ export {
6
+ runAutopilot
7
+ };
@@ -0,0 +1,39 @@
1
+ import * as cmd_ts_dist_cjs_runner_js from 'cmd-ts/dist/cjs/runner.js';
2
+ import * as cmd_ts_dist_cjs_helpdoc_js from 'cmd-ts/dist/cjs/helpdoc.js';
3
+ import * as cmd_ts_dist_cjs_argparser_js from 'cmd-ts/dist/cjs/argparser.js';
4
+
5
+ /**
6
+ * `glrs oc autopilot` — Interactive three-phase autopilot orchestrator.
7
+ *
8
+ * --plan / -p <path>: use an existing plan at this path.
9
+ * --fast / -f: enrich plan for fast-model execution, then run with mid-execute tier.
10
+ * --status: read and pretty-print the current autopilot status from .agent/autopilot-status.json.
11
+ *
12
+ * When no --plan is given, opens an interactive file picker.
13
+ * When a plan is provided and has checkboxes, skips scoping/planning and executes directly.
14
+ *
15
+ * This is a thin wrapper: parse args → create SessionRunner → attach CLI renderer → run.
16
+ */
17
+ declare const autopilotInteractiveCmd: Partial<cmd_ts_dist_cjs_argparser_js.Register> & {
18
+ parse(context: cmd_ts_dist_cjs_argparser_js.ParseContext): Promise<cmd_ts_dist_cjs_argparser_js.ParsingResult<{
19
+ plan: string | undefined;
20
+ fast: boolean;
21
+ resume: boolean;
22
+ maxIterationsPerPhase: number | undefined;
23
+ parallel: number | undefined;
24
+ ship: boolean;
25
+ status: boolean;
26
+ }>>;
27
+ } & cmd_ts_dist_cjs_helpdoc_js.PrintHelp & cmd_ts_dist_cjs_helpdoc_js.ProvidesHelp & cmd_ts_dist_cjs_helpdoc_js.Named & Partial<cmd_ts_dist_cjs_helpdoc_js.Versioned> & cmd_ts_dist_cjs_argparser_js.Register & cmd_ts_dist_cjs_runner_js.Handling<{
28
+ plan: string | undefined;
29
+ fast: boolean;
30
+ resume: boolean;
31
+ maxIterationsPerPhase: number | undefined;
32
+ parallel: number | undefined;
33
+ ship: boolean;
34
+ status: boolean;
35
+ }, Promise<void>> & {
36
+ run(context: cmd_ts_dist_cjs_argparser_js.ParseContext): Promise<cmd_ts_dist_cjs_argparser_js.ParsingResult<Promise<void>>>;
37
+ } & Partial<cmd_ts_dist_cjs_helpdoc_js.Versioned & cmd_ts_dist_cjs_helpdoc_js.Descriptive & cmd_ts_dist_cjs_helpdoc_js.Aliased>;
38
+
39
+ export { autopilotInteractiveCmd };