skillmux 0.1.0 → 0.1.1
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 +284 -111
- package/dist/cli.js +463 -180
- package/dist/cli.js.map +1 -1
- package/dist/index.js +463 -180
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -3,27 +3,6 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
|
-
// src/commands/agents.ts
|
|
7
|
-
import { homedir } from "os";
|
|
8
|
-
|
|
9
|
-
// src/config/resolve-skillmux-home.ts
|
|
10
|
-
import { join, resolve } from "path";
|
|
11
|
-
function buildConfigPath(skillmuxHome) {
|
|
12
|
-
return join(resolve(skillmuxHome), "config.json");
|
|
13
|
-
}
|
|
14
|
-
function resolveSkillmuxHome(homeDir) {
|
|
15
|
-
const resolvedHomeDir = resolve(homeDir);
|
|
16
|
-
const skillmuxHome = join(resolvedHomeDir, ".skillmux");
|
|
17
|
-
return {
|
|
18
|
-
skillmuxHome,
|
|
19
|
-
configPath: buildConfigPath(skillmuxHome)
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// src/discovery/discover-agents.ts
|
|
24
|
-
import * as fs2 from "fs/promises";
|
|
25
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
26
|
-
|
|
27
6
|
// src/config/default-agent-rules.ts
|
|
28
7
|
var supportedPlatforms = ["win32", "linux", "darwin"];
|
|
29
8
|
var builtInAgentIds = [
|
|
@@ -84,6 +63,27 @@ var defaultAgentRuleMap = Object.fromEntries(
|
|
|
84
63
|
defaultAgentRules.map((rule) => [rule.id, rule])
|
|
85
64
|
);
|
|
86
65
|
|
|
66
|
+
// src/commands/agents.ts
|
|
67
|
+
import { homedir } from "os";
|
|
68
|
+
|
|
69
|
+
// src/config/resolve-skillmux-home.ts
|
|
70
|
+
import { join, resolve } from "path";
|
|
71
|
+
function buildConfigPath(skillmuxHome) {
|
|
72
|
+
return join(resolve(skillmuxHome), "config.json");
|
|
73
|
+
}
|
|
74
|
+
function resolveSkillmuxHome(homeDir) {
|
|
75
|
+
const resolvedHomeDir = resolve(homeDir);
|
|
76
|
+
const skillmuxHome = join(resolvedHomeDir, ".skillmux");
|
|
77
|
+
return {
|
|
78
|
+
skillmuxHome,
|
|
79
|
+
configPath: buildConfigPath(skillmuxHome)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/discovery/discover-agents.ts
|
|
84
|
+
import * as fs2 from "fs/promises";
|
|
85
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
86
|
+
|
|
87
87
|
// src/config/load-user-config.ts
|
|
88
88
|
import * as fs from "fs/promises";
|
|
89
89
|
import { z } from "zod";
|
|
@@ -95,6 +95,15 @@ var SkillMuxError = class extends Error {
|
|
|
95
95
|
this.name = new.target.name;
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
|
+
var InvalidIdentifierError = class extends SkillMuxError {
|
|
99
|
+
constructor(kind, value) {
|
|
100
|
+
super(`Invalid ${kind}: ${value}`);
|
|
101
|
+
this.kind = kind;
|
|
102
|
+
this.value = value;
|
|
103
|
+
}
|
|
104
|
+
kind;
|
|
105
|
+
value;
|
|
106
|
+
};
|
|
98
107
|
var ManifestValidationError = class extends SkillMuxError {
|
|
99
108
|
constructor(message) {
|
|
100
109
|
super(message);
|
|
@@ -125,6 +134,9 @@ function createEmptyUserConfig() {
|
|
|
125
134
|
agents: {}
|
|
126
135
|
};
|
|
127
136
|
}
|
|
137
|
+
function stripUtf8Bom(contents) {
|
|
138
|
+
return contents.charCodeAt(0) === 65279 ? contents.slice(1) : contents;
|
|
139
|
+
}
|
|
128
140
|
function formatValidationIssues(error) {
|
|
129
141
|
return error.issues.map((issue) => {
|
|
130
142
|
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
@@ -135,7 +147,7 @@ async function loadUserConfig(skillmuxHome) {
|
|
|
135
147
|
const configPath = buildConfigPath(skillmuxHome);
|
|
136
148
|
try {
|
|
137
149
|
const contents = await fs.readFile(configPath, "utf8");
|
|
138
|
-
const parsed = JSON.parse(contents);
|
|
150
|
+
const parsed = JSON.parse(stripUtf8Bom(contents));
|
|
139
151
|
const validated = userConfigSchema.safeParse(parsed);
|
|
140
152
|
if (!validated.success) {
|
|
141
153
|
throw new UserConfigValidationError(
|
|
@@ -301,9 +313,212 @@ async function runAgents(options = {}) {
|
|
|
301
313
|
};
|
|
302
314
|
}
|
|
303
315
|
|
|
304
|
-
// src/commands/config.ts
|
|
316
|
+
// src/commands/config-add-agent.ts
|
|
305
317
|
import { homedir as homedir2 } from "os";
|
|
318
|
+
import { isAbsolute } from "path";
|
|
319
|
+
|
|
320
|
+
// src/core/ids.ts
|
|
321
|
+
var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
322
|
+
function normalizeId(value) {
|
|
323
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
324
|
+
return normalized.length > 0 ? normalized : "skill";
|
|
325
|
+
}
|
|
326
|
+
function isValidId(value) {
|
|
327
|
+
return ID_PATTERN.test(value);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/config/write-user-config.ts
|
|
331
|
+
import * as fs3 from "fs/promises";
|
|
332
|
+
async function writeUserConfig(skillmuxHome, config) {
|
|
333
|
+
const configPath = buildConfigPath(skillmuxHome);
|
|
334
|
+
await fs3.mkdir(skillmuxHome, { recursive: true });
|
|
335
|
+
await fs3.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
336
|
+
`, "utf8");
|
|
337
|
+
return {
|
|
338
|
+
skillmuxHome,
|
|
339
|
+
configPath
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/commands/config-add-agent.ts
|
|
344
|
+
function normalizeRelativePath(value, field) {
|
|
345
|
+
const trimmed = value.trim();
|
|
346
|
+
if (trimmed.length === 0) {
|
|
347
|
+
throw new UserConfigValidationError(`${field} must not be empty`);
|
|
348
|
+
}
|
|
349
|
+
if (isAbsolute(trimmed)) {
|
|
350
|
+
throw new UserConfigValidationError(`${field} must be a relative path`);
|
|
351
|
+
}
|
|
352
|
+
const normalized = trimmed.replaceAll("\\", "/");
|
|
353
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
354
|
+
throw new UserConfigValidationError(`${field} must stay within the configured home-relative tree`);
|
|
355
|
+
}
|
|
356
|
+
return normalized.replace(/^\.\/+/, "");
|
|
357
|
+
}
|
|
358
|
+
function normalizeAgentId(value) {
|
|
359
|
+
const trimmed = value.trim();
|
|
360
|
+
if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
|
|
361
|
+
throw new InvalidIdentifierError("agent id", value);
|
|
362
|
+
}
|
|
363
|
+
return normalizeId(trimmed);
|
|
364
|
+
}
|
|
365
|
+
function normalizePlatforms(value) {
|
|
366
|
+
if (value === void 0 || value.length === 0) {
|
|
367
|
+
return [process.platform];
|
|
368
|
+
}
|
|
369
|
+
const normalized = [...new Set(value.map((entry) => entry.trim().toLowerCase()))];
|
|
370
|
+
const invalid = normalized.filter(
|
|
371
|
+
(entry) => supportedPlatforms.includes(entry) === false
|
|
372
|
+
);
|
|
373
|
+
if (invalid.length > 0) {
|
|
374
|
+
throw new UserConfigValidationError(
|
|
375
|
+
`platform must be one of: ${supportedPlatforms.join(", ")}`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return normalized;
|
|
379
|
+
}
|
|
380
|
+
function buildAgentOverride(options) {
|
|
381
|
+
const agentId = normalizeAgentId(options.id);
|
|
382
|
+
const agent = {
|
|
383
|
+
supportedPlatforms: normalizePlatforms(options.platforms),
|
|
384
|
+
homeRelativeRootPath: normalizeRelativePath(options.root, "root"),
|
|
385
|
+
skillsDirectoryPath: normalizeRelativePath(options.skills ?? "skills", "skills")
|
|
386
|
+
};
|
|
387
|
+
if (options.name !== void 0 && options.name.trim().length > 0) {
|
|
388
|
+
agent.stableName = options.name.trim();
|
|
389
|
+
}
|
|
390
|
+
if (options.disabledByDefault === true) {
|
|
391
|
+
agent.enabledByDefault = false;
|
|
392
|
+
}
|
|
393
|
+
return { agentId, agent };
|
|
394
|
+
}
|
|
306
395
|
function buildTableOutput2(result) {
|
|
396
|
+
const summary = printTable(
|
|
397
|
+
[
|
|
398
|
+
{
|
|
399
|
+
agentId: result.agentId,
|
|
400
|
+
configPath: result.configPath,
|
|
401
|
+
changed: String(result.changed)
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
[
|
|
405
|
+
{ key: "agentId", label: "Agent" },
|
|
406
|
+
{ key: "configPath", label: "Config Path" },
|
|
407
|
+
{ key: "changed", label: "Changed" }
|
|
408
|
+
]
|
|
409
|
+
);
|
|
410
|
+
const detail = printTable(
|
|
411
|
+
[
|
|
412
|
+
{
|
|
413
|
+
stableName: result.agent.stableName ?? "",
|
|
414
|
+
platforms: (result.agent.supportedPlatforms ?? []).join(","),
|
|
415
|
+
root: result.agent.homeRelativeRootPath ?? "",
|
|
416
|
+
skills: result.agent.skillsDirectoryPath ?? "",
|
|
417
|
+
enabledByDefault: result.agent.enabledByDefault === void 0 ? "" : String(result.agent.enabledByDefault)
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
[
|
|
421
|
+
{ key: "stableName", label: "Name" },
|
|
422
|
+
{ key: "platforms", label: "Platforms" },
|
|
423
|
+
{ key: "root", label: "Root" },
|
|
424
|
+
{ key: "skills", label: "Skills Dir" },
|
|
425
|
+
{ key: "enabledByDefault", label: "Enabled By Default" }
|
|
426
|
+
]
|
|
427
|
+
);
|
|
428
|
+
return `${summary}${detail}`;
|
|
429
|
+
}
|
|
430
|
+
async function runConfigAddAgent(options) {
|
|
431
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
432
|
+
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
433
|
+
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
434
|
+
const configPath = buildConfigPath(skillmuxHome);
|
|
435
|
+
const config = await loadUserConfig(skillmuxHome);
|
|
436
|
+
const { agentId, agent } = buildAgentOverride(options);
|
|
437
|
+
const previous = config.agents[agentId];
|
|
438
|
+
const changed = JSON.stringify(previous ?? null) !== JSON.stringify(agent);
|
|
439
|
+
const nextConfig = {
|
|
440
|
+
...config,
|
|
441
|
+
agents: {
|
|
442
|
+
...config.agents,
|
|
443
|
+
[agentId]: agent
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
await writeUserConfig(skillmuxHome, nextConfig);
|
|
447
|
+
const resultWithoutOutput = {
|
|
448
|
+
skillmuxHome,
|
|
449
|
+
configPath,
|
|
450
|
+
agentId,
|
|
451
|
+
changed,
|
|
452
|
+
agent,
|
|
453
|
+
config: nextConfig
|
|
454
|
+
};
|
|
455
|
+
return {
|
|
456
|
+
...resultWithoutOutput,
|
|
457
|
+
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput2(resultWithoutOutput)
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/commands/config-remove-agent.ts
|
|
462
|
+
import { homedir as homedir3 } from "os";
|
|
463
|
+
function normalizeAgentId2(value) {
|
|
464
|
+
const trimmed = value.trim();
|
|
465
|
+
if (trimmed.length === 0 || /[a-z0-9]/i.test(trimmed) === false) {
|
|
466
|
+
throw new InvalidIdentifierError("agent id", value);
|
|
467
|
+
}
|
|
468
|
+
return normalizeId(trimmed);
|
|
469
|
+
}
|
|
470
|
+
function buildTableOutput3(result) {
|
|
471
|
+
return printTable(
|
|
472
|
+
[
|
|
473
|
+
{
|
|
474
|
+
agentId: result.agentId,
|
|
475
|
+
configPath: result.configPath,
|
|
476
|
+
changed: String(result.changed),
|
|
477
|
+
removed: String(result.removed)
|
|
478
|
+
}
|
|
479
|
+
],
|
|
480
|
+
[
|
|
481
|
+
{ key: "agentId", label: "Agent" },
|
|
482
|
+
{ key: "configPath", label: "Config Path" },
|
|
483
|
+
{ key: "changed", label: "Changed" },
|
|
484
|
+
{ key: "removed", label: "Removed" }
|
|
485
|
+
]
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
async function runConfigRemoveAgent(options) {
|
|
489
|
+
const homeDir = options.homeDir ?? homedir3();
|
|
490
|
+
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
491
|
+
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
492
|
+
const configPath = buildConfigPath(skillmuxHome);
|
|
493
|
+
const config = await loadUserConfig(skillmuxHome);
|
|
494
|
+
const agentId = normalizeAgentId2(options.id);
|
|
495
|
+
const removed = agentId in config.agents;
|
|
496
|
+
const nextConfig = {
|
|
497
|
+
...config,
|
|
498
|
+
agents: Object.fromEntries(
|
|
499
|
+
Object.entries(config.agents).filter(([currentAgentId]) => currentAgentId !== agentId)
|
|
500
|
+
)
|
|
501
|
+
};
|
|
502
|
+
if (removed) {
|
|
503
|
+
await writeUserConfig(skillmuxHome, nextConfig);
|
|
504
|
+
}
|
|
505
|
+
const resultWithoutOutput = {
|
|
506
|
+
skillmuxHome,
|
|
507
|
+
configPath,
|
|
508
|
+
agentId,
|
|
509
|
+
changed: removed,
|
|
510
|
+
removed,
|
|
511
|
+
config: nextConfig
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
...resultWithoutOutput,
|
|
515
|
+
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput3(resultWithoutOutput)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/commands/config.ts
|
|
520
|
+
import { homedir as homedir4 } from "os";
|
|
521
|
+
function buildTableOutput4(result) {
|
|
307
522
|
const summary = printTable(
|
|
308
523
|
[
|
|
309
524
|
{
|
|
@@ -338,35 +553,35 @@ ${printTable(agentRows, [
|
|
|
338
553
|
])}`;
|
|
339
554
|
}
|
|
340
555
|
async function runConfig(options = {}) {
|
|
341
|
-
const homeDir = options.homeDir ??
|
|
556
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
342
557
|
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
343
558
|
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
344
559
|
const config = await loadUserConfig(skillmuxHome);
|
|
345
560
|
const resultWithoutOutput = {
|
|
346
561
|
skillmuxHome,
|
|
347
|
-
configPath:
|
|
562
|
+
configPath: buildConfigPath(skillmuxHome),
|
|
348
563
|
config
|
|
349
564
|
};
|
|
350
565
|
return {
|
|
351
566
|
...resultWithoutOutput,
|
|
352
|
-
output: options.json === true ? printJson(resultWithoutOutput) :
|
|
567
|
+
output: options.json === true ? printJson(resultWithoutOutput) : buildTableOutput4(resultWithoutOutput)
|
|
353
568
|
};
|
|
354
569
|
}
|
|
355
570
|
|
|
356
571
|
// src/commands/doctor.ts
|
|
357
|
-
import * as
|
|
358
|
-
import { homedir as
|
|
572
|
+
import * as fs9 from "fs/promises";
|
|
573
|
+
import { homedir as homedir5 } from "os";
|
|
359
574
|
import { join as join5 } from "path";
|
|
360
575
|
|
|
361
576
|
// src/discovery/scan-agent-skills.ts
|
|
362
|
-
import * as
|
|
577
|
+
import * as fs6 from "fs/promises";
|
|
363
578
|
|
|
364
579
|
// src/discovery/infer-skill-entry.ts
|
|
365
|
-
import * as
|
|
580
|
+
import * as fs5 from "fs/promises";
|
|
366
581
|
import { basename, resolve as resolve4 } from "path";
|
|
367
582
|
|
|
368
583
|
// src/fs/path-utils.ts
|
|
369
|
-
import * as
|
|
584
|
+
import * as fs4 from "fs/promises";
|
|
370
585
|
import { dirname, parse, relative, resolve as resolve3, sep } from "path";
|
|
371
586
|
function normalizeAbsolutePath(path) {
|
|
372
587
|
const normalized = resolve3(path);
|
|
@@ -397,7 +612,7 @@ async function assertNoSymlinkAncestors(path, options) {
|
|
|
397
612
|
let current = options?.includeLeaf === true ? resolve3(path) : dirname(resolve3(path));
|
|
398
613
|
while (true) {
|
|
399
614
|
try {
|
|
400
|
-
const entry = await
|
|
615
|
+
const entry = await fs4.lstat(current);
|
|
401
616
|
if (entry.isSymbolicLink()) {
|
|
402
617
|
throw new Error(`Refusing to use path with symlink ancestor at ${current}`);
|
|
403
618
|
}
|
|
@@ -421,10 +636,10 @@ function buildIssue(code, severity, message, path) {
|
|
|
421
636
|
async function inferSkillEntry(options) {
|
|
422
637
|
const absolutePath = resolve4(options.path);
|
|
423
638
|
const skillName = basename(absolutePath);
|
|
424
|
-
const stats = await
|
|
639
|
+
const stats = await fs5.lstat(absolutePath);
|
|
425
640
|
if (stats.isSymbolicLink()) {
|
|
426
641
|
try {
|
|
427
|
-
const targetPath = await
|
|
642
|
+
const targetPath = await fs5.realpath(absolutePath);
|
|
428
643
|
if (isPathInside(options.skillmuxHome, targetPath)) {
|
|
429
644
|
return {
|
|
430
645
|
entry: {
|
|
@@ -510,7 +725,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
|
|
|
510
725
|
issues: []
|
|
511
726
|
};
|
|
512
727
|
}
|
|
513
|
-
const directoryEntries = await
|
|
728
|
+
const directoryEntries = await fs6.readdir(agent.absoluteSkillsDirectoryPath, {
|
|
514
729
|
withFileTypes: true
|
|
515
730
|
});
|
|
516
731
|
const sortedDirectoryEntries = [...directoryEntries].sort(
|
|
@@ -537,7 +752,7 @@ async function scanAgentSkills(agent, skillmuxHome) {
|
|
|
537
752
|
}
|
|
538
753
|
|
|
539
754
|
// src/manifest/read-manifest.ts
|
|
540
|
-
import * as
|
|
755
|
+
import * as fs8 from "fs/promises";
|
|
541
756
|
import { join as join4, resolve as resolve5 } from "path";
|
|
542
757
|
|
|
543
758
|
// src/manifest/build-empty-manifest.ts
|
|
@@ -557,18 +772,6 @@ function buildEmptyManifest(skillmuxHome) {
|
|
|
557
772
|
|
|
558
773
|
// src/manifest/manifest-schema.ts
|
|
559
774
|
import { z as z2 } from "zod";
|
|
560
|
-
|
|
561
|
-
// src/core/ids.ts
|
|
562
|
-
var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
563
|
-
function normalizeId(value) {
|
|
564
|
-
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
565
|
-
return normalized.length > 0 ? normalized : "skill";
|
|
566
|
-
}
|
|
567
|
-
function isValidId(value) {
|
|
568
|
-
return ID_PATTERN.test(value);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// src/manifest/manifest-schema.ts
|
|
572
775
|
var idSchema = z2.string().min(1).refine(isValidId, "Expected a canonical lowercase slug identifier");
|
|
573
776
|
var scanIssueSchema = z2.object({
|
|
574
777
|
code: z2.string().min(1),
|
|
@@ -675,7 +878,7 @@ var manifestSchema = z2.object({
|
|
|
675
878
|
|
|
676
879
|
// src/manifest/write-manifest.ts
|
|
677
880
|
import { randomUUID } from "crypto";
|
|
678
|
-
import * as
|
|
881
|
+
import * as fs7 from "fs/promises";
|
|
679
882
|
import { join as join3 } from "path";
|
|
680
883
|
function getManifestPath(home) {
|
|
681
884
|
return join3(home, "manifest.json");
|
|
@@ -684,16 +887,16 @@ function createManifestTempPath(manifestPath) {
|
|
|
684
887
|
return `${manifestPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
685
888
|
}
|
|
686
889
|
async function writeManifest(home, manifest) {
|
|
687
|
-
await
|
|
890
|
+
await fs7.mkdir(home, { recursive: true });
|
|
688
891
|
const manifestPath = getManifestPath(home);
|
|
689
892
|
const tempPath = createManifestTempPath(manifestPath);
|
|
690
893
|
const contents = `${JSON.stringify(manifest, null, 2)}
|
|
691
894
|
`;
|
|
692
|
-
await
|
|
895
|
+
await fs7.writeFile(tempPath, contents, "utf8");
|
|
693
896
|
try {
|
|
694
|
-
await
|
|
897
|
+
await fs7.rename(tempPath, manifestPath);
|
|
695
898
|
} catch (error) {
|
|
696
|
-
await
|
|
899
|
+
await fs7.unlink(tempPath).catch(() => void 0);
|
|
697
900
|
throw error;
|
|
698
901
|
}
|
|
699
902
|
}
|
|
@@ -715,7 +918,7 @@ function formatValidationIssues2(error) {
|
|
|
715
918
|
async function readManifest(home) {
|
|
716
919
|
const manifestPath = getManifestPath2(home);
|
|
717
920
|
try {
|
|
718
|
-
const contents = await
|
|
921
|
+
const contents = await fs8.readFile(manifestPath, "utf8");
|
|
719
922
|
const parsedJson = JSON.parse(contents);
|
|
720
923
|
const parsedManifest = manifestSchema.safeParse(parsedJson);
|
|
721
924
|
if (!parsedManifest.success) {
|
|
@@ -750,7 +953,7 @@ function buildIssue2(code, severity, message, path) {
|
|
|
750
953
|
}
|
|
751
954
|
async function pathExists2(path) {
|
|
752
955
|
try {
|
|
753
|
-
await
|
|
956
|
+
await fs9.access(path);
|
|
754
957
|
return true;
|
|
755
958
|
} catch (error) {
|
|
756
959
|
if (error.code === "ENOENT") {
|
|
@@ -814,7 +1017,7 @@ function addConflictingAgentPathIssues(agents, issues) {
|
|
|
814
1017
|
);
|
|
815
1018
|
}
|
|
816
1019
|
}
|
|
817
|
-
function
|
|
1020
|
+
function buildTableOutput5(issues) {
|
|
818
1021
|
if (issues.length === 0) {
|
|
819
1022
|
return "No doctor issues found.\n";
|
|
820
1023
|
}
|
|
@@ -846,7 +1049,7 @@ function buildJsonOutput(result) {
|
|
|
846
1049
|
});
|
|
847
1050
|
}
|
|
848
1051
|
async function runDoctor(options = {}) {
|
|
849
|
-
const homeDir = options.homeDir ??
|
|
1052
|
+
const homeDir = options.homeDir ?? homedir5();
|
|
850
1053
|
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
851
1054
|
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
852
1055
|
const [manifest, config, agents] = await Promise.all([
|
|
@@ -883,51 +1086,127 @@ async function runDoctor(options = {}) {
|
|
|
883
1086
|
};
|
|
884
1087
|
return {
|
|
885
1088
|
...resultWithoutOutput,
|
|
886
|
-
output: options.json === true ? buildJsonOutput(resultWithoutOutput) :
|
|
1089
|
+
output: options.json === true ? buildJsonOutput(resultWithoutOutput) : buildTableOutput5(dedupedIssues)
|
|
887
1090
|
};
|
|
888
1091
|
}
|
|
889
1092
|
|
|
890
1093
|
// src/commands/disable.ts
|
|
891
|
-
import * as
|
|
892
|
-
import { homedir as
|
|
893
|
-
import { join as
|
|
1094
|
+
import * as fs13 from "fs/promises";
|
|
1095
|
+
import { homedir as homedir6 } from "os";
|
|
1096
|
+
import { join as join7, resolve as resolve8 } from "path";
|
|
1097
|
+
|
|
1098
|
+
// src/fs/safe-copy.ts
|
|
1099
|
+
import * as fs10 from "fs/promises";
|
|
1100
|
+
import { dirname as dirname2, join as join6, resolve as resolve6 } from "path";
|
|
1101
|
+
async function assertDirectory(path) {
|
|
1102
|
+
const entry = await fs10.lstat(path);
|
|
1103
|
+
if (!entry.isDirectory()) {
|
|
1104
|
+
throw new Error(`Expected a directory at ${path}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async function assertRegularFile(path, label) {
|
|
1108
|
+
const entry = await fs10.lstat(path);
|
|
1109
|
+
if (!entry.isFile()) {
|
|
1110
|
+
throw new Error(`Expected ${label} to be a regular file at ${path}`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function assertTargetDoesNotExist(path) {
|
|
1114
|
+
try {
|
|
1115
|
+
await fs10.lstat(path);
|
|
1116
|
+
throw new Error(`Refusing to overwrite existing path at ${path}`);
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
if (error.code !== "ENOENT") {
|
|
1119
|
+
throw error;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
async function copyDirectoryContents(sourcePath, targetPath) {
|
|
1124
|
+
await fs10.mkdir(targetPath, { recursive: true });
|
|
1125
|
+
const entries = await fs10.readdir(sourcePath, { withFileTypes: true });
|
|
1126
|
+
for (const entry of entries) {
|
|
1127
|
+
const sourceEntryPath = join6(sourcePath, entry.name);
|
|
1128
|
+
const targetEntryPath = join6(targetPath, entry.name);
|
|
1129
|
+
const entryStats = await fs10.lstat(sourceEntryPath);
|
|
1130
|
+
if (entryStats.isSymbolicLink()) {
|
|
1131
|
+
throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
|
|
1132
|
+
}
|
|
1133
|
+
if (entryStats.isDirectory()) {
|
|
1134
|
+
await copyDirectoryContents(sourceEntryPath, targetEntryPath);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (entryStats.isFile()) {
|
|
1138
|
+
await fs10.mkdir(dirname2(targetEntryPath), { recursive: true });
|
|
1139
|
+
await fs10.copyFile(sourceEntryPath, targetEntryPath);
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function assertSkillSourceLayout(sourcePath) {
|
|
1146
|
+
const resolvedSourcePath = resolve6(sourcePath);
|
|
1147
|
+
const skillFilePath = join6(resolvedSourcePath, "SKILL.md");
|
|
1148
|
+
await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
|
|
1149
|
+
await assertDirectory(resolvedSourcePath);
|
|
1150
|
+
try {
|
|
1151
|
+
await assertRegularFile(skillFilePath, "SKILL.md");
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
if (error.code === "ENOENT") {
|
|
1154
|
+
throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
|
|
1155
|
+
}
|
|
1156
|
+
throw error;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function copySkillContentsToManagedStore(sourcePath, targetPath) {
|
|
1160
|
+
const resolvedSourcePath = resolve6(sourcePath);
|
|
1161
|
+
const resolvedTargetPath = resolve6(targetPath);
|
|
1162
|
+
if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
|
|
1163
|
+
throw new Error("Source and target paths must differ");
|
|
1164
|
+
}
|
|
1165
|
+
if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
|
|
1166
|
+
throw new Error("Refusing to copy into a child of the source directory");
|
|
1167
|
+
}
|
|
1168
|
+
await assertSkillSourceLayout(resolvedSourcePath);
|
|
1169
|
+
await assertNoSymlinkAncestors(resolvedTargetPath);
|
|
1170
|
+
await assertTargetDoesNotExist(resolvedTargetPath);
|
|
1171
|
+
await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
|
|
1172
|
+
}
|
|
894
1173
|
|
|
895
1174
|
// src/fs/link-ops.ts
|
|
896
|
-
import * as
|
|
897
|
-
import { dirname as
|
|
1175
|
+
import * as fs11 from "fs/promises";
|
|
1176
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
898
1177
|
var directoryLinkType = process.platform === "win32" ? "junction" : "dir";
|
|
899
1178
|
async function createManagedLink(linkPath, targetPath) {
|
|
900
|
-
const resolvedLinkPath =
|
|
901
|
-
const resolvedTargetPath =
|
|
1179
|
+
const resolvedLinkPath = resolve7(linkPath);
|
|
1180
|
+
const resolvedTargetPath = resolve7(targetPath);
|
|
902
1181
|
await assertNoSymlinkAncestors(resolvedLinkPath);
|
|
903
1182
|
await assertNoSymlinkAncestors(resolvedTargetPath, { includeLeaf: true });
|
|
904
|
-
await
|
|
1183
|
+
await fs11.mkdir(dirname3(resolvedLinkPath), { recursive: true });
|
|
905
1184
|
try {
|
|
906
|
-
const existingEntry = await
|
|
1185
|
+
const existingEntry = await fs11.lstat(resolvedLinkPath);
|
|
907
1186
|
if (!existingEntry.isSymbolicLink()) {
|
|
908
1187
|
throw new Error(`Refusing to replace non-link entry at ${resolvedLinkPath}`);
|
|
909
1188
|
}
|
|
910
|
-
const currentTargetPath = await
|
|
1189
|
+
const currentTargetPath = await fs11.realpath(resolvedLinkPath);
|
|
911
1190
|
if (pathsAreEqual(currentTargetPath, resolvedTargetPath)) {
|
|
912
1191
|
return;
|
|
913
1192
|
}
|
|
914
1193
|
throw new Error(`Refusing to replace link at ${resolvedLinkPath}`);
|
|
915
1194
|
} catch (error) {
|
|
916
|
-
if (error.code === "ENOENT" && await
|
|
917
|
-
await
|
|
1195
|
+
if (error.code === "ENOENT" && await fs11.lstat(resolvedLinkPath).then((entry) => entry.isSymbolicLink()).catch(() => false)) {
|
|
1196
|
+
await fs11.rm(resolvedLinkPath, { recursive: true, force: false });
|
|
918
1197
|
} else if (error.code !== "ENOENT") {
|
|
919
1198
|
throw error;
|
|
920
1199
|
}
|
|
921
1200
|
}
|
|
922
|
-
await
|
|
1201
|
+
await fs11.symlink(resolvedTargetPath, resolvedLinkPath, directoryLinkType);
|
|
923
1202
|
}
|
|
924
1203
|
async function isLinkPointingToTarget(linkPath, targetPath) {
|
|
925
1204
|
try {
|
|
926
|
-
const entry = await
|
|
1205
|
+
const entry = await fs11.lstat(linkPath);
|
|
927
1206
|
if (!entry.isSymbolicLink()) {
|
|
928
1207
|
return false;
|
|
929
1208
|
}
|
|
930
|
-
const resolvedTargetPath = await
|
|
1209
|
+
const resolvedTargetPath = await fs11.realpath(linkPath);
|
|
931
1210
|
return pathsAreEqual(resolvedTargetPath, targetPath);
|
|
932
1211
|
} catch (error) {
|
|
933
1212
|
if (error.code === "ENOENT") {
|
|
@@ -938,14 +1217,14 @@ async function isLinkPointingToTarget(linkPath, targetPath) {
|
|
|
938
1217
|
}
|
|
939
1218
|
|
|
940
1219
|
// src/fs/safe-remove-link.ts
|
|
941
|
-
import * as
|
|
1220
|
+
import * as fs12 from "fs/promises";
|
|
942
1221
|
async function safeRemoveLink(path) {
|
|
943
1222
|
try {
|
|
944
|
-
const entry = await
|
|
1223
|
+
const entry = await fs12.lstat(path);
|
|
945
1224
|
if (!entry.isSymbolicLink()) {
|
|
946
1225
|
return false;
|
|
947
1226
|
}
|
|
948
|
-
await
|
|
1227
|
+
await fs12.rm(path, { recursive: true, force: false });
|
|
949
1228
|
return true;
|
|
950
1229
|
} catch (error) {
|
|
951
1230
|
if (error.code === "ENOENT") {
|
|
@@ -985,6 +1264,38 @@ function upsertActivation(manifest, activation) {
|
|
|
985
1264
|
}
|
|
986
1265
|
manifest.activations[index] = activation;
|
|
987
1266
|
}
|
|
1267
|
+
function buildManagedSkillPath(skillmuxHome, skillId) {
|
|
1268
|
+
return resolve8(skillmuxHome, "skills", skillId);
|
|
1269
|
+
}
|
|
1270
|
+
async function tryAdoptManagedSkill(manifest, skillmuxHome, skillId, skillName, linkPath, timestamp) {
|
|
1271
|
+
try {
|
|
1272
|
+
const entry = await fs13.lstat(linkPath);
|
|
1273
|
+
if (!entry.isSymbolicLink()) {
|
|
1274
|
+
return void 0;
|
|
1275
|
+
}
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
if (error.code === "ENOENT") {
|
|
1278
|
+
return void 0;
|
|
1279
|
+
}
|
|
1280
|
+
throw error;
|
|
1281
|
+
}
|
|
1282
|
+
const sourcePath = await fs13.realpath(linkPath);
|
|
1283
|
+
await assertSkillSourceLayout(sourcePath);
|
|
1284
|
+
const managedSkillPath = buildManagedSkillPath(skillmuxHome, skillId);
|
|
1285
|
+
await copySkillContentsToManagedStore(sourcePath, managedSkillPath);
|
|
1286
|
+
const skill = {
|
|
1287
|
+
id: skillId,
|
|
1288
|
+
name: skillName,
|
|
1289
|
+
path: managedSkillPath,
|
|
1290
|
+
source: {
|
|
1291
|
+
kind: "imported",
|
|
1292
|
+
path: sourcePath
|
|
1293
|
+
},
|
|
1294
|
+
importedAt: timestamp
|
|
1295
|
+
};
|
|
1296
|
+
manifest.skills[skillId] = skill;
|
|
1297
|
+
return { skill, sourcePath };
|
|
1298
|
+
}
|
|
988
1299
|
async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
|
|
989
1300
|
const agentId = normalizeId(agentName);
|
|
990
1301
|
const agents = await discoverAgents({ homeDir, skillmuxHome });
|
|
@@ -999,7 +1310,7 @@ async function resolveTargetAgent(homeDir, skillmuxHome, agentName) {
|
|
|
999
1310
|
}
|
|
1000
1311
|
async function pathExists3(path) {
|
|
1001
1312
|
try {
|
|
1002
|
-
await
|
|
1313
|
+
await fs13.lstat(path);
|
|
1003
1314
|
return true;
|
|
1004
1315
|
} catch (error) {
|
|
1005
1316
|
if (error.code === "ENOENT") {
|
|
@@ -1009,28 +1320,38 @@ async function pathExists3(path) {
|
|
|
1009
1320
|
}
|
|
1010
1321
|
}
|
|
1011
1322
|
async function runDisable(options) {
|
|
1012
|
-
const homeDir = options.homeDir ??
|
|
1323
|
+
const homeDir = options.homeDir ?? homedir6();
|
|
1013
1324
|
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
1014
1325
|
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
1015
1326
|
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1016
1327
|
const manifest = await readManifest(skillmuxHome);
|
|
1017
1328
|
const skillId = normalizeId(options.skill);
|
|
1018
|
-
const
|
|
1329
|
+
const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
|
|
1330
|
+
const linkPath = join7(agent.absoluteSkillsDirectoryPath, skillId);
|
|
1331
|
+
const adoption = manifest.skills[skillId] ? void 0 : await tryAdoptManagedSkill(
|
|
1332
|
+
manifest,
|
|
1333
|
+
skillmuxHome,
|
|
1334
|
+
skillId,
|
|
1335
|
+
options.skill,
|
|
1336
|
+
linkPath,
|
|
1337
|
+
timestamp
|
|
1338
|
+
);
|
|
1339
|
+
const skill = manifest.skills[skillId] ?? adoption?.skill;
|
|
1019
1340
|
if (skill === void 0) {
|
|
1020
1341
|
throw new Error(`Managed skill not found: ${skillId}`);
|
|
1021
1342
|
}
|
|
1022
|
-
const agent = await resolveTargetAgent(homeDir, skillmuxHome, options.agent);
|
|
1023
1343
|
const currentActivation = manifest.activations.find(
|
|
1024
1344
|
(entry) => entry.skillId === skill.id && entry.agentId === agent.id
|
|
1025
1345
|
);
|
|
1026
|
-
const
|
|
1346
|
+
const activationLinkPath = currentActivation?.linkPath ?? linkPath;
|
|
1027
1347
|
const agentRecord = buildAgentRecord(agent, timestamp);
|
|
1028
1348
|
manifest.agents[agent.id] = agentRecord;
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1349
|
+
const adoptedLinkRemoved = adoption !== void 0 ? await safeRemoveLink(linkPath) : false;
|
|
1350
|
+
const linkMatchesSkill = adoption === void 0 ? await isLinkPointingToTarget(activationLinkPath, skill.path) : false;
|
|
1351
|
+
if (adoption === void 0 && !linkMatchesSkill && await pathExists3(activationLinkPath)) {
|
|
1031
1352
|
throw new Error(`Refusing to disable non-managed entry at ${linkPath}`);
|
|
1032
1353
|
}
|
|
1033
|
-
const removedLink = linkMatchesSkill ? await safeRemoveLink(
|
|
1354
|
+
const removedLink = adoptedLinkRemoved ? true : linkMatchesSkill ? await safeRemoveLink(activationLinkPath) : false;
|
|
1034
1355
|
if (removedLink === false && currentActivation?.state !== "enabled") {
|
|
1035
1356
|
return {
|
|
1036
1357
|
changed: false,
|
|
@@ -1057,9 +1378,9 @@ async function runDisable(options) {
|
|
|
1057
1378
|
}
|
|
1058
1379
|
|
|
1059
1380
|
// src/commands/enable.ts
|
|
1060
|
-
import * as
|
|
1061
|
-
import { homedir as
|
|
1062
|
-
import { join as
|
|
1381
|
+
import * as fs14 from "fs/promises";
|
|
1382
|
+
import { homedir as homedir7 } from "os";
|
|
1383
|
+
import { join as join8 } from "path";
|
|
1063
1384
|
function buildAgentRecord2(agent, timestamp) {
|
|
1064
1385
|
return {
|
|
1065
1386
|
id: agent.id,
|
|
@@ -1102,7 +1423,7 @@ async function resolveTargetAgent2(homeDir, skillmuxHome, agentName) {
|
|
|
1102
1423
|
return agent;
|
|
1103
1424
|
}
|
|
1104
1425
|
async function runEnable(options) {
|
|
1105
|
-
const homeDir = options.homeDir ??
|
|
1426
|
+
const homeDir = options.homeDir ?? homedir7();
|
|
1106
1427
|
const { skillmuxHome: defaultSkillmuxHome } = resolveSkillmuxHome(homeDir);
|
|
1107
1428
|
const skillmuxHome = options.skillmuxHome ?? defaultSkillmuxHome;
|
|
1108
1429
|
const timestamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1113,13 +1434,13 @@ async function runEnable(options) {
|
|
|
1113
1434
|
throw new Error(`Managed skill not found: ${skillId}`);
|
|
1114
1435
|
}
|
|
1115
1436
|
const agent = await resolveTargetAgent2(homeDir, skillmuxHome, options.agent);
|
|
1116
|
-
const linkPath =
|
|
1437
|
+
const linkPath = join8(agent.absoluteSkillsDirectoryPath, skill.id);
|
|
1117
1438
|
const currentActivation = manifest.activations.find(
|
|
1118
1439
|
(entry) => entry.skillId === skill.id && entry.agentId === agent.id
|
|
1119
1440
|
);
|
|
1120
1441
|
const agentRecord = buildAgentRecord2(agent, timestamp);
|
|
1121
1442
|
manifest.agents[agent.id] = agentRecord;
|
|
1122
|
-
await
|
|
1443
|
+
await fs14.mkdir(agent.absoluteSkillsDirectoryPath, { recursive: true });
|
|
1123
1444
|
const linkAlreadyEnabled = await isLinkPointingToTarget(linkPath, skill.path);
|
|
1124
1445
|
const activationAlreadyEnabled = currentActivation?.state === "enabled" && currentActivation.linkPath === linkPath;
|
|
1125
1446
|
if (linkAlreadyEnabled && activationAlreadyEnabled) {
|
|
@@ -1155,98 +1476,20 @@ async function runEnable(options) {
|
|
|
1155
1476
|
}
|
|
1156
1477
|
|
|
1157
1478
|
// src/commands/import.ts
|
|
1158
|
-
import { resolve as
|
|
1159
|
-
import { homedir as
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
import * as fs13 from "fs/promises";
|
|
1163
|
-
import { dirname as dirname3, join as join8, resolve as resolve7 } from "path";
|
|
1164
|
-
async function assertDirectory(path) {
|
|
1165
|
-
const entry = await fs13.lstat(path);
|
|
1166
|
-
if (!entry.isDirectory()) {
|
|
1167
|
-
throw new Error(`Expected a directory at ${path}`);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
async function assertRegularFile(path, label) {
|
|
1171
|
-
const entry = await fs13.lstat(path);
|
|
1172
|
-
if (!entry.isFile()) {
|
|
1173
|
-
throw new Error(`Expected ${label} to be a regular file at ${path}`);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
async function assertTargetDoesNotExist(path) {
|
|
1177
|
-
try {
|
|
1178
|
-
await fs13.lstat(path);
|
|
1179
|
-
throw new Error(`Refusing to overwrite existing path at ${path}`);
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
if (error.code !== "ENOENT") {
|
|
1182
|
-
throw error;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
async function copyDirectoryContents(sourcePath, targetPath) {
|
|
1187
|
-
await fs13.mkdir(targetPath, { recursive: true });
|
|
1188
|
-
const entries = await fs13.readdir(sourcePath, { withFileTypes: true });
|
|
1189
|
-
for (const entry of entries) {
|
|
1190
|
-
const sourceEntryPath = join8(sourcePath, entry.name);
|
|
1191
|
-
const targetEntryPath = join8(targetPath, entry.name);
|
|
1192
|
-
const entryStats = await fs13.lstat(sourceEntryPath);
|
|
1193
|
-
if (entryStats.isSymbolicLink()) {
|
|
1194
|
-
throw new Error(`Refusing to copy source symlink at ${sourceEntryPath}`);
|
|
1195
|
-
}
|
|
1196
|
-
if (entryStats.isDirectory()) {
|
|
1197
|
-
await copyDirectoryContents(sourceEntryPath, targetEntryPath);
|
|
1198
|
-
continue;
|
|
1199
|
-
}
|
|
1200
|
-
if (entryStats.isFile()) {
|
|
1201
|
-
await fs13.mkdir(dirname3(targetEntryPath), { recursive: true });
|
|
1202
|
-
await fs13.copyFile(sourceEntryPath, targetEntryPath);
|
|
1203
|
-
continue;
|
|
1204
|
-
}
|
|
1205
|
-
throw new Error(`Unsupported filesystem entry at ${sourceEntryPath}`);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
async function assertSkillSourceLayout(sourcePath) {
|
|
1209
|
-
const resolvedSourcePath = resolve7(sourcePath);
|
|
1210
|
-
const skillFilePath = join8(resolvedSourcePath, "SKILL.md");
|
|
1211
|
-
await assertNoSymlinkAncestors(resolvedSourcePath, { includeLeaf: true });
|
|
1212
|
-
await assertDirectory(resolvedSourcePath);
|
|
1213
|
-
try {
|
|
1214
|
-
await assertRegularFile(skillFilePath, "SKILL.md");
|
|
1215
|
-
} catch (error) {
|
|
1216
|
-
if (error.code === "ENOENT") {
|
|
1217
|
-
throw new Error(`Refusing to import ${resolvedSourcePath} without a root SKILL.md`);
|
|
1218
|
-
}
|
|
1219
|
-
throw error;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
async function copySkillContentsToManagedStore(sourcePath, targetPath) {
|
|
1223
|
-
const resolvedSourcePath = resolve7(sourcePath);
|
|
1224
|
-
const resolvedTargetPath = resolve7(targetPath);
|
|
1225
|
-
if (pathsAreEqual(resolvedSourcePath, resolvedTargetPath)) {
|
|
1226
|
-
throw new Error("Source and target paths must differ");
|
|
1227
|
-
}
|
|
1228
|
-
if (isPathInside(resolvedSourcePath, resolvedTargetPath)) {
|
|
1229
|
-
throw new Error("Refusing to copy into a child of the source directory");
|
|
1230
|
-
}
|
|
1231
|
-
await assertSkillSourceLayout(resolvedSourcePath);
|
|
1232
|
-
await assertNoSymlinkAncestors(resolvedTargetPath);
|
|
1233
|
-
await assertTargetDoesNotExist(resolvedTargetPath);
|
|
1234
|
-
await copyDirectoryContents(resolvedSourcePath, resolvedTargetPath);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// src/commands/import.ts
|
|
1238
|
-
function buildManagedSkillPath(skillmuxHome, skillId) {
|
|
1239
|
-
return resolve8(skillmuxHome, "skills", skillId);
|
|
1479
|
+
import { resolve as resolve9 } from "path";
|
|
1480
|
+
import { homedir as homedir8 } from "os";
|
|
1481
|
+
function buildManagedSkillPath2(skillmuxHome, skillId) {
|
|
1482
|
+
return resolve9(skillmuxHome, "skills", skillId);
|
|
1240
1483
|
}
|
|
1241
1484
|
async function runImport(options) {
|
|
1242
|
-
const homeDir = options.homeDir ??
|
|
1485
|
+
const homeDir = options.homeDir ?? homedir8();
|
|
1243
1486
|
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1244
1487
|
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1245
|
-
const sourcePath =
|
|
1488
|
+
const sourcePath = resolve9(options.sourcePath);
|
|
1246
1489
|
const skillId = normalizeId(options.skillName);
|
|
1247
1490
|
const importedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1248
1491
|
const manifest = await readManifest(skillmuxHome);
|
|
1249
|
-
const managedSkillPath =
|
|
1492
|
+
const managedSkillPath = buildManagedSkillPath2(skillmuxHome, skillId);
|
|
1250
1493
|
await assertSkillSourceLayout(sourcePath);
|
|
1251
1494
|
if (manifest.skills[skillId] !== void 0) {
|
|
1252
1495
|
throw new Error(`Managed skill already exists for ${skillId}`);
|
|
@@ -1273,7 +1516,7 @@ async function runImport(options) {
|
|
|
1273
1516
|
}
|
|
1274
1517
|
|
|
1275
1518
|
// src/commands/scan.ts
|
|
1276
|
-
import { homedir as
|
|
1519
|
+
import { homedir as homedir9 } from "os";
|
|
1277
1520
|
|
|
1278
1521
|
// src/output/format-issue.ts
|
|
1279
1522
|
function formatIssue(issue) {
|
|
@@ -1334,7 +1577,7 @@ ${result.issues.map(formatIssue).join("\n")}
|
|
|
1334
1577
|
`;
|
|
1335
1578
|
}
|
|
1336
1579
|
async function runScan(options = {}) {
|
|
1337
|
-
const homeDir = options.homeDir ??
|
|
1580
|
+
const homeDir = options.homeDir ?? homedir9();
|
|
1338
1581
|
const resolvedPaths = resolveSkillmuxHome(homeDir);
|
|
1339
1582
|
const skillmuxHome = options.skillmuxHome ?? resolvedPaths.skillmuxHome;
|
|
1340
1583
|
const manifest = await readManifest(skillmuxHome);
|
|
@@ -1383,6 +1626,13 @@ function buildRecordsView(scanResult) {
|
|
|
1383
1626
|
}
|
|
1384
1627
|
function buildAgentsView(scanResult) {
|
|
1385
1628
|
const groups = /* @__PURE__ */ new Map();
|
|
1629
|
+
for (const agent of scanResult.agents) {
|
|
1630
|
+
groups.set(agent.id, {
|
|
1631
|
+
agentId: agent.id,
|
|
1632
|
+
agentName: agent.stableName,
|
|
1633
|
+
entries: []
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1386
1636
|
for (const entry of scanResult.entries) {
|
|
1387
1637
|
const current = groups.get(entry.agentId) ?? {
|
|
1388
1638
|
agentId: entry.agentId,
|
|
@@ -1402,6 +1652,12 @@ function buildAgentsView(scanResult) {
|
|
|
1402
1652
|
}
|
|
1403
1653
|
function buildSkillsView(scanResult) {
|
|
1404
1654
|
const groups = /* @__PURE__ */ new Map();
|
|
1655
|
+
for (const skill of Object.values(scanResult.manifest.skills)) {
|
|
1656
|
+
groups.set(skill.id, {
|
|
1657
|
+
skillName: skill.id,
|
|
1658
|
+
entries: []
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1405
1661
|
for (const entry of scanResult.entries) {
|
|
1406
1662
|
const current = groups.get(entry.skillName) ?? {
|
|
1407
1663
|
skillName: entry.skillName,
|
|
@@ -1427,7 +1683,7 @@ function buildListData(scanResult, view) {
|
|
|
1427
1683
|
}
|
|
1428
1684
|
return buildRecordsView(scanResult);
|
|
1429
1685
|
}
|
|
1430
|
-
function
|
|
1686
|
+
function buildTableOutput6(data, view) {
|
|
1431
1687
|
if (view === "agents") {
|
|
1432
1688
|
const agentRows = data.agents;
|
|
1433
1689
|
return printTable(
|
|
@@ -1477,7 +1733,7 @@ async function runList(options = {}) {
|
|
|
1477
1733
|
const data = buildListData(scanResult, view);
|
|
1478
1734
|
return {
|
|
1479
1735
|
data,
|
|
1480
|
-
output: format === "json" ? printJson(data) :
|
|
1736
|
+
output: format === "json" ? printJson(data) : buildTableOutput6(data, view)
|
|
1481
1737
|
};
|
|
1482
1738
|
}
|
|
1483
1739
|
|
|
@@ -1511,10 +1767,37 @@ function buildCli() {
|
|
|
1511
1767
|
const result = await runDoctor({ json: options.json === true });
|
|
1512
1768
|
process.stdout.write(result.output);
|
|
1513
1769
|
});
|
|
1514
|
-
program.command("config")
|
|
1770
|
+
const configCommand = program.command("config");
|
|
1771
|
+
configCommand.option("--json", "Emit structured JSON output").action(async (options) => {
|
|
1515
1772
|
const result = await runConfig({ json: options.json === true });
|
|
1516
1773
|
process.stdout.write(result.output);
|
|
1517
1774
|
});
|
|
1775
|
+
configCommand.command("add-agent").requiredOption("--id <id>", "Agent id").requiredOption("--root <path>", "Home-relative root path").option("--skills <path>", "Skills directory path relative to the agent root", "skills").option("--name <name>", "Stable display name").option(
|
|
1776
|
+
"--platform <platform>",
|
|
1777
|
+
`Supported platform (${supportedPlatforms.join(", ")})`,
|
|
1778
|
+
(value, previous = []) => [...previous, value],
|
|
1779
|
+
[]
|
|
1780
|
+
).option("--disabled-by-default", "Mark this custom agent as disabled by default").option("--json", "Emit structured JSON output").action(
|
|
1781
|
+
async (options) => {
|
|
1782
|
+
const result = await runConfigAddAgent({
|
|
1783
|
+
id: options.id,
|
|
1784
|
+
root: options.root,
|
|
1785
|
+
skills: options.skills,
|
|
1786
|
+
name: options.name,
|
|
1787
|
+
platforms: options.platform,
|
|
1788
|
+
disabledByDefault: options.disabledByDefault === true,
|
|
1789
|
+
json: options.json === true
|
|
1790
|
+
});
|
|
1791
|
+
process.stdout.write(result.output);
|
|
1792
|
+
}
|
|
1793
|
+
);
|
|
1794
|
+
configCommand.command("remove-agent").requiredOption("--id <id>", "Agent id").option("--json", "Emit structured JSON output").action(async (options) => {
|
|
1795
|
+
const result = await runConfigRemoveAgent({
|
|
1796
|
+
id: options.id,
|
|
1797
|
+
json: options.json === true
|
|
1798
|
+
});
|
|
1799
|
+
process.stdout.write(result.output);
|
|
1800
|
+
});
|
|
1518
1801
|
program.command("enable").requiredOption("--skill <skill>", "Managed skill name or id").requiredOption("--agent <agent>", "Target agent id").action(async (options) => {
|
|
1519
1802
|
const result = await runEnable({
|
|
1520
1803
|
skill: options.skill,
|