@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.
@@ -576,8 +576,66 @@ async function checkSkillUpdate(skillName) {
576
576
  };
577
577
  }
578
578
 
579
+ // src/core/network/fetch.ts
580
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
581
+ var NetworkError = class extends Error {
582
+ kind;
583
+ url;
584
+ status;
585
+ constructor(message, kind, url, status) {
586
+ super(message);
587
+ this.name = "NetworkError";
588
+ this.kind = kind;
589
+ this.url = url;
590
+ this.status = status;
591
+ }
592
+ };
593
+ function isAbortError(error) {
594
+ return error instanceof Error && error.name === "AbortError";
595
+ }
596
+ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
597
+ try {
598
+ return await fetch(url, {
599
+ ...init,
600
+ signal: AbortSignal.timeout(timeoutMs)
601
+ });
602
+ } catch (error) {
603
+ if (isAbortError(error)) {
604
+ throw new NetworkError(`Request timed out after ${timeoutMs}ms`, "timeout", url);
605
+ }
606
+ throw new NetworkError("Network request failed", "network", url);
607
+ }
608
+ }
609
+ function ensureOkResponse(response, url) {
610
+ if (!response.ok) {
611
+ throw new NetworkError(`Request failed with status ${response.status}`, "http", url, response.status);
612
+ }
613
+ return response;
614
+ }
615
+ function formatNetworkError(error) {
616
+ if (error instanceof NetworkError) {
617
+ if (error.kind === "timeout") {
618
+ return "Network request timed out. Please check your connection and try again.";
619
+ }
620
+ if (error.kind === "http") {
621
+ return `Marketplace request failed with HTTP ${error.status ?? "unknown"}. Please try again shortly.`;
622
+ }
623
+ return "Network request failed. Please check your connection and try again.";
624
+ }
625
+ if (error instanceof Error) return error.message;
626
+ return String(error);
627
+ }
628
+
579
629
  // src/core/marketplace/skillsmp.ts
580
630
  var API_BASE = "https://www.agentskills.in/api/skills";
631
+ function parseScopedName(value) {
632
+ const match = value.match(/^@([^/]+)\/([^/]+)$/);
633
+ if (!match) return null;
634
+ return {
635
+ author: match[1],
636
+ name: match[2]
637
+ };
638
+ }
581
639
  function toResult(skill) {
582
640
  return {
583
641
  name: skill.name,
@@ -599,31 +657,34 @@ var SkillsMPAdapter = class {
599
657
  limit: String(limit),
600
658
  sortBy: "stars"
601
659
  });
602
- try {
603
- const response = await fetch(`${API_BASE}?${params}`);
604
- if (!response.ok) return [];
605
- const data = await response.json();
606
- return data.skills.map(toResult);
607
- } catch {
608
- return [];
609
- }
660
+ const url = `${API_BASE}?${params}`;
661
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
662
+ const data = await response.json();
663
+ return data.skills.map(toResult);
610
664
  }
611
665
  async getSkill(scopedName) {
612
- const params = new URLSearchParams({
613
- search: scopedName,
614
- limit: "1"
615
- });
616
- try {
617
- const response = await fetch(`${API_BASE}?${params}`);
618
- if (!response.ok) return null;
666
+ const parts = parseScopedName(scopedName);
667
+ const searchTerms = parts ? [parts.name, `${parts.author} ${parts.name}`, scopedName] : [scopedName];
668
+ const seen = /* @__PURE__ */ new Set();
669
+ for (const term of searchTerms) {
670
+ if (seen.has(term)) continue;
671
+ seen.add(term);
672
+ const params = new URLSearchParams({
673
+ search: term,
674
+ limit: "50",
675
+ sortBy: "stars"
676
+ });
677
+ const url = `${API_BASE}?${params}`;
678
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
619
679
  const data = await response.json();
620
680
  const match = data.skills.find(
621
681
  (s) => s.scopedName === scopedName || `@${s.author}/${s.name}` === scopedName
622
682
  );
623
- return match ? toResult(match) : null;
624
- } catch {
625
- return null;
683
+ if (match) {
684
+ return toResult(match);
685
+ }
626
686
  }
687
+ return null;
627
688
  }
628
689
  };
629
690
 
@@ -645,18 +706,14 @@ function toResult2(skill) {
645
706
  var SkillsShAdapter = class {
646
707
  name = "skills.sh";
647
708
  async search(query, limit = 20) {
648
- try {
649
- const params = new URLSearchParams({
650
- q: query,
651
- limit: String(limit)
652
- });
653
- const response = await fetch(`${API_BASE2}/search?${params}`);
654
- if (!response.ok) return [];
655
- const data = await response.json();
656
- return data.results.map(toResult2);
657
- } catch {
658
- return [];
659
- }
709
+ const params = new URLSearchParams({
710
+ q: query,
711
+ limit: String(limit)
712
+ });
713
+ const url = `${API_BASE2}/search?${params}`;
714
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
715
+ const data = await response.json();
716
+ return data.results.map(toResult2);
660
717
  }
661
718
  async getSkill(scopedName) {
662
719
  const results = await this.search(scopedName, 5);
@@ -665,6 +722,14 @@ var SkillsShAdapter = class {
665
722
  };
666
723
 
667
724
  // src/core/marketplace/client.ts
725
+ var MarketplaceUnavailableError = class extends Error {
726
+ details;
727
+ constructor(message, details) {
728
+ super(message);
729
+ this.name = "MarketplaceUnavailableError";
730
+ this.details = details;
731
+ }
732
+ };
668
733
  var MarketplaceClient = class {
669
734
  adapters;
670
735
  /**
@@ -704,11 +769,21 @@ var MarketplaceClient = class {
704
769
  * ```
705
770
  */
706
771
  async search(query, limit = 20) {
707
- const promises = this.adapters.map(
708
- (adapter) => adapter.search(query, limit).catch(() => [])
709
- );
710
- const allResults = await Promise.all(promises);
711
- const flat = allResults.flat();
772
+ const settled = await Promise.allSettled(this.adapters.map((adapter) => adapter.search(query, limit)));
773
+ const flat = [];
774
+ const failures = [];
775
+ for (const [index, result] of settled.entries()) {
776
+ const adapterName = this.adapters[index]?.name ?? "unknown";
777
+ if (result.status === "fulfilled") {
778
+ flat.push(...result.value);
779
+ } else {
780
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
781
+ failures.push(`${adapterName}: ${reason}`);
782
+ }
783
+ }
784
+ if (flat.length === 0 && failures.length > 0) {
785
+ throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
786
+ }
712
787
  const seen = /* @__PURE__ */ new Map();
713
788
  for (const result of flat) {
714
789
  const existing = seen.get(result.scopedName);
@@ -734,9 +809,18 @@ var MarketplaceClient = class {
734
809
  * ```
735
810
  */
736
811
  async getSkill(scopedName) {
812
+ const failures = [];
737
813
  for (const adapter of this.adapters) {
738
- const result = await adapter.getSkill(scopedName).catch(() => null);
739
- if (result) return result;
814
+ try {
815
+ const result = await adapter.getSkill(scopedName);
816
+ if (result) return result;
817
+ } catch (error) {
818
+ const reason = error instanceof Error ? error.message : String(error);
819
+ failures.push(`${adapter.name}: ${reason}`);
820
+ }
821
+ }
822
+ if (failures.length === this.adapters.length && this.adapters.length > 0) {
823
+ throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
740
824
  }
741
825
  return null;
742
826
  }
@@ -809,6 +893,273 @@ async function discoverSkillsMulti(dirs) {
809
893
  return all;
810
894
  }
811
895
 
896
+ // src/core/skills/recommendation.ts
897
+ var RECOMMENDATION_ERROR_CODES = {
898
+ QUERY_INVALID: "E_SKILLS_QUERY_INVALID",
899
+ NO_MATCHES: "E_SKILLS_NO_MATCHES",
900
+ SOURCE_UNAVAILABLE: "E_SKILLS_SOURCE_UNAVAILABLE",
901
+ CRITERIA_CONFLICT: "E_SKILLS_CRITERIA_CONFLICT"
902
+ };
903
+ var DEFAULT_WEIGHTS = {
904
+ mustHaveMatch: 10,
905
+ preferMatch: 4,
906
+ queryTokenMatch: 3,
907
+ starsFactor: 2,
908
+ metadataBoost: 2,
909
+ modernMarkerBoost: 3,
910
+ legacyMarkerPenalty: 3,
911
+ excludePenalty: 25,
912
+ missingMustHavePenalty: 20
913
+ };
914
+ var DEFAULT_MODERN_MARKERS = ["svelte 5", "runes", "lafs", "slsa", "drizzle", "better-auth"];
915
+ var DEFAULT_LEGACY_MARKERS = ["svelte 3", "jquery", "bower", "legacy", "book.json", "gitbook-cli"];
916
+ function tokenizeCriteriaValue(value) {
917
+ return value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
918
+ }
919
+ function normalizeList(value) {
920
+ if (value === void 0) return [];
921
+ if (!(typeof value === "string" || Array.isArray(value))) return [];
922
+ const source = Array.isArray(value) ? value : [value];
923
+ const flattened = source.flatMap((item) => typeof item === "string" ? tokenizeCriteriaValue(item) : []);
924
+ return Array.from(new Set(flattened)).sort((a, b) => a.localeCompare(b));
925
+ }
926
+ function hasAnyCriteriaInput(input) {
927
+ const query = typeof input.query === "string" ? input.query.trim() : "";
928
+ if (query.length > 0) return true;
929
+ const lists = [input.mustHave, input.prefer, input.exclude];
930
+ return lists.some((list) => normalizeList(list).length > 0);
931
+ }
932
+ function validateRecommendationCriteria(input) {
933
+ const issues = [];
934
+ if (input.query !== void 0 && typeof input.query !== "string") {
935
+ issues.push({
936
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
937
+ field: "query",
938
+ message: "query must be a string"
939
+ });
940
+ }
941
+ if (input.mustHave !== void 0 && !(typeof input.mustHave === "string" || Array.isArray(input.mustHave))) {
942
+ issues.push({
943
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
944
+ field: "mustHave",
945
+ message: "mustHave must be a string or string[]"
946
+ });
947
+ }
948
+ if (input.prefer !== void 0 && !(typeof input.prefer === "string" || Array.isArray(input.prefer))) {
949
+ issues.push({
950
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
951
+ field: "prefer",
952
+ message: "prefer must be a string or string[]"
953
+ });
954
+ }
955
+ if (input.exclude !== void 0 && !(typeof input.exclude === "string" || Array.isArray(input.exclude))) {
956
+ issues.push({
957
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
958
+ field: "exclude",
959
+ message: "exclude must be a string or string[]"
960
+ });
961
+ }
962
+ const mustHave = normalizeList(input.mustHave);
963
+ const prefer = normalizeList(input.prefer);
964
+ const exclude = normalizeList(input.exclude);
965
+ const conflict = mustHave.some((term) => exclude.includes(term)) || prefer.some((term) => exclude.includes(term));
966
+ if (conflict) {
967
+ issues.push({
968
+ code: RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
969
+ field: "exclude",
970
+ message: "criteria terms cannot appear in both prefer/must-have and exclude"
971
+ });
972
+ }
973
+ if (issues.length === 0 && !hasAnyCriteriaInput(input)) {
974
+ issues.push({
975
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
976
+ field: "query",
977
+ message: "at least one criteria value is required"
978
+ });
979
+ }
980
+ return {
981
+ valid: issues.length === 0,
982
+ issues
983
+ };
984
+ }
985
+ function normalizeRecommendationCriteria(input) {
986
+ const query = (input.query ?? "").trim().toLowerCase();
987
+ return {
988
+ query,
989
+ queryTokens: query ? Array.from(new Set(tokenizeCriteriaValue(query.replace(/\s+/g, ",")))).sort((a, b) => a.localeCompare(b)) : [],
990
+ mustHave: normalizeList(input.mustHave),
991
+ prefer: normalizeList(input.prefer),
992
+ exclude: normalizeList(input.exclude)
993
+ };
994
+ }
995
+ function countMatches(haystack, needles) {
996
+ let count = 0;
997
+ for (const needle of needles) {
998
+ if (haystack.includes(needle)) {
999
+ count += 1;
1000
+ }
1001
+ }
1002
+ return count;
1003
+ }
1004
+ function clampScore(value) {
1005
+ return Number(value.toFixed(6));
1006
+ }
1007
+ function buildSearchText(skill) {
1008
+ return `${skill.name} ${skill.scopedName} ${skill.description} ${skill.author}`.toLowerCase();
1009
+ }
1010
+ function scoreSkillRecommendation(skill, criteria, options = {}) {
1011
+ const weights = { ...DEFAULT_WEIGHTS, ...options.weights };
1012
+ const modernMarkers = (options.modernMarkers ?? DEFAULT_MODERN_MARKERS).map((marker) => marker.toLowerCase());
1013
+ const legacyMarkers = (options.legacyMarkers ?? DEFAULT_LEGACY_MARKERS).map((marker) => marker.toLowerCase());
1014
+ const text = buildSearchText(skill);
1015
+ const reasons = [];
1016
+ const tradeoffs = [];
1017
+ const mustHaveMatches = countMatches(text, criteria.mustHave);
1018
+ const missingMustHave = Math.max(criteria.mustHave.length - mustHaveMatches, 0);
1019
+ const preferMatches = countMatches(text, criteria.prefer);
1020
+ const queryMatches = countMatches(text, criteria.queryTokens);
1021
+ const excludeMatches = countMatches(text, criteria.exclude);
1022
+ const modernMatches = countMatches(text, modernMarkers);
1023
+ const legacyMatches = countMatches(text, legacyMarkers);
1024
+ const metadataSignal = skill.description.trim().length >= 80 ? 1 : 0;
1025
+ const starsSignal = Math.log10(skill.stars + 1);
1026
+ const sourceConfidence = skill.source === "agentskills.in" ? 1 : skill.source === "skills.sh" ? 0.8 : 0.6;
1027
+ const mustHaveScore = mustHaveMatches * weights.mustHaveMatch - missingMustHave * weights.missingMustHavePenalty;
1028
+ const preferScore = preferMatches * weights.preferMatch;
1029
+ const queryScore = queryMatches * weights.queryTokenMatch;
1030
+ const starsScore = starsSignal * weights.starsFactor;
1031
+ const metadataScore = (metadataSignal + sourceConfidence) * weights.metadataBoost;
1032
+ const modernityScore = modernMatches * weights.modernMarkerBoost - legacyMatches * weights.legacyMarkerPenalty;
1033
+ const exclusionPenalty = excludeMatches * weights.excludePenalty;
1034
+ const hasGitbookTopic = text.includes("gitbook");
1035
+ const hasGitSync = text.includes("git sync") || text.includes("git") && text.includes("sync");
1036
+ const hasApiWorkflow = text.includes("api") && (text.includes("workflow") || text.includes("sync"));
1037
+ const hasLegacyCli = text.includes("gitbook-cli") || text.includes("book.json");
1038
+ const topicScore = (hasGitbookTopic ? 3 : 0) + (hasGitSync ? 2 : 0) + (hasApiWorkflow ? 2 : 0) - (hasLegacyCli ? 4 : 0);
1039
+ const total = clampScore(
1040
+ mustHaveScore + preferScore + queryScore + starsScore + metadataScore + modernityScore + topicScore - exclusionPenalty
1041
+ );
1042
+ if (hasGitbookTopic) reasons.push({ code: "MATCH_TOPIC_GITBOOK" });
1043
+ if (hasGitSync) reasons.push({ code: "HAS_GIT_SYNC" });
1044
+ if (hasApiWorkflow) reasons.push({ code: "HAS_API_WORKFLOW" });
1045
+ if (hasLegacyCli) reasons.push({ code: "PENALTY_LEGACY_CLI" });
1046
+ if (mustHaveMatches > 0) reasons.push({ code: "MUST_HAVE_MATCH", detail: String(mustHaveMatches) });
1047
+ if (missingMustHave > 0) reasons.push({ code: "MISSING_MUST_HAVE", detail: String(missingMustHave) });
1048
+ if (preferMatches > 0) reasons.push({ code: "PREFER_MATCH", detail: String(preferMatches) });
1049
+ if (queryMatches > 0) reasons.push({ code: "QUERY_MATCH", detail: String(queryMatches) });
1050
+ if (starsSignal > 0) reasons.push({ code: "STAR_SIGNAL" });
1051
+ if (metadataSignal > 0) reasons.push({ code: "METADATA_SIGNAL" });
1052
+ if (modernMatches > 0) reasons.push({ code: "MODERN_MARKER", detail: String(modernMatches) });
1053
+ if (legacyMatches > 0) reasons.push({ code: "LEGACY_MARKER", detail: String(legacyMatches) });
1054
+ if (excludeMatches > 0) reasons.push({ code: "EXCLUDE_MATCH", detail: String(excludeMatches) });
1055
+ if (missingMustHave > 0) tradeoffs.push("Missing one or more required criteria terms.");
1056
+ if (excludeMatches > 0) tradeoffs.push("Matches one or more excluded terms.");
1057
+ if (skill.stars < 10) tradeoffs.push("Low quality signal from repository stars.");
1058
+ if (hasLegacyCli) tradeoffs.push("Contains legacy GitBook CLI markers.");
1059
+ const result = {
1060
+ skill,
1061
+ score: total,
1062
+ reasons,
1063
+ tradeoffs,
1064
+ excluded: excludeMatches > 0
1065
+ };
1066
+ if (options.includeDetails) {
1067
+ result.breakdown = {
1068
+ mustHave: clampScore(mustHaveScore),
1069
+ prefer: clampScore(preferScore),
1070
+ query: clampScore(queryScore),
1071
+ stars: clampScore(starsScore),
1072
+ metadata: clampScore(metadataScore),
1073
+ modernity: clampScore(modernityScore),
1074
+ exclusionPenalty: clampScore(exclusionPenalty),
1075
+ total
1076
+ };
1077
+ }
1078
+ return result;
1079
+ }
1080
+ function recommendSkills(skills, criteriaInput, options = {}) {
1081
+ const validation = validateRecommendationCriteria(criteriaInput);
1082
+ if (!validation.valid) {
1083
+ const first = validation.issues[0];
1084
+ const error = new Error(first?.message ?? "Invalid recommendation criteria");
1085
+ error.code = first?.code;
1086
+ error.issues = validation.issues;
1087
+ throw error;
1088
+ }
1089
+ const criteria = normalizeRecommendationCriteria(criteriaInput);
1090
+ const ranking = skills.map((skill) => scoreSkillRecommendation(skill, criteria, options)).sort((a, b) => {
1091
+ if (b.score !== a.score) return b.score - a.score;
1092
+ if (b.skill.stars !== a.skill.stars) return b.skill.stars - a.skill.stars;
1093
+ return a.skill.scopedName.localeCompare(b.skill.scopedName);
1094
+ });
1095
+ return {
1096
+ criteria,
1097
+ ranking: typeof options.top === "number" ? ranking.slice(0, Math.max(0, options.top)) : ranking
1098
+ };
1099
+ }
1100
+ var rankSkills = recommendSkills;
1101
+
1102
+ // src/core/skills/recommendation-api.ts
1103
+ function formatSkillRecommendations(result, opts) {
1104
+ const top = result.ranking;
1105
+ if (opts.mode === "human") {
1106
+ if (top.length === 0) return "No recommendations found.";
1107
+ const lines = ["Recommended skills:", ""];
1108
+ for (const [index, entry] of top.entries()) {
1109
+ const marker = index === 0 ? " (Recommended)" : "";
1110
+ lines.push(`${index + 1}) ${entry.skill.scopedName}${marker}`);
1111
+ lines.push(` why: ${entry.reasons.map((reason) => reason.code).join(", ") || "score-based match"}`);
1112
+ lines.push(` tradeoff: ${entry.tradeoffs[0] ?? "none"}`);
1113
+ }
1114
+ lines.push("");
1115
+ lines.push(`CHOOSE: ${top.map((_, index) => index + 1).join(",")}`);
1116
+ return lines.join("\n");
1117
+ }
1118
+ const options = top.map((entry, index) => ({
1119
+ rank: index + 1,
1120
+ scopedName: entry.skill.scopedName,
1121
+ score: entry.score,
1122
+ reasons: entry.reasons,
1123
+ tradeoffs: entry.tradeoffs,
1124
+ ...opts.details ? {
1125
+ description: entry.skill.description,
1126
+ source: entry.skill.source,
1127
+ evidence: entry.breakdown ?? null
1128
+ } : {}
1129
+ }));
1130
+ return {
1131
+ query: result.criteria.query,
1132
+ recommended: options[0] ?? null,
1133
+ options
1134
+ };
1135
+ }
1136
+ async function searchSkills(query, options = {}) {
1137
+ const trimmed = query.trim();
1138
+ if (!trimmed) {
1139
+ const error = new Error("query must be non-empty");
1140
+ error.code = RECOMMENDATION_ERROR_CODES.QUERY_INVALID;
1141
+ throw error;
1142
+ }
1143
+ const client = new MarketplaceClient();
1144
+ try {
1145
+ return await client.search(trimmed, options.limit ?? 20);
1146
+ } catch (error) {
1147
+ const wrapped = new Error(error instanceof Error ? error.message : String(error));
1148
+ wrapped.code = RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
1149
+ throw wrapped;
1150
+ }
1151
+ }
1152
+ async function recommendSkills2(query, criteria, options = {}) {
1153
+ const hits = await searchSkills(query, { limit: options.limit ?? Math.max((options.top ?? 3) * 5, 20) });
1154
+ const ranked = recommendSkills(hits, { ...criteria, query }, options);
1155
+ if (ranked.ranking.length === 0) {
1156
+ const error = new Error("no matches found");
1157
+ error.code = RECOMMENDATION_ERROR_CODES.NO_MATCHES;
1158
+ throw error;
1159
+ }
1160
+ return ranked;
1161
+ }
1162
+
812
1163
  // src/core/skills/audit/scanner.ts
813
1164
  import { readFile as readFile4 } from "fs/promises";
814
1165
  import { existsSync as existsSync6 } from "fs";
@@ -1222,13 +1573,13 @@ async function scanFile(filePath, rules) {
1222
1573
  }
1223
1574
  async function scanDirectory(dirPath) {
1224
1575
  const { readdir: readdir2 } = await import("fs/promises");
1225
- const { join: join8 } = await import("path");
1576
+ const { join: join9 } = await import("path");
1226
1577
  if (!existsSync6(dirPath)) return [];
1227
1578
  const entries = await readdir2(dirPath, { withFileTypes: true });
1228
1579
  const results = [];
1229
1580
  for (const entry of entries) {
1230
1581
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1231
- const skillFile = join8(dirPath, entry.name, "SKILL.md");
1582
+ const skillFile = join9(dirPath, entry.name, "SKILL.md");
1232
1583
  if (existsSync6(skillFile)) {
1233
1584
  results.push(await scanFile(skillFile));
1234
1585
  }
@@ -1442,9 +1793,9 @@ function getNestedValue(obj, keyPath) {
1442
1793
  return current;
1443
1794
  }
1444
1795
  async function ensureDir(filePath) {
1445
- const { mkdir: mkdir4 } = await import("fs/promises");
1446
- const { dirname: dirname4 } = await import("path");
1447
- await mkdir4(dirname4(filePath), { recursive: true });
1796
+ const { mkdir: mkdir5 } = await import("fs/promises");
1797
+ const { dirname: dirname5 } = await import("path");
1798
+ await mkdir5(dirname5(filePath), { recursive: true });
1448
1799
  }
1449
1800
 
1450
1801
  // src/core/formats/json.ts
@@ -1977,8 +2328,8 @@ async function removeInjection(filePath) {
1977
2328
  if (!MARKER_PATTERN.test(content)) return false;
1978
2329
  const cleaned = content.replace(MARKER_PATTERN, "").replace(/^\n{2,}/, "\n").trim();
1979
2330
  if (!cleaned) {
1980
- const { rm: rm2 } = await import("fs/promises");
1981
- await rm2(filePath);
2331
+ const { rm: rm3 } = await import("fs/promises");
2332
+ await rm3(filePath);
1982
2333
  } else {
1983
2334
  await writeFile6(filePath, cleaned + "\n", "utf-8");
1984
2335
  }
@@ -2042,6 +2393,398 @@ function groupByInstructFile(providers) {
2042
2393
  return groups;
2043
2394
  }
2044
2395
 
2396
+ // src/core/advanced/orchestration.ts
2397
+ import { existsSync as existsSync13, lstatSync as lstatSync2 } from "fs";
2398
+ import {
2399
+ cp as cp2,
2400
+ mkdir as mkdir4,
2401
+ readFile as readFile10,
2402
+ readlink as readlink2,
2403
+ rm as rm2,
2404
+ symlink as symlink2,
2405
+ writeFile as writeFile7
2406
+ } from "fs/promises";
2407
+ import { homedir as homedir4, tmpdir } from "os";
2408
+ import { basename as basename2, dirname as dirname4, join as join8 } from "path";
2409
+ var PRIORITY_ORDER = {
2410
+ high: 0,
2411
+ medium: 1,
2412
+ low: 2
2413
+ };
2414
+ var CANONICAL_SKILLS_DIR = join8(homedir4(), ".agents", "skills");
2415
+ function selectProvidersByMinimumPriority(providers, minimumPriority = "low") {
2416
+ const maxRank = PRIORITY_ORDER[minimumPriority];
2417
+ return [...providers].filter((provider) => PRIORITY_ORDER[provider.priority] <= maxRank).sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]);
2418
+ }
2419
+ function resolveSkillLinkPath(provider, skillName, isGlobal, projectDir) {
2420
+ const skillDir = isGlobal ? provider.pathSkills : join8(projectDir, provider.pathProjectSkills);
2421
+ return join8(skillDir, skillName);
2422
+ }
2423
+ async function snapshotConfigs(paths) {
2424
+ const snapshots = /* @__PURE__ */ new Map();
2425
+ for (const path of paths) {
2426
+ if (!path || snapshots.has(path)) continue;
2427
+ if (!existsSync13(path)) {
2428
+ snapshots.set(path, null);
2429
+ continue;
2430
+ }
2431
+ snapshots.set(path, await readFile10(path, "utf-8"));
2432
+ }
2433
+ return snapshots;
2434
+ }
2435
+ async function restoreConfigSnapshots(snapshots) {
2436
+ for (const [path, content] of snapshots) {
2437
+ if (content === null) {
2438
+ await rm2(path, { force: true });
2439
+ continue;
2440
+ }
2441
+ await mkdir4(dirname4(path), { recursive: true });
2442
+ await writeFile7(path, content, "utf-8");
2443
+ }
2444
+ }
2445
+ async function snapshotSkillState(providerTargets, operation, projectDir, backupRoot) {
2446
+ const skillName = operation.skillName;
2447
+ const isGlobal = operation.isGlobal ?? true;
2448
+ const canonicalPath = join8(CANONICAL_SKILLS_DIR, skillName);
2449
+ const canonicalExisted = existsSync13(canonicalPath);
2450
+ const canonicalBackupPath = join8(backupRoot, "canonical", skillName);
2451
+ if (canonicalExisted) {
2452
+ await mkdir4(dirname4(canonicalBackupPath), { recursive: true });
2453
+ await cp2(canonicalPath, canonicalBackupPath, { recursive: true });
2454
+ }
2455
+ const pathSnapshots = [];
2456
+ for (const provider of providerTargets) {
2457
+ const linkPath = resolveSkillLinkPath(provider, skillName, isGlobal, projectDir);
2458
+ if (!existsSync13(linkPath)) {
2459
+ pathSnapshots.push({ linkPath, state: "missing" });
2460
+ continue;
2461
+ }
2462
+ const stat = lstatSync2(linkPath);
2463
+ if (stat.isSymbolicLink()) {
2464
+ pathSnapshots.push({
2465
+ linkPath,
2466
+ state: "symlink",
2467
+ symlinkTarget: await readlink2(linkPath)
2468
+ });
2469
+ continue;
2470
+ }
2471
+ const backupPath = join8(backupRoot, "links", provider.id, `${skillName}-${basename2(linkPath)}`);
2472
+ await mkdir4(dirname4(backupPath), { recursive: true });
2473
+ if (stat.isDirectory()) {
2474
+ await cp2(linkPath, backupPath, { recursive: true });
2475
+ pathSnapshots.push({ linkPath, state: "directory", backupPath });
2476
+ continue;
2477
+ }
2478
+ await cp2(linkPath, backupPath);
2479
+ pathSnapshots.push({ linkPath, state: "file", backupPath });
2480
+ }
2481
+ return {
2482
+ skillName,
2483
+ isGlobal,
2484
+ canonicalPath,
2485
+ canonicalBackupPath: canonicalExisted ? canonicalBackupPath : void 0,
2486
+ canonicalExisted,
2487
+ pathSnapshots
2488
+ };
2489
+ }
2490
+ async function restoreSkillSnapshot(snapshot) {
2491
+ if (existsSync13(snapshot.canonicalPath)) {
2492
+ await rm2(snapshot.canonicalPath, { recursive: true, force: true });
2493
+ }
2494
+ if (snapshot.canonicalExisted && snapshot.canonicalBackupPath && existsSync13(snapshot.canonicalBackupPath)) {
2495
+ await mkdir4(dirname4(snapshot.canonicalPath), { recursive: true });
2496
+ await cp2(snapshot.canonicalBackupPath, snapshot.canonicalPath, { recursive: true });
2497
+ }
2498
+ for (const pathSnapshot of snapshot.pathSnapshots) {
2499
+ await rm2(pathSnapshot.linkPath, { recursive: true, force: true });
2500
+ if (pathSnapshot.state === "missing") continue;
2501
+ await mkdir4(dirname4(pathSnapshot.linkPath), { recursive: true });
2502
+ if (pathSnapshot.state === "symlink" && pathSnapshot.symlinkTarget) {
2503
+ const linkType = process.platform === "win32" ? "junction" : "dir";
2504
+ await symlink2(pathSnapshot.symlinkTarget, pathSnapshot.linkPath, linkType);
2505
+ continue;
2506
+ }
2507
+ if ((pathSnapshot.state === "directory" || pathSnapshot.state === "file") && pathSnapshot.backupPath) {
2508
+ if (pathSnapshot.state === "directory") {
2509
+ await cp2(pathSnapshot.backupPath, pathSnapshot.linkPath, { recursive: true });
2510
+ } else {
2511
+ await cp2(pathSnapshot.backupPath, pathSnapshot.linkPath);
2512
+ }
2513
+ }
2514
+ }
2515
+ }
2516
+ async function installBatchWithRollback(options) {
2517
+ const projectDir = options.projectDir ?? process.cwd();
2518
+ const minimumPriority = options.minimumPriority ?? "low";
2519
+ const mcpOps = options.mcp ?? [];
2520
+ const skillOps = options.skills ?? [];
2521
+ const baseProviders = options.providers ?? getInstalledProviders();
2522
+ const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
2523
+ const configPaths = providers.flatMap((provider) => {
2524
+ const paths = [];
2525
+ for (const operation of mcpOps) {
2526
+ const path = resolveConfigPath(provider, operation.scope ?? "project", projectDir);
2527
+ if (path) paths.push(path);
2528
+ }
2529
+ return paths;
2530
+ });
2531
+ const configSnapshots = await snapshotConfigs(configPaths);
2532
+ const backupRoot = join8(
2533
+ tmpdir(),
2534
+ `caamp-skill-backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
2535
+ );
2536
+ const skillSnapshots = await Promise.all(
2537
+ skillOps.map((operation) => snapshotSkillState(providers, operation, projectDir, backupRoot))
2538
+ );
2539
+ const appliedSkills = [];
2540
+ const rollbackErrors = [];
2541
+ let mcpApplied = 0;
2542
+ let skillsApplied = 0;
2543
+ let rollbackPerformed = false;
2544
+ try {
2545
+ for (const operation of mcpOps) {
2546
+ const scope = operation.scope ?? "project";
2547
+ for (const provider of providers) {
2548
+ const result = await installMcpServer(
2549
+ provider,
2550
+ operation.serverName,
2551
+ operation.config,
2552
+ scope,
2553
+ projectDir
2554
+ );
2555
+ if (!result.success) {
2556
+ throw new Error(result.error ?? `Failed MCP install for ${provider.id}`);
2557
+ }
2558
+ mcpApplied += 1;
2559
+ }
2560
+ }
2561
+ for (const operation of skillOps) {
2562
+ const isGlobal = operation.isGlobal ?? true;
2563
+ const result = await installSkill(
2564
+ operation.sourcePath,
2565
+ operation.skillName,
2566
+ providers,
2567
+ isGlobal,
2568
+ projectDir
2569
+ );
2570
+ const linkedProviders = providers.filter((provider) => result.linkedAgents.includes(provider.id));
2571
+ appliedSkills.push({
2572
+ skillName: operation.skillName,
2573
+ isGlobal,
2574
+ linkedProviders
2575
+ });
2576
+ if (result.errors.length > 0) {
2577
+ throw new Error(result.errors.join("; "));
2578
+ }
2579
+ skillsApplied += 1;
2580
+ }
2581
+ await rm2(backupRoot, { recursive: true, force: true });
2582
+ return {
2583
+ success: true,
2584
+ providerIds: providers.map((provider) => provider.id),
2585
+ mcpApplied,
2586
+ skillsApplied,
2587
+ rollbackPerformed: false,
2588
+ rollbackErrors: []
2589
+ };
2590
+ } catch (error) {
2591
+ rollbackPerformed = true;
2592
+ for (const applied of [...appliedSkills].reverse()) {
2593
+ try {
2594
+ await removeSkill(applied.skillName, applied.linkedProviders, applied.isGlobal, projectDir);
2595
+ } catch (err) {
2596
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2597
+ }
2598
+ }
2599
+ try {
2600
+ await restoreConfigSnapshots(configSnapshots);
2601
+ } catch (err) {
2602
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2603
+ }
2604
+ for (const snapshot of skillSnapshots) {
2605
+ try {
2606
+ await restoreSkillSnapshot(snapshot);
2607
+ } catch (err) {
2608
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2609
+ }
2610
+ }
2611
+ await rm2(backupRoot, { recursive: true, force: true });
2612
+ return {
2613
+ success: false,
2614
+ providerIds: providers.map((provider) => provider.id),
2615
+ mcpApplied,
2616
+ skillsApplied,
2617
+ rollbackPerformed,
2618
+ rollbackErrors,
2619
+ error: error instanceof Error ? error.message : String(error)
2620
+ };
2621
+ }
2622
+ }
2623
+ function stableStringify(value) {
2624
+ if (Array.isArray(value)) {
2625
+ return `[${value.map(stableStringify).join(",")}]`;
2626
+ }
2627
+ if (value && typeof value === "object") {
2628
+ const record = value;
2629
+ const keys = Object.keys(record).sort();
2630
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
2631
+ }
2632
+ return JSON.stringify(value);
2633
+ }
2634
+ async function detectMcpConfigConflicts(providers, operations, projectDir = process.cwd()) {
2635
+ const conflicts = [];
2636
+ for (const provider of providers) {
2637
+ for (const operation of operations) {
2638
+ const scope = operation.scope ?? "project";
2639
+ if (operation.config.type && !provider.supportedTransports.includes(operation.config.type)) {
2640
+ conflicts.push({
2641
+ providerId: provider.id,
2642
+ serverName: operation.serverName,
2643
+ scope,
2644
+ code: "unsupported-transport",
2645
+ message: `${provider.id} does not support transport ${operation.config.type}`
2646
+ });
2647
+ }
2648
+ if (operation.config.headers && !provider.supportsHeaders) {
2649
+ conflicts.push({
2650
+ providerId: provider.id,
2651
+ serverName: operation.serverName,
2652
+ scope,
2653
+ code: "unsupported-headers",
2654
+ message: `${provider.id} does not support header configuration`
2655
+ });
2656
+ }
2657
+ const existingEntries = await listMcpServers(provider, scope, projectDir);
2658
+ const current = existingEntries.find((entry) => entry.name === operation.serverName);
2659
+ if (!current) continue;
2660
+ const transform = getTransform(provider.id);
2661
+ const desired = transform ? transform(operation.serverName, operation.config) : operation.config;
2662
+ if (stableStringify(current.config) !== stableStringify(desired)) {
2663
+ conflicts.push({
2664
+ providerId: provider.id,
2665
+ serverName: operation.serverName,
2666
+ scope,
2667
+ code: "existing-mismatch",
2668
+ message: `${provider.id} has existing config mismatch for ${operation.serverName}`
2669
+ });
2670
+ }
2671
+ }
2672
+ }
2673
+ return conflicts;
2674
+ }
2675
+ async function applyMcpInstallWithPolicy(providers, operations, policy = "fail", projectDir = process.cwd()) {
2676
+ const conflicts = await detectMcpConfigConflicts(providers, operations, projectDir);
2677
+ const conflictKey = (providerId, serverName, scope) => `${providerId}::${serverName}::${scope}`;
2678
+ const conflictMap = /* @__PURE__ */ new Map();
2679
+ for (const conflict of conflicts) {
2680
+ conflictMap.set(conflictKey(conflict.providerId, conflict.serverName, conflict.scope), conflict);
2681
+ }
2682
+ if (policy === "fail" && conflicts.length > 0) {
2683
+ return { conflicts, applied: [], skipped: [] };
2684
+ }
2685
+ const applied = [];
2686
+ const skipped = [];
2687
+ for (const provider of providers) {
2688
+ for (const operation of operations) {
2689
+ const scope = operation.scope ?? "project";
2690
+ const key = conflictKey(provider.id, operation.serverName, scope);
2691
+ const conflict = conflictMap.get(key);
2692
+ if (policy === "skip" && conflict) {
2693
+ skipped.push({
2694
+ providerId: provider.id,
2695
+ serverName: operation.serverName,
2696
+ scope,
2697
+ reason: conflict.code
2698
+ });
2699
+ continue;
2700
+ }
2701
+ const result = await installMcpServer(
2702
+ provider,
2703
+ operation.serverName,
2704
+ operation.config,
2705
+ scope,
2706
+ projectDir
2707
+ );
2708
+ applied.push(result);
2709
+ }
2710
+ }
2711
+ return { conflicts, applied, skipped };
2712
+ }
2713
+ async function updateInstructionsSingleOperation(providers, content, scope = "project", projectDir = process.cwd()) {
2714
+ const actions = await injectAll(providers, projectDir, scope, content);
2715
+ const groupedByFile = groupByInstructFile(providers);
2716
+ const summary = {
2717
+ scope,
2718
+ updatedFiles: actions.size,
2719
+ actions: []
2720
+ };
2721
+ for (const [filePath, action] of actions.entries()) {
2722
+ const providersForFile = providers.filter((provider) => {
2723
+ const expectedPath = scope === "global" ? join8(provider.pathGlobal, provider.instructFile) : join8(projectDir, provider.instructFile);
2724
+ return expectedPath === filePath;
2725
+ });
2726
+ const fallback = groupedByFile.get(basename2(filePath)) ?? [];
2727
+ const selected = providersForFile.length > 0 ? providersForFile : fallback;
2728
+ summary.actions.push({
2729
+ file: filePath,
2730
+ action,
2731
+ providers: selected.map((provider) => provider.id),
2732
+ configFormats: Array.from(new Set(selected.map((provider) => provider.configFormat)))
2733
+ });
2734
+ }
2735
+ return summary;
2736
+ }
2737
+ async function configureProviderGlobalAndProject(provider, options) {
2738
+ const projectDir = options.projectDir ?? process.cwd();
2739
+ const globalOps = options.globalMcp ?? [];
2740
+ const projectOps = options.projectMcp ?? [];
2741
+ const globalResults = [];
2742
+ for (const operation of globalOps) {
2743
+ globalResults.push(await installMcpServer(
2744
+ provider,
2745
+ operation.serverName,
2746
+ operation.config,
2747
+ "global",
2748
+ projectDir
2749
+ ));
2750
+ }
2751
+ const projectResults = [];
2752
+ for (const operation of projectOps) {
2753
+ projectResults.push(await installMcpServer(
2754
+ provider,
2755
+ operation.serverName,
2756
+ operation.config,
2757
+ "project",
2758
+ projectDir
2759
+ ));
2760
+ }
2761
+ const instructionResults = {};
2762
+ const instructionContent = options.instructionContent;
2763
+ if (typeof instructionContent === "string") {
2764
+ instructionResults.global = await injectAll([provider], projectDir, "global", instructionContent);
2765
+ instructionResults.project = await injectAll([provider], projectDir, "project", instructionContent);
2766
+ } else if (instructionContent) {
2767
+ if (instructionContent.global) {
2768
+ instructionResults.global = await injectAll([provider], projectDir, "global", instructionContent.global);
2769
+ }
2770
+ if (instructionContent.project) {
2771
+ instructionResults.project = await injectAll([provider], projectDir, "project", instructionContent.project);
2772
+ }
2773
+ }
2774
+ return {
2775
+ providerId: provider.id,
2776
+ configPaths: {
2777
+ global: resolveConfigPath(provider, "global", projectDir),
2778
+ project: resolveConfigPath(provider, "project", projectDir)
2779
+ },
2780
+ mcp: {
2781
+ global: globalResults,
2782
+ project: projectResults
2783
+ },
2784
+ instructions: instructionResults
2785
+ };
2786
+ }
2787
+
2045
2788
  export {
2046
2789
  getAllProviders,
2047
2790
  getProvider,
@@ -2070,11 +2813,21 @@ export {
2070
2813
  removeSkillFromLock,
2071
2814
  getTrackedSkills,
2072
2815
  checkSkillUpdate,
2816
+ formatNetworkError,
2073
2817
  MarketplaceClient,
2074
2818
  parseSkillFile,
2075
2819
  discoverSkill,
2076
2820
  discoverSkills,
2077
2821
  discoverSkillsMulti,
2822
+ RECOMMENDATION_ERROR_CODES,
2823
+ tokenizeCriteriaValue,
2824
+ validateRecommendationCriteria,
2825
+ normalizeRecommendationCriteria,
2826
+ scoreSkillRecommendation,
2827
+ rankSkills,
2828
+ formatSkillRecommendations,
2829
+ searchSkills,
2830
+ recommendSkills2 as recommendSkills,
2078
2831
  scanFile,
2079
2832
  scanDirectory,
2080
2833
  toSarif,
@@ -2104,6 +2857,12 @@ export {
2104
2857
  checkAllInjections,
2105
2858
  injectAll,
2106
2859
  generateInjectionContent,
2107
- groupByInstructFile
2860
+ groupByInstructFile,
2861
+ selectProvidersByMinimumPriority,
2862
+ installBatchWithRollback,
2863
+ detectMcpConfigConflicts,
2864
+ applyMcpInstallWithPolicy,
2865
+ updateInstructionsSingleOperation,
2866
+ configureProviderGlobalAndProject
2108
2867
  };
2109
- //# sourceMappingURL=chunk-PCWTRJV2.js.map
2868
+ //# sourceMappingURL=chunk-ZYINKJDE.js.map