@cleocode/caamp 0.4.1 → 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,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CANONICAL_SKILLS_DIR,
3
4
  MarketplaceClient,
4
5
  RECOMMENDATION_ERROR_CODES,
5
6
  applyMcpInstallWithPolicy,
@@ -11,6 +12,7 @@ import {
11
12
  detectAllProviders,
12
13
  detectMcpConfigConflicts,
13
14
  detectProjectProviders,
15
+ discoverSkill,
14
16
  discoverSkillsMulti,
15
17
  formatNetworkError,
16
18
  formatSkillRecommendations,
@@ -21,15 +23,21 @@ import {
21
23
  getProviderCount,
22
24
  getProvidersByPriority,
23
25
  getRegistryVersion,
26
+ getSkill,
27
+ getSkillDir,
24
28
  getTrackedSkills,
25
29
  groupByInstructFile,
26
30
  injectAll,
27
31
  installBatchWithRollback,
28
32
  installMcpServerToAll,
29
33
  installSkill,
34
+ isCatalogAvailable,
30
35
  isMarketplaceScoped,
36
+ isVerbose,
31
37
  listCanonicalSkills,
32
38
  listMcpServers,
39
+ listProfiles,
40
+ listSkills,
33
41
  parseSource,
34
42
  readConfig,
35
43
  readLockFile,
@@ -42,6 +50,7 @@ import {
42
50
  removeSkillFromLock,
43
51
  resolveConfigPath,
44
52
  resolvePreferredConfigScope,
53
+ resolveProfile,
45
54
  resolveProviderConfigPath,
46
55
  resolveProviderSkillsDir,
47
56
  scanDirectory,
@@ -53,7 +62,7 @@ import {
53
62
  tokenizeCriteriaValue,
54
63
  updateInstructionsSingleOperation,
55
64
  validateSkill
56
- } from "./chunk-6HQDRJLS.js";
65
+ } from "./chunk-YCSZGZ5W.js";
57
66
 
58
67
  // src/cli.ts
59
68
  import { Command } from "commander";
@@ -209,7 +218,7 @@ async function cloneGitLabRepo(owner, repo, ref, subPath) {
209
218
 
210
219
  // src/commands/skills/install.ts
211
220
  function registerSkillsInstall(parent) {
212
- 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) => {
213
222
  let providers;
214
223
  if (opts.all) {
215
224
  providers = getInstalledProviders();
@@ -222,6 +231,63 @@ function registerSkillsInstall(parent) {
222
231
  console.error(pc2.red("No target providers found. Use --agent or --all."));
223
232
  process.exit(1);
224
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
+ }
225
291
  console.log(pc2.dim(`Installing to ${providers.length} provider(s)...`));
226
292
  let localPath;
227
293
  let cleanup;
@@ -302,6 +368,27 @@ function registerSkillsInstall(parent) {
302
368
  }
303
369
  } else if (parsed.type === "local") {
304
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
+ }
305
392
  } else {
306
393
  console.error(pc2.red(`Unsupported source type: ${parsed.type}`));
307
394
  process.exit(1);
@@ -322,14 +409,15 @@ function registerSkillsInstall(parent) {
322
409
  \u2713 Installed ${pc2.bold(skillName)}`));
323
410
  console.log(` Canonical: ${pc2.dim(result.canonicalPath)}`);
324
411
  console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
412
+ const isGlobal = sourceType === "package" ? true : opts.global ?? false;
325
413
  await recordSkillInstall(
326
414
  skillName,
327
- source,
415
+ sourceValue,
328
416
  sourceValue,
329
417
  sourceType,
330
418
  result.linkedAgents,
331
419
  result.canonicalPath,
332
- opts.global ?? false
420
+ isGlobal
333
421
  );
334
422
  }
335
423
  if (result.errors.length > 0) {
@@ -882,14 +970,14 @@ Show example inputs and expected outputs.
882
970
  console.log(pc8.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
883
971
  console.log(pc8.dim("\nNext steps:"));
884
972
  console.log(pc8.dim(" 1. Edit SKILL.md with your instructions"));
885
- console.log(pc8.dim(" 2. Validate: caamp skills validate " + join3(skillDir, "SKILL.md")));
886
- 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}`));
887
975
  });
888
976
  }
889
977
 
890
978
  // src/commands/skills/audit.ts
891
- import pc9 from "picocolors";
892
979
  import { existsSync as existsSync3, statSync } from "fs";
980
+ import pc9 from "picocolors";
893
981
  function registerSkillsAudit(parent) {
894
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) => {
895
983
  if (!existsSync3(path)) {
@@ -1338,10 +1426,29 @@ ${provider.toolName} config (${configPath}):
1338
1426
  // src/commands/doctor.ts
1339
1427
  import pc19 from "picocolors";
1340
1428
  import { execFileSync } from "child_process";
1341
- import { existsSync as existsSync6, readdirSync, lstatSync } from "fs";
1429
+ import { existsSync as existsSync6, readdirSync, lstatSync, readlinkSync } from "fs";
1342
1430
  import { homedir } from "os";
1343
- import { join as join4 } from "path";
1344
- 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
1345
1452
  function getNodeVersion() {
1346
1453
  return process.version;
1347
1454
  }
@@ -1361,7 +1468,7 @@ function checkEnvironment() {
1361
1468
  } else {
1362
1469
  checks.push({ label: "npm not found", status: "warn" });
1363
1470
  }
1364
- checks.push({ label: `CAAMP v${CAAMP_VERSION}`, status: "pass" });
1471
+ checks.push({ label: `CAAMP v${getCaampVersion()}`, status: "pass" });
1365
1472
  checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
1366
1473
  return { name: "Environment", checks };
1367
1474
  }
@@ -1416,35 +1523,52 @@ function checkInstalledProviders() {
1416
1523
  }
1417
1524
  function checkSkillSymlinks() {
1418
1525
  const checks = [];
1419
- const canonicalDir = join4(homedir(), ".agents", "skills");
1526
+ const canonicalDir = CANONICAL_SKILLS_DIR;
1420
1527
  if (!existsSync6(canonicalDir)) {
1421
1528
  checks.push({ label: "0 canonical skills", status: "pass" });
1422
1529
  checks.push({ label: "No broken symlinks", status: "pass" });
1423
1530
  return { name: "Skills", checks };
1424
1531
  }
1425
1532
  let canonicalCount = 0;
1533
+ let canonicalNames = [];
1426
1534
  try {
1427
- const entries = readdirSync(canonicalDir);
1428
- 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;
1429
1545
  checks.push({ label: `${canonicalCount} canonical skills`, status: "pass" });
1430
1546
  } catch {
1431
1547
  checks.push({ label: "Cannot read skills directory", status: "warn" });
1432
1548
  return { name: "Skills", checks };
1433
1549
  }
1434
1550
  const broken = [];
1435
- const providers = getAllProviders();
1436
- 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;
1437
1556
  const skillDir = provider.pathSkills;
1438
1557
  if (!existsSync6(skillDir)) continue;
1439
1558
  try {
1440
1559
  const entries = readdirSync(skillDir);
1441
1560
  for (const entry of entries) {
1442
- const fullPath = join4(skillDir, entry);
1561
+ const fullPath = join5(skillDir, entry);
1443
1562
  try {
1444
1563
  const stat = lstatSync(fullPath);
1445
- if (stat.isSymbolicLink()) {
1446
- if (!existsSync6(fullPath)) {
1447
- 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}`);
1448
1572
  }
1449
1573
  }
1450
1574
  } catch {
@@ -1457,11 +1581,20 @@ function checkSkillSymlinks() {
1457
1581
  checks.push({ label: "No broken symlinks", status: "pass" });
1458
1582
  } else {
1459
1583
  checks.push({
1460
- label: `${broken.length} broken symlinks`,
1584
+ label: `${broken.length} broken symlink${broken.length !== 1 ? "s" : ""}`,
1461
1585
  status: "warn",
1462
1586
  detail: broken.join(", ")
1463
1587
  });
1464
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
+ }
1465
1598
  return { name: "Skills", checks };
1466
1599
  }
1467
1600
  async function checkLockFile() {
@@ -1469,19 +1602,65 @@ async function checkLockFile() {
1469
1602
  try {
1470
1603
  const lock = await readLockFile();
1471
1604
  checks.push({ label: "Lock file valid", status: "pass" });
1472
- let orphaned = 0;
1605
+ const lockSkillNames = Object.keys(lock.skills);
1606
+ checks.push({ label: `${lockSkillNames.length} skill entries`, status: "pass" });
1607
+ const orphaned = [];
1473
1608
  for (const [name, entry] of Object.entries(lock.skills)) {
1474
1609
  if (entry.canonicalPath && !existsSync6(entry.canonicalPath)) {
1475
- orphaned++;
1610
+ orphaned.push(name);
1476
1611
  }
1477
1612
  }
1478
- if (orphaned === 0) {
1479
- checks.push({ label: `0 orphaned entries`, status: "pass" });
1613
+ if (orphaned.length === 0) {
1614
+ checks.push({ label: "0 orphaned entries", status: "pass" });
1480
1615
  } else {
1481
1616
  checks.push({
1482
- label: `${orphaned} orphaned skill entries`,
1617
+ label: `${orphaned.length} orphaned skill${orphaned.length !== 1 ? "s" : ""} (in lock, missing from disk)`,
1483
1618
  status: "warn",
1484
- 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)` : "")
1485
1664
  });
1486
1665
  }
1487
1666
  } catch (err) {
@@ -1561,7 +1740,7 @@ function registerDoctorCommand(program2) {
1561
1740
  }
1562
1741
  if (opts.json) {
1563
1742
  const output = {
1564
- version: CAAMP_VERSION,
1743
+ version: getCaampVersion(),
1565
1744
  sections: sections.map((s) => ({
1566
1745
  name: s.name,
1567
1746
  checks: s.checks
@@ -1772,9 +1951,9 @@ async function readMcpOperations(path) {
1772
1951
  );
1773
1952
  }
1774
1953
  const obj = item;
1775
- const serverName = obj["serverName"];
1776
- const config = obj["config"];
1777
- const scope = obj["scope"];
1954
+ const serverName = obj.serverName;
1955
+ const config = obj.config;
1956
+ const scope = obj.scope;
1778
1957
  if (typeof serverName !== "string" || serverName.length === 0) {
1779
1958
  throw new LAFSCommandError(
1780
1959
  "E_ADVANCED_VALIDATION_MCP_NAME",
@@ -1823,9 +2002,9 @@ async function readSkillOperations(path) {
1823
2002
  );
1824
2003
  }
1825
2004
  const obj = item;
1826
- const sourcePath = obj["sourcePath"];
1827
- const skillName = obj["skillName"];
1828
- const isGlobal = obj["isGlobal"];
2005
+ const sourcePath = obj.sourcePath;
2006
+ const skillName = obj.skillName;
2007
+ const isGlobal = obj.isGlobal;
1829
2008
  if (typeof sourcePath !== "string" || sourcePath.length === 0) {
1830
2009
  throw new LAFSCommandError(
1831
2010
  "E_ADVANCED_VALIDATION_SKILL_SOURCE",
@@ -2234,7 +2413,7 @@ function registerAdvancedCommands(program2) {
2234
2413
 
2235
2414
  // src/cli.ts
2236
2415
  var program = new Command();
2237
- 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");
2238
2417
  program.hook("preAction", (thisCommand) => {
2239
2418
  const opts = thisCommand.optsWithGlobals();
2240
2419
  if (opts.verbose) setVerbose(true);
@@ -2247,5 +2426,30 @@ registerInstructionsCommands(program);
2247
2426
  registerConfigCommand(program);
2248
2427
  registerDoctorCommand(program);
2249
2428
  registerAdvancedCommands(program);
2250
- 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
+ });
2251
2455
  //# sourceMappingURL=cli.js.map