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
package/dist/cli.js
CHANGED
|
@@ -1,2544 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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/adopt.ts
|
|
67
|
-
import { homedir } from "os";
|
|
68
|
-
import { join as join7, resolve as resolve8 } from "path";
|
|
69
|
-
|
|
70
|
-
// src/core/errors.ts
|
|
71
|
-
var SkillMuxError = class extends Error {
|
|
72
|
-
constructor(message) {
|
|
73
|
-
super(message);
|
|
74
|
-
this.name = new.target.name;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
var InvalidIdentifierError = class extends SkillMuxError {
|
|
78
|
-
constructor(kind, value) {
|
|
79
|
-
super(`Invalid ${kind}: ${value}`);
|
|
80
|
-
this.kind = kind;
|
|
81
|
-
this.value = value;
|
|
82
|
-
}
|
|
83
|
-
kind;
|
|
84
|
-
value;
|
|
85
|
-
};
|
|
86
|
-
var ManifestValidationError = class extends SkillMuxError {
|
|
87
|
-
constructor(message) {
|
|
88
|
-
super(message);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
var UserConfigValidationError = class extends SkillMuxError {
|
|
92
|
-
constructor(message) {
|
|
93
|
-
super(message);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var AdoptionError = class extends SkillMuxError {
|
|
97
|
-
constructor(message) {
|
|
98
|
-
super(message);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// src/core/ids.ts
|
|
103
|
-
var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
104
|
-
function normalizeId(value) {
|
|
105
|
-
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
106
|
-
return normalized.length > 0 ? normalized : "skill";
|
|
107
|
-
}
|
|
108
|
-
function isValidId(value) {
|
|
109
|
-
return ID_PATTERN.test(value);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// src/config/resolve-skillmux-home.ts
|
|
113
|
-
import { join, resolve } from "path";
|
|
114
|
-
function buildConfigPath(skillmuxHome) {
|
|
115
|
-
return join(resolve(skillmuxHome), "config.json");
|
|
116
|
-
}
|
|
117
|
-
function resolveSkillmuxHome(homeDir) {
|
|
118
|
-
const resolvedHomeDir = resolve(homeDir);
|
|
119
|
-
const skillmuxHome = join(resolvedHomeDir, ".skillmux");
|
|
120
|
-
return {
|
|
121
|
-
skillmuxHome,
|
|
122
|
-
configPath: buildConfigPath(skillmuxHome)
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/discovery/discover-agents.ts
|
|
127
|
-
import * as fs2 from "fs/promises";
|
|
128
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
129
|
-
|
|
130
|
-
// src/config/load-user-config.ts
|
|
131
|
-
import * as fs from "fs/promises";
|
|
132
|
-
import { z } from "zod";
|
|
133
|
-
var supportedPlatformSchema = z.enum(supportedPlatforms);
|
|
134
|
-
var agentOverrideSchema = z.object({
|
|
135
|
-
stableName: z.string().min(1).optional(),
|
|
136
|
-
supportedPlatforms: z.array(supportedPlatformSchema).min(1).optional(),
|
|
137
|
-
homeRelativeRootPath: z.string().min(1).optional(),
|
|
138
|
-
skillsDirectoryPath: z.string().min(1).optional(),
|
|
139
|
-
enabledByDefault: z.boolean().optional()
|
|
140
|
-
}).strict();
|
|
141
|
-
var userConfigSchema = z.object({
|
|
142
|
-
version: z.literal(1),
|
|
143
|
-
agents: z.record(z.string().min(1), agentOverrideSchema)
|
|
144
|
-
}).strict();
|
|
145
|
-
function createEmptyUserConfig() {
|
|
146
|
-
return {
|
|
147
|
-
version: 1,
|
|
148
|
-
agents: {}
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
function stripUtf8Bom(contents) {
|
|
152
|
-
return contents.charCodeAt(0) === 65279 ? contents.slice(1) : contents;
|
|
153
|
-
}
|
|
154
|
-
function formatValidationIssues(error) {
|
|
155
|
-
return error.issues.map((issue) => {
|
|
156
|
-
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
157
|
-
return `${path}: ${issue.message}`;
|
|
158
|
-
}).join("; ");
|
|
159
|
-
}
|
|
160
|
-
async function loadUserConfig(skillmuxHome) {
|
|
161
|
-
const configPath = buildConfigPath(skillmuxHome);
|
|
162
|
-
try {
|
|
163
|
-
const contents = await fs.readFile(configPath, "utf8");
|
|
164
|
-
const parsed = JSON.parse(stripUtf8Bom(contents));
|
|
165
|
-
const validated = userConfigSchema.safeParse(parsed);
|
|
166
|
-
if (!validated.success) {
|
|
167
|
-
throw new UserConfigValidationError(
|
|
168
|
-
`Invalid config at ${configPath}: ${formatValidationIssues(validated.error)}`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
return validated.data;
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
174
|
-
return createEmptyUserConfig();
|
|
175
|
-
}
|
|
176
|
-
if (error instanceof SyntaxError) {
|
|
177
|
-
throw new UserConfigValidationError(
|
|
178
|
-
`Invalid config at ${configPath}: malformed JSON`
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/discovery/discover-agents.ts
|
|
186
|
-
function mergeRule(rule, override) {
|
|
187
|
-
if (override === void 0) {
|
|
188
|
-
return rule;
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
...rule,
|
|
192
|
-
...override
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function buildCustomRule(id, override) {
|
|
196
|
-
if (override.homeRelativeRootPath === void 0 || override.skillsDirectoryPath === void 0) {
|
|
197
|
-
throw new Error(
|
|
198
|
-
`Custom agent override "${id}" must define homeRelativeRootPath and skillsDirectoryPath`
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
id,
|
|
203
|
-
stableName: override.stableName ?? id,
|
|
204
|
-
supportedPlatforms: override.supportedPlatforms ?? [...supportedPlatforms],
|
|
205
|
-
homeRelativeRootPath: override.homeRelativeRootPath,
|
|
206
|
-
skillsDirectoryPath: override.skillsDirectoryPath,
|
|
207
|
-
enabledByDefault: override.enabledByDefault ?? true,
|
|
208
|
-
discovery: "custom"
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
async function pathExists(path) {
|
|
212
|
-
try {
|
|
213
|
-
await fs2.access(path);
|
|
214
|
-
return true;
|
|
215
|
-
} catch {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function resolveAgentRulePaths(homeDir, rule) {
|
|
220
|
-
const absoluteRootPath = resolve2(homeDir, rule.homeRelativeRootPath);
|
|
221
|
-
return {
|
|
222
|
-
absoluteRootPath,
|
|
223
|
-
absoluteSkillsDirectoryPath: join2(
|
|
224
|
-
absoluteRootPath,
|
|
225
|
-
rule.skillsDirectoryPath
|
|
226
|
-
)
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
async function discoverAgents(options) {
|
|
230
|
-
const platform = options.platform ?? process.platform;
|
|
231
|
-
const homeDir = resolve2(options.homeDir);
|
|
232
|
-
const skillmuxHome = options.skillmuxHome ?? resolveSkillmuxHome(homeDir).skillmuxHome;
|
|
233
|
-
const userConfig = await loadUserConfig(skillmuxHome);
|
|
234
|
-
const discoveredAgents = [];
|
|
235
|
-
for (const agentId of builtInAgentIds) {
|
|
236
|
-
const mergedRule = mergeRule(
|
|
237
|
-
defaultAgentRuleMap[agentId],
|
|
238
|
-
userConfig.agents[agentId]
|
|
239
|
-
);
|
|
240
|
-
const resolvedPaths = resolveAgentRulePaths(homeDir, mergedRule);
|
|
241
|
-
discoveredAgents.push({
|
|
242
|
-
...mergedRule,
|
|
243
|
-
...resolvedPaths,
|
|
244
|
-
exists: await pathExists(resolvedPaths.absoluteSkillsDirectoryPath),
|
|
245
|
-
supportedOnPlatform: mergedRule.supportedPlatforms.some(
|
|
246
|
-
(supportedPlatform) => supportedPlatform === platform
|
|
247
|
-
)
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
for (const [agentId, override] of Object.entries(userConfig.agents)) {
|
|
251
|
-
if (Object.hasOwn(defaultAgentRuleMap, agentId)) {
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
const customRule = buildCustomRule(agentId, override);
|
|
255
|
-
const resolvedPaths = resolveAgentRulePaths(homeDir, customRule);
|
|
256
|
-
discoveredAgents.push({
|
|
257
|
-
...customRule,
|
|
258
|
-
...resolvedPaths,
|
|
259
|
-
exists: await pathExists(resolvedPaths.absoluteSkillsDirectoryPath),
|
|
260
|
-
supportedOnPlatform: customRule.supportedPlatforms.some(
|
|
261
|
-
(supportedPlatform) => supportedPlatform === platform
|
|
262
|
-
)
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
return discoveredAgents;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/discovery/scan-agent-skills.ts
|
|
269
|
-
import * as fs5 from "fs/promises";
|
|
270
|
-
import { join as join3 } from "path";
|
|
271
|
-
|
|
272
|
-
// src/discovery/infer-skill-entry.ts
|
|
273
|
-
import * as fs4 from "fs/promises";
|
|
274
|
-
import { basename, resolve as resolve4 } from "path";
|
|
275
|
-
|
|
276
|
-
// src/fs/path-utils.ts
|
|
277
|
-
import * as fs3 from "fs/promises";
|
|
278
|
-
import { dirname, parse, relative, resolve as resolve3, sep } from "path";
|
|
279
|
-
function normalizeAbsolutePath(path) {
|
|
280
|
-
const normalized = resolve3(path);
|
|
281
|
-
return process.platform === "win32" ? normalized.replaceAll("/", "\\").toLowerCase() : normalized;
|
|
282
|
-
}
|
|
283
|
-
function pathsAreEqual(left, right) {
|
|
284
|
-
return normalizeAbsolutePath(left) === normalizeAbsolutePath(right);
|
|
285
|
-
}
|
|
286
|
-
function isPathInside(parentPath, childPath) {
|
|
287
|
-
const parent = normalizeAbsolutePath(parentPath);
|
|
288
|
-
const child = normalizeAbsolutePath(childPath);
|
|
289
|
-
if (parse(parent).root !== parse(child).root) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
const relativePath = relative(parent, child);
|
|
293
|
-
if (relativePath === "") {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
if (relativePath === "..") {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (relativePath.startsWith(`..${sep}`)) {
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
async function assertNoSymlinkAncestors(path, options) {
|
|
305
|
-
let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
|
|
306
|
-
while (true) {
|
|
307
|
-
try {
|
|
308
|
-
const entry = await fs3.lstat(current);
|
|
309
|
-
if (entry.isSymbolicLink()) {
|
|
310
|
-
throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
|
|
311
|
-
}
|
|
312
|
-
} catch (error) {
|
|
313
|
-
if (error.code !== "ENOENT") {
|
|
314
|
-
throw error;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const parent = dirname(current);
|
|
318
|
-
if (parent === current) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
current = parent;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/discovery/infer-skill-entry.ts
|
|
326
|
-
function buildIssue(code, severity, message, path) {
|
|
327
|
-
return { code, severity, message, path };
|
|
328
|
-
}
|
|
329
|
-
async function inferSkillEntry(options) {
|
|
330
|
-
const absolutePath = resolve4(options.path);
|
|
331
|
-
const skillName = basename(absolutePath);
|
|
332
|
-
const stats = await fs4.lstat(absolutePath);
|
|
333
|
-
if (stats.isSymbolicLink()) {
|
|
334
|
-
try {
|
|
335
|
-
const targetPath = await fs4.realpath(absolutePath);
|
|
336
|
-
if (isPathInside(options.skillmuxHome, targetPath)) {
|
|
337
|
-
return {
|
|
338
|
-
entry: {
|
|
339
|
-
agentId: options.agentId,
|
|
340
|
-
agentName: options.agentName,
|
|
341
|
-
skillName,
|
|
342
|
-
kind: "managed-link",
|
|
343
|
-
path: absolutePath,
|
|
344
|
-
targetPath
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
entry: {
|
|
350
|
-
agentId: options.agentId,
|
|
351
|
-
agentName: options.agentName,
|
|
352
|
-
skillName,
|
|
353
|
-
kind: "unmanaged-link",
|
|
354
|
-
path: absolutePath,
|
|
355
|
-
targetPath
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
} catch (error) {
|
|
359
|
-
if (error.code !== "ENOENT") {
|
|
360
|
-
throw error;
|
|
361
|
-
}
|
|
362
|
-
return {
|
|
363
|
-
entry: {
|
|
364
|
-
agentId: options.agentId,
|
|
365
|
-
agentName: options.agentName,
|
|
366
|
-
skillName,
|
|
367
|
-
kind: "broken-link",
|
|
368
|
-
path: absolutePath
|
|
369
|
-
},
|
|
370
|
-
issue: buildIssue(
|
|
371
|
-
"broken-link",
|
|
372
|
-
"error",
|
|
373
|
-
"Skill entry points to a missing target",
|
|
374
|
-
absolutePath
|
|
375
|
-
)
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (stats.isDirectory()) {
|
|
380
|
-
return {
|
|
381
|
-
entry: {
|
|
382
|
-
agentId: options.agentId,
|
|
383
|
-
agentName: options.agentName,
|
|
384
|
-
skillName,
|
|
385
|
-
kind: "unmanaged-directory",
|
|
386
|
-
path: absolutePath
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
entry: {
|
|
392
|
-
agentId: options.agentId,
|
|
393
|
-
agentName: options.agentName,
|
|
394
|
-
skillName,
|
|
395
|
-
kind: "unknown",
|
|
396
|
-
path: absolutePath
|
|
397
|
-
},
|
|
398
|
-
issue: buildIssue(
|
|
399
|
-
"unknown-entry",
|
|
400
|
-
"warning",
|
|
401
|
-
"Skill entry is neither a managed link nor a directory",
|
|
402
|
-
absolutePath
|
|
403
|
-
)
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// src/discovery/scan-agent-skills.ts
|
|
408
|
-
async function scanAgentSkills(agent, skillmuxHome) {
|
|
409
|
-
if (!agent.exists || !agent.supportedOnPlatform) {
|
|
410
|
-
return {
|
|
411
|
-
entries: [],
|
|
412
|
-
issues: []
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
const directoryEntries = await fs5.readdir(agent.absoluteSkillsDirectoryPath, {
|
|
416
|
-
withFileTypes: true
|
|
417
|
-
});
|
|
418
|
-
const sortedDirectoryEntries = [...directoryEntries].sort(
|
|
419
|
-
(left, right) => left.name.localeCompare(right.name)
|
|
420
|
-
);
|
|
421
|
-
const entries = [];
|
|
422
|
-
const issues = [];
|
|
423
|
-
for (const directoryEntry of sortedDirectoryEntries) {
|
|
424
|
-
const result = await inferSkillEntry({
|
|
425
|
-
agentId: agent.id,
|
|
426
|
-
agentName: agent.stableName,
|
|
427
|
-
path: join3(agent.absoluteSkillsDirectoryPath, directoryEntry.name),
|
|
428
|
-
skillmuxHome
|
|
429
|
-
});
|
|
430
|
-
entries.push(result.entry);
|
|
431
|
-
if (result.issue !== void 0) {
|
|
432
|
-
issues.push(result.issue);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
entries,
|
|
437
|
-
issues
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// src/fs/safe-copy.ts
|
|
442
|
-
import * as fs6 from "fs/promises";
|
|
443
|
-
import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
|
|
444
|
-
async function assertDirectory(path) {
|
|
445
|
-
const entry = await fs6.lstat(path);
|
|
446
|
-
if (!entry.isDirectory()) {
|
|
447
|
-
throw new Error(`Expected a directory at ${path}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
async function assertRegularFile(path, label) {
|
|
451
|
-
const entry = await fs6.lstat(path);
|
|
452
|
-
if (!entry.isFile()) {
|
|
453
|
-
throw new Error(`Expected ${label} to be a regular file at ${path}`);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
async function assertTargetDoesNotExist(path) {
|
|
457
|
-
try {
|
|
458
|
-
await fs6.lstat(path);
|
|
459
|
-
throw new Error(`Refusing to overwrite existing path at ${path}`);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
if (error.code !== "ENOENT") {
|
|
462
|
-
throw error;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
async function copyDirectoryContents(sourcePath, targetPath) {
|
|
467
|
-
await fs6.mkdir(targetPath, { recursive: true });
|
|
468
|
-
const entries = await fs6.readdir(sourcePath, { withFileTypes: true });
|
|
469
|
-
for (const entry of entries) {
|
|
470
|
-
const sourceEntryPath = join4(sourcePath, entry.name);
|
|
471
|
-
const targetEntryPath = join4(targetPath, entry.name);
|
|
472
|
-
const entryStats = await fs6.lstat(sourceEntryPath);
|
|
473
|
-
if (entryStats.isSymbolicLink()) {
|
|
474
|
-
throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
|
|
475
|
-
}
|
|
476
|
-
if (entryStats.isDirectory()) {
|
|
477
|
-
await copyDirectoryContents(sourceEntryPath, targetEntryPath);
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
if (entryStats.isFile()) {
|
|
481
|
-
await fs6.mkdir(dirname2(targetEntryPath), { recursive: true });
|
|
482
|
-
await fs6.copyFile(sourceEntryPath, targetEntryPath);
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async function assertSkillSourceLayout(sourcePath) {
|
|
489
|
-
const resolvedSourcePath = resolve5(sourcePath);
|
|
490
|
-
const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
|
|
491
|
-
await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
|
|
492
|
-
await assertDirectory(resolvedSourcePath);
|
|
493
|
-
try {
|
|
494
|
-
await assertRegularFile(skillFilePath, "SKILL.md");
|
|
495
|
-
} catch (error) {
|
|
496
|
-
if (error.code === "ENOENT") {
|
|
497
|
-
throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
|
|
498
|
-
}
|
|
499
|
-
throw error;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
async function hasRootSkillFile(sourcePath) {
|
|
503
|
-
const resolvedSourcePath = resolve5(sourcePath);
|
|
504
|
-
const skillFilePath = join4(resolvedSourcePath, "SKILL.md");
|
|
505
|
-
try {
|
|
506
|
-
await assertDirectory(resolvedSourcePath);
|
|
507
|
-
await assertRegularFile(skillFilePath, "SKILL.md");
|
|
508
|
-
return true;
|
|
509
|
-
} catch (error) {
|
|
510
|
-
if (error.code === "ENOENT") {
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
throw error;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
async function copySkillContentsToManagedStore(sourcePath, targetPath) {
|
|
517
|
-
const resolvedSourcePath = resolve5(sourcePath);
|
|
518
|
-
const resolvedTargetPath = resolve5(targetPath);
|
|
519
|
-
if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
|
|
520
|
-
throw new Error("Source and target paths must differ");
|
|
521
|
-
}
|
|
522
|
-
if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
|
|
523
|
-
throw new Error("Refusing to copy into a child of the source directory");
|
|
524
|
-
}
|
|
525
|
-
await assertSkillSourceLayout(resolvedSourcePath);
|
|
526
|
-
await assertNoSymlinkAncestors(resolvedTargetPath);
|
|
527
|
-
await assertTargetDoesNotExist(resolvedTargetPath);
|
|
528
|
-
await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// src/core/batch-operation-error.ts
|
|
532
|
-
function getCauseMessage(cause) {
|
|
533
|
-
if (cause instanceof Error) {
|
|
534
|
-
return cause.message;
|
|
535
|
-
}
|
|
536
|
-
return String(cause);
|
|
537
|
-
}
|
|
538
|
-
function buildBatchOperationMessage(options) {
|
|
539
|
-
const completedSuffix = options.completedItems.length > 0 ? ` after ${options.completedAction}: ${options.completedItems.join(", ")}` : "";
|
|
540
|
-
return `Failed to ${options.failedAction}${completedSuffix}: ${getCauseMessage(options.cause)}`;
|
|
541
|
-
}
|
|
542
|
-
var BatchOperationError = class extends Error {
|
|
543
|
-
operation;
|
|
544
|
-
failedItem;
|
|
545
|
-
completedItems;
|
|
546
|
-
cause;
|
|
547
|
-
constructor(options) {
|
|
548
|
-
super(buildBatchOperationMessage(options), { cause: options.cause });
|
|
549
|
-
this.name = "BatchOperationError";
|
|
550
|
-
this.operation = options.operation;
|
|
551
|
-
this.failedItem = options.failedItem;
|
|
552
|
-
this.completedItems = [...options.completedItems];
|
|
553
|
-
this.cause = options.cause;
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
// src/fs/link-ops.ts
|
|
558
|
-
import * as fs7 from "fs/promises";
|
|
559
|
-
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
560
|
-
var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
|
|
561
|
-
async function createManagedLink(linkPath, targetPath) {
|
|
562
|
-
const resolvedLinkPath = resolve6(linkPath);
|
|
563
|
-
const resolvedTargetPath = resolve6(targetPath);
|
|
564
|
-
await assertNoSymlinkAncestors(resolvedLinkPath);
|
|
565
|
-
await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
|
|
566
|
-
await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
|
|
567
|
-
try {
|
|
568
|
-
const existingEntry = await fs7.lstat(resolvedLinkPath);
|
|
569
|
-
if (!existingEntry.isSymbolicLink()) {
|
|
570
|
-
throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
|
|
571
|
-
}
|
|
572
|
-
const currentTargetPath = await fs7.realpath(resolvedLinkPath);
|
|
573
|
-
if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
|
|
577
|
-
} catch (error) {
|
|
578
|
-
if (error.code === "ENOENT" && await fs7.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
|
|
579
|
-
await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
|
|
580
|
-
} else if (error.code !== "ENOENT") {
|
|
581
|
-
throw error;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
|
|
585
|
-
}
|
|
586
|
-
async function replaceEntryWithManagedLink(linkPath, targetPath, expectedCurrentPath) {
|
|
587
|
-
const resolvedLinkPath = resolve6(linkPath);
|
|
588
|
-
const resolvedTargetPath = resolve6(targetPath);
|
|
589
|
-
const resolvedExpectedCurrentPath = resolve6(expectedCurrentPath);
|
|
590
|
-
await assertNoSymlinkAncestors(resolvedLinkPath);
|
|
591
|
-
await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
|
|
592
|
-
await fs7.mkdir(dirname3(resolvedLinkPath), { recursive: true });
|
|
593
|
-
const existingEntry = await fs7.lstat(resolvedLinkPath);
|
|
594
|
-
if (existingEntry.isSymbolicLink()) {
|
|
595
|
-
const currentTargetPath = await fs7.realpath(resolvedLinkPath);
|
|
596
|
-
if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
|
|
597
|
-
return false;
|
|
598
|
-
}
|
|
599
|
-
if (!pathsAreEqual(currentTargetPath, resolvedExpectedCurrentPath)) {
|
|
600
|
-
throw new Error(`Refusing to replace unexpected link at ${resolvedLinkPath}`);
|
|
601
|
-
}
|
|
602
|
-
await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
|
|
603
|
-
await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
|
|
604
|
-
return true;
|
|
605
|
-
}
|
|
606
|
-
if (!existingEntry.isDirectory()) {
|
|
607
|
-
throw new Error(`Refusing to replace non-directory entry at ${resolvedLinkPath}`);
|
|
608
|
-
}
|
|
609
|
-
const currentPath = await fs7.realpath(resolvedLinkPath);
|
|
610
|
-
if (!pathsAreEqual(currentPath, resolvedExpectedCurrentPath)) {
|
|
611
|
-
throw new Error(`Refusing to replace unexpected directory at ${resolvedLinkPath}`);
|
|
612
|
-
}
|
|
613
|
-
await fs7.rm(resolvedLinkPath, { recursive: true, force: false });
|
|
614
|
-
await fs7.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
|
|
615
|
-
return true;
|
|
616
|
-
}
|
|
617
|
-
async function isLinkPointingToTarget(linkPath, targetPath) {
|
|
618
|
-
try {
|
|
619
|
-
const entry = await fs7.lstat(linkPath);
|
|
620
|
-
if (!entry.isSymbolicLink()) {
|
|
621
|
-
return false;
|
|
622
|
-
}
|
|
623
|
-
const resolvedTargetPath = await fs7.realpath(linkPath);
|
|
624
|
-
return pathsAreEqual(resolvedTargetPath, targetPath);
|
|
625
|
-
} catch (error) {
|
|
626
|
-
if (error.code === "ENOENT") {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// src/manifest/read-manifest.ts
|
|
634
|
-
import * as fs9 from "fs/promises";
|
|
635
|
-
import { join as join6, resolve as resolve7 } from "path";
|
|
636
|
-
|
|
637
|
-
// src/manifest/build-empty-manifest.ts
|
|
638
|
-
function buildEmptyManifest(skillmuxHome) {
|
|
639
|
-
return {
|
|
640
|
-
version: 1,
|
|
641
|
-
skillmuxHome,
|
|
642
|
-
skills: {},
|
|
643
|
-
agents: {},
|
|
644
|
-
activations: [],
|
|
645
|
-
lastScan: {
|
|
646
|
-
at: null,
|
|
647
|
-
issues: []
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// src/manifest/manifest-schema.ts
|
|
653
|
-
import { z as z2 } from "zod";
|
|
654
|
-
var idSchema = z2.string().min(1).refine(isValidId, "Expected a canonical lowercase slug identifier");
|
|
655
|
-
var scanIssueSchema = z2.object({
|
|
656
|
-
code: z2.string().min(1),
|
|
657
|
-
severity: z2.enum(["info", "warning", "error"]),
|
|
658
|
-
message: z2.string().min(1),
|
|
659
|
-
path: z2.string().min(1).optional()
|
|
660
|
-
}).strict();
|
|
661
|
-
var managedSkillSchema = z2.object({
|
|
662
|
-
id: idSchema,
|
|
663
|
-
name: z2.string().min(1),
|
|
664
|
-
path: z2.string().min(1),
|
|
665
|
-
source: z2.object({
|
|
666
|
-
kind: z2.enum(["local", "imported"]),
|
|
667
|
-
path: z2.string().min(1)
|
|
668
|
-
}).strict(),
|
|
669
|
-
importedAt: z2.string().min(1)
|
|
670
|
-
}).strict();
|
|
671
|
-
var agentRecordSchema = z2.object({
|
|
672
|
-
id: idSchema,
|
|
673
|
-
name: z2.string().min(1),
|
|
674
|
-
path: z2.string().min(1),
|
|
675
|
-
discovery: z2.enum(["builtin", "custom"]),
|
|
676
|
-
available: z2.boolean(),
|
|
677
|
-
lastSeenAt: z2.string().min(1).nullable()
|
|
678
|
-
}).strict();
|
|
679
|
-
var activationRecordSchema = z2.object({
|
|
680
|
-
skillId: idSchema,
|
|
681
|
-
agentId: idSchema,
|
|
682
|
-
linkPath: z2.string().min(1),
|
|
683
|
-
state: z2.enum(["enabled", "disabled"]),
|
|
684
|
-
updatedAt: z2.string().min(1)
|
|
685
|
-
}).strict();
|
|
686
|
-
var manifestSchema = z2.object({
|
|
687
|
-
version: z2.literal(1),
|
|
688
|
-
skillmuxHome: z2.string().min(1),
|
|
689
|
-
skills: z2.record(z2.string(), managedSkillSchema),
|
|
690
|
-
agents: z2.record(z2.string(), agentRecordSchema),
|
|
691
|
-
activations: z2.array(activationRecordSchema),
|
|
692
|
-
lastScan: z2.object({
|
|
693
|
-
at: z2.string().min(1).nullable(),
|
|
694
|
-
issues: z2.array(scanIssueSchema)
|
|
695
|
-
}).strict()
|
|
696
|
-
}).strict().superRefine((manifest, ctx) => {
|
|
697
|
-
for (const [skillId, skill] of Object.entries(manifest.skills)) {
|
|
698
|
-
if (!isValidId(skillId)) {
|
|
699
|
-
ctx.addIssue({
|
|
700
|
-
code: z2.ZodIssueCode.custom,
|
|
701
|
-
path: ["skills", skillId],
|
|
702
|
-
message: `Invalid skill id key: ${skillId}`
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
if (skill.id !== skillId) {
|
|
706
|
-
ctx.addIssue({
|
|
707
|
-
code: z2.ZodIssueCode.custom,
|
|
708
|
-
path: ["skills", skillId, "id"],
|
|
709
|
-
message: `Skill id must match its record key: ${skillId}`
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
for (const [agentId, agent] of Object.entries(manifest.agents)) {
|
|
714
|
-
if (!isValidId(agentId)) {
|
|
715
|
-
ctx.addIssue({
|
|
716
|
-
code: z2.ZodIssueCode.custom,
|
|
717
|
-
path: ["agents", agentId],
|
|
718
|
-
message: `Invalid agent id key: ${agentId}`
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
if (agent.id !== agentId) {
|
|
722
|
-
ctx.addIssue({
|
|
723
|
-
code: z2.ZodIssueCode.custom,
|
|
724
|
-
path: ["agents", agentId, "id"],
|
|
725
|
-
message: `Agent id must match its record key: ${agentId}`
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
const activationPairs = /* @__PURE__ */ new Set();
|
|
730
|
-
manifest.activations.forEach((activation, index) => {
|
|
731
|
-
if (!(activation.skillId in manifest.skills)) {
|
|
732
|
-
ctx.addIssue({
|
|
733
|
-
code: z2.ZodIssueCode.custom,
|
|
734
|
-
path: ["activations", index, "skillId"],
|
|
735
|
-
message: `Unknown skill reference: ${activation.skillId}`
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
if (!(activation.agentId in manifest.agents)) {
|
|
739
|
-
ctx.addIssue({
|
|
740
|
-
code: z2.ZodIssueCode.custom,
|
|
741
|
-
path: ["activations", index, "agentId"],
|
|
742
|
-
message: `Unknown agent reference: ${activation.agentId}`
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
const pairKey = `${activation.skillId}:${activation.agentId}`;
|
|
746
|
-
if (activationPairs.has(pairKey)) {
|
|
747
|
-
ctx.addIssue({
|
|
748
|
-
code: z2.ZodIssueCode.custom,
|
|
749
|
-
path: ["activations", index],
|
|
750
|
-
message: `Duplicate activation for ${pairKey}`
|
|
751
|
-
});
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
activationPairs.add(pairKey);
|
|
755
|
-
});
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
// src/manifest/write-manifest.ts
|
|
759
|
-
import { randomUUID } from "crypto";
|
|
760
|
-
import * as fs8 from "fs/promises";
|
|
761
|
-
import { join as join5 } from "path";
|
|
762
|
-
function getManifestPath(home) {
|
|
763
|
-
return join5(home, "manifest.json");
|
|
764
|
-
}
|
|
765
|
-
function createManifestTempPath(manifestPath) {
|
|
766
|
-
return `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
767
|
-
}
|
|
768
|
-
async function writeManifest(home, manifest) {
|
|
769
|
-
await fs8.mkdir(home, { recursive: true });
|
|
770
|
-
const manifestPath = getManifestPath(home);
|
|
771
|
-
const tempPath = createManifestTempPath(manifestPath);
|
|
772
|
-
const contents = `${JSON.stringify(manifest, null, 2)}
|
|
773
|
-
`;
|
|
774
|
-
await fs8.writeFile(tempPath, contents, "utf8");
|
|
775
|
-
try {
|
|
776
|
-
await fs8.rename(tempPath, manifestPath);
|
|
777
|
-
} catch (error) {
|
|
778
|
-
await fs8.unlink(tempPath).catch(() => void 0);
|
|
779
|
-
throw error;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// src/manifest/read-manifest.ts
|
|
784
|
-
function getManifestPath2(home) {
|
|
785
|
-
return join6(home, "manifest.json");
|
|
786
|
-
}
|
|
787
|
-
function normalizeHomePath(home) {
|
|
788
|
-
const resolvedHome = resolve7(home);
|
|
789
|
-
return process.platform === "win32" ? resolvedHome.toLowerCase() : resolvedHome;
|
|
790
|
-
}
|
|
791
|
-
function formatValidationIssues2(error) {
|
|
792
|
-
return error.issues.map((issue) => {
|
|
793
|
-
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
794
|
-
return `${path}: ${issue.message}`;
|
|
795
|
-
}).join("; ");
|
|
796
|
-
}
|
|
797
|
-
async function readManifest(home) {
|
|
798
|
-
const manifestPath = getManifestPath2(home);
|
|
799
|
-
try {
|
|
800
|
-
const contents = await fs9.readFile(manifestPath, "utf8");
|
|
801
|
-
const parsedJson = JSON.parse(contents);
|
|
802
|
-
const parsedManifest = manifestSchema.safeParse(parsedJson);
|
|
803
|
-
if (!parsedManifest.success) {
|
|
804
|
-
throw new ManifestValidationError(
|
|
805
|
-
`Invalid manifest at ${manifestPath}: ${formatValidationIssues2(parsedManifest.error)}`
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
if (normalizeHomePath(parsedManifest.data.skillmuxHome) !== normalizeHomePath(home)) {
|
|
809
|
-
throw new ManifestValidationError(
|
|
810
|
-
`Invalid manifest at ${manifestPath}: skillmuxHome must match ${home}`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
return parsedManifest.data;
|
|
814
|
-
} catch (error) {
|
|
815
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
816
|
-
const emptyManifest = buildEmptyManifest(home);
|
|
817
|
-
await writeManifest(home, emptyManifest);
|
|
818
|
-
return emptyManifest;
|
|
819
|
-
}
|
|
820
|
-
if (error instanceof SyntaxError) {
|
|
821
|
-
throw new ManifestValidationError(
|
|
822
|
-
`Invalid manifest at ${manifestPath}: malformed JSON`
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
throw error;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// src/output/print-json.ts
|
|
830
|
-
function printJson(value) {
|
|
831
|
-
return `${JSON.stringify(value, null, 2)}
|
|
832
|
-
`;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// src/commands/adopt.ts
|
|
836
|
-
function buildManagedSkillPath(skillmuxHome, skillId) {
|
|
837
|
-
return resolve8(skillmuxHome, "skills", skillId);
|
|
838
|
-
}
|
|
839
|
-
function buildAgentRecord(agent, timestamp) {
|
|
840
|
-
return {
|
|
841
|
-
id: agent.id,
|
|
842
|
-
name: agent.stableName,
|
|
843
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
844
|
-
discovery: agent.discovery,
|
|
845
|
-
available: agent.exists && agent.supportedOnPlatform,
|
|
846
|
-
lastSeenAt: agent.exists ? timestamp : null
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
function buildActivationRecord(skillId, agentId, linkPath, timestamp) {
|
|
850
|
-
return {
|
|
851
|
-
skillId,
|
|
852
|
-
agentId,
|
|
853
|
-
linkPath,
|
|
854
|
-
state: "enabled",
|
|
855
|
-
updatedAt: timestamp
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
function upsertActivation(manifest, activation) {
|
|
859
|
-
const index = manifest.activations.findIndex(
|
|
860
|
-
(entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
|
|
861
|
-
);
|
|
862
|
-
if (index === -1) {
|
|
863
|
-
manifest.activations.push(activation);
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
manifest.activations[index] = activation;
|
|
867
|
-
}
|
|
868
|
-
async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
|
|
869
|
-
const agentId = normalizeId(agentName);
|
|
870
|
-
const agents = await discoverAgents({ homeDir, skillmuxHome });
|
|
871
|
-
const agent = agents.find((entry) => entry.id === agentId);
|
|
872
|
-
if (agent === void 0) {
|
|
873
|
-
throw new AdoptionError(`Unknown agent: ${agentName}`);
|
|
874
|
-
}
|
|
875
|
-
if (!agent.supportedOnPlatform) {
|
|
876
|
-
throw new AdoptionError(
|
|
877
|
-
`Agent ${agent.id} is not supported on ${process.platform}`
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
return agent;
|
|
881
|
-
}
|
|
882
|
-
function filterEntries(scannedAgent, skillFilter) {
|
|
883
|
-
if (skillFilter === void 0) {
|
|
884
|
-
return scannedAgent.entries;
|
|
885
|
-
}
|
|
886
|
-
const skillId = normalizeId(skillFilter);
|
|
887
|
-
return scannedAgent.entries.filter(
|
|
888
|
-
(entry) => normalizeId(entry.skillName) === skillId
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
async function resolveAdoptionSource(entry) {
|
|
892
|
-
if (entry.kind === "unmanaged-link") {
|
|
893
|
-
return entry.targetPath;
|
|
894
|
-
}
|
|
895
|
-
if (entry.kind === "unmanaged-directory") {
|
|
896
|
-
return entry.path;
|
|
897
|
-
}
|
|
898
|
-
return void 0;
|
|
899
|
-
}
|
|
900
|
-
function buildManagedSkill(skillId, skillName, managedPath, sourcePath, timestamp) {
|
|
901
|
-
return {
|
|
902
|
-
id: skillId,
|
|
903
|
-
name: skillName,
|
|
904
|
-
path: managedPath,
|
|
905
|
-
source: {
|
|
906
|
-
kind: "imported",
|
|
907
|
-
path: sourcePath
|
|
908
|
-
},
|
|
909
|
-
importedAt: timestamp
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
async function reconcileManagedLink(manifest, skillId, entry, agentId, timestamp) {
|
|
913
|
-
if (entry.targetPath === void 0) {
|
|
914
|
-
throw new AdoptionError(`Managed link target is missing for ${entry.path}`);
|
|
915
|
-
}
|
|
916
|
-
await assertSkillSourceLayout(entry.targetPath);
|
|
917
|
-
const skill = manifest.skills[skillId];
|
|
918
|
-
if (skill === void 0 || !pathsAreEqual(skill.path, entry.targetPath)) {
|
|
919
|
-
throw new AdoptionError(
|
|
920
|
-
`Managed link for ${agentId}/${skillId} has no matching manifest skill record`
|
|
921
|
-
);
|
|
922
|
-
}
|
|
923
|
-
upsertActivation(
|
|
924
|
-
manifest,
|
|
925
|
-
buildActivationRecord(skillId, agentId, entry.path, timestamp)
|
|
926
|
-
);
|
|
927
|
-
}
|
|
928
|
-
function buildOutput(result, json) {
|
|
929
|
-
if (json) {
|
|
930
|
-
return printJson({
|
|
931
|
-
agent: result.agent,
|
|
932
|
-
adopted: result.adopted,
|
|
933
|
-
skipped: result.skipped
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
if (result.adopted.length === 0) {
|
|
937
|
-
return `No skills adopted for ${result.agent.id}.
|
|
938
|
-
`;
|
|
939
|
-
}
|
|
940
|
-
const adoptedSkills = result.adopted.map((entry) => entry.skillId).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
941
|
-
return `Adopted ${adoptedSkills} for ${result.agent.id}
|
|
942
|
-
`;
|
|
943
|
-
}
|
|
944
|
-
async function runAdoptSingle(options) {
|
|
945
|
-
const homeDir = options.homeDir ?? homedir();
|
|
946
|
-
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
947
|
-
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
948
|
-
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
949
|
-
const manifest = await readManifest(skillmuxHome);
|
|
950
|
-
const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
|
|
951
|
-
const agentRecord = buildAgentRecord(agent, timestamp);
|
|
952
|
-
const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
|
|
953
|
-
const entries = filterEntries(scannedAgent, options.skill);
|
|
954
|
-
const adopted = [];
|
|
955
|
-
const skipped = [];
|
|
956
|
-
manifest.agents[agent.id] = agentRecord;
|
|
957
|
-
for (const entry of entries) {
|
|
958
|
-
const skillId = normalizeId(entry.skillName);
|
|
959
|
-
if (entry.kind === "managed-link") {
|
|
960
|
-
await reconcileManagedLink(
|
|
961
|
-
manifest,
|
|
962
|
-
skillId,
|
|
963
|
-
entry,
|
|
964
|
-
agent.id,
|
|
965
|
-
timestamp
|
|
966
|
-
);
|
|
967
|
-
skipped.push({
|
|
968
|
-
skillId,
|
|
969
|
-
agentId: agent.id,
|
|
970
|
-
path: entry.path,
|
|
971
|
-
reason: "already-managed"
|
|
972
|
-
});
|
|
973
|
-
await writeManifest(skillmuxHome, manifest);
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
|
-
const sourcePath = await resolveAdoptionSource(entry);
|
|
977
|
-
if (sourcePath === void 0) {
|
|
978
|
-
skipped.push({
|
|
979
|
-
skillId,
|
|
980
|
-
agentId: agent.id,
|
|
981
|
-
path: entry.path,
|
|
982
|
-
reason: "not-adoptable"
|
|
983
|
-
});
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
|
-
if (!await hasRootSkillFile(sourcePath)) {
|
|
987
|
-
skipped.push({
|
|
988
|
-
skillId,
|
|
989
|
-
agentId: agent.id,
|
|
990
|
-
path: entry.path,
|
|
991
|
-
reason: "missing-skill-file"
|
|
992
|
-
});
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
const managedPath = buildManagedSkillPath(skillmuxHome, skillId);
|
|
996
|
-
if (manifest.skills[skillId] === void 0) {
|
|
997
|
-
await copySkillContentsToManagedStore(sourcePath, managedPath);
|
|
998
|
-
manifest.skills[skillId] = buildManagedSkill(
|
|
999
|
-
skillId,
|
|
1000
|
-
entry.skillName,
|
|
1001
|
-
managedPath,
|
|
1002
|
-
sourcePath,
|
|
1003
|
-
timestamp
|
|
1004
|
-
);
|
|
1005
|
-
} else if (await isLinkPointingToTarget(entry.path, manifest.skills[skillId].path)) {
|
|
1006
|
-
skipped.push({
|
|
1007
|
-
skillId,
|
|
1008
|
-
agentId: agent.id,
|
|
1009
|
-
path: entry.path,
|
|
1010
|
-
reason: "already-managed"
|
|
1011
|
-
});
|
|
1012
|
-
continue;
|
|
1013
|
-
} else {
|
|
1014
|
-
await assertSkillSourceLayout(manifest.skills[skillId].path);
|
|
1015
|
-
}
|
|
1016
|
-
await replaceEntryWithManagedLink(
|
|
1017
|
-
entry.path,
|
|
1018
|
-
manifest.skills[skillId].path,
|
|
1019
|
-
sourcePath
|
|
1020
|
-
);
|
|
1021
|
-
const activation = buildActivationRecord(
|
|
1022
|
-
skillId,
|
|
1023
|
-
agent.id,
|
|
1024
|
-
join7(agent.absoluteSkillsDirectoryPath, entry.skillName),
|
|
1025
|
-
timestamp
|
|
1026
|
-
);
|
|
1027
|
-
upsertActivation(manifest, activation);
|
|
1028
|
-
adopted.push({
|
|
1029
|
-
skillId,
|
|
1030
|
-
agentId: agent.id,
|
|
1031
|
-
sourcePath,
|
|
1032
|
-
managedPath: manifest.skills[skillId].path,
|
|
1033
|
-
linkPath: activation.linkPath
|
|
1034
|
-
});
|
|
1035
|
-
await writeManifest(skillmuxHome, manifest);
|
|
1036
|
-
}
|
|
1037
|
-
await writeManifest(skillmuxHome, manifest);
|
|
1038
|
-
const resultWithoutOutput = {
|
|
1039
|
-
agent: agentRecord,
|
|
1040
|
-
adopted,
|
|
1041
|
-
skipped,
|
|
1042
|
-
manifest
|
|
1043
|
-
};
|
|
1044
|
-
return {
|
|
1045
|
-
...resultWithoutOutput,
|
|
1046
|
-
output: buildOutput(resultWithoutOutput, options.json === true)
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
async function runAdopt(options) {
|
|
1050
|
-
if (options.skills !== void 0) {
|
|
1051
|
-
const results = [];
|
|
1052
|
-
const completedSkills = [];
|
|
1053
|
-
for (const skill of options.skills) {
|
|
1054
|
-
try {
|
|
1055
|
-
results.push(await runAdoptSingle({ ...options, skill, skills: void 0 }));
|
|
1056
|
-
completedSkills.push(skill);
|
|
1057
|
-
} catch (error) {
|
|
1058
|
-
throw new BatchOperationError({
|
|
1059
|
-
operation: "adopt",
|
|
1060
|
-
failedItem: skill,
|
|
1061
|
-
failedAction: `adopt ${skill} for ${options.agent}`,
|
|
1062
|
-
completedAction: "adopting",
|
|
1063
|
-
completedItems: completedSkills,
|
|
1064
|
-
cause: error
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (results.length === 0) {
|
|
1069
|
-
throw new AdoptionError("Adopt requires at least one target skill");
|
|
1070
|
-
}
|
|
1071
|
-
const lastResult = results[results.length - 1];
|
|
1072
|
-
const resultWithoutOutput = {
|
|
1073
|
-
agent: lastResult.agent,
|
|
1074
|
-
adopted: results.flatMap((result) => result.adopted),
|
|
1075
|
-
skipped: results.flatMap((result) => result.skipped),
|
|
1076
|
-
manifest: lastResult.manifest,
|
|
1077
|
-
results
|
|
1078
|
-
};
|
|
1079
|
-
return {
|
|
1080
|
-
...resultWithoutOutput,
|
|
1081
|
-
output: options.json === true ? printJson({
|
|
1082
|
-
agent: resultWithoutOutput.agent,
|
|
1083
|
-
adopted: resultWithoutOutput.adopted,
|
|
1084
|
-
skipped: resultWithoutOutput.skipped,
|
|
1085
|
-
results: resultWithoutOutput.results
|
|
1086
|
-
}) : results.map((result) => result.output).join("")
|
|
1087
|
-
};
|
|
1088
|
-
}
|
|
1089
|
-
return runAdoptSingle(options);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// src/commands/agents.ts
|
|
1093
|
-
import { homedir as homedir2 } from "os";
|
|
1094
|
-
|
|
1095
|
-
// src/output/print-table.ts
|
|
1096
|
-
function printTable(rows, columns) {
|
|
1097
|
-
const renderedRows = rows.map(
|
|
1098
|
-
(row) => columns.map((column) => String(row[column.key] ?? ""))
|
|
1099
|
-
);
|
|
1100
|
-
const widths = columns.map(
|
|
1101
|
-
(column, index) => Math.max(
|
|
1102
|
-
column.label.length,
|
|
1103
|
-
...renderedRows.map((row) => row[index]?.length ?? 0)
|
|
1104
|
-
)
|
|
1105
|
-
);
|
|
1106
|
-
const header = columns.map((column, index) => column.label.padEnd(widths[index])).join(" ");
|
|
1107
|
-
const separator = widths.map((width) => "-".repeat(width)).join(" ");
|
|
1108
|
-
const body = renderedRows.map(
|
|
1109
|
-
(row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ")
|
|
1110
|
-
);
|
|
1111
|
-
return `${[header, separator, ...body].join("\n")}
|
|
1112
|
-
`;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// src/commands/agents.ts
|
|
1116
|
-
function buildTableOutput(agents) {
|
|
1117
|
-
return printTable(
|
|
1118
|
-
agents.map((agent) => ({
|
|
1119
|
-
id: agent.id,
|
|
1120
|
-
name: agent.stableName,
|
|
1121
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
1122
|
-
exists: String(agent.exists),
|
|
1123
|
-
supported: String(agent.supportedOnPlatform),
|
|
1124
|
-
discovery: agent.discovery
|
|
1125
|
-
})),
|
|
1126
|
-
[
|
|
1127
|
-
{ key: "id", label: "Agent" },
|
|
1128
|
-
{ key: "name", label: "Name" },
|
|
1129
|
-
{ key: "path", label: "Path" },
|
|
1130
|
-
{ key: "exists", label: "Exists" },
|
|
1131
|
-
{ key: "supported", label: "Supported" },
|
|
1132
|
-
{ key: "discovery", label: "Discovery" }
|
|
1133
|
-
]
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
async function runAgents(options = {}) {
|
|
1137
|
-
const homeDir = options.homeDir ?? homedir2();
|
|
1138
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1139
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1140
|
-
const agents = await discoverAgents({
|
|
1141
|
-
homeDir,
|
|
1142
|
-
skillmuxHome,
|
|
1143
|
-
platform: options.platform
|
|
1144
|
-
});
|
|
1145
|
-
return {
|
|
1146
|
-
agents,
|
|
1147
|
-
output: options.json === true ? printJson(agents) : buildTableOutput(agents)
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// src/commands/config-add-agent.ts
|
|
1152
|
-
import { homedir as homedir3 } from "os";
|
|
1153
|
-
|
|
1154
|
-
// src/config/agent-override-validation.ts
|
|
1155
|
-
import { isAbsolute } from "path";
|
|
1156
|
-
function normalizeRelativePath(value, field) {
|
|
1157
|
-
const trimmed = value.trim();
|
|
1158
|
-
if (trimmed.length === 0) {
|
|
1159
|
-
throw new UserConfigValidationError(`${field} must not be empty`);
|
|
1160
|
-
}
|
|
1161
|
-
if (isAbsolute(trimmed)) {
|
|
1162
|
-
throw new UserConfigValidationError(`${field} must be a relative path`);
|
|
1163
|
-
}
|
|
1164
|
-
const normalized = trimmed.replaceAll("\\", "/");
|
|
1165
|
-
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
1166
|
-
throw new UserConfigValidationError(`${field} must stay within the configured home-relative tree`);
|
|
1167
|
-
}
|
|
1168
|
-
return normalized.replace(/^\.\/+/, "");
|
|
1169
|
-
}
|
|
1170
|
-
function normalizeAgentId(value) {
|
|
1171
|
-
const trimmed = value.trim();
|
|
1172
|
-
if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
|
|
1173
|
-
throw new InvalidIdentifierError("agent id", value);
|
|
1174
|
-
}
|
|
1175
|
-
return normalizeId(trimmed);
|
|
1176
|
-
}
|
|
1177
|
-
function normalizePlatforms(value) {
|
|
1178
|
-
if (value === void 0 || value.length === 0) {
|
|
1179
|
-
return [process.platform];
|
|
1180
|
-
}
|
|
1181
|
-
const normalized = [...new Set(value.map((entry) => entry.trim().toLowerCase()))];
|
|
1182
|
-
const invalid = normalized.filter(
|
|
1183
|
-
(entry) => supportedPlatforms.includes(entry) === false
|
|
1184
|
-
);
|
|
1185
|
-
if (invalid.length > 0) {
|
|
1186
|
-
throw new UserConfigValidationError(
|
|
1187
|
-
`platform must be one of: ${supportedPlatforms.join(", ")}`
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
return normalized;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// src/config/write-user-config.ts
|
|
1194
|
-
import * as fs10 from "fs/promises";
|
|
1195
|
-
async function writeUserConfig(skillmuxHome, config) {
|
|
1196
|
-
const configPath = buildConfigPath(skillmuxHome);
|
|
1197
|
-
await fs10.mkdir(skillmuxHome, { recursive: true });
|
|
1198
|
-
await fs10.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
1199
|
-
`, "utf8");
|
|
1200
|
-
return {
|
|
1201
|
-
skillmuxHome,
|
|
1202
|
-
configPath
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// src/commands/config-add-agent.ts
|
|
1207
|
-
function buildAgentOverride(options) {
|
|
1208
|
-
const agentId = normalizeAgentId(options.id);
|
|
1209
|
-
const agent = {
|
|
1210
|
-
supportedPlatforms: normalizePlatforms(options.platforms),
|
|
1211
|
-
homeRelativeRootPath: normalizeRelativePath(options.root, "root"),
|
|
1212
|
-
skillsDirectoryPath: normalizeRelativePath(options.skills ?? "skills", "skills")
|
|
1213
|
-
};
|
|
1214
|
-
if (options.name !== void 0 && options.name.trim().length > 0) {
|
|
1215
|
-
agent.stableName = options.name.trim();
|
|
1216
|
-
}
|
|
1217
|
-
if (options.disabledByDefault === true) {
|
|
1218
|
-
agent.enabledByDefault = false;
|
|
1219
|
-
}
|
|
1220
|
-
return { agentId, agent };
|
|
1221
|
-
}
|
|
1222
|
-
function buildTableOutput2(result) {
|
|
1223
|
-
const summary = printTable(
|
|
1224
|
-
[
|
|
1225
|
-
{
|
|
1226
|
-
agentId: result.agentId,
|
|
1227
|
-
configPath: result.configPath,
|
|
1228
|
-
changed: String(result.changed)
|
|
1229
|
-
}
|
|
1230
|
-
],
|
|
1231
|
-
[
|
|
1232
|
-
{ key: "agentId", label: "Agent" },
|
|
1233
|
-
{ key: "configPath", label: "Config Path" },
|
|
1234
|
-
{ key: "changed", label: "Changed" }
|
|
1235
|
-
]
|
|
1236
|
-
);
|
|
1237
|
-
const detail = printTable(
|
|
1238
|
-
[
|
|
1239
|
-
{
|
|
1240
|
-
stableName: result.agent.stableName ?? "",
|
|
1241
|
-
platforms: (result.agent.supportedPlatforms ?? []).join(","),
|
|
1242
|
-
root: result.agent.homeRelativeRootPath ?? "",
|
|
1243
|
-
skills: result.agent.skillsDirectoryPath ?? "",
|
|
1244
|
-
enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
|
|
1245
|
-
}
|
|
1246
|
-
],
|
|
1247
|
-
[
|
|
1248
|
-
{ key: "stableName", label: "Name" },
|
|
1249
|
-
{ key: "platforms", label: "Platforms" },
|
|
1250
|
-
{ key: "root", label: "Root" },
|
|
1251
|
-
{ key: "skills", label: "Skills Dir" },
|
|
1252
|
-
{ key: "enabledByDefault", label: "Enabled By Default" }
|
|
1253
|
-
]
|
|
1254
|
-
);
|
|
1255
|
-
return `${summary}${detail}`;
|
|
1256
|
-
}
|
|
1257
|
-
async function runConfigAddAgent(options) {
|
|
1258
|
-
const homeDir = options.homeDir ?? homedir3();
|
|
1259
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1260
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1261
|
-
const configPath = buildConfigPath(skillmuxHome);
|
|
1262
|
-
const config = await loadUserConfig(skillmuxHome);
|
|
1263
|
-
const { agentId, agent } = buildAgentOverride(options);
|
|
1264
|
-
const previous = config.agents[agentId];
|
|
1265
|
-
const changed = JSON.stringify(previous ?? null) !== JSON.stringify(agent);
|
|
1266
|
-
const nextConfig = {
|
|
1267
|
-
...config,
|
|
1268
|
-
agents: {
|
|
1269
|
-
...config.agents,
|
|
1270
|
-
[agentId]: agent
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
await writeUserConfig(skillmuxHome, nextConfig);
|
|
1274
|
-
const resultWithoutOutput = {
|
|
1275
|
-
skillmuxHome,
|
|
1276
|
-
configPath,
|
|
1277
|
-
agentId,
|
|
1278
|
-
changed,
|
|
1279
|
-
agent,
|
|
1280
|
-
config: nextConfig
|
|
1281
|
-
};
|
|
1282
|
-
return {
|
|
1283
|
-
...resultWithoutOutput,
|
|
1284
|
-
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput2(resultWithoutOutput)
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// src/commands/config-remove-agent.ts
|
|
1289
|
-
import { homedir as homedir4 } from "os";
|
|
1290
|
-
function normalizeAgentId2(value) {
|
|
1291
|
-
const trimmed = value.trim();
|
|
1292
|
-
if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
|
|
1293
|
-
throw new InvalidIdentifierError("agent id", value);
|
|
1294
|
-
}
|
|
1295
|
-
return normalizeId(trimmed);
|
|
1296
|
-
}
|
|
1297
|
-
function buildTableOutput3(result) {
|
|
1298
|
-
return printTable(
|
|
1299
|
-
[
|
|
1300
|
-
{
|
|
1301
|
-
agentId: result.agentId,
|
|
1302
|
-
configPath: result.configPath,
|
|
1303
|
-
changed: String(result.changed),
|
|
1304
|
-
removed: String(result.removed)
|
|
1305
|
-
}
|
|
1306
|
-
],
|
|
1307
|
-
[
|
|
1308
|
-
{ key: "agentId", label: "Agent" },
|
|
1309
|
-
{ key: "configPath", label: "Config Path" },
|
|
1310
|
-
{ key: "changed", label: "Changed" },
|
|
1311
|
-
{ key: "removed", label: "Removed" }
|
|
1312
|
-
]
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
async function runConfigRemoveAgent(options) {
|
|
1316
|
-
const homeDir = options.homeDir ?? homedir4();
|
|
1317
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1318
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1319
|
-
const configPath = buildConfigPath(skillmuxHome);
|
|
1320
|
-
const config = await loadUserConfig(skillmuxHome);
|
|
1321
|
-
const agentId = normalizeAgentId2(options.id);
|
|
1322
|
-
const removed = agentId in config.agents;
|
|
1323
|
-
const nextConfig = {
|
|
1324
|
-
...config,
|
|
1325
|
-
agents: Object.fromEntries(
|
|
1326
|
-
Object.entries(config.agents).filter(([currentAgentId]) => currentAgentId !== agentId)
|
|
1327
|
-
)
|
|
1328
|
-
};
|
|
1329
|
-
if (removed) {
|
|
1330
|
-
await writeUserConfig(skillmuxHome, nextConfig);
|
|
1331
|
-
}
|
|
1332
|
-
const resultWithoutOutput = {
|
|
1333
|
-
skillmuxHome,
|
|
1334
|
-
configPath,
|
|
1335
|
-
agentId,
|
|
1336
|
-
changed: removed,
|
|
1337
|
-
removed,
|
|
1338
|
-
config: nextConfig
|
|
1339
|
-
};
|
|
1340
|
-
return {
|
|
1341
|
-
...resultWithoutOutput,
|
|
1342
|
-
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput3(resultWithoutOutput)
|
|
1343
|
-
};
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
// src/commands/config-update-agent.ts
|
|
1347
|
-
import { homedir as homedir5 } from "os";
|
|
1348
|
-
function buildAgentPatch(options) {
|
|
1349
|
-
const patch = {};
|
|
1350
|
-
if (options.root !== void 0) {
|
|
1351
|
-
patch.homeRelativeRootPath = normalizeRelativePath(options.root, "root");
|
|
1352
|
-
}
|
|
1353
|
-
if (options.skills !== void 0) {
|
|
1354
|
-
patch.skillsDirectoryPath = normalizeRelativePath(options.skills, "skills");
|
|
1355
|
-
}
|
|
1356
|
-
if (options.name !== void 0 && options.name.trim().length > 0) {
|
|
1357
|
-
patch.stableName = options.name.trim();
|
|
1358
|
-
}
|
|
1359
|
-
if (options.platforms !== void 0) {
|
|
1360
|
-
patch.supportedPlatforms = normalizePlatforms(options.platforms);
|
|
1361
|
-
}
|
|
1362
|
-
if (options.enabledByDefault !== void 0 && options.disabledByDefault === true) {
|
|
1363
|
-
throw new UserConfigValidationError(
|
|
1364
|
-
"enabled-by-default and disabled-by-default cannot both be set"
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
if (options.enabledByDefault !== void 0) {
|
|
1368
|
-
patch.enabledByDefault = options.enabledByDefault;
|
|
1369
|
-
}
|
|
1370
|
-
if (options.disabledByDefault === true) {
|
|
1371
|
-
patch.enabledByDefault = false;
|
|
1372
|
-
}
|
|
1373
|
-
return patch;
|
|
1374
|
-
}
|
|
1375
|
-
function buildTableOutput4(result) {
|
|
1376
|
-
const summary = printTable(
|
|
1377
|
-
[
|
|
1378
|
-
{
|
|
1379
|
-
agentId: result.agentId,
|
|
1380
|
-
configPath: result.configPath,
|
|
1381
|
-
changed: String(result.changed)
|
|
1382
|
-
}
|
|
1383
|
-
],
|
|
1384
|
-
[
|
|
1385
|
-
{ key: "agentId", label: "Agent" },
|
|
1386
|
-
{ key: "configPath", label: "Config Path" },
|
|
1387
|
-
{ key: "changed", label: "Changed" }
|
|
1388
|
-
]
|
|
1389
|
-
);
|
|
1390
|
-
const detail = printTable(
|
|
1391
|
-
[
|
|
1392
|
-
{
|
|
1393
|
-
stableName: result.agent.stableName ?? "",
|
|
1394
|
-
platforms: (result.agent.supportedPlatforms ?? []).join(","),
|
|
1395
|
-
root: result.agent.homeRelativeRootPath ?? "",
|
|
1396
|
-
skills: result.agent.skillsDirectoryPath ?? "",
|
|
1397
|
-
enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
|
|
1398
|
-
}
|
|
1399
|
-
],
|
|
1400
|
-
[
|
|
1401
|
-
{ key: "stableName", label: "Name" },
|
|
1402
|
-
{ key: "platforms", label: "Platforms" },
|
|
1403
|
-
{ key: "root", label: "Root" },
|
|
1404
|
-
{ key: "skills", label: "Skills Dir" },
|
|
1405
|
-
{ key: "enabledByDefault", label: "Enabled By Default" }
|
|
1406
|
-
]
|
|
1407
|
-
);
|
|
1408
|
-
return `${summary}${detail}`;
|
|
1409
|
-
}
|
|
1410
|
-
async function runConfigUpdateAgent(options) {
|
|
1411
|
-
const homeDir = options.homeDir ?? homedir5();
|
|
1412
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1413
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1414
|
-
const configPath = buildConfigPath(skillmuxHome);
|
|
1415
|
-
const config = await loadUserConfig(skillmuxHome);
|
|
1416
|
-
const agentId = normalizeAgentId(options.id);
|
|
1417
|
-
const previous = config.agents[agentId];
|
|
1418
|
-
if (previous === void 0) {
|
|
1419
|
-
throw new UserConfigValidationError(`Agent override does not exist: ${agentId}`);
|
|
1420
|
-
}
|
|
1421
|
-
const agent = {
|
|
1422
|
-
...previous,
|
|
1423
|
-
...buildAgentPatch(options)
|
|
1424
|
-
};
|
|
1425
|
-
const changed = JSON.stringify(previous) !== JSON.stringify(agent);
|
|
1426
|
-
const nextConfig = {
|
|
1427
|
-
...config,
|
|
1428
|
-
agents: {
|
|
1429
|
-
...config.agents,
|
|
1430
|
-
[agentId]: agent
|
|
1431
|
-
}
|
|
1432
|
-
};
|
|
1433
|
-
if (changed) {
|
|
1434
|
-
await writeUserConfig(skillmuxHome, nextConfig);
|
|
1435
|
-
}
|
|
1436
|
-
const resultWithoutOutput = {
|
|
1437
|
-
skillmuxHome,
|
|
1438
|
-
configPath,
|
|
1439
|
-
agentId,
|
|
1440
|
-
changed,
|
|
1441
|
-
agent,
|
|
1442
|
-
config: nextConfig
|
|
1443
|
-
};
|
|
1444
|
-
return {
|
|
1445
|
-
...resultWithoutOutput,
|
|
1446
|
-
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput4(resultWithoutOutput)
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// src/commands/config.ts
|
|
1451
|
-
import { homedir as homedir6 } from "os";
|
|
1452
|
-
function buildTableOutput5(result) {
|
|
1453
|
-
const summary = printTable(
|
|
1454
|
-
[
|
|
1455
|
-
{
|
|
1456
|
-
skillmuxHome: result.skillmuxHome,
|
|
1457
|
-
configPath: result.configPath,
|
|
1458
|
-
overrides: String(Object.keys(result.config.agents).length)
|
|
1459
|
-
}
|
|
1460
|
-
],
|
|
1461
|
-
[
|
|
1462
|
-
{ key: "skillmuxHome", label: "SkillMux Home" },
|
|
1463
|
-
{ key: "configPath", label: "Config Path" },
|
|
1464
|
-
{ key: "overrides", label: "Overrides" }
|
|
1465
|
-
]
|
|
1466
|
-
);
|
|
1467
|
-
const agentRows = Object.entries(result.config.agents).sort(([left], [right]) => left.localeCompare(right)).map(([agentId, agent]) => ({
|
|
1468
|
-
agentId,
|
|
1469
|
-
stableName: agent.stableName ?? "",
|
|
1470
|
-
root: agent.homeRelativeRootPath ?? "",
|
|
1471
|
-
skills: agent.skillsDirectoryPath ?? ""
|
|
1472
|
-
}));
|
|
1473
|
-
if (agentRows.length === 0) {
|
|
1474
|
-
return `${summary}
|
|
1475
|
-
No user overrides configured.
|
|
1476
|
-
`;
|
|
1477
|
-
}
|
|
1478
|
-
return `${summary}
|
|
1479
|
-
${printTable(agentRows, [
|
|
1480
|
-
{ key: "agentId", label: "Agent" },
|
|
1481
|
-
{ key: "stableName", label: "Name" },
|
|
1482
|
-
{ key: "root", label: "Root" },
|
|
1483
|
-
{ key: "skills", label: "Skills Dir" }
|
|
1484
|
-
])}`;
|
|
1485
|
-
}
|
|
1486
|
-
async function runConfig(options = {}) {
|
|
1487
|
-
const homeDir = options.homeDir ?? homedir6();
|
|
1488
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1489
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1490
|
-
const config = await loadUserConfig(skillmuxHome);
|
|
1491
|
-
const resultWithoutOutput = {
|
|
1492
|
-
skillmuxHome,
|
|
1493
|
-
configPath: buildConfigPath(skillmuxHome),
|
|
1494
|
-
config
|
|
1495
|
-
};
|
|
1496
|
-
return {
|
|
1497
|
-
...resultWithoutOutput,
|
|
1498
|
-
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput5(resultWithoutOutput)
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// src/commands/doctor.ts
|
|
1503
|
-
import * as fs11 from "fs/promises";
|
|
1504
|
-
import { homedir as homedir7 } from "os";
|
|
1505
|
-
import { join as join8 } from "path";
|
|
1506
|
-
function buildIssue2(code, severity, message, path) {
|
|
1507
|
-
return path === void 0 ? { code, severity, message } : { code, severity, message, path };
|
|
1508
|
-
}
|
|
1509
|
-
async function pathExists2(path) {
|
|
1510
|
-
try {
|
|
1511
|
-
await fs11.access(path);
|
|
1512
|
-
return true;
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
if (error.code === "ENOENT") {
|
|
1515
|
-
return false;
|
|
1516
|
-
}
|
|
1517
|
-
throw error;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
async function addUnmanagedDirectoryIssues(entries, issues) {
|
|
1521
|
-
for (const entry of entries) {
|
|
1522
|
-
if (entry.kind !== "unmanaged-directory") {
|
|
1523
|
-
continue;
|
|
1524
|
-
}
|
|
1525
|
-
if (await pathExists2(join8(entry.path, "SKILL.md"))) {
|
|
1526
|
-
issues.push(
|
|
1527
|
-
buildIssue2(
|
|
1528
|
-
"unmanaged-skill-directory",
|
|
1529
|
-
"warning",
|
|
1530
|
-
`Unmanaged skill directory is present for ${entry.agentId}/${entry.skillName}`,
|
|
1531
|
-
entry.path
|
|
1532
|
-
)
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
async function addMissingManagedSkillIssues(manifest, issues) {
|
|
1538
|
-
for (const skill of Object.values(manifest.skills)) {
|
|
1539
|
-
if (await pathExists2(skill.path)) {
|
|
1540
|
-
continue;
|
|
1541
|
-
}
|
|
1542
|
-
issues.push(
|
|
1543
|
-
buildIssue2(
|
|
1544
|
-
"missing-managed-skill-path",
|
|
1545
|
-
"error",
|
|
1546
|
-
`Managed skill path is missing for ${skill.id}`,
|
|
1547
|
-
skill.path
|
|
1548
|
-
)
|
|
1549
|
-
);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
function addConflictingAgentPathIssues(agents, issues) {
|
|
1553
|
-
const pathToAgents = /* @__PURE__ */ new Map();
|
|
1554
|
-
for (const agent of agents) {
|
|
1555
|
-
const key = normalizeAbsolutePath(agent.absoluteSkillsDirectoryPath);
|
|
1556
|
-
const current = pathToAgents.get(key) ?? [];
|
|
1557
|
-
current.push(agent);
|
|
1558
|
-
pathToAgents.set(key, current);
|
|
1559
|
-
}
|
|
1560
|
-
for (const conflictedAgents of pathToAgents.values()) {
|
|
1561
|
-
if (conflictedAgents.length < 2) {
|
|
1562
|
-
continue;
|
|
1563
|
-
}
|
|
1564
|
-
const agentIds = conflictedAgents.map((agent) => agent.id).sort((left, right) => left.localeCompare(right));
|
|
1565
|
-
issues.push(
|
|
1566
|
-
buildIssue2(
|
|
1567
|
-
"conflicting-agent-path",
|
|
1568
|
-
"warning",
|
|
1569
|
-
`Multiple agents resolve to the same skills directory: ${agentIds.join(", ")}`,
|
|
1570
|
-
conflictedAgents[0].absoluteSkillsDirectoryPath
|
|
1571
|
-
)
|
|
1572
|
-
);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
function buildTableOutput6(issues) {
|
|
1576
|
-
if (issues.length === 0) {
|
|
1577
|
-
return "No doctor issues found.\n";
|
|
1578
|
-
}
|
|
1579
|
-
return printTable(
|
|
1580
|
-
issues.map((issue) => ({
|
|
1581
|
-
severity: issue.severity,
|
|
1582
|
-
code: issue.code,
|
|
1583
|
-
path: issue.path ?? "",
|
|
1584
|
-
message: issue.message
|
|
1585
|
-
})),
|
|
1586
|
-
[
|
|
1587
|
-
{ key: "severity", label: "Severity" },
|
|
1588
|
-
{ key: "code", label: "Code" },
|
|
1589
|
-
{ key: "path", label: "Path" },
|
|
1590
|
-
{ key: "message", label: "Message" }
|
|
1591
|
-
]
|
|
1592
|
-
);
|
|
1593
|
-
}
|
|
1594
|
-
function buildJsonOutput(result) {
|
|
1595
|
-
return printJson({
|
|
1596
|
-
skillmuxHome: result.skillmuxHome,
|
|
1597
|
-
issues: result.issues,
|
|
1598
|
-
agents: result.agents.map((agent) => ({
|
|
1599
|
-
id: agent.id,
|
|
1600
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
1601
|
-
supportedOnPlatform: agent.supportedOnPlatform
|
|
1602
|
-
})),
|
|
1603
|
-
entries: result.entries
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
async function runDoctor(options = {}) {
|
|
1607
|
-
const homeDir = options.homeDir ?? homedir7();
|
|
1608
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1609
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1610
|
-
const [manifest, config, agents] = await Promise.all([
|
|
1611
|
-
readManifest(skillmuxHome),
|
|
1612
|
-
loadUserConfig(skillmuxHome),
|
|
1613
|
-
discoverAgents({
|
|
1614
|
-
homeDir,
|
|
1615
|
-
skillmuxHome,
|
|
1616
|
-
platform: options.platform
|
|
1617
|
-
})
|
|
1618
|
-
]);
|
|
1619
|
-
const entries = [];
|
|
1620
|
-
const issues = [];
|
|
1621
|
-
for (const agent of agents) {
|
|
1622
|
-
const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
|
|
1623
|
-
entries.push(...scannedAgent.entries);
|
|
1624
|
-
issues.push(...scannedAgent.issues);
|
|
1625
|
-
}
|
|
1626
|
-
await addUnmanagedDirectoryIssues(entries, issues);
|
|
1627
|
-
await addMissingManagedSkillIssues(manifest, issues);
|
|
1628
|
-
addConflictingAgentPathIssues(agents, issues);
|
|
1629
|
-
const dedupedIssues = [...issues].sort((left, right) => {
|
|
1630
|
-
const leftKey = `${left.severity}:${left.code}:${left.path ?? ""}:${left.message}`;
|
|
1631
|
-
const rightKey = `${right.severity}:${right.code}:${right.path ?? ""}:${right.message}`;
|
|
1632
|
-
return leftKey.localeCompare(rightKey);
|
|
1633
|
-
});
|
|
1634
|
-
const resultWithoutOutput = {
|
|
1635
|
-
skillmuxHome,
|
|
1636
|
-
manifest,
|
|
1637
|
-
config,
|
|
1638
|
-
agents,
|
|
1639
|
-
entries,
|
|
1640
|
-
issues: dedupedIssues
|
|
1641
|
-
};
|
|
1642
|
-
return {
|
|
1643
|
-
...resultWithoutOutput,
|
|
1644
|
-
output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildTableOutput6(dedupedIssues)
|
|
1645
|
-
};
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
// src/commands/disable.ts
|
|
1649
|
-
import * as fs13 from "fs/promises";
|
|
1650
|
-
import { homedir as homedir8 } from "os";
|
|
1651
|
-
import { join as join9, resolve as resolve9 } from "path";
|
|
1652
|
-
|
|
1653
|
-
// src/fs/safe-remove-link.ts
|
|
1654
|
-
import * as fs12 from "fs/promises";
|
|
1655
|
-
async function safeRemoveLink(path) {
|
|
1656
|
-
try {
|
|
1657
|
-
const entry = await fs12.lstat(path);
|
|
1658
|
-
if (!entry.isSymbolicLink()) {
|
|
1659
|
-
return false;
|
|
1660
|
-
}
|
|
1661
|
-
await fs12.rm(path, { recursive: true, force: false });
|
|
1662
|
-
return true;
|
|
1663
|
-
} catch (error) {
|
|
1664
|
-
if (error.code === "ENOENT") {
|
|
1665
|
-
return false;
|
|
1666
|
-
}
|
|
1667
|
-
throw error;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// src/commands/disable.ts
|
|
1672
|
-
function buildAgentRecord2(agent, timestamp) {
|
|
1673
|
-
return {
|
|
1674
|
-
id: agent.id,
|
|
1675
|
-
name: agent.stableName,
|
|
1676
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
1677
|
-
discovery: agent.discovery,
|
|
1678
|
-
available: agent.exists && agent.supportedOnPlatform,
|
|
1679
|
-
lastSeenAt: agent.exists ? timestamp : null
|
|
1680
|
-
};
|
|
1681
|
-
}
|
|
1682
|
-
function buildActivationRecord2(skillId, agentId, linkPath, timestamp) {
|
|
1683
|
-
return {
|
|
1684
|
-
skillId,
|
|
1685
|
-
agentId,
|
|
1686
|
-
linkPath,
|
|
1687
|
-
state: "disabled",
|
|
1688
|
-
updatedAt: timestamp
|
|
1689
|
-
};
|
|
1690
|
-
}
|
|
1691
|
-
function upsertActivation2(manifest, activation) {
|
|
1692
|
-
const index = manifest.activations.findIndex(
|
|
1693
|
-
(entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
|
|
1694
|
-
);
|
|
1695
|
-
if (index === -1) {
|
|
1696
|
-
manifest.activations.push(activation);
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
manifest.activations[index] = activation;
|
|
1700
|
-
}
|
|
1701
|
-
function buildManagedSkillPath2(skillmuxHome, skillId) {
|
|
1702
|
-
return resolve9(skillmuxHome, "skills", skillId);
|
|
1703
|
-
}
|
|
1704
|
-
async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName, linkPath, timestamp) {
|
|
1705
|
-
try {
|
|
1706
|
-
const entry = await fs13.lstat(linkPath);
|
|
1707
|
-
if (!entry.isSymbolicLink()) {
|
|
1708
|
-
return void 0;
|
|
1709
|
-
}
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
if (error.code === "ENOENT") {
|
|
1712
|
-
return void 0;
|
|
1713
|
-
}
|
|
1714
|
-
throw error;
|
|
1715
|
-
}
|
|
1716
|
-
const sourcePath = await fs13.realpath(linkPath);
|
|
1717
|
-
await assertSkillSourceLayout(sourcePath);
|
|
1718
|
-
const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
|
|
1719
|
-
await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
|
|
1720
|
-
const skill = {
|
|
1721
|
-
id: skillId,
|
|
1722
|
-
name: skillName,
|
|
1723
|
-
path: managedSkillPath,
|
|
1724
|
-
source: {
|
|
1725
|
-
kind: "imported",
|
|
1726
|
-
path: sourcePath
|
|
1727
|
-
},
|
|
1728
|
-
importedAt: timestamp
|
|
1729
|
-
};
|
|
1730
|
-
manifest.skills[skillId] = skill;
|
|
1731
|
-
return { skill, sourcePath };
|
|
1732
|
-
}
|
|
1733
|
-
async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
|
|
1734
|
-
const agentId = normalizeId(agentName);
|
|
1735
|
-
const agents = await discoverAgents({ homeDir, skillmuxHome });
|
|
1736
|
-
const agent = agents.find((entry) => entry.id === agentId);
|
|
1737
|
-
if (agent === void 0) {
|
|
1738
|
-
throw new Error(`Unknown agent: ${agentName}`);
|
|
1739
|
-
}
|
|
1740
|
-
if (!agent.supportedOnPlatform) {
|
|
1741
|
-
throw new Error(`Agent ${agent.id} is not supported on ${process.platform}`);
|
|
1742
|
-
}
|
|
1743
|
-
return agent;
|
|
1744
|
-
}
|
|
1745
|
-
async function pathExists3(path) {
|
|
1746
|
-
try {
|
|
1747
|
-
await fs13.lstat(path);
|
|
1748
|
-
return true;
|
|
1749
|
-
} catch (error) {
|
|
1750
|
-
if (error.code === "ENOENT") {
|
|
1751
|
-
return false;
|
|
1752
|
-
}
|
|
1753
|
-
throw error;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
async function runDisableSingle(options) {
|
|
1757
|
-
if (options.agent === void 0) {
|
|
1758
|
-
throw new Error("Disable requires one target agent");
|
|
1759
|
-
}
|
|
1760
|
-
const homeDir = options.homeDir ?? homedir8();
|
|
1761
|
-
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
1762
|
-
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
1763
|
-
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1764
|
-
const manifest = await readManifest(skillmuxHome);
|
|
1765
|
-
const skillId = normalizeId(options.skill);
|
|
1766
|
-
const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
|
|
1767
|
-
const linkPath = join9(agent.absoluteSkillsDirectoryPath, skillId);
|
|
1768
|
-
const adoption = manifest.skills[skillId] ? void 0 : await tryAdoptManagedSkill(
|
|
1769
|
-
manifest,
|
|
1770
|
-
skillmuxHome,
|
|
1771
|
-
skillId,
|
|
1772
|
-
options.skill,
|
|
1773
|
-
linkPath,
|
|
1774
|
-
timestamp
|
|
1775
|
-
);
|
|
1776
|
-
const skill = manifest.skills[skillId] ?? adoption?.skill;
|
|
1777
|
-
if (skill === void 0) {
|
|
1778
|
-
throw new Error(`Managed skill not found: ${skillId}`);
|
|
1779
|
-
}
|
|
1780
|
-
const currentActivation = manifest.activations.find(
|
|
1781
|
-
(entry) => entry.skillId === skill.id && entry.agentId === agent.id
|
|
1782
|
-
);
|
|
1783
|
-
const activationLinkPath = currentActivation?.linkPath ?? linkPath;
|
|
1784
|
-
const agentRecord = buildAgentRecord2(agent, timestamp);
|
|
1785
|
-
manifest.agents[agent.id] = agentRecord;
|
|
1786
|
-
const adoptedLinkRemoved = adoption !== void 0 ? await safeRemoveLink(linkPath) : false;
|
|
1787
|
-
const linkMatchesSkill = adoption === void 0 ? await isLinkPointingToTarget(activationLinkPath, skill.path) : false;
|
|
1788
|
-
if (adoption === void 0 && !linkMatchesSkill && await pathExists3(activationLinkPath)) {
|
|
1789
|
-
throw new Error(`Refusing to disable non-managed entry at ${linkPath}`);
|
|
1790
|
-
}
|
|
1791
|
-
const removedLink = adoptedLinkRemoved ? true : linkMatchesSkill ? await safeRemoveLink(activationLinkPath) : false;
|
|
1792
|
-
if (removedLink === false && currentActivation?.state !== "enabled") {
|
|
1793
|
-
return {
|
|
1794
|
-
changed: false,
|
|
1795
|
-
skill,
|
|
1796
|
-
agent: agentRecord,
|
|
1797
|
-
activation: currentActivation ?? null,
|
|
1798
|
-
manifest,
|
|
1799
|
-
output: `${skill.id} is already disabled for ${agent.id}
|
|
1800
|
-
`
|
|
1801
|
-
};
|
|
1802
|
-
}
|
|
1803
|
-
const activation = buildActivationRecord2(skill.id, agent.id, linkPath, timestamp);
|
|
1804
|
-
upsertActivation2(manifest, activation);
|
|
1805
|
-
await writeManifest(skillmuxHome, manifest);
|
|
1806
|
-
return {
|
|
1807
|
-
changed: true,
|
|
1808
|
-
skill,
|
|
1809
|
-
agent: agentRecord,
|
|
1810
|
-
activation,
|
|
1811
|
-
manifest,
|
|
1812
|
-
output: `Disabled ${skill.id} for ${agent.id}
|
|
1813
|
-
`
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
async function runDisable(options) {
|
|
1817
|
-
if (options.agents !== void 0) {
|
|
1818
|
-
const results = [];
|
|
1819
|
-
for (const agent of options.agents) {
|
|
1820
|
-
try {
|
|
1821
|
-
results.push(await runDisableSingle({ ...options, agent, agents: void 0 }));
|
|
1822
|
-
} catch (error) {
|
|
1823
|
-
throw new BatchOperationError({
|
|
1824
|
-
operation: "disable",
|
|
1825
|
-
failedItem: agent,
|
|
1826
|
-
failedAction: `disable ${options.skill} for ${agent}`,
|
|
1827
|
-
completedAction: "disabling",
|
|
1828
|
-
completedItems: results.map((result) => result.agent.id),
|
|
1829
|
-
cause: error
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
if (results.length === 0) {
|
|
1834
|
-
throw new Error("Disable requires at least one target agent");
|
|
1835
|
-
}
|
|
1836
|
-
const lastResult = results[results.length - 1];
|
|
1837
|
-
return {
|
|
1838
|
-
changed: results.some((result) => result.changed),
|
|
1839
|
-
skill: lastResult.skill,
|
|
1840
|
-
results,
|
|
1841
|
-
changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
|
|
1842
|
-
manifest: lastResult.manifest,
|
|
1843
|
-
output: results.map((result) => result.output).join("")
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
return runDisableSingle(options);
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
// src/commands/enable.ts
|
|
1850
|
-
import * as fs14 from "fs/promises";
|
|
1851
|
-
import { homedir as homedir9 } from "os";
|
|
1852
|
-
import { join as join10 } from "path";
|
|
1853
|
-
function buildAgentRecord3(agent, timestamp) {
|
|
1854
|
-
return {
|
|
1855
|
-
id: agent.id,
|
|
1856
|
-
name: agent.stableName,
|
|
1857
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
1858
|
-
discovery: agent.discovery,
|
|
1859
|
-
available: agent.supportedOnPlatform,
|
|
1860
|
-
lastSeenAt: timestamp
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
function buildActivationRecord3(skillId, agentId, linkPath, timestamp, state) {
|
|
1864
|
-
return {
|
|
1865
|
-
skillId,
|
|
1866
|
-
agentId,
|
|
1867
|
-
linkPath,
|
|
1868
|
-
state,
|
|
1869
|
-
updatedAt: timestamp
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
function upsertActivation3(manifest, activation) {
|
|
1873
|
-
const index = manifest.activations.findIndex(
|
|
1874
|
-
(entry) => entry.skillId === activation.skillId && entry.agentId === activation.agentId
|
|
1875
|
-
);
|
|
1876
|
-
if (index === -1) {
|
|
1877
|
-
manifest.activations.push(activation);
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
manifest.activations[index] = activation;
|
|
1881
|
-
}
|
|
1882
|
-
async function resolveTargetAgent3(homeDir, skillmuxHome, agentName) {
|
|
1883
|
-
const agentId = normalizeId(agentName);
|
|
1884
|
-
const agents = await discoverAgents({ homeDir, skillmuxHome });
|
|
1885
|
-
const agent = agents.find((entry) => entry.id === agentId);
|
|
1886
|
-
if (agent === void 0) {
|
|
1887
|
-
throw new Error(`Unknown agent: ${agentName}`);
|
|
1888
|
-
}
|
|
1889
|
-
if (!agent.supportedOnPlatform) {
|
|
1890
|
-
throw new Error(`Agent ${agent.id} is not supported on ${process.platform}`);
|
|
1891
|
-
}
|
|
1892
|
-
return agent;
|
|
1893
|
-
}
|
|
1894
|
-
async function runEnableSingle(options) {
|
|
1895
|
-
if (options.agent === void 0) {
|
|
1896
|
-
throw new Error("Enable requires one target agent");
|
|
1897
|
-
}
|
|
1898
|
-
const homeDir = options.homeDir ?? homedir9();
|
|
1899
|
-
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
1900
|
-
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
1901
|
-
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1902
|
-
const manifest = await readManifest(skillmuxHome);
|
|
1903
|
-
const skillId = normalizeId(options.skill);
|
|
1904
|
-
const skill = manifest.skills[skillId];
|
|
1905
|
-
if (skill === void 0) {
|
|
1906
|
-
throw new Error(`Managed skill not found: ${skillId}`);
|
|
1907
|
-
}
|
|
1908
|
-
const agent = await resolveTargetAgent3(homeDir, skillmuxHome, options.agent);
|
|
1909
|
-
const linkPath = join10(agent.absoluteSkillsDirectoryPath, skill.id);
|
|
1910
|
-
const currentActivation = manifest.activations.find(
|
|
1911
|
-
(entry) => entry.skillId === skill.id && entry.agentId === agent.id
|
|
1912
|
-
);
|
|
1913
|
-
const agentRecord = buildAgentRecord3(agent, timestamp);
|
|
1914
|
-
manifest.agents[agent.id] = agentRecord;
|
|
1915
|
-
await fs14.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
|
|
1916
|
-
const linkAlreadyEnabled = await isLinkPointingToTarget(linkPath, skill.path);
|
|
1917
|
-
const activationAlreadyEnabled = currentActivation?.state === "enabled" && currentActivation.linkPath === linkPath;
|
|
1918
|
-
if (linkAlreadyEnabled && activationAlreadyEnabled) {
|
|
1919
|
-
return {
|
|
1920
|
-
changed: false,
|
|
1921
|
-
skill,
|
|
1922
|
-
agent: agentRecord,
|
|
1923
|
-
activation: currentActivation,
|
|
1924
|
-
manifest,
|
|
1925
|
-
output: `${skill.id} is already enabled for ${agent.id}
|
|
1926
|
-
`
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
await createManagedLink(linkPath, skill.path);
|
|
1930
|
-
const activation = buildActivationRecord3(
|
|
1931
|
-
skill.id,
|
|
1932
|
-
agent.id,
|
|
1933
|
-
linkPath,
|
|
1934
|
-
timestamp,
|
|
1935
|
-
"enabled"
|
|
1936
|
-
);
|
|
1937
|
-
upsertActivation3(manifest, activation);
|
|
1938
|
-
await writeManifest(skillmuxHome, manifest);
|
|
1939
|
-
return {
|
|
1940
|
-
changed: true,
|
|
1941
|
-
skill,
|
|
1942
|
-
agent: agentRecord,
|
|
1943
|
-
activation,
|
|
1944
|
-
manifest,
|
|
1945
|
-
output: `Enabled ${skill.id} for ${agent.id}
|
|
1946
|
-
`
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
async function runEnable(options) {
|
|
1950
|
-
if (options.agents !== void 0) {
|
|
1951
|
-
const results = [];
|
|
1952
|
-
for (const agent of options.agents) {
|
|
1953
|
-
try {
|
|
1954
|
-
results.push(await runEnableSingle({ ...options, agent, agents: void 0 }));
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
throw new BatchOperationError({
|
|
1957
|
-
operation: "enable",
|
|
1958
|
-
failedItem: agent,
|
|
1959
|
-
failedAction: `enable ${options.skill} for ${agent}`,
|
|
1960
|
-
completedAction: "enabling",
|
|
1961
|
-
completedItems: results.map((result) => result.agent.id),
|
|
1962
|
-
cause: error
|
|
1963
|
-
});
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
if (results.length === 0) {
|
|
1967
|
-
throw new Error("Enable requires at least one target agent");
|
|
1968
|
-
}
|
|
1969
|
-
const lastResult = results[results.length - 1];
|
|
1970
|
-
return {
|
|
1971
|
-
changed: results.some((result) => result.changed),
|
|
1972
|
-
skill: lastResult.skill,
|
|
1973
|
-
results,
|
|
1974
|
-
changedAgents: results.filter((result) => result.changed).map((result) => result.agent.id),
|
|
1975
|
-
manifest: lastResult.manifest,
|
|
1976
|
-
output: results.map((result) => result.output).join("")
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
return runEnableSingle(options);
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
// src/commands/import.ts
|
|
1983
|
-
import { resolve as resolve10 } from "path";
|
|
1984
|
-
import { homedir as homedir10 } from "os";
|
|
1985
|
-
function buildManagedSkillPath3(skillmuxHome, skillId) {
|
|
1986
|
-
return resolve10(skillmuxHome, "skills", skillId);
|
|
1987
|
-
}
|
|
1988
|
-
async function runImport(options) {
|
|
1989
|
-
const homeDir = options.homeDir ?? homedir10();
|
|
1990
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1991
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1992
|
-
const sourcePath = resolve10(options.sourcePath);
|
|
1993
|
-
const skillId = normalizeId(options.skillName);
|
|
1994
|
-
const importedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1995
|
-
const manifest = await readManifest(skillmuxHome);
|
|
1996
|
-
const managedSkillPath = buildManagedSkillPath3(skillmuxHome, skillId);
|
|
1997
|
-
await assertSkillSourceLayout(sourcePath);
|
|
1998
|
-
if (manifest.skills[skillId] !== void 0) {
|
|
1999
|
-
throw new Error(`Managed skill already exists for ${skillId}`);
|
|
2000
|
-
}
|
|
2001
|
-
await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
|
|
2002
|
-
const skill = {
|
|
2003
|
-
id: skillId,
|
|
2004
|
-
name: options.skillName,
|
|
2005
|
-
path: managedSkillPath,
|
|
2006
|
-
source: {
|
|
2007
|
-
kind: "local",
|
|
2008
|
-
path: sourcePath
|
|
2009
|
-
},
|
|
2010
|
-
importedAt
|
|
2011
|
-
};
|
|
2012
|
-
manifest.skills[skillId] = skill;
|
|
2013
|
-
await writeManifest(skillmuxHome, manifest);
|
|
2014
|
-
return {
|
|
2015
|
-
skill,
|
|
2016
|
-
manifest,
|
|
2017
|
-
output: `Imported ${skillId} to ${managedSkillPath}
|
|
2018
|
-
`
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
// src/commands/scan.ts
|
|
2023
|
-
import { homedir as homedir11 } from "os";
|
|
2024
|
-
|
|
2025
|
-
// src/output/format-issue.ts
|
|
2026
|
-
function formatIssue(issue) {
|
|
2027
|
-
if (issue.path === void 0) {
|
|
2028
|
-
return `[${issue.severity}] ${issue.code}: ${issue.message}`;
|
|
2029
|
-
}
|
|
2030
|
-
return `[${issue.severity}] ${issue.code} @ ${issue.path}: ${issue.message}`;
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
// src/commands/scan.ts
|
|
2034
|
-
function buildAgentRecord4(agent, timestamp, previousRecord) {
|
|
2035
|
-
const lastSeenAt = agent.exists ? timestamp : previousRecord?.lastSeenAt ?? null;
|
|
2036
|
-
return {
|
|
2037
|
-
id: agent.id,
|
|
2038
|
-
name: agent.stableName,
|
|
2039
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
2040
|
-
discovery: agent.discovery,
|
|
2041
|
-
available: agent.exists && agent.supportedOnPlatform,
|
|
2042
|
-
lastSeenAt
|
|
2043
|
-
};
|
|
2044
|
-
}
|
|
2045
|
-
function buildScanOutput(result, json) {
|
|
2046
|
-
if (json) {
|
|
2047
|
-
return printJson({
|
|
2048
|
-
lastScan: result.manifest.lastScan,
|
|
2049
|
-
agents: result.agents.map((agent) => ({
|
|
2050
|
-
id: agent.id,
|
|
2051
|
-
name: agent.stableName,
|
|
2052
|
-
path: agent.absoluteSkillsDirectoryPath,
|
|
2053
|
-
exists: agent.exists,
|
|
2054
|
-
supportedOnPlatform: agent.supportedOnPlatform
|
|
2055
|
-
})),
|
|
2056
|
-
entries: result.entries
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
if (result.entries.length === 0) {
|
|
2060
|
-
return "No skill entries found.\n";
|
|
2061
|
-
}
|
|
2062
|
-
const table = printTable(
|
|
2063
|
-
result.entries.map((entry) => ({
|
|
2064
|
-
agent: entry.agentId,
|
|
2065
|
-
skill: entry.skillName,
|
|
2066
|
-
kind: entry.kind,
|
|
2067
|
-
path: entry.path
|
|
2068
|
-
})),
|
|
2069
|
-
[
|
|
2070
|
-
{ key: "agent", label: "Agent" },
|
|
2071
|
-
{ key: "skill", label: "Skill" },
|
|
2072
|
-
{ key: "kind", label: "Kind" },
|
|
2073
|
-
{ key: "path", label: "Path" }
|
|
2074
|
-
]
|
|
2075
|
-
);
|
|
2076
|
-
if (result.issues.length === 0) {
|
|
2077
|
-
return table;
|
|
2078
|
-
}
|
|
2079
|
-
return `${table}
|
|
2080
|
-
${result.issues.map(formatIssue).join("\n")}
|
|
2081
|
-
`;
|
|
2082
|
-
}
|
|
2083
|
-
async function runScan(options = {}) {
|
|
2084
|
-
const homeDir = options.homeDir ?? homedir11();
|
|
2085
|
-
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
2086
|
-
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
2087
|
-
const manifest = await readManifest(skillmuxHome);
|
|
2088
|
-
const agents = await discoverAgents({
|
|
2089
|
-
homeDir,
|
|
2090
|
-
platform: options.platform,
|
|
2091
|
-
skillmuxHome
|
|
2092
|
-
});
|
|
2093
|
-
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
2094
|
-
const entries = [];
|
|
2095
|
-
const issues = [];
|
|
2096
|
-
for (const agent of agents) {
|
|
2097
|
-
const scannedAgent = await scanAgentSkills(agent, skillmuxHome);
|
|
2098
|
-
entries.push(...scannedAgent.entries);
|
|
2099
|
-
issues.push(...scannedAgent.issues);
|
|
2100
|
-
manifest.agents[agent.id] = buildAgentRecord4(
|
|
2101
|
-
agent,
|
|
2102
|
-
timestamp,
|
|
2103
|
-
manifest.agents[agent.id]
|
|
2104
|
-
);
|
|
2105
|
-
}
|
|
2106
|
-
manifest.lastScan = {
|
|
2107
|
-
at: timestamp,
|
|
2108
|
-
issues
|
|
2109
|
-
};
|
|
2110
|
-
await writeManifest(skillmuxHome, manifest);
|
|
2111
|
-
const resultWithoutOutput = {
|
|
2112
|
-
manifest,
|
|
2113
|
-
agents,
|
|
2114
|
-
entries,
|
|
2115
|
-
issues
|
|
2116
|
-
};
|
|
2117
|
-
return {
|
|
2118
|
-
...resultWithoutOutput,
|
|
2119
|
-
output: buildScanOutput(resultWithoutOutput, options.json === true)
|
|
2120
|
-
};
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
// src/commands/list.ts
|
|
2124
|
-
function buildRecordsView(scanResult) {
|
|
2125
|
-
return {
|
|
2126
|
-
view: "records",
|
|
2127
|
-
records: scanResult.entries,
|
|
2128
|
-
issues: scanResult.issues
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
function buildAgentsView(scanResult) {
|
|
2132
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2133
|
-
for (const agent of scanResult.agents) {
|
|
2134
|
-
groups.set(agent.id, {
|
|
2135
|
-
agentId: agent.id,
|
|
2136
|
-
agentName: agent.stableName,
|
|
2137
|
-
entries: []
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
|
-
for (const entry of scanResult.entries) {
|
|
2141
|
-
const current = groups.get(entry.agentId) ?? {
|
|
2142
|
-
agentId: entry.agentId,
|
|
2143
|
-
agentName: entry.agentName,
|
|
2144
|
-
entries: []
|
|
2145
|
-
};
|
|
2146
|
-
current.entries.push(entry);
|
|
2147
|
-
groups.set(entry.agentId, current);
|
|
2148
|
-
}
|
|
2149
|
-
return {
|
|
2150
|
-
view: "agents",
|
|
2151
|
-
agents: [...groups.values()].sort(
|
|
2152
|
-
(left, right) => left.agentId.localeCompare(right.agentId)
|
|
2153
|
-
),
|
|
2154
|
-
issues: scanResult.issues
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
function buildSkillsView(scanResult) {
|
|
2158
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2159
|
-
for (const skill of Object.values(scanResult.manifest.skills)) {
|
|
2160
|
-
groups.set(skill.id, {
|
|
2161
|
-
skillName: skill.id,
|
|
2162
|
-
entries: []
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
for (const entry of scanResult.entries) {
|
|
2166
|
-
const current = groups.get(entry.skillName) ?? {
|
|
2167
|
-
skillName: entry.skillName,
|
|
2168
|
-
entries: []
|
|
2169
|
-
};
|
|
2170
|
-
current.entries.push(entry);
|
|
2171
|
-
groups.set(entry.skillName, current);
|
|
2172
|
-
}
|
|
2173
|
-
return {
|
|
2174
|
-
view: "skills",
|
|
2175
|
-
skills: [...groups.values()].sort(
|
|
2176
|
-
(left, right) => left.skillName.localeCompare(right.skillName)
|
|
2177
|
-
),
|
|
2178
|
-
issues: scanResult.issues
|
|
2179
|
-
};
|
|
2180
|
-
}
|
|
2181
|
-
function buildListData(scanResult, view) {
|
|
2182
|
-
if (view === "agents") {
|
|
2183
|
-
return buildAgentsView(scanResult);
|
|
2184
|
-
}
|
|
2185
|
-
if (view === "skills") {
|
|
2186
|
-
return buildSkillsView(scanResult);
|
|
2187
|
-
}
|
|
2188
|
-
return buildRecordsView(scanResult);
|
|
2189
|
-
}
|
|
2190
|
-
function buildTableOutput7(data, view) {
|
|
2191
|
-
if (view === "agents") {
|
|
2192
|
-
const agentRows = data.agents;
|
|
2193
|
-
return printTable(
|
|
2194
|
-
agentRows.map((agent) => ({
|
|
2195
|
-
agent: agent.agentId,
|
|
2196
|
-
name: agent.agentName,
|
|
2197
|
-
entries: String(agent.entries.length)
|
|
2198
|
-
})),
|
|
2199
|
-
[
|
|
2200
|
-
{ key: "agent", label: "Agent" },
|
|
2201
|
-
{ key: "name", label: "Name" },
|
|
2202
|
-
{ key: "entries", label: "Entries" }
|
|
2203
|
-
]
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
if (view === "skills") {
|
|
2207
|
-
const skillRows = data.skills;
|
|
2208
|
-
return printTable(
|
|
2209
|
-
skillRows.map((skill) => ({
|
|
2210
|
-
skill: skill.skillName,
|
|
2211
|
-
entries: String(skill.entries.length)
|
|
2212
|
-
})),
|
|
2213
|
-
[
|
|
2214
|
-
{ key: "skill", label: "Skill" },
|
|
2215
|
-
{ key: "entries", label: "Entries" }
|
|
2216
|
-
]
|
|
2217
|
-
);
|
|
2218
|
-
}
|
|
2219
|
-
const records = data.records;
|
|
2220
|
-
return printTable(
|
|
2221
|
-
records.map((record) => ({
|
|
2222
|
-
agent: record.agentId,
|
|
2223
|
-
skill: record.skillName,
|
|
2224
|
-
kind: record.kind
|
|
2225
|
-
})),
|
|
2226
|
-
[
|
|
2227
|
-
{ key: "agent", label: "Agent" },
|
|
2228
|
-
{ key: "skill", label: "Skill" },
|
|
2229
|
-
{ key: "kind", label: "Kind" }
|
|
2230
|
-
]
|
|
2231
|
-
);
|
|
2232
|
-
}
|
|
2233
|
-
async function runList(options = {}) {
|
|
2234
|
-
const view = options.view ?? "records";
|
|
2235
|
-
const format = options.format ?? "table";
|
|
2236
|
-
const scanResult = await runScan(options);
|
|
2237
|
-
const data = buildListData(scanResult, view);
|
|
2238
|
-
return {
|
|
2239
|
-
data,
|
|
2240
|
-
output: format === "json" ? printJson(data) : buildTableOutput7(data, view)
|
|
2241
|
-
};
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
// src/commands/remove.ts
|
|
2245
|
-
import * as fs15 from "fs/promises";
|
|
2246
|
-
import { homedir as homedir12 } from "os";
|
|
2247
|
-
import { resolve as resolve11 } from "path";
|
|
2248
|
-
function buildManagedSkillPath4(skillmuxHome, skillId) {
|
|
2249
|
-
return resolve11(skillmuxHome, "skills", skillId);
|
|
2250
|
-
}
|
|
2251
|
-
function buildManifestPath(skillmuxHome) {
|
|
2252
|
-
return resolve11(skillmuxHome, "manifest.json");
|
|
2253
|
-
}
|
|
2254
|
-
function buildConfigPath2(skillmuxHome) {
|
|
2255
|
-
return resolve11(skillmuxHome, "config.json");
|
|
2256
|
-
}
|
|
2257
|
-
function resolveManagedSkill(manifest, skillNameOrId) {
|
|
2258
|
-
const skillId = normalizeId(skillNameOrId);
|
|
2259
|
-
const directMatch = manifest.skills[skillId];
|
|
2260
|
-
if (directMatch !== void 0) {
|
|
2261
|
-
return directMatch;
|
|
2262
|
-
}
|
|
2263
|
-
const nameMatches = Object.values(manifest.skills).filter(
|
|
2264
|
-
(skill) => normalizeId(skill.name) === skillId
|
|
2265
|
-
);
|
|
2266
|
-
if (nameMatches.length === 1) {
|
|
2267
|
-
return nameMatches[0];
|
|
2268
|
-
}
|
|
2269
|
-
if (nameMatches.length > 1) {
|
|
2270
|
-
const candidateIds = nameMatches.map((skill) => skill.id).sort((left, right) => left.localeCompare(right));
|
|
2271
|
-
throw new Error(
|
|
2272
|
-
`Ambiguous managed skill name ${skillNameOrId}: ${candidateIds.join(", ")}`
|
|
2273
|
-
);
|
|
2274
|
-
}
|
|
2275
|
-
throw new Error(`Managed skill not found: ${skillId}`);
|
|
2276
|
-
}
|
|
2277
|
-
function buildHumanOutput(skill, managedSkillPath) {
|
|
2278
|
-
return `Removed ${skill.id} from ${managedSkillPath}
|
|
2279
|
-
`;
|
|
2280
|
-
}
|
|
2281
|
-
function buildJsonOutput2(result) {
|
|
2282
|
-
return printJson({
|
|
2283
|
-
changed: result.changed,
|
|
2284
|
-
removedSkillId: result.removedSkillId,
|
|
2285
|
-
skill: result.skill,
|
|
2286
|
-
location: result.location,
|
|
2287
|
-
manifest: result.manifest
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
async function pathExists4(path) {
|
|
2291
|
-
try {
|
|
2292
|
-
await fs15.lstat(path);
|
|
2293
|
-
return true;
|
|
2294
|
-
} catch (error) {
|
|
2295
|
-
if (error.code === "ENOENT") {
|
|
2296
|
-
return false;
|
|
2297
|
-
}
|
|
2298
|
-
throw error;
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
async function assertManagedSkillRemovalSafety(skillmuxHome, skillPath, skillId) {
|
|
2302
|
-
const expectedSkillPath = buildManagedSkillPath4(skillmuxHome, skillId);
|
|
2303
|
-
if (!pathsAreEqual(skillPath, expectedSkillPath)) {
|
|
2304
|
-
throw new Error(`Refusing to remove unmanaged skill path at ${skillPath}`);
|
|
2305
|
-
}
|
|
2306
|
-
await assertNoSymlinkAncestors(skillPath, { includeLeaf: true });
|
|
2307
|
-
if (!await pathExists4(skillPath)) {
|
|
2308
|
-
return;
|
|
2309
|
-
}
|
|
2310
|
-
const entry = await fs15.lstat(skillPath);
|
|
2311
|
-
if (entry.isSymbolicLink()) {
|
|
2312
|
-
throw new Error(`Refusing to remove symlinked managed skill path at ${skillPath}`);
|
|
2313
|
-
}
|
|
2314
|
-
if (!entry.isDirectory()) {
|
|
2315
|
-
throw new Error(`Refusing to remove non-directory managed skill path at ${skillPath}`);
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
async function runRemoveSingle(options) {
|
|
2319
|
-
if (options.skill === void 0) {
|
|
2320
|
-
throw new Error("Remove requires one target skill");
|
|
2321
|
-
}
|
|
2322
|
-
const homeDir = options.homeDir ?? homedir12();
|
|
2323
|
-
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
2324
|
-
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
2325
|
-
const manifestPath = buildManifestPath(skillmuxHome);
|
|
2326
|
-
const configPath = buildConfigPath2(skillmuxHome);
|
|
2327
|
-
const manifest = await readManifest(skillmuxHome);
|
|
2328
|
-
const skill = resolveManagedSkill(manifest, options.skill);
|
|
2329
|
-
const managedSkillsDirectory = resolve11(skillmuxHome, "skills");
|
|
2330
|
-
const managedSkillPath = skill.path;
|
|
2331
|
-
const enabledActivations = manifest.activations.filter(
|
|
2332
|
-
(activation) => activation.skillId === skill.id && activation.state === "enabled"
|
|
2333
|
-
);
|
|
2334
|
-
if (enabledActivations.length > 0) {
|
|
2335
|
-
const enabledAgents = [...new Set(enabledActivations.map((activation) => activation.agentId))].sort(
|
|
2336
|
-
(left, right) => left.localeCompare(right)
|
|
2337
|
-
);
|
|
2338
|
-
throw new Error(
|
|
2339
|
-
`Cannot remove ${skill.id}; it is still enabled for: ${enabledAgents.join(", ")}`
|
|
2340
|
-
);
|
|
2341
|
-
}
|
|
2342
|
-
await assertManagedSkillRemovalSafety(skillmuxHome, managedSkillPath, skill.id);
|
|
2343
|
-
if (await pathExists4(managedSkillPath)) {
|
|
2344
|
-
await fs15.rm(managedSkillPath, { recursive: true, force: false });
|
|
2345
|
-
}
|
|
2346
|
-
delete manifest.skills[skill.id];
|
|
2347
|
-
manifest.activations = manifest.activations.filter(
|
|
2348
|
-
(activation) => activation.skillId !== skill.id
|
|
2349
|
-
);
|
|
2350
|
-
await writeManifest(skillmuxHome, manifest);
|
|
2351
|
-
const resultWithoutOutput = {
|
|
2352
|
-
changed: true,
|
|
2353
|
-
removedSkillId: skill.id,
|
|
2354
|
-
skill,
|
|
2355
|
-
location: {
|
|
2356
|
-
skillmuxHome,
|
|
2357
|
-
configPath,
|
|
2358
|
-
manifestPath,
|
|
2359
|
-
managedSkillsDirectory,
|
|
2360
|
-
managedSkillPath
|
|
2361
|
-
},
|
|
2362
|
-
manifest
|
|
2363
|
-
};
|
|
2364
|
-
return {
|
|
2365
|
-
...resultWithoutOutput,
|
|
2366
|
-
output: options.json === true ? buildJsonOutput2(resultWithoutOutput) : buildHumanOutput(skill, managedSkillPath)
|
|
2367
|
-
};
|
|
2368
|
-
}
|
|
2369
|
-
async function runRemove(options) {
|
|
2370
|
-
if (options.skills !== void 0) {
|
|
2371
|
-
const results = [];
|
|
2372
|
-
for (const skill of options.skills) {
|
|
2373
|
-
try {
|
|
2374
|
-
results.push(await runRemoveSingle({ ...options, skill, skills: void 0 }));
|
|
2375
|
-
} catch (error) {
|
|
2376
|
-
throw new BatchOperationError({
|
|
2377
|
-
operation: "remove",
|
|
2378
|
-
failedItem: skill,
|
|
2379
|
-
failedAction: `remove ${skill}`,
|
|
2380
|
-
completedAction: "removing",
|
|
2381
|
-
completedItems: results.map((result) => result.removedSkillId),
|
|
2382
|
-
cause: error
|
|
2383
|
-
});
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
if (results.length === 0) {
|
|
2387
|
-
throw new Error("Remove requires at least one target skill");
|
|
2388
|
-
}
|
|
2389
|
-
const lastResult = results[results.length - 1];
|
|
2390
|
-
const resultWithoutOutput = {
|
|
2391
|
-
changed: results.some((result) => result.changed),
|
|
2392
|
-
removedSkillIds: results.map((result) => result.removedSkillId),
|
|
2393
|
-
results,
|
|
2394
|
-
manifest: lastResult.manifest
|
|
2395
|
-
};
|
|
2396
|
-
return {
|
|
2397
|
-
...resultWithoutOutput,
|
|
2398
|
-
output: options.json === true ? printJson(resultWithoutOutput) : results.map((result) => result.output).join("")
|
|
2399
|
-
};
|
|
2400
|
-
}
|
|
2401
|
-
return runRemoveSingle(options);
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
// src/index.ts
|
|
2405
|
-
function collectValues(value, previous = []) {
|
|
2406
|
-
return [...previous, value];
|
|
2407
|
-
}
|
|
2408
|
-
function requireSingleValue(values, label) {
|
|
2409
|
-
if (values.length !== 1) {
|
|
2410
|
-
throw new Error(`Expected exactly one ${label}`);
|
|
2411
|
-
}
|
|
2412
|
-
return values[0];
|
|
2413
|
-
}
|
|
2414
|
-
function requireAtLeastOneValue(values, label) {
|
|
2415
|
-
if (values.length === 0) {
|
|
2416
|
-
throw new Error(`Expected at least one ${label}`);
|
|
2417
|
-
}
|
|
2418
|
-
return values;
|
|
2419
|
-
}
|
|
2420
|
-
function buildCli() {
|
|
2421
|
-
const program = new Command();
|
|
2422
|
-
program.name("skillmux");
|
|
2423
|
-
program.command("adopt").requiredOption("--agent <agent>", "Source agent id").option("--skill <skill>", "Repeatable installed skill to adopt", collectValues, []).option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2424
|
-
const result = options.skill.length === 0 ? await runAdopt({
|
|
2425
|
-
agent: options.agent,
|
|
2426
|
-
json: options.json === true
|
|
2427
|
-
}) : options.skill.length === 1 ? await runAdopt({
|
|
2428
|
-
agent: options.agent,
|
|
2429
|
-
skill: options.skill[0],
|
|
2430
|
-
json: options.json === true
|
|
2431
|
-
}) : await runAdopt({
|
|
2432
|
-
agent: options.agent,
|
|
2433
|
-
skills: options.skill,
|
|
2434
|
-
json: options.json === true
|
|
2435
|
-
});
|
|
2436
|
-
process.stdout.write(result.output);
|
|
2437
|
-
});
|
|
2438
|
-
program.command("scan").option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2439
|
-
const result = await runScan({ json: options.json === true });
|
|
2440
|
-
process.stdout.write(result.output);
|
|
2441
|
-
});
|
|
2442
|
-
program.command("agents").option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2443
|
-
const result = await runAgents({ json: options.json === true });
|
|
2444
|
-
process.stdout.write(result.output);
|
|
2445
|
-
});
|
|
2446
|
-
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) => {
|
|
2447
|
-
const result = await runList({
|
|
2448
|
-
view: options.view,
|
|
2449
|
-
format: options.format
|
|
2450
|
-
});
|
|
2451
|
-
process.stdout.write(result.output);
|
|
2452
|
-
});
|
|
2453
|
-
program.command("import").requiredOption("--source <path>", "Local skill source directory").requiredOption("--name <name>", "Managed skill name").action(async (options) => {
|
|
2454
|
-
const result = await runImport({
|
|
2455
|
-
sourcePath: options.source,
|
|
2456
|
-
skillName: options.name
|
|
2457
|
-
});
|
|
2458
|
-
process.stdout.write(result.output);
|
|
2459
|
-
});
|
|
2460
|
-
program.command("doctor").option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2461
|
-
const result = await runDoctor({ json: options.json === true });
|
|
2462
|
-
process.stdout.write(result.output);
|
|
2463
|
-
});
|
|
2464
|
-
const configCommand = program.command("config");
|
|
2465
|
-
configCommand.option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2466
|
-
const result = await runConfig({ json: options.json === true });
|
|
2467
|
-
process.stdout.write(result.output);
|
|
2468
|
-
});
|
|
2469
|
-
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(
|
|
2470
|
-
"--platform <platform>",
|
|
2471
|
-
`Supported platform (${supportedPlatforms.join(", ")})`,
|
|
2472
|
-
(value, previous = []) => [...previous, value],
|
|
2473
|
-
[]
|
|
2474
|
-
).option("--disabled-by-default", "Mark this custom agent as disabled by default").option("--json", "Emit structured JSON output").action(
|
|
2475
|
-
async (options) => {
|
|
2476
|
-
const result = await runConfigAddAgent({
|
|
2477
|
-
id: options.id,
|
|
2478
|
-
root: options.root,
|
|
2479
|
-
skills: options.skills,
|
|
2480
|
-
name: options.name,
|
|
2481
|
-
platforms: options.platform,
|
|
2482
|
-
disabledByDefault: options.disabledByDefault === true,
|
|
2483
|
-
json: options.json === true
|
|
2484
|
-
});
|
|
2485
|
-
process.stdout.write(result.output);
|
|
2486
|
-
}
|
|
2487
|
-
);
|
|
2488
|
-
configCommand.command("remove-agent").requiredOption("--id <id>", "Agent id").option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2489
|
-
const result = await runConfigRemoveAgent({
|
|
2490
|
-
id: options.id,
|
|
2491
|
-
json: options.json === true
|
|
2492
|
-
});
|
|
2493
|
-
process.stdout.write(result.output);
|
|
2494
|
-
});
|
|
2495
|
-
configCommand.command("update-agent").requiredOption("--id <id>", "Agent id").option("--root <path>", "Home-relative root path").option("--skills <path>", "Skills directory path relative to the agent root").option("--name <name>", "Stable display name").option(
|
|
2496
|
-
"--platform <platform>",
|
|
2497
|
-
`Supported platform (${supportedPlatforms.join(", ")})`,
|
|
2498
|
-
(value, previous = []) => [...previous, value],
|
|
2499
|
-
[]
|
|
2500
|
-
).option("--enabled-by-default", "Mark this custom agent as enabled by default").option("--disabled-by-default", "Mark this custom agent as disabled by default").option("--json", "Emit structured JSON output").action(
|
|
2501
|
-
async (options) => {
|
|
2502
|
-
const result = await runConfigUpdateAgent({
|
|
2503
|
-
id: options.id,
|
|
2504
|
-
root: options.root,
|
|
2505
|
-
skills: options.skills,
|
|
2506
|
-
name: options.name,
|
|
2507
|
-
platforms: options.platform !== void 0 && options.platform.length > 0 ? options.platform : void 0,
|
|
2508
|
-
enabledByDefault: options.enabledByDefault === true ? true : void 0,
|
|
2509
|
-
disabledByDefault: options.disabledByDefault === true,
|
|
2510
|
-
json: options.json === true
|
|
2511
|
-
});
|
|
2512
|
-
process.stdout.write(result.output);
|
|
2513
|
-
}
|
|
2514
|
-
);
|
|
2515
|
-
program.command("enable").requiredOption("--skill <skill>", "Managed skill name or id", collectValues, []).requiredOption("--agent <agent>", "Repeatable target agent", collectValues, []).action(async (options) => {
|
|
2516
|
-
const result = await runEnable({
|
|
2517
|
-
skill: requireSingleValue(options.skill, "skill"),
|
|
2518
|
-
agents: requireAtLeastOneValue(options.agent, "agent")
|
|
2519
|
-
});
|
|
2520
|
-
process.stdout.write(result.output);
|
|
2521
|
-
});
|
|
2522
|
-
program.command("disable").requiredOption("--skill <skill>", "Managed skill name or id", collectValues, []).requiredOption("--agent <agent>", "Repeatable target agent", collectValues, []).action(async (options) => {
|
|
2523
|
-
const result = await runDisable({
|
|
2524
|
-
skill: requireSingleValue(options.skill, "skill"),
|
|
2525
|
-
agents: requireAtLeastOneValue(options.agent, "agent")
|
|
2526
|
-
});
|
|
2527
|
-
process.stdout.write(result.output);
|
|
2528
|
-
});
|
|
2529
|
-
program.command("remove").requiredOption("--skill <skill>", "Repeatable managed skill name or id", collectValues, []).option("--json", "Emit structured JSON output").action(async (options) => {
|
|
2530
|
-
const skills = requireAtLeastOneValue(options.skill, "skill");
|
|
2531
|
-
const result = skills.length === 1 ? await runRemove({
|
|
2532
|
-
skill: skills[0],
|
|
2533
|
-
json: options.json === true
|
|
2534
|
-
}) : await runRemove({
|
|
2535
|
-
skills,
|
|
2536
|
-
json: options.json === true
|
|
2537
|
-
});
|
|
2538
|
-
process.stdout.write(result.output);
|
|
2539
|
-
});
|
|
2540
|
-
return program;
|
|
2541
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
buildCli
|
|
4
|
+
} from "./chunk-UMN3UJFN.js";
|
|
5
|
+
import "./chunk-DBEVDI27.js";
|
|
2542
6
|
|
|
2543
7
|
// src/cli.ts
|
|
2544
8
|
await buildCli().parseAsync(process.argv);
|