skillmux 0.1.2 → 0.1.3

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.
@@ -0,0 +1,1826 @@
1
+ // src/config/default-agent-rules.ts
2
+ var supportedPlatforms = ["win32", "linux", "darwin"];
3
+ var builtInAgentIds = [
4
+ "codex",
5
+ "claude",
6
+ "gemini",
7
+ "agents",
8
+ "openclaw"
9
+ ];
10
+ var defaultAgentRules = [
11
+ {
12
+ id: "codex",
13
+ stableName: "OpenAI Codex",
14
+ supportedPlatforms: [...supportedPlatforms],
15
+ homeRelativeRootPath: ".codex",
16
+ skillsDirectoryPath: "skills",
17
+ enabledByDefault: true,
18
+ discovery: "builtin"
19
+ },
20
+ {
21
+ id: "claude",
22
+ stableName: "Claude Code",
23
+ supportedPlatforms: [...supportedPlatforms],
24
+ homeRelativeRootPath: ".claude",
25
+ skillsDirectoryPath: "skills",
26
+ enabledByDefault: true,
27
+ discovery: "builtin"
28
+ },
29
+ {
30
+ id: "gemini",
31
+ stableName: "Gemini CLI",
32
+ supportedPlatforms: [...supportedPlatforms],
33
+ homeRelativeRootPath: ".gemini",
34
+ skillsDirectoryPath: "skills",
35
+ enabledByDefault: true,
36
+ discovery: "builtin"
37
+ },
38
+ {
39
+ id: "agents",
40
+ stableName: "Agents",
41
+ supportedPlatforms: [...supportedPlatforms],
42
+ homeRelativeRootPath: ".agents",
43
+ skillsDirectoryPath: "skills",
44
+ enabledByDefault: true,
45
+ discovery: "builtin"
46
+ },
47
+ {
48
+ id: "openclaw",
49
+ stableName: "OpenClaw",
50
+ supportedPlatforms: [...supportedPlatforms],
51
+ homeRelativeRootPath: ".openclaw",
52
+ skillsDirectoryPath: "skills",
53
+ enabledByDefault: true,
54
+ discovery: "builtin"
55
+ }
56
+ ];
57
+ var defaultAgentRuleMap = Object.fromEntries(
58
+ defaultAgentRules.map((rule) => [rule.id, rule])
59
+ );
60
+
61
+ // src/commands/adopt.ts
62
+ import { homedir } from "os";
63
+ import { join as join7, resolve as resolve8 } from "path";
64
+
65
+ // src/core/errors.ts
66
+ var SkillMuxError = class extends Error {
67
+ constructor(message) {
68
+ super(message);
69
+ this.name = new.target.name;
70
+ }
71
+ };
72
+ var InvalidIdentifierError = class extends SkillMuxError {
73
+ constructor(kind, value) {
74
+ super(`Invalid ${kind}: ${value}`);
75
+ this.kind = kind;
76
+ this.value = value;
77
+ }
78
+ kind;
79
+ value;
80
+ };
81
+ var ManifestValidationError = class extends SkillMuxError {
82
+ constructor(message) {
83
+ super(message);
84
+ }
85
+ };
86
+ var UserConfigValidationError = class extends SkillMuxError {
87
+ constructor(message) {
88
+ super(message);
89
+ }
90
+ };
91
+ var AdoptionError = class extends SkillMuxError {
92
+ constructor(message) {
93
+ super(message);
94
+ }
95
+ };
96
+
97
+ // src/core/ids.ts
98
+ var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
99
+ function normalizeId(value) {
100
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
101
+ return normalized.length > 0 ? normalized : "skill";
102
+ }
103
+ function isValidId(value) {
104
+ return ID_PATTERN.test(value);
105
+ }
106
+
107
+ // src/config/resolve-skillmux-home.ts
108
+ import { join, resolve } from "path";
109
+ function buildConfigPath(skillmuxHome) {
110
+ return join(resolve(skillmuxHome), "config.json");
111
+ }
112
+ function resolveSkillmuxHome(homeDir) {
113
+ const resolvedHomeDir = resolve(homeDir);
114
+ const skillmuxHome = join(resolvedHomeDir, ".skillmux");
115
+ return {
116
+ skillmuxHome,
117
+ configPath: buildConfigPath(skillmuxHome)
118
+ };
119
+ }
120
+
121
+ // src/discovery/discover-agents.ts
122
+ import * as fs2 from "fs/promises";
123
+ import { join as join2, resolve as resolve2 } from "path";
124
+
125
+ // src/config/load-user-config.ts
126
+ import * as fs from "fs/promises";
127
+ import { z } from "zod";
128
+ var supportedPlatformSchema = z.enum(supportedPlatforms);
129
+ var agentOverrideSchema = z.object({
130
+ stableName: z.string().min(1).optional(),
131
+ supportedPlatforms: z.array(supportedPlatformSchema).min(1).optional(),
132
+ homeRelativeRootPath: z.string().min(1).optional(),
133
+ skillsDirectoryPath: z.string().min(1).optional(),
134
+ enabledByDefault: z.boolean().optional()
135
+ }).strict();
136
+ var userConfigSchema = z.object({
137
+ version: z.literal(1),
138
+ agents: z.record(z.string().min(1), agentOverrideSchema)
139
+ }).strict();
140
+ function createEmptyUserConfig() {
141
+ return {
142
+ version: 1,
143
+ agents: {}
144
+ };
145
+ }
146
+ function stripUtf8Bom(contents) {
147
+ return contents.charCodeAt(0) === 65279 ? contents.slice(1) : contents;
148
+ }
149
+ function formatValidationIssues(error) {
150
+ return error.issues.map((issue) => {
151
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
152
+ return `${path}: ${issue.message}`;
153
+ }).join("; ");
154
+ }
155
+ async function loadUserConfig(skillmuxHome) {
156
+ const configPath = buildConfigPath(skillmuxHome);
157
+ try {
158
+ const contents = await fs.readFile(configPath, "utf8");
159
+ const parsed = JSON.parse(stripUtf8Bom(contents));
160
+ const validated = userConfigSchema.safeParse(parsed);
161
+ if (!validated.success) {
162
+ throw new UserConfigValidationError(
163
+ `Invalid config at ${configPath}: ${formatValidationIssues(validated.error)}`
164
+ );
165
+ }
166
+ return validated.data;
167
+ } catch (error) {
168
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
169
+ return createEmptyUserConfig();
170
+ }
171
+ if (error instanceof SyntaxError) {
172
+ throw new UserConfigValidationError(
173
+ `Invalid config at ${configPath}: malformed JSON`
174
+ );
175
+ }
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ // src/discovery/discover-agents.ts
181
+ function mergeRule(rule, override) {
182
+ if (override === void 0) {
183
+ return rule;
184
+ }
185
+ return {
186
+ ...rule,
187
+ ...override
188
+ };
189
+ }
190
+ function buildCustomRule(id, override) {
191
+ if (override.homeRelativeRootPath === void 0 || override.skillsDirectoryPath === void 0) {
192
+ throw new Error(
193
+ `Custom agent override "${id}" must define homeRelativeRootPath and skillsDirectoryPath`
194
+ );
195
+ }
196
+ return {
197
+ id,
198
+ stableName: override.stableName ?? id,
199
+ supportedPlatforms: override.supportedPlatforms ?? [...supportedPlatforms],
200
+ homeRelativeRootPath: override.homeRelativeRootPath,
201
+ skillsDirectoryPath: override.skillsDirectoryPath,
202
+ enabledByDefault: override.enabledByDefault ?? true,
203
+ discovery: "custom"
204
+ };
205
+ }
206
+ async function pathExists(path) {
207
+ try {
208
+ await fs2.access(path);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+ function resolveAgentRulePaths(homeDir, rule) {
215
+ const absoluteRootPath = resolve2(homeDir, rule.homeRelativeRootPath);
216
+ return {
217
+ absoluteRootPath,
218
+ absoluteSkillsDirectoryPath: join2(
219
+ absoluteRootPath,
220
+ rule.skillsDirectoryPath
221
+ )
222
+ };
223
+ }
224
+ async function discoverAgents(options) {
225
+ const platform = options.platform ?? process.platform;
226
+ const homeDir = resolve2(options.homeDir);
227
+ const skillmuxHome = options.skillmuxHome ?? resolveSkillmuxHome(homeDir).skillmuxHome;
228
+ const userConfig = await loadUserConfig(skillmuxHome);
229
+ const discoveredAgents = [];
230
+ for (const agentId of builtInAgentIds) {
231
+ const mergedRule = mergeRule(
232
+ defaultAgentRuleMap[agentId],
233
+ userConfig.agents[agentId]
234
+ );
235
+ const resolvedPaths = resolveAgentRulePaths(homeDir, mergedRule);
236
+ discoveredAgents.push({
237
+ ...mergedRule,
238
+ ...resolvedPaths,
239
+ exists: await pathExists(resolvedPaths.absoluteSkillsDirectoryPath),
240
+ supportedOnPlatform: mergedRule.supportedPlatforms.some(
241
+ (supportedPlatform) => supportedPlatform === platform
242
+ )
243
+ });
244
+ }
245
+ for (const [agentId, override] of Object.entries(userConfig.agents)) {
246
+ if (Object.hasOwn(defaultAgentRuleMap, agentId)) {
247
+ continue;
248
+ }
249
+ const customRule = buildCustomRule(agentId, override);
250
+ const resolvedPaths = resolveAgentRulePaths(homeDir, customRule);
251
+ discoveredAgents.push({
252
+ ...customRule,
253
+ ...resolvedPaths,
254
+ exists: await pathExists(resolvedPaths.absoluteSkillsDirectoryPath),
255
+ supportedOnPlatform: customRule.supportedPlatforms.some(
256
+ (supportedPlatform) => supportedPlatform === platform
257
+ )
258
+ });
259
+ }
260
+ return discoveredAgents;
261
+ }
262
+
263
+ // src/discovery/scan-agent-skills.ts
264
+ import * as fs5 from "fs/promises";
265
+ import { join as join3 } from "path";
266
+
267
+ // src/discovery/infer-skill-entry.ts
268
+ import * as fs4 from "fs/promises";
269
+ import { basename, resolve as resolve4 } from "path";
270
+
271
+ // src/fs/path-utils.ts
272
+ import * as fs3 from "fs/promises";
273
+ import { dirname, parse, relative, resolve as resolve3, sep } from "path";
274
+ function normalizeAbsolutePath(path) {
275
+ const normalized = resolve3(path);
276
+ return process.platform === "win32" ? normalized.replaceAll("/", "\\").toLowerCase() : normalized;
277
+ }
278
+ function pathsAreEqual(left, right) {
279
+ return normalizeAbsolutePath(left) === normalizeAbsolutePath(right);
280
+ }
281
+ function isPathInside(parentPath, childPath) {
282
+ const parent = normalizeAbsolutePath(parentPath);
283
+ const child = normalizeAbsolutePath(childPath);
284
+ if (parse(parent).root !== parse(child).root) {
285
+ return false;
286
+ }
287
+ const relativePath = relative(parent, child);
288
+ if (relativePath === "") {
289
+ return true;
290
+ }
291
+ if (relativePath === "..") {
292
+ return false;
293
+ }
294
+ if (relativePath.startsWith(`..${sep}`)) {
295
+ return false;
296
+ }
297
+ return true;
298
+ }
299
+ async function assertNoSymlinkAncestors(path, options) {
300
+ let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
301
+ while (true) {
302
+ try {
303
+ const entry = await fs3.lstat(current);
304
+ if (entry.isSymbolicLink()) {
305
+ throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
306
+ }
307
+ } catch (error) {
308
+ if (error.code !== "ENOENT") {
309
+ throw error;
310
+ }
311
+ }
312
+ const parent = dirname(current);
313
+ if (parent === current) {
314
+ return;
315
+ }
316
+ current = parent;
317
+ }
318
+ }
319
+
320
+ // src/discovery/infer-skill-entry.ts
321
+ function buildIssue(code, severity, message, path) {
322
+ return { code, severity, message, path };
323
+ }
324
+ async function inferSkillEntry(options) {
325
+ const absolutePath = resolve4(options.path);
326
+ const skillName = basename(absolutePath);
327
+ const stats = await fs4.lstat(absolutePath);
328
+ if (stats.isSymbolicLink()) {
329
+ try {
330
+ const targetPath = await fs4.realpath(absolutePath);
331
+ if (isPathInside(options.skillmuxHome, targetPath)) {
332
+ return {
333
+ entry: {
334
+ agentId: options.agentId,
335
+ agentName: options.agentName,
336
+ skillName,
337
+ kind: "managed-link",
338
+ path: absolutePath,
339
+ targetPath
340
+ }
341
+ };
342
+ }
343
+ return {
344
+ entry: {
345
+ agentId: options.agentId,
346
+ agentName: options.agentName,
347
+ skillName,
348
+ kind: "unmanaged-link",
349
+ path: absolutePath,
350
+ targetPath
351
+ }
352
+ };
353
+ } catch (error) {
354
+ if (error.code !== "ENOENT") {
355
+ throw error;
356
+ }
357
+ return {
358
+ entry: {
359
+ agentId: options.agentId,
360
+ agentName: options.agentName,
361
+ skillName,
362
+ kind: "broken-link",
363
+ path: absolutePath
364
+ },
365
+ issue: buildIssue(
366
+ "broken-link",
367
+ "error",
368
+ "Skill entry points to a missing target",
369
+ absolutePath
370
+ )
371
+ };
372
+ }
373
+ }
374
+ if (stats.isDirectory()) {
375
+ return {
376
+ entry: {
377
+ agentId: options.agentId,
378
+ agentName: options.agentName,
379
+ skillName,
380
+ kind: "unmanaged-directory",
381
+ path: absolutePath
382
+ }
383
+ };
384
+ }
385
+ return {
386
+ entry: {
387
+ agentId: options.agentId,
388
+ agentName: options.agentName,
389
+ skillName,
390
+ kind: "unknown",
391
+ path: absolutePath
392
+ },
393
+ issue: buildIssue(
394
+ "unknown-entry",
395
+ "warning",
396
+ "Skill entry is neither a managed link nor a directory",
397
+ absolutePath
398
+ )
399
+ };
400
+ }
401
+
402
+ // src/discovery/scan-agent-skills.ts
403
+ async function scanAgentSkills(agent, skillmuxHome) {
404
+ if (!agent.exists || !agent.supportedOnPlatform) {
405
+ return {
406
+ entries: [],
407
+ issues: []
408
+ };
409
+ }
410
+ const directoryEntries = await fs5.readdir(agent.absoluteSkillsDirectoryPath, {
411
+ withFileTypes: true
412
+ });
413
+ const sortedDirectoryEntries = [...directoryEntries].sort(
414
+ (left, right) => left.name.localeCompare(right.name)
415
+ );
416
+ const entries = [];
417
+ const issues = [];
418
+ for (const directoryEntry of sortedDirectoryEntries) {
419
+ const result = await inferSkillEntry({
420
+ agentId: agent.id,
421
+ agentName: agent.stableName,
422
+ path: join3(agent.absoluteSkillsDirectoryPath, directoryEntry.name),
423
+ skillmuxHome
424
+ });
425
+ entries.push(result.entry);
426
+ if (result.issue !== void 0) {
427
+ issues.push(result.issue);
428
+ }
429
+ }
430
+ return {
431
+ entries,
432
+ issues
433
+ };
434
+ }
435
+
436
+ // 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";
439
+ async function assertDirectory(path) {
440
+ const entry = await fs6.lstat(path);
441
+ if (!entry.isDirectory()) {
442
+ throw new Error(`Expected a directory at ${path}`);
443
+ }
444
+ }
445
+ async function assertRegularFile(path, label) {
446
+ const entry = await fs6.lstat(path);
447
+ if (!entry.isFile()) {
448
+ throw new Error(`Expected ${label} to be a regular file at ${path}`);
449
+ }
450
+ }
451
+ async function assertTargetDoesNotExist(path) {
452
+ try {
453
+ await fs6.lstat(path);
454
+ throw new Error(`Refusing to overwrite existing path at ${path}`);
455
+ } catch (error) {
456
+ if (error.code !== "ENOENT") {
457
+ throw error;
458
+ }
459
+ }
460
+ }
461
+ async function copyDirectoryContents(sourcePath, targetPath) {
462
+ await fs6.mkdir(targetPath, { recursive: true });
463
+ const entries = await fs6.readdir(sourcePath, { withFileTypes: true });
464
+ 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);
468
+ if (entryStats.isSymbolicLink()) {
469
+ throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
470
+ }
471
+ if (entryStats.isDirectory()) {
472
+ await copyDirectoryContents(sourceEntryPath, targetEntryPath);
473
+ continue;
474
+ }
475
+ if (entryStats.isFile()) {
476
+ await fs6.mkdir(dirname2(targetEntryPath), { recursive: true });
477
+ await fs6.copyFile(sourceEntryPath, targetEntryPath);
478
+ continue;
479
+ }
480
+ throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
481
+ }
482
+ }
483
+ async function assertSkillSourceLayout(sourcePath) {
484
+ const resolvedSourcePath = resolve5(sourcePath);
485
+ const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
486
+ await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
487
+ await assertDirectory(resolvedSourcePath);
488
+ try {
489
+ await assertRegularFile(skillFilePath, "SKILL.md");
490
+ } catch (error) {
491
+ if (error.code === "ENOENT") {
492
+ throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
493
+ }
494
+ throw error;
495
+ }
496
+ }
497
+ async function hasRootSkillFile(sourcePath) {
498
+ const resolvedSourcePath = resolve5(sourcePath);
499
+ const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
500
+ try {
501
+ await assertDirectory(resolvedSourcePath);
502
+ await assertRegularFile(skillFilePath, "SKILL.md");
503
+ return true;
504
+ } catch (error) {
505
+ if (error.code === "ENOENT") {
506
+ return false;
507
+ }
508
+ throw error;
509
+ }
510
+ }
511
+ async function copySkillContentsToManagedStore(sourcePath, targetPath) {
512
+ const resolvedSourcePath = resolve5(sourcePath);
513
+ const resolvedTargetPath = resolve5(targetPath);
514
+ if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
515
+ throw new Error("Source and target paths must differ");
516
+ }
517
+ if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
518
+ throw new Error("Refusing to copy into a child of the source directory");
519
+ }
520
+ await assertSkillSourceLayout(resolvedSourcePath);
521
+ await assertNoSymlinkAncestors(resolvedTargetPath);
522
+ await assertTargetDoesNotExist(resolvedTargetPath);
523
+ await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
524
+ }
525
+
526
+ // src/core/batch-operation-error.ts
527
+ function getCauseMessage(cause) {
528
+ if (cause instanceof Error) {
529
+ return cause.message;
530
+ }
531
+ return String(cause);
532
+ }
533
+ function buildBatchOperationMessage(options) {
534
+ const completedSuffix = options.completedItems.length > 0 ? ` after ${options.completedAction}: ${options.completedItems.join(", ")}` : "";
535
+ return `Failed to ${options.failedAction}${completedSuffix}: ${getCauseMessage(options.cause)}`;
536
+ }
537
+ var BatchOperationError = class extends Error {
538
+ operation;
539
+ failedItem;
540
+ completedItems;
541
+ cause;
542
+ constructor(options) {
543
+ super(buildBatchOperationMessage(options), { cause: options.cause });
544
+ this.name = "BatchOperationError";
545
+ this.operation = options.operation;
546
+ this.failedItem = options.failedItem;
547
+ this.completedItems = [...options.completedItems];
548
+ this.cause = options.cause;
549
+ }
550
+ };
551
+
552
+ // src/fs/link-ops.ts
553
+ import * as fs7 from "fs/promises";
554
+ import { dirname as dirname3, resolve as resolve6 } from "path";
555
+ var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
556
+ async function createManagedLink(linkPath, targetPath) {
557
+ const resolvedLinkPath = resolve6(linkPath);
558
+ const resolvedTargetPath = resolve6(targetPath);
559
+ await assertNoSymlinkAncestors(resolvedLinkPath);
560
+ await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
561
+ await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
562
+ try {
563
+ const existingEntry = await fs7.lstat(resolvedLinkPath);
564
+ if (!existingEntry.isSymbolicLink()) {
565
+ throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
566
+ }
567
+ const currentTargetPath = await fs7.realpath(resolvedLinkPath);
568
+ if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
569
+ return;
570
+ }
571
+ throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
572
+ } 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 });
575
+ } else if (error.code !== "ENOENT") {
576
+ throw error;
577
+ }
578
+ }
579
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
580
+ }
581
+ async function replaceEntryWithManagedLink(linkPath, targetPath, expectedCurrentPath) {
582
+ const resolvedLinkPath = resolve6(linkPath);
583
+ const resolvedTargetPath = resolve6(targetPath);
584
+ const resolvedExpectedCurrentPath = resolve6(expectedCurrentPath);
585
+ await assertNoSymlinkAncestors(resolvedLinkPath);
586
+ await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
587
+ await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
588
+ const existingEntry = await fs7.lstat(resolvedLinkPath);
589
+ if (existingEntry.isSymbolicLink()) {
590
+ const currentTargetPath = await fs7.realpath(resolvedLinkPath);
591
+ if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
592
+ return false;
593
+ }
594
+ if (!pathsAreEqual(currentTargetPath, resolvedExpectedCurrentPath)) {
595
+ throw new Error(`Refusing to replace unexpected link at ${resolvedLinkPath}`);
596
+ }
597
+ await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
598
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
599
+ return true;
600
+ }
601
+ if (!existingEntry.isDirectory()) {
602
+ throw new Error(`Refusing to replace non-directory entry at ${resolvedLinkPath}`);
603
+ }
604
+ const currentPath = await fs7.realpath(resolvedLinkPath);
605
+ if (!pathsAreEqual(currentPath, resolvedExpectedCurrentPath)) {
606
+ throw new Error(`Refusing to replace unexpected directory at ${resolvedLinkPath}`);
607
+ }
608
+ await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
609
+ await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
610
+ return true;
611
+ }
612
+ async function isLinkPointingToTarget(linkPath, targetPath) {
613
+ try {
614
+ const entry = await fs7.lstat(linkPath);
615
+ if (!entry.isSymbolicLink()) {
616
+ return false;
617
+ }
618
+ const resolvedTargetPath = await fs7.realpath(linkPath);
619
+ return pathsAreEqual(resolvedTargetPath, targetPath);
620
+ } catch (error) {
621
+ if (error.code === "ENOENT") {
622
+ return false;
623
+ }
624
+ throw error;
625
+ }
626
+ }
627
+
628
+ // src/manifest/read-manifest.ts
629
+ import * as fs9 from "fs/promises";
630
+ import { join as join6, resolve as resolve7 } from "path";
631
+
632
+ // src/manifest/build-empty-manifest.ts
633
+ function buildEmptyManifest(skillmuxHome) {
634
+ return {
635
+ version: 1,
636
+ skillmuxHome,
637
+ skills: {},
638
+ agents: {},
639
+ activations: [],
640
+ lastScan: {
641
+ at: null,
642
+ issues: []
643
+ }
644
+ };
645
+ }
646
+
647
+ // src/manifest/manifest-schema.ts
648
+ import { z as z2 } from "zod";
649
+ var idSchema = z2.string().min(1).refine(isValidId, "Expected a canonical lowercase slug identifier");
650
+ var scanIssueSchema = z2.object({
651
+ code: z2.string().min(1),
652
+ severity: z2.enum(["info", "warning", "error"]),
653
+ message: z2.string().min(1),
654
+ path: z2.string().min(1).optional()
655
+ }).strict();
656
+ var managedSkillSchema = z2.object({
657
+ id: idSchema,
658
+ name: z2.string().min(1),
659
+ path: z2.string().min(1),
660
+ source: z2.object({
661
+ kind: z2.enum(["local", "imported"]),
662
+ path: z2.string().min(1)
663
+ }).strict(),
664
+ importedAt: z2.string().min(1)
665
+ }).strict();
666
+ var agentRecordSchema = z2.object({
667
+ id: idSchema,
668
+ name: z2.string().min(1),
669
+ path: z2.string().min(1),
670
+ discovery: z2.enum(["builtin", "custom"]),
671
+ available: z2.boolean(),
672
+ lastSeenAt: z2.string().min(1).nullable()
673
+ }).strict();
674
+ var activationRecordSchema = z2.object({
675
+ skillId: idSchema,
676
+ agentId: idSchema,
677
+ linkPath: z2.string().min(1),
678
+ state: z2.enum(["enabled", "disabled"]),
679
+ updatedAt: z2.string().min(1)
680
+ }).strict();
681
+ var manifestSchema = z2.object({
682
+ version: z2.literal(1),
683
+ skillmuxHome: z2.string().min(1),
684
+ skills: z2.record(z2.string(), managedSkillSchema),
685
+ agents: z2.record(z2.string(), agentRecordSchema),
686
+ activations: z2.array(activationRecordSchema),
687
+ lastScan: z2.object({
688
+ at: z2.string().min(1).nullable(),
689
+ issues: z2.array(scanIssueSchema)
690
+ }).strict()
691
+ }).strict().superRefine((manifest, ctx) => {
692
+ for (const [skillId, skill] of Object.entries(manifest.skills)) {
693
+ if (!isValidId(skillId)) {
694
+ ctx.addIssue({
695
+ code: z2.ZodIssueCode.custom,
696
+ path: ["skills", skillId],
697
+ message: `Invalid skill id key: ${skillId}`
698
+ });
699
+ }
700
+ if (skill.id !== skillId) {
701
+ ctx.addIssue({
702
+ code: z2.ZodIssueCode.custom,
703
+ path: ["skills", skillId, "id"],
704
+ message: `Skill id must match its record key: ${skillId}`
705
+ });
706
+ }
707
+ }
708
+ for (const [agentId, agent] of Object.entries(manifest.agents)) {
709
+ if (!isValidId(agentId)) {
710
+ ctx.addIssue({
711
+ code: z2.ZodIssueCode.custom,
712
+ path: ["agents", agentId],
713
+ message: `Invalid agent id key: ${agentId}`
714
+ });
715
+ }
716
+ if (agent.id !== agentId) {
717
+ ctx.addIssue({
718
+ code: z2.ZodIssueCode.custom,
719
+ path: ["agents", agentId, "id"],
720
+ message: `Agent id must match its record key: ${agentId}`
721
+ });
722
+ }
723
+ }
724
+ const activationPairs = /* @__PURE__ */ new Set();
725
+ manifest.activations.forEach((activation, index) => {
726
+ if (!(activation.skillId in manifest.skills)) {
727
+ ctx.addIssue({
728
+ code: z2.ZodIssueCode.custom,
729
+ path: ["activations", index, "skillId"],
730
+ message: `Unknown skill reference: ${activation.skillId}`
731
+ });
732
+ }
733
+ if (!(activation.agentId in manifest.agents)) {
734
+ ctx.addIssue({
735
+ code: z2.ZodIssueCode.custom,
736
+ path: ["activations", index, "agentId"],
737
+ message: `Unknown agent reference: ${activation.agentId}`
738
+ });
739
+ }
740
+ const pairKey = `${activation.skillId}:${activation.agentId}`;
741
+ if (activationPairs.has(pairKey)) {
742
+ ctx.addIssue({
743
+ code: z2.ZodIssueCode.custom,
744
+ path: ["activations", index],
745
+ message: `Duplicate activation for ${pairKey}`
746
+ });
747
+ return;
748
+ }
749
+ activationPairs.add(pairKey);
750
+ });
751
+ });
752
+
753
+ // src/manifest/write-manifest.ts
754
+ import { randomUUID } from "crypto";
755
+ import * as fs8 from "fs/promises";
756
+ import { join as join5 } from "path";
757
+ function getManifestPath(home) {
758
+ return join5(home, "manifest.json");
759
+ }
760
+ function createManifestTempPath(manifestPath) {
761
+ return `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
762
+ }
763
+ async function writeManifest(home, manifest) {
764
+ await fs8.mkdir(home, { recursive: true });
765
+ const manifestPath = getManifestPath(home);
766
+ const tempPath = createManifestTempPath(manifestPath);
767
+ const contents = `${JSON.stringify(manifest, null, 2)}
768
+ `;
769
+ await fs8.writeFile(tempPath, contents, "utf8");
770
+ try {
771
+ await fs8.rename(tempPath, manifestPath);
772
+ } catch (error) {
773
+ await fs8.unlink(tempPath).catch(() => void 0);
774
+ throw error;
775
+ }
776
+ }
777
+
778
+ // src/manifest/read-manifest.ts
779
+ function getManifestPath2(home) {
780
+ return join6(home, "manifest.json");
781
+ }
782
+ function normalizeHomePath(home) {
783
+ const resolvedHome = resolve7(home);
784
+ return process.platform === "win32" ? resolvedHome.toLowerCase() : resolvedHome;
785
+ }
786
+ function formatValidationIssues2(error) {
787
+ return error.issues.map((issue) => {
788
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
789
+ return `${path}: ${issue.message}`;
790
+ }).join("; ");
791
+ }
792
+ async function readManifest(home) {
793
+ const manifestPath = getManifestPath2(home);
794
+ try {
795
+ const contents = await fs9.readFile(manifestPath, "utf8");
796
+ const parsedJson = JSON.parse(contents);
797
+ const parsedManifest = manifestSchema.safeParse(parsedJson);
798
+ if (!parsedManifest.success) {
799
+ throw new ManifestValidationError(
800
+ `Invalid manifest at ${manifestPath}: ${formatValidationIssues2(parsedManifest.error)}`
801
+ );
802
+ }
803
+ if (normalizeHomePath(parsedManifest.data.skillmuxHome) !== normalizeHomePath(home)) {
804
+ throw new ManifestValidationError(
805
+ `Invalid manifest at ${manifestPath}: skillmuxHome must match ${home}`
806
+ );
807
+ }
808
+ return parsedManifest.data;
809
+ } catch (error) {
810
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
811
+ const emptyManifest = buildEmptyManifest(home);
812
+ await writeManifest(home, emptyManifest);
813
+ return emptyManifest;
814
+ }
815
+ if (error instanceof SyntaxError) {
816
+ throw new ManifestValidationError(
817
+ `Invalid manifest at ${manifestPath}: malformed JSON`
818
+ );
819
+ }
820
+ throw error;
821
+ }
822
+ }
823
+
824
+ // src/output/print-json.ts
825
+ function printJson(value) {
826
+ return `${JSON.stringify(value, null, 2)}
827
+ `;
828
+ }
829
+
830
+ // src/commands/adopt.ts
831
+ function buildManagedSkillPath(skillmuxHome, skillId) {
832
+ return resolve8(skillmuxHome, "skills", skillId);
833
+ }
834
+ function buildAgentRecord(agent, timestamp) {
835
+ return {
836
+ id: agent.id,
837
+ name: agent.stableName,
838
+ path: agent.absoluteSkillsDirectoryPath,
839
+ discovery: agent.discovery,
840
+ available: agent.exists && agent.supportedOnPlatform,
841
+ lastSeenAt: agent.exists ? timestamp : null
842
+ };
843
+ }
844
+ function buildActivationRecord(skillId, agentId, linkPath, timestamp) {
845
+ return {
846
+ skillId,
847
+ agentId,
848
+ linkPath,
849
+ state: "enabled",
850
+ updatedAt: timestamp
851
+ };
852
+ }
853
+ function upsertActivation(manifest, activation) {
854
+ const index = manifest.activations.findIndex(
855
+ (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
856
+ );
857
+ if (index === -1) {
858
+ manifest.activations.push(activation);
859
+ return;
860
+ }
861
+ manifest.activations[index] = activation;
862
+ }
863
+ async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
864
+ const agentId = normalizeId(agentName);
865
+ const agents = await discoverAgents({ homeDir, skillmuxHome });
866
+ const agent = agents.find((entry) => entry.id === agentId);
867
+ if (agent === void 0) {
868
+ throw new AdoptionError(`Unknown agent: ${agentName}`);
869
+ }
870
+ if (!agent.supportedOnPlatform) {
871
+ throw new AdoptionError(
872
+ `Agent ${agent.id} is not supported on ${process.platform}`
873
+ );
874
+ }
875
+ return agent;
876
+ }
877
+ function filterEntries(scannedAgent, skillFilter) {
878
+ if (skillFilter === void 0) {
879
+ return scannedAgent.entries;
880
+ }
881
+ const skillId = normalizeId(skillFilter);
882
+ return scannedAgent.entries.filter(
883
+ (entry) => normalizeId(entry.skillName) === skillId
884
+ );
885
+ }
886
+ async function resolveAdoptionSource(entry) {
887
+ if (entry.kind === "unmanaged-link") {
888
+ return entry.targetPath;
889
+ }
890
+ if (entry.kind === "unmanaged-directory") {
891
+ return entry.path;
892
+ }
893
+ return void 0;
894
+ }
895
+ function buildManagedSkill(skillId, skillName, managedPath, sourcePath, timestamp) {
896
+ return {
897
+ id: skillId,
898
+ name: skillName,
899
+ path: managedPath,
900
+ source: {
901
+ kind: "imported",
902
+ path: sourcePath
903
+ },
904
+ importedAt: timestamp
905
+ };
906
+ }
907
+ async function reconcileManagedLink(manifest, skillId, entry, agentId, timestamp) {
908
+ if (entry.targetPath === void 0) {
909
+ throw new AdoptionError(`Managed link target is missing for ${entry.path}`);
910
+ }
911
+ await assertSkillSourceLayout(entry.targetPath);
912
+ const skill = manifest.skills[skillId];
913
+ if (skill === void 0 || !pathsAreEqual(skill.path, entry.targetPath)) {
914
+ throw new AdoptionError(
915
+ `Managed link for ${agentId}/${skillId} has no matching manifest skill record`
916
+ );
917
+ }
918
+ upsertActivation(
919
+ manifest,
920
+ buildActivationRecord(skillId, agentId, entry.path, timestamp)
921
+ );
922
+ }
923
+ function buildOutput(result, json) {
924
+ if (json) {
925
+ return printJson({
926
+ agent: result.agent,
927
+ adopted: result.adopted,
928
+ skipped: result.skipped
929
+ });
930
+ }
931
+ if (result.adopted.length === 0) {
932
+ return `No skills adopted for ${result.agent.id}.
933
+ `;
934
+ }
935
+ const adoptedSkills = result.adopted.map((entry) => entry.skillId).sort((left, right) => left.localeCompare(right)).join(", ");
936
+ return `Adopted ${adoptedSkills} for ${result.agent.id}
937
+ `;
938
+ }
939
+ async function runAdoptSingle(options) {
940
+ const homeDir = options.homeDir ?? homedir();
941
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
942
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
943
+ const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
944
+ const manifest = await readManifest(skillmuxHome);
945
+ const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
946
+ const agentRecord = buildAgentRecord(agent, timestamp);
947
+ const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
948
+ const entries = filterEntries(scannedAgent, options.skill);
949
+ const adopted = [];
950
+ const skipped = [];
951
+ manifest.agents[agent.id] = agentRecord;
952
+ for (const entry of entries) {
953
+ const skillId = normalizeId(entry.skillName);
954
+ if (entry.kind === "managed-link") {
955
+ await reconcileManagedLink(
956
+ manifest,
957
+ skillId,
958
+ entry,
959
+ agent.id,
960
+ timestamp
961
+ );
962
+ skipped.push({
963
+ skillId,
964
+ agentId: agent.id,
965
+ path: entry.path,
966
+ reason: "already-managed"
967
+ });
968
+ await writeManifest(skillmuxHome, manifest);
969
+ continue;
970
+ }
971
+ const sourcePath = await resolveAdoptionSource(entry);
972
+ if (sourcePath === void 0) {
973
+ skipped.push({
974
+ skillId,
975
+ agentId: agent.id,
976
+ path: entry.path,
977
+ reason: "not-adoptable"
978
+ });
979
+ continue;
980
+ }
981
+ if (!await hasRootSkillFile(sourcePath)) {
982
+ skipped.push({
983
+ skillId,
984
+ agentId: agent.id,
985
+ path: entry.path,
986
+ reason: "missing-skill-file"
987
+ });
988
+ continue;
989
+ }
990
+ const managedPath = buildManagedSkillPath(skillmuxHome, skillId);
991
+ if (manifest.skills[skillId] === void 0) {
992
+ await copySkillContentsToManagedStore(sourcePath, managedPath);
993
+ manifest.skills[skillId] = buildManagedSkill(
994
+ skillId,
995
+ entry.skillName,
996
+ managedPath,
997
+ sourcePath,
998
+ timestamp
999
+ );
1000
+ } else if (await isLinkPointingToTarget(entry.path, manifest.skills[skillId].path)) {
1001
+ skipped.push({
1002
+ skillId,
1003
+ agentId: agent.id,
1004
+ path: entry.path,
1005
+ reason: "already-managed"
1006
+ });
1007
+ continue;
1008
+ } else {
1009
+ await assertSkillSourceLayout(manifest.skills[skillId].path);
1010
+ }
1011
+ await replaceEntryWithManagedLink(
1012
+ entry.path,
1013
+ manifest.skills[skillId].path,
1014
+ sourcePath
1015
+ );
1016
+ const activation = buildActivationRecord(
1017
+ skillId,
1018
+ agent.id,
1019
+ join7(agent.absoluteSkillsDirectoryPath, entry.skillName),
1020
+ timestamp
1021
+ );
1022
+ upsertActivation(manifest, activation);
1023
+ adopted.push({
1024
+ skillId,
1025
+ agentId: agent.id,
1026
+ sourcePath,
1027
+ managedPath: manifest.skills[skillId].path,
1028
+ linkPath: activation.linkPath
1029
+ });
1030
+ await writeManifest(skillmuxHome, manifest);
1031
+ }
1032
+ await writeManifest(skillmuxHome, manifest);
1033
+ const resultWithoutOutput = {
1034
+ agent: agentRecord,
1035
+ adopted,
1036
+ skipped,
1037
+ manifest
1038
+ };
1039
+ return {
1040
+ ...resultWithoutOutput,
1041
+ output: buildOutput(resultWithoutOutput, options.json === true)
1042
+ };
1043
+ }
1044
+ async function runAdopt(options) {
1045
+ if (options.skills !== void 0) {
1046
+ const results = [];
1047
+ const completedSkills = [];
1048
+ for (const skill of options.skills) {
1049
+ try {
1050
+ results.push(await runAdoptSingle({ ...options, skill, skills: void 0 }));
1051
+ completedSkills.push(skill);
1052
+ } catch (error) {
1053
+ throw new BatchOperationError({
1054
+ operation: "adopt",
1055
+ failedItem: skill,
1056
+ failedAction: `adopt ${skill} for ${options.agent}`,
1057
+ completedAction: "adopting",
1058
+ completedItems: completedSkills,
1059
+ cause: error
1060
+ });
1061
+ }
1062
+ }
1063
+ if (results.length === 0) {
1064
+ throw new AdoptionError("Adopt requires at least one target skill");
1065
+ }
1066
+ const lastResult = results[results.length - 1];
1067
+ const resultWithoutOutput = {
1068
+ agent: lastResult.agent,
1069
+ adopted: results.flatMap((result) => result.adopted),
1070
+ skipped: results.flatMap((result) => result.skipped),
1071
+ manifest: lastResult.manifest,
1072
+ results
1073
+ };
1074
+ return {
1075
+ ...resultWithoutOutput,
1076
+ output: options.json === true ? printJson({
1077
+ agent: resultWithoutOutput.agent,
1078
+ adopted: resultWithoutOutput.adopted,
1079
+ skipped: resultWithoutOutput.skipped,
1080
+ results: resultWithoutOutput.results
1081
+ }) : results.map((result) => result.output).join("")
1082
+ };
1083
+ }
1084
+ return runAdoptSingle(options);
1085
+ }
1086
+
1087
+ // src/commands/disable.ts
1088
+ import * as fs11 from "fs/promises";
1089
+ import { homedir as homedir2 } from "os";
1090
+ import { join as join8, resolve as resolve9 } from "path";
1091
+
1092
+ // src/fs/safe-remove-link.ts
1093
+ import * as fs10 from "fs/promises";
1094
+ async function safeRemoveLink(path) {
1095
+ try {
1096
+ const entry = await fs10.lstat(path);
1097
+ if (!entry.isSymbolicLink()) {
1098
+ return false;
1099
+ }
1100
+ await fs10.rm(path, { recursive: true, force: false });
1101
+ return true;
1102
+ } catch (error) {
1103
+ if (error.code === "ENOENT") {
1104
+ return false;
1105
+ }
1106
+ throw error;
1107
+ }
1108
+ }
1109
+
1110
+ // src/commands/disable.ts
1111
+ function buildAgentRecord2(agent, timestamp) {
1112
+ return {
1113
+ id: agent.id,
1114
+ name: agent.stableName,
1115
+ path: agent.absoluteSkillsDirectoryPath,
1116
+ discovery: agent.discovery,
1117
+ available: agent.exists && agent.supportedOnPlatform,
1118
+ lastSeenAt: agent.exists ? timestamp : null
1119
+ };
1120
+ }
1121
+ function buildActivationRecord2(skillId, agentId, linkPath, timestamp) {
1122
+ return {
1123
+ skillId,
1124
+ agentId,
1125
+ linkPath,
1126
+ state: "disabled",
1127
+ updatedAt: timestamp
1128
+ };
1129
+ }
1130
+ function upsertActivation2(manifest, activation) {
1131
+ const index = manifest.activations.findIndex(
1132
+ (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
1133
+ );
1134
+ if (index === -1) {
1135
+ manifest.activations.push(activation);
1136
+ return;
1137
+ }
1138
+ manifest.activations[index] = activation;
1139
+ }
1140
+ function buildManagedSkillPath2(skillmuxHome, skillId) {
1141
+ return resolve9(skillmuxHome, "skills", skillId);
1142
+ }
1143
+ async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName, linkPath, timestamp) {
1144
+ try {
1145
+ const entry = await fs11.lstat(linkPath);
1146
+ if (!entry.isSymbolicLink()) {
1147
+ return void 0;
1148
+ }
1149
+ } catch (error) {
1150
+ if (error.code === "ENOENT") {
1151
+ return void 0;
1152
+ }
1153
+ throw error;
1154
+ }
1155
+ const sourcePath = await fs11.realpath(linkPath);
1156
+ await assertSkillSourceLayout(sourcePath);
1157
+ const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
1158
+ await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
1159
+ const skill = {
1160
+ id: skillId,
1161
+ name: skillName,
1162
+ path: managedSkillPath,
1163
+ source: {
1164
+ kind: "imported",
1165
+ path: sourcePath
1166
+ },
1167
+ importedAt: timestamp
1168
+ };
1169
+ manifest.skills[skillId] = skill;
1170
+ return { skill, sourcePath };
1171
+ }
1172
+ async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
1173
+ const agentId = normalizeId(agentName);
1174
+ const agents = await discoverAgents({ homeDir, skillmuxHome });
1175
+ const agent = agents.find((entry) => entry.id === agentId);
1176
+ if (agent === void 0) {
1177
+ throw new Error(`Unknown agent: ${agentName}`);
1178
+ }
1179
+ if (!agent.supportedOnPlatform) {
1180
+ throw new Error(`Agent ${agent.id} is not supported on ${process.platform}`);
1181
+ }
1182
+ return agent;
1183
+ }
1184
+ async function pathExists2(path) {
1185
+ try {
1186
+ await fs11.lstat(path);
1187
+ return true;
1188
+ } catch (error) {
1189
+ if (error.code === "ENOENT") {
1190
+ return false;
1191
+ }
1192
+ throw error;
1193
+ }
1194
+ }
1195
+ async function runDisableSingle(options) {
1196
+ if (options.agent === void 0) {
1197
+ throw new Error("Disable requires one target agent");
1198
+ }
1199
+ const homeDir = options.homeDir ?? homedir2();
1200
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1201
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1202
+ const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1203
+ const manifest = await readManifest(skillmuxHome);
1204
+ const skillId = normalizeId(options.skill);
1205
+ const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
1206
+ const linkPath = join8(agent.absoluteSkillsDirectoryPath, skillId);
1207
+ const adoption = manifest.skills[skillId] ? void 0 : await tryAdoptManagedSkill(
1208
+ manifest,
1209
+ skillmuxHome,
1210
+ skillId,
1211
+ options.skill,
1212
+ linkPath,
1213
+ timestamp
1214
+ );
1215
+ const skill = manifest.skills[skillId] ?? adoption?.skill;
1216
+ if (skill === void 0) {
1217
+ throw new Error(`Managed skill not found: ${skillId}`);
1218
+ }
1219
+ const currentActivation = manifest.activations.find(
1220
+ (entry) => entry.skillId === skill.id && entry.agentId === agent.id
1221
+ );
1222
+ const activationLinkPath = currentActivation?.linkPath ?? linkPath;
1223
+ const agentRecord = buildAgentRecord2(agent, timestamp);
1224
+ manifest.agents[agent.id] = agentRecord;
1225
+ const adoptedLinkRemoved = adoption !== void 0 ? await safeRemoveLink(linkPath) : false;
1226
+ const linkMatchesSkill = adoption === void 0 ? await isLinkPointingToTarget(activationLinkPath, skill.path) : false;
1227
+ if (adoption === void 0 && !linkMatchesSkill && await pathExists2(activationLinkPath)) {
1228
+ throw new Error(`Refusing to disable non-managed entry at ${linkPath}`);
1229
+ }
1230
+ const removedLink = adoptedLinkRemoved ? true : linkMatchesSkill ? await safeRemoveLink(activationLinkPath) : false;
1231
+ if (removedLink === false && currentActivation?.state !== "enabled") {
1232
+ return {
1233
+ changed: false,
1234
+ skill,
1235
+ agent: agentRecord,
1236
+ activation: currentActivation ?? null,
1237
+ manifest,
1238
+ output: `${skill.id} is already disabled for ${agent.id}
1239
+ `
1240
+ };
1241
+ }
1242
+ const activation = buildActivationRecord2(skill.id, agent.id, linkPath, timestamp);
1243
+ upsertActivation2(manifest, activation);
1244
+ await writeManifest(skillmuxHome, manifest);
1245
+ return {
1246
+ changed: true,
1247
+ skill,
1248
+ agent: agentRecord,
1249
+ activation,
1250
+ manifest,
1251
+ output: `Disabled ${skill.id} for ${agent.id}
1252
+ `
1253
+ };
1254
+ }
1255
+ async function runDisable(options) {
1256
+ if (options.agents !== void 0) {
1257
+ const results = [];
1258
+ for (const agent of options.agents) {
1259
+ try {
1260
+ results.push(await runDisableSingle({ ...options, agent, agents: void 0 }));
1261
+ } catch (error) {
1262
+ throw new BatchOperationError({
1263
+ operation: "disable",
1264
+ failedItem: agent,
1265
+ failedAction: `disable ${options.skill} for ${agent}`,
1266
+ completedAction: "disabling",
1267
+ completedItems: results.map((result) => result.agent.id),
1268
+ cause: error
1269
+ });
1270
+ }
1271
+ }
1272
+ if (results.length === 0) {
1273
+ throw new Error("Disable requires at least one target agent");
1274
+ }
1275
+ const lastResult = results[results.length - 1];
1276
+ return {
1277
+ changed: results.some((result) => result.changed),
1278
+ skill: lastResult.skill,
1279
+ results,
1280
+ changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
1281
+ manifest: lastResult.manifest,
1282
+ output: results.map((result) => result.output).join("")
1283
+ };
1284
+ }
1285
+ return runDisableSingle(options);
1286
+ }
1287
+
1288
+ // 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";
1292
+ function buildAgentRecord3(agent, timestamp) {
1293
+ return {
1294
+ id: agent.id,
1295
+ name: agent.stableName,
1296
+ path: agent.absoluteSkillsDirectoryPath,
1297
+ discovery: agent.discovery,
1298
+ available: agent.supportedOnPlatform,
1299
+ lastSeenAt: timestamp
1300
+ };
1301
+ }
1302
+ function buildActivationRecord3(skillId, agentId, linkPath, timestamp, state) {
1303
+ return {
1304
+ skillId,
1305
+ agentId,
1306
+ linkPath,
1307
+ state,
1308
+ updatedAt: timestamp
1309
+ };
1310
+ }
1311
+ function upsertActivation3(manifest, activation) {
1312
+ const index = manifest.activations.findIndex(
1313
+ (entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
1314
+ );
1315
+ if (index === -1) {
1316
+ manifest.activations.push(activation);
1317
+ return;
1318
+ }
1319
+ manifest.activations[index] = activation;
1320
+ }
1321
+ async function resolveTargetAgent3(homeDir, skillmuxHome, agentName) {
1322
+ const agentId = normalizeId(agentName);
1323
+ const agents = await discoverAgents({ homeDir, skillmuxHome });
1324
+ const agent = agents.find((entry) => entry.id === agentId);
1325
+ if (agent === void 0) {
1326
+ throw new Error(`Unknown agent: ${agentName}`);
1327
+ }
1328
+ if (!agent.supportedOnPlatform) {
1329
+ throw new Error(`Agent ${agent.id} is not supported on ${process.platform}`);
1330
+ }
1331
+ return agent;
1332
+ }
1333
+ async function runEnableSingle(options) {
1334
+ if (options.agent === void 0) {
1335
+ throw new Error("Enable requires one target agent");
1336
+ }
1337
+ const homeDir = options.homeDir ?? homedir3();
1338
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1339
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1340
+ const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1341
+ const manifest = await readManifest(skillmuxHome);
1342
+ const skillId = normalizeId(options.skill);
1343
+ const skill = manifest.skills[skillId];
1344
+ if (skill === void 0) {
1345
+ throw new Error(`Managed skill not found: ${skillId}`);
1346
+ }
1347
+ const agent = await resolveTargetAgent3(homeDir, skillmuxHome, options.agent);
1348
+ const linkPath = join9(agent.absoluteSkillsDirectoryPath, skill.id);
1349
+ const currentActivation = manifest.activations.find(
1350
+ (entry) => entry.skillId === skill.id && entry.agentId === agent.id
1351
+ );
1352
+ const agentRecord = buildAgentRecord3(agent, timestamp);
1353
+ manifest.agents[agent.id] = agentRecord;
1354
+ await fs12.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
1355
+ const linkAlreadyEnabled = await isLinkPointingToTarget(linkPath, skill.path);
1356
+ const activationAlreadyEnabled = currentActivation?.state === "enabled" && currentActivation.linkPath === linkPath;
1357
+ if (linkAlreadyEnabled && activationAlreadyEnabled) {
1358
+ return {
1359
+ changed: false,
1360
+ skill,
1361
+ agent: agentRecord,
1362
+ activation: currentActivation,
1363
+ manifest,
1364
+ output: `${skill.id} is already enabled for ${agent.id}
1365
+ `
1366
+ };
1367
+ }
1368
+ await createManagedLink(linkPath, skill.path);
1369
+ const activation = buildActivationRecord3(
1370
+ skill.id,
1371
+ agent.id,
1372
+ linkPath,
1373
+ timestamp,
1374
+ "enabled"
1375
+ );
1376
+ upsertActivation3(manifest, activation);
1377
+ await writeManifest(skillmuxHome, manifest);
1378
+ return {
1379
+ changed: true,
1380
+ skill,
1381
+ agent: agentRecord,
1382
+ activation,
1383
+ manifest,
1384
+ output: `Enabled ${skill.id} for ${agent.id}
1385
+ `
1386
+ };
1387
+ }
1388
+ async function runEnable(options) {
1389
+ if (options.agents !== void 0) {
1390
+ const results = [];
1391
+ for (const agent of options.agents) {
1392
+ try {
1393
+ results.push(await runEnableSingle({ ...options, agent, agents: void 0 }));
1394
+ } catch (error) {
1395
+ throw new BatchOperationError({
1396
+ operation: "enable",
1397
+ failedItem: agent,
1398
+ failedAction: `enable ${options.skill} for ${agent}`,
1399
+ completedAction: "enabling",
1400
+ completedItems: results.map((result) => result.agent.id),
1401
+ cause: error
1402
+ });
1403
+ }
1404
+ }
1405
+ if (results.length === 0) {
1406
+ throw new Error("Enable requires at least one target agent");
1407
+ }
1408
+ const lastResult = results[results.length - 1];
1409
+ return {
1410
+ changed: results.some((result) => result.changed),
1411
+ skill: lastResult.skill,
1412
+ results,
1413
+ changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
1414
+ manifest: lastResult.manifest,
1415
+ output: results.map((result) => result.output).join("")
1416
+ };
1417
+ }
1418
+ return runEnableSingle(options);
1419
+ }
1420
+
1421
+ // src/commands/scan.ts
1422
+ import { homedir as homedir4 } from "os";
1423
+
1424
+ // src/output/format-issue.ts
1425
+ function formatIssue(issue) {
1426
+ if (issue.path === void 0) {
1427
+ return `[${issue.severity}] ${issue.code}: ${issue.message}`;
1428
+ }
1429
+ return `[${issue.severity}] ${issue.code} @ ${issue.path}: ${issue.message}`;
1430
+ }
1431
+
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
+ // src/commands/scan.ts
1453
+ function buildAgentRecord4(agent, timestamp, previousRecord) {
1454
+ const lastSeenAt = agent.exists ? timestamp : previousRecord?.lastSeenAt ?? null;
1455
+ return {
1456
+ id: agent.id,
1457
+ name: agent.stableName,
1458
+ path: agent.absoluteSkillsDirectoryPath,
1459
+ discovery: agent.discovery,
1460
+ available: agent.exists && agent.supportedOnPlatform,
1461
+ lastSeenAt
1462
+ };
1463
+ }
1464
+ function buildScanOutput(result, json) {
1465
+ if (json) {
1466
+ return printJson({
1467
+ lastScan: result.manifest.lastScan,
1468
+ agents: result.agents.map((agent) => ({
1469
+ id: agent.id,
1470
+ name: agent.stableName,
1471
+ path: agent.absoluteSkillsDirectoryPath,
1472
+ exists: agent.exists,
1473
+ supportedOnPlatform: agent.supportedOnPlatform
1474
+ })),
1475
+ entries: result.entries
1476
+ });
1477
+ }
1478
+ if (result.entries.length === 0) {
1479
+ return "No skill entries found.\n";
1480
+ }
1481
+ const table = printTable(
1482
+ result.entries.map((entry) => ({
1483
+ agent: entry.agentId,
1484
+ skill: entry.skillName,
1485
+ kind: entry.kind,
1486
+ path: entry.path
1487
+ })),
1488
+ [
1489
+ { key: "agent", label: "Agent" },
1490
+ { key: "skill", label: "Skill" },
1491
+ { key: "kind", label: "Kind" },
1492
+ { key: "path", label: "Path" }
1493
+ ]
1494
+ );
1495
+ if (result.issues.length === 0) {
1496
+ return table;
1497
+ }
1498
+ return `${table}
1499
+ ${result.issues.map(formatIssue).join("\n")}
1500
+ `;
1501
+ }
1502
+ async function runScan(options = {}) {
1503
+ const homeDir = options.homeDir ?? homedir4();
1504
+ const resolvedPaths = resolveSkillmuxHome(homeDir);
1505
+ const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
1506
+ const manifest = await readManifest(skillmuxHome);
1507
+ const agents = await discoverAgents({
1508
+ homeDir,
1509
+ platform: options.platform,
1510
+ skillmuxHome
1511
+ });
1512
+ const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1513
+ const entries = [];
1514
+ const issues = [];
1515
+ for (const agent of agents) {
1516
+ const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
1517
+ entries.push(...scannedAgent.entries);
1518
+ issues.push(...scannedAgent.issues);
1519
+ manifest.agents[agent.id] = buildAgentRecord4(
1520
+ agent,
1521
+ timestamp,
1522
+ manifest.agents[agent.id]
1523
+ );
1524
+ }
1525
+ manifest.lastScan = {
1526
+ at: timestamp,
1527
+ issues
1528
+ };
1529
+ await writeManifest(skillmuxHome, manifest);
1530
+ const resultWithoutOutput = {
1531
+ manifest,
1532
+ agents,
1533
+ entries,
1534
+ issues
1535
+ };
1536
+ return {
1537
+ ...resultWithoutOutput,
1538
+ output: buildScanOutput(resultWithoutOutput, options.json === true)
1539
+ };
1540
+ }
1541
+
1542
+ // 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);
1548
+ }
1549
+ function buildManifestPath(skillmuxHome) {
1550
+ return resolve10(skillmuxHome, "manifest.json");
1551
+ }
1552
+ function buildConfigPath2(skillmuxHome) {
1553
+ return resolve10(skillmuxHome, "config.json");
1554
+ }
1555
+ function resolveManagedSkill(manifest, skillNameOrId) {
1556
+ const skillId = normalizeId(skillNameOrId);
1557
+ const directMatch = manifest.skills[skillId];
1558
+ if (directMatch !== void 0) {
1559
+ return directMatch;
1560
+ }
1561
+ const nameMatches = Object.values(manifest.skills).filter(
1562
+ (skill) => normalizeId(skill.name) === skillId
1563
+ );
1564
+ if (nameMatches.length === 1) {
1565
+ return nameMatches[0];
1566
+ }
1567
+ if (nameMatches.length > 1) {
1568
+ const candidateIds = nameMatches.map((skill) => skill.id).sort((left, right) => left.localeCompare(right));
1569
+ throw new Error(
1570
+ `Ambiguous managed skill name ${skillNameOrId}: ${candidateIds.join(", ")}`
1571
+ );
1572
+ }
1573
+ throw new Error(`Managed skill not found: ${skillId}`);
1574
+ }
1575
+ function buildHumanOutput(skill, managedSkillPath) {
1576
+ return `Removed ${skill.id} from ${managedSkillPath}
1577
+ `;
1578
+ }
1579
+ function buildJsonOutput(result) {
1580
+ return printJson({
1581
+ changed: result.changed,
1582
+ removedSkillId: result.removedSkillId,
1583
+ skill: result.skill,
1584
+ location: result.location,
1585
+ manifest: result.manifest
1586
+ });
1587
+ }
1588
+ async function pathExists3(path) {
1589
+ try {
1590
+ await fs13.lstat(path);
1591
+ return true;
1592
+ } catch (error) {
1593
+ if (error.code === "ENOENT") {
1594
+ return false;
1595
+ }
1596
+ throw error;
1597
+ }
1598
+ }
1599
+ async function assertManagedSkillRemovalSafety(skillmuxHome, skillPath, skillId) {
1600
+ const expectedSkillPath = buildManagedSkillPath3(skillmuxHome, skillId);
1601
+ if (!pathsAreEqual(skillPath, expectedSkillPath)) {
1602
+ throw new Error(`Refusing to remove unmanaged skill path at ${skillPath}`);
1603
+ }
1604
+ await assertNoSymlinkAncestors(skillPath, { includeLeaf: true });
1605
+ if (!await pathExists3(skillPath)) {
1606
+ return;
1607
+ }
1608
+ const entry = await fs13.lstat(skillPath);
1609
+ if (entry.isSymbolicLink()) {
1610
+ throw new Error(`Refusing to remove symlinked managed skill path at ${skillPath}`);
1611
+ }
1612
+ if (!entry.isDirectory()) {
1613
+ throw new Error(`Refusing to remove non-directory managed skill path at ${skillPath}`);
1614
+ }
1615
+ }
1616
+ async function runRemoveSingle(options) {
1617
+ if (options.skill === void 0) {
1618
+ throw new Error("Remove requires one target skill");
1619
+ }
1620
+ const homeDir = options.homeDir ?? homedir5();
1621
+ const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
1622
+ const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
1623
+ const manifestPath = buildManifestPath(skillmuxHome);
1624
+ const configPath = buildConfigPath2(skillmuxHome);
1625
+ const manifest = await readManifest(skillmuxHome);
1626
+ const skill = resolveManagedSkill(manifest, options.skill);
1627
+ const managedSkillsDirectory = resolve10(skillmuxHome, "skills");
1628
+ const managedSkillPath = skill.path;
1629
+ const enabledActivations = manifest.activations.filter(
1630
+ (activation) => activation.skillId === skill.id && activation.state === "enabled"
1631
+ );
1632
+ if (enabledActivations.length > 0) {
1633
+ const enabledAgents = [...new Set(enabledActivations.map((activation) => activation.agentId))].sort(
1634
+ (left, right) => left.localeCompare(right)
1635
+ );
1636
+ throw new Error(
1637
+ `Cannot remove ${skill.id}; it is still enabled for: ${enabledAgents.join(", ")}`
1638
+ );
1639
+ }
1640
+ await assertManagedSkillRemovalSafety(skillmuxHome, managedSkillPath, skill.id);
1641
+ if (await pathExists3(managedSkillPath)) {
1642
+ await fs13.rm(managedSkillPath, { recursive: true, force: false });
1643
+ }
1644
+ delete manifest.skills[skill.id];
1645
+ manifest.activations = manifest.activations.filter(
1646
+ (activation) => activation.skillId !== skill.id
1647
+ );
1648
+ await writeManifest(skillmuxHome, manifest);
1649
+ const resultWithoutOutput = {
1650
+ changed: true,
1651
+ removedSkillId: skill.id,
1652
+ skill,
1653
+ location: {
1654
+ skillmuxHome,
1655
+ configPath,
1656
+ manifestPath,
1657
+ managedSkillsDirectory,
1658
+ managedSkillPath
1659
+ },
1660
+ manifest
1661
+ };
1662
+ return {
1663
+ ...resultWithoutOutput,
1664
+ output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildHumanOutput(skill, managedSkillPath)
1665
+ };
1666
+ }
1667
+ async function runRemove(options) {
1668
+ if (options.skills !== void 0) {
1669
+ const results = [];
1670
+ for (const skill of options.skills) {
1671
+ try {
1672
+ results.push(await runRemoveSingle({ ...options, skill, skills: void 0 }));
1673
+ } catch (error) {
1674
+ throw new BatchOperationError({
1675
+ operation: "remove",
1676
+ failedItem: skill,
1677
+ failedAction: `remove ${skill}`,
1678
+ completedAction: "removing",
1679
+ completedItems: results.map((result) => result.removedSkillId),
1680
+ cause: error
1681
+ });
1682
+ }
1683
+ }
1684
+ if (results.length === 0) {
1685
+ throw new Error("Remove requires at least one target skill");
1686
+ }
1687
+ const lastResult = results[results.length - 1];
1688
+ const resultWithoutOutput = {
1689
+ changed: results.some((result) => result.changed),
1690
+ removedSkillIds: results.map((result) => result.removedSkillId),
1691
+ results,
1692
+ manifest: lastResult.manifest
1693
+ };
1694
+ return {
1695
+ ...resultWithoutOutput,
1696
+ output: options.json === true ? printJson(resultWithoutOutput) : results.map((result) => result.output).join("")
1697
+ };
1698
+ }
1699
+ return runRemoveSingle(options);
1700
+ }
1701
+
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
+ export {
1798
+ supportedPlatforms,
1799
+ InvalidIdentifierError,
1800
+ ManifestValidationError,
1801
+ UserConfigValidationError,
1802
+ normalizeId,
1803
+ buildConfigPath,
1804
+ resolveSkillmuxHome,
1805
+ loadUserConfig,
1806
+ discoverAgents,
1807
+ isPathInside,
1808
+ scanAgentSkills,
1809
+ assertSkillSourceLayout,
1810
+ copySkillContentsToManagedStore,
1811
+ buildEmptyManifest,
1812
+ manifestSchema,
1813
+ writeManifest,
1814
+ formatValidationIssues2 as formatValidationIssues,
1815
+ readManifest,
1816
+ printJson,
1817
+ runAdopt,
1818
+ printTable,
1819
+ dedupeAndSortIssues,
1820
+ collectDoctorIssues,
1821
+ runDisable,
1822
+ runEnable,
1823
+ runScan,
1824
+ runRemove
1825
+ };
1826
+ //# sourceMappingURL=chunk-DBEVDI27.js.map