@cleocode/caamp 1.0.3 → 1.0.5

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
@@ -34,6 +34,7 @@ import {
34
34
  isCatalogAvailable,
35
35
  isHuman,
36
36
  isMarketplaceScoped,
37
+ isQuiet,
37
38
  isVerbose,
38
39
  listCanonicalSkills,
39
40
  listMcpServers,
@@ -64,7 +65,7 @@ import {
64
65
  tokenizeCriteriaValue,
65
66
  updateInstructionsSingleOperation,
66
67
  validateSkill
67
- } from "./chunk-PBHH6KMJ.js";
68
+ } from "./chunk-HUMRYJPE.js";
68
69
 
69
70
  // src/cli.ts
70
71
  import { Command } from "commander";
@@ -714,40 +715,188 @@ function registerAdvancedCommands(program2) {
714
715
  }
715
716
 
716
717
  // src/commands/config.ts
717
- import pc from "picocolors";
718
718
  import { existsSync } from "fs";
719
+ import pc from "picocolors";
720
+
721
+ // src/core/lafs.ts
722
+ import { randomUUID as randomUUID2 } from "crypto";
723
+ import { resolveOutputFormat } from "@cleocode/lafs-protocol";
724
+ function resolveFormat(options) {
725
+ return resolveOutputFormat({
726
+ jsonFlag: options.jsonFlag ?? false,
727
+ humanFlag: (options.humanFlag ?? false) || isHuman(),
728
+ projectDefault: options.projectDefault ?? "json"
729
+ }).format;
730
+ }
731
+ function buildEnvelope(operation, mvi, result, error, page = null, sessionId, warnings) {
732
+ return {
733
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
734
+ _meta: {
735
+ specVersion: "1.0.0",
736
+ schemaVersion: "1.0.0",
737
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
738
+ operation,
739
+ requestId: randomUUID2(),
740
+ transport: "cli",
741
+ strict: true,
742
+ mvi,
743
+ contextVersion: 0,
744
+ ...sessionId && { sessionId },
745
+ ...warnings && warnings.length > 0 && { warnings }
746
+ },
747
+ success: error === null,
748
+ result,
749
+ error,
750
+ page
751
+ };
752
+ }
753
+ function emitError2(operation, mvi, code, message, category, details = {}, exitCode = 1) {
754
+ const envelope = buildEnvelope(operation, mvi, null, {
755
+ code,
756
+ message,
757
+ category,
758
+ retryable: category === "TRANSIENT" || category === "RATE_LIMIT",
759
+ retryAfterMs: null,
760
+ details
761
+ });
762
+ console.error(JSON.stringify(envelope, null, 2));
763
+ process.exit(exitCode);
764
+ }
765
+ function emitJsonError(operation, mvi, code, message, category, details = {}) {
766
+ const envelope = buildEnvelope(operation, mvi, null, {
767
+ code,
768
+ message,
769
+ category,
770
+ retryable: category === "TRANSIENT" || category === "RATE_LIMIT",
771
+ retryAfterMs: null,
772
+ details
773
+ });
774
+ console.error(JSON.stringify(envelope, null, 2));
775
+ }
776
+ function outputSuccess(operation, mvi, result, page, sessionId, warnings) {
777
+ const envelope = buildEnvelope(operation, mvi, result, null, page ?? null, sessionId, warnings);
778
+ if (isQuiet() && !envelope.error) {
779
+ return;
780
+ }
781
+ console.log(JSON.stringify(envelope, null, 2));
782
+ }
783
+ function handleFormatError(error, operation, mvi, jsonFlag) {
784
+ const message = error instanceof Error ? error.message : String(error);
785
+ if (jsonFlag) {
786
+ emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
787
+ } else {
788
+ console.error(message);
789
+ }
790
+ process.exit(1);
791
+ }
792
+ var ErrorCategories = {
793
+ VALIDATION: "VALIDATION",
794
+ AUTH: "AUTH",
795
+ PERMISSION: "PERMISSION",
796
+ NOT_FOUND: "NOT_FOUND",
797
+ CONFLICT: "CONFLICT",
798
+ RATE_LIMIT: "RATE_LIMIT",
799
+ TRANSIENT: "TRANSIENT",
800
+ INTERNAL: "INTERNAL",
801
+ CONTRACT: "CONTRACT",
802
+ MIGRATION: "MIGRATION"
803
+ };
804
+ var ErrorCodes = {
805
+ // Format errors
806
+ FORMAT_CONFLICT: "E_FORMAT_CONFLICT",
807
+ INVALID_JSON: "E_INVALID_JSON",
808
+ // Not found errors
809
+ SKILL_NOT_FOUND: "E_SKILL_NOT_FOUND",
810
+ PROVIDER_NOT_FOUND: "E_PROVIDER_NOT_FOUND",
811
+ MCP_SERVER_NOT_FOUND: "E_MCP_SERVER_NOT_FOUND",
812
+ FILE_NOT_FOUND: "E_FILE_NOT_FOUND",
813
+ // Validation errors
814
+ INVALID_INPUT: "E_INVALID_INPUT",
815
+ INVALID_CONSTRAINT: "E_INVALID_CONSTRAINT",
816
+ INVALID_FORMAT: "E_INVALID_FORMAT",
817
+ // Operation errors
818
+ INSTALL_FAILED: "E_INSTALL_FAILED",
819
+ REMOVE_FAILED: "E_REMOVE_FAILED",
820
+ UPDATE_FAILED: "E_UPDATE_FAILED",
821
+ VALIDATION_FAILED: "E_VALIDATION_FAILED",
822
+ AUDIT_FAILED: "E_AUDIT_FAILED",
823
+ // System errors
824
+ NETWORK_ERROR: "E_NETWORK_ERROR",
825
+ FILE_SYSTEM_ERROR: "E_FILE_SYSTEM_ERROR",
826
+ PERMISSION_DENIED: "E_PERMISSION_DENIED",
827
+ INTERNAL_ERROR: "E_INTERNAL_ERROR"
828
+ };
829
+
830
+ // src/commands/config.ts
719
831
  function registerConfigCommand(program2) {
720
832
  const config = program2.command("config").description("View provider configuration");
721
- config.command("show").description("Show provider configuration").argument("<provider>", "Provider ID or alias").option("-g, --global", "Show global config").option("--json", "Output as JSON").action(async (providerId, opts) => {
833
+ config.command("show").description("Show provider configuration").argument("<provider>", "Provider ID or alias").option("-g, --global", "Show global config").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (providerId, opts) => {
834
+ const operation = "config.show";
835
+ const mvi = "standard";
836
+ let format;
837
+ try {
838
+ format = resolveFormat({
839
+ jsonFlag: opts.json ?? false,
840
+ humanFlag: opts.human ?? false,
841
+ projectDefault: "json"
842
+ });
843
+ } catch (error) {
844
+ const message = error instanceof Error ? error.message : String(error);
845
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
846
+ process.exit(1);
847
+ }
722
848
  const provider = getProvider(providerId);
723
849
  if (!provider) {
724
- console.error(pc.red(`Provider not found: ${providerId}`));
850
+ const message = `Provider not found: ${providerId}`;
851
+ if (format === "json") {
852
+ emitJsonError(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
853
+ providerId
854
+ });
855
+ } else {
856
+ console.error(pc.red(message));
857
+ }
725
858
  process.exit(1);
726
859
  }
727
- const configPath = resolveProviderConfigPath(
728
- provider,
729
- opts.global ? "global" : "project"
730
- ) ?? provider.configPathGlobal;
860
+ const scope = opts.global ? "global" : "project";
861
+ const configPath = resolveProviderConfigPath(provider, scope) ?? provider.configPathGlobal;
731
862
  if (!existsSync(configPath)) {
732
- console.log(pc.dim(`No config file at: ${configPath}`));
733
- return;
863
+ const message = `No config file at: ${configPath}`;
864
+ if (format === "json") {
865
+ emitJsonError(operation, mvi, ErrorCodes.FILE_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
866
+ configPath,
867
+ scope
868
+ });
869
+ } else {
870
+ console.log(pc.dim(message));
871
+ }
872
+ process.exit(1);
734
873
  }
735
874
  try {
736
875
  const data = await readConfig(configPath, provider.configFormat);
737
- if (opts.json) {
738
- console.log(JSON.stringify(data, null, 2));
739
- } else {
740
- console.log(pc.bold(`
876
+ if (format === "json") {
877
+ outputSuccess(operation, mvi, {
878
+ provider: provider.id,
879
+ config: data,
880
+ format: provider.configFormat,
881
+ scope
882
+ });
883
+ return;
884
+ }
885
+ console.log(pc.bold(`
741
886
  ${provider.toolName} config (${configPath}):
742
887
  `));
743
- console.log(JSON.stringify(data, null, 2));
744
- }
888
+ console.log(JSON.stringify(data, null, 2));
745
889
  } catch (err) {
746
- console.error(pc.red(`Error reading config: ${err instanceof Error ? err.message : String(err)}`));
890
+ const message = `Error reading config: ${err instanceof Error ? err.message : String(err)}`;
891
+ if (format === "json") {
892
+ emitJsonError(operation, mvi, ErrorCodes.FILE_SYSTEM_ERROR, message, ErrorCategories.INTERNAL);
893
+ } else {
894
+ console.error(pc.red(message));
895
+ }
747
896
  process.exit(1);
748
897
  }
749
898
  });
750
- config.command("path").description("Show config file path").argument("<provider>", "Provider ID or alias").argument("[scope]", "Scope: project (default) or global", "project").action((providerId, scope) => {
899
+ config.command("path").description("Show config file path (outputs raw path for piping)").argument("<provider>", "Provider ID or alias").argument("[scope]", "Scope: project (default) or global", "project").action((providerId, scope) => {
751
900
  const provider = getProvider(providerId);
752
901
  if (!provider) {
753
902
  console.error(pc.red(`Provider not found: ${providerId}`));
@@ -768,11 +917,11 @@ ${provider.toolName} config (${configPath}):
768
917
  }
769
918
 
770
919
  // src/commands/doctor.ts
771
- import pc2 from "picocolors";
772
920
  import { execFileSync } from "child_process";
773
- import { existsSync as existsSync2, readdirSync, lstatSync, readlinkSync } from "fs";
921
+ import { existsSync as existsSync2, lstatSync, readdirSync, readlinkSync } from "fs";
774
922
  import { homedir } from "os";
775
923
  import { join as join2 } from "path";
924
+ import pc2 from "picocolors";
776
925
 
777
926
  // src/core/version.ts
778
927
  import { readFileSync } from "fs";
@@ -1064,57 +1213,181 @@ function formatSection(section) {
1064
1213
  return lines.join("\n");
1065
1214
  }
1066
1215
  function registerDoctorCommand(program2) {
1067
- program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON").action(async (opts) => {
1068
- const sections = [];
1069
- sections.push(checkEnvironment());
1070
- sections.push(checkRegistry());
1071
- sections.push(checkInstalledProviders());
1072
- sections.push(checkSkillSymlinks());
1073
- sections.push(await checkLockFile());
1074
- sections.push(await checkConfigFiles());
1075
- let passed = 0;
1076
- let warnings = 0;
1077
- let errors = 0;
1078
- for (const section of sections) {
1079
- for (const check of section.checks) {
1080
- if (check.status === "pass") passed++;
1081
- else if (check.status === "warn") warnings++;
1082
- else errors++;
1083
- }
1084
- }
1085
- if (opts.json) {
1086
- const output = {
1087
- version: getCaampVersion(),
1088
- sections: sections.map((s) => ({
1089
- name: s.name,
1090
- checks: s.checks
1091
- })),
1092
- summary: { passed, warnings, errors }
1093
- };
1094
- console.log(JSON.stringify(output, null, 2));
1095
- return;
1216
+ program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1217
+ const operation = "doctor.check";
1218
+ const mvi = "standard";
1219
+ let format;
1220
+ try {
1221
+ format = resolveFormat({
1222
+ jsonFlag: opts.json ?? false,
1223
+ humanFlag: opts.human ?? false,
1224
+ projectDefault: "json"
1225
+ });
1226
+ } catch (error) {
1227
+ handleFormatError(error, operation, mvi, opts.json);
1096
1228
  }
1097
- console.log(pc2.bold("\ncaamp doctor\n"));
1098
- for (const section of sections) {
1099
- console.log(formatSection(section));
1229
+ try {
1230
+ const sections = [];
1231
+ sections.push(checkEnvironment());
1232
+ sections.push(checkRegistry());
1233
+ sections.push(checkInstalledProviders());
1234
+ sections.push(checkSkillSymlinks());
1235
+ sections.push(await checkLockFile());
1236
+ sections.push(await checkConfigFiles());
1237
+ let passed = 0;
1238
+ let warnings = 0;
1239
+ let errors = 0;
1240
+ for (const section of sections) {
1241
+ for (const check of section.checks) {
1242
+ if (check.status === "pass") passed++;
1243
+ else if (check.status === "warn") warnings++;
1244
+ else errors++;
1245
+ }
1246
+ }
1247
+ const npmVersion = getNpmVersion() ?? "not found";
1248
+ const allProviders = getAllProviders();
1249
+ const malformedCount = allProviders.filter(
1250
+ (p) => !p.id || !p.toolName || !p.configKey || !p.configFormat
1251
+ ).length;
1252
+ const detectionResults = detectAllProviders();
1253
+ const installedProviders = detectionResults.filter((r) => r.installed);
1254
+ const { canonicalCount, brokenCount, staleCount } = countSkillIssues();
1255
+ const result = {
1256
+ environment: {
1257
+ node: getNodeVersion(),
1258
+ npm: npmVersion,
1259
+ caamp: getCaampVersion(),
1260
+ platform: `${process.platform} ${process.arch}`
1261
+ },
1262
+ registry: {
1263
+ loaded: true,
1264
+ count: getProviderCount(),
1265
+ valid: malformedCount === 0
1266
+ },
1267
+ providers: {
1268
+ installed: installedProviders.length,
1269
+ list: installedProviders.map((r) => r.provider.id)
1270
+ },
1271
+ skills: {
1272
+ canonical: canonicalCount,
1273
+ brokenLinks: brokenCount,
1274
+ staleLinks: staleCount
1275
+ },
1276
+ checks: sections.flatMap(
1277
+ (s) => s.checks.map((c) => ({
1278
+ label: `${s.name}: ${c.label}`,
1279
+ status: c.status,
1280
+ message: c.detail
1281
+ }))
1282
+ )
1283
+ };
1284
+ if (format === "json") {
1285
+ outputSuccess(operation, mvi, result);
1286
+ if (errors > 0) {
1287
+ process.exit(1);
1288
+ }
1289
+ return;
1290
+ }
1291
+ console.log(pc2.bold("\ncaamp doctor\n"));
1292
+ for (const section of sections) {
1293
+ console.log(formatSection(section));
1294
+ console.log();
1295
+ }
1296
+ const parts = [];
1297
+ parts.push(pc2.green(`${passed} checks passed`));
1298
+ if (warnings > 0) parts.push(pc2.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
1299
+ if (errors > 0) parts.push(pc2.red(`${errors} error${errors !== 1 ? "s" : ""}`));
1300
+ console.log(` ${pc2.bold("Summary")}: ${parts.join(", ")}`);
1100
1301
  console.log();
1101
- }
1102
- const parts = [];
1103
- parts.push(pc2.green(`${passed} checks passed`));
1104
- if (warnings > 0) parts.push(pc2.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
1105
- if (errors > 0) parts.push(pc2.red(`${errors} error${errors !== 1 ? "s" : ""}`));
1106
- console.log(` ${pc2.bold("Summary")}: ${parts.join(", ")}`);
1107
- console.log();
1108
- if (errors > 0) {
1302
+ if (errors > 0) {
1303
+ process.exit(1);
1304
+ }
1305
+ } catch (error) {
1306
+ const message = error instanceof Error ? error.message : String(error);
1307
+ if (format === "json") {
1308
+ emitJsonError(
1309
+ operation,
1310
+ mvi,
1311
+ ErrorCodes.INTERNAL_ERROR,
1312
+ message,
1313
+ ErrorCategories.INTERNAL
1314
+ );
1315
+ } else {
1316
+ console.error(pc2.red(`Error: ${message}`));
1317
+ }
1109
1318
  process.exit(1);
1110
1319
  }
1111
1320
  });
1112
1321
  }
1322
+ function countSkillIssues() {
1323
+ const canonicalDir = CANONICAL_SKILLS_DIR;
1324
+ let canonicalCount = 0;
1325
+ if (existsSync2(canonicalDir)) {
1326
+ try {
1327
+ const names = readdirSync(canonicalDir).filter((name) => {
1328
+ const full = join2(canonicalDir, name);
1329
+ try {
1330
+ const stat = lstatSync(full);
1331
+ return stat.isDirectory() || stat.isSymbolicLink();
1332
+ } catch {
1333
+ return false;
1334
+ }
1335
+ });
1336
+ canonicalCount = names.length;
1337
+ } catch {
1338
+ }
1339
+ }
1340
+ let brokenCount = 0;
1341
+ let staleCount = 0;
1342
+ const results = detectAllProviders();
1343
+ const installed = results.filter((r) => r.installed);
1344
+ for (const r of installed) {
1345
+ const provider = r.provider;
1346
+ const skillDir = provider.pathSkills;
1347
+ if (!existsSync2(skillDir)) continue;
1348
+ try {
1349
+ const entries = readdirSync(skillDir);
1350
+ for (const entry of entries) {
1351
+ const fullPath = join2(skillDir, entry);
1352
+ try {
1353
+ const stat = lstatSync(fullPath);
1354
+ if (!stat.isSymbolicLink()) continue;
1355
+ if (!existsSync2(fullPath)) {
1356
+ brokenCount++;
1357
+ } else {
1358
+ const target = readlinkSync(fullPath);
1359
+ const isCanonical = target.includes("/.agents/skills/") || target.includes("\\.agents\\skills\\");
1360
+ if (!isCanonical) {
1361
+ staleCount++;
1362
+ }
1363
+ }
1364
+ } catch {
1365
+ }
1366
+ }
1367
+ } catch {
1368
+ }
1369
+ }
1370
+ return { canonicalCount, brokenCount, staleCount };
1371
+ }
1113
1372
 
1114
1373
  // src/commands/instructions/inject.ts
1115
1374
  import pc3 from "picocolors";
1116
1375
  function registerInstructionsInject(parent) {
1117
- parent.command("inject").description("Inject instruction blocks into all provider files").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Inject into global instruction files").option("--content <text>", "Custom content to inject").option("--dry-run", "Preview without writing").option("--all", "Target all known providers").action(async (opts) => {
1376
+ parent.command("inject").description("Inject instruction blocks into all provider files").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Inject into global instruction files").option("--content <text>", "Custom content to inject").option("--dry-run", "Preview without writing").option("--all", "Target all known providers").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1377
+ const operation = "instructions.inject";
1378
+ const mvi = "standard";
1379
+ let format;
1380
+ try {
1381
+ format = resolveFormat({
1382
+ jsonFlag: opts.json ?? false,
1383
+ humanFlag: opts.human ?? false,
1384
+ projectDefault: "json"
1385
+ });
1386
+ } catch (error) {
1387
+ const message = error instanceof Error ? error.message : String(error);
1388
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1389
+ process.exit(1);
1390
+ }
1118
1391
  let providers;
1119
1392
  if (opts.all) {
1120
1393
  providers = getAllProviders();
@@ -1124,36 +1397,80 @@ function registerInstructionsInject(parent) {
1124
1397
  providers = getInstalledProviders();
1125
1398
  }
1126
1399
  if (providers.length === 0) {
1127
- console.error(pc3.red("No providers found."));
1400
+ const message = "No providers found.";
1401
+ if (format === "json") {
1402
+ emitJsonError(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
1403
+ } else {
1404
+ console.error(pc3.red(message));
1405
+ }
1128
1406
  process.exit(1);
1129
1407
  }
1130
1408
  const content = opts.content ?? generateInjectionContent();
1131
1409
  const scope = opts.global ? "global" : "project";
1132
1410
  const groups = groupByInstructFile(providers);
1133
1411
  if (opts.dryRun) {
1134
- console.log(pc3.bold("Dry run - would inject into:\n"));
1135
- for (const [file, group] of groups) {
1136
- console.log(` ${pc3.bold(file)}: ${group.map((p) => p.id).join(", ")}`);
1137
- }
1138
- console.log(pc3.dim(`
1412
+ if (format === "json") {
1413
+ outputSuccess(operation, mvi, {
1414
+ injected: [],
1415
+ providers: providers.map((p) => p.id),
1416
+ count: 0,
1417
+ dryRun: true,
1418
+ wouldInject: Array.from(groups.entries()).map(([file, group]) => ({
1419
+ file,
1420
+ providers: group.map((p) => p.id)
1421
+ }))
1422
+ });
1423
+ } else {
1424
+ console.log(pc3.bold("Dry run - would inject into:\n"));
1425
+ for (const [file, group] of groups) {
1426
+ console.log(` ${pc3.bold(file)}: ${group.map((p) => p.id).join(", ")}`);
1427
+ }
1428
+ console.log(pc3.dim(`
1139
1429
  Scope: ${scope}`));
1140
- console.log(pc3.dim(` Content length: ${content.length} chars`));
1430
+ console.log(pc3.dim(` Content length: ${content.length} chars`));
1431
+ }
1141
1432
  return;
1142
1433
  }
1143
1434
  const results = await injectAll(providers, process.cwd(), scope, content);
1144
- for (const [file, action] of results) {
1145
- const icon = action === "created" ? pc3.green("+") : action === "updated" ? pc3.yellow("~") : pc3.blue("^");
1146
- console.log(` ${icon} ${file} (${action})`);
1435
+ const injected = [];
1436
+ for (const [file] of results) {
1437
+ injected.push(file);
1147
1438
  }
1148
- console.log(pc3.bold(`
1439
+ if (format === "json") {
1440
+ outputSuccess(operation, mvi, {
1441
+ injected,
1442
+ providers: providers.map((p) => p.id),
1443
+ count: results.size
1444
+ });
1445
+ } else {
1446
+ for (const [file, action] of results) {
1447
+ const icon = action === "created" ? pc3.green("+") : action === "updated" ? pc3.yellow("~") : pc3.blue("^");
1448
+ console.log(` ${icon} ${file} (${action})`);
1449
+ }
1450
+ console.log(pc3.bold(`
1149
1451
  ${results.size} file(s) processed.`));
1452
+ }
1150
1453
  });
1151
1454
  }
1152
1455
 
1153
1456
  // src/commands/instructions/check.ts
1154
1457
  import pc4 from "picocolors";
1155
1458
  function registerInstructionsCheck(parent) {
1156
- parent.command("check").description("Check injection status across providers").option("-a, --agent <name>", "Check specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Check global instruction files").option("--json", "Output as JSON").option("--all", "Check all known providers").action(async (opts) => {
1459
+ parent.command("check").description("Check injection status across providers").option("-a, --agent <name>", "Check specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Check global instruction files").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").option("--all", "Check all known providers").action(async (opts) => {
1460
+ const operation = "instructions.check";
1461
+ const mvi = "standard";
1462
+ let format;
1463
+ try {
1464
+ format = resolveFormat({
1465
+ jsonFlag: opts.json ?? false,
1466
+ humanFlag: opts.human ?? false,
1467
+ projectDefault: "json"
1468
+ });
1469
+ } catch (error) {
1470
+ const message = error instanceof Error ? error.message : String(error);
1471
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1472
+ process.exit(1);
1473
+ }
1157
1474
  let providers;
1158
1475
  if (opts.all) {
1159
1476
  providers = getAllProviders();
@@ -1164,8 +1481,19 @@ function registerInstructionsCheck(parent) {
1164
1481
  }
1165
1482
  const scope = opts.global ? "global" : "project";
1166
1483
  const results = await checkAllInjections(providers, process.cwd(), scope);
1167
- if (opts.json) {
1168
- console.log(JSON.stringify(results, null, 2));
1484
+ const providerStatus = results.map((r) => ({
1485
+ id: r.provider,
1486
+ present: r.status === "current" || r.status === "outdated",
1487
+ path: r.file
1488
+ }));
1489
+ const present = providerStatus.filter((p) => p.present).length;
1490
+ const missing = providerStatus.filter((p) => !p.present).length;
1491
+ if (format === "json") {
1492
+ outputSuccess(operation, mvi, {
1493
+ providers: providerStatus,
1494
+ present,
1495
+ missing
1496
+ });
1169
1497
  return;
1170
1498
  }
1171
1499
  console.log(pc4.bold(`
@@ -1201,30 +1529,67 @@ Instruction file status (${scope}):
1201
1529
  // src/commands/instructions/update.ts
1202
1530
  import pc5 from "picocolors";
1203
1531
  function registerInstructionsUpdate(parent) {
1204
- parent.command("update").description("Update all instruction file injections").option("-g, --global", "Update global instruction files").option("-y, --yes", "Skip confirmation").action(async (opts) => {
1532
+ parent.command("update").description("Update all instruction file injections").option("-g, --global", "Update global instruction files").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1533
+ const operation = "instructions.update";
1534
+ const mvi = "standard";
1535
+ let format;
1536
+ try {
1537
+ format = resolveFormat({
1538
+ jsonFlag: opts.json ?? false,
1539
+ humanFlag: opts.human ?? false,
1540
+ projectDefault: "json"
1541
+ });
1542
+ } catch (error) {
1543
+ const message = error instanceof Error ? error.message : String(error);
1544
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1545
+ process.exit(1);
1546
+ }
1205
1547
  const providers = getInstalledProviders();
1206
1548
  const scope = opts.global ? "global" : "project";
1207
1549
  const content = generateInjectionContent();
1208
1550
  const checks = await checkAllInjections(providers, process.cwd(), scope, content);
1209
1551
  const needsUpdate = checks.filter((c) => c.status !== "current");
1210
1552
  if (needsUpdate.length === 0) {
1211
- console.log(pc5.green("All instruction files are up to date."));
1553
+ if (format === "json") {
1554
+ outputSuccess(operation, mvi, {
1555
+ updated: [],
1556
+ failed: [],
1557
+ count: { updated: 0, failed: 0 }
1558
+ });
1559
+ } else {
1560
+ console.log(pc5.green("All instruction files are up to date."));
1561
+ }
1212
1562
  return;
1213
1563
  }
1214
- console.log(pc5.bold(`${needsUpdate.length} file(s) need updating:
1564
+ if (format === "human") {
1565
+ console.log(pc5.bold(`${needsUpdate.length} file(s) need updating:
1215
1566
  `));
1216
- for (const c of needsUpdate) {
1217
- console.log(` ${c.file} (${c.status})`);
1567
+ for (const c of needsUpdate) {
1568
+ console.log(` ${c.file} (${c.status})`);
1569
+ }
1218
1570
  }
1219
1571
  const providerIds = new Set(needsUpdate.map((c) => c.provider));
1220
1572
  const toUpdate = providers.filter((p) => providerIds.has(p.id));
1221
1573
  const results = await injectAll(toUpdate, process.cwd(), scope, content);
1222
- console.log();
1223
- for (const [file, action] of results) {
1224
- console.log(` ${pc5.green("\u2713")} ${file} (${action})`);
1574
+ const updated = [];
1575
+ for (const [file] of results) {
1576
+ updated.push(file);
1225
1577
  }
1226
- console.log(pc5.bold(`
1578
+ if (format === "human") {
1579
+ console.log();
1580
+ for (const [file, action] of results) {
1581
+ console.log(` ${pc5.green("\u2713")} ${file} (${action})`);
1582
+ }
1583
+ console.log(pc5.bold(`
1227
1584
  ${results.size} file(s) updated.`));
1585
+ }
1586
+ if (format === "json") {
1587
+ outputSuccess(operation, mvi, {
1588
+ updated,
1589
+ failed: [],
1590
+ count: { updated: updated.length, failed: 0 }
1591
+ });
1592
+ }
1228
1593
  });
1229
1594
  }
1230
1595
 
@@ -1239,7 +1604,21 @@ function registerInstructionsCommands(program2) {
1239
1604
  // src/commands/mcp/install.ts
1240
1605
  import pc6 from "picocolors";
1241
1606
  function registerMcpInstall(parent) {
1242
- parent.command("install").description("Install MCP server to agent configs").argument("<source>", "MCP server source (URL, npm package, or command)").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Install to global/user config").option("-n, --name <name>", "Override inferred server name").option("-t, --transport <type>", "Transport type: http (default) or sse", "http").option("--header <header>", "HTTP header (Key: Value)", (v, prev) => [...prev, v], []).option("-y, --yes", "Skip confirmation").option("--all", "Install to all detected agents").option("--dry-run", "Preview without writing").action(async (source, opts) => {
1607
+ parent.command("install").description("Install MCP server to agent configs").argument("<source>", "MCP server source (URL, npm package, or command)").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Install to global/user config").option("-n, --name <name>", "Override inferred server name").option("-t, --transport <type>", "Transport type: http (default) or sse", "http").option("--header <header>", "HTTP header (Key: Value)", (v, prev) => [...prev, v], []).option("-y, --yes", "Skip confirmation").option("--all", "Install to all detected agents").option("--dry-run", "Preview without writing").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (source, opts) => {
1608
+ const operation = "mcp.install";
1609
+ const mvi = "standard";
1610
+ let format;
1611
+ try {
1612
+ format = resolveFormat({
1613
+ jsonFlag: opts.json ?? false,
1614
+ humanFlag: (opts.human ?? false) || isHuman(),
1615
+ projectDefault: "json"
1616
+ });
1617
+ } catch (error) {
1618
+ const message = error instanceof Error ? error.message : String(error);
1619
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1620
+ process.exit(1);
1621
+ }
1243
1622
  const parsed = parseSource(source);
1244
1623
  const serverName = opts.name ?? parsed.inferredName;
1245
1624
  const headers = {};
@@ -1259,34 +1638,55 @@ function registerMcpInstall(parent) {
1259
1638
  providers = getInstalledProviders();
1260
1639
  }
1261
1640
  if (providers.length === 0) {
1262
- console.error(pc6.red("No target providers found."));
1641
+ const message = "No target providers found.";
1642
+ if (format === "json") {
1643
+ emitJsonError(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
1644
+ } else {
1645
+ console.error(pc6.red(message));
1646
+ }
1263
1647
  process.exit(1);
1264
1648
  }
1265
1649
  const scope = opts.global ? "global" : "project";
1266
1650
  if (opts.dryRun) {
1267
- console.log(pc6.bold("Dry run - would install:"));
1268
- console.log(` Server: ${pc6.bold(serverName)}`);
1269
- console.log(` Config: ${JSON.stringify(config, null, 2)}`);
1270
- console.log(` Scope: ${scope}`);
1271
- console.log(` Providers: ${providers.map((p) => p.id).join(", ")}`);
1651
+ if (format === "json") {
1652
+ outputSuccess(operation, mvi, {
1653
+ installed: [{
1654
+ name: serverName,
1655
+ providers: providers.map((p) => p.id),
1656
+ config
1657
+ }],
1658
+ dryRun: true
1659
+ });
1660
+ } else {
1661
+ console.log(pc6.bold("Dry run - would install:"));
1662
+ console.log(` Server: ${pc6.bold(serverName)}`);
1663
+ console.log(` Config: ${JSON.stringify(config, null, 2)}`);
1664
+ console.log(` Scope: ${scope}`);
1665
+ console.log(` Providers: ${providers.map((p) => p.id).join(", ")}`);
1666
+ }
1272
1667
  return;
1273
1668
  }
1274
- console.log(pc6.dim(`Installing "${serverName}" to ${providers.length} provider(s)...
1669
+ if (format === "human") {
1670
+ console.log(pc6.dim(`Installing "${serverName}" to ${providers.length} provider(s)...
1275
1671
  `));
1672
+ }
1276
1673
  const results = await installMcpServerToAll(
1277
1674
  providers,
1278
1675
  serverName,
1279
1676
  config,
1280
1677
  scope
1281
1678
  );
1282
- for (const r of results) {
1283
- if (r.success) {
1284
- console.log(` ${pc6.green("\u2713")} ${r.provider.toolName.padEnd(22)} ${pc6.dim(r.configPath)}`);
1285
- } else {
1286
- console.log(` ${pc6.red("\u2717")} ${r.provider.toolName.padEnd(22)} ${pc6.red(r.error ?? "failed")}`);
1679
+ const succeeded = results.filter((r) => r.success);
1680
+ const failed = results.filter((r) => !r.success);
1681
+ if (format === "human") {
1682
+ for (const r of results) {
1683
+ if (r.success) {
1684
+ console.log(` ${pc6.green("\u2713")} ${r.provider.toolName.padEnd(22)} ${pc6.dim(r.configPath)}`);
1685
+ } else {
1686
+ console.log(` ${pc6.red("\u2717")} ${r.provider.toolName.padEnd(22)} ${pc6.red(r.error ?? "failed")}`);
1687
+ }
1287
1688
  }
1288
1689
  }
1289
- const succeeded = results.filter((r) => r.success);
1290
1690
  if (succeeded.length > 0) {
1291
1691
  await recordMcpInstall(
1292
1692
  serverName,
@@ -1296,15 +1696,40 @@ function registerMcpInstall(parent) {
1296
1696
  opts.global ?? false
1297
1697
  );
1298
1698
  }
1299
- console.log(pc6.bold(`
1699
+ if (format === "json") {
1700
+ outputSuccess(operation, mvi, {
1701
+ installed: succeeded.map((r) => ({
1702
+ name: serverName,
1703
+ providers: [r.provider.id],
1704
+ config
1705
+ })),
1706
+ dryRun: false
1707
+ });
1708
+ } else {
1709
+ console.log(pc6.bold(`
1300
1710
  ${succeeded.length}/${results.length} providers configured.`));
1711
+ }
1301
1712
  });
1302
1713
  }
1303
1714
 
1304
1715
  // src/commands/mcp/remove.ts
1305
1716
  import pc7 from "picocolors";
1306
1717
  function registerMcpRemove(parent) {
1307
- parent.command("remove").description("Remove MCP server from agent configs").argument("<name>", "MCP server name to remove").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Remove from global config").option("--all", "Remove from all detected agents").action(async (name, opts) => {
1718
+ parent.command("remove").description("Remove MCP server from agent configs").argument("<name>", "MCP server name to remove").option("-a, --agent <name>", "Target specific agent(s)", (v, prev) => [...prev, v], []).option("-g, --global", "Remove from global config").option("--all", "Remove from all detected agents").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (name, opts) => {
1719
+ const operation = "mcp.remove";
1720
+ const mvi = "standard";
1721
+ let format;
1722
+ try {
1723
+ format = resolveFormat({
1724
+ jsonFlag: opts.json ?? false,
1725
+ humanFlag: (opts.human ?? false) || isHuman(),
1726
+ projectDefault: "json"
1727
+ });
1728
+ } catch (error) {
1729
+ const message = error instanceof Error ? error.message : String(error);
1730
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1731
+ process.exit(1);
1732
+ }
1308
1733
  let providers;
1309
1734
  if (opts.all) {
1310
1735
  providers = getInstalledProviders();
@@ -1313,21 +1738,45 @@ function registerMcpRemove(parent) {
1313
1738
  } else {
1314
1739
  providers = getInstalledProviders();
1315
1740
  }
1741
+ if (providers.length === 0) {
1742
+ const message = "No target providers found.";
1743
+ if (format === "json") {
1744
+ emitJsonError(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
1745
+ } else {
1746
+ console.error(pc7.red(message));
1747
+ }
1748
+ process.exit(1);
1749
+ }
1316
1750
  const scope = opts.global ? "global" : "project";
1317
- let removed = 0;
1751
+ const removed = [];
1752
+ const notFound = [];
1318
1753
  for (const provider of providers) {
1319
1754
  const success = await removeMcpServer(provider, name, scope);
1320
1755
  if (success) {
1321
- console.log(` ${pc7.green("\u2713")} Removed from ${provider.toolName}`);
1322
- removed++;
1756
+ removed.push(provider.id);
1757
+ if (format === "human") {
1758
+ console.log(` ${pc7.green("\u2713")} Removed from ${provider.toolName}`);
1759
+ }
1760
+ } else {
1761
+ notFound.push(provider.id);
1323
1762
  }
1324
1763
  }
1325
- if (removed > 0) {
1764
+ if (removed.length > 0) {
1326
1765
  await removeMcpFromLock(name);
1327
- console.log(pc7.green(`
1328
- \u2713 Removed "${name}" from ${removed} provider(s).`));
1766
+ }
1767
+ if (format === "json") {
1768
+ outputSuccess(operation, mvi, {
1769
+ removed,
1770
+ providers: removed,
1771
+ notFound: notFound.length > 0 ? notFound : void 0
1772
+ });
1329
1773
  } else {
1330
- console.log(pc7.yellow(`Server "${name}" not found in any provider config.`));
1774
+ if (removed.length > 0) {
1775
+ console.log(pc7.green(`
1776
+ \u2713 Removed "${name}" from ${removed.length} provider(s).`));
1777
+ } else {
1778
+ console.log(pc7.yellow(`Server "${name}" not found in any provider config.`));
1779
+ }
1331
1780
  }
1332
1781
  });
1333
1782
  }
@@ -1335,20 +1784,51 @@ function registerMcpRemove(parent) {
1335
1784
  // src/commands/mcp/list.ts
1336
1785
  import pc8 from "picocolors";
1337
1786
  function registerMcpList(parent) {
1338
- parent.command("list").description("List configured MCP servers").option("-a, --agent <name>", "List for specific agent").option("-g, --global", "List global config").option("--json", "Output as JSON").action(async (opts) => {
1787
+ parent.command("list").description("List configured MCP servers").option("-a, --agent <name>", "List for specific agent").option("-g, --global", "List global config").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1788
+ const operation = "mcp.list";
1789
+ const mvi = "standard";
1790
+ let format;
1791
+ try {
1792
+ format = resolveFormat({
1793
+ jsonFlag: opts.json ?? false,
1794
+ humanFlag: (opts.human ?? false) || isHuman(),
1795
+ projectDefault: "json"
1796
+ });
1797
+ } catch (error) {
1798
+ const message = error instanceof Error ? error.message : String(error);
1799
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1800
+ process.exit(1);
1801
+ }
1339
1802
  const providers = opts.agent ? [getProvider(opts.agent)].filter((p) => p !== void 0) : getInstalledProviders();
1803
+ if (opts.agent && providers.length === 0) {
1804
+ const message = `Provider not found: ${opts.agent}`;
1805
+ if (format === "json") {
1806
+ emitJsonError(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
1807
+ agent: opts.agent
1808
+ });
1809
+ } else {
1810
+ console.error(pc8.red(message));
1811
+ }
1812
+ process.exit(1);
1813
+ }
1340
1814
  const allEntries = [];
1341
1815
  for (const provider of providers) {
1342
1816
  const scope = resolvePreferredConfigScope(provider, opts.global);
1343
1817
  const entries = await listMcpServers(provider, scope);
1344
- allEntries.push(...entries);
1345
- }
1346
- if (opts.json) {
1347
- console.log(JSON.stringify(allEntries.map((e) => ({
1348
- provider: e.providerId,
1349
- name: e.name,
1350
- config: e.config
1351
- })), null, 2));
1818
+ for (const entry of entries) {
1819
+ allEntries.push({
1820
+ name: entry.name,
1821
+ command: typeof entry.config.command === "string" ? entry.config.command : void 0,
1822
+ scope
1823
+ });
1824
+ }
1825
+ }
1826
+ if (format === "json") {
1827
+ outputSuccess(operation, mvi, {
1828
+ servers: allEntries,
1829
+ count: allEntries.length,
1830
+ scope: opts.global ? "global" : opts.agent ? `agent:${opts.agent}` : "project"
1831
+ });
1352
1832
  return;
1353
1833
  }
1354
1834
  if (allEntries.length === 0) {
@@ -1359,45 +1839,70 @@ function registerMcpList(parent) {
1359
1839
  ${allEntries.length} MCP server(s) configured:
1360
1840
  `));
1361
1841
  for (const entry of allEntries) {
1362
- console.log(` ${pc8.bold(entry.name.padEnd(25))} ${pc8.dim(entry.providerId)}`);
1842
+ const scopeIndicator = entry.scope === "global" ? pc8.dim("[G] ") : pc8.dim("[P] ");
1843
+ console.log(` ${scopeIndicator}${pc8.bold(entry.name.padEnd(25))} ${entry.command ? pc8.dim(entry.command) : ""}`);
1363
1844
  }
1364
1845
  console.log();
1846
+ console.log(pc8.dim("G = global config, P = project config"));
1847
+ console.log();
1365
1848
  });
1366
1849
  }
1367
1850
 
1368
1851
  // src/commands/mcp/detect.ts
1369
- import pc9 from "picocolors";
1370
1852
  import { existsSync as existsSync3 } from "fs";
1853
+ import pc9 from "picocolors";
1371
1854
  function registerMcpDetect(parent) {
1372
- parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON").action(async (opts) => {
1855
+ parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1856
+ const operation = "mcp.detect";
1857
+ const mvi = "standard";
1858
+ let format;
1859
+ try {
1860
+ format = resolveFormat({
1861
+ jsonFlag: opts.json ?? false,
1862
+ humanFlag: (opts.human ?? false) || isHuman(),
1863
+ projectDefault: "json"
1864
+ });
1865
+ } catch (error) {
1866
+ const message = error instanceof Error ? error.message : String(error);
1867
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
1868
+ process.exit(1);
1869
+ }
1373
1870
  const providers = getInstalledProviders();
1374
- const detected = [];
1871
+ const providersResult = [];
1872
+ let totalConfigs = 0;
1375
1873
  for (const provider of providers) {
1376
1874
  const globalPath = resolveConfigPath(provider, "global");
1377
1875
  const projectPath = resolveConfigPath(provider, "project");
1378
1876
  const globalEntries = await listMcpServers(provider, "global");
1379
1877
  const projectEntries = await listMcpServers(provider, "project");
1380
- detected.push({
1381
- provider: provider.id,
1382
- hasGlobalConfig: globalPath !== null && existsSync3(globalPath),
1383
- hasProjectConfig: projectPath !== null && existsSync3(projectPath),
1384
- globalServers: globalEntries.map((e) => e.name),
1385
- projectServers: projectEntries.map((e) => e.name)
1878
+ const configsFound = (globalPath && existsSync3(globalPath) ? 1 : 0) + (projectPath && existsSync3(projectPath) ? 1 : 0);
1879
+ totalConfigs += configsFound;
1880
+ const allServers = [...globalEntries.map((e) => e.name), ...projectEntries.map((e) => e.name)];
1881
+ providersResult.push({
1882
+ id: provider.id,
1883
+ configsFound,
1884
+ servers: allServers
1386
1885
  });
1387
1886
  }
1388
- if (opts.json) {
1389
- console.log(JSON.stringify(detected, null, 2));
1887
+ if (format === "json") {
1888
+ outputSuccess(operation, mvi, {
1889
+ providers: providersResult,
1890
+ totalConfigs
1891
+ });
1390
1892
  return;
1391
1893
  }
1392
1894
  console.log(pc9.bold(`
1393
- ${detected.length} provider(s) with MCP support:
1895
+ ${providers.length} provider(s) with MCP support:
1394
1896
  `));
1395
- for (const d of detected) {
1396
- const globalIcon = d.hasGlobalConfig ? pc9.green("G") : pc9.dim("-");
1397
- const projectIcon = d.hasProjectConfig ? pc9.green("P") : pc9.dim("-");
1398
- const servers = [...d.globalServers, ...d.projectServers];
1399
- const serverList = servers.length > 0 ? pc9.dim(servers.join(", ")) : pc9.dim("no servers");
1400
- console.log(` [${globalIcon}${projectIcon}] ${pc9.bold(d.provider.padEnd(20))} ${serverList}`);
1897
+ for (const provider of providersResult) {
1898
+ const globalPath = resolveConfigPath(providers.find((p) => p.id === provider.id), "global");
1899
+ const projectPath = resolveConfigPath(providers.find((p) => p.id === provider.id), "project");
1900
+ const hasGlobal = globalPath && existsSync3(globalPath);
1901
+ const hasProject = projectPath && existsSync3(projectPath);
1902
+ const globalIcon = hasGlobal ? pc9.green("G") : pc9.dim("-");
1903
+ const projectIcon = hasProject ? pc9.green("P") : pc9.dim("-");
1904
+ const serverList = provider.servers.length > 0 ? pc9.dim(provider.servers.join(", ")) : pc9.dim("no servers");
1905
+ console.log(` [${globalIcon}${projectIcon}] ${pc9.bold(provider.id.padEnd(20))} ${serverList}`);
1401
1906
  }
1402
1907
  console.log(pc9.dim("\nG = global config, P = project config"));
1403
1908
  console.log();
@@ -1414,13 +1919,40 @@ function registerMcpCommands(program2) {
1414
1919
  }
1415
1920
 
1416
1921
  // src/commands/providers.ts
1922
+ import { randomUUID as randomUUID3 } from "crypto";
1923
+ import { resolveOutputFormat as resolveOutputFormat2 } from "@cleocode/lafs-protocol";
1417
1924
  import pc10 from "picocolors";
1418
1925
  function registerProvidersCommand(program2) {
1419
1926
  const providers = program2.command("providers").description("Manage AI agent providers");
1420
- providers.command("list").description("List all supported providers").option("--json", "Output as JSON").option("--tier <tier>", "Filter by priority tier (high, medium, low)").action(async (opts) => {
1927
+ providers.command("list").description("List all supported providers").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").option("--tier <tier>", "Filter by priority tier (high, medium, low)").action(async (opts) => {
1928
+ const operation = "providers.list";
1929
+ const mvi = "standard";
1930
+ let format;
1931
+ try {
1932
+ format = resolveOutputFormat2({
1933
+ jsonFlag: opts.json ?? false,
1934
+ humanFlag: (opts.human ?? false) || isHuman(),
1935
+ projectDefault: "json"
1936
+ }).format;
1937
+ } catch (error) {
1938
+ const message = error instanceof Error ? error.message : String(error);
1939
+ emitJsonError2(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
1940
+ process.exit(1);
1941
+ }
1421
1942
  const all = opts.tier ? getProvidersByPriority(opts.tier) : getAllProviders();
1422
- if (opts.json) {
1423
- console.log(JSON.stringify(all, null, 2));
1943
+ if (format === "json") {
1944
+ const envelope = buildEnvelope4(
1945
+ operation,
1946
+ mvi,
1947
+ {
1948
+ providers: all,
1949
+ count: all.length,
1950
+ version: getRegistryVersion(),
1951
+ tier: opts.tier || null
1952
+ },
1953
+ null
1954
+ );
1955
+ console.log(JSON.stringify(envelope, null, 2));
1424
1956
  return;
1425
1957
  }
1426
1958
  console.log(pc10.bold(`
@@ -1440,16 +1972,43 @@ CAMP Provider Registry v${getRegistryVersion()}`));
1440
1972
  console.log();
1441
1973
  }
1442
1974
  });
1443
- providers.command("detect").description("Auto-detect installed providers").option("--json", "Output as JSON").option("--project", "Include project-level detection").action(async (opts) => {
1975
+ providers.command("detect").description("Auto-detect installed providers").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").option("--project", "Include project-level detection").action(async (opts) => {
1976
+ const operation = "providers.detect";
1977
+ const mvi = "standard";
1978
+ let format;
1979
+ try {
1980
+ format = resolveOutputFormat2({
1981
+ jsonFlag: opts.json ?? false,
1982
+ humanFlag: (opts.human ?? false) || isHuman(),
1983
+ projectDefault: "json"
1984
+ }).format;
1985
+ } catch (error) {
1986
+ const message = error instanceof Error ? error.message : String(error);
1987
+ emitJsonError2(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
1988
+ process.exit(1);
1989
+ }
1444
1990
  const results = opts.project ? detectProjectProviders(process.cwd()) : detectAllProviders();
1445
1991
  const installed = results.filter((r) => r.installed);
1446
- if (opts.json) {
1447
- console.log(JSON.stringify(installed.map((r) => ({
1448
- id: r.provider.id,
1449
- toolName: r.provider.toolName,
1450
- methods: r.methods,
1451
- projectDetected: r.projectDetected
1452
- })), null, 2));
1992
+ if (format === "json") {
1993
+ const envelope = buildEnvelope4(
1994
+ operation,
1995
+ mvi,
1996
+ {
1997
+ installed: installed.map((r) => ({
1998
+ id: r.provider.id,
1999
+ toolName: r.provider.toolName,
2000
+ methods: r.methods,
2001
+ projectDetected: r.projectDetected
2002
+ })),
2003
+ notInstalled: results.filter((r) => !r.installed).map((r) => r.provider.id),
2004
+ count: {
2005
+ installed: installed.length,
2006
+ total: results.length
2007
+ }
2008
+ },
2009
+ null
2010
+ );
2011
+ console.log(JSON.stringify(envelope, null, 2));
1453
2012
  return;
1454
2013
  }
1455
2014
  console.log(pc10.bold(`
@@ -1467,14 +2026,43 @@ Detected ${installed.length} installed providers:
1467
2026
  }
1468
2027
  console.log();
1469
2028
  });
1470
- providers.command("show").description("Show provider details").argument("<id>", "Provider ID or alias").option("--json", "Output as JSON").action(async (id, opts) => {
2029
+ providers.command("show").description("Show provider details").argument("<id>", "Provider ID or alias").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (id, opts) => {
2030
+ const operation = "providers.show";
2031
+ const mvi = "standard";
2032
+ let format;
2033
+ try {
2034
+ format = resolveOutputFormat2({
2035
+ jsonFlag: opts.json ?? false,
2036
+ humanFlag: (opts.human ?? false) || isHuman(),
2037
+ projectDefault: "json"
2038
+ }).format;
2039
+ } catch (error) {
2040
+ const message = error instanceof Error ? error.message : String(error);
2041
+ emitJsonError2(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
2042
+ process.exit(1);
2043
+ }
1471
2044
  const provider = getProvider(id);
1472
2045
  if (!provider) {
1473
- console.error(pc10.red(`Provider not found: ${id}`));
2046
+ const message = `Provider not found: ${id}`;
2047
+ if (format === "json") {
2048
+ emitJsonError2(operation, mvi, "E_PROVIDER_NOT_FOUND", message, "NOT_FOUND", {
2049
+ id
2050
+ });
2051
+ } else {
2052
+ console.error(pc10.red(message));
2053
+ }
1474
2054
  process.exit(1);
1475
2055
  }
1476
- if (opts.json) {
1477
- console.log(JSON.stringify(provider, null, 2));
2056
+ if (format === "json") {
2057
+ const envelope = buildEnvelope4(
2058
+ operation,
2059
+ mvi,
2060
+ {
2061
+ provider
2062
+ },
2063
+ null
2064
+ );
2065
+ console.log(JSON.stringify(envelope, null, 2));
1478
2066
  return;
1479
2067
  }
1480
2068
  console.log(pc10.bold(`
@@ -1505,6 +2093,37 @@ ${provider.toolName}`));
1505
2093
  console.log();
1506
2094
  });
1507
2095
  }
2096
+ function buildEnvelope4(operation, mvi, result, error) {
2097
+ return {
2098
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
2099
+ _meta: {
2100
+ specVersion: "1.0.0",
2101
+ schemaVersion: "1.0.0",
2102
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2103
+ operation,
2104
+ requestId: randomUUID3(),
2105
+ transport: "cli",
2106
+ strict: true,
2107
+ mvi,
2108
+ contextVersion: 0
2109
+ },
2110
+ success: error === null,
2111
+ result,
2112
+ error,
2113
+ page: null
2114
+ };
2115
+ }
2116
+ function emitJsonError2(operation, mvi, code, message, category, details = {}) {
2117
+ const envelope = buildEnvelope4(operation, mvi, null, {
2118
+ code,
2119
+ message,
2120
+ category,
2121
+ retryable: false,
2122
+ retryAfterMs: null,
2123
+ details
2124
+ });
2125
+ console.error(JSON.stringify(envelope, null, 2));
2126
+ }
1508
2127
 
1509
2128
  // src/commands/skills/install.ts
1510
2129
  import { existsSync as existsSync4 } from "fs";
@@ -1564,7 +2183,21 @@ async function cloneGitLabRepo(owner, repo, ref, subPath) {
1564
2183
 
1565
2184
  // src/commands/skills/install.ts
1566
2185
  function registerSkillsInstall(parent) {
1567
- 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) => {
2186
+ 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)").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (source, opts) => {
2187
+ const operation = "skills.install";
2188
+ const mvi = "standard";
2189
+ let format;
2190
+ try {
2191
+ format = resolveFormat({
2192
+ jsonFlag: opts.json ?? false,
2193
+ humanFlag: opts.human ?? false,
2194
+ projectDefault: "json"
2195
+ });
2196
+ } catch (error) {
2197
+ const message = error instanceof Error ? error.message : String(error);
2198
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
2199
+ process.exit(1);
2200
+ }
1568
2201
  let providers;
1569
2202
  if (opts.all) {
1570
2203
  providers = getInstalledProviders();
@@ -1574,119 +2207,50 @@ function registerSkillsInstall(parent) {
1574
2207
  providers = getInstalledProviders();
1575
2208
  }
1576
2209
  if (providers.length === 0) {
1577
- console.error(pc11.red("No target providers found. Use --agent or --all."));
2210
+ const message = "No target providers found. Use --agent or --all.";
2211
+ if (format === "json") {
2212
+ emitError2(operation, mvi, ErrorCodes.PROVIDER_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
2213
+ }
2214
+ console.error(pc11.red(message));
1578
2215
  process.exit(1);
1579
2216
  }
1580
2217
  if (opts.profile) {
1581
- if (!isCatalogAvailable()) {
1582
- console.error(pc11.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
1583
- process.exit(1);
1584
- }
1585
- const profileSkills = resolveProfile(opts.profile);
1586
- if (profileSkills.length === 0) {
1587
- const available = listProfiles();
1588
- console.error(pc11.red(`Profile not found: ${opts.profile}`));
1589
- if (available.length > 0) {
1590
- console.log(pc11.dim("Available profiles: " + available.join(", ")));
1591
- }
1592
- process.exit(1);
1593
- }
1594
- console.log(`Installing profile ${pc11.bold(opts.profile)} (${profileSkills.length} skill(s))...`);
1595
- console.log(pc11.dim(`Target: ${providers.length} provider(s)`));
1596
- let installed = 0;
1597
- let failed = 0;
1598
- for (const name of profileSkills) {
1599
- const skillDir = getSkillDir(name);
1600
- try {
1601
- const result = await installSkill(
1602
- skillDir,
1603
- name,
1604
- providers,
1605
- opts.global ?? false
1606
- );
1607
- if (result.success) {
1608
- console.log(pc11.green(` + ${name}`));
1609
- await recordSkillInstall(
1610
- name,
1611
- `@cleocode/ct-skills:${name}`,
1612
- `@cleocode/ct-skills:${name}`,
1613
- "package",
1614
- result.linkedAgents,
1615
- result.canonicalPath,
1616
- true
1617
- );
1618
- installed++;
1619
- } else {
1620
- console.log(pc11.yellow(` ! ${name}: ${result.errors.join(", ")}`));
1621
- failed++;
1622
- }
1623
- } catch (err) {
1624
- console.log(pc11.red(` x ${name}: ${err instanceof Error ? err.message : String(err)}`));
1625
- failed++;
1626
- }
1627
- }
1628
- console.log(`
1629
- ${pc11.green(`${installed} installed`)}, ${failed > 0 ? pc11.yellow(`${failed} failed`) : "0 failed"}`);
2218
+ await handleProfileInstall(opts.profile, providers, opts.global ?? false, format, operation, mvi);
1630
2219
  return;
1631
2220
  }
1632
2221
  if (!source) {
1633
- console.error(pc11.red("Missing required argument: source"));
2222
+ const message = "Missing required argument: source";
2223
+ if (format === "json") {
2224
+ emitError2(operation, mvi, ErrorCodes.INVALID_INPUT, message, ErrorCategories.VALIDATION);
2225
+ }
2226
+ console.error(pc11.red(message));
1634
2227
  console.log(pc11.dim("Usage: caamp skills install <source> or caamp skills install --profile <name>"));
1635
2228
  process.exit(1);
1636
2229
  }
1637
- console.log(pc11.dim(`Installing to ${providers.length} provider(s)...`));
2230
+ if (format === "human") {
2231
+ console.log(pc11.dim(`Installing to ${providers.length} provider(s)...`));
2232
+ }
1638
2233
  let localPath;
1639
2234
  let cleanup;
1640
2235
  let skillName;
1641
2236
  let sourceValue;
1642
2237
  let sourceType;
1643
2238
  if (isMarketplaceScoped(source)) {
1644
- console.log(pc11.dim(`Searching marketplace for ${source}...`));
1645
- const client = new MarketplaceClient();
1646
- let skill;
1647
- try {
1648
- skill = await client.getSkill(source);
1649
- } catch (error) {
1650
- console.error(pc11.red(`Marketplace lookup failed: ${formatNetworkError(error)}`));
1651
- process.exit(1);
1652
- }
1653
- if (!skill) {
1654
- console.error(pc11.red(`Skill not found: ${source}`));
1655
- process.exit(1);
1656
- }
1657
- console.log(` Found: ${pc11.bold(skill.name)} by ${skill.author} (${pc11.dim(skill.repoFullName)})`);
1658
- const parsed = parseSource(skill.githubUrl);
1659
- if (parsed.type !== "github" || !parsed.owner || !parsed.repo) {
1660
- console.error(pc11.red("Could not resolve GitHub source"));
1661
- process.exit(1);
1662
- }
1663
- try {
1664
- const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
1665
- let cloneError;
1666
- let cloned = false;
1667
- for (const subPath of subPathCandidates) {
1668
- try {
1669
- const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
1670
- if (subPath && !existsSync4(result.localPath)) {
1671
- await result.cleanup();
1672
- continue;
1673
- }
1674
- localPath = result.localPath;
1675
- cleanup = result.cleanup;
1676
- cloned = true;
1677
- break;
1678
- } catch (error) {
1679
- cloneError = error;
1680
- }
1681
- }
1682
- if (!cloned) {
1683
- throw cloneError ?? new Error("Unable to resolve skill path from marketplace metadata");
1684
- }
1685
- skillName = skill.name;
1686
- sourceValue = skill.githubUrl;
1687
- sourceType = parsed.type;
1688
- } catch (error) {
1689
- console.error(pc11.red(`Failed to fetch source repository: ${formatNetworkError(error)}`));
2239
+ const sourceResult = await handleMarketplaceSource(
2240
+ source,
2241
+ providers,
2242
+ opts.global ?? false,
2243
+ format,
2244
+ operation,
2245
+ mvi
2246
+ );
2247
+ if (sourceResult.success) {
2248
+ localPath = sourceResult.localPath;
2249
+ cleanup = sourceResult.cleanup;
2250
+ skillName = sourceResult.skillName;
2251
+ sourceValue = sourceResult.sourceValue;
2252
+ sourceType = sourceResult.sourceType;
2253
+ } else {
1690
2254
  process.exit(1);
1691
2255
  }
1692
2256
  } else {
@@ -1700,7 +2264,11 @@ ${pc11.green(`${installed} installed`)}, ${failed > 0 ? pc11.yellow(`${failed} f
1700
2264
  localPath = result.localPath;
1701
2265
  cleanup = result.cleanup;
1702
2266
  } catch (error) {
1703
- console.error(pc11.red(`Failed to clone GitHub repository: ${formatNetworkError(error)}`));
2267
+ const message = `Failed to clone GitHub repository: ${formatNetworkError(error)}`;
2268
+ if (format === "json") {
2269
+ emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
2270
+ }
2271
+ console.error(pc11.red(message));
1704
2272
  process.exit(1);
1705
2273
  }
1706
2274
  } else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
@@ -1709,7 +2277,11 @@ ${pc11.green(`${installed} installed`)}, ${failed > 0 ? pc11.yellow(`${failed} f
1709
2277
  localPath = result.localPath;
1710
2278
  cleanup = result.cleanup;
1711
2279
  } catch (error) {
1712
- console.error(pc11.red(`Failed to clone GitLab repository: ${formatNetworkError(error)}`));
2280
+ const message = `Failed to clone GitLab repository: ${formatNetworkError(error)}`;
2281
+ if (format === "json") {
2282
+ emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
2283
+ }
2284
+ console.error(pc11.red(message));
1713
2285
  process.exit(1);
1714
2286
  }
1715
2287
  } else if (parsed.type === "local") {
@@ -1720,7 +2292,11 @@ ${pc11.green(`${installed} installed`)}, ${failed > 0 ? pc11.yellow(`${failed} f
1720
2292
  }
1721
2293
  } else if (parsed.type === "package") {
1722
2294
  if (!isCatalogAvailable()) {
1723
- console.error(pc11.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
2295
+ const message = "@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills";
2296
+ if (format === "json") {
2297
+ emitJsonError(operation, mvi, ErrorCodes.INVALID_INPUT, message, ErrorCategories.VALIDATION);
2298
+ }
2299
+ console.error(pc11.red(message));
1724
2300
  process.exit(1);
1725
2301
  }
1726
2302
  const catalogSkill = getSkill(parsed.inferredName);
@@ -1729,64 +2305,351 @@ ${pc11.green(`${installed} installed`)}, ${failed > 0 ? pc11.yellow(`${failed} f
1729
2305
  skillName = catalogSkill.name;
1730
2306
  sourceValue = `@cleocode/ct-skills:${catalogSkill.name}`;
1731
2307
  sourceType = "package";
1732
- console.log(` Found in catalog: ${pc11.bold(catalogSkill.name)} v${catalogSkill.version} (${pc11.dim(catalogSkill.category)})`);
2308
+ if (format === "human") {
2309
+ console.log(` Found in catalog: ${pc11.bold(catalogSkill.name)} v${catalogSkill.version} (${pc11.dim(catalogSkill.category)})`);
2310
+ }
1733
2311
  } else {
1734
- console.error(pc11.red(`Skill not found in catalog: ${parsed.inferredName}`));
2312
+ const message = `Skill not found in catalog: ${parsed.inferredName}`;
2313
+ if (format === "json") {
2314
+ emitJsonError(operation, mvi, ErrorCodes.SKILL_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
2315
+ availableSkills: listSkills()
2316
+ });
2317
+ }
2318
+ console.error(pc11.red(message));
1735
2319
  console.log(pc11.dim("Available skills: " + listSkills().join(", ")));
1736
2320
  process.exit(1);
1737
2321
  }
1738
2322
  } else {
1739
- console.error(pc11.red(`Unsupported source type: ${parsed.type}`));
2323
+ const message = `Unsupported source type: ${parsed.type}`;
2324
+ if (format === "json") {
2325
+ emitJsonError(operation, mvi, ErrorCodes.INVALID_FORMAT, message, ErrorCategories.VALIDATION);
2326
+ }
2327
+ console.error(pc11.red(message));
1740
2328
  process.exit(1);
1741
2329
  }
1742
2330
  }
1743
2331
  try {
1744
- if (!localPath) {
1745
- throw new Error("No local skill path resolved for installation");
1746
- }
2332
+ if (!localPath) {
2333
+ const message = "No local skill path resolved for installation";
2334
+ if (format === "json") {
2335
+ emitJsonError(operation, mvi, ErrorCodes.INTERNAL_ERROR, message, ErrorCategories.INTERNAL);
2336
+ }
2337
+ console.error(pc11.red(message));
2338
+ process.exit(1);
2339
+ }
2340
+ const result = await installSkill(
2341
+ localPath,
2342
+ skillName,
2343
+ providers,
2344
+ opts.global ?? false
2345
+ );
2346
+ if (result.success) {
2347
+ const isGlobal = sourceType === "package" ? true : opts.global ?? false;
2348
+ await recordSkillInstall(
2349
+ skillName,
2350
+ sourceValue,
2351
+ sourceValue,
2352
+ sourceType,
2353
+ result.linkedAgents,
2354
+ result.canonicalPath,
2355
+ isGlobal
2356
+ );
2357
+ const installedItem = {
2358
+ name: skillName,
2359
+ scopedName: sourceValue,
2360
+ canonicalPath: result.canonicalPath,
2361
+ providers: result.linkedAgents
2362
+ };
2363
+ const summary = {
2364
+ installed: [installedItem],
2365
+ failed: [],
2366
+ count: {
2367
+ installed: 1,
2368
+ failed: 0,
2369
+ total: 1
2370
+ }
2371
+ };
2372
+ if (format === "json") {
2373
+ outputSuccess(operation, mvi, summary);
2374
+ } else {
2375
+ console.log(pc11.green(`
2376
+ \u2713 Installed ${pc11.bold(skillName)}`));
2377
+ console.log(` Canonical: ${pc11.dim(result.canonicalPath)}`);
2378
+ console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
2379
+ if (result.errors.length > 0) {
2380
+ console.log(pc11.yellow("\nWarnings:"));
2381
+ for (const err of result.errors) {
2382
+ console.log(` ${pc11.yellow("!")} ${err}`);
2383
+ }
2384
+ }
2385
+ }
2386
+ } else {
2387
+ const summary = {
2388
+ installed: [],
2389
+ failed: [{
2390
+ name: skillName,
2391
+ error: result.errors.join(", ")
2392
+ }],
2393
+ count: {
2394
+ installed: 0,
2395
+ failed: 1,
2396
+ total: 1
2397
+ }
2398
+ };
2399
+ if (format === "json") {
2400
+ const envelope = buildEnvelope(operation, mvi, summary, {
2401
+ code: ErrorCodes.INSTALL_FAILED,
2402
+ message: result.errors.join(", "),
2403
+ category: ErrorCategories.INTERNAL,
2404
+ retryable: false,
2405
+ retryAfterMs: null,
2406
+ details: { skillName, sourceValue }
2407
+ });
2408
+ console.error(JSON.stringify(envelope, null, 2));
2409
+ } else {
2410
+ console.log(pc11.yellow(`
2411
+ \u2717 Failed to install ${pc11.bold(skillName)}`));
2412
+ console.log(pc11.yellow("Errors:"));
2413
+ for (const err of result.errors) {
2414
+ console.log(` ${pc11.yellow("!")} ${err}`);
2415
+ }
2416
+ }
2417
+ process.exit(1);
2418
+ }
2419
+ } finally {
2420
+ if (cleanup) await cleanup();
2421
+ }
2422
+ });
2423
+ }
2424
+ async function handleProfileInstall(profileName, providers, isGlobal, format, operation, mvi) {
2425
+ if (!isCatalogAvailable()) {
2426
+ const message = "@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills";
2427
+ if (format === "json") {
2428
+ emitError2(operation, mvi, ErrorCodes.INVALID_INPUT, message, ErrorCategories.VALIDATION);
2429
+ }
2430
+ console.error(pc11.red(message));
2431
+ process.exit(1);
2432
+ }
2433
+ const profileSkills = resolveProfile(profileName);
2434
+ if (profileSkills.length === 0) {
2435
+ const message = `Profile not found: ${profileName}`;
2436
+ if (format === "json") {
2437
+ emitJsonError(operation, mvi, ErrorCodes.SKILL_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
2438
+ availableProfiles: listProfiles()
2439
+ });
2440
+ }
2441
+ console.error(pc11.red(message));
2442
+ const available = listProfiles();
2443
+ if (available.length > 0) {
2444
+ console.log(pc11.dim("Available profiles: " + available.join(", ")));
2445
+ }
2446
+ process.exit(1);
2447
+ }
2448
+ if (format === "human") {
2449
+ console.log(`Installing profile ${pc11.bold(profileName)} (${profileSkills.length} skill(s))...`);
2450
+ console.log(pc11.dim(`Target: ${providers.length} provider(s)`));
2451
+ }
2452
+ const installed = [];
2453
+ const failed = [];
2454
+ for (const name of profileSkills) {
2455
+ const skillDir = getSkillDir(name);
2456
+ try {
1747
2457
  const result = await installSkill(
1748
- localPath,
1749
- skillName,
2458
+ skillDir,
2459
+ name,
1750
2460
  providers,
1751
- opts.global ?? false
2461
+ isGlobal
1752
2462
  );
1753
2463
  if (result.success) {
1754
- console.log(pc11.green(`
1755
- \u2713 Installed ${pc11.bold(skillName)}`));
1756
- console.log(` Canonical: ${pc11.dim(result.canonicalPath)}`);
1757
- console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
1758
- const isGlobal = sourceType === "package" ? true : opts.global ?? false;
2464
+ if (format === "human") {
2465
+ console.log(pc11.green(` + ${name}`));
2466
+ }
1759
2467
  await recordSkillInstall(
1760
- skillName,
1761
- sourceValue,
1762
- sourceValue,
1763
- sourceType,
2468
+ name,
2469
+ `@cleocode/ct-skills:${name}`,
2470
+ `@cleocode/ct-skills:${name}`,
2471
+ "package",
1764
2472
  result.linkedAgents,
1765
2473
  result.canonicalPath,
1766
- isGlobal
2474
+ true
1767
2475
  );
2476
+ installed.push({
2477
+ name,
2478
+ scopedName: `@cleocode/ct-skills:${name}`,
2479
+ canonicalPath: result.canonicalPath,
2480
+ providers: result.linkedAgents
2481
+ });
2482
+ } else {
2483
+ if (format === "human") {
2484
+ console.log(pc11.yellow(` ! ${name}: ${result.errors.join(", ")}`));
2485
+ }
2486
+ failed.push({
2487
+ name,
2488
+ error: result.errors.join(", ")
2489
+ });
1768
2490
  }
1769
- if (result.errors.length > 0) {
1770
- console.log(pc11.yellow("\nWarnings:"));
1771
- for (const err of result.errors) {
1772
- console.log(` ${pc11.yellow("!")} ${err}`);
2491
+ } catch (err) {
2492
+ const errorMsg = err instanceof Error ? err.message : String(err);
2493
+ if (format === "human") {
2494
+ console.log(pc11.red(` x ${name}: ${errorMsg}`));
2495
+ }
2496
+ failed.push({
2497
+ name,
2498
+ error: errorMsg
2499
+ });
2500
+ }
2501
+ }
2502
+ const summary = {
2503
+ installed,
2504
+ failed,
2505
+ count: {
2506
+ installed: installed.length,
2507
+ failed: failed.length,
2508
+ total: profileSkills.length
2509
+ }
2510
+ };
2511
+ if (format === "json") {
2512
+ if (failed.length > 0) {
2513
+ const envelope = buildEnvelope(operation, mvi, summary, {
2514
+ code: ErrorCodes.INSTALL_FAILED,
2515
+ message: `${failed.length} skill(s) failed to install`,
2516
+ category: ErrorCategories.INTERNAL,
2517
+ retryable: false,
2518
+ retryAfterMs: null,
2519
+ details: { failed: failed.map((f) => f.name) }
2520
+ });
2521
+ console.error(JSON.stringify(envelope, null, 2));
2522
+ process.exit(1);
2523
+ } else {
2524
+ outputSuccess(operation, mvi, summary);
2525
+ }
2526
+ } else {
2527
+ console.log(`
2528
+ ${pc11.green(`${installed.length} installed`)}, ${failed.length > 0 ? pc11.yellow(`${failed.length} failed`) : "0 failed"}`);
2529
+ if (failed.length > 0) {
2530
+ process.exit(1);
2531
+ }
2532
+ }
2533
+ }
2534
+ async function handleMarketplaceSource(source, _providers, _isGlobal, format, operation, mvi) {
2535
+ if (format === "human") {
2536
+ console.log(pc11.dim(`Searching marketplace for ${source}...`));
2537
+ }
2538
+ const client = new MarketplaceClient();
2539
+ let skill;
2540
+ try {
2541
+ skill = await client.getSkill(source);
2542
+ } catch (error) {
2543
+ const message = `Marketplace lookup failed: ${formatNetworkError(error)}`;
2544
+ if (format === "json") {
2545
+ emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
2546
+ }
2547
+ console.error(pc11.red(message));
2548
+ return { success: false };
2549
+ }
2550
+ if (!skill) {
2551
+ const message = `Skill not found: ${source}`;
2552
+ if (format === "json") {
2553
+ emitJsonError(operation, mvi, ErrorCodes.SKILL_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
2554
+ }
2555
+ console.error(pc11.red(message));
2556
+ return { success: false };
2557
+ }
2558
+ if (format === "human") {
2559
+ console.log(` Found: ${pc11.bold(skill.name)} by ${skill.author} (${pc11.dim(skill.repoFullName)})`);
2560
+ }
2561
+ const parsed = parseSource(skill.githubUrl);
2562
+ if (parsed.type !== "github" || !parsed.owner || !parsed.repo) {
2563
+ const message = "Could not resolve GitHub source";
2564
+ if (format === "json") {
2565
+ emitJsonError(operation, mvi, ErrorCodes.INVALID_FORMAT, message, ErrorCategories.VALIDATION);
2566
+ }
2567
+ console.error(pc11.red(message));
2568
+ return { success: false };
2569
+ }
2570
+ try {
2571
+ const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
2572
+ let cloneError;
2573
+ let cloned = false;
2574
+ let localPath;
2575
+ let cleanup;
2576
+ for (const subPath of subPathCandidates) {
2577
+ try {
2578
+ const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
2579
+ if (subPath && !existsSync4(result.localPath)) {
2580
+ await result.cleanup();
2581
+ continue;
1773
2582
  }
2583
+ localPath = result.localPath;
2584
+ cleanup = result.cleanup;
2585
+ cloned = true;
2586
+ break;
2587
+ } catch (error) {
2588
+ cloneError = error;
1774
2589
  }
1775
- } finally {
1776
- if (cleanup) await cleanup();
1777
2590
  }
1778
- });
2591
+ if (!cloned) {
2592
+ throw cloneError ?? new Error("Unable to resolve skill path from marketplace metadata");
2593
+ }
2594
+ return {
2595
+ success: true,
2596
+ localPath,
2597
+ cleanup,
2598
+ skillName: skill.name,
2599
+ sourceValue: skill.githubUrl,
2600
+ sourceType: parsed.type
2601
+ };
2602
+ } catch (error) {
2603
+ const message = `Failed to fetch source repository: ${formatNetworkError(error)}`;
2604
+ if (format === "json") {
2605
+ emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
2606
+ }
2607
+ console.error(pc11.red(message));
2608
+ return { success: false };
2609
+ }
1779
2610
  }
1780
2611
 
1781
2612
  // src/commands/skills/remove.ts
1782
2613
  import pc12 from "picocolors";
1783
2614
  function registerSkillsRemove(parent) {
1784
- parent.command("remove").description("Remove installed skill(s)").argument("[name]", "Skill name to remove").option("-g, --global", "Remove from global scope").option("-y, --yes", "Skip confirmation").action(async (name, opts) => {
2615
+ parent.command("remove").description("Remove installed skill(s)").argument("[name]", "Skill name to remove").option("-g, --global", "Remove from global scope").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (name, opts) => {
2616
+ const operation = "skills.remove";
2617
+ const mvi = "standard";
2618
+ let format;
2619
+ try {
2620
+ format = resolveFormat({
2621
+ jsonFlag: opts.json ?? false,
2622
+ humanFlag: (opts.human ?? false) || isHuman(),
2623
+ projectDefault: "json"
2624
+ });
2625
+ } catch (error) {
2626
+ const message = error instanceof Error ? error.message : String(error);
2627
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
2628
+ process.exit(1);
2629
+ }
1785
2630
  const providers = getInstalledProviders();
1786
2631
  if (name) {
1787
2632
  const result = await removeSkill(name, providers, opts.global ?? false);
1788
- if (result.removed.length > 0) {
1789
- console.log(pc12.green(`\u2713 Removed ${pc12.bold(name)} from: ${result.removed.join(", ")}`));
2633
+ const removed = result.removed;
2634
+ const count = {
2635
+ removed: removed.length,
2636
+ total: providers.length
2637
+ };
2638
+ if (format === "json") {
2639
+ if (removed.length > 0) {
2640
+ await removeSkillFromLock(name);
2641
+ }
2642
+ const errors = result.errors.length > 0 ? result.errors.map((err) => ({ message: err })) : void 0;
2643
+ outputSuccess(operation, mvi, {
2644
+ removed,
2645
+ providers: providers.map((p) => p.id),
2646
+ count,
2647
+ ...errors && { errors }
2648
+ });
2649
+ return;
2650
+ }
2651
+ if (removed.length > 0) {
2652
+ console.log(pc12.green(`\u2713 Removed ${pc12.bold(name)} from: ${removed.join(", ")}`));
1790
2653
  await removeSkillFromLock(name);
1791
2654
  } else {
1792
2655
  console.log(pc12.yellow(`Skill ${name} not found in any provider.`));
@@ -1799,7 +2662,24 @@ function registerSkillsRemove(parent) {
1799
2662
  } else {
1800
2663
  const skills = await listCanonicalSkills();
1801
2664
  if (skills.length === 0) {
1802
- console.log(pc12.dim("No skills installed."));
2665
+ if (format === "json") {
2666
+ outputSuccess(operation, mvi, {
2667
+ removed: [],
2668
+ providers: [],
2669
+ count: { removed: 0, total: 0 }
2670
+ });
2671
+ } else {
2672
+ console.log(pc12.dim("No skills installed."));
2673
+ }
2674
+ return;
2675
+ }
2676
+ if (format === "json") {
2677
+ outputSuccess(operation, mvi, {
2678
+ removed: [],
2679
+ providers: [],
2680
+ count: { removed: 0, total: 0 },
2681
+ available: skills
2682
+ });
1803
2683
  return;
1804
2684
  }
1805
2685
  console.log(pc12.bold("Installed skills:"));
@@ -1812,23 +2692,23 @@ function registerSkillsRemove(parent) {
1812
2692
  }
1813
2693
 
1814
2694
  // src/commands/skills/list.ts
1815
- import { randomUUID as randomUUID2 } from "crypto";
1816
- import { resolveOutputFormat } from "@cleocode/lafs-protocol";
2695
+ import { randomUUID as randomUUID4 } from "crypto";
2696
+ import { resolveOutputFormat as resolveOutputFormat3 } from "@cleocode/lafs-protocol";
1817
2697
  import pc13 from "picocolors";
1818
2698
  function registerSkillsList(parent) {
1819
2699
  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 (default)").option("--human", "Output in human-readable format").action(async (opts) => {
1820
2700
  const operation = "skills.list";
1821
- const mvi = true;
2701
+ const mvi = "standard";
1822
2702
  let format;
1823
2703
  try {
1824
- format = resolveOutputFormat({
2704
+ format = resolveOutputFormat3({
1825
2705
  jsonFlag: opts.json ?? false,
1826
2706
  humanFlag: (opts.human ?? false) || isHuman(),
1827
2707
  projectDefault: "json"
1828
2708
  }).format;
1829
2709
  } catch (error) {
1830
2710
  const message = error instanceof Error ? error.message : String(error);
1831
- emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
2711
+ emitJsonError3(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
1832
2712
  process.exit(1);
1833
2713
  }
1834
2714
  let dirs = [];
@@ -1837,7 +2717,7 @@ function registerSkillsList(parent) {
1837
2717
  if (!provider) {
1838
2718
  const message = `Provider not found: ${opts.agent}`;
1839
2719
  if (format === "json") {
1840
- emitJsonError(operation, mvi, "E_PROVIDER_NOT_FOUND", message, "NOT_FOUND", {
2720
+ emitJsonError3(operation, mvi, "E_PROVIDER_NOT_FOUND", message, "NOT_FOUND", {
1841
2721
  agent: opts.agent
1842
2722
  });
1843
2723
  } else {
@@ -1855,7 +2735,7 @@ function registerSkillsList(parent) {
1855
2735
  }
1856
2736
  const skills = await discoverSkillsMulti(dirs);
1857
2737
  if (format === "json") {
1858
- const envelope = buildEnvelope(
2738
+ const envelope = buildEnvelope5(
1859
2739
  operation,
1860
2740
  mvi,
1861
2741
  {
@@ -1884,7 +2764,7 @@ Install with: caamp skills install <name>`));
1884
2764
  console.log(pc13.dim(`Remove with: caamp skills remove <name>`));
1885
2765
  });
1886
2766
  }
1887
- function buildEnvelope(operation, mvi, result, error) {
2767
+ function buildEnvelope5(operation, mvi, result, error) {
1888
2768
  return {
1889
2769
  $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
1890
2770
  _meta: {
@@ -1892,7 +2772,7 @@ function buildEnvelope(operation, mvi, result, error) {
1892
2772
  schemaVersion: "1.0.0",
1893
2773
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1894
2774
  operation,
1895
- requestId: randomUUID2(),
2775
+ requestId: randomUUID4(),
1896
2776
  transport: "cli",
1897
2777
  strict: true,
1898
2778
  mvi,
@@ -1904,8 +2784,8 @@ function buildEnvelope(operation, mvi, result, error) {
1904
2784
  page: null
1905
2785
  };
1906
2786
  }
1907
- function emitJsonError(operation, mvi, code, message, category, details = {}) {
1908
- const envelope = buildEnvelope(operation, mvi, null, {
2787
+ function emitJsonError3(operation, mvi, code, message, category, details = {}) {
2788
+ const envelope = buildEnvelope5(operation, mvi, null, {
1909
2789
  code,
1910
2790
  message,
1911
2791
  category,
@@ -1917,9 +2797,9 @@ function emitJsonError(operation, mvi, code, message, category, details = {}) {
1917
2797
  }
1918
2798
 
1919
2799
  // src/commands/skills/find.ts
1920
- import { randomUUID as randomUUID3 } from "crypto";
2800
+ import { randomUUID as randomUUID5 } from "crypto";
1921
2801
  import {
1922
- resolveOutputFormat as resolveOutputFormat2
2802
+ resolveOutputFormat as resolveOutputFormat4
1923
2803
  } from "@cleocode/lafs-protocol";
1924
2804
  import pc14 from "picocolors";
1925
2805
  var SkillsFindValidationError = class extends Error {
@@ -1937,7 +2817,7 @@ function registerSkillsFind(parent) {
1937
2817
  const mvi = !details;
1938
2818
  let format;
1939
2819
  try {
1940
- format = resolveOutputFormat2({
2820
+ format = resolveOutputFormat4({
1941
2821
  jsonFlag: opts.json ?? false,
1942
2822
  humanFlag: (opts.human ?? false) || isHuman(),
1943
2823
  projectDefault: "json"
@@ -1945,7 +2825,7 @@ function registerSkillsFind(parent) {
1945
2825
  } catch (error) {
1946
2826
  const message = error instanceof Error ? error.message : String(error);
1947
2827
  if (opts.json) {
1948
- emitJsonError2(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
2828
+ emitJsonError4(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
1949
2829
  } else {
1950
2830
  console.error(pc14.red(message));
1951
2831
  }
@@ -1981,7 +2861,7 @@ function registerSkillsFind(parent) {
1981
2861
  const selectedObjects = resultOptions.filter(
1982
2862
  (option) => selectedRanks.includes(Number(option.rank ?? 0))
1983
2863
  );
1984
- const envelope = buildEnvelope2(
2864
+ const envelope = buildEnvelope6(
1985
2865
  operation,
1986
2866
  mvi,
1987
2867
  {
@@ -2004,7 +2884,7 @@ function registerSkillsFind(parent) {
2004
2884
  const errorCode = error instanceof SkillsFindValidationError ? error.code : error.code ?? RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
2005
2885
  const category = errorCode === RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT ? "CONFLICT" : errorCode === RECOMMENDATION_ERROR_CODES.NO_MATCHES ? "NOT_FOUND" : errorCode === RECOMMENDATION_ERROR_CODES.QUERY_INVALID ? "VALIDATION" : "INTERNAL";
2006
2886
  if (format === "json") {
2007
- emitJsonError2(operation, mvi, errorCode, message, category, {
2887
+ emitJsonError4(operation, mvi, errorCode, message, category, {
2008
2888
  query: query ?? null
2009
2889
  });
2010
2890
  } else {
@@ -2029,7 +2909,7 @@ function registerSkillsFind(parent) {
2029
2909
  } catch (error) {
2030
2910
  const message = formatNetworkError(error);
2031
2911
  if (format === "json") {
2032
- emitJsonError2(operation, mvi, "E_SEARCH_FAILED", message, "TRANSIENT", {
2912
+ emitJsonError4(operation, mvi, "E_SEARCH_FAILED", message, "TRANSIENT", {
2033
2913
  query,
2034
2914
  limit
2035
2915
  });
@@ -2039,7 +2919,7 @@ function registerSkillsFind(parent) {
2039
2919
  process.exit(1);
2040
2920
  }
2041
2921
  if (format === "json") {
2042
- const envelope = buildEnvelope2(
2922
+ const envelope = buildEnvelope6(
2043
2923
  operation,
2044
2924
  mvi,
2045
2925
  {
@@ -2149,7 +3029,7 @@ function validateSelectedRanks(selectedRanks, total) {
2149
3029
  }
2150
3030
  }
2151
3031
  }
2152
- function buildEnvelope2(operation, mvi, result, error) {
3032
+ function buildEnvelope6(operation, mvi, result, error) {
2153
3033
  return {
2154
3034
  $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
2155
3035
  _meta: {
@@ -2157,7 +3037,7 @@ function buildEnvelope2(operation, mvi, result, error) {
2157
3037
  schemaVersion: "1.0.0",
2158
3038
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2159
3039
  operation,
2160
- requestId: randomUUID3(),
3040
+ requestId: randomUUID5(),
2161
3041
  transport: "cli",
2162
3042
  strict: true,
2163
3043
  mvi,
@@ -2169,8 +3049,8 @@ function buildEnvelope2(operation, mvi, result, error) {
2169
3049
  page: null
2170
3050
  };
2171
3051
  }
2172
- function emitJsonError2(operation, mvi, code, message, category, details = {}) {
2173
- const envelope = buildEnvelope2(operation, mvi, null, {
3052
+ function emitJsonError4(operation, mvi, code, message, category, details = {}) {
3053
+ const envelope = buildEnvelope6(operation, mvi, null, {
2174
3054
  code,
2175
3055
  message,
2176
3056
  category,
@@ -2184,47 +3064,90 @@ function emitJsonError2(operation, mvi, code, message, category, details = {}) {
2184
3064
  // src/commands/skills/check.ts
2185
3065
  import pc15 from "picocolors";
2186
3066
  function registerSkillsCheck(parent) {
2187
- parent.command("check").description("Check for available skill updates").option("--json", "Output as JSON").action(async (opts) => {
3067
+ parent.command("check").description("Check for available skill updates").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
3068
+ const operation = "skills.check";
3069
+ const mvi = "standard";
3070
+ let format;
3071
+ try {
3072
+ format = resolveFormat({
3073
+ jsonFlag: opts.json ?? false,
3074
+ humanFlag: (opts.human ?? false) || isHuman(),
3075
+ projectDefault: "json"
3076
+ });
3077
+ } catch (error) {
3078
+ const message = error instanceof Error ? error.message : String(error);
3079
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
3080
+ process.exit(1);
3081
+ }
2188
3082
  const tracked = await getTrackedSkills();
2189
3083
  const entries = Object.entries(tracked);
2190
3084
  if (entries.length === 0) {
2191
- console.log(pc15.dim("No tracked skills."));
3085
+ if (format === "json") {
3086
+ outputSuccess(operation, mvi, {
3087
+ skills: [],
3088
+ outdated: 0,
3089
+ total: 0
3090
+ });
3091
+ } else {
3092
+ console.log(pc15.dim("No tracked skills."));
3093
+ }
2192
3094
  return;
2193
3095
  }
2194
- console.log(pc15.dim(`Checking ${entries.length} skill(s) for updates...
3096
+ if (format === "human") {
3097
+ console.log(pc15.dim(`Checking ${entries.length} skill(s) for updates...
2195
3098
  `));
2196
- const results = [];
3099
+ }
3100
+ const skillResults = [];
3101
+ let updatesAvailable = 0;
2197
3102
  for (const [name, entry] of entries) {
2198
3103
  const update = await checkSkillUpdate(name);
2199
- results.push({ name, entry, ...update });
3104
+ const hasUpdate = update.hasUpdate ?? false;
3105
+ if (hasUpdate) {
3106
+ updatesAvailable++;
3107
+ }
3108
+ skillResults.push({
3109
+ name,
3110
+ currentVersion: update.currentVersion ?? entry.version ?? "unknown",
3111
+ latestVersion: update.latestVersion ?? "unknown",
3112
+ hasUpdate,
3113
+ source: entry.source,
3114
+ agents: entry.agents
3115
+ });
2200
3116
  }
2201
- if (opts.json) {
2202
- console.log(JSON.stringify(results, null, 2));
3117
+ if (format === "json") {
3118
+ outputSuccess(operation, mvi, {
3119
+ skills: skillResults.map((s) => ({
3120
+ name: s.name,
3121
+ currentVersion: s.currentVersion,
3122
+ latestVersion: s.latestVersion,
3123
+ hasUpdate: s.hasUpdate
3124
+ })),
3125
+ outdated: updatesAvailable,
3126
+ total: entries.length
3127
+ });
2203
3128
  return;
2204
3129
  }
2205
- let updatesAvailable = 0;
2206
- for (const r of results) {
3130
+ for (const r of skillResults) {
2207
3131
  let statusLabel;
2208
- if (r.status === "update-available") {
3132
+ if (r.hasUpdate) {
2209
3133
  statusLabel = pc15.yellow("update available");
2210
- updatesAvailable++;
2211
- } else if (r.status === "up-to-date") {
3134
+ } else if (r.currentVersion !== "unknown") {
2212
3135
  statusLabel = pc15.green("up to date");
2213
3136
  } else {
2214
3137
  statusLabel = pc15.dim("unknown");
2215
3138
  }
2216
3139
  console.log(` ${pc15.bold(r.name.padEnd(30))} ${statusLabel}`);
2217
- if (r.currentVersion || r.latestVersion) {
2218
- const current = r.currentVersion ? r.currentVersion.slice(0, 12) : "?";
2219
- const latest = r.latestVersion ?? "?";
3140
+ if (r.currentVersion !== "unknown" || r.latestVersion !== "unknown") {
3141
+ const current = r.currentVersion !== "unknown" ? r.currentVersion.slice(0, 12) : "?";
3142
+ const latest = r.latestVersion !== "unknown" ? r.latestVersion : "?";
2220
3143
  if (r.hasUpdate) {
2221
3144
  console.log(` ${pc15.dim("current:")} ${current} ${pc15.dim("->")} ${pc15.cyan(latest)}`);
2222
3145
  } else {
2223
3146
  console.log(` ${pc15.dim("version:")} ${current}`);
2224
3147
  }
2225
3148
  }
2226
- console.log(` ${pc15.dim(`source: ${r.entry.source}`)}`);
2227
- console.log(` ${pc15.dim(`agents: ${r.entry.agents.join(", ")}`)}`);
3149
+ console.log(` ${pc15.dim(`source: ${r.source}`)}`);
3150
+ console.log(` ${pc15.dim(`agents: ${r.agents.join(", ")}`)}`);
2228
3151
  console.log();
2229
3152
  }
2230
3153
  if (updatesAvailable > 0) {
@@ -2239,14 +3162,39 @@ function registerSkillsCheck(parent) {
2239
3162
  // src/commands/skills/update.ts
2240
3163
  import pc16 from "picocolors";
2241
3164
  function registerSkillsUpdate(parent) {
2242
- parent.command("update").description("Update all outdated skills").option("-y, --yes", "Skip confirmation").action(async (opts) => {
3165
+ parent.command("update").description("Update all outdated skills").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
3166
+ const operation = "skills.update";
3167
+ const mvi = "standard";
3168
+ let format;
3169
+ try {
3170
+ format = resolveFormat({
3171
+ jsonFlag: opts.json ?? false,
3172
+ humanFlag: (opts.human ?? false) || isHuman(),
3173
+ projectDefault: "json"
3174
+ });
3175
+ } catch (error) {
3176
+ const message = error instanceof Error ? error.message : String(error);
3177
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
3178
+ process.exit(1);
3179
+ }
2243
3180
  const tracked = await getTrackedSkills();
2244
3181
  const entries = Object.entries(tracked);
2245
3182
  if (entries.length === 0) {
2246
- console.log(pc16.dim("No tracked skills to update."));
3183
+ if (format === "json") {
3184
+ outputSuccess(operation, mvi, {
3185
+ updated: [],
3186
+ failed: [],
3187
+ skipped: [],
3188
+ count: { updated: 0, failed: 0, skipped: 0 }
3189
+ });
3190
+ } else {
3191
+ console.log(pc16.dim("No tracked skills to update."));
3192
+ }
2247
3193
  return;
2248
3194
  }
2249
- console.log(pc16.dim(`Checking ${entries.length} skill(s) for updates...`));
3195
+ if (format === "human") {
3196
+ console.log(pc16.dim(`Checking ${entries.length} skill(s) for updates...`));
3197
+ }
2250
3198
  const outdated = [];
2251
3199
  for (const [name] of entries) {
2252
3200
  const result = await checkSkillUpdate(name);
@@ -2259,18 +3207,29 @@ function registerSkillsUpdate(parent) {
2259
3207
  }
2260
3208
  }
2261
3209
  if (outdated.length === 0) {
2262
- console.log(pc16.green("\nAll skills are up to date."));
3210
+ if (format === "json") {
3211
+ outputSuccess(operation, mvi, {
3212
+ updated: [],
3213
+ failed: [],
3214
+ skipped: [],
3215
+ count: { updated: 0, failed: 0, skipped: 0 }
3216
+ });
3217
+ } else {
3218
+ console.log(pc16.green("\nAll skills are up to date."));
3219
+ }
2263
3220
  return;
2264
3221
  }
2265
- console.log(pc16.yellow(`
3222
+ if (format === "human") {
3223
+ console.log(pc16.yellow(`
2266
3224
  ${outdated.length} skill(s) have updates available:
2267
3225
  `));
2268
- for (const skill of outdated) {
2269
- const current = skill.currentVersion?.slice(0, 12) ?? "?";
2270
- const latest = skill.latestVersion ?? "?";
2271
- console.log(` ${pc16.bold(skill.name)} ${pc16.dim(current)} ${pc16.dim("->")} ${pc16.cyan(latest)}`);
3226
+ for (const skill of outdated) {
3227
+ const current = skill.currentVersion?.slice(0, 12) ?? "?";
3228
+ const latest = skill.latestVersion ?? "?";
3229
+ console.log(` ${pc16.bold(skill.name)} ${pc16.dim(current)} ${pc16.dim("->")} ${pc16.cyan(latest)}`);
3230
+ }
2272
3231
  }
2273
- if (!opts.yes) {
3232
+ if (!opts.yes && format === "human") {
2274
3233
  const readline = await import("readline");
2275
3234
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2276
3235
  const answer = await new Promise((resolve) => {
@@ -2282,13 +3241,18 @@ ${outdated.length} skill(s) have updates available:
2282
3241
  return;
2283
3242
  }
2284
3243
  }
2285
- console.log();
2286
- let successCount = 0;
2287
- let failCount = 0;
3244
+ if (format === "human") {
3245
+ console.log();
3246
+ }
3247
+ const updated = [];
3248
+ const failed = [];
3249
+ const skipped = [];
2288
3250
  for (const skill of outdated) {
2289
3251
  const entry = tracked[skill.name];
2290
3252
  if (!entry) continue;
2291
- console.log(pc16.dim(`Updating ${pc16.bold(skill.name)}...`));
3253
+ if (format === "human") {
3254
+ console.log(pc16.dim(`Updating ${pc16.bold(skill.name)}...`));
3255
+ }
2292
3256
  try {
2293
3257
  const parsed = parseSource(entry.source);
2294
3258
  let localPath;
@@ -2302,13 +3266,19 @@ ${outdated.length} skill(s) have updates available:
2302
3266
  localPath = result.localPath;
2303
3267
  cleanup = result.cleanup;
2304
3268
  } else {
2305
- console.log(pc16.yellow(` Skipped ${skill.name}: source type "${parsed.type}" does not support auto-update`));
3269
+ if (format === "human") {
3270
+ console.log(pc16.yellow(` Skipped ${skill.name}: source type "${parsed.type}" does not support auto-update`));
3271
+ }
3272
+ skipped.push(skill.name);
2306
3273
  continue;
2307
3274
  }
2308
3275
  try {
2309
3276
  const providers = entry.agents.map((a) => getProvider(a)).filter((p) => p !== void 0);
2310
3277
  if (providers.length === 0) {
2311
- console.log(pc16.yellow(` Skipped ${skill.name}: no valid providers found`));
3278
+ if (format === "human") {
3279
+ console.log(pc16.yellow(` Skipped ${skill.name}: no valid providers found`));
3280
+ }
3281
+ skipped.push(skill.name);
2312
3282
  continue;
2313
3283
  }
2314
3284
  const installResult = await installSkill(
@@ -2330,13 +3300,17 @@ ${outdated.length} skill(s) have updates available:
2330
3300
  entry.projectDir,
2331
3301
  skill.latestVersion
2332
3302
  );
2333
- console.log(pc16.green(` Updated ${pc16.bold(skill.name)}`));
2334
- successCount++;
3303
+ if (format === "human") {
3304
+ console.log(pc16.green(` Updated ${pc16.bold(skill.name)}`));
3305
+ }
3306
+ updated.push(skill.name);
2335
3307
  } else {
2336
- console.log(pc16.red(` Failed to update ${skill.name}: no agents linked`));
2337
- failCount++;
3308
+ if (format === "human") {
3309
+ console.log(pc16.red(` Failed to update ${skill.name}: no agents linked`));
3310
+ }
3311
+ failed.push({ name: skill.name, error: "no agents linked" });
2338
3312
  }
2339
- if (installResult.errors.length > 0) {
3313
+ if (installResult.errors.length > 0 && format === "human") {
2340
3314
  for (const err of installResult.errors) {
2341
3315
  console.log(pc16.yellow(` ${err}`));
2342
3316
  }
@@ -2346,31 +3320,67 @@ ${outdated.length} skill(s) have updates available:
2346
3320
  }
2347
3321
  } catch (err) {
2348
3322
  const msg = err instanceof Error ? err.message : String(err);
2349
- console.log(pc16.red(` Failed to update ${skill.name}: ${msg}`));
2350
- failCount++;
3323
+ if (format === "human") {
3324
+ console.log(pc16.red(` Failed to update ${skill.name}: ${msg}`));
3325
+ }
3326
+ failed.push({ name: skill.name, error: msg });
2351
3327
  }
2352
3328
  }
3329
+ if (format === "json") {
3330
+ outputSuccess(operation, mvi, {
3331
+ updated,
3332
+ failed,
3333
+ skipped,
3334
+ count: {
3335
+ updated: updated.length,
3336
+ failed: failed.length,
3337
+ skipped: skipped.length
3338
+ }
3339
+ });
3340
+ return;
3341
+ }
2353
3342
  console.log();
2354
- if (successCount > 0) {
2355
- console.log(pc16.green(`Updated ${successCount} skill(s).`));
3343
+ if (updated.length > 0) {
3344
+ console.log(pc16.green(`Updated ${updated.length} skill(s).`));
2356
3345
  }
2357
- if (failCount > 0) {
2358
- console.log(pc16.red(`Failed to update ${failCount} skill(s).`));
3346
+ if (failed.length > 0) {
3347
+ console.log(pc16.red(`Failed to update ${failed.length} skill(s).`));
2359
3348
  }
2360
3349
  });
2361
3350
  }
2362
3351
 
2363
3352
  // src/commands/skills/init.ts
2364
- import pc17 from "picocolors";
2365
- import { writeFile, mkdir } from "fs/promises";
2366
3353
  import { existsSync as existsSync5 } from "fs";
3354
+ import { mkdir, writeFile } from "fs/promises";
2367
3355
  import { join as join5 } from "path";
3356
+ import pc17 from "picocolors";
2368
3357
  function registerSkillsInit(parent) {
2369
- parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").action(async (name, opts) => {
3358
+ parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (name, opts) => {
3359
+ const operation = "skills.init";
3360
+ const mvi = "standard";
3361
+ let format;
3362
+ try {
3363
+ format = resolveFormat({
3364
+ jsonFlag: opts.json ?? false,
3365
+ humanFlag: (opts.human ?? false) || isHuman(),
3366
+ projectDefault: "json"
3367
+ });
3368
+ } catch (error) {
3369
+ const message = error instanceof Error ? error.message : String(error);
3370
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
3371
+ process.exit(1);
3372
+ }
2370
3373
  const skillName = name ?? "my-skill";
2371
3374
  const skillDir = join5(opts.dir, skillName);
2372
3375
  if (existsSync5(skillDir)) {
2373
- console.error(pc17.red(`Directory already exists: ${skillDir}`));
3376
+ const message = `Directory already exists: ${skillDir}`;
3377
+ if (format === "json") {
3378
+ emitJsonError(operation, mvi, ErrorCodes.INVALID_CONSTRAINT, message, ErrorCategories.CONFLICT, {
3379
+ path: skillDir
3380
+ });
3381
+ } else {
3382
+ console.error(pc17.red(message));
3383
+ }
2374
3384
  process.exit(1);
2375
3385
  }
2376
3386
  await mkdir(skillDir, { recursive: true });
@@ -2398,6 +3408,16 @@ Provide detailed instructions for the AI agent here.
2398
3408
  Show example inputs and expected outputs.
2399
3409
  `;
2400
3410
  await writeFile(join5(skillDir, "SKILL.md"), template, "utf-8");
3411
+ const result = {
3412
+ name: skillName,
3413
+ directory: skillDir,
3414
+ template: "SKILL.md",
3415
+ created: true
3416
+ };
3417
+ if (format === "json") {
3418
+ outputSuccess(operation, mvi, result);
3419
+ return;
3420
+ }
2401
3421
  console.log(pc17.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
2402
3422
  console.log(pc17.dim("\nNext steps:"));
2403
3423
  console.log(pc17.dim(" 1. Edit SKILL.md with your instructions"));
@@ -2410,32 +3430,126 @@ Show example inputs and expected outputs.
2410
3430
  import { existsSync as existsSync6, statSync } from "fs";
2411
3431
  import pc18 from "picocolors";
2412
3432
  function registerSkillsAudit(parent) {
2413
- 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) => {
3433
+ 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 (raw SARIF, not LAFS envelope)").option("--json", "Output as JSON (LAFS envelope)").option("--human", "Output in human-readable format").action(async (path, opts) => {
3434
+ const operation = "skills.audit";
3435
+ const mvi = "standard";
2414
3436
  if (!existsSync6(path)) {
2415
- console.error(pc18.red(`Path not found: ${path}`));
3437
+ const message = `Path not found: ${path}`;
3438
+ if (opts.sarif) {
3439
+ console.error(JSON.stringify({
3440
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
3441
+ version: "2.1.0",
3442
+ runs: [{
3443
+ tool: { driver: { name: "caamp-skills-audit" } },
3444
+ invocations: [{
3445
+ executionSuccessful: false,
3446
+ exitCode: 1,
3447
+ exitCodeDescription: message
3448
+ }],
3449
+ results: []
3450
+ }]
3451
+ }, null, 2));
3452
+ } else {
3453
+ emitJsonError(operation, mvi, ErrorCodes.FILE_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
3454
+ path
3455
+ });
3456
+ }
3457
+ process.exit(1);
3458
+ }
3459
+ let format;
3460
+ try {
3461
+ if (opts.sarif) {
3462
+ format = "sarif";
3463
+ } else {
3464
+ format = resolveFormat({
3465
+ jsonFlag: opts.json ?? false,
3466
+ humanFlag: (opts.human ?? false) || isHuman(),
3467
+ projectDefault: "json"
3468
+ });
3469
+ }
3470
+ } catch (error) {
3471
+ const message = error instanceof Error ? error.message : String(error);
3472
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
2416
3473
  process.exit(1);
2417
3474
  }
2418
3475
  const stat = statSync(path);
2419
3476
  let results;
2420
- if (stat.isFile()) {
2421
- results = [await scanFile(path)];
2422
- } else {
2423
- results = await scanDirectory(path);
3477
+ try {
3478
+ if (stat.isFile()) {
3479
+ results = [await scanFile(path)];
3480
+ } else {
3481
+ results = await scanDirectory(path);
3482
+ }
3483
+ } catch (error) {
3484
+ const message = error instanceof Error ? error.message : String(error);
3485
+ if (format === "sarif") {
3486
+ console.error(JSON.stringify({
3487
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
3488
+ version: "2.1.0",
3489
+ runs: [{
3490
+ tool: { driver: { name: "caamp-skills-audit" } },
3491
+ invocations: [{
3492
+ executionSuccessful: false,
3493
+ exitCode: 1,
3494
+ exitCodeDescription: message
3495
+ }],
3496
+ results: []
3497
+ }]
3498
+ }, null, 2));
3499
+ } else {
3500
+ emitJsonError(operation, mvi, ErrorCodes.AUDIT_FAILED, message, ErrorCategories.INTERNAL, {
3501
+ path
3502
+ });
3503
+ }
3504
+ process.exit(1);
2424
3505
  }
2425
3506
  if (results.length === 0) {
3507
+ if (format === "sarif") {
3508
+ console.log(JSON.stringify(toSarif([]), null, 2));
3509
+ return;
3510
+ }
3511
+ if (format === "json") {
3512
+ const summary2 = {
3513
+ scanned: 0,
3514
+ findings: 0,
3515
+ files: []
3516
+ };
3517
+ outputSuccess(operation, mvi, summary2);
3518
+ return;
3519
+ }
2426
3520
  console.log(pc18.dim("No SKILL.md files found to scan."));
2427
3521
  return;
2428
3522
  }
2429
- if (opts.sarif) {
3523
+ const summary = {
3524
+ scanned: results.length,
3525
+ findings: results.reduce((acc, r) => acc + r.findings.length, 0),
3526
+ files: results.map((r) => ({
3527
+ path: r.file,
3528
+ score: r.score,
3529
+ findings: r.findings.map((f) => ({
3530
+ level: f.rule.severity,
3531
+ code: f.rule.id,
3532
+ message: `${f.rule.name}: ${f.rule.description}`,
3533
+ line: f.line
3534
+ }))
3535
+ }))
3536
+ };
3537
+ const allPassed = results.every((r) => r.passed);
3538
+ if (format === "sarif") {
2430
3539
  console.log(JSON.stringify(toSarif(results), null, 2));
3540
+ if (!allPassed) {
3541
+ process.exit(1);
3542
+ }
2431
3543
  return;
2432
3544
  }
2433
- if (opts.json) {
2434
- console.log(JSON.stringify(results, null, 2));
3545
+ if (format === "json") {
3546
+ outputSuccess(operation, mvi, summary);
3547
+ if (!allPassed) {
3548
+ process.exit(1);
3549
+ }
2435
3550
  return;
2436
3551
  }
2437
3552
  let totalFindings = 0;
2438
- let allPassed = true;
2439
3553
  for (const result of results) {
2440
3554
  const icon = result.passed ? pc18.green("\u2713") : pc18.red("\u2717");
2441
3555
  console.log(`
@@ -2445,7 +3559,6 @@ ${icon} ${pc18.bold(result.file)} (score: ${result.score}/100)`);
2445
3559
  continue;
2446
3560
  }
2447
3561
  totalFindings += result.findings.length;
2448
- if (!result.passed) allPassed = false;
2449
3562
  for (const f of result.findings) {
2450
3563
  const sev = f.rule.severity === "critical" ? pc18.red(f.rule.severity) : f.rule.severity === "high" ? pc18.red(f.rule.severity) : f.rule.severity === "medium" ? pc18.yellow(f.rule.severity) : pc18.dim(f.rule.severity);
2451
3564
  console.log(` ${sev.padEnd(20)} ${f.rule.id} ${f.rule.name}`);
@@ -2461,22 +3574,64 @@ ${results.length} file(s) scanned, ${totalFindings} finding(s)`));
2461
3574
  }
2462
3575
 
2463
3576
  // src/commands/skills/validate.ts
3577
+ import { resolveOutputFormat as resolveOutputFormat5 } from "@cleocode/lafs-protocol";
2464
3578
  import pc19 from "picocolors";
2465
3579
  function registerSkillsValidate(parent) {
2466
- parent.command("validate").description("Validate SKILL.md format").argument("[path]", "Path to SKILL.md", "SKILL.md").option("--json", "Output as JSON").action(async (path, opts) => {
2467
- const result = await validateSkill(path);
2468
- if (opts.json) {
2469
- console.log(JSON.stringify(result, null, 2));
2470
- return;
3580
+ parent.command("validate").description("Validate SKILL.md format").argument("[path]", "Path to SKILL.md", "SKILL.md").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (path, opts) => {
3581
+ const operation = "skills.validate";
3582
+ const mvi = "standard";
3583
+ let format;
3584
+ try {
3585
+ format = resolveOutputFormat5({
3586
+ jsonFlag: opts.json ?? false,
3587
+ humanFlag: (opts.human ?? false) || isHuman(),
3588
+ projectDefault: "json"
3589
+ }).format;
3590
+ } catch (error) {
3591
+ const message = error instanceof Error ? error.message : String(error);
3592
+ emitJsonError(operation, mvi, ErrorCodes.FORMAT_CONFLICT, message, ErrorCategories.VALIDATION);
3593
+ process.exit(1);
2471
3594
  }
2472
- if (result.valid) {
2473
- console.log(pc19.green(`\u2713 ${path} is valid`));
2474
- } else {
2475
- console.log(pc19.red(`\u2717 ${path} has validation errors`));
3595
+ let result;
3596
+ try {
3597
+ result = await validateSkill(path);
3598
+ } catch (error) {
3599
+ const message = error instanceof Error ? error.message : String(error);
3600
+ if (format === "json") {
3601
+ emitJsonError(operation, mvi, ErrorCodes.FILE_NOT_FOUND, message, ErrorCategories.NOT_FOUND, {
3602
+ path
3603
+ });
3604
+ } else {
3605
+ console.error(pc19.red(message));
3606
+ }
3607
+ process.exit(1);
2476
3608
  }
2477
- for (const issue of result.issues) {
2478
- const icon = issue.level === "error" ? pc19.red("\u2717") : pc19.yellow("!");
2479
- console.log(` ${icon} [${issue.field}] ${issue.message}`);
3609
+ if (format === "json") {
3610
+ const envelope = buildEnvelope(
3611
+ operation,
3612
+ mvi,
3613
+ {
3614
+ valid: result.valid,
3615
+ file: path,
3616
+ issues: result.issues.map((issue) => ({
3617
+ level: issue.level === "error" ? "error" : "warn",
3618
+ field: issue.field,
3619
+ message: issue.message
3620
+ }))
3621
+ },
3622
+ null
3623
+ );
3624
+ console.log(JSON.stringify(envelope, null, 2));
3625
+ } else {
3626
+ if (result.valid) {
3627
+ console.log(pc19.green(`\u2713 ${path} is valid`));
3628
+ } else {
3629
+ console.log(pc19.red(`\u2717 ${path} has validation errors`));
3630
+ }
3631
+ for (const issue of result.issues) {
3632
+ const icon = issue.level === "error" ? pc19.red("\u2717") : pc19.yellow("!");
3633
+ console.log(` ${icon} [${issue.field}] ${issue.message}`);
3634
+ }
2480
3635
  }
2481
3636
  if (!result.valid) {
2482
3637
  process.exit(1);