@cleocode/caamp 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/{chunk-RW745KDU.js → chunk-ZYINKJDE.js} +948 -122
- package/dist/chunk-ZYINKJDE.js.map +1 -0
- package/dist/cli.js +1225 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1764 -80
- package/dist/index.js +39 -1
- package/package.json +14 -10
- package/dist/chunk-RW745KDU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
MarketplaceClient,
|
|
4
|
+
RECOMMENDATION_ERROR_CODES,
|
|
5
|
+
applyMcpInstallWithPolicy,
|
|
4
6
|
buildServerConfig,
|
|
5
7
|
checkAllInjections,
|
|
6
8
|
checkSkillUpdate,
|
|
9
|
+
configureProviderGlobalAndProject,
|
|
7
10
|
detectAllProviders,
|
|
11
|
+
detectMcpConfigConflicts,
|
|
8
12
|
detectProjectProviders,
|
|
9
13
|
discoverSkillsMulti,
|
|
14
|
+
formatNetworkError,
|
|
15
|
+
formatSkillRecommendations,
|
|
10
16
|
generateInjectionContent,
|
|
11
17
|
getAllProviders,
|
|
12
18
|
getInstalledProviders,
|
|
@@ -17,6 +23,7 @@ import {
|
|
|
17
23
|
getTrackedSkills,
|
|
18
24
|
groupByInstructFile,
|
|
19
25
|
injectAll,
|
|
26
|
+
installBatchWithRollback,
|
|
20
27
|
installMcpServerToAll,
|
|
21
28
|
installSkill,
|
|
22
29
|
isMarketplaceScoped,
|
|
@@ -24,6 +31,8 @@ import {
|
|
|
24
31
|
listMcpServers,
|
|
25
32
|
parseSource,
|
|
26
33
|
readConfig,
|
|
34
|
+
readLockFile,
|
|
35
|
+
recommendSkills,
|
|
27
36
|
recordMcpInstall,
|
|
28
37
|
recordSkillInstall,
|
|
29
38
|
removeMcpFromLock,
|
|
@@ -33,9 +42,14 @@ import {
|
|
|
33
42
|
resolveConfigPath,
|
|
34
43
|
scanDirectory,
|
|
35
44
|
scanFile,
|
|
45
|
+
selectProvidersByMinimumPriority,
|
|
46
|
+
setQuiet,
|
|
47
|
+
setVerbose,
|
|
36
48
|
toSarif,
|
|
49
|
+
tokenizeCriteriaValue,
|
|
50
|
+
updateInstructionsSingleOperation,
|
|
37
51
|
validateSkill
|
|
38
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-ZYINKJDE.js";
|
|
39
53
|
|
|
40
54
|
// src/cli.ts
|
|
41
55
|
import { Command } from "commander";
|
|
@@ -134,6 +148,7 @@ ${provider.toolName}`));
|
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
// src/commands/skills/install.ts
|
|
151
|
+
import { existsSync } from "fs";
|
|
137
152
|
import pc2 from "picocolors";
|
|
138
153
|
|
|
139
154
|
// src/core/sources/github.ts
|
|
@@ -189,6 +204,28 @@ async function cloneGitLabRepo(owner, repo, ref, subPath) {
|
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
// src/commands/skills/install.ts
|
|
207
|
+
function normalizeSkillSubPath(path) {
|
|
208
|
+
if (!path) return void 0;
|
|
209
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/SKILL\.md$/i, "").trim();
|
|
210
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
211
|
+
}
|
|
212
|
+
function marketplacePathCandidates(skillPath, parsedPath) {
|
|
213
|
+
const candidates = [];
|
|
214
|
+
const base = normalizeSkillSubPath(skillPath);
|
|
215
|
+
const parsed = normalizeSkillSubPath(parsedPath);
|
|
216
|
+
if (base) candidates.push(base);
|
|
217
|
+
if (parsed) candidates.push(parsed);
|
|
218
|
+
if (base && base.startsWith("skills/") && !base.startsWith(".claude/")) {
|
|
219
|
+
candidates.push(`.claude/${base}`);
|
|
220
|
+
}
|
|
221
|
+
if (parsed && parsed.startsWith("skills/") && !parsed.startsWith(".claude/")) {
|
|
222
|
+
candidates.push(`.claude/${parsed}`);
|
|
223
|
+
}
|
|
224
|
+
if (candidates.length === 0) {
|
|
225
|
+
candidates.push(void 0);
|
|
226
|
+
}
|
|
227
|
+
return Array.from(new Set(candidates));
|
|
228
|
+
}
|
|
192
229
|
function registerSkillsInstall(parent) {
|
|
193
230
|
parent.command("install").description("Install a skill from GitHub, URL, or marketplace").argument("<source>", "Skill source (GitHub URL, owner/repo, @author/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").action(async (source, opts) => {
|
|
194
231
|
let providers;
|
|
@@ -208,11 +245,17 @@ function registerSkillsInstall(parent) {
|
|
|
208
245
|
let cleanup;
|
|
209
246
|
let skillName;
|
|
210
247
|
let sourceValue;
|
|
211
|
-
let sourceType
|
|
248
|
+
let sourceType;
|
|
212
249
|
if (isMarketplaceScoped(source)) {
|
|
213
250
|
console.log(pc2.dim(`Searching marketplace for ${source}...`));
|
|
214
251
|
const client = new MarketplaceClient();
|
|
215
|
-
|
|
252
|
+
let skill;
|
|
253
|
+
try {
|
|
254
|
+
skill = await client.getSkill(source);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(pc2.red(`Marketplace lookup failed: ${formatNetworkError(error)}`));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
216
259
|
if (!skill) {
|
|
217
260
|
console.error(pc2.red(`Skill not found: ${source}`));
|
|
218
261
|
process.exit(1);
|
|
@@ -223,23 +266,58 @@ function registerSkillsInstall(parent) {
|
|
|
223
266
|
console.error(pc2.red("Could not resolve GitHub source"));
|
|
224
267
|
process.exit(1);
|
|
225
268
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
try {
|
|
270
|
+
const subPathCandidates = marketplacePathCandidates(skill.path, parsed.path);
|
|
271
|
+
let cloneError;
|
|
272
|
+
let cloned = false;
|
|
273
|
+
for (const subPath of subPathCandidates) {
|
|
274
|
+
try {
|
|
275
|
+
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
|
|
276
|
+
if (subPath && !existsSync(result.localPath)) {
|
|
277
|
+
await result.cleanup();
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
localPath = result.localPath;
|
|
281
|
+
cleanup = result.cleanup;
|
|
282
|
+
cloned = true;
|
|
283
|
+
break;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
cloneError = error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!cloned) {
|
|
289
|
+
throw cloneError ?? new Error("Unable to resolve skill path from marketplace metadata");
|
|
290
|
+
}
|
|
291
|
+
skillName = skill.name;
|
|
292
|
+
sourceValue = skill.githubUrl;
|
|
293
|
+
sourceType = parsed.type;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(pc2.red(`Failed to fetch source repository: ${formatNetworkError(error)}`));
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
231
298
|
} else {
|
|
232
299
|
const parsed = parseSource(source);
|
|
233
300
|
skillName = parsed.inferredName;
|
|
234
301
|
sourceValue = parsed.value;
|
|
302
|
+
sourceType = parsed.type;
|
|
235
303
|
if (parsed.type === "github" && parsed.owner && parsed.repo) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
304
|
+
try {
|
|
305
|
+
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
306
|
+
localPath = result.localPath;
|
|
307
|
+
cleanup = result.cleanup;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(pc2.red(`Failed to clone GitHub repository: ${formatNetworkError(error)}`));
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
239
312
|
} else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
313
|
+
try {
|
|
314
|
+
const result = await cloneGitLabRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
|
|
315
|
+
localPath = result.localPath;
|
|
316
|
+
cleanup = result.cleanup;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error(pc2.red(`Failed to clone GitLab repository: ${formatNetworkError(error)}`));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
243
321
|
} else if (parsed.type === "local") {
|
|
244
322
|
localPath = parsed.value;
|
|
245
323
|
} else {
|
|
@@ -248,6 +326,9 @@ function registerSkillsInstall(parent) {
|
|
|
248
326
|
}
|
|
249
327
|
}
|
|
250
328
|
try {
|
|
329
|
+
if (!localPath) {
|
|
330
|
+
throw new Error("No local skill path resolved for installation");
|
|
331
|
+
}
|
|
251
332
|
const result = await installSkill(
|
|
252
333
|
localPath,
|
|
253
334
|
skillName,
|
|
@@ -354,19 +435,125 @@ ${skills.length} skill(s) found:
|
|
|
354
435
|
}
|
|
355
436
|
|
|
356
437
|
// src/commands/skills/find.ts
|
|
438
|
+
import { randomUUID } from "crypto";
|
|
439
|
+
import {
|
|
440
|
+
resolveOutputFormat
|
|
441
|
+
} from "@cleocode/lafs-protocol";
|
|
357
442
|
import pc5 from "picocolors";
|
|
443
|
+
var SkillsFindValidationError = class extends Error {
|
|
444
|
+
code;
|
|
445
|
+
constructor(code, message) {
|
|
446
|
+
super(message);
|
|
447
|
+
this.code = code;
|
|
448
|
+
this.name = "SkillsFindValidationError";
|
|
449
|
+
}
|
|
450
|
+
};
|
|
358
451
|
function registerSkillsFind(parent) {
|
|
359
|
-
parent.command("find").description("Search marketplace for skills").argument("[query]", "Search query").option("--json", "Output as JSON").option("-l, --limit <n>", "Max results", "20").action(async (query, opts) => {
|
|
452
|
+
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) => {
|
|
453
|
+
const operation = opts.recommend ? "skills.find.recommend" : "skills.find.search";
|
|
454
|
+
const details = Boolean(opts.details);
|
|
455
|
+
const mvi = !details;
|
|
456
|
+
let format;
|
|
457
|
+
try {
|
|
458
|
+
format = resolveOutputFormat({
|
|
459
|
+
jsonFlag: opts.json ?? false,
|
|
460
|
+
humanFlag: opts.human ?? false,
|
|
461
|
+
projectDefault: opts.recommend ? "json" : "human"
|
|
462
|
+
}).format;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
465
|
+
if (opts.json) {
|
|
466
|
+
emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
|
|
467
|
+
} else {
|
|
468
|
+
console.error(pc5.red(message));
|
|
469
|
+
}
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
if (opts.recommend) {
|
|
473
|
+
try {
|
|
474
|
+
const top = parseTop(opts.top);
|
|
475
|
+
const mustHave = parseConstraintList(opts.mustHave);
|
|
476
|
+
const prefer = parseConstraintList(opts.prefer);
|
|
477
|
+
const exclude = parseConstraintList(opts.exclude);
|
|
478
|
+
validateCriteriaConflicts(mustHave, prefer, exclude);
|
|
479
|
+
const selectedRanks = parseSelectList(opts.select);
|
|
480
|
+
const seedQuery = buildSeedQuery(query, mustHave, prefer, exclude);
|
|
481
|
+
const recommendation = await recommendSkills(
|
|
482
|
+
seedQuery,
|
|
483
|
+
{
|
|
484
|
+
mustHave,
|
|
485
|
+
prefer,
|
|
486
|
+
exclude
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
top,
|
|
490
|
+
includeDetails: details
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
const options = normalizeRecommendationOptions(recommendation.ranking, details);
|
|
494
|
+
validateSelectedRanks(selectedRanks, options.length);
|
|
495
|
+
const selected = selectedRanks.length > 0 ? options.filter((option) => selectedRanks.includes(option.rank)) : [];
|
|
496
|
+
if (format === "json") {
|
|
497
|
+
const result = formatSkillRecommendations(recommendation, { mode: "json", details });
|
|
498
|
+
const resultOptions = Array.isArray(result.options) ? result.options : [];
|
|
499
|
+
const selectedObjects = resultOptions.filter(
|
|
500
|
+
(option) => selectedRanks.includes(Number(option.rank ?? 0))
|
|
501
|
+
);
|
|
502
|
+
const envelope = buildEnvelope(
|
|
503
|
+
operation,
|
|
504
|
+
mvi,
|
|
505
|
+
{
|
|
506
|
+
...result,
|
|
507
|
+
selected: selectedObjects
|
|
508
|
+
},
|
|
509
|
+
null
|
|
510
|
+
);
|
|
511
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const human = formatSkillRecommendations(recommendation, { mode: "human", details });
|
|
515
|
+
console.log(human);
|
|
516
|
+
if (selected.length > 0) {
|
|
517
|
+
console.log(`Selected: ${selected.map((option) => option.scopedName).join(", ")}`);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
522
|
+
const errorCode = error instanceof SkillsFindValidationError ? error.code : error.code ?? RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
|
|
523
|
+
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";
|
|
524
|
+
if (format === "json") {
|
|
525
|
+
emitJsonError(operation, mvi, errorCode, message, category, {
|
|
526
|
+
query: query ?? null
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
console.error(pc5.red(`Recommendation failed: ${message}`));
|
|
530
|
+
}
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
360
534
|
if (!query) {
|
|
361
535
|
console.log(pc5.dim("Usage: caamp skills find <query>"));
|
|
362
536
|
return;
|
|
363
537
|
}
|
|
364
538
|
const limit = parseInt(opts.limit, 10);
|
|
365
539
|
const client = new MarketplaceClient();
|
|
366
|
-
|
|
540
|
+
if (format === "human") {
|
|
541
|
+
console.log(pc5.dim(`Searching marketplaces for "${query}"...
|
|
367
542
|
`));
|
|
368
|
-
|
|
369
|
-
|
|
543
|
+
}
|
|
544
|
+
let results;
|
|
545
|
+
try {
|
|
546
|
+
results = await client.search(query, limit);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
const message = formatNetworkError(error);
|
|
549
|
+
if (format === "json") {
|
|
550
|
+
console.log(JSON.stringify({ error: message }));
|
|
551
|
+
} else {
|
|
552
|
+
console.error(pc5.red(`Marketplace search failed: ${message}`));
|
|
553
|
+
}
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
if (format === "json") {
|
|
370
557
|
console.log(JSON.stringify(results, null, 2));
|
|
371
558
|
return;
|
|
372
559
|
}
|
|
@@ -381,13 +568,118 @@ function registerSkillsFind(parent) {
|
|
|
381
568
|
console.log(` ${pc5.dim(`from ${skill.source}`)}`);
|
|
382
569
|
console.log();
|
|
383
570
|
}
|
|
384
|
-
console.log(pc5.dim(
|
|
571
|
+
console.log(pc5.dim("Install with: caamp skills install <scopedName>"));
|
|
385
572
|
});
|
|
386
573
|
}
|
|
387
574
|
function formatStars(n) {
|
|
388
575
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
389
576
|
return String(n);
|
|
390
577
|
}
|
|
578
|
+
function parseConstraintList(values) {
|
|
579
|
+
const normalized = values.flatMap((value) => tokenizeCriteriaValue(value));
|
|
580
|
+
return Array.from(new Set(normalized));
|
|
581
|
+
}
|
|
582
|
+
function parseTop(value) {
|
|
583
|
+
const parsed = Number.parseInt(value, 10);
|
|
584
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 20) {
|
|
585
|
+
throw new SkillsFindValidationError(RECOMMENDATION_ERROR_CODES.QUERY_INVALID, "--top must be an integer between 1 and 20");
|
|
586
|
+
}
|
|
587
|
+
return parsed;
|
|
588
|
+
}
|
|
589
|
+
function parseSelectList(value) {
|
|
590
|
+
if (!value) return [];
|
|
591
|
+
const parsed = value.split(",").map((entry) => Number.parseInt(entry.trim(), 10)).filter((entry) => Number.isInteger(entry) && entry > 0);
|
|
592
|
+
return Array.from(new Set(parsed));
|
|
593
|
+
}
|
|
594
|
+
function buildSeedQuery(query, mustHave, prefer, exclude) {
|
|
595
|
+
if (query && query.trim().length > 0) {
|
|
596
|
+
return query;
|
|
597
|
+
}
|
|
598
|
+
const seedTerms = [...mustHave, ...prefer, ...exclude].filter((term) => term.length > 0);
|
|
599
|
+
if (seedTerms.length > 0) {
|
|
600
|
+
return seedTerms.join(" ");
|
|
601
|
+
}
|
|
602
|
+
throw new SkillsFindValidationError(
|
|
603
|
+
RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
604
|
+
"Recommendation mode requires a query or at least one criteria flag."
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
function normalizeRecommendationOptions(ranking, details) {
|
|
608
|
+
return ranking.map((entry, index) => {
|
|
609
|
+
const whyCodes = entry.reasons.map((reason) => reason.code);
|
|
610
|
+
return {
|
|
611
|
+
rank: index + 1,
|
|
612
|
+
scopedName: entry.skill.scopedName,
|
|
613
|
+
description: entry.skill.description,
|
|
614
|
+
score: entry.score,
|
|
615
|
+
why: whyCodes.length > 0 ? whyCodes.join(", ") : "score-based match",
|
|
616
|
+
source: entry.skill.source,
|
|
617
|
+
...details ? {
|
|
618
|
+
evidence: {
|
|
619
|
+
reasons: entry.reasons,
|
|
620
|
+
breakdown: entry.breakdown
|
|
621
|
+
}
|
|
622
|
+
} : {}
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function validateCriteriaConflicts(mustHave, prefer, exclude) {
|
|
627
|
+
const overlap = mustHave.filter((term) => exclude.includes(term));
|
|
628
|
+
if (overlap.length > 0) {
|
|
629
|
+
throw new SkillsFindValidationError(
|
|
630
|
+
RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
631
|
+
"A criteria term cannot be both required and excluded."
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const preferOverlap = prefer.filter((term) => exclude.includes(term));
|
|
635
|
+
if (preferOverlap.length > 0) {
|
|
636
|
+
throw new SkillsFindValidationError(
|
|
637
|
+
RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
638
|
+
"A criteria term cannot be both preferred and excluded."
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function validateSelectedRanks(selectedRanks, total) {
|
|
643
|
+
for (const rank of selectedRanks) {
|
|
644
|
+
if (rank < 1 || rank > total) {
|
|
645
|
+
throw new SkillsFindValidationError(
|
|
646
|
+
RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
647
|
+
`--select rank ${rank} is out of range (1-${total}).`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function buildEnvelope(operation, mvi, result, error) {
|
|
653
|
+
return {
|
|
654
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
655
|
+
_meta: {
|
|
656
|
+
specVersion: "1.0.0",
|
|
657
|
+
schemaVersion: "1.0.0",
|
|
658
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
659
|
+
operation,
|
|
660
|
+
requestId: randomUUID(),
|
|
661
|
+
transport: "cli",
|
|
662
|
+
strict: true,
|
|
663
|
+
mvi,
|
|
664
|
+
contextVersion: 0
|
|
665
|
+
},
|
|
666
|
+
success: error === null,
|
|
667
|
+
result,
|
|
668
|
+
error,
|
|
669
|
+
page: null
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function emitJsonError(operation, mvi, code, message, category, details = {}) {
|
|
673
|
+
const envelope = buildEnvelope(operation, mvi, null, {
|
|
674
|
+
code,
|
|
675
|
+
message,
|
|
676
|
+
category,
|
|
677
|
+
retryable: false,
|
|
678
|
+
retryAfterMs: null,
|
|
679
|
+
details
|
|
680
|
+
});
|
|
681
|
+
console.error(JSON.stringify(envelope, null, 2));
|
|
682
|
+
}
|
|
391
683
|
|
|
392
684
|
// src/commands/skills/check.ts
|
|
393
685
|
import pc6 from "picocolors";
|
|
@@ -571,13 +863,13 @@ ${outdated.length} skill(s) have updates available:
|
|
|
571
863
|
// src/commands/skills/init.ts
|
|
572
864
|
import pc8 from "picocolors";
|
|
573
865
|
import { writeFile, mkdir } from "fs/promises";
|
|
574
|
-
import { existsSync } from "fs";
|
|
866
|
+
import { existsSync as existsSync2 } from "fs";
|
|
575
867
|
import { join as join4 } from "path";
|
|
576
868
|
function registerSkillsInit(parent) {
|
|
577
869
|
parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").action(async (name, opts) => {
|
|
578
870
|
const skillName = name ?? "my-skill";
|
|
579
871
|
const skillDir = join4(opts.dir, skillName);
|
|
580
|
-
if (
|
|
872
|
+
if (existsSync2(skillDir)) {
|
|
581
873
|
console.error(pc8.red(`Directory already exists: ${skillDir}`));
|
|
582
874
|
process.exit(1);
|
|
583
875
|
}
|
|
@@ -616,10 +908,10 @@ Show example inputs and expected outputs.
|
|
|
616
908
|
|
|
617
909
|
// src/commands/skills/audit.ts
|
|
618
910
|
import pc9 from "picocolors";
|
|
619
|
-
import { existsSync as
|
|
911
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
620
912
|
function registerSkillsAudit(parent) {
|
|
621
913
|
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) => {
|
|
622
|
-
if (!
|
|
914
|
+
if (!existsSync3(path)) {
|
|
623
915
|
console.error(pc9.red(`Path not found: ${path}`));
|
|
624
916
|
process.exit(1);
|
|
625
917
|
}
|
|
@@ -837,7 +1129,7 @@ ${allEntries.length} MCP server(s) configured:
|
|
|
837
1129
|
|
|
838
1130
|
// src/commands/mcp/detect.ts
|
|
839
1131
|
import pc14 from "picocolors";
|
|
840
|
-
import { existsSync as
|
|
1132
|
+
import { existsSync as existsSync4 } from "fs";
|
|
841
1133
|
function registerMcpDetect(parent) {
|
|
842
1134
|
parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON").action(async (opts) => {
|
|
843
1135
|
const providers = getInstalledProviders();
|
|
@@ -849,8 +1141,8 @@ function registerMcpDetect(parent) {
|
|
|
849
1141
|
const projectEntries = await listMcpServers(provider, "project");
|
|
850
1142
|
detected.push({
|
|
851
1143
|
provider: provider.id,
|
|
852
|
-
hasGlobalConfig: globalPath !== null &&
|
|
853
|
-
hasProjectConfig: projectPath !== null &&
|
|
1144
|
+
hasGlobalConfig: globalPath !== null && existsSync4(globalPath),
|
|
1145
|
+
hasProjectConfig: projectPath !== null && existsSync4(projectPath),
|
|
854
1146
|
globalServers: globalEntries.map((e) => e.name),
|
|
855
1147
|
projectServers: projectEntries.map((e) => e.name)
|
|
856
1148
|
});
|
|
@@ -1011,7 +1303,7 @@ function registerInstructionsCommands(program2) {
|
|
|
1011
1303
|
// src/commands/config.ts
|
|
1012
1304
|
import pc18 from "picocolors";
|
|
1013
1305
|
import { join as join5 } from "path";
|
|
1014
|
-
import { existsSync as
|
|
1306
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1015
1307
|
function registerConfigCommand(program2) {
|
|
1016
1308
|
const config = program2.command("config").description("View provider configuration");
|
|
1017
1309
|
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) => {
|
|
@@ -1021,7 +1313,7 @@ function registerConfigCommand(program2) {
|
|
|
1021
1313
|
process.exit(1);
|
|
1022
1314
|
}
|
|
1023
1315
|
const configPath = opts.global ? provider.configPathGlobal : provider.configPathProject ? join5(process.cwd(), provider.configPathProject) : provider.configPathGlobal;
|
|
1024
|
-
if (!
|
|
1316
|
+
if (!existsSync5(configPath)) {
|
|
1025
1317
|
console.log(pc18.dim(`No config file at: ${configPath}`));
|
|
1026
1318
|
return;
|
|
1027
1319
|
}
|
|
@@ -1059,13 +1351,917 @@ ${provider.toolName} config (${configPath}):
|
|
|
1059
1351
|
});
|
|
1060
1352
|
}
|
|
1061
1353
|
|
|
1354
|
+
// src/commands/doctor.ts
|
|
1355
|
+
import pc19 from "picocolors";
|
|
1356
|
+
import { execFileSync } from "child_process";
|
|
1357
|
+
import { existsSync as existsSync6, readdirSync, lstatSync } from "fs";
|
|
1358
|
+
import { homedir } from "os";
|
|
1359
|
+
import { join as join6 } from "path";
|
|
1360
|
+
var CAAMP_VERSION = "0.2.0";
|
|
1361
|
+
function getNodeVersion() {
|
|
1362
|
+
return process.version;
|
|
1363
|
+
}
|
|
1364
|
+
function getNpmVersion() {
|
|
1365
|
+
try {
|
|
1366
|
+
return execFileSync("npm", ["--version"], { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
1367
|
+
} catch {
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
function checkEnvironment() {
|
|
1372
|
+
const checks = [];
|
|
1373
|
+
checks.push({ label: `Node.js ${getNodeVersion()}`, status: "pass" });
|
|
1374
|
+
const npmVersion = getNpmVersion();
|
|
1375
|
+
if (npmVersion) {
|
|
1376
|
+
checks.push({ label: `npm ${npmVersion}`, status: "pass" });
|
|
1377
|
+
} else {
|
|
1378
|
+
checks.push({ label: "npm not found", status: "warn" });
|
|
1379
|
+
}
|
|
1380
|
+
checks.push({ label: `CAAMP v${CAAMP_VERSION}`, status: "pass" });
|
|
1381
|
+
checks.push({ label: `${process.platform} ${process.arch}`, status: "pass" });
|
|
1382
|
+
return { name: "Environment", checks };
|
|
1383
|
+
}
|
|
1384
|
+
function checkRegistry() {
|
|
1385
|
+
const checks = [];
|
|
1386
|
+
try {
|
|
1387
|
+
const providers = getAllProviders();
|
|
1388
|
+
const count = getProviderCount();
|
|
1389
|
+
checks.push({ label: `${count} providers loaded`, status: "pass" });
|
|
1390
|
+
const malformed = [];
|
|
1391
|
+
for (const p of providers) {
|
|
1392
|
+
if (!p.id || !p.toolName || !p.configKey || !p.configFormat) {
|
|
1393
|
+
malformed.push(p.id || "(unknown)");
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (malformed.length === 0) {
|
|
1397
|
+
checks.push({ label: "All entries valid", status: "pass" });
|
|
1398
|
+
} else {
|
|
1399
|
+
checks.push({
|
|
1400
|
+
label: `${malformed.length} malformed entries`,
|
|
1401
|
+
status: "fail",
|
|
1402
|
+
detail: malformed.join(", ")
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
} catch (err) {
|
|
1406
|
+
checks.push({
|
|
1407
|
+
label: "Failed to load registry",
|
|
1408
|
+
status: "fail",
|
|
1409
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
return { name: "Registry", checks };
|
|
1413
|
+
}
|
|
1414
|
+
function checkInstalledProviders() {
|
|
1415
|
+
const checks = [];
|
|
1416
|
+
try {
|
|
1417
|
+
const results = detectAllProviders();
|
|
1418
|
+
const installed = results.filter((r) => r.installed);
|
|
1419
|
+
checks.push({ label: `${installed.length} found`, status: "pass" });
|
|
1420
|
+
for (const r of installed) {
|
|
1421
|
+
const methods = r.methods.join(", ");
|
|
1422
|
+
checks.push({ label: `${r.provider.toolName} (${methods})`, status: "pass" });
|
|
1423
|
+
}
|
|
1424
|
+
} catch (err) {
|
|
1425
|
+
checks.push({
|
|
1426
|
+
label: "Detection failed",
|
|
1427
|
+
status: "fail",
|
|
1428
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
return { name: "Installed Providers", checks };
|
|
1432
|
+
}
|
|
1433
|
+
function checkSkillSymlinks() {
|
|
1434
|
+
const checks = [];
|
|
1435
|
+
const canonicalDir = join6(homedir(), ".agents", "skills");
|
|
1436
|
+
if (!existsSync6(canonicalDir)) {
|
|
1437
|
+
checks.push({ label: "0 canonical skills", status: "pass" });
|
|
1438
|
+
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
1439
|
+
return { name: "Skills", checks };
|
|
1440
|
+
}
|
|
1441
|
+
let canonicalCount = 0;
|
|
1442
|
+
try {
|
|
1443
|
+
const entries = readdirSync(canonicalDir);
|
|
1444
|
+
canonicalCount = entries.length;
|
|
1445
|
+
checks.push({ label: `${canonicalCount} canonical skills`, status: "pass" });
|
|
1446
|
+
} catch {
|
|
1447
|
+
checks.push({ label: "Cannot read skills directory", status: "warn" });
|
|
1448
|
+
return { name: "Skills", checks };
|
|
1449
|
+
}
|
|
1450
|
+
const broken = [];
|
|
1451
|
+
const providers = getAllProviders();
|
|
1452
|
+
for (const provider of providers) {
|
|
1453
|
+
const skillDir = provider.pathSkills;
|
|
1454
|
+
if (!existsSync6(skillDir)) continue;
|
|
1455
|
+
try {
|
|
1456
|
+
const entries = readdirSync(skillDir);
|
|
1457
|
+
for (const entry of entries) {
|
|
1458
|
+
const fullPath = join6(skillDir, entry);
|
|
1459
|
+
try {
|
|
1460
|
+
const stat = lstatSync(fullPath);
|
|
1461
|
+
if (stat.isSymbolicLink()) {
|
|
1462
|
+
if (!existsSync6(fullPath)) {
|
|
1463
|
+
broken.push(`${provider.id}/${entry}`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
} catch {
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
if (broken.length === 0) {
|
|
1473
|
+
checks.push({ label: "No broken symlinks", status: "pass" });
|
|
1474
|
+
} else {
|
|
1475
|
+
checks.push({
|
|
1476
|
+
label: `${broken.length} broken symlinks`,
|
|
1477
|
+
status: "warn",
|
|
1478
|
+
detail: broken.join(", ")
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
return { name: "Skills", checks };
|
|
1482
|
+
}
|
|
1483
|
+
async function checkLockFile() {
|
|
1484
|
+
const checks = [];
|
|
1485
|
+
try {
|
|
1486
|
+
const lock = await readLockFile();
|
|
1487
|
+
checks.push({ label: "Lock file valid", status: "pass" });
|
|
1488
|
+
let orphaned = 0;
|
|
1489
|
+
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
1490
|
+
if (entry.canonicalPath && !existsSync6(entry.canonicalPath)) {
|
|
1491
|
+
orphaned++;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (orphaned === 0) {
|
|
1495
|
+
checks.push({ label: `0 orphaned entries`, status: "pass" });
|
|
1496
|
+
} else {
|
|
1497
|
+
checks.push({
|
|
1498
|
+
label: `${orphaned} orphaned skill entries`,
|
|
1499
|
+
status: "warn",
|
|
1500
|
+
detail: "Skills tracked in lock file but missing from disk"
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
} catch (err) {
|
|
1504
|
+
checks.push({
|
|
1505
|
+
label: "Failed to read lock file",
|
|
1506
|
+
status: "fail",
|
|
1507
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
return { name: "Lock File", checks };
|
|
1511
|
+
}
|
|
1512
|
+
async function checkConfigFiles() {
|
|
1513
|
+
const checks = [];
|
|
1514
|
+
const results = detectAllProviders();
|
|
1515
|
+
const installed = results.filter((r) => r.installed);
|
|
1516
|
+
for (const r of installed) {
|
|
1517
|
+
const provider = r.provider;
|
|
1518
|
+
const configPath = provider.configPathGlobal;
|
|
1519
|
+
if (!existsSync6(configPath)) {
|
|
1520
|
+
checks.push({
|
|
1521
|
+
label: `${provider.id}: no config file found`,
|
|
1522
|
+
status: "warn",
|
|
1523
|
+
detail: configPath
|
|
1524
|
+
});
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
try {
|
|
1528
|
+
await readConfig(configPath, provider.configFormat);
|
|
1529
|
+
const relPath = configPath.replace(homedir(), "~");
|
|
1530
|
+
checks.push({
|
|
1531
|
+
label: `${provider.id}: ${relPath} readable`,
|
|
1532
|
+
status: "pass"
|
|
1533
|
+
});
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
checks.push({
|
|
1536
|
+
label: `${provider.id}: config parse error`,
|
|
1537
|
+
status: "fail",
|
|
1538
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (installed.length === 0) {
|
|
1543
|
+
checks.push({ label: "No installed providers to check", status: "pass" });
|
|
1544
|
+
}
|
|
1545
|
+
return { name: "Config Files", checks };
|
|
1546
|
+
}
|
|
1547
|
+
function formatSection(section) {
|
|
1548
|
+
const lines = [];
|
|
1549
|
+
lines.push(` ${pc19.bold(section.name)}`);
|
|
1550
|
+
for (const check of section.checks) {
|
|
1551
|
+
const icon = check.status === "pass" ? pc19.green("\u2713") : check.status === "warn" ? pc19.yellow("\u26A0") : pc19.red("\u2717");
|
|
1552
|
+
lines.push(` ${icon} ${check.label}`);
|
|
1553
|
+
if (check.detail) {
|
|
1554
|
+
lines.push(` ${pc19.dim(check.detail)}`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return lines.join("\n");
|
|
1558
|
+
}
|
|
1559
|
+
function registerDoctorCommand(program2) {
|
|
1560
|
+
program2.command("doctor").description("Diagnose configuration issues and health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1561
|
+
const sections = [];
|
|
1562
|
+
sections.push(checkEnvironment());
|
|
1563
|
+
sections.push(checkRegistry());
|
|
1564
|
+
sections.push(checkInstalledProviders());
|
|
1565
|
+
sections.push(checkSkillSymlinks());
|
|
1566
|
+
sections.push(await checkLockFile());
|
|
1567
|
+
sections.push(await checkConfigFiles());
|
|
1568
|
+
let passed = 0;
|
|
1569
|
+
let warnings = 0;
|
|
1570
|
+
let errors = 0;
|
|
1571
|
+
for (const section of sections) {
|
|
1572
|
+
for (const check of section.checks) {
|
|
1573
|
+
if (check.status === "pass") passed++;
|
|
1574
|
+
else if (check.status === "warn") warnings++;
|
|
1575
|
+
else errors++;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
if (opts.json) {
|
|
1579
|
+
const output = {
|
|
1580
|
+
version: CAAMP_VERSION,
|
|
1581
|
+
sections: sections.map((s) => ({
|
|
1582
|
+
name: s.name,
|
|
1583
|
+
checks: s.checks
|
|
1584
|
+
})),
|
|
1585
|
+
summary: { passed, warnings, errors }
|
|
1586
|
+
};
|
|
1587
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
console.log(pc19.bold("\ncaamp doctor\n"));
|
|
1591
|
+
for (const section of sections) {
|
|
1592
|
+
console.log(formatSection(section));
|
|
1593
|
+
console.log();
|
|
1594
|
+
}
|
|
1595
|
+
const parts = [];
|
|
1596
|
+
parts.push(pc19.green(`${passed} checks passed`));
|
|
1597
|
+
if (warnings > 0) parts.push(pc19.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
|
|
1598
|
+
if (errors > 0) parts.push(pc19.red(`${errors} error${errors !== 1 ? "s" : ""}`));
|
|
1599
|
+
console.log(` ${pc19.bold("Summary")}: ${parts.join(", ")}`);
|
|
1600
|
+
console.log();
|
|
1601
|
+
if (errors > 0) {
|
|
1602
|
+
process.exit(1);
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// src/commands/advanced/common.ts
|
|
1608
|
+
import { readFile } from "fs/promises";
|
|
1609
|
+
|
|
1610
|
+
// src/commands/advanced/lafs.ts
|
|
1611
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1612
|
+
import {
|
|
1613
|
+
isRegisteredErrorCode
|
|
1614
|
+
} from "@cleocode/lafs-protocol";
|
|
1615
|
+
var LAFSCommandError = class extends Error {
|
|
1616
|
+
code;
|
|
1617
|
+
category;
|
|
1618
|
+
recoverable;
|
|
1619
|
+
suggestion;
|
|
1620
|
+
retryAfterMs;
|
|
1621
|
+
details;
|
|
1622
|
+
constructor(code, message, suggestion, recoverable = true, details) {
|
|
1623
|
+
super(message);
|
|
1624
|
+
this.name = "LAFSCommandError";
|
|
1625
|
+
this.code = code;
|
|
1626
|
+
this.category = inferErrorCategory(code);
|
|
1627
|
+
this.recoverable = recoverable;
|
|
1628
|
+
this.suggestion = suggestion;
|
|
1629
|
+
this.retryAfterMs = null;
|
|
1630
|
+
this.details = details;
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
function inferErrorCategory(code) {
|
|
1634
|
+
if (code.includes("VALIDATION")) return "VALIDATION";
|
|
1635
|
+
if (code.includes("NOT_FOUND")) return "NOT_FOUND";
|
|
1636
|
+
if (code.includes("CONFLICT")) return "CONFLICT";
|
|
1637
|
+
if (code.includes("AUTH")) return "AUTH";
|
|
1638
|
+
if (code.includes("PERMISSION")) return "PERMISSION";
|
|
1639
|
+
if (code.includes("RATE_LIMIT")) return "RATE_LIMIT";
|
|
1640
|
+
if (code.includes("MIGRATION")) return "MIGRATION";
|
|
1641
|
+
if (code.includes("CONTRACT")) return "CONTRACT";
|
|
1642
|
+
return "INTERNAL";
|
|
1643
|
+
}
|
|
1644
|
+
function baseMeta(operation, mvi) {
|
|
1645
|
+
return {
|
|
1646
|
+
specVersion: "1.0.0",
|
|
1647
|
+
schemaVersion: "1.0.0",
|
|
1648
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1649
|
+
operation,
|
|
1650
|
+
requestId: randomUUID2(),
|
|
1651
|
+
transport: "cli",
|
|
1652
|
+
strict: true,
|
|
1653
|
+
mvi,
|
|
1654
|
+
contextVersion: 0
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
function emitSuccess(operation, result, mvi = true) {
|
|
1658
|
+
const envelope = {
|
|
1659
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
1660
|
+
_meta: {
|
|
1661
|
+
...baseMeta(operation, mvi)
|
|
1662
|
+
},
|
|
1663
|
+
success: true,
|
|
1664
|
+
result,
|
|
1665
|
+
error: null,
|
|
1666
|
+
page: null
|
|
1667
|
+
};
|
|
1668
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
1669
|
+
}
|
|
1670
|
+
function emitError(operation, error, mvi = true) {
|
|
1671
|
+
let envelope;
|
|
1672
|
+
if (error instanceof LAFSCommandError) {
|
|
1673
|
+
envelope = {
|
|
1674
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
1675
|
+
_meta: {
|
|
1676
|
+
...baseMeta(operation, mvi)
|
|
1677
|
+
},
|
|
1678
|
+
success: false,
|
|
1679
|
+
result: null,
|
|
1680
|
+
error: {
|
|
1681
|
+
code: isRegisteredErrorCode(error.code) ? error.code : "E_INTERNAL_UNEXPECTED",
|
|
1682
|
+
message: error.message,
|
|
1683
|
+
category: error.category,
|
|
1684
|
+
retryable: error.recoverable,
|
|
1685
|
+
retryAfterMs: error.retryAfterMs,
|
|
1686
|
+
details: {
|
|
1687
|
+
hint: error.suggestion,
|
|
1688
|
+
...error.details !== void 0 ? { payload: error.details } : {}
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1691
|
+
page: null
|
|
1692
|
+
};
|
|
1693
|
+
} else {
|
|
1694
|
+
envelope = {
|
|
1695
|
+
$schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
1696
|
+
_meta: {
|
|
1697
|
+
...baseMeta(operation, mvi)
|
|
1698
|
+
},
|
|
1699
|
+
success: false,
|
|
1700
|
+
result: null,
|
|
1701
|
+
error: {
|
|
1702
|
+
code: "E_INTERNAL_UNEXPECTED",
|
|
1703
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1704
|
+
category: "INTERNAL",
|
|
1705
|
+
retryable: false,
|
|
1706
|
+
retryAfterMs: null,
|
|
1707
|
+
details: {
|
|
1708
|
+
hint: "Rerun with --verbose and validate your inputs."
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
page: null
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
console.error(JSON.stringify(envelope, null, 2));
|
|
1715
|
+
}
|
|
1716
|
+
async function runLafsCommand(command, mvi, action) {
|
|
1717
|
+
try {
|
|
1718
|
+
const result = await action();
|
|
1719
|
+
emitSuccess(command, result, mvi);
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
emitError(command, error, mvi);
|
|
1722
|
+
process.exit(1);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/commands/advanced/common.ts
|
|
1727
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
|
|
1728
|
+
function parsePriority(value) {
|
|
1729
|
+
if (!VALID_PRIORITIES.has(value)) {
|
|
1730
|
+
throw new LAFSCommandError(
|
|
1731
|
+
"E_ADVANCED_VALIDATION_PRIORITY",
|
|
1732
|
+
`Invalid tier: ${value}`,
|
|
1733
|
+
"Use one of: high, medium, low."
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
return value;
|
|
1737
|
+
}
|
|
1738
|
+
function resolveProviders(options) {
|
|
1739
|
+
if (options.all) {
|
|
1740
|
+
return getAllProviders();
|
|
1741
|
+
}
|
|
1742
|
+
const targetAgents = options.agent ?? [];
|
|
1743
|
+
if (targetAgents.length === 0) {
|
|
1744
|
+
return getInstalledProviders();
|
|
1745
|
+
}
|
|
1746
|
+
const providers = targetAgents.map((id) => getProvider(id)).filter((provider) => provider !== void 0);
|
|
1747
|
+
if (providers.length !== targetAgents.length) {
|
|
1748
|
+
const found = new Set(providers.map((provider) => provider.id));
|
|
1749
|
+
const missing = targetAgents.filter((id) => !found.has(id));
|
|
1750
|
+
throw new LAFSCommandError(
|
|
1751
|
+
"E_ADVANCED_PROVIDER_NOT_FOUND",
|
|
1752
|
+
`Unknown provider(s): ${missing.join(", ")}`,
|
|
1753
|
+
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
return providers;
|
|
1757
|
+
}
|
|
1758
|
+
async function readJsonFile(path) {
|
|
1759
|
+
try {
|
|
1760
|
+
const raw = await readFile(path, "utf-8");
|
|
1761
|
+
return JSON.parse(raw);
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
throw new LAFSCommandError(
|
|
1764
|
+
"E_ADVANCED_INPUT_JSON",
|
|
1765
|
+
`Failed to read JSON file: ${path}`,
|
|
1766
|
+
"Confirm the path exists and contains valid JSON.",
|
|
1767
|
+
true,
|
|
1768
|
+
{ reason: error instanceof Error ? error.message : String(error) }
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
async function readMcpOperations(path) {
|
|
1773
|
+
const value = await readJsonFile(path);
|
|
1774
|
+
if (!Array.isArray(value)) {
|
|
1775
|
+
throw new LAFSCommandError(
|
|
1776
|
+
"E_ADVANCED_VALIDATION_MCP_ARRAY",
|
|
1777
|
+
`MCP operations file must be a JSON array: ${path}`,
|
|
1778
|
+
"Provide an array of objects with serverName and config fields."
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const operations = [];
|
|
1782
|
+
for (const [index, item] of value.entries()) {
|
|
1783
|
+
if (!item || typeof item !== "object") {
|
|
1784
|
+
throw new LAFSCommandError(
|
|
1785
|
+
"E_ADVANCED_VALIDATION_MCP_ITEM",
|
|
1786
|
+
`Invalid MCP operation at index ${index}`,
|
|
1787
|
+
"Each operation must be an object with serverName and config."
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
const obj = item;
|
|
1791
|
+
const serverName = obj["serverName"];
|
|
1792
|
+
const config = obj["config"];
|
|
1793
|
+
const scope = obj["scope"];
|
|
1794
|
+
if (typeof serverName !== "string" || serverName.length === 0) {
|
|
1795
|
+
throw new LAFSCommandError(
|
|
1796
|
+
"E_ADVANCED_VALIDATION_MCP_NAME",
|
|
1797
|
+
`Invalid serverName at index ${index}`,
|
|
1798
|
+
"Set serverName to a non-empty string."
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
1802
|
+
throw new LAFSCommandError(
|
|
1803
|
+
"E_ADVANCED_VALIDATION_MCP_CONFIG",
|
|
1804
|
+
`Invalid config at index ${index}`,
|
|
1805
|
+
"Set config to an object matching McpServerConfig."
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
if (scope !== void 0 && scope !== "project" && scope !== "global") {
|
|
1809
|
+
throw new LAFSCommandError(
|
|
1810
|
+
"E_ADVANCED_VALIDATION_SCOPE",
|
|
1811
|
+
`Invalid scope at index ${index}: ${String(scope)}`,
|
|
1812
|
+
"Use scope value 'project' or 'global'."
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
operations.push({
|
|
1816
|
+
serverName,
|
|
1817
|
+
config,
|
|
1818
|
+
...scope ? { scope } : {}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
return operations;
|
|
1822
|
+
}
|
|
1823
|
+
async function readSkillOperations(path) {
|
|
1824
|
+
const value = await readJsonFile(path);
|
|
1825
|
+
if (!Array.isArray(value)) {
|
|
1826
|
+
throw new LAFSCommandError(
|
|
1827
|
+
"E_ADVANCED_VALIDATION_SKILL_ARRAY",
|
|
1828
|
+
`Skill operations file must be a JSON array: ${path}`,
|
|
1829
|
+
"Provide an array of objects with sourcePath and skillName fields."
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
const operations = [];
|
|
1833
|
+
for (const [index, item] of value.entries()) {
|
|
1834
|
+
if (!item || typeof item !== "object") {
|
|
1835
|
+
throw new LAFSCommandError(
|
|
1836
|
+
"E_ADVANCED_VALIDATION_SKILL_ITEM",
|
|
1837
|
+
`Invalid skill operation at index ${index}`,
|
|
1838
|
+
"Each operation must be an object with sourcePath and skillName."
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
const obj = item;
|
|
1842
|
+
const sourcePath = obj["sourcePath"];
|
|
1843
|
+
const skillName = obj["skillName"];
|
|
1844
|
+
const isGlobal = obj["isGlobal"];
|
|
1845
|
+
if (typeof sourcePath !== "string" || sourcePath.length === 0) {
|
|
1846
|
+
throw new LAFSCommandError(
|
|
1847
|
+
"E_ADVANCED_VALIDATION_SKILL_SOURCE",
|
|
1848
|
+
`Invalid sourcePath at index ${index}`,
|
|
1849
|
+
"Set sourcePath to a non-empty string."
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
if (typeof skillName !== "string" || skillName.length === 0) {
|
|
1853
|
+
throw new LAFSCommandError(
|
|
1854
|
+
"E_ADVANCED_VALIDATION_SKILL_NAME",
|
|
1855
|
+
`Invalid skillName at index ${index}`,
|
|
1856
|
+
"Set skillName to a non-empty string."
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
if (isGlobal !== void 0 && typeof isGlobal !== "boolean") {
|
|
1860
|
+
throw new LAFSCommandError(
|
|
1861
|
+
"E_ADVANCED_VALIDATION_SKILL_SCOPE",
|
|
1862
|
+
`Invalid isGlobal value at index ${index}`,
|
|
1863
|
+
"Set isGlobal to true or false when provided."
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
operations.push({
|
|
1867
|
+
sourcePath,
|
|
1868
|
+
skillName,
|
|
1869
|
+
...isGlobal !== void 0 ? { isGlobal } : {}
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
return operations;
|
|
1873
|
+
}
|
|
1874
|
+
async function readTextInput(inlineContent, filePath) {
|
|
1875
|
+
if (inlineContent && filePath) {
|
|
1876
|
+
throw new LAFSCommandError(
|
|
1877
|
+
"E_ADVANCED_VALIDATION_INPUT_MODE",
|
|
1878
|
+
"Provide either inline content or a content file, not both.",
|
|
1879
|
+
"Use --content OR --content-file."
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
if (inlineContent) return inlineContent;
|
|
1883
|
+
if (!filePath) return void 0;
|
|
1884
|
+
try {
|
|
1885
|
+
return await readFile(filePath, "utf-8");
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
throw new LAFSCommandError(
|
|
1888
|
+
"E_ADVANCED_INPUT_TEXT",
|
|
1889
|
+
`Failed to read content file: ${filePath}`,
|
|
1890
|
+
"Confirm the file exists and is readable.",
|
|
1891
|
+
true,
|
|
1892
|
+
{ reason: error instanceof Error ? error.message : String(error) }
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// src/commands/advanced/providers.ts
|
|
1898
|
+
function registerAdvancedProviders(parent) {
|
|
1899
|
+
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 () => {
|
|
1900
|
+
const providers = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
1901
|
+
const minTier = parsePriority(opts.minTier);
|
|
1902
|
+
const selected = selectProvidersByMinimumPriority(providers, minTier);
|
|
1903
|
+
return {
|
|
1904
|
+
objective: "Filter providers by minimum priority tier",
|
|
1905
|
+
constraints: {
|
|
1906
|
+
minTier,
|
|
1907
|
+
selectionMode: opts.all ? "registry" : "detected-or-explicit"
|
|
1908
|
+
},
|
|
1909
|
+
acceptanceCriteria: {
|
|
1910
|
+
selectedCount: selected.length,
|
|
1911
|
+
orderedByPriority: true
|
|
1912
|
+
},
|
|
1913
|
+
data: opts.details ? selected : selected.map((provider) => ({
|
|
1914
|
+
id: provider.id,
|
|
1915
|
+
priority: provider.priority,
|
|
1916
|
+
status: provider.status,
|
|
1917
|
+
configFormat: provider.configFormat
|
|
1918
|
+
}))
|
|
1919
|
+
};
|
|
1920
|
+
}));
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// src/commands/advanced/batch.ts
|
|
1924
|
+
function registerAdvancedBatch(parent) {
|
|
1925
|
+
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 () => {
|
|
1926
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
1927
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
1928
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
1929
|
+
const mcp = opts.mcpFile ? await readMcpOperations(opts.mcpFile) : [];
|
|
1930
|
+
const skills = opts.skillsFile ? await readSkillOperations(opts.skillsFile) : [];
|
|
1931
|
+
if (mcp.length === 0 && skills.length === 0) {
|
|
1932
|
+
throw new LAFSCommandError(
|
|
1933
|
+
"E_ADVANCED_VALIDATION_NO_OPS",
|
|
1934
|
+
"No operations provided.",
|
|
1935
|
+
"Provide --mcp-file and/or --skills-file."
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (providers.length === 0) {
|
|
1939
|
+
throw new LAFSCommandError(
|
|
1940
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
1941
|
+
"No target providers resolved for this batch operation.",
|
|
1942
|
+
"Use --all or pass provider IDs with --agent."
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
const result = await installBatchWithRollback({
|
|
1946
|
+
providers,
|
|
1947
|
+
minimumPriority,
|
|
1948
|
+
mcp,
|
|
1949
|
+
skills,
|
|
1950
|
+
projectDir: opts.projectDir
|
|
1951
|
+
});
|
|
1952
|
+
if (!result.success) {
|
|
1953
|
+
throw new LAFSCommandError(
|
|
1954
|
+
"E_ADVANCED_BATCH_FAILED",
|
|
1955
|
+
result.error ?? "Batch operation failed.",
|
|
1956
|
+
"Check rollbackErrors and input configs, then retry.",
|
|
1957
|
+
true,
|
|
1958
|
+
result
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
return {
|
|
1962
|
+
objective: "Install MCP and skills with rollback safety",
|
|
1963
|
+
constraints: {
|
|
1964
|
+
minimumPriority,
|
|
1965
|
+
providerCount: providers.length,
|
|
1966
|
+
mcpOps: mcp.length,
|
|
1967
|
+
skillOps: skills.length
|
|
1968
|
+
},
|
|
1969
|
+
acceptanceCriteria: {
|
|
1970
|
+
success: result.success,
|
|
1971
|
+
rollbackPerformed: result.rollbackPerformed
|
|
1972
|
+
},
|
|
1973
|
+
data: opts.details ? result : {
|
|
1974
|
+
providerCount: result.providerIds.length,
|
|
1975
|
+
mcpApplied: result.mcpApplied,
|
|
1976
|
+
skillsApplied: result.skillsApplied,
|
|
1977
|
+
rollbackPerformed: result.rollbackPerformed
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
}));
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/commands/advanced/conflicts.ts
|
|
1984
|
+
function registerAdvancedConflicts(parent) {
|
|
1985
|
+
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 () => {
|
|
1986
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
1987
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
1988
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
1989
|
+
const operations = await readMcpOperations(opts.mcpFile);
|
|
1990
|
+
if (providers.length === 0) {
|
|
1991
|
+
throw new LAFSCommandError(
|
|
1992
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
1993
|
+
"No target providers resolved for conflict detection.",
|
|
1994
|
+
"Use --all or pass provider IDs with --agent."
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
const conflicts = await detectMcpConfigConflicts(
|
|
1998
|
+
providers,
|
|
1999
|
+
operations,
|
|
2000
|
+
opts.projectDir
|
|
2001
|
+
);
|
|
2002
|
+
const countByCode = conflicts.reduce((acc, conflict) => {
|
|
2003
|
+
acc[conflict.code] = (acc[conflict.code] ?? 0) + 1;
|
|
2004
|
+
return acc;
|
|
2005
|
+
}, {});
|
|
2006
|
+
return {
|
|
2007
|
+
objective: "Detect MCP configuration conflicts before mutation",
|
|
2008
|
+
constraints: {
|
|
2009
|
+
minimumPriority,
|
|
2010
|
+
providerCount: providers.length,
|
|
2011
|
+
operationCount: operations.length
|
|
2012
|
+
},
|
|
2013
|
+
acceptanceCriteria: {
|
|
2014
|
+
conflictCount: conflicts.length
|
|
2015
|
+
},
|
|
2016
|
+
data: opts.details ? conflicts : {
|
|
2017
|
+
conflictCount: conflicts.length,
|
|
2018
|
+
countByCode,
|
|
2019
|
+
sample: conflicts.slice(0, 5)
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
}));
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// src/commands/advanced/apply.ts
|
|
2026
|
+
var VALID_POLICIES = /* @__PURE__ */ new Set(["fail", "skip", "overwrite"]);
|
|
2027
|
+
function parsePolicy(value) {
|
|
2028
|
+
if (!VALID_POLICIES.has(value)) {
|
|
2029
|
+
throw new LAFSCommandError(
|
|
2030
|
+
"E_ADVANCED_VALIDATION_POLICY",
|
|
2031
|
+
`Invalid policy: ${value}`,
|
|
2032
|
+
"Use one of: fail, skip, overwrite."
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
return value;
|
|
2036
|
+
}
|
|
2037
|
+
function registerAdvancedApply(parent) {
|
|
2038
|
+
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 () => {
|
|
2039
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
2040
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
2041
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
2042
|
+
const operations = await readMcpOperations(opts.mcpFile);
|
|
2043
|
+
const policy = parsePolicy(opts.policy);
|
|
2044
|
+
if (providers.length === 0) {
|
|
2045
|
+
throw new LAFSCommandError(
|
|
2046
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
2047
|
+
"No target providers resolved for apply operation.",
|
|
2048
|
+
"Use --all or pass provider IDs with --agent."
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
const result = await applyMcpInstallWithPolicy(
|
|
2052
|
+
providers,
|
|
2053
|
+
operations,
|
|
2054
|
+
policy,
|
|
2055
|
+
opts.projectDir
|
|
2056
|
+
);
|
|
2057
|
+
if (policy === "fail" && result.conflicts.length > 0) {
|
|
2058
|
+
throw new LAFSCommandError(
|
|
2059
|
+
"E_ADVANCED_CONFLICTS_BLOCKING",
|
|
2060
|
+
"Conflicts detected and policy is set to fail.",
|
|
2061
|
+
"Run `caamp advanced conflicts` to inspect, or rerun with --policy skip/overwrite.",
|
|
2062
|
+
true,
|
|
2063
|
+
result
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
const failedWrites = result.applied.filter((entry) => !entry.success);
|
|
2067
|
+
if (failedWrites.length > 0) {
|
|
2068
|
+
throw new LAFSCommandError(
|
|
2069
|
+
"E_ADVANCED_APPLY_WRITE_FAILED",
|
|
2070
|
+
"One or more MCP writes failed.",
|
|
2071
|
+
"Check result details, fix provider config issues, and retry.",
|
|
2072
|
+
true,
|
|
2073
|
+
result
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
return {
|
|
2077
|
+
objective: "Apply MCP operations with policy-driven conflict handling",
|
|
2078
|
+
constraints: {
|
|
2079
|
+
policy,
|
|
2080
|
+
minimumPriority,
|
|
2081
|
+
providerCount: providers.length,
|
|
2082
|
+
operationCount: operations.length
|
|
2083
|
+
},
|
|
2084
|
+
acceptanceCriteria: {
|
|
2085
|
+
conflicts: result.conflicts.length,
|
|
2086
|
+
writesSucceeded: result.applied.length
|
|
2087
|
+
},
|
|
2088
|
+
data: opts.details ? result : {
|
|
2089
|
+
conflicts: result.conflicts.length,
|
|
2090
|
+
applied: result.applied.length,
|
|
2091
|
+
skipped: result.skipped.length
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
}));
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// src/commands/advanced/instructions.ts
|
|
2098
|
+
function registerAdvancedInstructions(parent) {
|
|
2099
|
+
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 () => {
|
|
2100
|
+
const minimumPriority = parsePriority(opts.minTier);
|
|
2101
|
+
const baseProviders = resolveProviders({ all: opts.all, agent: opts.agent });
|
|
2102
|
+
const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
|
|
2103
|
+
const scope = opts.scope === "global" ? "global" : opts.scope === "project" ? "project" : null;
|
|
2104
|
+
if (!scope) {
|
|
2105
|
+
throw new LAFSCommandError(
|
|
2106
|
+
"E_ADVANCED_VALIDATION_SCOPE",
|
|
2107
|
+
`Invalid scope: ${opts.scope}`,
|
|
2108
|
+
"Use --scope project or --scope global."
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
const content = await readTextInput(opts.content, opts.contentFile);
|
|
2112
|
+
if (!content || content.trim().length === 0) {
|
|
2113
|
+
throw new LAFSCommandError(
|
|
2114
|
+
"E_ADVANCED_VALIDATION_CONTENT",
|
|
2115
|
+
"Instruction content is required.",
|
|
2116
|
+
"Provide --content or --content-file with non-empty text."
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
if (providers.length === 0) {
|
|
2120
|
+
throw new LAFSCommandError(
|
|
2121
|
+
"E_ADVANCED_NO_TARGET_PROVIDERS",
|
|
2122
|
+
"No target providers resolved for instruction update.",
|
|
2123
|
+
"Use --all or pass provider IDs with --agent."
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
const summary = await updateInstructionsSingleOperation(
|
|
2127
|
+
providers,
|
|
2128
|
+
content,
|
|
2129
|
+
scope,
|
|
2130
|
+
opts.projectDir
|
|
2131
|
+
);
|
|
2132
|
+
return {
|
|
2133
|
+
objective: "Update instruction files across providers in one operation",
|
|
2134
|
+
constraints: {
|
|
2135
|
+
scope,
|
|
2136
|
+
minimumPriority,
|
|
2137
|
+
providerCount: providers.length
|
|
2138
|
+
},
|
|
2139
|
+
acceptanceCriteria: {
|
|
2140
|
+
updatedFiles: summary.updatedFiles
|
|
2141
|
+
},
|
|
2142
|
+
data: opts.details ? summary : {
|
|
2143
|
+
updatedFiles: summary.updatedFiles,
|
|
2144
|
+
files: summary.actions.map((entry) => ({
|
|
2145
|
+
file: entry.file,
|
|
2146
|
+
action: entry.action
|
|
2147
|
+
}))
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
}));
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// src/commands/advanced/configure.ts
|
|
2154
|
+
function registerAdvancedConfigure(parent) {
|
|
2155
|
+
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 () => {
|
|
2156
|
+
const provider = getProvider(opts.agent);
|
|
2157
|
+
if (!provider) {
|
|
2158
|
+
throw new LAFSCommandError(
|
|
2159
|
+
"E_ADVANCED_PROVIDER_NOT_FOUND",
|
|
2160
|
+
`Unknown provider: ${opts.agent}`,
|
|
2161
|
+
"Check `caamp providers list` for valid provider IDs/aliases."
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
const globalMcp = opts.globalMcpFile ? await readMcpOperations(opts.globalMcpFile) : [];
|
|
2165
|
+
const projectMcp = opts.projectMcpFile ? await readMcpOperations(opts.projectMcpFile) : [];
|
|
2166
|
+
const sharedInstruction = await readTextInput(opts.instruction, opts.instructionFile);
|
|
2167
|
+
const globalInstruction = await readTextInput(
|
|
2168
|
+
opts.instructionGlobal,
|
|
2169
|
+
opts.instructionGlobalFile
|
|
2170
|
+
);
|
|
2171
|
+
const projectInstruction = await readTextInput(
|
|
2172
|
+
opts.instructionProject,
|
|
2173
|
+
opts.instructionProjectFile
|
|
2174
|
+
);
|
|
2175
|
+
let instructionContent;
|
|
2176
|
+
if (globalInstruction || projectInstruction) {
|
|
2177
|
+
instructionContent = {
|
|
2178
|
+
...globalInstruction ? { global: globalInstruction } : {},
|
|
2179
|
+
...projectInstruction ? { project: projectInstruction } : {}
|
|
2180
|
+
};
|
|
2181
|
+
} else if (sharedInstruction) {
|
|
2182
|
+
instructionContent = sharedInstruction;
|
|
2183
|
+
}
|
|
2184
|
+
if (globalMcp.length === 0 && projectMcp.length === 0 && !instructionContent) {
|
|
2185
|
+
throw new LAFSCommandError(
|
|
2186
|
+
"E_ADVANCED_VALIDATION_NO_OPS",
|
|
2187
|
+
"No configuration operations were provided.",
|
|
2188
|
+
"Provide MCP files and/or instruction content."
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
const result = await configureProviderGlobalAndProject(provider, {
|
|
2192
|
+
globalMcp: globalMcp.map((entry) => ({
|
|
2193
|
+
serverName: entry.serverName,
|
|
2194
|
+
config: entry.config
|
|
2195
|
+
})),
|
|
2196
|
+
projectMcp: projectMcp.map((entry) => ({
|
|
2197
|
+
serverName: entry.serverName,
|
|
2198
|
+
config: entry.config
|
|
2199
|
+
})),
|
|
2200
|
+
instructionContent,
|
|
2201
|
+
projectDir: opts.projectDir
|
|
2202
|
+
});
|
|
2203
|
+
const globalFailures = result.mcp.global.filter((entry) => !entry.success);
|
|
2204
|
+
const projectFailures = result.mcp.project.filter((entry) => !entry.success);
|
|
2205
|
+
if (globalFailures.length > 0 || projectFailures.length > 0) {
|
|
2206
|
+
throw new LAFSCommandError(
|
|
2207
|
+
"E_ADVANCED_CONFIGURE_FAILED",
|
|
2208
|
+
"One or more MCP writes failed during configure operation.",
|
|
2209
|
+
"Inspect the failed write entries and provider config paths, then retry.",
|
|
2210
|
+
true,
|
|
2211
|
+
result
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
return {
|
|
2215
|
+
objective: "Configure global and project settings in one operation",
|
|
2216
|
+
constraints: {
|
|
2217
|
+
provider: provider.id,
|
|
2218
|
+
globalMcpOps: globalMcp.length,
|
|
2219
|
+
projectMcpOps: projectMcp.length,
|
|
2220
|
+
instructionMode: instructionContent ? typeof instructionContent === "string" ? "shared" : "scoped" : "none"
|
|
2221
|
+
},
|
|
2222
|
+
acceptanceCriteria: {
|
|
2223
|
+
globalWrites: result.mcp.global.length,
|
|
2224
|
+
projectWrites: result.mcp.project.length
|
|
2225
|
+
},
|
|
2226
|
+
data: opts.details ? result : {
|
|
2227
|
+
providerId: result.providerId,
|
|
2228
|
+
configPaths: result.configPaths,
|
|
2229
|
+
globalWrites: result.mcp.global.length,
|
|
2230
|
+
projectWrites: result.mcp.project.length,
|
|
2231
|
+
instructionUpdates: {
|
|
2232
|
+
global: result.instructions.global?.size ?? 0,
|
|
2233
|
+
project: result.instructions.project?.size ?? 0
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
}));
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/commands/advanced/index.ts
|
|
2241
|
+
function registerAdvancedCommands(program2) {
|
|
2242
|
+
const advanced = program2.command("advanced").description("LAFS-compliant wrappers for advanced orchestration APIs");
|
|
2243
|
+
registerAdvancedProviders(advanced);
|
|
2244
|
+
registerAdvancedBatch(advanced);
|
|
2245
|
+
registerAdvancedConflicts(advanced);
|
|
2246
|
+
registerAdvancedApply(advanced);
|
|
2247
|
+
registerAdvancedInstructions(advanced);
|
|
2248
|
+
registerAdvancedConfigure(advanced);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
1062
2251
|
// src/cli.ts
|
|
1063
2252
|
var program = new Command();
|
|
1064
|
-
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version("0.
|
|
2253
|
+
program.name("caamp").description("Central AI Agent Managed Packages - unified provider registry and package manager").version("0.3.0").option("-v, --verbose", "Show debug output").option("-q, --quiet", "Suppress non-error output");
|
|
2254
|
+
program.hook("preAction", (thisCommand) => {
|
|
2255
|
+
const opts = thisCommand.optsWithGlobals();
|
|
2256
|
+
if (opts.verbose) setVerbose(true);
|
|
2257
|
+
if (opts.quiet) setQuiet(true);
|
|
2258
|
+
});
|
|
1065
2259
|
registerProvidersCommand(program);
|
|
1066
2260
|
registerSkillsCommands(program);
|
|
1067
2261
|
registerMcpCommands(program);
|
|
1068
2262
|
registerInstructionsCommands(program);
|
|
1069
2263
|
registerConfigCommand(program);
|
|
2264
|
+
registerDoctorCommand(program);
|
|
2265
|
+
registerAdvancedCommands(program);
|
|
1070
2266
|
program.parse();
|
|
1071
2267
|
//# sourceMappingURL=cli.js.map
|