terminal-pilot 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cli.js +5708 -1261
  2. package/dist/cli.js.map +4 -4
  3. package/dist/commands/close-session.js +249 -4
  4. package/dist/commands/close-session.js.map +4 -4
  5. package/dist/commands/create-session.js +249 -4
  6. package/dist/commands/create-session.js.map +4 -4
  7. package/dist/commands/fill.js +249 -4
  8. package/dist/commands/fill.js.map +4 -4
  9. package/dist/commands/get-session.js +249 -4
  10. package/dist/commands/get-session.js.map +4 -4
  11. package/dist/commands/index.js +2376 -184
  12. package/dist/commands/index.js.map +4 -4
  13. package/dist/commands/install.js +1377 -16
  14. package/dist/commands/install.js.map +4 -4
  15. package/dist/commands/installer.js +181 -15
  16. package/dist/commands/installer.js.map +4 -4
  17. package/dist/commands/list-sessions.js +249 -4
  18. package/dist/commands/list-sessions.js.map +4 -4
  19. package/dist/commands/press-key.js +249 -4
  20. package/dist/commands/press-key.js.map +4 -4
  21. package/dist/commands/read-history.js +249 -4
  22. package/dist/commands/read-history.js.map +4 -4
  23. package/dist/commands/read-screen.js +249 -4
  24. package/dist/commands/read-screen.js.map +4 -4
  25. package/dist/commands/resize.js +249 -4
  26. package/dist/commands/resize.js.map +4 -4
  27. package/dist/commands/runtime.js +8 -2
  28. package/dist/commands/runtime.js.map +3 -3
  29. package/dist/commands/screenshot.js +1014 -8
  30. package/dist/commands/screenshot.js.map +4 -4
  31. package/dist/commands/send-signal.js +249 -4
  32. package/dist/commands/send-signal.js.map +4 -4
  33. package/dist/commands/type.js +249 -4
  34. package/dist/commands/type.js.map +4 -4
  35. package/dist/commands/uninstall.js +421 -16
  36. package/dist/commands/uninstall.js.map +4 -4
  37. package/dist/commands/wait-for-exit.js +249 -4
  38. package/dist/commands/wait-for-exit.js.map +4 -4
  39. package/dist/commands/wait-for.js +249 -4
  40. package/dist/commands/wait-for.js.map +4 -4
  41. package/dist/testing/cli-repl.js +5633 -1185
  42. package/dist/testing/cli-repl.js.map +4 -4
  43. package/dist/testing/qa-cli.js +5674 -1226
  44. package/dist/testing/qa-cli.js.map +4 -4
  45. package/package.json +17 -8
@@ -1,22 +1,1383 @@
1
- // src/commands/install.ts
2
- import { installSkill } from "@poe-code/agent-skill-config";
3
- import { defineCommand, S } from "@poe-code/cmdkit";
4
-
5
- // src/commands/installer.ts
1
+ // ../agent-skill-config/src/configs.ts
6
2
  import os from "node:os";
7
3
  import path from "node:path";
8
- import * as nodeFs from "node:fs/promises";
4
+
5
+ // ../agent-defs/src/agents/claude-code.ts
6
+ var claudeCodeAgent = {
7
+ id: "claude-code",
8
+ name: "claude-code",
9
+ label: "Claude Code",
10
+ summary: "Configure Claude Code to route through Poe.",
11
+ aliases: ["claude"],
12
+ binaryName: "claude",
13
+ configPath: "~/.claude/settings.json",
14
+ branding: {
15
+ colors: {
16
+ dark: "#C15F3C",
17
+ light: "#C15F3C"
18
+ }
19
+ }
20
+ };
21
+
22
+ // ../agent-defs/src/agents/claude-desktop.ts
23
+ var claudeDesktopAgent = {
24
+ id: "claude-desktop",
25
+ name: "claude-desktop",
26
+ label: "Claude Desktop",
27
+ summary: "Anthropic's official desktop application for Claude",
28
+ configPath: "~/.claude/settings.json",
29
+ branding: {
30
+ colors: {
31
+ dark: "#D97757",
32
+ light: "#D97757"
33
+ }
34
+ }
35
+ };
36
+
37
+ // ../agent-defs/src/agents/codex.ts
38
+ var codexAgent = {
39
+ id: "codex",
40
+ name: "codex",
41
+ label: "Codex",
42
+ summary: "Configure Codex to use Poe as the model provider.",
43
+ binaryName: "codex",
44
+ configPath: "~/.codex/config.toml",
45
+ branding: {
46
+ colors: {
47
+ dark: "#D5D9DF",
48
+ light: "#7A7F86"
49
+ }
50
+ }
51
+ };
52
+
53
+ // ../agent-defs/src/agents/opencode.ts
54
+ var openCodeAgent = {
55
+ id: "opencode",
56
+ name: "opencode",
57
+ label: "OpenCode CLI",
58
+ summary: "Configure OpenCode CLI to use the Poe API.",
59
+ binaryName: "opencode",
60
+ configPath: "~/.config/opencode/config.json",
61
+ branding: {
62
+ colors: {
63
+ dark: "#4A4F55",
64
+ light: "#2F3338"
65
+ }
66
+ }
67
+ };
68
+
69
+ // ../agent-defs/src/agents/kimi.ts
70
+ var kimiAgent = {
71
+ id: "kimi",
72
+ name: "kimi",
73
+ label: "Kimi",
74
+ summary: "Configure Kimi CLI to use Poe API",
75
+ aliases: ["kimi-cli"],
76
+ binaryName: "kimi",
77
+ configPath: "~/.kimi/config.toml",
78
+ branding: {
79
+ colors: {
80
+ dark: "#7B68EE",
81
+ light: "#6A5ACD"
82
+ }
83
+ }
84
+ };
85
+
86
+ // ../agent-defs/src/registry.ts
87
+ var allAgents = [
88
+ claudeCodeAgent,
89
+ claudeDesktopAgent,
90
+ codexAgent,
91
+ openCodeAgent,
92
+ kimiAgent
93
+ ];
94
+ var lookup = /* @__PURE__ */ new Map();
95
+ for (const agent of allAgents) {
96
+ const values = [agent.id, agent.name, ...agent.aliases ?? []];
97
+ for (const value of values) {
98
+ const normalized = value.toLowerCase();
99
+ if (!lookup.has(normalized)) {
100
+ lookup.set(normalized, agent.id);
101
+ }
102
+ }
103
+ }
104
+ function resolveAgentId(input) {
105
+ if (!input) {
106
+ return void 0;
107
+ }
108
+ return lookup.get(input.toLowerCase());
109
+ }
110
+
111
+ // ../agent-skill-config/src/configs.ts
112
+ var agentSkillConfigs = {
113
+ "claude-code": {
114
+ globalSkillDir: "~/.claude/skills",
115
+ localSkillDir: ".claude/skills"
116
+ },
117
+ codex: {
118
+ globalSkillDir: "~/.codex/skills",
119
+ localSkillDir: ".codex/skills"
120
+ },
121
+ opencode: {
122
+ globalSkillDir: "~/.config/opencode/skills",
123
+ localSkillDir: ".opencode/skills"
124
+ }
125
+ };
126
+ var supportedAgents = Object.keys(agentSkillConfigs);
127
+ function resolveAgentSupport(input, registry = agentSkillConfigs) {
128
+ const resolvedId = resolveAgentId(input);
129
+ if (!resolvedId) {
130
+ return { status: "unknown", input };
131
+ }
132
+ const config = registry[resolvedId];
133
+ if (!config) {
134
+ return { status: "unsupported", input, id: resolvedId };
135
+ }
136
+ return { status: "supported", input, id: resolvedId, config };
137
+ }
138
+
139
+ // ../config-mutations/src/mutations/file-mutation.ts
140
+ function ensureDirectory(options) {
141
+ return {
142
+ kind: "ensureDirectory",
143
+ path: options.path,
144
+ label: options.label
145
+ };
146
+ }
147
+ function remove(options) {
148
+ return {
149
+ kind: "removeFile",
150
+ target: options.target,
151
+ whenEmpty: options.whenEmpty,
152
+ whenContentMatches: options.whenContentMatches,
153
+ label: options.label
154
+ };
155
+ }
156
+ function removeDirectory(options) {
157
+ return {
158
+ kind: "removeDirectory",
159
+ path: options.path,
160
+ force: options.force,
161
+ label: options.label
162
+ };
163
+ }
164
+ function chmod(options) {
165
+ return {
166
+ kind: "chmod",
167
+ target: options.target,
168
+ mode: options.mode,
169
+ label: options.label
170
+ };
171
+ }
172
+ function backup(options) {
173
+ return {
174
+ kind: "backup",
175
+ target: options.target,
176
+ label: options.label
177
+ };
178
+ }
179
+ var fileMutation = {
180
+ ensureDirectory,
181
+ remove,
182
+ removeDirectory,
183
+ chmod,
184
+ backup
185
+ };
186
+
187
+ // ../config-mutations/src/mutations/template-mutation.ts
188
+ function write(options) {
189
+ return {
190
+ kind: "templateWrite",
191
+ target: options.target,
192
+ templateId: options.templateId,
193
+ context: options.context,
194
+ label: options.label
195
+ };
196
+ }
197
+ function mergeToml(options) {
198
+ return {
199
+ kind: "templateMergeToml",
200
+ target: options.target,
201
+ templateId: options.templateId,
202
+ context: options.context,
203
+ label: options.label
204
+ };
205
+ }
206
+ function mergeJson(options) {
207
+ return {
208
+ kind: "templateMergeJson",
209
+ target: options.target,
210
+ templateId: options.templateId,
211
+ context: options.context,
212
+ label: options.label
213
+ };
214
+ }
215
+ var templateMutation = {
216
+ write,
217
+ mergeToml,
218
+ mergeJson
219
+ };
220
+
221
+ // ../config-mutations/src/execution/apply-mutation.ts
222
+ import Mustache from "mustache";
223
+
224
+ // ../config-mutations/src/formats/json.ts
225
+ import * as jsonc from "jsonc-parser";
226
+ function isConfigObject(value) {
227
+ return typeof value === "object" && value !== null && !Array.isArray(value);
228
+ }
229
+ function parse2(content) {
230
+ if (!content || content.trim() === "") {
231
+ return {};
232
+ }
233
+ const errors = [];
234
+ const parsed = jsonc.parse(content, errors, {
235
+ allowTrailingComma: true,
236
+ disallowComments: false
237
+ });
238
+ if (errors.length > 0) {
239
+ throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
240
+ }
241
+ if (parsed === null || parsed === void 0) {
242
+ return {};
243
+ }
244
+ if (!isConfigObject(parsed)) {
245
+ throw new Error("Expected JSON object.");
246
+ }
247
+ return parsed;
248
+ }
249
+ function serialize(obj) {
250
+ return `${JSON.stringify(obj, null, 2)}
251
+ `;
252
+ }
253
+ function merge(base, patch) {
254
+ const result = { ...base };
255
+ for (const [key, value] of Object.entries(patch)) {
256
+ if (value === void 0) {
257
+ continue;
258
+ }
259
+ const existing = result[key];
260
+ if (isConfigObject(existing) && isConfigObject(value)) {
261
+ result[key] = merge(existing, value);
262
+ continue;
263
+ }
264
+ result[key] = value;
265
+ }
266
+ return result;
267
+ }
268
+ function prune(obj, shape) {
269
+ let changed = false;
270
+ const result = { ...obj };
271
+ for (const [key, pattern] of Object.entries(shape)) {
272
+ if (!(key in result)) {
273
+ continue;
274
+ }
275
+ const current = result[key];
276
+ if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
277
+ delete result[key];
278
+ changed = true;
279
+ continue;
280
+ }
281
+ if (isConfigObject(pattern) && isConfigObject(current)) {
282
+ const { changed: childChanged, result: childResult } = prune(
283
+ current,
284
+ pattern
285
+ );
286
+ if (childChanged) {
287
+ changed = true;
288
+ }
289
+ if (Object.keys(childResult).length === 0) {
290
+ delete result[key];
291
+ } else {
292
+ result[key] = childResult;
293
+ }
294
+ continue;
295
+ }
296
+ delete result[key];
297
+ changed = true;
298
+ }
299
+ return { changed, result };
300
+ }
301
+ var jsonFormat = {
302
+ parse: parse2,
303
+ serialize,
304
+ merge,
305
+ prune
306
+ };
307
+
308
+ // ../config-mutations/src/formats/toml.ts
309
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
310
+ function isConfigObject2(value) {
311
+ return typeof value === "object" && value !== null && !Array.isArray(value);
312
+ }
313
+ function parse3(content) {
314
+ if (!content || content.trim() === "") {
315
+ return {};
316
+ }
317
+ const parsed = parseToml(content);
318
+ if (!isConfigObject2(parsed)) {
319
+ throw new Error("Expected TOML document to be a table.");
320
+ }
321
+ return parsed;
322
+ }
323
+ function serialize2(obj) {
324
+ const serialized = stringifyToml(obj);
325
+ return serialized.endsWith("\n") ? serialized : `${serialized}
326
+ `;
327
+ }
328
+ function merge2(base, patch) {
329
+ const result = { ...base };
330
+ for (const [key, value] of Object.entries(patch)) {
331
+ if (value === void 0) {
332
+ continue;
333
+ }
334
+ const existing = result[key];
335
+ if (isConfigObject2(existing) && isConfigObject2(value)) {
336
+ result[key] = merge2(existing, value);
337
+ continue;
338
+ }
339
+ result[key] = value;
340
+ }
341
+ return result;
342
+ }
343
+ function prune2(obj, shape) {
344
+ let changed = false;
345
+ const result = { ...obj };
346
+ for (const [key, pattern] of Object.entries(shape)) {
347
+ if (!(key in result)) {
348
+ continue;
349
+ }
350
+ const current = result[key];
351
+ if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
352
+ delete result[key];
353
+ changed = true;
354
+ continue;
355
+ }
356
+ if (isConfigObject2(pattern) && isConfigObject2(current)) {
357
+ const { changed: childChanged, result: childResult } = prune2(
358
+ current,
359
+ pattern
360
+ );
361
+ if (childChanged) {
362
+ changed = true;
363
+ }
364
+ if (Object.keys(childResult).length === 0) {
365
+ delete result[key];
366
+ } else {
367
+ result[key] = childResult;
368
+ }
369
+ continue;
370
+ }
371
+ delete result[key];
372
+ changed = true;
373
+ }
374
+ return { changed, result };
375
+ }
376
+ var tomlFormat = {
377
+ parse: parse3,
378
+ serialize: serialize2,
379
+ merge: merge2,
380
+ prune: prune2
381
+ };
382
+
383
+ // ../config-mutations/src/formats/index.ts
384
+ var formatRegistry = {
385
+ json: jsonFormat,
386
+ toml: tomlFormat
387
+ };
388
+ var extensionMap = {
389
+ ".json": "json",
390
+ ".toml": "toml"
391
+ };
392
+ function getConfigFormat(pathOrFormat) {
393
+ if (pathOrFormat in formatRegistry) {
394
+ return formatRegistry[pathOrFormat];
395
+ }
396
+ const ext = getExtension(pathOrFormat);
397
+ const formatName = extensionMap[ext];
398
+ if (!formatName) {
399
+ throw new Error(
400
+ `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
401
+ );
402
+ }
403
+ return formatRegistry[formatName];
404
+ }
405
+ function detectFormat(path4) {
406
+ const ext = getExtension(path4);
407
+ return extensionMap[ext];
408
+ }
409
+ function getExtension(path4) {
410
+ const lastDot = path4.lastIndexOf(".");
411
+ if (lastDot === -1) {
412
+ return "";
413
+ }
414
+ return path4.slice(lastDot).toLowerCase();
415
+ }
416
+
417
+ // ../config-mutations/src/execution/path-utils.ts
418
+ import path2 from "node:path";
419
+ function expandHome(targetPath, homeDir) {
420
+ if (!targetPath?.startsWith("~")) {
421
+ return targetPath;
422
+ }
423
+ if (targetPath.startsWith("~./")) {
424
+ targetPath = `~/.${targetPath.slice(3)}`;
425
+ }
426
+ let remainder = targetPath.slice(1);
427
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
428
+ remainder = remainder.slice(1);
429
+ } else if (remainder.startsWith(".")) {
430
+ remainder = remainder.slice(1);
431
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
432
+ remainder = remainder.slice(1);
433
+ }
434
+ }
435
+ return remainder.length === 0 ? homeDir : path2.join(homeDir, remainder);
436
+ }
437
+ function validateHomePath(targetPath) {
438
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
439
+ throw new Error("Target path must be a non-empty string.");
440
+ }
441
+ if (!targetPath.startsWith("~")) {
442
+ throw new Error(
443
+ `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
444
+ );
445
+ }
446
+ }
447
+ function resolvePath(rawPath, homeDir, pathMapper) {
448
+ validateHomePath(rawPath);
449
+ const expanded = expandHome(rawPath, homeDir);
450
+ if (!pathMapper) {
451
+ return expanded;
452
+ }
453
+ const rawDirectory = path2.dirname(expanded);
454
+ const mappedDirectory = pathMapper.mapTargetDirectory({
455
+ targetDirectory: rawDirectory
456
+ });
457
+ const filename = path2.basename(expanded);
458
+ return filename.length === 0 ? mappedDirectory : path2.join(mappedDirectory, filename);
459
+ }
460
+
461
+ // ../config-mutations/src/fs-utils.ts
462
+ function isNotFound(error) {
463
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
464
+ }
465
+ async function readFileIfExists(fs, target) {
466
+ try {
467
+ return await fs.readFile(target, "utf8");
468
+ } catch (error) {
469
+ if (isNotFound(error)) {
470
+ return null;
471
+ }
472
+ throw error;
473
+ }
474
+ }
475
+ async function pathExists(fs, target) {
476
+ try {
477
+ await fs.stat(target);
478
+ return true;
479
+ } catch (error) {
480
+ if (isNotFound(error)) {
481
+ return false;
482
+ }
483
+ throw error;
484
+ }
485
+ }
486
+ function createTimestamp() {
487
+ return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
488
+ }
489
+
490
+ // ../config-mutations/src/execution/apply-mutation.ts
491
+ function resolveValue(resolver, options) {
492
+ if (typeof resolver === "function") {
493
+ return resolver(options);
494
+ }
495
+ return resolver;
496
+ }
497
+ function createInvalidDocumentBackupPath(targetPath) {
498
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
499
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
500
+ }
501
+ async function backupInvalidDocument(fs, targetPath, content) {
502
+ const backupPath = createInvalidDocumentBackupPath(targetPath);
503
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
504
+ }
505
+ function describeMutation(kind, targetPath) {
506
+ const displayPath = targetPath ?? "target";
507
+ switch (kind) {
508
+ case "ensureDirectory":
509
+ return `Create ${displayPath}`;
510
+ case "removeDirectory":
511
+ return `Remove directory ${displayPath}`;
512
+ case "backup":
513
+ return `Backup ${displayPath}`;
514
+ case "templateWrite":
515
+ return `Write ${displayPath}`;
516
+ case "chmod":
517
+ return `Set permissions on ${displayPath}`;
518
+ case "removeFile":
519
+ return `Remove ${displayPath}`;
520
+ case "configMerge":
521
+ case "configPrune":
522
+ case "configTransform":
523
+ case "templateMergeToml":
524
+ case "templateMergeJson":
525
+ return `Update ${displayPath}`;
526
+ default:
527
+ return "Operation";
528
+ }
529
+ }
530
+ function pruneKeysByPrefix(table, prefix) {
531
+ const result = {};
532
+ for (const [key, value] of Object.entries(table)) {
533
+ if (!key.startsWith(prefix)) {
534
+ result[key] = value;
535
+ }
536
+ }
537
+ return result;
538
+ }
539
+ function isConfigObject3(value) {
540
+ return typeof value === "object" && value !== null && !Array.isArray(value);
541
+ }
542
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
543
+ const result = { ...base };
544
+ const prefixMap = pruneByPrefix ?? {};
545
+ for (const [key, value] of Object.entries(patch)) {
546
+ const current = result[key];
547
+ const prefix = prefixMap[key];
548
+ if (isConfigObject3(current) && isConfigObject3(value)) {
549
+ if (prefix) {
550
+ const pruned = pruneKeysByPrefix(current, prefix);
551
+ result[key] = { ...pruned, ...value };
552
+ } else {
553
+ result[key] = mergeWithPruneByPrefix(
554
+ current,
555
+ value,
556
+ prefixMap
557
+ );
558
+ }
559
+ continue;
560
+ }
561
+ result[key] = value;
562
+ }
563
+ return result;
564
+ }
565
+ async function applyMutation(mutation, context, options) {
566
+ switch (mutation.kind) {
567
+ case "ensureDirectory":
568
+ return applyEnsureDirectory(mutation, context, options);
569
+ case "removeDirectory":
570
+ return applyRemoveDirectory(mutation, context, options);
571
+ case "removeFile":
572
+ return applyRemoveFile(mutation, context, options);
573
+ case "chmod":
574
+ return applyChmod(mutation, context, options);
575
+ case "backup":
576
+ return applyBackup(mutation, context, options);
577
+ case "configMerge":
578
+ return applyConfigMerge(mutation, context, options);
579
+ case "configPrune":
580
+ return applyConfigPrune(mutation, context, options);
581
+ case "configTransform":
582
+ return applyConfigTransform(mutation, context, options);
583
+ case "templateWrite":
584
+ return applyTemplateWrite(mutation, context, options);
585
+ case "templateMergeToml":
586
+ return applyTemplateMerge(mutation, context, options, "toml");
587
+ case "templateMergeJson":
588
+ return applyTemplateMerge(mutation, context, options, "json");
589
+ default: {
590
+ const never = mutation;
591
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
592
+ }
593
+ }
594
+ }
595
+ async function applyEnsureDirectory(mutation, context, options) {
596
+ const rawPath = resolveValue(mutation.path, options);
597
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
598
+ const details = {
599
+ kind: mutation.kind,
600
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
601
+ targetPath
602
+ };
603
+ const existed = await pathExists(context.fs, targetPath);
604
+ if (!context.dryRun) {
605
+ await context.fs.mkdir(targetPath, { recursive: true });
606
+ }
607
+ return {
608
+ outcome: {
609
+ changed: !existed,
610
+ effect: "mkdir",
611
+ detail: existed ? "noop" : "create"
612
+ },
613
+ details
614
+ };
615
+ }
616
+ async function applyRemoveDirectory(mutation, context, options) {
617
+ const rawPath = resolveValue(mutation.path, options);
618
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
619
+ const details = {
620
+ kind: mutation.kind,
621
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
622
+ targetPath
623
+ };
624
+ const existed = await pathExists(context.fs, targetPath);
625
+ if (!existed) {
626
+ return {
627
+ outcome: { changed: false, effect: "none", detail: "noop" },
628
+ details
629
+ };
630
+ }
631
+ if (typeof context.fs.rm !== "function") {
632
+ return {
633
+ outcome: { changed: false, effect: "none", detail: "noop" },
634
+ details
635
+ };
636
+ }
637
+ if (mutation.force) {
638
+ if (!context.dryRun) {
639
+ await context.fs.rm(targetPath, { recursive: true, force: true });
640
+ }
641
+ return {
642
+ outcome: { changed: true, effect: "delete", detail: "delete" },
643
+ details
644
+ };
645
+ }
646
+ const entries = await context.fs.readdir(targetPath);
647
+ if (entries.length > 0) {
648
+ return {
649
+ outcome: { changed: false, effect: "none", detail: "noop" },
650
+ details
651
+ };
652
+ }
653
+ if (!context.dryRun) {
654
+ await context.fs.rm(targetPath, { recursive: true, force: true });
655
+ }
656
+ return {
657
+ outcome: { changed: true, effect: "delete", detail: "delete" },
658
+ details
659
+ };
660
+ }
661
+ async function applyRemoveFile(mutation, context, options) {
662
+ const rawPath = resolveValue(mutation.target, options);
663
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
664
+ const details = {
665
+ kind: mutation.kind,
666
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
667
+ targetPath
668
+ };
669
+ try {
670
+ const content = await context.fs.readFile(targetPath, "utf8");
671
+ const trimmed = content.trim();
672
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
673
+ return {
674
+ outcome: { changed: false, effect: "none", detail: "noop" },
675
+ details
676
+ };
677
+ }
678
+ if (mutation.whenEmpty && trimmed.length > 0) {
679
+ return {
680
+ outcome: { changed: false, effect: "none", detail: "noop" },
681
+ details
682
+ };
683
+ }
684
+ if (!context.dryRun) {
685
+ await context.fs.unlink(targetPath);
686
+ }
687
+ return {
688
+ outcome: { changed: true, effect: "delete", detail: "delete" },
689
+ details
690
+ };
691
+ } catch (error) {
692
+ if (isNotFound(error)) {
693
+ return {
694
+ outcome: { changed: false, effect: "none", detail: "noop" },
695
+ details
696
+ };
697
+ }
698
+ throw error;
699
+ }
700
+ }
701
+ async function applyChmod(mutation, context, options) {
702
+ const rawPath = resolveValue(mutation.target, options);
703
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
704
+ const details = {
705
+ kind: mutation.kind,
706
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
707
+ targetPath
708
+ };
709
+ if (typeof context.fs.chmod !== "function") {
710
+ return {
711
+ outcome: { changed: false, effect: "none", detail: "noop" },
712
+ details
713
+ };
714
+ }
715
+ try {
716
+ const stat = await context.fs.stat(targetPath);
717
+ const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
718
+ if (currentMode === mutation.mode) {
719
+ return {
720
+ outcome: { changed: false, effect: "none", detail: "noop" },
721
+ details
722
+ };
723
+ }
724
+ if (!context.dryRun) {
725
+ await context.fs.chmod(targetPath, mutation.mode);
726
+ }
727
+ return {
728
+ outcome: { changed: true, effect: "chmod", detail: "update" },
729
+ details
730
+ };
731
+ } catch (error) {
732
+ if (isNotFound(error)) {
733
+ return {
734
+ outcome: { changed: false, effect: "none", detail: "noop" },
735
+ details
736
+ };
737
+ }
738
+ throw error;
739
+ }
740
+ }
741
+ async function applyBackup(mutation, context, options) {
742
+ const rawPath = resolveValue(mutation.target, options);
743
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
744
+ const details = {
745
+ kind: mutation.kind,
746
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
747
+ targetPath
748
+ };
749
+ const content = await readFileIfExists(context.fs, targetPath);
750
+ if (content === null) {
751
+ return {
752
+ outcome: { changed: false, effect: "none", detail: "noop" },
753
+ details
754
+ };
755
+ }
756
+ if (!context.dryRun) {
757
+ const backupPath = `${targetPath}.backup-${createTimestamp()}`;
758
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
759
+ }
760
+ return {
761
+ outcome: { changed: true, effect: "copy", detail: "backup" },
762
+ details
763
+ };
764
+ }
765
+ async function applyConfigMerge(mutation, context, options) {
766
+ const rawPath = resolveValue(mutation.target, options);
767
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
768
+ const details = {
769
+ kind: mutation.kind,
770
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
771
+ targetPath
772
+ };
773
+ const formatName = mutation.format ?? detectFormat(rawPath);
774
+ if (!formatName) {
775
+ throw new Error(
776
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
777
+ );
778
+ }
779
+ const format = getConfigFormat(formatName);
780
+ const rawContent = await readFileIfExists(context.fs, targetPath);
781
+ let current;
782
+ try {
783
+ current = rawContent === null ? {} : format.parse(rawContent);
784
+ } catch {
785
+ if (rawContent !== null) {
786
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
787
+ }
788
+ current = {};
789
+ }
790
+ const value = resolveValue(mutation.value, options);
791
+ let merged;
792
+ if (mutation.pruneByPrefix) {
793
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
794
+ } else {
795
+ merged = format.merge(current, value);
796
+ }
797
+ const serialized = format.serialize(merged);
798
+ const changed = serialized !== rawContent;
799
+ if (changed && !context.dryRun) {
800
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
801
+ }
802
+ return {
803
+ outcome: {
804
+ changed,
805
+ effect: changed ? "write" : "none",
806
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
807
+ },
808
+ details
809
+ };
810
+ }
811
+ async function applyConfigPrune(mutation, context, options) {
812
+ const rawPath = resolveValue(mutation.target, options);
813
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
814
+ const details = {
815
+ kind: mutation.kind,
816
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
817
+ targetPath
818
+ };
819
+ const rawContent = await readFileIfExists(context.fs, targetPath);
820
+ if (rawContent === null) {
821
+ return {
822
+ outcome: { changed: false, effect: "none", detail: "noop" },
823
+ details
824
+ };
825
+ }
826
+ const formatName = mutation.format ?? detectFormat(rawPath);
827
+ if (!formatName) {
828
+ throw new Error(
829
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
830
+ );
831
+ }
832
+ const format = getConfigFormat(formatName);
833
+ let current;
834
+ try {
835
+ current = format.parse(rawContent);
836
+ } catch {
837
+ return {
838
+ outcome: { changed: false, effect: "none", detail: "noop" },
839
+ details
840
+ };
841
+ }
842
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
843
+ return {
844
+ outcome: { changed: false, effect: "none", detail: "noop" },
845
+ details
846
+ };
847
+ }
848
+ const shape = resolveValue(mutation.shape, options);
849
+ const { changed, result } = format.prune(current, shape);
850
+ if (!changed) {
851
+ return {
852
+ outcome: { changed: false, effect: "none", detail: "noop" },
853
+ details
854
+ };
855
+ }
856
+ if (Object.keys(result).length === 0) {
857
+ if (!context.dryRun) {
858
+ await context.fs.unlink(targetPath);
859
+ }
860
+ return {
861
+ outcome: { changed: true, effect: "delete", detail: "delete" },
862
+ details
863
+ };
864
+ }
865
+ const serialized = format.serialize(result);
866
+ if (!context.dryRun) {
867
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
868
+ }
869
+ return {
870
+ outcome: { changed: true, effect: "write", detail: "update" },
871
+ details
872
+ };
873
+ }
874
+ async function applyConfigTransform(mutation, context, options) {
875
+ const rawPath = resolveValue(mutation.target, options);
876
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
877
+ const details = {
878
+ kind: mutation.kind,
879
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
880
+ targetPath
881
+ };
882
+ const formatName = mutation.format ?? detectFormat(rawPath);
883
+ if (!formatName) {
884
+ throw new Error(
885
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
886
+ );
887
+ }
888
+ const format = getConfigFormat(formatName);
889
+ const rawContent = await readFileIfExists(context.fs, targetPath);
890
+ let current;
891
+ try {
892
+ current = rawContent === null ? {} : format.parse(rawContent);
893
+ } catch {
894
+ if (rawContent !== null) {
895
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
896
+ }
897
+ current = {};
898
+ }
899
+ const { content: transformed, changed } = mutation.transform(current, options);
900
+ if (!changed) {
901
+ return {
902
+ outcome: { changed: false, effect: "none", detail: "noop" },
903
+ details
904
+ };
905
+ }
906
+ if (transformed === null) {
907
+ if (rawContent === null) {
908
+ return {
909
+ outcome: { changed: false, effect: "none", detail: "noop" },
910
+ details
911
+ };
912
+ }
913
+ if (!context.dryRun) {
914
+ await context.fs.unlink(targetPath);
915
+ }
916
+ return {
917
+ outcome: { changed: true, effect: "delete", detail: "delete" },
918
+ details
919
+ };
920
+ }
921
+ const serialized = format.serialize(transformed);
922
+ if (!context.dryRun) {
923
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
924
+ }
925
+ return {
926
+ outcome: {
927
+ changed: true,
928
+ effect: "write",
929
+ detail: rawContent === null ? "create" : "update"
930
+ },
931
+ details
932
+ };
933
+ }
934
+ async function applyTemplateWrite(mutation, context, options) {
935
+ if (!context.templates) {
936
+ throw new Error(
937
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
938
+ );
939
+ }
940
+ const rawPath = resolveValue(mutation.target, options);
941
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
942
+ const details = {
943
+ kind: mutation.kind,
944
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
945
+ targetPath
946
+ };
947
+ const template = await context.templates(mutation.templateId);
948
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
949
+ const rendered = Mustache.render(template, templateContext);
950
+ const existed = await pathExists(context.fs, targetPath);
951
+ if (!context.dryRun) {
952
+ await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
953
+ }
954
+ return {
955
+ outcome: {
956
+ changed: true,
957
+ effect: "write",
958
+ detail: existed ? "update" : "create"
959
+ },
960
+ details
961
+ };
962
+ }
963
+ async function applyTemplateMerge(mutation, context, options, formatName) {
964
+ if (!context.templates) {
965
+ throw new Error(
966
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
967
+ );
968
+ }
969
+ const rawPath = resolveValue(mutation.target, options);
970
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
971
+ const details = {
972
+ kind: mutation.kind,
973
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
974
+ targetPath
975
+ };
976
+ const format = getConfigFormat(formatName);
977
+ const template = await context.templates(mutation.templateId);
978
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
979
+ const rendered = Mustache.render(template, templateContext);
980
+ let templateDoc;
981
+ try {
982
+ templateDoc = format.parse(rendered);
983
+ } catch (error) {
984
+ throw new Error(
985
+ `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`,
986
+ { cause: error }
987
+ );
988
+ }
989
+ const rawContent = await readFileIfExists(context.fs, targetPath);
990
+ let current;
991
+ try {
992
+ current = rawContent === null ? {} : format.parse(rawContent);
993
+ } catch {
994
+ if (rawContent !== null) {
995
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
996
+ }
997
+ current = {};
998
+ }
999
+ const merged = format.merge(current, templateDoc);
1000
+ const serialized = format.serialize(merged);
1001
+ const changed = serialized !== rawContent;
1002
+ if (changed && !context.dryRun) {
1003
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1004
+ }
1005
+ return {
1006
+ outcome: {
1007
+ changed,
1008
+ effect: changed ? "write" : "none",
1009
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1010
+ },
1011
+ details
1012
+ };
1013
+ }
1014
+
1015
+ // ../config-mutations/src/execution/run-mutations.ts
1016
+ async function runMutations(mutations, context, options) {
1017
+ const effects = [];
1018
+ let anyChanged = false;
1019
+ const resolverOptions = options ?? {};
1020
+ for (const mutation of mutations) {
1021
+ const { outcome } = await executeMutation(
1022
+ mutation,
1023
+ context,
1024
+ resolverOptions
1025
+ );
1026
+ effects.push(outcome);
1027
+ if (outcome.changed) {
1028
+ anyChanged = true;
1029
+ }
1030
+ }
1031
+ return {
1032
+ changed: anyChanged,
1033
+ effects
1034
+ };
1035
+ }
1036
+ async function executeMutation(mutation, context, options) {
1037
+ context.observers?.onStart?.({
1038
+ kind: mutation.kind,
1039
+ label: mutation.label ?? mutation.kind,
1040
+ targetPath: void 0
1041
+ // Will be resolved during apply
1042
+ });
1043
+ try {
1044
+ const { outcome, details } = await applyMutation(mutation, context, options);
1045
+ context.observers?.onComplete?.(details, outcome);
1046
+ return { outcome, details };
1047
+ } catch (error) {
1048
+ context.observers?.onError?.(
1049
+ {
1050
+ kind: mutation.kind,
1051
+ label: mutation.label ?? mutation.kind,
1052
+ targetPath: void 0
1053
+ },
1054
+ error
1055
+ );
1056
+ throw error;
1057
+ }
1058
+ }
1059
+
1060
+ // ../config-mutations/src/template/render.ts
1061
+ import Mustache2 from "mustache";
1062
+ var originalEscape = Mustache2.escape;
1063
+
1064
+ // ../agent-skill-config/src/templates.ts
9
1065
  import { readFile } from "node:fs/promises";
10
- import { UserError } from "@poe-code/cmdkit";
11
- import {
12
- getAgentConfig,
13
- resolveAgentSupport as resolveSkillAgentSupport,
14
- supportedAgents as skillSupportedAgents
15
- } from "@poe-code/agent-skill-config";
1066
+
1067
+ // ../agent-skill-config/src/apply.ts
1068
+ var UnsupportedAgentError = class extends Error {
1069
+ constructor(agentId) {
1070
+ super(`Unsupported agent: ${agentId}`);
1071
+ this.name = "UnsupportedAgentError";
1072
+ }
1073
+ };
1074
+ function toHomeRelative(localSkillDir) {
1075
+ if (localSkillDir.startsWith("~/") || localSkillDir === "~") {
1076
+ return localSkillDir;
1077
+ }
1078
+ const normalized = localSkillDir.startsWith("./") ? localSkillDir.slice(2) : localSkillDir;
1079
+ return `~/${normalized}`;
1080
+ }
1081
+ var SKILL_TEMPLATE_ID = "__skill_content__";
1082
+ async function installSkill(agentId, skill, options) {
1083
+ const support = resolveAgentSupport(agentId);
1084
+ if (support.status !== "supported") {
1085
+ throw new UnsupportedAgentError(agentId);
1086
+ }
1087
+ const scope = options.scope ?? "local";
1088
+ const config = support.config;
1089
+ const skillDir = scope === "global" ? config.globalSkillDir : toHomeRelative(config.localSkillDir);
1090
+ const skillFolderPath = `${skillDir}/${skill.name}`;
1091
+ const skillFilePath = `${skillFolderPath}/SKILL.md`;
1092
+ const displayPath = `${scope === "global" ? config.globalSkillDir : config.localSkillDir}/${skill.name}/SKILL.md`;
1093
+ await runMutations(
1094
+ [
1095
+ fileMutation.ensureDirectory({
1096
+ path: skillFolderPath,
1097
+ label: `Ensure skill directory ${skill.name}`
1098
+ }),
1099
+ templateMutation.write({
1100
+ target: skillFilePath,
1101
+ templateId: SKILL_TEMPLATE_ID,
1102
+ label: `Write skill ${skill.name}`
1103
+ })
1104
+ ],
1105
+ {
1106
+ fs: options.fs,
1107
+ homeDir: scope === "global" ? options.homeDir : options.cwd,
1108
+ dryRun: options.dryRun,
1109
+ observers: options.observers,
1110
+ templates: async (templateId) => {
1111
+ if (templateId === SKILL_TEMPLATE_ID) {
1112
+ return skill.content;
1113
+ }
1114
+ throw new Error(`Unknown template: ${templateId}`);
1115
+ }
1116
+ }
1117
+ );
1118
+ return { skillPath: skillFilePath, displayPath };
1119
+ }
1120
+
1121
+ // ../cmdkit/src/index.ts
1122
+ import { fileURLToPath } from "node:url";
1123
+
1124
+ // ../cmdkit-schema/src/index.ts
1125
+ function assertValidEnumValues(values) {
1126
+ if (values.length === 0) {
1127
+ throw new Error("Enum schema requires at least one value");
1128
+ }
1129
+ const uniqueValues = new Set(values);
1130
+ if (uniqueValues.size !== values.length) {
1131
+ throw new Error("Enum schema values must be unique");
1132
+ }
1133
+ }
1134
+ var S = {
1135
+ String(options = {}) {
1136
+ return {
1137
+ kind: "string",
1138
+ ...options
1139
+ };
1140
+ },
1141
+ Number(options = {}) {
1142
+ return {
1143
+ kind: "number",
1144
+ ...options
1145
+ };
1146
+ },
1147
+ Boolean(options = {}) {
1148
+ return {
1149
+ kind: "boolean",
1150
+ ...options
1151
+ };
1152
+ },
1153
+ Enum(values, options = {}) {
1154
+ assertValidEnumValues(values);
1155
+ return {
1156
+ kind: "enum",
1157
+ values,
1158
+ ...options
1159
+ };
1160
+ },
1161
+ Array(item, options = {}) {
1162
+ return {
1163
+ kind: "array",
1164
+ item,
1165
+ ...options
1166
+ };
1167
+ },
1168
+ Object(shape) {
1169
+ return {
1170
+ kind: "object",
1171
+ shape
1172
+ };
1173
+ },
1174
+ Optional(inner) {
1175
+ return {
1176
+ kind: "optional",
1177
+ inner
1178
+ };
1179
+ }
1180
+ };
1181
+
1182
+ // ../cmdkit/src/index.ts
1183
+ var commandConfigSymbol = /* @__PURE__ */ Symbol("cmdkit.command.config");
1184
+ var commandSourcePathSymbol = /* @__PURE__ */ Symbol("cmdkit.command.sourcePath");
1185
+ var UserError = class extends Error {
1186
+ constructor(message) {
1187
+ super(message);
1188
+ this.name = "UserError";
1189
+ }
1190
+ };
1191
+ function cloneScope(scope) {
1192
+ return scope === void 0 ? void 0 : [...scope];
1193
+ }
1194
+ function cloneSecretDefinition(secret) {
1195
+ return {
1196
+ env: secret.env,
1197
+ description: secret.description,
1198
+ optional: secret.optional
1199
+ };
1200
+ }
1201
+ function cloneSecrets(secrets) {
1202
+ if (secrets === void 0) {
1203
+ return {};
1204
+ }
1205
+ return Object.fromEntries(
1206
+ Object.entries(secrets).map(([key, secret]) => [key, cloneSecretDefinition(secret)])
1207
+ );
1208
+ }
1209
+ function cloneRequires(requires) {
1210
+ if (requires === void 0) {
1211
+ return void 0;
1212
+ }
1213
+ return {
1214
+ auth: requires.auth,
1215
+ apiVersion: requires.apiVersion,
1216
+ check: requires.check
1217
+ };
1218
+ }
1219
+ function parseStackPath(candidate) {
1220
+ if (candidate.startsWith("file://")) {
1221
+ try {
1222
+ return fileURLToPath(candidate);
1223
+ } catch {
1224
+ return void 0;
1225
+ }
1226
+ }
1227
+ if (candidate.startsWith("/")) {
1228
+ return candidate;
1229
+ }
1230
+ return void 0;
1231
+ }
1232
+ function extractStackPath(line) {
1233
+ const trimmed = line.trim();
1234
+ const fileIndex = trimmed.indexOf("file://");
1235
+ if (fileIndex >= 0) {
1236
+ const location2 = trimmed.slice(fileIndex);
1237
+ const separatorIndex2 = location2.lastIndexOf(":");
1238
+ const previousSeparatorIndex2 = separatorIndex2 >= 0 ? location2.lastIndexOf(":", separatorIndex2 - 1) : -1;
1239
+ const candidate2 = separatorIndex2 >= 0 && previousSeparatorIndex2 >= 0 ? location2.slice(0, previousSeparatorIndex2) : location2;
1240
+ return parseStackPath(candidate2);
1241
+ }
1242
+ const slashIndex = trimmed.indexOf("/");
1243
+ if (slashIndex < 0) {
1244
+ return void 0;
1245
+ }
1246
+ const location = trimmed.slice(slashIndex);
1247
+ const separatorIndex = location.lastIndexOf(":");
1248
+ const previousSeparatorIndex = separatorIndex >= 0 ? location.lastIndexOf(":", separatorIndex - 1) : -1;
1249
+ const candidate = separatorIndex >= 0 && previousSeparatorIndex >= 0 ? location.slice(0, previousSeparatorIndex) : location;
1250
+ return parseStackPath(candidate);
1251
+ }
1252
+ function inferCommandSourcePath() {
1253
+ const stack = new Error().stack;
1254
+ if (typeof stack !== "string") {
1255
+ return void 0;
1256
+ }
1257
+ for (const line of stack.split("\n").slice(1)) {
1258
+ const candidate = extractStackPath(line);
1259
+ if (candidate === void 0) {
1260
+ continue;
1261
+ }
1262
+ if (candidate.includes("/packages/cmdkit/src/index.ts") || candidate.includes("/packages/cmdkit/dist/index.js")) {
1263
+ continue;
1264
+ }
1265
+ return candidate;
1266
+ }
1267
+ return void 0;
1268
+ }
1269
+ function composeChecks(parentCheck, childCheck) {
1270
+ if (parentCheck === void 0) {
1271
+ return childCheck;
1272
+ }
1273
+ if (childCheck === void 0) {
1274
+ return parentCheck;
1275
+ }
1276
+ return async (ctx) => {
1277
+ const parentResult = await parentCheck(ctx);
1278
+ if (!parentResult.ok) {
1279
+ return parentResult;
1280
+ }
1281
+ return childCheck(ctx);
1282
+ };
1283
+ }
1284
+ function mergeRequires(parent, child) {
1285
+ if (parent === void 0 && child === void 0) {
1286
+ return void 0;
1287
+ }
1288
+ const merged = {
1289
+ auth: child?.auth ?? parent?.auth,
1290
+ apiVersion: child?.apiVersion ?? parent?.apiVersion,
1291
+ check: composeChecks(parent?.check, child?.check)
1292
+ };
1293
+ if (merged.auth === void 0 && merged.apiVersion === void 0 && merged.check === void 0) {
1294
+ return void 0;
1295
+ }
1296
+ return merged;
1297
+ }
1298
+ function mergeSecrets(parent, child) {
1299
+ return cloneSecrets({
1300
+ ...parent,
1301
+ ...child
1302
+ });
1303
+ }
1304
+ function resolveCommandScope(ownScope, inheritedScope) {
1305
+ return cloneScope(ownScope ?? inheritedScope) ?? ["cli", "sdk"];
1306
+ }
1307
+ function createBaseCommand(config) {
1308
+ const command = {
1309
+ kind: "command",
1310
+ name: config.name,
1311
+ description: config.description,
1312
+ aliases: [...config.aliases ?? []],
1313
+ positional: [...config.positional ?? []],
1314
+ params: config.params,
1315
+ secrets: cloneSecrets(config.secrets),
1316
+ scope: resolveCommandScope(config.scope, void 0),
1317
+ confirm: config.confirm ?? false,
1318
+ requires: cloneRequires(config.requires),
1319
+ handler: config.handler,
1320
+ render: config.render
1321
+ };
1322
+ Object.defineProperty(command, commandConfigSymbol, {
1323
+ value: {
1324
+ scope: cloneScope(config.scope),
1325
+ secrets: cloneSecrets(config.secrets),
1326
+ requires: cloneRequires(config.requires),
1327
+ sourcePath: inferCommandSourcePath()
1328
+ }
1329
+ });
1330
+ return command;
1331
+ }
1332
+ function getInternalCommandConfig(command) {
1333
+ return command[commandConfigSymbol];
1334
+ }
1335
+ function materializeCommand(command, inherited) {
1336
+ const internal = getInternalCommandConfig(command);
1337
+ const materialized = {
1338
+ kind: "command",
1339
+ name: command.name,
1340
+ description: command.description,
1341
+ aliases: [...command.aliases],
1342
+ positional: [...command.positional],
1343
+ params: command.params,
1344
+ secrets: mergeSecrets(inherited.secrets, internal.secrets),
1345
+ scope: resolveCommandScope(internal.scope, inherited.scope),
1346
+ confirm: command.confirm,
1347
+ requires: mergeRequires(inherited.requires, internal.requires),
1348
+ handler: command.handler,
1349
+ render: command.render
1350
+ };
1351
+ Object.defineProperty(materialized, commandConfigSymbol, {
1352
+ value: {
1353
+ scope: cloneScope(internal.scope),
1354
+ secrets: cloneSecrets(internal.secrets),
1355
+ requires: cloneRequires(internal.requires),
1356
+ sourcePath: internal.sourcePath
1357
+ }
1358
+ });
1359
+ Object.defineProperty(materialized, commandSourcePathSymbol, {
1360
+ value: internal.sourcePath
1361
+ });
1362
+ return materialized;
1363
+ }
1364
+ function defineCommand(config) {
1365
+ return materializeCommand(createBaseCommand(config), {
1366
+ scope: void 0,
1367
+ secrets: {},
1368
+ requires: void 0
1369
+ });
1370
+ }
1371
+
1372
+ // src/commands/installer.ts
1373
+ import os2 from "node:os";
1374
+ import path3 from "node:path";
1375
+ import * as nodeFs from "node:fs/promises";
1376
+ import { readFile as readFile2 } from "node:fs/promises";
16
1377
  var DEFAULT_INSTALL_AGENT = "claude-code";
17
1378
  var DEFAULT_INSTALL_SCOPE = "local";
18
1379
  var TERMINAL_PILOT_SKILL_NAME = "terminal-pilot";
19
- var installableAgents = skillSupportedAgents;
1380
+ var installableAgents = supportedAgents;
20
1381
  function isNotFoundError(error) {
21
1382
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
22
1383
  }
@@ -24,7 +1385,7 @@ function resolveInstallerServices(installer) {
24
1385
  return {
25
1386
  fs: installer?.fs ?? nodeFs,
26
1387
  cwd: installer?.cwd ?? process.cwd(),
27
- homeDir: installer?.homeDir ?? os.homedir(),
1388
+ homeDir: installer?.homeDir ?? os2.homedir(),
28
1389
  platform: installer?.platform ?? process.platform
29
1390
  };
30
1391
  }
@@ -32,7 +1393,7 @@ function throwUnsupportedAgent(agent) {
32
1393
  throw new UserError(`Unsupported agent: ${agent}`);
33
1394
  }
34
1395
  function resolveInstallableAgent(agent) {
35
- const skillSupport = resolveSkillAgentSupport(agent);
1396
+ const skillSupport = resolveAgentSupport(agent);
36
1397
  if (skillSupport.status !== "supported" || !skillSupport.id) {
37
1398
  throwUnsupportedAgent(agent);
38
1399
  }
@@ -62,7 +1423,7 @@ async function loadTerminalPilotTemplate() {
62
1423
  ];
63
1424
  for (const candidate of candidates) {
64
1425
  try {
65
- terminalPilotTemplateCache = await readFile(candidate, "utf8");
1426
+ terminalPilotTemplateCache = await readFile2(candidate, "utf8");
66
1427
  return terminalPilotTemplateCache;
67
1428
  } catch (error) {
68
1429
  if (!isNotFoundError(error)) {