poe-code 3.0.201 → 3.0.203

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