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