@cleocode/caamp 0.1.0 → 0.3.0

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.js CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  listMcpServers,
25
25
  parseSource,
26
26
  readConfig,
27
+ readLockFile,
27
28
  recordMcpInstall,
28
29
  recordSkillInstall,
29
30
  removeMcpFromLock,
@@ -33,9 +34,11 @@ import {
33
34
  resolveConfigPath,
34
35
  scanDirectory,
35
36
  scanFile,
37
+ setQuiet,
38
+ setVerbose,
36
39
  toSarif,
37
40
  validateSkill
38
- } from "./chunk-63BH7QMR.js";
41
+ } from "./chunk-PCWTRJV2.js";
39
42
 
40
43
  // src/cli.ts
41
44
  import { Command } from "commander";
@@ -208,7 +211,7 @@ function registerSkillsInstall(parent) {
208
211
  let cleanup;
209
212
  let skillName;
210
213
  let sourceValue;
211
- let sourceType = "github";
214
+ let sourceType;
212
215
  if (isMarketplaceScoped(source)) {
213
216
  console.log(pc2.dim(`Searching marketplace for ${source}...`));
214
217
  const client = new MarketplaceClient();
@@ -228,10 +231,12 @@ function registerSkillsInstall(parent) {
228
231
  cleanup = result.cleanup;
229
232
  skillName = skill.name;
230
233
  sourceValue = skill.githubUrl;
234
+ sourceType = parsed.type;
231
235
  } else {
232
236
  const parsed = parseSource(source);
233
237
  skillName = parsed.inferredName;
234
238
  sourceValue = parsed.value;
239
+ sourceType = parsed.type;
235
240
  if (parsed.type === "github" && parsed.owner && parsed.repo) {
236
241
  const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
237
242
  localPath = result.localPath;
@@ -399,6 +404,8 @@ function registerSkillsCheck(parent) {
399
404
  console.log(pc6.dim("No tracked skills."));
400
405
  return;
401
406
  }
407
+ console.log(pc6.dim(`Checking ${entries.length} skill(s) for updates...
408
+ `));
402
409
  const results = [];
403
410
  for (const [name, entry] of entries) {
404
411
  const update = await checkSkillUpdate(name);
@@ -408,16 +415,37 @@ function registerSkillsCheck(parent) {
408
415
  console.log(JSON.stringify(results, null, 2));
409
416
  return;
410
417
  }
411
- console.log(pc6.bold(`
412
- ${entries.length} tracked skill(s):
413
- `));
418
+ let updatesAvailable = 0;
414
419
  for (const r of results) {
415
- const status = r.hasUpdate ? pc6.yellow("update available") : pc6.green("up to date");
416
- console.log(` ${pc6.bold(r.name.padEnd(30))} ${status}`);
420
+ let statusLabel;
421
+ if (r.status === "update-available") {
422
+ statusLabel = pc6.yellow("update available");
423
+ updatesAvailable++;
424
+ } else if (r.status === "up-to-date") {
425
+ statusLabel = pc6.green("up to date");
426
+ } else {
427
+ statusLabel = pc6.dim("unknown");
428
+ }
429
+ console.log(` ${pc6.bold(r.name.padEnd(30))} ${statusLabel}`);
430
+ if (r.currentVersion || r.latestVersion) {
431
+ const current = r.currentVersion ? r.currentVersion.slice(0, 12) : "?";
432
+ const latest = r.latestVersion ?? "?";
433
+ if (r.hasUpdate) {
434
+ console.log(` ${pc6.dim("current:")} ${current} ${pc6.dim("->")} ${pc6.cyan(latest)}`);
435
+ } else {
436
+ console.log(` ${pc6.dim("version:")} ${current}`);
437
+ }
438
+ }
417
439
  console.log(` ${pc6.dim(`source: ${r.entry.source}`)}`);
418
440
  console.log(` ${pc6.dim(`agents: ${r.entry.agents.join(", ")}`)}`);
419
441
  console.log();
420
442
  }
443
+ if (updatesAvailable > 0) {
444
+ console.log(pc6.yellow(`${updatesAvailable} update(s) available.`));
445
+ console.log(pc6.dim("Run `caamp skills update` to update all."));
446
+ } else {
447
+ console.log(pc6.green("All skills are up to date."));
448
+ }
421
449
  });
422
450
  }
423
451
 
@@ -432,9 +460,116 @@ function registerSkillsUpdate(parent) {
432
460
  return;
433
461
  }
434
462
  console.log(pc7.dim(`Checking ${entries.length} skill(s) for updates...`));
435
- console.log(pc7.dim("All skills are up to date."));
436
- console.log(pc7.dim("\nTo reinstall a specific skill:"));
437
- console.log(pc7.dim(" caamp skills install <source>"));
463
+ const outdated = [];
464
+ for (const [name] of entries) {
465
+ const result = await checkSkillUpdate(name);
466
+ if (result.hasUpdate) {
467
+ outdated.push({
468
+ name,
469
+ currentVersion: result.currentVersion,
470
+ latestVersion: result.latestVersion
471
+ });
472
+ }
473
+ }
474
+ if (outdated.length === 0) {
475
+ console.log(pc7.green("\nAll skills are up to date."));
476
+ return;
477
+ }
478
+ console.log(pc7.yellow(`
479
+ ${outdated.length} skill(s) have updates available:
480
+ `));
481
+ for (const skill of outdated) {
482
+ const current = skill.currentVersion?.slice(0, 12) ?? "?";
483
+ const latest = skill.latestVersion ?? "?";
484
+ console.log(` ${pc7.bold(skill.name)} ${pc7.dim(current)} ${pc7.dim("->")} ${pc7.cyan(latest)}`);
485
+ }
486
+ if (!opts.yes) {
487
+ const readline = await import("readline");
488
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
489
+ const answer = await new Promise((resolve) => {
490
+ rl.question(pc7.dim("\nProceed with update? [y/N] "), resolve);
491
+ });
492
+ rl.close();
493
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
494
+ console.log(pc7.dim("Update cancelled."));
495
+ return;
496
+ }
497
+ }
498
+ console.log();
499
+ let successCount = 0;
500
+ let failCount = 0;
501
+ for (const skill of outdated) {
502
+ const entry = tracked[skill.name];
503
+ if (!entry) continue;
504
+ console.log(pc7.dim(`Updating ${pc7.bold(skill.name)}...`));
505
+ try {
506
+ const parsed = parseSource(entry.source);
507
+ let localPath;
508
+ let cleanup;
509
+ if (parsed.type === "github" && parsed.owner && parsed.repo) {
510
+ const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
511
+ localPath = result.localPath;
512
+ cleanup = result.cleanup;
513
+ } else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
514
+ const result = await cloneGitLabRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
515
+ localPath = result.localPath;
516
+ cleanup = result.cleanup;
517
+ } else {
518
+ console.log(pc7.yellow(` Skipped ${skill.name}: source type "${parsed.type}" does not support auto-update`));
519
+ continue;
520
+ }
521
+ try {
522
+ const providers = entry.agents.map((a) => getProvider(a)).filter((p) => p !== void 0);
523
+ if (providers.length === 0) {
524
+ console.log(pc7.yellow(` Skipped ${skill.name}: no valid providers found`));
525
+ continue;
526
+ }
527
+ const installResult = await installSkill(
528
+ localPath,
529
+ skill.name,
530
+ providers,
531
+ entry.isGlobal,
532
+ entry.projectDir
533
+ );
534
+ if (installResult.success) {
535
+ await recordSkillInstall(
536
+ skill.name,
537
+ entry.scopedName,
538
+ entry.source,
539
+ entry.sourceType,
540
+ installResult.linkedAgents,
541
+ installResult.canonicalPath,
542
+ entry.isGlobal,
543
+ entry.projectDir,
544
+ skill.latestVersion
545
+ );
546
+ console.log(pc7.green(` Updated ${pc7.bold(skill.name)}`));
547
+ successCount++;
548
+ } else {
549
+ console.log(pc7.red(` Failed to update ${skill.name}: no agents linked`));
550
+ failCount++;
551
+ }
552
+ if (installResult.errors.length > 0) {
553
+ for (const err of installResult.errors) {
554
+ console.log(pc7.yellow(` ${err}`));
555
+ }
556
+ }
557
+ } finally {
558
+ if (cleanup) await cleanup();
559
+ }
560
+ } catch (err) {
561
+ const msg = err instanceof Error ? err.message : String(err);
562
+ console.log(pc7.red(` Failed to update ${skill.name}: ${msg}`));
563
+ failCount++;
564
+ }
565
+ }
566
+ console.log();
567
+ if (successCount > 0) {
568
+ console.log(pc7.green(`Updated ${successCount} skill(s).`));
569
+ }
570
+ if (failCount > 0) {
571
+ console.log(pc7.red(`Failed to update ${failCount} skill(s).`));
572
+ }
438
573
  });
439
574
  }
440
575
 
@@ -929,13 +1064,272 @@ ${provider.toolName} config (${configPath}):
929
1064
  });
930
1065
  }
931
1066
 
1067
+ // src/commands/doctor.ts
1068
+ import pc19 from "picocolors";
1069
+ import { execFileSync } from "child_process";
1070
+ import { existsSync as existsSync5, readdirSync, lstatSync } from "fs";
1071
+ import { homedir } from "os";
1072
+ import { join as join6 } from "path";
1073
+ var CAAMP_VERSION = "0.2.0";
1074
+ function getNodeVersion() {
1075
+ return process.version;
1076
+ }
1077
+ function getNpmVersion() {
1078
+ try {
1079
+ return execFileSync("npm", ["--version"], { stdio: "pipe", encoding: "utf-8" }).trim();
1080
+ } catch {
1081
+ return null;
1082
+ }
1083
+ }
1084
+ function checkEnvironment() {
1085
+ const checks = [];
1086
+ checks.push({ label: `Node.js ${getNodeVersion()}`, status: "pass" });
1087
+ const npmVersion = getNpmVersion();
1088
+ if (npmVersion) {
1089
+ checks.push({ label: `npm ${npmVersion}`, status: "pass" });
1090
+ } else {
1091
+ checks.push({ label: "npm not found", status: "warn" });
1092
+ }
1093
+ checks.push({ label: `CAAMP v${CAAMP_VERSION}`, status: "pass" });
1094
+ checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
1095
+ return { name: "Environment", checks };
1096
+ }
1097
+ function checkRegistry() {
1098
+ const checks = [];
1099
+ try {
1100
+ const providers = getAllProviders();
1101
+ const count = getProviderCount();
1102
+ checks.push({ label: `${count} providers loaded`, status: "pass" });
1103
+ const malformed = [];
1104
+ for (const p of providers) {
1105
+ if (!p.id || !p.toolName || !p.configKey || !p.configFormat) {
1106
+ malformed.push(p.id || "(unknown)");
1107
+ }
1108
+ }
1109
+ if (malformed.length === 0) {
1110
+ checks.push({ label: "All entries valid", status: "pass" });
1111
+ } else {
1112
+ checks.push({
1113
+ label: `${malformed.length} malformed entries`,
1114
+ status: "fail",
1115
+ detail: malformed.join(", ")
1116
+ });
1117
+ }
1118
+ } catch (err) {
1119
+ checks.push({
1120
+ label: "Failed to load registry",
1121
+ status: "fail",
1122
+ detail: err instanceof Error ? err.message : String(err)
1123
+ });
1124
+ }
1125
+ return { name: "Registry", checks };
1126
+ }
1127
+ function checkInstalledProviders() {
1128
+ const checks = [];
1129
+ try {
1130
+ const results = detectAllProviders();
1131
+ const installed = results.filter((r) => r.installed);
1132
+ checks.push({ label: `${installed.length} found`, status: "pass" });
1133
+ for (const r of installed) {
1134
+ const methods = r.methods.join(", ");
1135
+ checks.push({ label: `${r.provider.toolName} (${methods})`, status: "pass" });
1136
+ }
1137
+ } catch (err) {
1138
+ checks.push({
1139
+ label: "Detection failed",
1140
+ status: "fail",
1141
+ detail: err instanceof Error ? err.message : String(err)
1142
+ });
1143
+ }
1144
+ return { name: "Installed Providers", checks };
1145
+ }
1146
+ function checkSkillSymlinks() {
1147
+ const checks = [];
1148
+ const canonicalDir = join6(homedir(), ".agents", "skills");
1149
+ if (!existsSync5(canonicalDir)) {
1150
+ checks.push({ label: "0 canonical skills", status: "pass" });
1151
+ checks.push({ label: "No broken symlinks", status: "pass" });
1152
+ return { name: "Skills", checks };
1153
+ }
1154
+ let canonicalCount = 0;
1155
+ try {
1156
+ const entries = readdirSync(canonicalDir);
1157
+ canonicalCount = entries.length;
1158
+ checks.push({ label: `${canonicalCount} canonical skills`, status: "pass" });
1159
+ } catch {
1160
+ checks.push({ label: "Cannot read skills directory", status: "warn" });
1161
+ return { name: "Skills", checks };
1162
+ }
1163
+ const broken = [];
1164
+ const providers = getAllProviders();
1165
+ for (const provider of providers) {
1166
+ const skillDir = provider.pathSkills;
1167
+ if (!existsSync5(skillDir)) continue;
1168
+ try {
1169
+ const entries = readdirSync(skillDir);
1170
+ for (const entry of entries) {
1171
+ const fullPath = join6(skillDir, entry);
1172
+ try {
1173
+ const stat = lstatSync(fullPath);
1174
+ if (stat.isSymbolicLink()) {
1175
+ if (!existsSync5(fullPath)) {
1176
+ broken.push(`${provider.id}/${entry}`);
1177
+ }
1178
+ }
1179
+ } catch {
1180
+ }
1181
+ }
1182
+ } catch {
1183
+ }
1184
+ }
1185
+ if (broken.length === 0) {
1186
+ checks.push({ label: "No broken symlinks", status: "pass" });
1187
+ } else {
1188
+ checks.push({
1189
+ label: `${broken.length} broken symlinks`,
1190
+ status: "warn",
1191
+ detail: broken.join(", ")
1192
+ });
1193
+ }
1194
+ return { name: "Skills", checks };
1195
+ }
1196
+ async function checkLockFile() {
1197
+ const checks = [];
1198
+ try {
1199
+ const lock = await readLockFile();
1200
+ checks.push({ label: "Lock file valid", status: "pass" });
1201
+ let orphaned = 0;
1202
+ for (const [name, entry] of Object.entries(lock.skills)) {
1203
+ if (entry.canonicalPath && !existsSync5(entry.canonicalPath)) {
1204
+ orphaned++;
1205
+ }
1206
+ }
1207
+ if (orphaned === 0) {
1208
+ checks.push({ label: `0 orphaned entries`, status: "pass" });
1209
+ } else {
1210
+ checks.push({
1211
+ label: `${orphaned} orphaned skill entries`,
1212
+ status: "warn",
1213
+ detail: "Skills tracked in lock file but missing from disk"
1214
+ });
1215
+ }
1216
+ } catch (err) {
1217
+ checks.push({
1218
+ label: "Failed to read lock file",
1219
+ status: "fail",
1220
+ detail: err instanceof Error ? err.message : String(err)
1221
+ });
1222
+ }
1223
+ return { name: "Lock File", checks };
1224
+ }
1225
+ async function checkConfigFiles() {
1226
+ const checks = [];
1227
+ const results = detectAllProviders();
1228
+ const installed = results.filter((r) => r.installed);
1229
+ for (const r of installed) {
1230
+ const provider = r.provider;
1231
+ const configPath = provider.configPathGlobal;
1232
+ if (!existsSync5(configPath)) {
1233
+ checks.push({
1234
+ label: `${provider.id}: no config file found`,
1235
+ status: "warn",
1236
+ detail: configPath
1237
+ });
1238
+ continue;
1239
+ }
1240
+ try {
1241
+ await readConfig(configPath, provider.configFormat);
1242
+ const relPath = configPath.replace(homedir(), "~");
1243
+ checks.push({
1244
+ label: `${provider.id}: ${relPath} readable`,
1245
+ status: "pass"
1246
+ });
1247
+ } catch (err) {
1248
+ checks.push({
1249
+ label: `${provider.id}: config parse error`,
1250
+ status: "fail",
1251
+ detail: err instanceof Error ? err.message : String(err)
1252
+ });
1253
+ }
1254
+ }
1255
+ if (installed.length === 0) {
1256
+ checks.push({ label: "No installed providers to check", status: "pass" });
1257
+ }
1258
+ return { name: "Config Files", checks };
1259
+ }
1260
+ function formatSection(section) {
1261
+ const lines = [];
1262
+ lines.push(` ${pc19.bold(section.name)}`);
1263
+ for (const check of section.checks) {
1264
+ const icon = check.status === "pass" ? pc19.green("\u2713") : check.status === "warn" ? pc19.yellow("\u26A0") : pc19.red("\u2717");
1265
+ lines.push(` ${icon} ${check.label}`);
1266
+ if (check.detail) {
1267
+ lines.push(` ${pc19.dim(check.detail)}`);
1268
+ }
1269
+ }
1270
+ return lines.join("\n");
1271
+ }
1272
+ function registerDoctorCommand(program2) {
1273
+ program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON").action(async (opts) => {
1274
+ const sections = [];
1275
+ sections.push(checkEnvironment());
1276
+ sections.push(checkRegistry());
1277
+ sections.push(checkInstalledProviders());
1278
+ sections.push(checkSkillSymlinks());
1279
+ sections.push(await checkLockFile());
1280
+ sections.push(await checkConfigFiles());
1281
+ let passed = 0;
1282
+ let warnings = 0;
1283
+ let errors = 0;
1284
+ for (const section of sections) {
1285
+ for (const check of section.checks) {
1286
+ if (check.status === "pass") passed++;
1287
+ else if (check.status === "warn") warnings++;
1288
+ else errors++;
1289
+ }
1290
+ }
1291
+ if (opts.json) {
1292
+ const output = {
1293
+ version: CAAMP_VERSION,
1294
+ sections: sections.map((s) => ({
1295
+ name: s.name,
1296
+ checks: s.checks
1297
+ })),
1298
+ summary: { passed, warnings, errors }
1299
+ };
1300
+ console.log(JSON.stringify(output, null, 2));
1301
+ return;
1302
+ }
1303
+ console.log(pc19.bold("\ncaamp doctor\n"));
1304
+ for (const section of sections) {
1305
+ console.log(formatSection(section));
1306
+ console.log();
1307
+ }
1308
+ const parts = [];
1309
+ parts.push(pc19.green(`${passed} checks passed`));
1310
+ if (warnings > 0) parts.push(pc19.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
1311
+ if (errors > 0) parts.push(pc19.red(`${errors} error${errors !== 1 ? "s" : ""}`));
1312
+ console.log(` ${pc19.bold("Summary")}: ${parts.join(", ")}`);
1313
+ console.log();
1314
+ if (errors > 0) {
1315
+ process.exit(1);
1316
+ }
1317
+ });
1318
+ }
1319
+
932
1320
  // src/cli.ts
933
1321
  var program = new Command();
934
- program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version("0.1.0");
1322
+ 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");
1323
+ program.hook("preAction", (thisCommand) => {
1324
+ const opts = thisCommand.optsWithGlobals();
1325
+ if (opts.verbose) setVerbose(true);
1326
+ if (opts.quiet) setQuiet(true);
1327
+ });
935
1328
  registerProvidersCommand(program);
936
1329
  registerSkillsCommands(program);
937
1330
  registerMcpCommands(program);
938
1331
  registerInstructionsCommands(program);
939
1332
  registerConfigCommand(program);
1333
+ registerDoctorCommand(program);
940
1334
  program.parse();
941
1335
  //# sourceMappingURL=cli.js.map