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