@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/{chunk-PBHH6KMJ.js → chunk-HUMRYJPE.js} +165 -165
- package/dist/{chunk-PBHH6KMJ.js.map → chunk-HUMRYJPE.js.map} +1 -1
- package/dist/cli.js +1531 -376
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
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-
|
|
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
|
-
|
|
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
|
|
728
|
-
|
|
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
|
-
|
|
733
|
-
|
|
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 (
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
744
|
-
}
|
|
888
|
+
console.log(JSON.stringify(data, null, 2));
|
|
745
889
|
} catch (err) {
|
|
746
|
-
|
|
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,
|
|
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
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1435
|
+
const injected = [];
|
|
1436
|
+
for (const [file] of results) {
|
|
1437
|
+
injected.push(file);
|
|
1147
1438
|
}
|
|
1148
|
-
|
|
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
|
-
|
|
1168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1564
|
+
if (format === "human") {
|
|
1565
|
+
console.log(pc5.bold(`${needsUpdate.length} file(s) need updating:
|
|
1215
1566
|
`));
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
-
|
|
1223
|
-
for (const [file
|
|
1224
|
-
|
|
1574
|
+
const updated = [];
|
|
1575
|
+
for (const [file] of results) {
|
|
1576
|
+
updated.push(file);
|
|
1225
1577
|
}
|
|
1226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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 (
|
|
1389
|
-
|
|
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
|
-
${
|
|
1895
|
+
${providers.length} provider(s) with MCP support:
|
|
1394
1896
|
`));
|
|
1395
|
-
for (const
|
|
1396
|
-
const
|
|
1397
|
-
const
|
|
1398
|
-
const
|
|
1399
|
-
const
|
|
1400
|
-
|
|
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 (
|
|
1423
|
-
|
|
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 (
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
-
|
|
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 (
|
|
1477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1749
|
-
|
|
2458
|
+
skillDir,
|
|
2459
|
+
name,
|
|
1750
2460
|
providers,
|
|
1751
|
-
|
|
2461
|
+
isGlobal
|
|
1752
2462
|
);
|
|
1753
2463
|
if (result.success) {
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2468
|
+
name,
|
|
2469
|
+
`@cleocode/ct-skills:${name}`,
|
|
2470
|
+
`@cleocode/ct-skills:${name}`,
|
|
2471
|
+
"package",
|
|
1764
2472
|
result.linkedAgents,
|
|
1765
2473
|
result.canonicalPath,
|
|
1766
|
-
|
|
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
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
-
|
|
1789
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2701
|
+
const mvi = "standard";
|
|
1822
2702
|
let format;
|
|
1823
2703
|
try {
|
|
1824
|
-
format =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
1908
|
-
const envelope =
|
|
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
|
|
2800
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
1921
2801
|
import {
|
|
1922
|
-
resolveOutputFormat as
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
2173
|
-
const envelope =
|
|
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
|
-
|
|
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
|
-
|
|
3096
|
+
if (format === "human") {
|
|
3097
|
+
console.log(pc15.dim(`Checking ${entries.length} skill(s) for updates...
|
|
2195
3098
|
`));
|
|
2196
|
-
|
|
3099
|
+
}
|
|
3100
|
+
const skillResults = [];
|
|
3101
|
+
let updatesAvailable = 0;
|
|
2197
3102
|
for (const [name, entry] of entries) {
|
|
2198
3103
|
const update = await checkSkillUpdate(name);
|
|
2199
|
-
|
|
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 (
|
|
2202
|
-
|
|
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
|
-
|
|
2206
|
-
for (const r of results) {
|
|
3130
|
+
for (const r of skillResults) {
|
|
2207
3131
|
let statusLabel;
|
|
2208
|
-
if (r.
|
|
3132
|
+
if (r.hasUpdate) {
|
|
2209
3133
|
statusLabel = pc15.yellow("update available");
|
|
2210
|
-
|
|
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.
|
|
2227
|
-
console.log(` ${pc15.dim(`agents: ${r.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3222
|
+
if (format === "human") {
|
|
3223
|
+
console.log(pc16.yellow(`
|
|
2266
3224
|
${outdated.length} skill(s) have updates available:
|
|
2267
3225
|
`));
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2334
|
-
|
|
3303
|
+
if (format === "human") {
|
|
3304
|
+
console.log(pc16.green(` Updated ${pc16.bold(skill.name)}`));
|
|
3305
|
+
}
|
|
3306
|
+
updated.push(skill.name);
|
|
2335
3307
|
} else {
|
|
2336
|
-
|
|
2337
|
-
|
|
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
|
-
|
|
2350
|
-
|
|
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 (
|
|
2355
|
-
console.log(pc16.green(`Updated ${
|
|
3343
|
+
if (updated.length > 0) {
|
|
3344
|
+
console.log(pc16.green(`Updated ${updated.length} skill(s).`));
|
|
2356
3345
|
}
|
|
2357
|
-
if (
|
|
2358
|
-
console.log(pc16.red(`Failed to update ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
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
|
-
|
|
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 (
|
|
2434
|
-
|
|
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
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
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
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
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
|
-
|
|
2478
|
-
const
|
|
2479
|
-
|
|
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);
|