poe-code 3.0.391 → 3.0.393

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.
package/dist/skills.js CHANGED
@@ -1,59 +1,2880 @@
1
- import * as fs from "node:fs/promises";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import { installSkill as installAgentSkill } from "@poe-code/agent-skill-config";
5
- function createNodeFileSystem() {
1
+ // src/skills.ts
2
+ import * as fs4 from "node:fs/promises";
3
+ import * as os2 from "node:os";
4
+ import * as path8 from "node:path";
5
+
6
+ // packages/agent-skill-config/src/configs.ts
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+
10
+ // packages/agent-defs/src/agents/claude-code.ts
11
+ var claudeCodeAgent = {
12
+ id: "claude-code",
13
+ name: "claude-code",
14
+ label: "Claude Code",
15
+ summary: "Configure Claude Code to route through Poe.",
16
+ aliases: ["claude"],
17
+ binaryName: "claude",
18
+ apiShapes: ["anthropic-messages"],
19
+ otelCapture: {
20
+ env: {
21
+ CLAUDE_CODE_ENABLE_TELEMETRY: "1"
22
+ }
23
+ },
24
+ configPath: "~/.claude.json",
25
+ branding: {
26
+ colors: {
27
+ dark: "#C15F3C",
28
+ light: "#C15F3C"
29
+ }
30
+ }
31
+ };
32
+
33
+ // packages/agent-defs/src/agents/claude-desktop.ts
34
+ var claudeDesktopAgent = {
35
+ id: "claude-desktop",
36
+ name: "claude-desktop",
37
+ label: "Claude Desktop",
38
+ summary: "Anthropic's official desktop application for Claude",
39
+ configPath: "~/.config/Claude/claude_desktop_config.json",
40
+ configPaths: {
41
+ darwin: "~/Library/Application Support/Claude/claude_desktop_config.json",
42
+ linux: "~/.config/Claude/claude_desktop_config.json",
43
+ win32: "~/AppData/Roaming/Claude/claude_desktop_config.json"
44
+ },
45
+ branding: {
46
+ colors: {
47
+ dark: "#D97757",
48
+ light: "#D97757"
49
+ }
50
+ }
51
+ };
52
+
53
+ // packages/agent-defs/src/agents/codex.ts
54
+ var codexAgent = {
55
+ id: "codex",
56
+ name: "codex",
57
+ label: "Codex",
58
+ summary: "Configure Codex to use Poe as the model provider.",
59
+ binaryName: "codex",
60
+ apiShapes: ["openai-responses"],
61
+ otelCapture: {
62
+ args: (endpoint, content) => [
63
+ "-c",
64
+ `otel.trace_exporter={"otlp-http"={endpoint=${JSON.stringify(`${endpoint}/v1/traces`)},protocol="json"}}`,
65
+ "-c",
66
+ `otel.exporter={"otlp-http"={endpoint=${JSON.stringify(`${endpoint}/v1/logs`)},protocol="json"}}`,
67
+ "-c",
68
+ `otel.log_user_prompt=${content}`
69
+ ]
70
+ },
71
+ configPath: "~/.codex/config.toml",
72
+ branding: {
73
+ colors: {
74
+ dark: "#D5D9DF",
75
+ light: "#7A7F86"
76
+ }
77
+ }
78
+ };
79
+
80
+ // packages/agent-defs/src/agents/cursor.ts
81
+ var cursorAgent = {
82
+ id: "cursor",
83
+ name: "cursor",
84
+ aliases: ["cursor-agent"],
85
+ label: "Cursor",
86
+ summary: "Cursor's CLI coding agent.",
87
+ binaryName: "cursor-agent",
88
+ configPath: "~/.cursor/mcp.json",
89
+ branding: {
90
+ colors: {
91
+ dark: "#FFFFFF",
92
+ light: "#000000"
93
+ }
94
+ }
95
+ };
96
+
97
+ // packages/agent-defs/src/agents/gemini-cli.ts
98
+ var geminiCliAgent = {
99
+ id: "gemini-cli",
100
+ name: "gemini-cli",
101
+ aliases: ["gemini"],
102
+ label: "Gemini CLI",
103
+ summary: "Configure Google's Gemini CLI to use a compatible Google generations API.",
104
+ binaryName: "gemini",
105
+ configPath: "~/.gemini/settings.json",
106
+ apiShapes: ["google-generations"],
107
+ branding: {
108
+ colors: {
109
+ dark: "#8AB4F8",
110
+ light: "#1A73E8"
111
+ }
112
+ }
113
+ };
114
+
115
+ // packages/agent-defs/src/agents/opencode.ts
116
+ var openCodeAgent = {
117
+ id: "opencode",
118
+ name: "opencode",
119
+ label: "OpenCode CLI",
120
+ summary: "Configure OpenCode CLI to use the Poe API.",
121
+ binaryName: "opencode",
122
+ apiShapes: ["openai-chat-completions"],
123
+ otelCapture: {
124
+ env: {
125
+ OPENCODE_CONFIG_CONTENT: '{"experimental":{"openTelemetry":true}}'
126
+ }
127
+ },
128
+ configPath: "~/.config/opencode/opencode.json",
129
+ branding: {
130
+ colors: {
131
+ dark: "#4A4F55",
132
+ light: "#2F3338"
133
+ }
134
+ }
135
+ };
136
+
137
+ // packages/agent-defs/src/agents/kimi.ts
138
+ var kimiAgent = {
139
+ id: "kimi",
140
+ name: "kimi",
141
+ label: "Kimi",
142
+ summary: "Configure Kimi CLI to use Poe API",
143
+ aliases: ["kimi-cli"],
144
+ binaryName: "kimi",
145
+ apiShapes: ["openai-chat-completions"],
146
+ configPath: "~/.kimi/mcp.json",
147
+ branding: {
148
+ colors: {
149
+ dark: "#7B68EE",
150
+ light: "#6A5ACD"
151
+ }
152
+ }
153
+ };
154
+
155
+ // packages/agent-defs/src/agents/goose.ts
156
+ var gooseAgent = {
157
+ id: "goose",
158
+ name: "goose",
159
+ label: "Goose",
160
+ summary: "Block's open-source AI agent with ACP support.",
161
+ binaryName: "goose",
162
+ apiShapes: ["openai-chat-completions"],
163
+ otelCapture: {},
164
+ configPath: "~/.config/goose/config.yaml",
165
+ branding: {
166
+ colors: {
167
+ dark: "#FF6B35",
168
+ light: "#E85D26"
169
+ }
170
+ }
171
+ };
172
+
173
+ // packages/agent-defs/src/agents/poe-agent.ts
174
+ var poeAgentAgent = {
175
+ id: "poe-agent",
176
+ name: "poe-agent",
177
+ label: "Poe Agent",
178
+ summary: "Run one-shot prompts with the built-in Poe agent runtime.",
179
+ apiShapes: ["openai-responses", "openai-chat-completions"],
180
+ configPath: "~/.poe-code/config.json",
181
+ branding: {
182
+ colors: {
183
+ dark: "#A465F7",
184
+ light: "#7A3FD3"
185
+ }
186
+ }
187
+ };
188
+
189
+ // packages/agent-defs/src/registry.ts
190
+ function freezeAgent(agent) {
191
+ if (agent.aliases !== void 0) {
192
+ Object.freeze(agent.aliases);
193
+ }
194
+ if (agent.apiShapes !== void 0) {
195
+ Object.freeze(agent.apiShapes);
196
+ }
197
+ if (agent.otelCapture?.env !== void 0) {
198
+ Object.freeze(agent.otelCapture.env);
199
+ }
200
+ if (agent.otelCapture !== void 0) {
201
+ Object.freeze(agent.otelCapture);
202
+ }
203
+ Object.freeze(agent.branding.colors);
204
+ Object.freeze(agent.branding);
205
+ return Object.freeze(agent);
206
+ }
207
+ var allAgents = Object.freeze([
208
+ freezeAgent(claudeCodeAgent),
209
+ freezeAgent(claudeDesktopAgent),
210
+ freezeAgent(codexAgent),
211
+ freezeAgent(cursorAgent),
212
+ freezeAgent(geminiCliAgent),
213
+ freezeAgent(openCodeAgent),
214
+ freezeAgent(kimiAgent),
215
+ freezeAgent(gooseAgent),
216
+ freezeAgent(poeAgentAgent)
217
+ ]);
218
+ var lookup = /* @__PURE__ */ new Map();
219
+ for (const agent of allAgents) {
220
+ const values = [agent.id, agent.name, ...agent.aliases ?? []];
221
+ for (const value of values) {
222
+ const normalized = value.toLowerCase();
223
+ if (!lookup.has(normalized)) {
224
+ lookup.set(normalized, agent.id);
225
+ }
226
+ }
227
+ }
228
+ function resolveAgentId(input) {
229
+ if (!input) {
230
+ return void 0;
231
+ }
232
+ return lookup.get(input.trim().toLowerCase());
233
+ }
234
+
235
+ // packages/agent-skill-config/src/configs.ts
236
+ var agentSkillConfigs = {
237
+ "claude-code": {
238
+ globalSkillDir: "~/.claude/skills",
239
+ localSkillDir: ".claude/skills"
240
+ },
241
+ codex: {
242
+ globalSkillDir: "~/.codex/skills",
243
+ localSkillDir: ".codex/skills"
244
+ },
245
+ cursor: {
246
+ globalSkillDir: "~/.cursor/skills-cursor",
247
+ localSkillDir: ".cursor/skills"
248
+ },
249
+ "gemini-cli": {
250
+ globalSkillDir: "~/.gemini/skills",
251
+ localSkillDir: ".gemini/skills"
252
+ },
253
+ opencode: {
254
+ globalSkillDir: "~/.config/opencode/skills",
255
+ localSkillDir: ".opencode/skills"
256
+ },
257
+ goose: {
258
+ globalSkillDir: "~/.agents/skills",
259
+ localSkillDir: ".agents/skills"
260
+ }
261
+ };
262
+ var supportedAgents = Object.freeze(Object.keys(agentSkillConfigs));
263
+ function resolveAgentSupport(input, registry = agentSkillConfigs) {
264
+ const resolvedId = resolveAgentId(input);
265
+ if (!resolvedId) {
266
+ return { status: "unknown", input };
267
+ }
268
+ const config2 = registry[resolvedId];
269
+ if (!config2) {
270
+ return { status: "unsupported", input, id: resolvedId };
271
+ }
272
+ return { status: "supported", input, id: resolvedId, config: { ...config2 } };
273
+ }
274
+
275
+ // packages/config-mutations/src/mutations/file-mutation.ts
276
+ function ensureDirectory(options) {
277
+ return {
278
+ kind: "ensureDirectory",
279
+ path: options.path,
280
+ label: options.label
281
+ };
282
+ }
283
+ function remove(options) {
284
+ return {
285
+ kind: "removeFile",
286
+ target: options.target,
287
+ whenEmpty: options.whenEmpty,
288
+ whenContentMatches: options.whenContentMatches,
289
+ label: options.label
290
+ };
291
+ }
292
+ function removeDirectory(options) {
293
+ return {
294
+ kind: "removeDirectory",
295
+ path: options.path,
296
+ force: options.force,
297
+ label: options.label
298
+ };
299
+ }
300
+ function chmod(options) {
301
+ return {
302
+ kind: "chmod",
303
+ target: options.target,
304
+ mode: options.mode,
305
+ label: options.label
306
+ };
307
+ }
308
+ function backup(options) {
309
+ return {
310
+ kind: "backup",
311
+ target: options.target,
312
+ once: options.once,
313
+ label: options.label
314
+ };
315
+ }
316
+ function restoreBackup(options) {
317
+ return {
318
+ kind: "restoreBackup",
319
+ target: options.target,
320
+ label: options.label
321
+ };
322
+ }
323
+ var fileMutation = {
324
+ ensureDirectory,
325
+ remove,
326
+ removeDirectory,
327
+ chmod,
328
+ backup,
329
+ restoreBackup
330
+ };
331
+
332
+ // packages/config-mutations/src/mutations/template-mutation.ts
333
+ function write(options) {
334
+ return {
335
+ kind: "templateWrite",
336
+ target: options.target,
337
+ templateId: options.templateId,
338
+ context: options.context,
339
+ label: options.label
340
+ };
341
+ }
342
+ function mergeToml(options) {
343
+ return {
344
+ kind: "templateMergeToml",
345
+ target: options.target,
346
+ templateId: options.templateId,
347
+ context: options.context,
348
+ label: options.label
349
+ };
350
+ }
351
+ function mergeJson(options) {
352
+ return {
353
+ kind: "templateMergeJson",
354
+ target: options.target,
355
+ templateId: options.templateId,
356
+ context: options.context,
357
+ label: options.label
358
+ };
359
+ }
360
+ var templateMutation = {
361
+ write,
362
+ mergeToml,
363
+ mergeJson
364
+ };
365
+
366
+ // packages/config-mutations/src/execution/apply-mutation.ts
367
+ import { randomUUID } from "node:crypto";
368
+ import path3 from "node:path";
369
+
370
+ // packages/toolcraft-design/src/internal/color-support.ts
371
+ function supportsColor(env = process.env, stream = process.stdout) {
372
+ if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "0") {
373
+ return true;
374
+ }
375
+ if (env.NO_COLOR !== void 0) {
376
+ return false;
377
+ }
378
+ if (stream.isTTY !== true) {
379
+ return false;
380
+ }
381
+ return typeof env.TERM === "string" && env.TERM.length > 0 && env.TERM !== "dumb";
382
+ }
383
+
384
+ // packages/toolcraft-design/src/components/color.ts
385
+ var reset = "\x1B[0m";
386
+ var ansiStyles = {
387
+ reset: { open: reset },
388
+ bold: { open: "\x1B[1m" },
389
+ dim: { open: "\x1B[2m" },
390
+ italic: { open: "\x1B[3m" },
391
+ underline: { open: "\x1B[4m" },
392
+ inverse: { open: "\x1B[7m" },
393
+ strikethrough: { open: "\x1B[9m" },
394
+ black: { open: "\x1B[30m" },
395
+ red: { open: "\x1B[31m" },
396
+ green: { open: "\x1B[32m" },
397
+ yellow: { open: "\x1B[33m" },
398
+ blue: { open: "\x1B[34m" },
399
+ magenta: { open: "\x1B[35m" },
400
+ cyan: { open: "\x1B[36m" },
401
+ white: { open: "\x1B[37m" },
402
+ gray: { open: "\x1B[90m" },
403
+ magentaBright: { open: "\x1B[95m" },
404
+ cyanBright: { open: "\x1B[96m" },
405
+ bgRed: { open: "\x1B[41m" },
406
+ bgGreen: { open: "\x1B[42m" },
407
+ bgYellow: { open: "\x1B[43m" },
408
+ bgBlue: { open: "\x1B[44m" },
409
+ bgMagenta: { open: "\x1B[45m" }
410
+ };
411
+ var styleNames = Object.keys(ansiStyles);
412
+ function replaceAll(value, search, replacement) {
413
+ return value.split(search).join(replacement);
414
+ }
415
+ function applyStyles(text3, styles) {
416
+ if (!supportsColor() || styles.length === 0) {
417
+ return text3;
418
+ }
419
+ const open = styles.map((style) => style.open).join("");
420
+ const output = text3.includes(reset) ? replaceAll(text3, reset, `${reset}${open}`) : text3;
421
+ return `${open}${output}${reset}`;
422
+ }
423
+ function clampRgb(value) {
424
+ if (Number.isNaN(value)) {
425
+ return 0;
426
+ }
427
+ return Math.min(255, Math.max(0, Math.round(value)));
428
+ }
429
+ function hexChannel(value, offset) {
430
+ return Number.parseInt(value.slice(offset, offset + 2), 16);
431
+ }
432
+ function normalizeHex(value) {
433
+ const normalized = value.startsWith("#") ? value.slice(1) : value;
434
+ if (normalized.length !== 3 && normalized.length !== 6 || Array.from(normalized).some((char) => !"0123456789abcdefABCDEF".includes(char))) {
435
+ throw new Error(`Invalid hexadecimal color: ${value}`);
436
+ }
437
+ if (normalized.length === 3) {
438
+ const red = normalized[0];
439
+ const green = normalized[1];
440
+ const blue = normalized[2];
441
+ return [
442
+ Number.parseInt(`${red}${red}`, 16),
443
+ Number.parseInt(`${green}${green}`, 16),
444
+ Number.parseInt(`${blue}${blue}`, 16)
445
+ ];
446
+ }
447
+ return [
448
+ hexChannel(normalized, 0),
449
+ hexChannel(normalized, 2),
450
+ hexChannel(normalized, 4)
451
+ ];
452
+ }
453
+ function rgbStyle(red, green, blue) {
454
+ return {
455
+ open: `\x1B[38;2;${clampRgb(red)};${clampRgb(green)};${clampRgb(blue)}m`
456
+ };
457
+ }
458
+ function bgRgbStyle(red, green, blue) {
459
+ return {
460
+ open: `\x1B[48;2;${clampRgb(red)};${clampRgb(green)};${clampRgb(blue)}m`
461
+ };
462
+ }
463
+ function createColor(styles = []) {
464
+ const builder = ((text3) => applyStyles(String(text3), styles));
465
+ for (const name of styleNames) {
466
+ Object.defineProperty(builder, name, {
467
+ configurable: true,
468
+ enumerable: true,
469
+ get: () => createColor([...styles, ansiStyles[name]])
470
+ });
471
+ }
472
+ builder.hex = (value) => {
473
+ const [red, green, blue] = normalizeHex(value);
474
+ return createColor([...styles, rgbStyle(red, green, blue)]);
475
+ };
476
+ builder.rgb = (red, green, blue) => createColor([...styles, rgbStyle(red, green, blue)]);
477
+ builder.bgHex = (value) => {
478
+ const [red, green, blue] = normalizeHex(value);
479
+ return createColor([...styles, bgRgbStyle(red, green, blue)]);
480
+ };
481
+ builder.bgRgb = (red, green, blue) => createColor([...styles, bgRgbStyle(red, green, blue)]);
482
+ return builder;
483
+ }
484
+ var color = createColor();
485
+
486
+ // packages/toolcraft-design/src/tokens/brand.ts
487
+ var brands = {
488
+ purple: { name: "purple", primary: "#a200ff" },
489
+ blue: { name: "blue", primary: "#2f6fed" },
490
+ green: { name: "green", primary: "#1f9d57" }
491
+ };
492
+
493
+ // packages/toolcraft-design/src/internal/theme-state.ts
494
+ var defaults = {
495
+ brand: "purple",
496
+ label: "Poe"
497
+ };
498
+ var config = { ...defaults };
499
+ var revision = 0;
500
+ var brandConfigured = false;
501
+ function getThemeConfig() {
502
+ return { ...config };
503
+ }
504
+ function getThemeRevision() {
505
+ return revision;
506
+ }
507
+ function isThemeBrandConfigured() {
508
+ return brandConfigured;
509
+ }
510
+
511
+ // packages/toolcraft-design/src/tokens/colors.ts
512
+ var brand = brands.purple.primary;
513
+ function withStyles(palette, styles) {
514
+ return Object.defineProperty(palette, "styles", {
515
+ value: styles,
516
+ enumerable: false
517
+ });
518
+ }
519
+ function brandColor(activeBrand, purple) {
520
+ return activeBrand.name === "purple" ? purple : color.hex(activeBrand.primary);
521
+ }
522
+ function brandBackground(activeBrand, purple) {
523
+ return activeBrand.name === "purple" ? purple : color.bgHex(activeBrand.primary);
524
+ }
525
+ function createPalette(activeBrand, mode) {
526
+ const isPurple = activeBrand.name === "purple";
527
+ if (mode === "light") {
528
+ const active2 = color.hex(activeBrand.primary);
529
+ const prompt2 = isPurple ? color.hex("#006699") : active2;
530
+ const number2 = isPurple ? color.hex("#0077cc") : active2;
531
+ return withStyles(
532
+ {
533
+ header: (text3) => active2.bold(text3),
534
+ divider: (text3) => color.hex("#666666")(text3),
535
+ prompt: (text3) => prompt2.bold(text3),
536
+ number: (text3) => number2.bold(text3),
537
+ intro: (text3) => color.bgHex(activeBrand.primary).white(` ${getThemeConfig().label} - ${text3} `),
538
+ get resolvedSymbol() {
539
+ return active2("\u25C7");
540
+ },
541
+ get errorSymbol() {
542
+ return color.hex("#cc0000")("\u25A0");
543
+ },
544
+ accent: (text3) => prompt2.bold(text3),
545
+ muted: (text3) => color.hex("#666666")(text3),
546
+ success: (text3) => color.hex("#008800")(text3),
547
+ warning: (text3) => color.hex("#cc6600")(text3),
548
+ error: (text3) => color.hex("#cc0000")(text3),
549
+ info: (text3) => active2(text3),
550
+ badge: (text3) => color.bgHex("#cc6600").white(` ${text3} `)
551
+ },
552
+ {
553
+ accent: { fg: isPurple ? "#006699" : activeBrand.primary, bold: true },
554
+ muted: { fg: "#666666" },
555
+ success: { fg: "#008800" },
556
+ warning: { fg: "#cc6600" },
557
+ error: { fg: "#cc0000" },
558
+ info: { fg: activeBrand.primary }
559
+ }
560
+ );
561
+ }
562
+ const active = brandColor(activeBrand, color.magenta);
563
+ const activeBright = brandColor(activeBrand, color.magentaBright);
564
+ const activeBackground = brandBackground(activeBrand, color.bgMagenta);
565
+ const prompt = isPurple ? color.cyan : active;
566
+ const number = isPurple ? color.cyanBright : active;
567
+ return withStyles(
568
+ {
569
+ header: (text3) => activeBright.bold(text3),
570
+ divider: (text3) => color.dim(text3),
571
+ prompt: (text3) => prompt(text3),
572
+ number: (text3) => number(text3),
573
+ intro: (text3) => activeBackground.white(` ${getThemeConfig().label} - ${text3} `),
574
+ get resolvedSymbol() {
575
+ return active("\u25C7");
576
+ },
577
+ get errorSymbol() {
578
+ return color.red("\u25A0");
579
+ },
580
+ accent: (text3) => prompt(text3),
581
+ muted: (text3) => color.dim(text3),
582
+ success: (text3) => color.green(text3),
583
+ warning: (text3) => color.yellow(text3),
584
+ error: (text3) => color.red(text3),
585
+ info: (text3) => active(text3),
586
+ badge: (text3) => color.bgYellow.black(` ${text3} `)
587
+ },
588
+ {
589
+ accent: { fg: isPurple ? "cyan" : activeBrand.primary, bold: true },
590
+ muted: { dim: true },
591
+ success: { fg: "green" },
592
+ warning: { fg: "yellow" },
593
+ error: { fg: "red" },
594
+ info: { fg: isPurple ? "magenta" : activeBrand.primary }
595
+ }
596
+ );
597
+ }
598
+ var dark = createPalette(brands.purple, "dark");
599
+ var light = createPalette(brands.purple, "light");
600
+
601
+ // packages/toolcraft-design/src/internal/output-format.ts
602
+ import { AsyncLocalStorage } from "node:async_hooks";
603
+ var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
604
+ var formatStorage = new AsyncLocalStorage();
605
+ var cached;
606
+ function resolveOutputFormat(env = process.env) {
607
+ const scoped = formatStorage.getStore();
608
+ if (scoped) {
609
+ return scoped;
610
+ }
611
+ if (cached) {
612
+ return cached;
613
+ }
614
+ const raw = env.OUTPUT_FORMAT?.toLowerCase();
615
+ cached = VALID_FORMATS.has(raw) ? raw : "terminal";
616
+ return cached;
617
+ }
618
+
619
+ // packages/toolcraft-design/src/internal/theme-detect.ts
620
+ function detectThemeFromEnv(env) {
621
+ const apple = env.APPLE_INTERFACE_STYLE;
622
+ if (typeof apple === "string") {
623
+ return apple.toLowerCase() === "dark" ? "dark" : "light";
624
+ }
625
+ const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
626
+ if (typeof vscodeKind === "string") {
627
+ const normalized = vscodeKind.toLowerCase();
628
+ if (normalized.includes("light")) {
629
+ return "light";
630
+ }
631
+ if (normalized.includes("dark")) {
632
+ return "dark";
633
+ }
634
+ }
635
+ const colorFGBG = env.COLORFGBG;
636
+ if (typeof colorFGBG === "string") {
637
+ const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
638
+ const background = parts.at(-1);
639
+ if (Number.isFinite(background)) {
640
+ return background >= 8 ? "light" : "dark";
641
+ }
642
+ }
643
+ return void 0;
644
+ }
645
+ function resolveThemeName(env = process.env) {
646
+ const raw = (env.POE_CODE_THEME ?? env.POE_THEME)?.toLowerCase();
647
+ if (raw === "light" || raw === "dark") {
648
+ return raw;
649
+ }
650
+ const detected = detectThemeFromEnv(env);
651
+ if (detected) {
652
+ return detected;
653
+ }
654
+ return "dark";
655
+ }
656
+ var themeCache = /* @__PURE__ */ new Map();
657
+ var cachedRevision = -1;
658
+ function getTheme(env) {
659
+ const themeName = resolveThemeName(env);
660
+ const config2 = getThemeConfig();
661
+ const requestedBrand = env?.POE_BRAND?.toLowerCase();
662
+ const activeBrandName = !isThemeBrandConfigured() && requestedBrand && Object.hasOwn(brands, requestedBrand) ? requestedBrand : config2.brand;
663
+ const revision2 = getThemeRevision();
664
+ if (revision2 !== cachedRevision) {
665
+ themeCache.clear();
666
+ cachedRevision = revision2;
667
+ }
668
+ const cacheKey = `${activeBrandName}:${themeName}`;
669
+ const cachedTheme = themeCache.get(cacheKey);
670
+ if (cachedTheme) {
671
+ return cachedTheme;
672
+ }
673
+ const activeBrand = brands[activeBrandName];
674
+ const theme = activeBrandName === "purple" ? themeName === "light" ? light : dark : createPalette(activeBrand, themeName);
675
+ themeCache.set(cacheKey, theme);
676
+ return theme;
677
+ }
678
+
679
+ // packages/toolcraft-design/src/components/symbols.ts
680
+ var symbols = {
681
+ get info() {
682
+ const format = resolveOutputFormat();
683
+ if (format === "json") return "info";
684
+ if (format === "markdown") return "(i)";
685
+ return color.magenta("\u25CF");
686
+ },
687
+ get success() {
688
+ const format = resolveOutputFormat();
689
+ if (format === "json") return "success";
690
+ if (format === "markdown") return "[ok]";
691
+ return color.magenta("\u25C6");
692
+ },
693
+ get resolved() {
694
+ const format = resolveOutputFormat();
695
+ if (format === "json") return "resolved";
696
+ if (format === "markdown") return ">";
697
+ return getTheme().resolvedSymbol;
698
+ },
699
+ get errorResolved() {
700
+ const format = resolveOutputFormat();
701
+ if (format === "json") return "error";
702
+ if (format === "markdown") return "[!]";
703
+ return getTheme().errorSymbol;
704
+ },
705
+ get bar() {
706
+ const format = resolveOutputFormat();
707
+ if (format === "json") return "";
708
+ if (format === "markdown") return "|";
709
+ return "\u2502";
710
+ },
711
+ cornerTopRight: "\u256E",
712
+ cornerBottomRight: "\u256F",
713
+ get warning() {
714
+ const format = resolveOutputFormat();
715
+ if (format === "json") return "warning";
716
+ if (format === "markdown") return "[!]";
717
+ return "\u25B2";
718
+ },
719
+ get active() {
720
+ const format = resolveOutputFormat();
721
+ if (format === "json") return "active";
722
+ if (format === "markdown") return "[x]";
723
+ return "\u25C6";
724
+ },
725
+ get inactive() {
726
+ const format = resolveOutputFormat();
727
+ if (format === "json") return "inactive";
728
+ if (format === "markdown") return "[ ]";
729
+ return "\u25CB";
730
+ }
731
+ };
732
+
733
+ // packages/toolcraft-design/src/internal/strip-ansi.ts
734
+ function stripAnsi(value) {
735
+ let output = "";
736
+ let index = 0;
737
+ while (index < value.length) {
738
+ const char = value[index];
739
+ if (char === "\x1B") {
740
+ index = skipEscapeSequence(value, index);
741
+ continue;
742
+ }
743
+ if (char === "\x9B") {
744
+ index = skipCsiSequence(value, index + 1);
745
+ continue;
746
+ }
747
+ output += char;
748
+ index += char.length;
749
+ }
750
+ return output;
751
+ }
752
+ function skipEscapeSequence(value, index) {
753
+ const next = value[index + 1];
754
+ if (next === "[") {
755
+ return skipCsiSequence(value, index + 2);
756
+ }
757
+ if (next === "]") {
758
+ return skipOscSequence(value, index + 2);
759
+ }
760
+ return Math.min(value.length, index + 2);
761
+ }
762
+ function skipCsiSequence(value, index) {
763
+ while (index < value.length) {
764
+ const codePoint = value.charCodeAt(index);
765
+ index += 1;
766
+ if (codePoint >= 64 && codePoint <= 126) {
767
+ break;
768
+ }
769
+ }
770
+ return index;
771
+ }
772
+ function skipOscSequence(value, index) {
773
+ while (index < value.length) {
774
+ if (value[index] === "\x07") {
775
+ return index + 1;
776
+ }
777
+ if (value[index] === "\x1B" && value[index + 1] === "\\") {
778
+ return index + 2;
779
+ }
780
+ index += 1;
781
+ }
782
+ return index;
783
+ }
784
+
785
+ // packages/toolcraft-design/src/prompts/primitives/log.ts
786
+ function renderMarkdownInline(value) {
787
+ return stripAnsi(value).replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
788
+ }
789
+ function writeTerminalMessage(msg, {
790
+ symbol: symbol2 = color.gray("\u2502"),
791
+ secondarySymbol = color.gray("\u2502"),
792
+ spacing: spacing2 = 1,
793
+ withGuide = true
794
+ } = {}) {
795
+ const lines = [];
796
+ const showGuide = withGuide !== false;
797
+ const contentLines = msg.split("\n");
798
+ const prefix = showGuide ? `${symbol2} ` : "";
799
+ const continuationPrefix = showGuide ? `${secondarySymbol} ` : "";
800
+ const emptyGuide = showGuide ? secondarySymbol : "";
801
+ for (let index = 0; index < spacing2; index += 1) {
802
+ lines.push(emptyGuide);
803
+ }
804
+ if (contentLines.length === 0) {
805
+ process.stdout.write("\n");
806
+ return;
807
+ }
808
+ const [firstLine = "", ...continuationLines] = contentLines;
809
+ if (firstLine.length > 0) {
810
+ lines.push(`${prefix}${firstLine}`);
811
+ } else {
812
+ lines.push(showGuide ? symbol2 : "");
813
+ }
814
+ for (const line of continuationLines) {
815
+ if (line.length > 0) {
816
+ lines.push(`${continuationPrefix}${line}`);
817
+ continue;
818
+ }
819
+ lines.push(emptyGuide);
820
+ }
821
+ process.stdout.write(`${lines.join("\n")}
822
+ `);
823
+ }
824
+ function message(msg, options) {
825
+ const format = resolveOutputFormat();
826
+ if (format === "markdown") {
827
+ process.stdout.write(`- ${renderMarkdownInline(msg)}
828
+ `);
829
+ return;
830
+ }
831
+ if (format === "json") {
832
+ process.stdout.write(
833
+ `${JSON.stringify({ level: "message", message: stripAnsi(msg) })}
834
+ `
835
+ );
836
+ return;
837
+ }
838
+ writeTerminalMessage(msg, options);
839
+ }
840
+ function info(msg) {
841
+ const format = resolveOutputFormat();
842
+ if (format === "markdown") {
843
+ process.stdout.write(`- **info:** ${renderMarkdownInline(msg)}
844
+ `);
845
+ return;
846
+ }
847
+ if (format === "json") {
848
+ process.stdout.write(
849
+ `${JSON.stringify({ level: "info", message: stripAnsi(msg) })}
850
+ `
851
+ );
852
+ return;
853
+ }
854
+ message(msg, { symbol: symbols.info });
855
+ }
856
+ function success(msg) {
857
+ const format = resolveOutputFormat();
858
+ if (format === "markdown") {
859
+ process.stdout.write(`- **success:** ${renderMarkdownInline(msg)}
860
+ `);
861
+ return;
862
+ }
863
+ if (format === "json") {
864
+ process.stdout.write(
865
+ `${JSON.stringify({ level: "success", message: stripAnsi(msg) })}
866
+ `
867
+ );
868
+ return;
869
+ }
870
+ message(msg, { symbol: symbols.success });
871
+ }
872
+ function warn(msg) {
873
+ const format = resolveOutputFormat();
874
+ if (format === "markdown") {
875
+ process.stdout.write(`- **warning:** ${renderMarkdownInline(msg)}
876
+ `);
877
+ return;
878
+ }
879
+ if (format === "json") {
880
+ process.stdout.write(
881
+ `${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}
882
+ `
883
+ );
884
+ return;
885
+ }
886
+ message(msg, { symbol: color.yellow("\u25B2") });
887
+ }
888
+ function error(msg) {
889
+ const format = resolveOutputFormat();
890
+ if (format === "markdown") {
891
+ process.stdout.write(`- **error:** ${renderMarkdownInline(msg)}
892
+ `);
893
+ return;
894
+ }
895
+ if (format === "json") {
896
+ process.stdout.write(
897
+ `${JSON.stringify({ level: "error", message: stripAnsi(msg) })}
898
+ `
899
+ );
900
+ return;
901
+ }
902
+ message(msg, { symbol: color.red("\u25A0") });
903
+ }
904
+ var log = {
905
+ info,
906
+ success,
907
+ message,
908
+ warn,
909
+ error
910
+ };
911
+
912
+ // packages/toolcraft-design/src/components/logger.ts
913
+ function createLogger(emitter) {
914
+ const emit = (level, message2) => {
915
+ if (emitter) {
916
+ emitter(message2);
917
+ return;
918
+ }
919
+ if (level === "success") {
920
+ log.success(message2);
921
+ return;
922
+ }
923
+ if (level === "warn") {
924
+ log.warn(message2);
925
+ return;
926
+ }
927
+ if (level === "error") {
928
+ log.error(message2);
929
+ return;
930
+ }
931
+ log.info(message2);
932
+ };
933
+ return {
934
+ info(message2) {
935
+ emit("info", message2);
936
+ },
937
+ success(message2) {
938
+ emit("success", message2);
939
+ },
940
+ warn(message2) {
941
+ emit("warn", message2);
942
+ },
943
+ error(message2) {
944
+ emit("error", message2);
945
+ },
946
+ resolved(label, value) {
947
+ if (emitter) {
948
+ emitter(`${label}: ${value}`);
949
+ return;
950
+ }
951
+ log.message(`${label}
952
+ ${value}`, { symbol: symbols.resolved });
953
+ },
954
+ errorResolved(label, value) {
955
+ if (emitter) {
956
+ emitter(`${label}: ${value}`);
957
+ return;
958
+ }
959
+ log.message(`${label}
960
+ ${value}`, { symbol: symbols.errorResolved });
961
+ },
962
+ message(message2, symbol2) {
963
+ if (emitter) {
964
+ emitter(message2);
965
+ return;
966
+ }
967
+ log.message(message2, { symbol: symbol2 ?? color.gray("\u2502") });
968
+ }
969
+ };
970
+ }
971
+ var logger = createLogger();
972
+
973
+ // packages/toolcraft-design/src/components/help-formatter.ts
974
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
975
+
976
+ // packages/toolcraft-design/src/components/table.ts
977
+ var graphemeSegmenter2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
978
+
979
+ // packages/toolcraft-design/src/components/detail-card.ts
980
+ import stringWidth from "fast-string-width";
981
+ import { wrapAnsi } from "fast-wrap-ansi";
982
+
983
+ // packages/toolcraft-design/src/components/template.ts
984
+ var MAX_PARTIAL_DEPTH = 100;
985
+ var HTML_ESCAPE = {
986
+ "&": "&amp;",
987
+ "<": "&lt;",
988
+ ">": "&gt;",
989
+ '"': "&quot;",
990
+ "'": "&#39;",
991
+ "/": "&#x2F;",
992
+ "`": "&#x60;",
993
+ "=": "&#x3D;"
994
+ };
995
+ function renderTemplate(template, view, options = {}) {
996
+ const prepared = options.yield === void 0 ? template : resolveTemplatePartials(template, options.partials ?? {}).split("{{yield}}").join(options.yield);
997
+ const tokens = parseTemplate(prepared);
998
+ validatePartialReferences(tokens, options.partials ?? {}, []);
999
+ if (options.validate === true) {
1000
+ const expanded = resolveTemplatePartials(prepared, options.partials ?? {});
1001
+ validateVariables(parseTemplate(expanded), { view });
1002
+ }
1003
+ const state = {
1004
+ escape: options.escape === "none" ? String : escapeHtml,
1005
+ partials: options.partials ?? {},
1006
+ partialStack: [],
1007
+ preserveMissing: options.yield !== void 0 && options.escape === "none",
1008
+ validate: options.validate === true
1009
+ };
1010
+ return renderTokens(tokens, { view }, prepared, state);
1011
+ }
1012
+ function resolveTemplatePartials(template, partials) {
1013
+ return expandTemplatePartials(template, partials, []);
1014
+ }
1015
+ function renderTemplateInContext(template, context, state) {
1016
+ return renderTokens(parseTemplate(template), context, template, state);
1017
+ }
1018
+ function parseTemplate(template) {
1019
+ const root = [];
1020
+ const stack = [];
1021
+ let tokens = root;
1022
+ let index = 0;
1023
+ while (index < template.length) {
1024
+ const open = template.indexOf("{{", index);
1025
+ if (open === -1) {
1026
+ appendText(tokens, template.slice(index), index);
1027
+ break;
1028
+ }
1029
+ appendText(tokens, template.slice(index, open), index);
1030
+ const parsed = parseTag(template, open);
1031
+ const standalone = getStandalone(template, open, parsed.end, parsed.kind);
1032
+ if (standalone !== void 0) {
1033
+ trimTextAfter(tokens, standalone.lineStart);
1034
+ }
1035
+ if (parsed.kind === "comment") {
1036
+ index = standalone?.nextIndex ?? parsed.end;
1037
+ continue;
1038
+ }
1039
+ if (parsed.kind === "delimiter") {
1040
+ throw new Error("Custom delimiters are not supported");
1041
+ }
1042
+ if (parsed.kind === "section" || parsed.kind === "inverted") {
1043
+ const token = {
1044
+ type: parsed.kind,
1045
+ name: parsed.name,
1046
+ children: [],
1047
+ rawStart: parsed.end,
1048
+ rawEnd: standalone?.lineStart ?? open
1049
+ };
1050
+ tokens.push(token);
1051
+ stack.push({ token, parent: tokens });
1052
+ tokens = token.children;
1053
+ index = standalone?.nextIndex ?? parsed.end;
1054
+ continue;
1055
+ }
1056
+ if (parsed.kind === "partial") {
1057
+ tokens.push({
1058
+ type: "partial",
1059
+ name: parsed.name,
1060
+ indent: standalone === void 0 ? "" : template.slice(standalone.lineStart, open)
1061
+ });
1062
+ index = standalone?.nextIndex ?? parsed.end;
1063
+ continue;
1064
+ }
1065
+ if (parsed.kind === "close") {
1066
+ const frame2 = stack.pop();
1067
+ if (frame2 === void 0) {
1068
+ throw new Error(`Closing unopened section "${parsed.name}"`);
1069
+ }
1070
+ if (frame2.token.name !== parsed.name) {
1071
+ throw new Error(`Unclosed section "${frame2.token.name}" before closing "${parsed.name}"`);
1072
+ }
1073
+ frame2.token.rawEnd = open;
1074
+ tokens = frame2.parent;
1075
+ index = standalone?.nextIndex ?? parsed.end;
1076
+ continue;
1077
+ }
1078
+ tokens.push({ type: parsed.kind, name: parsed.name, raw: template.slice(open, parsed.end) });
1079
+ index = parsed.end;
1080
+ }
1081
+ const frame = stack.pop();
1082
+ if (frame !== void 0) {
1083
+ throw new Error(`Unclosed section "${frame.token.name}"`);
1084
+ }
1085
+ return root;
1086
+ }
1087
+ function parseTag(template, open) {
1088
+ if (template.startsWith("{{{", open)) {
1089
+ const close2 = template.indexOf("}}}", open + 3);
1090
+ if (close2 === -1) {
1091
+ throw new Error("Unclosed unescaped tag");
1092
+ }
1093
+ return { kind: "unescaped", name: template.slice(open + 3, close2).trim(), end: close2 + 3 };
1094
+ }
1095
+ const close = template.indexOf("}}", open + 2);
1096
+ if (close === -1) {
1097
+ throw new Error("Unclosed tag");
1098
+ }
1099
+ const raw = template.slice(open + 2, close).trim();
1100
+ const sigil = raw[0];
1101
+ const name = sigil === void 0 ? "" : raw.slice(1).trim();
1102
+ const end = close + 2;
1103
+ if (sigil === "#") return { kind: "section", name, end };
1104
+ if (sigil === "^") return { kind: "inverted", name, end };
1105
+ if (sigil === "/") return { kind: "close", name, end };
1106
+ if (sigil === "!") return { kind: "comment", name, end };
1107
+ if (sigil === "&") return { kind: "unescaped", name, end };
1108
+ if (sigil === ">") return { kind: "partial", name, end };
1109
+ if (sigil === "=" && raw.endsWith("=")) return { kind: "delimiter", name, end };
1110
+ return { kind: "name", name: raw, end };
1111
+ }
1112
+ function getStandalone(template, tagStart, tagEnd, kind) {
1113
+ if (!["section", "inverted", "close", "comment", "partial", "delimiter"].includes(kind)) {
1114
+ return void 0;
1115
+ }
1116
+ const lineStart = template.lastIndexOf("\n", tagStart - 1) + 1;
1117
+ if (!isWhitespace(template.slice(lineStart, tagStart))) {
1118
+ return void 0;
1119
+ }
1120
+ let cursor = tagEnd;
1121
+ while (cursor < template.length && (template[cursor] === " " || template[cursor] === " ")) {
1122
+ cursor += 1;
1123
+ }
1124
+ if (template.startsWith("\r\n", cursor)) {
1125
+ return { lineStart, nextIndex: cursor + 2 };
1126
+ }
1127
+ if (template[cursor] === "\n") {
1128
+ return { lineStart, nextIndex: cursor + 1 };
1129
+ }
1130
+ if (cursor === template.length) {
1131
+ return { lineStart, nextIndex: cursor };
1132
+ }
1133
+ return void 0;
1134
+ }
1135
+ function renderTokens(tokens, context, template, state) {
1136
+ let output = "";
1137
+ for (const token of tokens) {
1138
+ switch (token.type) {
1139
+ case "text":
1140
+ output += token.value;
1141
+ continue;
1142
+ case "name":
1143
+ case "unescaped": {
1144
+ const result = lookup2(context, token.name);
1145
+ if (!result.hit || result.value == null) {
1146
+ if (state.validate) {
1147
+ throw new Error(`Template variable "${token.name}" not found.`);
1148
+ }
1149
+ if (state.preserveMissing) {
1150
+ output += token.raw;
1151
+ }
1152
+ continue;
1153
+ }
1154
+ const rendered = String(result.value);
1155
+ output += token.type === "name" ? state.escape(rendered) : rendered;
1156
+ continue;
1157
+ }
1158
+ case "partial": {
1159
+ if (!Object.hasOwn(state.partials, token.name)) {
1160
+ throw new Error(`Partial "${token.name}" not found.`);
1161
+ }
1162
+ if (state.partialStack.includes(token.name)) {
1163
+ throw new Error(
1164
+ `Circular partial reference detected: ${[...state.partialStack, token.name].join(" -> ")}.`
1165
+ );
1166
+ }
1167
+ if (state.partialStack.length >= MAX_PARTIAL_DEPTH) {
1168
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1169
+ }
1170
+ const partial = indentPartial(state.partials[token.name], token.indent);
1171
+ output += renderTokens(parseTemplate(partial), context, partial, {
1172
+ ...state,
1173
+ partialStack: [...state.partialStack, token.name]
1174
+ });
1175
+ continue;
1176
+ }
1177
+ case "inverted": {
1178
+ const result = lookup2(context, token.name);
1179
+ if (!result.hit && state.validate) {
1180
+ throw new Error(`Template variable "${token.name}" not found.`);
1181
+ }
1182
+ const value = result.value;
1183
+ if (!value || Array.isArray(value) && value.length === 0) {
1184
+ output += renderTokens(token.children, context, template, state);
1185
+ }
1186
+ continue;
1187
+ }
1188
+ case "section": {
1189
+ const result = lookup2(context, token.name);
1190
+ if (!result.hit && state.validate) {
1191
+ throw new Error(`Template variable "${token.name}" not found.`);
1192
+ }
1193
+ const value = result.value;
1194
+ if (!value) {
1195
+ continue;
1196
+ }
1197
+ if (Array.isArray(value)) {
1198
+ for (const item of value) {
1199
+ output += renderTokens(token.children, pushContext(context, item), template, state);
1200
+ }
1201
+ continue;
1202
+ }
1203
+ if (typeof value === "function") {
1204
+ const raw = template.slice(token.rawStart, token.rawEnd);
1205
+ const rendered = value.call(
1206
+ context.view,
1207
+ raw,
1208
+ (nextTemplate) => renderTemplateInContext(nextTemplate, context, state)
1209
+ );
1210
+ if (rendered != null) {
1211
+ output += String(rendered);
1212
+ }
1213
+ continue;
1214
+ }
1215
+ if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
1216
+ output += renderTokens(token.children, pushContext(context, value), template, state);
1217
+ continue;
1218
+ }
1219
+ output += renderTokens(token.children, context, template, state);
1220
+ }
1221
+ }
1222
+ }
1223
+ return output;
1224
+ }
1225
+ function lookup2(context, name) {
1226
+ if (name === ".") {
1227
+ return { hit: true, value: callLambda(context.view, context.view) };
1228
+ }
1229
+ let cursor = context;
1230
+ while (cursor !== void 0) {
1231
+ const result = name.includes(".") ? lookupDotted(cursor.view, name) : lookupName(cursor.view, name);
1232
+ if (result.hit) {
1233
+ return { hit: true, value: callLambda(result.value, cursor.view) };
1234
+ }
1235
+ cursor = cursor.parent;
1236
+ }
1237
+ return { hit: false, value: void 0 };
1238
+ }
1239
+ function validateVariables(tokens, context) {
1240
+ for (const token of tokens) {
1241
+ if (token.type === "text" || token.type === "partial") {
1242
+ continue;
1243
+ }
1244
+ if (token.type === "name" || token.type === "unescaped") {
1245
+ if (!lookup2(context, token.name).hit) {
1246
+ throw new Error(`Template variable "${token.name}" not found.`);
1247
+ }
1248
+ continue;
1249
+ }
1250
+ if (token.type !== "section" && token.type !== "inverted") {
1251
+ continue;
1252
+ }
1253
+ const result = lookup2(context, token.name);
1254
+ if (!result.hit) {
1255
+ throw new Error(`Template variable "${token.name}" not found.`);
1256
+ }
1257
+ if (Array.isArray(result.value) && result.value.length > 0) {
1258
+ for (const item of result.value) {
1259
+ validateVariables(token.children, pushContext(context, item));
1260
+ }
1261
+ continue;
1262
+ }
1263
+ if (typeof result.value === "object" && result.value !== null || typeof result.value === "string" || typeof result.value === "number") {
1264
+ validateVariables(token.children, pushContext(context, result.value));
1265
+ continue;
1266
+ }
1267
+ validateVariables(token.children, context);
1268
+ }
1269
+ }
1270
+ function validatePartialReferences(tokens, partials, partialStack) {
1271
+ for (const token of tokens) {
1272
+ if (token.type === "section" || token.type === "inverted") {
1273
+ validatePartialReferences(token.children, partials, partialStack);
1274
+ continue;
1275
+ }
1276
+ if (token.type !== "partial") {
1277
+ continue;
1278
+ }
1279
+ if (!Object.hasOwn(partials, token.name)) {
1280
+ throw new Error(`Partial "${token.name}" not found.`);
1281
+ }
1282
+ if (partialStack.includes(token.name)) {
1283
+ throw new Error(
1284
+ `Circular partial reference detected: ${[...partialStack, token.name].join(" -> ")}.`
1285
+ );
1286
+ }
1287
+ if (partialStack.length >= MAX_PARTIAL_DEPTH) {
1288
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1289
+ }
1290
+ validatePartialReferences(parseTemplate(partials[token.name]), partials, [
1291
+ ...partialStack,
1292
+ token.name
1293
+ ]);
1294
+ }
1295
+ }
1296
+ function indentPartial(partial, indent) {
1297
+ if (indent === "") {
1298
+ return partial;
1299
+ }
1300
+ return partial.split("\n").map((line) => line === "" ? "" : `${indent}${line}`).join("\n");
1301
+ }
1302
+ function expandTemplatePartials(template, partials, partialStack) {
1303
+ let output = "";
1304
+ let index = 0;
1305
+ while (index < template.length) {
1306
+ const open = template.indexOf("{{", index);
1307
+ if (open === -1) {
1308
+ output += template.slice(index);
1309
+ break;
1310
+ }
1311
+ const parsed = parseTag(template, open);
1312
+ if (parsed.kind !== "partial") {
1313
+ output += template.slice(index, parsed.end);
1314
+ index = parsed.end;
1315
+ continue;
1316
+ }
1317
+ if (!Object.hasOwn(partials, parsed.name)) {
1318
+ throw new Error(`Partial "${parsed.name}" not found.`);
1319
+ }
1320
+ if (partialStack.includes(parsed.name)) {
1321
+ throw new Error(
1322
+ `Circular partial reference detected: ${[...partialStack, parsed.name].join(" -> ")}.`
1323
+ );
1324
+ }
1325
+ if (partialStack.length >= MAX_PARTIAL_DEPTH) {
1326
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1327
+ }
1328
+ const standalone = getStandalone(template, open, parsed.end, parsed.kind);
1329
+ const beforePartial = standalone === void 0 ? template.slice(index, open) : template.slice(index, standalone.lineStart);
1330
+ const indent = standalone === void 0 ? "" : template.slice(standalone.lineStart, open);
1331
+ const partial = indentPartial(partials[parsed.name], indent);
1332
+ output += beforePartial + expandTemplatePartials(partial, partials, [...partialStack, parsed.name]);
1333
+ index = standalone?.nextIndex ?? parsed.end;
1334
+ }
1335
+ return output;
1336
+ }
1337
+ function lookupName(view, name) {
1338
+ if (!isPropertyContainer(view) || !hasProperty(view, name)) {
1339
+ return { hit: false, value: void 0 };
1340
+ }
1341
+ return { hit: true, value: view[name] };
1342
+ }
1343
+ function lookupDotted(view, name) {
1344
+ const parts = name.split(".");
1345
+ let value = view;
1346
+ for (const part of parts) {
1347
+ if (!isPropertyContainer(value) || !hasProperty(value, part)) {
1348
+ return { hit: false, value: void 0 };
1349
+ }
1350
+ value = Object(value)[part];
1351
+ }
1352
+ return { hit: true, value };
1353
+ }
1354
+ function callLambda(value, view) {
1355
+ return typeof value === "function" ? value.call(view) : value;
1356
+ }
1357
+ function pushContext(parent, view) {
1358
+ return { view, parent };
1359
+ }
1360
+ function appendText(tokens, value, start) {
1361
+ if (value === "") {
1362
+ return;
1363
+ }
1364
+ const previous = tokens[tokens.length - 1];
1365
+ if (previous?.type === "text") {
1366
+ previous.value += value;
1367
+ return;
1368
+ }
1369
+ tokens.push({ type: "text", value, start });
1370
+ }
1371
+ function trimTextAfter(tokens, lineStart) {
1372
+ const previous = tokens[tokens.length - 1];
1373
+ if (previous?.type !== "text") {
1374
+ return;
1375
+ }
1376
+ const keep = Math.max(0, lineStart - previous.start);
1377
+ previous.value = previous.value.slice(0, keep);
1378
+ if (previous.value === "") {
1379
+ tokens.pop();
1380
+ }
1381
+ }
1382
+ function escapeHtml(value) {
1383
+ return value.replace(/[&<>"'`=/]/g, (char) => HTML_ESCAPE[char] ?? char);
1384
+ }
1385
+ function isWhitespace(value) {
1386
+ return value.trim() === "";
1387
+ }
1388
+ function isPropertyContainer(value) {
1389
+ return typeof value === "object" && value !== null || typeof value === "function";
1390
+ }
1391
+ function hasProperty(value, key) {
1392
+ return Object.prototype.hasOwnProperty.call(value, key);
1393
+ }
1394
+
1395
+ // packages/toolcraft-design/src/components/browser.ts
1396
+ import { spawn } from "node:child_process";
1397
+ import process2 from "node:process";
1398
+
1399
+ // packages/frontmatter/src/parse.ts
1400
+ import { LineCounter, parse, parseDocument } from "yaml";
1401
+
1402
+ // packages/frontmatter/src/stringify.ts
1403
+ import { stringify } from "yaml";
1404
+
1405
+ // packages/toolcraft-design/src/dashboard/terminal-width.ts
1406
+ var graphemeSegmenter3 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1407
+
1408
+ // packages/toolcraft-design/src/acp/writer.ts
1409
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
1410
+ var storage = new AsyncLocalStorage2();
1411
+
1412
+ // packages/toolcraft-design/src/dashboard/terminal.ts
1413
+ import readline from "node:readline";
1414
+ import { PassThrough } from "node:stream";
1415
+
1416
+ // packages/toolcraft-design/src/explorer/state.ts
1417
+ var REGION_HEADER = 1 << 0;
1418
+ var REGION_LIST = 1 << 1;
1419
+ var REGION_DETAIL = 1 << 2;
1420
+ var REGION_FOOTER = 1 << 3;
1421
+ var REGION_MODAL = 1 << 4;
1422
+ var REGION_TOAST = 1 << 5;
1423
+ var REGION_ALL = REGION_HEADER | REGION_LIST | REGION_DETAIL | REGION_FOOTER | REGION_MODAL | REGION_TOAST;
1424
+
1425
+ // packages/toolcraft-design/src/prompts/interactive/glyphs.ts
1426
+ function supportsUnicode() {
1427
+ if (!process.platform.startsWith("win")) {
1428
+ return process.env.TERM !== "linux";
1429
+ }
1430
+ return Boolean(
1431
+ process.env.CI || process.env.WT_SESSION || process.env.TERMINUS_SUBLIME || process.env.ConEmuTask === "{cmd::Cmder}" || process.env.TERM_PROGRAM === "Terminus-Sublime" || process.env.TERM_PROGRAM === "vscode" || process.env.TERM === "xterm-256color" || process.env.TERM === "alacritty" || process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm"
1432
+ );
1433
+ }
1434
+ var UNICODE = supportsUnicode();
1435
+ function glyph(unicode, ascii) {
1436
+ return UNICODE ? unicode : ascii;
1437
+ }
1438
+ var GLYPHS = {
1439
+ stepActive: glyph("\u25C6", "*"),
1440
+ stepCancel: glyph("\u25A0", "x"),
1441
+ stepError: glyph("\u25B2", "x"),
1442
+ stepSubmit: glyph("\u25C7", "o"),
1443
+ barStart: glyph("\u250C", "T"),
1444
+ bar: glyph("\u2502", "|"),
1445
+ barEnd: glyph("\u2514", "-"),
1446
+ radioActive: glyph("\u25CF", ">"),
1447
+ radioInactive: glyph("\u25CB", " "),
1448
+ checkboxActive: glyph("\u25FB", "[ ]"),
1449
+ checkboxSelected: glyph("\u25FC", "[+]"),
1450
+ checkboxInactive: glyph("\u25FB", "[ ]"),
1451
+ passwordMask: glyph("\u2022", "*"),
1452
+ ellipsis: "..."
1453
+ };
1454
+
1455
+ // packages/toolcraft-design/src/prompts/interactive/core.ts
1456
+ import { EventEmitter } from "node:events";
1457
+ import * as readline2 from "node:readline";
1458
+
1459
+ // packages/toolcraft-design/src/prompts/interactive/wrap.ts
1460
+ import { wrapAnsi as wrapAnsi2 } from "fast-wrap-ansi";
1461
+ import stringWidth2 from "fast-string-width";
1462
+
1463
+ // packages/toolcraft-design/src/prompts/interactive/pagination.ts
1464
+ import { wrapAnsi as wrapAnsi3 } from "fast-wrap-ansi";
1465
+
1466
+ // packages/toolcraft-design/src/static/spinner.ts
1467
+ var SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
1468
+
1469
+ // packages/config-mutations/src/formats/json.ts
1470
+ import * as jsonc from "jsonc-parser";
1471
+
1472
+ // packages/config-mutations/src/formats/object.ts
1473
+ function cloneConfigObject(value) {
1474
+ const result = {};
1475
+ for (const [key, entry] of Object.entries(value)) {
1476
+ setConfigEntry(result, key, cloneConfigValue(entry));
1477
+ }
1478
+ return result;
1479
+ }
1480
+ function setConfigEntry(target, key, value) {
1481
+ Object.defineProperty(target, key, {
1482
+ configurable: true,
1483
+ enumerable: true,
1484
+ writable: true,
1485
+ value
1486
+ });
1487
+ }
1488
+ function hasConfigEntry(target, key) {
1489
+ return Object.prototype.hasOwnProperty.call(target, key);
1490
+ }
1491
+ function cloneConfigValue(value) {
1492
+ if (Array.isArray(value)) {
1493
+ return value.map((entry) => cloneConfigValue(entry));
1494
+ }
1495
+ if (value && typeof value === "object" && !(value instanceof Date)) {
1496
+ return cloneConfigObject(value);
1497
+ }
1498
+ return value;
1499
+ }
1500
+
1501
+ // packages/config-mutations/src/formats/json.ts
1502
+ function isConfigObject(value) {
1503
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1504
+ }
1505
+ function detectIndent(content) {
1506
+ const match = content.match(/^[\t ]+/m);
1507
+ if (match) {
1508
+ return match[0];
1509
+ }
1510
+ return " ";
1511
+ }
1512
+ function parse4(content) {
1513
+ if (!content || content.trim() === "") {
1514
+ return {};
1515
+ }
1516
+ const errors = [];
1517
+ const parsed = jsonc.parse(content, errors, {
1518
+ allowTrailingComma: true,
1519
+ disallowComments: false
1520
+ });
1521
+ if (errors.length > 0) {
1522
+ throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
1523
+ }
1524
+ if (parsed === null || parsed === void 0) {
1525
+ return {};
1526
+ }
1527
+ if (!isConfigObject(parsed)) {
1528
+ throw new Error("Expected JSON object.");
1529
+ }
1530
+ return cloneConfigObject(parsed);
1531
+ }
1532
+ function serialize(obj) {
1533
+ return `${JSON.stringify(obj, null, 2)}
1534
+ `;
1535
+ }
1536
+ function merge(base, patch) {
1537
+ const result = cloneConfigObject(base);
1538
+ for (const [key, value] of Object.entries(patch)) {
1539
+ if (value === void 0) {
1540
+ continue;
1541
+ }
1542
+ const existing = hasConfigEntry(result, key) ? result[key] : void 0;
1543
+ if (isConfigObject(existing) && isConfigObject(value)) {
1544
+ setConfigEntry(result, key, merge(existing, value));
1545
+ continue;
1546
+ }
1547
+ setConfigEntry(result, key, value);
1548
+ }
1549
+ return result;
1550
+ }
1551
+ function configValuesEqual(left, right) {
1552
+ return JSON.stringify(left) === JSON.stringify(right);
1553
+ }
1554
+ function prune(obj, shape) {
1555
+ let changed = false;
1556
+ const result = cloneConfigObject(obj);
1557
+ for (const [key, pattern] of Object.entries(shape)) {
1558
+ if (!hasConfigEntry(result, key)) {
1559
+ continue;
1560
+ }
1561
+ const current = result[key];
1562
+ if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
1563
+ delete result[key];
1564
+ changed = true;
1565
+ continue;
1566
+ }
1567
+ if (isConfigObject(pattern) && isConfigObject(current)) {
1568
+ const { changed: childChanged, result: childResult } = prune(
1569
+ current,
1570
+ pattern
1571
+ );
1572
+ if (childChanged) {
1573
+ changed = true;
1574
+ }
1575
+ if (Object.keys(childResult).length === 0) {
1576
+ delete result[key];
1577
+ } else {
1578
+ setConfigEntry(result, key, childResult);
1579
+ }
1580
+ continue;
1581
+ }
1582
+ if (isConfigObject(pattern) && Object.keys(pattern).length > 0) {
1583
+ continue;
1584
+ }
1585
+ delete result[key];
1586
+ changed = true;
1587
+ }
1588
+ return { changed, result };
1589
+ }
1590
+ function modifyAtPath(content, path9, value) {
1591
+ const indent = detectIndent(content);
1592
+ const formattingOptions = {
1593
+ tabSize: indent === " " ? 1 : indent.length,
1594
+ insertSpaces: indent !== " ",
1595
+ eol: "\n"
1596
+ };
1597
+ const edits = jsonc.modify(content, path9, value, { formattingOptions });
1598
+ let result = jsonc.applyEdits(content, edits);
1599
+ if (!result.endsWith("\n")) {
1600
+ result += "\n";
1601
+ }
1602
+ return result;
1603
+ }
1604
+ function removeAtPath(content, path9) {
1605
+ return modifyAtPath(content, path9, void 0);
1606
+ }
1607
+ function serializeUpdate(content, current, next) {
1608
+ let result = content || "{}";
1609
+ result = applyObjectUpdate(result, [], current, next);
1610
+ if (!result.endsWith("\n")) {
1611
+ result += "\n";
1612
+ }
1613
+ return result;
1614
+ }
1615
+ function applyObjectUpdate(content, path9, current, next) {
1616
+ let result = content;
1617
+ for (const key of Object.keys(current)) {
1618
+ if (!hasConfigEntry(next, key)) {
1619
+ result = removeAtPath(result, [...path9, key]);
1620
+ }
1621
+ }
1622
+ for (const [key, nextValue] of Object.entries(next)) {
1623
+ const nextPath = [...path9, key];
1624
+ const hasCurrent = hasConfigEntry(current, key);
1625
+ const currentValue = hasCurrent ? current[key] : void 0;
1626
+ if (hasCurrent && isConfigObject(currentValue) && isConfigObject(nextValue)) {
1627
+ result = applyObjectUpdate(result, nextPath, currentValue, nextValue);
1628
+ continue;
1629
+ }
1630
+ if (!hasCurrent || !configValuesEqual(currentValue, nextValue)) {
1631
+ result = modifyAtPath(result, nextPath, nextValue);
1632
+ }
1633
+ }
1634
+ return result;
1635
+ }
1636
+ var jsonFormat = {
1637
+ parse: parse4,
1638
+ serialize,
1639
+ serializeUpdate,
1640
+ merge,
1641
+ prune
1642
+ };
1643
+
1644
+ // packages/config-mutations/src/formats/toml.ts
1645
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
1646
+ function isConfigObject2(value) {
1647
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1648
+ }
1649
+ function parse5(content) {
1650
+ if (!content || content.trim() === "") {
1651
+ return {};
1652
+ }
1653
+ const parsed = parseToml(content);
1654
+ if (!isConfigObject2(parsed)) {
1655
+ throw new Error("Expected TOML document to be a table.");
1656
+ }
1657
+ return cloneConfigObject(parsed);
1658
+ }
1659
+ function serialize2(obj) {
1660
+ const serialized = stringifyToml(obj);
1661
+ return serialized.endsWith("\n") ? serialized : `${serialized}
1662
+ `;
1663
+ }
1664
+ function merge2(base, patch) {
1665
+ const result = cloneConfigObject(base);
1666
+ for (const [key, value] of Object.entries(patch)) {
1667
+ if (value === void 0) {
1668
+ continue;
1669
+ }
1670
+ const existing = hasConfigEntry(result, key) ? result[key] : void 0;
1671
+ if (isConfigObject2(existing) && isConfigObject2(value)) {
1672
+ setConfigEntry(result, key, merge2(existing, value));
1673
+ continue;
1674
+ }
1675
+ setConfigEntry(result, key, value);
1676
+ }
1677
+ return result;
1678
+ }
1679
+ function prune2(obj, shape) {
1680
+ let changed = false;
1681
+ const result = cloneConfigObject(obj);
1682
+ for (const [key, pattern] of Object.entries(shape)) {
1683
+ if (!hasConfigEntry(result, key)) {
1684
+ continue;
1685
+ }
1686
+ const current = result[key];
1687
+ if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
1688
+ delete result[key];
1689
+ changed = true;
1690
+ continue;
1691
+ }
1692
+ if (isConfigObject2(pattern) && isConfigObject2(current)) {
1693
+ const { changed: childChanged, result: childResult } = prune2(
1694
+ current,
1695
+ pattern
1696
+ );
1697
+ if (childChanged) {
1698
+ changed = true;
1699
+ }
1700
+ if (Object.keys(childResult).length === 0) {
1701
+ delete result[key];
1702
+ } else {
1703
+ setConfigEntry(result, key, childResult);
1704
+ }
1705
+ continue;
1706
+ }
1707
+ if (!isConfigObject2(pattern) || Object.keys(pattern).length === 0) {
1708
+ delete result[key];
1709
+ changed = true;
1710
+ }
1711
+ }
1712
+ return { changed, result };
1713
+ }
1714
+ var tomlFormat = {
1715
+ parse: parse5,
1716
+ serialize: serialize2,
1717
+ merge: merge2,
1718
+ prune: prune2
1719
+ };
1720
+
1721
+ // packages/config-mutations/src/formats/yaml.ts
1722
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1723
+ function isConfigObject3(value) {
1724
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1725
+ }
1726
+ function parse6(content) {
1727
+ if (!content || content.trim() === "") {
1728
+ return {};
1729
+ }
1730
+ const parsed = parseYaml(content);
1731
+ if (parsed === null || parsed === void 0) {
1732
+ return {};
1733
+ }
1734
+ if (!isConfigObject3(parsed)) {
1735
+ throw new Error("Expected YAML object.");
1736
+ }
1737
+ return cloneConfigObject(parsed);
1738
+ }
1739
+ function serialize3(obj) {
1740
+ const serialized = stringifyYaml(obj);
1741
+ return serialized.endsWith("\n") ? serialized : `${serialized}
1742
+ `;
1743
+ }
1744
+ function merge3(base, patch) {
1745
+ const result = cloneConfigObject(base);
1746
+ for (const [key, value] of Object.entries(patch)) {
1747
+ if (value === void 0) {
1748
+ continue;
1749
+ }
1750
+ const existing = hasConfigEntry(result, key) ? result[key] : void 0;
1751
+ if (isConfigObject3(existing) && isConfigObject3(value)) {
1752
+ setConfigEntry(result, key, merge3(existing, value));
1753
+ continue;
1754
+ }
1755
+ setConfigEntry(result, key, value);
1756
+ }
1757
+ return result;
1758
+ }
1759
+ function prune3(obj, shape) {
1760
+ let changed = false;
1761
+ const result = cloneConfigObject(obj);
1762
+ for (const [key, pattern] of Object.entries(shape)) {
1763
+ if (!hasConfigEntry(result, key)) {
1764
+ continue;
1765
+ }
1766
+ const current = result[key];
1767
+ if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
1768
+ delete result[key];
1769
+ changed = true;
1770
+ continue;
1771
+ }
1772
+ if (isConfigObject3(pattern) && isConfigObject3(current)) {
1773
+ const { changed: childChanged, result: childResult } = prune3(current, pattern);
1774
+ if (childChanged) {
1775
+ changed = true;
1776
+ }
1777
+ if (Object.keys(childResult).length === 0) {
1778
+ delete result[key];
1779
+ } else {
1780
+ setConfigEntry(result, key, childResult);
1781
+ }
1782
+ continue;
1783
+ }
1784
+ if (!isConfigObject3(pattern) || Object.keys(pattern).length === 0) {
1785
+ delete result[key];
1786
+ changed = true;
1787
+ }
1788
+ }
1789
+ return { changed, result };
1790
+ }
1791
+ var yamlFormat = {
1792
+ parse: parse6,
1793
+ serialize: serialize3,
1794
+ merge: merge3,
1795
+ prune: prune3
1796
+ };
1797
+
1798
+ // packages/config-mutations/src/formats/index.ts
1799
+ var formatRegistry = {
1800
+ json: jsonFormat,
1801
+ toml: tomlFormat,
1802
+ yaml: yamlFormat
1803
+ };
1804
+ var extensionMap = {
1805
+ ".json": "json",
1806
+ ".toml": "toml",
1807
+ ".yaml": "yaml",
1808
+ ".yml": "yaml"
1809
+ };
1810
+ function getConfigFormat(pathOrFormat) {
1811
+ if (Object.prototype.hasOwnProperty.call(formatRegistry, pathOrFormat)) {
1812
+ return formatRegistry[pathOrFormat];
1813
+ }
1814
+ const ext = getExtension(pathOrFormat);
1815
+ const formatName = extensionMap[ext];
1816
+ if (!formatName) {
1817
+ throw new Error(
1818
+ `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
1819
+ );
1820
+ }
1821
+ return formatRegistry[formatName];
1822
+ }
1823
+ function detectFormat(path9) {
1824
+ const ext = getExtension(path9);
1825
+ return extensionMap[ext];
1826
+ }
1827
+ function getExtension(path9) {
1828
+ const lastDot = path9.lastIndexOf(".");
1829
+ if (lastDot === -1) {
1830
+ return "";
1831
+ }
1832
+ return path9.slice(lastDot).toLowerCase();
1833
+ }
1834
+
1835
+ // packages/config-mutations/src/execution/path-utils.ts
1836
+ import path2 from "node:path";
1837
+ function expandHome(targetPath, homeDir) {
1838
+ if (!targetPath?.startsWith("~")) {
1839
+ return targetPath;
1840
+ }
1841
+ if (targetPath.startsWith("~./")) {
1842
+ targetPath = `~/.${targetPath.slice(3)}`;
1843
+ }
1844
+ let remainder = targetPath.slice(1);
1845
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1846
+ remainder = remainder.slice(1);
1847
+ } else if (remainder.startsWith(".")) {
1848
+ remainder = remainder.slice(1);
1849
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1850
+ remainder = remainder.slice(1);
1851
+ }
1852
+ }
1853
+ return remainder.length === 0 ? homeDir : path2.join(homeDir, remainder);
1854
+ }
1855
+ function validateHomePath(targetPath) {
1856
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
1857
+ throw new Error("Target path must be a non-empty string.");
1858
+ }
1859
+ if (!targetPath.startsWith("~")) {
1860
+ throw new Error(
1861
+ `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
1862
+ );
1863
+ }
1864
+ }
1865
+ function resolvePath(rawPath, homeDir, pathMapper) {
1866
+ validateHomePath(rawPath);
1867
+ const expanded = expandHome(rawPath, homeDir);
1868
+ const canonicalHome = path2.resolve(homeDir);
1869
+ const canonicalExpanded = path2.resolve(expanded);
1870
+ const relative = path2.relative(canonicalHome, canonicalExpanded);
1871
+ if (relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
1872
+ throw new Error(`Target path resolves outside home directory: "${rawPath}"`);
1873
+ }
1874
+ if (!pathMapper) {
1875
+ return canonicalExpanded;
1876
+ }
1877
+ const rawDirectory = path2.dirname(expanded);
1878
+ const mappedDirectory = pathMapper.mapTargetDirectory({
1879
+ targetDirectory: rawDirectory
1880
+ });
1881
+ const filename = path2.basename(expanded);
1882
+ return filename.length === 0 ? mappedDirectory : path2.join(mappedDirectory, filename);
1883
+ }
1884
+
1885
+ // packages/config-mutations/src/error-codes.ts
1886
+ function hasOwnErrorCode(error2, code) {
1887
+ return typeof error2 === "object" && error2 !== null && Object.prototype.hasOwnProperty.call(error2, "code") && error2.code === code;
1888
+ }
1889
+
1890
+ // packages/config-mutations/src/fs-utils.ts
1891
+ function isNotFound(error2) {
1892
+ return hasOwnErrorCode(error2, "ENOENT");
1893
+ }
1894
+ async function readFileIfExists(fs5, target) {
1895
+ try {
1896
+ return await fs5.readFile(target, "utf8");
1897
+ } catch (error2) {
1898
+ if (isNotFound(error2)) {
1899
+ return null;
1900
+ }
1901
+ throw error2;
1902
+ }
1903
+ }
1904
+ async function pathExists(fs5, target) {
1905
+ try {
1906
+ await fs5.stat(target);
1907
+ return true;
1908
+ } catch (error2) {
1909
+ if (isNotFound(error2)) {
1910
+ return false;
1911
+ }
1912
+ throw error2;
1913
+ }
1914
+ }
1915
+ function createTimestamp() {
1916
+ return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
1917
+ }
1918
+
1919
+ // packages/config-mutations/src/execution/apply-mutation.ts
1920
+ function resolveValue(resolver, options) {
1921
+ if (typeof resolver === "function") {
1922
+ return resolver(options);
1923
+ }
1924
+ return resolver;
1925
+ }
1926
+ function createInvalidDocumentBackupPath(targetPath) {
1927
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
1928
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
1929
+ }
1930
+ async function backupInvalidDocument(context, targetPath, content) {
1931
+ const baseBackupPath = createInvalidDocumentBackupPath(targetPath);
1932
+ let attempt = 0;
1933
+ while (true) {
1934
+ const backupPath = attempt === 0 ? baseBackupPath : `${baseBackupPath}-${attempt}`;
1935
+ await assertRegularWriteTarget(context, backupPath);
1936
+ try {
1937
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8", flag: "wx" });
1938
+ return;
1939
+ } catch (error2) {
1940
+ if (!isAlreadyExists(error2)) {
1941
+ await context.fs.unlink(backupPath).catch(() => void 0);
1942
+ throw error2;
1943
+ }
1944
+ attempt += 1;
1945
+ }
1946
+ }
1947
+ }
1948
+ function isAlreadyExists(error2) {
1949
+ return hasOwnErrorCode(error2, "EEXIST");
1950
+ }
1951
+ async function assertRegularWriteTarget(context, targetPath) {
1952
+ const boundary = path3.dirname(path3.resolve(context.homeDir));
1953
+ let currentPath = path3.resolve(targetPath);
1954
+ while (currentPath !== boundary) {
1955
+ try {
1956
+ if ((await context.fs.lstat(currentPath)).isSymbolicLink()) {
1957
+ throw new Error(`Refusing mutation write through symbolic link: ${currentPath}`);
1958
+ }
1959
+ } catch (error2) {
1960
+ if (!isNotFound(error2)) {
1961
+ throw error2;
1962
+ }
1963
+ }
1964
+ const parentPath = path3.dirname(currentPath);
1965
+ if (parentPath === currentPath) {
1966
+ return;
1967
+ }
1968
+ currentPath = parentPath;
1969
+ }
1970
+ }
1971
+ async function writeAtomically(context, targetPath, content) {
1972
+ await assertRegularWriteTarget(context, targetPath);
1973
+ for (let attempt = 0; attempt < 10; attempt += 1) {
1974
+ const tempPath = `${targetPath}.mutation-tmp-${process.pid}-${randomUUID()}`;
1975
+ let tempCreated = false;
1976
+ try {
1977
+ await assertRegularWriteTarget(context, tempPath);
1978
+ await context.fs.writeFile(tempPath, content, { encoding: "utf8", flag: "wx" });
1979
+ tempCreated = true;
1980
+ await context.fs.rename(tempPath, targetPath);
1981
+ tempCreated = false;
1982
+ return;
1983
+ } catch (error2) {
1984
+ const alreadyExists = isAlreadyExists(error2);
1985
+ if (tempCreated || !alreadyExists) {
1986
+ try {
1987
+ await context.fs.unlink(tempPath);
1988
+ } catch (cleanupError) {
1989
+ if (!isNotFound(cleanupError)) {
1990
+ void cleanupError;
1991
+ }
1992
+ }
1993
+ }
1994
+ if (alreadyExists) {
1995
+ continue;
1996
+ }
1997
+ throw error2;
1998
+ }
1999
+ }
2000
+ throw new Error(`Unable to create temporary mutation file for ${targetPath}.`);
2001
+ }
2002
+ function describeMutation(kind, targetPath) {
2003
+ const displayPath = targetPath ?? "target";
2004
+ switch (kind) {
2005
+ case "ensureDirectory":
2006
+ return `Create ${displayPath}`;
2007
+ case "removeDirectory":
2008
+ return `Remove directory ${displayPath}`;
2009
+ case "backup":
2010
+ return `Backup ${displayPath}`;
2011
+ case "restoreBackup":
2012
+ return `Restore ${displayPath}`;
2013
+ case "templateWrite":
2014
+ return `Write ${displayPath}`;
2015
+ case "chmod":
2016
+ return `Set permissions on ${displayPath}`;
2017
+ case "removeFile":
2018
+ return `Remove ${displayPath}`;
2019
+ case "configMerge":
2020
+ case "configPrune":
2021
+ case "configTransform":
2022
+ case "templateMergeToml":
2023
+ case "templateMergeJson":
2024
+ return `Update ${displayPath}`;
2025
+ default:
2026
+ return "Operation";
2027
+ }
2028
+ }
2029
+ function mutationTargetPath(mutation, options) {
2030
+ switch (mutation.kind) {
2031
+ case "ensureDirectory":
2032
+ case "removeDirectory":
2033
+ return resolveValue(mutation.path, options);
2034
+ case "removeFile":
2035
+ case "chmod":
2036
+ case "backup":
2037
+ case "restoreBackup":
2038
+ case "configMerge":
2039
+ case "configPrune":
2040
+ case "configTransform":
2041
+ case "templateWrite":
2042
+ case "templateMergeToml":
2043
+ case "templateMergeJson":
2044
+ return resolveValue(mutation.target, options);
2045
+ default:
2046
+ return void 0;
2047
+ }
2048
+ }
2049
+ function resolveMutationDetails(mutation, context, options) {
2050
+ try {
2051
+ const rawTarget = mutationTargetPath(mutation, options);
2052
+ if (rawTarget === void 0) {
2053
+ return {
2054
+ kind: mutation.kind,
2055
+ label: mutation.label ?? mutation.kind
2056
+ };
2057
+ }
2058
+ try {
2059
+ const targetPath = resolvePath(rawTarget, context.homeDir, context.pathMapper);
2060
+ return {
2061
+ kind: mutation.kind,
2062
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2063
+ targetPath
2064
+ };
2065
+ } catch {
2066
+ return {
2067
+ kind: mutation.kind,
2068
+ label: mutation.label ?? describeMutation(mutation.kind, rawTarget),
2069
+ targetPath: void 0
2070
+ };
2071
+ }
2072
+ } catch {
2073
+ return {
2074
+ kind: mutation.kind,
2075
+ label: mutation.label ?? mutation.kind
2076
+ };
2077
+ }
2078
+ }
2079
+ function pruneKeysByPrefix(table, prefix) {
2080
+ const result = {};
2081
+ for (const [key, value] of Object.entries(table)) {
2082
+ if (!key.startsWith(prefix)) {
2083
+ setConfigEntry(result, key, value);
2084
+ }
2085
+ }
2086
+ return result;
2087
+ }
2088
+ function isConfigObject4(value) {
2089
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2090
+ }
2091
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
2092
+ const result = cloneConfigObject(base);
2093
+ const prefixMap = pruneByPrefix ?? {};
2094
+ for (const [key, value] of Object.entries(patch)) {
2095
+ if (value === void 0) {
2096
+ continue;
2097
+ }
2098
+ const current = result[key];
2099
+ const prefix = prefixMap[key];
2100
+ if (isConfigObject4(current) && isConfigObject4(value)) {
2101
+ if (prefix) {
2102
+ const pruned = pruneKeysByPrefix(current, prefix);
2103
+ setConfigEntry(result, key, mergePrunedConfigObject(pruned, value));
2104
+ } else {
2105
+ setConfigEntry(result, key, mergeWithPruneByPrefix(current, value, prefixMap));
2106
+ }
2107
+ continue;
2108
+ }
2109
+ setConfigEntry(result, key, value);
2110
+ }
2111
+ return result;
2112
+ }
2113
+ function mergePrunedConfigObject(base, patch) {
2114
+ const result = cloneConfigObject(base);
2115
+ for (const [key, value] of Object.entries(patch)) {
2116
+ if (value === void 0) {
2117
+ continue;
2118
+ }
2119
+ setConfigEntry(result, key, value);
2120
+ }
2121
+ return result;
2122
+ }
2123
+ function serializeConfigUpdate(format, rawContent, current, next) {
2124
+ if (rawContent !== null && format.serializeUpdate) {
2125
+ return format.serializeUpdate(rawContent, current, next);
2126
+ }
2127
+ return format.serialize(next);
2128
+ }
2129
+ async function applyMutation(mutation, context, options) {
2130
+ switch (mutation.kind) {
2131
+ case "ensureDirectory":
2132
+ return applyEnsureDirectory(mutation, context, options);
2133
+ case "removeDirectory":
2134
+ return applyRemoveDirectory(mutation, context, options);
2135
+ case "removeFile":
2136
+ return applyRemoveFile(mutation, context, options);
2137
+ case "chmod":
2138
+ return applyChmod(mutation, context, options);
2139
+ case "backup":
2140
+ return applyBackup(mutation, context, options);
2141
+ case "restoreBackup":
2142
+ return applyRestoreBackup(mutation, context, options);
2143
+ case "configMerge":
2144
+ return applyConfigMerge(mutation, context, options);
2145
+ case "configPrune":
2146
+ return applyConfigPrune(mutation, context, options);
2147
+ case "configTransform":
2148
+ return applyConfigTransform(mutation, context, options);
2149
+ case "templateWrite":
2150
+ return applyTemplateWrite(mutation, context, options);
2151
+ case "templateMergeToml":
2152
+ return applyTemplateMerge(mutation, context, options, "toml");
2153
+ case "templateMergeJson":
2154
+ return applyTemplateMerge(mutation, context, options, "json");
2155
+ default: {
2156
+ const never = mutation;
2157
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
2158
+ }
2159
+ }
2160
+ }
2161
+ async function applyEnsureDirectory(mutation, context, options) {
2162
+ const rawPath = resolveValue(mutation.path, options);
2163
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2164
+ const details = {
2165
+ kind: mutation.kind,
2166
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2167
+ targetPath
2168
+ };
2169
+ await assertRegularWriteTarget(context, targetPath);
2170
+ const existed = await pathExists(context.fs, targetPath);
2171
+ if (!context.dryRun) {
2172
+ await context.fs.mkdir(targetPath, { recursive: true });
2173
+ }
2174
+ return {
2175
+ outcome: {
2176
+ changed: !existed,
2177
+ effect: "mkdir",
2178
+ detail: existed ? "noop" : "create"
2179
+ },
2180
+ details
2181
+ };
2182
+ }
2183
+ async function applyRemoveDirectory(mutation, context, options) {
2184
+ const rawPath = resolveValue(mutation.path, options);
2185
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2186
+ const details = {
2187
+ kind: mutation.kind,
2188
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2189
+ targetPath
2190
+ };
2191
+ const existed = await pathExists(context.fs, targetPath);
2192
+ if (!existed) {
2193
+ return {
2194
+ outcome: { changed: false, effect: "none", detail: "noop" },
2195
+ details
2196
+ };
2197
+ }
2198
+ if (typeof context.fs.rm !== "function") {
2199
+ return {
2200
+ outcome: { changed: false, effect: "none", detail: "noop" },
2201
+ details
2202
+ };
2203
+ }
2204
+ if (mutation.force) {
2205
+ if (!context.dryRun) {
2206
+ await context.fs.rm(targetPath, { recursive: true, force: true });
2207
+ }
2208
+ return {
2209
+ outcome: { changed: true, effect: "delete", detail: "delete" },
2210
+ details
2211
+ };
2212
+ }
2213
+ const entries = await context.fs.readdir(targetPath);
2214
+ if (entries.length > 0) {
2215
+ return {
2216
+ outcome: { changed: false, effect: "none", detail: "noop" },
2217
+ details
2218
+ };
2219
+ }
2220
+ if (!context.dryRun) {
2221
+ await context.fs.rm(targetPath, { recursive: true, force: true });
2222
+ }
2223
+ return {
2224
+ outcome: { changed: true, effect: "delete", detail: "delete" },
2225
+ details
2226
+ };
2227
+ }
2228
+ async function applyRemoveFile(mutation, context, options) {
2229
+ const rawPath = resolveValue(mutation.target, options);
2230
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2231
+ const details = {
2232
+ kind: mutation.kind,
2233
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2234
+ targetPath
2235
+ };
2236
+ try {
2237
+ const content = await context.fs.readFile(targetPath, "utf8");
2238
+ const trimmed = content.trim();
2239
+ if (mutation.whenContentMatches) {
2240
+ mutation.whenContentMatches.lastIndex = 0;
2241
+ }
2242
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
2243
+ return {
2244
+ outcome: { changed: false, effect: "none", detail: "noop" },
2245
+ details
2246
+ };
2247
+ }
2248
+ if (mutation.whenEmpty && trimmed.length > 0) {
2249
+ return {
2250
+ outcome: { changed: false, effect: "none", detail: "noop" },
2251
+ details
2252
+ };
2253
+ }
2254
+ if (!context.dryRun) {
2255
+ await context.fs.unlink(targetPath);
2256
+ }
2257
+ return {
2258
+ outcome: { changed: true, effect: "delete", detail: "delete" },
2259
+ details
2260
+ };
2261
+ } catch (error2) {
2262
+ if (isNotFound(error2)) {
2263
+ return {
2264
+ outcome: { changed: false, effect: "none", detail: "noop" },
2265
+ details
2266
+ };
2267
+ }
2268
+ throw error2;
2269
+ }
2270
+ }
2271
+ async function applyChmod(mutation, context, options) {
2272
+ const rawPath = resolveValue(mutation.target, options);
2273
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2274
+ const details = {
2275
+ kind: mutation.kind,
2276
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2277
+ targetPath
2278
+ };
2279
+ if (typeof context.fs.chmod !== "function") {
2280
+ return {
2281
+ outcome: { changed: false, effect: "none", detail: "noop" },
2282
+ details
2283
+ };
2284
+ }
2285
+ try {
2286
+ await assertRegularWriteTarget(context, targetPath);
2287
+ const stat3 = await context.fs.stat(targetPath);
2288
+ const currentMode = typeof stat3.mode === "number" ? stat3.mode & 511 : null;
2289
+ if (currentMode === mutation.mode) {
2290
+ return {
2291
+ outcome: { changed: false, effect: "none", detail: "noop" },
2292
+ details
2293
+ };
2294
+ }
2295
+ if (!context.dryRun) {
2296
+ await context.fs.chmod(targetPath, mutation.mode);
2297
+ }
2298
+ return {
2299
+ outcome: { changed: true, effect: "chmod", detail: "update" },
2300
+ details
2301
+ };
2302
+ } catch (error2) {
2303
+ if (isNotFound(error2)) {
2304
+ return {
2305
+ outcome: { changed: false, effect: "none", detail: "noop" },
2306
+ details
2307
+ };
2308
+ }
2309
+ throw error2;
2310
+ }
2311
+ }
2312
+ async function applyBackup(mutation, context, options) {
2313
+ const rawPath = resolveValue(mutation.target, options);
2314
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2315
+ const details = {
2316
+ kind: mutation.kind,
2317
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2318
+ targetPath
2319
+ };
2320
+ await assertRegularWriteTarget(context, targetPath);
2321
+ if (mutation.once && await findLatestGeneratedBackup(context.fs, targetPath) !== null) {
2322
+ return {
2323
+ outcome: { changed: false, effect: "none", detail: "noop" },
2324
+ details
2325
+ };
2326
+ }
2327
+ const content = await readFileIfExists(context.fs, targetPath);
2328
+ if (content === null && !mutation.once) {
2329
+ return {
2330
+ outcome: { changed: false, effect: "none", detail: "noop" },
2331
+ details
2332
+ };
2333
+ }
2334
+ if (!context.dryRun) {
2335
+ const baseBackupPath = `${targetPath}.backup-${createTimestamp()}${content === null ? ".missing" : ""}`;
2336
+ let attempt = 0;
2337
+ while (true) {
2338
+ const backupPath = attempt === 0 ? baseBackupPath : `${baseBackupPath}-${attempt}`;
2339
+ try {
2340
+ await assertRegularWriteTarget(context, backupPath);
2341
+ await context.fs.writeFile(backupPath, content ?? "", { encoding: "utf8", flag: "wx" });
2342
+ break;
2343
+ } catch (error2) {
2344
+ if (!isAlreadyExists(error2)) {
2345
+ await context.fs.unlink(backupPath).catch(() => void 0);
2346
+ throw error2;
2347
+ }
2348
+ attempt += 1;
2349
+ }
2350
+ }
2351
+ }
2352
+ return {
2353
+ outcome: { changed: true, effect: "copy", detail: "backup" },
2354
+ details
2355
+ };
2356
+ }
2357
+ async function applyRestoreBackup(mutation, context, options) {
2358
+ const rawPath = resolveValue(mutation.target, options);
2359
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2360
+ const details = {
2361
+ kind: mutation.kind,
2362
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2363
+ targetPath
2364
+ };
2365
+ const backup2 = await findLatestGeneratedBackup(context.fs, targetPath);
2366
+ if (backup2 === null) {
2367
+ return { outcome: { changed: false, effect: "none", detail: "noop" }, details };
2368
+ }
2369
+ if (!context.dryRun) {
2370
+ await assertRegularWriteTarget(context, backup2.path);
2371
+ if (backup2.originallyMissing) {
2372
+ try {
2373
+ await context.fs.unlink(targetPath);
2374
+ } catch (error2) {
2375
+ if (!isNotFound(error2)) {
2376
+ throw error2;
2377
+ }
2378
+ }
2379
+ } else {
2380
+ const content = await context.fs.readFile(backup2.path, "utf8");
2381
+ await writeAtomically(context, targetPath, content);
2382
+ }
2383
+ await context.fs.unlink(backup2.path);
2384
+ }
2385
+ return { outcome: { changed: true, effect: "copy", detail: "restore" }, details };
2386
+ }
2387
+ async function findLatestGeneratedBackup(fs5, targetPath) {
2388
+ const separatorIndex = targetPath.lastIndexOf("/");
2389
+ const directoryPath = separatorIndex <= 0 ? "/" : targetPath.slice(0, separatorIndex);
2390
+ const targetName = targetPath.slice(separatorIndex + 1);
2391
+ let entries;
2392
+ try {
2393
+ entries = await fs5.readdir(directoryPath);
2394
+ } catch (error2) {
2395
+ if (isNotFound(error2)) {
2396
+ return null;
2397
+ }
2398
+ throw error2;
2399
+ }
2400
+ const backupName = entries.filter((entry) => isGeneratedBackupName(entry, targetName)).sort().at(-1);
2401
+ return backupName === void 0 ? null : {
2402
+ path: `${directoryPath}/${backupName}`,
2403
+ originallyMissing: backupName.endsWith(".missing")
2404
+ };
2405
+ }
2406
+ function isGeneratedBackupName(entry, targetName) {
2407
+ const prefix = `${targetName}.backup-`;
2408
+ if (!entry.startsWith(prefix)) {
2409
+ return false;
2410
+ }
2411
+ const suffix = entry.slice(prefix.length);
2412
+ const timestamp = suffix.slice(0, 24);
2413
+ if (timestamp.length !== 24 || timestamp[4] !== "-" || timestamp[7] !== "-" || timestamp[10] !== "T" || timestamp[13] !== "-" || timestamp[16] !== "-" || timestamp[19] !== "-" || timestamp[23] !== "Z") {
2414
+ return false;
2415
+ }
2416
+ const parsedTimestamp = `${timestamp.slice(0, 13)}:${timestamp.slice(14, 16)}:${timestamp.slice(17, 19)}.${timestamp.slice(20)}`;
2417
+ if (Number.isNaN(Date.parse(parsedTimestamp))) {
2418
+ return false;
2419
+ }
2420
+ const collisionSuffix = suffix.slice(24);
2421
+ if (collisionSuffix.length === 0) {
2422
+ return true;
2423
+ }
2424
+ if (collisionSuffix === ".missing") {
2425
+ return true;
2426
+ }
2427
+ return collisionSuffix[0] === "-" && collisionSuffix.slice(1).length > 0 && [...collisionSuffix.slice(1)].every((character) => character >= "0" && character <= "9");
2428
+ }
2429
+ async function applyConfigMerge(mutation, context, options) {
2430
+ const rawPath = resolveValue(mutation.target, options);
2431
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2432
+ const details = {
2433
+ kind: mutation.kind,
2434
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2435
+ targetPath
2436
+ };
2437
+ const formatName = mutation.format ?? detectFormat(rawPath);
2438
+ if (!formatName) {
2439
+ throw new Error(
2440
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
2441
+ );
2442
+ }
2443
+ const format = getConfigFormat(formatName);
2444
+ const rawContent = await readFileIfExists(context.fs, targetPath);
2445
+ let preserveContent = rawContent;
2446
+ let current;
2447
+ try {
2448
+ current = rawContent === null ? {} : format.parse(rawContent);
2449
+ } catch {
2450
+ if (rawContent !== null && !context.dryRun) {
2451
+ await backupInvalidDocument(context, targetPath, rawContent);
2452
+ }
2453
+ current = {};
2454
+ preserveContent = null;
2455
+ }
2456
+ const value = resolveValue(mutation.value, options);
2457
+ if (!isConfigObject4(value)) {
2458
+ throw new Error(`configMerge value must be an object for "${rawPath}".`);
2459
+ }
2460
+ let merged;
2461
+ if (mutation.pruneByPrefix) {
2462
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
2463
+ } else {
2464
+ merged = format.merge(current, value);
2465
+ }
2466
+ const serialized = serializeConfigUpdate(format, preserveContent, current, merged);
2467
+ const changed = serialized !== rawContent;
2468
+ if (changed && !context.dryRun) {
2469
+ await writeAtomically(context, targetPath, serialized);
2470
+ }
2471
+ return {
2472
+ outcome: {
2473
+ changed,
2474
+ effect: changed ? "write" : "none",
2475
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
2476
+ },
2477
+ details
2478
+ };
2479
+ }
2480
+ async function applyConfigPrune(mutation, context, options) {
2481
+ const rawPath = resolveValue(mutation.target, options);
2482
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2483
+ const details = {
2484
+ kind: mutation.kind,
2485
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2486
+ targetPath
2487
+ };
2488
+ const rawContent = await readFileIfExists(context.fs, targetPath);
2489
+ if (rawContent === null) {
6
2490
  return {
7
- readFile: ((filePath, encoding) => {
8
- if (encoding) {
9
- return fs.readFile(filePath, encoding);
10
- }
11
- return fs.readFile(filePath);
12
- }),
13
- symlink: (target, filePath) => fs.symlink(target, filePath),
14
- readlink: (filePath) => fs.readlink(filePath, { encoding: "utf8" }),
15
- realpath: (filePath) => fs.realpath(filePath),
16
- writeFile: (filePath, data, options) => fs.writeFile(filePath, data, options),
17
- mkdir: (filePath, options) => fs.mkdir(filePath, options).then(() => { }),
18
- stat: (filePath) => fs.stat(filePath),
19
- lstat: (filePath) => fs.lstat(filePath),
20
- rename: (oldPath, newPath) => fs.rename(oldPath, newPath),
21
- rm: (filePath, options) => fs.rm(filePath, options),
22
- unlink: (filePath) => fs.unlink(filePath),
23
- readdir: (filePath) => fs.readdir(filePath),
24
- copyFile: (src, dest) => fs.copyFile(src, dest),
25
- chmod: (filePath, mode) => fs.chmod(filePath, mode)
2491
+ outcome: { changed: false, effect: "none", detail: "noop" },
2492
+ details
26
2493
  };
2494
+ }
2495
+ const formatName = mutation.format ?? detectFormat(rawPath);
2496
+ if (!formatName) {
2497
+ throw new Error(
2498
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
2499
+ );
2500
+ }
2501
+ const format = getConfigFormat(formatName);
2502
+ let current;
2503
+ try {
2504
+ current = format.parse(rawContent);
2505
+ } catch {
2506
+ return {
2507
+ outcome: { changed: false, effect: "none", detail: "noop" },
2508
+ details
2509
+ };
2510
+ }
2511
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
2512
+ return {
2513
+ outcome: { changed: false, effect: "none", detail: "noop" },
2514
+ details
2515
+ };
2516
+ }
2517
+ const shape = resolveValue(mutation.shape, options);
2518
+ const { changed, result } = format.prune(current, shape);
2519
+ if (!changed) {
2520
+ return {
2521
+ outcome: { changed: false, effect: "none", detail: "noop" },
2522
+ details
2523
+ };
2524
+ }
2525
+ if (Object.keys(result).length === 0) {
2526
+ if (!context.dryRun) {
2527
+ await context.fs.unlink(targetPath);
2528
+ }
2529
+ return {
2530
+ outcome: { changed: true, effect: "delete", detail: "delete" },
2531
+ details
2532
+ };
2533
+ }
2534
+ const serialized = serializeConfigUpdate(format, rawContent, current, result);
2535
+ if (!context.dryRun) {
2536
+ await writeAtomically(context, targetPath, serialized);
2537
+ }
2538
+ return {
2539
+ outcome: { changed: true, effect: "write", detail: "update" },
2540
+ details
2541
+ };
2542
+ }
2543
+ async function applyConfigTransform(mutation, context, options) {
2544
+ const rawPath = resolveValue(mutation.target, options);
2545
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2546
+ const details = {
2547
+ kind: mutation.kind,
2548
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2549
+ targetPath
2550
+ };
2551
+ const formatName = mutation.format ?? detectFormat(rawPath);
2552
+ if (!formatName) {
2553
+ throw new Error(
2554
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
2555
+ );
2556
+ }
2557
+ const format = getConfigFormat(formatName);
2558
+ const rawContent = await readFileIfExists(context.fs, targetPath);
2559
+ let preserveContent = rawContent;
2560
+ let current;
2561
+ try {
2562
+ current = rawContent === null ? {} : format.parse(rawContent);
2563
+ } catch {
2564
+ if (rawContent !== null && !context.dryRun) {
2565
+ await backupInvalidDocument(context, targetPath, rawContent);
2566
+ }
2567
+ current = {};
2568
+ preserveContent = null;
2569
+ }
2570
+ const { content: transformed, changed } = mutation.transform(current, options);
2571
+ if (!changed) {
2572
+ return {
2573
+ outcome: { changed: false, effect: "none", detail: "noop" },
2574
+ details
2575
+ };
2576
+ }
2577
+ if (transformed === null) {
2578
+ if (rawContent === null) {
2579
+ return {
2580
+ outcome: { changed: false, effect: "none", detail: "noop" },
2581
+ details
2582
+ };
2583
+ }
2584
+ if (!context.dryRun) {
2585
+ await context.fs.unlink(targetPath);
2586
+ }
2587
+ return {
2588
+ outcome: { changed: true, effect: "delete", detail: "delete" },
2589
+ details
2590
+ };
2591
+ }
2592
+ const serialized = serializeConfigUpdate(format, preserveContent, current, transformed);
2593
+ const serializedChanged = serialized !== rawContent;
2594
+ if (!serializedChanged) {
2595
+ return {
2596
+ outcome: { changed: false, effect: "none", detail: "noop" },
2597
+ details
2598
+ };
2599
+ }
2600
+ if (!context.dryRun) {
2601
+ await writeAtomically(context, targetPath, serialized);
2602
+ }
2603
+ return {
2604
+ outcome: {
2605
+ changed: true,
2606
+ effect: "write",
2607
+ detail: rawContent === null ? "create" : "update"
2608
+ },
2609
+ details
2610
+ };
2611
+ }
2612
+ async function applyTemplateWrite(mutation, context, options) {
2613
+ if (!context.templates) {
2614
+ throw new Error(
2615
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
2616
+ );
2617
+ }
2618
+ const rawPath = resolveValue(mutation.target, options);
2619
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2620
+ const details = {
2621
+ kind: mutation.kind,
2622
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2623
+ targetPath
2624
+ };
2625
+ const template = await context.templates(mutation.templateId);
2626
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2627
+ const rendered = renderTemplate(template, templateContext);
2628
+ const current = await readFileIfExists(context.fs, targetPath);
2629
+ const changed = current !== rendered;
2630
+ if (changed && !context.dryRun) {
2631
+ await writeAtomically(context, targetPath, rendered);
2632
+ }
2633
+ return {
2634
+ outcome: {
2635
+ changed,
2636
+ effect: changed ? "write" : "none",
2637
+ detail: changed ? current === null ? "create" : "update" : "noop"
2638
+ },
2639
+ details
2640
+ };
2641
+ }
2642
+ async function applyTemplateMerge(mutation, context, options, formatName) {
2643
+ if (!context.templates) {
2644
+ throw new Error(
2645
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
2646
+ );
2647
+ }
2648
+ const rawPath = resolveValue(mutation.target, options);
2649
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2650
+ const details = {
2651
+ kind: mutation.kind,
2652
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2653
+ targetPath
2654
+ };
2655
+ const format = getConfigFormat(formatName);
2656
+ const template = await context.templates(mutation.templateId);
2657
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2658
+ const rendered = renderTemplate(template, templateContext);
2659
+ let templateDoc;
2660
+ try {
2661
+ templateDoc = format.parse(rendered);
2662
+ } catch (error2) {
2663
+ throw new Error(
2664
+ `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error2}`,
2665
+ { cause: error2 }
2666
+ );
2667
+ }
2668
+ const rawContent = await readFileIfExists(context.fs, targetPath);
2669
+ let current;
2670
+ try {
2671
+ current = rawContent === null ? {} : format.parse(rawContent);
2672
+ } catch {
2673
+ if (rawContent !== null && !context.dryRun) {
2674
+ await backupInvalidDocument(context, targetPath, rawContent);
2675
+ }
2676
+ current = {};
2677
+ }
2678
+ const merged = format.merge(current, templateDoc);
2679
+ const serialized = format.serialize(merged);
2680
+ const changed = serialized !== rawContent;
2681
+ if (changed && !context.dryRun) {
2682
+ await writeAtomically(context, targetPath, serialized);
2683
+ }
2684
+ return {
2685
+ outcome: {
2686
+ changed,
2687
+ effect: changed ? "write" : "none",
2688
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
2689
+ },
2690
+ details
2691
+ };
2692
+ }
2693
+
2694
+ // packages/config-mutations/src/execution/run-mutations.ts
2695
+ async function runMutations(mutations, context, options) {
2696
+ const effects = [];
2697
+ let anyChanged = false;
2698
+ const resolverOptions = options ?? {};
2699
+ for (const mutation of mutations) {
2700
+ const { outcome } = await executeMutation(
2701
+ mutation,
2702
+ context,
2703
+ resolverOptions
2704
+ );
2705
+ effects.push(outcome);
2706
+ if (outcome.changed) {
2707
+ anyChanged = true;
2708
+ }
2709
+ }
2710
+ return {
2711
+ changed: anyChanged,
2712
+ effects
2713
+ };
2714
+ }
2715
+ async function executeMutation(mutation, context, options) {
2716
+ const pendingDetails = resolveMutationDetails(mutation, context, options);
2717
+ context.observers?.onStart?.(pendingDetails);
2718
+ try {
2719
+ const { outcome, details } = await applyMutation(mutation, context, options);
2720
+ context.observers?.onComplete?.(details, outcome);
2721
+ return { outcome, details };
2722
+ } catch (error2) {
2723
+ context.observers?.onError?.(pendingDetails, error2);
2724
+ throw error2;
2725
+ }
2726
+ }
2727
+
2728
+ // packages/agent-skill-config/src/error-codes.ts
2729
+ function hasOwnErrorCode2(error2, code) {
2730
+ return error2 instanceof Error && Object.prototype.hasOwnProperty.call(error2, "code") && error2.code === code;
2731
+ }
2732
+
2733
+ // packages/agent-skill-config/src/templates.ts
2734
+ import { readFile, stat } from "node:fs/promises";
2735
+ import path4 from "node:path";
2736
+ import { fileURLToPath } from "node:url";
2737
+
2738
+ // packages/agent-skill-config/src/apply.ts
2739
+ var UnsupportedAgentError = class extends Error {
2740
+ constructor(agentId) {
2741
+ super(`Unsupported agent: ${agentId}`);
2742
+ this.name = "UnsupportedAgentError";
2743
+ }
2744
+ };
2745
+ function toHomeRelative(localSkillDir) {
2746
+ if (localSkillDir.startsWith("~/") || localSkillDir === "~") {
2747
+ return localSkillDir;
2748
+ }
2749
+ const normalized = localSkillDir.startsWith("./") ? localSkillDir.slice(2) : localSkillDir;
2750
+ return `~/${normalized}`;
2751
+ }
2752
+ async function pathExists2(fs5, targetPath) {
2753
+ try {
2754
+ await fs5.stat(targetPath);
2755
+ return true;
2756
+ } catch (error2) {
2757
+ if (hasOwnErrorCode2(error2, "ENOENT")) {
2758
+ return false;
2759
+ }
2760
+ throw error2;
2761
+ }
2762
+ }
2763
+ var SKILL_TEMPLATE_ID = "__skill_content__";
2764
+ async function installSkill(agentId, skill, options) {
2765
+ const support = resolveAgentSupport(agentId);
2766
+ if (support.status !== "supported") {
2767
+ throw new UnsupportedAgentError(agentId);
2768
+ }
2769
+ const scope = options.scope ?? "local";
2770
+ const config2 = support.config;
2771
+ if (skill.name.length === 0 || skill.name !== skill.name.trim() || skill.name === "." || skill.name === ".." || skill.name.includes("/") || skill.name.includes("\\") || skill.name.includes("\n") || skill.name.includes("\r")) {
2772
+ throw new Error(`Invalid skill name: ${skill.name}`);
2773
+ }
2774
+ const skillDir = scope === "global" ? config2.globalSkillDir : toHomeRelative(config2.localSkillDir);
2775
+ const skillFolderPath = `${skillDir}/${skill.name}`;
2776
+ const skillFilePath = `${skillFolderPath}/SKILL.md`;
2777
+ const displayPath = `${scope === "global" ? config2.globalSkillDir : config2.localSkillDir}/${skill.name}/SKILL.md`;
2778
+ const absoluteSkillPath = `${scope === "global" ? options.homeDir : options.cwd}/${skillFilePath.slice(2)}`;
2779
+ if (await pathExists2(options.fs, absoluteSkillPath)) {
2780
+ throw new Error(`Skill already exists: ${displayPath}`);
2781
+ }
2782
+ await runMutations(
2783
+ [
2784
+ fileMutation.ensureDirectory({
2785
+ path: skillFolderPath,
2786
+ label: `Ensure skill directory ${skill.name}`
2787
+ }),
2788
+ templateMutation.write({
2789
+ target: skillFilePath,
2790
+ templateId: SKILL_TEMPLATE_ID,
2791
+ label: `Write skill ${skill.name}`
2792
+ })
2793
+ ],
2794
+ {
2795
+ fs: options.fs,
2796
+ homeDir: scope === "global" ? options.homeDir : options.cwd,
2797
+ dryRun: options.dryRun,
2798
+ observers: options.observers,
2799
+ templates: async (templateId) => {
2800
+ if (templateId === SKILL_TEMPLATE_ID) {
2801
+ return skill.content;
2802
+ }
2803
+ throw new Error(`Unknown template: ${templateId}`);
2804
+ }
2805
+ }
2806
+ );
2807
+ return { skillPath: absoluteSkillPath, displayPath };
2808
+ }
2809
+
2810
+ // packages/agent-skill-config/src/resolve-skill-reference.ts
2811
+ import * as fs from "node:fs";
2812
+ import path5 from "node:path";
2813
+
2814
+ // packages/agent-skill-config/src/git-exclude.ts
2815
+ import { execFileSync } from "node:child_process";
2816
+ import { randomUUID as randomUUID2 } from "node:crypto";
2817
+ import * as fs2 from "node:fs";
2818
+ import path6 from "node:path";
2819
+
2820
+ // packages/agent-skill-config/src/bridge-active-skills.ts
2821
+ import * as fs3 from "node:fs";
2822
+ import { createHash, randomUUID as randomUUID3 } from "node:crypto";
2823
+ import path7 from "node:path";
2824
+
2825
+ // src/skills.ts
2826
+ function createNodeFileSystem() {
2827
+ return {
2828
+ readFile: ((filePath, encoding) => {
2829
+ if (encoding) {
2830
+ return fs4.readFile(filePath, encoding);
2831
+ }
2832
+ return fs4.readFile(filePath);
2833
+ }),
2834
+ symlink: (target, filePath) => fs4.symlink(target, filePath),
2835
+ readlink: (filePath) => fs4.readlink(filePath, { encoding: "utf8" }),
2836
+ realpath: (filePath) => fs4.realpath(filePath),
2837
+ writeFile: (filePath, data, options) => fs4.writeFile(filePath, data, options),
2838
+ mkdir: (filePath, options) => fs4.mkdir(filePath, options).then(() => {
2839
+ }),
2840
+ stat: (filePath) => fs4.stat(filePath),
2841
+ lstat: (filePath) => fs4.lstat(filePath),
2842
+ rename: (oldPath, newPath) => fs4.rename(oldPath, newPath),
2843
+ rm: (filePath, options) => fs4.rm(filePath, options),
2844
+ unlink: (filePath) => fs4.unlink(filePath),
2845
+ readdir: (filePath) => fs4.readdir(filePath),
2846
+ copyFile: (src, dest) => fs4.copyFile(src, dest),
2847
+ chmod: (filePath, mode) => fs4.chmod(filePath, mode)
2848
+ };
27
2849
  }
28
2850
  async function resolveSkillContent(source, options) {
29
- if ("content" in source) {
30
- return source.content;
31
- }
32
- const sourcePath = path.isAbsolute(source.file)
33
- ? source.file
34
- : path.resolve(options.cwd, source.file);
35
- return options.fs.readFile(sourcePath, "utf8");
36
- }
37
- /**
38
- * Install arbitrary skill content into an agent's native skill directory.
39
- *
40
- * The actual mutation and validation are delegated to poe-code's agent skill
41
- * configuration machinery, so SDK and CLI installs share the same safety rules.
42
- */
43
- export async function installSkill(agentId, source, options = {}) {
44
- const cwd = options.cwd ?? process.cwd();
45
- const homeDir = options.homeDir ?? os.homedir();
46
- const fileSystem = options.fs ?? createNodeFileSystem();
47
- const content = await resolveSkillContent(source, { cwd, fs: fileSystem });
48
- return installAgentSkill(agentId, {
49
- name: source.name,
50
- content
51
- }, {
52
- fs: fileSystem,
53
- cwd,
54
- homeDir,
55
- scope: options.scope ?? "local",
56
- dryRun: options.dryRun
57
- });
2851
+ if ("content" in source) {
2852
+ return source.content;
2853
+ }
2854
+ const sourcePath = path8.isAbsolute(source.file) ? source.file : path8.resolve(options.cwd, source.file);
2855
+ return options.fs.readFile(sourcePath, "utf8");
2856
+ }
2857
+ async function installSkill2(agentId, source, options = {}) {
2858
+ const cwd = options.cwd ?? process.cwd();
2859
+ const homeDir = options.homeDir ?? os2.homedir();
2860
+ const fileSystem = options.fs ?? createNodeFileSystem();
2861
+ const content = await resolveSkillContent(source, { cwd, fs: fileSystem });
2862
+ return installSkill(
2863
+ agentId,
2864
+ {
2865
+ name: source.name,
2866
+ content
2867
+ },
2868
+ {
2869
+ fs: fileSystem,
2870
+ cwd,
2871
+ homeDir,
2872
+ scope: options.scope ?? "local",
2873
+ dryRun: options.dryRun
2874
+ }
2875
+ );
58
2876
  }
59
- //# sourceMappingURL=skills.js.map
2877
+ export {
2878
+ installSkill2 as installSkill
2879
+ };
2880
+ //# sourceMappingURL=skills.js.map