poe-code 3.0.201 → 3.0.203

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 (49) hide show
  1. package/dist/cli/commands/experiment.js +11 -4
  2. package/dist/cli/commands/experiment.js.map +1 -1
  3. package/dist/cli/commands/ralph.js +12 -5
  4. package/dist/cli/commands/ralph.js.map +1 -1
  5. package/dist/cli/commands/runtime-options.d.ts +10 -0
  6. package/dist/cli/commands/runtime-options.js +23 -0
  7. package/dist/cli/commands/runtime-options.js.map +1 -0
  8. package/dist/cli/commands/spawn.js +9 -3
  9. package/dist/cli/commands/spawn.js.map +1 -1
  10. package/dist/index.js +22975 -20513
  11. package/dist/index.js.map +4 -4
  12. package/dist/providers/claude-code.js +2741 -1706
  13. package/dist/providers/claude-code.js.map +4 -4
  14. package/dist/providers/codex.js +2770 -1735
  15. package/dist/providers/codex.js.map +4 -4
  16. package/dist/providers/goose.js +2640 -1605
  17. package/dist/providers/goose.js.map +4 -4
  18. package/dist/providers/kimi.js +2740 -1705
  19. package/dist/providers/kimi.js.map +4 -4
  20. package/dist/providers/opencode.js +2741 -1706
  21. package/dist/providers/opencode.js.map +4 -4
  22. package/dist/providers/poe-agent.js +19779 -17296
  23. package/dist/providers/poe-agent.js.map +4 -4
  24. package/dist/providers/spawn-options.d.ts +6 -0
  25. package/dist/sdk/experiment.js +5 -0
  26. package/dist/sdk/experiment.js.map +1 -1
  27. package/dist/sdk/ralph.js +5 -0
  28. package/dist/sdk/ralph.js.map +1 -1
  29. package/dist/sdk/spawn.js +17 -1
  30. package/dist/sdk/spawn.js.map +1 -1
  31. package/dist/sdk/types.d.ts +11 -0
  32. package/package.json +1 -1
  33. package/packages/memory/dist/index.js +2642 -444
  34. package/packages/memory/dist/index.js.map +4 -4
  35. package/packages/superintendent/dist/commands/run.d.ts +35 -0
  36. package/packages/superintendent/dist/commands/run.js +49 -1
  37. package/packages/superintendent/dist/commands/superintendent-group.d.ts +30 -0
  38. package/packages/superintendent/dist/runtime/agent-runner.d.ts +30 -0
  39. package/packages/superintendent/dist/runtime/agent-runner.js +119 -0
  40. package/packages/superintendent/dist/runtime/loop.d.ts +6 -1
  41. package/packages/superintendent/dist/runtime/loop.js +3 -11
  42. package/packages/superintendent/dist/runtime/run-builder.d.ts +1 -0
  43. package/packages/superintendent/dist/runtime/run-builder.js +3 -25
  44. package/packages/superintendent/dist/runtime/run-inspector.d.ts +1 -0
  45. package/packages/superintendent/dist/runtime/run-inspector.js +3 -25
  46. package/packages/superintendent/dist/runtime/run-owner-review.d.ts +1 -0
  47. package/packages/superintendent/dist/runtime/run-owner-review.js +3 -25
  48. package/packages/superintendent/dist/runtime/run-superintendent.d.ts +1 -0
  49. package/packages/superintendent/dist/runtime/run-superintendent.js +3 -25
@@ -52,19 +52,11 @@ var require_config_toml = __commonJS({
52
52
  }
53
53
  });
54
54
 
55
- // packages/agent-spawn/src/run-command.ts
56
- import { spawn } from "node:child_process";
55
+ // packages/agent-spawn/src/register-factories.ts
56
+ import { spawn as spawnChildProcess2 } from "node:child_process";
57
57
 
58
- // packages/agent-spawn/src/types.ts
59
- function resolveModeConfig(modeConfig) {
60
- if (Array.isArray(modeConfig)) {
61
- return { args: modeConfig };
62
- }
63
- return {
64
- args: modeConfig.args ?? [],
65
- env: modeConfig.env && Object.keys(modeConfig.env).length > 0 ? modeConfig.env : void 0
66
- };
67
- }
58
+ // packages/agent-harness-tools/src/paths.ts
59
+ import path from "node:path";
68
60
 
69
61
  // packages/agent-defs/src/agents/claude-code.ts
70
62
  var claudeCodeAgent = {
@@ -205,1925 +197,2968 @@ function resolveAgentId(input) {
205
197
  return lookup.get(input.toLowerCase());
206
198
  }
207
199
 
208
- // packages/agent-spawn/src/configs/mcp.ts
209
- function toJsonMcpServers(servers) {
210
- const out = {};
211
- for (const [name, server] of Object.entries(servers)) {
212
- const mapped = { command: server.command };
213
- if (server.args && server.args.length > 0) {
214
- mapped.args = server.args;
215
- }
216
- if (server.env && Object.keys(server.env).length > 0) {
217
- mapped.env = server.env;
218
- }
219
- if (server.timeout !== void 0) {
220
- mapped.timeout = server.timeout;
200
+ // packages/agent-harness-tools/src/select-agent.ts
201
+ var loopAgents = allAgents.filter(
202
+ (agent) => agent.binaryName !== void 0 || agent.id === "poe-agent"
203
+ );
204
+ var supportedAgents = loopAgents.map((agent) => agent.id).join(", ");
205
+
206
+ // packages/file-lock/src/lock.ts
207
+ import * as fsPromises from "node:fs/promises";
208
+ import * as os from "node:os";
209
+
210
+ // packages/agent-harness-tools/src/run-logs.ts
211
+ import path2 from "node:path";
212
+
213
+ // packages/agent-harness-tools/src/log-stream.ts
214
+ import nodeFs from "node:fs";
215
+
216
+ // packages/agent-harness-tools/src/run-poe-command.ts
217
+ import { randomBytes } from "node:crypto";
218
+
219
+ // packages/agent-harness-tools/src/poe-command-execution.ts
220
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
221
+ import os3 from "node:os";
222
+
223
+ // packages/poe-code-config/src/runtime.ts
224
+ import { existsSync } from "node:fs";
225
+ import path3 from "node:path";
226
+ var defaultWorkspaceExclude = [
227
+ ".git",
228
+ "node_modules",
229
+ "dist",
230
+ ".turbo",
231
+ ".next",
232
+ ".poe-code/state.json"
233
+ ];
234
+ var runtimeConfigScope = {
235
+ scope: "runtime",
236
+ schema: {
237
+ type: {
238
+ type: "string",
239
+ default: "host",
240
+ doc: "Runtime backend: host, docker, or e2b"
241
+ },
242
+ build_args: {
243
+ type: "json",
244
+ default: {},
245
+ parse: parseBuildArgs,
246
+ doc: "Build arguments passed to the runtime image build"
247
+ },
248
+ mounts: {
249
+ type: "json",
250
+ default: [],
251
+ parse: parseMounts,
252
+ doc: "Additional runtime mounts"
253
+ },
254
+ runner: {
255
+ type: "json",
256
+ default: createDefaultRunnerScope(),
257
+ parse: parseRunner,
258
+ doc: "Runner process and workspace transfer settings"
259
+ },
260
+ link: {
261
+ type: "string",
262
+ default: "",
263
+ doc: "Informational link for the runtime definition"
264
+ },
265
+ image: {
266
+ type: "string",
267
+ default: "",
268
+ doc: "Prebuilt Docker image"
269
+ },
270
+ dockerfile: {
271
+ type: "string",
272
+ default: "",
273
+ doc: "Path to the Dockerfile used for docker or e2b builds"
274
+ },
275
+ build_context: {
276
+ type: "string",
277
+ default: "",
278
+ doc: "Path to the Docker build context"
279
+ },
280
+ engine: {
281
+ type: "string",
282
+ default: "",
283
+ doc: "Container engine for Docker runtime"
284
+ },
285
+ network: {
286
+ type: "string",
287
+ default: "",
288
+ doc: "Docker network"
289
+ },
290
+ extra_args: {
291
+ type: "json",
292
+ default: void 0,
293
+ parse: parseOptionalStringArray,
294
+ doc: "Extra Docker runtime arguments"
295
+ },
296
+ template_id: {
297
+ type: "string",
298
+ default: "",
299
+ doc: "Prebuilt E2B template id"
300
+ },
301
+ cpu: {
302
+ type: "json",
303
+ default: void 0,
304
+ parse: parseOptionalNumber,
305
+ doc: "E2B CPU count"
306
+ },
307
+ memory_mb: {
308
+ type: "json",
309
+ default: void 0,
310
+ parse: parseOptionalNumber,
311
+ doc: "E2B memory in megabytes"
312
+ },
313
+ timeout_minutes: {
314
+ type: "json",
315
+ default: void 0,
316
+ parse: parseOptionalNumber,
317
+ doc: "E2B timeout in minutes"
318
+ },
319
+ preserve_after_exit_hours: {
320
+ type: "json",
321
+ default: void 0,
322
+ parse: parseOptionalNumber,
323
+ doc: "Hours to keep an E2B sandbox alive after job exit"
324
+ },
325
+ api_key_env: {
326
+ type: "string",
327
+ default: "",
328
+ doc: "Environment variable name containing the E2B API key"
221
329
  }
222
- out[name] = mapped;
223
330
  }
224
- return out;
225
- }
226
- function toTomlString(value) {
227
- return JSON.stringify(value);
331
+ };
332
+ function parseRunner(raw) {
333
+ if (raw === void 0) {
334
+ return createDefaultRunnerScope();
335
+ }
336
+ const record = asRecord(raw);
337
+ if (record === void 0) {
338
+ throw new Error("runner: expected an object.");
339
+ }
340
+ const uploadMaxFileMb = parseOptionalNumber(record.upload_max_file_mb, "runner.upload_max_file_mb") ?? 100;
341
+ if (uploadMaxFileMb <= 0) {
342
+ throw new Error("runner.upload_max_file_mb: expected a positive finite number.");
343
+ }
344
+ return omitUndefined({
345
+ detach: parseOptionalBoolean(record.detach, "runner.detach") ?? false,
346
+ upload_max_file_mb: uploadMaxFileMb,
347
+ download_conflict: parseDownloadConflict(record.download_conflict),
348
+ workspace: parseRunnerWorkspace(record.workspace)
349
+ });
228
350
  }
229
- function toTomlArray(values) {
230
- const serialized = values.map((value) => toTomlString(value));
231
- return `[${serialized.join(", ")}]`;
351
+ function createDefaultRunnerScope() {
352
+ return {
353
+ detach: false,
354
+ upload_max_file_mb: 100,
355
+ download_conflict: "refuse",
356
+ workspace: {
357
+ exclude: [...defaultWorkspaceExclude]
358
+ }
359
+ };
232
360
  }
233
- function toTomlInlineTable(values) {
234
- const parts = [];
235
- for (const [key, value] of Object.entries(values)) {
236
- parts.push(`${JSON.stringify(key)}=${toTomlString(value)}`);
361
+ function parseRunnerWorkspace(value) {
362
+ if (value === void 0) {
363
+ return {
364
+ exclude: [...defaultWorkspaceExclude]
365
+ };
237
366
  }
238
- return `{${parts.join(", ")}}`;
367
+ const record = asRecord(value);
368
+ if (record === void 0) {
369
+ throw new Error("runner.workspace: expected an object.");
370
+ }
371
+ return {
372
+ exclude: parseOptionalStringArray(record.exclude, "runner.workspace.exclude") ?? [
373
+ ...defaultWorkspaceExclude
374
+ ]
375
+ };
239
376
  }
240
- function serializeJsonMcpArgs(servers) {
241
- return ["--mcp-config", JSON.stringify({ mcpServers: toJsonMcpServers(servers) })];
377
+ function parseDownloadConflict(value) {
378
+ if (value === void 0) {
379
+ return "refuse";
380
+ }
381
+ if (value === "refuse" || value === "overwrite") {
382
+ return value;
383
+ }
384
+ throw new Error('runner.download_conflict: expected "refuse" or "overwrite".');
242
385
  }
243
- function serializeOpenCodeMcpEnv(servers) {
244
- const mcp = {};
245
- for (const [name, server] of Object.entries(servers)) {
246
- const entry = { type: "local", command: [server.command, ...server.args ?? []] };
247
- if (server.env && Object.keys(server.env).length > 0) {
248
- entry.environment = server.env;
386
+ function parseBuildArgs(value) {
387
+ if (value === void 0) {
388
+ return {};
389
+ }
390
+ const record = asRecord(value);
391
+ if (record === void 0) {
392
+ throw new Error("build_args: expected an object.");
393
+ }
394
+ const parsed = {};
395
+ for (const [key, entry] of Object.entries(record)) {
396
+ if (typeof entry !== "string") {
397
+ throw new Error(`build_args.${key}: expected a string.`);
249
398
  }
250
- mcp[name] = entry;
399
+ parsed[key] = entry;
251
400
  }
252
- return { OPENCODE_CONFIG_CONTENT: JSON.stringify({ mcp }) };
401
+ return parsed;
253
402
  }
254
- function serializeCodexMcpArgs(servers) {
255
- const args = [];
256
- for (const [name, server] of Object.entries(servers)) {
257
- const prefix = `mcp_servers.${name}`;
258
- args.push("-c", `${prefix}.command=${toTomlString(server.command)}`);
259
- if (server.args && server.args.length > 0) {
260
- args.push("-c", `${prefix}.args=${toTomlArray(server.args)}`);
403
+ function parseMounts(value) {
404
+ if (value === void 0) {
405
+ return [];
406
+ }
407
+ if (!Array.isArray(value)) {
408
+ throw new Error("mounts: expected an array.");
409
+ }
410
+ return value.map((entry, index) => {
411
+ const record = asRecord(entry);
412
+ if (record === void 0) {
413
+ throw new Error(`mounts[${index}]: expected an object.`);
261
414
  }
262
- if (server.env && Object.keys(server.env).length > 0) {
263
- args.push("-c", `${prefix}.env=${toTomlInlineTable(server.env)}`);
415
+ const source = record.source;
416
+ const target = record.target;
417
+ if (typeof source !== "string") {
418
+ throw new Error(`mounts[${index}].source: expected a string.`);
264
419
  }
265
- if (server.timeout !== void 0) {
266
- args.push("-c", `${prefix}.timeout=${server.timeout}`);
420
+ if (typeof target !== "string") {
421
+ throw new Error(`mounts[${index}].target: expected a string.`);
267
422
  }
423
+ return omitUndefined({
424
+ source,
425
+ target,
426
+ readonly: parseOptionalBoolean(record.readonly, `mounts[${index}].readonly`)
427
+ });
428
+ });
429
+ }
430
+ function parseOptionalStringArray(value, key = "") {
431
+ if (value === void 0) {
432
+ return void 0;
268
433
  }
269
- return args;
434
+ if (!Array.isArray(value)) {
435
+ throw new Error(`${key ? `${key}: ` : ""}expected an array.`);
436
+ }
437
+ return value.map((entry, index) => {
438
+ if (typeof entry !== "string") {
439
+ throw new Error(`${key}[${index}]: expected a string.`);
440
+ }
441
+ return entry;
442
+ });
270
443
  }
271
- function serializeGooseMcpArgs(servers) {
272
- return Object.values(servers).flatMap((server) => [
273
- "--with-extension",
274
- [server.command, ...server.args ?? []].join(" ")
275
- ]);
444
+ function parseOptionalNumber(value, key = "") {
445
+ if (value === void 0) {
446
+ return void 0;
447
+ }
448
+ if (typeof value !== "number" || !Number.isFinite(value)) {
449
+ throw new Error(`${key ? `${key}: ` : ""}expected a finite number.`);
450
+ }
451
+ return value;
452
+ }
453
+ function parseOptionalBoolean(value, key) {
454
+ if (value === void 0) {
455
+ return void 0;
456
+ }
457
+ if (typeof value !== "boolean") {
458
+ throw new Error(`${key}: expected a boolean.`);
459
+ }
460
+ return value;
461
+ }
462
+ function asRecord(value) {
463
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
464
+ return void 0;
465
+ }
466
+ return value;
467
+ }
468
+ function omitUndefined(value) {
469
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
276
470
  }
277
471
 
278
- // packages/agent-spawn/src/configs/claude-code.ts
279
- var claudeCodeSpawnConfig = {
280
- kind: "cli",
281
- agentId: "claude-code",
282
- // ACP adapter support: yes (adapter: "claude")
283
- adapter: "claude",
284
- promptFlag: "-p",
285
- modelFlag: "--model",
286
- modelStripProviderPrefix: true,
287
- modelTransform: (model) => model.replaceAll(".", "-"),
288
- defaultArgs: [
289
- "--output-format",
290
- "stream-json",
291
- "--verbose"
292
- ],
293
- mcpArgs: serializeJsonMcpArgs,
294
- modes: {
295
- yolo: ["--dangerously-skip-permissions"],
296
- edit: ["--permission-mode", "acceptEdits", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,NotebookEdit"],
297
- read: ["--permission-mode", "plan"]
298
- },
299
- stdinMode: {
300
- omitPrompt: true,
301
- extraArgs: ["--input-format", "text"]
302
- },
303
- interactive: {
304
- defaultArgs: []
305
- },
306
- resumeCommand: (threadId) => ["--resume", threadId]
307
- };
472
+ // packages/poe-code-config/src/schema.ts
473
+ function defineScope(scope, schema) {
474
+ return {
475
+ scope,
476
+ schema
477
+ };
478
+ }
308
479
 
309
- // packages/agent-spawn/src/configs/codex.ts
310
- var codexSpawnConfig = {
311
- kind: "cli",
312
- agentId: "codex",
313
- // ACP adapter support: yes (adapter: "codex")
314
- adapter: "codex",
315
- promptFlag: "exec",
316
- modelFlag: "--model",
317
- modelStripProviderPrefix: true,
318
- defaultArgs: ["--skip-git-repo-check", "--json"],
319
- mcpArgs: serializeCodexMcpArgs,
320
- mcpArgsBeforeCommand: true,
321
- modes: {
322
- yolo: ["-s", "danger-full-access"],
323
- edit: ["-s", "workspace-write"],
324
- read: ["-s", "read-only"]
325
- },
326
- stdinMode: {
327
- omitPrompt: true,
328
- extraArgs: ["-"]
329
- },
330
- interactive: {
331
- defaultArgs: ["-a", "never"]
332
- },
333
- resumeCommand: (threadId, cwd) => ["resume", "-C", cwd, threadId]
334
- };
480
+ // packages/poe-code-config/src/plan-scope.ts
481
+ var planConfigScope = defineScope("plan", {
482
+ plan_directory: {
483
+ type: "string",
484
+ default: "docs/plans",
485
+ env: "POE_PLAN_DIRECTORY",
486
+ doc: "Directory where planning documents are stored"
487
+ }
488
+ });
335
489
 
336
- // packages/agent-spawn/src/configs/opencode.ts
337
- var openCodeSpawnConfig = {
338
- kind: "cli",
339
- agentId: "opencode",
340
- // ACP adapter support: yes (adapter: "opencode").
341
- // OpenCode's `--format json` emits NDJSON events with `{ type, sessionID, part }`
342
- // (no `{ event, ... }` field), so it needs the OpenCode adapter (not "native").
343
- adapter: "opencode",
344
- promptFlag: "run",
345
- modelFlag: "--model",
346
- modelStripProviderPrefix: false,
347
- modelTransform: (model) => {
348
- return model.startsWith("poe/") ? model : `poe/${model}`;
349
- },
350
- defaultArgs: ["--format", "json"],
351
- modes: {
352
- yolo: [],
353
- edit: [],
354
- read: ["--agent", "plan"]
355
- },
356
- interactive: {
357
- defaultArgs: [],
358
- promptFlag: "--prompt"
359
- },
360
- resumeCommand: (threadId, cwd) => [cwd, "--session", threadId],
361
- mcpEnv: serializeOpenCodeMcpEnv
362
- };
363
- var openCodeAcpSpawnConfig = {
364
- kind: "acp",
365
- agentId: "opencode",
366
- acpArgs: ["acp"],
367
- skipAuth: true,
368
- mcpEnv: serializeOpenCodeMcpEnv
369
- };
490
+ // packages/poe-code-config/src/store.ts
491
+ import path8 from "node:path";
370
492
 
371
- // packages/agent-spawn/src/configs/kimi.ts
372
- var kimiSpawnConfig = {
373
- kind: "cli",
374
- agentId: "kimi",
375
- // ACP adapter support: yes (adapter: "kimi").
376
- // Kimi's `--output-format stream-json` emits OpenAI-style `{ role, content }` JSON
377
- // (no `{ event, ... }` field), so it needs the Kimi adapter (not "native").
378
- adapter: "kimi",
379
- promptFlag: "-p",
380
- modelStripProviderPrefix: true,
381
- defaultArgs: ["--print", "--output-format", "stream-json"],
382
- mcpArgs: serializeJsonMcpArgs,
383
- modes: {
384
- yolo: ["--yolo"],
385
- edit: [],
386
- read: []
387
- },
388
- stdinMode: {
389
- omitPrompt: true,
390
- extraArgs: ["--input-format", "stream-json"]
391
- },
392
- interactive: {
393
- defaultArgs: [],
394
- promptFlag: "-p"
395
- },
396
- resumeCommand: (threadId, cwd) => ["--session", threadId, "--work-dir", cwd]
397
- };
398
- var kimiAcpSpawnConfig = {
399
- kind: "acp",
400
- agentId: "kimi",
401
- acpArgs: ["acp"]
402
- };
493
+ // packages/config-extends/src/discover.ts
494
+ import path4 from "node:path";
403
495
 
404
- // packages/agent-spawn/src/configs/goose.ts
405
- var gooseFileSecretsEnv = { GOOSE_DISABLE_KEYRING: "1" };
406
- var gooseSpawnConfig = {
407
- kind: "cli",
408
- agentId: "goose",
409
- adapter: "native",
410
- promptFlag: "--text",
411
- modelFlag: "--model",
412
- modelStripProviderPrefix: false,
413
- defaultArgs: ["run", "--output-format", "stream-json"],
414
- defaultArgsPosition: "beforePrompt",
415
- mcpArgs: serializeGooseMcpArgs,
416
- mcpArgsPosition: "beforePrompt",
417
- modes: {
418
- yolo: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "auto" } },
419
- edit: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "smart_approve" } },
420
- read: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "chat" } }
421
- },
422
- stdinMode: {
423
- omitPrompt: true,
424
- extraArgs: ["--instructions", "-"]
425
- },
426
- interactive: {
427
- defaultArgs: ["session"],
428
- defaultArgsPosition: "beforePrompt"
429
- },
430
- resumeCommand: () => ["run", "--resume", "--text", "continue"]
431
- };
432
- var gooseAcpSpawnConfig = {
433
- kind: "acp",
434
- agentId: "goose",
435
- acpArgs: ["acp"],
436
- env: gooseFileSecretsEnv,
437
- skipAuth: true
438
- };
496
+ // packages/config-extends/src/parse.ts
497
+ import path5 from "node:path";
498
+ import matter from "gray-matter";
499
+ import { parse as parseYaml } from "yaml";
439
500
 
440
- // packages/agent-spawn/src/configs/index.ts
441
- var allSpawnConfigs = [
442
- claudeCodeSpawnConfig,
443
- codexSpawnConfig,
444
- openCodeSpawnConfig,
445
- kimiSpawnConfig,
446
- gooseSpawnConfig
447
- ];
448
- var lookup2 = /* @__PURE__ */ new Map();
449
- for (const config of allSpawnConfigs) {
450
- lookup2.set(config.agentId, config);
501
+ // packages/config-extends/src/resolve.ts
502
+ import path6 from "node:path";
503
+
504
+ // packages/config-mutations/src/mutations/config-mutation.ts
505
+ function merge(options) {
506
+ return {
507
+ kind: "configMerge",
508
+ target: options.target,
509
+ value: options.value,
510
+ format: options.format,
511
+ pruneByPrefix: options.pruneByPrefix,
512
+ label: options.label
513
+ };
451
514
  }
452
- var acpLookup = /* @__PURE__ */ new Map();
453
- acpLookup.set(openCodeAcpSpawnConfig.agentId, openCodeAcpSpawnConfig);
454
- acpLookup.set(kimiAcpSpawnConfig.agentId, kimiAcpSpawnConfig);
455
- acpLookup.set(gooseAcpSpawnConfig.agentId, gooseAcpSpawnConfig);
456
- function getSpawnConfig(input) {
457
- const resolvedId = resolveAgentId(input);
458
- if (!resolvedId) {
459
- return void 0;
460
- }
461
- return lookup2.get(resolvedId);
515
+ function prune(options) {
516
+ return {
517
+ kind: "configPrune",
518
+ target: options.target,
519
+ shape: options.shape,
520
+ format: options.format,
521
+ onlyIf: options.onlyIf,
522
+ label: options.label
523
+ };
462
524
  }
463
- function listMcpSupportedAgents() {
464
- const supported = [];
465
- for (const config of allSpawnConfigs) {
466
- if (config.kind !== "cli" || typeof config.mcpArgs !== "function" && typeof config.mcpEnv !== "function") {
467
- continue;
468
- }
469
- supported.push(config.agentId);
470
- }
471
- return supported;
525
+ function transform(options) {
526
+ return {
527
+ kind: "configTransform",
528
+ target: options.target,
529
+ format: options.format,
530
+ transform: options.transform,
531
+ label: options.label
532
+ };
472
533
  }
534
+ var configMutation = {
535
+ merge,
536
+ prune,
537
+ transform
538
+ };
473
539
 
474
- // packages/agent-spawn/src/spawn.ts
475
- import { spawn as spawnChildProcess } from "node:child_process";
476
- import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
477
- import path from "node:path";
478
-
479
- // packages/agent-spawn/src/configs/resolve-config.ts
480
- function resolveConfig(agentId) {
481
- const resolvedAgentId = resolveAgentId(agentId);
482
- if (!resolvedAgentId) {
483
- throw new Error(`Unknown agent "${agentId}".`);
484
- }
485
- const agentDefinition = allAgents.find((agent) => agent.id === resolvedAgentId);
486
- if (!agentDefinition) {
487
- throw new Error(`Unknown agent "${agentId}".`);
488
- }
489
- const spawnConfig = getSpawnConfig(resolvedAgentId);
490
- const binaryName = agentDefinition.binaryName;
491
- return { agentId: resolvedAgentId, binaryName, spawnConfig };
540
+ // packages/config-mutations/src/mutations/file-mutation.ts
541
+ function ensureDirectory(options) {
542
+ return {
543
+ kind: "ensureDirectory",
544
+ path: options.path,
545
+ label: options.label
546
+ };
492
547
  }
493
-
494
- // packages/agent-spawn/src/mcp-args.ts
495
- function hasMcpServers(servers) {
496
- if (!servers) {
497
- return false;
498
- }
499
- return Object.keys(servers).length > 0;
548
+ function remove(options) {
549
+ return {
550
+ kind: "removeFile",
551
+ target: options.target,
552
+ whenEmpty: options.whenEmpty,
553
+ whenContentMatches: options.whenContentMatches,
554
+ label: options.label
555
+ };
500
556
  }
501
- function getMcpArgs(config, servers) {
502
- if (!hasMcpServers(servers)) {
503
- return [];
504
- }
505
- if (!config.mcpArgs && !config.mcpEnv) {
506
- throw new Error(formatUnsupportedMcpSpawnMessage(config.agentId));
507
- }
508
- if (!config.mcpArgs) {
509
- return [];
510
- }
511
- return config.mcpArgs(servers);
557
+ function removeDirectory(options) {
558
+ return {
559
+ kind: "removeDirectory",
560
+ path: options.path,
561
+ force: options.force,
562
+ label: options.label
563
+ };
512
564
  }
513
- function formatUnsupportedMcpSpawnMessage(agentId) {
514
- const supported = listMcpSupportedAgents();
515
- const supportedText = supported.length > 0 ? supported.join(", ") : "(none)";
516
- return `Agent "${agentId}" does not support MCP servers at spawn time.
517
- Agents with spawn-time MCP support: ${supportedText}`;
565
+ function chmod(options) {
566
+ return {
567
+ kind: "chmod",
568
+ target: options.target,
569
+ mode: options.mode,
570
+ label: options.label
571
+ };
518
572
  }
519
-
520
- // packages/agent-spawn/src/model-utils.ts
521
- function stripModelNamespace(model) {
522
- const slashIndex = model.indexOf("/");
523
- return slashIndex === -1 ? model : model.slice(slashIndex + 1);
573
+ function backup(options) {
574
+ return {
575
+ kind: "backup",
576
+ target: options.target,
577
+ label: options.label
578
+ };
524
579
  }
580
+ var fileMutation = {
581
+ ensureDirectory,
582
+ remove,
583
+ removeDirectory,
584
+ chmod,
585
+ backup
586
+ };
525
587
 
526
- // packages/agent-spawn/src/spawn.ts
527
- function resolveCliConfig(agentId) {
528
- const resolved = resolveConfig(agentId);
529
- if (!resolved.spawnConfig) {
530
- throw new Error(`Agent "${resolved.agentId}" has no spawn config.`);
588
+ // packages/config-mutations/src/execution/apply-mutation.ts
589
+ import Mustache from "mustache";
590
+
591
+ // packages/config-mutations/src/formats/json.ts
592
+ import * as jsonc from "jsonc-parser";
593
+ function isConfigObject(value) {
594
+ return typeof value === "object" && value !== null && !Array.isArray(value);
595
+ }
596
+ function parse2(content) {
597
+ if (!content || content.trim() === "") {
598
+ return {};
531
599
  }
532
- if (resolved.spawnConfig.kind !== "cli") {
533
- throw new Error(`Agent "${resolved.agentId}" does not support CLI spawn.`);
600
+ const errors = [];
601
+ const parsed = jsonc.parse(content, errors, {
602
+ allowTrailingComma: true,
603
+ disallowComments: false
604
+ });
605
+ if (errors.length > 0) {
606
+ throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
534
607
  }
535
- if (!resolved.binaryName) {
536
- throw new Error(`Agent "${resolved.agentId}" has no binaryName.`);
608
+ if (parsed === null || parsed === void 0) {
609
+ return {};
537
610
  }
538
- return {
539
- agentId: resolved.agentId,
540
- binaryName: resolved.binaryName,
541
- spawnConfig: resolved.spawnConfig
542
- };
611
+ if (!isConfigObject(parsed)) {
612
+ throw new Error("Expected JSON object.");
613
+ }
614
+ return parsed;
543
615
  }
544
- function getDefaultArgsPosition(config) {
545
- return config.defaultArgsPosition ?? "afterPrompt";
616
+ function serialize(obj) {
617
+ return `${JSON.stringify(obj, null, 2)}
618
+ `;
546
619
  }
547
- function getMcpArgsPosition(config) {
548
- if (config.mcpArgsPosition) {
549
- return config.mcpArgsPosition;
620
+ function merge2(base, patch) {
621
+ const result = { ...base };
622
+ for (const [key, value] of Object.entries(patch)) {
623
+ if (value === void 0) {
624
+ continue;
625
+ }
626
+ const existing = result[key];
627
+ if (isConfigObject(existing) && isConfigObject(value)) {
628
+ result[key] = merge2(existing, value);
629
+ continue;
630
+ }
631
+ result[key] = value;
550
632
  }
551
- return config.mcpArgsBeforeCommand ? "beforeCommand" : "afterCommand";
633
+ return result;
552
634
  }
553
- function buildCliArgs(config, options, stdinMode) {
554
- const mcpArgs = getMcpArgs(config, options.mcpServers);
555
- const defaultArgsPosition = getDefaultArgsPosition(config);
556
- const mcpArgsPosition = getMcpArgsPosition(config);
557
- const args = [];
558
- if (mcpArgsPosition === "beforeCommand") {
559
- args.push(...mcpArgs);
560
- }
561
- if (defaultArgsPosition === "beforePrompt") {
562
- args.push(...config.defaultArgs);
563
- }
564
- if (mcpArgsPosition === "beforePrompt") {
565
- args.push(...mcpArgs);
566
- }
567
- if (stdinMode) {
568
- args.push(
569
- config.promptFlag,
570
- ...stdinMode.omitPrompt ? [] : [options.prompt],
571
- ...stdinMode.extraArgs
572
- );
573
- } else {
574
- args.push(config.promptFlag, options.prompt);
575
- }
576
- if (options.model && config.modelFlag) {
577
- let model = config.modelStripProviderPrefix ? stripModelNamespace(options.model) : options.model;
578
- if (config.modelTransform) model = config.modelTransform(model);
579
- args.push(config.modelFlag, model);
580
- }
581
- if (defaultArgsPosition === "afterPrompt") {
582
- args.push(...config.defaultArgs);
583
- }
584
- if (mcpArgsPosition === "afterCommand") {
585
- args.push(...mcpArgs);
586
- }
587
- const mode = resolveModeConfig(config.modes[options.mode ?? "yolo"]);
588
- args.push(...mode.args);
589
- if (options.args && options.args.length > 0) {
590
- args.push(...options.args);
635
+ function prune2(obj, shape) {
636
+ let changed = false;
637
+ const result = { ...obj };
638
+ for (const [key, pattern] of Object.entries(shape)) {
639
+ if (!(key in result)) {
640
+ continue;
641
+ }
642
+ const current = result[key];
643
+ if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
644
+ delete result[key];
645
+ changed = true;
646
+ continue;
647
+ }
648
+ if (isConfigObject(pattern) && isConfigObject(current)) {
649
+ const { changed: childChanged, result: childResult } = prune2(
650
+ current,
651
+ pattern
652
+ );
653
+ if (childChanged) {
654
+ changed = true;
655
+ }
656
+ if (Object.keys(childResult).length === 0) {
657
+ delete result[key];
658
+ } else {
659
+ result[key] = childResult;
660
+ }
661
+ continue;
662
+ }
663
+ delete result[key];
664
+ changed = true;
591
665
  }
592
- return { args, env: mode.env };
593
- }
594
- function buildSpawnArgs(agentId, options) {
595
- const { binaryName, spawnConfig } = resolveCliConfig(agentId);
596
- const stdinMode = options.useStdin && spawnConfig.stdinMode ? spawnConfig.stdinMode : void 0;
597
- const result = buildCliArgs(spawnConfig, options, stdinMode);
598
- return { binaryName, args: result.args, env: result.env };
666
+ return { changed, result };
599
667
  }
600
-
601
- // packages/agent-spawn/src/spawn-interactive.ts
602
- import { spawn as spawnChildProcess2 } from "node:child_process";
603
-
604
- // packages/design-system/src/tokens/colors.ts
605
- import chalk from "chalk";
606
- var dark = {
607
- header: (text4) => chalk.magentaBright.bold(text4),
608
- divider: (text4) => chalk.dim(text4),
609
- prompt: (text4) => chalk.cyan(text4),
610
- number: (text4) => chalk.cyanBright(text4),
611
- intro: (text4) => chalk.bgMagenta.white(` Poe - ${text4} `),
612
- resolvedSymbol: chalk.magenta("\u25C7"),
613
- errorSymbol: chalk.red("\u25A0"),
614
- accent: (text4) => chalk.cyan(text4),
615
- muted: (text4) => chalk.dim(text4),
616
- success: (text4) => chalk.green(text4),
617
- warning: (text4) => chalk.yellow(text4),
618
- error: (text4) => chalk.red(text4),
619
- info: (text4) => chalk.magenta(text4),
620
- badge: (text4) => chalk.bgYellow.black(` ${text4} `)
621
- };
622
- var light = {
623
- header: (text4) => chalk.hex("#a200ff").bold(text4),
624
- divider: (text4) => chalk.hex("#666666")(text4),
625
- prompt: (text4) => chalk.hex("#006699").bold(text4),
626
- number: (text4) => chalk.hex("#0077cc").bold(text4),
627
- intro: (text4) => chalk.bgHex("#a200ff").white(` Poe - ${text4} `),
628
- resolvedSymbol: chalk.hex("#a200ff")("\u25C7"),
629
- errorSymbol: chalk.hex("#cc0000")("\u25A0"),
630
- accent: (text4) => chalk.hex("#006699").bold(text4),
631
- muted: (text4) => chalk.hex("#666666")(text4),
632
- success: (text4) => chalk.hex("#008800")(text4),
633
- warning: (text4) => chalk.hex("#cc6600")(text4),
634
- error: (text4) => chalk.hex("#cc0000")(text4),
635
- info: (text4) => chalk.hex("#a200ff")(text4),
636
- badge: (text4) => chalk.bgHex("#cc6600").white(` ${text4} `)
668
+ var jsonFormat = {
669
+ parse: parse2,
670
+ serialize,
671
+ merge: merge2,
672
+ prune: prune2
637
673
  };
638
674
 
639
- // packages/design-system/src/tokens/typography.ts
640
- import chalk2 from "chalk";
641
-
642
- // packages/design-system/src/components/text.ts
643
- import chalk3 from "chalk";
644
-
645
- // packages/design-system/src/internal/output-format.ts
646
- import { AsyncLocalStorage } from "node:async_hooks";
647
- var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
648
- var formatStorage = new AsyncLocalStorage();
649
- var cached;
650
- function resolveOutputFormat(env = process.env) {
651
- const scoped = formatStorage.getStore();
652
- if (scoped) {
653
- return scoped;
675
+ // packages/config-mutations/src/formats/toml.ts
676
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
677
+ function isConfigObject2(value) {
678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
679
+ }
680
+ function parse3(content) {
681
+ if (!content || content.trim() === "") {
682
+ return {};
654
683
  }
655
- if (cached) {
656
- return cached;
684
+ const parsed = parseToml(content);
685
+ if (!isConfigObject2(parsed)) {
686
+ throw new Error("Expected TOML document to be a table.");
657
687
  }
658
- const raw = env.OUTPUT_FORMAT?.toLowerCase();
659
- cached = VALID_FORMATS.has(raw) ? raw : "terminal";
660
- return cached;
688
+ return parsed;
661
689
  }
662
-
663
- // packages/design-system/src/internal/theme-detect.ts
664
- function detectThemeFromEnv(env) {
665
- const apple = env.APPLE_INTERFACE_STYLE;
666
- if (typeof apple === "string") {
667
- return apple.toLowerCase() === "dark" ? "dark" : "light";
668
- }
669
- const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
670
- if (typeof vscodeKind === "string") {
671
- const normalized = vscodeKind.toLowerCase();
672
- if (normalized.includes("light")) {
673
- return "light";
690
+ function serialize2(obj) {
691
+ const serialized = stringifyToml(obj);
692
+ return serialized.endsWith("\n") ? serialized : `${serialized}
693
+ `;
694
+ }
695
+ function merge3(base, patch) {
696
+ const result = { ...base };
697
+ for (const [key, value] of Object.entries(patch)) {
698
+ if (value === void 0) {
699
+ continue;
674
700
  }
675
- if (normalized.includes("dark")) {
676
- return "dark";
701
+ const existing = result[key];
702
+ if (isConfigObject2(existing) && isConfigObject2(value)) {
703
+ result[key] = merge3(existing, value);
704
+ continue;
677
705
  }
706
+ result[key] = value;
678
707
  }
679
- const colorFGBG = env.COLORFGBG;
680
- if (typeof colorFGBG === "string") {
681
- const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
682
- const background = parts.at(-1);
683
- if (Number.isFinite(background)) {
684
- return background >= 8 ? "light" : "dark";
708
+ return result;
709
+ }
710
+ function prune3(obj, shape) {
711
+ let changed = false;
712
+ const result = { ...obj };
713
+ for (const [key, pattern] of Object.entries(shape)) {
714
+ if (!(key in result)) {
715
+ continue;
716
+ }
717
+ const current = result[key];
718
+ if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
719
+ delete result[key];
720
+ changed = true;
721
+ continue;
722
+ }
723
+ if (isConfigObject2(pattern) && isConfigObject2(current)) {
724
+ const { changed: childChanged, result: childResult } = prune3(
725
+ current,
726
+ pattern
727
+ );
728
+ if (childChanged) {
729
+ changed = true;
730
+ }
731
+ if (Object.keys(childResult).length === 0) {
732
+ delete result[key];
733
+ } else {
734
+ result[key] = childResult;
735
+ }
736
+ continue;
685
737
  }
738
+ delete result[key];
739
+ changed = true;
686
740
  }
687
- return void 0;
741
+ return { changed, result };
688
742
  }
689
- function resolveThemeName(env = process.env) {
690
- const raw = (env.POE_CODE_THEME ?? env.POE_THEME)?.toLowerCase();
691
- if (raw === "light" || raw === "dark") {
692
- return raw;
743
+ var tomlFormat = {
744
+ parse: parse3,
745
+ serialize: serialize2,
746
+ merge: merge3,
747
+ prune: prune3
748
+ };
749
+
750
+ // packages/config-mutations/src/formats/yaml.ts
751
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
752
+ function isConfigObject3(value) {
753
+ return typeof value === "object" && value !== null && !Array.isArray(value);
754
+ }
755
+ function parse4(content) {
756
+ if (!content || content.trim() === "") {
757
+ return {};
693
758
  }
694
- const detected = detectThemeFromEnv(env);
695
- if (detected) {
696
- return detected;
759
+ const parsed = parseYaml2(content);
760
+ if (parsed === null || parsed === void 0) {
761
+ return {};
697
762
  }
698
- return "dark";
699
- }
700
- var cachedTheme;
701
- function getTheme(env) {
702
- if (cachedTheme) {
703
- return cachedTheme;
763
+ if (!isConfigObject3(parsed)) {
764
+ throw new Error("Expected YAML object.");
704
765
  }
705
- const themeName = resolveThemeName(env);
706
- cachedTheme = themeName === "light" ? light : dark;
707
- return cachedTheme;
766
+ return parsed;
708
767
  }
709
-
710
- // packages/design-system/src/components/symbols.ts
711
- import chalk4 from "chalk";
712
- var symbols = {
713
- get info() {
714
- const format = resolveOutputFormat();
715
- if (format === "json") return "info";
716
- if (format === "markdown") return "(i)";
717
- return chalk4.magenta("\u25CF");
718
- },
719
- get success() {
720
- const format = resolveOutputFormat();
721
- if (format === "json") return "success";
722
- if (format === "markdown") return "[ok]";
723
- return chalk4.magenta("\u25C6");
724
- },
725
- get resolved() {
726
- const format = resolveOutputFormat();
727
- if (format === "json") return "resolved";
728
- if (format === "markdown") return ">";
729
- return getTheme().resolvedSymbol;
730
- },
731
- get errorResolved() {
732
- const format = resolveOutputFormat();
733
- if (format === "json") return "error";
734
- if (format === "markdown") return "[!]";
735
- return getTheme().errorSymbol;
736
- },
737
- get bar() {
738
- const format = resolveOutputFormat();
739
- if (format === "json") return "";
740
- if (format === "markdown") return "|";
741
- return "\u2502";
742
- },
743
- cornerTopRight: "\u256E",
744
- cornerBottomRight: "\u256F",
745
- get warning() {
746
- const format = resolveOutputFormat();
747
- if (format === "json") return "warning";
748
- if (format === "markdown") return "[!]";
749
- return "\u25B2";
750
- },
751
- get active() {
752
- const format = resolveOutputFormat();
753
- if (format === "json") return "active";
754
- if (format === "markdown") return "[x]";
755
- return "\u25C6";
756
- },
757
- get inactive() {
758
- const format = resolveOutputFormat();
759
- if (format === "json") return "inactive";
760
- if (format === "markdown") return "[ ]";
761
- return "\u25CB";
762
- }
763
- };
764
-
765
- // packages/design-system/src/components/logger.ts
766
- import chalk6 from "chalk";
767
-
768
- // packages/design-system/src/prompts/primitives/log.ts
769
- import chalk5 from "chalk";
770
-
771
- // packages/design-system/src/internal/strip-ansi.ts
772
- function stripAnsi(value) {
773
- return value.replace(/\u001b\[[0-9;]*m/g, "");
768
+ function serialize3(obj) {
769
+ const serialized = stringifyYaml(obj);
770
+ return serialized.endsWith("\n") ? serialized : `${serialized}
771
+ `;
774
772
  }
775
-
776
- // packages/design-system/src/prompts/primitives/log.ts
777
- function writeTerminalMessage(msg, {
778
- symbol = chalk5.gray("\u2502"),
779
- secondarySymbol = chalk5.gray("\u2502"),
780
- spacing: spacing2 = 1,
781
- withGuide = true
782
- } = {}) {
783
- const lines = [];
784
- const showGuide = withGuide !== false;
785
- const contentLines = msg.split("\n");
786
- const prefix = showGuide ? `${symbol} ` : "";
787
- const continuationPrefix = showGuide ? `${secondarySymbol} ` : "";
788
- const emptyGuide = showGuide ? secondarySymbol : "";
789
- for (let index = 0; index < spacing2; index += 1) {
790
- lines.push(emptyGuide);
791
- }
792
- if (contentLines.length === 0) {
793
- process.stdout.write("\n");
794
- return;
795
- }
796
- const [firstLine = "", ...continuationLines] = contentLines;
797
- if (firstLine.length > 0) {
798
- lines.push(`${prefix}${firstLine}`);
799
- } else {
800
- lines.push(showGuide ? symbol : "");
801
- }
802
- for (const line of continuationLines) {
803
- if (line.length > 0) {
804
- lines.push(`${continuationPrefix}${line}`);
773
+ function merge4(base, patch) {
774
+ const result = { ...base };
775
+ for (const [key, value] of Object.entries(patch)) {
776
+ if (value === void 0) {
805
777
  continue;
806
778
  }
807
- lines.push(emptyGuide);
779
+ const existing = result[key];
780
+ if (isConfigObject3(existing) && isConfigObject3(value)) {
781
+ result[key] = merge4(existing, value);
782
+ continue;
783
+ }
784
+ result[key] = value;
808
785
  }
809
- process.stdout.write(`${lines.join("\n")}
810
- `);
786
+ return result;
811
787
  }
812
- function message(msg, options) {
813
- const format = resolveOutputFormat();
814
- if (format === "markdown") {
815
- process.stdout.write(`- ${stripAnsi(msg)}
816
- `);
817
- return;
818
- }
819
- if (format === "json") {
820
- process.stdout.write(
821
- `${JSON.stringify({ level: "message", message: stripAnsi(msg) })}
822
- `
823
- );
824
- return;
788
+ function prune4(obj, shape) {
789
+ let changed = false;
790
+ const result = { ...obj };
791
+ for (const [key, pattern] of Object.entries(shape)) {
792
+ if (!(key in result)) {
793
+ continue;
794
+ }
795
+ const current = result[key];
796
+ if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
797
+ delete result[key];
798
+ changed = true;
799
+ continue;
800
+ }
801
+ if (isConfigObject3(pattern) && isConfigObject3(current)) {
802
+ const { changed: childChanged, result: childResult } = prune4(current, pattern);
803
+ if (childChanged) {
804
+ changed = true;
805
+ }
806
+ if (Object.keys(childResult).length === 0) {
807
+ delete result[key];
808
+ } else {
809
+ result[key] = childResult;
810
+ }
811
+ continue;
812
+ }
813
+ delete result[key];
814
+ changed = true;
825
815
  }
826
- writeTerminalMessage(msg, options);
816
+ return { changed, result };
827
817
  }
828
- function info(msg) {
829
- const format = resolveOutputFormat();
830
- if (format === "markdown") {
831
- process.stdout.write(`- **info:** ${stripAnsi(msg)}
832
- `);
833
- return;
818
+ var yamlFormat = {
819
+ parse: parse4,
820
+ serialize: serialize3,
821
+ merge: merge4,
822
+ prune: prune4
823
+ };
824
+
825
+ // packages/config-mutations/src/formats/index.ts
826
+ var formatRegistry = {
827
+ json: jsonFormat,
828
+ toml: tomlFormat,
829
+ yaml: yamlFormat
830
+ };
831
+ var extensionMap = {
832
+ ".json": "json",
833
+ ".toml": "toml",
834
+ ".yaml": "yaml",
835
+ ".yml": "yaml"
836
+ };
837
+ function getConfigFormat(pathOrFormat) {
838
+ if (pathOrFormat in formatRegistry) {
839
+ return formatRegistry[pathOrFormat];
834
840
  }
835
- if (format === "json") {
836
- process.stdout.write(
837
- `${JSON.stringify({ level: "info", message: stripAnsi(msg) })}
838
- `
841
+ const ext = getExtension(pathOrFormat);
842
+ const formatName = extensionMap[ext];
843
+ if (!formatName) {
844
+ throw new Error(
845
+ `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
839
846
  );
840
- return;
841
847
  }
842
- message(msg, { symbol: symbols.info });
848
+ return formatRegistry[formatName];
843
849
  }
844
- function success(msg) {
845
- const format = resolveOutputFormat();
846
- if (format === "markdown") {
847
- process.stdout.write(`- **success:** ${stripAnsi(msg)}
848
- `);
849
- return;
850
- }
851
- if (format === "json") {
852
- process.stdout.write(
853
- `${JSON.stringify({ level: "success", message: stripAnsi(msg) })}
854
- `
855
- );
856
- return;
850
+ function detectFormat(path18) {
851
+ const ext = getExtension(path18);
852
+ return extensionMap[ext];
853
+ }
854
+ function getExtension(path18) {
855
+ const lastDot = path18.lastIndexOf(".");
856
+ if (lastDot === -1) {
857
+ return "";
857
858
  }
858
- message(msg, { symbol: symbols.success });
859
+ return path18.slice(lastDot).toLowerCase();
859
860
  }
860
- function warn(msg) {
861
- const format = resolveOutputFormat();
862
- if (format === "markdown") {
863
- process.stdout.write(`- **warning:** ${stripAnsi(msg)}
864
- `);
865
- return;
861
+
862
+ // packages/config-mutations/src/execution/path-utils.ts
863
+ import path7 from "node:path";
864
+ function expandHome(targetPath, homeDir) {
865
+ if (!targetPath?.startsWith("~")) {
866
+ return targetPath;
866
867
  }
867
- if (format === "json") {
868
- process.stdout.write(
869
- `${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}
870
- `
871
- );
872
- return;
868
+ if (targetPath.startsWith("~./")) {
869
+ targetPath = `~/.${targetPath.slice(3)}`;
873
870
  }
874
- message(msg, { symbol: chalk5.yellow("\u25B2") });
871
+ let remainder = targetPath.slice(1);
872
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
873
+ remainder = remainder.slice(1);
874
+ } else if (remainder.startsWith(".")) {
875
+ remainder = remainder.slice(1);
876
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
877
+ remainder = remainder.slice(1);
878
+ }
879
+ }
880
+ return remainder.length === 0 ? homeDir : path7.join(homeDir, remainder);
875
881
  }
876
- function error(msg) {
877
- const format = resolveOutputFormat();
878
- if (format === "markdown") {
879
- process.stdout.write(`- **error:** ${stripAnsi(msg)}
880
- `);
881
- return;
882
+ function validateHomePath(targetPath) {
883
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
884
+ throw new Error("Target path must be a non-empty string.");
882
885
  }
883
- if (format === "json") {
884
- process.stdout.write(
885
- `${JSON.stringify({ level: "error", message: stripAnsi(msg) })}
886
- `
886
+ if (!targetPath.startsWith("~")) {
887
+ throw new Error(
888
+ `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
887
889
  );
888
- return;
889
890
  }
890
- message(msg, { symbol: chalk5.red("\u25A0") });
891
891
  }
892
- var log = {
893
- info,
894
- success,
895
- message,
896
- warn,
897
- error
898
- };
892
+ function resolvePath(rawPath, homeDir, pathMapper) {
893
+ validateHomePath(rawPath);
894
+ const expanded = expandHome(rawPath, homeDir);
895
+ if (!pathMapper) {
896
+ return expanded;
897
+ }
898
+ const rawDirectory = path7.dirname(expanded);
899
+ const mappedDirectory = pathMapper.mapTargetDirectory({
900
+ targetDirectory: rawDirectory
901
+ });
902
+ const filename = path7.basename(expanded);
903
+ return filename.length === 0 ? mappedDirectory : path7.join(mappedDirectory, filename);
904
+ }
899
905
 
900
- // packages/design-system/src/components/logger.ts
901
- function createLogger(emitter) {
902
- const emit = (level, message2) => {
903
- if (emitter) {
904
- emitter(message2);
905
- return;
906
+ // packages/config-mutations/src/fs-utils.ts
907
+ function isNotFound(error2) {
908
+ return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
909
+ }
910
+ async function readFileIfExists(fs, target) {
911
+ try {
912
+ return await fs.readFile(target, "utf8");
913
+ } catch (error2) {
914
+ if (isNotFound(error2)) {
915
+ return null;
906
916
  }
907
- if (level === "success") {
908
- log.success(message2);
909
- return;
917
+ throw error2;
918
+ }
919
+ }
920
+ async function pathExists(fs, target) {
921
+ try {
922
+ await fs.stat(target);
923
+ return true;
924
+ } catch (error2) {
925
+ if (isNotFound(error2)) {
926
+ return false;
910
927
  }
911
- if (level === "warn") {
912
- log.warn(message2);
913
- return;
928
+ throw error2;
929
+ }
930
+ }
931
+ function createTimestamp() {
932
+ return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
933
+ }
934
+
935
+ // packages/config-mutations/src/execution/apply-mutation.ts
936
+ function resolveValue(resolver, options) {
937
+ if (typeof resolver === "function") {
938
+ return resolver(options);
939
+ }
940
+ return resolver;
941
+ }
942
+ function createInvalidDocumentBackupPath(targetPath) {
943
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
944
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
945
+ }
946
+ async function backupInvalidDocument(fs, targetPath, content) {
947
+ const backupPath = createInvalidDocumentBackupPath(targetPath);
948
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
949
+ }
950
+ function describeMutation(kind, targetPath) {
951
+ const displayPath = targetPath ?? "target";
952
+ switch (kind) {
953
+ case "ensureDirectory":
954
+ return `Create ${displayPath}`;
955
+ case "removeDirectory":
956
+ return `Remove directory ${displayPath}`;
957
+ case "backup":
958
+ return `Backup ${displayPath}`;
959
+ case "templateWrite":
960
+ return `Write ${displayPath}`;
961
+ case "chmod":
962
+ return `Set permissions on ${displayPath}`;
963
+ case "removeFile":
964
+ return `Remove ${displayPath}`;
965
+ case "configMerge":
966
+ case "configPrune":
967
+ case "configTransform":
968
+ case "templateMergeToml":
969
+ case "templateMergeJson":
970
+ return `Update ${displayPath}`;
971
+ default:
972
+ return "Operation";
973
+ }
974
+ }
975
+ function pruneKeysByPrefix(table, prefix) {
976
+ const result = {};
977
+ for (const [key, value] of Object.entries(table)) {
978
+ if (!key.startsWith(prefix)) {
979
+ result[key] = value;
914
980
  }
915
- if (level === "error") {
916
- log.error(message2);
917
- return;
981
+ }
982
+ return result;
983
+ }
984
+ function isConfigObject4(value) {
985
+ return typeof value === "object" && value !== null && !Array.isArray(value);
986
+ }
987
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
988
+ const result = { ...base };
989
+ const prefixMap = pruneByPrefix ?? {};
990
+ for (const [key, value] of Object.entries(patch)) {
991
+ const current = result[key];
992
+ const prefix = prefixMap[key];
993
+ if (isConfigObject4(current) && isConfigObject4(value)) {
994
+ if (prefix) {
995
+ const pruned = pruneKeysByPrefix(current, prefix);
996
+ result[key] = { ...pruned, ...value };
997
+ } else {
998
+ result[key] = mergeWithPruneByPrefix(
999
+ current,
1000
+ value,
1001
+ prefixMap
1002
+ );
1003
+ }
1004
+ continue;
918
1005
  }
919
- log.info(message2);
1006
+ result[key] = value;
1007
+ }
1008
+ return result;
1009
+ }
1010
+ async function applyMutation(mutation, context, options) {
1011
+ switch (mutation.kind) {
1012
+ case "ensureDirectory":
1013
+ return applyEnsureDirectory(mutation, context, options);
1014
+ case "removeDirectory":
1015
+ return applyRemoveDirectory(mutation, context, options);
1016
+ case "removeFile":
1017
+ return applyRemoveFile(mutation, context, options);
1018
+ case "chmod":
1019
+ return applyChmod(mutation, context, options);
1020
+ case "backup":
1021
+ return applyBackup(mutation, context, options);
1022
+ case "configMerge":
1023
+ return applyConfigMerge(mutation, context, options);
1024
+ case "configPrune":
1025
+ return applyConfigPrune(mutation, context, options);
1026
+ case "configTransform":
1027
+ return applyConfigTransform(mutation, context, options);
1028
+ case "templateWrite":
1029
+ return applyTemplateWrite(mutation, context, options);
1030
+ case "templateMergeToml":
1031
+ return applyTemplateMerge(mutation, context, options, "toml");
1032
+ case "templateMergeJson":
1033
+ return applyTemplateMerge(mutation, context, options, "json");
1034
+ default: {
1035
+ const never = mutation;
1036
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
1037
+ }
1038
+ }
1039
+ }
1040
+ async function applyEnsureDirectory(mutation, context, options) {
1041
+ const rawPath = resolveValue(mutation.path, options);
1042
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1043
+ const details = {
1044
+ kind: mutation.kind,
1045
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1046
+ targetPath
920
1047
  };
1048
+ const existed = await pathExists(context.fs, targetPath);
1049
+ if (!context.dryRun) {
1050
+ await context.fs.mkdir(targetPath, { recursive: true });
1051
+ }
921
1052
  return {
922
- info(message2) {
923
- emit("info", message2);
924
- },
925
- success(message2) {
926
- emit("success", message2);
927
- },
928
- warn(message2) {
929
- emit("warn", message2);
930
- },
931
- error(message2) {
932
- emit("error", message2);
933
- },
934
- resolved(label, value) {
935
- if (emitter) {
936
- emitter(`${label}: ${value}`);
937
- return;
938
- }
939
- log.message(`${label}
940
- ${value}`, { symbol: symbols.resolved });
941
- },
942
- errorResolved(label, value) {
943
- if (emitter) {
944
- emitter(`${label}: ${value}`);
945
- return;
946
- }
947
- log.message(`${label}
948
- ${value}`, { symbol: symbols.errorResolved });
1053
+ outcome: {
1054
+ changed: !existed,
1055
+ effect: "mkdir",
1056
+ detail: existed ? "noop" : "create"
949
1057
  },
950
- message(message2, symbol) {
951
- if (emitter) {
952
- emitter(message2);
953
- return;
954
- }
955
- log.message(message2, { symbol: symbol ?? chalk6.gray("\u2502") });
1058
+ details
1059
+ };
1060
+ }
1061
+ async function applyRemoveDirectory(mutation, context, options) {
1062
+ const rawPath = resolveValue(mutation.path, options);
1063
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1064
+ const details = {
1065
+ kind: mutation.kind,
1066
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1067
+ targetPath
1068
+ };
1069
+ const existed = await pathExists(context.fs, targetPath);
1070
+ if (!existed) {
1071
+ return {
1072
+ outcome: { changed: false, effect: "none", detail: "noop" },
1073
+ details
1074
+ };
1075
+ }
1076
+ if (typeof context.fs.rm !== "function") {
1077
+ return {
1078
+ outcome: { changed: false, effect: "none", detail: "noop" },
1079
+ details
1080
+ };
1081
+ }
1082
+ if (mutation.force) {
1083
+ if (!context.dryRun) {
1084
+ await context.fs.rm(targetPath, { recursive: true, force: true });
956
1085
  }
1086
+ return {
1087
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1088
+ details
1089
+ };
1090
+ }
1091
+ const entries = await context.fs.readdir(targetPath);
1092
+ if (entries.length > 0) {
1093
+ return {
1094
+ outcome: { changed: false, effect: "none", detail: "noop" },
1095
+ details
1096
+ };
1097
+ }
1098
+ if (!context.dryRun) {
1099
+ await context.fs.rm(targetPath, { recursive: true, force: true });
1100
+ }
1101
+ return {
1102
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1103
+ details
1104
+ };
1105
+ }
1106
+ async function applyRemoveFile(mutation, context, options) {
1107
+ const rawPath = resolveValue(mutation.target, options);
1108
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1109
+ const details = {
1110
+ kind: mutation.kind,
1111
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1112
+ targetPath
1113
+ };
1114
+ try {
1115
+ const content = await context.fs.readFile(targetPath, "utf8");
1116
+ const trimmed = content.trim();
1117
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
1118
+ return {
1119
+ outcome: { changed: false, effect: "none", detail: "noop" },
1120
+ details
1121
+ };
1122
+ }
1123
+ if (mutation.whenEmpty && trimmed.length > 0) {
1124
+ return {
1125
+ outcome: { changed: false, effect: "none", detail: "noop" },
1126
+ details
1127
+ };
1128
+ }
1129
+ if (!context.dryRun) {
1130
+ await context.fs.unlink(targetPath);
1131
+ }
1132
+ return {
1133
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1134
+ details
1135
+ };
1136
+ } catch (error2) {
1137
+ if (isNotFound(error2)) {
1138
+ return {
1139
+ outcome: { changed: false, effect: "none", detail: "noop" },
1140
+ details
1141
+ };
1142
+ }
1143
+ throw error2;
1144
+ }
1145
+ }
1146
+ async function applyChmod(mutation, context, options) {
1147
+ const rawPath = resolveValue(mutation.target, options);
1148
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1149
+ const details = {
1150
+ kind: mutation.kind,
1151
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1152
+ targetPath
1153
+ };
1154
+ if (typeof context.fs.chmod !== "function") {
1155
+ return {
1156
+ outcome: { changed: false, effect: "none", detail: "noop" },
1157
+ details
1158
+ };
1159
+ }
1160
+ try {
1161
+ const stat2 = await context.fs.stat(targetPath);
1162
+ const currentMode = typeof stat2.mode === "number" ? stat2.mode & 511 : null;
1163
+ if (currentMode === mutation.mode) {
1164
+ return {
1165
+ outcome: { changed: false, effect: "none", detail: "noop" },
1166
+ details
1167
+ };
1168
+ }
1169
+ if (!context.dryRun) {
1170
+ await context.fs.chmod(targetPath, mutation.mode);
1171
+ }
1172
+ return {
1173
+ outcome: { changed: true, effect: "chmod", detail: "update" },
1174
+ details
1175
+ };
1176
+ } catch (error2) {
1177
+ if (isNotFound(error2)) {
1178
+ return {
1179
+ outcome: { changed: false, effect: "none", detail: "noop" },
1180
+ details
1181
+ };
1182
+ }
1183
+ throw error2;
1184
+ }
1185
+ }
1186
+ async function applyBackup(mutation, context, options) {
1187
+ const rawPath = resolveValue(mutation.target, options);
1188
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1189
+ const details = {
1190
+ kind: mutation.kind,
1191
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1192
+ targetPath
1193
+ };
1194
+ const content = await readFileIfExists(context.fs, targetPath);
1195
+ if (content === null) {
1196
+ return {
1197
+ outcome: { changed: false, effect: "none", detail: "noop" },
1198
+ details
1199
+ };
1200
+ }
1201
+ if (!context.dryRun) {
1202
+ const backupPath = `${targetPath}.backup-${createTimestamp()}`;
1203
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
1204
+ }
1205
+ return {
1206
+ outcome: { changed: true, effect: "copy", detail: "backup" },
1207
+ details
1208
+ };
1209
+ }
1210
+ async function applyConfigMerge(mutation, context, options) {
1211
+ const rawPath = resolveValue(mutation.target, options);
1212
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1213
+ const details = {
1214
+ kind: mutation.kind,
1215
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1216
+ targetPath
1217
+ };
1218
+ const formatName = mutation.format ?? detectFormat(rawPath);
1219
+ if (!formatName) {
1220
+ throw new Error(
1221
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1222
+ );
1223
+ }
1224
+ const format = getConfigFormat(formatName);
1225
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1226
+ let current;
1227
+ try {
1228
+ current = rawContent === null ? {} : format.parse(rawContent);
1229
+ } catch {
1230
+ if (rawContent !== null) {
1231
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1232
+ }
1233
+ current = {};
1234
+ }
1235
+ const value = resolveValue(mutation.value, options);
1236
+ let merged;
1237
+ if (mutation.pruneByPrefix) {
1238
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
1239
+ } else {
1240
+ merged = format.merge(current, value);
1241
+ }
1242
+ const serialized = format.serialize(merged);
1243
+ const changed = serialized !== rawContent;
1244
+ if (changed && !context.dryRun) {
1245
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1246
+ }
1247
+ return {
1248
+ outcome: {
1249
+ changed,
1250
+ effect: changed ? "write" : "none",
1251
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1252
+ },
1253
+ details
1254
+ };
1255
+ }
1256
+ async function applyConfigPrune(mutation, context, options) {
1257
+ const rawPath = resolveValue(mutation.target, options);
1258
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1259
+ const details = {
1260
+ kind: mutation.kind,
1261
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1262
+ targetPath
1263
+ };
1264
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1265
+ if (rawContent === null) {
1266
+ return {
1267
+ outcome: { changed: false, effect: "none", detail: "noop" },
1268
+ details
1269
+ };
1270
+ }
1271
+ const formatName = mutation.format ?? detectFormat(rawPath);
1272
+ if (!formatName) {
1273
+ throw new Error(
1274
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1275
+ );
1276
+ }
1277
+ const format = getConfigFormat(formatName);
1278
+ let current;
1279
+ try {
1280
+ current = format.parse(rawContent);
1281
+ } catch {
1282
+ return {
1283
+ outcome: { changed: false, effect: "none", detail: "noop" },
1284
+ details
1285
+ };
1286
+ }
1287
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
1288
+ return {
1289
+ outcome: { changed: false, effect: "none", detail: "noop" },
1290
+ details
1291
+ };
1292
+ }
1293
+ const shape = resolveValue(mutation.shape, options);
1294
+ const { changed, result } = format.prune(current, shape);
1295
+ if (!changed) {
1296
+ return {
1297
+ outcome: { changed: false, effect: "none", detail: "noop" },
1298
+ details
1299
+ };
1300
+ }
1301
+ if (Object.keys(result).length === 0) {
1302
+ if (!context.dryRun) {
1303
+ await context.fs.unlink(targetPath);
1304
+ }
1305
+ return {
1306
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1307
+ details
1308
+ };
1309
+ }
1310
+ const serialized = format.serialize(result);
1311
+ if (!context.dryRun) {
1312
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1313
+ }
1314
+ return {
1315
+ outcome: { changed: true, effect: "write", detail: "update" },
1316
+ details
1317
+ };
1318
+ }
1319
+ async function applyConfigTransform(mutation, context, options) {
1320
+ const rawPath = resolveValue(mutation.target, options);
1321
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1322
+ const details = {
1323
+ kind: mutation.kind,
1324
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1325
+ targetPath
1326
+ };
1327
+ const formatName = mutation.format ?? detectFormat(rawPath);
1328
+ if (!formatName) {
1329
+ throw new Error(
1330
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1331
+ );
1332
+ }
1333
+ const format = getConfigFormat(formatName);
1334
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1335
+ let current;
1336
+ try {
1337
+ current = rawContent === null ? {} : format.parse(rawContent);
1338
+ } catch {
1339
+ if (rawContent !== null) {
1340
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1341
+ }
1342
+ current = {};
1343
+ }
1344
+ const { content: transformed, changed } = mutation.transform(current, options);
1345
+ if (!changed) {
1346
+ return {
1347
+ outcome: { changed: false, effect: "none", detail: "noop" },
1348
+ details
1349
+ };
1350
+ }
1351
+ if (transformed === null) {
1352
+ if (rawContent === null) {
1353
+ return {
1354
+ outcome: { changed: false, effect: "none", detail: "noop" },
1355
+ details
1356
+ };
1357
+ }
1358
+ if (!context.dryRun) {
1359
+ await context.fs.unlink(targetPath);
1360
+ }
1361
+ return {
1362
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1363
+ details
1364
+ };
1365
+ }
1366
+ const serialized = format.serialize(transformed);
1367
+ if (!context.dryRun) {
1368
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1369
+ }
1370
+ return {
1371
+ outcome: {
1372
+ changed: true,
1373
+ effect: "write",
1374
+ detail: rawContent === null ? "create" : "update"
1375
+ },
1376
+ details
1377
+ };
1378
+ }
1379
+ async function applyTemplateWrite(mutation, context, options) {
1380
+ if (!context.templates) {
1381
+ throw new Error(
1382
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1383
+ );
1384
+ }
1385
+ const rawPath = resolveValue(mutation.target, options);
1386
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1387
+ const details = {
1388
+ kind: mutation.kind,
1389
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1390
+ targetPath
1391
+ };
1392
+ const template = await context.templates(mutation.templateId);
1393
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1394
+ const rendered = Mustache.render(template, templateContext);
1395
+ const existed = await pathExists(context.fs, targetPath);
1396
+ if (!context.dryRun) {
1397
+ await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
1398
+ }
1399
+ return {
1400
+ outcome: {
1401
+ changed: true,
1402
+ effect: "write",
1403
+ detail: existed ? "update" : "create"
1404
+ },
1405
+ details
1406
+ };
1407
+ }
1408
+ async function applyTemplateMerge(mutation, context, options, formatName) {
1409
+ if (!context.templates) {
1410
+ throw new Error(
1411
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1412
+ );
1413
+ }
1414
+ const rawPath = resolveValue(mutation.target, options);
1415
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1416
+ const details = {
1417
+ kind: mutation.kind,
1418
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1419
+ targetPath
1420
+ };
1421
+ const format = getConfigFormat(formatName);
1422
+ const template = await context.templates(mutation.templateId);
1423
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1424
+ const rendered = Mustache.render(template, templateContext);
1425
+ let templateDoc;
1426
+ try {
1427
+ templateDoc = format.parse(rendered);
1428
+ } catch (error2) {
1429
+ throw new Error(
1430
+ `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error2}`,
1431
+ { cause: error2 }
1432
+ );
1433
+ }
1434
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1435
+ let current;
1436
+ try {
1437
+ current = rawContent === null ? {} : format.parse(rawContent);
1438
+ } catch {
1439
+ if (rawContent !== null) {
1440
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1441
+ }
1442
+ current = {};
1443
+ }
1444
+ const merged = format.merge(current, templateDoc);
1445
+ const serialized = format.serialize(merged);
1446
+ const changed = serialized !== rawContent;
1447
+ if (changed && !context.dryRun) {
1448
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1449
+ }
1450
+ return {
1451
+ outcome: {
1452
+ changed,
1453
+ effect: changed ? "write" : "none",
1454
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1455
+ },
1456
+ details
957
1457
  };
958
1458
  }
959
- var logger = createLogger();
960
-
961
- // packages/design-system/src/components/table.ts
962
- import { Table } from "console-table-printer";
963
-
964
- // packages/design-system/src/acp/components.ts
965
- import chalk7 from "chalk";
966
-
967
- // packages/design-system/src/acp/writer.ts
968
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
969
- var storage = new AsyncLocalStorage2();
970
-
971
- // packages/design-system/src/acp/components.ts
972
- var AGENT_PREFIX = `${chalk7.green.bold("\u2713")} agent: `;
973
-
974
- // packages/design-system/src/dashboard/buffer.ts
975
- import chalk8 from "chalk";
976
-
977
- // packages/design-system/src/dashboard/terminal.ts
978
- import readline from "node:readline";
979
- import { PassThrough } from "node:stream";
980
-
981
- // packages/design-system/src/prompts/index.ts
982
- import chalk15 from "chalk";
983
- import * as clack from "@clack/prompts";
984
-
985
- // packages/design-system/src/prompts/primitives/cancel.ts
986
- import chalk9 from "chalk";
987
- import { isCancel } from "@clack/prompts";
988
-
989
- // packages/design-system/src/prompts/primitives/intro.ts
990
- import chalk10 from "chalk";
991
-
992
- // packages/design-system/src/prompts/primitives/note.ts
993
- import chalk11 from "chalk";
994
-
995
- // packages/design-system/src/prompts/primitives/outro.ts
996
- import chalk12 from "chalk";
997
1459
 
998
- // packages/design-system/src/prompts/primitives/spinner.ts
999
- import chalk14 from "chalk";
1460
+ // packages/config-mutations/src/execution/run-mutations.ts
1461
+ async function runMutations(mutations, context, options) {
1462
+ const effects = [];
1463
+ let anyChanged = false;
1464
+ const resolverOptions = options ?? {};
1465
+ for (const mutation of mutations) {
1466
+ const { outcome } = await executeMutation(
1467
+ mutation,
1468
+ context,
1469
+ resolverOptions
1470
+ );
1471
+ effects.push(outcome);
1472
+ if (outcome.changed) {
1473
+ anyChanged = true;
1474
+ }
1475
+ }
1476
+ return {
1477
+ changed: anyChanged,
1478
+ effects
1479
+ };
1480
+ }
1481
+ async function executeMutation(mutation, context, options) {
1482
+ context.observers?.onStart?.({
1483
+ kind: mutation.kind,
1484
+ label: mutation.label ?? mutation.kind,
1485
+ targetPath: void 0
1486
+ // Will be resolved during apply
1487
+ });
1488
+ try {
1489
+ const { outcome, details } = await applyMutation(mutation, context, options);
1490
+ context.observers?.onComplete?.(details, outcome);
1491
+ return { outcome, details };
1492
+ } catch (error2) {
1493
+ context.observers?.onError?.(
1494
+ {
1495
+ kind: mutation.kind,
1496
+ label: mutation.label ?? mutation.kind,
1497
+ targetPath: void 0
1498
+ },
1499
+ error2
1500
+ );
1501
+ throw error2;
1502
+ }
1503
+ }
1000
1504
 
1001
- // packages/design-system/src/static/spinner.ts
1002
- import chalk13 from "chalk";
1505
+ // packages/config-mutations/src/template/render.ts
1506
+ import Mustache2 from "mustache";
1507
+ var originalEscape = Mustache2.escape;
1003
1508
 
1004
- // packages/design-system/src/static/menu.ts
1005
- import chalk16 from "chalk";
1509
+ // packages/poe-code-config/src/store.ts
1510
+ var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
1511
+ `;
1006
1512
 
1007
- // packages/agent-spawn/src/autonomous.ts
1008
- var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
1513
+ // packages/poe-code-config/src/inspect.ts
1514
+ import path9 from "node:path";
1515
+ var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
1516
+ `;
1009
1517
 
1010
- // packages/agent-spawn/src/acp/replay.ts
1011
- import path2 from "node:path";
1012
- import { homedir as homedir2 } from "node:os";
1013
- import { open, readdir } from "node:fs/promises";
1014
- import { createInterface } from "node:readline";
1518
+ // packages/poe-code-config/src/state/index.ts
1519
+ import os2 from "node:os";
1015
1520
 
1016
- // packages/poe-acp-client/src/acp-client.ts
1017
- import { isAbsolute } from "node:path";
1521
+ // packages/poe-code-config/src/state/jobs.ts
1522
+ import path10 from "node:path";
1018
1523
 
1019
- // packages/poe-acp-client/src/acp-transport.ts
1020
- import {
1021
- spawn as spawnChildProcess3
1022
- } from "node:child_process";
1524
+ // packages/poe-code-config/src/state/fs.ts
1525
+ import * as nodeFs2 from "node:fs/promises";
1023
1526
 
1024
- // packages/poe-acp-client/src/run-report.ts
1025
- import * as fsPromises from "node:fs/promises";
1026
- import { homedir } from "node:os";
1027
- import { join } from "node:path";
1527
+ // packages/poe-code-config/src/state/templates.ts
1528
+ import path11 from "node:path";
1028
1529
 
1029
- // packages/agent-spawn/src/acp/spawn.ts
1030
- import { spawn as spawnChildProcess4 } from "node:child_process";
1530
+ // packages/agent-harness-tools/src/execution-env.ts
1531
+ var executionEnvFactories = /* @__PURE__ */ new Map();
1532
+ function registerExecutionEnvFactory(factory) {
1533
+ executionEnvFactories.set(factory.type, factory);
1534
+ }
1031
1535
 
1032
- // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
1033
- import path3 from "node:path";
1034
- import { homedir as homedir3 } from "node:os";
1035
- import { mkdir, open as open2 } from "node:fs/promises";
1536
+ // packages/agent-harness-tools/src/workspace-transfer.ts
1537
+ import { createHash } from "node:crypto";
1538
+ import { promises as nodeFs3 } from "node:fs";
1539
+ import path12 from "node:path";
1036
1540
 
1037
- // src/utils/command-checks.ts
1038
- function formatCommandRunnerResult(result) {
1039
- const stdout = result.stdout.length > 0 ? result.stdout : "<empty>";
1040
- const stderr = result.stderr.length > 0 ? result.stderr : "<empty>";
1041
- return `stdout:
1042
- ${stdout}
1043
- stderr:
1044
- ${stderr}`;
1045
- }
1046
- function createSpawnHealthCheck(agentId, options) {
1047
- const prompt = `Output exactly: ${options.expectedOutput}`;
1048
- const { binaryName, args, env: modeEnv } = buildSpawnArgs(agentId, {
1049
- prompt,
1050
- model: options.model,
1051
- mode: "yolo"
1052
- });
1053
- return {
1054
- id: `${agentId}-cli-health`,
1055
- description: `spawn ${agentId} (expecting "${options.expectedOutput}")`,
1056
- async run(context) {
1057
- if (context.isDryRun) {
1058
- context.logDryRun?.(
1059
- `Dry run: ${[binaryName, ...args].join(" ")} (expecting "${options.expectedOutput}")`
1060
- );
1061
- return;
1062
- }
1063
- const result = modeEnv ? await context.runCommand(binaryName, args, { env: modeEnv }) : await context.runCommand(binaryName, args);
1064
- if (result.exitCode !== 0) {
1065
- throw new Error(
1066
- `spawn ${agentId} failed with exit code ${result.exitCode}.
1067
- ${formatCommandRunnerResult(result)}`
1068
- );
1069
- }
1070
- if (!result.stdout.includes(options.expectedOutput)) {
1071
- throw new Error(
1072
- `spawn ${agentId}: expected "${options.expectedOutput}" in stdout.
1073
- ${formatCommandRunnerResult(result)}`
1074
- );
1075
- }
1076
- }
1077
- };
1078
- }
1079
- function createBinaryExistsCheck(binaryName, id, description) {
1080
- return {
1081
- id,
1082
- description,
1083
- async run({ runCommand: runCommand2 }) {
1084
- const commonPaths = [
1085
- `/usr/local/bin/${binaryName}`,
1086
- `/usr/bin/${binaryName}`,
1087
- `$HOME/.local/bin/${binaryName}`,
1088
- `$HOME/.claude/local/bin/${binaryName}`
1089
- ];
1090
- const detectors = [
1091
- {
1092
- command: "which",
1093
- args: [binaryName],
1094
- validate: (result) => result.exitCode === 0
1095
- },
1096
- {
1097
- command: "where",
1098
- args: [binaryName],
1099
- validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
1100
- },
1101
- // Check common installation paths using shell expansion for $HOME
1102
- {
1103
- command: "sh",
1104
- args: [
1105
- "-c",
1106
- commonPaths.map((p) => `test -f "${p}"`).join(" || ")
1107
- ],
1108
- validate: (result) => result.exitCode === 0
1109
- }
1110
- ];
1111
- for (const detector of detectors) {
1112
- const result = await runCommand2(detector.command, detector.args);
1113
- if (detector.validate(result)) {
1114
- return;
1541
+ // packages/process-runner/src/docker/context.ts
1542
+ import { execSync } from "node:child_process";
1543
+ function detectContext() {
1544
+ try {
1545
+ const output = execSync("colima list --json", {
1546
+ encoding: "utf-8",
1547
+ stdio: ["pipe", "pipe", "ignore"]
1548
+ });
1549
+ const lines = output.trim().split("\n").filter(Boolean);
1550
+ for (const line of lines) {
1551
+ const profile = JSON.parse(line);
1552
+ if (profile.status === "Running" && profile.runtime === "docker") {
1553
+ const name = profile.name ?? profile.profile;
1554
+ if (!name) {
1555
+ continue;
1115
1556
  }
1116
- }
1117
- throw new Error(`${binaryName} CLI binary not found on PATH.`);
1118
- }
1119
- };
1120
- }
1121
-
1122
- // packages/config-mutations/src/mutations/config-mutation.ts
1123
- function merge(options) {
1124
- return {
1125
- kind: "configMerge",
1126
- target: options.target,
1127
- value: options.value,
1128
- format: options.format,
1129
- pruneByPrefix: options.pruneByPrefix,
1130
- label: options.label
1131
- };
1132
- }
1133
- function prune(options) {
1134
- return {
1135
- kind: "configPrune",
1136
- target: options.target,
1137
- shape: options.shape,
1138
- format: options.format,
1139
- onlyIf: options.onlyIf,
1140
- label: options.label
1141
- };
1557
+ return name === "default" ? "colima" : `colima-${name}`;
1558
+ }
1559
+ }
1560
+ } catch {
1561
+ return null;
1562
+ }
1563
+ return null;
1142
1564
  }
1143
- function transform(options) {
1144
- return {
1145
- kind: "configTransform",
1146
- target: options.target,
1147
- format: options.format,
1148
- transform: options.transform,
1149
- label: options.label
1150
- };
1565
+ function buildContextArgs(engine, context) {
1566
+ if (engine === "docker" && context) {
1567
+ return ["--context", context];
1568
+ }
1569
+ return [];
1151
1570
  }
1152
- var configMutation = {
1153
- merge,
1154
- prune,
1155
- transform
1156
- };
1157
1571
 
1158
- // packages/config-mutations/src/mutations/file-mutation.ts
1159
- function ensureDirectory(options) {
1160
- return {
1161
- kind: "ensureDirectory",
1162
- path: options.path,
1163
- label: options.label
1164
- };
1572
+ // packages/process-runner/src/docker/engine.ts
1573
+ import { execSync as execSync2 } from "node:child_process";
1574
+ function detectEngine() {
1575
+ if (isEngineAvailable("docker")) {
1576
+ return "docker";
1577
+ }
1578
+ if (isEngineAvailable("podman")) {
1579
+ return "podman";
1580
+ }
1581
+ throw new Error(
1582
+ "No container engine found. Please install Docker or Podman:\n - Docker Desktop: https://www.docker.com/products/docker-desktop\n - Colima (macOS): brew install colima && colima start\n - Podman: https://podman.io/docs/installation"
1583
+ );
1165
1584
  }
1166
- function remove(options) {
1167
- return {
1168
- kind: "removeFile",
1169
- target: options.target,
1170
- whenEmpty: options.whenEmpty,
1171
- whenContentMatches: options.whenContentMatches,
1172
- label: options.label
1173
- };
1585
+ function isEngineAvailable(engine) {
1586
+ try {
1587
+ execSync2(`${engine} --version`, {
1588
+ stdio: "ignore"
1589
+ });
1590
+ return true;
1591
+ } catch {
1592
+ return false;
1593
+ }
1174
1594
  }
1175
- function removeDirectory(options) {
1176
- return {
1177
- kind: "removeDirectory",
1178
- path: options.path,
1179
- force: options.force,
1180
- label: options.label
1181
- };
1595
+
1596
+ // packages/process-runner/src/docker/docker-runner.ts
1597
+ import * as childProcess from "node:child_process";
1598
+ import { randomBytes as randomBytes2 } from "node:crypto";
1599
+
1600
+ // packages/process-runner/src/docker/args.ts
1601
+ import path13 from "node:path";
1602
+ function buildDockerRunArgs(input) {
1603
+ const args = [input.engine];
1604
+ if (input.engine === "docker" && input.context) {
1605
+ args.push("--context", input.context);
1606
+ }
1607
+ args.push("run");
1608
+ if (input.rm) {
1609
+ args.push("--rm");
1610
+ }
1611
+ if (input.detached) {
1612
+ args.push("-d");
1613
+ }
1614
+ if (input.interactive) {
1615
+ args.push("-i");
1616
+ }
1617
+ if (input.tty) {
1618
+ args.push("-t");
1619
+ }
1620
+ args.push("--name", input.containerName);
1621
+ if (input.cwd !== void 0) {
1622
+ args.push("-w", input.cwd);
1623
+ }
1624
+ for (const [key, value] of Object.entries(input.env ?? {})) {
1625
+ args.push("-e", `${key}=${value}`);
1626
+ }
1627
+ for (const mount of input.mounts) {
1628
+ const volume = `${path13.resolve(mount.source)}:${mount.target}${mount.readonly ? ":ro" : ""}`;
1629
+ args.push("-v", volume);
1630
+ }
1631
+ for (const port of input.ports) {
1632
+ const mapping = `${port.host}:${port.container}${port.protocol === void 0 || port.protocol === "tcp" ? "" : `/${port.protocol}`}`;
1633
+ args.push("-p", mapping);
1634
+ }
1635
+ if (input.network !== void 0) {
1636
+ args.push("--network", input.network);
1637
+ }
1638
+ args.push(...input.extraArgs, input.image, input.command, ...input.args);
1639
+ return args;
1182
1640
  }
1183
- function chmod(options) {
1641
+
1642
+ // packages/process-runner/src/docker/docker-execution-env.ts
1643
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
1644
+ import { mkdtempSync, rmSync } from "node:fs";
1645
+ import { readFile as readFile2 } from "node:fs/promises";
1646
+ import { tmpdir } from "node:os";
1647
+ import path14 from "node:path";
1648
+
1649
+ // packages/process-runner/src/host/host-runner.ts
1650
+ import { spawn as spawnChildProcess } from "node:child_process";
1651
+ function createHostRunner(options = {}) {
1652
+ const detached = options.detached === true;
1184
1653
  return {
1185
- kind: "chmod",
1186
- target: options.target,
1187
- mode: options.mode,
1188
- label: options.label
1654
+ name: "host",
1655
+ exec(spec) {
1656
+ const stdinMode = spec.stdin ?? "ignore";
1657
+ const stdoutMode = spec.stdout ?? "pipe";
1658
+ const stderrMode = spec.stderr ?? "pipe";
1659
+ const stdio = stdinMode === "inherit" && stdoutMode === "inherit" && stderrMode === "inherit" ? "inherit" : [stdinMode, stdoutMode, stderrMode];
1660
+ const child = spawnChildProcess(spec.command, spec.args ?? [], {
1661
+ cwd: spec.cwd,
1662
+ env: spec.env,
1663
+ stdio,
1664
+ ...detached ? { detached: true } : {}
1665
+ });
1666
+ if (detached) {
1667
+ child.unref();
1668
+ }
1669
+ const kill = (signal) => {
1670
+ if (detached && process.platform !== "win32" && child.pid !== void 0) {
1671
+ process.kill(-child.pid, signal);
1672
+ return;
1673
+ }
1674
+ child.kill(signal);
1675
+ };
1676
+ let settled = false;
1677
+ let resolveResult = null;
1678
+ const result = new Promise((resolve2) => {
1679
+ resolveResult = resolve2;
1680
+ });
1681
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
1682
+ kill("SIGTERM");
1683
+ });
1684
+ child.once("close", (code) => {
1685
+ if (settled) return;
1686
+ settled = true;
1687
+ cleanupAbort();
1688
+ resolveResult?.({ exitCode: code ?? 1 });
1689
+ });
1690
+ child.once("error", () => {
1691
+ if (settled) return;
1692
+ settled = true;
1693
+ cleanupAbort();
1694
+ resolveResult?.({ exitCode: 1 });
1695
+ });
1696
+ return {
1697
+ pid: child.pid ?? null,
1698
+ stdin: child.stdin,
1699
+ stdout: child.stdout,
1700
+ stderr: child.stderr,
1701
+ result,
1702
+ kill
1703
+ };
1704
+ }
1189
1705
  };
1190
1706
  }
1191
- function backup(options) {
1192
- return {
1193
- kind: "backup",
1194
- target: options.target,
1195
- label: options.label
1707
+ function bindAbortSignal(signal, onAbort) {
1708
+ if (signal === void 0) {
1709
+ return () => {
1710
+ };
1711
+ }
1712
+ if (signal.aborted) {
1713
+ onAbort();
1714
+ return () => {
1715
+ };
1716
+ }
1717
+ signal.addEventListener("abort", onAbort, { once: true });
1718
+ return () => {
1719
+ signal.removeEventListener("abort", onAbort);
1196
1720
  };
1197
1721
  }
1198
- var fileMutation = {
1199
- ensureDirectory,
1200
- remove,
1201
- removeDirectory,
1202
- chmod,
1203
- backup
1204
- };
1205
-
1206
- // packages/config-mutations/src/execution/apply-mutation.ts
1207
- import Mustache from "mustache";
1208
1722
 
1209
- // packages/config-mutations/src/formats/json.ts
1210
- import * as jsonc from "jsonc-parser";
1211
- function isConfigObject(value) {
1212
- return typeof value === "object" && value !== null && !Array.isArray(value);
1723
+ // packages/process-runner/src/docker/docker-execution-env.ts
1724
+ var containerCommand = ["sh", "-c", "while :; do sleep 3600; done"];
1725
+ var dockerExecutionEnvFactory = {
1726
+ type: "docker",
1727
+ supportsDetach: true,
1728
+ async open(spec) {
1729
+ const runtime = parseDockerRuntime(spec.runtime);
1730
+ const runner = spec.hostRunner ?? createHostRunner();
1731
+ const engine = runtime.engine ?? detectEngine();
1732
+ const context = detectContext();
1733
+ const image = await resolveImage({
1734
+ spec,
1735
+ runtime,
1736
+ runner,
1737
+ engine,
1738
+ context
1739
+ });
1740
+ const containerName = createContainerName();
1741
+ const runArgs = buildDockerRunArgs({
1742
+ engine,
1743
+ context,
1744
+ image,
1745
+ command: containerCommand[0],
1746
+ args: containerCommand.slice(1),
1747
+ cwd: void 0,
1748
+ env: void 0,
1749
+ mounts: runtime.mounts ?? [],
1750
+ ports: [],
1751
+ network: runtime.network,
1752
+ containerName,
1753
+ detached: true,
1754
+ interactive: true,
1755
+ tty: false,
1756
+ rm: false,
1757
+ extraArgs: runtime.extra_args ?? []
1758
+ });
1759
+ const [command, ...args] = runArgs;
1760
+ const id = (await runAndRead(runner, { command, args, stdout: "pipe", stderr: "pipe" })).trim();
1761
+ return createDockerEnv({
1762
+ id,
1763
+ spec,
1764
+ runner,
1765
+ engine,
1766
+ context
1767
+ });
1768
+ },
1769
+ async attach(envId) {
1770
+ const engine = detectEngine();
1771
+ return createDockerEnv({
1772
+ id: envId,
1773
+ spec: createAttachedSpec(),
1774
+ runner: createHostRunner(),
1775
+ engine,
1776
+ context: detectContext()
1777
+ });
1778
+ }
1779
+ };
1780
+ function createDockerEnv(input) {
1781
+ const containerRef = input.id;
1782
+ return {
1783
+ id: containerRef,
1784
+ job: null,
1785
+ async uploadWorkspace() {
1786
+ const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-upload-"));
1787
+ const archivePath = path14.join(tempDir, "workspace.tar");
1788
+ try {
1789
+ const excludeArgs = input.spec.uploadIgnoreFiles.flatMap((ignored) => [
1790
+ "--exclude",
1791
+ ignored
1792
+ ]);
1793
+ const tarArgs = [...excludeArgs, "-cf", archivePath, "-C", input.spec.cwd, "."];
1794
+ await runOrThrow(input.runner, {
1795
+ command: "tar",
1796
+ args: tarArgs,
1797
+ stdout: "pipe",
1798
+ stderr: "pipe"
1799
+ });
1800
+ await runOrThrow(input.runner, {
1801
+ command: input.engine,
1802
+ args: [
1803
+ ...buildContextArgs(input.engine, input.context),
1804
+ "cp",
1805
+ archivePath,
1806
+ `${containerRef}:/tmp/poe-workspace-upload.tar`
1807
+ ],
1808
+ stdout: "pipe",
1809
+ stderr: "pipe"
1810
+ });
1811
+ await runOrThrow(input.runner, {
1812
+ command: input.engine,
1813
+ args: [
1814
+ ...buildContextArgs(input.engine, input.context),
1815
+ "exec",
1816
+ containerRef,
1817
+ "sh",
1818
+ "-c",
1819
+ `mkdir -p ${shellQuote(input.spec.cwd)} && tar -xf /tmp/poe-workspace-upload.tar -C ${shellQuote(input.spec.cwd)}`
1820
+ ],
1821
+ stdout: "pipe",
1822
+ stderr: "pipe"
1823
+ });
1824
+ return { files: 0, bytes: 0, skipped: [] };
1825
+ } finally {
1826
+ rmSync(tempDir, { recursive: true, force: true });
1827
+ }
1828
+ },
1829
+ async downloadWorkspace(opts) {
1830
+ const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-download-"));
1831
+ const archivePath = path14.join(tempDir, "workspace.tar");
1832
+ try {
1833
+ await runOrThrow(input.runner, {
1834
+ command: input.engine,
1835
+ args: [
1836
+ ...buildContextArgs(input.engine, input.context),
1837
+ "exec",
1838
+ containerRef,
1839
+ "sh",
1840
+ "-c",
1841
+ `tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote(input.spec.cwd)} .`
1842
+ ],
1843
+ stdout: "pipe",
1844
+ stderr: "pipe"
1845
+ });
1846
+ await runOrThrow(input.runner, {
1847
+ command: input.engine,
1848
+ args: [
1849
+ ...buildContextArgs(input.engine, input.context),
1850
+ "cp",
1851
+ `${containerRef}:/tmp/poe-workspace-download.tar`,
1852
+ archivePath
1853
+ ],
1854
+ stdout: "pipe",
1855
+ stderr: "pipe"
1856
+ });
1857
+ const extractMode = opts.conflictPolicy === "refuse" ? "-xkf" : "-xf";
1858
+ await runOrThrow(input.runner, {
1859
+ command: "tar",
1860
+ args: [extractMode, archivePath, "-C", input.spec.cwd],
1861
+ stdout: "pipe",
1862
+ stderr: "pipe"
1863
+ });
1864
+ return { files: 0, bytes: 0, conflicts: [] };
1865
+ } finally {
1866
+ rmSync(tempDir, { recursive: true, force: true });
1867
+ }
1868
+ },
1869
+ exec(spec) {
1870
+ return input.runner.exec({
1871
+ command: input.engine,
1872
+ args: [
1873
+ ...buildContextArgs(input.engine, input.context),
1874
+ "exec",
1875
+ ...spec.stdin === "pipe" || spec.stdin === "inherit" ? ["-i"] : [],
1876
+ ...spec.tty === true ? ["-t"] : [],
1877
+ ...spec.cwd !== void 0 ? ["-w", spec.cwd] : [],
1878
+ ...buildEnvArgs(spec.env),
1879
+ containerRef,
1880
+ spec.command,
1881
+ ...spec.args ?? []
1882
+ ],
1883
+ stdin: spec.stdin,
1884
+ stdout: spec.stdout,
1885
+ stderr: spec.stderr,
1886
+ tty: spec.tty
1887
+ });
1888
+ },
1889
+ async detach() {
1890
+ return createContainerJob(containerRef, input.runner, input.engine, input.context);
1891
+ },
1892
+ shell() {
1893
+ const shellSpec = input.spec.shellSpec;
1894
+ return this.exec({
1895
+ command: shellSpec?.command ?? input.spec.env.SHELL ?? "sh",
1896
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
1897
+ cwd: input.spec.cwd,
1898
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env,
1899
+ stdin: "inherit",
1900
+ stdout: "inherit",
1901
+ stderr: "inherit",
1902
+ tty: true
1903
+ });
1904
+ },
1905
+ async close() {
1906
+ await runOrThrow(input.runner, {
1907
+ command: input.engine,
1908
+ args: [...buildContextArgs(input.engine, input.context), "rm", "-f", containerRef],
1909
+ stdout: "pipe",
1910
+ stderr: "pipe"
1911
+ });
1912
+ }
1913
+ };
1213
1914
  }
1214
- function parse3(content) {
1215
- if (!content || content.trim() === "") {
1216
- return {};
1915
+ async function resolveImage(input) {
1916
+ if (input.runtime.image !== void 0) {
1917
+ return input.runtime.image;
1217
1918
  }
1218
- const errors = [];
1219
- const parsed = jsonc.parse(content, errors, {
1220
- allowTrailingComma: true,
1221
- disallowComments: false
1919
+ const dockerfilePath = path14.resolve(
1920
+ input.spec.cwd,
1921
+ input.runtime.dockerfile ?? path14.join(".poe-code", "Dockerfile")
1922
+ );
1923
+ const buildContext = path14.resolve(input.spec.cwd, input.runtime.build_context ?? ".");
1924
+ const dockerfileBytes = await readFile2(dockerfilePath);
1925
+ const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
1926
+ const cached2 = await input.spec.state?.templates.get("docker", hash);
1927
+ if (cached2?.image !== void 0) {
1928
+ return cached2.image;
1929
+ }
1930
+ const image = `poe-code/local:${hash}`;
1931
+ await buildImage({
1932
+ runner: input.runner,
1933
+ engine: input.engine,
1934
+ context: input.context,
1935
+ image,
1936
+ dockerfilePath,
1937
+ buildContext,
1938
+ buildArgs: input.runtime.build_args ?? {}
1222
1939
  });
1223
- if (errors.length > 0) {
1224
- throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
1940
+ await input.spec.state?.templates.put("docker", {
1941
+ hash,
1942
+ image,
1943
+ runtime_type: "docker",
1944
+ dockerfile_path: dockerfilePath,
1945
+ built_at: (/* @__PURE__ */ new Date()).toISOString()
1946
+ });
1947
+ return image;
1948
+ }
1949
+ function hashDockerTemplate(dockerfileBytes, buildArgs) {
1950
+ const hash = createHash2("sha256");
1951
+ hash.update(dockerfileBytes);
1952
+ hash.update("\0");
1953
+ for (const [key, value] of sortedBuildArgs(buildArgs)) {
1954
+ hash.update(key);
1955
+ hash.update("=");
1956
+ hash.update(value);
1957
+ hash.update("\0");
1958
+ }
1959
+ return hash.digest("hex");
1960
+ }
1961
+ async function buildImage(input) {
1962
+ await runOrThrow(input.runner, {
1963
+ command: input.engine,
1964
+ args: [
1965
+ ...buildContextArgs(input.engine, input.context),
1966
+ "build",
1967
+ "--tag",
1968
+ input.image,
1969
+ "-f",
1970
+ input.dockerfilePath,
1971
+ ...sortedBuildArgs(input.buildArgs).flatMap(([key, value]) => [
1972
+ "--build-arg",
1973
+ `${key}=${value}`
1974
+ ]),
1975
+ input.buildContext
1976
+ ],
1977
+ stdout: "pipe",
1978
+ stderr: "pipe"
1979
+ });
1980
+ }
1981
+ function parseDockerRuntime(runtime) {
1982
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
1983
+ throw new Error("docker runtime must be an object");
1225
1984
  }
1226
- if (parsed === null || parsed === void 0) {
1227
- return {};
1985
+ const record = runtime;
1986
+ if (record.type !== "docker") {
1987
+ throw new Error('docker runtime type must be "docker"');
1228
1988
  }
1229
- if (!isConfigObject(parsed)) {
1230
- throw new Error("Expected JSON object.");
1989
+ return record;
1990
+ }
1991
+ async function runAndRead(runner, spec) {
1992
+ const handle = runner.exec(spec);
1993
+ const stdout = readStream(handle.stdout);
1994
+ const stderr = readStream(handle.stderr);
1995
+ const result = await handle.result;
1996
+ const output = await stdout;
1997
+ if (result.exitCode !== 0) {
1998
+ const errorOutput = await stderr;
1999
+ throw new Error(
2000
+ `Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}${errorOutput ? `
2001
+ ${errorOutput}` : ""}`
2002
+ );
1231
2003
  }
1232
- return parsed;
2004
+ return output;
1233
2005
  }
1234
- function serialize(obj) {
1235
- return `${JSON.stringify(obj, null, 2)}
1236
- `;
2006
+ async function runOrThrow(runner, spec) {
2007
+ await runAndRead(runner, spec);
1237
2008
  }
1238
- function merge2(base, patch) {
1239
- const result = { ...base };
1240
- for (const [key, value] of Object.entries(patch)) {
1241
- if (value === void 0) {
1242
- continue;
1243
- }
1244
- const existing = result[key];
1245
- if (isConfigObject(existing) && isConfigObject(value)) {
1246
- result[key] = merge2(existing, value);
1247
- continue;
1248
- }
1249
- result[key] = value;
2009
+ async function readStream(stream) {
2010
+ if (stream === null) {
2011
+ return "";
1250
2012
  }
1251
- return result;
2013
+ stream.setEncoding("utf8");
2014
+ const chunks = [];
2015
+ for await (const chunk of stream) {
2016
+ chunks.push(String(chunk));
2017
+ }
2018
+ return chunks.join("");
1252
2019
  }
1253
- function prune2(obj, shape) {
1254
- let changed = false;
1255
- const result = { ...obj };
1256
- for (const [key, pattern] of Object.entries(shape)) {
1257
- if (!(key in result)) {
1258
- continue;
2020
+ function sortedBuildArgs(buildArgs) {
2021
+ return Object.entries(buildArgs).sort(([left], [right]) => left.localeCompare(right));
2022
+ }
2023
+ function buildEnvArgs(env) {
2024
+ if (env === void 0) {
2025
+ return [];
2026
+ }
2027
+ return Object.entries(env).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
2028
+ }
2029
+ function createContainerName() {
2030
+ return `poe-env-${randomBytes3(6).toString("hex")}`;
2031
+ }
2032
+ async function createContainerJob(containerId, runner, engine, context) {
2033
+ return {
2034
+ id: containerId,
2035
+ envId: containerId,
2036
+ tool: "docker",
2037
+ argv: ["attach", containerId],
2038
+ async status() {
2039
+ const handle = runner.exec({
2040
+ command: engine,
2041
+ args: [
2042
+ ...buildContextArgs(engine, context),
2043
+ "inspect",
2044
+ "-f",
2045
+ "{{.State.Status}}",
2046
+ containerId
2047
+ ],
2048
+ stdout: "pipe",
2049
+ stderr: "pipe"
2050
+ });
2051
+ const stdout = await readStream(handle.stdout);
2052
+ const result = await handle.result;
2053
+ if (result.exitCode !== 0) {
2054
+ return "lost";
2055
+ }
2056
+ return stdout.trim() === "running" ? "running" : "exited";
2057
+ },
2058
+ async *stream() {
2059
+ },
2060
+ async wait() {
2061
+ const handle = runner.exec({
2062
+ command: engine,
2063
+ args: [...buildContextArgs(engine, context), "wait", containerId],
2064
+ stdout: "pipe",
2065
+ stderr: "pipe"
2066
+ });
2067
+ const stdout = await readStream(handle.stdout);
2068
+ const result = await handle.result;
2069
+ return { exitCode: Number.parseInt(stdout.trim(), 10) || result.exitCode };
2070
+ },
2071
+ async kill(signal) {
2072
+ const args = signal === void 0 || signal === "SIGTERM" ? ["stop", containerId] : ["kill", ...signal === "SIGKILL" ? [] : [`--signal=${signal}`], containerId];
2073
+ await runOrThrow(runner, {
2074
+ command: engine,
2075
+ args: [...buildContextArgs(engine, context), ...args],
2076
+ stdout: "pipe",
2077
+ stderr: "pipe"
2078
+ });
1259
2079
  }
1260
- const current = result[key];
1261
- if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
1262
- delete result[key];
1263
- changed = true;
1264
- continue;
2080
+ };
2081
+ }
2082
+ function createAttachedSpec() {
2083
+ return {
2084
+ cwd: "/workspace",
2085
+ runtime: {
2086
+ type: "docker",
2087
+ image: "attached",
2088
+ build_args: {},
2089
+ mounts: []
2090
+ },
2091
+ env: {},
2092
+ uploadIgnoreFiles: [],
2093
+ jobLabel: {
2094
+ tool: "docker",
2095
+ argv: []
1265
2096
  }
1266
- if (isConfigObject(pattern) && isConfigObject(current)) {
1267
- const { changed: childChanged, result: childResult } = prune2(
1268
- current,
1269
- pattern
1270
- );
1271
- if (childChanged) {
1272
- changed = true;
1273
- }
1274
- if (Object.keys(childResult).length === 0) {
1275
- delete result[key];
1276
- } else {
1277
- result[key] = childResult;
2097
+ };
2098
+ }
2099
+ function shellQuote(value) {
2100
+ return `'${value.replaceAll("'", "'\\''")}'`;
2101
+ }
2102
+
2103
+ // packages/process-runner/src/host/host-execution-env.ts
2104
+ var hostExecutionEnvFactory = {
2105
+ type: "host",
2106
+ supportsDetach: false,
2107
+ async open(openSpec) {
2108
+ return {
2109
+ id: "host",
2110
+ job: null,
2111
+ async uploadWorkspace() {
2112
+ return {
2113
+ files: 0,
2114
+ bytes: 0,
2115
+ skipped: []
2116
+ };
2117
+ },
2118
+ async downloadWorkspace() {
2119
+ return {
2120
+ files: 0,
2121
+ bytes: 0,
2122
+ conflicts: []
2123
+ };
2124
+ },
2125
+ exec(spec) {
2126
+ return createHostRunner().exec(spec);
2127
+ },
2128
+ async detach() {
2129
+ throw new Error("host runtime does not support detach because host has no addressable env");
2130
+ },
2131
+ shell() {
2132
+ const shellSpec = openSpec.shellSpec;
2133
+ return createHostRunner().exec({
2134
+ command: shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
2135
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
2136
+ cwd: openSpec.cwd,
2137
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : openSpec.env,
2138
+ stdin: "inherit",
2139
+ stdout: "inherit",
2140
+ stderr: "inherit",
2141
+ tty: true
2142
+ });
2143
+ },
2144
+ async close() {
1278
2145
  }
1279
- continue;
1280
- }
1281
- delete result[key];
1282
- changed = true;
2146
+ };
2147
+ },
2148
+ async attach() {
2149
+ throw new Error("host runtime does not support reattach");
1283
2150
  }
1284
- return { changed, result };
1285
- }
1286
- var jsonFormat = {
1287
- parse: parse3,
1288
- serialize,
1289
- merge: merge2,
1290
- prune: prune2
1291
2151
  };
1292
2152
 
1293
- // packages/config-mutations/src/formats/toml.ts
1294
- import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
1295
- function isConfigObject2(value) {
1296
- return typeof value === "object" && value !== null && !Array.isArray(value);
2153
+ // packages/process-runner/src/testing/mock-runner.ts
2154
+ import { Readable, Writable } from "node:stream";
2155
+
2156
+ // packages/agent-spawn/src/register-factories.ts
2157
+ registerExecutionEnvFactory(hostExecutionEnvFactory);
2158
+ registerExecutionEnvFactory(dockerExecutionEnvFactory);
2159
+ if (process.env.VITEST === "true") {
2160
+ registerExecutionEnvFactory(createTestHostExecutionEnvFactory());
1297
2161
  }
1298
- function parse4(content) {
1299
- if (!content || content.trim() === "") {
1300
- return {};
1301
- }
1302
- const parsed = parseToml(content);
1303
- if (!isConfigObject2(parsed)) {
1304
- throw new Error("Expected TOML document to be a table.");
2162
+ function createTestHostExecutionEnvFactory() {
2163
+ return {
2164
+ type: "host",
2165
+ supportsDetach: false,
2166
+ open: ((openSpec) => {
2167
+ return {
2168
+ id: "host",
2169
+ job: null,
2170
+ async uploadWorkspace() {
2171
+ return { files: 0, bytes: 0, skipped: [] };
2172
+ },
2173
+ async downloadWorkspace() {
2174
+ return { files: 0, bytes: 0, conflicts: [] };
2175
+ },
2176
+ exec(spec) {
2177
+ return runHost(spawnChildProcess2, spec);
2178
+ },
2179
+ async detach() {
2180
+ throw new Error(
2181
+ "host runtime does not support detach because host has no addressable env"
2182
+ );
2183
+ },
2184
+ shell() {
2185
+ return runHost(spawnChildProcess2, {
2186
+ command: openSpec.shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
2187
+ args: openSpec.shellSpec?.args,
2188
+ cwd: openSpec.cwd,
2189
+ env: openSpec.shellSpec && "env" in openSpec.shellSpec ? openSpec.shellSpec.env : openSpec.env,
2190
+ stdin: "inherit",
2191
+ stdout: "inherit",
2192
+ stderr: "inherit",
2193
+ tty: true
2194
+ });
2195
+ },
2196
+ async close() {
2197
+ }
2198
+ };
2199
+ }),
2200
+ async attach() {
2201
+ throw new Error("host runtime does not support reattach");
2202
+ }
2203
+ };
2204
+ }
2205
+ function runHost(spawnProcess, spec) {
2206
+ const stdin = spec.stdin ?? "ignore";
2207
+ const stdout = spec.stdout ?? "pipe";
2208
+ const stderr = spec.stderr ?? "pipe";
2209
+ const stdio = stdin === "inherit" && stdout === "inherit" && stderr === "inherit" ? "inherit" : [stdin, stdout, stderr];
2210
+ const child = spawnProcess(spec.command, spec.args ?? [], {
2211
+ cwd: spec.cwd,
2212
+ env: spec.env,
2213
+ stdio
2214
+ });
2215
+ const result = new Promise((resolve2) => {
2216
+ child.once("close", (code) => {
2217
+ resolve2({ exitCode: code ?? 1 });
2218
+ });
2219
+ child.once("error", () => {
2220
+ resolve2({ exitCode: 1 });
2221
+ });
2222
+ });
2223
+ const kill = (signal) => {
2224
+ child.kill(signal);
2225
+ };
2226
+ if (spec.signal?.aborted) {
2227
+ kill("SIGTERM");
2228
+ } else {
2229
+ spec.signal?.addEventListener("abort", () => kill("SIGTERM"), { once: true });
1305
2230
  }
1306
- return parsed;
1307
- }
1308
- function serialize2(obj) {
1309
- const serialized = stringifyToml(obj);
1310
- return serialized.endsWith("\n") ? serialized : `${serialized}
1311
- `;
2231
+ return {
2232
+ pid: child.pid ?? null,
2233
+ stdin: child.stdin,
2234
+ stdout: child.stdout,
2235
+ stderr: child.stderr,
2236
+ result,
2237
+ kill
2238
+ };
1312
2239
  }
1313
- function merge3(base, patch) {
1314
- const result = { ...base };
1315
- for (const [key, value] of Object.entries(patch)) {
1316
- if (value === void 0) {
1317
- continue;
1318
- }
1319
- const existing = result[key];
1320
- if (isConfigObject2(existing) && isConfigObject2(value)) {
1321
- result[key] = merge3(existing, value);
1322
- continue;
1323
- }
1324
- result[key] = value;
2240
+
2241
+ // packages/agent-spawn/src/run-command.ts
2242
+ import { spawn as spawn2 } from "node:child_process";
2243
+
2244
+ // packages/agent-spawn/src/types.ts
2245
+ function resolveModeConfig(modeConfig) {
2246
+ if (Array.isArray(modeConfig)) {
2247
+ return { args: modeConfig };
1325
2248
  }
1326
- return result;
2249
+ return {
2250
+ args: modeConfig.args ?? [],
2251
+ env: modeConfig.env && Object.keys(modeConfig.env).length > 0 ? modeConfig.env : void 0
2252
+ };
1327
2253
  }
1328
- function prune3(obj, shape) {
1329
- let changed = false;
1330
- const result = { ...obj };
1331
- for (const [key, pattern] of Object.entries(shape)) {
1332
- if (!(key in result)) {
1333
- continue;
2254
+
2255
+ // packages/agent-spawn/src/configs/mcp.ts
2256
+ function toJsonMcpServers(servers) {
2257
+ const out = {};
2258
+ for (const [name, server] of Object.entries(servers)) {
2259
+ const mapped = { command: server.command };
2260
+ if (server.args && server.args.length > 0) {
2261
+ mapped.args = server.args;
1334
2262
  }
1335
- const current = result[key];
1336
- if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
1337
- delete result[key];
1338
- changed = true;
1339
- continue;
2263
+ if (server.env && Object.keys(server.env).length > 0) {
2264
+ mapped.env = server.env;
1340
2265
  }
1341
- if (isConfigObject2(pattern) && isConfigObject2(current)) {
1342
- const { changed: childChanged, result: childResult } = prune3(
1343
- current,
1344
- pattern
1345
- );
1346
- if (childChanged) {
1347
- changed = true;
1348
- }
1349
- if (Object.keys(childResult).length === 0) {
1350
- delete result[key];
1351
- } else {
1352
- result[key] = childResult;
1353
- }
1354
- continue;
2266
+ if (server.timeout !== void 0) {
2267
+ mapped.timeout = server.timeout;
1355
2268
  }
1356
- delete result[key];
1357
- changed = true;
2269
+ out[name] = mapped;
1358
2270
  }
1359
- return { changed, result };
2271
+ return out;
1360
2272
  }
1361
- var tomlFormat = {
1362
- parse: parse4,
1363
- serialize: serialize2,
1364
- merge: merge3,
1365
- prune: prune3
1366
- };
1367
-
1368
- // packages/config-mutations/src/formats/yaml.ts
1369
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1370
- function isConfigObject3(value) {
1371
- return typeof value === "object" && value !== null && !Array.isArray(value);
2273
+ function toTomlString(value) {
2274
+ return JSON.stringify(value);
1372
2275
  }
1373
- function parse5(content) {
1374
- if (!content || content.trim() === "") {
1375
- return {};
1376
- }
1377
- const parsed = parseYaml(content);
1378
- if (parsed === null || parsed === void 0) {
1379
- return {};
1380
- }
1381
- if (!isConfigObject3(parsed)) {
1382
- throw new Error("Expected YAML object.");
2276
+ function toTomlArray(values) {
2277
+ const serialized = values.map((value) => toTomlString(value));
2278
+ return `[${serialized.join(", ")}]`;
2279
+ }
2280
+ function toTomlInlineTable(values) {
2281
+ const parts = [];
2282
+ for (const [key, value] of Object.entries(values)) {
2283
+ parts.push(`${JSON.stringify(key)}=${toTomlString(value)}`);
1383
2284
  }
1384
- return parsed;
2285
+ return `{${parts.join(", ")}}`;
1385
2286
  }
1386
- function serialize3(obj) {
1387
- const serialized = stringifyYaml(obj);
1388
- return serialized.endsWith("\n") ? serialized : `${serialized}
1389
- `;
2287
+ function serializeJsonMcpArgs(servers) {
2288
+ return ["--mcp-config", JSON.stringify({ mcpServers: toJsonMcpServers(servers) })];
1390
2289
  }
1391
- function merge4(base, patch) {
1392
- const result = { ...base };
1393
- for (const [key, value] of Object.entries(patch)) {
1394
- if (value === void 0) {
1395
- continue;
1396
- }
1397
- const existing = result[key];
1398
- if (isConfigObject3(existing) && isConfigObject3(value)) {
1399
- result[key] = merge4(existing, value);
1400
- continue;
2290
+ function serializeOpenCodeMcpEnv(servers) {
2291
+ const mcp = {};
2292
+ for (const [name, server] of Object.entries(servers)) {
2293
+ const entry = { type: "local", command: [server.command, ...server.args ?? []] };
2294
+ if (server.env && Object.keys(server.env).length > 0) {
2295
+ entry.environment = server.env;
1401
2296
  }
1402
- result[key] = value;
2297
+ mcp[name] = entry;
1403
2298
  }
1404
- return result;
2299
+ return { OPENCODE_CONFIG_CONTENT: JSON.stringify({ mcp }) };
1405
2300
  }
1406
- function prune4(obj, shape) {
1407
- let changed = false;
1408
- const result = { ...obj };
1409
- for (const [key, pattern] of Object.entries(shape)) {
1410
- if (!(key in result)) {
1411
- continue;
2301
+ function serializeCodexMcpArgs(servers) {
2302
+ const args = [];
2303
+ for (const [name, server] of Object.entries(servers)) {
2304
+ const prefix = `mcp_servers.${name}`;
2305
+ args.push("-c", `${prefix}.command=${toTomlString(server.command)}`);
2306
+ if (server.args && server.args.length > 0) {
2307
+ args.push("-c", `${prefix}.args=${toTomlArray(server.args)}`);
1412
2308
  }
1413
- const current = result[key];
1414
- if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
1415
- delete result[key];
1416
- changed = true;
1417
- continue;
2309
+ if (server.env && Object.keys(server.env).length > 0) {
2310
+ args.push("-c", `${prefix}.env=${toTomlInlineTable(server.env)}`);
1418
2311
  }
1419
- if (isConfigObject3(pattern) && isConfigObject3(current)) {
1420
- const { changed: childChanged, result: childResult } = prune4(current, pattern);
1421
- if (childChanged) {
1422
- changed = true;
1423
- }
1424
- if (Object.keys(childResult).length === 0) {
1425
- delete result[key];
1426
- } else {
1427
- result[key] = childResult;
1428
- }
1429
- continue;
2312
+ if (server.timeout !== void 0) {
2313
+ args.push("-c", `${prefix}.timeout=${server.timeout}`);
1430
2314
  }
1431
- delete result[key];
1432
- changed = true;
1433
2315
  }
1434
- return { changed, result };
2316
+ return args;
1435
2317
  }
1436
- var yamlFormat = {
1437
- parse: parse5,
1438
- serialize: serialize3,
1439
- merge: merge4,
1440
- prune: prune4
2318
+ function serializeGooseMcpArgs(servers) {
2319
+ return Object.values(servers).flatMap((server) => [
2320
+ "--with-extension",
2321
+ [server.command, ...server.args ?? []].join(" ")
2322
+ ]);
2323
+ }
2324
+
2325
+ // packages/agent-spawn/src/configs/claude-code.ts
2326
+ var claudeCodeSpawnConfig = {
2327
+ kind: "cli",
2328
+ agentId: "claude-code",
2329
+ // ACP adapter support: yes (adapter: "claude")
2330
+ adapter: "claude",
2331
+ promptFlag: "-p",
2332
+ modelFlag: "--model",
2333
+ modelStripProviderPrefix: true,
2334
+ modelTransform: (model) => model.replaceAll(".", "-"),
2335
+ defaultArgs: [
2336
+ "--output-format",
2337
+ "stream-json",
2338
+ "--verbose"
2339
+ ],
2340
+ mcpArgs: serializeJsonMcpArgs,
2341
+ modes: {
2342
+ yolo: ["--dangerously-skip-permissions"],
2343
+ edit: ["--permission-mode", "acceptEdits", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,NotebookEdit"],
2344
+ read: ["--permission-mode", "plan"]
2345
+ },
2346
+ stdinMode: {
2347
+ omitPrompt: true,
2348
+ extraArgs: ["--input-format", "text"]
2349
+ },
2350
+ interactive: {
2351
+ defaultArgs: []
2352
+ },
2353
+ resumeCommand: (threadId) => ["--resume", threadId]
2354
+ };
2355
+
2356
+ // packages/agent-spawn/src/configs/codex.ts
2357
+ var codexSpawnConfig = {
2358
+ kind: "cli",
2359
+ agentId: "codex",
2360
+ // ACP adapter support: yes (adapter: "codex")
2361
+ adapter: "codex",
2362
+ promptFlag: "exec",
2363
+ modelFlag: "--model",
2364
+ modelStripProviderPrefix: true,
2365
+ defaultArgs: ["--skip-git-repo-check", "--json"],
2366
+ mcpArgs: serializeCodexMcpArgs,
2367
+ mcpArgsBeforeCommand: true,
2368
+ modes: {
2369
+ yolo: ["-s", "danger-full-access"],
2370
+ edit: ["-s", "workspace-write"],
2371
+ read: ["-s", "read-only"]
2372
+ },
2373
+ stdinMode: {
2374
+ omitPrompt: true,
2375
+ extraArgs: ["-"]
2376
+ },
2377
+ interactive: {
2378
+ defaultArgs: ["-a", "never"]
2379
+ },
2380
+ resumeCommand: (threadId, cwd) => ["resume", "-C", cwd, threadId]
2381
+ };
2382
+
2383
+ // packages/agent-spawn/src/configs/opencode.ts
2384
+ var openCodeSpawnConfig = {
2385
+ kind: "cli",
2386
+ agentId: "opencode",
2387
+ // ACP adapter support: yes (adapter: "opencode").
2388
+ // OpenCode's `--format json` emits NDJSON events with `{ type, sessionID, part }`
2389
+ // (no `{ event, ... }` field), so it needs the OpenCode adapter (not "native").
2390
+ adapter: "opencode",
2391
+ promptFlag: "run",
2392
+ modelFlag: "--model",
2393
+ modelStripProviderPrefix: false,
2394
+ modelTransform: (model) => {
2395
+ return model.startsWith("poe/") ? model : `poe/${model}`;
2396
+ },
2397
+ defaultArgs: ["--format", "json"],
2398
+ modes: {
2399
+ yolo: [],
2400
+ edit: [],
2401
+ read: ["--agent", "plan"]
2402
+ },
2403
+ interactive: {
2404
+ defaultArgs: [],
2405
+ promptFlag: "--prompt"
2406
+ },
2407
+ resumeCommand: (threadId, cwd) => [cwd, "--session", threadId],
2408
+ mcpEnv: serializeOpenCodeMcpEnv
2409
+ };
2410
+ var openCodeAcpSpawnConfig = {
2411
+ kind: "acp",
2412
+ agentId: "opencode",
2413
+ acpArgs: ["acp"],
2414
+ skipAuth: true,
2415
+ mcpEnv: serializeOpenCodeMcpEnv
1441
2416
  };
1442
2417
 
1443
- // packages/config-mutations/src/formats/index.ts
1444
- var formatRegistry = {
1445
- json: jsonFormat,
1446
- toml: tomlFormat,
1447
- yaml: yamlFormat
2418
+ // packages/agent-spawn/src/configs/kimi.ts
2419
+ var kimiSpawnConfig = {
2420
+ kind: "cli",
2421
+ agentId: "kimi",
2422
+ // ACP adapter support: yes (adapter: "kimi").
2423
+ // Kimi's `--output-format stream-json` emits OpenAI-style `{ role, content }` JSON
2424
+ // (no `{ event, ... }` field), so it needs the Kimi adapter (not "native").
2425
+ adapter: "kimi",
2426
+ promptFlag: "-p",
2427
+ modelStripProviderPrefix: true,
2428
+ defaultArgs: ["--print", "--output-format", "stream-json"],
2429
+ mcpArgs: serializeJsonMcpArgs,
2430
+ modes: {
2431
+ yolo: ["--yolo"],
2432
+ edit: [],
2433
+ read: []
2434
+ },
2435
+ stdinMode: {
2436
+ omitPrompt: true,
2437
+ extraArgs: ["--input-format", "stream-json"]
2438
+ },
2439
+ interactive: {
2440
+ defaultArgs: [],
2441
+ promptFlag: "-p"
2442
+ },
2443
+ resumeCommand: (threadId, cwd) => ["--session", threadId, "--work-dir", cwd]
1448
2444
  };
1449
- var extensionMap = {
1450
- ".json": "json",
1451
- ".toml": "toml",
1452
- ".yaml": "yaml",
1453
- ".yml": "yaml"
2445
+ var kimiAcpSpawnConfig = {
2446
+ kind: "acp",
2447
+ agentId: "kimi",
2448
+ acpArgs: ["acp"]
1454
2449
  };
1455
- function getConfigFormat(pathOrFormat) {
1456
- if (pathOrFormat in formatRegistry) {
1457
- return formatRegistry[pathOrFormat];
1458
- }
1459
- const ext = getExtension(pathOrFormat);
1460
- const formatName = extensionMap[ext];
1461
- if (!formatName) {
1462
- throw new Error(
1463
- `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
1464
- );
1465
- }
1466
- return formatRegistry[formatName];
1467
- }
1468
- function detectFormat(path5) {
1469
- const ext = getExtension(path5);
1470
- return extensionMap[ext];
1471
- }
1472
- function getExtension(path5) {
1473
- const lastDot = path5.lastIndexOf(".");
1474
- if (lastDot === -1) {
1475
- return "";
1476
- }
1477
- return path5.slice(lastDot).toLowerCase();
1478
- }
1479
2450
 
1480
- // packages/config-mutations/src/execution/path-utils.ts
1481
- import path4 from "node:path";
1482
- function expandHome(targetPath, homeDir) {
1483
- if (!targetPath?.startsWith("~")) {
1484
- return targetPath;
1485
- }
1486
- if (targetPath.startsWith("~./")) {
1487
- targetPath = `~/.${targetPath.slice(3)}`;
1488
- }
1489
- let remainder = targetPath.slice(1);
1490
- if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1491
- remainder = remainder.slice(1);
1492
- } else if (remainder.startsWith(".")) {
1493
- remainder = remainder.slice(1);
1494
- if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1495
- remainder = remainder.slice(1);
1496
- }
1497
- }
1498
- return remainder.length === 0 ? homeDir : path4.join(homeDir, remainder);
1499
- }
1500
- function validateHomePath(targetPath) {
1501
- if (typeof targetPath !== "string" || targetPath.length === 0) {
1502
- throw new Error("Target path must be a non-empty string.");
1503
- }
1504
- if (!targetPath.startsWith("~")) {
1505
- throw new Error(
1506
- `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
1507
- );
1508
- }
1509
- }
1510
- function resolvePath(rawPath, homeDir, pathMapper) {
1511
- validateHomePath(rawPath);
1512
- const expanded = expandHome(rawPath, homeDir);
1513
- if (!pathMapper) {
1514
- return expanded;
1515
- }
1516
- const rawDirectory = path4.dirname(expanded);
1517
- const mappedDirectory = pathMapper.mapTargetDirectory({
1518
- targetDirectory: rawDirectory
1519
- });
1520
- const filename = path4.basename(expanded);
1521
- return filename.length === 0 ? mappedDirectory : path4.join(mappedDirectory, filename);
1522
- }
2451
+ // packages/agent-spawn/src/configs/goose.ts
2452
+ var gooseFileSecretsEnv = { GOOSE_DISABLE_KEYRING: "1" };
2453
+ var gooseSpawnConfig = {
2454
+ kind: "cli",
2455
+ agentId: "goose",
2456
+ adapter: "native",
2457
+ promptFlag: "--text",
2458
+ modelFlag: "--model",
2459
+ modelStripProviderPrefix: false,
2460
+ defaultArgs: ["run", "--output-format", "stream-json"],
2461
+ defaultArgsPosition: "beforePrompt",
2462
+ mcpArgs: serializeGooseMcpArgs,
2463
+ mcpArgsPosition: "beforePrompt",
2464
+ modes: {
2465
+ yolo: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "auto" } },
2466
+ edit: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "smart_approve" } },
2467
+ read: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "chat" } }
2468
+ },
2469
+ stdinMode: {
2470
+ omitPrompt: true,
2471
+ extraArgs: ["--instructions", "-"]
2472
+ },
2473
+ interactive: {
2474
+ defaultArgs: ["session"],
2475
+ defaultArgsPosition: "beforePrompt"
2476
+ },
2477
+ resumeCommand: () => ["run", "--resume", "--text", "continue"]
2478
+ };
2479
+ var gooseAcpSpawnConfig = {
2480
+ kind: "acp",
2481
+ agentId: "goose",
2482
+ acpArgs: ["acp"],
2483
+ env: gooseFileSecretsEnv,
2484
+ skipAuth: true
2485
+ };
1523
2486
 
1524
- // packages/config-mutations/src/fs-utils.ts
1525
- function isNotFound(error2) {
1526
- return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
2487
+ // packages/agent-spawn/src/configs/index.ts
2488
+ var allSpawnConfigs = [
2489
+ claudeCodeSpawnConfig,
2490
+ codexSpawnConfig,
2491
+ openCodeSpawnConfig,
2492
+ kimiSpawnConfig,
2493
+ gooseSpawnConfig
2494
+ ];
2495
+ var lookup2 = /* @__PURE__ */ new Map();
2496
+ for (const config of allSpawnConfigs) {
2497
+ lookup2.set(config.agentId, config);
1527
2498
  }
1528
- async function readFileIfExists(fs, target) {
1529
- try {
1530
- return await fs.readFile(target, "utf8");
1531
- } catch (error2) {
1532
- if (isNotFound(error2)) {
1533
- return null;
1534
- }
1535
- throw error2;
2499
+ var acpLookup = /* @__PURE__ */ new Map();
2500
+ acpLookup.set(openCodeAcpSpawnConfig.agentId, openCodeAcpSpawnConfig);
2501
+ acpLookup.set(kimiAcpSpawnConfig.agentId, kimiAcpSpawnConfig);
2502
+ acpLookup.set(gooseAcpSpawnConfig.agentId, gooseAcpSpawnConfig);
2503
+ function getSpawnConfig(input) {
2504
+ const resolvedId = resolveAgentId(input);
2505
+ if (!resolvedId) {
2506
+ return void 0;
1536
2507
  }
2508
+ return lookup2.get(resolvedId);
1537
2509
  }
1538
- async function pathExists(fs, target) {
1539
- try {
1540
- await fs.stat(target);
1541
- return true;
1542
- } catch (error2) {
1543
- if (isNotFound(error2)) {
1544
- return false;
2510
+ function listMcpSupportedAgents() {
2511
+ const supported = [];
2512
+ for (const config of allSpawnConfigs) {
2513
+ if (config.kind !== "cli" || typeof config.mcpArgs !== "function" && typeof config.mcpEnv !== "function") {
2514
+ continue;
1545
2515
  }
1546
- throw error2;
2516
+ supported.push(config.agentId);
1547
2517
  }
1548
- }
1549
- function createTimestamp() {
1550
- return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
2518
+ return supported;
1551
2519
  }
1552
2520
 
1553
- // packages/config-mutations/src/execution/apply-mutation.ts
1554
- function resolveValue(resolver, options) {
1555
- if (typeof resolver === "function") {
1556
- return resolver(options);
2521
+ // packages/agent-spawn/src/spawn.ts
2522
+ import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
2523
+ import path15 from "node:path";
2524
+
2525
+ // packages/agent-spawn/src/configs/resolve-config.ts
2526
+ function resolveConfig(agentId) {
2527
+ const resolvedAgentId = resolveAgentId(agentId);
2528
+ if (!resolvedAgentId) {
2529
+ throw new Error(`Unknown agent "${agentId}".`);
1557
2530
  }
1558
- return resolver;
1559
- }
1560
- function createInvalidDocumentBackupPath(targetPath) {
1561
- const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
1562
- return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
1563
- }
1564
- async function backupInvalidDocument(fs, targetPath, content) {
1565
- const backupPath = createInvalidDocumentBackupPath(targetPath);
1566
- await fs.writeFile(backupPath, content, { encoding: "utf8" });
2531
+ const agentDefinition = allAgents.find((agent) => agent.id === resolvedAgentId);
2532
+ if (!agentDefinition) {
2533
+ throw new Error(`Unknown agent "${agentId}".`);
2534
+ }
2535
+ const spawnConfig = getSpawnConfig(resolvedAgentId);
2536
+ const binaryName = agentDefinition.binaryName;
2537
+ return { agentId: resolvedAgentId, binaryName, spawnConfig };
1567
2538
  }
1568
- function describeMutation(kind, targetPath) {
1569
- const displayPath = targetPath ?? "target";
1570
- switch (kind) {
1571
- case "ensureDirectory":
1572
- return `Create ${displayPath}`;
1573
- case "removeDirectory":
1574
- return `Remove directory ${displayPath}`;
1575
- case "backup":
1576
- return `Backup ${displayPath}`;
1577
- case "templateWrite":
1578
- return `Write ${displayPath}`;
1579
- case "chmod":
1580
- return `Set permissions on ${displayPath}`;
1581
- case "removeFile":
1582
- return `Remove ${displayPath}`;
1583
- case "configMerge":
1584
- case "configPrune":
1585
- case "configTransform":
1586
- case "templateMergeToml":
1587
- case "templateMergeJson":
1588
- return `Update ${displayPath}`;
1589
- default:
1590
- return "Operation";
2539
+
2540
+ // packages/agent-spawn/src/mcp-args.ts
2541
+ function hasMcpServers(servers) {
2542
+ if (!servers) {
2543
+ return false;
1591
2544
  }
2545
+ return Object.keys(servers).length > 0;
1592
2546
  }
1593
- function pruneKeysByPrefix(table, prefix) {
1594
- const result = {};
1595
- for (const [key, value] of Object.entries(table)) {
1596
- if (!key.startsWith(prefix)) {
1597
- result[key] = value;
1598
- }
2547
+ function getMcpArgs(config, servers) {
2548
+ if (!hasMcpServers(servers)) {
2549
+ return [];
1599
2550
  }
1600
- return result;
2551
+ if (!config.mcpArgs && !config.mcpEnv) {
2552
+ throw new Error(formatUnsupportedMcpSpawnMessage(config.agentId));
2553
+ }
2554
+ if (!config.mcpArgs) {
2555
+ return [];
2556
+ }
2557
+ return config.mcpArgs(servers);
1601
2558
  }
1602
- function isConfigObject4(value) {
1603
- return typeof value === "object" && value !== null && !Array.isArray(value);
2559
+ function formatUnsupportedMcpSpawnMessage(agentId) {
2560
+ const supported = listMcpSupportedAgents();
2561
+ const supportedText = supported.length > 0 ? supported.join(", ") : "(none)";
2562
+ return `Agent "${agentId}" does not support MCP servers at spawn time.
2563
+ Agents with spawn-time MCP support: ${supportedText}`;
1604
2564
  }
1605
- function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
1606
- const result = { ...base };
1607
- const prefixMap = pruneByPrefix ?? {};
1608
- for (const [key, value] of Object.entries(patch)) {
1609
- const current = result[key];
1610
- const prefix = prefixMap[key];
1611
- if (isConfigObject4(current) && isConfigObject4(value)) {
1612
- if (prefix) {
1613
- const pruned = pruneKeysByPrefix(current, prefix);
1614
- result[key] = { ...pruned, ...value };
1615
- } else {
1616
- result[key] = mergeWithPruneByPrefix(
1617
- current,
1618
- value,
1619
- prefixMap
1620
- );
1621
- }
1622
- continue;
1623
- }
1624
- result[key] = value;
1625
- }
1626
- return result;
2565
+
2566
+ // packages/agent-spawn/src/model-utils.ts
2567
+ function stripModelNamespace(model) {
2568
+ const slashIndex = model.indexOf("/");
2569
+ return slashIndex === -1 ? model : model.slice(slashIndex + 1);
1627
2570
  }
1628
- async function applyMutation(mutation, context, options) {
1629
- switch (mutation.kind) {
1630
- case "ensureDirectory":
1631
- return applyEnsureDirectory(mutation, context, options);
1632
- case "removeDirectory":
1633
- return applyRemoveDirectory(mutation, context, options);
1634
- case "removeFile":
1635
- return applyRemoveFile(mutation, context, options);
1636
- case "chmod":
1637
- return applyChmod(mutation, context, options);
1638
- case "backup":
1639
- return applyBackup(mutation, context, options);
1640
- case "configMerge":
1641
- return applyConfigMerge(mutation, context, options);
1642
- case "configPrune":
1643
- return applyConfigPrune(mutation, context, options);
1644
- case "configTransform":
1645
- return applyConfigTransform(mutation, context, options);
1646
- case "templateWrite":
1647
- return applyTemplateWrite(mutation, context, options);
1648
- case "templateMergeToml":
1649
- return applyTemplateMerge(mutation, context, options, "toml");
1650
- case "templateMergeJson":
1651
- return applyTemplateMerge(mutation, context, options, "json");
1652
- default: {
1653
- const never = mutation;
1654
- throw new Error(`Unknown mutation kind: ${never.kind}`);
1655
- }
2571
+
2572
+ // packages/agent-spawn/src/spawn.ts
2573
+ function resolveCliConfig(agentId) {
2574
+ const resolved = resolveConfig(agentId);
2575
+ if (!resolved.spawnConfig) {
2576
+ throw new Error(`Agent "${resolved.agentId}" has no spawn config.`);
1656
2577
  }
1657
- }
1658
- async function applyEnsureDirectory(mutation, context, options) {
1659
- const rawPath = resolveValue(mutation.path, options);
1660
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1661
- const details = {
1662
- kind: mutation.kind,
1663
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1664
- targetPath
1665
- };
1666
- const existed = await pathExists(context.fs, targetPath);
1667
- if (!context.dryRun) {
1668
- await context.fs.mkdir(targetPath, { recursive: true });
2578
+ if (resolved.spawnConfig.kind !== "cli") {
2579
+ throw new Error(`Agent "${resolved.agentId}" does not support CLI spawn.`);
2580
+ }
2581
+ if (!resolved.binaryName) {
2582
+ throw new Error(`Agent "${resolved.agentId}" has no binaryName.`);
1669
2583
  }
1670
2584
  return {
1671
- outcome: {
1672
- changed: !existed,
1673
- effect: "mkdir",
1674
- detail: existed ? "noop" : "create"
1675
- },
1676
- details
2585
+ agentId: resolved.agentId,
2586
+ binaryName: resolved.binaryName,
2587
+ spawnConfig: resolved.spawnConfig
1677
2588
  };
1678
2589
  }
1679
- async function applyRemoveDirectory(mutation, context, options) {
1680
- const rawPath = resolveValue(mutation.path, options);
1681
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1682
- const details = {
1683
- kind: mutation.kind,
1684
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1685
- targetPath
1686
- };
1687
- const existed = await pathExists(context.fs, targetPath);
1688
- if (!existed) {
1689
- return {
1690
- outcome: { changed: false, effect: "none", detail: "noop" },
1691
- details
1692
- };
2590
+ function getDefaultArgsPosition(config) {
2591
+ return config.defaultArgsPosition ?? "afterPrompt";
2592
+ }
2593
+ function getMcpArgsPosition(config) {
2594
+ if (config.mcpArgsPosition) {
2595
+ return config.mcpArgsPosition;
1693
2596
  }
1694
- if (typeof context.fs.rm !== "function") {
1695
- return {
1696
- outcome: { changed: false, effect: "none", detail: "noop" },
1697
- details
1698
- };
2597
+ return config.mcpArgsBeforeCommand ? "beforeCommand" : "afterCommand";
2598
+ }
2599
+ function buildCliArgs(config, options, stdinMode) {
2600
+ const mcpArgs = getMcpArgs(config, options.mcpServers);
2601
+ const defaultArgsPosition = getDefaultArgsPosition(config);
2602
+ const mcpArgsPosition = getMcpArgsPosition(config);
2603
+ const args = [];
2604
+ if (mcpArgsPosition === "beforeCommand") {
2605
+ args.push(...mcpArgs);
1699
2606
  }
1700
- if (mutation.force) {
1701
- if (!context.dryRun) {
1702
- await context.fs.rm(targetPath, { recursive: true, force: true });
1703
- }
1704
- return {
1705
- outcome: { changed: true, effect: "delete", detail: "delete" },
1706
- details
1707
- };
2607
+ if (defaultArgsPosition === "beforePrompt") {
2608
+ args.push(...config.defaultArgs);
1708
2609
  }
1709
- const entries = await context.fs.readdir(targetPath);
1710
- if (entries.length > 0) {
1711
- return {
1712
- outcome: { changed: false, effect: "none", detail: "noop" },
1713
- details
1714
- };
2610
+ if (mcpArgsPosition === "beforePrompt") {
2611
+ args.push(...mcpArgs);
1715
2612
  }
1716
- if (!context.dryRun) {
1717
- await context.fs.rm(targetPath, { recursive: true, force: true });
2613
+ if (stdinMode) {
2614
+ args.push(
2615
+ config.promptFlag,
2616
+ ...stdinMode.omitPrompt ? [] : [options.prompt],
2617
+ ...stdinMode.extraArgs
2618
+ );
2619
+ } else {
2620
+ args.push(config.promptFlag, options.prompt);
1718
2621
  }
1719
- return {
1720
- outcome: { changed: true, effect: "delete", detail: "delete" },
1721
- details
1722
- };
2622
+ if (options.model && config.modelFlag) {
2623
+ let model = config.modelStripProviderPrefix ? stripModelNamespace(options.model) : options.model;
2624
+ if (config.modelTransform) model = config.modelTransform(model);
2625
+ args.push(config.modelFlag, model);
2626
+ }
2627
+ if (defaultArgsPosition === "afterPrompt") {
2628
+ args.push(...config.defaultArgs);
2629
+ }
2630
+ if (mcpArgsPosition === "afterCommand") {
2631
+ args.push(...mcpArgs);
2632
+ }
2633
+ const mode = resolveModeConfig(config.modes[options.mode ?? "yolo"]);
2634
+ args.push(...mode.args);
2635
+ if (options.args && options.args.length > 0) {
2636
+ args.push(...options.args);
2637
+ }
2638
+ return { args, env: mode.env };
1723
2639
  }
1724
- async function applyRemoveFile(mutation, context, options) {
1725
- const rawPath = resolveValue(mutation.target, options);
1726
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1727
- const details = {
1728
- kind: mutation.kind,
1729
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1730
- targetPath
1731
- };
1732
- try {
1733
- const content = await context.fs.readFile(targetPath, "utf8");
1734
- const trimmed = content.trim();
1735
- if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
1736
- return {
1737
- outcome: { changed: false, effect: "none", detail: "noop" },
1738
- details
1739
- };
1740
- }
1741
- if (mutation.whenEmpty && trimmed.length > 0) {
1742
- return {
1743
- outcome: { changed: false, effect: "none", detail: "noop" },
1744
- details
1745
- };
1746
- }
1747
- if (!context.dryRun) {
1748
- await context.fs.unlink(targetPath);
1749
- }
1750
- return {
1751
- outcome: { changed: true, effect: "delete", detail: "delete" },
1752
- details
1753
- };
1754
- } catch (error2) {
1755
- if (isNotFound(error2)) {
1756
- return {
1757
- outcome: { changed: false, effect: "none", detail: "noop" },
1758
- details
1759
- };
1760
- }
1761
- throw error2;
2640
+ function buildSpawnArgs(agentId, options) {
2641
+ const { binaryName, spawnConfig } = resolveCliConfig(agentId);
2642
+ const stdinMode = options.useStdin && spawnConfig.stdinMode ? spawnConfig.stdinMode : void 0;
2643
+ const result = buildCliArgs(spawnConfig, options, stdinMode);
2644
+ return { binaryName, args: result.args, env: result.env };
2645
+ }
2646
+
2647
+ // packages/design-system/src/tokens/colors.ts
2648
+ import chalk from "chalk";
2649
+ var dark = {
2650
+ header: (text4) => chalk.magentaBright.bold(text4),
2651
+ divider: (text4) => chalk.dim(text4),
2652
+ prompt: (text4) => chalk.cyan(text4),
2653
+ number: (text4) => chalk.cyanBright(text4),
2654
+ intro: (text4) => chalk.bgMagenta.white(` Poe - ${text4} `),
2655
+ resolvedSymbol: chalk.magenta("\u25C7"),
2656
+ errorSymbol: chalk.red("\u25A0"),
2657
+ accent: (text4) => chalk.cyan(text4),
2658
+ muted: (text4) => chalk.dim(text4),
2659
+ success: (text4) => chalk.green(text4),
2660
+ warning: (text4) => chalk.yellow(text4),
2661
+ error: (text4) => chalk.red(text4),
2662
+ info: (text4) => chalk.magenta(text4),
2663
+ badge: (text4) => chalk.bgYellow.black(` ${text4} `)
2664
+ };
2665
+ var light = {
2666
+ header: (text4) => chalk.hex("#a200ff").bold(text4),
2667
+ divider: (text4) => chalk.hex("#666666")(text4),
2668
+ prompt: (text4) => chalk.hex("#006699").bold(text4),
2669
+ number: (text4) => chalk.hex("#0077cc").bold(text4),
2670
+ intro: (text4) => chalk.bgHex("#a200ff").white(` Poe - ${text4} `),
2671
+ resolvedSymbol: chalk.hex("#a200ff")("\u25C7"),
2672
+ errorSymbol: chalk.hex("#cc0000")("\u25A0"),
2673
+ accent: (text4) => chalk.hex("#006699").bold(text4),
2674
+ muted: (text4) => chalk.hex("#666666")(text4),
2675
+ success: (text4) => chalk.hex("#008800")(text4),
2676
+ warning: (text4) => chalk.hex("#cc6600")(text4),
2677
+ error: (text4) => chalk.hex("#cc0000")(text4),
2678
+ info: (text4) => chalk.hex("#a200ff")(text4),
2679
+ badge: (text4) => chalk.bgHex("#cc6600").white(` ${text4} `)
2680
+ };
2681
+
2682
+ // packages/design-system/src/tokens/typography.ts
2683
+ import chalk2 from "chalk";
2684
+
2685
+ // packages/design-system/src/components/text.ts
2686
+ import chalk3 from "chalk";
2687
+
2688
+ // packages/design-system/src/internal/output-format.ts
2689
+ import { AsyncLocalStorage } from "node:async_hooks";
2690
+ var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
2691
+ var formatStorage = new AsyncLocalStorage();
2692
+ var cached;
2693
+ function resolveOutputFormat(env = process.env) {
2694
+ const scoped = formatStorage.getStore();
2695
+ if (scoped) {
2696
+ return scoped;
2697
+ }
2698
+ if (cached) {
2699
+ return cached;
1762
2700
  }
2701
+ const raw = env.OUTPUT_FORMAT?.toLowerCase();
2702
+ cached = VALID_FORMATS.has(raw) ? raw : "terminal";
2703
+ return cached;
1763
2704
  }
1764
- async function applyChmod(mutation, context, options) {
1765
- const rawPath = resolveValue(mutation.target, options);
1766
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1767
- const details = {
1768
- kind: mutation.kind,
1769
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1770
- targetPath
1771
- };
1772
- if (typeof context.fs.chmod !== "function") {
1773
- return {
1774
- outcome: { changed: false, effect: "none", detail: "noop" },
1775
- details
1776
- };
2705
+
2706
+ // packages/design-system/src/internal/theme-detect.ts
2707
+ function detectThemeFromEnv(env) {
2708
+ const apple = env.APPLE_INTERFACE_STYLE;
2709
+ if (typeof apple === "string") {
2710
+ return apple.toLowerCase() === "dark" ? "dark" : "light";
1777
2711
  }
1778
- try {
1779
- const stat = await context.fs.stat(targetPath);
1780
- const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
1781
- if (currentMode === mutation.mode) {
1782
- return {
1783
- outcome: { changed: false, effect: "none", detail: "noop" },
1784
- details
1785
- };
2712
+ const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
2713
+ if (typeof vscodeKind === "string") {
2714
+ const normalized = vscodeKind.toLowerCase();
2715
+ if (normalized.includes("light")) {
2716
+ return "light";
1786
2717
  }
1787
- if (!context.dryRun) {
1788
- await context.fs.chmod(targetPath, mutation.mode);
2718
+ if (normalized.includes("dark")) {
2719
+ return "dark";
1789
2720
  }
1790
- return {
1791
- outcome: { changed: true, effect: "chmod", detail: "update" },
1792
- details
1793
- };
1794
- } catch (error2) {
1795
- if (isNotFound(error2)) {
1796
- return {
1797
- outcome: { changed: false, effect: "none", detail: "noop" },
1798
- details
1799
- };
2721
+ }
2722
+ const colorFGBG = env.COLORFGBG;
2723
+ if (typeof colorFGBG === "string") {
2724
+ const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
2725
+ const background = parts.at(-1);
2726
+ if (Number.isFinite(background)) {
2727
+ return background >= 8 ? "light" : "dark";
1800
2728
  }
1801
- throw error2;
1802
2729
  }
2730
+ return void 0;
1803
2731
  }
1804
- async function applyBackup(mutation, context, options) {
1805
- const rawPath = resolveValue(mutation.target, options);
1806
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1807
- const details = {
1808
- kind: mutation.kind,
1809
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1810
- targetPath
1811
- };
1812
- const content = await readFileIfExists(context.fs, targetPath);
1813
- if (content === null) {
1814
- return {
1815
- outcome: { changed: false, effect: "none", detail: "noop" },
1816
- details
1817
- };
2732
+ function resolveThemeName(env = process.env) {
2733
+ const raw = (env.POE_CODE_THEME ?? env.POE_THEME)?.toLowerCase();
2734
+ if (raw === "light" || raw === "dark") {
2735
+ return raw;
1818
2736
  }
1819
- if (!context.dryRun) {
1820
- const backupPath = `${targetPath}.backup-${createTimestamp()}`;
1821
- await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
2737
+ const detected = detectThemeFromEnv(env);
2738
+ if (detected) {
2739
+ return detected;
1822
2740
  }
1823
- return {
1824
- outcome: { changed: true, effect: "copy", detail: "backup" },
1825
- details
1826
- };
2741
+ return "dark";
1827
2742
  }
1828
- async function applyConfigMerge(mutation, context, options) {
1829
- const rawPath = resolveValue(mutation.target, options);
1830
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1831
- const details = {
1832
- kind: mutation.kind,
1833
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1834
- targetPath
1835
- };
1836
- const formatName = mutation.format ?? detectFormat(rawPath);
1837
- if (!formatName) {
1838
- throw new Error(
1839
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1840
- );
1841
- }
1842
- const format = getConfigFormat(formatName);
1843
- const rawContent = await readFileIfExists(context.fs, targetPath);
1844
- let current;
1845
- try {
1846
- current = rawContent === null ? {} : format.parse(rawContent);
1847
- } catch {
1848
- if (rawContent !== null) {
1849
- await backupInvalidDocument(context.fs, targetPath, rawContent);
1850
- }
1851
- current = {};
1852
- }
1853
- const value = resolveValue(mutation.value, options);
1854
- let merged;
1855
- if (mutation.pruneByPrefix) {
1856
- merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
1857
- } else {
1858
- merged = format.merge(current, value);
1859
- }
1860
- const serialized = format.serialize(merged);
1861
- const changed = serialized !== rawContent;
1862
- if (changed && !context.dryRun) {
1863
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2743
+ var cachedTheme;
2744
+ function getTheme(env) {
2745
+ if (cachedTheme) {
2746
+ return cachedTheme;
1864
2747
  }
1865
- return {
1866
- outcome: {
1867
- changed,
1868
- effect: changed ? "write" : "none",
1869
- detail: changed ? rawContent === null ? "create" : "update" : "noop"
1870
- },
1871
- details
1872
- };
2748
+ const themeName = resolveThemeName(env);
2749
+ cachedTheme = themeName === "light" ? light : dark;
2750
+ return cachedTheme;
1873
2751
  }
1874
- async function applyConfigPrune(mutation, context, options) {
1875
- const rawPath = resolveValue(mutation.target, options);
1876
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1877
- const details = {
1878
- kind: mutation.kind,
1879
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1880
- targetPath
1881
- };
1882
- const rawContent = await readFileIfExists(context.fs, targetPath);
1883
- if (rawContent === null) {
1884
- return {
1885
- outcome: { changed: false, effect: "none", detail: "noop" },
1886
- details
1887
- };
1888
- }
1889
- const formatName = mutation.format ?? detectFormat(rawPath);
1890
- if (!formatName) {
1891
- throw new Error(
1892
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1893
- );
2752
+
2753
+ // packages/design-system/src/components/symbols.ts
2754
+ import chalk4 from "chalk";
2755
+ var symbols = {
2756
+ get info() {
2757
+ const format = resolveOutputFormat();
2758
+ if (format === "json") return "info";
2759
+ if (format === "markdown") return "(i)";
2760
+ return chalk4.magenta("\u25CF");
2761
+ },
2762
+ get success() {
2763
+ const format = resolveOutputFormat();
2764
+ if (format === "json") return "success";
2765
+ if (format === "markdown") return "[ok]";
2766
+ return chalk4.magenta("\u25C6");
2767
+ },
2768
+ get resolved() {
2769
+ const format = resolveOutputFormat();
2770
+ if (format === "json") return "resolved";
2771
+ if (format === "markdown") return ">";
2772
+ return getTheme().resolvedSymbol;
2773
+ },
2774
+ get errorResolved() {
2775
+ const format = resolveOutputFormat();
2776
+ if (format === "json") return "error";
2777
+ if (format === "markdown") return "[!]";
2778
+ return getTheme().errorSymbol;
2779
+ },
2780
+ get bar() {
2781
+ const format = resolveOutputFormat();
2782
+ if (format === "json") return "";
2783
+ if (format === "markdown") return "|";
2784
+ return "\u2502";
2785
+ },
2786
+ cornerTopRight: "\u256E",
2787
+ cornerBottomRight: "\u256F",
2788
+ get warning() {
2789
+ const format = resolveOutputFormat();
2790
+ if (format === "json") return "warning";
2791
+ if (format === "markdown") return "[!]";
2792
+ return "\u25B2";
2793
+ },
2794
+ get active() {
2795
+ const format = resolveOutputFormat();
2796
+ if (format === "json") return "active";
2797
+ if (format === "markdown") return "[x]";
2798
+ return "\u25C6";
2799
+ },
2800
+ get inactive() {
2801
+ const format = resolveOutputFormat();
2802
+ if (format === "json") return "inactive";
2803
+ if (format === "markdown") return "[ ]";
2804
+ return "\u25CB";
1894
2805
  }
1895
- const format = getConfigFormat(formatName);
1896
- let current;
1897
- try {
1898
- current = format.parse(rawContent);
1899
- } catch {
1900
- return {
1901
- outcome: { changed: false, effect: "none", detail: "noop" },
1902
- details
1903
- };
2806
+ };
2807
+
2808
+ // packages/design-system/src/components/logger.ts
2809
+ import chalk6 from "chalk";
2810
+
2811
+ // packages/design-system/src/prompts/primitives/log.ts
2812
+ import chalk5 from "chalk";
2813
+
2814
+ // packages/design-system/src/internal/strip-ansi.ts
2815
+ function stripAnsi(value) {
2816
+ return value.replace(/\u001b\[[0-9;]*m/g, "");
2817
+ }
2818
+
2819
+ // packages/design-system/src/prompts/primitives/log.ts
2820
+ function writeTerminalMessage(msg, {
2821
+ symbol = chalk5.gray("\u2502"),
2822
+ secondarySymbol = chalk5.gray("\u2502"),
2823
+ spacing: spacing2 = 1,
2824
+ withGuide = true
2825
+ } = {}) {
2826
+ const lines = [];
2827
+ const showGuide = withGuide !== false;
2828
+ const contentLines = msg.split("\n");
2829
+ const prefix = showGuide ? `${symbol} ` : "";
2830
+ const continuationPrefix = showGuide ? `${secondarySymbol} ` : "";
2831
+ const emptyGuide = showGuide ? secondarySymbol : "";
2832
+ for (let index = 0; index < spacing2; index += 1) {
2833
+ lines.push(emptyGuide);
1904
2834
  }
1905
- if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
1906
- return {
1907
- outcome: { changed: false, effect: "none", detail: "noop" },
1908
- details
1909
- };
2835
+ if (contentLines.length === 0) {
2836
+ process.stdout.write("\n");
2837
+ return;
1910
2838
  }
1911
- const shape = resolveValue(mutation.shape, options);
1912
- const { changed, result } = format.prune(current, shape);
1913
- if (!changed) {
1914
- return {
1915
- outcome: { changed: false, effect: "none", detail: "noop" },
1916
- details
1917
- };
2839
+ const [firstLine = "", ...continuationLines] = contentLines;
2840
+ if (firstLine.length > 0) {
2841
+ lines.push(`${prefix}${firstLine}`);
2842
+ } else {
2843
+ lines.push(showGuide ? symbol : "");
1918
2844
  }
1919
- if (Object.keys(result).length === 0) {
1920
- if (!context.dryRun) {
1921
- await context.fs.unlink(targetPath);
2845
+ for (const line of continuationLines) {
2846
+ if (line.length > 0) {
2847
+ lines.push(`${continuationPrefix}${line}`);
2848
+ continue;
1922
2849
  }
1923
- return {
1924
- outcome: { changed: true, effect: "delete", detail: "delete" },
1925
- details
1926
- };
1927
- }
1928
- const serialized = format.serialize(result);
1929
- if (!context.dryRun) {
1930
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2850
+ lines.push(emptyGuide);
1931
2851
  }
1932
- return {
1933
- outcome: { changed: true, effect: "write", detail: "update" },
1934
- details
1935
- };
2852
+ process.stdout.write(`${lines.join("\n")}
2853
+ `);
1936
2854
  }
1937
- async function applyConfigTransform(mutation, context, options) {
1938
- const rawPath = resolveValue(mutation.target, options);
1939
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1940
- const details = {
1941
- kind: mutation.kind,
1942
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1943
- targetPath
1944
- };
1945
- const formatName = mutation.format ?? detectFormat(rawPath);
1946
- if (!formatName) {
1947
- throw new Error(
1948
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
2855
+ function message(msg, options) {
2856
+ const format = resolveOutputFormat();
2857
+ if (format === "markdown") {
2858
+ process.stdout.write(`- ${stripAnsi(msg)}
2859
+ `);
2860
+ return;
2861
+ }
2862
+ if (format === "json") {
2863
+ process.stdout.write(
2864
+ `${JSON.stringify({ level: "message", message: stripAnsi(msg) })}
2865
+ `
1949
2866
  );
2867
+ return;
1950
2868
  }
1951
- const format = getConfigFormat(formatName);
1952
- const rawContent = await readFileIfExists(context.fs, targetPath);
1953
- let current;
1954
- try {
1955
- current = rawContent === null ? {} : format.parse(rawContent);
1956
- } catch {
1957
- if (rawContent !== null) {
1958
- await backupInvalidDocument(context.fs, targetPath, rawContent);
1959
- }
1960
- current = {};
2869
+ writeTerminalMessage(msg, options);
2870
+ }
2871
+ function info(msg) {
2872
+ const format = resolveOutputFormat();
2873
+ if (format === "markdown") {
2874
+ process.stdout.write(`- **info:** ${stripAnsi(msg)}
2875
+ `);
2876
+ return;
1961
2877
  }
1962
- const { content: transformed, changed } = mutation.transform(current, options);
1963
- if (!changed) {
1964
- return {
1965
- outcome: { changed: false, effect: "none", detail: "noop" },
1966
- details
1967
- };
2878
+ if (format === "json") {
2879
+ process.stdout.write(
2880
+ `${JSON.stringify({ level: "info", message: stripAnsi(msg) })}
2881
+ `
2882
+ );
2883
+ return;
1968
2884
  }
1969
- if (transformed === null) {
1970
- if (rawContent === null) {
1971
- return {
1972
- outcome: { changed: false, effect: "none", detail: "noop" },
1973
- details
1974
- };
1975
- }
1976
- if (!context.dryRun) {
1977
- await context.fs.unlink(targetPath);
1978
- }
1979
- return {
1980
- outcome: { changed: true, effect: "delete", detail: "delete" },
1981
- details
1982
- };
2885
+ message(msg, { symbol: symbols.info });
2886
+ }
2887
+ function success(msg) {
2888
+ const format = resolveOutputFormat();
2889
+ if (format === "markdown") {
2890
+ process.stdout.write(`- **success:** ${stripAnsi(msg)}
2891
+ `);
2892
+ return;
1983
2893
  }
1984
- const serialized = format.serialize(transformed);
1985
- if (!context.dryRun) {
1986
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2894
+ if (format === "json") {
2895
+ process.stdout.write(
2896
+ `${JSON.stringify({ level: "success", message: stripAnsi(msg) })}
2897
+ `
2898
+ );
2899
+ return;
1987
2900
  }
1988
- return {
1989
- outcome: {
1990
- changed: true,
1991
- effect: "write",
1992
- detail: rawContent === null ? "create" : "update"
1993
- },
1994
- details
1995
- };
2901
+ message(msg, { symbol: symbols.success });
1996
2902
  }
1997
- async function applyTemplateWrite(mutation, context, options) {
1998
- if (!context.templates) {
1999
- throw new Error(
2000
- "Template mutations require a templates loader. Provide templates function to runMutations context."
2001
- );
2903
+ function warn(msg) {
2904
+ const format = resolveOutputFormat();
2905
+ if (format === "markdown") {
2906
+ process.stdout.write(`- **warning:** ${stripAnsi(msg)}
2907
+ `);
2908
+ return;
2002
2909
  }
2003
- const rawPath = resolveValue(mutation.target, options);
2004
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2005
- const details = {
2006
- kind: mutation.kind,
2007
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2008
- targetPath
2009
- };
2010
- const template = await context.templates(mutation.templateId);
2011
- const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2012
- const rendered = Mustache.render(template, templateContext);
2013
- const existed = await pathExists(context.fs, targetPath);
2014
- if (!context.dryRun) {
2015
- await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
2910
+ if (format === "json") {
2911
+ process.stdout.write(
2912
+ `${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}
2913
+ `
2914
+ );
2915
+ return;
2016
2916
  }
2017
- return {
2018
- outcome: {
2019
- changed: true,
2020
- effect: "write",
2021
- detail: existed ? "update" : "create"
2022
- },
2023
- details
2024
- };
2917
+ message(msg, { symbol: chalk5.yellow("\u25B2") });
2025
2918
  }
2026
- async function applyTemplateMerge(mutation, context, options, formatName) {
2027
- if (!context.templates) {
2028
- throw new Error(
2029
- "Template mutations require a templates loader. Provide templates function to runMutations context."
2030
- );
2919
+ function error(msg) {
2920
+ const format = resolveOutputFormat();
2921
+ if (format === "markdown") {
2922
+ process.stdout.write(`- **error:** ${stripAnsi(msg)}
2923
+ `);
2924
+ return;
2031
2925
  }
2032
- const rawPath = resolveValue(mutation.target, options);
2033
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2034
- const details = {
2035
- kind: mutation.kind,
2036
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2037
- targetPath
2038
- };
2039
- const format = getConfigFormat(formatName);
2040
- const template = await context.templates(mutation.templateId);
2041
- const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2042
- const rendered = Mustache.render(template, templateContext);
2043
- let templateDoc;
2044
- try {
2045
- templateDoc = format.parse(rendered);
2046
- } catch (error2) {
2047
- throw new Error(
2048
- `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error2}`,
2049
- { cause: error2 }
2926
+ if (format === "json") {
2927
+ process.stdout.write(
2928
+ `${JSON.stringify({ level: "error", message: stripAnsi(msg) })}
2929
+ `
2050
2930
  );
2931
+ return;
2051
2932
  }
2052
- const rawContent = await readFileIfExists(context.fs, targetPath);
2053
- let current;
2054
- try {
2055
- current = rawContent === null ? {} : format.parse(rawContent);
2056
- } catch {
2057
- if (rawContent !== null) {
2058
- await backupInvalidDocument(context.fs, targetPath, rawContent);
2933
+ message(msg, { symbol: chalk5.red("\u25A0") });
2934
+ }
2935
+ var log = {
2936
+ info,
2937
+ success,
2938
+ message,
2939
+ warn,
2940
+ error
2941
+ };
2942
+
2943
+ // packages/design-system/src/components/logger.ts
2944
+ function createLogger(emitter) {
2945
+ const emit = (level, message2) => {
2946
+ if (emitter) {
2947
+ emitter(message2);
2948
+ return;
2059
2949
  }
2060
- current = {};
2061
- }
2062
- const merged = format.merge(current, templateDoc);
2063
- const serialized = format.serialize(merged);
2064
- const changed = serialized !== rawContent;
2065
- if (changed && !context.dryRun) {
2066
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2067
- }
2950
+ if (level === "success") {
2951
+ log.success(message2);
2952
+ return;
2953
+ }
2954
+ if (level === "warn") {
2955
+ log.warn(message2);
2956
+ return;
2957
+ }
2958
+ if (level === "error") {
2959
+ log.error(message2);
2960
+ return;
2961
+ }
2962
+ log.info(message2);
2963
+ };
2068
2964
  return {
2069
- outcome: {
2070
- changed,
2071
- effect: changed ? "write" : "none",
2072
- detail: changed ? rawContent === null ? "create" : "update" : "noop"
2965
+ info(message2) {
2966
+ emit("info", message2);
2073
2967
  },
2074
- details
2968
+ success(message2) {
2969
+ emit("success", message2);
2970
+ },
2971
+ warn(message2) {
2972
+ emit("warn", message2);
2973
+ },
2974
+ error(message2) {
2975
+ emit("error", message2);
2976
+ },
2977
+ resolved(label, value) {
2978
+ if (emitter) {
2979
+ emitter(`${label}: ${value}`);
2980
+ return;
2981
+ }
2982
+ log.message(`${label}
2983
+ ${value}`, { symbol: symbols.resolved });
2984
+ },
2985
+ errorResolved(label, value) {
2986
+ if (emitter) {
2987
+ emitter(`${label}: ${value}`);
2988
+ return;
2989
+ }
2990
+ log.message(`${label}
2991
+ ${value}`, { symbol: symbols.errorResolved });
2992
+ },
2993
+ message(message2, symbol) {
2994
+ if (emitter) {
2995
+ emitter(message2);
2996
+ return;
2997
+ }
2998
+ log.message(message2, { symbol: symbol ?? chalk6.gray("\u2502") });
2999
+ }
2075
3000
  };
2076
3001
  }
3002
+ var logger = createLogger();
2077
3003
 
2078
- // packages/config-mutations/src/execution/run-mutations.ts
2079
- async function runMutations(mutations, context, options) {
2080
- const effects = [];
2081
- let anyChanged = false;
2082
- const resolverOptions = options ?? {};
2083
- for (const mutation of mutations) {
2084
- const { outcome } = await executeMutation(
2085
- mutation,
2086
- context,
2087
- resolverOptions
2088
- );
2089
- effects.push(outcome);
2090
- if (outcome.changed) {
2091
- anyChanged = true;
2092
- }
2093
- }
3004
+ // packages/design-system/src/components/table.ts
3005
+ import { Table } from "console-table-printer";
3006
+
3007
+ // packages/design-system/src/acp/components.ts
3008
+ import chalk7 from "chalk";
3009
+
3010
+ // packages/design-system/src/acp/writer.ts
3011
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
3012
+ var storage = new AsyncLocalStorage2();
3013
+
3014
+ // packages/design-system/src/acp/components.ts
3015
+ var AGENT_PREFIX = `${chalk7.green.bold("\u2713")} agent: `;
3016
+
3017
+ // packages/design-system/src/dashboard/buffer.ts
3018
+ import chalk8 from "chalk";
3019
+
3020
+ // packages/design-system/src/dashboard/terminal.ts
3021
+ import readline from "node:readline";
3022
+ import { PassThrough } from "node:stream";
3023
+
3024
+ // packages/design-system/src/prompts/index.ts
3025
+ import chalk15 from "chalk";
3026
+ import * as clack from "@clack/prompts";
3027
+
3028
+ // packages/design-system/src/prompts/primitives/cancel.ts
3029
+ import chalk9 from "chalk";
3030
+ import { isCancel } from "@clack/prompts";
3031
+
3032
+ // packages/design-system/src/prompts/primitives/intro.ts
3033
+ import chalk10 from "chalk";
3034
+
3035
+ // packages/design-system/src/prompts/primitives/note.ts
3036
+ import chalk11 from "chalk";
3037
+
3038
+ // packages/design-system/src/prompts/primitives/outro.ts
3039
+ import chalk12 from "chalk";
3040
+
3041
+ // packages/design-system/src/prompts/primitives/spinner.ts
3042
+ import chalk14 from "chalk";
3043
+
3044
+ // packages/design-system/src/static/spinner.ts
3045
+ import chalk13 from "chalk";
3046
+
3047
+ // packages/design-system/src/static/menu.ts
3048
+ import chalk16 from "chalk";
3049
+
3050
+ // packages/agent-spawn/src/autonomous.ts
3051
+ var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
3052
+
3053
+ // packages/agent-spawn/src/acp/replay.ts
3054
+ import path16 from "node:path";
3055
+ import { homedir as homedir2 } from "node:os";
3056
+ import { open as open2, readdir } from "node:fs/promises";
3057
+ import { createInterface } from "node:readline";
3058
+
3059
+ // packages/poe-acp-client/src/acp-client.ts
3060
+ import { isAbsolute } from "node:path";
3061
+
3062
+ // packages/poe-acp-client/src/acp-transport.ts
3063
+ import {
3064
+ spawn as spawnChildProcess3
3065
+ } from "node:child_process";
3066
+
3067
+ // packages/poe-acp-client/src/run-report.ts
3068
+ import * as fsPromises2 from "node:fs/promises";
3069
+ import { homedir } from "node:os";
3070
+ import { join } from "node:path";
3071
+
3072
+ // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
3073
+ import path17 from "node:path";
3074
+ import { homedir as homedir3 } from "node:os";
3075
+ import { mkdir, open as open3 } from "node:fs/promises";
3076
+
3077
+ // src/utils/command-checks.ts
3078
+ function formatCommandRunnerResult(result) {
3079
+ const stdout = result.stdout.length > 0 ? result.stdout : "<empty>";
3080
+ const stderr = result.stderr.length > 0 ? result.stderr : "<empty>";
3081
+ return `stdout:
3082
+ ${stdout}
3083
+ stderr:
3084
+ ${stderr}`;
3085
+ }
3086
+ function createSpawnHealthCheck(agentId, options) {
3087
+ const prompt = `Output exactly: ${options.expectedOutput}`;
3088
+ const { binaryName, args, env: modeEnv } = buildSpawnArgs(agentId, {
3089
+ prompt,
3090
+ model: options.model,
3091
+ mode: "yolo"
3092
+ });
2094
3093
  return {
2095
- changed: anyChanged,
2096
- effects
3094
+ id: `${agentId}-cli-health`,
3095
+ description: `spawn ${agentId} (expecting "${options.expectedOutput}")`,
3096
+ async run(context) {
3097
+ if (context.isDryRun) {
3098
+ context.logDryRun?.(
3099
+ `Dry run: ${[binaryName, ...args].join(" ")} (expecting "${options.expectedOutput}")`
3100
+ );
3101
+ return;
3102
+ }
3103
+ const result = modeEnv ? await context.runCommand(binaryName, args, { env: modeEnv }) : await context.runCommand(binaryName, args);
3104
+ if (result.exitCode !== 0) {
3105
+ throw new Error(
3106
+ `spawn ${agentId} failed with exit code ${result.exitCode}.
3107
+ ${formatCommandRunnerResult(result)}`
3108
+ );
3109
+ }
3110
+ if (!result.stdout.includes(options.expectedOutput)) {
3111
+ throw new Error(
3112
+ `spawn ${agentId}: expected "${options.expectedOutput}" in stdout.
3113
+ ${formatCommandRunnerResult(result)}`
3114
+ );
3115
+ }
3116
+ }
2097
3117
  };
2098
3118
  }
2099
- async function executeMutation(mutation, context, options) {
2100
- context.observers?.onStart?.({
2101
- kind: mutation.kind,
2102
- label: mutation.label ?? mutation.kind,
2103
- targetPath: void 0
2104
- // Will be resolved during apply
2105
- });
2106
- try {
2107
- const { outcome, details } = await applyMutation(mutation, context, options);
2108
- context.observers?.onComplete?.(details, outcome);
2109
- return { outcome, details };
2110
- } catch (error2) {
2111
- context.observers?.onError?.(
2112
- {
2113
- kind: mutation.kind,
2114
- label: mutation.label ?? mutation.kind,
2115
- targetPath: void 0
2116
- },
2117
- error2
2118
- );
2119
- throw error2;
2120
- }
3119
+ function createBinaryExistsCheck(binaryName, id, description) {
3120
+ return {
3121
+ id,
3122
+ description,
3123
+ async run({ runCommand: runCommand2 }) {
3124
+ const commonPaths = [
3125
+ `/usr/local/bin/${binaryName}`,
3126
+ `/usr/bin/${binaryName}`,
3127
+ `$HOME/.local/bin/${binaryName}`,
3128
+ `$HOME/.claude/local/bin/${binaryName}`
3129
+ ];
3130
+ const detectors = [
3131
+ {
3132
+ command: "which",
3133
+ args: [binaryName],
3134
+ validate: (result) => result.exitCode === 0
3135
+ },
3136
+ {
3137
+ command: "where",
3138
+ args: [binaryName],
3139
+ validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
3140
+ },
3141
+ // Check common installation paths using shell expansion for $HOME
3142
+ {
3143
+ command: "sh",
3144
+ args: [
3145
+ "-c",
3146
+ commonPaths.map((p) => `test -f "${p}"`).join(" || ")
3147
+ ],
3148
+ validate: (result) => result.exitCode === 0
3149
+ }
3150
+ ];
3151
+ for (const detector of detectors) {
3152
+ const result = await runCommand2(detector.command, detector.args);
3153
+ if (detector.validate(result)) {
3154
+ return;
3155
+ }
3156
+ }
3157
+ throw new Error(`${binaryName} CLI binary not found on PATH.`);
3158
+ }
3159
+ };
2121
3160
  }
2122
3161
 
2123
- // packages/config-mutations/src/template/render.ts
2124
- import Mustache2 from "mustache";
2125
- var originalEscape = Mustache2.escape;
2126
-
2127
3162
  // src/cli/constants.ts
2128
3163
  var CLAUDE_CODE_VARIANTS = {
2129
3164
  haiku: "anthropic/claude-haiku-4.5",