skillmux 0.1.3 → 0.1.5

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.
@@ -59,8 +59,8 @@ var defaultAgentRuleMap = Object.fromEntries(
59
59
  );
60
60
 
61
61
  // src/commands/adopt.ts
62
- import { homedir } from "os";
63
- import { join as join7, resolve as resolve8 } from "path";
62
+ import { homedir as homedir2 } from "os";
63
+ import { join as join8, resolve as resolve8 } from "path";
64
64
 
65
65
  // src/core/errors.ts
66
66
  var SkillMuxError = class extends Error {
@@ -119,8 +119,13 @@ function resolveSkillmuxHome(homeDir) {
119
119
  }
120
120
 
121
121
  // src/discovery/discover-agents.ts
122
- import * as fs2 from "fs/promises";
123
- import { join as join2, resolve as resolve2 } from "path";
122
+ import * as fs3 from "fs/promises";
123
+ import { join as join3, resolve as resolve2 } from "path";
124
+
125
+ // src/config/auto-register-agents.ts
126
+ import { readdirSync, statSync } from "fs";
127
+ import { homedir } from "os";
128
+ import { join as join2 } from "path";
124
129
 
125
130
  // src/config/load-user-config.ts
126
131
  import * as fs from "fs/promises";
@@ -131,16 +136,27 @@ var agentOverrideSchema = z.object({
131
136
  supportedPlatforms: z.array(supportedPlatformSchema).min(1).optional(),
132
137
  homeRelativeRootPath: z.string().min(1).optional(),
133
138
  skillsDirectoryPath: z.string().min(1).optional(),
134
- enabledByDefault: z.boolean().optional()
139
+ enabledByDefault: z.boolean().optional(),
140
+ autoDiscovered: z.literal(true).optional()
135
141
  }).strict();
136
142
  var userConfigSchema = z.object({
137
143
  version: z.literal(1),
138
- agents: z.record(z.string().min(1), agentOverrideSchema)
144
+ agents: z.record(z.string().min(1), agentOverrideSchema),
145
+ autoDiscover: z.object({
146
+ lastRunAt: z.string().nullable(),
147
+ intervalMs: z.number().int().nonnegative()
148
+ }).optional(),
149
+ removedAutoAgentIds: z.array(z.string().min(1)).optional()
139
150
  }).strict();
140
151
  function createEmptyUserConfig() {
141
152
  return {
142
153
  version: 1,
143
- agents: {}
154
+ agents: {},
155
+ autoDiscover: {
156
+ lastRunAt: null,
157
+ intervalMs: 36e5
158
+ },
159
+ removedAutoAgentIds: []
144
160
  };
145
161
  }
146
162
  function stripUtf8Bom(contents) {
@@ -177,6 +193,101 @@ async function loadUserConfig(skillmuxHome) {
177
193
  }
178
194
  }
179
195
 
196
+ // src/config/write-user-config.ts
197
+ import * as fs2 from "fs/promises";
198
+ async function writeUserConfig(skillmuxHome, config) {
199
+ const configPath = buildConfigPath(skillmuxHome);
200
+ await fs2.mkdir(skillmuxHome, { recursive: true });
201
+ await fs2.writeFile(configPath, `${JSON.stringify(config, null, 2)}
202
+ `, "utf8");
203
+ return {
204
+ skillmuxHome,
205
+ configPath
206
+ };
207
+ }
208
+
209
+ // src/config/auto-register-agents.ts
210
+ function isDotDir(name) {
211
+ return name.startsWith(".");
212
+ }
213
+ function isValidAgentId(name) {
214
+ if (name.length < 2) return false;
215
+ const stem = name.slice(1);
216
+ return /^[a-z][a-z0-9-]*$/u.test(stem);
217
+ }
218
+ function dirExists(dirPath) {
219
+ try {
220
+ return statSync(dirPath).isDirectory();
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ function isBuiltInAgentId(id) {
226
+ return builtInAgentIds.includes(id);
227
+ }
228
+ function makeAutoDiscoveredOverride(dirName) {
229
+ const stem = dirName.slice(1);
230
+ return {
231
+ stableName: stem.charAt(0).toUpperCase() + stem.slice(1),
232
+ supportedPlatforms: ["win32", "linux", "darwin"],
233
+ homeRelativeRootPath: dirName,
234
+ skillsDirectoryPath: "skills",
235
+ enabledByDefault: true,
236
+ autoDiscovered: true
237
+ };
238
+ }
239
+ async function autoRegisterNewAgents(homeDir) {
240
+ const resolvedHomeDir = homeDir ?? homedir();
241
+ const { skillmuxHome } = resolveSkillmuxHome(resolvedHomeDir);
242
+ const config = await loadUserConfig(skillmuxHome);
243
+ const autoDiscover = config.autoDiscover ?? {
244
+ lastRunAt: null,
245
+ intervalMs: 36e5
246
+ };
247
+ if (autoDiscover.lastRunAt !== null && autoDiscover.intervalMs > 0) {
248
+ const elapsed = Date.now() - new Date(autoDiscover.lastRunAt).getTime();
249
+ if (elapsed < autoDiscover.intervalMs) {
250
+ return;
251
+ }
252
+ }
253
+ const wellKnownNonAgentDirs = /* @__PURE__ */ new Set([".skillmux"]);
254
+ const removedIds = new Set(config.removedAutoAgentIds ?? []);
255
+ let changed = false;
256
+ let entries;
257
+ try {
258
+ entries = readdirSync(resolvedHomeDir);
259
+ } catch {
260
+ return;
261
+ }
262
+ const nextAgents = { ...config.agents };
263
+ for (const name of entries) {
264
+ if (!isDotDir(name)) continue;
265
+ if (wellKnownNonAgentDirs.has(name)) continue;
266
+ if (!isValidAgentId(name)) continue;
267
+ const agentId = name.slice(1).toLowerCase().replace(/[^a-z0-9]+/g, "-");
268
+ if (agentId in nextAgents) continue;
269
+ if (isBuiltInAgentId(agentId)) continue;
270
+ if (removedIds.has(agentId)) continue;
271
+ const skillsDir = join2(resolvedHomeDir, name, "skills");
272
+ if (!dirExists(skillsDir)) continue;
273
+ nextAgents[agentId] = makeAutoDiscoveredOverride(name);
274
+ changed = true;
275
+ }
276
+ if (!changed && autoDiscover.lastRunAt !== null) {
277
+ return;
278
+ }
279
+ const nextConfig = {
280
+ ...config,
281
+ agents: nextAgents,
282
+ autoDiscover: {
283
+ ...autoDiscover,
284
+ lastRunAt: (/* @__PURE__ */ new Date()).toISOString()
285
+ },
286
+ removedAutoAgentIds: [...removedIds]
287
+ };
288
+ await writeUserConfig(skillmuxHome, nextConfig);
289
+ }
290
+
180
291
  // src/discovery/discover-agents.ts
181
292
  function mergeRule(rule, override) {
182
293
  if (override === void 0) {
@@ -205,7 +316,7 @@ function buildCustomRule(id, override) {
205
316
  }
206
317
  async function pathExists(path) {
207
318
  try {
208
- await fs2.access(path);
319
+ await fs3.access(path);
209
320
  return true;
210
321
  } catch {
211
322
  return false;
@@ -215,7 +326,7 @@ function resolveAgentRulePaths(homeDir, rule) {
215
326
  const absoluteRootPath = resolve2(homeDir, rule.homeRelativeRootPath);
216
327
  return {
217
328
  absoluteRootPath,
218
- absoluteSkillsDirectoryPath: join2(
329
+ absoluteSkillsDirectoryPath: join3(
219
330
  absoluteRootPath,
220
331
  rule.skillsDirectoryPath
221
332
  )
@@ -225,6 +336,7 @@ async function discoverAgents(options) {
225
336
  const platform = options.platform ?? process.platform;
226
337
  const homeDir = resolve2(options.homeDir);
227
338
  const skillmuxHome = options.skillmuxHome ?? resolveSkillmuxHome(homeDir).skillmuxHome;
339
+ await autoRegisterNewAgents(homeDir);
228
340
  const userConfig = await loadUserConfig(skillmuxHome);
229
341
  const discoveredAgents = [];
230
342
  for (const agentId of builtInAgentIds) {
@@ -254,22 +366,23 @@ async function discoverAgents(options) {
254
366
  exists: await pathExists(resolvedPaths.absoluteSkillsDirectoryPath),
255
367
  supportedOnPlatform: customRule.supportedPlatforms.some(
256
368
  (supportedPlatform) => supportedPlatform === platform
257
- )
369
+ ),
370
+ autoDiscovered: override.autoDiscovered === true || void 0
258
371
  });
259
372
  }
260
373
  return discoveredAgents;
261
374
  }
262
375
 
263
376
  // src/discovery/scan-agent-skills.ts
264
- import * as fs5 from "fs/promises";
265
- import { join as join3 } from "path";
377
+ import * as fs6 from "fs/promises";
378
+ import { join as join4 } from "path";
266
379
 
267
380
  // src/discovery/infer-skill-entry.ts
268
- import * as fs4 from "fs/promises";
381
+ import * as fs5 from "fs/promises";
269
382
  import { basename, resolve as resolve4 } from "path";
270
383
 
271
384
  // src/fs/path-utils.ts
272
- import * as fs3 from "fs/promises";
385
+ import * as fs4 from "fs/promises";
273
386
  import { dirname, parse, relative, resolve as resolve3, sep } from "path";
274
387
  function normalizeAbsolutePath(path) {
275
388
  const normalized = resolve3(path);
@@ -300,7 +413,7 @@ async function assertNoSymlinkAncestors(path, options) {
300
413
  let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
301
414
  while (true) {
302
415
  try {
303
- const entry = await fs3.lstat(current);
416
+ const entry = await fs4.lstat(current);
304
417
  if (entry.isSymbolicLink()) {
305
418
  throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
306
419
  }
@@ -324,10 +437,10 @@ function buildIssue(code, severity, message, path) {
324
437
  async function inferSkillEntry(options) {
325
438
  const absolutePath = resolve4(options.path);
326
439
  const skillName = basename(absolutePath);
327
- const stats = await fs4.lstat(absolutePath);
440
+ const stats = await fs5.lstat(absolutePath);
328
441
  if (stats.isSymbolicLink()) {
329
442
  try {
330
- const targetPath = await fs4.realpath(absolutePath);
443
+ const targetPath = await fs5.realpath(absolutePath);
331
444
  if (isPathInside(options.skillmuxHome, targetPath)) {
332
445
  return {
333
446
  entry: {
@@ -407,7 +520,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
407
520
  issues: []
408
521
  };
409
522
  }
410
- const directoryEntries = await fs5.readdir(agent.absoluteSkillsDirectoryPath, {
523
+ const directoryEntries = await fs6.readdir(agent.absoluteSkillsDirectoryPath, {
411
524
  withFileTypes: true
412
525
  });
413
526
  const sortedDirectoryEntries = [...directoryEntries].sort(
@@ -419,7 +532,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
419
532
  const result = await inferSkillEntry({
420
533
  agentId: agent.id,
421
534
  agentName: agent.stableName,
422
- path: join3(agent.absoluteSkillsDirectoryPath, directoryEntry.name),
535
+ path: join4(agent.absoluteSkillsDirectoryPath, directoryEntry.name),
423
536
  skillmuxHome
424
537
  });
425
538
  entries.push(result.entry);
@@ -434,23 +547,23 @@ async function scanAgentSkills(agent, skillmuxHome) {
434
547
  }
435
548
 
436
549
  // src/fs/safe-copy.ts
437
- import * as fs6 from "fs/promises";
438
- import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
550
+ import * as fs7 from "fs/promises";
551
+ import { dirname as dirname2, join as join5, resolve as resolve5 } from "path";
439
552
  async function assertDirectory(path) {
440
- const entry = await fs6.lstat(path);
553
+ const entry = await fs7.lstat(path);
441
554
  if (!entry.isDirectory()) {
442
555
  throw new Error(`Expected a directory at ${path}`);
443
556
  }
444
557
  }
445
558
  async function assertRegularFile(path, label) {
446
- const entry = await fs6.lstat(path);
559
+ const entry = await fs7.lstat(path);
447
560
  if (!entry.isFile()) {
448
561
  throw new Error(`Expected ${label} to be a regular file at ${path}`);
449
562
  }
450
563
  }
451
564
  async function assertTargetDoesNotExist(path) {
452
565
  try {
453
- await fs6.lstat(path);
566
+ await fs7.lstat(path);
454
567
  throw new Error(`Refusing to overwrite existing path at ${path}`);
455
568
  } catch (error) {
456
569
  if (error.code !== "ENOENT") {
@@ -459,12 +572,12 @@ async function assertTargetDoesNotExist(path) {
459
572
  }
460
573
  }
461
574
  async function copyDirectoryContents(sourcePath, targetPath) {
462
- await fs6.mkdir(targetPath, { recursive: true });
463
- const entries = await fs6.readdir(sourcePath, { withFileTypes: true });
575
+ await fs7.mkdir(targetPath, { recursive: true });
576
+ const entries = await fs7.readdir(sourcePath, { withFileTypes: true });
464
577
  for (const entry of entries) {
465
- const sourceEntryPath = join4(sourcePath, entry.name);
466
- const targetEntryPath = join4(targetPath, entry.name);
467
- const entryStats = await fs6.lstat(sourceEntryPath);
578
+ const sourceEntryPath = join5(sourcePath, entry.name);
579
+ const targetEntryPath = join5(targetPath, entry.name);
580
+ const entryStats = await fs7.lstat(sourceEntryPath);
468
581
  if (entryStats.isSymbolicLink()) {
469
582
  throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
470
583
  }
@@ -473,8 +586,8 @@ async function copyDirectoryContents(sourcePath, targetPath) {
473
586
  continue;
474
587
  }
475
588
  if (entryStats.isFile()) {
476
- await fs6.mkdir(dirname2(targetEntryPath), { recursive: true });
477
- await fs6.copyFile(sourceEntryPath, targetEntryPath);
589
+ await fs7.mkdir(dirname2(targetEntryPath), { recursive: true });
590
+ await fs7.copyFile(sourceEntryPath, targetEntryPath);
478
591
  continue;
479
592
  }
480
593
  throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
@@ -482,7 +595,7 @@ async function copyDirectoryContents(sourcePath, targetPath) {
482
595
  }
483
596
  async function assertSkillSourceLayout(sourcePath) {
484
597
  const resolvedSourcePath = resolve5(sourcePath);
485
- const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
598
+ const skillFilePath = join5(resolvedSourcePath, "SKILL.md");
486
599
  await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
487
600
  await assertDirectory(resolvedSourcePath);
488
601
  try {
@@ -496,7 +609,7 @@ async function assertSkillSourceLayout(sourcePath) {
496
609
  }
497
610
  async function hasRootSkillFile(sourcePath) {
498
611
  const resolvedSourcePath = resolve5(sourcePath);
499
- const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
612
+ const skillFilePath = join5(resolvedSourcePath, "SKILL.md");
500
613
  try {
501
614
  await assertDirectory(resolvedSourcePath);
502
615
  await assertRegularFile(skillFilePath, "SKILL.md");
@@ -550,7 +663,7 @@ var BatchOperationError = class extends Error {
550
663
  };
551
664
 
552
665
  // src/fs/link-ops.ts
553
- import * as fs7 from "fs/promises";
666
+ import * as fs8 from "fs/promises";
554
667
  import { dirname as dirname3, resolve as resolve6 } from "path";
555
668
  var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
556
669
  async function createManagedLink(linkPath, targetPath) {
@@ -558,25 +671,25 @@ async function createManagedLink(linkPath, targetPath) {
558
671
  const resolvedTargetPath = resolve6(targetPath);
559
672
  await assertNoSymlinkAncestors(resolvedLinkPath);
560
673
  await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
561
- await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
674
+ await fs8.mkdir(dirname3(resolvedLinkPath), { recursive: true });
562
675
  try {
563
- const existingEntry = await fs7.lstat(resolvedLinkPath);
676
+ const existingEntry = await fs8.lstat(resolvedLinkPath);
564
677
  if (!existingEntry.isSymbolicLink()) {
565
678
  throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
566
679
  }
567
- const currentTargetPath = await fs7.realpath(resolvedLinkPath);
680
+ const currentTargetPath = await fs8.realpath(resolvedLinkPath);
568
681
  if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
569
682
  return;
570
683
  }
571
684
  throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
572
685
  } catch (error) {
573
- if (error.code === "ENOENT" && await fs7.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
574
- await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
686
+ if (error.code === "ENOENT" && await fs8.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
687
+ await fs8.rm(resolvedLinkPath, { recursive: true, force: false });
575
688
  } else if (error.code !== "ENOENT") {
576
689
  throw error;
577
690
  }
578
691
  }
579
- await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
692
+ await fs8.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
580
693
  }
581
694
  async function replaceEntryWithManagedLink(linkPath, targetPath, expectedCurrentPath) {
582
695
  const resolvedLinkPath = resolve6(linkPath);
@@ -584,38 +697,38 @@ async function replaceEntryWithManagedLink(linkPath, targetPath, expectedCurrent
584
697
  const resolvedExpectedCurrentPath = resolve6(expectedCurrentPath);
585
698
  await assertNoSymlinkAncestors(resolvedLinkPath);
586
699
  await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
587
- await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
588
- const existingEntry = await fs7.lstat(resolvedLinkPath);
700
+ await fs8.mkdir(dirname3(resolvedLinkPath), { recursive: true });
701
+ const existingEntry = await fs8.lstat(resolvedLinkPath);
589
702
  if (existingEntry.isSymbolicLink()) {
590
- const currentTargetPath = await fs7.realpath(resolvedLinkPath);
703
+ const currentTargetPath = await fs8.realpath(resolvedLinkPath);
591
704
  if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
592
705
  return false;
593
706
  }
594
707
  if (!pathsAreEqual(currentTargetPath, resolvedExpectedCurrentPath)) {
595
708
  throw new Error(`Refusing to replace unexpected link at ${resolvedLinkPath}`);
596
709
  }
597
- await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
598
- await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
710
+ await fs8.rm(resolvedLinkPath, { recursive: true, force: false });
711
+ await fs8.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
599
712
  return true;
600
713
  }
601
714
  if (!existingEntry.isDirectory()) {
602
715
  throw new Error(`Refusing to replace non-directory entry at ${resolvedLinkPath}`);
603
716
  }
604
- const currentPath = await fs7.realpath(resolvedLinkPath);
717
+ const currentPath = await fs8.realpath(resolvedLinkPath);
605
718
  if (!pathsAreEqual(currentPath, resolvedExpectedCurrentPath)) {
606
719
  throw new Error(`Refusing to replace unexpected directory at ${resolvedLinkPath}`);
607
720
  }
608
- await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
609
- await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
721
+ await fs8.rm(resolvedLinkPath, { recursive: true, force: false });
722
+ await fs8.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
610
723
  return true;
611
724
  }
612
725
  async function isLinkPointingToTarget(linkPath, targetPath) {
613
726
  try {
614
- const entry = await fs7.lstat(linkPath);
727
+ const entry = await fs8.lstat(linkPath);
615
728
  if (!entry.isSymbolicLink()) {
616
729
  return false;
617
730
  }
618
- const resolvedTargetPath = await fs7.realpath(linkPath);
731
+ const resolvedTargetPath = await fs8.realpath(linkPath);
619
732
  return pathsAreEqual(resolvedTargetPath, targetPath);
620
733
  } catch (error) {
621
734
  if (error.code === "ENOENT") {
@@ -626,8 +739,8 @@ async function isLinkPointingToTarget(linkPath, targetPath) {
626
739
  }
627
740
 
628
741
  // src/manifest/read-manifest.ts
629
- import * as fs9 from "fs/promises";
630
- import { join as join6, resolve as resolve7 } from "path";
742
+ import * as fs10 from "fs/promises";
743
+ import { join as join7, resolve as resolve7 } from "path";
631
744
 
632
745
  // src/manifest/build-empty-manifest.ts
633
746
  function buildEmptyManifest(skillmuxHome) {
@@ -752,32 +865,32 @@ var manifestSchema = z2.object({
752
865
 
753
866
  // src/manifest/write-manifest.ts
754
867
  import { randomUUID } from "crypto";
755
- import * as fs8 from "fs/promises";
756
- import { join as join5 } from "path";
868
+ import * as fs9 from "fs/promises";
869
+ import { join as join6 } from "path";
757
870
  function getManifestPath(home) {
758
- return join5(home, "manifest.json");
871
+ return join6(home, "manifest.json");
759
872
  }
760
873
  function createManifestTempPath(manifestPath) {
761
874
  return `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
762
875
  }
763
876
  async function writeManifest(home, manifest) {
764
- await fs8.mkdir(home, { recursive: true });
877
+ await fs9.mkdir(home, { recursive: true });
765
878
  const manifestPath = getManifestPath(home);
766
879
  const tempPath = createManifestTempPath(manifestPath);
767
880
  const contents = `${JSON.stringify(manifest, null, 2)}
768
881
  `;
769
- await fs8.writeFile(tempPath, contents, "utf8");
882
+ await fs9.writeFile(tempPath, contents, "utf8");
770
883
  try {
771
- await fs8.rename(tempPath, manifestPath);
884
+ await fs9.rename(tempPath, manifestPath);
772
885
  } catch (error) {
773
- await fs8.unlink(tempPath).catch(() => void 0);
886
+ await fs9.unlink(tempPath).catch(() => void 0);
774
887
  throw error;
775
888
  }
776
889
  }
777
890
 
778
891
  // src/manifest/read-manifest.ts
779
892
  function getManifestPath2(home) {
780
- return join6(home, "manifest.json");
893
+ return join7(home, "manifest.json");
781
894
  }
782
895
  function normalizeHomePath(home) {
783
896
  const resolvedHome = resolve7(home);
@@ -792,7 +905,7 @@ function formatValidationIssues2(error) {
792
905
  async function readManifest(home) {
793
906
  const manifestPath = getManifestPath2(home);
794
907
  try {
795
- const contents = await fs9.readFile(manifestPath, "utf8");
908
+ const contents = await fs10.readFile(manifestPath, "utf8");
796
909
  const parsedJson = JSON.parse(contents);
797
910
  const parsedManifest = manifestSchema.safeParse(parsedJson);
798
911
  if (!parsedManifest.success) {
@@ -937,7 +1050,7 @@ function buildOutput(result, json) {
937
1050
  `;
938
1051
  }
939
1052
  async function runAdoptSingle(options) {
940
- const homeDir = options.homeDir ?? homedir();
1053
+ const homeDir = options.homeDir ?? homedir2();
941
1054
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
942
1055
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
943
1056
  const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
@@ -1016,7 +1129,7 @@ async function runAdoptSingle(options) {
1016
1129
  const activation = buildActivationRecord(
1017
1130
  skillId,
1018
1131
  agent.id,
1019
- join7(agent.absoluteSkillsDirectoryPath, entry.skillName),
1132
+ join8(agent.absoluteSkillsDirectoryPath, entry.skillName),
1020
1133
  timestamp
1021
1134
  );
1022
1135
  upsertActivation(manifest, activation);
@@ -1084,20 +1197,503 @@ async function runAdopt(options) {
1084
1197
  return runAdoptSingle(options);
1085
1198
  }
1086
1199
 
1087
- // src/commands/disable.ts
1200
+ // src/commands/config-add-agent.ts
1201
+ import { homedir as homedir3 } from "os";
1202
+
1203
+ // src/config/agent-override-validation.ts
1204
+ import { isAbsolute } from "path";
1205
+ function normalizeRelativePath(value, field) {
1206
+ const trimmed = value.trim();
1207
+ if (trimmed.length === 0) {
1208
+ throw new UserConfigValidationError(`${field} must not be empty`);
1209
+ }
1210
+ if (isAbsolute(trimmed)) {
1211
+ throw new UserConfigValidationError(`${field} must be a relative path`);
1212
+ }
1213
+ const normalized = trimmed.replaceAll("\\", "/");
1214
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
1215
+ throw new UserConfigValidationError(`${field} must stay within the configured home-relative tree`);
1216
+ }
1217
+ return normalized.replace(/^\.\/+/, "");
1218
+ }
1219
+ function normalizeAgentId(value) {
1220
+ const trimmed = value.trim();
1221
+ if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
1222
+ throw new InvalidIdentifierError("agent id", value);
1223
+ }
1224
+ return normalizeId(trimmed);
1225
+ }
1226
+ function normalizePlatforms(value) {
1227
+ if (value === void 0 || value.length === 0) {
1228
+ return [process.platform];
1229
+ }
1230
+ const normalized = [...new Set(value.map((entry) => entry.trim().toLowerCase()))];
1231
+ const invalid = normalized.filter(
1232
+ (entry) => supportedPlatforms.includes(entry) === false
1233
+ );
1234
+ if (invalid.length > 0) {
1235
+ throw new UserConfigValidationError(
1236
+ `platform must be one of: ${supportedPlatforms.join(", ")}`
1237
+ );
1238
+ }
1239
+ return normalized;
1240
+ }
1241
+
1242
+ // src/output/print-table.ts
1243
+ function printTable(rows, columns) {
1244
+ const renderedRows = rows.map(
1245
+ (row) => columns.map((column) => String(row[column.key] ?? ""))
1246
+ );
1247
+ const widths = columns.map(
1248
+ (column, index) => Math.max(
1249
+ column.label.length,
1250
+ ...renderedRows.map((row) => row[index]?.length ?? 0)
1251
+ )
1252
+ );
1253
+ const header = columns.map((column, index) => column.label.padEnd(widths[index])).join(" ");
1254
+ const separator = widths.map((width) => "-".repeat(width)).join(" ");
1255
+ const body = renderedRows.map(
1256
+ (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ")
1257
+ );
1258
+ return `${[header, separator, ...body].join("\n")}
1259
+ `;
1260
+ }
1261
+
1262
+ // src/commands/config-add-agent.ts
1263
+ function buildAgentOverride(options) {
1264
+ const agentId = normalizeAgentId(options.id);
1265
+ const agent = {
1266
+ supportedPlatforms: normalizePlatforms(options.platforms),
1267
+ homeRelativeRootPath: normalizeRelativePath(options.root, "root"),
1268
+ skillsDirectoryPath: normalizeRelativePath(options.skills ?? "skills", "skills")
1269
+ };
1270
+ if (options.name !== void 0 && options.name.trim().length > 0) {
1271
+ agent.stableName = options.name.trim();
1272
+ }
1273
+ if (options.disabledByDefault === true) {
1274
+ agent.enabledByDefault = false;
1275
+ }
1276
+ return { agentId, agent };
1277
+ }
1278
+ function buildTableOutput(result) {
1279
+ const summary = printTable(
1280
+ [
1281
+ {
1282
+ agentId: result.agentId,
1283
+ configPath: result.configPath,
1284
+ changed: String(result.changed)
1285
+ }
1286
+ ],
1287
+ [
1288
+ { key: "agentId", label: "Agent" },
1289
+ { key: "configPath", label: "Config Path" },
1290
+ { key: "changed", label: "Changed" }
1291
+ ]
1292
+ );
1293
+ const detail = printTable(
1294
+ [
1295
+ {
1296
+ stableName: result.agent.stableName ?? "",
1297
+ platforms: (result.agent.supportedPlatforms ?? []).join(","),
1298
+ root: result.agent.homeRelativeRootPath ?? "",
1299
+ skills: result.agent.skillsDirectoryPath ?? "",
1300
+ enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
1301
+ }
1302
+ ],
1303
+ [
1304
+ { key: "stableName", label: "Name" },
1305
+ { key: "platforms", label: "Platforms" },
1306
+ { key: "root", label: "Root" },
1307
+ { key: "skills", label: "Skills Dir" },
1308
+ { key: "enabledByDefault", label: "Enabled By Default" }
1309
+ ]
1310
+ );
1311
+ return `${summary}${detail}`;
1312
+ }
1313
+ async function runConfigAddAgent(options) {
1314
+ const homeDir = options.homeDir ?? homedir3();
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, agent } = buildAgentOverride(options);
1320
+ const previous = config.agents[agentId];
1321
+ const changed = JSON.stringify(previous ?? null) !== JSON.stringify(agent);
1322
+ const nextConfig = {
1323
+ ...config,
1324
+ agents: {
1325
+ ...config.agents,
1326
+ [agentId]: agent
1327
+ }
1328
+ };
1329
+ await writeUserConfig(skillmuxHome, nextConfig);
1330
+ const resultWithoutOutput = {
1331
+ skillmuxHome,
1332
+ configPath,
1333
+ agentId,
1334
+ changed,
1335
+ agent,
1336
+ config: nextConfig
1337
+ };
1338
+ return {
1339
+ ...resultWithoutOutput,
1340
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput(resultWithoutOutput)
1341
+ };
1342
+ }
1343
+
1344
+ // src/commands/config-remove-agent.ts
1345
+ import { homedir as homedir4 } from "os";
1346
+ function normalizeAgentId2(value) {
1347
+ const trimmed = value.trim();
1348
+ if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
1349
+ throw new InvalidIdentifierError("agent id", value);
1350
+ }
1351
+ return normalizeId(trimmed);
1352
+ }
1353
+ function buildTableOutput2(result) {
1354
+ return printTable(
1355
+ [
1356
+ {
1357
+ agentId: result.agentId,
1358
+ configPath: result.configPath,
1359
+ changed: String(result.changed),
1360
+ removed: String(result.removed)
1361
+ }
1362
+ ],
1363
+ [
1364
+ { key: "agentId", label: "Agent" },
1365
+ { key: "configPath", label: "Config Path" },
1366
+ { key: "changed", label: "Changed" },
1367
+ { key: "removed", label: "Removed" }
1368
+ ]
1369
+ );
1370
+ }
1371
+ async function runConfigRemoveAgent(options) {
1372
+ const homeDir = options.homeDir ?? homedir4();
1373
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1374
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1375
+ const configPath = buildConfigPath(skillmuxHome);
1376
+ const config = await loadUserConfig(skillmuxHome);
1377
+ const agentId = normalizeAgentId2(options.id);
1378
+ const removed = agentId in config.agents;
1379
+ const removedOverride = config.agents[agentId];
1380
+ const wasAutoDiscovered = removedOverride?.autoDiscovered === true;
1381
+ const removedAutoAgentIds = wasAutoDiscovered ? [...config.removedAutoAgentIds ?? [], agentId] : config.removedAutoAgentIds;
1382
+ const nextConfig = {
1383
+ ...config,
1384
+ agents: Object.fromEntries(
1385
+ Object.entries(config.agents).filter(([currentAgentId]) => currentAgentId !== agentId)
1386
+ ),
1387
+ ...wasAutoDiscovered ? { removedAutoAgentIds } : {}
1388
+ };
1389
+ if (removed) {
1390
+ await writeUserConfig(skillmuxHome, nextConfig);
1391
+ }
1392
+ const resultWithoutOutput = {
1393
+ skillmuxHome,
1394
+ configPath,
1395
+ agentId,
1396
+ changed: removed,
1397
+ removed,
1398
+ config: nextConfig
1399
+ };
1400
+ return {
1401
+ ...resultWithoutOutput,
1402
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput2(resultWithoutOutput)
1403
+ };
1404
+ }
1405
+
1406
+ // src/commands/config-update-agent.ts
1407
+ import { homedir as homedir5 } from "os";
1408
+ function buildAgentPatch(options) {
1409
+ const patch = {};
1410
+ if (options.root !== void 0) {
1411
+ patch.homeRelativeRootPath = normalizeRelativePath(options.root, "root");
1412
+ }
1413
+ if (options.skills !== void 0) {
1414
+ patch.skillsDirectoryPath = normalizeRelativePath(options.skills, "skills");
1415
+ }
1416
+ if (options.name !== void 0 && options.name.trim().length > 0) {
1417
+ patch.stableName = options.name.trim();
1418
+ }
1419
+ if (options.platforms !== void 0) {
1420
+ patch.supportedPlatforms = normalizePlatforms(options.platforms);
1421
+ }
1422
+ if (options.enabledByDefault !== void 0 && options.disabledByDefault === true) {
1423
+ throw new UserConfigValidationError(
1424
+ "enabled-by-default and disabled-by-default cannot both be set"
1425
+ );
1426
+ }
1427
+ if (options.enabledByDefault !== void 0) {
1428
+ patch.enabledByDefault = options.enabledByDefault;
1429
+ }
1430
+ if (options.disabledByDefault === true) {
1431
+ patch.enabledByDefault = false;
1432
+ }
1433
+ return patch;
1434
+ }
1435
+ function buildTableOutput3(result) {
1436
+ const summary = printTable(
1437
+ [
1438
+ {
1439
+ agentId: result.agentId,
1440
+ configPath: result.configPath,
1441
+ changed: String(result.changed)
1442
+ }
1443
+ ],
1444
+ [
1445
+ { key: "agentId", label: "Agent" },
1446
+ { key: "configPath", label: "Config Path" },
1447
+ { key: "changed", label: "Changed" }
1448
+ ]
1449
+ );
1450
+ const detail = printTable(
1451
+ [
1452
+ {
1453
+ stableName: result.agent.stableName ?? "",
1454
+ platforms: (result.agent.supportedPlatforms ?? []).join(","),
1455
+ root: result.agent.homeRelativeRootPath ?? "",
1456
+ skills: result.agent.skillsDirectoryPath ?? "",
1457
+ enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
1458
+ }
1459
+ ],
1460
+ [
1461
+ { key: "stableName", label: "Name" },
1462
+ { key: "platforms", label: "Platforms" },
1463
+ { key: "root", label: "Root" },
1464
+ { key: "skills", label: "Skills Dir" },
1465
+ { key: "enabledByDefault", label: "Enabled By Default" }
1466
+ ]
1467
+ );
1468
+ return `${summary}${detail}`;
1469
+ }
1470
+ async function runConfigUpdateAgent(options) {
1471
+ const homeDir = options.homeDir ?? homedir5();
1472
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1473
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1474
+ const configPath = buildConfigPath(skillmuxHome);
1475
+ const config = await loadUserConfig(skillmuxHome);
1476
+ const agentId = normalizeAgentId(options.id);
1477
+ const previous = config.agents[agentId];
1478
+ if (previous === void 0) {
1479
+ throw new UserConfigValidationError(`Agent override does not exist: ${agentId}`);
1480
+ }
1481
+ const agent = {
1482
+ ...previous,
1483
+ ...buildAgentPatch(options)
1484
+ };
1485
+ if (previous.autoDiscovered === true) {
1486
+ delete agent.autoDiscovered;
1487
+ }
1488
+ const changed = JSON.stringify(previous) !== JSON.stringify(agent);
1489
+ const nextConfig = {
1490
+ ...config,
1491
+ agents: {
1492
+ ...config.agents,
1493
+ [agentId]: agent
1494
+ }
1495
+ };
1496
+ if (changed) {
1497
+ await writeUserConfig(skillmuxHome, nextConfig);
1498
+ }
1499
+ const resultWithoutOutput = {
1500
+ skillmuxHome,
1501
+ configPath,
1502
+ agentId,
1503
+ changed,
1504
+ agent,
1505
+ config: nextConfig
1506
+ };
1507
+ return {
1508
+ ...resultWithoutOutput,
1509
+ output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput3(resultWithoutOutput)
1510
+ };
1511
+ }
1512
+
1513
+ // src/commands/doctor.ts
1514
+ import { homedir as homedir6 } from "os";
1515
+
1516
+ // src/diagnostics/collect-doctor-issues.ts
1088
1517
  import * as fs11 from "fs/promises";
1089
- import { homedir as homedir2 } from "os";
1090
- import { join as join8, resolve as resolve9 } from "path";
1518
+ import { join as join9 } from "path";
1519
+ function buildIssue2(code, severity, message, path) {
1520
+ return path === void 0 ? { code, severity, message } : { code, severity, message, path };
1521
+ }
1522
+ async function pathExists2(path) {
1523
+ try {
1524
+ await fs11.access(path);
1525
+ return true;
1526
+ } catch (error) {
1527
+ if (error.code === "ENOENT") {
1528
+ return false;
1529
+ }
1530
+ throw error;
1531
+ }
1532
+ }
1533
+ async function addUnmanagedDirectoryIssues(entries, issues) {
1534
+ for (const entry of entries) {
1535
+ if (entry.kind !== "unmanaged-directory") {
1536
+ continue;
1537
+ }
1538
+ if (await pathExists2(join9(entry.path, "SKILL.md"))) {
1539
+ issues.push(
1540
+ buildIssue2(
1541
+ "unmanaged-skill-directory",
1542
+ "warning",
1543
+ `Unmanaged skill directory is present for ${entry.agentId}/${entry.skillName}`,
1544
+ entry.path
1545
+ )
1546
+ );
1547
+ }
1548
+ }
1549
+ }
1550
+ async function addMissingManagedSkillIssues(manifest, issues) {
1551
+ for (const skill of Object.values(manifest.skills)) {
1552
+ if (await pathExists2(skill.path)) {
1553
+ continue;
1554
+ }
1555
+ issues.push(
1556
+ buildIssue2(
1557
+ "missing-managed-skill-path",
1558
+ "error",
1559
+ `Managed skill path is missing for ${skill.id}`,
1560
+ skill.path
1561
+ )
1562
+ );
1563
+ }
1564
+ }
1565
+ function addConflictingAgentPathIssues(agents, issues) {
1566
+ const pathToAgents = /* @__PURE__ */ new Map();
1567
+ for (const agent of agents) {
1568
+ const key = normalizeAbsolutePath(agent.absoluteSkillsDirectoryPath);
1569
+ const current = pathToAgents.get(key) ?? [];
1570
+ current.push(agent);
1571
+ pathToAgents.set(key, current);
1572
+ }
1573
+ for (const conflictedAgents of pathToAgents.values()) {
1574
+ if (conflictedAgents.length < 2) {
1575
+ continue;
1576
+ }
1577
+ const agentIds = conflictedAgents.map((agent) => agent.id).sort((left, right) => left.localeCompare(right));
1578
+ issues.push(
1579
+ buildIssue2(
1580
+ "conflicting-agent-path",
1581
+ "warning",
1582
+ `Multiple agents resolve to the same skills directory: ${agentIds.join(", ")}`,
1583
+ conflictedAgents[0].absoluteSkillsDirectoryPath
1584
+ )
1585
+ );
1586
+ }
1587
+ }
1588
+ function issueSortKey(issue) {
1589
+ return `${issue.severity}:${issue.code}:${issue.path ?? ""}:${issue.message}`;
1590
+ }
1591
+ function sortIssues(issues) {
1592
+ return [...issues].sort(
1593
+ (left, right) => issueSortKey(left).localeCompare(issueSortKey(right))
1594
+ );
1595
+ }
1596
+ function dedupeAndSortIssues(issues) {
1597
+ const issueByKey = /* @__PURE__ */ new Map();
1598
+ for (const issue of issues) {
1599
+ issueByKey.set(issueSortKey(issue), issue);
1600
+ }
1601
+ return sortIssues([...issueByKey.values()]);
1602
+ }
1603
+ async function collectDoctorIssues(input) {
1604
+ const issues = [];
1605
+ await addUnmanagedDirectoryIssues(input.entries, issues);
1606
+ await addMissingManagedSkillIssues(input.manifest, issues);
1607
+ addConflictingAgentPathIssues(input.agents, issues);
1608
+ return dedupeAndSortIssues(issues);
1609
+ }
1610
+
1611
+ // src/commands/doctor.ts
1612
+ function buildTableOutput4(issues) {
1613
+ if (issues.length === 0) {
1614
+ return "No doctor issues found.\n";
1615
+ }
1616
+ return printTable(
1617
+ issues.map((issue) => ({
1618
+ severity: issue.severity,
1619
+ code: issue.code,
1620
+ path: issue.path ?? "",
1621
+ message: issue.message
1622
+ })),
1623
+ [
1624
+ { key: "severity", label: "Severity" },
1625
+ { key: "code", label: "Code" },
1626
+ { key: "path", label: "Path" },
1627
+ { key: "message", label: "Message" }
1628
+ ]
1629
+ );
1630
+ }
1631
+ function buildJsonOutput(result) {
1632
+ return printJson({
1633
+ skillmuxHome: result.skillmuxHome,
1634
+ issues: result.issues,
1635
+ agents: result.agents.map((agent) => ({
1636
+ id: agent.id,
1637
+ path: agent.absoluteSkillsDirectoryPath,
1638
+ supportedOnPlatform: agent.supportedOnPlatform
1639
+ })),
1640
+ entries: result.entries
1641
+ });
1642
+ }
1643
+ async function runDoctor(options = {}) {
1644
+ const homeDir = options.homeDir ?? homedir6();
1645
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1646
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1647
+ const [manifest, config, agents] = await Promise.all([
1648
+ readManifest(skillmuxHome),
1649
+ loadUserConfig(skillmuxHome),
1650
+ discoverAgents({
1651
+ homeDir,
1652
+ skillmuxHome,
1653
+ platform: options.platform
1654
+ })
1655
+ ]);
1656
+ const entries = [];
1657
+ const issues = [];
1658
+ for (const agent of agents) {
1659
+ const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
1660
+ entries.push(...scannedAgent.entries);
1661
+ issues.push(...scannedAgent.issues);
1662
+ }
1663
+ const doctorIssues = await collectDoctorIssues({
1664
+ manifest,
1665
+ agents,
1666
+ entries
1667
+ });
1668
+ const dedupedIssues = dedupeAndSortIssues([...issues, ...doctorIssues]);
1669
+ const resultWithoutOutput = {
1670
+ skillmuxHome,
1671
+ manifest,
1672
+ config,
1673
+ agents,
1674
+ entries,
1675
+ issues: dedupedIssues
1676
+ };
1677
+ return {
1678
+ ...resultWithoutOutput,
1679
+ output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildTableOutput4(dedupedIssues)
1680
+ };
1681
+ }
1682
+
1683
+ // src/commands/disable.ts
1684
+ import * as fs13 from "fs/promises";
1685
+ import { homedir as homedir7 } from "os";
1686
+ import { join as join10, resolve as resolve9 } from "path";
1091
1687
 
1092
1688
  // src/fs/safe-remove-link.ts
1093
- import * as fs10 from "fs/promises";
1689
+ import * as fs12 from "fs/promises";
1094
1690
  async function safeRemoveLink(path) {
1095
1691
  try {
1096
- const entry = await fs10.lstat(path);
1692
+ const entry = await fs12.lstat(path);
1097
1693
  if (!entry.isSymbolicLink()) {
1098
1694
  return false;
1099
1695
  }
1100
- await fs10.rm(path, { recursive: true, force: false });
1696
+ await fs12.rm(path, { recursive: true, force: false });
1101
1697
  return true;
1102
1698
  } catch (error) {
1103
1699
  if (error.code === "ENOENT") {
@@ -1142,7 +1738,7 @@ function buildManagedSkillPath2(skillmuxHome, skillId) {
1142
1738
  }
1143
1739
  async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName, linkPath, timestamp) {
1144
1740
  try {
1145
- const entry = await fs11.lstat(linkPath);
1741
+ const entry = await fs13.lstat(linkPath);
1146
1742
  if (!entry.isSymbolicLink()) {
1147
1743
  return void 0;
1148
1744
  }
@@ -1152,7 +1748,7 @@ async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName,
1152
1748
  }
1153
1749
  throw error;
1154
1750
  }
1155
- const sourcePath = await fs11.realpath(linkPath);
1751
+ const sourcePath = await fs13.realpath(linkPath);
1156
1752
  await assertSkillSourceLayout(sourcePath);
1157
1753
  const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
1158
1754
  await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
@@ -1181,9 +1777,9 @@ async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
1181
1777
  }
1182
1778
  return agent;
1183
1779
  }
1184
- async function pathExists2(path) {
1780
+ async function pathExists3(path) {
1185
1781
  try {
1186
- await fs11.lstat(path);
1782
+ await fs13.lstat(path);
1187
1783
  return true;
1188
1784
  } catch (error) {
1189
1785
  if (error.code === "ENOENT") {
@@ -1196,14 +1792,14 @@ async function runDisableSingle(options) {
1196
1792
  if (options.agent === void 0) {
1197
1793
  throw new Error("Disable requires one target agent");
1198
1794
  }
1199
- const homeDir = options.homeDir ?? homedir2();
1795
+ const homeDir = options.homeDir ?? homedir7();
1200
1796
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1201
1797
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1202
1798
  const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1203
1799
  const manifest = await readManifest(skillmuxHome);
1204
1800
  const skillId = normalizeId(options.skill);
1205
1801
  const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
1206
- const linkPath = join8(agent.absoluteSkillsDirectoryPath, skillId);
1802
+ const linkPath = join10(agent.absoluteSkillsDirectoryPath, skillId);
1207
1803
  const adoption = manifest.skills[skillId] ? void 0 : await tryAdoptManagedSkill(
1208
1804
  manifest,
1209
1805
  skillmuxHome,
@@ -1224,7 +1820,7 @@ async function runDisableSingle(options) {
1224
1820
  manifest.agents[agent.id] = agentRecord;
1225
1821
  const adoptedLinkRemoved = adoption !== void 0 ? await safeRemoveLink(linkPath) : false;
1226
1822
  const linkMatchesSkill = adoption === void 0 ? await isLinkPointingToTarget(activationLinkPath, skill.path) : false;
1227
- if (adoption === void 0 && !linkMatchesSkill && await pathExists2(activationLinkPath)) {
1823
+ if (adoption === void 0 && !linkMatchesSkill && await pathExists3(activationLinkPath)) {
1228
1824
  throw new Error(`Refusing to disable non-managed entry at ${linkPath}`);
1229
1825
  }
1230
1826
  const removedLink = adoptedLinkRemoved ? true : linkMatchesSkill ? await safeRemoveLink(activationLinkPath) : false;
@@ -1286,9 +1882,9 @@ async function runDisable(options) {
1286
1882
  }
1287
1883
 
1288
1884
  // src/commands/enable.ts
1289
- import * as fs12 from "fs/promises";
1290
- import { homedir as homedir3 } from "os";
1291
- import { join as join9 } from "path";
1885
+ import * as fs14 from "fs/promises";
1886
+ import { homedir as homedir8 } from "os";
1887
+ import { join as join11 } from "path";
1292
1888
  function buildAgentRecord3(agent, timestamp) {
1293
1889
  return {
1294
1890
  id: agent.id,
@@ -1334,7 +1930,7 @@ async function runEnableSingle(options) {
1334
1930
  if (options.agent === void 0) {
1335
1931
  throw new Error("Enable requires one target agent");
1336
1932
  }
1337
- const homeDir = options.homeDir ?? homedir3();
1933
+ const homeDir = options.homeDir ?? homedir8();
1338
1934
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1339
1935
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1340
1936
  const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
@@ -1345,13 +1941,13 @@ async function runEnableSingle(options) {
1345
1941
  throw new Error(`Managed skill not found: ${skillId}`);
1346
1942
  }
1347
1943
  const agent = await resolveTargetAgent3(homeDir, skillmuxHome, options.agent);
1348
- const linkPath = join9(agent.absoluteSkillsDirectoryPath, skill.id);
1944
+ const linkPath = join11(agent.absoluteSkillsDirectoryPath, skill.id);
1349
1945
  const currentActivation = manifest.activations.find(
1350
1946
  (entry) => entry.skillId === skill.id && entry.agentId === agent.id
1351
1947
  );
1352
1948
  const agentRecord = buildAgentRecord3(agent, timestamp);
1353
1949
  manifest.agents[agent.id] = agentRecord;
1354
- await fs12.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
1950
+ await fs14.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
1355
1951
  const linkAlreadyEnabled = await isLinkPointingToTarget(linkPath, skill.path);
1356
1952
  const activationAlreadyEnabled = currentActivation?.state === "enabled" && currentActivation.linkPath === linkPath;
1357
1953
  if (linkAlreadyEnabled && activationAlreadyEnabled) {
@@ -1418,8 +2014,48 @@ async function runEnable(options) {
1418
2014
  return runEnableSingle(options);
1419
2015
  }
1420
2016
 
2017
+ // src/commands/import.ts
2018
+ import { resolve as resolve10 } from "path";
2019
+ import { homedir as homedir9 } from "os";
2020
+ function buildManagedSkillPath3(skillmuxHome, skillId) {
2021
+ return resolve10(skillmuxHome, "skills", skillId);
2022
+ }
2023
+ async function runImport(options) {
2024
+ const homeDir = options.homeDir ?? homedir9();
2025
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
2026
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
2027
+ const sourcePath = resolve10(options.sourcePath);
2028
+ const skillId = normalizeId(options.skillName);
2029
+ const importedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
2030
+ const manifest = await readManifest(skillmuxHome);
2031
+ const managedSkillPath = buildManagedSkillPath3(skillmuxHome, skillId);
2032
+ await assertSkillSourceLayout(sourcePath);
2033
+ if (manifest.skills[skillId] !== void 0) {
2034
+ throw new Error(`Managed skill already exists for ${skillId}`);
2035
+ }
2036
+ await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
2037
+ const skill = {
2038
+ id: skillId,
2039
+ name: options.skillName,
2040
+ path: managedSkillPath,
2041
+ source: {
2042
+ kind: "local",
2043
+ path: sourcePath
2044
+ },
2045
+ importedAt
2046
+ };
2047
+ manifest.skills[skillId] = skill;
2048
+ await writeManifest(skillmuxHome, manifest);
2049
+ return {
2050
+ skill,
2051
+ manifest,
2052
+ output: `Imported ${skillId} to ${managedSkillPath}
2053
+ `
2054
+ };
2055
+ }
2056
+
1421
2057
  // src/commands/scan.ts
1422
- import { homedir as homedir4 } from "os";
2058
+ import { homedir as homedir10 } from "os";
1423
2059
 
1424
2060
  // src/output/format-issue.ts
1425
2061
  function formatIssue(issue) {
@@ -1429,26 +2065,6 @@ function formatIssue(issue) {
1429
2065
  return `[${issue.severity}] ${issue.code} @ ${issue.path}: ${issue.message}`;
1430
2066
  }
1431
2067
 
1432
- // src/output/print-table.ts
1433
- function printTable(rows, columns) {
1434
- const renderedRows = rows.map(
1435
- (row) => columns.map((column) => String(row[column.key] ?? ""))
1436
- );
1437
- const widths = columns.map(
1438
- (column, index) => Math.max(
1439
- column.label.length,
1440
- ...renderedRows.map((row) => row[index]?.length ?? 0)
1441
- )
1442
- );
1443
- const header = columns.map((column, index) => column.label.padEnd(widths[index])).join(" ");
1444
- const separator = widths.map((width) => "-".repeat(width)).join(" ");
1445
- const body = renderedRows.map(
1446
- (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ")
1447
- );
1448
- return `${[header, separator, ...body].join("\n")}
1449
- `;
1450
- }
1451
-
1452
2068
  // src/commands/scan.ts
1453
2069
  function buildAgentRecord4(agent, timestamp, previousRecord) {
1454
2070
  const lastSeenAt = agent.exists ? timestamp : previousRecord?.lastSeenAt ?? null;
@@ -1500,7 +2116,7 @@ ${result.issues.map(formatIssue).join("\n")}
1500
2116
  `;
1501
2117
  }
1502
2118
  async function runScan(options = {}) {
1503
- const homeDir = options.homeDir ?? homedir4();
2119
+ const homeDir = options.homeDir ?? homedir10();
1504
2120
  const resolvedPaths = resolveSkillmuxHome(homeDir);
1505
2121
  const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1506
2122
  const manifest = await readManifest(skillmuxHome);
@@ -1540,17 +2156,17 @@ async function runScan(options = {}) {
1540
2156
  }
1541
2157
 
1542
2158
  // src/commands/remove.ts
1543
- import * as fs13 from "fs/promises";
1544
- import { homedir as homedir5 } from "os";
1545
- import { resolve as resolve10 } from "path";
1546
- function buildManagedSkillPath3(skillmuxHome, skillId) {
1547
- return resolve10(skillmuxHome, "skills", skillId);
2159
+ import * as fs15 from "fs/promises";
2160
+ import { homedir as homedir11 } from "os";
2161
+ import { resolve as resolve11 } from "path";
2162
+ function buildManagedSkillPath4(skillmuxHome, skillId) {
2163
+ return resolve11(skillmuxHome, "skills", skillId);
1548
2164
  }
1549
2165
  function buildManifestPath(skillmuxHome) {
1550
- return resolve10(skillmuxHome, "manifest.json");
2166
+ return resolve11(skillmuxHome, "manifest.json");
1551
2167
  }
1552
2168
  function buildConfigPath2(skillmuxHome) {
1553
- return resolve10(skillmuxHome, "config.json");
2169
+ return resolve11(skillmuxHome, "config.json");
1554
2170
  }
1555
2171
  function resolveManagedSkill(manifest, skillNameOrId) {
1556
2172
  const skillId = normalizeId(skillNameOrId);
@@ -1576,7 +2192,7 @@ function buildHumanOutput(skill, managedSkillPath) {
1576
2192
  return `Removed ${skill.id} from ${managedSkillPath}
1577
2193
  `;
1578
2194
  }
1579
- function buildJsonOutput(result) {
2195
+ function buildJsonOutput2(result) {
1580
2196
  return printJson({
1581
2197
  changed: result.changed,
1582
2198
  removedSkillId: result.removedSkillId,
@@ -1585,9 +2201,9 @@ function buildJsonOutput(result) {
1585
2201
  manifest: result.manifest
1586
2202
  });
1587
2203
  }
1588
- async function pathExists3(path) {
2204
+ async function pathExists4(path) {
1589
2205
  try {
1590
- await fs13.lstat(path);
2206
+ await fs15.lstat(path);
1591
2207
  return true;
1592
2208
  } catch (error) {
1593
2209
  if (error.code === "ENOENT") {
@@ -1597,15 +2213,15 @@ async function pathExists3(path) {
1597
2213
  }
1598
2214
  }
1599
2215
  async function assertManagedSkillRemovalSafety(skillmuxHome, skillPath, skillId) {
1600
- const expectedSkillPath = buildManagedSkillPath3(skillmuxHome, skillId);
2216
+ const expectedSkillPath = buildManagedSkillPath4(skillmuxHome, skillId);
1601
2217
  if (!pathsAreEqual(skillPath, expectedSkillPath)) {
1602
2218
  throw new Error(`Refusing to remove unmanaged skill path at ${skillPath}`);
1603
2219
  }
1604
2220
  await assertNoSymlinkAncestors(skillPath, { includeLeaf: true });
1605
- if (!await pathExists3(skillPath)) {
2221
+ if (!await pathExists4(skillPath)) {
1606
2222
  return;
1607
2223
  }
1608
- const entry = await fs13.lstat(skillPath);
2224
+ const entry = await fs15.lstat(skillPath);
1609
2225
  if (entry.isSymbolicLink()) {
1610
2226
  throw new Error(`Refusing to remove symlinked managed skill path at ${skillPath}`);
1611
2227
  }
@@ -1617,14 +2233,14 @@ async function runRemoveSingle(options) {
1617
2233
  if (options.skill === void 0) {
1618
2234
  throw new Error("Remove requires one target skill");
1619
2235
  }
1620
- const homeDir = options.homeDir ?? homedir5();
2236
+ const homeDir = options.homeDir ?? homedir11();
1621
2237
  const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1622
2238
  const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1623
2239
  const manifestPath = buildManifestPath(skillmuxHome);
1624
2240
  const configPath = buildConfigPath2(skillmuxHome);
1625
2241
  const manifest = await readManifest(skillmuxHome);
1626
2242
  const skill = resolveManagedSkill(manifest, options.skill);
1627
- const managedSkillsDirectory = resolve10(skillmuxHome, "skills");
2243
+ const managedSkillsDirectory = resolve11(skillmuxHome, "skills");
1628
2244
  const managedSkillPath = skill.path;
1629
2245
  const enabledActivations = manifest.activations.filter(
1630
2246
  (activation) => activation.skillId === skill.id && activation.state === "enabled"
@@ -1638,8 +2254,8 @@ async function runRemoveSingle(options) {
1638
2254
  );
1639
2255
  }
1640
2256
  await assertManagedSkillRemovalSafety(skillmuxHome, managedSkillPath, skill.id);
1641
- if (await pathExists3(managedSkillPath)) {
1642
- await fs13.rm(managedSkillPath, { recursive: true, force: false });
2257
+ if (await pathExists4(managedSkillPath)) {
2258
+ await fs15.rm(managedSkillPath, { recursive: true, force: false });
1643
2259
  }
1644
2260
  delete manifest.skills[skill.id];
1645
2261
  manifest.activations = manifest.activations.filter(
@@ -1661,7 +2277,7 @@ async function runRemoveSingle(options) {
1661
2277
  };
1662
2278
  return {
1663
2279
  ...resultWithoutOutput,
1664
- output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildHumanOutput(skill, managedSkillPath)
2280
+ output: options.json === true ? buildJsonOutput2(resultWithoutOutput) : buildHumanOutput(skill, managedSkillPath)
1665
2281
  };
1666
2282
  }
1667
2283
  async function runRemove(options) {
@@ -1699,128 +2315,32 @@ async function runRemove(options) {
1699
2315
  return runRemoveSingle(options);
1700
2316
  }
1701
2317
 
1702
- // src/diagnostics/collect-doctor-issues.ts
1703
- import * as fs14 from "fs/promises";
1704
- import { join as join10 } from "path";
1705
- function buildIssue2(code, severity, message, path) {
1706
- return path === void 0 ? { code, severity, message } : { code, severity, message, path };
1707
- }
1708
- async function pathExists4(path) {
1709
- try {
1710
- await fs14.access(path);
1711
- return true;
1712
- } catch (error) {
1713
- if (error.code === "ENOENT") {
1714
- return false;
1715
- }
1716
- throw error;
1717
- }
1718
- }
1719
- async function addUnmanagedDirectoryIssues(entries, issues) {
1720
- for (const entry of entries) {
1721
- if (entry.kind !== "unmanaged-directory") {
1722
- continue;
1723
- }
1724
- if (await pathExists4(join10(entry.path, "SKILL.md"))) {
1725
- issues.push(
1726
- buildIssue2(
1727
- "unmanaged-skill-directory",
1728
- "warning",
1729
- `Unmanaged skill directory is present for ${entry.agentId}/${entry.skillName}`,
1730
- entry.path
1731
- )
1732
- );
1733
- }
1734
- }
1735
- }
1736
- async function addMissingManagedSkillIssues(manifest, issues) {
1737
- for (const skill of Object.values(manifest.skills)) {
1738
- if (await pathExists4(skill.path)) {
1739
- continue;
1740
- }
1741
- issues.push(
1742
- buildIssue2(
1743
- "missing-managed-skill-path",
1744
- "error",
1745
- `Managed skill path is missing for ${skill.id}`,
1746
- skill.path
1747
- )
1748
- );
1749
- }
1750
- }
1751
- function addConflictingAgentPathIssues(agents, issues) {
1752
- const pathToAgents = /* @__PURE__ */ new Map();
1753
- for (const agent of agents) {
1754
- const key = normalizeAbsolutePath(agent.absoluteSkillsDirectoryPath);
1755
- const current = pathToAgents.get(key) ?? [];
1756
- current.push(agent);
1757
- pathToAgents.set(key, current);
1758
- }
1759
- for (const conflictedAgents of pathToAgents.values()) {
1760
- if (conflictedAgents.length < 2) {
1761
- continue;
1762
- }
1763
- const agentIds = conflictedAgents.map((agent) => agent.id).sort((left, right) => left.localeCompare(right));
1764
- issues.push(
1765
- buildIssue2(
1766
- "conflicting-agent-path",
1767
- "warning",
1768
- `Multiple agents resolve to the same skills directory: ${agentIds.join(", ")}`,
1769
- conflictedAgents[0].absoluteSkillsDirectoryPath
1770
- )
1771
- );
1772
- }
1773
- }
1774
- function issueSortKey(issue) {
1775
- return `${issue.severity}:${issue.code}:${issue.path ?? ""}:${issue.message}`;
1776
- }
1777
- function sortIssues(issues) {
1778
- return [...issues].sort(
1779
- (left, right) => issueSortKey(left).localeCompare(issueSortKey(right))
1780
- );
1781
- }
1782
- function dedupeAndSortIssues(issues) {
1783
- const issueByKey = /* @__PURE__ */ new Map();
1784
- for (const issue of issues) {
1785
- issueByKey.set(issueSortKey(issue), issue);
1786
- }
1787
- return sortIssues([...issueByKey.values()]);
1788
- }
1789
- async function collectDoctorIssues(input) {
1790
- const issues = [];
1791
- await addUnmanagedDirectoryIssues(input.entries, issues);
1792
- await addMissingManagedSkillIssues(input.manifest, issues);
1793
- addConflictingAgentPathIssues(input.agents, issues);
1794
- return dedupeAndSortIssues(issues);
1795
- }
1796
-
1797
2318
  export {
1798
2319
  supportedPlatforms,
1799
- InvalidIdentifierError,
1800
2320
  ManifestValidationError,
1801
- UserConfigValidationError,
1802
- normalizeId,
1803
2321
  buildConfigPath,
1804
2322
  resolveSkillmuxHome,
1805
2323
  loadUserConfig,
1806
2324
  discoverAgents,
1807
2325
  isPathInside,
1808
2326
  scanAgentSkills,
1809
- assertSkillSourceLayout,
1810
- copySkillContentsToManagedStore,
1811
2327
  buildEmptyManifest,
1812
2328
  manifestSchema,
1813
- writeManifest,
1814
2329
  formatValidationIssues2 as formatValidationIssues,
1815
- readManifest,
1816
2330
  printJson,
1817
2331
  runAdopt,
1818
2332
  printTable,
2333
+ normalizeAgentId,
2334
+ runConfigAddAgent,
2335
+ runConfigRemoveAgent,
2336
+ runConfigUpdateAgent,
1819
2337
  dedupeAndSortIssues,
1820
2338
  collectDoctorIssues,
2339
+ runDoctor,
1821
2340
  runDisable,
1822
2341
  runEnable,
2342
+ runImport,
1823
2343
  runScan,
1824
2344
  runRemove
1825
2345
  };
1826
- //# sourceMappingURL=chunk-DBEVDI27.js.map
2346
+ //# sourceMappingURL=chunk-OY3C7VIL.js.map