@cleocode/caamp 0.4.0 → 0.5.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 +32 -10
- package/dist/{chunk-ZYINKJDE.js → chunk-YCSZGZ5W.js} +712 -270
- package/dist/chunk-YCSZGZ5W.js.map +1 -0
- package/dist/cli.d.ts +0 -0
- package/dist/cli.js +259 -71
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +362 -22
- package/dist/index.js +33 -1
- package/dist/index.js.map +0 -0
- package/package.json +7 -3
- package/providers/registry.json +0 -49
- package/dist/chunk-ZYINKJDE.js.map +0 -1
package/dist/cli.d.ts
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CANONICAL_SKILLS_DIR,
|
|
3
4
|
MarketplaceClient,
|
|
4
5
|
RECOMMENDATION_ERROR_CODES,
|
|
5
6
|
applyMcpInstallWithPolicy,
|
|
6
7
|
buildServerConfig,
|
|
8
|
+
buildSkillSubPathCandidates,
|
|
7
9
|
checkAllInjections,
|
|
8
10
|
checkSkillUpdate,
|
|
9
11
|
configureProviderGlobalAndProject,
|
|
10
12
|
detectAllProviders,
|
|
11
13
|
detectMcpConfigConflicts,
|
|
12
14
|
detectProjectProviders,
|
|
15
|
+
discoverSkill,
|
|
13
16
|
discoverSkillsMulti,
|
|
14
17
|
formatNetworkError,
|
|
15
18
|
formatSkillRecommendations,
|
|
@@ -20,15 +23,21 @@ import {
|
|
|
20
23
|
getProviderCount,
|
|
21
24
|
getProvidersByPriority,
|
|
22
25
|
getRegistryVersion,
|
|
26
|
+
getSkill,
|
|
27
|
+
getSkillDir,
|
|
23
28
|
getTrackedSkills,
|
|
24
29
|
groupByInstructFile,
|
|
25
30
|
injectAll,
|
|
26
31
|
installBatchWithRollback,
|
|
27
32
|
installMcpServerToAll,
|
|
28
33
|
installSkill,
|
|
34
|
+
isCatalogAvailable,
|
|
29
35
|
isMarketplaceScoped,
|
|
36
|
+
isVerbose,
|
|
30
37
|
listCanonicalSkills,
|
|
31
38
|
listMcpServers,
|
|
39
|
+
listProfiles,
|
|
40
|
+
listSkills,
|
|
32
41
|
parseSource,
|
|
33
42
|
readConfig,
|
|
34
43
|
readLockFile,
|
|
@@ -40,6 +49,10 @@ import {
|
|
|
40
49
|
removeSkill,
|
|
41
50
|
removeSkillFromLock,
|
|
42
51
|
resolveConfigPath,
|
|
52
|
+
resolvePreferredConfigScope,
|
|
53
|
+
resolveProfile,
|
|
54
|
+
resolveProviderConfigPath,
|
|
55
|
+
resolveProviderSkillsDir,
|
|
43
56
|
scanDirectory,
|
|
44
57
|
scanFile,
|
|
45
58
|
selectProvidersByMinimumPriority,
|
|
@@ -49,7 +62,7 @@ import {
|
|
|
49
62
|
tokenizeCriteriaValue,
|
|
50
63
|
updateInstructionsSingleOperation,
|
|
51
64
|
validateSkill
|
|
52
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-YCSZGZ5W.js";
|
|
53
66
|
|
|
54
67
|
// src/cli.ts
|
|
55
68
|
import { Command } from "commander";
|
|
@@ -204,30 +217,8 @@ async function cloneGitLabRepo(owner, repo, ref, subPath) {
|
|
|
204
217
|
}
|
|
205
218
|
|
|
206
219
|
// src/commands/skills/install.ts
|
|
207
|
-
function normalizeSkillSubPath(path) {
|
|
208
|
-
if (!path) return void 0;
|
|
209
|
-
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/SKILL\.md$/i, "").trim();
|
|
210
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
211
|
-
}
|
|
212
|
-
function marketplacePathCandidates(skillPath, parsedPath) {
|
|
213
|
-
const candidates = [];
|
|
214
|
-
const base = normalizeSkillSubPath(skillPath);
|
|
215
|
-
const parsed = normalizeSkillSubPath(parsedPath);
|
|
216
|
-
if (base) candidates.push(base);
|
|
217
|
-
if (parsed) candidates.push(parsed);
|
|
218
|
-
if (base && base.startsWith("skills/") && !base.startsWith(".claude/")) {
|
|
219
|
-
candidates.push(`.claude/${base}`);
|
|
220
|
-
}
|
|
221
|
-
if (parsed && parsed.startsWith("skills/") && !parsed.startsWith(".claude/")) {
|
|
222
|
-
candidates.push(`.claude/${parsed}`);
|
|
223
|
-
}
|
|
224
|
-
if (candidates.length === 0) {
|
|
225
|
-
candidates.push(void 0);
|
|
226
|
-
}
|
|
227
|
-
return Array.from(new Set(candidates));
|
|
228
|
-
}
|
|
229
220
|
function registerSkillsInstall(parent) {
|
|
230
|
-
parent.command("install").description("Install a skill from GitHub, URL, or
|
|
221
|
+
parent.command("install").description("Install a skill from GitHub, URL, marketplace, or ct-skills catalog").argument("[source]", "Skill source (GitHub URL, owner/repo, @author/name, skill-name)").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Install globally").option("-y, --yes", "Skip confirmation").option("--all", "Install to all detected agents").option("--profile <name>", "Install a ct-skills profile (minimal, core, recommended, full)").action(async (source, opts) => {
|
|
231
222
|
let providers;
|
|
232
223
|
if (opts.all) {
|
|
233
224
|
providers = getInstalledProviders();
|
|
@@ -240,6 +231,63 @@ function registerSkillsInstall(parent) {
|
|
|
240
231
|
console.error(pc2.red("No target providers found. Use --agent or --all."));
|
|
241
232
|
process.exit(1);
|
|
242
233
|
}
|
|
234
|
+
if (opts.profile) {
|
|
235
|
+
if (!isCatalogAvailable()) {
|
|
236
|
+
console.error(pc2.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
const profileSkills = resolveProfile(opts.profile);
|
|
240
|
+
if (profileSkills.length === 0) {
|
|
241
|
+
const available = listProfiles();
|
|
242
|
+
console.error(pc2.red(`Profile not found: ${opts.profile}`));
|
|
243
|
+
if (available.length > 0) {
|
|
244
|
+
console.log(pc2.dim("Available profiles: " + available.join(", ")));
|
|
245
|
+
}
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
console.log(`Installing profile ${pc2.bold(opts.profile)} (${profileSkills.length} skill(s))...`);
|
|
249
|
+
console.log(pc2.dim(`Target: ${providers.length} provider(s)`));
|
|
250
|
+
let installed = 0;
|
|
251
|
+
let failed = 0;
|
|
252
|
+
for (const name of profileSkills) {
|
|
253
|
+
const skillDir = getSkillDir(name);
|
|
254
|
+
try {
|
|
255
|
+
const result = await installSkill(
|
|
256
|
+
skillDir,
|
|
257
|
+
name,
|
|
258
|
+
providers,
|
|
259
|
+
opts.global ?? false
|
|
260
|
+
);
|
|
261
|
+
if (result.success) {
|
|
262
|
+
console.log(pc2.green(` + ${name}`));
|
|
263
|
+
await recordSkillInstall(
|
|
264
|
+
name,
|
|
265
|
+
`@cleocode/ct-skills:${name}`,
|
|
266
|
+
`@cleocode/ct-skills:${name}`,
|
|
267
|
+
"package",
|
|
268
|
+
result.linkedAgents,
|
|
269
|
+
result.canonicalPath,
|
|
270
|
+
true
|
|
271
|
+
);
|
|
272
|
+
installed++;
|
|
273
|
+
} else {
|
|
274
|
+
console.log(pc2.yellow(` ! ${name}: ${result.errors.join(", ")}`));
|
|
275
|
+
failed++;
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.log(pc2.red(` x ${name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
279
|
+
failed++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
console.log(`
|
|
283
|
+
${pc2.green(`${installed} installed`)}, ${failed > 0 ? pc2.yellow(`${failed} failed`) : "0 failed"}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!source) {
|
|
287
|
+
console.error(pc2.red("Missing required argument: source"));
|
|
288
|
+
console.log(pc2.dim("Usage: caamp skills install <source> or caamp skills install --profile <name>"));
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
243
291
|
console.log(pc2.dim(`Installing to ${providers.length} provider(s)...`));
|
|
244
292
|
let localPath;
|
|
245
293
|
let cleanup;
|
|
@@ -267,7 +315,7 @@ function registerSkillsInstall(parent) {
|
|
|
267
315
|
process.exit(1);
|
|
268
316
|
}
|
|
269
317
|
try {
|
|
270
|
-
const subPathCandidates =
|
|
318
|
+
const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
|
|
271
319
|
let cloneError;
|
|
272
320
|
let cloned = false;
|
|
273
321
|
for (const subPath of subPathCandidates) {
|
|
@@ -320,6 +368,27 @@ function registerSkillsInstall(parent) {
|
|
|
320
368
|
}
|
|
321
369
|
} else if (parsed.type === "local") {
|
|
322
370
|
localPath = parsed.value;
|
|
371
|
+
const discovered = await discoverSkill(localPath);
|
|
372
|
+
if (discovered) {
|
|
373
|
+
skillName = discovered.name;
|
|
374
|
+
}
|
|
375
|
+
} else if (parsed.type === "package") {
|
|
376
|
+
if (!isCatalogAvailable()) {
|
|
377
|
+
console.error(pc2.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
const catalogSkill = getSkill(parsed.inferredName);
|
|
381
|
+
if (catalogSkill) {
|
|
382
|
+
localPath = getSkillDir(catalogSkill.name);
|
|
383
|
+
skillName = catalogSkill.name;
|
|
384
|
+
sourceValue = `@cleocode/ct-skills:${catalogSkill.name}`;
|
|
385
|
+
sourceType = "package";
|
|
386
|
+
console.log(` Found in catalog: ${pc2.bold(catalogSkill.name)} v${catalogSkill.version} (${pc2.dim(catalogSkill.category)})`);
|
|
387
|
+
} else {
|
|
388
|
+
console.error(pc2.red(`Skill not found in catalog: ${parsed.inferredName}`));
|
|
389
|
+
console.log(pc2.dim("Available skills: " + listSkills().join(", ")));
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
323
392
|
} else {
|
|
324
393
|
console.error(pc2.red(`Unsupported source type: ${parsed.type}`));
|
|
325
394
|
process.exit(1);
|
|
@@ -340,14 +409,15 @@ function registerSkillsInstall(parent) {
|
|
|
340
409
|
\u2713 Installed ${pc2.bold(skillName)}`));
|
|
341
410
|
console.log(` Canonical: ${pc2.dim(result.canonicalPath)}`);
|
|
342
411
|
console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
|
|
412
|
+
const isGlobal = sourceType === "package" ? true : opts.global ?? false;
|
|
343
413
|
await recordSkillInstall(
|
|
344
414
|
skillName,
|
|
345
|
-
|
|
415
|
+
sourceValue,
|
|
346
416
|
sourceValue,
|
|
347
417
|
sourceType,
|
|
348
418
|
result.linkedAgents,
|
|
349
419
|
result.canonicalPath,
|
|
350
|
-
|
|
420
|
+
isGlobal
|
|
351
421
|
);
|
|
352
422
|
}
|
|
353
423
|
if (result.errors.length > 0) {
|
|
@@ -397,7 +467,6 @@ function registerSkillsRemove(parent) {
|
|
|
397
467
|
|
|
398
468
|
// src/commands/skills/list.ts
|
|
399
469
|
import pc4 from "picocolors";
|
|
400
|
-
import { join as join3 } from "path";
|
|
401
470
|
function registerSkillsList(parent) {
|
|
402
471
|
parent.command("list").description("List installed skills").option("-g, --global", "List global skills").option("-a, --agent <name>", "List skills for specific agent").option("--json", "Output as JSON").action(async (opts) => {
|
|
403
472
|
let dirs = [];
|
|
@@ -407,13 +476,13 @@ function registerSkillsList(parent) {
|
|
|
407
476
|
console.error(pc4.red(`Provider not found: ${opts.agent}`));
|
|
408
477
|
process.exit(1);
|
|
409
478
|
}
|
|
410
|
-
dirs = opts.global ? [provider
|
|
479
|
+
dirs = opts.global ? [resolveProviderSkillsDir(provider, "global")] : [resolveProviderSkillsDir(provider, "project")];
|
|
411
480
|
} else if (opts.global) {
|
|
412
481
|
const providers = getInstalledProviders();
|
|
413
|
-
dirs = providers.map((p) => p
|
|
482
|
+
dirs = providers.map((p) => resolveProviderSkillsDir(p, "global")).filter(Boolean);
|
|
414
483
|
} else {
|
|
415
484
|
const providers = getInstalledProviders();
|
|
416
|
-
dirs = providers.map((p) =>
|
|
485
|
+
dirs = providers.map((p) => resolveProviderSkillsDir(p, "project")).filter(Boolean);
|
|
417
486
|
}
|
|
418
487
|
const skills = await discoverSkillsMulti(dirs);
|
|
419
488
|
if (opts.json) {
|
|
@@ -864,11 +933,11 @@ ${outdated.length} skill(s) have updates available:
|
|
|
864
933
|
import pc8 from "picocolors";
|
|
865
934
|
import { writeFile, mkdir } from "fs/promises";
|
|
866
935
|
import { existsSync as existsSync2 } from "fs";
|
|
867
|
-
import { join as
|
|
936
|
+
import { join as join3 } from "path";
|
|
868
937
|
function registerSkillsInit(parent) {
|
|
869
938
|
parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").action(async (name, opts) => {
|
|
870
939
|
const skillName = name ?? "my-skill";
|
|
871
|
-
const skillDir =
|
|
940
|
+
const skillDir = join3(opts.dir, skillName);
|
|
872
941
|
if (existsSync2(skillDir)) {
|
|
873
942
|
console.error(pc8.red(`Directory already exists: ${skillDir}`));
|
|
874
943
|
process.exit(1);
|
|
@@ -897,18 +966,18 @@ Provide detailed instructions for the AI agent here.
|
|
|
897
966
|
|
|
898
967
|
Show example inputs and expected outputs.
|
|
899
968
|
`;
|
|
900
|
-
await writeFile(
|
|
969
|
+
await writeFile(join3(skillDir, "SKILL.md"), template, "utf-8");
|
|
901
970
|
console.log(pc8.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
|
|
902
971
|
console.log(pc8.dim("\nNext steps:"));
|
|
903
972
|
console.log(pc8.dim(" 1. Edit SKILL.md with your instructions"));
|
|
904
|
-
console.log(pc8.dim(
|
|
905
|
-
console.log(pc8.dim(
|
|
973
|
+
console.log(pc8.dim(` 2. Validate: caamp skills validate ${join3(skillDir, "SKILL.md")}`));
|
|
974
|
+
console.log(pc8.dim(` 3. Install: caamp skills install ${skillDir}`));
|
|
906
975
|
});
|
|
907
976
|
}
|
|
908
977
|
|
|
909
978
|
// src/commands/skills/audit.ts
|
|
910
|
-
import pc9 from "picocolors";
|
|
911
979
|
import { existsSync as existsSync3, statSync } from "fs";
|
|
980
|
+
import pc9 from "picocolors";
|
|
912
981
|
function registerSkillsAudit(parent) {
|
|
913
982
|
parent.command("audit").description("Security scan skill files (46+ rules, SARIF output)").argument("[path]", "Path to SKILL.md or directory", ".").option("--sarif", "Output in SARIF format").option("--json", "Output as JSON").action(async (path, opts) => {
|
|
914
983
|
if (!existsSync3(path)) {
|
|
@@ -1101,7 +1170,7 @@ function registerMcpList(parent) {
|
|
|
1101
1170
|
const providers = opts.agent ? [getProvider(opts.agent)].filter((p) => p !== void 0) : getInstalledProviders();
|
|
1102
1171
|
const allEntries = [];
|
|
1103
1172
|
for (const provider of providers) {
|
|
1104
|
-
const scope = opts.global
|
|
1173
|
+
const scope = resolvePreferredConfigScope(provider, opts.global);
|
|
1105
1174
|
const entries = await listMcpServers(provider, scope);
|
|
1106
1175
|
allEntries.push(...entries);
|
|
1107
1176
|
}
|
|
@@ -1302,7 +1371,6 @@ function registerInstructionsCommands(program2) {
|
|
|
1302
1371
|
|
|
1303
1372
|
// src/commands/config.ts
|
|
1304
1373
|
import pc18 from "picocolors";
|
|
1305
|
-
import { join as join5 } from "path";
|
|
1306
1374
|
import { existsSync as existsSync5 } from "fs";
|
|
1307
1375
|
function registerConfigCommand(program2) {
|
|
1308
1376
|
const config = program2.command("config").description("View provider configuration");
|
|
@@ -1312,7 +1380,10 @@ function registerConfigCommand(program2) {
|
|
|
1312
1380
|
console.error(pc18.red(`Provider not found: ${providerId}`));
|
|
1313
1381
|
process.exit(1);
|
|
1314
1382
|
}
|
|
1315
|
-
const configPath =
|
|
1383
|
+
const configPath = resolveProviderConfigPath(
|
|
1384
|
+
provider,
|
|
1385
|
+
opts.global ? "global" : "project"
|
|
1386
|
+
) ?? provider.configPathGlobal;
|
|
1316
1387
|
if (!existsSync5(configPath)) {
|
|
1317
1388
|
console.log(pc18.dim(`No config file at: ${configPath}`));
|
|
1318
1389
|
return;
|
|
@@ -1341,8 +1412,9 @@ ${provider.toolName} config (${configPath}):
|
|
|
1341
1412
|
if (scope === "global") {
|
|
1342
1413
|
console.log(provider.configPathGlobal);
|
|
1343
1414
|
} else {
|
|
1344
|
-
|
|
1345
|
-
|
|
1415
|
+
const projectPath = resolveProviderConfigPath(provider, "project");
|
|
1416
|
+
if (projectPath) {
|
|
1417
|
+
console.log(projectPath);
|
|
1346
1418
|
} else {
|
|
1347
1419
|
console.log(pc18.dim(`${provider.toolName} has no project-level config`));
|
|
1348
1420
|
console.log(provider.configPathGlobal);
|
|
@@ -1354,10 +1426,29 @@ ${provider.toolName} config (${configPath}):
|
|
|
1354
1426
|
// src/commands/doctor.ts
|
|
1355
1427
|
import pc19 from "picocolors";
|
|
1356
1428
|
import { execFileSync } from "child_process";
|
|
1357
|
-
import { existsSync as existsSync6, readdirSync, lstatSync } from "fs";
|
|
1429
|
+
import { existsSync as existsSync6, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
1358
1430
|
import { homedir } from "os";
|
|
1359
|
-
import { join as
|
|
1360
|
-
|
|
1431
|
+
import { join as join5 } from "path";
|
|
1432
|
+
|
|
1433
|
+
// src/core/version.ts
|
|
1434
|
+
import { readFileSync } from "fs";
|
|
1435
|
+
import { dirname, join as join4 } from "path";
|
|
1436
|
+
import { fileURLToPath } from "url";
|
|
1437
|
+
var cachedVersion = null;
|
|
1438
|
+
function getCaampVersion() {
|
|
1439
|
+
if (cachedVersion) return cachedVersion;
|
|
1440
|
+
try {
|
|
1441
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
1442
|
+
const packageJsonPath = join4(currentDir, "..", "..", "package.json");
|
|
1443
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1444
|
+
cachedVersion = packageJson.version ?? "0.0.0";
|
|
1445
|
+
} catch {
|
|
1446
|
+
cachedVersion = "0.0.0";
|
|
1447
|
+
}
|
|
1448
|
+
return cachedVersion;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/commands/doctor.ts
|
|
1361
1452
|
function getNodeVersion() {
|
|
1362
1453
|
return process.version;
|
|
1363
1454
|
}
|
|
@@ -1377,7 +1468,7 @@ function checkEnvironment() {
|
|
|
1377
1468
|
} else {
|
|
1378
1469
|
checks.push({ label: "npm not found", status: "warn" });
|
|
1379
1470
|
}
|
|
1380
|
-
checks.push({ label: `CAAMP v${
|
|
1471
|
+
checks.push({ label: `CAAMP v${getCaampVersion()}`, status: "pass" });
|
|
1381
1472
|
checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
|
|
1382
1473
|
return { name: "Environment", checks };
|
|
1383
1474
|
}
|
|
@@ -1432,35 +1523,52 @@ function checkInstalledProviders() {
|
|
|
1432
1523
|
}
|
|
1433
1524
|
function checkSkillSymlinks() {
|
|
1434
1525
|
const checks = [];
|
|
1435
|
-
const canonicalDir =
|
|
1526
|
+
const canonicalDir = CANONICAL_SKILLS_DIR;
|
|
1436
1527
|
if (!existsSync6(canonicalDir)) {
|
|
1437
1528
|
checks.push({ label: "0 canonical skills", status: "pass" });
|
|
1438
1529
|
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
1439
1530
|
return { name: "Skills", checks };
|
|
1440
1531
|
}
|
|
1441
1532
|
let canonicalCount = 0;
|
|
1533
|
+
let canonicalNames = [];
|
|
1442
1534
|
try {
|
|
1443
|
-
|
|
1444
|
-
|
|
1535
|
+
canonicalNames = readdirSync(canonicalDir).filter((name) => {
|
|
1536
|
+
const full = join5(canonicalDir, name);
|
|
1537
|
+
try {
|
|
1538
|
+
const stat = lstatSync(full);
|
|
1539
|
+
return stat.isDirectory() || stat.isSymbolicLink();
|
|
1540
|
+
} catch {
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
canonicalCount = canonicalNames.length;
|
|
1445
1545
|
checks.push({ label: `${canonicalCount} canonical skills`, status: "pass" });
|
|
1446
1546
|
} catch {
|
|
1447
1547
|
checks.push({ label: "Cannot read skills directory", status: "warn" });
|
|
1448
1548
|
return { name: "Skills", checks };
|
|
1449
1549
|
}
|
|
1450
1550
|
const broken = [];
|
|
1451
|
-
const
|
|
1452
|
-
|
|
1551
|
+
const stale = [];
|
|
1552
|
+
const results = detectAllProviders();
|
|
1553
|
+
const installed = results.filter((r) => r.installed);
|
|
1554
|
+
for (const r of installed) {
|
|
1555
|
+
const provider = r.provider;
|
|
1453
1556
|
const skillDir = provider.pathSkills;
|
|
1454
1557
|
if (!existsSync6(skillDir)) continue;
|
|
1455
1558
|
try {
|
|
1456
1559
|
const entries = readdirSync(skillDir);
|
|
1457
1560
|
for (const entry of entries) {
|
|
1458
|
-
const fullPath =
|
|
1561
|
+
const fullPath = join5(skillDir, entry);
|
|
1459
1562
|
try {
|
|
1460
1563
|
const stat = lstatSync(fullPath);
|
|
1461
|
-
if (stat.isSymbolicLink())
|
|
1462
|
-
|
|
1463
|
-
|
|
1564
|
+
if (!stat.isSymbolicLink()) continue;
|
|
1565
|
+
if (!existsSync6(fullPath)) {
|
|
1566
|
+
broken.push(`${provider.id}/${entry}`);
|
|
1567
|
+
} else {
|
|
1568
|
+
const target = readlinkSync(fullPath);
|
|
1569
|
+
const isCanonical = target.includes("/.agents/skills/") || target.includes("\\.agents\\skills\\");
|
|
1570
|
+
if (!isCanonical) {
|
|
1571
|
+
stale.push(`${provider.id}/${entry}`);
|
|
1464
1572
|
}
|
|
1465
1573
|
}
|
|
1466
1574
|
} catch {
|
|
@@ -1473,11 +1581,20 @@ function checkSkillSymlinks() {
|
|
|
1473
1581
|
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
1474
1582
|
} else {
|
|
1475
1583
|
checks.push({
|
|
1476
|
-
label: `${broken.length} broken
|
|
1584
|
+
label: `${broken.length} broken symlink${broken.length !== 1 ? "s" : ""}`,
|
|
1477
1585
|
status: "warn",
|
|
1478
1586
|
detail: broken.join(", ")
|
|
1479
1587
|
});
|
|
1480
1588
|
}
|
|
1589
|
+
if (stale.length === 0) {
|
|
1590
|
+
checks.push({ label: "No stale symlinks", status: "pass" });
|
|
1591
|
+
} else {
|
|
1592
|
+
checks.push({
|
|
1593
|
+
label: `${stale.length} stale symlink${stale.length !== 1 ? "s" : ""} (not pointing to ~/.agents/skills/)`,
|
|
1594
|
+
status: "warn",
|
|
1595
|
+
detail: stale.join(", ")
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1481
1598
|
return { name: "Skills", checks };
|
|
1482
1599
|
}
|
|
1483
1600
|
async function checkLockFile() {
|
|
@@ -1485,19 +1602,65 @@ async function checkLockFile() {
|
|
|
1485
1602
|
try {
|
|
1486
1603
|
const lock = await readLockFile();
|
|
1487
1604
|
checks.push({ label: "Lock file valid", status: "pass" });
|
|
1488
|
-
|
|
1605
|
+
const lockSkillNames = Object.keys(lock.skills);
|
|
1606
|
+
checks.push({ label: `${lockSkillNames.length} skill entries`, status: "pass" });
|
|
1607
|
+
const orphaned = [];
|
|
1489
1608
|
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
1490
1609
|
if (entry.canonicalPath && !existsSync6(entry.canonicalPath)) {
|
|
1491
|
-
orphaned
|
|
1610
|
+
orphaned.push(name);
|
|
1492
1611
|
}
|
|
1493
1612
|
}
|
|
1494
|
-
if (orphaned === 0) {
|
|
1495
|
-
checks.push({ label:
|
|
1613
|
+
if (orphaned.length === 0) {
|
|
1614
|
+
checks.push({ label: "0 orphaned entries", status: "pass" });
|
|
1496
1615
|
} else {
|
|
1497
1616
|
checks.push({
|
|
1498
|
-
label: `${orphaned} orphaned skill
|
|
1617
|
+
label: `${orphaned.length} orphaned skill${orphaned.length !== 1 ? "s" : ""} (in lock, missing from disk)`,
|
|
1499
1618
|
status: "warn",
|
|
1500
|
-
detail: "
|
|
1619
|
+
detail: orphaned.join(", ")
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
const canonicalDir = CANONICAL_SKILLS_DIR;
|
|
1623
|
+
if (existsSync6(canonicalDir)) {
|
|
1624
|
+
const onDisk = readdirSync(canonicalDir).filter((name) => {
|
|
1625
|
+
try {
|
|
1626
|
+
const stat = lstatSync(join5(canonicalDir, name));
|
|
1627
|
+
return stat.isDirectory() || stat.isSymbolicLink();
|
|
1628
|
+
} catch {
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
const untracked = onDisk.filter((name) => !lock.skills[name]);
|
|
1633
|
+
if (untracked.length === 0) {
|
|
1634
|
+
checks.push({ label: "0 untracked skills", status: "pass" });
|
|
1635
|
+
} else {
|
|
1636
|
+
checks.push({
|
|
1637
|
+
label: `${untracked.length} untracked skill${untracked.length !== 1 ? "s" : ""} (on disk, not in lock)`,
|
|
1638
|
+
status: "warn",
|
|
1639
|
+
detail: untracked.join(", ")
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
const results = detectAllProviders();
|
|
1644
|
+
const installed = results.filter((r) => r.installed);
|
|
1645
|
+
const mismatches = [];
|
|
1646
|
+
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
1647
|
+
if (!entry.agents || entry.agents.length === 0) continue;
|
|
1648
|
+
for (const agentId of entry.agents) {
|
|
1649
|
+
const provider = installed.find((r) => r.provider.id === agentId);
|
|
1650
|
+
if (!provider) continue;
|
|
1651
|
+
const linkPath = join5(provider.provider.pathSkills, name);
|
|
1652
|
+
if (!existsSync6(linkPath)) {
|
|
1653
|
+
mismatches.push(`${name} missing from ${agentId}`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
if (mismatches.length === 0) {
|
|
1658
|
+
checks.push({ label: "Lock agent-lists match symlinks", status: "pass" });
|
|
1659
|
+
} else {
|
|
1660
|
+
checks.push({
|
|
1661
|
+
label: `${mismatches.length} agent-list mismatch${mismatches.length !== 1 ? "es" : ""}`,
|
|
1662
|
+
status: "warn",
|
|
1663
|
+
detail: mismatches.slice(0, 5).join(", ") + (mismatches.length > 5 ? ` (+${mismatches.length - 5} more)` : "")
|
|
1501
1664
|
});
|
|
1502
1665
|
}
|
|
1503
1666
|
} catch (err) {
|
|
@@ -1577,7 +1740,7 @@ function registerDoctorCommand(program2) {
|
|
|
1577
1740
|
}
|
|
1578
1741
|
if (opts.json) {
|
|
1579
1742
|
const output = {
|
|
1580
|
-
version:
|
|
1743
|
+
version: getCaampVersion(),
|
|
1581
1744
|
sections: sections.map((s) => ({
|
|
1582
1745
|
name: s.name,
|
|
1583
1746
|
checks: s.checks
|
|
@@ -1788,9 +1951,9 @@ async function readMcpOperations(path) {
|
|
|
1788
1951
|
);
|
|
1789
1952
|
}
|
|
1790
1953
|
const obj = item;
|
|
1791
|
-
const serverName = obj
|
|
1792
|
-
const config = obj
|
|
1793
|
-
const scope = obj
|
|
1954
|
+
const serverName = obj.serverName;
|
|
1955
|
+
const config = obj.config;
|
|
1956
|
+
const scope = obj.scope;
|
|
1794
1957
|
if (typeof serverName !== "string" || serverName.length === 0) {
|
|
1795
1958
|
throw new LAFSCommandError(
|
|
1796
1959
|
"E_ADVANCED_VALIDATION_MCP_NAME",
|
|
@@ -1839,9 +2002,9 @@ async function readSkillOperations(path) {
|
|
|
1839
2002
|
);
|
|
1840
2003
|
}
|
|
1841
2004
|
const obj = item;
|
|
1842
|
-
const sourcePath = obj
|
|
1843
|
-
const skillName = obj
|
|
1844
|
-
const isGlobal = obj
|
|
2005
|
+
const sourcePath = obj.sourcePath;
|
|
2006
|
+
const skillName = obj.skillName;
|
|
2007
|
+
const isGlobal = obj.isGlobal;
|
|
1845
2008
|
if (typeof sourcePath !== "string" || sourcePath.length === 0) {
|
|
1846
2009
|
throw new LAFSCommandError(
|
|
1847
2010
|
"E_ADVANCED_VALIDATION_SKILL_SOURCE",
|
|
@@ -2250,7 +2413,7 @@ function registerAdvancedCommands(program2) {
|
|
|
2250
2413
|
|
|
2251
2414
|
// src/cli.ts
|
|
2252
2415
|
var program = new Command();
|
|
2253
|
-
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version(
|
|
2416
|
+
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version(getCaampVersion()).option("-v, --verbose", "Show debug output").option("-q, --quiet", "Suppress non-error output");
|
|
2254
2417
|
program.hook("preAction", (thisCommand) => {
|
|
2255
2418
|
const opts = thisCommand.optsWithGlobals();
|
|
2256
2419
|
if (opts.verbose) setVerbose(true);
|
|
@@ -2263,5 +2426,30 @@ registerInstructionsCommands(program);
|
|
|
2263
2426
|
registerConfigCommand(program);
|
|
2264
2427
|
registerDoctorCommand(program);
|
|
2265
2428
|
registerAdvancedCommands(program);
|
|
2266
|
-
|
|
2429
|
+
function toError(error) {
|
|
2430
|
+
if (error instanceof Error) return error;
|
|
2431
|
+
return new Error(String(error));
|
|
2432
|
+
}
|
|
2433
|
+
function handleFatal(error, source) {
|
|
2434
|
+
const normalized = toError(error);
|
|
2435
|
+
console.error(`Fatal error (${source}): ${normalized.message}`);
|
|
2436
|
+
if (isVerbose() && normalized.stack) {
|
|
2437
|
+
console.error(normalized.stack);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
process.on("uncaughtException", (error) => {
|
|
2441
|
+
handleFatal(error, "uncaughtException");
|
|
2442
|
+
process.exit(1);
|
|
2443
|
+
});
|
|
2444
|
+
process.on("unhandledRejection", (reason) => {
|
|
2445
|
+
handleFatal(reason, "unhandledRejection");
|
|
2446
|
+
process.exit(1);
|
|
2447
|
+
});
|
|
2448
|
+
async function main() {
|
|
2449
|
+
await program.parseAsync(process.argv);
|
|
2450
|
+
}
|
|
2451
|
+
main().catch((error) => {
|
|
2452
|
+
handleFatal(error, "cli");
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
});
|
|
2267
2455
|
//# sourceMappingURL=cli.js.map
|