@cleocode/caamp 1.0.1 → 1.0.3
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/README.md +18 -15
- package/dist/{chunk-VAFYP7CI.js → chunk-PBHH6KMJ.js} +2934 -2912
- package/dist/chunk-PBHH6KMJ.js.map +1 -0
- package/dist/cli.js +2136 -2048
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +13 -5
- package/dist/chunk-VAFYP7CI.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
installMcpServerToAll,
|
|
33
33
|
installSkill,
|
|
34
34
|
isCatalogAvailable,
|
|
35
|
+
isHuman,
|
|
35
36
|
isMarketplaceScoped,
|
|
36
37
|
isVerbose,
|
|
37
38
|
listCanonicalSkills,
|
|
@@ -56,1019 +57,1187 @@ import {
|
|
|
56
57
|
scanDirectory,
|
|
57
58
|
scanFile,
|
|
58
59
|
selectProvidersByMinimumPriority,
|
|
60
|
+
setHuman,
|
|
59
61
|
setQuiet,
|
|
60
62
|
setVerbose,
|
|
61
63
|
toSarif,
|
|
62
64
|
tokenizeCriteriaValue,
|
|
63
65
|
updateInstructionsSingleOperation,
|
|
64
66
|
validateSkill
|
|
65
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-PBHH6KMJ.js";
|
|
66
68
|
|
|
67
69
|
// src/cli.ts
|
|
68
70
|
import { Command } from "commander";
|
|
69
71
|
|
|
70
|
-
// src/commands/
|
|
71
|
-
import
|
|
72
|
-
function registerProvidersCommand(program2) {
|
|
73
|
-
const providers = program2.command("providers").description("Manage AI agent providers");
|
|
74
|
-
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) => {
|
|
75
|
-
const all = opts.tier ? getProvidersByPriority(opts.tier) : getAllProviders();
|
|
76
|
-
if (opts.json) {
|
|
77
|
-
console.log(JSON.stringify(all, null, 2));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
console.log(pc.bold(`
|
|
81
|
-
CAMP Provider Registry v${getRegistryVersion()}`));
|
|
82
|
-
console.log(pc.dim(`${getProviderCount()} providers
|
|
83
|
-
`));
|
|
84
|
-
const tiers = ["high", "medium", "low"];
|
|
85
|
-
for (const tier of tiers) {
|
|
86
|
-
const tierProviders = all.filter((p) => p.priority === tier);
|
|
87
|
-
if (tierProviders.length === 0) continue;
|
|
88
|
-
const tierLabel = tier === "high" ? pc.green("HIGH") : tier === "medium" ? pc.yellow("MEDIUM") : pc.dim("LOW");
|
|
89
|
-
console.log(`${tierLabel} priority:`);
|
|
90
|
-
for (const p of tierProviders) {
|
|
91
|
-
const status = p.status === "active" ? pc.green("active") : p.status === "beta" ? pc.yellow("beta") : pc.dim(p.status);
|
|
92
|
-
console.log(` ${pc.bold(p.agentFlag.padEnd(20))} ${p.toolName.padEnd(22)} ${p.vendor.padEnd(16)} [${status}]`);
|
|
93
|
-
}
|
|
94
|
-
console.log();
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
providers.command("detect").description("Auto-detect installed providers").option("--json", "Output as JSON").option("--project", "Include project-level detection").action(async (opts) => {
|
|
98
|
-
const results = opts.project ? detectProjectProviders(process.cwd()) : detectAllProviders();
|
|
99
|
-
const installed = results.filter((r) => r.installed);
|
|
100
|
-
if (opts.json) {
|
|
101
|
-
console.log(JSON.stringify(installed.map((r) => ({
|
|
102
|
-
id: r.provider.id,
|
|
103
|
-
toolName: r.provider.toolName,
|
|
104
|
-
methods: r.methods,
|
|
105
|
-
projectDetected: r.projectDetected
|
|
106
|
-
})), null, 2));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
console.log(pc.bold(`
|
|
110
|
-
Detected ${installed.length} installed providers:
|
|
111
|
-
`));
|
|
112
|
-
for (const r of installed) {
|
|
113
|
-
const methods = r.methods.join(", ");
|
|
114
|
-
const project = r.projectDetected ? pc.green(" [project]") : "";
|
|
115
|
-
console.log(` ${pc.green("\u2713")} ${pc.bold(r.provider.toolName.padEnd(22))} via ${pc.dim(methods)}${project}`);
|
|
116
|
-
}
|
|
117
|
-
const notInstalled = results.filter((r) => !r.installed);
|
|
118
|
-
if (notInstalled.length > 0) {
|
|
119
|
-
console.log(pc.dim(`
|
|
120
|
-
${notInstalled.length} providers not detected`));
|
|
121
|
-
}
|
|
122
|
-
console.log();
|
|
123
|
-
});
|
|
124
|
-
providers.command("show").description("Show provider details").argument("<id>", "Provider ID or alias").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
125
|
-
const provider = getProvider(id);
|
|
126
|
-
if (!provider) {
|
|
127
|
-
console.error(pc.red(`Provider not found: ${id}`));
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
if (opts.json) {
|
|
131
|
-
console.log(JSON.stringify(provider, null, 2));
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
console.log(pc.bold(`
|
|
135
|
-
${provider.toolName}`));
|
|
136
|
-
console.log(pc.dim(`by ${provider.vendor}
|
|
137
|
-
`));
|
|
138
|
-
console.log(` ID: ${provider.id}`);
|
|
139
|
-
console.log(` Flag: --agent ${provider.agentFlag}`);
|
|
140
|
-
if (provider.aliases.length > 0) {
|
|
141
|
-
console.log(` Aliases: ${provider.aliases.join(", ")}`);
|
|
142
|
-
}
|
|
143
|
-
console.log(` Status: ${provider.status}`);
|
|
144
|
-
console.log(` Priority: ${provider.priority}`);
|
|
145
|
-
console.log();
|
|
146
|
-
console.log(` Instruction: ${provider.instructFile}`);
|
|
147
|
-
console.log(` Config format: ${provider.configFormat}`);
|
|
148
|
-
console.log(` Config key: ${provider.configKey}`);
|
|
149
|
-
console.log(` Transports: ${provider.supportedTransports.join(", ")}`);
|
|
150
|
-
console.log(` Headers: ${provider.supportsHeaders ? "yes" : "no"}`);
|
|
151
|
-
console.log();
|
|
152
|
-
console.log(pc.dim(" Paths:"));
|
|
153
|
-
console.log(` Global dir: ${provider.pathGlobal}`);
|
|
154
|
-
console.log(` Project dir: ${provider.pathProject || "(none)"}`);
|
|
155
|
-
console.log(` Global config: ${provider.configPathGlobal}`);
|
|
156
|
-
console.log(` Project config: ${provider.configPathProject || "(none)"}`);
|
|
157
|
-
console.log(` Global skills: ${provider.pathSkills}`);
|
|
158
|
-
console.log(` Project skills: ${provider.pathProjectSkills || "(none)"}`);
|
|
159
|
-
console.log();
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// src/commands/skills/install.ts
|
|
164
|
-
import { existsSync } from "fs";
|
|
165
|
-
import pc2 from "picocolors";
|
|
72
|
+
// src/commands/advanced/common.ts
|
|
73
|
+
import { readFile } from "fs/promises";
|
|
166
74
|
|
|
167
|
-
// src/
|
|
168
|
-
import {
|
|
169
|
-
import {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
75
|
+
// src/commands/advanced/lafs.ts
|
|
76
|
+
import { randomUUID } from "crypto";
|
|
77
|
+
import {
|
|
78
|
+
isRegisteredErrorCode
|
|
79
|
+
} from "@cleocode/lafs-protocol";
|
|
80
|
+
var LAFSCommandError = class extends Error {
|
|
81
|
+
code;
|
|
82
|
+
category;
|
|
83
|
+
recoverable;
|
|
84
|
+
suggestion;
|
|
85
|
+
retryAfterMs;
|
|
86
|
+
details;
|
|
87
|
+
constructor(code, message, suggestion, recoverable = true, details) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.name = "LAFSCommandError";
|
|
90
|
+
this.code = code;
|
|
91
|
+
this.category = inferErrorCategory(code);
|
|
92
|
+
this.recoverable = recoverable;
|
|
93
|
+
this.suggestion = suggestion;
|
|
94
|
+
this.retryAfterMs = null;
|
|
95
|
+
this.details = details;
|
|
179
96
|
}
|
|
180
|
-
|
|
181
|
-
|
|
97
|
+
};
|
|
98
|
+
function inferErrorCategory(code) {
|
|
99
|
+
if (code.includes("VALIDATION")) return "VALIDATION";
|
|
100
|
+
if (code.includes("NOT_FOUND")) return "NOT_FOUND";
|
|
101
|
+
if (code.includes("CONFLICT")) return "CONFLICT";
|
|
102
|
+
if (code.includes("AUTH")) return "AUTH";
|
|
103
|
+
if (code.includes("PERMISSION")) return "PERMISSION";
|
|
104
|
+
if (code.includes("RATE_LIMIT")) return "RATE_LIMIT";
|
|
105
|
+
if (code.includes("MIGRATION")) return "MIGRATION";
|
|
106
|
+
if (code.includes("CONTRACT")) return "CONTRACT";
|
|
107
|
+
return "INTERNAL";
|
|
108
|
+
}
|
|
109
|
+
function baseMeta(operation, mvi) {
|
|
182
110
|
return {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
111
|
+
specVersion: "1.0.0",
|
|
112
|
+
schemaVersion: "1.0.0",
|
|
113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
114
|
+
operation,
|
|
115
|
+
requestId: randomUUID(),
|
|
116
|
+
transport: "cli",
|
|
117
|
+
strict: true,
|
|
118
|
+
mvi,
|
|
119
|
+
contextVersion: 0
|
|
190
120
|
};
|
|
191
121
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const cloneOptions = ["--depth", "1"];
|
|
203
|
-
if (ref) {
|
|
204
|
-
cloneOptions.push("--branch", ref);
|
|
205
|
-
}
|
|
206
|
-
await git.clone(repoUrl, tmpDir, cloneOptions);
|
|
207
|
-
const localPath = subPath ? join2(tmpDir, subPath) : tmpDir;
|
|
208
|
-
return {
|
|
209
|
-
localPath,
|
|
210
|
-
cleanup: async () => {
|
|
211
|
-
try {
|
|
212
|
-
await rm2(tmpDir, { recursive: true });
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
}
|
|
122
|
+
function emitSuccess(operation, result, mvi = true) {
|
|
123
|
+
const envelope = {
|
|
124
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
125
|
+
_meta: {
|
|
126
|
+
...baseMeta(operation, mvi)
|
|
127
|
+
},
|
|
128
|
+
success: true,
|
|
129
|
+
result,
|
|
130
|
+
error: null,
|
|
131
|
+
page: null
|
|
216
132
|
};
|
|
133
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
217
134
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
const profileSkills = resolveProfile(opts.profile);
|
|
240
|
-
if (profileSkills.length === 0) {
|
|
241
|
-
const available = listProfiles();
|
|
242
|
-
console.error(pc2.red(`Profile not found: ${opts.profile}`));
|
|
243
|
-
if (available.length > 0) {
|
|
244
|
-
console.log(pc2.dim("Available profiles: " + available.join(", ")));
|
|
245
|
-
}
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
248
|
-
console.log(`Installing profile ${pc2.bold(opts.profile)} (${profileSkills.length} skill(s))...`);
|
|
249
|
-
console.log(pc2.dim(`Target: ${providers.length} provider(s)`));
|
|
250
|
-
let installed = 0;
|
|
251
|
-
let failed = 0;
|
|
252
|
-
for (const name of profileSkills) {
|
|
253
|
-
const skillDir = getSkillDir(name);
|
|
254
|
-
try {
|
|
255
|
-
const result = await installSkill(
|
|
256
|
-
skillDir,
|
|
257
|
-
name,
|
|
258
|
-
providers,
|
|
259
|
-
opts.global ?? false
|
|
260
|
-
);
|
|
261
|
-
if (result.success) {
|
|
262
|
-
console.log(pc2.green(` + ${name}`));
|
|
263
|
-
await recordSkillInstall(
|
|
264
|
-
name,
|
|
265
|
-
`@cleocode/ct-skills:${name}`,
|
|
266
|
-
`@cleocode/ct-skills:${name}`,
|
|
267
|
-
"package",
|
|
268
|
-
result.linkedAgents,
|
|
269
|
-
result.canonicalPath,
|
|
270
|
-
true
|
|
271
|
-
);
|
|
272
|
-
installed++;
|
|
273
|
-
} else {
|
|
274
|
-
console.log(pc2.yellow(` ! ${name}: ${result.errors.join(", ")}`));
|
|
275
|
-
failed++;
|
|
276
|
-
}
|
|
277
|
-
} catch (err) {
|
|
278
|
-
console.log(pc2.red(` x ${name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
279
|
-
failed++;
|
|
135
|
+
function emitError(operation, error, mvi = true) {
|
|
136
|
+
let envelope;
|
|
137
|
+
if (error instanceof LAFSCommandError) {
|
|
138
|
+
envelope = {
|
|
139
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
140
|
+
_meta: {
|
|
141
|
+
...baseMeta(operation, mvi)
|
|
142
|
+
},
|
|
143
|
+
success: false,
|
|
144
|
+
result: null,
|
|
145
|
+
error: {
|
|
146
|
+
code: isRegisteredErrorCode(error.code) ? error.code : "E_INTERNAL_UNEXPECTED",
|
|
147
|
+
message: error.message,
|
|
148
|
+
category: error.category,
|
|
149
|
+
retryable: error.recoverable,
|
|
150
|
+
retryAfterMs: error.retryAfterMs,
|
|
151
|
+
details: {
|
|
152
|
+
hint: error.suggestion,
|
|
153
|
+
...error.details !== void 0 ? { payload: error.details } : {}
|
|
280
154
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
let skill;
|
|
301
|
-
try {
|
|
302
|
-
skill = await client.getSkill(source);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error(pc2.red(`Marketplace lookup failed: ${formatNetworkError(error)}`));
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
if (!skill) {
|
|
308
|
-
console.error(pc2.red(`Skill not found: ${source}`));
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
console.log(` Found: ${pc2.bold(skill.name)} by ${skill.author} (${pc2.dim(skill.repoFullName)})`);
|
|
312
|
-
const parsed = parseSource(skill.githubUrl);
|
|
313
|
-
if (parsed.type !== "github" || !parsed.owner || !parsed.repo) {
|
|
314
|
-
console.error(pc2.red("Could not resolve GitHub source"));
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
|
|
319
|
-
let cloneError;
|
|
320
|
-
let cloned = false;
|
|
321
|
-
for (const subPath of subPathCandidates) {
|
|
322
|
-
try {
|
|
323
|
-
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
|
|
324
|
-
if (subPath && !existsSync(result.localPath)) {
|
|
325
|
-
await result.cleanup();
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
localPath = result.localPath;
|
|
329
|
-
cleanup = result.cleanup;
|
|
330
|
-
cloned = true;
|
|
331
|
-
break;
|
|
332
|
-
} catch (error) {
|
|
333
|
-
cloneError = error;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if (!cloned) {
|
|
337
|
-
throw cloneError ?? new Error("Unable to resolve skill path from marketplace metadata");
|
|
155
|
+
},
|
|
156
|
+
page: null
|
|
157
|
+
};
|
|
158
|
+
} else {
|
|
159
|
+
envelope = {
|
|
160
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
161
|
+
_meta: {
|
|
162
|
+
...baseMeta(operation, mvi)
|
|
163
|
+
},
|
|
164
|
+
success: false,
|
|
165
|
+
result: null,
|
|
166
|
+
error: {
|
|
167
|
+
code: "E_INTERNAL_UNEXPECTED",
|
|
168
|
+
message: error instanceof Error ? error.message : String(error),
|
|
169
|
+
category: "INTERNAL",
|
|
170
|
+
retryable: false,
|
|
171
|
+
retryAfterMs: null,
|
|
172
|
+
details: {
|
|
173
|
+
hint: "Rerun with --verbose and validate your inputs."
|
|
338
174
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
175
|
+
},
|
|
176
|
+
page: null
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
console.error(JSON.stringify(envelope, null, 2));
|
|
180
|
+
}
|
|
181
|
+
async function runLafsCommand(command, mvi, action) {
|
|
182
|
+
try {
|
|
183
|
+
const result = await action();
|
|
184
|
+
emitSuccess(command, result, mvi);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
emitError(command, error, mvi);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/commands/advanced/common.ts
|
|
192
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
|
|
193
|
+
function parsePriority(value) {
|
|
194
|
+
if (!VALID_PRIORITIES.has(value)) {
|
|
195
|
+
throw new LAFSCommandError(
|
|
196
|
+
"E_ADVANCED_VALIDATION_PRIORITY",
|
|
197
|
+
`Invalid tier: ${value}`,
|
|
198
|
+
"Use one of: high, medium, low."
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function resolveProviders(options) {
|
|
204
|
+
if (options.all) {
|
|
205
|
+
return getAllProviders();
|
|
206
|
+
}
|
|
207
|
+
const targetAgents = options.agent ?? [];
|
|
208
|
+
if (targetAgents.length === 0) {
|
|
209
|
+
return getInstalledProviders();
|
|
210
|
+
}
|
|
211
|
+
const providers = targetAgents.map((id) => getProvider(id)).filter((provider) => provider !== void 0);
|
|
212
|
+
if (providers.length !== targetAgents.length) {
|
|
213
|
+
const found = new Set(providers.map((provider) => provider.id));
|
|
214
|
+
const missing = targetAgents.filter((id) => !found.has(id));
|
|
215
|
+
throw new LAFSCommandError(
|
|
216
|
+
"E_ADVANCED_PROVIDER_NOT_FOUND",
|
|
217
|
+
`Unknown provider(s): ${missing.join(", ")}`,
|
|
218
|
+
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return providers;
|
|
222
|
+
}
|
|
223
|
+
async function readJsonFile(path) {
|
|
224
|
+
try {
|
|
225
|
+
const raw = await readFile(path, "utf-8");
|
|
226
|
+
return JSON.parse(raw);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new LAFSCommandError(
|
|
229
|
+
"E_ADVANCED_INPUT_JSON",
|
|
230
|
+
`Failed to read JSON file: ${path}`,
|
|
231
|
+
"Confirm the path exists and contains valid JSON.",
|
|
232
|
+
true,
|
|
233
|
+
{ reason: error instanceof Error ? error.message : String(error) }
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function readMcpOperations(path) {
|
|
238
|
+
const value = await readJsonFile(path);
|
|
239
|
+
if (!Array.isArray(value)) {
|
|
240
|
+
throw new LAFSCommandError(
|
|
241
|
+
"E_ADVANCED_VALIDATION_MCP_ARRAY",
|
|
242
|
+
`MCP operations file must be a JSON array: ${path}`,
|
|
243
|
+
"Provide an array of objects with serverName and config fields."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const operations = [];
|
|
247
|
+
for (const [index, item] of value.entries()) {
|
|
248
|
+
if (!item || typeof item !== "object") {
|
|
249
|
+
throw new LAFSCommandError(
|
|
250
|
+
"E_ADVANCED_VALIDATION_MCP_ITEM",
|
|
251
|
+
`Invalid MCP operation at index ${index}`,
|
|
252
|
+
"Each operation must be an object with serverName and config."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const obj = item;
|
|
256
|
+
const serverName = obj.serverName;
|
|
257
|
+
const config = obj.config;
|
|
258
|
+
const scope = obj.scope;
|
|
259
|
+
if (typeof serverName !== "string" || serverName.length === 0) {
|
|
260
|
+
throw new LAFSCommandError(
|
|
261
|
+
"E_ADVANCED_VALIDATION_MCP_NAME",
|
|
262
|
+
`Invalid serverName at index ${index}`,
|
|
263
|
+
"Set serverName to a non-empty string."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
267
|
+
throw new LAFSCommandError(
|
|
268
|
+
"E_ADVANCED_VALIDATION_MCP_CONFIG",
|
|
269
|
+
`Invalid config at index ${index}`,
|
|
270
|
+
"Set config to an object matching McpServerConfig."
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (scope !== void 0 && scope !== "project" && scope !== "global") {
|
|
274
|
+
throw new LAFSCommandError(
|
|
275
|
+
"E_ADVANCED_VALIDATION_SCOPE",
|
|
276
|
+
`Invalid scope at index ${index}: ${String(scope)}`,
|
|
277
|
+
"Use scope value 'project' or 'global'."
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
operations.push({
|
|
281
|
+
serverName,
|
|
282
|
+
config,
|
|
283
|
+
...scope ? { scope } : {}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return operations;
|
|
287
|
+
}
|
|
288
|
+
async function readSkillOperations(path) {
|
|
289
|
+
const value = await readJsonFile(path);
|
|
290
|
+
if (!Array.isArray(value)) {
|
|
291
|
+
throw new LAFSCommandError(
|
|
292
|
+
"E_ADVANCED_VALIDATION_SKILL_ARRAY",
|
|
293
|
+
`Skill operations file must be a JSON array: ${path}`,
|
|
294
|
+
"Provide an array of objects with sourcePath and skillName fields."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const operations = [];
|
|
298
|
+
for (const [index, item] of value.entries()) {
|
|
299
|
+
if (!item || typeof item !== "object") {
|
|
300
|
+
throw new LAFSCommandError(
|
|
301
|
+
"E_ADVANCED_VALIDATION_SKILL_ITEM",
|
|
302
|
+
`Invalid skill operation at index ${index}`,
|
|
303
|
+
"Each operation must be an object with sourcePath and skillName."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const obj = item;
|
|
307
|
+
const sourcePath = obj.sourcePath;
|
|
308
|
+
const skillName = obj.skillName;
|
|
309
|
+
const isGlobal = obj.isGlobal;
|
|
310
|
+
if (typeof sourcePath !== "string" || sourcePath.length === 0) {
|
|
311
|
+
throw new LAFSCommandError(
|
|
312
|
+
"E_ADVANCED_VALIDATION_SKILL_SOURCE",
|
|
313
|
+
`Invalid sourcePath at index ${index}`,
|
|
314
|
+
"Set sourcePath to a non-empty string."
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (typeof skillName !== "string" || skillName.length === 0) {
|
|
318
|
+
throw new LAFSCommandError(
|
|
319
|
+
"E_ADVANCED_VALIDATION_SKILL_NAME",
|
|
320
|
+
`Invalid skillName at index ${index}`,
|
|
321
|
+
"Set skillName to a non-empty string."
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (isGlobal !== void 0 && typeof isGlobal !== "boolean") {
|
|
325
|
+
throw new LAFSCommandError(
|
|
326
|
+
"E_ADVANCED_VALIDATION_SKILL_SCOPE",
|
|
327
|
+
`Invalid isGlobal value at index ${index}`,
|
|
328
|
+
"Set isGlobal to true or false when provided."
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
operations.push({
|
|
332
|
+
sourcePath,
|
|
333
|
+
skillName,
|
|
334
|
+
...isGlobal !== void 0 ? { isGlobal } : {}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return operations;
|
|
338
|
+
}
|
|
339
|
+
async function readTextInput(inlineContent, filePath) {
|
|
340
|
+
if (inlineContent && filePath) {
|
|
341
|
+
throw new LAFSCommandError(
|
|
342
|
+
"E_ADVANCED_VALIDATION_INPUT_MODE",
|
|
343
|
+
"Provide either inline content or a content file, not both.",
|
|
344
|
+
"Use --content OR --content-file."
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (inlineContent) return inlineContent;
|
|
348
|
+
if (!filePath) return void 0;
|
|
349
|
+
try {
|
|
350
|
+
return await readFile(filePath, "utf-8");
|
|
351
|
+
} catch (error) {
|
|
352
|
+
throw new LAFSCommandError(
|
|
353
|
+
"E_ADVANCED_INPUT_TEXT",
|
|
354
|
+
`Failed to read content file: ${filePath}`,
|
|
355
|
+
"Confirm the file exists and is readable.",
|
|
356
|
+
true,
|
|
357
|
+
{ reason: error instanceof Error ? error.message : String(error) }
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/commands/advanced/providers.ts
|
|
363
|
+
function registerAdvancedProviders(parent) {
|
|
364
|
+
parent.command("providers").description("Select providers by priority using advanced wrapper logic").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--details", "Include full provider objects").action(async (opts) => runLafsCommand("advanced.providers", !opts.details, async () => {
|
|
365
|
+
const providers = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
366
|
+
const minTier = parsePriority(opts.minTier);
|
|
367
|
+
const selected = selectProvidersByMinimumPriority(providers, minTier);
|
|
368
|
+
return {
|
|
369
|
+
objective: "Filter providers by minimum priority tier",
|
|
370
|
+
constraints: {
|
|
371
|
+
minTier,
|
|
372
|
+
selectionMode: opts.all ? "registry" : "detected-or-explicit"
|
|
373
|
+
},
|
|
374
|
+
acceptanceCriteria: {
|
|
375
|
+
selectedCount: selected.length,
|
|
376
|
+
orderedByPriority: true
|
|
377
|
+
},
|
|
378
|
+
data: opts.details ? selected : selected.map((provider) => ({
|
|
379
|
+
id: provider.id,
|
|
380
|
+
priority: provider.priority,
|
|
381
|
+
status: provider.status,
|
|
382
|
+
configFormat: provider.configFormat
|
|
383
|
+
}))
|
|
384
|
+
};
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/commands/advanced/batch.ts
|
|
389
|
+
function registerAdvancedBatch(parent) {
|
|
390
|
+
parent.command("batch").description("Run rollback-capable batch install for MCP + skills").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--mcp-file <path>", "JSON file containing McpBatchOperation[]").option("--skills-file <path>", "JSON file containing SkillBatchOperation[]").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include detailed operation result").action(async (opts) => runLafsCommand("advanced.batch", !opts.details, async () => {
|
|
391
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
392
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
393
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
394
|
+
const mcp = opts.mcpFile ? await readMcpOperations(opts.mcpFile) : [];
|
|
395
|
+
const skills = opts.skillsFile ? await readSkillOperations(opts.skillsFile) : [];
|
|
396
|
+
if (mcp.length === 0 && skills.length === 0) {
|
|
397
|
+
throw new LAFSCommandError(
|
|
398
|
+
"E_ADVANCED_VALIDATION_NO_OPS",
|
|
399
|
+
"No operations provided.",
|
|
400
|
+
"Provide --mcp-file and/or --skills-file."
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (providers.length === 0) {
|
|
404
|
+
throw new LAFSCommandError(
|
|
405
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
406
|
+
"No target providers resolved for this batch operation.",
|
|
407
|
+
"Use --all or pass provider IDs with --agent."
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
const result = await installBatchWithRollback({
|
|
411
|
+
providers,
|
|
412
|
+
minimumPriority,
|
|
413
|
+
mcp,
|
|
414
|
+
skills,
|
|
415
|
+
projectDir: opts.projectDir
|
|
416
|
+
});
|
|
417
|
+
if (!result.success) {
|
|
418
|
+
throw new LAFSCommandError(
|
|
419
|
+
"E_ADVANCED_BATCH_FAILED",
|
|
420
|
+
result.error ?? "Batch operation failed.",
|
|
421
|
+
"Check rollbackErrors and input configs, then retry.",
|
|
422
|
+
true,
|
|
423
|
+
result
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
objective: "Install MCP and skills with rollback safety",
|
|
428
|
+
constraints: {
|
|
429
|
+
minimumPriority,
|
|
430
|
+
providerCount: providers.length,
|
|
431
|
+
mcpOps: mcp.length,
|
|
432
|
+
skillOps: skills.length
|
|
433
|
+
},
|
|
434
|
+
acceptanceCriteria: {
|
|
435
|
+
success: result.success,
|
|
436
|
+
rollbackPerformed: result.rollbackPerformed
|
|
437
|
+
},
|
|
438
|
+
data: opts.details ? result : {
|
|
439
|
+
providerCount: result.providerIds.length,
|
|
440
|
+
mcpApplied: result.mcpApplied,
|
|
441
|
+
skillsApplied: result.skillsApplied,
|
|
442
|
+
rollbackPerformed: result.rollbackPerformed
|
|
345
443
|
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} else {
|
|
388
|
-
console.error(pc2.red(`Skill not found in catalog: ${parsed.inferredName}`));
|
|
389
|
-
console.log(pc2.dim("Available skills: " + listSkills().join(", ")));
|
|
390
|
-
process.exit(1);
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
console.error(pc2.red(`Unsupported source type: ${parsed.type}`));
|
|
394
|
-
process.exit(1);
|
|
444
|
+
};
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/commands/advanced/conflicts.ts
|
|
449
|
+
function registerAdvancedConflicts(parent) {
|
|
450
|
+
parent.command("conflicts").description("Preflight MCP conflict detection across providers").requiredOption("--mcp-file <path>", "JSON file containing McpBatchOperation[]").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include full conflict list").action(async (opts) => runLafsCommand("advanced.conflicts", !opts.details, async () => {
|
|
451
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
452
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
453
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
454
|
+
const operations = await readMcpOperations(opts.mcpFile);
|
|
455
|
+
if (providers.length === 0) {
|
|
456
|
+
throw new LAFSCommandError(
|
|
457
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
458
|
+
"No target providers resolved for conflict detection.",
|
|
459
|
+
"Use --all or pass provider IDs with --agent."
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const conflicts = await detectMcpConfigConflicts(
|
|
463
|
+
providers,
|
|
464
|
+
operations,
|
|
465
|
+
opts.projectDir
|
|
466
|
+
);
|
|
467
|
+
const countByCode = conflicts.reduce((acc, conflict) => {
|
|
468
|
+
acc[conflict.code] = (acc[conflict.code] ?? 0) + 1;
|
|
469
|
+
return acc;
|
|
470
|
+
}, {});
|
|
471
|
+
return {
|
|
472
|
+
objective: "Detect MCP configuration conflicts before mutation",
|
|
473
|
+
constraints: {
|
|
474
|
+
minimumPriority,
|
|
475
|
+
providerCount: providers.length,
|
|
476
|
+
operationCount: operations.length
|
|
477
|
+
},
|
|
478
|
+
acceptanceCriteria: {
|
|
479
|
+
conflictCount: conflicts.length
|
|
480
|
+
},
|
|
481
|
+
data: opts.details ? conflicts : {
|
|
482
|
+
conflictCount: conflicts.length,
|
|
483
|
+
countByCode,
|
|
484
|
+
sample: conflicts.slice(0, 5)
|
|
395
485
|
}
|
|
486
|
+
};
|
|
487
|
+
}));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/commands/advanced/apply.ts
|
|
491
|
+
var VALID_POLICIES = /* @__PURE__ */ new Set(["fail", "skip", "overwrite"]);
|
|
492
|
+
function parsePolicy(value) {
|
|
493
|
+
if (!VALID_POLICIES.has(value)) {
|
|
494
|
+
throw new LAFSCommandError(
|
|
495
|
+
"E_ADVANCED_VALIDATION_POLICY",
|
|
496
|
+
`Invalid policy: ${value}`,
|
|
497
|
+
"Use one of: fail, skip, overwrite."
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
function registerAdvancedApply(parent) {
|
|
503
|
+
parent.command("apply").description("Apply MCP operations with configurable conflict policy").requiredOption("--mcp-file <path>", "JSON file containing McpBatchOperation[]").option("--policy <policy>", "Conflict policy: fail|skip|overwrite", "fail").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include detailed apply result").action(async (opts) => runLafsCommand("advanced.apply", !opts.details, async () => {
|
|
504
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
505
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
506
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
507
|
+
const operations = await readMcpOperations(opts.mcpFile);
|
|
508
|
+
const policy = parsePolicy(opts.policy);
|
|
509
|
+
if (providers.length === 0) {
|
|
510
|
+
throw new LAFSCommandError(
|
|
511
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
512
|
+
"No target providers resolved for apply operation.",
|
|
513
|
+
"Use --all or pass provider IDs with --agent."
|
|
514
|
+
);
|
|
396
515
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
516
|
+
const result = await applyMcpInstallWithPolicy(
|
|
517
|
+
providers,
|
|
518
|
+
operations,
|
|
519
|
+
policy,
|
|
520
|
+
opts.projectDir
|
|
521
|
+
);
|
|
522
|
+
if (policy === "fail" && result.conflicts.length > 0) {
|
|
523
|
+
throw new LAFSCommandError(
|
|
524
|
+
"E_ADVANCED_CONFLICTS_BLOCKING",
|
|
525
|
+
"Conflicts detected and policy is set to fail.",
|
|
526
|
+
"Run `caamp advanced conflicts` to inspect, or rerun with --policy skip/overwrite.",
|
|
527
|
+
true,
|
|
528
|
+
result
|
|
406
529
|
);
|
|
407
|
-
if (result.success) {
|
|
408
|
-
console.log(pc2.green(`
|
|
409
|
-
\u2713 Installed ${pc2.bold(skillName)}`));
|
|
410
|
-
console.log(` Canonical: ${pc2.dim(result.canonicalPath)}`);
|
|
411
|
-
console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
|
|
412
|
-
const isGlobal = sourceType === "package" ? true : opts.global ?? false;
|
|
413
|
-
await recordSkillInstall(
|
|
414
|
-
skillName,
|
|
415
|
-
sourceValue,
|
|
416
|
-
sourceValue,
|
|
417
|
-
sourceType,
|
|
418
|
-
result.linkedAgents,
|
|
419
|
-
result.canonicalPath,
|
|
420
|
-
isGlobal
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
if (result.errors.length > 0) {
|
|
424
|
-
console.log(pc2.yellow("\nWarnings:"));
|
|
425
|
-
for (const err of result.errors) {
|
|
426
|
-
console.log(` ${pc2.yellow("!")} ${err}`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
} finally {
|
|
430
|
-
if (cleanup) await cleanup();
|
|
431
530
|
}
|
|
432
|
-
|
|
531
|
+
const failedWrites = result.applied.filter((entry) => !entry.success);
|
|
532
|
+
if (failedWrites.length > 0) {
|
|
533
|
+
throw new LAFSCommandError(
|
|
534
|
+
"E_ADVANCED_APPLY_WRITE_FAILED",
|
|
535
|
+
"One or more MCP writes failed.",
|
|
536
|
+
"Check result details, fix provider config issues, and retry.",
|
|
537
|
+
true,
|
|
538
|
+
result
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
objective: "Apply MCP operations with policy-driven conflict handling",
|
|
543
|
+
constraints: {
|
|
544
|
+
policy,
|
|
545
|
+
minimumPriority,
|
|
546
|
+
providerCount: providers.length,
|
|
547
|
+
operationCount: operations.length
|
|
548
|
+
},
|
|
549
|
+
acceptanceCriteria: {
|
|
550
|
+
conflicts: result.conflicts.length,
|
|
551
|
+
writesSucceeded: result.applied.length
|
|
552
|
+
},
|
|
553
|
+
data: opts.details ? result : {
|
|
554
|
+
conflicts: result.conflicts.length,
|
|
555
|
+
applied: result.applied.length,
|
|
556
|
+
skipped: result.skipped.length
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}));
|
|
433
560
|
}
|
|
434
561
|
|
|
435
|
-
// src/commands/
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
562
|
+
// src/commands/advanced/instructions.ts
|
|
563
|
+
function registerAdvancedInstructions(parent) {
|
|
564
|
+
parent.command("instructions").description("Single-operation instruction update across providers").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--scope <scope>", "Instruction scope: project|global", "project").option("--content <text>", "Inline content to inject").option("--content-file <path>", "File containing content to inject").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include detailed per-file actions").action(async (opts) => runLafsCommand("advanced.instructions", !opts.details, async () => {
|
|
565
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
566
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
567
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
568
|
+
const scope = opts.scope === "global" ? "global" : opts.scope === "project" ? "project" : null;
|
|
569
|
+
if (!scope) {
|
|
570
|
+
throw new LAFSCommandError(
|
|
571
|
+
"E_ADVANCED_VALIDATION_SCOPE",
|
|
572
|
+
`Invalid scope: ${opts.scope}`,
|
|
573
|
+
"Use --scope project or --scope global."
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
const content = await readTextInput(opts.content, opts.contentFile);
|
|
577
|
+
if (!content || content.trim().length === 0) {
|
|
578
|
+
throw new LAFSCommandError(
|
|
579
|
+
"E_ADVANCED_VALIDATION_CONTENT",
|
|
580
|
+
"Instruction content is required.",
|
|
581
|
+
"Provide --content or --content-file with non-empty text."
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (providers.length === 0) {
|
|
585
|
+
throw new LAFSCommandError(
|
|
586
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
587
|
+
"No target providers resolved for instruction update.",
|
|
588
|
+
"Use --all or pass provider IDs with --agent."
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
const summary = await updateInstructionsSingleOperation(
|
|
592
|
+
providers,
|
|
593
|
+
content,
|
|
594
|
+
scope,
|
|
595
|
+
opts.projectDir
|
|
596
|
+
);
|
|
597
|
+
return {
|
|
598
|
+
objective: "Update instruction files across providers in one operation",
|
|
599
|
+
constraints: {
|
|
600
|
+
scope,
|
|
601
|
+
minimumPriority,
|
|
602
|
+
providerCount: providers.length
|
|
603
|
+
},
|
|
604
|
+
acceptanceCriteria: {
|
|
605
|
+
updatedFiles: summary.updatedFiles
|
|
606
|
+
},
|
|
607
|
+
data: opts.details ? summary : {
|
|
608
|
+
updatedFiles: summary.updatedFiles,
|
|
609
|
+
files: summary.actions.map((entry) => ({
|
|
610
|
+
file: entry.file,
|
|
611
|
+
action: entry.action
|
|
612
|
+
}))
|
|
447
613
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
614
|
+
};
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/commands/advanced/configure.ts
|
|
619
|
+
function registerAdvancedConfigure(parent) {
|
|
620
|
+
parent.command("configure").description("Configure global + project scope for one provider in one operation").requiredOption("-a, --agent <name>", "Target provider ID or alias").option("--global-mcp-file <path>", "JSON file for global MCP operations").option("--project-mcp-file <path>", "JSON file for project MCP operations").option("--instruction <text>", "Instruction content for both scopes").option("--instruction-file <path>", "Instruction content file for both scopes").option("--instruction-global <text>", "Instruction content for global scope").option("--instruction-global-file <path>", "Instruction content file for global scope").option("--instruction-project <text>", "Instruction content for project scope").option("--instruction-project-file <path>", "Instruction content file for project scope").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include detailed write results").action(async (opts) => runLafsCommand("advanced.configure", !opts.details, async () => {
|
|
621
|
+
const provider = getProvider(opts.agent);
|
|
622
|
+
if (!provider) {
|
|
623
|
+
throw new LAFSCommandError(
|
|
624
|
+
"E_ADVANCED_PROVIDER_NOT_FOUND",
|
|
625
|
+
`Unknown provider: ${opts.agent}`,
|
|
626
|
+
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const globalMcp = opts.globalMcpFile ? await readMcpOperations(opts.globalMcpFile) : [];
|
|
630
|
+
const projectMcp = opts.projectMcpFile ? await readMcpOperations(opts.projectMcpFile) : [];
|
|
631
|
+
const sharedInstruction = await readTextInput(opts.instruction, opts.instructionFile);
|
|
632
|
+
const globalInstruction = await readTextInput(
|
|
633
|
+
opts.instructionGlobal,
|
|
634
|
+
opts.instructionGlobalFile
|
|
635
|
+
);
|
|
636
|
+
const projectInstruction = await readTextInput(
|
|
637
|
+
opts.instructionProject,
|
|
638
|
+
opts.instructionProjectFile
|
|
639
|
+
);
|
|
640
|
+
let instructionContent;
|
|
641
|
+
if (globalInstruction || projectInstruction) {
|
|
642
|
+
instructionContent = {
|
|
643
|
+
...globalInstruction ? { global: globalInstruction } : {},
|
|
644
|
+
...projectInstruction ? { project: projectInstruction } : {}
|
|
645
|
+
};
|
|
646
|
+
} else if (sharedInstruction) {
|
|
647
|
+
instructionContent = sharedInstruction;
|
|
648
|
+
}
|
|
649
|
+
if (globalMcp.length === 0 && projectMcp.length === 0 && !instructionContent) {
|
|
650
|
+
throw new LAFSCommandError(
|
|
651
|
+
"E_ADVANCED_VALIDATION_NO_OPS",
|
|
652
|
+
"No configuration operations were provided.",
|
|
653
|
+
"Provide MCP files and/or instruction content."
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
const result = await configureProviderGlobalAndProject(provider, {
|
|
657
|
+
globalMcp: globalMcp.map((entry) => ({
|
|
658
|
+
serverName: entry.serverName,
|
|
659
|
+
config: entry.config
|
|
660
|
+
})),
|
|
661
|
+
projectMcp: projectMcp.map((entry) => ({
|
|
662
|
+
serverName: entry.serverName,
|
|
663
|
+
config: entry.config
|
|
664
|
+
})),
|
|
665
|
+
instructionContent,
|
|
666
|
+
projectDir: opts.projectDir
|
|
667
|
+
});
|
|
668
|
+
const globalFailures = result.mcp.global.filter((entry) => !entry.success);
|
|
669
|
+
const projectFailures = result.mcp.project.filter((entry) => !entry.success);
|
|
670
|
+
if (globalFailures.length > 0 || projectFailures.length > 0) {
|
|
671
|
+
throw new LAFSCommandError(
|
|
672
|
+
"E_ADVANCED_CONFIGURE_FAILED",
|
|
673
|
+
"One or more MCP writes failed during configure operation.",
|
|
674
|
+
"Inspect the failed write entries and provider config paths, then retry.",
|
|
675
|
+
true,
|
|
676
|
+
result
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
objective: "Configure global and project settings in one operation",
|
|
681
|
+
constraints: {
|
|
682
|
+
provider: provider.id,
|
|
683
|
+
globalMcpOps: globalMcp.length,
|
|
684
|
+
projectMcpOps: projectMcp.length,
|
|
685
|
+
instructionMode: instructionContent ? typeof instructionContent === "string" ? "shared" : "scoped" : "none"
|
|
686
|
+
},
|
|
687
|
+
acceptanceCriteria: {
|
|
688
|
+
globalWrites: result.mcp.global.length,
|
|
689
|
+
projectWrites: result.mcp.project.length
|
|
690
|
+
},
|
|
691
|
+
data: opts.details ? result : {
|
|
692
|
+
providerId: result.providerId,
|
|
693
|
+
configPaths: result.configPaths,
|
|
694
|
+
globalWrites: result.mcp.global.length,
|
|
695
|
+
projectWrites: result.mcp.project.length,
|
|
696
|
+
instructionUpdates: {
|
|
697
|
+
global: result.instructions.global?.size ?? 0,
|
|
698
|
+
project: result.instructions.project?.size ?? 0
|
|
451
699
|
}
|
|
452
700
|
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (skills.length === 0) {
|
|
456
|
-
console.log(pc3.dim("No skills installed."));
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
console.log(pc3.bold("Installed skills:"));
|
|
460
|
-
for (const s of skills) {
|
|
461
|
-
console.log(` ${s}`);
|
|
462
|
-
}
|
|
463
|
-
console.log(pc3.dim("\nUse: caamp skills remove <name>"));
|
|
464
|
-
}
|
|
465
|
-
});
|
|
701
|
+
};
|
|
702
|
+
}));
|
|
466
703
|
}
|
|
467
704
|
|
|
468
|
-
// src/commands/
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
process.exit(1);
|
|
478
|
-
}
|
|
479
|
-
dirs = opts.global ? [resolveProviderSkillsDir(provider, "global")] : [resolveProviderSkillsDir(provider, "project")];
|
|
480
|
-
} else if (opts.global) {
|
|
481
|
-
const providers = getInstalledProviders();
|
|
482
|
-
dirs = providers.map((p) => resolveProviderSkillsDir(p, "global")).filter(Boolean);
|
|
483
|
-
} else {
|
|
484
|
-
const providers = getInstalledProviders();
|
|
485
|
-
dirs = providers.map((p) => resolveProviderSkillsDir(p, "project")).filter(Boolean);
|
|
486
|
-
}
|
|
487
|
-
const skills = await discoverSkillsMulti(dirs);
|
|
488
|
-
if (opts.json) {
|
|
489
|
-
console.log(JSON.stringify(skills, null, 2));
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
if (skills.length === 0) {
|
|
493
|
-
console.log(pc4.dim("No skills found."));
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
console.log(pc4.bold(`
|
|
497
|
-
${skills.length} skill(s) found:
|
|
498
|
-
`));
|
|
499
|
-
for (const skill of skills) {
|
|
500
|
-
console.log(` ${pc4.bold(skill.name.padEnd(30))} ${pc4.dim(skill.metadata.description ?? "")}`);
|
|
501
|
-
}
|
|
502
|
-
console.log();
|
|
503
|
-
});
|
|
705
|
+
// src/commands/advanced/index.ts
|
|
706
|
+
function registerAdvancedCommands(program2) {
|
|
707
|
+
const advanced = program2.command("advanced").description("LAFS-compliant wrappers for advanced orchestration APIs");
|
|
708
|
+
registerAdvancedProviders(advanced);
|
|
709
|
+
registerAdvancedBatch(advanced);
|
|
710
|
+
registerAdvancedConflicts(advanced);
|
|
711
|
+
registerAdvancedApply(advanced);
|
|
712
|
+
registerAdvancedInstructions(advanced);
|
|
713
|
+
registerAdvancedConfigure(advanced);
|
|
504
714
|
}
|
|
505
715
|
|
|
506
|
-
// src/commands/
|
|
507
|
-
import
|
|
508
|
-
import {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
super(message);
|
|
516
|
-
this.code = code;
|
|
517
|
-
this.name = "SkillsFindValidationError";
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
function registerSkillsFind(parent) {
|
|
521
|
-
parent.command("find").description("Search marketplace for skills").argument("[query]", "Search query").option("--recommend", "Recommend skills from constraints").option("--top <n>", "Number of recommendation candidates", "3").option("--must-have <term>", "Required criteria term", (value, previous) => [...previous, value], []).option("--prefer <term>", "Preferred criteria term", (value, previous) => [...previous, value], []).option("--exclude <term>", "Excluded criteria term", (value, previous) => [...previous, value], []).option("--details", "Include expanded machine output").option("--human", "Force human-readable output").option("--json", "Output as JSON").option("--select <indexes>", "Pre-select recommendation ranks (comma-separated)").option("-l, --limit <n>", "Max results", "20").action(async (query, opts) => {
|
|
522
|
-
const operation = opts.recommend ? "skills.find.recommend" : "skills.find.search";
|
|
523
|
-
const details = Boolean(opts.details);
|
|
524
|
-
const mvi = !details;
|
|
525
|
-
let format;
|
|
526
|
-
try {
|
|
527
|
-
format = resolveOutputFormat({
|
|
528
|
-
jsonFlag: opts.json ?? false,
|
|
529
|
-
humanFlag: opts.human ?? false,
|
|
530
|
-
projectDefault: opts.recommend ? "json" : "human"
|
|
531
|
-
}).format;
|
|
532
|
-
} catch (error) {
|
|
533
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
534
|
-
if (opts.json) {
|
|
535
|
-
emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
|
|
536
|
-
} else {
|
|
537
|
-
console.error(pc5.red(message));
|
|
538
|
-
}
|
|
716
|
+
// src/commands/config.ts
|
|
717
|
+
import pc from "picocolors";
|
|
718
|
+
import { existsSync } from "fs";
|
|
719
|
+
function registerConfigCommand(program2) {
|
|
720
|
+
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) => {
|
|
722
|
+
const provider = getProvider(providerId);
|
|
723
|
+
if (!provider) {
|
|
724
|
+
console.error(pc.red(`Provider not found: ${providerId}`));
|
|
539
725
|
process.exit(1);
|
|
540
726
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
validateCriteriaConflicts(mustHave, prefer, exclude);
|
|
548
|
-
const selectedRanks = parseSelectList(opts.select);
|
|
549
|
-
const seedQuery = buildSeedQuery(query, mustHave, prefer, exclude);
|
|
550
|
-
const recommendation = await recommendSkills(
|
|
551
|
-
seedQuery,
|
|
552
|
-
{
|
|
553
|
-
mustHave,
|
|
554
|
-
prefer,
|
|
555
|
-
exclude
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
top,
|
|
559
|
-
includeDetails: details
|
|
560
|
-
}
|
|
561
|
-
);
|
|
562
|
-
const options = normalizeRecommendationOptions(recommendation.ranking, details);
|
|
563
|
-
validateSelectedRanks(selectedRanks, options.length);
|
|
564
|
-
const selected = selectedRanks.length > 0 ? options.filter((option) => selectedRanks.includes(option.rank)) : [];
|
|
565
|
-
if (format === "json") {
|
|
566
|
-
const result = formatSkillRecommendations(recommendation, { mode: "json", details });
|
|
567
|
-
const resultOptions = Array.isArray(result.options) ? result.options : [];
|
|
568
|
-
const selectedObjects = resultOptions.filter(
|
|
569
|
-
(option) => selectedRanks.includes(Number(option.rank ?? 0))
|
|
570
|
-
);
|
|
571
|
-
const envelope = buildEnvelope(
|
|
572
|
-
operation,
|
|
573
|
-
mvi,
|
|
574
|
-
{
|
|
575
|
-
...result,
|
|
576
|
-
selected: selectedObjects
|
|
577
|
-
},
|
|
578
|
-
null
|
|
579
|
-
);
|
|
580
|
-
console.log(JSON.stringify(envelope, null, 2));
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
const human = formatSkillRecommendations(recommendation, { mode: "human", details });
|
|
584
|
-
console.log(human);
|
|
585
|
-
if (selected.length > 0) {
|
|
586
|
-
console.log(`Selected: ${selected.map((option) => option.scopedName).join(", ")}`);
|
|
587
|
-
}
|
|
588
|
-
return;
|
|
589
|
-
} catch (error) {
|
|
590
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
591
|
-
const errorCode = error instanceof SkillsFindValidationError ? error.code : error.code ?? RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
|
|
592
|
-
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";
|
|
593
|
-
if (format === "json") {
|
|
594
|
-
emitJsonError(operation, mvi, errorCode, message, category, {
|
|
595
|
-
query: query ?? null
|
|
596
|
-
});
|
|
597
|
-
} else {
|
|
598
|
-
console.error(pc5.red(`Recommendation failed: ${message}`));
|
|
599
|
-
}
|
|
600
|
-
process.exit(1);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
if (!query) {
|
|
604
|
-
console.log(pc5.dim("Usage: caamp skills find <query>"));
|
|
727
|
+
const configPath = resolveProviderConfigPath(
|
|
728
|
+
provider,
|
|
729
|
+
opts.global ? "global" : "project"
|
|
730
|
+
) ?? provider.configPathGlobal;
|
|
731
|
+
if (!existsSync(configPath)) {
|
|
732
|
+
console.log(pc.dim(`No config file at: ${configPath}`));
|
|
605
733
|
return;
|
|
606
734
|
}
|
|
607
|
-
const limit = parseInt(opts.limit, 10);
|
|
608
|
-
const client = new MarketplaceClient();
|
|
609
|
-
if (format === "human") {
|
|
610
|
-
console.log(pc5.dim(`Searching marketplaces for "${query}"...
|
|
611
|
-
`));
|
|
612
|
-
}
|
|
613
|
-
let results;
|
|
614
735
|
try {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (format === "json") {
|
|
619
|
-
console.log(JSON.stringify({ error: message }));
|
|
736
|
+
const data = await readConfig(configPath, provider.configFormat);
|
|
737
|
+
if (opts.json) {
|
|
738
|
+
console.log(JSON.stringify(data, null, 2));
|
|
620
739
|
} else {
|
|
621
|
-
console.
|
|
740
|
+
console.log(pc.bold(`
|
|
741
|
+
${provider.toolName} config (${configPath}):
|
|
742
|
+
`));
|
|
743
|
+
console.log(JSON.stringify(data, null, 2));
|
|
622
744
|
}
|
|
745
|
+
} catch (err) {
|
|
746
|
+
console.error(pc.red(`Error reading config: ${err instanceof Error ? err.message : String(err)}`));
|
|
623
747
|
process.exit(1);
|
|
624
748
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
return;
|
|
749
|
+
});
|
|
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) => {
|
|
751
|
+
const provider = getProvider(providerId);
|
|
752
|
+
if (!provider) {
|
|
753
|
+
console.error(pc.red(`Provider not found: ${providerId}`));
|
|
754
|
+
process.exit(1);
|
|
632
755
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
756
|
+
if (scope === "global") {
|
|
757
|
+
console.log(provider.configPathGlobal);
|
|
758
|
+
} else {
|
|
759
|
+
const projectPath = resolveProviderConfigPath(provider, "project");
|
|
760
|
+
if (projectPath) {
|
|
761
|
+
console.log(projectPath);
|
|
762
|
+
} else {
|
|
763
|
+
console.log(pc.dim(`${provider.toolName} has no project-level config`));
|
|
764
|
+
console.log(provider.configPathGlobal);
|
|
765
|
+
}
|
|
639
766
|
}
|
|
640
|
-
console.log(pc5.dim("Install with: caamp skills install <scopedName>"));
|
|
641
767
|
});
|
|
642
768
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
769
|
+
|
|
770
|
+
// src/commands/doctor.ts
|
|
771
|
+
import pc2 from "picocolors";
|
|
772
|
+
import { execFileSync } from "child_process";
|
|
773
|
+
import { existsSync as existsSync2, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
774
|
+
import { homedir } from "os";
|
|
775
|
+
import { join as join2 } from "path";
|
|
776
|
+
|
|
777
|
+
// src/core/version.ts
|
|
778
|
+
import { readFileSync } from "fs";
|
|
779
|
+
import { dirname, join } from "path";
|
|
780
|
+
import { fileURLToPath } from "url";
|
|
781
|
+
var cachedVersion = null;
|
|
782
|
+
function getCaampVersion() {
|
|
783
|
+
if (cachedVersion) return cachedVersion;
|
|
784
|
+
try {
|
|
785
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
786
|
+
const packageJsonPath = join(currentDir, "..", "package.json");
|
|
787
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
788
|
+
cachedVersion = packageJson.version ?? "0.0.0";
|
|
789
|
+
} catch {
|
|
790
|
+
cachedVersion = "0.0.0";
|
|
791
|
+
}
|
|
792
|
+
return cachedVersion;
|
|
646
793
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
794
|
+
|
|
795
|
+
// src/commands/doctor.ts
|
|
796
|
+
function getNodeVersion() {
|
|
797
|
+
return process.version;
|
|
650
798
|
}
|
|
651
|
-
function
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
799
|
+
function getNpmVersion() {
|
|
800
|
+
try {
|
|
801
|
+
return execFileSync("npm", ["--version"], { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
802
|
+
} catch {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function checkEnvironment() {
|
|
807
|
+
const checks = [];
|
|
808
|
+
checks.push({ label: `Node.js ${getNodeVersion()}`, status: "pass" });
|
|
809
|
+
const npmVersion = getNpmVersion();
|
|
810
|
+
if (npmVersion) {
|
|
811
|
+
checks.push({ label: `npm ${npmVersion}`, status: "pass" });
|
|
812
|
+
} else {
|
|
813
|
+
checks.push({ label: "npm not found", status: "warn" });
|
|
814
|
+
}
|
|
815
|
+
checks.push({ label: `CAAMP v${getCaampVersion()}`, status: "pass" });
|
|
816
|
+
checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
|
|
817
|
+
return { name: "Environment", checks };
|
|
818
|
+
}
|
|
819
|
+
function checkRegistry() {
|
|
820
|
+
const checks = [];
|
|
821
|
+
try {
|
|
822
|
+
const providers = getAllProviders();
|
|
823
|
+
const count = getProviderCount();
|
|
824
|
+
checks.push({ label: `${count} providers loaded`, status: "pass" });
|
|
825
|
+
const malformed = [];
|
|
826
|
+
for (const p of providers) {
|
|
827
|
+
if (!p.id || !p.toolName || !p.configKey || !p.configFormat) {
|
|
828
|
+
malformed.push(p.id || "(unknown)");
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (malformed.length === 0) {
|
|
832
|
+
checks.push({ label: "All entries valid", status: "pass" });
|
|
833
|
+
} else {
|
|
834
|
+
checks.push({
|
|
835
|
+
label: `${malformed.length} malformed entries`,
|
|
836
|
+
status: "fail",
|
|
837
|
+
detail: malformed.join(", ")
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
} catch (err) {
|
|
841
|
+
checks.push({
|
|
842
|
+
label: "Failed to load registry",
|
|
843
|
+
status: "fail",
|
|
844
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
845
|
+
});
|
|
655
846
|
}
|
|
656
|
-
return
|
|
847
|
+
return { name: "Registry", checks };
|
|
657
848
|
}
|
|
658
|
-
function
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
849
|
+
function checkInstalledProviders() {
|
|
850
|
+
const checks = [];
|
|
851
|
+
try {
|
|
852
|
+
const results = detectAllProviders();
|
|
853
|
+
const installed = results.filter((r) => r.installed);
|
|
854
|
+
checks.push({ label: `${installed.length} found`, status: "pass" });
|
|
855
|
+
for (const r of installed) {
|
|
856
|
+
const methods = r.methods.join(", ");
|
|
857
|
+
checks.push({ label: `${r.provider.toolName} (${methods})`, status: "pass" });
|
|
858
|
+
}
|
|
859
|
+
} catch (err) {
|
|
860
|
+
checks.push({
|
|
861
|
+
label: "Detection failed",
|
|
862
|
+
status: "fail",
|
|
863
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
return { name: "Installed Providers", checks };
|
|
662
867
|
}
|
|
663
|
-
function
|
|
664
|
-
|
|
665
|
-
|
|
868
|
+
function checkSkillSymlinks() {
|
|
869
|
+
const checks = [];
|
|
870
|
+
const canonicalDir = CANONICAL_SKILLS_DIR;
|
|
871
|
+
if (!existsSync2(canonicalDir)) {
|
|
872
|
+
checks.push({ label: "0 canonical skills", status: "pass" });
|
|
873
|
+
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
874
|
+
return { name: "Skills", checks };
|
|
666
875
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
876
|
+
let canonicalCount = 0;
|
|
877
|
+
let canonicalNames = [];
|
|
878
|
+
try {
|
|
879
|
+
canonicalNames = readdirSync(canonicalDir).filter((name) => {
|
|
880
|
+
const full = join2(canonicalDir, name);
|
|
881
|
+
try {
|
|
882
|
+
const stat = lstatSync(full);
|
|
883
|
+
return stat.isDirectory() || stat.isSymbolicLink();
|
|
884
|
+
} catch {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
canonicalCount = canonicalNames.length;
|
|
889
|
+
checks.push({ label: `${canonicalCount} canonical skills`, status: "pass" });
|
|
890
|
+
} catch {
|
|
891
|
+
checks.push({ label: "Cannot read skills directory", status: "warn" });
|
|
892
|
+
return { name: "Skills", checks };
|
|
670
893
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
);
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
894
|
+
const broken = [];
|
|
895
|
+
const stale = [];
|
|
896
|
+
const results = detectAllProviders();
|
|
897
|
+
const installed = results.filter((r) => r.installed);
|
|
898
|
+
for (const r of installed) {
|
|
899
|
+
const provider = r.provider;
|
|
900
|
+
const skillDir = provider.pathSkills;
|
|
901
|
+
if (!existsSync2(skillDir)) continue;
|
|
902
|
+
try {
|
|
903
|
+
const entries = readdirSync(skillDir);
|
|
904
|
+
for (const entry of entries) {
|
|
905
|
+
const fullPath = join2(skillDir, entry);
|
|
906
|
+
try {
|
|
907
|
+
const stat = lstatSync(fullPath);
|
|
908
|
+
if (!stat.isSymbolicLink()) continue;
|
|
909
|
+
if (!existsSync2(fullPath)) {
|
|
910
|
+
broken.push(`${provider.id}/${entry}`);
|
|
911
|
+
} else {
|
|
912
|
+
const target = readlinkSync(fullPath);
|
|
913
|
+
const isCanonical = target.includes("/.agents/skills/") || target.includes("\\.agents\\skills\\");
|
|
914
|
+
if (!isCanonical) {
|
|
915
|
+
stale.push(`${provider.id}/${entry}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
690
919
|
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
function validateCriteriaConflicts(mustHave, prefer, exclude) {
|
|
696
|
-
const overlap = mustHave.filter((term) => exclude.includes(term));
|
|
697
|
-
if (overlap.length > 0) {
|
|
698
|
-
throw new SkillsFindValidationError(
|
|
699
|
-
RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
700
|
-
"A criteria term cannot be both required and excluded."
|
|
701
|
-
);
|
|
920
|
+
}
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
702
923
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
924
|
+
if (broken.length === 0) {
|
|
925
|
+
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
926
|
+
} else {
|
|
927
|
+
checks.push({
|
|
928
|
+
label: `${broken.length} broken symlink${broken.length !== 1 ? "s" : ""}`,
|
|
929
|
+
status: "warn",
|
|
930
|
+
detail: broken.join(", ")
|
|
931
|
+
});
|
|
709
932
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
933
|
+
if (stale.length === 0) {
|
|
934
|
+
checks.push({ label: "No stale symlinks", status: "pass" });
|
|
935
|
+
} else {
|
|
936
|
+
checks.push({
|
|
937
|
+
label: `${stale.length} stale symlink${stale.length !== 1 ? "s" : ""} (not pointing to ~/.agents/skills/)`,
|
|
938
|
+
status: "warn",
|
|
939
|
+
detail: stale.join(", ")
|
|
940
|
+
});
|
|
719
941
|
}
|
|
942
|
+
return { name: "Skills", checks };
|
|
720
943
|
}
|
|
721
|
-
function
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
contextVersion: 0
|
|
734
|
-
},
|
|
735
|
-
success: error === null,
|
|
736
|
-
result,
|
|
737
|
-
error,
|
|
738
|
-
page: null
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
function emitJsonError(operation, mvi, code, message, category, details = {}) {
|
|
742
|
-
const envelope = buildEnvelope(operation, mvi, null, {
|
|
743
|
-
code,
|
|
744
|
-
message,
|
|
745
|
-
category,
|
|
746
|
-
retryable: false,
|
|
747
|
-
retryAfterMs: null,
|
|
748
|
-
details
|
|
749
|
-
});
|
|
750
|
-
console.error(JSON.stringify(envelope, null, 2));
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// src/commands/skills/check.ts
|
|
754
|
-
import pc6 from "picocolors";
|
|
755
|
-
function registerSkillsCheck(parent) {
|
|
756
|
-
parent.command("check").description("Check for available skill updates").option("--json", "Output as JSON").action(async (opts) => {
|
|
757
|
-
const tracked = await getTrackedSkills();
|
|
758
|
-
const entries = Object.entries(tracked);
|
|
759
|
-
if (entries.length === 0) {
|
|
760
|
-
console.log(pc6.dim("No tracked skills."));
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
console.log(pc6.dim(`Checking ${entries.length} skill(s) for updates...
|
|
764
|
-
`));
|
|
765
|
-
const results = [];
|
|
766
|
-
for (const [name, entry] of entries) {
|
|
767
|
-
const update = await checkSkillUpdate(name);
|
|
768
|
-
results.push({ name, entry, ...update });
|
|
944
|
+
async function checkLockFile() {
|
|
945
|
+
const checks = [];
|
|
946
|
+
try {
|
|
947
|
+
const lock = await readLockFile();
|
|
948
|
+
checks.push({ label: "Lock file valid", status: "pass" });
|
|
949
|
+
const lockSkillNames = Object.keys(lock.skills);
|
|
950
|
+
checks.push({ label: `${lockSkillNames.length} skill entries`, status: "pass" });
|
|
951
|
+
const orphaned = [];
|
|
952
|
+
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
953
|
+
if (entry.canonicalPath && !existsSync2(entry.canonicalPath)) {
|
|
954
|
+
orphaned.push(name);
|
|
955
|
+
}
|
|
769
956
|
}
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
957
|
+
if (orphaned.length === 0) {
|
|
958
|
+
checks.push({ label: "0 orphaned entries", status: "pass" });
|
|
959
|
+
} else {
|
|
960
|
+
checks.push({
|
|
961
|
+
label: `${orphaned.length} orphaned skill${orphaned.length !== 1 ? "s" : ""} (in lock, missing from disk)`,
|
|
962
|
+
status: "warn",
|
|
963
|
+
detail: orphaned.join(", ")
|
|
964
|
+
});
|
|
773
965
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
966
|
+
const canonicalDir = CANONICAL_SKILLS_DIR;
|
|
967
|
+
if (existsSync2(canonicalDir)) {
|
|
968
|
+
const onDisk = readdirSync(canonicalDir).filter((name) => {
|
|
969
|
+
try {
|
|
970
|
+
const stat = lstatSync(join2(canonicalDir, name));
|
|
971
|
+
return stat.isDirectory() || stat.isSymbolicLink();
|
|
972
|
+
} catch {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
const untracked = onDisk.filter((name) => !lock.skills[name]);
|
|
977
|
+
if (untracked.length === 0) {
|
|
978
|
+
checks.push({ label: "0 untracked skills", status: "pass" });
|
|
782
979
|
} else {
|
|
783
|
-
|
|
980
|
+
checks.push({
|
|
981
|
+
label: `${untracked.length} untracked skill${untracked.length !== 1 ? "s" : ""} (on disk, not in lock)`,
|
|
982
|
+
status: "warn",
|
|
983
|
+
detail: untracked.join(", ")
|
|
984
|
+
});
|
|
784
985
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
986
|
+
}
|
|
987
|
+
const results = detectAllProviders();
|
|
988
|
+
const installed = results.filter((r) => r.installed);
|
|
989
|
+
const mismatches = [];
|
|
990
|
+
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
991
|
+
if (!entry.agents || entry.agents.length === 0) continue;
|
|
992
|
+
for (const agentId of entry.agents) {
|
|
993
|
+
const provider = installed.find((r) => r.provider.id === agentId);
|
|
994
|
+
if (!provider) continue;
|
|
995
|
+
const linkPath = join2(provider.provider.pathSkills, name);
|
|
996
|
+
if (!existsSync2(linkPath)) {
|
|
997
|
+
mismatches.push(`${name} missing from ${agentId}`);
|
|
793
998
|
}
|
|
794
999
|
}
|
|
795
|
-
console.log(` ${pc6.dim(`source: ${r.entry.source}`)}`);
|
|
796
|
-
console.log(` ${pc6.dim(`agents: ${r.entry.agents.join(", ")}`)}`);
|
|
797
|
-
console.log();
|
|
798
1000
|
}
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
console.log(pc6.dim("Run `caamp skills update` to update all."));
|
|
1001
|
+
if (mismatches.length === 0) {
|
|
1002
|
+
checks.push({ label: "Lock agent-lists match symlinks", status: "pass" });
|
|
802
1003
|
} else {
|
|
803
|
-
|
|
1004
|
+
checks.push({
|
|
1005
|
+
label: `${mismatches.length} agent-list mismatch${mismatches.length !== 1 ? "es" : ""}`,
|
|
1006
|
+
status: "warn",
|
|
1007
|
+
detail: mismatches.slice(0, 5).join(", ") + (mismatches.length > 5 ? ` (+${mismatches.length - 5} more)` : "")
|
|
1008
|
+
});
|
|
804
1009
|
}
|
|
805
|
-
})
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
checks.push({
|
|
1012
|
+
label: "Failed to read lock file",
|
|
1013
|
+
status: "fail",
|
|
1014
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return { name: "Lock File", checks };
|
|
806
1018
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
if (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const result = await checkSkillUpdate(name);
|
|
822
|
-
if (result.hasUpdate) {
|
|
823
|
-
outdated.push({
|
|
824
|
-
name,
|
|
825
|
-
currentVersion: result.currentVersion,
|
|
826
|
-
latestVersion: result.latestVersion
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
if (outdated.length === 0) {
|
|
831
|
-
console.log(pc7.green("\nAll skills are up to date."));
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
console.log(pc7.yellow(`
|
|
835
|
-
${outdated.length} skill(s) have updates available:
|
|
836
|
-
`));
|
|
837
|
-
for (const skill of outdated) {
|
|
838
|
-
const current = skill.currentVersion?.slice(0, 12) ?? "?";
|
|
839
|
-
const latest = skill.latestVersion ?? "?";
|
|
840
|
-
console.log(` ${pc7.bold(skill.name)} ${pc7.dim(current)} ${pc7.dim("->")} ${pc7.cyan(latest)}`);
|
|
1019
|
+
async function checkConfigFiles() {
|
|
1020
|
+
const checks = [];
|
|
1021
|
+
const results = detectAllProviders();
|
|
1022
|
+
const installed = results.filter((r) => r.installed);
|
|
1023
|
+
for (const r of installed) {
|
|
1024
|
+
const provider = r.provider;
|
|
1025
|
+
const configPath = provider.configPathGlobal;
|
|
1026
|
+
if (!existsSync2(configPath)) {
|
|
1027
|
+
checks.push({
|
|
1028
|
+
label: `${provider.id}: no config file found`,
|
|
1029
|
+
status: "warn",
|
|
1030
|
+
detail: configPath
|
|
1031
|
+
});
|
|
1032
|
+
continue;
|
|
841
1033
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
1034
|
+
try {
|
|
1035
|
+
await readConfig(configPath, provider.configFormat);
|
|
1036
|
+
const relPath = configPath.replace(homedir(), "~");
|
|
1037
|
+
checks.push({
|
|
1038
|
+
label: `${provider.id}: ${relPath} readable`,
|
|
1039
|
+
status: "pass"
|
|
1040
|
+
});
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
checks.push({
|
|
1043
|
+
label: `${provider.id}: config parse error`,
|
|
1044
|
+
status: "fail",
|
|
1045
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
847
1046
|
});
|
|
848
|
-
rl.close();
|
|
849
|
-
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
850
|
-
console.log(pc7.dim("Update cancelled."));
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
1047
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
);
|
|
890
|
-
if (installResult.success) {
|
|
891
|
-
await recordSkillInstall(
|
|
892
|
-
skill.name,
|
|
893
|
-
entry.scopedName,
|
|
894
|
-
entry.source,
|
|
895
|
-
entry.sourceType,
|
|
896
|
-
installResult.linkedAgents,
|
|
897
|
-
installResult.canonicalPath,
|
|
898
|
-
entry.isGlobal,
|
|
899
|
-
entry.projectDir,
|
|
900
|
-
skill.latestVersion
|
|
901
|
-
);
|
|
902
|
-
console.log(pc7.green(` Updated ${pc7.bold(skill.name)}`));
|
|
903
|
-
successCount++;
|
|
904
|
-
} else {
|
|
905
|
-
console.log(pc7.red(` Failed to update ${skill.name}: no agents linked`));
|
|
906
|
-
failCount++;
|
|
907
|
-
}
|
|
908
|
-
if (installResult.errors.length > 0) {
|
|
909
|
-
for (const err of installResult.errors) {
|
|
910
|
-
console.log(pc7.yellow(` ${err}`));
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
} finally {
|
|
914
|
-
if (cleanup) await cleanup();
|
|
915
|
-
}
|
|
916
|
-
} catch (err) {
|
|
917
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
918
|
-
console.log(pc7.red(` Failed to update ${skill.name}: ${msg}`));
|
|
919
|
-
failCount++;
|
|
1048
|
+
}
|
|
1049
|
+
if (installed.length === 0) {
|
|
1050
|
+
checks.push({ label: "No installed providers to check", status: "pass" });
|
|
1051
|
+
}
|
|
1052
|
+
return { name: "Config Files", checks };
|
|
1053
|
+
}
|
|
1054
|
+
function formatSection(section) {
|
|
1055
|
+
const lines = [];
|
|
1056
|
+
lines.push(` ${pc2.bold(section.name)}`);
|
|
1057
|
+
for (const check of section.checks) {
|
|
1058
|
+
const icon = check.status === "pass" ? pc2.green("\u2713") : check.status === "warn" ? pc2.yellow("\u26A0") : pc2.red("\u2717");
|
|
1059
|
+
lines.push(` ${icon} ${check.label}`);
|
|
1060
|
+
if (check.detail) {
|
|
1061
|
+
lines.push(` ${pc2.dim(check.detail)}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return lines.join("\n");
|
|
1065
|
+
}
|
|
1066
|
+
function registerDoctorCommand(program2) {
|
|
1067
|
+
program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1068
|
+
const sections = [];
|
|
1069
|
+
sections.push(checkEnvironment());
|
|
1070
|
+
sections.push(checkRegistry());
|
|
1071
|
+
sections.push(checkInstalledProviders());
|
|
1072
|
+
sections.push(checkSkillSymlinks());
|
|
1073
|
+
sections.push(await checkLockFile());
|
|
1074
|
+
sections.push(await checkConfigFiles());
|
|
1075
|
+
let passed = 0;
|
|
1076
|
+
let warnings = 0;
|
|
1077
|
+
let errors = 0;
|
|
1078
|
+
for (const section of sections) {
|
|
1079
|
+
for (const check of section.checks) {
|
|
1080
|
+
if (check.status === "pass") passed++;
|
|
1081
|
+
else if (check.status === "warn") warnings++;
|
|
1082
|
+
else errors++;
|
|
920
1083
|
}
|
|
921
1084
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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;
|
|
925
1096
|
}
|
|
926
|
-
|
|
927
|
-
|
|
1097
|
+
console.log(pc2.bold("\ncaamp doctor\n"));
|
|
1098
|
+
for (const section of sections) {
|
|
1099
|
+
console.log(formatSection(section));
|
|
1100
|
+
console.log();
|
|
928
1101
|
}
|
|
929
|
-
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
import { join as join3 } from "path";
|
|
937
|
-
function registerSkillsInit(parent) {
|
|
938
|
-
parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").action(async (name, opts) => {
|
|
939
|
-
const skillName = name ?? "my-skill";
|
|
940
|
-
const skillDir = join3(opts.dir, skillName);
|
|
941
|
-
if (existsSync2(skillDir)) {
|
|
942
|
-
console.error(pc8.red(`Directory already exists: ${skillDir}`));
|
|
1102
|
+
const parts = [];
|
|
1103
|
+
parts.push(pc2.green(`${passed} checks passed`));
|
|
1104
|
+
if (warnings > 0) parts.push(pc2.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
|
|
1105
|
+
if (errors > 0) parts.push(pc2.red(`${errors} error${errors !== 1 ? "s" : ""}`));
|
|
1106
|
+
console.log(` ${pc2.bold("Summary")}: ${parts.join(", ")}`);
|
|
1107
|
+
console.log();
|
|
1108
|
+
if (errors > 0) {
|
|
943
1109
|
process.exit(1);
|
|
944
1110
|
}
|
|
945
|
-
await mkdir(skillDir, { recursive: true });
|
|
946
|
-
const template = `---
|
|
947
|
-
name: ${skillName}
|
|
948
|
-
description: Describe what this skill does and when to use it
|
|
949
|
-
license: MIT
|
|
950
|
-
metadata:
|
|
951
|
-
author: your-name
|
|
952
|
-
version: "1.0"
|
|
953
|
-
---
|
|
954
|
-
|
|
955
|
-
# ${skillName}
|
|
956
|
-
|
|
957
|
-
## When to use this skill
|
|
958
|
-
|
|
959
|
-
Describe the conditions under which an AI agent should activate this skill.
|
|
960
|
-
|
|
961
|
-
## Instructions
|
|
962
|
-
|
|
963
|
-
Provide detailed instructions for the AI agent here.
|
|
964
|
-
|
|
965
|
-
## Examples
|
|
966
|
-
|
|
967
|
-
Show example inputs and expected outputs.
|
|
968
|
-
`;
|
|
969
|
-
await writeFile(join3(skillDir, "SKILL.md"), template, "utf-8");
|
|
970
|
-
console.log(pc8.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
|
|
971
|
-
console.log(pc8.dim("\nNext steps:"));
|
|
972
|
-
console.log(pc8.dim(" 1. Edit SKILL.md with your instructions"));
|
|
973
|
-
console.log(pc8.dim(` 2. Validate: caamp skills validate ${join3(skillDir, "SKILL.md")}`));
|
|
974
|
-
console.log(pc8.dim(` 3. Install: caamp skills install ${skillDir}`));
|
|
975
1111
|
});
|
|
976
1112
|
}
|
|
977
1113
|
|
|
978
|
-
// src/commands/
|
|
979
|
-
import
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const stat = statSync(path);
|
|
988
|
-
let results;
|
|
989
|
-
if (stat.isFile()) {
|
|
990
|
-
results = [await scanFile(path)];
|
|
1114
|
+
// src/commands/instructions/inject.ts
|
|
1115
|
+
import pc3 from "picocolors";
|
|
1116
|
+
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) => {
|
|
1118
|
+
let providers;
|
|
1119
|
+
if (opts.all) {
|
|
1120
|
+
providers = getAllProviders();
|
|
1121
|
+
} else if (opts.agent.length > 0) {
|
|
1122
|
+
providers = opts.agent.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
991
1123
|
} else {
|
|
992
|
-
|
|
1124
|
+
providers = getInstalledProviders();
|
|
993
1125
|
}
|
|
994
|
-
if (
|
|
995
|
-
console.
|
|
1126
|
+
if (providers.length === 0) {
|
|
1127
|
+
console.error(pc3.red("No providers found."));
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
const content = opts.content ?? generateInjectionContent();
|
|
1131
|
+
const scope = opts.global ? "global" : "project";
|
|
1132
|
+
const groups = groupByInstructFile(providers);
|
|
1133
|
+
if (opts.dryRun) {
|
|
1134
|
+
console.log(pc3.bold("Dry run - would inject into:\n"));
|
|
1135
|
+
for (const [file, group] of groups) {
|
|
1136
|
+
console.log(` ${pc3.bold(file)}: ${group.map((p) => p.id).join(", ")}`);
|
|
1137
|
+
}
|
|
1138
|
+
console.log(pc3.dim(`
|
|
1139
|
+
Scope: ${scope}`));
|
|
1140
|
+
console.log(pc3.dim(` Content length: ${content.length} chars`));
|
|
996
1141
|
return;
|
|
997
1142
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1143
|
+
const results = await injectAll(providers, process.cwd(), scope, content);
|
|
1144
|
+
for (const [file, action] of results) {
|
|
1145
|
+
const icon = action === "created" ? pc3.green("+") : action === "updated" ? pc3.yellow("~") : pc3.blue("^");
|
|
1146
|
+
console.log(` ${icon} ${file} (${action})`);
|
|
1147
|
+
}
|
|
1148
|
+
console.log(pc3.bold(`
|
|
1149
|
+
${results.size} file(s) processed.`));
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/commands/instructions/check.ts
|
|
1154
|
+
import pc4 from "picocolors";
|
|
1155
|
+
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) => {
|
|
1157
|
+
let providers;
|
|
1158
|
+
if (opts.all) {
|
|
1159
|
+
providers = getAllProviders();
|
|
1160
|
+
} else if (opts.agent.length > 0) {
|
|
1161
|
+
providers = opts.agent.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
1162
|
+
} else {
|
|
1163
|
+
providers = getInstalledProviders();
|
|
1001
1164
|
}
|
|
1165
|
+
const scope = opts.global ? "global" : "project";
|
|
1166
|
+
const results = await checkAllInjections(providers, process.cwd(), scope);
|
|
1002
1167
|
if (opts.json) {
|
|
1003
1168
|
console.log(JSON.stringify(results, null, 2));
|
|
1004
1169
|
return;
|
|
1005
1170
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1171
|
+
console.log(pc4.bold(`
|
|
1172
|
+
Instruction file status (${scope}):
|
|
1173
|
+
`));
|
|
1174
|
+
for (const r of results) {
|
|
1175
|
+
let icon;
|
|
1176
|
+
let label;
|
|
1177
|
+
switch (r.status) {
|
|
1178
|
+
case "current":
|
|
1179
|
+
icon = pc4.green("\u2713");
|
|
1180
|
+
label = "current";
|
|
1181
|
+
break;
|
|
1182
|
+
case "outdated":
|
|
1183
|
+
icon = pc4.yellow("~");
|
|
1184
|
+
label = "outdated";
|
|
1185
|
+
break;
|
|
1186
|
+
case "missing":
|
|
1187
|
+
icon = pc4.red("\u2717");
|
|
1188
|
+
label = "missing";
|
|
1189
|
+
break;
|
|
1190
|
+
case "none":
|
|
1191
|
+
icon = pc4.dim("-");
|
|
1192
|
+
label = "no injection";
|
|
1193
|
+
break;
|
|
1022
1194
|
}
|
|
1195
|
+
console.log(` ${icon} ${r.file.padEnd(40)} ${label}`);
|
|
1023
1196
|
}
|
|
1024
|
-
console.log(
|
|
1025
|
-
${results.length} file(s) scanned, ${totalFindings} finding(s)`));
|
|
1026
|
-
if (!allPassed) {
|
|
1027
|
-
process.exit(1);
|
|
1028
|
-
}
|
|
1197
|
+
console.log();
|
|
1029
1198
|
});
|
|
1030
1199
|
}
|
|
1031
1200
|
|
|
1032
|
-
// src/commands/
|
|
1033
|
-
import
|
|
1034
|
-
function
|
|
1035
|
-
parent.command("
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1201
|
+
// src/commands/instructions/update.ts
|
|
1202
|
+
import pc5 from "picocolors";
|
|
1203
|
+
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) => {
|
|
1205
|
+
const providers = getInstalledProviders();
|
|
1206
|
+
const scope = opts.global ? "global" : "project";
|
|
1207
|
+
const content = generateInjectionContent();
|
|
1208
|
+
const checks = await checkAllInjections(providers, process.cwd(), scope, content);
|
|
1209
|
+
const needsUpdate = checks.filter((c) => c.status !== "current");
|
|
1210
|
+
if (needsUpdate.length === 0) {
|
|
1211
|
+
console.log(pc5.green("All instruction files are up to date."));
|
|
1039
1212
|
return;
|
|
1040
1213
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
console.log(
|
|
1045
|
-
}
|
|
1046
|
-
for (const issue of result.issues) {
|
|
1047
|
-
const icon = issue.level === "error" ? pc10.red("\u2717") : pc10.yellow("!");
|
|
1048
|
-
console.log(` ${icon} [${issue.field}] ${issue.message}`);
|
|
1214
|
+
console.log(pc5.bold(`${needsUpdate.length} file(s) need updating:
|
|
1215
|
+
`));
|
|
1216
|
+
for (const c of needsUpdate) {
|
|
1217
|
+
console.log(` ${c.file} (${c.status})`);
|
|
1049
1218
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1219
|
+
const providerIds = new Set(needsUpdate.map((c) => c.provider));
|
|
1220
|
+
const toUpdate = providers.filter((p) => providerIds.has(p.id));
|
|
1221
|
+
const results = await injectAll(toUpdate, process.cwd(), scope, content);
|
|
1222
|
+
console.log();
|
|
1223
|
+
for (const [file, action] of results) {
|
|
1224
|
+
console.log(` ${pc5.green("\u2713")} ${file} (${action})`);
|
|
1052
1225
|
}
|
|
1226
|
+
console.log(pc5.bold(`
|
|
1227
|
+
${results.size} file(s) updated.`));
|
|
1053
1228
|
});
|
|
1054
1229
|
}
|
|
1055
1230
|
|
|
1056
|
-
// src/commands/
|
|
1057
|
-
function
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
registerSkillsFind(skills);
|
|
1063
|
-
registerSkillsCheck(skills);
|
|
1064
|
-
registerSkillsUpdate(skills);
|
|
1065
|
-
registerSkillsInit(skills);
|
|
1066
|
-
registerSkillsAudit(skills);
|
|
1067
|
-
registerSkillsValidate(skills);
|
|
1231
|
+
// src/commands/instructions/index.ts
|
|
1232
|
+
function registerInstructionsCommands(program2) {
|
|
1233
|
+
const instructions = program2.command("instructions").description("Manage instruction file injections");
|
|
1234
|
+
registerInstructionsInject(instructions);
|
|
1235
|
+
registerInstructionsCheck(instructions);
|
|
1236
|
+
registerInstructionsUpdate(instructions);
|
|
1068
1237
|
}
|
|
1069
1238
|
|
|
1070
1239
|
// src/commands/mcp/install.ts
|
|
1071
|
-
import
|
|
1240
|
+
import pc6 from "picocolors";
|
|
1072
1241
|
function registerMcpInstall(parent) {
|
|
1073
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) => {
|
|
1074
1243
|
const parsed = parseSource(source);
|
|
@@ -1090,19 +1259,19 @@ function registerMcpInstall(parent) {
|
|
|
1090
1259
|
providers = getInstalledProviders();
|
|
1091
1260
|
}
|
|
1092
1261
|
if (providers.length === 0) {
|
|
1093
|
-
console.error(
|
|
1262
|
+
console.error(pc6.red("No target providers found."));
|
|
1094
1263
|
process.exit(1);
|
|
1095
1264
|
}
|
|
1096
1265
|
const scope = opts.global ? "global" : "project";
|
|
1097
1266
|
if (opts.dryRun) {
|
|
1098
|
-
console.log(
|
|
1099
|
-
console.log(` Server: ${
|
|
1267
|
+
console.log(pc6.bold("Dry run - would install:"));
|
|
1268
|
+
console.log(` Server: ${pc6.bold(serverName)}`);
|
|
1100
1269
|
console.log(` Config: ${JSON.stringify(config, null, 2)}`);
|
|
1101
1270
|
console.log(` Scope: ${scope}`);
|
|
1102
1271
|
console.log(` Providers: ${providers.map((p) => p.id).join(", ")}`);
|
|
1103
1272
|
return;
|
|
1104
1273
|
}
|
|
1105
|
-
console.log(
|
|
1274
|
+
console.log(pc6.dim(`Installing "${serverName}" to ${providers.length} provider(s)...
|
|
1106
1275
|
`));
|
|
1107
1276
|
const results = await installMcpServerToAll(
|
|
1108
1277
|
providers,
|
|
@@ -1112,9 +1281,9 @@ function registerMcpInstall(parent) {
|
|
|
1112
1281
|
);
|
|
1113
1282
|
for (const r of results) {
|
|
1114
1283
|
if (r.success) {
|
|
1115
|
-
console.log(` ${
|
|
1284
|
+
console.log(` ${pc6.green("\u2713")} ${r.provider.toolName.padEnd(22)} ${pc6.dim(r.configPath)}`);
|
|
1116
1285
|
} else {
|
|
1117
|
-
console.log(` ${
|
|
1286
|
+
console.log(` ${pc6.red("\u2717")} ${r.provider.toolName.padEnd(22)} ${pc6.red(r.error ?? "failed")}`);
|
|
1118
1287
|
}
|
|
1119
1288
|
}
|
|
1120
1289
|
const succeeded = results.filter((r) => r.success);
|
|
@@ -1127,13 +1296,13 @@ function registerMcpInstall(parent) {
|
|
|
1127
1296
|
opts.global ?? false
|
|
1128
1297
|
);
|
|
1129
1298
|
}
|
|
1130
|
-
console.log(
|
|
1299
|
+
console.log(pc6.bold(`
|
|
1131
1300
|
${succeeded.length}/${results.length} providers configured.`));
|
|
1132
1301
|
});
|
|
1133
1302
|
}
|
|
1134
1303
|
|
|
1135
1304
|
// src/commands/mcp/remove.ts
|
|
1136
|
-
import
|
|
1305
|
+
import pc7 from "picocolors";
|
|
1137
1306
|
function registerMcpRemove(parent) {
|
|
1138
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) => {
|
|
1139
1308
|
let providers;
|
|
@@ -1149,1275 +1318,1194 @@ function registerMcpRemove(parent) {
|
|
|
1149
1318
|
for (const provider of providers) {
|
|
1150
1319
|
const success = await removeMcpServer(provider, name, scope);
|
|
1151
1320
|
if (success) {
|
|
1152
|
-
console.log(` ${
|
|
1321
|
+
console.log(` ${pc7.green("\u2713")} Removed from ${provider.toolName}`);
|
|
1153
1322
|
removed++;
|
|
1154
1323
|
}
|
|
1155
1324
|
}
|
|
1156
1325
|
if (removed > 0) {
|
|
1157
1326
|
await removeMcpFromLock(name);
|
|
1158
|
-
console.log(
|
|
1327
|
+
console.log(pc7.green(`
|
|
1159
1328
|
\u2713 Removed "${name}" from ${removed} provider(s).`));
|
|
1160
1329
|
} else {
|
|
1161
|
-
console.log(
|
|
1330
|
+
console.log(pc7.yellow(`Server "${name}" not found in any provider config.`));
|
|
1162
1331
|
}
|
|
1163
1332
|
});
|
|
1164
1333
|
}
|
|
1165
1334
|
|
|
1166
1335
|
// src/commands/mcp/list.ts
|
|
1167
|
-
import
|
|
1336
|
+
import pc8 from "picocolors";
|
|
1168
1337
|
function registerMcpList(parent) {
|
|
1169
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) => {
|
|
1170
1339
|
const providers = opts.agent ? [getProvider(opts.agent)].filter((p) => p !== void 0) : getInstalledProviders();
|
|
1171
1340
|
const allEntries = [];
|
|
1172
1341
|
for (const provider of providers) {
|
|
1173
|
-
const scope = resolvePreferredConfigScope(provider, opts.global);
|
|
1174
|
-
const entries = await listMcpServers(provider, scope);
|
|
1175
|
-
allEntries.push(...entries);
|
|
1176
|
-
}
|
|
1177
|
-
if (opts.json) {
|
|
1178
|
-
console.log(JSON.stringify(allEntries.map((e) => ({
|
|
1179
|
-
provider: e.providerId,
|
|
1180
|
-
name: e.name,
|
|
1181
|
-
config: e.config
|
|
1182
|
-
})), null, 2));
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
if (allEntries.length === 0) {
|
|
1186
|
-
console.log(
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
console.log(
|
|
1190
|
-
${allEntries.length} MCP server(s) configured:
|
|
1191
|
-
`));
|
|
1192
|
-
for (const entry of allEntries) {
|
|
1193
|
-
console.log(` ${
|
|
1194
|
-
}
|
|
1195
|
-
console.log();
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// src/commands/mcp/detect.ts
|
|
1200
|
-
import pc14 from "picocolors";
|
|
1201
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1202
|
-
function registerMcpDetect(parent) {
|
|
1203
|
-
parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON").action(async (opts) => {
|
|
1204
|
-
const providers = getInstalledProviders();
|
|
1205
|
-
const detected = [];
|
|
1206
|
-
for (const provider of providers) {
|
|
1207
|
-
const globalPath = resolveConfigPath(provider, "global");
|
|
1208
|
-
const projectPath = resolveConfigPath(provider, "project");
|
|
1209
|
-
const globalEntries = await listMcpServers(provider, "global");
|
|
1210
|
-
const projectEntries = await listMcpServers(provider, "project");
|
|
1211
|
-
detected.push({
|
|
1212
|
-
provider: provider.id,
|
|
1213
|
-
hasGlobalConfig: globalPath !== null && existsSync4(globalPath),
|
|
1214
|
-
hasProjectConfig: projectPath !== null && existsSync4(projectPath),
|
|
1215
|
-
globalServers: globalEntries.map((e) => e.name),
|
|
1216
|
-
projectServers: projectEntries.map((e) => e.name)
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
if (opts.json) {
|
|
1220
|
-
console.log(JSON.stringify(detected, null, 2));
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
console.log(pc14.bold(`
|
|
1224
|
-
${detected.length} provider(s) with MCP support:
|
|
1225
|
-
`));
|
|
1226
|
-
for (const d of detected) {
|
|
1227
|
-
const globalIcon = d.hasGlobalConfig ? pc14.green("G") : pc14.dim("-");
|
|
1228
|
-
const projectIcon = d.hasProjectConfig ? pc14.green("P") : pc14.dim("-");
|
|
1229
|
-
const servers = [...d.globalServers, ...d.projectServers];
|
|
1230
|
-
const serverList = servers.length > 0 ? pc14.dim(servers.join(", ")) : pc14.dim("no servers");
|
|
1231
|
-
console.log(` [${globalIcon}${projectIcon}] ${pc14.bold(d.provider.padEnd(20))} ${serverList}`);
|
|
1232
|
-
}
|
|
1233
|
-
console.log(pc14.dim("\nG = global config, P = project config"));
|
|
1234
|
-
console.log();
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// src/commands/mcp/index.ts
|
|
1239
|
-
function registerMcpCommands(program2) {
|
|
1240
|
-
const mcp = program2.command("mcp").description("Manage MCP server configurations");
|
|
1241
|
-
registerMcpInstall(mcp);
|
|
1242
|
-
registerMcpRemove(mcp);
|
|
1243
|
-
registerMcpList(mcp);
|
|
1244
|
-
registerMcpDetect(mcp);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// src/commands/instructions/inject.ts
|
|
1248
|
-
import pc15 from "picocolors";
|
|
1249
|
-
function registerInstructionsInject(parent) {
|
|
1250
|
-
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) => {
|
|
1251
|
-
let providers;
|
|
1252
|
-
if (opts.all) {
|
|
1253
|
-
providers = getAllProviders();
|
|
1254
|
-
} else if (opts.agent.length > 0) {
|
|
1255
|
-
providers = opts.agent.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
1256
|
-
} else {
|
|
1257
|
-
providers = getInstalledProviders();
|
|
1258
|
-
}
|
|
1259
|
-
if (providers.length === 0) {
|
|
1260
|
-
console.error(pc15.red("No providers found."));
|
|
1261
|
-
process.exit(1);
|
|
1262
|
-
}
|
|
1263
|
-
const content = opts.content ?? generateInjectionContent();
|
|
1264
|
-
const scope = opts.global ? "global" : "project";
|
|
1265
|
-
const groups = groupByInstructFile(providers);
|
|
1266
|
-
if (opts.dryRun) {
|
|
1267
|
-
console.log(pc15.bold("Dry run - would inject into:\n"));
|
|
1268
|
-
for (const [file, group] of groups) {
|
|
1269
|
-
console.log(` ${pc15.bold(file)}: ${group.map((p) => p.id).join(", ")}`);
|
|
1270
|
-
}
|
|
1271
|
-
console.log(pc15.dim(`
|
|
1272
|
-
Scope: ${scope}`));
|
|
1273
|
-
console.log(pc15.dim(` Content length: ${content.length} chars`));
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
const results = await injectAll(providers, process.cwd(), scope, content);
|
|
1277
|
-
for (const [file, action] of results) {
|
|
1278
|
-
const icon = action === "created" ? pc15.green("+") : action === "updated" ? pc15.yellow("~") : pc15.blue("^");
|
|
1279
|
-
console.log(` ${icon} ${file} (${action})`);
|
|
1280
|
-
}
|
|
1281
|
-
console.log(pc15.bold(`
|
|
1282
|
-
${results.size} file(s) processed.`));
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// src/commands/instructions/check.ts
|
|
1287
|
-
import pc16 from "picocolors";
|
|
1288
|
-
function registerInstructionsCheck(parent) {
|
|
1289
|
-
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) => {
|
|
1290
|
-
let providers;
|
|
1291
|
-
if (opts.all) {
|
|
1292
|
-
providers = getAllProviders();
|
|
1293
|
-
} else if (opts.agent.length > 0) {
|
|
1294
|
-
providers = opts.agent.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
1295
|
-
} else {
|
|
1296
|
-
providers = getInstalledProviders();
|
|
1297
|
-
}
|
|
1298
|
-
const scope = opts.global ? "global" : "project";
|
|
1299
|
-
const results = await checkAllInjections(providers, process.cwd(), scope);
|
|
1300
|
-
if (opts.json) {
|
|
1301
|
-
console.log(JSON.stringify(results, null, 2));
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
console.log(pc16.bold(`
|
|
1305
|
-
Instruction file status (${scope}):
|
|
1306
|
-
`));
|
|
1307
|
-
for (const r of results) {
|
|
1308
|
-
let icon;
|
|
1309
|
-
let label;
|
|
1310
|
-
switch (r.status) {
|
|
1311
|
-
case "current":
|
|
1312
|
-
icon = pc16.green("\u2713");
|
|
1313
|
-
label = "current";
|
|
1314
|
-
break;
|
|
1315
|
-
case "outdated":
|
|
1316
|
-
icon = pc16.yellow("~");
|
|
1317
|
-
label = "outdated";
|
|
1318
|
-
break;
|
|
1319
|
-
case "missing":
|
|
1320
|
-
icon = pc16.red("\u2717");
|
|
1321
|
-
label = "missing";
|
|
1322
|
-
break;
|
|
1323
|
-
case "none":
|
|
1324
|
-
icon = pc16.dim("-");
|
|
1325
|
-
label = "no injection";
|
|
1326
|
-
break;
|
|
1327
|
-
}
|
|
1328
|
-
console.log(` ${icon} ${r.file.padEnd(40)} ${label}`);
|
|
1342
|
+
const scope = resolvePreferredConfigScope(provider, opts.global);
|
|
1343
|
+
const entries = await listMcpServers(provider, scope);
|
|
1344
|
+
allEntries.push(...entries);
|
|
1345
|
+
}
|
|
1346
|
+
if (opts.json) {
|
|
1347
|
+
console.log(JSON.stringify(allEntries.map((e) => ({
|
|
1348
|
+
provider: e.providerId,
|
|
1349
|
+
name: e.name,
|
|
1350
|
+
config: e.config
|
|
1351
|
+
})), null, 2));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (allEntries.length === 0) {
|
|
1355
|
+
console.log(pc8.dim("No MCP servers configured."));
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
console.log(pc8.bold(`
|
|
1359
|
+
${allEntries.length} MCP server(s) configured:
|
|
1360
|
+
`));
|
|
1361
|
+
for (const entry of allEntries) {
|
|
1362
|
+
console.log(` ${pc8.bold(entry.name.padEnd(25))} ${pc8.dim(entry.providerId)}`);
|
|
1329
1363
|
}
|
|
1330
1364
|
console.log();
|
|
1331
1365
|
});
|
|
1332
1366
|
}
|
|
1333
1367
|
|
|
1334
|
-
// src/commands/
|
|
1335
|
-
import
|
|
1336
|
-
|
|
1337
|
-
|
|
1368
|
+
// src/commands/mcp/detect.ts
|
|
1369
|
+
import pc9 from "picocolors";
|
|
1370
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1371
|
+
function registerMcpDetect(parent) {
|
|
1372
|
+
parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON").action(async (opts) => {
|
|
1338
1373
|
const providers = getInstalledProviders();
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1374
|
+
const detected = [];
|
|
1375
|
+
for (const provider of providers) {
|
|
1376
|
+
const globalPath = resolveConfigPath(provider, "global");
|
|
1377
|
+
const projectPath = resolveConfigPath(provider, "project");
|
|
1378
|
+
const globalEntries = await listMcpServers(provider, "global");
|
|
1379
|
+
const projectEntries = await listMcpServers(provider, "project");
|
|
1380
|
+
detected.push({
|
|
1381
|
+
provider: provider.id,
|
|
1382
|
+
hasGlobalConfig: globalPath !== null && existsSync3(globalPath),
|
|
1383
|
+
hasProjectConfig: projectPath !== null && existsSync3(projectPath),
|
|
1384
|
+
globalServers: globalEntries.map((e) => e.name),
|
|
1385
|
+
projectServers: projectEntries.map((e) => e.name)
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
if (opts.json) {
|
|
1389
|
+
console.log(JSON.stringify(detected, null, 2));
|
|
1345
1390
|
return;
|
|
1346
1391
|
}
|
|
1347
|
-
console.log(
|
|
1392
|
+
console.log(pc9.bold(`
|
|
1393
|
+
${detected.length} provider(s) with MCP support:
|
|
1348
1394
|
`));
|
|
1349
|
-
for (const
|
|
1350
|
-
|
|
1395
|
+
for (const d of detected) {
|
|
1396
|
+
const globalIcon = d.hasGlobalConfig ? pc9.green("G") : pc9.dim("-");
|
|
1397
|
+
const projectIcon = d.hasProjectConfig ? pc9.green("P") : pc9.dim("-");
|
|
1398
|
+
const servers = [...d.globalServers, ...d.projectServers];
|
|
1399
|
+
const serverList = servers.length > 0 ? pc9.dim(servers.join(", ")) : pc9.dim("no servers");
|
|
1400
|
+
console.log(` [${globalIcon}${projectIcon}] ${pc9.bold(d.provider.padEnd(20))} ${serverList}`);
|
|
1351
1401
|
}
|
|
1352
|
-
|
|
1353
|
-
const toUpdate = providers.filter((p) => providerIds.has(p.id));
|
|
1354
|
-
const results = await injectAll(toUpdate, process.cwd(), scope, content);
|
|
1402
|
+
console.log(pc9.dim("\nG = global config, P = project config"));
|
|
1355
1403
|
console.log();
|
|
1356
|
-
for (const [file, action] of results) {
|
|
1357
|
-
console.log(` ${pc17.green("\u2713")} ${file} (${action})`);
|
|
1358
|
-
}
|
|
1359
|
-
console.log(pc17.bold(`
|
|
1360
|
-
${results.size} file(s) updated.`));
|
|
1361
1404
|
});
|
|
1362
1405
|
}
|
|
1363
1406
|
|
|
1364
|
-
// src/commands/
|
|
1365
|
-
function
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1407
|
+
// src/commands/mcp/index.ts
|
|
1408
|
+
function registerMcpCommands(program2) {
|
|
1409
|
+
const mcp = program2.command("mcp").description("Manage MCP server configurations");
|
|
1410
|
+
registerMcpInstall(mcp);
|
|
1411
|
+
registerMcpRemove(mcp);
|
|
1412
|
+
registerMcpList(mcp);
|
|
1413
|
+
registerMcpDetect(mcp);
|
|
1370
1414
|
}
|
|
1371
1415
|
|
|
1372
|
-
// src/commands/
|
|
1373
|
-
import
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
console.error(pc18.red(`Provider not found: ${providerId}`));
|
|
1381
|
-
process.exit(1);
|
|
1382
|
-
}
|
|
1383
|
-
const configPath = resolveProviderConfigPath(
|
|
1384
|
-
provider,
|
|
1385
|
-
opts.global ? "global" : "project"
|
|
1386
|
-
) ?? provider.configPathGlobal;
|
|
1387
|
-
if (!existsSync5(configPath)) {
|
|
1388
|
-
console.log(pc18.dim(`No config file at: ${configPath}`));
|
|
1416
|
+
// src/commands/providers.ts
|
|
1417
|
+
import pc10 from "picocolors";
|
|
1418
|
+
function registerProvidersCommand(program2) {
|
|
1419
|
+
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) => {
|
|
1421
|
+
const all = opts.tier ? getProvidersByPriority(opts.tier) : getAllProviders();
|
|
1422
|
+
if (opts.json) {
|
|
1423
|
+
console.log(JSON.stringify(all, null, 2));
|
|
1389
1424
|
return;
|
|
1390
1425
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1395
|
-
} else {
|
|
1396
|
-
console.log(pc18.bold(`
|
|
1397
|
-
${provider.toolName} config (${configPath}):
|
|
1426
|
+
console.log(pc10.bold(`
|
|
1427
|
+
CAMP Provider Registry v${getRegistryVersion()}`));
|
|
1428
|
+
console.log(pc10.dim(`${getProviderCount()} providers
|
|
1398
1429
|
`));
|
|
1399
|
-
|
|
1430
|
+
const tiers = ["high", "medium", "low"];
|
|
1431
|
+
for (const tier of tiers) {
|
|
1432
|
+
const tierProviders = all.filter((p) => p.priority === tier);
|
|
1433
|
+
if (tierProviders.length === 0) continue;
|
|
1434
|
+
const tierLabel = tier === "high" ? pc10.green("HIGH") : tier === "medium" ? pc10.yellow("MEDIUM") : pc10.dim("LOW");
|
|
1435
|
+
console.log(`${tierLabel} priority:`);
|
|
1436
|
+
for (const p of tierProviders) {
|
|
1437
|
+
const status = p.status === "active" ? pc10.green("active") : p.status === "beta" ? pc10.yellow("beta") : pc10.dim(p.status);
|
|
1438
|
+
console.log(` ${pc10.bold(p.agentFlag.padEnd(20))} ${p.toolName.padEnd(22)} ${p.vendor.padEnd(16)} [${status}]`);
|
|
1400
1439
|
}
|
|
1401
|
-
|
|
1402
|
-
console.error(pc18.red(`Error reading config: ${err instanceof Error ? err.message : String(err)}`));
|
|
1403
|
-
process.exit(1);
|
|
1440
|
+
console.log();
|
|
1404
1441
|
}
|
|
1405
1442
|
});
|
|
1406
|
-
|
|
1407
|
-
const
|
|
1443
|
+
providers.command("detect").description("Auto-detect installed providers").option("--json", "Output as JSON").option("--project", "Include project-level detection").action(async (opts) => {
|
|
1444
|
+
const results = opts.project ? detectProjectProviders(process.cwd()) : detectAllProviders();
|
|
1445
|
+
const installed = results.filter((r) => r.installed);
|
|
1446
|
+
if (opts.json) {
|
|
1447
|
+
console.log(JSON.stringify(installed.map((r) => ({
|
|
1448
|
+
id: r.provider.id,
|
|
1449
|
+
toolName: r.provider.toolName,
|
|
1450
|
+
methods: r.methods,
|
|
1451
|
+
projectDetected: r.projectDetected
|
|
1452
|
+
})), null, 2));
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
console.log(pc10.bold(`
|
|
1456
|
+
Detected ${installed.length} installed providers:
|
|
1457
|
+
`));
|
|
1458
|
+
for (const r of installed) {
|
|
1459
|
+
const methods = r.methods.join(", ");
|
|
1460
|
+
const project = r.projectDetected ? pc10.green(" [project]") : "";
|
|
1461
|
+
console.log(` ${pc10.green("\u2713")} ${pc10.bold(r.provider.toolName.padEnd(22))} via ${pc10.dim(methods)}${project}`);
|
|
1462
|
+
}
|
|
1463
|
+
const notInstalled = results.filter((r) => !r.installed);
|
|
1464
|
+
if (notInstalled.length > 0) {
|
|
1465
|
+
console.log(pc10.dim(`
|
|
1466
|
+
${notInstalled.length} providers not detected`));
|
|
1467
|
+
}
|
|
1468
|
+
console.log();
|
|
1469
|
+
});
|
|
1470
|
+
providers.command("show").description("Show provider details").argument("<id>", "Provider ID or alias").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
1471
|
+
const provider = getProvider(id);
|
|
1408
1472
|
if (!provider) {
|
|
1409
|
-
console.error(
|
|
1473
|
+
console.error(pc10.red(`Provider not found: ${id}`));
|
|
1410
1474
|
process.exit(1);
|
|
1411
1475
|
}
|
|
1412
|
-
if (
|
|
1413
|
-
console.log(provider
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1476
|
+
if (opts.json) {
|
|
1477
|
+
console.log(JSON.stringify(provider, null, 2));
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
console.log(pc10.bold(`
|
|
1481
|
+
${provider.toolName}`));
|
|
1482
|
+
console.log(pc10.dim(`by ${provider.vendor}
|
|
1483
|
+
`));
|
|
1484
|
+
console.log(` ID: ${provider.id}`);
|
|
1485
|
+
console.log(` Flag: --agent ${provider.agentFlag}`);
|
|
1486
|
+
if (provider.aliases.length > 0) {
|
|
1487
|
+
console.log(` Aliases: ${provider.aliases.join(", ")}`);
|
|
1422
1488
|
}
|
|
1489
|
+
console.log(` Status: ${provider.status}`);
|
|
1490
|
+
console.log(` Priority: ${provider.priority}`);
|
|
1491
|
+
console.log();
|
|
1492
|
+
console.log(` Instruction: ${provider.instructFile}`);
|
|
1493
|
+
console.log(` Config format: ${provider.configFormat}`);
|
|
1494
|
+
console.log(` Config key: ${provider.configKey}`);
|
|
1495
|
+
console.log(` Transports: ${provider.supportedTransports.join(", ")}`);
|
|
1496
|
+
console.log(` Headers: ${provider.supportsHeaders ? "yes" : "no"}`);
|
|
1497
|
+
console.log();
|
|
1498
|
+
console.log(pc10.dim(" Paths:"));
|
|
1499
|
+
console.log(` Global dir: ${provider.pathGlobal}`);
|
|
1500
|
+
console.log(` Project dir: ${provider.pathProject || "(none)"}`);
|
|
1501
|
+
console.log(` Global config: ${provider.configPathGlobal}`);
|
|
1502
|
+
console.log(` Project config: ${provider.configPathProject || "(none)"}`);
|
|
1503
|
+
console.log(` Global skills: ${provider.pathSkills}`);
|
|
1504
|
+
console.log(` Project skills: ${provider.pathProjectSkills || "(none)"}`);
|
|
1505
|
+
console.log();
|
|
1423
1506
|
});
|
|
1424
1507
|
}
|
|
1425
1508
|
|
|
1426
|
-
// src/commands/
|
|
1427
|
-
import
|
|
1428
|
-
import
|
|
1429
|
-
import { existsSync as existsSync6, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
1430
|
-
import { homedir } from "os";
|
|
1431
|
-
import { join as join5 } from "path";
|
|
1432
|
-
|
|
1433
|
-
// src/core/version.ts
|
|
1434
|
-
import { readFileSync } from "fs";
|
|
1435
|
-
import { dirname, join as join4 } from "path";
|
|
1436
|
-
import { fileURLToPath } from "url";
|
|
1437
|
-
var cachedVersion = null;
|
|
1438
|
-
function getCaampVersion() {
|
|
1439
|
-
if (cachedVersion) return cachedVersion;
|
|
1440
|
-
try {
|
|
1441
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
1442
|
-
const packageJsonPath = join4(currentDir, "..", "..", "package.json");
|
|
1443
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1444
|
-
cachedVersion = packageJson.version ?? "0.0.0";
|
|
1445
|
-
} catch {
|
|
1446
|
-
cachedVersion = "0.0.0";
|
|
1447
|
-
}
|
|
1448
|
-
return cachedVersion;
|
|
1449
|
-
}
|
|
1509
|
+
// src/commands/skills/install.ts
|
|
1510
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1511
|
+
import pc11 from "picocolors";
|
|
1450
1512
|
|
|
1451
|
-
// src/
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const checks = [];
|
|
1464
|
-
checks.push({ label: `Node.js ${getNodeVersion()}`, status: "pass" });
|
|
1465
|
-
const npmVersion = getNpmVersion();
|
|
1466
|
-
if (npmVersion) {
|
|
1467
|
-
checks.push({ label: `npm ${npmVersion}`, status: "pass" });
|
|
1468
|
-
} else {
|
|
1469
|
-
checks.push({ label: "npm not found", status: "warn" });
|
|
1470
|
-
}
|
|
1471
|
-
checks.push({ label: `CAAMP v${getCaampVersion()}`, status: "pass" });
|
|
1472
|
-
checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
|
|
1473
|
-
return { name: "Environment", checks };
|
|
1474
|
-
}
|
|
1475
|
-
function checkRegistry() {
|
|
1476
|
-
const checks = [];
|
|
1477
|
-
try {
|
|
1478
|
-
const providers = getAllProviders();
|
|
1479
|
-
const count = getProviderCount();
|
|
1480
|
-
checks.push({ label: `${count} providers loaded`, status: "pass" });
|
|
1481
|
-
const malformed = [];
|
|
1482
|
-
for (const p of providers) {
|
|
1483
|
-
if (!p.id || !p.toolName || !p.configKey || !p.configFormat) {
|
|
1484
|
-
malformed.push(p.id || "(unknown)");
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
if (malformed.length === 0) {
|
|
1488
|
-
checks.push({ label: "All entries valid", status: "pass" });
|
|
1489
|
-
} else {
|
|
1490
|
-
checks.push({
|
|
1491
|
-
label: `${malformed.length} malformed entries`,
|
|
1492
|
-
status: "fail",
|
|
1493
|
-
detail: malformed.join(", ")
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
} catch (err) {
|
|
1497
|
-
checks.push({
|
|
1498
|
-
label: "Failed to load registry",
|
|
1499
|
-
status: "fail",
|
|
1500
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
1501
|
-
});
|
|
1513
|
+
// src/core/sources/github.ts
|
|
1514
|
+
import { simpleGit } from "simple-git";
|
|
1515
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
1516
|
+
import { tmpdir } from "os";
|
|
1517
|
+
import { join as join3 } from "path";
|
|
1518
|
+
async function cloneRepo(owner, repo, ref, subPath) {
|
|
1519
|
+
const tmpDir = await mkdtemp(join3(tmpdir(), "caamp-"));
|
|
1520
|
+
const repoUrl = `https://github.com/${owner}/${repo}.git`;
|
|
1521
|
+
const git = simpleGit();
|
|
1522
|
+
const cloneOptions = ["--depth", "1"];
|
|
1523
|
+
if (ref) {
|
|
1524
|
+
cloneOptions.push("--branch", ref);
|
|
1502
1525
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const methods = r.methods.join(", ");
|
|
1513
|
-
checks.push({ label: `${r.provider.toolName} (${methods})`, status: "pass" });
|
|
1526
|
+
await git.clone(repoUrl, tmpDir, cloneOptions);
|
|
1527
|
+
const localPath = subPath ? join3(tmpDir, subPath) : tmpDir;
|
|
1528
|
+
return {
|
|
1529
|
+
localPath,
|
|
1530
|
+
cleanup: async () => {
|
|
1531
|
+
try {
|
|
1532
|
+
await rm(tmpDir, { recursive: true });
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1514
1535
|
}
|
|
1515
|
-
}
|
|
1516
|
-
checks.push({
|
|
1517
|
-
label: "Detection failed",
|
|
1518
|
-
status: "fail",
|
|
1519
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
return { name: "Installed Providers", checks };
|
|
1536
|
+
};
|
|
1523
1537
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1538
|
+
|
|
1539
|
+
// src/core/sources/gitlab.ts
|
|
1540
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1541
|
+
import { mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
|
|
1542
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1543
|
+
import { join as join4 } from "path";
|
|
1544
|
+
async function cloneGitLabRepo(owner, repo, ref, subPath) {
|
|
1545
|
+
const tmpDir = await mkdtemp2(join4(tmpdir2(), "caamp-gl-"));
|
|
1546
|
+
const repoUrl = `https://gitlab.com/${owner}/${repo}.git`;
|
|
1547
|
+
const git = simpleGit2();
|
|
1548
|
+
const cloneOptions = ["--depth", "1"];
|
|
1549
|
+
if (ref) {
|
|
1550
|
+
cloneOptions.push("--branch", ref);
|
|
1531
1551
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1552
|
+
await git.clone(repoUrl, tmpDir, cloneOptions);
|
|
1553
|
+
const localPath = subPath ? join4(tmpDir, subPath) : tmpDir;
|
|
1554
|
+
return {
|
|
1555
|
+
localPath,
|
|
1556
|
+
cleanup: async () => {
|
|
1537
1557
|
try {
|
|
1538
|
-
|
|
1539
|
-
return stat.isDirectory() || stat.isSymbolicLink();
|
|
1558
|
+
await rm2(tmpDir, { recursive: true });
|
|
1540
1559
|
} catch {
|
|
1541
|
-
return false;
|
|
1542
1560
|
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/commands/skills/install.ts
|
|
1566
|
+
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) => {
|
|
1568
|
+
let providers;
|
|
1569
|
+
if (opts.all) {
|
|
1570
|
+
providers = getInstalledProviders();
|
|
1571
|
+
} else if (opts.agent.length > 0) {
|
|
1572
|
+
providers = opts.agent.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
1573
|
+
} else {
|
|
1574
|
+
providers = getInstalledProviders();
|
|
1575
|
+
}
|
|
1576
|
+
if (providers.length === 0) {
|
|
1577
|
+
console.error(pc11.red("No target providers found. Use --agent or --all."));
|
|
1578
|
+
process.exit(1);
|
|
1579
|
+
}
|
|
1580
|
+
if (opts.profile) {
|
|
1581
|
+
if (!isCatalogAvailable()) {
|
|
1582
|
+
console.error(pc11.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
|
|
1583
|
+
process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
const profileSkills = resolveProfile(opts.profile);
|
|
1586
|
+
if (profileSkills.length === 0) {
|
|
1587
|
+
const available = listProfiles();
|
|
1588
|
+
console.error(pc11.red(`Profile not found: ${opts.profile}`));
|
|
1589
|
+
if (available.length > 0) {
|
|
1590
|
+
console.log(pc11.dim("Available profiles: " + available.join(", ")));
|
|
1591
|
+
}
|
|
1592
|
+
process.exit(1);
|
|
1593
|
+
}
|
|
1594
|
+
console.log(`Installing profile ${pc11.bold(opts.profile)} (${profileSkills.length} skill(s))...`);
|
|
1595
|
+
console.log(pc11.dim(`Target: ${providers.length} provider(s)`));
|
|
1596
|
+
let installed = 0;
|
|
1597
|
+
let failed = 0;
|
|
1598
|
+
for (const name of profileSkills) {
|
|
1599
|
+
const skillDir = getSkillDir(name);
|
|
1562
1600
|
try {
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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++;
|
|
1567
1619
|
} else {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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"}`);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (!source) {
|
|
1633
|
+
console.error(pc11.red("Missing required argument: source"));
|
|
1634
|
+
console.log(pc11.dim("Usage: caamp skills install <source> or caamp skills install --profile <name>"));
|
|
1635
|
+
process.exit(1);
|
|
1636
|
+
}
|
|
1637
|
+
console.log(pc11.dim(`Installing to ${providers.length} provider(s)...`));
|
|
1638
|
+
let localPath;
|
|
1639
|
+
let cleanup;
|
|
1640
|
+
let skillName;
|
|
1641
|
+
let sourceValue;
|
|
1642
|
+
let sourceType;
|
|
1643
|
+
if (isMarketplaceScoped(source)) {
|
|
1644
|
+
console.log(pc11.dim(`Searching marketplace for ${source}...`));
|
|
1645
|
+
const client = new MarketplaceClient();
|
|
1646
|
+
let skill;
|
|
1647
|
+
try {
|
|
1648
|
+
skill = await client.getSkill(source);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
console.error(pc11.red(`Marketplace lookup failed: ${formatNetworkError(error)}`));
|
|
1651
|
+
process.exit(1);
|
|
1652
|
+
}
|
|
1653
|
+
if (!skill) {
|
|
1654
|
+
console.error(pc11.red(`Skill not found: ${source}`));
|
|
1655
|
+
process.exit(1);
|
|
1656
|
+
}
|
|
1657
|
+
console.log(` Found: ${pc11.bold(skill.name)} by ${skill.author} (${pc11.dim(skill.repoFullName)})`);
|
|
1658
|
+
const parsed = parseSource(skill.githubUrl);
|
|
1659
|
+
if (parsed.type !== "github" || !parsed.owner || !parsed.repo) {
|
|
1660
|
+
console.error(pc11.red("Could not resolve GitHub source"));
|
|
1661
|
+
process.exit(1);
|
|
1662
|
+
}
|
|
1663
|
+
try {
|
|
1664
|
+
const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
|
|
1665
|
+
let cloneError;
|
|
1666
|
+
let cloned = false;
|
|
1667
|
+
for (const subPath of subPathCandidates) {
|
|
1668
|
+
try {
|
|
1669
|
+
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
|
|
1670
|
+
if (subPath && !existsSync4(result.localPath)) {
|
|
1671
|
+
await result.cleanup();
|
|
1672
|
+
continue;
|
|
1572
1673
|
}
|
|
1674
|
+
localPath = result.localPath;
|
|
1675
|
+
cleanup = result.cleanup;
|
|
1676
|
+
cloned = true;
|
|
1677
|
+
break;
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
cloneError = error;
|
|
1573
1680
|
}
|
|
1574
|
-
}
|
|
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)}`));
|
|
1690
|
+
process.exit(1);
|
|
1691
|
+
}
|
|
1692
|
+
} else {
|
|
1693
|
+
const parsed = parseSource(source);
|
|
1694
|
+
skillName = parsed.inferredName;
|
|
1695
|
+
sourceValue = parsed.value;
|
|
1696
|
+
sourceType = parsed.type;
|
|
1697
|
+
if (parsed.type === "github" && parsed.owner && parsed.repo) {
|
|
1698
|
+
try {
|
|
1699
|
+
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
1700
|
+
localPath = result.localPath;
|
|
1701
|
+
cleanup = result.cleanup;
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
console.error(pc11.red(`Failed to clone GitHub repository: ${formatNetworkError(error)}`));
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
} else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
|
|
1707
|
+
try {
|
|
1708
|
+
const result = await cloneGitLabRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
1709
|
+
localPath = result.localPath;
|
|
1710
|
+
cleanup = result.cleanup;
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
console.error(pc11.red(`Failed to clone GitLab repository: ${formatNetworkError(error)}`));
|
|
1713
|
+
process.exit(1);
|
|
1714
|
+
}
|
|
1715
|
+
} else if (parsed.type === "local") {
|
|
1716
|
+
localPath = parsed.value;
|
|
1717
|
+
const discovered = await discoverSkill(localPath);
|
|
1718
|
+
if (discovered) {
|
|
1719
|
+
skillName = discovered.name;
|
|
1720
|
+
}
|
|
1721
|
+
} else if (parsed.type === "package") {
|
|
1722
|
+
if (!isCatalogAvailable()) {
|
|
1723
|
+
console.error(pc11.red("@cleocode/ct-skills is not installed. Run: npm install @cleocode/ct-skills"));
|
|
1724
|
+
process.exit(1);
|
|
1725
|
+
}
|
|
1726
|
+
const catalogSkill = getSkill(parsed.inferredName);
|
|
1727
|
+
if (catalogSkill) {
|
|
1728
|
+
localPath = getSkillDir(catalogSkill.name);
|
|
1729
|
+
skillName = catalogSkill.name;
|
|
1730
|
+
sourceValue = `@cleocode/ct-skills:${catalogSkill.name}`;
|
|
1731
|
+
sourceType = "package";
|
|
1732
|
+
console.log(` Found in catalog: ${pc11.bold(catalogSkill.name)} v${catalogSkill.version} (${pc11.dim(catalogSkill.category)})`);
|
|
1733
|
+
} else {
|
|
1734
|
+
console.error(pc11.red(`Skill not found in catalog: ${parsed.inferredName}`));
|
|
1735
|
+
console.log(pc11.dim("Available skills: " + listSkills().join(", ")));
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
} else {
|
|
1739
|
+
console.error(pc11.red(`Unsupported source type: ${parsed.type}`));
|
|
1740
|
+
process.exit(1);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
if (!localPath) {
|
|
1745
|
+
throw new Error("No local skill path resolved for installation");
|
|
1746
|
+
}
|
|
1747
|
+
const result = await installSkill(
|
|
1748
|
+
localPath,
|
|
1749
|
+
skillName,
|
|
1750
|
+
providers,
|
|
1751
|
+
opts.global ?? false
|
|
1752
|
+
);
|
|
1753
|
+
if (result.success) {
|
|
1754
|
+
console.log(pc11.green(`
|
|
1755
|
+
\u2713 Installed ${pc11.bold(skillName)}`));
|
|
1756
|
+
console.log(` Canonical: ${pc11.dim(result.canonicalPath)}`);
|
|
1757
|
+
console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
|
|
1758
|
+
const isGlobal = sourceType === "package" ? true : opts.global ?? false;
|
|
1759
|
+
await recordSkillInstall(
|
|
1760
|
+
skillName,
|
|
1761
|
+
sourceValue,
|
|
1762
|
+
sourceValue,
|
|
1763
|
+
sourceType,
|
|
1764
|
+
result.linkedAgents,
|
|
1765
|
+
result.canonicalPath,
|
|
1766
|
+
isGlobal
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
if (result.errors.length > 0) {
|
|
1770
|
+
console.log(pc11.yellow("\nWarnings:"));
|
|
1771
|
+
for (const err of result.errors) {
|
|
1772
|
+
console.log(` ${pc11.yellow("!")} ${err}`);
|
|
1575
1773
|
}
|
|
1576
1774
|
}
|
|
1577
|
-
}
|
|
1775
|
+
} finally {
|
|
1776
|
+
if (cleanup) await cleanup();
|
|
1578
1777
|
}
|
|
1579
|
-
}
|
|
1580
|
-
if (broken.length === 0) {
|
|
1581
|
-
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
1582
|
-
} else {
|
|
1583
|
-
checks.push({
|
|
1584
|
-
label: `${broken.length} broken symlink${broken.length !== 1 ? "s" : ""}`,
|
|
1585
|
-
status: "warn",
|
|
1586
|
-
detail: broken.join(", ")
|
|
1587
|
-
});
|
|
1588
|
-
}
|
|
1589
|
-
if (stale.length === 0) {
|
|
1590
|
-
checks.push({ label: "No stale symlinks", status: "pass" });
|
|
1591
|
-
} else {
|
|
1592
|
-
checks.push({
|
|
1593
|
-
label: `${stale.length} stale symlink${stale.length !== 1 ? "s" : ""} (not pointing to ~/.agents/skills/)`,
|
|
1594
|
-
status: "warn",
|
|
1595
|
-
detail: stale.join(", ")
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
return { name: "Skills", checks };
|
|
1778
|
+
});
|
|
1599
1779
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
if (orphaned.length === 0) {
|
|
1614
|
-
checks.push({ label: "0 orphaned entries", status: "pass" });
|
|
1615
|
-
} else {
|
|
1616
|
-
checks.push({
|
|
1617
|
-
label: `${orphaned.length} orphaned skill${orphaned.length !== 1 ? "s" : ""} (in lock, missing from disk)`,
|
|
1618
|
-
status: "warn",
|
|
1619
|
-
detail: orphaned.join(", ")
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
const canonicalDir = CANONICAL_SKILLS_DIR;
|
|
1623
|
-
if (existsSync6(canonicalDir)) {
|
|
1624
|
-
const onDisk = readdirSync(canonicalDir).filter((name) => {
|
|
1625
|
-
try {
|
|
1626
|
-
const stat = lstatSync(join5(canonicalDir, name));
|
|
1627
|
-
return stat.isDirectory() || stat.isSymbolicLink();
|
|
1628
|
-
} catch {
|
|
1629
|
-
return false;
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
const untracked = onDisk.filter((name) => !lock.skills[name]);
|
|
1633
|
-
if (untracked.length === 0) {
|
|
1634
|
-
checks.push({ label: "0 untracked skills", status: "pass" });
|
|
1780
|
+
|
|
1781
|
+
// src/commands/skills/remove.ts
|
|
1782
|
+
import pc12 from "picocolors";
|
|
1783
|
+
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) => {
|
|
1785
|
+
const providers = getInstalledProviders();
|
|
1786
|
+
if (name) {
|
|
1787
|
+
const result = await removeSkill(name, providers, opts.global ?? false);
|
|
1788
|
+
if (result.removed.length > 0) {
|
|
1789
|
+
console.log(pc12.green(`\u2713 Removed ${pc12.bold(name)} from: ${result.removed.join(", ")}`));
|
|
1790
|
+
await removeSkillFromLock(name);
|
|
1635
1791
|
} else {
|
|
1636
|
-
|
|
1637
|
-
label: `${untracked.length} untracked skill${untracked.length !== 1 ? "s" : ""} (on disk, not in lock)`,
|
|
1638
|
-
status: "warn",
|
|
1639
|
-
detail: untracked.join(", ")
|
|
1640
|
-
});
|
|
1792
|
+
console.log(pc12.yellow(`Skill ${name} not found in any provider.`));
|
|
1641
1793
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const mismatches = [];
|
|
1646
|
-
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
1647
|
-
if (!entry.agents || entry.agents.length === 0) continue;
|
|
1648
|
-
for (const agentId of entry.agents) {
|
|
1649
|
-
const provider = installed.find((r) => r.provider.id === agentId);
|
|
1650
|
-
if (!provider) continue;
|
|
1651
|
-
const linkPath = join5(provider.provider.pathSkills, name);
|
|
1652
|
-
if (!existsSync6(linkPath)) {
|
|
1653
|
-
mismatches.push(`${name} missing from ${agentId}`);
|
|
1794
|
+
if (result.errors.length > 0) {
|
|
1795
|
+
for (const err of result.errors) {
|
|
1796
|
+
console.log(pc12.red(` ${err}`));
|
|
1654
1797
|
}
|
|
1655
1798
|
}
|
|
1656
|
-
}
|
|
1657
|
-
if (mismatches.length === 0) {
|
|
1658
|
-
checks.push({ label: "Lock agent-lists match symlinks", status: "pass" });
|
|
1659
1799
|
} else {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
} catch (err) {
|
|
1667
|
-
checks.push({
|
|
1668
|
-
label: "Failed to read lock file",
|
|
1669
|
-
status: "fail",
|
|
1670
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
1671
|
-
});
|
|
1672
|
-
}
|
|
1673
|
-
return { name: "Lock File", checks };
|
|
1674
|
-
}
|
|
1675
|
-
async function checkConfigFiles() {
|
|
1676
|
-
const checks = [];
|
|
1677
|
-
const results = detectAllProviders();
|
|
1678
|
-
const installed = results.filter((r) => r.installed);
|
|
1679
|
-
for (const r of installed) {
|
|
1680
|
-
const provider = r.provider;
|
|
1681
|
-
const configPath = provider.configPathGlobal;
|
|
1682
|
-
if (!existsSync6(configPath)) {
|
|
1683
|
-
checks.push({
|
|
1684
|
-
label: `${provider.id}: no config file found`,
|
|
1685
|
-
status: "warn",
|
|
1686
|
-
detail: configPath
|
|
1687
|
-
});
|
|
1688
|
-
continue;
|
|
1689
|
-
}
|
|
1690
|
-
try {
|
|
1691
|
-
await readConfig(configPath, provider.configFormat);
|
|
1692
|
-
const relPath = configPath.replace(homedir(), "~");
|
|
1693
|
-
checks.push({
|
|
1694
|
-
label: `${provider.id}: ${relPath} readable`,
|
|
1695
|
-
status: "pass"
|
|
1696
|
-
});
|
|
1697
|
-
} catch (err) {
|
|
1698
|
-
checks.push({
|
|
1699
|
-
label: `${provider.id}: config parse error`,
|
|
1700
|
-
status: "fail",
|
|
1701
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
if (installed.length === 0) {
|
|
1706
|
-
checks.push({ label: "No installed providers to check", status: "pass" });
|
|
1707
|
-
}
|
|
1708
|
-
return { name: "Config Files", checks };
|
|
1709
|
-
}
|
|
1710
|
-
function formatSection(section) {
|
|
1711
|
-
const lines = [];
|
|
1712
|
-
lines.push(` ${pc19.bold(section.name)}`);
|
|
1713
|
-
for (const check of section.checks) {
|
|
1714
|
-
const icon = check.status === "pass" ? pc19.green("\u2713") : check.status === "warn" ? pc19.yellow("\u26A0") : pc19.red("\u2717");
|
|
1715
|
-
lines.push(` ${icon} ${check.label}`);
|
|
1716
|
-
if (check.detail) {
|
|
1717
|
-
lines.push(` ${pc19.dim(check.detail)}`);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
return lines.join("\n");
|
|
1721
|
-
}
|
|
1722
|
-
function registerDoctorCommand(program2) {
|
|
1723
|
-
program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1724
|
-
const sections = [];
|
|
1725
|
-
sections.push(checkEnvironment());
|
|
1726
|
-
sections.push(checkRegistry());
|
|
1727
|
-
sections.push(checkInstalledProviders());
|
|
1728
|
-
sections.push(checkSkillSymlinks());
|
|
1729
|
-
sections.push(await checkLockFile());
|
|
1730
|
-
sections.push(await checkConfigFiles());
|
|
1731
|
-
let passed = 0;
|
|
1732
|
-
let warnings = 0;
|
|
1733
|
-
let errors = 0;
|
|
1734
|
-
for (const section of sections) {
|
|
1735
|
-
for (const check of section.checks) {
|
|
1736
|
-
if (check.status === "pass") passed++;
|
|
1737
|
-
else if (check.status === "warn") warnings++;
|
|
1738
|
-
else errors++;
|
|
1800
|
+
const skills = await listCanonicalSkills();
|
|
1801
|
+
if (skills.length === 0) {
|
|
1802
|
+
console.log(pc12.dim("No skills installed."));
|
|
1803
|
+
return;
|
|
1739
1804
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
name: s.name,
|
|
1746
|
-
checks: s.checks
|
|
1747
|
-
})),
|
|
1748
|
-
summary: { passed, warnings, errors }
|
|
1749
|
-
};
|
|
1750
|
-
console.log(JSON.stringify(output, null, 2));
|
|
1751
|
-
return;
|
|
1752
|
-
}
|
|
1753
|
-
console.log(pc19.bold("\ncaamp doctor\n"));
|
|
1754
|
-
for (const section of sections) {
|
|
1755
|
-
console.log(formatSection(section));
|
|
1756
|
-
console.log();
|
|
1757
|
-
}
|
|
1758
|
-
const parts = [];
|
|
1759
|
-
parts.push(pc19.green(`${passed} checks passed`));
|
|
1760
|
-
if (warnings > 0) parts.push(pc19.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
|
|
1761
|
-
if (errors > 0) parts.push(pc19.red(`${errors} error${errors !== 1 ? "s" : ""}`));
|
|
1762
|
-
console.log(` ${pc19.bold("Summary")}: ${parts.join(", ")}`);
|
|
1763
|
-
console.log();
|
|
1764
|
-
if (errors > 0) {
|
|
1765
|
-
process.exit(1);
|
|
1805
|
+
console.log(pc12.bold("Installed skills:"));
|
|
1806
|
+
for (const s of skills) {
|
|
1807
|
+
console.log(` ${s}`);
|
|
1808
|
+
}
|
|
1809
|
+
console.log(pc12.dim("\nUse: caamp skills remove <name>"));
|
|
1766
1810
|
}
|
|
1767
1811
|
});
|
|
1768
1812
|
}
|
|
1769
1813
|
|
|
1770
|
-
// src/commands/
|
|
1771
|
-
import { readFile } from "fs/promises";
|
|
1772
|
-
|
|
1773
|
-
// src/commands/advanced/lafs.ts
|
|
1814
|
+
// src/commands/skills/list.ts
|
|
1774
1815
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1775
|
-
import {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1816
|
+
import { resolveOutputFormat } from "@cleocode/lafs-protocol";
|
|
1817
|
+
import pc13 from "picocolors";
|
|
1818
|
+
function registerSkillsList(parent) {
|
|
1819
|
+
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
|
+
const operation = "skills.list";
|
|
1821
|
+
const mvi = true;
|
|
1822
|
+
let format;
|
|
1823
|
+
try {
|
|
1824
|
+
format = resolveOutputFormat({
|
|
1825
|
+
jsonFlag: opts.json ?? false,
|
|
1826
|
+
humanFlag: (opts.human ?? false) || isHuman(),
|
|
1827
|
+
projectDefault: "json"
|
|
1828
|
+
}).format;
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1831
|
+
emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
let dirs = [];
|
|
1835
|
+
if (opts.agent) {
|
|
1836
|
+
const provider = getProvider(opts.agent);
|
|
1837
|
+
if (!provider) {
|
|
1838
|
+
const message = `Provider not found: ${opts.agent}`;
|
|
1839
|
+
if (format === "json") {
|
|
1840
|
+
emitJsonError(operation, mvi, "E_PROVIDER_NOT_FOUND", message, "NOT_FOUND", {
|
|
1841
|
+
agent: opts.agent
|
|
1842
|
+
});
|
|
1843
|
+
} else {
|
|
1844
|
+
console.error(pc13.red(message));
|
|
1845
|
+
}
|
|
1846
|
+
process.exit(1);
|
|
1847
|
+
}
|
|
1848
|
+
dirs = opts.global ? [resolveProviderSkillsDir(provider, "global")] : [resolveProviderSkillsDir(provider, "project")];
|
|
1849
|
+
} else if (opts.global) {
|
|
1850
|
+
const providers = getInstalledProviders();
|
|
1851
|
+
dirs = providers.map((p) => resolveProviderSkillsDir(p, "global")).filter(Boolean);
|
|
1852
|
+
} else {
|
|
1853
|
+
const providers = getInstalledProviders();
|
|
1854
|
+
dirs = providers.map((p) => resolveProviderSkillsDir(p, "project")).filter(Boolean);
|
|
1855
|
+
}
|
|
1856
|
+
const skills = await discoverSkillsMulti(dirs);
|
|
1857
|
+
if (format === "json") {
|
|
1858
|
+
const envelope = buildEnvelope(
|
|
1859
|
+
operation,
|
|
1860
|
+
mvi,
|
|
1861
|
+
{
|
|
1862
|
+
skills,
|
|
1863
|
+
count: skills.length,
|
|
1864
|
+
scope: opts.global ? "global" : opts.agent ? `agent:${opts.agent}` : "project"
|
|
1865
|
+
},
|
|
1866
|
+
null
|
|
1867
|
+
);
|
|
1868
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (skills.length === 0) {
|
|
1872
|
+
console.log(pc13.dim("No skills found."));
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
console.log(pc13.bold(`
|
|
1876
|
+
${skills.length} skill(s) found:
|
|
1877
|
+
`));
|
|
1878
|
+
skills.forEach((skill, index) => {
|
|
1879
|
+
const num = (index + 1).toString().padStart(2);
|
|
1880
|
+
console.log(` ${pc13.cyan(num)}. ${pc13.bold(skill.name.padEnd(30))} ${pc13.dim(skill.metadata?.description ?? "")}`);
|
|
1881
|
+
});
|
|
1882
|
+
console.log(pc13.dim(`
|
|
1883
|
+
Install with: caamp skills install <name>`));
|
|
1884
|
+
console.log(pc13.dim(`Remove with: caamp skills remove <name>`));
|
|
1885
|
+
});
|
|
1806
1886
|
}
|
|
1807
|
-
function
|
|
1887
|
+
function buildEnvelope(operation, mvi, result, error) {
|
|
1808
1888
|
return {
|
|
1809
|
-
specVersion: "1.0.0",
|
|
1810
|
-
schemaVersion: "1.0.0",
|
|
1811
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1812
|
-
operation,
|
|
1813
|
-
requestId: randomUUID2(),
|
|
1814
|
-
transport: "cli",
|
|
1815
|
-
strict: true,
|
|
1816
|
-
mvi,
|
|
1817
|
-
contextVersion: 0
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
function emitSuccess(operation, result, mvi = true) {
|
|
1821
|
-
const envelope = {
|
|
1822
1889
|
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
1823
1890
|
_meta: {
|
|
1824
|
-
|
|
1891
|
+
specVersion: "1.0.0",
|
|
1892
|
+
schemaVersion: "1.0.0",
|
|
1893
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1894
|
+
operation,
|
|
1895
|
+
requestId: randomUUID2(),
|
|
1896
|
+
transport: "cli",
|
|
1897
|
+
strict: true,
|
|
1898
|
+
mvi,
|
|
1899
|
+
contextVersion: 0
|
|
1825
1900
|
},
|
|
1826
|
-
success:
|
|
1901
|
+
success: error === null,
|
|
1827
1902
|
result,
|
|
1828
|
-
error
|
|
1903
|
+
error,
|
|
1829
1904
|
page: null
|
|
1830
1905
|
};
|
|
1831
|
-
console.log(JSON.stringify(envelope, null, 2));
|
|
1832
1906
|
}
|
|
1833
|
-
function
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
result: null,
|
|
1843
|
-
error: {
|
|
1844
|
-
code: isRegisteredErrorCode(error.code) ? error.code : "E_INTERNAL_UNEXPECTED",
|
|
1845
|
-
message: error.message,
|
|
1846
|
-
category: error.category,
|
|
1847
|
-
retryable: error.recoverable,
|
|
1848
|
-
retryAfterMs: error.retryAfterMs,
|
|
1849
|
-
details: {
|
|
1850
|
-
hint: error.suggestion,
|
|
1851
|
-
...error.details !== void 0 ? { payload: error.details } : {}
|
|
1852
|
-
}
|
|
1853
|
-
},
|
|
1854
|
-
page: null
|
|
1855
|
-
};
|
|
1856
|
-
} else {
|
|
1857
|
-
envelope = {
|
|
1858
|
-
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
1859
|
-
_meta: {
|
|
1860
|
-
...baseMeta(operation, mvi)
|
|
1861
|
-
},
|
|
1862
|
-
success: false,
|
|
1863
|
-
result: null,
|
|
1864
|
-
error: {
|
|
1865
|
-
code: "E_INTERNAL_UNEXPECTED",
|
|
1866
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1867
|
-
category: "INTERNAL",
|
|
1868
|
-
retryable: false,
|
|
1869
|
-
retryAfterMs: null,
|
|
1870
|
-
details: {
|
|
1871
|
-
hint: "Rerun with --verbose and validate your inputs."
|
|
1872
|
-
}
|
|
1873
|
-
},
|
|
1874
|
-
page: null
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1907
|
+
function emitJsonError(operation, mvi, code, message, category, details = {}) {
|
|
1908
|
+
const envelope = buildEnvelope(operation, mvi, null, {
|
|
1909
|
+
code,
|
|
1910
|
+
message,
|
|
1911
|
+
category,
|
|
1912
|
+
retryable: false,
|
|
1913
|
+
retryAfterMs: null,
|
|
1914
|
+
details
|
|
1915
|
+
});
|
|
1877
1916
|
console.error(JSON.stringify(envelope, null, 2));
|
|
1878
1917
|
}
|
|
1879
|
-
async function runLafsCommand(command, mvi, action) {
|
|
1880
|
-
try {
|
|
1881
|
-
const result = await action();
|
|
1882
|
-
emitSuccess(command, result, mvi);
|
|
1883
|
-
} catch (error) {
|
|
1884
|
-
emitError(command, error, mvi);
|
|
1885
|
-
process.exit(1);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
1918
|
|
|
1889
|
-
// src/commands/
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
function resolveProviders(options) {
|
|
1902
|
-
if (options.all) {
|
|
1903
|
-
return getAllProviders();
|
|
1904
|
-
}
|
|
1905
|
-
const targetAgents = options.agent ?? [];
|
|
1906
|
-
if (targetAgents.length === 0) {
|
|
1907
|
-
return getInstalledProviders();
|
|
1908
|
-
}
|
|
1909
|
-
const providers = targetAgents.map((id) => getProvider(id)).filter((provider) => provider !== void 0);
|
|
1910
|
-
if (providers.length !== targetAgents.length) {
|
|
1911
|
-
const found = new Set(providers.map((provider) => provider.id));
|
|
1912
|
-
const missing = targetAgents.filter((id) => !found.has(id));
|
|
1913
|
-
throw new LAFSCommandError(
|
|
1914
|
-
"E_ADVANCED_PROVIDER_NOT_FOUND",
|
|
1915
|
-
`Unknown provider(s): ${missing.join(", ")}`,
|
|
1916
|
-
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1919
|
-
return providers;
|
|
1920
|
-
}
|
|
1921
|
-
async function readJsonFile(path) {
|
|
1922
|
-
try {
|
|
1923
|
-
const raw = await readFile(path, "utf-8");
|
|
1924
|
-
return JSON.parse(raw);
|
|
1925
|
-
} catch (error) {
|
|
1926
|
-
throw new LAFSCommandError(
|
|
1927
|
-
"E_ADVANCED_INPUT_JSON",
|
|
1928
|
-
`Failed to read JSON file: ${path}`,
|
|
1929
|
-
"Confirm the path exists and contains valid JSON.",
|
|
1930
|
-
true,
|
|
1931
|
-
{ reason: error instanceof Error ? error.message : String(error) }
|
|
1932
|
-
);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
async function readMcpOperations(path) {
|
|
1936
|
-
const value = await readJsonFile(path);
|
|
1937
|
-
if (!Array.isArray(value)) {
|
|
1938
|
-
throw new LAFSCommandError(
|
|
1939
|
-
"E_ADVANCED_VALIDATION_MCP_ARRAY",
|
|
1940
|
-
`MCP operations file must be a JSON array: ${path}`,
|
|
1941
|
-
"Provide an array of objects with serverName and config fields."
|
|
1942
|
-
);
|
|
1919
|
+
// src/commands/skills/find.ts
|
|
1920
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1921
|
+
import {
|
|
1922
|
+
resolveOutputFormat as resolveOutputFormat2
|
|
1923
|
+
} from "@cleocode/lafs-protocol";
|
|
1924
|
+
import pc14 from "picocolors";
|
|
1925
|
+
var SkillsFindValidationError = class extends Error {
|
|
1926
|
+
code;
|
|
1927
|
+
constructor(code, message) {
|
|
1928
|
+
super(message);
|
|
1929
|
+
this.code = code;
|
|
1930
|
+
this.name = "SkillsFindValidationError";
|
|
1943
1931
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1932
|
+
};
|
|
1933
|
+
function registerSkillsFind(parent) {
|
|
1934
|
+
parent.command("find").description("Search marketplace for skills").argument("[query]", "Search query").option("--recommend", "Recommend skills from constraints").option("--top <n>", "Number of recommendation candidates", "3").option("--must-have <term>", "Required criteria term", (value, previous) => [...previous, value], []).option("--prefer <term>", "Preferred criteria term", (value, previous) => [...previous, value], []).option("--exclude <term>", "Excluded criteria term", (value, previous) => [...previous, value], []).option("--details", "Include expanded machine output").option("--human", "Force human-readable output").option("--json", "Output as JSON").option("--select <indexes>", "Pre-select recommendation ranks (comma-separated)").option("-l, --limit <n>", "Max results", "20").action(async (query, opts) => {
|
|
1935
|
+
const operation = opts.recommend ? "skills.find.recommend" : "skills.find.search";
|
|
1936
|
+
const details = Boolean(opts.details);
|
|
1937
|
+
const mvi = !details;
|
|
1938
|
+
let format;
|
|
1939
|
+
try {
|
|
1940
|
+
format = resolveOutputFormat2({
|
|
1941
|
+
jsonFlag: opts.json ?? false,
|
|
1942
|
+
humanFlag: (opts.human ?? false) || isHuman(),
|
|
1943
|
+
projectDefault: "json"
|
|
1944
|
+
}).format;
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1947
|
+
if (opts.json) {
|
|
1948
|
+
emitJsonError2(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
|
|
1949
|
+
} else {
|
|
1950
|
+
console.error(pc14.red(message));
|
|
1951
|
+
}
|
|
1952
|
+
process.exit(1);
|
|
1963
1953
|
}
|
|
1964
|
-
if (
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1954
|
+
if (opts.recommend) {
|
|
1955
|
+
try {
|
|
1956
|
+
const top = parseTop(opts.top);
|
|
1957
|
+
const mustHave = parseConstraintList(opts.mustHave);
|
|
1958
|
+
const prefer = parseConstraintList(opts.prefer);
|
|
1959
|
+
const exclude = parseConstraintList(opts.exclude);
|
|
1960
|
+
validateCriteriaConflicts(mustHave, prefer, exclude);
|
|
1961
|
+
const selectedRanks = parseSelectList(opts.select);
|
|
1962
|
+
const seedQuery = buildSeedQuery(query, mustHave, prefer, exclude);
|
|
1963
|
+
const recommendation = await recommendSkills(
|
|
1964
|
+
seedQuery,
|
|
1965
|
+
{
|
|
1966
|
+
mustHave,
|
|
1967
|
+
prefer,
|
|
1968
|
+
exclude
|
|
1969
|
+
},
|
|
1970
|
+
{
|
|
1971
|
+
top,
|
|
1972
|
+
includeDetails: details
|
|
1973
|
+
}
|
|
1974
|
+
);
|
|
1975
|
+
const options = normalizeRecommendationOptions(recommendation.ranking, details);
|
|
1976
|
+
validateSelectedRanks(selectedRanks, options.length);
|
|
1977
|
+
const selected = selectedRanks.length > 0 ? options.filter((option) => selectedRanks.includes(option.rank)) : [];
|
|
1978
|
+
if (format === "json") {
|
|
1979
|
+
const result = formatSkillRecommendations(recommendation, { mode: "json", details });
|
|
1980
|
+
const resultOptions = Array.isArray(result.options) ? result.options : [];
|
|
1981
|
+
const selectedObjects = resultOptions.filter(
|
|
1982
|
+
(option) => selectedRanks.includes(Number(option.rank ?? 0))
|
|
1983
|
+
);
|
|
1984
|
+
const envelope = buildEnvelope2(
|
|
1985
|
+
operation,
|
|
1986
|
+
mvi,
|
|
1987
|
+
{
|
|
1988
|
+
...result,
|
|
1989
|
+
selected: selectedObjects
|
|
1990
|
+
},
|
|
1991
|
+
null
|
|
1992
|
+
);
|
|
1993
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
const human = formatSkillRecommendations(recommendation, { mode: "human", details });
|
|
1997
|
+
console.log(human);
|
|
1998
|
+
if (selected.length > 0) {
|
|
1999
|
+
console.log(`Selected: ${selected.map((option) => option.scopedName).join(", ")}`);
|
|
2000
|
+
}
|
|
2001
|
+
return;
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2004
|
+
const errorCode = error instanceof SkillsFindValidationError ? error.code : error.code ?? RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
|
|
2005
|
+
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
|
+
if (format === "json") {
|
|
2007
|
+
emitJsonError2(operation, mvi, errorCode, message, category, {
|
|
2008
|
+
query: query ?? null
|
|
2009
|
+
});
|
|
2010
|
+
} else {
|
|
2011
|
+
console.error(pc14.red(`Recommendation failed: ${message}`));
|
|
2012
|
+
}
|
|
2013
|
+
process.exit(1);
|
|
2014
|
+
}
|
|
1970
2015
|
}
|
|
1971
|
-
if (
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
`Invalid scope at index ${index}: ${String(scope)}`,
|
|
1975
|
-
"Use scope value 'project' or 'global'."
|
|
1976
|
-
);
|
|
2016
|
+
if (!query) {
|
|
2017
|
+
console.log(pc14.dim("Usage: caamp skills find <query>"));
|
|
2018
|
+
return;
|
|
1977
2019
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
}
|
|
1984
|
-
return operations;
|
|
1985
|
-
}
|
|
1986
|
-
async function readSkillOperations(path) {
|
|
1987
|
-
const value = await readJsonFile(path);
|
|
1988
|
-
if (!Array.isArray(value)) {
|
|
1989
|
-
throw new LAFSCommandError(
|
|
1990
|
-
"E_ADVANCED_VALIDATION_SKILL_ARRAY",
|
|
1991
|
-
`Skill operations file must be a JSON array: ${path}`,
|
|
1992
|
-
"Provide an array of objects with sourcePath and skillName fields."
|
|
1993
|
-
);
|
|
1994
|
-
}
|
|
1995
|
-
const operations = [];
|
|
1996
|
-
for (const [index, item] of value.entries()) {
|
|
1997
|
-
if (!item || typeof item !== "object") {
|
|
1998
|
-
throw new LAFSCommandError(
|
|
1999
|
-
"E_ADVANCED_VALIDATION_SKILL_ITEM",
|
|
2000
|
-
`Invalid skill operation at index ${index}`,
|
|
2001
|
-
"Each operation must be an object with sourcePath and skillName."
|
|
2002
|
-
);
|
|
2020
|
+
const limit = parseInt(opts.limit, 10);
|
|
2021
|
+
const client = new MarketplaceClient();
|
|
2022
|
+
if (format === "human") {
|
|
2023
|
+
console.log(pc14.dim(`Searching marketplaces for "${query}"...
|
|
2024
|
+
`));
|
|
2003
2025
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
"
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2026
|
+
let results;
|
|
2027
|
+
try {
|
|
2028
|
+
results = await client.search(query, limit);
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
const message = formatNetworkError(error);
|
|
2031
|
+
if (format === "json") {
|
|
2032
|
+
emitJsonError2(operation, mvi, "E_SEARCH_FAILED", message, "TRANSIENT", {
|
|
2033
|
+
query,
|
|
2034
|
+
limit
|
|
2035
|
+
});
|
|
2036
|
+
} else {
|
|
2037
|
+
console.error(pc14.red(`Marketplace search failed: ${message}`));
|
|
2038
|
+
}
|
|
2039
|
+
process.exit(1);
|
|
2014
2040
|
}
|
|
2015
|
-
if (
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2041
|
+
if (format === "json") {
|
|
2042
|
+
const envelope = buildEnvelope2(
|
|
2043
|
+
operation,
|
|
2044
|
+
mvi,
|
|
2045
|
+
{
|
|
2046
|
+
query,
|
|
2047
|
+
results,
|
|
2048
|
+
count: results.length,
|
|
2049
|
+
limit
|
|
2050
|
+
},
|
|
2051
|
+
null
|
|
2020
2052
|
);
|
|
2053
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
2054
|
+
return;
|
|
2021
2055
|
}
|
|
2022
|
-
if (
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
`Invalid isGlobal value at index ${index}`,
|
|
2026
|
-
"Set isGlobal to true or false when provided."
|
|
2027
|
-
);
|
|
2056
|
+
if (results.length === 0) {
|
|
2057
|
+
console.log(pc14.yellow("No results found."));
|
|
2058
|
+
return;
|
|
2028
2059
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2060
|
+
console.log(pc14.dim(`Found ${results.length} result(s) for "${query}":
|
|
2061
|
+
`));
|
|
2062
|
+
results.forEach((skill, index) => {
|
|
2063
|
+
const num = (index + 1).toString().padStart(2);
|
|
2064
|
+
const stars = skill.stars > 0 ? pc14.yellow(`\u2605 ${formatStars(skill.stars)}`) : "";
|
|
2065
|
+
console.log(` ${pc14.cyan(num)}. ${pc14.bold(skill.scopedName)} ${stars}`);
|
|
2066
|
+
console.log(` ${pc14.dim(skill.description?.slice(0, 80) ?? "")}`);
|
|
2067
|
+
console.log(` ${pc14.dim(`from ${skill.source}`)}`);
|
|
2068
|
+
console.log();
|
|
2033
2069
|
});
|
|
2070
|
+
console.log(pc14.dim("Install with: caamp skills install <name>"));
|
|
2071
|
+
console.log(pc14.dim("Or select by number: caamp skills install <n>"));
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
function formatStars(n) {
|
|
2075
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2076
|
+
return String(n);
|
|
2077
|
+
}
|
|
2078
|
+
function parseConstraintList(values) {
|
|
2079
|
+
const normalized = values.flatMap((value) => tokenizeCriteriaValue(value));
|
|
2080
|
+
return Array.from(new Set(normalized));
|
|
2081
|
+
}
|
|
2082
|
+
function parseTop(value) {
|
|
2083
|
+
const parsed = Number.parseInt(value, 10);
|
|
2084
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 20) {
|
|
2085
|
+
throw new SkillsFindValidationError(RECOMMENDATION_ERROR_CODES.QUERY_INVALID, "--top must be an integer between 1 and 20");
|
|
2034
2086
|
}
|
|
2035
|
-
return
|
|
2087
|
+
return parsed;
|
|
2036
2088
|
}
|
|
2037
|
-
|
|
2038
|
-
if (
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2089
|
+
function parseSelectList(value) {
|
|
2090
|
+
if (!value) return [];
|
|
2091
|
+
const parsed = value.split(",").map((entry) => Number.parseInt(entry.trim(), 10)).filter((entry) => Number.isInteger(entry) && entry > 0);
|
|
2092
|
+
return Array.from(new Set(parsed));
|
|
2093
|
+
}
|
|
2094
|
+
function buildSeedQuery(query, mustHave, prefer, exclude) {
|
|
2095
|
+
if (query && query.trim().length > 0) {
|
|
2096
|
+
return query;
|
|
2044
2097
|
}
|
|
2045
|
-
|
|
2046
|
-
if (
|
|
2047
|
-
|
|
2048
|
-
return await readFile(filePath, "utf-8");
|
|
2049
|
-
} catch (error) {
|
|
2050
|
-
throw new LAFSCommandError(
|
|
2051
|
-
"E_ADVANCED_INPUT_TEXT",
|
|
2052
|
-
`Failed to read content file: ${filePath}`,
|
|
2053
|
-
"Confirm the file exists and is readable.",
|
|
2054
|
-
true,
|
|
2055
|
-
{ reason: error instanceof Error ? error.message : String(error) }
|
|
2056
|
-
);
|
|
2098
|
+
const seedTerms = [...mustHave, ...prefer, ...exclude].filter((term) => term.length > 0);
|
|
2099
|
+
if (seedTerms.length > 0) {
|
|
2100
|
+
return seedTerms.join(" ");
|
|
2057
2101
|
}
|
|
2102
|
+
throw new SkillsFindValidationError(
|
|
2103
|
+
RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2104
|
+
"Recommendation mode requires a query or at least one criteria flag."
|
|
2105
|
+
);
|
|
2058
2106
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
parent.command("providers").description("Select providers by priority using advanced wrapper logic").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--details", "Include full provider objects").action(async (opts) => runLafsCommand("advanced.providers", !opts.details, async () => {
|
|
2063
|
-
const providers = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
2064
|
-
const minTier = parsePriority(opts.minTier);
|
|
2065
|
-
const selected = selectProvidersByMinimumPriority(providers, minTier);
|
|
2107
|
+
function normalizeRecommendationOptions(ranking, details) {
|
|
2108
|
+
return ranking.map((entry, index) => {
|
|
2109
|
+
const whyCodes = entry.reasons.map((reason) => reason.code);
|
|
2066
2110
|
return {
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
status: provider.status,
|
|
2080
|
-
configFormat: provider.configFormat
|
|
2081
|
-
}))
|
|
2111
|
+
rank: index + 1,
|
|
2112
|
+
scopedName: entry.skill.scopedName,
|
|
2113
|
+
description: entry.skill.description,
|
|
2114
|
+
score: entry.score,
|
|
2115
|
+
why: whyCodes.length > 0 ? whyCodes.join(", ") : "score-based match",
|
|
2116
|
+
source: entry.skill.source,
|
|
2117
|
+
...details ? {
|
|
2118
|
+
evidence: {
|
|
2119
|
+
reasons: entry.reasons,
|
|
2120
|
+
breakdown: entry.breakdown
|
|
2121
|
+
}
|
|
2122
|
+
} : {}
|
|
2082
2123
|
};
|
|
2083
|
-
})
|
|
2124
|
+
});
|
|
2084
2125
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2126
|
+
function validateCriteriaConflicts(mustHave, prefer, exclude) {
|
|
2127
|
+
const overlap = mustHave.filter((term) => exclude.includes(term));
|
|
2128
|
+
if (overlap.length > 0) {
|
|
2129
|
+
throw new SkillsFindValidationError(
|
|
2130
|
+
RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
2131
|
+
"A criteria term cannot be both required and excluded."
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
const preferOverlap = prefer.filter((term) => exclude.includes(term));
|
|
2135
|
+
if (preferOverlap.length > 0) {
|
|
2136
|
+
throw new SkillsFindValidationError(
|
|
2137
|
+
RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
2138
|
+
"A criteria term cannot be both preferred and excluded."
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
function validateSelectedRanks(selectedRanks, total) {
|
|
2143
|
+
for (const rank of selectedRanks) {
|
|
2144
|
+
if (rank < 1 || rank > total) {
|
|
2145
|
+
throw new SkillsFindValidationError(
|
|
2146
|
+
RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2147
|
+
`--select rank ${rank} is out of range (1-${total}).`
|
|
2099
2148
|
);
|
|
2100
2149
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function buildEnvelope2(operation, mvi, result, error) {
|
|
2153
|
+
return {
|
|
2154
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
2155
|
+
_meta: {
|
|
2156
|
+
specVersion: "1.0.0",
|
|
2157
|
+
schemaVersion: "1.0.0",
|
|
2158
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2159
|
+
operation,
|
|
2160
|
+
requestId: randomUUID3(),
|
|
2161
|
+
transport: "cli",
|
|
2162
|
+
strict: true,
|
|
2163
|
+
mvi,
|
|
2164
|
+
contextVersion: 0
|
|
2165
|
+
},
|
|
2166
|
+
success: error === null,
|
|
2167
|
+
result,
|
|
2168
|
+
error,
|
|
2169
|
+
page: null
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
function emitJsonError2(operation, mvi, code, message, category, details = {}) {
|
|
2173
|
+
const envelope = buildEnvelope2(operation, mvi, null, {
|
|
2174
|
+
code,
|
|
2175
|
+
message,
|
|
2176
|
+
category,
|
|
2177
|
+
retryable: false,
|
|
2178
|
+
retryAfterMs: null,
|
|
2179
|
+
details
|
|
2180
|
+
});
|
|
2181
|
+
console.error(JSON.stringify(envelope, null, 2));
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// src/commands/skills/check.ts
|
|
2185
|
+
import pc15 from "picocolors";
|
|
2186
|
+
function registerSkillsCheck(parent) {
|
|
2187
|
+
parent.command("check").description("Check for available skill updates").option("--json", "Output as JSON").action(async (opts) => {
|
|
2188
|
+
const tracked = await getTrackedSkills();
|
|
2189
|
+
const entries = Object.entries(tracked);
|
|
2190
|
+
if (entries.length === 0) {
|
|
2191
|
+
console.log(pc15.dim("No tracked skills."));
|
|
2192
|
+
return;
|
|
2107
2193
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
});
|
|
2115
|
-
if (!result.success) {
|
|
2116
|
-
throw new LAFSCommandError(
|
|
2117
|
-
"E_ADVANCED_BATCH_FAILED",
|
|
2118
|
-
result.error ?? "Batch operation failed.",
|
|
2119
|
-
"Check rollbackErrors and input configs, then retry.",
|
|
2120
|
-
true,
|
|
2121
|
-
result
|
|
2122
|
-
);
|
|
2194
|
+
console.log(pc15.dim(`Checking ${entries.length} skill(s) for updates...
|
|
2195
|
+
`));
|
|
2196
|
+
const results = [];
|
|
2197
|
+
for (const [name, entry] of entries) {
|
|
2198
|
+
const update = await checkSkillUpdate(name);
|
|
2199
|
+
results.push({ name, entry, ...update });
|
|
2123
2200
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
mcpApplied: result.mcpApplied,
|
|
2139
|
-
skillsApplied: result.skillsApplied,
|
|
2140
|
-
rollbackPerformed: result.rollbackPerformed
|
|
2201
|
+
if (opts.json) {
|
|
2202
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
let updatesAvailable = 0;
|
|
2206
|
+
for (const r of results) {
|
|
2207
|
+
let statusLabel;
|
|
2208
|
+
if (r.status === "update-available") {
|
|
2209
|
+
statusLabel = pc15.yellow("update available");
|
|
2210
|
+
updatesAvailable++;
|
|
2211
|
+
} else if (r.status === "up-to-date") {
|
|
2212
|
+
statusLabel = pc15.green("up to date");
|
|
2213
|
+
} else {
|
|
2214
|
+
statusLabel = pc15.dim("unknown");
|
|
2141
2215
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2216
|
+
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 ?? "?";
|
|
2220
|
+
if (r.hasUpdate) {
|
|
2221
|
+
console.log(` ${pc15.dim("current:")} ${current} ${pc15.dim("->")} ${pc15.cyan(latest)}`);
|
|
2222
|
+
} else {
|
|
2223
|
+
console.log(` ${pc15.dim("version:")} ${current}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
console.log(` ${pc15.dim(`source: ${r.entry.source}`)}`);
|
|
2227
|
+
console.log(` ${pc15.dim(`agents: ${r.entry.agents.join(", ")}`)}`);
|
|
2228
|
+
console.log();
|
|
2229
|
+
}
|
|
2230
|
+
if (updatesAvailable > 0) {
|
|
2231
|
+
console.log(pc15.yellow(`${updatesAvailable} update(s) available.`));
|
|
2232
|
+
console.log(pc15.dim("Run `caamp skills update` to update all."));
|
|
2233
|
+
} else {
|
|
2234
|
+
console.log(pc15.green("All skills are up to date."));
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2144
2237
|
}
|
|
2145
2238
|
|
|
2146
|
-
// src/commands/
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
const
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
2156
|
-
"No target providers resolved for conflict detection.",
|
|
2157
|
-
"Use --all or pass provider IDs with --agent."
|
|
2158
|
-
);
|
|
2239
|
+
// src/commands/skills/update.ts
|
|
2240
|
+
import pc16 from "picocolors";
|
|
2241
|
+
function registerSkillsUpdate(parent) {
|
|
2242
|
+
parent.command("update").description("Update all outdated skills").option("-y, --yes", "Skip confirmation").action(async (opts) => {
|
|
2243
|
+
const tracked = await getTrackedSkills();
|
|
2244
|
+
const entries = Object.entries(tracked);
|
|
2245
|
+
if (entries.length === 0) {
|
|
2246
|
+
console.log(pc16.dim("No tracked skills to update."));
|
|
2247
|
+
return;
|
|
2159
2248
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
objective: "Detect MCP configuration conflicts before mutation",
|
|
2171
|
-
constraints: {
|
|
2172
|
-
minimumPriority,
|
|
2173
|
-
providerCount: providers.length,
|
|
2174
|
-
operationCount: operations.length
|
|
2175
|
-
},
|
|
2176
|
-
acceptanceCriteria: {
|
|
2177
|
-
conflictCount: conflicts.length
|
|
2178
|
-
},
|
|
2179
|
-
data: opts.details ? conflicts : {
|
|
2180
|
-
conflictCount: conflicts.length,
|
|
2181
|
-
countByCode,
|
|
2182
|
-
sample: conflicts.slice(0, 5)
|
|
2249
|
+
console.log(pc16.dim(`Checking ${entries.length} skill(s) for updates...`));
|
|
2250
|
+
const outdated = [];
|
|
2251
|
+
for (const [name] of entries) {
|
|
2252
|
+
const result = await checkSkillUpdate(name);
|
|
2253
|
+
if (result.hasUpdate) {
|
|
2254
|
+
outdated.push({
|
|
2255
|
+
name,
|
|
2256
|
+
currentVersion: result.currentVersion,
|
|
2257
|
+
latestVersion: result.latestVersion
|
|
2258
|
+
});
|
|
2183
2259
|
}
|
|
2184
|
-
};
|
|
2185
|
-
}));
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
// src/commands/advanced/apply.ts
|
|
2189
|
-
var VALID_POLICIES = /* @__PURE__ */ new Set(["fail", "skip", "overwrite"]);
|
|
2190
|
-
function parsePolicy(value) {
|
|
2191
|
-
if (!VALID_POLICIES.has(value)) {
|
|
2192
|
-
throw new LAFSCommandError(
|
|
2193
|
-
"E_ADVANCED_VALIDATION_POLICY",
|
|
2194
|
-
`Invalid policy: ${value}`,
|
|
2195
|
-
"Use one of: fail, skip, overwrite."
|
|
2196
|
-
);
|
|
2197
|
-
}
|
|
2198
|
-
return value;
|
|
2199
|
-
}
|
|
2200
|
-
function registerAdvancedApply(parent) {
|
|
2201
|
-
parent.command("apply").description("Apply MCP operations with configurable conflict policy").requiredOption("--mcp-file <path>", "JSON file containing McpBatchOperation[]").option("--policy <policy>", "Conflict policy: fail|skip|overwrite", "fail").option("-a, --agent <name>", "Target specific provider(s)", (v, prev) => [...prev, v], []).option("--all", "Use all registry providers (not only detected)").option("--min-tier <tier>", "Minimum priority tier: high|medium|low", "low").option("--project-dir <path>", "Project directory to resolve project-scope paths").option("--details", "Include detailed apply result").action(async (opts) => runLafsCommand("advanced.apply", !opts.details, async () => {
|
|
2202
|
-
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
2203
|
-
const minimumPriority = parsePriority(opts.minTier);
|
|
2204
|
-
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
2205
|
-
const operations = await readMcpOperations(opts.mcpFile);
|
|
2206
|
-
const policy = parsePolicy(opts.policy);
|
|
2207
|
-
if (providers.length === 0) {
|
|
2208
|
-
throw new LAFSCommandError(
|
|
2209
|
-
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
2210
|
-
"No target providers resolved for apply operation.",
|
|
2211
|
-
"Use --all or pass provider IDs with --agent."
|
|
2212
|
-
);
|
|
2213
2260
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
policy,
|
|
2218
|
-
opts.projectDir
|
|
2219
|
-
);
|
|
2220
|
-
if (policy === "fail" && result.conflicts.length > 0) {
|
|
2221
|
-
throw new LAFSCommandError(
|
|
2222
|
-
"E_ADVANCED_CONFLICTS_BLOCKING",
|
|
2223
|
-
"Conflicts detected and policy is set to fail.",
|
|
2224
|
-
"Run `caamp advanced conflicts` to inspect, or rerun with --policy skip/overwrite.",
|
|
2225
|
-
true,
|
|
2226
|
-
result
|
|
2227
|
-
);
|
|
2261
|
+
if (outdated.length === 0) {
|
|
2262
|
+
console.log(pc16.green("\nAll skills are up to date."));
|
|
2263
|
+
return;
|
|
2228
2264
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
result
|
|
2237
|
-
);
|
|
2265
|
+
console.log(pc16.yellow(`
|
|
2266
|
+
${outdated.length} skill(s) have updates available:
|
|
2267
|
+
`));
|
|
2268
|
+
for (const skill of outdated) {
|
|
2269
|
+
const current = skill.currentVersion?.slice(0, 12) ?? "?";
|
|
2270
|
+
const latest = skill.latestVersion ?? "?";
|
|
2271
|
+
console.log(` ${pc16.bold(skill.name)} ${pc16.dim(current)} ${pc16.dim("->")} ${pc16.cyan(latest)}`);
|
|
2238
2272
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2273
|
+
if (!opts.yes) {
|
|
2274
|
+
const readline = await import("readline");
|
|
2275
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2276
|
+
const answer = await new Promise((resolve) => {
|
|
2277
|
+
rl.question(pc16.dim("\nProceed with update? [y/N] "), resolve);
|
|
2278
|
+
});
|
|
2279
|
+
rl.close();
|
|
2280
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
2281
|
+
console.log(pc16.dim("Update cancelled."));
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
console.log();
|
|
2286
|
+
let successCount = 0;
|
|
2287
|
+
let failCount = 0;
|
|
2288
|
+
for (const skill of outdated) {
|
|
2289
|
+
const entry = tracked[skill.name];
|
|
2290
|
+
if (!entry) continue;
|
|
2291
|
+
console.log(pc16.dim(`Updating ${pc16.bold(skill.name)}...`));
|
|
2292
|
+
try {
|
|
2293
|
+
const parsed = parseSource(entry.source);
|
|
2294
|
+
let localPath;
|
|
2295
|
+
let cleanup;
|
|
2296
|
+
if (parsed.type === "github" && parsed.owner && parsed.repo) {
|
|
2297
|
+
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
2298
|
+
localPath = result.localPath;
|
|
2299
|
+
cleanup = result.cleanup;
|
|
2300
|
+
} else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
|
|
2301
|
+
const result = await cloneGitLabRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
2302
|
+
localPath = result.localPath;
|
|
2303
|
+
cleanup = result.cleanup;
|
|
2304
|
+
} else {
|
|
2305
|
+
console.log(pc16.yellow(` Skipped ${skill.name}: source type "${parsed.type}" does not support auto-update`));
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
try {
|
|
2309
|
+
const providers = entry.agents.map((a) => getProvider(a)).filter((p) => p !== void 0);
|
|
2310
|
+
if (providers.length === 0) {
|
|
2311
|
+
console.log(pc16.yellow(` Skipped ${skill.name}: no valid providers found`));
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
const installResult = await installSkill(
|
|
2315
|
+
localPath,
|
|
2316
|
+
skill.name,
|
|
2317
|
+
providers,
|
|
2318
|
+
entry.isGlobal,
|
|
2319
|
+
entry.projectDir
|
|
2320
|
+
);
|
|
2321
|
+
if (installResult.success) {
|
|
2322
|
+
await recordSkillInstall(
|
|
2323
|
+
skill.name,
|
|
2324
|
+
entry.scopedName,
|
|
2325
|
+
entry.source,
|
|
2326
|
+
entry.sourceType,
|
|
2327
|
+
installResult.linkedAgents,
|
|
2328
|
+
installResult.canonicalPath,
|
|
2329
|
+
entry.isGlobal,
|
|
2330
|
+
entry.projectDir,
|
|
2331
|
+
skill.latestVersion
|
|
2332
|
+
);
|
|
2333
|
+
console.log(pc16.green(` Updated ${pc16.bold(skill.name)}`));
|
|
2334
|
+
successCount++;
|
|
2335
|
+
} else {
|
|
2336
|
+
console.log(pc16.red(` Failed to update ${skill.name}: no agents linked`));
|
|
2337
|
+
failCount++;
|
|
2338
|
+
}
|
|
2339
|
+
if (installResult.errors.length > 0) {
|
|
2340
|
+
for (const err of installResult.errors) {
|
|
2341
|
+
console.log(pc16.yellow(` ${err}`));
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
} finally {
|
|
2345
|
+
if (cleanup) await cleanup();
|
|
2346
|
+
}
|
|
2347
|
+
} catch (err) {
|
|
2348
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2349
|
+
console.log(pc16.red(` Failed to update ${skill.name}: ${msg}`));
|
|
2350
|
+
failCount++;
|
|
2255
2351
|
}
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2352
|
+
}
|
|
2353
|
+
console.log();
|
|
2354
|
+
if (successCount > 0) {
|
|
2355
|
+
console.log(pc16.green(`Updated ${successCount} skill(s).`));
|
|
2356
|
+
}
|
|
2357
|
+
if (failCount > 0) {
|
|
2358
|
+
console.log(pc16.red(`Failed to update ${failCount} skill(s).`));
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2258
2361
|
}
|
|
2259
2362
|
|
|
2260
|
-
// src/commands/
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
);
|
|
2363
|
+
// src/commands/skills/init.ts
|
|
2364
|
+
import pc17 from "picocolors";
|
|
2365
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
2366
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2367
|
+
import { join as join5 } from "path";
|
|
2368
|
+
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) => {
|
|
2370
|
+
const skillName = name ?? "my-skill";
|
|
2371
|
+
const skillDir = join5(opts.dir, skillName);
|
|
2372
|
+
if (existsSync5(skillDir)) {
|
|
2373
|
+
console.error(pc17.red(`Directory already exists: ${skillDir}`));
|
|
2374
|
+
process.exit(1);
|
|
2273
2375
|
}
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2376
|
+
await mkdir(skillDir, { recursive: true });
|
|
2377
|
+
const template = `---
|
|
2378
|
+
name: ${skillName}
|
|
2379
|
+
description: Describe what this skill does and when to use it
|
|
2380
|
+
license: MIT
|
|
2381
|
+
metadata:
|
|
2382
|
+
author: your-name
|
|
2383
|
+
version: "1.0"
|
|
2384
|
+
---
|
|
2385
|
+
|
|
2386
|
+
# ${skillName}
|
|
2387
|
+
|
|
2388
|
+
## When to use this skill
|
|
2389
|
+
|
|
2390
|
+
Describe the conditions under which an AI agent should activate this skill.
|
|
2391
|
+
|
|
2392
|
+
## Instructions
|
|
2393
|
+
|
|
2394
|
+
Provide detailed instructions for the AI agent here.
|
|
2395
|
+
|
|
2396
|
+
## Examples
|
|
2397
|
+
|
|
2398
|
+
Show example inputs and expected outputs.
|
|
2399
|
+
`;
|
|
2400
|
+
await writeFile(join5(skillDir, "SKILL.md"), template, "utf-8");
|
|
2401
|
+
console.log(pc17.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
|
|
2402
|
+
console.log(pc17.dim("\nNext steps:"));
|
|
2403
|
+
console.log(pc17.dim(" 1. Edit SKILL.md with your instructions"));
|
|
2404
|
+
console.log(pc17.dim(` 2. Validate: caamp skills validate ${join5(skillDir, "SKILL.md")}`));
|
|
2405
|
+
console.log(pc17.dim(` 3. Install: caamp skills install ${skillDir}`));
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/commands/skills/audit.ts
|
|
2410
|
+
import { existsSync as existsSync6, statSync } from "fs";
|
|
2411
|
+
import pc18 from "picocolors";
|
|
2412
|
+
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) => {
|
|
2414
|
+
if (!existsSync6(path)) {
|
|
2415
|
+
console.error(pc18.red(`Path not found: ${path}`));
|
|
2416
|
+
process.exit(1);
|
|
2281
2417
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
);
|
|
2418
|
+
const stat = statSync(path);
|
|
2419
|
+
let results;
|
|
2420
|
+
if (stat.isFile()) {
|
|
2421
|
+
results = [await scanFile(path)];
|
|
2422
|
+
} else {
|
|
2423
|
+
results = await scanDirectory(path);
|
|
2288
2424
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
}))
|
|
2425
|
+
if (results.length === 0) {
|
|
2426
|
+
console.log(pc18.dim("No SKILL.md files found to scan."));
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (opts.sarif) {
|
|
2430
|
+
console.log(JSON.stringify(toSarif(results), null, 2));
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
if (opts.json) {
|
|
2434
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
let totalFindings = 0;
|
|
2438
|
+
let allPassed = true;
|
|
2439
|
+
for (const result of results) {
|
|
2440
|
+
const icon = result.passed ? pc18.green("\u2713") : pc18.red("\u2717");
|
|
2441
|
+
console.log(`
|
|
2442
|
+
${icon} ${pc18.bold(result.file)} (score: ${result.score}/100)`);
|
|
2443
|
+
if (result.findings.length === 0) {
|
|
2444
|
+
console.log(pc18.dim(" No issues found."));
|
|
2445
|
+
continue;
|
|
2311
2446
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2447
|
+
totalFindings += result.findings.length;
|
|
2448
|
+
if (!result.passed) allPassed = false;
|
|
2449
|
+
for (const f of result.findings) {
|
|
2450
|
+
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
|
+
console.log(` ${sev.padEnd(20)} ${f.rule.id} ${f.rule.name}`);
|
|
2452
|
+
console.log(` ${pc18.dim(`L${f.line}: ${f.context.slice(0, 80)}`)}`);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
console.log(pc18.bold(`
|
|
2456
|
+
${results.length} file(s) scanned, ${totalFindings} finding(s)`));
|
|
2457
|
+
if (!allPassed) {
|
|
2458
|
+
process.exit(1);
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2314
2461
|
}
|
|
2315
2462
|
|
|
2316
|
-
// src/commands/
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
2325
|
-
);
|
|
2463
|
+
// src/commands/skills/validate.ts
|
|
2464
|
+
import pc19 from "picocolors";
|
|
2465
|
+
function registerSkillsValidate(parent) {
|
|
2466
|
+
parent.command("validate").description("Validate SKILL.md format").argument("[path]", "Path to SKILL.md", "SKILL.md").option("--json", "Output as JSON").action(async (path, opts) => {
|
|
2467
|
+
const result = await validateSkill(path);
|
|
2468
|
+
if (opts.json) {
|
|
2469
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2470
|
+
return;
|
|
2326
2471
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
opts.instructionGlobal,
|
|
2332
|
-
opts.instructionGlobalFile
|
|
2333
|
-
);
|
|
2334
|
-
const projectInstruction = await readTextInput(
|
|
2335
|
-
opts.instructionProject,
|
|
2336
|
-
opts.instructionProjectFile
|
|
2337
|
-
);
|
|
2338
|
-
let instructionContent;
|
|
2339
|
-
if (globalInstruction || projectInstruction) {
|
|
2340
|
-
instructionContent = {
|
|
2341
|
-
...globalInstruction ? { global: globalInstruction } : {},
|
|
2342
|
-
...projectInstruction ? { project: projectInstruction } : {}
|
|
2343
|
-
};
|
|
2344
|
-
} else if (sharedInstruction) {
|
|
2345
|
-
instructionContent = sharedInstruction;
|
|
2472
|
+
if (result.valid) {
|
|
2473
|
+
console.log(pc19.green(`\u2713 ${path} is valid`));
|
|
2474
|
+
} else {
|
|
2475
|
+
console.log(pc19.red(`\u2717 ${path} has validation errors`));
|
|
2346
2476
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
"No configuration operations were provided.",
|
|
2351
|
-
"Provide MCP files and/or instruction content."
|
|
2352
|
-
);
|
|
2477
|
+
for (const issue of result.issues) {
|
|
2478
|
+
const icon = issue.level === "error" ? pc19.red("\u2717") : pc19.yellow("!");
|
|
2479
|
+
console.log(` ${icon} [${issue.field}] ${issue.message}`);
|
|
2353
2480
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
serverName: entry.serverName,
|
|
2357
|
-
config: entry.config
|
|
2358
|
-
})),
|
|
2359
|
-
projectMcp: projectMcp.map((entry) => ({
|
|
2360
|
-
serverName: entry.serverName,
|
|
2361
|
-
config: entry.config
|
|
2362
|
-
})),
|
|
2363
|
-
instructionContent,
|
|
2364
|
-
projectDir: opts.projectDir
|
|
2365
|
-
});
|
|
2366
|
-
const globalFailures = result.mcp.global.filter((entry) => !entry.success);
|
|
2367
|
-
const projectFailures = result.mcp.project.filter((entry) => !entry.success);
|
|
2368
|
-
if (globalFailures.length > 0 || projectFailures.length > 0) {
|
|
2369
|
-
throw new LAFSCommandError(
|
|
2370
|
-
"E_ADVANCED_CONFIGURE_FAILED",
|
|
2371
|
-
"One or more MCP writes failed during configure operation.",
|
|
2372
|
-
"Inspect the failed write entries and provider config paths, then retry.",
|
|
2373
|
-
true,
|
|
2374
|
-
result
|
|
2375
|
-
);
|
|
2481
|
+
if (!result.valid) {
|
|
2482
|
+
process.exit(1);
|
|
2376
2483
|
}
|
|
2377
|
-
|
|
2378
|
-
objective: "Configure global and project settings in one operation",
|
|
2379
|
-
constraints: {
|
|
2380
|
-
provider: provider.id,
|
|
2381
|
-
globalMcpOps: globalMcp.length,
|
|
2382
|
-
projectMcpOps: projectMcp.length,
|
|
2383
|
-
instructionMode: instructionContent ? typeof instructionContent === "string" ? "shared" : "scoped" : "none"
|
|
2384
|
-
},
|
|
2385
|
-
acceptanceCriteria: {
|
|
2386
|
-
globalWrites: result.mcp.global.length,
|
|
2387
|
-
projectWrites: result.mcp.project.length
|
|
2388
|
-
},
|
|
2389
|
-
data: opts.details ? result : {
|
|
2390
|
-
providerId: result.providerId,
|
|
2391
|
-
configPaths: result.configPaths,
|
|
2392
|
-
globalWrites: result.mcp.global.length,
|
|
2393
|
-
projectWrites: result.mcp.project.length,
|
|
2394
|
-
instructionUpdates: {
|
|
2395
|
-
global: result.instructions.global?.size ?? 0,
|
|
2396
|
-
project: result.instructions.project?.size ?? 0
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
};
|
|
2400
|
-
}));
|
|
2484
|
+
});
|
|
2401
2485
|
}
|
|
2402
2486
|
|
|
2403
|
-
// src/commands/
|
|
2404
|
-
function
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2487
|
+
// src/commands/skills/index.ts
|
|
2488
|
+
function registerSkillsCommands(program2) {
|
|
2489
|
+
const skills = program2.command("skills").description("Manage AI agent skills");
|
|
2490
|
+
registerSkillsInstall(skills);
|
|
2491
|
+
registerSkillsRemove(skills);
|
|
2492
|
+
registerSkillsList(skills);
|
|
2493
|
+
registerSkillsFind(skills);
|
|
2494
|
+
registerSkillsCheck(skills);
|
|
2495
|
+
registerSkillsUpdate(skills);
|
|
2496
|
+
registerSkillsInit(skills);
|
|
2497
|
+
registerSkillsAudit(skills);
|
|
2498
|
+
registerSkillsValidate(skills);
|
|
2412
2499
|
}
|
|
2413
2500
|
|
|
2414
2501
|
// src/cli.ts
|
|
2415
2502
|
var program = new Command();
|
|
2416
|
-
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version(getCaampVersion()).option("-v, --verbose", "Show debug output").option("-q, --quiet", "Suppress non-error output");
|
|
2503
|
+
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version(getCaampVersion()).option("-v, --verbose", "Show debug output").option("-q, --quiet", "Suppress non-error output").option("--human", "Output in human-readable format (default: JSON for LLM agents)");
|
|
2417
2504
|
program.hook("preAction", (thisCommand) => {
|
|
2418
2505
|
const opts = thisCommand.optsWithGlobals();
|
|
2419
2506
|
if (opts.verbose) setVerbose(true);
|
|
2420
2507
|
if (opts.quiet) setQuiet(true);
|
|
2508
|
+
if (opts.human) setHuman(true);
|
|
2421
2509
|
});
|
|
2422
2510
|
registerProvidersCommand(program);
|
|
2423
2511
|
registerSkillsCommands(program);
|