poe-code 3.0.60 → 3.0.62

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.
@@ -1,113 +1,1888 @@
1
- import { DEFAULT_FRONTIER_MODEL, FRONTIER_MODELS, PROVIDER_NAME } from "../cli/constants.js";
2
- import { createBinaryExistsCheck, createSpawnHealthCheck } from "../utils/command-checks.js";
3
- import { configMutation, fileMutation } from "@poe-code/config-mutations";
4
- import { createProvider } from "./create-provider.js";
5
- import { openCodeAgent } from "@poe-code/agent-defs";
6
- function providerModel(model) {
7
- const value = model ?? DEFAULT_FRONTIER_MODEL;
8
- const prefix = `${PROVIDER_NAME}/`;
9
- return value.startsWith(prefix) ? value : `${prefix}${value}`;
10
- }
11
- export const OPEN_CODE_INSTALL_DEFINITION = {
12
- id: "opencode",
13
- summary: "OpenCode CLI",
14
- check: createBinaryExistsCheck("opencode", "opencode-cli-binary", "OpenCode CLI binary must exist"),
15
- steps: [
16
- {
17
- id: "install-opencode-cli-npm",
18
- command: "npm",
19
- args: ["install", "-g", "opencode-ai"]
20
- }
21
- ],
22
- successMessage: "Installed OpenCode CLI via npm."
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
23
9
  };
24
- function getModelArgs(model) {
25
- return ["--model", providerModel(model)];
26
- }
27
- export const openCodeService = createProvider({
28
- ...openCodeAgent,
29
- supportsStdinPrompt: false,
30
- configurePrompts: {
31
- model: {
32
- label: "OpenCode model",
33
- defaultValue: DEFAULT_FRONTIER_MODEL,
34
- choices: FRONTIER_MODELS.map((id) => ({
35
- title: id,
36
- value: id
37
- }))
38
- }
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/templates/python/env.hbs
28
+ var require_env = __commonJS({
29
+ "src/templates/python/env.hbs"(exports, module) {
30
+ module.exports = "POE_API_KEY={{apiKey}}\nPOE_BASE_URL=https://api.poe.com/v1\nMODEL={{model}}\n";
31
+ }
32
+ });
33
+
34
+ // src/templates/python/main.py.hbs
35
+ var require_main_py = __commonJS({
36
+ "src/templates/python/main.py.hbs"(exports, module) {
37
+ module.exports = 'import os\nfrom openai import OpenAI\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nclient = OpenAI(\n api_key=os.getenv("POE_API_KEY"),\n base_url=os.getenv("POE_BASE_URL")\n)\n\nresponse = client.chat.completions.create(\n model=os.getenv("MODEL", "{{model}}"),\n messages=[{"role": "user", "content": "Tell me a joke"}]\n)\n\nprint(response.choices[0].message.content)\n';
38
+ }
39
+ });
40
+
41
+ // src/templates/python/requirements.txt.hbs
42
+ var require_requirements_txt = __commonJS({
43
+ "src/templates/python/requirements.txt.hbs"(exports, module) {
44
+ module.exports = "openai>=1.0.0\npython-dotenv>=1.0.0\n";
45
+ }
46
+ });
47
+
48
+ // src/templates/codex/config.toml.hbs
49
+ var require_config_toml = __commonJS({
50
+ "src/templates/codex/config.toml.hbs"(exports, module) {
51
+ module.exports = 'model_provider = "poe"\nmodel = "{{{model}}}"\nmodel_reasoning_effort = "{{reasoningEffort}}"\n\n[model_providers.poe]\nname = "poe"\nbase_url = "{{{baseUrl}}}"\nwire_api = "responses"\nexperimental_bearer_token = "{{apiKey}}"\n';
52
+ }
53
+ });
54
+
55
+ // src/cli/constants.ts
56
+ var FRONTIER_MODELS = [
57
+ "anthropic/claude-opus-4.6",
58
+ "anthropic/claude-sonnet-4.6",
59
+ "openai/gpt-5.2",
60
+ "google/gemini-3-pro"
61
+ ];
62
+ var DEFAULT_FRONTIER_MODEL = "anthropic/claude-sonnet-4.6";
63
+ var CLAUDE_CODE_VARIANTS = {
64
+ haiku: "anthropic/claude-haiku-4.5",
65
+ sonnet: "anthropic/claude-sonnet-4.6",
66
+ opus: "anthropic/claude-opus-4.6"
67
+ };
68
+ var DEFAULT_CLAUDE_CODE_MODEL = CLAUDE_CODE_VARIANTS.sonnet;
69
+ var CODEX_MODELS = [
70
+ "openai/gpt-5.2-codex",
71
+ "openai/gpt-5.2",
72
+ "openai/gpt-5.2-chat",
73
+ "openai/gpt-5.2-pro",
74
+ "openai/gpt-5.1",
75
+ "openai/gpt-5.1-codex-mini"
76
+ ];
77
+ var DEFAULT_CODEX_MODEL = CODEX_MODELS[0];
78
+ var KIMI_MODELS = [
79
+ "novitaai/kimi-k2.5",
80
+ "novitaai/kimi-k2-thinking"
81
+ ];
82
+ var DEFAULT_KIMI_MODEL = KIMI_MODELS[0];
83
+ var PROVIDER_NAME = "poe";
84
+
85
+ // packages/agent-spawn/src/run-command.ts
86
+ import { spawn } from "node:child_process";
87
+
88
+ // packages/agent-defs/src/agents/claude-code.ts
89
+ var claudeCodeAgent = {
90
+ id: "claude-code",
91
+ name: "claude-code",
92
+ label: "Claude Code",
93
+ summary: "Configure Claude Code to route through Poe.",
94
+ aliases: ["claude"],
95
+ binaryName: "claude",
96
+ configPath: "~/.claude/settings.json",
97
+ branding: {
98
+ colors: {
99
+ dark: "#C15F3C",
100
+ light: "#C15F3C"
101
+ }
102
+ }
103
+ };
104
+
105
+ // packages/agent-defs/src/agents/claude-desktop.ts
106
+ var claudeDesktopAgent = {
107
+ id: "claude-desktop",
108
+ name: "claude-desktop",
109
+ label: "Claude Desktop",
110
+ summary: "Anthropic's official desktop application for Claude",
111
+ configPath: "~/.claude/settings.json",
112
+ branding: {
113
+ colors: {
114
+ dark: "#D97757",
115
+ light: "#D97757"
116
+ }
117
+ }
118
+ };
119
+
120
+ // packages/agent-defs/src/agents/codex.ts
121
+ var codexAgent = {
122
+ id: "codex",
123
+ name: "codex",
124
+ label: "Codex",
125
+ summary: "Configure Codex to use Poe as the model provider.",
126
+ binaryName: "codex",
127
+ configPath: "~/.codex/config.toml",
128
+ branding: {
129
+ colors: {
130
+ dark: "#D5D9DF",
131
+ light: "#7A7F86"
132
+ }
133
+ }
134
+ };
135
+
136
+ // packages/agent-defs/src/agents/opencode.ts
137
+ var openCodeAgent = {
138
+ id: "opencode",
139
+ name: "opencode",
140
+ label: "OpenCode CLI",
141
+ summary: "Configure OpenCode CLI to use the Poe API.",
142
+ binaryName: "opencode",
143
+ configPath: "~/.config/opencode/config.json",
144
+ branding: {
145
+ colors: {
146
+ dark: "#4A4F55",
147
+ light: "#2F3338"
148
+ }
149
+ }
150
+ };
151
+
152
+ // packages/agent-defs/src/agents/kimi.ts
153
+ var kimiAgent = {
154
+ id: "kimi",
155
+ name: "kimi",
156
+ label: "Kimi",
157
+ summary: "Configure Kimi CLI to use Poe API",
158
+ aliases: ["kimi-cli"],
159
+ binaryName: "kimi",
160
+ configPath: "~/.kimi/config.toml",
161
+ branding: {
162
+ colors: {
163
+ dark: "#7B68EE",
164
+ light: "#6A5ACD"
165
+ }
166
+ }
167
+ };
168
+
169
+ // packages/agent-defs/src/registry.ts
170
+ var allAgents = [
171
+ claudeCodeAgent,
172
+ claudeDesktopAgent,
173
+ codexAgent,
174
+ openCodeAgent,
175
+ kimiAgent
176
+ ];
177
+ var lookup = /* @__PURE__ */ new Map();
178
+ for (const agent of allAgents) {
179
+ const values = [agent.id, agent.name, ...agent.aliases ?? []];
180
+ for (const value of values) {
181
+ const normalized = value.toLowerCase();
182
+ if (!lookup.has(normalized)) {
183
+ lookup.set(normalized, agent.id);
184
+ }
185
+ }
186
+ }
187
+ function resolveAgentId(input) {
188
+ if (!input) {
189
+ return void 0;
190
+ }
191
+ return lookup.get(input.toLowerCase());
192
+ }
193
+
194
+ // packages/agent-spawn/src/configs/claude-code.ts
195
+ var claudeCodeSpawnConfig = {
196
+ kind: "cli",
197
+ agentId: "claude-code",
198
+ // ACP adapter support: yes (adapter: "claude")
199
+ adapter: "claude",
200
+ promptFlag: "-p",
201
+ modelFlag: "--model",
202
+ modelStripProviderPrefix: true,
203
+ modelTransform: (model) => model.replaceAll(".", "-"),
204
+ defaultArgs: [
205
+ "--output-format",
206
+ "stream-json",
207
+ "--verbose"
208
+ ],
209
+ modes: {
210
+ yolo: ["--dangerously-skip-permissions"],
211
+ edit: ["--permission-mode", "acceptEdits", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,NotebookEdit"],
212
+ read: ["--permission-mode", "plan"]
213
+ },
214
+ stdinMode: {
215
+ omitPrompt: true,
216
+ extraArgs: ["--input-format", "text"]
217
+ },
218
+ interactive: {
219
+ defaultArgs: []
220
+ },
221
+ resumeCommand: (threadId) => ["--resume", threadId]
222
+ };
223
+
224
+ // packages/agent-spawn/src/configs/codex.ts
225
+ var codexSpawnConfig = {
226
+ kind: "cli",
227
+ agentId: "codex",
228
+ // ACP adapter support: yes (adapter: "codex")
229
+ adapter: "codex",
230
+ promptFlag: "exec",
231
+ modelFlag: "--model",
232
+ modelStripProviderPrefix: true,
233
+ defaultArgs: ["--skip-git-repo-check", "--json"],
234
+ modes: {
235
+ yolo: ["-s", "danger-full-access"],
236
+ edit: ["-s", "workspace-write"],
237
+ read: ["-s", "read-only"]
238
+ },
239
+ stdinMode: {
240
+ omitPrompt: true,
241
+ extraArgs: ["-"]
242
+ },
243
+ interactive: {
244
+ defaultArgs: ["-a", "never"]
245
+ },
246
+ resumeCommand: (threadId, cwd) => ["resume", "-C", cwd, threadId]
247
+ };
248
+
249
+ // packages/agent-spawn/src/configs/opencode.ts
250
+ var openCodeSpawnConfig = {
251
+ kind: "cli",
252
+ agentId: "opencode",
253
+ // ACP adapter support: yes (adapter: "opencode").
254
+ // OpenCode's `--format json` emits NDJSON events with `{ type, sessionID, part }`
255
+ // (no `{ event, ... }` field), so it needs the OpenCode adapter (not "native").
256
+ adapter: "opencode",
257
+ promptFlag: "run",
258
+ modelFlag: "--model",
259
+ modelStripProviderPrefix: false,
260
+ modelTransform: (model) => {
261
+ return model.startsWith("poe/") ? model : `poe/${model}`;
262
+ },
263
+ defaultArgs: ["--format", "json"],
264
+ modes: {
265
+ yolo: [],
266
+ edit: [],
267
+ read: ["--agent", "plan"]
268
+ },
269
+ interactive: {
270
+ defaultArgs: [],
271
+ promptFlag: "--prompt"
272
+ },
273
+ resumeCommand: (threadId, cwd) => [cwd, "--session", threadId]
274
+ };
275
+
276
+ // packages/agent-spawn/src/configs/kimi.ts
277
+ var kimiSpawnConfig = {
278
+ kind: "cli",
279
+ agentId: "kimi",
280
+ // ACP adapter support: yes (adapter: "kimi").
281
+ // Kimi's `--output-format stream-json` emits OpenAI-style `{ role, content }` JSON
282
+ // (no `{ event, ... }` field), so it needs the Kimi adapter (not "native").
283
+ adapter: "kimi",
284
+ promptFlag: "-p",
285
+ modelStripProviderPrefix: true,
286
+ defaultArgs: ["--print", "--output-format", "stream-json"],
287
+ modes: {
288
+ yolo: ["--yolo"],
289
+ edit: [],
290
+ read: []
291
+ },
292
+ stdinMode: {
293
+ omitPrompt: true,
294
+ extraArgs: ["--input-format", "stream-json"]
295
+ },
296
+ interactive: {
297
+ defaultArgs: [],
298
+ promptFlag: "-p"
299
+ },
300
+ resumeCommand: (threadId, cwd) => ["--session", threadId, "--work-dir", cwd]
301
+ };
302
+
303
+ // packages/agent-spawn/src/configs/index.ts
304
+ var allSpawnConfigs = [
305
+ claudeCodeSpawnConfig,
306
+ codexSpawnConfig,
307
+ openCodeSpawnConfig,
308
+ kimiSpawnConfig
309
+ ];
310
+ var lookup2 = /* @__PURE__ */ new Map();
311
+ for (const config of allSpawnConfigs) {
312
+ lookup2.set(config.agentId, config);
313
+ }
314
+ function getSpawnConfig(input) {
315
+ const resolvedId = resolveAgentId(input);
316
+ if (!resolvedId) {
317
+ return void 0;
318
+ }
319
+ return lookup2.get(resolvedId);
320
+ }
321
+
322
+ // packages/agent-spawn/src/spawn.ts
323
+ import { spawn as spawnChildProcess } from "node:child_process";
324
+
325
+ // packages/agent-spawn/src/configs/resolve-config.ts
326
+ function resolveConfig(agentId) {
327
+ const resolvedAgentId = resolveAgentId(agentId);
328
+ if (!resolvedAgentId) {
329
+ throw new Error(`Unknown agent "${agentId}".`);
330
+ }
331
+ const agentDefinition = allAgents.find((agent) => agent.id === resolvedAgentId);
332
+ if (!agentDefinition) {
333
+ throw new Error(`Unknown agent "${agentId}".`);
334
+ }
335
+ const spawnConfig = getSpawnConfig(resolvedAgentId);
336
+ const binaryName = agentDefinition.binaryName;
337
+ return { agentId: resolvedAgentId, binaryName, spawnConfig };
338
+ }
339
+
340
+ // packages/agent-spawn/src/model-utils.ts
341
+ function stripModelNamespace(model) {
342
+ const slashIndex = model.indexOf("/");
343
+ return slashIndex === -1 ? model : model.slice(slashIndex + 1);
344
+ }
345
+
346
+ // packages/agent-spawn/src/spawn.ts
347
+ function resolveCliConfig(agentId) {
348
+ const resolved = resolveConfig(agentId);
349
+ if (!resolved.spawnConfig) {
350
+ throw new Error(`Agent "${resolved.agentId}" has no spawn config.`);
351
+ }
352
+ if (resolved.spawnConfig.kind !== "cli") {
353
+ throw new Error(`Agent "${resolved.agentId}" does not support CLI spawn.`);
354
+ }
355
+ if (!resolved.binaryName) {
356
+ throw new Error(`Agent "${resolved.agentId}" has no binaryName.`);
357
+ }
358
+ return {
359
+ agentId: resolved.agentId,
360
+ binaryName: resolved.binaryName,
361
+ spawnConfig: resolved.spawnConfig
362
+ };
363
+ }
364
+ function buildCliArgs(config, options, stdinMode) {
365
+ const args = stdinMode ? [
366
+ config.promptFlag,
367
+ ...stdinMode.omitPrompt ? [] : [options.prompt],
368
+ ...stdinMode.extraArgs
369
+ ] : [config.promptFlag, options.prompt];
370
+ if (options.model && config.modelFlag) {
371
+ let model = config.modelStripProviderPrefix ? stripModelNamespace(options.model) : options.model;
372
+ if (config.modelTransform) model = config.modelTransform(model);
373
+ args.push(config.modelFlag, model);
374
+ }
375
+ args.push(...config.defaultArgs);
376
+ args.push(...config.modes[options.mode ?? "yolo"]);
377
+ if (options.args && options.args.length > 0) {
378
+ args.push(...options.args);
379
+ }
380
+ return args;
381
+ }
382
+ function buildSpawnArgs(agentId, options) {
383
+ const { binaryName, spawnConfig } = resolveCliConfig(agentId);
384
+ const stdinMode = options.useStdin && spawnConfig.stdinMode ? spawnConfig.stdinMode : void 0;
385
+ return { binaryName, args: buildCliArgs(spawnConfig, options, stdinMode) };
386
+ }
387
+
388
+ // packages/agent-spawn/src/spawn-interactive.ts
389
+ import { spawn as spawnChildProcess2 } from "node:child_process";
390
+
391
+ // packages/design-system/src/tokens/colors.ts
392
+ import chalk from "chalk";
393
+ var dark = {
394
+ header: (text4) => chalk.magentaBright.bold(text4),
395
+ divider: (text4) => chalk.dim(text4),
396
+ prompt: (text4) => chalk.cyan(text4),
397
+ number: (text4) => chalk.cyanBright(text4),
398
+ intro: (text4) => chalk.bgMagenta.white(` Poe - ${text4} `),
399
+ resolvedSymbol: chalk.magenta("\u25C7"),
400
+ errorSymbol: chalk.red("\u25A0"),
401
+ accent: (text4) => chalk.cyan(text4),
402
+ muted: (text4) => chalk.dim(text4),
403
+ success: (text4) => chalk.green(text4),
404
+ warning: (text4) => chalk.yellow(text4),
405
+ error: (text4) => chalk.red(text4),
406
+ info: (text4) => chalk.magenta(text4),
407
+ badge: (text4) => chalk.bgYellow.black(` ${text4} `)
408
+ };
409
+ var light = {
410
+ header: (text4) => chalk.hex("#a200ff").bold(text4),
411
+ divider: (text4) => chalk.hex("#666666")(text4),
412
+ prompt: (text4) => chalk.hex("#006699").bold(text4),
413
+ number: (text4) => chalk.hex("#0077cc").bold(text4),
414
+ intro: (text4) => chalk.bgHex("#a200ff").white(` Poe - ${text4} `),
415
+ resolvedSymbol: chalk.hex("#a200ff")("\u25C7"),
416
+ errorSymbol: chalk.hex("#cc0000")("\u25A0"),
417
+ accent: (text4) => chalk.hex("#006699").bold(text4),
418
+ muted: (text4) => chalk.hex("#666666")(text4),
419
+ success: (text4) => chalk.hex("#008800")(text4),
420
+ warning: (text4) => chalk.hex("#cc6600")(text4),
421
+ error: (text4) => chalk.hex("#cc0000")(text4),
422
+ info: (text4) => chalk.hex("#a200ff")(text4),
423
+ badge: (text4) => chalk.bgHex("#cc6600").white(` ${text4} `)
424
+ };
425
+
426
+ // packages/design-system/src/tokens/typography.ts
427
+ import chalk2 from "chalk";
428
+
429
+ // packages/design-system/src/components/text.ts
430
+ import chalk3 from "chalk";
431
+
432
+ // packages/design-system/src/internal/theme-detect.ts
433
+ function detectThemeFromEnv(env) {
434
+ const apple = env.APPLE_INTERFACE_STYLE;
435
+ if (typeof apple === "string") {
436
+ return apple.toLowerCase() === "dark" ? "dark" : "light";
437
+ }
438
+ const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
439
+ if (typeof vscodeKind === "string") {
440
+ const normalized = vscodeKind.toLowerCase();
441
+ if (normalized.includes("light")) {
442
+ return "light";
443
+ }
444
+ if (normalized.includes("dark")) {
445
+ return "dark";
446
+ }
447
+ }
448
+ const colorFGBG = env.COLORFGBG;
449
+ if (typeof colorFGBG === "string") {
450
+ const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
451
+ const background = parts.at(-1);
452
+ if (Number.isFinite(background)) {
453
+ return background >= 8 ? "light" : "dark";
454
+ }
455
+ }
456
+ return void 0;
457
+ }
458
+ function resolveThemeName(env = process.env) {
459
+ const raw = env.POE_CODE_THEME?.toLowerCase();
460
+ if (raw === "light" || raw === "dark") {
461
+ return raw;
462
+ }
463
+ const detected = detectThemeFromEnv(env);
464
+ if (detected) {
465
+ return detected;
466
+ }
467
+ return "dark";
468
+ }
469
+ var cachedTheme;
470
+ function getTheme(env) {
471
+ if (cachedTheme) {
472
+ return cachedTheme;
473
+ }
474
+ const themeName = resolveThemeName(env);
475
+ cachedTheme = themeName === "light" ? light : dark;
476
+ return cachedTheme;
477
+ }
478
+
479
+ // packages/design-system/src/components/symbols.ts
480
+ import chalk4 from "chalk";
481
+ var symbols = {
482
+ get info() {
483
+ return chalk4.magenta("\u25CF");
484
+ },
485
+ get success() {
486
+ return chalk4.magenta("\u25C6");
487
+ },
488
+ get resolved() {
489
+ const theme = getTheme();
490
+ return theme.resolvedSymbol;
491
+ },
492
+ get errorResolved() {
493
+ const theme = getTheme();
494
+ return theme.errorSymbol;
495
+ },
496
+ bar: "\u2502",
497
+ cornerTopRight: "\u256E",
498
+ cornerBottomRight: "\u256F",
499
+ warning: "\u25B2",
500
+ active: "\u25C6",
501
+ inactive: "\u25CB"
502
+ };
503
+
504
+ // packages/design-system/src/components/logger.ts
505
+ import { log } from "@clack/prompts";
506
+ import chalk5 from "chalk";
507
+
508
+ // packages/design-system/src/internal/output-format.ts
509
+ var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
510
+ var cached;
511
+ function resolveOutputFormat(env = process.env) {
512
+ if (cached) {
513
+ return cached;
514
+ }
515
+ const raw = env.OUTPUT_FORMAT?.toLowerCase();
516
+ cached = VALID_FORMATS.has(raw) ? raw : "terminal";
517
+ return cached;
518
+ }
519
+
520
+ // packages/design-system/src/components/logger.ts
521
+ function createLogger(emitter) {
522
+ const emit = (level, message) => {
523
+ if (emitter) {
524
+ emitter(message);
525
+ return;
526
+ }
527
+ if (resolveOutputFormat() !== "terminal") {
528
+ process.stdout.write(message + "\n");
529
+ return;
530
+ }
531
+ if (level === "success") {
532
+ log.message(message, { symbol: symbols.success });
533
+ return;
534
+ }
535
+ if (level === "warn") {
536
+ log.warn(message);
537
+ return;
538
+ }
539
+ if (level === "error") {
540
+ log.error(message);
541
+ return;
542
+ }
543
+ log.message(message, { symbol: symbols.info });
544
+ };
545
+ return {
546
+ info(message) {
547
+ emit("info", message);
548
+ },
549
+ success(message) {
550
+ emit("success", message);
39
551
  },
40
- isolatedEnv: {
41
- agentBinary: openCodeAgent.binaryName,
42
- configProbe: {
43
- kind: "isolatedFile",
44
- relativePath: ".config/opencode/config.json"
552
+ warn(message) {
553
+ emit("warn", message);
554
+ },
555
+ error(message) {
556
+ emit("error", message);
557
+ },
558
+ resolved(label, value) {
559
+ if (emitter) {
560
+ emitter(`${label}: ${value}`);
561
+ return;
562
+ }
563
+ log.message(`${label}
564
+ ${value}`, { symbol: symbols.resolved });
565
+ },
566
+ errorResolved(label, value) {
567
+ if (emitter) {
568
+ emitter(`${label}: ${value}`);
569
+ return;
570
+ }
571
+ log.message(`${label}
572
+ ${value}`, { symbol: symbols.errorResolved });
573
+ },
574
+ message(message, symbol) {
575
+ if (emitter) {
576
+ emitter(message);
577
+ return;
578
+ }
579
+ log.message(message, { symbol: symbol ?? chalk5.gray("\u2502") });
580
+ }
581
+ };
582
+ }
583
+ var logger = createLogger();
584
+
585
+ // packages/design-system/src/components/table.ts
586
+ import { Table } from "console-table-printer";
587
+
588
+ // packages/design-system/src/acp/components.ts
589
+ import chalk6 from "chalk";
590
+ var AGENT_PREFIX = `${chalk6.green.bold("\u2713")} agent: `;
591
+
592
+ // packages/design-system/src/prompts/index.ts
593
+ import * as clack from "@clack/prompts";
594
+ import { isCancel, cancel, log as log2 } from "@clack/prompts";
595
+
596
+ // packages/design-system/src/static/spinner.ts
597
+ import chalk7 from "chalk";
598
+
599
+ // packages/design-system/src/static/menu.ts
600
+ import chalk8 from "chalk";
601
+
602
+ // packages/agent-spawn/src/acp/spawn.ts
603
+ import { spawn as spawnChildProcess3 } from "node:child_process";
604
+
605
+ // src/utils/command-checks.ts
606
+ function formatCommandRunnerResult(result) {
607
+ const stdout = result.stdout.length > 0 ? result.stdout : "<empty>";
608
+ const stderr = result.stderr.length > 0 ? result.stderr : "<empty>";
609
+ return `stdout:
610
+ ${stdout}
611
+ stderr:
612
+ ${stderr}`;
613
+ }
614
+ function createSpawnHealthCheck(agentId, options) {
615
+ const prompt = `Output exactly: ${options.expectedOutput}`;
616
+ const { binaryName, args } = buildSpawnArgs(agentId, {
617
+ prompt,
618
+ model: options.model,
619
+ mode: "yolo"
620
+ });
621
+ return {
622
+ id: `${agentId}-cli-health`,
623
+ description: `spawn ${agentId} (expecting "${options.expectedOutput}")`,
624
+ async run(context) {
625
+ if (context.isDryRun) {
626
+ context.logDryRun?.(
627
+ `Dry run: ${[binaryName, ...args].join(" ")} (expecting "${options.expectedOutput}")`
628
+ );
629
+ return;
630
+ }
631
+ const result = await context.runCommand(binaryName, args);
632
+ if (result.exitCode !== 0) {
633
+ throw new Error(
634
+ `spawn ${agentId} failed with exit code ${result.exitCode}.
635
+ ${formatCommandRunnerResult(result)}`
636
+ );
637
+ }
638
+ if (!result.stdout.includes(options.expectedOutput)) {
639
+ throw new Error(
640
+ `spawn ${agentId}: expected "${options.expectedOutput}" in stdout.
641
+ ${formatCommandRunnerResult(result)}`
642
+ );
643
+ }
644
+ }
645
+ };
646
+ }
647
+ function createBinaryExistsCheck(binaryName, id, description) {
648
+ return {
649
+ id,
650
+ description,
651
+ async run({ runCommand: runCommand2 }) {
652
+ const commonPaths = [
653
+ `/usr/local/bin/${binaryName}`,
654
+ `/usr/bin/${binaryName}`,
655
+ `$HOME/.local/bin/${binaryName}`,
656
+ `$HOME/.claude/local/bin/${binaryName}`
657
+ ];
658
+ const detectors = [
659
+ {
660
+ command: "which",
661
+ args: [binaryName],
662
+ validate: (result) => result.exitCode === 0
663
+ },
664
+ {
665
+ command: "where",
666
+ args: [binaryName],
667
+ validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
45
668
  },
46
- env: {
47
- XDG_CONFIG_HOME: { kind: "isolatedDir", relativePath: ".config" },
48
- XDG_DATA_HOME: { kind: "isolatedDir", relativePath: ".local/share" }
669
+ // Check common installation paths using shell expansion for $HOME
670
+ {
671
+ command: "sh",
672
+ args: [
673
+ "-c",
674
+ commonPaths.map((p) => `test -f "${p}"`).join(" || ")
675
+ ],
676
+ validate: (result) => result.exitCode === 0
677
+ }
678
+ ];
679
+ for (const detector of detectors) {
680
+ const result = await runCommand2(detector.command, detector.args);
681
+ if (detector.validate(result)) {
682
+ return;
49
683
  }
684
+ }
685
+ throw new Error(`${binaryName} CLI binary not found on PATH.`);
686
+ }
687
+ };
688
+ }
689
+
690
+ // packages/config-mutations/src/mutations/config-mutation.ts
691
+ function merge(options) {
692
+ return {
693
+ kind: "configMerge",
694
+ target: options.target,
695
+ value: options.value,
696
+ format: options.format,
697
+ pruneByPrefix: options.pruneByPrefix,
698
+ label: options.label
699
+ };
700
+ }
701
+ function prune(options) {
702
+ return {
703
+ kind: "configPrune",
704
+ target: options.target,
705
+ shape: options.shape,
706
+ format: options.format,
707
+ onlyIf: options.onlyIf,
708
+ label: options.label
709
+ };
710
+ }
711
+ function transform(options) {
712
+ return {
713
+ kind: "configTransform",
714
+ target: options.target,
715
+ format: options.format,
716
+ transform: options.transform,
717
+ label: options.label
718
+ };
719
+ }
720
+ var configMutation = {
721
+ merge,
722
+ prune,
723
+ transform
724
+ };
725
+
726
+ // packages/config-mutations/src/mutations/file-mutation.ts
727
+ function ensureDirectory(options) {
728
+ return {
729
+ kind: "ensureDirectory",
730
+ path: options.path,
731
+ label: options.label
732
+ };
733
+ }
734
+ function remove(options) {
735
+ return {
736
+ kind: "removeFile",
737
+ target: options.target,
738
+ whenEmpty: options.whenEmpty,
739
+ whenContentMatches: options.whenContentMatches,
740
+ label: options.label
741
+ };
742
+ }
743
+ function removeDirectory(options) {
744
+ return {
745
+ kind: "removeDirectory",
746
+ path: options.path,
747
+ force: options.force,
748
+ label: options.label
749
+ };
750
+ }
751
+ function chmod(options) {
752
+ return {
753
+ kind: "chmod",
754
+ target: options.target,
755
+ mode: options.mode,
756
+ label: options.label
757
+ };
758
+ }
759
+ function backup(options) {
760
+ return {
761
+ kind: "backup",
762
+ target: options.target,
763
+ label: options.label
764
+ };
765
+ }
766
+ var fileMutation = {
767
+ ensureDirectory,
768
+ remove,
769
+ removeDirectory,
770
+ chmod,
771
+ backup
772
+ };
773
+
774
+ // packages/config-mutations/src/execution/apply-mutation.ts
775
+ import Mustache from "mustache";
776
+
777
+ // packages/config-mutations/src/formats/json.ts
778
+ import * as jsonc from "jsonc-parser";
779
+ function isConfigObject(value) {
780
+ return typeof value === "object" && value !== null && !Array.isArray(value);
781
+ }
782
+ function parse2(content) {
783
+ if (!content || content.trim() === "") {
784
+ return {};
785
+ }
786
+ const errors = [];
787
+ const parsed = jsonc.parse(content, errors, {
788
+ allowTrailingComma: true,
789
+ disallowComments: false
790
+ });
791
+ if (errors.length > 0) {
792
+ throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
793
+ }
794
+ if (parsed === null || parsed === void 0) {
795
+ return {};
796
+ }
797
+ if (!isConfigObject(parsed)) {
798
+ throw new Error("Expected JSON object.");
799
+ }
800
+ return parsed;
801
+ }
802
+ function serialize(obj) {
803
+ return `${JSON.stringify(obj, null, 2)}
804
+ `;
805
+ }
806
+ function merge2(base, patch) {
807
+ const result = { ...base };
808
+ for (const [key, value] of Object.entries(patch)) {
809
+ if (value === void 0) {
810
+ continue;
811
+ }
812
+ const existing = result[key];
813
+ if (isConfigObject(existing) && isConfigObject(value)) {
814
+ result[key] = merge2(existing, value);
815
+ continue;
816
+ }
817
+ result[key] = value;
818
+ }
819
+ return result;
820
+ }
821
+ function prune2(obj, shape) {
822
+ let changed = false;
823
+ const result = { ...obj };
824
+ for (const [key, pattern] of Object.entries(shape)) {
825
+ if (!(key in result)) {
826
+ continue;
827
+ }
828
+ const current = result[key];
829
+ if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
830
+ delete result[key];
831
+ changed = true;
832
+ continue;
833
+ }
834
+ if (isConfigObject(pattern) && isConfigObject(current)) {
835
+ const { changed: childChanged, result: childResult } = prune2(
836
+ current,
837
+ pattern
838
+ );
839
+ if (childChanged) {
840
+ changed = true;
841
+ }
842
+ if (Object.keys(childResult).length === 0) {
843
+ delete result[key];
844
+ } else {
845
+ result[key] = childResult;
846
+ }
847
+ continue;
848
+ }
849
+ delete result[key];
850
+ changed = true;
851
+ }
852
+ return { changed, result };
853
+ }
854
+ var jsonFormat = {
855
+ parse: parse2,
856
+ serialize,
857
+ merge: merge2,
858
+ prune: prune2
859
+ };
860
+
861
+ // packages/config-mutations/src/formats/toml.ts
862
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
863
+ function isConfigObject2(value) {
864
+ return typeof value === "object" && value !== null && !Array.isArray(value);
865
+ }
866
+ function parse3(content) {
867
+ if (!content || content.trim() === "") {
868
+ return {};
869
+ }
870
+ const parsed = parseToml(content);
871
+ if (!isConfigObject2(parsed)) {
872
+ throw new Error("Expected TOML document to be a table.");
873
+ }
874
+ return parsed;
875
+ }
876
+ function serialize2(obj) {
877
+ const serialized = stringifyToml(obj);
878
+ return serialized.endsWith("\n") ? serialized : `${serialized}
879
+ `;
880
+ }
881
+ function merge3(base, patch) {
882
+ const result = { ...base };
883
+ for (const [key, value] of Object.entries(patch)) {
884
+ if (value === void 0) {
885
+ continue;
886
+ }
887
+ const existing = result[key];
888
+ if (isConfigObject2(existing) && isConfigObject2(value)) {
889
+ result[key] = merge3(existing, value);
890
+ continue;
891
+ }
892
+ result[key] = value;
893
+ }
894
+ return result;
895
+ }
896
+ function prune3(obj, shape) {
897
+ let changed = false;
898
+ const result = { ...obj };
899
+ for (const [key, pattern] of Object.entries(shape)) {
900
+ if (!(key in result)) {
901
+ continue;
902
+ }
903
+ const current = result[key];
904
+ if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
905
+ delete result[key];
906
+ changed = true;
907
+ continue;
908
+ }
909
+ if (isConfigObject2(pattern) && isConfigObject2(current)) {
910
+ const { changed: childChanged, result: childResult } = prune3(
911
+ current,
912
+ pattern
913
+ );
914
+ if (childChanged) {
915
+ changed = true;
916
+ }
917
+ if (Object.keys(childResult).length === 0) {
918
+ delete result[key];
919
+ } else {
920
+ result[key] = childResult;
921
+ }
922
+ continue;
923
+ }
924
+ delete result[key];
925
+ changed = true;
926
+ }
927
+ return { changed, result };
928
+ }
929
+ var tomlFormat = {
930
+ parse: parse3,
931
+ serialize: serialize2,
932
+ merge: merge3,
933
+ prune: prune3
934
+ };
935
+
936
+ // packages/config-mutations/src/formats/index.ts
937
+ var formatRegistry = {
938
+ json: jsonFormat,
939
+ toml: tomlFormat
940
+ };
941
+ var extensionMap = {
942
+ ".json": "json",
943
+ ".toml": "toml"
944
+ };
945
+ function getConfigFormat(pathOrFormat) {
946
+ if (pathOrFormat in formatRegistry) {
947
+ return formatRegistry[pathOrFormat];
948
+ }
949
+ const ext = getExtension(pathOrFormat);
950
+ const formatName = extensionMap[ext];
951
+ if (!formatName) {
952
+ throw new Error(
953
+ `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
954
+ );
955
+ }
956
+ return formatRegistry[formatName];
957
+ }
958
+ function detectFormat(path2) {
959
+ const ext = getExtension(path2);
960
+ return extensionMap[ext];
961
+ }
962
+ function getExtension(path2) {
963
+ const lastDot = path2.lastIndexOf(".");
964
+ if (lastDot === -1) {
965
+ return "";
966
+ }
967
+ return path2.slice(lastDot).toLowerCase();
968
+ }
969
+
970
+ // packages/config-mutations/src/execution/path-utils.ts
971
+ import path from "node:path";
972
+ function expandHome(targetPath, homeDir) {
973
+ if (!targetPath?.startsWith("~")) {
974
+ return targetPath;
975
+ }
976
+ if (targetPath.startsWith("~./")) {
977
+ targetPath = `~/.${targetPath.slice(3)}`;
978
+ }
979
+ let remainder = targetPath.slice(1);
980
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
981
+ remainder = remainder.slice(1);
982
+ } else if (remainder.startsWith(".")) {
983
+ remainder = remainder.slice(1);
984
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
985
+ remainder = remainder.slice(1);
986
+ }
987
+ }
988
+ return remainder.length === 0 ? homeDir : path.join(homeDir, remainder);
989
+ }
990
+ function validateHomePath(targetPath) {
991
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
992
+ throw new Error("Target path must be a non-empty string.");
993
+ }
994
+ if (!targetPath.startsWith("~")) {
995
+ throw new Error(
996
+ `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
997
+ );
998
+ }
999
+ }
1000
+ function resolvePath(rawPath, homeDir, pathMapper) {
1001
+ validateHomePath(rawPath);
1002
+ const expanded = expandHome(rawPath, homeDir);
1003
+ if (!pathMapper) {
1004
+ return expanded;
1005
+ }
1006
+ const rawDirectory = path.dirname(expanded);
1007
+ const mappedDirectory = pathMapper.mapTargetDirectory({
1008
+ targetDirectory: rawDirectory
1009
+ });
1010
+ const filename = path.basename(expanded);
1011
+ return filename.length === 0 ? mappedDirectory : path.join(mappedDirectory, filename);
1012
+ }
1013
+
1014
+ // packages/config-mutations/src/fs-utils.ts
1015
+ function isNotFound(error) {
1016
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1017
+ }
1018
+ async function readFileIfExists(fs, target) {
1019
+ try {
1020
+ return await fs.readFile(target, "utf8");
1021
+ } catch (error) {
1022
+ if (isNotFound(error)) {
1023
+ return null;
1024
+ }
1025
+ throw error;
1026
+ }
1027
+ }
1028
+ async function pathExists(fs, target) {
1029
+ try {
1030
+ await fs.stat(target);
1031
+ return true;
1032
+ } catch (error) {
1033
+ if (isNotFound(error)) {
1034
+ return false;
1035
+ }
1036
+ throw error;
1037
+ }
1038
+ }
1039
+ function createTimestamp() {
1040
+ return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
1041
+ }
1042
+
1043
+ // packages/config-mutations/src/execution/apply-mutation.ts
1044
+ function resolveValue(resolver, options) {
1045
+ if (typeof resolver === "function") {
1046
+ return resolver(options);
1047
+ }
1048
+ return resolver;
1049
+ }
1050
+ function createInvalidDocumentBackupPath(targetPath) {
1051
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
1052
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
1053
+ }
1054
+ async function backupInvalidDocument(fs, targetPath, content) {
1055
+ const backupPath = createInvalidDocumentBackupPath(targetPath);
1056
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
1057
+ }
1058
+ function describeMutation(kind, targetPath) {
1059
+ const displayPath = targetPath ?? "target";
1060
+ switch (kind) {
1061
+ case "ensureDirectory":
1062
+ return `Create ${displayPath}`;
1063
+ case "removeDirectory":
1064
+ return `Remove directory ${displayPath}`;
1065
+ case "backup":
1066
+ return `Backup ${displayPath}`;
1067
+ case "templateWrite":
1068
+ return `Write ${displayPath}`;
1069
+ case "chmod":
1070
+ return `Set permissions on ${displayPath}`;
1071
+ case "removeFile":
1072
+ return `Remove ${displayPath}`;
1073
+ case "configMerge":
1074
+ case "configPrune":
1075
+ case "configTransform":
1076
+ case "templateMergeToml":
1077
+ case "templateMergeJson":
1078
+ return `Update ${displayPath}`;
1079
+ default:
1080
+ return "Operation";
1081
+ }
1082
+ }
1083
+ function pruneKeysByPrefix(table, prefix) {
1084
+ const result = {};
1085
+ for (const [key, value] of Object.entries(table)) {
1086
+ if (!key.startsWith(prefix)) {
1087
+ result[key] = value;
1088
+ }
1089
+ }
1090
+ return result;
1091
+ }
1092
+ function isConfigObject3(value) {
1093
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1094
+ }
1095
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
1096
+ const result = { ...base };
1097
+ const prefixMap = pruneByPrefix ?? {};
1098
+ for (const [key, value] of Object.entries(patch)) {
1099
+ const current = result[key];
1100
+ const prefix = prefixMap[key];
1101
+ if (isConfigObject3(current) && isConfigObject3(value)) {
1102
+ if (prefix) {
1103
+ const pruned = pruneKeysByPrefix(current, prefix);
1104
+ result[key] = { ...pruned, ...value };
1105
+ } else {
1106
+ result[key] = mergeWithPruneByPrefix(
1107
+ current,
1108
+ value,
1109
+ prefixMap
1110
+ );
1111
+ }
1112
+ continue;
1113
+ }
1114
+ result[key] = value;
1115
+ }
1116
+ return result;
1117
+ }
1118
+ async function applyMutation(mutation, context, options) {
1119
+ switch (mutation.kind) {
1120
+ case "ensureDirectory":
1121
+ return applyEnsureDirectory(mutation, context, options);
1122
+ case "removeDirectory":
1123
+ return applyRemoveDirectory(mutation, context, options);
1124
+ case "removeFile":
1125
+ return applyRemoveFile(mutation, context, options);
1126
+ case "chmod":
1127
+ return applyChmod(mutation, context, options);
1128
+ case "backup":
1129
+ return applyBackup(mutation, context, options);
1130
+ case "configMerge":
1131
+ return applyConfigMerge(mutation, context, options);
1132
+ case "configPrune":
1133
+ return applyConfigPrune(mutation, context, options);
1134
+ case "configTransform":
1135
+ return applyConfigTransform(mutation, context, options);
1136
+ case "templateWrite":
1137
+ return applyTemplateWrite(mutation, context, options);
1138
+ case "templateMergeToml":
1139
+ return applyTemplateMerge(mutation, context, options, "toml");
1140
+ case "templateMergeJson":
1141
+ return applyTemplateMerge(mutation, context, options, "json");
1142
+ default: {
1143
+ const never = mutation;
1144
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
1145
+ }
1146
+ }
1147
+ }
1148
+ async function applyEnsureDirectory(mutation, context, options) {
1149
+ const rawPath = resolveValue(mutation.path, options);
1150
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1151
+ const details = {
1152
+ kind: mutation.kind,
1153
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1154
+ targetPath
1155
+ };
1156
+ const existed = await pathExists(context.fs, targetPath);
1157
+ if (!context.dryRun) {
1158
+ await context.fs.mkdir(targetPath, { recursive: true });
1159
+ }
1160
+ return {
1161
+ outcome: {
1162
+ changed: !existed,
1163
+ effect: "mkdir",
1164
+ detail: existed ? "noop" : "create"
1165
+ },
1166
+ details
1167
+ };
1168
+ }
1169
+ async function applyRemoveDirectory(mutation, context, options) {
1170
+ const rawPath = resolveValue(mutation.path, options);
1171
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1172
+ const details = {
1173
+ kind: mutation.kind,
1174
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1175
+ targetPath
1176
+ };
1177
+ const existed = await pathExists(context.fs, targetPath);
1178
+ if (!existed) {
1179
+ return {
1180
+ outcome: { changed: false, effect: "none", detail: "noop" },
1181
+ details
1182
+ };
1183
+ }
1184
+ if (typeof context.fs.rm !== "function") {
1185
+ return {
1186
+ outcome: { changed: false, effect: "none", detail: "noop" },
1187
+ details
1188
+ };
1189
+ }
1190
+ if (mutation.force) {
1191
+ if (!context.dryRun) {
1192
+ await context.fs.rm(targetPath, { recursive: true, force: true });
1193
+ }
1194
+ return {
1195
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1196
+ details
1197
+ };
1198
+ }
1199
+ const entries = await context.fs.readdir(targetPath);
1200
+ if (entries.length > 0) {
1201
+ return {
1202
+ outcome: { changed: false, effect: "none", detail: "noop" },
1203
+ details
1204
+ };
1205
+ }
1206
+ if (!context.dryRun) {
1207
+ await context.fs.rm(targetPath, { recursive: true, force: true });
1208
+ }
1209
+ return {
1210
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1211
+ details
1212
+ };
1213
+ }
1214
+ async function applyRemoveFile(mutation, context, options) {
1215
+ const rawPath = resolveValue(mutation.target, options);
1216
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1217
+ const details = {
1218
+ kind: mutation.kind,
1219
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1220
+ targetPath
1221
+ };
1222
+ try {
1223
+ const content = await context.fs.readFile(targetPath, "utf8");
1224
+ const trimmed = content.trim();
1225
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
1226
+ return {
1227
+ outcome: { changed: false, effect: "none", detail: "noop" },
1228
+ details
1229
+ };
1230
+ }
1231
+ if (mutation.whenEmpty && trimmed.length > 0) {
1232
+ return {
1233
+ outcome: { changed: false, effect: "none", detail: "noop" },
1234
+ details
1235
+ };
1236
+ }
1237
+ if (!context.dryRun) {
1238
+ await context.fs.unlink(targetPath);
1239
+ }
1240
+ return {
1241
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1242
+ details
1243
+ };
1244
+ } catch (error) {
1245
+ if (isNotFound(error)) {
1246
+ return {
1247
+ outcome: { changed: false, effect: "none", detail: "noop" },
1248
+ details
1249
+ };
1250
+ }
1251
+ throw error;
1252
+ }
1253
+ }
1254
+ async function applyChmod(mutation, context, options) {
1255
+ const rawPath = resolveValue(mutation.target, options);
1256
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1257
+ const details = {
1258
+ kind: mutation.kind,
1259
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1260
+ targetPath
1261
+ };
1262
+ if (typeof context.fs.chmod !== "function") {
1263
+ return {
1264
+ outcome: { changed: false, effect: "none", detail: "noop" },
1265
+ details
1266
+ };
1267
+ }
1268
+ try {
1269
+ const stat = await context.fs.stat(targetPath);
1270
+ const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
1271
+ if (currentMode === mutation.mode) {
1272
+ return {
1273
+ outcome: { changed: false, effect: "none", detail: "noop" },
1274
+ details
1275
+ };
1276
+ }
1277
+ if (!context.dryRun) {
1278
+ await context.fs.chmod(targetPath, mutation.mode);
1279
+ }
1280
+ return {
1281
+ outcome: { changed: true, effect: "chmod", detail: "update" },
1282
+ details
1283
+ };
1284
+ } catch (error) {
1285
+ if (isNotFound(error)) {
1286
+ return {
1287
+ outcome: { changed: false, effect: "none", detail: "noop" },
1288
+ details
1289
+ };
1290
+ }
1291
+ throw error;
1292
+ }
1293
+ }
1294
+ async function applyBackup(mutation, context, options) {
1295
+ const rawPath = resolveValue(mutation.target, options);
1296
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1297
+ const details = {
1298
+ kind: mutation.kind,
1299
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1300
+ targetPath
1301
+ };
1302
+ const content = await readFileIfExists(context.fs, targetPath);
1303
+ if (content === null) {
1304
+ return {
1305
+ outcome: { changed: false, effect: "none", detail: "noop" },
1306
+ details
1307
+ };
1308
+ }
1309
+ if (!context.dryRun) {
1310
+ const backupPath = `${targetPath}.backup-${createTimestamp()}`;
1311
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
1312
+ }
1313
+ return {
1314
+ outcome: { changed: true, effect: "copy", detail: "backup" },
1315
+ details
1316
+ };
1317
+ }
1318
+ async function applyConfigMerge(mutation, context, options) {
1319
+ const rawPath = resolveValue(mutation.target, options);
1320
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1321
+ const details = {
1322
+ kind: mutation.kind,
1323
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1324
+ targetPath
1325
+ };
1326
+ const formatName = mutation.format ?? detectFormat(rawPath);
1327
+ if (!formatName) {
1328
+ throw new Error(
1329
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1330
+ );
1331
+ }
1332
+ const format = getConfigFormat(formatName);
1333
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1334
+ let current;
1335
+ try {
1336
+ current = rawContent === null ? {} : format.parse(rawContent);
1337
+ } catch {
1338
+ if (rawContent !== null) {
1339
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1340
+ }
1341
+ current = {};
1342
+ }
1343
+ const value = resolveValue(mutation.value, options);
1344
+ let merged;
1345
+ if (mutation.pruneByPrefix) {
1346
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
1347
+ } else {
1348
+ merged = format.merge(current, value);
1349
+ }
1350
+ const serialized = format.serialize(merged);
1351
+ const changed = serialized !== rawContent;
1352
+ if (changed && !context.dryRun) {
1353
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1354
+ }
1355
+ return {
1356
+ outcome: {
1357
+ changed,
1358
+ effect: changed ? "write" : "none",
1359
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
50
1360
  },
51
- manifest: {
52
- configure: [
53
- fileMutation.ensureDirectory({ path: "~/.config/opencode" }),
54
- configMutation.merge({
55
- target: "~/.config/opencode/config.json",
56
- value: (ctx) => {
57
- const { model } = (ctx ?? {});
58
- return {
59
- $schema: "https://opencode.ai/config.json",
60
- model: providerModel(model),
61
- enabled_providers: [PROVIDER_NAME]
62
- };
63
- }
64
- }),
65
- fileMutation.ensureDirectory({ path: "~/.local/share/opencode" }),
66
- configMutation.merge({
67
- target: "~/.local/share/opencode/auth.json",
68
- value: (ctx) => {
69
- const { apiKey } = (ctx ?? {});
70
- return {
71
- [PROVIDER_NAME]: {
72
- type: "api",
73
- key: apiKey ?? ""
74
- }
75
- };
76
- }
77
- })
78
- ],
79
- unconfigure: [
80
- configMutation.prune({
81
- target: "~/.config/opencode/config.json",
82
- shape: { enabled_providers: true }
83
- }),
84
- configMutation.prune({
85
- target: "~/.local/share/opencode/auth.json",
86
- shape: { [PROVIDER_NAME]: true }
87
- })
88
- ]
1361
+ details
1362
+ };
1363
+ }
1364
+ async function applyConfigPrune(mutation, context, options) {
1365
+ const rawPath = resolveValue(mutation.target, options);
1366
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1367
+ const details = {
1368
+ kind: mutation.kind,
1369
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1370
+ targetPath
1371
+ };
1372
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1373
+ if (rawContent === null) {
1374
+ return {
1375
+ outcome: { changed: false, effect: "none", detail: "noop" },
1376
+ details
1377
+ };
1378
+ }
1379
+ const formatName = mutation.format ?? detectFormat(rawPath);
1380
+ if (!formatName) {
1381
+ throw new Error(
1382
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1383
+ );
1384
+ }
1385
+ const format = getConfigFormat(formatName);
1386
+ let current;
1387
+ try {
1388
+ current = format.parse(rawContent);
1389
+ } catch {
1390
+ return {
1391
+ outcome: { changed: false, effect: "none", detail: "noop" },
1392
+ details
1393
+ };
1394
+ }
1395
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
1396
+ return {
1397
+ outcome: { changed: false, effect: "none", detail: "noop" },
1398
+ details
1399
+ };
1400
+ }
1401
+ const shape = resolveValue(mutation.shape, options);
1402
+ const { changed, result } = format.prune(current, shape);
1403
+ if (!changed) {
1404
+ return {
1405
+ outcome: { changed: false, effect: "none", detail: "noop" },
1406
+ details
1407
+ };
1408
+ }
1409
+ if (Object.keys(result).length === 0) {
1410
+ if (!context.dryRun) {
1411
+ await context.fs.unlink(targetPath);
1412
+ }
1413
+ return {
1414
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1415
+ details
1416
+ };
1417
+ }
1418
+ const serialized = format.serialize(result);
1419
+ if (!context.dryRun) {
1420
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1421
+ }
1422
+ return {
1423
+ outcome: { changed: true, effect: "write", detail: "update" },
1424
+ details
1425
+ };
1426
+ }
1427
+ async function applyConfigTransform(mutation, context, options) {
1428
+ const rawPath = resolveValue(mutation.target, options);
1429
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1430
+ const details = {
1431
+ kind: mutation.kind,
1432
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1433
+ targetPath
1434
+ };
1435
+ const formatName = mutation.format ?? detectFormat(rawPath);
1436
+ if (!formatName) {
1437
+ throw new Error(
1438
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1439
+ );
1440
+ }
1441
+ const format = getConfigFormat(formatName);
1442
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1443
+ let current;
1444
+ try {
1445
+ current = rawContent === null ? {} : format.parse(rawContent);
1446
+ } catch {
1447
+ if (rawContent !== null) {
1448
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1449
+ }
1450
+ current = {};
1451
+ }
1452
+ const { content: transformed, changed } = mutation.transform(current, options);
1453
+ if (!changed) {
1454
+ return {
1455
+ outcome: { changed: false, effect: "none", detail: "noop" },
1456
+ details
1457
+ };
1458
+ }
1459
+ if (transformed === null) {
1460
+ if (rawContent === null) {
1461
+ return {
1462
+ outcome: { changed: false, effect: "none", detail: "noop" },
1463
+ details
1464
+ };
1465
+ }
1466
+ if (!context.dryRun) {
1467
+ await context.fs.unlink(targetPath);
1468
+ }
1469
+ return {
1470
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1471
+ details
1472
+ };
1473
+ }
1474
+ const serialized = format.serialize(transformed);
1475
+ if (!context.dryRun) {
1476
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1477
+ }
1478
+ return {
1479
+ outcome: {
1480
+ changed: true,
1481
+ effect: "write",
1482
+ detail: rawContent === null ? "create" : "update"
89
1483
  },
90
- install: OPEN_CODE_INSTALL_DEFINITION,
91
- test(context) {
92
- return context.runCheck(createSpawnHealthCheck("opencode", {
93
- model: context.model,
94
- expectedOutput: "OPEN_CODE_OK"
95
- }));
1484
+ details
1485
+ };
1486
+ }
1487
+ async function applyTemplateWrite(mutation, context, options) {
1488
+ if (!context.templates) {
1489
+ throw new Error(
1490
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1491
+ );
1492
+ }
1493
+ const rawPath = resolveValue(mutation.target, options);
1494
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1495
+ const details = {
1496
+ kind: mutation.kind,
1497
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1498
+ targetPath
1499
+ };
1500
+ const template = await context.templates(mutation.templateId);
1501
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1502
+ const rendered = Mustache.render(template, templateContext);
1503
+ const existed = await pathExists(context.fs, targetPath);
1504
+ if (!context.dryRun) {
1505
+ await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
1506
+ }
1507
+ return {
1508
+ outcome: {
1509
+ changed: true,
1510
+ effect: "write",
1511
+ detail: existed ? "update" : "create"
96
1512
  },
97
- spawn(context, options) {
98
- const opts = (options ?? {});
99
- const args = [
100
- ...getModelArgs(opts.model),
101
- "run",
102
- opts.prompt,
103
- ...(opts.args ?? [])
104
- ];
105
- if (opts.cwd) {
106
- return context.command.runCommand("poe-code", ["wrap", "opencode", ...args], {
107
- cwd: opts.cwd
108
- });
1513
+ details
1514
+ };
1515
+ }
1516
+ async function applyTemplateMerge(mutation, context, options, formatName) {
1517
+ if (!context.templates) {
1518
+ throw new Error(
1519
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1520
+ );
1521
+ }
1522
+ const rawPath = resolveValue(mutation.target, options);
1523
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1524
+ const details = {
1525
+ kind: mutation.kind,
1526
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1527
+ targetPath
1528
+ };
1529
+ const format = getConfigFormat(formatName);
1530
+ const template = await context.templates(mutation.templateId);
1531
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1532
+ const rendered = Mustache.render(template, templateContext);
1533
+ let templateDoc;
1534
+ try {
1535
+ templateDoc = format.parse(rendered);
1536
+ } catch (error) {
1537
+ throw new Error(
1538
+ `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`,
1539
+ { cause: error }
1540
+ );
1541
+ }
1542
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1543
+ let current;
1544
+ try {
1545
+ current = rawContent === null ? {} : format.parse(rawContent);
1546
+ } catch {
1547
+ if (rawContent !== null) {
1548
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1549
+ }
1550
+ current = {};
1551
+ }
1552
+ const merged = format.merge(current, templateDoc);
1553
+ const serialized = format.serialize(merged);
1554
+ const changed = serialized !== rawContent;
1555
+ if (changed && !context.dryRun) {
1556
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1557
+ }
1558
+ return {
1559
+ outcome: {
1560
+ changed,
1561
+ effect: changed ? "write" : "none",
1562
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1563
+ },
1564
+ details
1565
+ };
1566
+ }
1567
+
1568
+ // packages/config-mutations/src/execution/run-mutations.ts
1569
+ async function runMutations(mutations, context, options) {
1570
+ const effects = [];
1571
+ let anyChanged = false;
1572
+ const resolverOptions = options ?? {};
1573
+ for (const mutation of mutations) {
1574
+ const { outcome } = await executeMutation(
1575
+ mutation,
1576
+ context,
1577
+ resolverOptions
1578
+ );
1579
+ effects.push(outcome);
1580
+ if (outcome.changed) {
1581
+ anyChanged = true;
1582
+ }
1583
+ }
1584
+ return {
1585
+ changed: anyChanged,
1586
+ effects
1587
+ };
1588
+ }
1589
+ async function executeMutation(mutation, context, options) {
1590
+ context.observers?.onStart?.({
1591
+ kind: mutation.kind,
1592
+ label: mutation.label ?? mutation.kind,
1593
+ targetPath: void 0
1594
+ // Will be resolved during apply
1595
+ });
1596
+ try {
1597
+ const { outcome, details } = await applyMutation(mutation, context, options);
1598
+ context.observers?.onComplete?.(details, outcome);
1599
+ return { outcome, details };
1600
+ } catch (error) {
1601
+ context.observers?.onError?.(
1602
+ {
1603
+ kind: mutation.kind,
1604
+ label: mutation.label ?? mutation.kind,
1605
+ targetPath: void 0
1606
+ },
1607
+ error
1608
+ );
1609
+ throw error;
1610
+ }
1611
+ }
1612
+
1613
+ // packages/config-mutations/src/template/render.ts
1614
+ import Mustache2 from "mustache";
1615
+ var originalEscape = Mustache2.escape;
1616
+
1617
+ // src/services/service-install.ts
1618
+ async function runServiceInstall(definition, context) {
1619
+ const checkContext = {
1620
+ isDryRun: context.isDryRun,
1621
+ runCommand: context.runCommand
1622
+ };
1623
+ let needsInstall = false;
1624
+ try {
1625
+ await definition.check.run(checkContext);
1626
+ context.logger(`${definition.summary} already installed.`);
1627
+ } catch (error) {
1628
+ const detail = error instanceof Error ? error.message : String(error);
1629
+ context.logger(`${definition.summary} not detected: ${detail}`);
1630
+ needsInstall = true;
1631
+ }
1632
+ if (!needsInstall) {
1633
+ return false;
1634
+ }
1635
+ if (context.isDryRun) {
1636
+ logInstallDryRun(definition, context);
1637
+ return true;
1638
+ }
1639
+ const platformSteps = filterStepsByPlatform(definition.steps, context.platform);
1640
+ for (const step of platformSteps) {
1641
+ await runInstallStep(step, context);
1642
+ }
1643
+ await definition.check.run(checkContext);
1644
+ if (definition.postChecks) {
1645
+ for (const postCheck of definition.postChecks) {
1646
+ await postCheck.run(checkContext);
1647
+ }
1648
+ }
1649
+ context.logger(
1650
+ definition.successMessage ?? `${definition.summary} installed.`
1651
+ );
1652
+ return true;
1653
+ }
1654
+ function describeInstallCommand(step) {
1655
+ return `[${step.id}] ${formatCommand2(step.command, step.args)}`;
1656
+ }
1657
+ function formatCommand2(command, args) {
1658
+ return [command, ...args.map(quoteIfNeeded)].join(" ");
1659
+ }
1660
+ function quoteIfNeeded(value) {
1661
+ if (value.length === 0) {
1662
+ return '""';
1663
+ }
1664
+ if (value.includes(" ") || value.includes(" ") || value.includes("\n")) {
1665
+ return `"${value.replaceAll('"', '\\"')}"`;
1666
+ }
1667
+ return value;
1668
+ }
1669
+ function filterStepsByPlatform(steps, platform) {
1670
+ return steps.filter(
1671
+ (step) => !step.platforms || step.platforms.includes(platform)
1672
+ );
1673
+ }
1674
+ function logInstallDryRun(definition, context) {
1675
+ context.logger(`Dry run: would install ${definition.summary}.`);
1676
+ const platformSteps = filterStepsByPlatform(definition.steps, context.platform);
1677
+ for (const step of platformSteps) {
1678
+ context.logger(`Dry run: ${describeInstallCommand(step)}`);
1679
+ }
1680
+ }
1681
+ async function runInstallStep(step, context) {
1682
+ context.logger(`Running ${describeInstallCommand(step)}`);
1683
+ const result = await context.runCommand(step.command, step.args);
1684
+ if (result.exitCode !== 0) {
1685
+ const stderr = result.stderr.trim();
1686
+ const suffix = stderr.length > 0 ? `: ${stderr}` : "";
1687
+ throw new Error(
1688
+ `${describeInstallCommand(step)} failed with exit code ${result.exitCode}${suffix}`
1689
+ );
1690
+ }
1691
+ }
1692
+
1693
+ // src/providers/create-provider.ts
1694
+ var templateImports = {
1695
+ "python/env.hbs": () => Promise.resolve().then(() => __toESM(require_env(), 1)),
1696
+ "python/main.py.hbs": () => Promise.resolve().then(() => __toESM(require_main_py(), 1)),
1697
+ "python/requirements.txt.hbs": () => Promise.resolve().then(() => __toESM(require_requirements_txt(), 1)),
1698
+ "codex/config.toml.hbs": () => Promise.resolve().then(() => __toESM(require_config_toml(), 1))
1699
+ };
1700
+ async function loadTemplate(templateId) {
1701
+ const loader = templateImports[templateId];
1702
+ if (!loader) {
1703
+ throw new Error(`Template not found: ${templateId}`);
1704
+ }
1705
+ const module = await loader();
1706
+ return module.default;
1707
+ }
1708
+ function createProvider(opts) {
1709
+ const provider2 = {
1710
+ id: opts.id,
1711
+ summary: opts.summary,
1712
+ name: opts.name,
1713
+ aliases: opts.aliases,
1714
+ label: opts.label,
1715
+ branding: opts.branding,
1716
+ disabled: opts.disabled,
1717
+ supportsStdinPrompt: opts.supportsStdinPrompt,
1718
+ configurePrompts: opts.configurePrompts,
1719
+ postConfigureMessages: opts.postConfigureMessages,
1720
+ isolatedEnv: opts.isolatedEnv,
1721
+ async configure(context, runOptions) {
1722
+ await runMutations(opts.manifest.configure, {
1723
+ fs: context.fs,
1724
+ homeDir: context.env.homeDir,
1725
+ observers: runOptions?.observers,
1726
+ templates: loadTemplate,
1727
+ pathMapper: context.pathMapper
1728
+ }, context.options);
1729
+ context.command.flushDryRun({ emitIfEmpty: false });
1730
+ },
1731
+ async unconfigure(context, runOptions) {
1732
+ if (!opts.manifest.unconfigure) {
1733
+ return false;
1734
+ }
1735
+ const result = await runMutations(opts.manifest.unconfigure, {
1736
+ fs: context.fs,
1737
+ homeDir: context.env.homeDir,
1738
+ observers: runOptions?.observers,
1739
+ templates: loadTemplate,
1740
+ pathMapper: context.pathMapper
1741
+ }, context.options);
1742
+ context.command.flushDryRun({ emitIfEmpty: false });
1743
+ return result.changed;
1744
+ }
1745
+ };
1746
+ if (opts.install) {
1747
+ provider2.install = createInstallRunner(opts.install);
1748
+ }
1749
+ if (opts.test) {
1750
+ provider2.test = opts.test;
1751
+ }
1752
+ if (opts.spawn) {
1753
+ provider2.spawn = opts.spawn;
1754
+ }
1755
+ return provider2;
1756
+ }
1757
+ function createInstallRunner(definition) {
1758
+ return async (context) => {
1759
+ await runServiceInstall(definition, {
1760
+ isDryRun: context.logger.context.dryRun,
1761
+ runCommand: context.command.runCommand,
1762
+ logger: (message) => context.logger.verbose(message),
1763
+ platform: context.env.platform
1764
+ });
1765
+ };
1766
+ }
1767
+
1768
+ // src/providers/opencode.ts
1769
+ function providerModel(model) {
1770
+ const value = model ?? DEFAULT_FRONTIER_MODEL;
1771
+ const prefix = `${PROVIDER_NAME}/`;
1772
+ return value.startsWith(prefix) ? value : `${prefix}${value}`;
1773
+ }
1774
+ var OPEN_CODE_INSTALL_DEFINITION = {
1775
+ id: "opencode",
1776
+ summary: "OpenCode CLI",
1777
+ check: createBinaryExistsCheck(
1778
+ "opencode",
1779
+ "opencode-cli-binary",
1780
+ "OpenCode CLI binary must exist"
1781
+ ),
1782
+ steps: [
1783
+ {
1784
+ id: "install-opencode-cli-npm",
1785
+ command: "npm",
1786
+ args: ["install", "-g", "opencode-ai"]
1787
+ }
1788
+ ],
1789
+ successMessage: "Installed OpenCode CLI via npm."
1790
+ };
1791
+ function getModelArgs(model) {
1792
+ return ["--model", providerModel(model)];
1793
+ }
1794
+ var openCodeService = createProvider({
1795
+ ...openCodeAgent,
1796
+ supportsStdinPrompt: false,
1797
+ configurePrompts: {
1798
+ model: {
1799
+ label: "OpenCode model",
1800
+ defaultValue: DEFAULT_FRONTIER_MODEL,
1801
+ choices: FRONTIER_MODELS.map((id) => ({
1802
+ title: id,
1803
+ value: id
1804
+ }))
1805
+ }
1806
+ },
1807
+ isolatedEnv: {
1808
+ agentBinary: openCodeAgent.binaryName,
1809
+ configProbe: {
1810
+ kind: "isolatedFile",
1811
+ relativePath: ".config/opencode/config.json"
1812
+ },
1813
+ env: {
1814
+ XDG_CONFIG_HOME: { kind: "isolatedDir", relativePath: ".config" },
1815
+ XDG_DATA_HOME: { kind: "isolatedDir", relativePath: ".local/share" }
1816
+ }
1817
+ },
1818
+ manifest: {
1819
+ configure: [
1820
+ fileMutation.ensureDirectory({ path: "~/.config/opencode" }),
1821
+ configMutation.merge({
1822
+ target: "~/.config/opencode/config.json",
1823
+ value: (ctx) => {
1824
+ const { model } = ctx ?? {};
1825
+ return {
1826
+ $schema: "https://opencode.ai/config.json",
1827
+ model: providerModel(model),
1828
+ enabled_providers: [PROVIDER_NAME]
1829
+ };
1830
+ }
1831
+ }),
1832
+ fileMutation.ensureDirectory({ path: "~/.local/share/opencode" }),
1833
+ configMutation.merge({
1834
+ target: "~/.local/share/opencode/auth.json",
1835
+ value: (ctx) => {
1836
+ const { apiKey } = ctx ?? {};
1837
+ return {
1838
+ [PROVIDER_NAME]: {
1839
+ type: "api",
1840
+ key: apiKey ?? ""
1841
+ }
1842
+ };
109
1843
  }
110
- return context.command.runCommand("poe-code", ["wrap", "opencode", ...args]);
1844
+ })
1845
+ ],
1846
+ unconfigure: [
1847
+ configMutation.prune({
1848
+ target: "~/.config/opencode/config.json",
1849
+ shape: { enabled_providers: true }
1850
+ }),
1851
+ configMutation.prune({
1852
+ target: "~/.local/share/opencode/auth.json",
1853
+ shape: { [PROVIDER_NAME]: true }
1854
+ })
1855
+ ]
1856
+ },
1857
+ install: OPEN_CODE_INSTALL_DEFINITION,
1858
+ test(context) {
1859
+ return context.runCheck(
1860
+ createSpawnHealthCheck("opencode", {
1861
+ model: context.model,
1862
+ expectedOutput: "OPEN_CODE_OK"
1863
+ })
1864
+ );
1865
+ },
1866
+ spawn(context, options) {
1867
+ const opts = options ?? {};
1868
+ const args = [
1869
+ ...getModelArgs(opts.model),
1870
+ "run",
1871
+ opts.prompt,
1872
+ ...opts.args ?? []
1873
+ ];
1874
+ if (opts.cwd) {
1875
+ return context.command.runCommand("poe-code", ["wrap", "opencode", ...args], {
1876
+ cwd: opts.cwd
1877
+ });
111
1878
  }
1879
+ return context.command.runCommand("poe-code", ["wrap", "opencode", ...args]);
1880
+ }
112
1881
  });
113
- //# sourceMappingURL=opencode.js.map
1882
+ var provider = openCodeService;
1883
+ export {
1884
+ OPEN_CODE_INSTALL_DEFINITION,
1885
+ openCodeService,
1886
+ provider
1887
+ };
1888
+ //# sourceMappingURL=opencode.js.map