@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/README.md +18 -2
- package/dist/{chunk-PCWTRJV2.js → chunk-ZYINKJDE.js} +805 -46
- package/dist/chunk-ZYINKJDE.js.map +1 -0
- package/dist/cli.js +966 -34
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +275 -21
- package/dist/index.js +31 -1
- package/package.json +2 -1
- package/dist/chunk-PCWTRJV2.js.map +0 -1
|
@@ -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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
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
|
-
|
|
739
|
-
|
|
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:
|
|
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 =
|
|
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:
|
|
1446
|
-
const { dirname:
|
|
1447
|
-
await
|
|
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:
|
|
1981
|
-
await
|
|
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-
|
|
2868
|
+
//# sourceMappingURL=chunk-ZYINKJDE.js.map
|