godmode 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,779 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HelpPage
4
+ } from "./chunk-YDXTIN53.js";
5
+
6
+ // ../../commands/agent/dist/index.js
7
+ import { spawnSync } from "child_process";
8
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
9
+ import { resolve as resolve3 } from "path";
10
+ import { closeSync, existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync as writeFileSync2 } from "fs";
11
+ import { resolve as resolve2 } from "path";
12
+ import { randomUUID } from "crypto";
13
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { resolve } from "path";
16
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
17
+ var DEFAULT_HARNESS_ORDER = ["claude", "codex", "gemini", "pi"];
18
+ var HARNESSES = {
19
+ claude: {
20
+ id: "claude",
21
+ displayName: "Claude Code",
22
+ command: "claude",
23
+ modelFlags: ["--model"],
24
+ effortFlags: ["--effort"],
25
+ helpArgs: ["--help"],
26
+ promptHints: ["[prompt]", "your prompt"],
27
+ buildTurnArgs: ({ prompt, model, effort, passthroughArgs, resumeToken }) => [
28
+ "-p",
29
+ "--verbose",
30
+ "--output-format",
31
+ "stream-json",
32
+ ...resumeToken ? ["--resume", resumeToken] : [],
33
+ ...model ? ["--model", model] : [],
34
+ ...effort ? ["--effort", effort] : [],
35
+ prompt,
36
+ ...passthroughArgs
37
+ ]
38
+ },
39
+ codex: {
40
+ id: "codex",
41
+ displayName: "Codex CLI",
42
+ command: "codex",
43
+ modelFlags: ["-m", "--model"],
44
+ effortFlags: [],
45
+ helpArgs: ["--help"],
46
+ promptHints: ["[prompt]", "optional user prompt"],
47
+ buildTurnArgs: ({ prompt, model, passthroughArgs }) => [
48
+ "exec",
49
+ "--json",
50
+ ...model ? ["-m", model] : [],
51
+ prompt,
52
+ ...passthroughArgs
53
+ ]
54
+ },
55
+ gemini: {
56
+ id: "gemini",
57
+ displayName: "Gemini CLI",
58
+ command: "gemini",
59
+ modelFlags: ["-m", "--model"],
60
+ effortFlags: [],
61
+ helpArgs: ["--help"],
62
+ promptHints: ["query", "initial prompt"],
63
+ buildTurnArgs: ({ prompt, model, passthroughArgs, resumeToken }) => [
64
+ ...resumeToken ? ["--resume", resumeToken] : [],
65
+ ...model ? ["-m", model] : [],
66
+ "-p",
67
+ prompt,
68
+ "-o",
69
+ "stream-json",
70
+ ...passthroughArgs
71
+ ]
72
+ },
73
+ pi: {
74
+ id: "pi",
75
+ displayName: "Pi",
76
+ command: "pi",
77
+ modelFlags: ["--model"],
78
+ effortFlags: ["--thinking"],
79
+ helpArgs: ["--help"],
80
+ promptHints: ["[messages...]", "initial prompt"],
81
+ buildTurnArgs: ({ prompt, model, effort, passthroughArgs, resumeToken, sessionDir }) => [
82
+ "--print",
83
+ "--mode",
84
+ "json",
85
+ ...sessionDir ? ["--session-dir", sessionDir] : [],
86
+ ...resumeToken ? ["--continue"] : [],
87
+ ...model ? ["--model", model] : [],
88
+ ...effort ? ["--thinking", effort] : [],
89
+ prompt,
90
+ ...passthroughArgs
91
+ ]
92
+ }
93
+ };
94
+ function detectHarness(executor, requested) {
95
+ const ids = requested ? [requested] : DEFAULT_HARNESS_ORDER;
96
+ for (const id of ids) {
97
+ const harness = HARNESSES[id];
98
+ const result = executor(harness.command, harness.helpArgs);
99
+ if (!result.error && result.status === 0) return harness;
100
+ }
101
+ throw new Error(requested ? `Harness "${requested}" not found.` : "No supported coding harness found.");
102
+ }
103
+ function event(ts, id, turn, partial) {
104
+ return { ts, id, turn, ...partial };
105
+ }
106
+ function normalizeTurn(harness, id, turn, stdout, stderr, ts) {
107
+ const lines = stdout.split(/\r?\n/);
108
+ if (harness === "claude") return parseClaude(lines, id, turn, stderr, ts);
109
+ if (harness === "gemini") return parseGemini(lines, id, turn, stderr, ts);
110
+ if (harness === "pi") return parsePi(lines, id, turn, stderr, ts);
111
+ return {
112
+ sessionId: null,
113
+ assistantText: stdout.trim(),
114
+ events: [
115
+ event(ts, id, turn, { type: "turn.started" }),
116
+ event(ts, id, turn, { type: "assistant.completed", text: stdout.trim() }),
117
+ event(ts, id, turn, { type: "turn.completed", status: "completed" })
118
+ ]
119
+ };
120
+ }
121
+ function parseClaude(lines, id, turn, stderr, ts) {
122
+ const events = [];
123
+ let sessionId = null;
124
+ let assistantText = "";
125
+ let usage;
126
+ let timing;
127
+ for (const line of lines) {
128
+ if (!line.trim()) continue;
129
+ let parsed;
130
+ try {
131
+ parsed = JSON.parse(line);
132
+ } catch {
133
+ continue;
134
+ }
135
+ if (parsed.type === "system" && parsed.subtype === "init") {
136
+ sessionId = parsed.session_id ?? sessionId;
137
+ events.push(event(ts, id, turn, { type: "turn.started" }));
138
+ events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
139
+ }
140
+ if (parsed.type === "assistant") {
141
+ const text = (parsed.message?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("");
142
+ if (text) assistantText = text;
143
+ if (text) events.push(event(ts, id, turn, { type: "assistant.completed", text }));
144
+ }
145
+ if (parsed.type === "result") {
146
+ sessionId = parsed.session_id ?? sessionId;
147
+ usage = {
148
+ inputTokens: parsed.usage?.input_tokens,
149
+ outputTokens: parsed.usage?.output_tokens,
150
+ cachedTokens: parsed.usage?.cache_read_input_tokens
151
+ };
152
+ timing = { durationMs: parsed.duration_ms };
153
+ events.push(event(ts, id, turn, { type: "turn.completed", status: parsed.is_error ? "failed" : "completed" }));
154
+ }
155
+ }
156
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
157
+ return { sessionId, assistantText, events, usage, timing };
158
+ }
159
+ function parseGemini(lines, id, turn, stderr, ts) {
160
+ const events = [];
161
+ let sessionId = null;
162
+ let assistantText = "";
163
+ let usage;
164
+ let timing;
165
+ for (const line of lines) {
166
+ if (!line.trim()) continue;
167
+ let parsed;
168
+ try {
169
+ parsed = JSON.parse(line);
170
+ } catch {
171
+ continue;
172
+ }
173
+ if (parsed.type === "init") {
174
+ sessionId = parsed.session_id ?? sessionId;
175
+ events.push(event(ts, id, turn, { type: "turn.started" }));
176
+ events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
177
+ }
178
+ if (parsed.type === "message" && parsed.role === "assistant" && parsed.delta === true && typeof parsed.content === "string") {
179
+ assistantText += parsed.content;
180
+ events.push(event(ts, id, turn, { type: "assistant.delta", text: parsed.content }));
181
+ }
182
+ if (parsed.type === "result") {
183
+ usage = {
184
+ inputTokens: parsed.stats?.input_tokens,
185
+ outputTokens: parsed.stats?.output_tokens,
186
+ cachedTokens: parsed.stats?.cached,
187
+ totalTokens: parsed.stats?.total_tokens
188
+ };
189
+ timing = { durationMs: parsed.stats?.duration_ms };
190
+ events.push(event(ts, id, turn, { type: "assistant.completed", text: assistantText }));
191
+ events.push(event(ts, id, turn, { type: "turn.completed", status: parsed.status === "success" ? "completed" : "failed" }));
192
+ }
193
+ }
194
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
195
+ return { sessionId, assistantText, events, usage, timing };
196
+ }
197
+ function parsePi(lines, id, turn, stderr, ts) {
198
+ const events = [];
199
+ let sessionId = null;
200
+ let assistantText = "";
201
+ for (const line of lines) {
202
+ if (!line.trim()) continue;
203
+ let parsed;
204
+ try {
205
+ parsed = JSON.parse(line);
206
+ } catch {
207
+ continue;
208
+ }
209
+ if (parsed.type === "session") {
210
+ sessionId = parsed.id ?? sessionId;
211
+ events.push(event(ts, id, turn, { type: "turn.started" }));
212
+ events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
213
+ }
214
+ if (parsed.type === "turn_start") events.push(event(ts, id, turn, { type: "state.changed", state: "running" }));
215
+ if (parsed.type === "message_update" && parsed.assistantMessageEvent?.type === "thinking_start") {
216
+ events.push(event(ts, id, turn, { type: "state.changed", state: "thinking" }));
217
+ }
218
+ if (parsed.type === "message_update" && parsed.assistantMessageEvent?.type === "text_delta") {
219
+ const delta = parsed.assistantMessageEvent.delta ?? "";
220
+ assistantText += delta;
221
+ if (delta) events.push(event(ts, id, turn, { type: "assistant.delta", text: delta }));
222
+ }
223
+ if (parsed.type === "turn_end") {
224
+ const text = (parsed.message?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("");
225
+ if (text) assistantText = text;
226
+ events.push(event(ts, id, turn, { type: "assistant.completed", text: assistantText }));
227
+ events.push(event(ts, id, turn, { type: "turn.completed", status: "completed" }));
228
+ }
229
+ }
230
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
231
+ return { sessionId, assistantText, events };
232
+ }
233
+ var GODMODE_HOME = process.platform === "linux" && process.env.XDG_CONFIG_HOME ? resolve(process.env.XDG_CONFIG_HOME, "godmode") : resolve(homedir(), ".godmode");
234
+ var CODING_AGENTS_HOME = resolve(GODMODE_HOME, "coding-agents");
235
+ var RUNS_DIR = resolve(CODING_AGENTS_HOME, "runs");
236
+ var ACTIVE_RUNS_PATH = resolve(CODING_AGENTS_HOME, "active-runs.json");
237
+ function timestamp() {
238
+ return (/* @__PURE__ */ new Date()).toISOString();
239
+ }
240
+ function ensureDirs() {
241
+ mkdirSync(RUNS_DIR, { recursive: true });
242
+ }
243
+ function shellEscape(value) {
244
+ return `'${value.replace(/'/g, `"'"'`)}'`;
245
+ }
246
+ function writeJson(path, value) {
247
+ mkdirSync(resolve(path, ".."), { recursive: true });
248
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
249
+ `);
250
+ }
251
+ function readJson(path) {
252
+ try {
253
+ return JSON.parse(readFileSync(path, "utf-8"));
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+ function readJsonSettingsFile(path) {
259
+ return readJson(path) ?? {};
260
+ }
261
+ function readYamlSettingsFile(path) {
262
+ try {
263
+ return parseYaml(readFileSync(path, "utf-8")) ?? {};
264
+ } catch {
265
+ return {};
266
+ }
267
+ }
268
+ function migrateSettingsJson(dir) {
269
+ const yamlPath = resolve(dir, "settings.yaml");
270
+ if (existsSync(yamlPath)) return readYamlSettingsFile(yamlPath);
271
+ const jsonPath = resolve(dir, "settings.json");
272
+ if (!existsSync(jsonPath)) return {};
273
+ const settings = readJsonSettingsFile(jsonPath);
274
+ mkdirSync(dir, { recursive: true });
275
+ writeFileSync(yamlPath, stringifyYaml(settings));
276
+ process.stderr.write(`Migrated ${jsonPath} -> ${yamlPath}. Future agent settings are read from settings.yaml.
277
+ `);
278
+ return settings;
279
+ }
280
+ function loadSettings(cwd = process.cwd()) {
281
+ const globalSettings = migrateSettingsJson(GODMODE_HOME);
282
+ const projectSettings = migrateSettingsJson(resolve(cwd, ".godmode"));
283
+ return {
284
+ ...globalSettings,
285
+ ...projectSettings,
286
+ plugins: {
287
+ ...globalSettings.plugins ?? {},
288
+ ...projectSettings.plugins ?? {},
289
+ "coding-agents": {
290
+ ...globalSettings.plugins?.["coding-agents"] ?? {},
291
+ ...projectSettings.plugins?.["coding-agents"] ?? {}
292
+ }
293
+ }
294
+ };
295
+ }
296
+ function loadActiveRuns() {
297
+ return readJson(ACTIVE_RUNS_PATH) ?? {};
298
+ }
299
+ function saveActiveRun(cwd, id) {
300
+ const current = loadActiveRuns();
301
+ current[cwd] = id;
302
+ writeJson(ACTIVE_RUNS_PATH, current);
303
+ }
304
+ function activeRunIdForCwd(cwd) {
305
+ return loadActiveRuns()[cwd] ?? null;
306
+ }
307
+ function runDir(id) {
308
+ return resolve(RUNS_DIR, id);
309
+ }
310
+ function runPath(id) {
311
+ return resolve(runDir(id), "run.json");
312
+ }
313
+ function turnDir(id, turn) {
314
+ return resolve(runDir(id), "turns", String(turn).padStart(4, "0"));
315
+ }
316
+ function loadRun(id) {
317
+ const run = readJson(runPath(id));
318
+ if (!run) throw new Error(`Run not found: ${id}`);
319
+ return run;
320
+ }
321
+ function saveRun(run) {
322
+ run.updatedAt = timestamp();
323
+ writeJson(runPath(run.id), run);
324
+ }
325
+ function listRuns() {
326
+ ensureDirs();
327
+ return readdirSync(RUNS_DIR, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readJson(runPath(entry.name))).filter((value) => !!value).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
328
+ }
329
+ function createRun(cwd, harness, model, effort) {
330
+ ensureDirs();
331
+ const id = `run-${Date.now()}-${randomUUID().slice(0, 8)}`;
332
+ const run = {
333
+ id,
334
+ cwd,
335
+ harness: harness.id,
336
+ harnessName: harness.displayName,
337
+ sessions: {
338
+ zmx: `godmode-agent-${harness.id}-${id.replace(/^run-/, "")}`
339
+ },
340
+ model: model ?? null,
341
+ effort: effort ?? null,
342
+ status: "idle",
343
+ createdAt: timestamp(),
344
+ updatedAt: timestamp(),
345
+ lastTurn: 0
346
+ };
347
+ mkdirSync(runDir(id), { recursive: true });
348
+ mkdirSync(resolve(runDir(id), "turns"), { recursive: true });
349
+ saveRun(run);
350
+ saveActiveRun(cwd, id);
351
+ return run;
352
+ }
353
+ function buildShellCommand(harness, run, turn, prompt, passthroughArgs) {
354
+ const dir = turnDir(run.id, turn);
355
+ mkdirSync(dir, { recursive: true });
356
+ const stdoutPath = resolve(dir, "stdout.log");
357
+ const stderrPath = resolve(dir, "stderr.log");
358
+ const exitCodePath = resolve(dir, "exit-code.txt");
359
+ const sessionDir = resolve(runDir(run.id), "pi-session");
360
+ mkdirSync(sessionDir, { recursive: true });
361
+ const args = harness.buildTurnArgs({
362
+ prompt,
363
+ model: run.model ?? void 0,
364
+ effort: run.effort ?? void 0,
365
+ passthroughArgs,
366
+ resumeToken: run.sessions[run.harness] ?? void 0,
367
+ sessionDir
368
+ });
369
+ const command = `${[harness.command, ...args].map(shellEscape).join(" ")} > ${shellEscape(stdoutPath)} 2> ${shellEscape(stderrPath)}`;
370
+ const shell = `${command}; code=$?; printf '%s' "$code" > ${shellEscape(exitCodePath)}; exit "$code"`;
371
+ return { shell, stdoutPath, stderrPath, exitCodePath };
372
+ }
373
+ function waitForFile(path, timeoutMs = 3e5) {
374
+ const started = Date.now();
375
+ while (!existsSync(path)) {
376
+ if (Date.now() - started > timeoutMs) throw new Error(`Timed out waiting for ${path}`);
377
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
378
+ }
379
+ }
380
+ function latestTurn(run) {
381
+ if (run.lastTurn < 1) throw new Error(`Run ${run.id} has no turns yet.`);
382
+ const turn = readJson(resolve(turnDir(run.id, run.lastTurn), "normalized.json"));
383
+ if (!turn) throw new Error(`Turn ${run.lastTurn} not found for ${run.id}`);
384
+ return turn;
385
+ }
386
+ function resolveRunId(cwd, explicit) {
387
+ if (explicit) return explicit;
388
+ const active = activeRunIdForCwd(cwd);
389
+ if (!active) throw new Error(`No active coding-agent run for ${cwd}`);
390
+ return active;
391
+ }
392
+ function renderTurn(turn, outputMode) {
393
+ if (outputMode === "assistant-text") return turn.assistant.text;
394
+ if (outputMode === "raw") {
395
+ const stdout = existsSync2(turn.paths.stdout) ? readFileSync2(turn.paths.stdout, "utf-8") : "";
396
+ const stderr = existsSync2(turn.paths.stderr) ? readFileSync2(turn.paths.stderr, "utf-8") : "";
397
+ return JSON.stringify({ stdout, stderr }, null, 2);
398
+ }
399
+ if (outputMode === "events") {
400
+ return existsSync2(turn.paths.events) ? readFileSync2(turn.paths.events, "utf-8").trimEnd() : "";
401
+ }
402
+ return JSON.stringify(turn, null, 2);
403
+ }
404
+ function renderStatus(run) {
405
+ return JSON.stringify(run, null, 2);
406
+ }
407
+ function writeEvents(path, events) {
408
+ writeFileSyncSafe(path, `${events.map((event2) => JSON.stringify(event2)).join("\n")}
409
+ `);
410
+ }
411
+ function writeFileSyncSafe(path, content) {
412
+ writeFileSync2(path, content);
413
+ }
414
+ function attachHelp() {
415
+ return [
416
+ "Usage: godmode agent attach run <id>",
417
+ " or: godmode agent attach session <zmx-session-id>"
418
+ ].join("\n");
419
+ }
420
+ var AgentHelp = class extends HelpPage {
421
+ usage() {
422
+ return [
423
+ "godmode agent start [--harness <name>] [--model <id>] <prompt>",
424
+ "godmode agent send [--harness <name>] [--model <id>] <prompt>",
425
+ "godmode agent attach run <id>",
426
+ "godmode agent attach session <session-id>",
427
+ "godmode agent output [id] [--json|--assistant-text|--events|--raw]",
428
+ "godmode agent status [id]",
429
+ "godmode agent list"
430
+ ];
431
+ }
432
+ sections() {
433
+ return [
434
+ { title: "Options:", rows: [
435
+ [" --harness <name>", "coding agent harness (claude, codex, gemini, etc.)"],
436
+ [" --model <id>", "model identifier passed to the harness"],
437
+ [" --effort <level>", "effort level passed to the harness"],
438
+ [" --json", "output machine-readable events"],
439
+ [" --follow", "stream output until the run completes"]
440
+ ] },
441
+ { title: "Shortcuts:", rows: [
442
+ ["godmode agent <prompt>", "send prompt to active run, or start a new run"]
443
+ ] }
444
+ ];
445
+ }
446
+ };
447
+ function agentHelp() {
448
+ const lines = [];
449
+ const origLog = console.log;
450
+ console.log = (...args) => {
451
+ lines.push(args.join(" "));
452
+ };
453
+ try {
454
+ new AgentHelp().render();
455
+ } finally {
456
+ console.log = origLog;
457
+ }
458
+ return lines.join("\n");
459
+ }
460
+ function followOutput(run, turn, outputMode, writer) {
461
+ const dir = turnDir(run.id, turn);
462
+ const stdoutPath = resolve2(dir, "stdout.log");
463
+ const stderrPath = resolve2(dir, "stderr.log");
464
+ const statePath = runPath(run.id);
465
+ let assistant = "";
466
+ const emit = (event2) => {
467
+ if (outputMode === "events") writer.write(`${JSON.stringify(event2)}
468
+ `);
469
+ if (outputMode === "assistant-text") {
470
+ if (event2.type === "assistant.delta" && event2.text) writer.write(event2.text);
471
+ if (event2.type === "assistant.completed" && !assistant && event2.text) writer.write(event2.text);
472
+ }
473
+ };
474
+ streamFile(stdoutPath, (line) => {
475
+ const normalized = normalizeTurn(run.harness, run.id, turn, `${line}
476
+ `, "", timestamp());
477
+ for (const event2 of normalized.events) {
478
+ if (event2.type === "assistant.delta" && event2.text) assistant += event2.text;
479
+ emit(event2);
480
+ }
481
+ }, () => (readJson2(statePath)?.status ?? "completed") === "running");
482
+ if (outputMode === "assistant-text") writer.write("\n");
483
+ if (outputMode === "events" && existsSync2(stderrPath)) {
484
+ for (const line of readFileSync2(stderrPath, "utf-8").split(/\r?\n/).filter(Boolean)) {
485
+ writer.write(`${JSON.stringify({ ts: timestamp(), id: run.id, turn, type: "warning", message: line })}
486
+ `);
487
+ }
488
+ }
489
+ }
490
+ function streamFile(path, onLine, until) {
491
+ let offset = 0;
492
+ let pending = "";
493
+ while (true) {
494
+ if (existsSync2(path)) {
495
+ const size = statSync(path).size;
496
+ if (size > offset) {
497
+ const fd = openSync(path, "r");
498
+ const buffer = Buffer.alloc(size - offset);
499
+ try {
500
+ const bytes = buffer.length ? readSync(fd, buffer, 0, buffer.length, offset) : 0;
501
+ offset += bytes;
502
+ pending += buffer.toString("utf-8", 0, bytes);
503
+ const lines = pending.split(/\r?\n/);
504
+ pending = lines.pop() ?? "";
505
+ for (const line of lines) if (line) onLine(line);
506
+ } finally {
507
+ closeSync(fd);
508
+ }
509
+ }
510
+ }
511
+ if (!until()) {
512
+ if (pending) onLine(pending);
513
+ return;
514
+ }
515
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
516
+ }
517
+ }
518
+ function readJson2(path) {
519
+ try {
520
+ return JSON.parse(readFileSync2(path, "utf-8"));
521
+ } catch {
522
+ return null;
523
+ }
524
+ }
525
+ function defaultExecutor(command, args, stdio = "pipe") {
526
+ const spawnStdio = stdio === "inherit" ? "inherit" : "pipe";
527
+ const result = spawnSync(command, args, { encoding: "utf-8", stdio: spawnStdio });
528
+ return {
529
+ status: result.status,
530
+ stdout: stdio === "pipe" && typeof result.stdout === "string" ? result.stdout : "",
531
+ stderr: stdio === "pipe" && typeof result.stderr === "string" ? result.stderr : "",
532
+ error: result.error ?? void 0
533
+ };
534
+ }
535
+ function parseOutputMode(args) {
536
+ let outputMode = "json";
537
+ let follow = false;
538
+ const remaining = [];
539
+ for (const arg of args) {
540
+ if (arg === "--json") outputMode = "json";
541
+ else if (arg === "--assistant-text") outputMode = "assistant-text";
542
+ else if (arg === "--events") outputMode = "events";
543
+ else if (arg === "--raw") outputMode = "raw";
544
+ else if (arg === "--follow") follow = true;
545
+ else remaining.push(arg);
546
+ }
547
+ return { outputMode, follow, remaining };
548
+ }
549
+ function parseStartSendArgs(action, argv) {
550
+ const { outputMode, remaining } = parseOutputMode(argv);
551
+ let harness;
552
+ let model;
553
+ let effort;
554
+ let passthroughArgs = [];
555
+ const promptParts = [];
556
+ for (let i = 0; i < remaining.length; i++) {
557
+ const arg = remaining[i];
558
+ if (arg === "--") {
559
+ passthroughArgs = remaining.slice(i + 1);
560
+ break;
561
+ }
562
+ if (arg === "--harness") {
563
+ const value = remaining[++i];
564
+ if (!value || !(value in HARNESSES)) throw new Error("Missing or invalid value for --harness");
565
+ harness = value;
566
+ continue;
567
+ }
568
+ if (arg.startsWith("--harness=")) {
569
+ const value = arg.slice("--harness=".length);
570
+ if (!value || !(value in HARNESSES)) throw new Error("Missing or invalid value for --harness");
571
+ harness = value;
572
+ continue;
573
+ }
574
+ if (arg === "--model") {
575
+ model = remaining[++i];
576
+ if (!model) throw new Error("Missing value for --model");
577
+ continue;
578
+ }
579
+ if (arg.startsWith("--model=")) {
580
+ model = arg.slice("--model=".length);
581
+ if (!model) throw new Error("Missing value for --model");
582
+ continue;
583
+ }
584
+ if (arg === "--effort") {
585
+ effort = remaining[++i];
586
+ if (!effort) throw new Error("Missing value for --effort");
587
+ continue;
588
+ }
589
+ if (arg.startsWith("--effort=")) {
590
+ effort = arg.slice("--effort=".length);
591
+ if (!effort) throw new Error("Missing value for --effort");
592
+ continue;
593
+ }
594
+ if (arg === "--help" || arg === "-h") throw new Error(agentHelp());
595
+ if (arg.startsWith("-")) throw new Error(`Unknown flag: ${arg}. Use -- to pass flags through to the native harness.`);
596
+ promptParts.push(arg);
597
+ }
598
+ return { action, prompt: promptParts.join(" ").trim(), harness, model, effort, passthroughArgs, outputMode };
599
+ }
600
+ function parseOutputArgs(argv) {
601
+ const { outputMode, follow, remaining } = parseOutputMode(argv);
602
+ return { outputMode, follow, id: remaining.find((arg) => !arg.startsWith("-")) };
603
+ }
604
+ function classifyStatus(exitCode, stdout, stderr) {
605
+ const text = `${stdout}
606
+ ${stderr}`.toLowerCase();
607
+ if (exitCode === 0) return "completed";
608
+ if (text.includes("auth") || text.includes("api key") || text.includes("permission") || text.includes("may not exist")) return "blocked";
609
+ return "failed";
610
+ }
611
+ function resolveRunAndHarness(parsed, executor, cwd, options) {
612
+ const settings = options.settings ?? loadSettings(cwd);
613
+ const plugin = settings.plugins?.["coding-agents"];
614
+ const requestedHarnessId = parsed.harness ?? plugin?.harness;
615
+ let model = parsed.model ?? plugin?.model;
616
+ let effort = parsed.effort ?? plugin?.effort;
617
+ let run;
618
+ let harness;
619
+ if (parsed.action === "start") {
620
+ harness = detectHarness(executor, requestedHarnessId);
621
+ if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
622
+ run = createRun(cwd, harness, model, effort);
623
+ } else {
624
+ const active = activeRunIdForCwd(cwd);
625
+ if (active) {
626
+ run = loadRun(active);
627
+ if (parsed.harness && parsed.harness !== run.harness) throw new Error(`Active run uses harness ${run.harness}; start a new run for ${parsed.harness}.`);
628
+ harness = detectHarness(executor, run.harness);
629
+ model = parsed.model ?? run.model ?? plugin?.model ?? void 0;
630
+ effort = parsed.effort ?? run.effort ?? plugin?.effort ?? void 0;
631
+ if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
632
+ run.model = model ?? run.model;
633
+ run.effort = effort ?? run.effort;
634
+ } else {
635
+ harness = detectHarness(executor, requestedHarnessId);
636
+ if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
637
+ run = createRun(cwd, harness, model, effort);
638
+ }
639
+ }
640
+ run.status = "running";
641
+ run.model = model ?? run.model;
642
+ run.effort = effort ?? run.effort;
643
+ run.lastTurn += 1;
644
+ saveRun(run);
645
+ saveActiveRun(cwd, run.id);
646
+ return { run, harness };
647
+ }
648
+ function executeTurn(parsed, options) {
649
+ const executor = options.executor ?? defaultExecutor;
650
+ const writer = options.writer ?? process.stdout;
651
+ const errorWriter = options.errorWriter ?? process.stderr;
652
+ const cwd = options.cwd ?? process.cwd();
653
+ const zmxCheck = executor("zmx", ["--help"]);
654
+ if (zmxCheck.error || zmxCheck.status !== 0) {
655
+ errorWriter.write("zmx not found. Install zmx to use coding agents.\n");
656
+ return 2;
657
+ }
658
+ const { run, harness } = resolveRunAndHarness(parsed, executor, cwd, options);
659
+ const { shell, stdoutPath, stderrPath, exitCodePath } = buildShellCommand(harness, run, run.lastTurn, parsed.prompt, parsed.passthroughArgs);
660
+ const launch = executor("zmx", ["run", run.sessions.zmx, "sh", "-lc", shell], "ignore");
661
+ if (launch.error || launch.status !== 0) {
662
+ run.status = "failed";
663
+ saveRun(run);
664
+ errorWriter.write(`Failed to queue turn in zmx session ${run.sessions.zmx}.
665
+ `);
666
+ return 3;
667
+ }
668
+ waitForFile(exitCodePath);
669
+ const exitCode = Number(readFileSync3(exitCodePath, "utf-8").trim() || "1");
670
+ const stdout = existsSync3(stdoutPath) ? readFileSync3(stdoutPath, "utf-8") : "";
671
+ const stderr = existsSync3(stderrPath) ? readFileSync3(stderrPath, "utf-8") : "";
672
+ const normalized = normalizeTurn(run.harness, run.id, run.lastTurn, stdout, stderr, (/* @__PURE__ */ new Date()).toISOString());
673
+ const turnStatus = classifyStatus(exitCode, stdout, stderr);
674
+ const turn = {
675
+ id: run.id,
676
+ turn: run.lastTurn,
677
+ status: turnStatus,
678
+ prompt: parsed.prompt,
679
+ harness: run.harness,
680
+ sessions: {
681
+ zmx: run.sessions.zmx,
682
+ ...normalized.sessionId || run.sessions[run.harness] ? { [run.harness]: normalized.sessionId ?? run.sessions[run.harness] } : {}
683
+ },
684
+ assistant: { text: normalized.assistantText },
685
+ usage: normalized.usage,
686
+ timing: normalized.timing,
687
+ paths: {
688
+ stdout: stdoutPath,
689
+ stderr: stderrPath,
690
+ events: resolve3(turnDir(run.id, run.lastTurn), "events.jsonl")
691
+ },
692
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
693
+ };
694
+ writeEvents(turn.paths.events, normalized.events);
695
+ writeJson(resolve3(turnDir(run.id, run.lastTurn), "normalized.json"), turn);
696
+ if (turn.sessions[run.harness]) run.sessions[run.harness] = turn.sessions[run.harness];
697
+ run.status = turnStatus;
698
+ saveRun(run);
699
+ writer.write(`${renderTurn(turn, parsed.outputMode)}
700
+ `);
701
+ return turnStatus === "completed" ? 0 : 1;
702
+ }
703
+ function attachToTarget(argv, options) {
704
+ const executor = options.executor ?? defaultExecutor;
705
+ const errorWriter = options.errorWriter ?? process.stderr;
706
+ if (argv.length < 2) {
707
+ errorWriter.write(`${attachHelp()}
708
+ `);
709
+ return 1;
710
+ }
711
+ const session = argv[0] === "run" ? loadRun(argv[1]).sessions.zmx : argv[0] === "session" ? argv[1] : null;
712
+ if (!session) {
713
+ errorWriter.write(`${attachHelp()}
714
+ `);
715
+ return 1;
716
+ }
717
+ return executor("zmx", ["attach", session], "inherit").status ?? 1;
718
+ }
719
+ function outputCommand(argv, options) {
720
+ const writer = options.writer ?? process.stdout;
721
+ const cwd = options.cwd ?? process.cwd();
722
+ const parsed = parseOutputArgs(argv);
723
+ const run = loadRun(resolveRunId(cwd, parsed.id));
724
+ if (parsed.follow && run.status === "running") {
725
+ followOutput(run, run.lastTurn, parsed.outputMode, writer);
726
+ return 0;
727
+ }
728
+ writer.write(`${renderTurn(latestTurn(run), parsed.outputMode)}
729
+ `);
730
+ return 0;
731
+ }
732
+ function statusCommand(argv, options) {
733
+ const writer = options.writer ?? process.stdout;
734
+ const cwd = options.cwd ?? process.cwd();
735
+ writer.write(`${renderStatus(loadRun(resolveRunId(cwd, argv.find((arg) => !arg.startsWith("-")))))}
736
+ `);
737
+ return 0;
738
+ }
739
+ function listCommand(options) {
740
+ const writer = options.writer ?? process.stdout;
741
+ writer.write(`${JSON.stringify(listRuns(), null, 2)}
742
+ `);
743
+ return 0;
744
+ }
745
+ async function runAgentCommand(argv, options = {}) {
746
+ ensureDirs();
747
+ const writer = options.writer ?? process.stdout;
748
+ const errorWriter = options.errorWriter ?? process.stderr;
749
+ if (!argv.length) {
750
+ writer.write(`${agentHelp()}
751
+ `);
752
+ return 0;
753
+ }
754
+ try {
755
+ const [cmd, ...rest] = argv;
756
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
757
+ writer.write(`${agentHelp()}
758
+ `);
759
+ return 0;
760
+ }
761
+ if (cmd === "start") return executeTurn(parseStartSendArgs("start", rest), options);
762
+ if (cmd === "send") return executeTurn(parseStartSendArgs("send", rest), options);
763
+ if (cmd === "attach") return attachToTarget(rest, options);
764
+ if (cmd === "output") return outputCommand(rest, options);
765
+ if (cmd === "status") return statusCommand(rest, options);
766
+ if (cmd === "list") return listCommand(options);
767
+ return executeTurn(parseStartSendArgs("send", argv), options);
768
+ } catch (error) {
769
+ errorWriter.write(`${error.message || error}
770
+ `);
771
+ return 1;
772
+ }
773
+ }
774
+ export {
775
+ HARNESSES,
776
+ agentHelp,
777
+ defaultExecutor,
778
+ runAgentCommand
779
+ };