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