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.
- package/README.md +24 -1
- package/dist/chunk-DBEVDI27.js +1826 -0
- package/dist/chunk-DBEVDI27.js.map +1 -0
- package/dist/chunk-UMN3UJFN.js +836 -0
- package/dist/chunk-UMN3UJFN.js.map +1 -0
- package/dist/cli.js +4 -2540
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -2539
- package/dist/index.js.map +1 -1
- package/dist/launch-tui-PHWJPIQZ.js +1790 -0
- package/dist/launch-tui-PHWJPIQZ.js.map +1 -0
- package/package.json +11 -2
|
@@ -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
|