skillmux 0.1.1 → 0.1.2

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/index.js CHANGED
@@ -61,30 +61,9 @@ var defaultAgentRuleMap = Object.fromEntries(
61
61
  defaultAgentRules.map((rule) => [rule.id, rule])
62
62
  );
63
63
 
64
- // src/commands/agents.ts
64
+ // src/commands/adopt.ts
65
65
  import { homedir } from "os";
66
-
67
- // src/config/resolve-skillmux-home.ts
68
- import { join, resolve } from "path";
69
- function buildConfigPath(skillmuxHome) {
70
- return join(resolve(skillmuxHome), "config.json");
71
- }
72
- function resolveSkillmuxHome(homeDir) {
73
- const resolvedHomeDir = resolve(homeDir);
74
- const skillmuxHome = join(resolvedHomeDir, ".skillmux");
75
- return {
76
- skillmuxHome,
77
- configPath: buildConfigPath(skillmuxHome)
78
- };
79
- }
80
-
81
- // src/discovery/discover-agents.ts
82
- import * as fs2 from "fs/promises";
83
- import { join as join2, resolve as resolve2 } from "path";
84
-
85
- // src/config/load-user-config.ts
86
- import * as fs from "fs/promises";
87
- import { z } from "zod";
66
+ import { join as join7, resolve as resolve8 } from "path";
88
67
 
89
68
  // src/core/errors.ts
90
69
  var SkillMuxError = class extends Error {
@@ -112,8 +91,43 @@ var UserConfigValidationError = class extends SkillMuxError {
112
91
  super(message);
113
92
  }
114
93
  };
94
+ var AdoptionError = class extends SkillMuxError {
95
+ constructor(message) {
96
+ super(message);
97
+ }
98
+ };
99
+
100
+ // src/core/ids.ts
101
+ var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
102
+ function normalizeId(value) {
103
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
104
+ return normalized.length > 0 ? normalized : "skill";
105
+ }
106
+ function isValidId(value) {
107
+ return ID_PATTERN.test(value);
108
+ }
109
+
110
+ // src/config/resolve-skillmux-home.ts
111
+ import { join, resolve } from "path";
112
+ function buildConfigPath(skillmuxHome) {
113
+ return join(resolve(skillmuxHome), "config.json");
114
+ }
115
+ function resolveSkillmuxHome(homeDir) {
116
+ const resolvedHomeDir = resolve(homeDir);
117
+ const skillmuxHome = join(resolvedHomeDir, ".skillmux");
118
+ return {
119
+ skillmuxHome,
120
+ configPath: buildConfigPath(skillmuxHome)
121
+ };
122
+ }
123
+
124
+ // src/discovery/discover-agents.ts
125
+ import * as fs2 from "fs/promises";
126
+ import { join as join2, resolve as resolve2 } from "path";
115
127
 
116
128
  // src/config/load-user-config.ts
129
+ import * as fs from "fs/promises";
130
+ import { z } from "zod";
117
131
  var supportedPlatformSchema = z.enum(supportedPlatforms);
118
132
  var agentOverrideSchema = z.object({
119
133
  stableName: z.string().min(1).optional(),
@@ -249,422 +263,95 @@ async function discoverAgents(options) {
249
263
  return discoveredAgents;
250
264
  }
251
265
 
252
- // src/output/print-json.ts
253
- function printJson(value) {
254
- return `${JSON.stringify(value, null, 2)}
255
- `;
256
- }
257
-
258
- // src/output/print-table.ts
259
- function printTable(rows, columns) {
260
- const renderedRows = rows.map(
261
- (row) => columns.map((column) => String(row[column.key] ?? ""))
262
- );
263
- const widths = columns.map(
264
- (column, index) => Math.max(
265
- column.label.length,
266
- ...renderedRows.map((row) => row[index]?.length ?? 0)
267
- )
268
- );
269
- const header = columns.map((column, index) => column.label.padEnd(widths[index])).join(" ");
270
- const separator = widths.map((width) => "-".repeat(width)).join(" ");
271
- const body = renderedRows.map(
272
- (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ")
273
- );
274
- return `${[header, separator, ...body].join("\n")}
275
- `;
276
- }
277
-
278
- // src/commands/agents.ts
279
- function buildTableOutput(agents) {
280
- return printTable(
281
- agents.map((agent) => ({
282
- id: agent.id,
283
- name: agent.stableName,
284
- path: agent.absoluteSkillsDirectoryPath,
285
- exists: String(agent.exists),
286
- supported: String(agent.supportedOnPlatform),
287
- discovery: agent.discovery
288
- })),
289
- [
290
- { key: "id", label: "Agent" },
291
- { key: "name", label: "Name" },
292
- { key: "path", label: "Path" },
293
- { key: "exists", label: "Exists" },
294
- { key: "supported", label: "Supported" },
295
- { key: "discovery", label: "Discovery" }
296
- ]
297
- );
298
- }
299
- async function runAgents(options = {}) {
300
- const homeDir = options.homeDir ?? homedir();
301
- const resolvedPaths = resolveSkillmuxHome(homeDir);
302
- const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
303
- const agents = await discoverAgents({
304
- homeDir,
305
- skillmuxHome,
306
- platform: options.platform
307
- });
308
- return {
309
- agents,
310
- output: options.json === true ? printJson(agents) : buildTableOutput(agents)
311
- };
312
- }
313
-
314
- // src/commands/config-add-agent.ts
315
- import { homedir as homedir2 } from "os";
316
- import { isAbsolute } from "path";
266
+ // src/discovery/scan-agent-skills.ts
267
+ import * as fs5 from "fs/promises";
268
+ import { join as join3 } from "path";
317
269
 
318
- // src/core/ids.ts
319
- var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
320
- function normalizeId(value) {
321
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
322
- return normalized.length > 0 ? normalized : "skill";
323
- }
324
- function isValidId(value) {
325
- return ID_PATTERN.test(value);
326
- }
270
+ // src/discovery/infer-skill-entry.ts
271
+ import * as fs4 from "fs/promises";
272
+ import { basename, resolve as resolve4 } from "path";
327
273
 
328
- // src/config/write-user-config.ts
274
+ // src/fs/path-utils.ts
329
275
  import * as fs3 from "fs/promises";
330
- async function writeUserConfig(skillmuxHome, config) {
331
- const configPath = buildConfigPath(skillmuxHome);
332
- await fs3.mkdir(skillmuxHome, { recursive: true });
333
- await fs3.writeFile(configPath, `${JSON.stringify(config, null, 2)}
334
- `, "utf8");
335
- return {
336
- skillmuxHome,
337
- configPath
338
- };
276
+ import { dirname, parse, relative, resolve as resolve3, sep } from "path";
277
+ function normalizeAbsolutePath(path) {
278
+ const normalized = resolve3(path);
279
+ return process.platform === "win32" ? normalized.replaceAll("/", "\\").toLowerCase() : normalized;
339
280
  }
340
-
341
- // src/commands/config-add-agent.ts
342
- function normalizeRelativePath(value, field) {
343
- const trimmed = value.trim();
344
- if (trimmed.length === 0) {
345
- throw new UserConfigValidationError(`${field} must not be empty`);
281
+ function pathsAreEqual(left, right) {
282
+ return normalizeAbsolutePath(left) === normalizeAbsolutePath(right);
283
+ }
284
+ function isPathInside(parentPath, childPath) {
285
+ const parent = normalizeAbsolutePath(parentPath);
286
+ const child = normalizeAbsolutePath(childPath);
287
+ if (parse(parent).root !== parse(child).root) {
288
+ return false;
346
289
  }
347
- if (isAbsolute(trimmed)) {
348
- throw new UserConfigValidationError(`${field} must be a relative path`);
290
+ const relativePath = relative(parent, child);
291
+ if (relativePath === "") {
292
+ return true;
349
293
  }
350
- const normalized = trimmed.replaceAll("\\", "/");
351
- if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
352
- throw new UserConfigValidationError(`${field} must stay within the configured home-relative tree`);
294
+ if (relativePath === "..") {
295
+ return false;
353
296
  }
354
- return normalized.replace(/^\.\/+/, "");
355
- }
356
- function normalizeAgentId(value) {
357
- const trimmed = value.trim();
358
- if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
359
- throw new InvalidIdentifierError("agent id", value);
297
+ if (relativePath.startsWith(`..${sep}`)) {
298
+ return false;
360
299
  }
361
- return normalizeId(trimmed);
300
+ return true;
362
301
  }
363
- function normalizePlatforms(value) {
364
- if (value === void 0 || value.length === 0) {
365
- return [process.platform];
366
- }
367
- const normalized = [...new Set(value.map((entry) => entry.trim().toLowerCase()))];
368
- const invalid = normalized.filter(
369
- (entry) => supportedPlatforms.includes(entry) === false
370
- );
371
- if (invalid.length > 0) {
372
- throw new UserConfigValidationError(
373
- `platform must be one of: ${supportedPlatforms.join(", ")}`
374
- );
302
+ async function assertNoSymlinkAncestors(path, options) {
303
+ let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
304
+ while (true) {
305
+ try {
306
+ const entry = await fs3.lstat(current);
307
+ if (entry.isSymbolicLink()) {
308
+ throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
309
+ }
310
+ } catch (error) {
311
+ if (error.code !== "ENOENT") {
312
+ throw error;
313
+ }
314
+ }
315
+ const parent = dirname(current);
316
+ if (parent === current) {
317
+ return;
318
+ }
319
+ current = parent;
375
320
  }
376
- return normalized;
377
321
  }
378
- function buildAgentOverride(options) {
379
- const agentId = normalizeAgentId(options.id);
380
- const agent = {
381
- supportedPlatforms: normalizePlatforms(options.platforms),
382
- homeRelativeRootPath: normalizeRelativePath(options.root, "root"),
383
- skillsDirectoryPath: normalizeRelativePath(options.skills ?? "skills", "skills")
384
- };
385
- if (options.name !== void 0 && options.name.trim().length > 0) {
386
- agent.stableName = options.name.trim();
387
- }
388
- if (options.disabledByDefault === true) {
389
- agent.enabledByDefault = false;
390
- }
391
- return { agentId, agent };
322
+
323
+ // src/discovery/infer-skill-entry.ts
324
+ function buildIssue(code, severity, message, path) {
325
+ return { code, severity, message, path };
392
326
  }
393
- function buildTableOutput2(result) {
394
- const summary = printTable(
395
- [
396
- {
397
- agentId: result.agentId,
398
- configPath: result.configPath,
399
- changed: String(result.changed)
400
- }
401
- ],
402
- [
403
- { key: "agentId", label: "Agent" },
404
- { key: "configPath", label: "Config Path" },
405
- { key: "changed", label: "Changed" }
406
- ]
407
- );
408
- const detail = printTable(
409
- [
410
- {
411
- stableName: result.agent.stableName ?? "",
412
- platforms: (result.agent.supportedPlatforms ?? []).join(","),
413
- root: result.agent.homeRelativeRootPath ?? "",
414
- skills: result.agent.skillsDirectoryPath ?? "",
415
- enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
416
- }
417
- ],
418
- [
419
- { key: "stableName", label: "Name" },
420
- { key: "platforms", label: "Platforms" },
421
- { key: "root", label: "Root" },
422
- { key: "skills", label: "Skills Dir" },
423
- { key: "enabledByDefault", label: "Enabled By Default" }
424
- ]
425
- );
426
- return `${summary}${detail}`;
427
- }
428
- async function runConfigAddAgent(options) {
429
- const homeDir = options.homeDir ?? homedir2();
430
- const resolvedPaths = resolveSkillmuxHome(homeDir);
431
- const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
432
- const configPath = buildConfigPath(skillmuxHome);
433
- const config = await loadUserConfig(skillmuxHome);
434
- const { agentId, agent } = buildAgentOverride(options);
435
- const previous = config.agents[agentId];
436
- const changed = JSON.stringify(previous ?? null) !== JSON.stringify(agent);
437
- const nextConfig = {
438
- ...config,
439
- agents: {
440
- ...config.agents,
441
- [agentId]: agent
442
- }
443
- };
444
- await writeUserConfig(skillmuxHome, nextConfig);
445
- const resultWithoutOutput = {
446
- skillmuxHome,
447
- configPath,
448
- agentId,
449
- changed,
450
- agent,
451
- config: nextConfig
452
- };
453
- return {
454
- ...resultWithoutOutput,
455
- output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput2(resultWithoutOutput)
456
- };
457
- }
458
-
459
- // src/commands/config-remove-agent.ts
460
- import { homedir as homedir3 } from "os";
461
- function normalizeAgentId2(value) {
462
- const trimmed = value.trim();
463
- if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
464
- throw new InvalidIdentifierError("agent id", value);
465
- }
466
- return normalizeId(trimmed);
467
- }
468
- function buildTableOutput3(result) {
469
- return printTable(
470
- [
471
- {
472
- agentId: result.agentId,
473
- configPath: result.configPath,
474
- changed: String(result.changed),
475
- removed: String(result.removed)
476
- }
477
- ],
478
- [
479
- { key: "agentId", label: "Agent" },
480
- { key: "configPath", label: "Config Path" },
481
- { key: "changed", label: "Changed" },
482
- { key: "removed", label: "Removed" }
483
- ]
484
- );
485
- }
486
- async function runConfigRemoveAgent(options) {
487
- const homeDir = options.homeDir ?? homedir3();
488
- const resolvedPaths = resolveSkillmuxHome(homeDir);
489
- const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
490
- const configPath = buildConfigPath(skillmuxHome);
491
- const config = await loadUserConfig(skillmuxHome);
492
- const agentId = normalizeAgentId2(options.id);
493
- const removed = agentId in config.agents;
494
- const nextConfig = {
495
- ...config,
496
- agents: Object.fromEntries(
497
- Object.entries(config.agents).filter(([currentAgentId]) => currentAgentId !== agentId)
498
- )
499
- };
500
- if (removed) {
501
- await writeUserConfig(skillmuxHome, nextConfig);
502
- }
503
- const resultWithoutOutput = {
504
- skillmuxHome,
505
- configPath,
506
- agentId,
507
- changed: removed,
508
- removed,
509
- config: nextConfig
510
- };
511
- return {
512
- ...resultWithoutOutput,
513
- output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput3(resultWithoutOutput)
514
- };
515
- }
516
-
517
- // src/commands/config.ts
518
- import { homedir as homedir4 } from "os";
519
- function buildTableOutput4(result) {
520
- const summary = printTable(
521
- [
522
- {
523
- skillmuxHome: result.skillmuxHome,
524
- configPath: result.configPath,
525
- overrides: String(Object.keys(result.config.agents).length)
526
- }
527
- ],
528
- [
529
- { key: "skillmuxHome", label: "SkillMux Home" },
530
- { key: "configPath", label: "Config Path" },
531
- { key: "overrides", label: "Overrides" }
532
- ]
533
- );
534
- const agentRows = Object.entries(result.config.agents).sort(([left], [right]) => left.localeCompare(right)).map(([agentId, agent]) => ({
535
- agentId,
536
- stableName: agent.stableName ?? "",
537
- root: agent.homeRelativeRootPath ?? "",
538
- skills: agent.skillsDirectoryPath ?? ""
539
- }));
540
- if (agentRows.length === 0) {
541
- return `${summary}
542
- No user overrides configured.
543
- `;
544
- }
545
- return `${summary}
546
- ${printTable(agentRows, [
547
- { key: "agentId", label: "Agent" },
548
- { key: "stableName", label: "Name" },
549
- { key: "root", label: "Root" },
550
- { key: "skills", label: "Skills Dir" }
551
- ])}`;
552
- }
553
- async function runConfig(options = {}) {
554
- const homeDir = options.homeDir ?? homedir4();
555
- const resolvedPaths = resolveSkillmuxHome(homeDir);
556
- const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
557
- const config = await loadUserConfig(skillmuxHome);
558
- const resultWithoutOutput = {
559
- skillmuxHome,
560
- configPath: buildConfigPath(skillmuxHome),
561
- config
562
- };
563
- return {
564
- ...resultWithoutOutput,
565
- output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput4(resultWithoutOutput)
566
- };
567
- }
568
-
569
- // src/commands/doctor.ts
570
- import * as fs9 from "fs/promises";
571
- import { homedir as homedir5 } from "os";
572
- import { join as join5 } from "path";
573
-
574
- // src/discovery/scan-agent-skills.ts
575
- import * as fs6 from "fs/promises";
576
-
577
- // src/discovery/infer-skill-entry.ts
578
- import * as fs5 from "fs/promises";
579
- import { basename, resolve as resolve4 } from "path";
580
-
581
- // src/fs/path-utils.ts
582
- import * as fs4 from "fs/promises";
583
- import { dirname, parse, relative, resolve as resolve3, sep } from "path";
584
- function normalizeAbsolutePath(path) {
585
- const normalized = resolve3(path);
586
- return process.platform === "win32" ? normalized.replaceAll("/", "\\").toLowerCase() : normalized;
587
- }
588
- function pathsAreEqual(left, right) {
589
- return normalizeAbsolutePath(left) === normalizeAbsolutePath(right);
590
- }
591
- function isPathInside(parentPath, childPath) {
592
- const parent = normalizeAbsolutePath(parentPath);
593
- const child = normalizeAbsolutePath(childPath);
594
- if (parse(parent).root !== parse(child).root) {
595
- return false;
596
- }
597
- const relativePath = relative(parent, child);
598
- if (relativePath === "") {
599
- return true;
600
- }
601
- if (relativePath === "..") {
602
- return false;
603
- }
604
- if (relativePath.startsWith(`..${sep}`)) {
605
- return false;
606
- }
607
- return true;
608
- }
609
- async function assertNoSymlinkAncestors(path, options) {
610
- let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
611
- while (true) {
612
- try {
613
- const entry = await fs4.lstat(current);
614
- if (entry.isSymbolicLink()) {
615
- throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
616
- }
617
- } catch (error) {
618
- if (error.code !== "ENOENT") {
619
- throw error;
620
- }
621
- }
622
- const parent = dirname(current);
623
- if (parent === current) {
624
- return;
625
- }
626
- current = parent;
627
- }
628
- }
629
-
630
- // src/discovery/infer-skill-entry.ts
631
- function buildIssue(code, severity, message, path) {
632
- return { code, severity, message, path };
633
- }
634
- async function inferSkillEntry(options) {
635
- const absolutePath = resolve4(options.path);
636
- const skillName = basename(absolutePath);
637
- const stats = await fs5.lstat(absolutePath);
638
- if (stats.isSymbolicLink()) {
639
- try {
640
- const targetPath = await fs5.realpath(absolutePath);
641
- if (isPathInside(options.skillmuxHome, targetPath)) {
642
- return {
643
- entry: {
644
- agentId: options.agentId,
645
- agentName: options.agentName,
646
- skillName,
647
- kind: "managed-link",
648
- path: absolutePath,
649
- targetPath
650
- }
651
- };
327
+ async function inferSkillEntry(options) {
328
+ const absolutePath = resolve4(options.path);
329
+ const skillName = basename(absolutePath);
330
+ const stats = await fs4.lstat(absolutePath);
331
+ if (stats.isSymbolicLink()) {
332
+ try {
333
+ const targetPath = await fs4.realpath(absolutePath);
334
+ if (isPathInside(options.skillmuxHome, targetPath)) {
335
+ return {
336
+ entry: {
337
+ agentId: options.agentId,
338
+ agentName: options.agentName,
339
+ skillName,
340
+ kind: "managed-link",
341
+ path: absolutePath,
342
+ targetPath
343
+ }
344
+ };
652
345
  }
653
346
  return {
654
347
  entry: {
655
348
  agentId: options.agentId,
656
349
  agentName: options.agentName,
657
350
  skillName,
658
- kind: "unknown",
351
+ kind: "unmanaged-link",
659
352
  path: absolutePath,
660
353
  targetPath
661
- },
662
- issue: buildIssue(
663
- "unknown-entry",
664
- "warning",
665
- "Skill entry is a link that points outside the SkillMux managed store",
666
- absolutePath
667
- )
354
+ }
668
355
  };
669
356
  } catch (error) {
670
357
  if (error.code !== "ENOENT") {
@@ -723,7 +410,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
723
410
  issues: []
724
411
  };
725
412
  }
726
- const directoryEntries = await fs6.readdir(agent.absoluteSkillsDirectoryPath, {
413
+ const directoryEntries = await fs5.readdir(agent.absoluteSkillsDirectoryPath, {
727
414
  withFileTypes: true
728
415
  });
729
416
  const sortedDirectoryEntries = [...directoryEntries].sort(
@@ -735,7 +422,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
735
422
  const result = await inferSkillEntry({
736
423
  agentId: agent.id,
737
424
  agentName: agent.stableName,
738
- path: `${agent.absoluteSkillsDirectoryPath}/${directoryEntry.name}`,
425
+ path: join3(agent.absoluteSkillsDirectoryPath, directoryEntry.name),
739
426
  skillmuxHome
740
427
  });
741
428
  entries.push(result.entry);
@@ -749,9 +436,201 @@ async function scanAgentSkills(agent, skillmuxHome) {
749
436
  };
750
437
  }
751
438
 
752
- // src/manifest/read-manifest.ts
753
- import * as fs8 from "fs/promises";
754
- import { join as join4, resolve as resolve5 } from "path";
439
+ // src/fs/safe-copy.ts
440
+ import * as fs6 from "fs/promises";
441
+ import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
442
+ async function assertDirectory(path) {
443
+ const entry = await fs6.lstat(path);
444
+ if (!entry.isDirectory()) {
445
+ throw new Error(`Expected a directory at ${path}`);
446
+ }
447
+ }
448
+ async function assertRegularFile(path, label) {
449
+ const entry = await fs6.lstat(path);
450
+ if (!entry.isFile()) {
451
+ throw new Error(`Expected ${label} to be a regular file at ${path}`);
452
+ }
453
+ }
454
+ async function assertTargetDoesNotExist(path) {
455
+ try {
456
+ await fs6.lstat(path);
457
+ throw new Error(`Refusing to overwrite existing path at ${path}`);
458
+ } catch (error) {
459
+ if (error.code !== "ENOENT") {
460
+ throw error;
461
+ }
462
+ }
463
+ }
464
+ async function copyDirectoryContents(sourcePath, targetPath) {
465
+ await fs6.mkdir(targetPath, { recursive: true });
466
+ const entries = await fs6.readdir(sourcePath, { withFileTypes: true });
467
+ for (const entry of entries) {
468
+ const sourceEntryPath = join4(sourcePath, entry.name);
469
+ const targetEntryPath = join4(targetPath, entry.name);
470
+ const entryStats = await fs6.lstat(sourceEntryPath);
471
+ if (entryStats.isSymbolicLink()) {
472
+ throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
473
+ }
474
+ if (entryStats.isDirectory()) {
475
+ await copyDirectoryContents(sourceEntryPath, targetEntryPath);
476
+ continue;
477
+ }
478
+ if (entryStats.isFile()) {
479
+ await fs6.mkdir(dirname2(targetEntryPath), { recursive: true });
480
+ await fs6.copyFile(sourceEntryPath, targetEntryPath);
481
+ continue;
482
+ }
483
+ throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
484
+ }
485
+ }
486
+ async function assertSkillSourceLayout(sourcePath) {
487
+ const resolvedSourcePath = resolve5(sourcePath);
488
+ const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
489
+ await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
490
+ await assertDirectory(resolvedSourcePath);
491
+ try {
492
+ await assertRegularFile(skillFilePath, "SKILL.md");
493
+ } catch (error) {
494
+ if (error.code === "ENOENT") {
495
+ throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
496
+ }
497
+ throw error;
498
+ }
499
+ }
500
+ async function hasRootSkillFile(sourcePath) {
501
+ const resolvedSourcePath = resolve5(sourcePath);
502
+ const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
503
+ try {
504
+ await assertDirectory(resolvedSourcePath);
505
+ await assertRegularFile(skillFilePath, "SKILL.md");
506
+ return true;
507
+ } catch (error) {
508
+ if (error.code === "ENOENT") {
509
+ return false;
510
+ }
511
+ throw error;
512
+ }
513
+ }
514
+ async function copySkillContentsToManagedStore(sourcePath, targetPath) {
515
+ const resolvedSourcePath = resolve5(sourcePath);
516
+ const resolvedTargetPath = resolve5(targetPath);
517
+ if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
518
+ throw new Error("Source and target paths must differ");
519
+ }
520
+ if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
521
+ throw new Error("Refusing to copy into a child of the source directory");
522
+ }
523
+ await assertSkillSourceLayout(resolvedSourcePath);
524
+ await assertNoSymlinkAncestors(resolvedTargetPath);
525
+ await assertTargetDoesNotExist(resolvedTargetPath);
526
+ await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
527
+ }
528
+
529
+ // src/core/batch-operation-error.ts
530
+ function getCauseMessage(cause) {
531
+ if (cause instanceof Error) {
532
+ return cause.message;
533
+ }
534
+ return String(cause);
535
+ }
536
+ function buildBatchOperationMessage(options) {
537
+ const completedSuffix = options.completedItems.length > 0 ? ` after ${options.completedAction}: ${options.completedItems.join(", ")}` : "";
538
+ return `Failed to ${options.failedAction}${completedSuffix}: ${getCauseMessage(options.cause)}`;
539
+ }
540
+ var BatchOperationError = class extends Error {
541
+ operation;
542
+ failedItem;
543
+ completedItems;
544
+ cause;
545
+ constructor(options) {
546
+ super(buildBatchOperationMessage(options), { cause: options.cause });
547
+ this.name = "BatchOperationError";
548
+ this.operation = options.operation;
549
+ this.failedItem = options.failedItem;
550
+ this.completedItems = [...options.completedItems];
551
+ this.cause = options.cause;
552
+ }
553
+ };
554
+
555
+ // src/fs/link-ops.ts
556
+ import * as fs7 from "fs/promises";
557
+ import { dirname as dirname3, resolve as resolve6 } from "path";
558
+ var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
559
+ async function createManagedLink(linkPath, targetPath) {
560
+ const resolvedLinkPath = resolve6(linkPath);
561
+ const resolvedTargetPath = resolve6(targetPath);
562
+ await assertNoSymlinkAncestors(resolvedLinkPath);
563
+ await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
564
+ await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
565
+ try {
566
+ const existingEntry = await fs7.lstat(resolvedLinkPath);
567
+ if (!existingEntry.isSymbolicLink()) {
568
+ throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
569
+ }
570
+ const currentTargetPath = await fs7.realpath(resolvedLinkPath);
571
+ if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
572
+ return;
573
+ }
574
+ throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
575
+ } catch (error) {
576
+ if (error.code === "ENOENT" && await fs7.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
577
+ await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
578
+ } else if (error.code !== "ENOENT") {
579
+ throw error;
580
+ }
581
+ }
582
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
583
+ }
584
+ async function replaceEntryWithManagedLink(linkPath, targetPath, expectedCurrentPath) {
585
+ const resolvedLinkPath = resolve6(linkPath);
586
+ const resolvedTargetPath = resolve6(targetPath);
587
+ const resolvedExpectedCurrentPath = resolve6(expectedCurrentPath);
588
+ await assertNoSymlinkAncestors(resolvedLinkPath);
589
+ await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
590
+ await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
591
+ const existingEntry = await fs7.lstat(resolvedLinkPath);
592
+ if (existingEntry.isSymbolicLink()) {
593
+ const currentTargetPath = await fs7.realpath(resolvedLinkPath);
594
+ if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
595
+ return false;
596
+ }
597
+ if (!pathsAreEqual(currentTargetPath, resolvedExpectedCurrentPath)) {
598
+ throw new Error(`Refusing to replace unexpected link at ${resolvedLinkPath}`);
599
+ }
600
+ await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
601
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
602
+ return true;
603
+ }
604
+ if (!existingEntry.isDirectory()) {
605
+ throw new Error(`Refusing to replace non-directory entry at ${resolvedLinkPath}`);
606
+ }
607
+ const currentPath = await fs7.realpath(resolvedLinkPath);
608
+ if (!pathsAreEqual(currentPath, resolvedExpectedCurrentPath)) {
609
+ throw new Error(`Refusing to replace unexpected directory at ${resolvedLinkPath}`);
610
+ }
611
+ await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
612
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
613
+ return true;
614
+ }
615
+ async function isLinkPointingToTarget(linkPath, targetPath) {
616
+ try {
617
+ const entry = await fs7.lstat(linkPath);
618
+ if (!entry.isSymbolicLink()) {
619
+ return false;
620
+ }
621
+ const resolvedTargetPath = await fs7.realpath(linkPath);
622
+ return pathsAreEqual(resolvedTargetPath, targetPath);
623
+ } catch (error) {
624
+ if (error.code === "ENOENT") {
625
+ return false;
626
+ }
627
+ throw error;
628
+ }
629
+ }
630
+
631
+ // src/manifest/read-manifest.ts
632
+ import * as fs9 from "fs/promises";
633
+ import { join as join6, resolve as resolve7 } from "path";
755
634
 
756
635
  // src/manifest/build-empty-manifest.ts
757
636
  function buildEmptyManifest(skillmuxHome) {
@@ -876,35 +755,35 @@ var manifestSchema = z2.object({
876
755
 
877
756
  // src/manifest/write-manifest.ts
878
757
  import { randomUUID } from "crypto";
879
- import * as fs7 from "fs/promises";
880
- import { join as join3 } from "path";
758
+ import * as fs8 from "fs/promises";
759
+ import { join as join5 } from "path";
881
760
  function getManifestPath(home) {
882
- return join3(home, "manifest.json");
761
+ return join5(home, "manifest.json");
883
762
  }
884
763
  function createManifestTempPath(manifestPath) {
885
764
  return `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
886
765
  }
887
766
  async function writeManifest(home, manifest) {
888
- await fs7.mkdir(home, { recursive: true });
767
+ await fs8.mkdir(home, { recursive: true });
889
768
  const manifestPath = getManifestPath(home);
890
769
  const tempPath = createManifestTempPath(manifestPath);
891
770
  const contents = `${JSON.stringify(manifest, null, 2)}
892
771
  `;
893
- await fs7.writeFile(tempPath, contents, "utf8");
772
+ await fs8.writeFile(tempPath, contents, "utf8");
894
773
  try {
895
- await fs7.rename(tempPath, manifestPath);
774
+ await fs8.rename(tempPath, manifestPath);
896
775
  } catch (error) {
897
- await fs7.unlink(tempPath).catch(() => void 0);
776
+ await fs8.unlink(tempPath).catch(() => void 0);
898
777
  throw error;
899
778
  }
900
779
  }
901
780
 
902
781
  // src/manifest/read-manifest.ts
903
782
  function getManifestPath2(home) {
904
- return join4(home, "manifest.json");
783
+ return join6(home, "manifest.json");
905
784
  }
906
785
  function normalizeHomePath(home) {
907
- const resolvedHome = resolve5(home);
786
+ const resolvedHome = resolve7(home);
908
787
  return process.platform === "win32" ? resolvedHome.toLowerCase() : resolvedHome;
909
788
  }
910
789
  function formatValidationIssues2(error) {
@@ -916,7 +795,7 @@ function formatValidationIssues2(error) {
916
795
  async function readManifest(home) {
917
796
  const manifestPath = getManifestPath2(home);
918
797
  try {
919
- const contents = await fs8.readFile(manifestPath, "utf8");
798
+ const contents = await fs9.readFile(manifestPath, "utf8");
920
799
  const parsedJson = JSON.parse(contents);
921
800
  const parsedManifest = manifestSchema.safeParse(parsedJson);
922
801
  if (!parsedManifest.success) {
@@ -945,13 +824,689 @@ async function readManifest(home) {
945
824
  }
946
825
  }
947
826
 
827
+ // src/output/print-json.ts
828
+ function printJson(value) {
829
+ return `${JSON.stringify(value, null, 2)}
830
+ `;
831
+ }
832
+
833
+ // src/commands/adopt.ts
834
+ function buildManagedSkillPath(skillmuxHome, skillId) {
835
+ return resolve8(skillmuxHome, "skills", skillId);
836
+ }
837
+ function buildAgentRecord(agent, timestamp) {
838
+ return {
839
+ id: agent.id,
840
+ name: agent.stableName,
841
+ path: agent.absoluteSkillsDirectoryPath,
842
+ discovery: agent.discovery,
843
+ available: agent.exists && agent.supportedOnPlatform,
844
+ lastSeenAt: agent.exists ? timestamp : null
845
+ };
846
+ }
847
+ function buildActivationRecord(skillId, agentId, linkPath, timestamp) {
848
+ return {
849
+ skillId,
850
+ agentId,
851
+ linkPath,
852
+ state: "enabled",
853
+ updatedAt: timestamp
854
+ };
855
+ }
856
+ function upsertActivation(manifest, activation) {
857
+ const index = manifest.activations.findIndex(
858
+ (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
859
+ );
860
+ if (index === -1) {
861
+ manifest.activations.push(activation);
862
+ return;
863
+ }
864
+ manifest.activations[index] = activation;
865
+ }
866
+ async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
867
+ const agentId = normalizeId(agentName);
868
+ const agents = await discoverAgents({ homeDir, skillmuxHome });
869
+ const agent = agents.find((entry) => entry.id === agentId);
870
+ if (agent === void 0) {
871
+ throw new AdoptionError(`Unknown agent: ${agentName}`);
872
+ }
873
+ if (!agent.supportedOnPlatform) {
874
+ throw new AdoptionError(
875
+ `Agent ${agent.id} is not supported on ${process.platform}`
876
+ );
877
+ }
878
+ return agent;
879
+ }
880
+ function filterEntries(scannedAgent, skillFilter) {
881
+ if (skillFilter === void 0) {
882
+ return scannedAgent.entries;
883
+ }
884
+ const skillId = normalizeId(skillFilter);
885
+ return scannedAgent.entries.filter(
886
+ (entry) => normalizeId(entry.skillName) === skillId
887
+ );
888
+ }
889
+ async function resolveAdoptionSource(entry) {
890
+ if (entry.kind === "unmanaged-link") {
891
+ return entry.targetPath;
892
+ }
893
+ if (entry.kind === "unmanaged-directory") {
894
+ return entry.path;
895
+ }
896
+ return void 0;
897
+ }
898
+ function buildManagedSkill(skillId, skillName, managedPath, sourcePath, timestamp) {
899
+ return {
900
+ id: skillId,
901
+ name: skillName,
902
+ path: managedPath,
903
+ source: {
904
+ kind: "imported",
905
+ path: sourcePath
906
+ },
907
+ importedAt: timestamp
908
+ };
909
+ }
910
+ async function reconcileManagedLink(manifest, skillId, entry, agentId, timestamp) {
911
+ if (entry.targetPath === void 0) {
912
+ throw new AdoptionError(`Managed link target is missing for ${entry.path}`);
913
+ }
914
+ await assertSkillSourceLayout(entry.targetPath);
915
+ const skill = manifest.skills[skillId];
916
+ if (skill === void 0 || !pathsAreEqual(skill.path, entry.targetPath)) {
917
+ throw new AdoptionError(
918
+ `Managed link for ${agentId}/${skillId} has no matching manifest skill record`
919
+ );
920
+ }
921
+ upsertActivation(
922
+ manifest,
923
+ buildActivationRecord(skillId, agentId, entry.path, timestamp)
924
+ );
925
+ }
926
+ function buildOutput(result, json) {
927
+ if (json) {
928
+ return printJson({
929
+ agent: result.agent,
930
+ adopted: result.adopted,
931
+ skipped: result.skipped
932
+ });
933
+ }
934
+ if (result.adopted.length === 0) {
935
+ return `No skills adopted for ${result.agent.id}.
936
+ `;
937
+ }
938
+ const adoptedSkills = result.adopted.map((entry) => entry.skillId).sort((left, right) => left.localeCompare(right)).join(", ");
939
+ return `Adopted ${adoptedSkills} for ${result.agent.id}
940
+ `;
941
+ }
942
+ async function runAdoptSingle(options) {
943
+ const homeDir = options.homeDir ?? homedir();
944
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
945
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
946
+ const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
947
+ const manifest = await readManifest(skillmuxHome);
948
+ const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
949
+ const agentRecord = buildAgentRecord(agent, timestamp);
950
+ const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
951
+ const entries = filterEntries(scannedAgent, options.skill);
952
+ const adopted = [];
953
+ const skipped = [];
954
+ manifest.agents[agent.id] = agentRecord;
955
+ for (const entry of entries) {
956
+ const skillId = normalizeId(entry.skillName);
957
+ if (entry.kind === "managed-link") {
958
+ await reconcileManagedLink(
959
+ manifest,
960
+ skillId,
961
+ entry,
962
+ agent.id,
963
+ timestamp
964
+ );
965
+ skipped.push({
966
+ skillId,
967
+ agentId: agent.id,
968
+ path: entry.path,
969
+ reason: "already-managed"
970
+ });
971
+ await writeManifest(skillmuxHome, manifest);
972
+ continue;
973
+ }
974
+ const sourcePath = await resolveAdoptionSource(entry);
975
+ if (sourcePath === void 0) {
976
+ skipped.push({
977
+ skillId,
978
+ agentId: agent.id,
979
+ path: entry.path,
980
+ reason: "not-adoptable"
981
+ });
982
+ continue;
983
+ }
984
+ if (!await hasRootSkillFile(sourcePath)) {
985
+ skipped.push({
986
+ skillId,
987
+ agentId: agent.id,
988
+ path: entry.path,
989
+ reason: "missing-skill-file"
990
+ });
991
+ continue;
992
+ }
993
+ const managedPath = buildManagedSkillPath(skillmuxHome, skillId);
994
+ if (manifest.skills[skillId] === void 0) {
995
+ await copySkillContentsToManagedStore(sourcePath, managedPath);
996
+ manifest.skills[skillId] = buildManagedSkill(
997
+ skillId,
998
+ entry.skillName,
999
+ managedPath,
1000
+ sourcePath,
1001
+ timestamp
1002
+ );
1003
+ } else if (await isLinkPointingToTarget(entry.path, manifest.skills[skillId].path)) {
1004
+ skipped.push({
1005
+ skillId,
1006
+ agentId: agent.id,
1007
+ path: entry.path,
1008
+ reason: "already-managed"
1009
+ });
1010
+ continue;
1011
+ } else {
1012
+ await assertSkillSourceLayout(manifest.skills[skillId].path);
1013
+ }
1014
+ await replaceEntryWithManagedLink(
1015
+ entry.path,
1016
+ manifest.skills[skillId].path,
1017
+ sourcePath
1018
+ );
1019
+ const activation = buildActivationRecord(
1020
+ skillId,
1021
+ agent.id,
1022
+ join7(agent.absoluteSkillsDirectoryPath, entry.skillName),
1023
+ timestamp
1024
+ );
1025
+ upsertActivation(manifest, activation);
1026
+ adopted.push({
1027
+ skillId,
1028
+ agentId: agent.id,
1029
+ sourcePath,
1030
+ managedPath: manifest.skills[skillId].path,
1031
+ linkPath: activation.linkPath
1032
+ });
1033
+ await writeManifest(skillmuxHome, manifest);
1034
+ }
1035
+ await writeManifest(skillmuxHome, manifest);
1036
+ const resultWithoutOutput = {
1037
+ agent: agentRecord,
1038
+ adopted,
1039
+ skipped,
1040
+ manifest
1041
+ };
1042
+ return {
1043
+ ...resultWithoutOutput,
1044
+ output: buildOutput(resultWithoutOutput, options.json === true)
1045
+ };
1046
+ }
1047
+ async function runAdopt(options) {
1048
+ if (options.skills !== void 0) {
1049
+ const results = [];
1050
+ const completedSkills = [];
1051
+ for (const skill of options.skills) {
1052
+ try {
1053
+ results.push(await runAdoptSingle({ ...options, skill, skills: void 0 }));
1054
+ completedSkills.push(skill);
1055
+ } catch (error) {
1056
+ throw new BatchOperationError({
1057
+ operation: "adopt",
1058
+ failedItem: skill,
1059
+ failedAction: `adopt ${skill} for ${options.agent}`,
1060
+ completedAction: "adopting",
1061
+ completedItems: completedSkills,
1062
+ cause: error
1063
+ });
1064
+ }
1065
+ }
1066
+ if (results.length === 0) {
1067
+ throw new AdoptionError("Adopt requires at least one target skill");
1068
+ }
1069
+ const lastResult = results[results.length - 1];
1070
+ const resultWithoutOutput = {
1071
+ agent: lastResult.agent,
1072
+ adopted: results.flatMap((result) => result.adopted),
1073
+ skipped: results.flatMap((result) => result.skipped),
1074
+ manifest: lastResult.manifest,
1075
+ results
1076
+ };
1077
+ return {
1078
+ ...resultWithoutOutput,
1079
+ output: options.json === true ? printJson({
1080
+ agent: resultWithoutOutput.agent,
1081
+ adopted: resultWithoutOutput.adopted,
1082
+ skipped: resultWithoutOutput.skipped,
1083
+ results: resultWithoutOutput.results
1084
+ }) : results.map((result) => result.output).join("")
1085
+ };
1086
+ }
1087
+ return runAdoptSingle(options);
1088
+ }
1089
+
1090
+ // src/commands/agents.ts
1091
+ import { homedir as homedir2 } from "os";
1092
+
1093
+ // src/output/print-table.ts
1094
+ function printTable(rows, columns) {
1095
+ const renderedRows = rows.map(
1096
+ (row) => columns.map((column) => String(row[column.key] ?? ""))
1097
+ );
1098
+ const widths = columns.map(
1099
+ (column, index) => Math.max(
1100
+ column.label.length,
1101
+ ...renderedRows.map((row) => row[index]?.length ?? 0)
1102
+ )
1103
+ );
1104
+ const header = columns.map((column, index) => column.label.padEnd(widths[index])).join(" ");
1105
+ const separator = widths.map((width) => "-".repeat(width)).join(" ");
1106
+ const body = renderedRows.map(
1107
+ (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ")
1108
+ );
1109
+ return `${[header, separator, ...body].join("\n")}
1110
+ `;
1111
+ }
1112
+
1113
+ // src/commands/agents.ts
1114
+ function buildTableOutput(agents) {
1115
+ return printTable(
1116
+ agents.map((agent) => ({
1117
+ id: agent.id,
1118
+ name: agent.stableName,
1119
+ path: agent.absoluteSkillsDirectoryPath,
1120
+ exists: String(agent.exists),
1121
+ supported: String(agent.supportedOnPlatform),
1122
+ discovery: agent.discovery
1123
+ })),
1124
+ [
1125
+ { key: "id", label: "Agent" },
1126
+ { key: "name", label: "Name" },
1127
+ { key: "path", label: "Path" },
1128
+ { key: "exists", label: "Exists" },
1129
+ { key: "supported", label: "Supported" },
1130
+ { key: "discovery", label: "Discovery" }
1131
+ ]
1132
+ );
1133
+ }
1134
+ async function runAgents(options = {}) {
1135
+ const homeDir = options.homeDir ?? homedir2();
1136
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1137
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1138
+ const agents = await discoverAgents({
1139
+ homeDir,
1140
+ skillmuxHome,
1141
+ platform: options.platform
1142
+ });
1143
+ return {
1144
+ agents,
1145
+ output: options.json === true ? printJson(agents) : buildTableOutput(agents)
1146
+ };
1147
+ }
1148
+
1149
+ // src/commands/config-add-agent.ts
1150
+ import { homedir as homedir3 } from "os";
1151
+
1152
+ // src/config/agent-override-validation.ts
1153
+ import { isAbsolute } from "path";
1154
+ function normalizeRelativePath(value, field) {
1155
+ const trimmed = value.trim();
1156
+ if (trimmed.length === 0) {
1157
+ throw new UserConfigValidationError(`${field} must not be empty`);
1158
+ }
1159
+ if (isAbsolute(trimmed)) {
1160
+ throw new UserConfigValidationError(`${field} must be a relative path`);
1161
+ }
1162
+ const normalized = trimmed.replaceAll("\\", "/");
1163
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
1164
+ throw new UserConfigValidationError(`${field} must stay within the configured home-relative tree`);
1165
+ }
1166
+ return normalized.replace(/^\.\/+/, "");
1167
+ }
1168
+ function normalizeAgentId(value) {
1169
+ const trimmed = value.trim();
1170
+ if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
1171
+ throw new InvalidIdentifierError("agent id", value);
1172
+ }
1173
+ return normalizeId(trimmed);
1174
+ }
1175
+ function normalizePlatforms(value) {
1176
+ if (value === void 0 || value.length === 0) {
1177
+ return [process.platform];
1178
+ }
1179
+ const normalized = [...new Set(value.map((entry) => entry.trim().toLowerCase()))];
1180
+ const invalid = normalized.filter(
1181
+ (entry) => supportedPlatforms.includes(entry) === false
1182
+ );
1183
+ if (invalid.length > 0) {
1184
+ throw new UserConfigValidationError(
1185
+ `platform must be one of: ${supportedPlatforms.join(", ")}`
1186
+ );
1187
+ }
1188
+ return normalized;
1189
+ }
1190
+
1191
+ // src/config/write-user-config.ts
1192
+ import * as fs10 from "fs/promises";
1193
+ async function writeUserConfig(skillmuxHome, config) {
1194
+ const configPath = buildConfigPath(skillmuxHome);
1195
+ await fs10.mkdir(skillmuxHome, { recursive: true });
1196
+ await fs10.writeFile(configPath, `${JSON.stringify(config, null, 2)}
1197
+ `, "utf8");
1198
+ return {
1199
+ skillmuxHome,
1200
+ configPath
1201
+ };
1202
+ }
1203
+
1204
+ // src/commands/config-add-agent.ts
1205
+ function buildAgentOverride(options) {
1206
+ const agentId = normalizeAgentId(options.id);
1207
+ const agent = {
1208
+ supportedPlatforms: normalizePlatforms(options.platforms),
1209
+ homeRelativeRootPath: normalizeRelativePath(options.root, "root"),
1210
+ skillsDirectoryPath: normalizeRelativePath(options.skills ?? "skills", "skills")
1211
+ };
1212
+ if (options.name !== void 0 && options.name.trim().length > 0) {
1213
+ agent.stableName = options.name.trim();
1214
+ }
1215
+ if (options.disabledByDefault === true) {
1216
+ agent.enabledByDefault = false;
1217
+ }
1218
+ return { agentId, agent };
1219
+ }
1220
+ function buildTableOutput2(result) {
1221
+ const summary = printTable(
1222
+ [
1223
+ {
1224
+ agentId: result.agentId,
1225
+ configPath: result.configPath,
1226
+ changed: String(result.changed)
1227
+ }
1228
+ ],
1229
+ [
1230
+ { key: "agentId", label: "Agent" },
1231
+ { key: "configPath", label: "Config Path" },
1232
+ { key: "changed", label: "Changed" }
1233
+ ]
1234
+ );
1235
+ const detail = printTable(
1236
+ [
1237
+ {
1238
+ stableName: result.agent.stableName ?? "",
1239
+ platforms: (result.agent.supportedPlatforms ?? []).join(","),
1240
+ root: result.agent.homeRelativeRootPath ?? "",
1241
+ skills: result.agent.skillsDirectoryPath ?? "",
1242
+ enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
1243
+ }
1244
+ ],
1245
+ [
1246
+ { key: "stableName", label: "Name" },
1247
+ { key: "platforms", label: "Platforms" },
1248
+ { key: "root", label: "Root" },
1249
+ { key: "skills", label: "Skills Dir" },
1250
+ { key: "enabledByDefault", label: "Enabled By Default" }
1251
+ ]
1252
+ );
1253
+ return `${summary}${detail}`;
1254
+ }
1255
+ async function runConfigAddAgent(options) {
1256
+ const homeDir = options.homeDir ?? homedir3();
1257
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1258
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1259
+ const configPath = buildConfigPath(skillmuxHome);
1260
+ const config = await loadUserConfig(skillmuxHome);
1261
+ const { agentId, agent } = buildAgentOverride(options);
1262
+ const previous = config.agents[agentId];
1263
+ const changed = JSON.stringify(previous ?? null) !== JSON.stringify(agent);
1264
+ const nextConfig = {
1265
+ ...config,
1266
+ agents: {
1267
+ ...config.agents,
1268
+ [agentId]: agent
1269
+ }
1270
+ };
1271
+ await writeUserConfig(skillmuxHome, nextConfig);
1272
+ const resultWithoutOutput = {
1273
+ skillmuxHome,
1274
+ configPath,
1275
+ agentId,
1276
+ changed,
1277
+ agent,
1278
+ config: nextConfig
1279
+ };
1280
+ return {
1281
+ ...resultWithoutOutput,
1282
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput2(resultWithoutOutput)
1283
+ };
1284
+ }
1285
+
1286
+ // src/commands/config-remove-agent.ts
1287
+ import { homedir as homedir4 } from "os";
1288
+ function normalizeAgentId2(value) {
1289
+ const trimmed = value.trim();
1290
+ if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
1291
+ throw new InvalidIdentifierError("agent id", value);
1292
+ }
1293
+ return normalizeId(trimmed);
1294
+ }
1295
+ function buildTableOutput3(result) {
1296
+ return printTable(
1297
+ [
1298
+ {
1299
+ agentId: result.agentId,
1300
+ configPath: result.configPath,
1301
+ changed: String(result.changed),
1302
+ removed: String(result.removed)
1303
+ }
1304
+ ],
1305
+ [
1306
+ { key: "agentId", label: "Agent" },
1307
+ { key: "configPath", label: "Config Path" },
1308
+ { key: "changed", label: "Changed" },
1309
+ { key: "removed", label: "Removed" }
1310
+ ]
1311
+ );
1312
+ }
1313
+ async function runConfigRemoveAgent(options) {
1314
+ const homeDir = options.homeDir ?? homedir4();
1315
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1316
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1317
+ const configPath = buildConfigPath(skillmuxHome);
1318
+ const config = await loadUserConfig(skillmuxHome);
1319
+ const agentId = normalizeAgentId2(options.id);
1320
+ const removed = agentId in config.agents;
1321
+ const nextConfig = {
1322
+ ...config,
1323
+ agents: Object.fromEntries(
1324
+ Object.entries(config.agents).filter(([currentAgentId]) => currentAgentId !== agentId)
1325
+ )
1326
+ };
1327
+ if (removed) {
1328
+ await writeUserConfig(skillmuxHome, nextConfig);
1329
+ }
1330
+ const resultWithoutOutput = {
1331
+ skillmuxHome,
1332
+ configPath,
1333
+ agentId,
1334
+ changed: removed,
1335
+ removed,
1336
+ config: nextConfig
1337
+ };
1338
+ return {
1339
+ ...resultWithoutOutput,
1340
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput3(resultWithoutOutput)
1341
+ };
1342
+ }
1343
+
1344
+ // src/commands/config-update-agent.ts
1345
+ import { homedir as homedir5 } from "os";
1346
+ function buildAgentPatch(options) {
1347
+ const patch = {};
1348
+ if (options.root !== void 0) {
1349
+ patch.homeRelativeRootPath = normalizeRelativePath(options.root, "root");
1350
+ }
1351
+ if (options.skills !== void 0) {
1352
+ patch.skillsDirectoryPath = normalizeRelativePath(options.skills, "skills");
1353
+ }
1354
+ if (options.name !== void 0 && options.name.trim().length > 0) {
1355
+ patch.stableName = options.name.trim();
1356
+ }
1357
+ if (options.platforms !== void 0) {
1358
+ patch.supportedPlatforms = normalizePlatforms(options.platforms);
1359
+ }
1360
+ if (options.enabledByDefault !== void 0 && options.disabledByDefault === true) {
1361
+ throw new UserConfigValidationError(
1362
+ "enabled-by-default and disabled-by-default cannot both be set"
1363
+ );
1364
+ }
1365
+ if (options.enabledByDefault !== void 0) {
1366
+ patch.enabledByDefault = options.enabledByDefault;
1367
+ }
1368
+ if (options.disabledByDefault === true) {
1369
+ patch.enabledByDefault = false;
1370
+ }
1371
+ return patch;
1372
+ }
1373
+ function buildTableOutput4(result) {
1374
+ const summary = printTable(
1375
+ [
1376
+ {
1377
+ agentId: result.agentId,
1378
+ configPath: result.configPath,
1379
+ changed: String(result.changed)
1380
+ }
1381
+ ],
1382
+ [
1383
+ { key: "agentId", label: "Agent" },
1384
+ { key: "configPath", label: "Config Path" },
1385
+ { key: "changed", label: "Changed" }
1386
+ ]
1387
+ );
1388
+ const detail = printTable(
1389
+ [
1390
+ {
1391
+ stableName: result.agent.stableName ?? "",
1392
+ platforms: (result.agent.supportedPlatforms ?? []).join(","),
1393
+ root: result.agent.homeRelativeRootPath ?? "",
1394
+ skills: result.agent.skillsDirectoryPath ?? "",
1395
+ enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
1396
+ }
1397
+ ],
1398
+ [
1399
+ { key: "stableName", label: "Name" },
1400
+ { key: "platforms", label: "Platforms" },
1401
+ { key: "root", label: "Root" },
1402
+ { key: "skills", label: "Skills Dir" },
1403
+ { key: "enabledByDefault", label: "Enabled By Default" }
1404
+ ]
1405
+ );
1406
+ return `${summary}${detail}`;
1407
+ }
1408
+ async function runConfigUpdateAgent(options) {
1409
+ const homeDir = options.homeDir ?? homedir5();
1410
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1411
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1412
+ const configPath = buildConfigPath(skillmuxHome);
1413
+ const config = await loadUserConfig(skillmuxHome);
1414
+ const agentId = normalizeAgentId(options.id);
1415
+ const previous = config.agents[agentId];
1416
+ if (previous === void 0) {
1417
+ throw new UserConfigValidationError(`Agent override does not exist: ${agentId}`);
1418
+ }
1419
+ const agent = {
1420
+ ...previous,
1421
+ ...buildAgentPatch(options)
1422
+ };
1423
+ const changed = JSON.stringify(previous) !== JSON.stringify(agent);
1424
+ const nextConfig = {
1425
+ ...config,
1426
+ agents: {
1427
+ ...config.agents,
1428
+ [agentId]: agent
1429
+ }
1430
+ };
1431
+ if (changed) {
1432
+ await writeUserConfig(skillmuxHome, nextConfig);
1433
+ }
1434
+ const resultWithoutOutput = {
1435
+ skillmuxHome,
1436
+ configPath,
1437
+ agentId,
1438
+ changed,
1439
+ agent,
1440
+ config: nextConfig
1441
+ };
1442
+ return {
1443
+ ...resultWithoutOutput,
1444
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput4(resultWithoutOutput)
1445
+ };
1446
+ }
1447
+
1448
+ // src/commands/config.ts
1449
+ import { homedir as homedir6 } from "os";
1450
+ function buildTableOutput5(result) {
1451
+ const summary = printTable(
1452
+ [
1453
+ {
1454
+ skillmuxHome: result.skillmuxHome,
1455
+ configPath: result.configPath,
1456
+ overrides: String(Object.keys(result.config.agents).length)
1457
+ }
1458
+ ],
1459
+ [
1460
+ { key: "skillmuxHome", label: "SkillMux Home" },
1461
+ { key: "configPath", label: "Config Path" },
1462
+ { key: "overrides", label: "Overrides" }
1463
+ ]
1464
+ );
1465
+ const agentRows = Object.entries(result.config.agents).sort(([left], [right]) => left.localeCompare(right)).map(([agentId, agent]) => ({
1466
+ agentId,
1467
+ stableName: agent.stableName ?? "",
1468
+ root: agent.homeRelativeRootPath ?? "",
1469
+ skills: agent.skillsDirectoryPath ?? ""
1470
+ }));
1471
+ if (agentRows.length === 0) {
1472
+ return `${summary}
1473
+ No user overrides configured.
1474
+ `;
1475
+ }
1476
+ return `${summary}
1477
+ ${printTable(agentRows, [
1478
+ { key: "agentId", label: "Agent" },
1479
+ { key: "stableName", label: "Name" },
1480
+ { key: "root", label: "Root" },
1481
+ { key: "skills", label: "Skills Dir" }
1482
+ ])}`;
1483
+ }
1484
+ async function runConfig(options = {}) {
1485
+ const homeDir = options.homeDir ?? homedir6();
1486
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1487
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1488
+ const config = await loadUserConfig(skillmuxHome);
1489
+ const resultWithoutOutput = {
1490
+ skillmuxHome,
1491
+ configPath: buildConfigPath(skillmuxHome),
1492
+ config
1493
+ };
1494
+ return {
1495
+ ...resultWithoutOutput,
1496
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput5(resultWithoutOutput)
1497
+ };
1498
+ }
1499
+
948
1500
  // src/commands/doctor.ts
1501
+ import * as fs11 from "fs/promises";
1502
+ import { homedir as homedir7 } from "os";
1503
+ import { join as join8 } from "path";
949
1504
  function buildIssue2(code, severity, message, path) {
950
1505
  return path === void 0 ? { code, severity, message } : { code, severity, message, path };
951
1506
  }
952
1507
  async function pathExists2(path) {
953
1508
  try {
954
- await fs9.access(path);
1509
+ await fs11.access(path);
955
1510
  return true;
956
1511
  } catch (error) {
957
1512
  if (error.code === "ENOENT") {
@@ -965,7 +1520,7 @@ async function addUnmanagedDirectoryIssues(entries, issues) {
965
1520
  if (entry.kind !== "unmanaged-directory") {
966
1521
  continue;
967
1522
  }
968
- if (await pathExists2(join5(entry.path, "SKILL.md"))) {
1523
+ if (await pathExists2(join8(entry.path, "SKILL.md"))) {
969
1524
  issues.push(
970
1525
  buildIssue2(
971
1526
  "unmanaged-skill-directory",
@@ -1015,7 +1570,7 @@ function addConflictingAgentPathIssues(agents, issues) {
1015
1570
  );
1016
1571
  }
1017
1572
  }
1018
- function buildTableOutput5(issues) {
1573
+ function buildTableOutput6(issues) {
1019
1574
  if (issues.length === 0) {
1020
1575
  return "No doctor issues found.\n";
1021
1576
  }
@@ -1047,7 +1602,7 @@ function buildJsonOutput(result) {
1047
1602
  });
1048
1603
  }
1049
1604
  async function runDoctor(options = {}) {
1050
- const homeDir = options.homeDir ?? homedir5();
1605
+ const homeDir = options.homeDir ?? homedir7();
1051
1606
  const resolvedPaths = resolveSkillmuxHome(homeDir);
1052
1607
  const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1053
1608
  const [manifest, config, agents] = await Promise.all([
@@ -1084,135 +1639,14 @@ async function runDoctor(options = {}) {
1084
1639
  };
1085
1640
  return {
1086
1641
  ...resultWithoutOutput,
1087
- output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildTableOutput5(dedupedIssues)
1642
+ output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildTableOutput6(dedupedIssues)
1088
1643
  };
1089
1644
  }
1090
1645
 
1091
1646
  // src/commands/disable.ts
1092
1647
  import * as fs13 from "fs/promises";
1093
- import { homedir as homedir6 } from "os";
1094
- import { join as join7, resolve as resolve8 } from "path";
1095
-
1096
- // src/fs/safe-copy.ts
1097
- import * as fs10 from "fs/promises";
1098
- import { dirname as dirname2, join as join6, resolve as resolve6 } from "path";
1099
- async function assertDirectory(path) {
1100
- const entry = await fs10.lstat(path);
1101
- if (!entry.isDirectory()) {
1102
- throw new Error(`Expected a directory at ${path}`);
1103
- }
1104
- }
1105
- async function assertRegularFile(path, label) {
1106
- const entry = await fs10.lstat(path);
1107
- if (!entry.isFile()) {
1108
- throw new Error(`Expected ${label} to be a regular file at ${path}`);
1109
- }
1110
- }
1111
- async function assertTargetDoesNotExist(path) {
1112
- try {
1113
- await fs10.lstat(path);
1114
- throw new Error(`Refusing to overwrite existing path at ${path}`);
1115
- } catch (error) {
1116
- if (error.code !== "ENOENT") {
1117
- throw error;
1118
- }
1119
- }
1120
- }
1121
- async function copyDirectoryContents(sourcePath, targetPath) {
1122
- await fs10.mkdir(targetPath, { recursive: true });
1123
- const entries = await fs10.readdir(sourcePath, { withFileTypes: true });
1124
- for (const entry of entries) {
1125
- const sourceEntryPath = join6(sourcePath, entry.name);
1126
- const targetEntryPath = join6(targetPath, entry.name);
1127
- const entryStats = await fs10.lstat(sourceEntryPath);
1128
- if (entryStats.isSymbolicLink()) {
1129
- throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
1130
- }
1131
- if (entryStats.isDirectory()) {
1132
- await copyDirectoryContents(sourceEntryPath, targetEntryPath);
1133
- continue;
1134
- }
1135
- if (entryStats.isFile()) {
1136
- await fs10.mkdir(dirname2(targetEntryPath), { recursive: true });
1137
- await fs10.copyFile(sourceEntryPath, targetEntryPath);
1138
- continue;
1139
- }
1140
- throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
1141
- }
1142
- }
1143
- async function assertSkillSourceLayout(sourcePath) {
1144
- const resolvedSourcePath = resolve6(sourcePath);
1145
- const skillFilePath = join6(resolvedSourcePath, "SKILL.md");
1146
- await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
1147
- await assertDirectory(resolvedSourcePath);
1148
- try {
1149
- await assertRegularFile(skillFilePath, "SKILL.md");
1150
- } catch (error) {
1151
- if (error.code === "ENOENT") {
1152
- throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
1153
- }
1154
- throw error;
1155
- }
1156
- }
1157
- async function copySkillContentsToManagedStore(sourcePath, targetPath) {
1158
- const resolvedSourcePath = resolve6(sourcePath);
1159
- const resolvedTargetPath = resolve6(targetPath);
1160
- if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
1161
- throw new Error("Source and target paths must differ");
1162
- }
1163
- if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
1164
- throw new Error("Refusing to copy into a child of the source directory");
1165
- }
1166
- await assertSkillSourceLayout(resolvedSourcePath);
1167
- await assertNoSymlinkAncestors(resolvedTargetPath);
1168
- await assertTargetDoesNotExist(resolvedTargetPath);
1169
- await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
1170
- }
1171
-
1172
- // src/fs/link-ops.ts
1173
- import * as fs11 from "fs/promises";
1174
- import { dirname as dirname3, resolve as resolve7 } from "path";
1175
- var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
1176
- async function createManagedLink(linkPath, targetPath) {
1177
- const resolvedLinkPath = resolve7(linkPath);
1178
- const resolvedTargetPath = resolve7(targetPath);
1179
- await assertNoSymlinkAncestors(resolvedLinkPath);
1180
- await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
1181
- await fs11.mkdir(dirname3(resolvedLinkPath), { recursive: true });
1182
- try {
1183
- const existingEntry = await fs11.lstat(resolvedLinkPath);
1184
- if (!existingEntry.isSymbolicLink()) {
1185
- throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
1186
- }
1187
- const currentTargetPath = await fs11.realpath(resolvedLinkPath);
1188
- if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
1189
- return;
1190
- }
1191
- throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
1192
- } catch (error) {
1193
- if (error.code === "ENOENT" && await fs11.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
1194
- await fs11.rm(resolvedLinkPath, { recursive: true, force: false });
1195
- } else if (error.code !== "ENOENT") {
1196
- throw error;
1197
- }
1198
- }
1199
- await fs11.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
1200
- }
1201
- async function isLinkPointingToTarget(linkPath, targetPath) {
1202
- try {
1203
- const entry = await fs11.lstat(linkPath);
1204
- if (!entry.isSymbolicLink()) {
1205
- return false;
1206
- }
1207
- const resolvedTargetPath = await fs11.realpath(linkPath);
1208
- return pathsAreEqual(resolvedTargetPath, targetPath);
1209
- } catch (error) {
1210
- if (error.code === "ENOENT") {
1211
- return false;
1212
- }
1213
- throw error;
1214
- }
1215
- }
1648
+ import { homedir as homedir8 } from "os";
1649
+ import { join as join9, resolve as resolve9 } from "path";
1216
1650
 
1217
1651
  // src/fs/safe-remove-link.ts
1218
1652
  import * as fs12 from "fs/promises";
@@ -1233,7 +1667,7 @@ async function safeRemoveLink(path) {
1233
1667
  }
1234
1668
 
1235
1669
  // src/commands/disable.ts
1236
- function buildAgentRecord(agent, timestamp) {
1670
+ function buildAgentRecord2(agent, timestamp) {
1237
1671
  return {
1238
1672
  id: agent.id,
1239
1673
  name: agent.stableName,
@@ -1243,7 +1677,7 @@ function buildAgentRecord(agent, timestamp) {
1243
1677
  lastSeenAt: agent.exists ? timestamp : null
1244
1678
  };
1245
1679
  }
1246
- function buildActivationRecord(skillId, agentId, linkPath, timestamp) {
1680
+ function buildActivationRecord2(skillId, agentId, linkPath, timestamp) {
1247
1681
  return {
1248
1682
  skillId,
1249
1683
  agentId,
@@ -1252,7 +1686,7 @@ function buildActivationRecord(skillId, agentId, linkPath, timestamp) {
1252
1686
  updatedAt: timestamp
1253
1687
  };
1254
1688
  }
1255
- function upsertActivation(manifest, activation) {
1689
+ function upsertActivation2(manifest, activation) {
1256
1690
  const index = manifest.activations.findIndex(
1257
1691
  (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
1258
1692
  );
@@ -1262,8 +1696,8 @@ function upsertActivation(manifest, activation) {
1262
1696
  }
1263
1697
  manifest.activations[index] = activation;
1264
1698
  }
1265
- function buildManagedSkillPath(skillmuxHome, skillId) {
1266
- return resolve8(skillmuxHome, "skills", skillId);
1699
+ function buildManagedSkillPath2(skillmuxHome, skillId) {
1700
+ return resolve9(skillmuxHome, "skills", skillId);
1267
1701
  }
1268
1702
  async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName, linkPath, timestamp) {
1269
1703
  try {
@@ -1279,7 +1713,7 @@ async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName,
1279
1713
  }
1280
1714
  const sourcePath = await fs13.realpath(linkPath);
1281
1715
  await assertSkillSourceLayout(sourcePath);
1282
- const managedSkillPath = buildManagedSkillPath(skillmuxHome, skillId);
1716
+ const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
1283
1717
  await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
1284
1718
  const skill = {
1285
1719
  id: skillId,
@@ -1294,7 +1728,7 @@ async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName,
1294
1728
  manifest.skills[skillId] = skill;
1295
1729
  return { skill, sourcePath };
1296
1730
  }
1297
- async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
1731
+ async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
1298
1732
  const agentId = normalizeId(agentName);
1299
1733
  const agents = await discoverAgents({ homeDir, skillmuxHome });
1300
1734
  const agent = agents.find((entry) => entry.id === agentId);
@@ -1317,15 +1751,18 @@ async function pathExists3(path) {
1317
1751
  throw error;
1318
1752
  }
1319
1753
  }
1320
- async function runDisable(options) {
1321
- const homeDir = options.homeDir ?? homedir6();
1754
+ async function runDisableSingle(options) {
1755
+ if (options.agent === void 0) {
1756
+ throw new Error("Disable requires one target agent");
1757
+ }
1758
+ const homeDir = options.homeDir ?? homedir8();
1322
1759
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1323
1760
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1324
1761
  const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1325
1762
  const manifest = await readManifest(skillmuxHome);
1326
1763
  const skillId = normalizeId(options.skill);
1327
- const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
1328
- const linkPath = join7(agent.absoluteSkillsDirectoryPath, skillId);
1764
+ const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
1765
+ const linkPath = join9(agent.absoluteSkillsDirectoryPath, skillId);
1329
1766
  const adoption = manifest.skills[skillId] ? void 0 : await tryAdoptManagedSkill(
1330
1767
  manifest,
1331
1768
  skillmuxHome,
@@ -1342,7 +1779,7 @@ async function runDisable(options) {
1342
1779
  (entry) => entry.skillId === skill.id && entry.agentId === agent.id
1343
1780
  );
1344
1781
  const activationLinkPath = currentActivation?.linkPath ?? linkPath;
1345
- const agentRecord = buildAgentRecord(agent, timestamp);
1782
+ const agentRecord = buildAgentRecord2(agent, timestamp);
1346
1783
  manifest.agents[agent.id] = agentRecord;
1347
1784
  const adoptedLinkRemoved = adoption !== void 0 ? await safeRemoveLink(linkPath) : false;
1348
1785
  const linkMatchesSkill = adoption === void 0 ? await isLinkPointingToTarget(activationLinkPath, skill.path) : false;
@@ -1361,8 +1798,8 @@ async function runDisable(options) {
1361
1798
  `
1362
1799
  };
1363
1800
  }
1364
- const activation = buildActivationRecord(skill.id, agent.id, linkPath, timestamp);
1365
- upsertActivation(manifest, activation);
1801
+ const activation = buildActivationRecord2(skill.id, agent.id, linkPath, timestamp);
1802
+ upsertActivation2(manifest, activation);
1366
1803
  await writeManifest(skillmuxHome, manifest);
1367
1804
  return {
1368
1805
  changed: true,
@@ -1374,12 +1811,44 @@ async function runDisable(options) {
1374
1811
  `
1375
1812
  };
1376
1813
  }
1814
+ async function runDisable(options) {
1815
+ if (options.agents !== void 0) {
1816
+ const results = [];
1817
+ for (const agent of options.agents) {
1818
+ try {
1819
+ results.push(await runDisableSingle({ ...options, agent, agents: void 0 }));
1820
+ } catch (error) {
1821
+ throw new BatchOperationError({
1822
+ operation: "disable",
1823
+ failedItem: agent,
1824
+ failedAction: `disable ${options.skill} for ${agent}`,
1825
+ completedAction: "disabling",
1826
+ completedItems: results.map((result) => result.agent.id),
1827
+ cause: error
1828
+ });
1829
+ }
1830
+ }
1831
+ if (results.length === 0) {
1832
+ throw new Error("Disable requires at least one target agent");
1833
+ }
1834
+ const lastResult = results[results.length - 1];
1835
+ return {
1836
+ changed: results.some((result) => result.changed),
1837
+ skill: lastResult.skill,
1838
+ results,
1839
+ changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
1840
+ manifest: lastResult.manifest,
1841
+ output: results.map((result) => result.output).join("")
1842
+ };
1843
+ }
1844
+ return runDisableSingle(options);
1845
+ }
1377
1846
 
1378
1847
  // src/commands/enable.ts
1379
1848
  import * as fs14 from "fs/promises";
1380
- import { homedir as homedir7 } from "os";
1381
- import { join as join8 } from "path";
1382
- function buildAgentRecord2(agent, timestamp) {
1849
+ import { homedir as homedir9 } from "os";
1850
+ import { join as join10 } from "path";
1851
+ function buildAgentRecord3(agent, timestamp) {
1383
1852
  return {
1384
1853
  id: agent.id,
1385
1854
  name: agent.stableName,
@@ -1389,7 +1858,7 @@ function buildAgentRecord2(agent, timestamp) {
1389
1858
  lastSeenAt: timestamp
1390
1859
  };
1391
1860
  }
1392
- function buildActivationRecord2(skillId, agentId, linkPath, timestamp, state) {
1861
+ function buildActivationRecord3(skillId, agentId, linkPath, timestamp, state) {
1393
1862
  return {
1394
1863
  skillId,
1395
1864
  agentId,
@@ -1398,7 +1867,7 @@ function buildActivationRecord2(skillId, agentId, linkPath, timestamp, state) {
1398
1867
  updatedAt: timestamp
1399
1868
  };
1400
1869
  }
1401
- function upsertActivation2(manifest, activation) {
1870
+ function upsertActivation3(manifest, activation) {
1402
1871
  const index = manifest.activations.findIndex(
1403
1872
  (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
1404
1873
  );
@@ -1408,7 +1877,7 @@ function upsertActivation2(manifest, activation) {
1408
1877
  }
1409
1878
  manifest.activations[index] = activation;
1410
1879
  }
1411
- async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
1880
+ async function resolveTargetAgent3(homeDir, skillmuxHome, agentName) {
1412
1881
  const agentId = normalizeId(agentName);
1413
1882
  const agents = await discoverAgents({ homeDir, skillmuxHome });
1414
1883
  const agent = agents.find((entry) => entry.id === agentId);
@@ -1420,8 +1889,11 @@ async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
1420
1889
  }
1421
1890
  return agent;
1422
1891
  }
1423
- async function runEnable(options) {
1424
- const homeDir = options.homeDir ?? homedir7();
1892
+ async function runEnableSingle(options) {
1893
+ if (options.agent === void 0) {
1894
+ throw new Error("Enable requires one target agent");
1895
+ }
1896
+ const homeDir = options.homeDir ?? homedir9();
1425
1897
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1426
1898
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1427
1899
  const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
@@ -1431,12 +1903,12 @@ async function runEnable(options) {
1431
1903
  if (skill === void 0) {
1432
1904
  throw new Error(`Managed skill not found: ${skillId}`);
1433
1905
  }
1434
- const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
1435
- const linkPath = join8(agent.absoluteSkillsDirectoryPath, skill.id);
1906
+ const agent = await resolveTargetAgent3(homeDir, skillmuxHome, options.agent);
1907
+ const linkPath = join10(agent.absoluteSkillsDirectoryPath, skill.id);
1436
1908
  const currentActivation = manifest.activations.find(
1437
1909
  (entry) => entry.skillId === skill.id && entry.agentId === agent.id
1438
1910
  );
1439
- const agentRecord = buildAgentRecord2(agent, timestamp);
1911
+ const agentRecord = buildAgentRecord3(agent, timestamp);
1440
1912
  manifest.agents[agent.id] = agentRecord;
1441
1913
  await fs14.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
1442
1914
  const linkAlreadyEnabled = await isLinkPointingToTarget(linkPath, skill.path);
@@ -1453,14 +1925,14 @@ async function runEnable(options) {
1453
1925
  };
1454
1926
  }
1455
1927
  await createManagedLink(linkPath, skill.path);
1456
- const activation = buildActivationRecord2(
1928
+ const activation = buildActivationRecord3(
1457
1929
  skill.id,
1458
1930
  agent.id,
1459
1931
  linkPath,
1460
1932
  timestamp,
1461
1933
  "enabled"
1462
1934
  );
1463
- upsertActivation2(manifest, activation);
1935
+ upsertActivation3(manifest, activation);
1464
1936
  await writeManifest(skillmuxHome, manifest);
1465
1937
  return {
1466
1938
  changed: true,
@@ -1472,22 +1944,54 @@ async function runEnable(options) {
1472
1944
  `
1473
1945
  };
1474
1946
  }
1947
+ async function runEnable(options) {
1948
+ if (options.agents !== void 0) {
1949
+ const results = [];
1950
+ for (const agent of options.agents) {
1951
+ try {
1952
+ results.push(await runEnableSingle({ ...options, agent, agents: void 0 }));
1953
+ } catch (error) {
1954
+ throw new BatchOperationError({
1955
+ operation: "enable",
1956
+ failedItem: agent,
1957
+ failedAction: `enable ${options.skill} for ${agent}`,
1958
+ completedAction: "enabling",
1959
+ completedItems: results.map((result) => result.agent.id),
1960
+ cause: error
1961
+ });
1962
+ }
1963
+ }
1964
+ if (results.length === 0) {
1965
+ throw new Error("Enable requires at least one target agent");
1966
+ }
1967
+ const lastResult = results[results.length - 1];
1968
+ return {
1969
+ changed: results.some((result) => result.changed),
1970
+ skill: lastResult.skill,
1971
+ results,
1972
+ changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
1973
+ manifest: lastResult.manifest,
1974
+ output: results.map((result) => result.output).join("")
1975
+ };
1976
+ }
1977
+ return runEnableSingle(options);
1978
+ }
1475
1979
 
1476
1980
  // src/commands/import.ts
1477
- import { resolve as resolve9 } from "path";
1478
- import { homedir as homedir8 } from "os";
1479
- function buildManagedSkillPath2(skillmuxHome, skillId) {
1480
- return resolve9(skillmuxHome, "skills", skillId);
1981
+ import { resolve as resolve10 } from "path";
1982
+ import { homedir as homedir10 } from "os";
1983
+ function buildManagedSkillPath3(skillmuxHome, skillId) {
1984
+ return resolve10(skillmuxHome, "skills", skillId);
1481
1985
  }
1482
1986
  async function runImport(options) {
1483
- const homeDir = options.homeDir ?? homedir8();
1987
+ const homeDir = options.homeDir ?? homedir10();
1484
1988
  const resolvedPaths = resolveSkillmuxHome(homeDir);
1485
1989
  const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1486
- const sourcePath = resolve9(options.sourcePath);
1990
+ const sourcePath = resolve10(options.sourcePath);
1487
1991
  const skillId = normalizeId(options.skillName);
1488
1992
  const importedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1489
1993
  const manifest = await readManifest(skillmuxHome);
1490
- const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
1994
+ const managedSkillPath = buildManagedSkillPath3(skillmuxHome, skillId);
1491
1995
  await assertSkillSourceLayout(sourcePath);
1492
1996
  if (manifest.skills[skillId] !== void 0) {
1493
1997
  throw new Error(`Managed skill already exists for ${skillId}`);
@@ -1514,7 +2018,7 @@ async function runImport(options) {
1514
2018
  }
1515
2019
 
1516
2020
  // src/commands/scan.ts
1517
- import { homedir as homedir9 } from "os";
2021
+ import { homedir as homedir11 } from "os";
1518
2022
 
1519
2023
  // src/output/format-issue.ts
1520
2024
  function formatIssue(issue) {
@@ -1525,7 +2029,7 @@ function formatIssue(issue) {
1525
2029
  }
1526
2030
 
1527
2031
  // src/commands/scan.ts
1528
- function buildAgentRecord3(agent, timestamp, previousRecord) {
2032
+ function buildAgentRecord4(agent, timestamp, previousRecord) {
1529
2033
  const lastSeenAt = agent.exists ? timestamp : previousRecord?.lastSeenAt ?? null;
1530
2034
  return {
1531
2035
  id: agent.id,
@@ -1575,7 +2079,7 @@ ${result.issues.map(formatIssue).join("\n")}
1575
2079
  `;
1576
2080
  }
1577
2081
  async function runScan(options = {}) {
1578
- const homeDir = options.homeDir ?? homedir9();
2082
+ const homeDir = options.homeDir ?? homedir11();
1579
2083
  const resolvedPaths = resolveSkillmuxHome(homeDir);
1580
2084
  const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1581
2085
  const manifest = await readManifest(skillmuxHome);
@@ -1591,7 +2095,7 @@ async function runScan(options = {}) {
1591
2095
  const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
1592
2096
  entries.push(...scannedAgent.entries);
1593
2097
  issues.push(...scannedAgent.issues);
1594
- manifest.agents[agent.id] = buildAgentRecord3(
2098
+ manifest.agents[agent.id] = buildAgentRecord4(
1595
2099
  agent,
1596
2100
  timestamp,
1597
2101
  manifest.agents[agent.id]
@@ -1681,7 +2185,7 @@ function buildListData(scanResult, view) {
1681
2185
  }
1682
2186
  return buildRecordsView(scanResult);
1683
2187
  }
1684
- function buildTableOutput6(data, view) {
2188
+ function buildTableOutput7(data, view) {
1685
2189
  if (view === "agents") {
1686
2190
  const agentRows = data.agents;
1687
2191
  return printTable(
@@ -1731,14 +2235,204 @@ async function runList(options = {}) {
1731
2235
  const data = buildListData(scanResult, view);
1732
2236
  return {
1733
2237
  data,
1734
- output: format === "json" ? printJson(data) : buildTableOutput6(data, view)
2238
+ output: format === "json" ? printJson(data) : buildTableOutput7(data, view)
2239
+ };
2240
+ }
2241
+
2242
+ // src/commands/remove.ts
2243
+ import * as fs15 from "fs/promises";
2244
+ import { homedir as homedir12 } from "os";
2245
+ import { resolve as resolve11 } from "path";
2246
+ function buildManagedSkillPath4(skillmuxHome, skillId) {
2247
+ return resolve11(skillmuxHome, "skills", skillId);
2248
+ }
2249
+ function buildManifestPath(skillmuxHome) {
2250
+ return resolve11(skillmuxHome, "manifest.json");
2251
+ }
2252
+ function buildConfigPath2(skillmuxHome) {
2253
+ return resolve11(skillmuxHome, "config.json");
2254
+ }
2255
+ function resolveManagedSkill(manifest, skillNameOrId) {
2256
+ const skillId = normalizeId(skillNameOrId);
2257
+ const directMatch = manifest.skills[skillId];
2258
+ if (directMatch !== void 0) {
2259
+ return directMatch;
2260
+ }
2261
+ const nameMatches = Object.values(manifest.skills).filter(
2262
+ (skill) => normalizeId(skill.name) === skillId
2263
+ );
2264
+ if (nameMatches.length === 1) {
2265
+ return nameMatches[0];
2266
+ }
2267
+ if (nameMatches.length > 1) {
2268
+ const candidateIds = nameMatches.map((skill) => skill.id).sort((left, right) => left.localeCompare(right));
2269
+ throw new Error(
2270
+ `Ambiguous managed skill name ${skillNameOrId}: ${candidateIds.join(", ")}`
2271
+ );
2272
+ }
2273
+ throw new Error(`Managed skill not found: ${skillId}`);
2274
+ }
2275
+ function buildHumanOutput(skill, managedSkillPath) {
2276
+ return `Removed ${skill.id} from ${managedSkillPath}
2277
+ `;
2278
+ }
2279
+ function buildJsonOutput2(result) {
2280
+ return printJson({
2281
+ changed: result.changed,
2282
+ removedSkillId: result.removedSkillId,
2283
+ skill: result.skill,
2284
+ location: result.location,
2285
+ manifest: result.manifest
2286
+ });
2287
+ }
2288
+ async function pathExists4(path) {
2289
+ try {
2290
+ await fs15.lstat(path);
2291
+ return true;
2292
+ } catch (error) {
2293
+ if (error.code === "ENOENT") {
2294
+ return false;
2295
+ }
2296
+ throw error;
2297
+ }
2298
+ }
2299
+ async function assertManagedSkillRemovalSafety(skillmuxHome, skillPath, skillId) {
2300
+ const expectedSkillPath = buildManagedSkillPath4(skillmuxHome, skillId);
2301
+ if (!pathsAreEqual(skillPath, expectedSkillPath)) {
2302
+ throw new Error(`Refusing to remove unmanaged skill path at ${skillPath}`);
2303
+ }
2304
+ await assertNoSymlinkAncestors(skillPath, { includeLeaf: true });
2305
+ if (!await pathExists4(skillPath)) {
2306
+ return;
2307
+ }
2308
+ const entry = await fs15.lstat(skillPath);
2309
+ if (entry.isSymbolicLink()) {
2310
+ throw new Error(`Refusing to remove symlinked managed skill path at ${skillPath}`);
2311
+ }
2312
+ if (!entry.isDirectory()) {
2313
+ throw new Error(`Refusing to remove non-directory managed skill path at ${skillPath}`);
2314
+ }
2315
+ }
2316
+ async function runRemoveSingle(options) {
2317
+ if (options.skill === void 0) {
2318
+ throw new Error("Remove requires one target skill");
2319
+ }
2320
+ const homeDir = options.homeDir ?? homedir12();
2321
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
2322
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
2323
+ const manifestPath = buildManifestPath(skillmuxHome);
2324
+ const configPath = buildConfigPath2(skillmuxHome);
2325
+ const manifest = await readManifest(skillmuxHome);
2326
+ const skill = resolveManagedSkill(manifest, options.skill);
2327
+ const managedSkillsDirectory = resolve11(skillmuxHome, "skills");
2328
+ const managedSkillPath = skill.path;
2329
+ const enabledActivations = manifest.activations.filter(
2330
+ (activation) => activation.skillId === skill.id && activation.state === "enabled"
2331
+ );
2332
+ if (enabledActivations.length > 0) {
2333
+ const enabledAgents = [...new Set(enabledActivations.map((activation) => activation.agentId))].sort(
2334
+ (left, right) => left.localeCompare(right)
2335
+ );
2336
+ throw new Error(
2337
+ `Cannot remove ${skill.id}; it is still enabled for: ${enabledAgents.join(", ")}`
2338
+ );
2339
+ }
2340
+ await assertManagedSkillRemovalSafety(skillmuxHome, managedSkillPath, skill.id);
2341
+ if (await pathExists4(managedSkillPath)) {
2342
+ await fs15.rm(managedSkillPath, { recursive: true, force: false });
2343
+ }
2344
+ delete manifest.skills[skill.id];
2345
+ manifest.activations = manifest.activations.filter(
2346
+ (activation) => activation.skillId !== skill.id
2347
+ );
2348
+ await writeManifest(skillmuxHome, manifest);
2349
+ const resultWithoutOutput = {
2350
+ changed: true,
2351
+ removedSkillId: skill.id,
2352
+ skill,
2353
+ location: {
2354
+ skillmuxHome,
2355
+ configPath,
2356
+ manifestPath,
2357
+ managedSkillsDirectory,
2358
+ managedSkillPath
2359
+ },
2360
+ manifest
2361
+ };
2362
+ return {
2363
+ ...resultWithoutOutput,
2364
+ output: options.json === true ? buildJsonOutput2(resultWithoutOutput) : buildHumanOutput(skill, managedSkillPath)
1735
2365
  };
1736
2366
  }
2367
+ async function runRemove(options) {
2368
+ if (options.skills !== void 0) {
2369
+ const results = [];
2370
+ for (const skill of options.skills) {
2371
+ try {
2372
+ results.push(await runRemoveSingle({ ...options, skill, skills: void 0 }));
2373
+ } catch (error) {
2374
+ throw new BatchOperationError({
2375
+ operation: "remove",
2376
+ failedItem: skill,
2377
+ failedAction: `remove ${skill}`,
2378
+ completedAction: "removing",
2379
+ completedItems: results.map((result) => result.removedSkillId),
2380
+ cause: error
2381
+ });
2382
+ }
2383
+ }
2384
+ if (results.length === 0) {
2385
+ throw new Error("Remove requires at least one target skill");
2386
+ }
2387
+ const lastResult = results[results.length - 1];
2388
+ const resultWithoutOutput = {
2389
+ changed: results.some((result) => result.changed),
2390
+ removedSkillIds: results.map((result) => result.removedSkillId),
2391
+ results,
2392
+ manifest: lastResult.manifest
2393
+ };
2394
+ return {
2395
+ ...resultWithoutOutput,
2396
+ output: options.json === true ? printJson(resultWithoutOutput) : results.map((result) => result.output).join("")
2397
+ };
2398
+ }
2399
+ return runRemoveSingle(options);
2400
+ }
1737
2401
 
1738
2402
  // src/index.ts
2403
+ function collectValues(value, previous = []) {
2404
+ return [...previous, value];
2405
+ }
2406
+ function requireSingleValue(values, label) {
2407
+ if (values.length !== 1) {
2408
+ throw new Error(`Expected exactly one ${label}`);
2409
+ }
2410
+ return values[0];
2411
+ }
2412
+ function requireAtLeastOneValue(values, label) {
2413
+ if (values.length === 0) {
2414
+ throw new Error(`Expected at least one ${label}`);
2415
+ }
2416
+ return values;
2417
+ }
1739
2418
  function buildCli() {
1740
2419
  const program = new Command();
1741
2420
  program.name("skillmux");
2421
+ program.command("adopt").requiredOption("--agent <agent>", "Source agent id").option("--skill <skill>", "Repeatable installed skill to adopt", collectValues, []).option("--json", "Emit structured JSON output").action(async (options) => {
2422
+ const result = options.skill.length === 0 ? await runAdopt({
2423
+ agent: options.agent,
2424
+ json: options.json === true
2425
+ }) : options.skill.length === 1 ? await runAdopt({
2426
+ agent: options.agent,
2427
+ skill: options.skill[0],
2428
+ json: options.json === true
2429
+ }) : await runAdopt({
2430
+ agent: options.agent,
2431
+ skills: options.skill,
2432
+ json: options.json === true
2433
+ });
2434
+ process.stdout.write(result.output);
2435
+ });
1742
2436
  program.command("scan").option("--json", "Emit structured JSON output").action(async (options) => {
1743
2437
  const result = await runScan({ json: options.json === true });
1744
2438
  process.stdout.write(result.output);
@@ -1796,17 +2490,48 @@ function buildCli() {
1796
2490
  });
1797
2491
  process.stdout.write(result.output);
1798
2492
  });
1799
- program.command("enable").requiredOption("--skill <skill>", "Managed skill name or id").requiredOption("--agent <agent>", "Target agent id").action(async (options) => {
2493
+ configCommand.command("update-agent").requiredOption("--id <id>", "Agent id").option("--root <path>", "Home-relative root path").option("--skills <path>", "Skills directory path relative to the agent root").option("--name <name>", "Stable display name").option(
2494
+ "--platform <platform>",
2495
+ `Supported platform (${supportedPlatforms.join(", ")})`,
2496
+ (value, previous = []) => [...previous, value],
2497
+ []
2498
+ ).option("--enabled-by-default", "Mark this custom agent as enabled by default").option("--disabled-by-default", "Mark this custom agent as disabled by default").option("--json", "Emit structured JSON output").action(
2499
+ async (options) => {
2500
+ const result = await runConfigUpdateAgent({
2501
+ id: options.id,
2502
+ root: options.root,
2503
+ skills: options.skills,
2504
+ name: options.name,
2505
+ platforms: options.platform !== void 0 && options.platform.length > 0 ? options.platform : void 0,
2506
+ enabledByDefault: options.enabledByDefault === true ? true : void 0,
2507
+ disabledByDefault: options.disabledByDefault === true,
2508
+ json: options.json === true
2509
+ });
2510
+ process.stdout.write(result.output);
2511
+ }
2512
+ );
2513
+ program.command("enable").requiredOption("--skill <skill>", "Managed skill name or id", collectValues, []).requiredOption("--agent <agent>", "Repeatable target agent", collectValues, []).action(async (options) => {
1800
2514
  const result = await runEnable({
1801
- skill: options.skill,
1802
- agent: options.agent
2515
+ skill: requireSingleValue(options.skill, "skill"),
2516
+ agents: requireAtLeastOneValue(options.agent, "agent")
1803
2517
  });
1804
2518
  process.stdout.write(result.output);
1805
2519
  });
1806
- program.command("disable").requiredOption("--skill <skill>", "Managed skill name or id").requiredOption("--agent <agent>", "Target agent id").action(async (options) => {
2520
+ program.command("disable").requiredOption("--skill <skill>", "Managed skill name or id", collectValues, []).requiredOption("--agent <agent>", "Repeatable target agent", collectValues, []).action(async (options) => {
1807
2521
  const result = await runDisable({
1808
- skill: options.skill,
1809
- agent: options.agent
2522
+ skill: requireSingleValue(options.skill, "skill"),
2523
+ agents: requireAtLeastOneValue(options.agent, "agent")
2524
+ });
2525
+ process.stdout.write(result.output);
2526
+ });
2527
+ program.command("remove").requiredOption("--skill <skill>", "Repeatable managed skill name or id", collectValues, []).option("--json", "Emit structured JSON output").action(async (options) => {
2528
+ const skills = requireAtLeastOneValue(options.skill, "skill");
2529
+ const result = skills.length === 1 ? await runRemove({
2530
+ skill: skills[0],
2531
+ json: options.json === true
2532
+ }) : await runRemove({
2533
+ skills,
2534
+ json: options.json === true
1810
2535
  });
1811
2536
  process.stdout.write(result.output);
1812
2537
  });