@cleocode/caamp 1.4.0 → 1.5.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
@@ -50,6 +50,7 @@ import {
50
50
  readConfig,
51
51
  readLockFile,
52
52
  recommendSkills,
53
+ reconcileCleoLock,
53
54
  recordMcpInstall,
54
55
  recordSkillInstall,
55
56
  removeMcpFromLock,
@@ -73,7 +74,7 @@ import {
73
74
  tokenizeCriteriaValue,
74
75
  updateInstructionsSingleOperation,
75
76
  validateSkill
76
- } from "./chunk-QZOOTKAJ.js";
77
+ } from "./chunk-G7UPJOYG.js";
77
78
 
78
79
  // src/cli.ts
79
80
  import { Command } from "commander";
@@ -1173,6 +1174,69 @@ async function checkLockFile() {
1173
1174
  }
1174
1175
  return { name: "Lock File", checks };
1175
1176
  }
1177
+ async function checkMcpLockEntries() {
1178
+ const checks = [];
1179
+ try {
1180
+ const lock = await readLockFile();
1181
+ const lockNames = Object.keys(lock.mcpServers);
1182
+ checks.push({ label: `${lockNames.length} MCP server entries in lock`, status: "pass" });
1183
+ const results = detectAllProviders();
1184
+ const installed = results.filter((r) => r.installed);
1185
+ const liveCleoNames = /* @__PURE__ */ new Set();
1186
+ let untrackedCount = 0;
1187
+ for (const scope of ["project", "global"]) {
1188
+ for (const r of installed) {
1189
+ try {
1190
+ const entries = await listMcpServers(r.provider, scope);
1191
+ for (const entry of entries) {
1192
+ const channel = resolveChannelFromServerName(entry.name);
1193
+ if (!channel) continue;
1194
+ liveCleoNames.add(entry.name);
1195
+ if (!lock.mcpServers[entry.name]) {
1196
+ untrackedCount++;
1197
+ }
1198
+ }
1199
+ } catch {
1200
+ }
1201
+ }
1202
+ }
1203
+ if (untrackedCount === 0) {
1204
+ checks.push({ label: "All CLEO servers tracked in lock", status: "pass" });
1205
+ } else {
1206
+ checks.push({
1207
+ label: `${untrackedCount} untracked CLEO server${untrackedCount !== 1 ? "s" : ""} (in config, not in lock)`,
1208
+ status: "warn",
1209
+ detail: "Run `caamp cleo repair` to backfill lock entries"
1210
+ });
1211
+ }
1212
+ let orphanedCount = 0;
1213
+ const orphanedNames = [];
1214
+ for (const serverName of lockNames) {
1215
+ const channel = resolveChannelFromServerName(serverName);
1216
+ if (!channel) continue;
1217
+ if (!liveCleoNames.has(serverName)) {
1218
+ orphanedCount++;
1219
+ orphanedNames.push(serverName);
1220
+ }
1221
+ }
1222
+ if (orphanedCount === 0) {
1223
+ checks.push({ label: "No orphaned CLEO lock entries", status: "pass" });
1224
+ } else {
1225
+ checks.push({
1226
+ label: `${orphanedCount} orphaned CLEO lock entr${orphanedCount !== 1 ? "ies" : "y"} (in lock, not in any config)`,
1227
+ status: "warn",
1228
+ detail: orphanedNames.join(", ") + " \u2014 Run `caamp cleo repair --prune` to clean up"
1229
+ });
1230
+ }
1231
+ } catch (err) {
1232
+ checks.push({
1233
+ label: "Failed to check MCP lock entries",
1234
+ status: "fail",
1235
+ detail: err instanceof Error ? err.message : String(err)
1236
+ });
1237
+ }
1238
+ return { name: "MCP Lock", checks };
1239
+ }
1176
1240
  async function checkConfigFiles() {
1177
1241
  const checks = [];
1178
1242
  const results = detectAllProviders();
@@ -1241,6 +1305,7 @@ function registerDoctorCommand(program2) {
1241
1305
  sections.push(checkInstalledProviders());
1242
1306
  sections.push(checkSkillSymlinks());
1243
1307
  sections.push(await checkLockFile());
1308
+ sections.push(await checkMcpLockEntries());
1244
1309
  sections.push(await checkConfigFiles());
1245
1310
  let passed = 0;
1246
1311
  let warnings = 0;
@@ -1260,6 +1325,7 @@ function registerDoctorCommand(program2) {
1260
1325
  const detectionResults = detectAllProviders();
1261
1326
  const installedProviders = detectionResults.filter((r) => r.installed);
1262
1327
  const { canonicalCount, brokenCount, staleCount } = countSkillIssues();
1328
+ const { tracked: mcpTracked, untracked: mcpUntracked, orphaned: mcpOrphaned } = countMcpLockIssues(sections);
1263
1329
  const result = {
1264
1330
  environment: {
1265
1331
  node: getNodeVersion(),
@@ -1281,6 +1347,11 @@ function registerDoctorCommand(program2) {
1281
1347
  brokenLinks: brokenCount,
1282
1348
  staleLinks: staleCount
1283
1349
  },
1350
+ mcpServers: {
1351
+ tracked: mcpTracked,
1352
+ untracked: mcpUntracked,
1353
+ orphaned: mcpOrphaned
1354
+ },
1284
1355
  checks: sections.flatMap(
1285
1356
  (s) => s.checks.map((c) => ({
1286
1357
  label: `${s.name}: ${c.label}`,
@@ -1377,6 +1448,26 @@ function countSkillIssues() {
1377
1448
  }
1378
1449
  return { canonicalCount, brokenCount, staleCount };
1379
1450
  }
1451
+ function countMcpLockIssues(sections) {
1452
+ const mcpSection = sections.find((s) => s.name === "MCP Lock");
1453
+ if (!mcpSection) return { tracked: 0, untracked: 0, orphaned: 0 };
1454
+ let tracked = 0;
1455
+ let untracked = 0;
1456
+ let orphaned = 0;
1457
+ for (const check of mcpSection.checks) {
1458
+ const countMatch = check.label.match(/^(\d+)/);
1459
+ if (!countMatch?.[1]) continue;
1460
+ const count = Number.parseInt(countMatch[1], 10);
1461
+ if (check.label.includes("MCP server entries in lock")) {
1462
+ tracked = count;
1463
+ } else if (check.label.includes("untracked")) {
1464
+ untracked = count;
1465
+ } else if (check.label.includes("orphaned")) {
1466
+ orphaned = count;
1467
+ }
1468
+ }
1469
+ return { tracked, untracked, orphaned };
1470
+ }
1380
1471
 
1381
1472
  // src/commands/instructions/inject.ts
1382
1473
  import pc3 from "picocolors";
@@ -2066,14 +2157,14 @@ async function executeCleoShow(opts, operation) {
2066
2157
  const header = [
2067
2158
  "Channel".padEnd(10),
2068
2159
  "Version".padEnd(10),
2069
- "Provider".padEnd(15),
2160
+ "Provider".padEnd(22),
2070
2161
  "Scope".padEnd(9),
2071
2162
  "Command".padEnd(33),
2072
2163
  "Status".padEnd(10),
2073
2164
  "Installed".padEnd(12)
2074
2165
  ].join("");
2075
2166
  console.log(` ${pc6.dim(header)}`);
2076
- console.log(` ${pc6.dim("-".repeat(99))}`);
2167
+ console.log(` ${pc6.dim("-".repeat(106))}`);
2077
2168
  for (const entry of entries) {
2078
2169
  const commandStr = entry.command ? `${entry.command} ${entry.args.join(" ")}`.slice(0, 31).padEnd(33) : pc6.dim("-").padEnd(33);
2079
2170
  const versionStr = (entry.version ?? "-").padEnd(10);
@@ -2087,7 +2178,7 @@ async function executeCleoShow(opts, operation) {
2087
2178
  statusStr = pc6.red("broken".padEnd(10));
2088
2179
  }
2089
2180
  console.log(
2090
- ` ${entry.channel.padEnd(10)}${versionStr}${entry.providerName.padEnd(15)}${entry.scope.padEnd(9)}${commandStr}${statusStr}${installedStr}`
2181
+ ` ${entry.channel.padEnd(10)}${versionStr}${entry.providerName.padEnd(22)}${entry.scope.padEnd(9)}${commandStr}${statusStr}${installedStr}`
2091
2182
  );
2092
2183
  }
2093
2184
  console.log();
@@ -2121,6 +2212,71 @@ async function executeCleoShow(opts, operation) {
2121
2212
  );
2122
2213
  }
2123
2214
  }
2215
+ async function executeCleoRepair(opts, operation) {
2216
+ const mvi = "standard";
2217
+ let format;
2218
+ try {
2219
+ format = resolveFormat({
2220
+ jsonFlag: opts.json ?? false,
2221
+ humanFlag: (opts.human ?? false) || isHuman(),
2222
+ projectDefault: "json"
2223
+ });
2224
+ } catch (error) {
2225
+ const message = error instanceof Error ? error.message : String(error);
2226
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
2227
+ process.exit(1);
2228
+ }
2229
+ const providerIds = opts.provider.length > 0 ? opts.provider : void 0;
2230
+ const result = await reconcileCleoLock({
2231
+ providerIds,
2232
+ all: opts.all,
2233
+ global: opts.global,
2234
+ project: opts.project,
2235
+ prune: opts.prune,
2236
+ dryRun: opts.dryRun
2237
+ });
2238
+ if (format === "human") {
2239
+ const prefix = opts.dryRun ? "CLEO Lock Repair (dry run)" : "CLEO Lock Repair";
2240
+ console.log(pc6.bold(prefix));
2241
+ console.log();
2242
+ if (result.backfilled.length > 0) {
2243
+ for (const entry of result.backfilled) {
2244
+ const agents = entry.agents.join(", ");
2245
+ const versionStr = entry.version ? `(${entry.version})` : "";
2246
+ console.log(
2247
+ ` ${pc6.green("+")} ${entry.serverName.padEnd(12)}${entry.channel.padEnd(10)}${agents.padEnd(22)}${entry.scope.padEnd(10)}${entry.source} ${pc6.dim(versionStr)}`
2248
+ );
2249
+ }
2250
+ }
2251
+ if (result.pruned.length > 0) {
2252
+ for (const name of result.pruned) {
2253
+ console.log(` ${pc6.red("-")} ${name} (removed from lock)`);
2254
+ }
2255
+ }
2256
+ if (result.backfilled.length === 0 && result.pruned.length === 0) {
2257
+ console.log(pc6.dim(" No changes needed. All CLEO entries are tracked."));
2258
+ }
2259
+ console.log();
2260
+ console.log(
2261
+ ` ${result.backfilled.length} backfilled | ${result.pruned.length} pruned | ${result.alreadyTracked} already tracked`
2262
+ );
2263
+ if (result.errors.length > 0) {
2264
+ console.log();
2265
+ for (const err of result.errors) {
2266
+ console.log(` ${pc6.red("!")} ${err.message}`);
2267
+ }
2268
+ }
2269
+ }
2270
+ if (format === "json") {
2271
+ outputSuccess(operation, mvi, {
2272
+ backfilled: result.backfilled,
2273
+ pruned: result.pruned,
2274
+ alreadyTracked: result.alreadyTracked,
2275
+ dryRun: opts.dryRun ?? false,
2276
+ errors: result.errors
2277
+ });
2278
+ }
2279
+ }
2124
2280
  function buildInstallOptions(command) {
2125
2281
  return command.requiredOption("--channel <channel>", "CLEO channel: stable|beta|dev").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Apply to all detected providers").option("-g, --global", "Use global scope").option("--version <tag>", "Tag/version for stable or beta").option("--command <command>", "Dev channel command").option("--arg <arg>", "Dev command arg (repeatable)", collect, []).option("--env <kv>", "Environment assignment KEY=value (repeatable)", collect, []).option("--cleo-dir <path>", "CLEO_DIR override for dev channel").option("--dry-run", "Preview without writing").option("-y, --yes", "Skip confirmation").option("--interactive", "Guided interactive setup").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format");
2126
2282
  }
@@ -2142,6 +2298,9 @@ function registerMcpCleoCommands(parent) {
2142
2298
  cleo.command("show").description("Show installed CLEO MCP channel profiles").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Inspect all detected providers").option("-g, --global", "Global scope only").option("-p, --project", "Project scope only").option("--channel <channel>", "Filter channel: stable|beta|dev").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
2143
2299
  await executeCleoShow(opts, "mcp.cleo.show");
2144
2300
  });
2301
+ cleo.command("repair").description("Repair lock file by backfilling untracked CLEO entries").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Scan all detected providers").option("-g, --global", "Global scope only").option("-p, --project", "Project scope only").option("--prune", "Remove orphaned lock entries not in any config").option("--dry-run", "Preview without writing").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
2302
+ await executeCleoRepair(opts, "mcp.cleo.repair");
2303
+ });
2145
2304
  }
2146
2305
  function registerMcpCleoCompatibilityCommands(parent) {
2147
2306
  parent.command("update").description("Update channel-managed MCP profile").argument("<name>", "Managed MCP profile name (cleo)").requiredOption("--channel <channel>", "CLEO channel: stable|beta|dev").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Apply to all detected providers").option("-g, --global", "Use global scope").option("--version <tag>", "Tag/version for stable or beta").option("--command <command>", "Dev channel command").option("--arg <arg>", "Dev command arg (repeatable)", collect, []).option("--env <kv>", "Environment assignment KEY=value (repeatable)", collect, []).option("--cleo-dir <path>", "CLEO_DIR override for dev channel").option("--dry-run", "Preview without writing").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (name, opts) => {
@@ -2206,6 +2365,9 @@ function registerCleoCommands(program2) {
2206
2365
  cleo.command("show").description("Show installed CLEO channel profiles").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Inspect all detected providers").option("-g, --global", "Global scope only").option("-p, --project", "Project scope only").option("--channel <channel>", "Filter channel: stable|beta|dev").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
2207
2366
  await executeCleoShow(opts, "cleo.show");
2208
2367
  });
2368
+ cleo.command("repair").description("Repair lock file by backfilling untracked CLEO entries").option("--provider <id>", "Target provider (repeatable)", collect, []).option("--all", "Scan all detected providers").option("-g, --global", "Global scope only").option("-p, --project", "Project scope only").option("--prune", "Remove orphaned lock entries not in any config").option("--dry-run", "Preview without writing").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
2369
+ await executeCleoRepair(opts, "cleo.repair");
2370
+ });
2209
2371
  }
2210
2372
 
2211
2373
  // src/commands/mcp/detect.ts