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