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