@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/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-ZYINKJDE.js";
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 marketplace").argument("<source>", "Skill source (GitHub URL, owner/repo, @author/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").action(async (source, opts) => {
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 = marketplacePathCandidates(skill.path, parsed.path);
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
- source,
415
+ sourceValue,
346
416
  sourceValue,
347
417
  sourceType,
348
418
  result.linkedAgents,
349
419
  result.canonicalPath,
350
- opts.global ?? false
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.pathSkills] : [join3(process.cwd(), provider.pathProjectSkills)];
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.pathSkills).filter(Boolean);
482
+ dirs = providers.map((p) => resolveProviderSkillsDir(p, "global")).filter(Boolean);
414
483
  } else {
415
484
  const providers = getInstalledProviders();
416
- dirs = providers.map((p) => join3(process.cwd(), p.pathProjectSkills)).filter(Boolean);
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 join4 } from "path";
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 = join4(opts.dir, skillName);
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(join4(skillDir, "SKILL.md"), template, "utf-8");
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(" 2. Validate: caamp skills validate " + join4(skillDir, "SKILL.md")));
905
- console.log(pc8.dim(" 3. Install: caamp skills install " + skillDir));
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 ? "global" : provider.configPathProject ? "project" : "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 = opts.global ? provider.configPathGlobal : provider.configPathProject ? join5(process.cwd(), provider.configPathProject) : provider.configPathGlobal;
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
- if (provider.configPathProject) {
1345
- console.log(join5(process.cwd(), provider.configPathProject));
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 join6 } from "path";
1360
- var CAAMP_VERSION = "0.2.0";
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${CAAMP_VERSION}`, status: "pass" });
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 = join6(homedir(), ".agents", "skills");
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
- const entries = readdirSync(canonicalDir);
1444
- canonicalCount = entries.length;
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 providers = getAllProviders();
1452
- for (const provider of providers) {
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 = join6(skillDir, entry);
1561
+ const fullPath = join5(skillDir, entry);
1459
1562
  try {
1460
1563
  const stat = lstatSync(fullPath);
1461
- if (stat.isSymbolicLink()) {
1462
- if (!existsSync6(fullPath)) {
1463
- broken.push(`${provider.id}/${entry}`);
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 symlinks`,
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
- let orphaned = 0;
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: `0 orphaned entries`, status: "pass" });
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 entries`,
1617
+ label: `${orphaned.length} orphaned skill${orphaned.length !== 1 ? "s" : ""} (in lock, missing from disk)`,
1499
1618
  status: "warn",
1500
- detail: "Skills tracked in lock file but missing from disk"
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: CAAMP_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["serverName"];
1792
- const config = obj["config"];
1793
- const scope = obj["scope"];
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["sourcePath"];
1843
- const skillName = obj["skillName"];
1844
- const isGlobal = obj["isGlobal"];
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("0.3.0").option("-v, --verbose", "Show debug output").option("-q, --quiet", "Suppress non-error output");
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
- program.parse();
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