@cleocode/caamp 0.3.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/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,
@@ -25,6 +32,7 @@ import {
25
32
  parseSource,
26
33
  readConfig,
27
34
  readLockFile,
35
+ recommendSkills,
28
36
  recordMcpInstall,
29
37
  recordSkillInstall,
30
38
  removeMcpFromLock,
@@ -34,11 +42,14 @@ import {
34
42
  resolveConfigPath,
35
43
  scanDirectory,
36
44
  scanFile,
45
+ selectProvidersByMinimumPriority,
37
46
  setQuiet,
38
47
  setVerbose,
39
48
  toSarif,
49
+ tokenizeCriteriaValue,
50
+ updateInstructionsSingleOperation,
40
51
  validateSkill
41
- } from "./chunk-PCWTRJV2.js";
52
+ } from "./chunk-ZYINKJDE.js";
42
53
 
43
54
  // src/cli.ts
44
55
  import { Command } from "commander";
@@ -137,6 +148,7 @@ ${provider.toolName}`));
137
148
  }
138
149
 
139
150
  // src/commands/skills/install.ts
151
+ import { existsSync } from "fs";
140
152
  import pc2 from "picocolors";
141
153
 
142
154
  // src/core/sources/github.ts
@@ -192,6 +204,28 @@ async function cloneGitLabRepo(owner, repo, ref, subPath) {
192
204
  }
193
205
 
194
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
+ }
195
229
  function registerSkillsInstall(parent) {
196
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) => {
197
231
  let providers;
@@ -215,7 +249,13 @@ function registerSkillsInstall(parent) {
215
249
  if (isMarketplaceScoped(source)) {
216
250
  console.log(pc2.dim(`Searching marketplace for ${source}...`));
217
251
  const client = new MarketplaceClient();
218
- const skill = await client.getSkill(source);
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
+ }
219
259
  if (!skill) {
220
260
  console.error(pc2.red(`Skill not found: ${source}`));
221
261
  process.exit(1);
@@ -226,25 +266,58 @@ function registerSkillsInstall(parent) {
226
266
  console.error(pc2.red("Could not resolve GitHub source"));
227
267
  process.exit(1);
228
268
  }
229
- const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, skill.path ? skill.path.replace(/\/SKILL\.md$/, "") : void 0);
230
- localPath = result.localPath;
231
- cleanup = result.cleanup;
232
- skillName = skill.name;
233
- sourceValue = skill.githubUrl;
234
- sourceType = parsed.type;
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
+ }
235
298
  } else {
236
299
  const parsed = parseSource(source);
237
300
  skillName = parsed.inferredName;
238
301
  sourceValue = parsed.value;
239
302
  sourceType = parsed.type;
240
303
  if (parsed.type === "github" && parsed.owner && parsed.repo) {
241
- const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
242
- localPath = result.localPath;
243
- cleanup = result.cleanup;
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
+ }
244
312
  } else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
245
- const result = await cloneGitLabRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
246
- localPath = result.localPath;
247
- cleanup = result.cleanup;
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
+ }
248
321
  } else if (parsed.type === "local") {
249
322
  localPath = parsed.value;
250
323
  } else {
@@ -253,6 +326,9 @@ function registerSkillsInstall(parent) {
253
326
  }
254
327
  }
255
328
  try {
329
+ if (!localPath) {
330
+ throw new Error("No local skill path resolved for installation");
331
+ }
256
332
  const result = await installSkill(
257
333
  localPath,
258
334
  skillName,
@@ -359,19 +435,125 @@ ${skills.length} skill(s) found:
359
435
  }
360
436
 
361
437
  // src/commands/skills/find.ts
438
+ import { randomUUID } from "crypto";
439
+ import {
440
+ resolveOutputFormat
441
+ } from "@cleocode/lafs-protocol";
362
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
+ };
363
451
  function registerSkillsFind(parent) {
364
- 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
+ }
365
534
  if (!query) {
366
535
  console.log(pc5.dim("Usage: caamp skills find <query>"));
367
536
  return;
368
537
  }
369
538
  const limit = parseInt(opts.limit, 10);
370
539
  const client = new MarketplaceClient();
371
- console.log(pc5.dim(`Searching marketplaces for "${query}"...
540
+ if (format === "human") {
541
+ console.log(pc5.dim(`Searching marketplaces for "${query}"...
372
542
  `));
373
- const results = await client.search(query, limit);
374
- if (opts.json) {
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") {
375
557
  console.log(JSON.stringify(results, null, 2));
376
558
  return;
377
559
  }
@@ -386,13 +568,118 @@ function registerSkillsFind(parent) {
386
568
  console.log(` ${pc5.dim(`from ${skill.source}`)}`);
387
569
  console.log();
388
570
  }
389
- console.log(pc5.dim(`Install with: caamp skills install <scopedName>`));
571
+ console.log(pc5.dim("Install with: caamp skills install <scopedName>"));
390
572
  });
391
573
  }
392
574
  function formatStars(n) {
393
575
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
394
576
  return String(n);
395
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
+ }
396
683
 
397
684
  // src/commands/skills/check.ts
398
685
  import pc6 from "picocolors";
@@ -576,13 +863,13 @@ ${outdated.length} skill(s) have updates available:
576
863
  // src/commands/skills/init.ts
577
864
  import pc8 from "picocolors";
578
865
  import { writeFile, mkdir } from "fs/promises";
579
- import { existsSync } from "fs";
866
+ import { existsSync as existsSync2 } from "fs";
580
867
  import { join as join4 } from "path";
581
868
  function registerSkillsInit(parent) {
582
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) => {
583
870
  const skillName = name ?? "my-skill";
584
871
  const skillDir = join4(opts.dir, skillName);
585
- if (existsSync(skillDir)) {
872
+ if (existsSync2(skillDir)) {
586
873
  console.error(pc8.red(`Directory already exists: ${skillDir}`));
587
874
  process.exit(1);
588
875
  }
@@ -621,10 +908,10 @@ Show example inputs and expected outputs.
621
908
 
622
909
  // src/commands/skills/audit.ts
623
910
  import pc9 from "picocolors";
624
- import { existsSync as existsSync2, statSync } from "fs";
911
+ import { existsSync as existsSync3, statSync } from "fs";
625
912
  function registerSkillsAudit(parent) {
626
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) => {
627
- if (!existsSync2(path)) {
914
+ if (!existsSync3(path)) {
628
915
  console.error(pc9.red(`Path not found: ${path}`));
629
916
  process.exit(1);
630
917
  }
@@ -842,7 +1129,7 @@ ${allEntries.length} MCP server(s) configured:
842
1129
 
843
1130
  // src/commands/mcp/detect.ts
844
1131
  import pc14 from "picocolors";
845
- import { existsSync as existsSync3 } from "fs";
1132
+ import { existsSync as existsSync4 } from "fs";
846
1133
  function registerMcpDetect(parent) {
847
1134
  parent.command("detect").description("Auto-detect installed MCP tools and their configurations").option("--json", "Output as JSON").action(async (opts) => {
848
1135
  const providers = getInstalledProviders();
@@ -854,8 +1141,8 @@ function registerMcpDetect(parent) {
854
1141
  const projectEntries = await listMcpServers(provider, "project");
855
1142
  detected.push({
856
1143
  provider: provider.id,
857
- hasGlobalConfig: globalPath !== null && existsSync3(globalPath),
858
- hasProjectConfig: projectPath !== null && existsSync3(projectPath),
1144
+ hasGlobalConfig: globalPath !== null && existsSync4(globalPath),
1145
+ hasProjectConfig: projectPath !== null && existsSync4(projectPath),
859
1146
  globalServers: globalEntries.map((e) => e.name),
860
1147
  projectServers: projectEntries.map((e) => e.name)
861
1148
  });
@@ -1016,7 +1303,7 @@ function registerInstructionsCommands(program2) {
1016
1303
  // src/commands/config.ts
1017
1304
  import pc18 from "picocolors";
1018
1305
  import { join as join5 } from "path";
1019
- import { existsSync as existsSync4 } from "fs";
1306
+ import { existsSync as existsSync5 } from "fs";
1020
1307
  function registerConfigCommand(program2) {
1021
1308
  const config = program2.command("config").description("View provider configuration");
1022
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) => {
@@ -1026,7 +1313,7 @@ function registerConfigCommand(program2) {
1026
1313
  process.exit(1);
1027
1314
  }
1028
1315
  const configPath = opts.global ? provider.configPathGlobal : provider.configPathProject ? join5(process.cwd(), provider.configPathProject) : provider.configPathGlobal;
1029
- if (!existsSync4(configPath)) {
1316
+ if (!existsSync5(configPath)) {
1030
1317
  console.log(pc18.dim(`No config file at: ${configPath}`));
1031
1318
  return;
1032
1319
  }
@@ -1067,7 +1354,7 @@ ${provider.toolName} config (${configPath}):
1067
1354
  // src/commands/doctor.ts
1068
1355
  import pc19 from "picocolors";
1069
1356
  import { execFileSync } from "child_process";
1070
- import { existsSync as existsSync5, readdirSync, lstatSync } from "fs";
1357
+ import { existsSync as existsSync6, readdirSync, lstatSync } from "fs";
1071
1358
  import { homedir } from "os";
1072
1359
  import { join as join6 } from "path";
1073
1360
  var CAAMP_VERSION = "0.2.0";
@@ -1146,7 +1433,7 @@ function checkInstalledProviders() {
1146
1433
  function checkSkillSymlinks() {
1147
1434
  const checks = [];
1148
1435
  const canonicalDir = join6(homedir(), ".agents", "skills");
1149
- if (!existsSync5(canonicalDir)) {
1436
+ if (!existsSync6(canonicalDir)) {
1150
1437
  checks.push({ label: "0 canonical skills", status: "pass" });
1151
1438
  checks.push({ label: "No broken symlinks", status: "pass" });
1152
1439
  return { name: "Skills", checks };
@@ -1164,7 +1451,7 @@ function checkSkillSymlinks() {
1164
1451
  const providers = getAllProviders();
1165
1452
  for (const provider of providers) {
1166
1453
  const skillDir = provider.pathSkills;
1167
- if (!existsSync5(skillDir)) continue;
1454
+ if (!existsSync6(skillDir)) continue;
1168
1455
  try {
1169
1456
  const entries = readdirSync(skillDir);
1170
1457
  for (const entry of entries) {
@@ -1172,7 +1459,7 @@ function checkSkillSymlinks() {
1172
1459
  try {
1173
1460
  const stat = lstatSync(fullPath);
1174
1461
  if (stat.isSymbolicLink()) {
1175
- if (!existsSync5(fullPath)) {
1462
+ if (!existsSync6(fullPath)) {
1176
1463
  broken.push(`${provider.id}/${entry}`);
1177
1464
  }
1178
1465
  }
@@ -1200,7 +1487,7 @@ async function checkLockFile() {
1200
1487
  checks.push({ label: "Lock file valid", status: "pass" });
1201
1488
  let orphaned = 0;
1202
1489
  for (const [name, entry] of Object.entries(lock.skills)) {
1203
- if (entry.canonicalPath && !existsSync5(entry.canonicalPath)) {
1490
+ if (entry.canonicalPath && !existsSync6(entry.canonicalPath)) {
1204
1491
  orphaned++;
1205
1492
  }
1206
1493
  }
@@ -1229,7 +1516,7 @@ async function checkConfigFiles() {
1229
1516
  for (const r of installed) {
1230
1517
  const provider = r.provider;
1231
1518
  const configPath = provider.configPathGlobal;
1232
- if (!existsSync5(configPath)) {
1519
+ if (!existsSync6(configPath)) {
1233
1520
  checks.push({
1234
1521
  label: `${provider.id}: no config file found`,
1235
1522
  status: "warn",
@@ -1317,6 +1604,650 @@ function registerDoctorCommand(program2) {
1317
1604
  });
1318
1605
  }
1319
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
+
1320
2251
  // src/cli.ts
1321
2252
  var program = new Command();
1322
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");
@@ -1331,5 +2262,6 @@ registerMcpCommands(program);
1331
2262
  registerInstructionsCommands(program);
1332
2263
  registerConfigCommand(program);
1333
2264
  registerDoctorCommand(program);
2265
+ registerAdvancedCommands(program);
1334
2266
  program.parse();
1335
2267
  //# sourceMappingURL=cli.js.map