@cleocode/caamp 1.9.1 → 2026.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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/{chunk-TI6WOJDG.js → chunk-3IEKCREL.js} +3 -3
- package/dist/chunk-3IEKCREL.js.map +1 -0
- package/dist/{chunk-ZF4W3K5H.js → chunk-3WKBFXLE.js} +1429 -1380
- package/dist/chunk-3WKBFXLE.js.map +1 -0
- package/dist/{chunk-O7IVK5JY.js → chunk-5UUABVWS.js} +2 -2
- package/dist/chunk-5UUABVWS.js.map +1 -0
- package/dist/{chunk-J7UN457C.js → chunk-OH6Z2N3G.js} +72 -34
- package/dist/chunk-OH6Z2N3G.js.map +1 -0
- package/dist/cli.js +2510 -2008
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-E2XQ7TQG.js → hooks-Q7KO2SGK.js} +3 -3
- package/dist/index.d.ts +3309 -3334
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/{injector-NSDP5Z2P.js → injector-XCWEBXWK.js} +3 -3
- package/package.json +22 -22
- package/providers/hook-mappings.json +130 -0
- package/dist/chunk-J7UN457C.js.map +0 -1
- package/dist/chunk-O7IVK5JY.js.map +0 -1
- package/dist/chunk-TI6WOJDG.js.map +0 -1
- package/dist/chunk-ZF4W3K5H.js.map +0 -1
- /package/dist/{hooks-E2XQ7TQG.js.map → hooks-Q7KO2SGK.js.map} +0 -0
- /package/dist/{injector-NSDP5Z2P.js.map → injector-XCWEBXWK.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
getAllProviders,
|
|
3
3
|
groupByInstructFile,
|
|
4
4
|
injectAll
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5UUABVWS.js";
|
|
6
6
|
import {
|
|
7
7
|
__export,
|
|
8
8
|
getAgentsConfigPath,
|
|
@@ -15,7 +15,33 @@ import {
|
|
|
15
15
|
resolveProviderConfigPath,
|
|
16
16
|
resolveProviderProjectPath,
|
|
17
17
|
resolveProviderSkillsDirs
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-3IEKCREL.js";
|
|
19
|
+
|
|
20
|
+
// src/core/logger.ts
|
|
21
|
+
var verboseMode = false;
|
|
22
|
+
var quietMode = false;
|
|
23
|
+
var humanMode = false;
|
|
24
|
+
function setVerbose(v) {
|
|
25
|
+
verboseMode = v;
|
|
26
|
+
}
|
|
27
|
+
function setQuiet(q) {
|
|
28
|
+
quietMode = q;
|
|
29
|
+
}
|
|
30
|
+
function debug(...args) {
|
|
31
|
+
if (verboseMode) console.error("[debug]", ...args);
|
|
32
|
+
}
|
|
33
|
+
function isVerbose() {
|
|
34
|
+
return verboseMode;
|
|
35
|
+
}
|
|
36
|
+
function isQuiet() {
|
|
37
|
+
return quietMode;
|
|
38
|
+
}
|
|
39
|
+
function setHuman(h) {
|
|
40
|
+
humanMode = h;
|
|
41
|
+
}
|
|
42
|
+
function isHuman() {
|
|
43
|
+
return humanMode;
|
|
44
|
+
}
|
|
19
45
|
|
|
20
46
|
// src/core/formats/utils.ts
|
|
21
47
|
function deepMerge(target, source) {
|
|
@@ -49,35 +75,9 @@ async function ensureDir(filePath) {
|
|
|
49
75
|
await mkdir4(dirname3(filePath), { recursive: true });
|
|
50
76
|
}
|
|
51
77
|
|
|
52
|
-
// src/core/logger.ts
|
|
53
|
-
var verboseMode = false;
|
|
54
|
-
var quietMode = false;
|
|
55
|
-
var humanMode = false;
|
|
56
|
-
function setVerbose(v) {
|
|
57
|
-
verboseMode = v;
|
|
58
|
-
}
|
|
59
|
-
function setQuiet(q) {
|
|
60
|
-
quietMode = q;
|
|
61
|
-
}
|
|
62
|
-
function debug(...args) {
|
|
63
|
-
if (verboseMode) console.error("[debug]", ...args);
|
|
64
|
-
}
|
|
65
|
-
function isVerbose() {
|
|
66
|
-
return verboseMode;
|
|
67
|
-
}
|
|
68
|
-
function isQuiet() {
|
|
69
|
-
return quietMode;
|
|
70
|
-
}
|
|
71
|
-
function setHuman(h) {
|
|
72
|
-
humanMode = h;
|
|
73
|
-
}
|
|
74
|
-
function isHuman() {
|
|
75
|
-
return humanMode;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
78
|
// src/core/formats/json.ts
|
|
79
|
-
import { readFile, writeFile } from "fs/promises";
|
|
80
79
|
import { existsSync } from "fs";
|
|
80
|
+
import { readFile, writeFile } from "fs/promises";
|
|
81
81
|
import * as jsonc from "jsonc-parser";
|
|
82
82
|
async function readJsonConfig(filePath) {
|
|
83
83
|
if (!existsSync(filePath)) return {};
|
|
@@ -154,37 +154,32 @@ async function removeJsonConfig(filePath, configKey, serverName) {
|
|
|
154
154
|
return true;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// src/core/formats/
|
|
157
|
+
// src/core/formats/toml.ts
|
|
158
158
|
import { existsSync as existsSync2 } from "fs";
|
|
159
159
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
160
|
-
import
|
|
161
|
-
async function
|
|
160
|
+
import TOML from "@iarna/toml";
|
|
161
|
+
async function readTomlConfig(filePath) {
|
|
162
162
|
if (!existsSync2(filePath)) return {};
|
|
163
163
|
const content = await readFile2(filePath, "utf-8");
|
|
164
164
|
if (!content.trim()) return {};
|
|
165
|
-
const result =
|
|
166
|
-
return result
|
|
165
|
+
const result = TOML.parse(content);
|
|
166
|
+
return result;
|
|
167
167
|
}
|
|
168
|
-
async function
|
|
168
|
+
async function writeTomlConfig(filePath, configKey, serverName, serverConfig) {
|
|
169
169
|
await ensureDir(filePath);
|
|
170
|
-
const existing = await
|
|
170
|
+
const existing = await readTomlConfig(filePath);
|
|
171
171
|
const keyParts = configKey.split(".");
|
|
172
172
|
let newEntry = { [serverName]: serverConfig };
|
|
173
173
|
for (const part of [...keyParts].reverse()) {
|
|
174
174
|
newEntry = { [part]: newEntry };
|
|
175
175
|
}
|
|
176
176
|
const merged = deepMerge(existing, newEntry);
|
|
177
|
-
const content =
|
|
178
|
-
indent: 2,
|
|
179
|
-
lineWidth: -1,
|
|
180
|
-
noRefs: true,
|
|
181
|
-
sortKeys: false
|
|
182
|
-
});
|
|
177
|
+
const content = TOML.stringify(merged);
|
|
183
178
|
await writeFile2(filePath, content, "utf-8");
|
|
184
179
|
}
|
|
185
|
-
async function
|
|
180
|
+
async function removeTomlConfig(filePath, configKey, serverName) {
|
|
186
181
|
if (!existsSync2(filePath)) return false;
|
|
187
|
-
const existing = await
|
|
182
|
+
const existing = await readTomlConfig(filePath);
|
|
188
183
|
const keyParts = configKey.split(".");
|
|
189
184
|
let current = existing;
|
|
190
185
|
for (const part of keyParts) {
|
|
@@ -194,42 +189,42 @@ async function removeYamlConfig(filePath, configKey, serverName) {
|
|
|
194
189
|
}
|
|
195
190
|
if (!(serverName in current)) return false;
|
|
196
191
|
delete current[serverName];
|
|
197
|
-
const content =
|
|
198
|
-
indent: 2,
|
|
199
|
-
lineWidth: -1,
|
|
200
|
-
noRefs: true,
|
|
201
|
-
sortKeys: false
|
|
202
|
-
});
|
|
192
|
+
const content = TOML.stringify(existing);
|
|
203
193
|
await writeFile2(filePath, content, "utf-8");
|
|
204
194
|
return true;
|
|
205
195
|
}
|
|
206
196
|
|
|
207
|
-
// src/core/formats/
|
|
197
|
+
// src/core/formats/yaml.ts
|
|
208
198
|
import { existsSync as existsSync3 } from "fs";
|
|
209
199
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
210
|
-
import
|
|
211
|
-
async function
|
|
200
|
+
import yaml from "js-yaml";
|
|
201
|
+
async function readYamlConfig(filePath) {
|
|
212
202
|
if (!existsSync3(filePath)) return {};
|
|
213
203
|
const content = await readFile3(filePath, "utf-8");
|
|
214
204
|
if (!content.trim()) return {};
|
|
215
|
-
const result =
|
|
216
|
-
return result;
|
|
205
|
+
const result = yaml.load(content);
|
|
206
|
+
return result ?? {};
|
|
217
207
|
}
|
|
218
|
-
async function
|
|
208
|
+
async function writeYamlConfig(filePath, configKey, serverName, serverConfig) {
|
|
219
209
|
await ensureDir(filePath);
|
|
220
|
-
const existing = await
|
|
210
|
+
const existing = await readYamlConfig(filePath);
|
|
221
211
|
const keyParts = configKey.split(".");
|
|
222
212
|
let newEntry = { [serverName]: serverConfig };
|
|
223
213
|
for (const part of [...keyParts].reverse()) {
|
|
224
214
|
newEntry = { [part]: newEntry };
|
|
225
215
|
}
|
|
226
216
|
const merged = deepMerge(existing, newEntry);
|
|
227
|
-
const content =
|
|
217
|
+
const content = yaml.dump(merged, {
|
|
218
|
+
indent: 2,
|
|
219
|
+
lineWidth: -1,
|
|
220
|
+
noRefs: true,
|
|
221
|
+
sortKeys: false
|
|
222
|
+
});
|
|
228
223
|
await writeFile3(filePath, content, "utf-8");
|
|
229
224
|
}
|
|
230
|
-
async function
|
|
225
|
+
async function removeYamlConfig(filePath, configKey, serverName) {
|
|
231
226
|
if (!existsSync3(filePath)) return false;
|
|
232
|
-
const existing = await
|
|
227
|
+
const existing = await readYamlConfig(filePath);
|
|
233
228
|
const keyParts = configKey.split(".");
|
|
234
229
|
let current = existing;
|
|
235
230
|
for (const part of keyParts) {
|
|
@@ -239,7 +234,12 @@ async function removeTomlConfig(filePath, configKey, serverName) {
|
|
|
239
234
|
}
|
|
240
235
|
if (!(serverName in current)) return false;
|
|
241
236
|
delete current[serverName];
|
|
242
|
-
const content =
|
|
237
|
+
const content = yaml.dump(existing, {
|
|
238
|
+
indent: 2,
|
|
239
|
+
lineWidth: -1,
|
|
240
|
+
noRefs: true,
|
|
241
|
+
sortKeys: false
|
|
242
|
+
});
|
|
243
243
|
await writeFile3(filePath, content, "utf-8");
|
|
244
244
|
return true;
|
|
245
245
|
}
|
|
@@ -802,15 +802,7 @@ async function listCanonicalSkills() {
|
|
|
802
802
|
|
|
803
803
|
// src/core/advanced/orchestration.ts
|
|
804
804
|
import { existsSync as existsSync7, lstatSync as lstatSync2 } from "fs";
|
|
805
|
-
import {
|
|
806
|
-
cp as cp2,
|
|
807
|
-
mkdir as mkdir2,
|
|
808
|
-
readFile as readFile4,
|
|
809
|
-
readlink,
|
|
810
|
-
rm as rm2,
|
|
811
|
-
symlink as symlink2,
|
|
812
|
-
writeFile as writeFile4
|
|
813
|
-
} from "fs/promises";
|
|
805
|
+
import { cp as cp2, mkdir as mkdir2, readFile as readFile4, readlink, rm as rm2, symlink as symlink2, writeFile as writeFile4 } from "fs/promises";
|
|
814
806
|
import { tmpdir } from "os";
|
|
815
807
|
import { basename, dirname, join as join3 } from "path";
|
|
816
808
|
|
|
@@ -983,7 +975,9 @@ async function installBatchWithRollback(options) {
|
|
|
983
975
|
isGlobal,
|
|
984
976
|
projectDir
|
|
985
977
|
);
|
|
986
|
-
const linkedProviders = providers.filter(
|
|
978
|
+
const linkedProviders = providers.filter(
|
|
979
|
+
(provider) => result.linkedAgents.includes(provider.id)
|
|
980
|
+
);
|
|
987
981
|
appliedSkills.push({
|
|
988
982
|
skillName: operation.skillName,
|
|
989
983
|
isGlobal,
|
|
@@ -1093,7 +1087,10 @@ async function applyMcpInstallWithPolicy(providers, operations, policy = "fail",
|
|
|
1093
1087
|
const conflictKey = (providerId, serverName, scope) => `${providerId}::${serverName}::${scope}`;
|
|
1094
1088
|
const conflictMap = /* @__PURE__ */ new Map();
|
|
1095
1089
|
for (const conflict of conflicts) {
|
|
1096
|
-
conflictMap.set(
|
|
1090
|
+
conflictMap.set(
|
|
1091
|
+
conflictKey(conflict.providerId, conflict.serverName, conflict.scope),
|
|
1092
|
+
conflict
|
|
1093
|
+
);
|
|
1097
1094
|
}
|
|
1098
1095
|
if (policy === "fail" && conflicts.length > 0) {
|
|
1099
1096
|
return { conflicts, applied: [], skipped: [] };
|
|
@@ -1156,35 +1153,59 @@ async function configureProviderGlobalAndProject(provider, options) {
|
|
|
1156
1153
|
const projectOps = options.projectMcp ?? [];
|
|
1157
1154
|
const globalResults = [];
|
|
1158
1155
|
for (const operation of globalOps) {
|
|
1159
|
-
globalResults.push(
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1156
|
+
globalResults.push(
|
|
1157
|
+
await installMcpServer(
|
|
1158
|
+
provider,
|
|
1159
|
+
operation.serverName,
|
|
1160
|
+
operation.config,
|
|
1161
|
+
"global",
|
|
1162
|
+
projectDir
|
|
1163
|
+
)
|
|
1164
|
+
);
|
|
1166
1165
|
}
|
|
1167
1166
|
const projectResults = [];
|
|
1168
1167
|
for (const operation of projectOps) {
|
|
1169
|
-
projectResults.push(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1168
|
+
projectResults.push(
|
|
1169
|
+
await installMcpServer(
|
|
1170
|
+
provider,
|
|
1171
|
+
operation.serverName,
|
|
1172
|
+
operation.config,
|
|
1173
|
+
"project",
|
|
1174
|
+
projectDir
|
|
1175
|
+
)
|
|
1176
|
+
);
|
|
1176
1177
|
}
|
|
1177
1178
|
const instructionResults = {};
|
|
1178
1179
|
const instructionContent = options.instructionContent;
|
|
1179
1180
|
if (typeof instructionContent === "string") {
|
|
1180
|
-
instructionResults.global = await injectAll(
|
|
1181
|
-
|
|
1181
|
+
instructionResults.global = await injectAll(
|
|
1182
|
+
[provider],
|
|
1183
|
+
projectDir,
|
|
1184
|
+
"global",
|
|
1185
|
+
instructionContent
|
|
1186
|
+
);
|
|
1187
|
+
instructionResults.project = await injectAll(
|
|
1188
|
+
[provider],
|
|
1189
|
+
projectDir,
|
|
1190
|
+
"project",
|
|
1191
|
+
instructionContent
|
|
1192
|
+
);
|
|
1182
1193
|
} else if (instructionContent) {
|
|
1183
1194
|
if (instructionContent.global) {
|
|
1184
|
-
instructionResults.global = await injectAll(
|
|
1195
|
+
instructionResults.global = await injectAll(
|
|
1196
|
+
[provider],
|
|
1197
|
+
projectDir,
|
|
1198
|
+
"global",
|
|
1199
|
+
instructionContent.global
|
|
1200
|
+
);
|
|
1185
1201
|
}
|
|
1186
1202
|
if (instructionContent.project) {
|
|
1187
|
-
instructionResults.project = await injectAll(
|
|
1203
|
+
instructionResults.project = await injectAll(
|
|
1204
|
+
[provider],
|
|
1205
|
+
projectDir,
|
|
1206
|
+
"project",
|
|
1207
|
+
instructionContent.project
|
|
1208
|
+
);
|
|
1188
1209
|
}
|
|
1189
1210
|
}
|
|
1190
1211
|
return {
|
|
@@ -1205,7 +1226,7 @@ async function configureProviderGlobalAndProject(provider, options) {
|
|
|
1205
1226
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1206
1227
|
import { existsSync as existsSync8 } from "fs";
|
|
1207
1228
|
import { homedir } from "os";
|
|
1208
|
-
import { isAbsolute, resolve
|
|
1229
|
+
import { isAbsolute, resolve } from "path";
|
|
1209
1230
|
var CLEO_SERVER_NAMES = {
|
|
1210
1231
|
stable: "cleo",
|
|
1211
1232
|
beta: "cleo-beta",
|
|
@@ -1288,7 +1309,7 @@ function buildCleoProfile(options) {
|
|
|
1288
1309
|
function expandHome(pathValue) {
|
|
1289
1310
|
if (pathValue === "~") return homedir();
|
|
1290
1311
|
if (pathValue.startsWith("~/")) {
|
|
1291
|
-
return
|
|
1312
|
+
return resolve(homedir(), pathValue.slice(2));
|
|
1292
1313
|
}
|
|
1293
1314
|
return pathValue;
|
|
1294
1315
|
}
|
|
@@ -1296,7 +1317,7 @@ function checkCommandReachability(command) {
|
|
|
1296
1317
|
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
|
1297
1318
|
if (hasPathSeparator || command.startsWith("~")) {
|
|
1298
1319
|
const expanded = expandHome(command);
|
|
1299
|
-
const candidate = isAbsolute(expanded) ? expanded :
|
|
1320
|
+
const candidate = isAbsolute(expanded) ? expanded : resolve(process.cwd(), expanded);
|
|
1300
1321
|
if (existsSync8(candidate)) {
|
|
1301
1322
|
return { reachable: true, method: "path", detail: candidate };
|
|
1302
1323
|
}
|
|
@@ -1337,12 +1358,12 @@ function isCleoSource(source) {
|
|
|
1337
1358
|
}
|
|
1338
1359
|
|
|
1339
1360
|
// src/core/lock-utils.ts
|
|
1340
|
-
import { open, readFile as readFile5, writeFile as writeFile5, mkdir as mkdir3, rm as rm3, rename, stat } from "fs/promises";
|
|
1341
1361
|
import { existsSync as existsSync9 } from "fs";
|
|
1362
|
+
import { mkdir as mkdir3, open, readFile as readFile5, rename, rm as rm3, stat, writeFile as writeFile5 } from "fs/promises";
|
|
1342
1363
|
var LOCK_GUARD_PATH = `${LOCK_FILE_PATH}.lock`;
|
|
1343
1364
|
var STALE_LOCK_MS = 5e3;
|
|
1344
1365
|
function sleep(ms) {
|
|
1345
|
-
return new Promise((
|
|
1366
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1346
1367
|
}
|
|
1347
1368
|
async function removeStaleLock() {
|
|
1348
1369
|
try {
|
|
@@ -1452,9 +1473,7 @@ async function getLastSelectedAgents() {
|
|
|
1452
1473
|
function inferCleoLockData(config, channel) {
|
|
1453
1474
|
const command = typeof config.command === "string" ? config.command : "";
|
|
1454
1475
|
const args = Array.isArray(config.args) ? config.args.filter((a) => typeof a === "string") : [];
|
|
1455
|
-
const packageArg = args.find(
|
|
1456
|
-
(a) => a.includes(CLEO_MCP_NPM_PACKAGE)
|
|
1457
|
-
);
|
|
1476
|
+
const packageArg = args.find((a) => a.includes(CLEO_MCP_NPM_PACKAGE));
|
|
1458
1477
|
if (packageArg) {
|
|
1459
1478
|
const version = extractVersionTag(packageArg);
|
|
1460
1479
|
return {
|
|
@@ -1639,7 +1658,9 @@ function inferName(source, type) {
|
|
|
1639
1658
|
}
|
|
1640
1659
|
if (type === "command") {
|
|
1641
1660
|
const parts = source.split(/\s+/);
|
|
1642
|
-
const command = parts.find(
|
|
1661
|
+
const command = parts.find(
|
|
1662
|
+
(p) => !p.startsWith("-") && p !== "npx" && p !== "node" && p !== "python" && p !== "python3"
|
|
1663
|
+
);
|
|
1643
1664
|
return command ?? parts[0] ?? source;
|
|
1644
1665
|
}
|
|
1645
1666
|
return source;
|
|
@@ -1751,1169 +1772,13 @@ function isMarketplaceScoped(input) {
|
|
|
1751
1772
|
return /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(input);
|
|
1752
1773
|
}
|
|
1753
1774
|
|
|
1754
|
-
// src/core/
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
/** Classification of the failure. */
|
|
1758
|
-
kind;
|
|
1759
|
-
/** URL that was being fetched. */
|
|
1760
|
-
url;
|
|
1761
|
-
/** HTTP status code (only present for `"http"` kind). */
|
|
1762
|
-
status;
|
|
1763
|
-
constructor(message, kind, url, status) {
|
|
1764
|
-
super(message);
|
|
1765
|
-
this.name = "NetworkError";
|
|
1766
|
-
this.kind = kind;
|
|
1767
|
-
this.url = url;
|
|
1768
|
-
this.status = status;
|
|
1769
|
-
}
|
|
1770
|
-
};
|
|
1771
|
-
function isAbortError(error) {
|
|
1772
|
-
return error instanceof Error && error.name === "AbortError";
|
|
1773
|
-
}
|
|
1774
|
-
async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
1775
|
-
try {
|
|
1776
|
-
return await fetch(url, {
|
|
1777
|
-
...init,
|
|
1778
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
1779
|
-
});
|
|
1780
|
-
} catch (error) {
|
|
1781
|
-
if (isAbortError(error)) {
|
|
1782
|
-
throw new NetworkError(`Request timed out after ${timeoutMs}ms`, "timeout", url);
|
|
1783
|
-
}
|
|
1784
|
-
throw new NetworkError("Network request failed", "network", url);
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
function ensureOkResponse(response, url) {
|
|
1788
|
-
if (!response.ok) {
|
|
1789
|
-
throw new NetworkError(`Request failed with status ${response.status}`, "http", url, response.status);
|
|
1790
|
-
}
|
|
1791
|
-
return response;
|
|
1792
|
-
}
|
|
1793
|
-
function formatNetworkError(error) {
|
|
1794
|
-
if (error instanceof NetworkError) {
|
|
1795
|
-
if (error.kind === "timeout") {
|
|
1796
|
-
return "Network request timed out. Please check your connection and try again.";
|
|
1797
|
-
}
|
|
1798
|
-
if (error.kind === "http") {
|
|
1799
|
-
return `Marketplace request failed with HTTP ${error.status ?? "unknown"}. Please try again shortly.`;
|
|
1800
|
-
}
|
|
1801
|
-
return "Network request failed. Please check your connection and try again.";
|
|
1802
|
-
}
|
|
1803
|
-
if (error instanceof Error) return error.message;
|
|
1804
|
-
return String(error);
|
|
1805
|
-
}
|
|
1775
|
+
// src/core/skills/audit/scanner.ts
|
|
1776
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1777
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1806
1778
|
|
|
1807
|
-
// src/core/
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
const match = value.match(/^@([^/]+)\/([^/]+)$/);
|
|
1811
|
-
if (!match) return null;
|
|
1812
|
-
return {
|
|
1813
|
-
author: match[1],
|
|
1814
|
-
name: match[2]
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
function toResult(skill) {
|
|
1818
|
-
return {
|
|
1819
|
-
name: skill.name,
|
|
1820
|
-
scopedName: skill.scopedName,
|
|
1821
|
-
description: skill.description,
|
|
1822
|
-
author: skill.author,
|
|
1823
|
-
stars: skill.stars,
|
|
1824
|
-
githubUrl: skill.githubUrl,
|
|
1825
|
-
repoFullName: skill.repoFullName,
|
|
1826
|
-
path: skill.path,
|
|
1827
|
-
source: "agentskills.in"
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
var SkillsMPAdapter = class {
|
|
1831
|
-
/** The marketplace identifier used in search results. */
|
|
1832
|
-
name = "agentskills.in";
|
|
1833
|
-
/**
|
|
1834
|
-
* Search for skills by query string.
|
|
1835
|
-
*
|
|
1836
|
-
* @param query - Search query to match against skill names and descriptions.
|
|
1837
|
-
* @param limit - Maximum number of results to return.
|
|
1838
|
-
* @returns Array of marketplace results sorted by stars.
|
|
1839
|
-
*/
|
|
1840
|
-
async search(query, limit = 20) {
|
|
1841
|
-
const params = new URLSearchParams({
|
|
1842
|
-
search: query,
|
|
1843
|
-
limit: String(limit),
|
|
1844
|
-
sortBy: "stars"
|
|
1845
|
-
});
|
|
1846
|
-
const url = `${API_BASE}?${params}`;
|
|
1847
|
-
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
1848
|
-
const data = await response.json();
|
|
1849
|
-
return data.skills.map(toResult);
|
|
1850
|
-
}
|
|
1851
|
-
/**
|
|
1852
|
-
* Look up a specific skill by its scoped name.
|
|
1853
|
-
*
|
|
1854
|
-
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
1855
|
-
* @returns The matching marketplace result, or `null` if not found.
|
|
1856
|
-
*/
|
|
1857
|
-
async getSkill(scopedName) {
|
|
1858
|
-
const parts = parseScopedName(scopedName);
|
|
1859
|
-
const searchTerms = parts ? [parts.name, `${parts.author} ${parts.name}`, scopedName] : [scopedName];
|
|
1860
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1861
|
-
for (const term of searchTerms) {
|
|
1862
|
-
if (seen.has(term)) continue;
|
|
1863
|
-
seen.add(term);
|
|
1864
|
-
const params = new URLSearchParams({
|
|
1865
|
-
search: term,
|
|
1866
|
-
limit: "50",
|
|
1867
|
-
sortBy: "stars"
|
|
1868
|
-
});
|
|
1869
|
-
const url = `${API_BASE}?${params}`;
|
|
1870
|
-
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
1871
|
-
const data = await response.json();
|
|
1872
|
-
const match = data.skills.find(
|
|
1873
|
-
(s) => s.scopedName === scopedName || `@${s.author}/${s.name}` === scopedName
|
|
1874
|
-
);
|
|
1875
|
-
if (match) {
|
|
1876
|
-
return toResult(match);
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
return null;
|
|
1880
|
-
}
|
|
1881
|
-
};
|
|
1882
|
-
|
|
1883
|
-
// src/core/marketplace/skillssh.ts
|
|
1884
|
-
var API_BASE2 = "https://skills.sh/api";
|
|
1885
|
-
function toResult2(skill) {
|
|
1886
|
-
return {
|
|
1887
|
-
name: skill.name,
|
|
1888
|
-
scopedName: `@${skill.author}/${skill.name}`,
|
|
1889
|
-
description: skill.description,
|
|
1890
|
-
author: skill.author,
|
|
1891
|
-
stars: skill.stars ?? 0,
|
|
1892
|
-
githubUrl: skill.url,
|
|
1893
|
-
repoFullName: skill.repo,
|
|
1894
|
-
path: "",
|
|
1895
|
-
source: "skills.sh"
|
|
1896
|
-
};
|
|
1897
|
-
}
|
|
1898
|
-
var SkillsShAdapter = class {
|
|
1899
|
-
/** The marketplace identifier used in search results. */
|
|
1900
|
-
name = "skills.sh";
|
|
1901
|
-
/**
|
|
1902
|
-
* Search for skills by query string.
|
|
1903
|
-
*
|
|
1904
|
-
* @param query - Search query to match against skill names.
|
|
1905
|
-
* @param limit - Maximum number of results to return.
|
|
1906
|
-
* @returns Array of marketplace results.
|
|
1907
|
-
*/
|
|
1908
|
-
async search(query, limit = 20) {
|
|
1909
|
-
const params = new URLSearchParams({
|
|
1910
|
-
q: query,
|
|
1911
|
-
limit: String(limit)
|
|
1912
|
-
});
|
|
1913
|
-
const url = `${API_BASE2}/search?${params}`;
|
|
1914
|
-
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
1915
|
-
const data = await response.json();
|
|
1916
|
-
return data.results.map(toResult2);
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Look up a specific skill by its scoped name.
|
|
1920
|
-
*
|
|
1921
|
-
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
1922
|
-
* @returns The matching marketplace result, or `null` if not found.
|
|
1923
|
-
*/
|
|
1924
|
-
async getSkill(scopedName) {
|
|
1925
|
-
const results = await this.search(scopedName, 5);
|
|
1926
|
-
return results.find((r) => r.scopedName === scopedName) ?? null;
|
|
1927
|
-
}
|
|
1928
|
-
};
|
|
1929
|
-
|
|
1930
|
-
// src/core/marketplace/client.ts
|
|
1931
|
-
var MarketplaceUnavailableError = class extends Error {
|
|
1932
|
-
/** Per-adapter failure messages. */
|
|
1933
|
-
details;
|
|
1934
|
-
constructor(message, details) {
|
|
1935
|
-
super(message);
|
|
1936
|
-
this.name = "MarketplaceUnavailableError";
|
|
1937
|
-
this.details = details;
|
|
1938
|
-
}
|
|
1939
|
-
};
|
|
1940
|
-
var MarketplaceClient = class {
|
|
1941
|
-
/** Configured marketplace adapters. */
|
|
1942
|
-
adapters;
|
|
1943
|
-
/**
|
|
1944
|
-
* Create a new marketplace client.
|
|
1945
|
-
*
|
|
1946
|
-
* @param adapters - Custom marketplace adapters (defaults to agentskills.in and skills.sh)
|
|
1947
|
-
*
|
|
1948
|
-
* @example
|
|
1949
|
-
* ```typescript
|
|
1950
|
-
* // Use default adapters
|
|
1951
|
-
* const client = new MarketplaceClient();
|
|
1952
|
-
*
|
|
1953
|
-
* // Use custom adapters
|
|
1954
|
-
* const client = new MarketplaceClient([myAdapter]);
|
|
1955
|
-
* ```
|
|
1956
|
-
*/
|
|
1957
|
-
constructor(adapters) {
|
|
1958
|
-
this.adapters = adapters ?? [
|
|
1959
|
-
new SkillsMPAdapter(),
|
|
1960
|
-
new SkillsShAdapter()
|
|
1961
|
-
];
|
|
1962
|
-
}
|
|
1963
|
-
/**
|
|
1964
|
-
* Search all marketplaces and return deduplicated, sorted results.
|
|
1965
|
-
*
|
|
1966
|
-
* Queries all adapters in parallel and deduplicates by `scopedName`,
|
|
1967
|
-
* keeping the entry with the highest star count. Results are sorted by
|
|
1968
|
-
* stars descending.
|
|
1969
|
-
*
|
|
1970
|
-
* @param query - Search query string
|
|
1971
|
-
* @param limit - Maximum number of results to return (default: 20)
|
|
1972
|
-
* @returns Deduplicated and sorted marketplace results
|
|
1973
|
-
*
|
|
1974
|
-
* @example
|
|
1975
|
-
* ```typescript
|
|
1976
|
-
* const results = await client.search("code review", 10);
|
|
1977
|
-
* ```
|
|
1978
|
-
*/
|
|
1979
|
-
async search(query, limit = 20) {
|
|
1980
|
-
const settled = await Promise.allSettled(this.adapters.map((adapter) => adapter.search(query, limit)));
|
|
1981
|
-
const flat = [];
|
|
1982
|
-
const failures = [];
|
|
1983
|
-
for (const [index, result] of settled.entries()) {
|
|
1984
|
-
const adapterName = this.adapters[index]?.name ?? "unknown";
|
|
1985
|
-
if (result.status === "fulfilled") {
|
|
1986
|
-
flat.push(...result.value);
|
|
1987
|
-
} else {
|
|
1988
|
-
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
1989
|
-
failures.push(`${adapterName}: ${reason}`);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
if (flat.length === 0 && failures.length > 0) {
|
|
1993
|
-
throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
|
|
1994
|
-
}
|
|
1995
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1996
|
-
for (const result of flat) {
|
|
1997
|
-
const existing = seen.get(result.scopedName);
|
|
1998
|
-
if (!existing || result.stars > existing.stars) {
|
|
1999
|
-
seen.set(result.scopedName, result);
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
const deduplicated = Array.from(seen.values());
|
|
2003
|
-
deduplicated.sort((a, b) => b.stars - a.stars);
|
|
2004
|
-
return deduplicated.slice(0, limit);
|
|
2005
|
-
}
|
|
2006
|
-
/**
|
|
2007
|
-
* Get a specific skill by its scoped name from any marketplace.
|
|
2008
|
-
*
|
|
2009
|
-
* Tries each adapter in order and returns the first match.
|
|
2010
|
-
*
|
|
2011
|
-
* @param scopedName - Scoped skill name (e.g. `"@author/my-skill"`)
|
|
2012
|
-
* @returns The marketplace result, or `null` if not found in any marketplace
|
|
2013
|
-
*
|
|
2014
|
-
* @example
|
|
2015
|
-
* ```typescript
|
|
2016
|
-
* const skill = await client.getSkill("@anthropic/memory");
|
|
2017
|
-
* ```
|
|
2018
|
-
*/
|
|
2019
|
-
async getSkill(scopedName) {
|
|
2020
|
-
const failures = [];
|
|
2021
|
-
for (const adapter of this.adapters) {
|
|
2022
|
-
try {
|
|
2023
|
-
const result = await adapter.getSkill(scopedName);
|
|
2024
|
-
if (result) return result;
|
|
2025
|
-
} catch (error) {
|
|
2026
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
2027
|
-
failures.push(`${adapter.name}: ${reason}`);
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
if (failures.length === this.adapters.length && this.adapters.length > 0) {
|
|
2031
|
-
throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
|
|
2032
|
-
}
|
|
2033
|
-
return null;
|
|
2034
|
-
}
|
|
2035
|
-
};
|
|
2036
|
-
|
|
2037
|
-
// src/core/skills/library-loader.ts
|
|
2038
|
-
import { createRequire } from "module";
|
|
2039
|
-
import { existsSync as existsSync10, readdirSync, readFileSync } from "fs";
|
|
2040
|
-
import { basename as basename2, dirname as dirname2, join as join4 } from "path";
|
|
2041
|
-
var require2 = createRequire(import.meta.url);
|
|
2042
|
-
function loadLibraryFromModule(root) {
|
|
2043
|
-
let mod;
|
|
2044
|
-
try {
|
|
2045
|
-
mod = require2(root);
|
|
2046
|
-
} catch {
|
|
2047
|
-
throw new Error(`Failed to load skill library module from ${root}`);
|
|
2048
|
-
}
|
|
2049
|
-
const requiredMethods = [
|
|
2050
|
-
"listSkills",
|
|
2051
|
-
"getSkill",
|
|
2052
|
-
"getSkillPath",
|
|
2053
|
-
"getSkillDir",
|
|
2054
|
-
"readSkillContent",
|
|
2055
|
-
"getCoreSkills",
|
|
2056
|
-
"getSkillsByCategory",
|
|
2057
|
-
"getSkillDependencies",
|
|
2058
|
-
"resolveDependencyTree",
|
|
2059
|
-
"listProfiles",
|
|
2060
|
-
"getProfile",
|
|
2061
|
-
"resolveProfile",
|
|
2062
|
-
"listSharedResources",
|
|
2063
|
-
"getSharedResourcePath",
|
|
2064
|
-
"readSharedResource",
|
|
2065
|
-
"listProtocols",
|
|
2066
|
-
"getProtocolPath",
|
|
2067
|
-
"readProtocol",
|
|
2068
|
-
"validateSkillFrontmatter",
|
|
2069
|
-
"validateAll",
|
|
2070
|
-
"getDispatchMatrix"
|
|
2071
|
-
];
|
|
2072
|
-
for (const method of requiredMethods) {
|
|
2073
|
-
if (typeof mod[method] !== "function") {
|
|
2074
|
-
throw new Error(
|
|
2075
|
-
`Skill library at ${root} does not implement required method: ${method}`
|
|
2076
|
-
);
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
if (!mod.version || typeof mod.version !== "string") {
|
|
2080
|
-
throw new Error(`Skill library at ${root} is missing 'version' property`);
|
|
2081
|
-
}
|
|
2082
|
-
if (!mod.libraryRoot || typeof mod.libraryRoot !== "string") {
|
|
2083
|
-
throw new Error(`Skill library at ${root} is missing 'libraryRoot' property`);
|
|
2084
|
-
}
|
|
2085
|
-
return mod;
|
|
2086
|
-
}
|
|
2087
|
-
function buildLibraryFromFiles(root) {
|
|
2088
|
-
const catalogPath = join4(root, "skills.json");
|
|
2089
|
-
if (!existsSync10(catalogPath)) {
|
|
2090
|
-
throw new Error(`No skills.json found at ${root}`);
|
|
2091
|
-
}
|
|
2092
|
-
const catalogData = JSON.parse(readFileSync(catalogPath, "utf-8"));
|
|
2093
|
-
const entries = catalogData.skills ?? [];
|
|
2094
|
-
const version = catalogData.version ?? "0.0.0";
|
|
2095
|
-
const manifestPath = join4(root, "skills", "manifest.json");
|
|
2096
|
-
let manifest;
|
|
2097
|
-
if (existsSync10(manifestPath)) {
|
|
2098
|
-
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
2099
|
-
} else {
|
|
2100
|
-
manifest = {
|
|
2101
|
-
$schema: "",
|
|
2102
|
-
_meta: {},
|
|
2103
|
-
dispatch_matrix: { by_task_type: {}, by_keyword: {}, by_protocol: {} },
|
|
2104
|
-
skills: []
|
|
2105
|
-
};
|
|
2106
|
-
}
|
|
2107
|
-
const profilesDir = join4(root, "profiles");
|
|
2108
|
-
const profiles = /* @__PURE__ */ new Map();
|
|
2109
|
-
if (existsSync10(profilesDir)) {
|
|
2110
|
-
for (const file of readdirSync(profilesDir)) {
|
|
2111
|
-
if (!file.endsWith(".json")) continue;
|
|
2112
|
-
try {
|
|
2113
|
-
const profile = JSON.parse(
|
|
2114
|
-
readFileSync(join4(profilesDir, file), "utf-8")
|
|
2115
|
-
);
|
|
2116
|
-
profiles.set(profile.name, profile);
|
|
2117
|
-
} catch {
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
const skillMap = /* @__PURE__ */ new Map();
|
|
2122
|
-
for (const entry of entries) {
|
|
2123
|
-
skillMap.set(entry.name, entry);
|
|
2124
|
-
}
|
|
2125
|
-
function getSkillDir2(name) {
|
|
2126
|
-
const entry = skillMap.get(name);
|
|
2127
|
-
if (entry) {
|
|
2128
|
-
return dirname2(join4(root, entry.path));
|
|
2129
|
-
}
|
|
2130
|
-
return join4(root, "skills", name);
|
|
2131
|
-
}
|
|
2132
|
-
function resolveDeps(names, visited = /* @__PURE__ */ new Set()) {
|
|
2133
|
-
const result = [];
|
|
2134
|
-
for (const name of names) {
|
|
2135
|
-
if (visited.has(name)) continue;
|
|
2136
|
-
visited.add(name);
|
|
2137
|
-
const entry = skillMap.get(name);
|
|
2138
|
-
if (entry && entry.dependencies.length > 0) {
|
|
2139
|
-
result.push(...resolveDeps(entry.dependencies, visited));
|
|
2140
|
-
}
|
|
2141
|
-
result.push(name);
|
|
2142
|
-
}
|
|
2143
|
-
return result;
|
|
2144
|
-
}
|
|
2145
|
-
function resolveProfileByName(name, visited = /* @__PURE__ */ new Set()) {
|
|
2146
|
-
if (visited.has(name)) return [];
|
|
2147
|
-
visited.add(name);
|
|
2148
|
-
const profile = profiles.get(name);
|
|
2149
|
-
if (!profile) return [];
|
|
2150
|
-
let skills = [];
|
|
2151
|
-
if (profile.extends) {
|
|
2152
|
-
skills = resolveProfileByName(profile.extends, visited);
|
|
2153
|
-
}
|
|
2154
|
-
skills.push(...profile.skills);
|
|
2155
|
-
return resolveDeps([...new Set(skills)]);
|
|
2156
|
-
}
|
|
2157
|
-
function discoverFiles(dir, ext) {
|
|
2158
|
-
if (!existsSync10(dir)) return [];
|
|
2159
|
-
return readdirSync(dir).filter((f) => f.endsWith(ext)).map((f) => basename2(f, ext));
|
|
2160
|
-
}
|
|
2161
|
-
const library = {
|
|
2162
|
-
version,
|
|
2163
|
-
libraryRoot: root,
|
|
2164
|
-
skills: entries,
|
|
2165
|
-
manifest,
|
|
2166
|
-
listSkills() {
|
|
2167
|
-
return entries.map((e) => e.name);
|
|
2168
|
-
},
|
|
2169
|
-
getSkill(name) {
|
|
2170
|
-
return skillMap.get(name);
|
|
2171
|
-
},
|
|
2172
|
-
getSkillPath(name) {
|
|
2173
|
-
const entry = skillMap.get(name);
|
|
2174
|
-
if (entry) {
|
|
2175
|
-
return join4(root, entry.path);
|
|
2176
|
-
}
|
|
2177
|
-
return join4(root, "skills", name, "SKILL.md");
|
|
2178
|
-
},
|
|
2179
|
-
getSkillDir: getSkillDir2,
|
|
2180
|
-
readSkillContent(name) {
|
|
2181
|
-
const skillPath = library.getSkillPath(name);
|
|
2182
|
-
if (!existsSync10(skillPath)) {
|
|
2183
|
-
throw new Error(`Skill content not found: ${skillPath}`);
|
|
2184
|
-
}
|
|
2185
|
-
return readFileSync(skillPath, "utf-8");
|
|
2186
|
-
},
|
|
2187
|
-
getCoreSkills() {
|
|
2188
|
-
return entries.filter((e) => e.core);
|
|
2189
|
-
},
|
|
2190
|
-
getSkillsByCategory(category) {
|
|
2191
|
-
return entries.filter((e) => e.category === category);
|
|
2192
|
-
},
|
|
2193
|
-
getSkillDependencies(name) {
|
|
2194
|
-
return skillMap.get(name)?.dependencies ?? [];
|
|
2195
|
-
},
|
|
2196
|
-
resolveDependencyTree(names) {
|
|
2197
|
-
return resolveDeps(names);
|
|
2198
|
-
},
|
|
2199
|
-
listProfiles() {
|
|
2200
|
-
return [...profiles.keys()];
|
|
2201
|
-
},
|
|
2202
|
-
getProfile(name) {
|
|
2203
|
-
return profiles.get(name);
|
|
2204
|
-
},
|
|
2205
|
-
resolveProfile(name) {
|
|
2206
|
-
return resolveProfileByName(name);
|
|
2207
|
-
},
|
|
2208
|
-
listSharedResources() {
|
|
2209
|
-
return discoverFiles(join4(root, "skills", "_shared"), ".md");
|
|
2210
|
-
},
|
|
2211
|
-
getSharedResourcePath(name) {
|
|
2212
|
-
const resourcePath = join4(root, "skills", "_shared", `${name}.md`);
|
|
2213
|
-
return existsSync10(resourcePath) ? resourcePath : void 0;
|
|
2214
|
-
},
|
|
2215
|
-
readSharedResource(name) {
|
|
2216
|
-
const resourcePath = library.getSharedResourcePath(name);
|
|
2217
|
-
if (!resourcePath) return void 0;
|
|
2218
|
-
return readFileSync(resourcePath, "utf-8");
|
|
2219
|
-
},
|
|
2220
|
-
listProtocols() {
|
|
2221
|
-
const rootProtocols = discoverFiles(join4(root, "protocols"), ".md");
|
|
2222
|
-
if (rootProtocols.length > 0) return rootProtocols;
|
|
2223
|
-
return discoverFiles(join4(root, "skills", "protocols"), ".md");
|
|
2224
|
-
},
|
|
2225
|
-
getProtocolPath(name) {
|
|
2226
|
-
const rootPath = join4(root, "protocols", `${name}.md`);
|
|
2227
|
-
if (existsSync10(rootPath)) return rootPath;
|
|
2228
|
-
const skillsPath = join4(root, "skills", "protocols", `${name}.md`);
|
|
2229
|
-
return existsSync10(skillsPath) ? skillsPath : void 0;
|
|
2230
|
-
},
|
|
2231
|
-
readProtocol(name) {
|
|
2232
|
-
const protocolPath = library.getProtocolPath(name);
|
|
2233
|
-
if (!protocolPath) return void 0;
|
|
2234
|
-
return readFileSync(protocolPath, "utf-8");
|
|
2235
|
-
},
|
|
2236
|
-
validateSkillFrontmatter(name) {
|
|
2237
|
-
const entry = skillMap.get(name);
|
|
2238
|
-
if (!entry) {
|
|
2239
|
-
return {
|
|
2240
|
-
valid: false,
|
|
2241
|
-
issues: [{ level: "error", field: "name", message: `Skill not found: ${name}` }]
|
|
2242
|
-
};
|
|
2243
|
-
}
|
|
2244
|
-
const issues = [];
|
|
2245
|
-
if (!entry.name) {
|
|
2246
|
-
issues.push({ level: "error", field: "name", message: "Missing name" });
|
|
2247
|
-
}
|
|
2248
|
-
if (!entry.description) {
|
|
2249
|
-
issues.push({ level: "error", field: "description", message: "Missing description" });
|
|
2250
|
-
}
|
|
2251
|
-
if (!entry.version) {
|
|
2252
|
-
issues.push({ level: "warn", field: "version", message: "Missing version" });
|
|
2253
|
-
}
|
|
2254
|
-
const skillPath = join4(root, entry.path);
|
|
2255
|
-
if (!existsSync10(skillPath)) {
|
|
2256
|
-
issues.push({ level: "error", field: "path", message: `SKILL.md not found at ${entry.path}` });
|
|
2257
|
-
}
|
|
2258
|
-
return {
|
|
2259
|
-
valid: !issues.some((i) => i.level === "error"),
|
|
2260
|
-
issues
|
|
2261
|
-
};
|
|
2262
|
-
},
|
|
2263
|
-
validateAll() {
|
|
2264
|
-
const results = /* @__PURE__ */ new Map();
|
|
2265
|
-
for (const entry of entries) {
|
|
2266
|
-
results.set(entry.name, library.validateSkillFrontmatter(entry.name));
|
|
2267
|
-
}
|
|
2268
|
-
return results;
|
|
2269
|
-
},
|
|
2270
|
-
getDispatchMatrix() {
|
|
2271
|
-
return manifest.dispatch_matrix;
|
|
2272
|
-
}
|
|
2273
|
-
};
|
|
2274
|
-
return library;
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
// src/core/skills/catalog.ts
|
|
2278
|
-
var catalog_exports = {};
|
|
2279
|
-
__export(catalog_exports, {
|
|
2280
|
-
clearRegisteredLibrary: () => clearRegisteredLibrary,
|
|
2281
|
-
getCoreSkills: () => getCoreSkills,
|
|
2282
|
-
getDispatchMatrix: () => getDispatchMatrix,
|
|
2283
|
-
getLibraryRoot: () => getLibraryRoot,
|
|
2284
|
-
getManifest: () => getManifest,
|
|
2285
|
-
getProfile: () => getProfile,
|
|
2286
|
-
getProtocolPath: () => getProtocolPath,
|
|
2287
|
-
getSharedResourcePath: () => getSharedResourcePath,
|
|
2288
|
-
getSkill: () => getSkill,
|
|
2289
|
-
getSkillDependencies: () => getSkillDependencies,
|
|
2290
|
-
getSkillDir: () => getSkillDir,
|
|
2291
|
-
getSkillPath: () => getSkillPath,
|
|
2292
|
-
getSkills: () => getSkills,
|
|
2293
|
-
getSkillsByCategory: () => getSkillsByCategory,
|
|
2294
|
-
getVersion: () => getVersion,
|
|
2295
|
-
isCatalogAvailable: () => isCatalogAvailable,
|
|
2296
|
-
listProfiles: () => listProfiles,
|
|
2297
|
-
listProtocols: () => listProtocols,
|
|
2298
|
-
listSharedResources: () => listSharedResources,
|
|
2299
|
-
listSkills: () => listSkills,
|
|
2300
|
-
readProtocol: () => readProtocol,
|
|
2301
|
-
readSharedResource: () => readSharedResource,
|
|
2302
|
-
readSkillContent: () => readSkillContent,
|
|
2303
|
-
registerSkillLibrary: () => registerSkillLibrary,
|
|
2304
|
-
registerSkillLibraryFromPath: () => registerSkillLibraryFromPath,
|
|
2305
|
-
resolveDependencyTree: () => resolveDependencyTree,
|
|
2306
|
-
resolveProfile: () => resolveProfile,
|
|
2307
|
-
validateAll: () => validateAll,
|
|
2308
|
-
validateSkillFrontmatter: () => validateSkillFrontmatter
|
|
2309
|
-
});
|
|
2310
|
-
import { existsSync as existsSync11 } from "fs";
|
|
2311
|
-
import { join as join5 } from "path";
|
|
2312
|
-
var _library = null;
|
|
2313
|
-
function registerSkillLibrary(library) {
|
|
2314
|
-
_library = library;
|
|
2315
|
-
}
|
|
2316
|
-
function registerSkillLibraryFromPath(root) {
|
|
2317
|
-
const indexPath = join5(root, "index.js");
|
|
2318
|
-
if (existsSync11(indexPath)) {
|
|
2319
|
-
_library = loadLibraryFromModule(root);
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
_library = buildLibraryFromFiles(root);
|
|
2323
|
-
}
|
|
2324
|
-
function clearRegisteredLibrary() {
|
|
2325
|
-
_library = null;
|
|
2326
|
-
}
|
|
2327
|
-
function discoverLibrary() {
|
|
2328
|
-
const envPath = process.env["CAAMP_SKILL_LIBRARY"];
|
|
2329
|
-
if (envPath && existsSync11(envPath)) {
|
|
2330
|
-
try {
|
|
2331
|
-
const indexPath = join5(envPath, "index.js");
|
|
2332
|
-
if (existsSync11(indexPath)) {
|
|
2333
|
-
return loadLibraryFromModule(envPath);
|
|
2334
|
-
}
|
|
2335
|
-
if (existsSync11(join5(envPath, "skills.json"))) {
|
|
2336
|
-
return buildLibraryFromFiles(envPath);
|
|
2337
|
-
}
|
|
2338
|
-
} catch {
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
return null;
|
|
2342
|
-
}
|
|
2343
|
-
function getLibrary() {
|
|
2344
|
-
if (!_library) {
|
|
2345
|
-
const discovered = discoverLibrary();
|
|
2346
|
-
if (discovered) {
|
|
2347
|
-
_library = discovered;
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
if (!_library) {
|
|
2351
|
-
throw new Error(
|
|
2352
|
-
"No skill library registered. Register one with registerSkillLibraryFromPath() or set the CAAMP_SKILL_LIBRARY environment variable."
|
|
2353
|
-
);
|
|
2354
|
-
}
|
|
2355
|
-
return _library;
|
|
2356
|
-
}
|
|
2357
|
-
function isCatalogAvailable() {
|
|
2358
|
-
try {
|
|
2359
|
-
getLibrary();
|
|
2360
|
-
return true;
|
|
2361
|
-
} catch {
|
|
2362
|
-
return false;
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
function getSkills() {
|
|
2366
|
-
return getLibrary().skills;
|
|
2367
|
-
}
|
|
2368
|
-
function getManifest() {
|
|
2369
|
-
return getLibrary().manifest;
|
|
2370
|
-
}
|
|
2371
|
-
function listSkills() {
|
|
2372
|
-
return getLibrary().listSkills();
|
|
2373
|
-
}
|
|
2374
|
-
function getSkill(name) {
|
|
2375
|
-
return getLibrary().getSkill(name);
|
|
2376
|
-
}
|
|
2377
|
-
function getSkillPath(name) {
|
|
2378
|
-
return getLibrary().getSkillPath(name);
|
|
2379
|
-
}
|
|
2380
|
-
function getSkillDir(name) {
|
|
2381
|
-
return getLibrary().getSkillDir(name);
|
|
2382
|
-
}
|
|
2383
|
-
function readSkillContent(name) {
|
|
2384
|
-
return getLibrary().readSkillContent(name);
|
|
2385
|
-
}
|
|
2386
|
-
function getCoreSkills() {
|
|
2387
|
-
return getLibrary().getCoreSkills();
|
|
2388
|
-
}
|
|
2389
|
-
function getSkillsByCategory(category) {
|
|
2390
|
-
return getLibrary().getSkillsByCategory(category);
|
|
2391
|
-
}
|
|
2392
|
-
function getSkillDependencies(name) {
|
|
2393
|
-
return getLibrary().getSkillDependencies(name);
|
|
2394
|
-
}
|
|
2395
|
-
function resolveDependencyTree(names) {
|
|
2396
|
-
return getLibrary().resolveDependencyTree(names);
|
|
2397
|
-
}
|
|
2398
|
-
function listProfiles() {
|
|
2399
|
-
return getLibrary().listProfiles();
|
|
2400
|
-
}
|
|
2401
|
-
function getProfile(name) {
|
|
2402
|
-
return getLibrary().getProfile(name);
|
|
2403
|
-
}
|
|
2404
|
-
function resolveProfile(name) {
|
|
2405
|
-
return getLibrary().resolveProfile(name);
|
|
2406
|
-
}
|
|
2407
|
-
function listSharedResources() {
|
|
2408
|
-
return getLibrary().listSharedResources();
|
|
2409
|
-
}
|
|
2410
|
-
function getSharedResourcePath(name) {
|
|
2411
|
-
return getLibrary().getSharedResourcePath(name);
|
|
2412
|
-
}
|
|
2413
|
-
function readSharedResource(name) {
|
|
2414
|
-
return getLibrary().readSharedResource(name);
|
|
2415
|
-
}
|
|
2416
|
-
function listProtocols() {
|
|
2417
|
-
return getLibrary().listProtocols();
|
|
2418
|
-
}
|
|
2419
|
-
function getProtocolPath(name) {
|
|
2420
|
-
return getLibrary().getProtocolPath(name);
|
|
2421
|
-
}
|
|
2422
|
-
function readProtocol(name) {
|
|
2423
|
-
return getLibrary().readProtocol(name);
|
|
2424
|
-
}
|
|
2425
|
-
function validateSkillFrontmatter(name) {
|
|
2426
|
-
return getLibrary().validateSkillFrontmatter(name);
|
|
2427
|
-
}
|
|
2428
|
-
function validateAll() {
|
|
2429
|
-
return getLibrary().validateAll();
|
|
2430
|
-
}
|
|
2431
|
-
function getDispatchMatrix() {
|
|
2432
|
-
return getLibrary().getDispatchMatrix();
|
|
2433
|
-
}
|
|
2434
|
-
function getVersion() {
|
|
2435
|
-
return getLibrary().version;
|
|
2436
|
-
}
|
|
2437
|
-
function getLibraryRoot() {
|
|
2438
|
-
return getLibrary().libraryRoot;
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
// src/core/skills/discovery.ts
|
|
2442
|
-
import { readFile as readFile6, readdir } from "fs/promises";
|
|
2443
|
-
import { existsSync as existsSync12 } from "fs";
|
|
2444
|
-
import { join as join6 } from "path";
|
|
2445
|
-
import matter from "gray-matter";
|
|
2446
|
-
async function parseSkillFile(filePath) {
|
|
2447
|
-
try {
|
|
2448
|
-
const content = await readFile6(filePath, "utf-8");
|
|
2449
|
-
const { data } = matter(content);
|
|
2450
|
-
if (!data.name || !data.description) {
|
|
2451
|
-
return null;
|
|
2452
|
-
}
|
|
2453
|
-
const allowedTools = data["allowed-tools"] ?? data.allowedTools;
|
|
2454
|
-
return {
|
|
2455
|
-
name: String(data.name),
|
|
2456
|
-
description: String(data.description),
|
|
2457
|
-
license: data.license ? String(data.license) : void 0,
|
|
2458
|
-
compatibility: data.compatibility ? String(data.compatibility) : void 0,
|
|
2459
|
-
metadata: data.metadata,
|
|
2460
|
-
allowedTools: typeof allowedTools === "string" ? allowedTools.split(/\s+/) : Array.isArray(allowedTools) ? allowedTools.map(String) : void 0,
|
|
2461
|
-
version: data.version ? String(data.version) : void 0
|
|
2462
|
-
};
|
|
2463
|
-
} catch {
|
|
2464
|
-
return null;
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
async function discoverSkill(skillDir) {
|
|
2468
|
-
const skillFile = join6(skillDir, "SKILL.md");
|
|
2469
|
-
if (!existsSync12(skillFile)) return null;
|
|
2470
|
-
const metadata = await parseSkillFile(skillFile);
|
|
2471
|
-
if (!metadata) return null;
|
|
2472
|
-
return {
|
|
2473
|
-
name: metadata.name,
|
|
2474
|
-
scopedName: metadata.name,
|
|
2475
|
-
path: skillDir,
|
|
2476
|
-
metadata
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
async function discoverSkills(rootDir) {
|
|
2480
|
-
if (!existsSync12(rootDir)) return [];
|
|
2481
|
-
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
2482
|
-
const skills = [];
|
|
2483
|
-
for (const entry of entries) {
|
|
2484
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
2485
|
-
const skillDir = join6(rootDir, entry.name);
|
|
2486
|
-
const skill = await discoverSkill(skillDir);
|
|
2487
|
-
if (skill) {
|
|
2488
|
-
skills.push(skill);
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
return skills;
|
|
2492
|
-
}
|
|
2493
|
-
async function discoverSkillsMulti(dirs) {
|
|
2494
|
-
const all = [];
|
|
2495
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2496
|
-
for (const dir of dirs) {
|
|
2497
|
-
const skills = await discoverSkills(dir);
|
|
2498
|
-
for (const skill of skills) {
|
|
2499
|
-
if (!seen.has(skill.name)) {
|
|
2500
|
-
seen.add(skill.name);
|
|
2501
|
-
all.push(skill);
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
return all;
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
|
-
// src/core/skills/lock.ts
|
|
2509
|
-
import { simpleGit } from "simple-git";
|
|
2510
|
-
import { execFile } from "child_process";
|
|
2511
|
-
import { promisify } from "util";
|
|
2512
|
-
var execFileAsync = promisify(execFile);
|
|
2513
|
-
async function recordSkillInstall(skillName, scopedName, source, sourceType, agents, canonicalPath, isGlobal, projectDir, version) {
|
|
2514
|
-
await updateLockFile((lock) => {
|
|
2515
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2516
|
-
const existing = lock.skills[skillName];
|
|
2517
|
-
lock.skills[skillName] = {
|
|
2518
|
-
name: skillName,
|
|
2519
|
-
scopedName: existing?.scopedName ?? scopedName,
|
|
2520
|
-
source: existing?.source ?? source,
|
|
2521
|
-
sourceType: existing?.sourceType ?? sourceType,
|
|
2522
|
-
version: version ?? existing?.version,
|
|
2523
|
-
installedAt: existing?.installedAt ?? now,
|
|
2524
|
-
updatedAt: now,
|
|
2525
|
-
agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
|
|
2526
|
-
canonicalPath,
|
|
2527
|
-
isGlobal: existing?.isGlobal ?? isGlobal,
|
|
2528
|
-
projectDir: existing?.projectDir ?? projectDir
|
|
2529
|
-
};
|
|
2530
|
-
});
|
|
2531
|
-
}
|
|
2532
|
-
async function removeSkillFromLock(skillName) {
|
|
2533
|
-
let removed = false;
|
|
2534
|
-
await updateLockFile((lock) => {
|
|
2535
|
-
if (!(skillName in lock.skills)) return;
|
|
2536
|
-
delete lock.skills[skillName];
|
|
2537
|
-
removed = true;
|
|
2538
|
-
});
|
|
2539
|
-
return removed;
|
|
2540
|
-
}
|
|
2541
|
-
async function getTrackedSkills() {
|
|
2542
|
-
const lock = await readLockFile();
|
|
2543
|
-
return lock.skills;
|
|
2544
|
-
}
|
|
2545
|
-
async function fetchLatestSha(repoUrl, ref) {
|
|
2546
|
-
try {
|
|
2547
|
-
const git = simpleGit();
|
|
2548
|
-
const target = ref ?? "HEAD";
|
|
2549
|
-
const args = target === "HEAD" ? [repoUrl, "HEAD"] : ["--refs", repoUrl, target];
|
|
2550
|
-
const result = await git.listRemote(args);
|
|
2551
|
-
const firstLine = result.trim().split("\n")[0];
|
|
2552
|
-
if (!firstLine) return null;
|
|
2553
|
-
const sha = firstLine.split(" ")[0];
|
|
2554
|
-
return sha ?? null;
|
|
2555
|
-
} catch {
|
|
2556
|
-
return null;
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
async function fetchLatestPackageVersion(packageName) {
|
|
2560
|
-
try {
|
|
2561
|
-
const { stdout } = await execFileAsync("npm", ["view", packageName, "version"]);
|
|
2562
|
-
return stdout.trim() || null;
|
|
2563
|
-
} catch {
|
|
2564
|
-
return null;
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
async function checkSkillUpdate(skillName) {
|
|
2568
|
-
const lock = await readLockFile();
|
|
2569
|
-
const entry = lock.skills[skillName];
|
|
2570
|
-
if (!entry) {
|
|
2571
|
-
return { hasUpdate: false, status: "unknown" };
|
|
2572
|
-
}
|
|
2573
|
-
if (entry.sourceType !== "github" && entry.sourceType !== "gitlab" && entry.sourceType !== "library") {
|
|
2574
|
-
return {
|
|
2575
|
-
hasUpdate: false,
|
|
2576
|
-
currentVersion: entry.version,
|
|
2577
|
-
status: "unknown"
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
const parsed = parseSource(entry.source);
|
|
2581
|
-
if (!parsed.owner) {
|
|
2582
|
-
return {
|
|
2583
|
-
hasUpdate: false,
|
|
2584
|
-
currentVersion: entry.version,
|
|
2585
|
-
status: "unknown"
|
|
2586
|
-
};
|
|
2587
|
-
}
|
|
2588
|
-
if (entry.sourceType === "library") {
|
|
2589
|
-
const packageName = parsed.owner;
|
|
2590
|
-
const latestVersion = await fetchLatestPackageVersion(packageName);
|
|
2591
|
-
if (!latestVersion) {
|
|
2592
|
-
return {
|
|
2593
|
-
hasUpdate: false,
|
|
2594
|
-
currentVersion: entry.version,
|
|
2595
|
-
status: "unknown"
|
|
2596
|
-
};
|
|
2597
|
-
}
|
|
2598
|
-
const currentVersion2 = entry.version;
|
|
2599
|
-
const hasUpdate2 = !currentVersion2 || currentVersion2 !== latestVersion;
|
|
2600
|
-
return {
|
|
2601
|
-
hasUpdate: hasUpdate2,
|
|
2602
|
-
currentVersion: currentVersion2 ?? "unknown",
|
|
2603
|
-
latestVersion,
|
|
2604
|
-
status: hasUpdate2 ? "update-available" : "up-to-date"
|
|
2605
|
-
};
|
|
2606
|
-
}
|
|
2607
|
-
if (!parsed.repo) {
|
|
2608
|
-
return {
|
|
2609
|
-
hasUpdate: false,
|
|
2610
|
-
currentVersion: entry.version,
|
|
2611
|
-
status: "unknown"
|
|
2612
|
-
};
|
|
2613
|
-
}
|
|
2614
|
-
const host = parsed.type === "gitlab" ? "gitlab.com" : "github.com";
|
|
2615
|
-
const repoUrl = `https://${host}/${parsed.owner}/${parsed.repo}.git`;
|
|
2616
|
-
const latestSha = await fetchLatestSha(repoUrl, parsed.ref);
|
|
2617
|
-
if (!latestSha) {
|
|
2618
|
-
return {
|
|
2619
|
-
hasUpdate: false,
|
|
2620
|
-
currentVersion: entry.version,
|
|
2621
|
-
status: "unknown"
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
const currentVersion = entry.version;
|
|
2625
|
-
const hasUpdate = !currentVersion || !latestSha.startsWith(currentVersion.slice(0, 7));
|
|
2626
|
-
return {
|
|
2627
|
-
hasUpdate,
|
|
2628
|
-
currentVersion: currentVersion ?? "unknown",
|
|
2629
|
-
latestVersion: latestSha.slice(0, 12),
|
|
2630
|
-
status: hasUpdate ? "update-available" : "up-to-date"
|
|
2631
|
-
};
|
|
2632
|
-
}
|
|
2633
|
-
async function checkAllSkillUpdates() {
|
|
2634
|
-
const lock = await readLockFile();
|
|
2635
|
-
const skillNames = Object.keys(lock.skills);
|
|
2636
|
-
const results = {};
|
|
2637
|
-
await Promise.all(skillNames.map(async (name) => {
|
|
2638
|
-
results[name] = await checkSkillUpdate(name);
|
|
2639
|
-
}));
|
|
2640
|
-
return results;
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// src/core/skills/recommendation.ts
|
|
2644
|
-
var RECOMMENDATION_ERROR_CODES = {
|
|
2645
|
-
QUERY_INVALID: "E_SKILLS_QUERY_INVALID",
|
|
2646
|
-
NO_MATCHES: "E_SKILLS_NO_MATCHES",
|
|
2647
|
-
SOURCE_UNAVAILABLE: "E_SKILLS_SOURCE_UNAVAILABLE",
|
|
2648
|
-
CRITERIA_CONFLICT: "E_SKILLS_CRITERIA_CONFLICT"
|
|
2649
|
-
};
|
|
2650
|
-
var DEFAULT_WEIGHTS = {
|
|
2651
|
-
mustHaveMatch: 10,
|
|
2652
|
-
preferMatch: 4,
|
|
2653
|
-
queryTokenMatch: 3,
|
|
2654
|
-
starsFactor: 2,
|
|
2655
|
-
metadataBoost: 2,
|
|
2656
|
-
modernMarkerBoost: 3,
|
|
2657
|
-
legacyMarkerPenalty: 3,
|
|
2658
|
-
excludePenalty: 25,
|
|
2659
|
-
missingMustHavePenalty: 20
|
|
2660
|
-
};
|
|
2661
|
-
var DEFAULT_MODERN_MARKERS = ["svelte 5", "runes", "lafs", "slsa", "drizzle", "better-auth"];
|
|
2662
|
-
var DEFAULT_LEGACY_MARKERS = ["svelte 3", "jquery", "bower", "legacy", "book.json", "gitbook-cli"];
|
|
2663
|
-
function tokenizeCriteriaValue(value) {
|
|
2664
|
-
return value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
2665
|
-
}
|
|
2666
|
-
function normalizeList(value) {
|
|
2667
|
-
if (value === void 0) return [];
|
|
2668
|
-
if (!(typeof value === "string" || Array.isArray(value))) return [];
|
|
2669
|
-
const source = Array.isArray(value) ? value : [value];
|
|
2670
|
-
const flattened = source.flatMap((item) => typeof item === "string" ? tokenizeCriteriaValue(item) : []);
|
|
2671
|
-
return Array.from(new Set(flattened)).sort((a, b) => a.localeCompare(b));
|
|
2672
|
-
}
|
|
2673
|
-
function hasAnyCriteriaInput(input) {
|
|
2674
|
-
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
2675
|
-
if (query.length > 0) return true;
|
|
2676
|
-
const lists = [input.mustHave, input.prefer, input.exclude];
|
|
2677
|
-
return lists.some((list) => normalizeList(list).length > 0);
|
|
2678
|
-
}
|
|
2679
|
-
function validateRecommendationCriteria(input) {
|
|
2680
|
-
const issues = [];
|
|
2681
|
-
if (input.query !== void 0 && typeof input.query !== "string") {
|
|
2682
|
-
issues.push({
|
|
2683
|
-
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2684
|
-
field: "query",
|
|
2685
|
-
message: "query must be a string"
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
if (input.mustHave !== void 0 && !(typeof input.mustHave === "string" || Array.isArray(input.mustHave))) {
|
|
2689
|
-
issues.push({
|
|
2690
|
-
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2691
|
-
field: "mustHave",
|
|
2692
|
-
message: "mustHave must be a string or string[]"
|
|
2693
|
-
});
|
|
2694
|
-
}
|
|
2695
|
-
if (input.prefer !== void 0 && !(typeof input.prefer === "string" || Array.isArray(input.prefer))) {
|
|
2696
|
-
issues.push({
|
|
2697
|
-
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2698
|
-
field: "prefer",
|
|
2699
|
-
message: "prefer must be a string or string[]"
|
|
2700
|
-
});
|
|
2701
|
-
}
|
|
2702
|
-
if (input.exclude !== void 0 && !(typeof input.exclude === "string" || Array.isArray(input.exclude))) {
|
|
2703
|
-
issues.push({
|
|
2704
|
-
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2705
|
-
field: "exclude",
|
|
2706
|
-
message: "exclude must be a string or string[]"
|
|
2707
|
-
});
|
|
2708
|
-
}
|
|
2709
|
-
const mustHave = normalizeList(input.mustHave);
|
|
2710
|
-
const prefer = normalizeList(input.prefer);
|
|
2711
|
-
const exclude = normalizeList(input.exclude);
|
|
2712
|
-
const conflict = mustHave.some((term) => exclude.includes(term)) || prefer.some((term) => exclude.includes(term));
|
|
2713
|
-
if (conflict) {
|
|
2714
|
-
issues.push({
|
|
2715
|
-
code: RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
2716
|
-
field: "exclude",
|
|
2717
|
-
message: "criteria terms cannot appear in both prefer/must-have and exclude"
|
|
2718
|
-
});
|
|
2719
|
-
}
|
|
2720
|
-
if (issues.length === 0 && !hasAnyCriteriaInput(input)) {
|
|
2721
|
-
issues.push({
|
|
2722
|
-
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2723
|
-
field: "query",
|
|
2724
|
-
message: "at least one criteria value is required"
|
|
2725
|
-
});
|
|
2726
|
-
}
|
|
2727
|
-
return {
|
|
2728
|
-
valid: issues.length === 0,
|
|
2729
|
-
issues
|
|
2730
|
-
};
|
|
2731
|
-
}
|
|
2732
|
-
function normalizeRecommendationCriteria(input) {
|
|
2733
|
-
const query = (input.query ?? "").trim().toLowerCase();
|
|
2734
|
-
return {
|
|
2735
|
-
query,
|
|
2736
|
-
queryTokens: query ? Array.from(new Set(tokenizeCriteriaValue(query.replace(/\s+/g, ",")))).sort((a, b) => a.localeCompare(b)) : [],
|
|
2737
|
-
mustHave: normalizeList(input.mustHave),
|
|
2738
|
-
prefer: normalizeList(input.prefer),
|
|
2739
|
-
exclude: normalizeList(input.exclude)
|
|
2740
|
-
};
|
|
2741
|
-
}
|
|
2742
|
-
function countMatches(haystack, needles) {
|
|
2743
|
-
let count = 0;
|
|
2744
|
-
for (const needle of needles) {
|
|
2745
|
-
if (haystack.includes(needle)) {
|
|
2746
|
-
count += 1;
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
return count;
|
|
2750
|
-
}
|
|
2751
|
-
function clampScore(value) {
|
|
2752
|
-
return Number(value.toFixed(6));
|
|
2753
|
-
}
|
|
2754
|
-
function buildSearchText(skill) {
|
|
2755
|
-
return `${skill.name} ${skill.scopedName} ${skill.description} ${skill.author}`.toLowerCase();
|
|
2756
|
-
}
|
|
2757
|
-
function scoreSkillRecommendation(skill, criteria, options = {}) {
|
|
2758
|
-
const weights = { ...DEFAULT_WEIGHTS, ...options.weights };
|
|
2759
|
-
const modernMarkers = (options.modernMarkers ?? DEFAULT_MODERN_MARKERS).map((marker) => marker.toLowerCase());
|
|
2760
|
-
const legacyMarkers = (options.legacyMarkers ?? DEFAULT_LEGACY_MARKERS).map((marker) => marker.toLowerCase());
|
|
2761
|
-
const text = buildSearchText(skill);
|
|
2762
|
-
const reasons = [];
|
|
2763
|
-
const tradeoffs = [];
|
|
2764
|
-
const mustHaveMatches = countMatches(text, criteria.mustHave);
|
|
2765
|
-
const missingMustHave = Math.max(criteria.mustHave.length - mustHaveMatches, 0);
|
|
2766
|
-
const preferMatches = countMatches(text, criteria.prefer);
|
|
2767
|
-
const queryMatches = countMatches(text, criteria.queryTokens);
|
|
2768
|
-
const excludeMatches = countMatches(text, criteria.exclude);
|
|
2769
|
-
const modernMatches = countMatches(text, modernMarkers);
|
|
2770
|
-
const legacyMatches = countMatches(text, legacyMarkers);
|
|
2771
|
-
const metadataSignal = skill.description.trim().length >= 80 ? 1 : 0;
|
|
2772
|
-
const starsSignal = Math.log10(skill.stars + 1);
|
|
2773
|
-
const sourceConfidence = skill.source === "agentskills.in" ? 1 : skill.source === "skills.sh" ? 0.8 : 0.6;
|
|
2774
|
-
const mustHaveScore = mustHaveMatches * weights.mustHaveMatch - missingMustHave * weights.missingMustHavePenalty;
|
|
2775
|
-
const preferScore = preferMatches * weights.preferMatch;
|
|
2776
|
-
const queryScore = queryMatches * weights.queryTokenMatch;
|
|
2777
|
-
const starsScore = starsSignal * weights.starsFactor;
|
|
2778
|
-
const metadataScore = (metadataSignal + sourceConfidence) * weights.metadataBoost;
|
|
2779
|
-
const modernityScore = modernMatches * weights.modernMarkerBoost - legacyMatches * weights.legacyMarkerPenalty;
|
|
2780
|
-
const exclusionPenalty = excludeMatches * weights.excludePenalty;
|
|
2781
|
-
const hasGitbookTopic = text.includes("gitbook");
|
|
2782
|
-
const hasGitSync = text.includes("git sync") || text.includes("git") && text.includes("sync");
|
|
2783
|
-
const hasApiWorkflow = text.includes("api") && (text.includes("workflow") || text.includes("sync"));
|
|
2784
|
-
const hasLegacyCli = text.includes("gitbook-cli") || text.includes("book.json");
|
|
2785
|
-
const topicScore = (hasGitbookTopic ? 3 : 0) + (hasGitSync ? 2 : 0) + (hasApiWorkflow ? 2 : 0) - (hasLegacyCli ? 4 : 0);
|
|
2786
|
-
const total = clampScore(
|
|
2787
|
-
mustHaveScore + preferScore + queryScore + starsScore + metadataScore + modernityScore + topicScore - exclusionPenalty
|
|
2788
|
-
);
|
|
2789
|
-
if (hasGitbookTopic) reasons.push({ code: "MATCH_TOPIC_GITBOOK" });
|
|
2790
|
-
if (hasGitSync) reasons.push({ code: "HAS_GIT_SYNC" });
|
|
2791
|
-
if (hasApiWorkflow) reasons.push({ code: "HAS_API_WORKFLOW" });
|
|
2792
|
-
if (hasLegacyCli) reasons.push({ code: "PENALTY_LEGACY_CLI" });
|
|
2793
|
-
if (mustHaveMatches > 0) reasons.push({ code: "MUST_HAVE_MATCH", detail: String(mustHaveMatches) });
|
|
2794
|
-
if (missingMustHave > 0) reasons.push({ code: "MISSING_MUST_HAVE", detail: String(missingMustHave) });
|
|
2795
|
-
if (preferMatches > 0) reasons.push({ code: "PREFER_MATCH", detail: String(preferMatches) });
|
|
2796
|
-
if (queryMatches > 0) reasons.push({ code: "QUERY_MATCH", detail: String(queryMatches) });
|
|
2797
|
-
if (starsSignal > 0) reasons.push({ code: "STAR_SIGNAL" });
|
|
2798
|
-
if (metadataSignal > 0) reasons.push({ code: "METADATA_SIGNAL" });
|
|
2799
|
-
if (modernMatches > 0) reasons.push({ code: "MODERN_MARKER", detail: String(modernMatches) });
|
|
2800
|
-
if (legacyMatches > 0) reasons.push({ code: "LEGACY_MARKER", detail: String(legacyMatches) });
|
|
2801
|
-
if (excludeMatches > 0) reasons.push({ code: "EXCLUDE_MATCH", detail: String(excludeMatches) });
|
|
2802
|
-
if (missingMustHave > 0) tradeoffs.push("Missing one or more required criteria terms.");
|
|
2803
|
-
if (excludeMatches > 0) tradeoffs.push("Matches one or more excluded terms.");
|
|
2804
|
-
if (skill.stars < 10) tradeoffs.push("Low quality signal from repository stars.");
|
|
2805
|
-
if (hasLegacyCli) tradeoffs.push("Contains legacy GitBook CLI markers.");
|
|
2806
|
-
const result = {
|
|
2807
|
-
skill,
|
|
2808
|
-
score: total,
|
|
2809
|
-
reasons,
|
|
2810
|
-
tradeoffs,
|
|
2811
|
-
excluded: excludeMatches > 0
|
|
2812
|
-
};
|
|
2813
|
-
if (options.includeDetails) {
|
|
2814
|
-
result.breakdown = {
|
|
2815
|
-
mustHave: clampScore(mustHaveScore),
|
|
2816
|
-
prefer: clampScore(preferScore),
|
|
2817
|
-
query: clampScore(queryScore),
|
|
2818
|
-
stars: clampScore(starsScore),
|
|
2819
|
-
metadata: clampScore(metadataScore),
|
|
2820
|
-
modernity: clampScore(modernityScore),
|
|
2821
|
-
exclusionPenalty: clampScore(exclusionPenalty),
|
|
2822
|
-
total
|
|
2823
|
-
};
|
|
2824
|
-
}
|
|
2825
|
-
return result;
|
|
2826
|
-
}
|
|
2827
|
-
function recommendSkills(skills, criteriaInput, options = {}) {
|
|
2828
|
-
const validation = validateRecommendationCriteria(criteriaInput);
|
|
2829
|
-
if (!validation.valid) {
|
|
2830
|
-
const first = validation.issues[0];
|
|
2831
|
-
const error = new Error(first?.message ?? "Invalid recommendation criteria");
|
|
2832
|
-
error.code = first?.code;
|
|
2833
|
-
error.issues = validation.issues;
|
|
2834
|
-
throw error;
|
|
2835
|
-
}
|
|
2836
|
-
const criteria = normalizeRecommendationCriteria(criteriaInput);
|
|
2837
|
-
const ranking = skills.map((skill) => scoreSkillRecommendation(skill, criteria, options)).sort((a, b) => {
|
|
2838
|
-
if (b.score !== a.score) return b.score - a.score;
|
|
2839
|
-
if (b.skill.stars !== a.skill.stars) return b.skill.stars - a.skill.stars;
|
|
2840
|
-
return a.skill.scopedName.localeCompare(b.skill.scopedName);
|
|
2841
|
-
});
|
|
2842
|
-
return {
|
|
2843
|
-
criteria,
|
|
2844
|
-
ranking: typeof options.top === "number" ? ranking.slice(0, Math.max(0, options.top)) : ranking
|
|
2845
|
-
};
|
|
2846
|
-
}
|
|
2847
|
-
var rankSkills = recommendSkills;
|
|
2848
|
-
|
|
2849
|
-
// src/core/skills/recommendation-api.ts
|
|
2850
|
-
function formatSkillRecommendations(result, opts) {
|
|
2851
|
-
const top = result.ranking;
|
|
2852
|
-
if (opts.mode === "human") {
|
|
2853
|
-
if (top.length === 0) return "No recommendations found.";
|
|
2854
|
-
const lines = ["Recommended skills:", ""];
|
|
2855
|
-
for (const [index, entry] of top.entries()) {
|
|
2856
|
-
const marker = index === 0 ? " (Recommended)" : "";
|
|
2857
|
-
lines.push(`${index + 1}) ${entry.skill.scopedName}${marker}`);
|
|
2858
|
-
lines.push(` why: ${entry.reasons.map((reason) => reason.code).join(", ") || "score-based match"}`);
|
|
2859
|
-
lines.push(` tradeoff: ${entry.tradeoffs[0] ?? "none"}`);
|
|
2860
|
-
}
|
|
2861
|
-
lines.push("");
|
|
2862
|
-
lines.push(`CHOOSE: ${top.map((_, index) => index + 1).join(",")}`);
|
|
2863
|
-
return lines.join("\n");
|
|
2864
|
-
}
|
|
2865
|
-
const options = top.map((entry, index) => ({
|
|
2866
|
-
rank: index + 1,
|
|
2867
|
-
scopedName: entry.skill.scopedName,
|
|
2868
|
-
score: entry.score,
|
|
2869
|
-
reasons: entry.reasons,
|
|
2870
|
-
tradeoffs: entry.tradeoffs,
|
|
2871
|
-
...opts.details ? {
|
|
2872
|
-
description: entry.skill.description,
|
|
2873
|
-
source: entry.skill.source,
|
|
2874
|
-
evidence: entry.breakdown ?? null
|
|
2875
|
-
} : {}
|
|
2876
|
-
}));
|
|
2877
|
-
return {
|
|
2878
|
-
query: result.criteria.query,
|
|
2879
|
-
recommended: options[0] ?? null,
|
|
2880
|
-
options
|
|
2881
|
-
};
|
|
2882
|
-
}
|
|
2883
|
-
async function searchSkills(query, options = {}) {
|
|
2884
|
-
const trimmed = query.trim();
|
|
2885
|
-
if (!trimmed) {
|
|
2886
|
-
const error = new Error("query must be non-empty");
|
|
2887
|
-
error.code = RECOMMENDATION_ERROR_CODES.QUERY_INVALID;
|
|
2888
|
-
throw error;
|
|
2889
|
-
}
|
|
2890
|
-
const client = new MarketplaceClient();
|
|
2891
|
-
try {
|
|
2892
|
-
return await client.search(trimmed, options.limit ?? 20);
|
|
2893
|
-
} catch (error) {
|
|
2894
|
-
const wrapped = new Error(error instanceof Error ? error.message : String(error));
|
|
2895
|
-
wrapped.code = RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
|
|
2896
|
-
throw wrapped;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
async function recommendSkills2(query, criteria, options = {}) {
|
|
2900
|
-
const hits = await searchSkills(query, { limit: options.limit ?? Math.max((options.top ?? 3) * 5, 20) });
|
|
2901
|
-
const ranked = recommendSkills(hits, { ...criteria, query }, options);
|
|
2902
|
-
if (ranked.ranking.length === 0) {
|
|
2903
|
-
const error = new Error("no matches found");
|
|
2904
|
-
error.code = RECOMMENDATION_ERROR_CODES.NO_MATCHES;
|
|
2905
|
-
throw error;
|
|
2906
|
-
}
|
|
2907
|
-
return ranked;
|
|
2908
|
-
}
|
|
2909
|
-
|
|
2910
|
-
// src/core/skills/audit/scanner.ts
|
|
2911
|
-
import { existsSync as existsSync13 } from "fs";
|
|
2912
|
-
import { readFile as readFile7 } from "fs/promises";
|
|
2913
|
-
|
|
2914
|
-
// src/core/skills/audit/rules.ts
|
|
2915
|
-
function rule(id, name, description, severity, category, pattern) {
|
|
2916
|
-
return { id, name, description, severity, category, pattern };
|
|
1779
|
+
// src/core/skills/audit/rules.ts
|
|
1780
|
+
function rule(id, name, description, severity, category, pattern) {
|
|
1781
|
+
return { id, name, description, severity, category, pattern };
|
|
2917
1782
|
}
|
|
2918
1783
|
var AUDIT_RULES = [
|
|
2919
1784
|
// ── Prompt Injection ────────────────────────────────────────
|
|
@@ -2998,14 +1863,7 @@ var AUDIT_RULES = [
|
|
|
2998
1863
|
"command-injection",
|
|
2999
1864
|
/(?:curl|wget|fetch)\s+.*\|\s*(?:sh|bash|zsh|python|node|eval)/i
|
|
3000
1865
|
),
|
|
3001
|
-
rule(
|
|
3002
|
-
"CI003",
|
|
3003
|
-
"Eval usage",
|
|
3004
|
-
"Dynamic code execution",
|
|
3005
|
-
"high",
|
|
3006
|
-
"command-injection",
|
|
3007
|
-
/\beval\s*\(/
|
|
3008
|
-
),
|
|
1866
|
+
rule("CI003", "Eval usage", "Dynamic code execution", "high", "command-injection", /\beval\s*\(/),
|
|
3009
1867
|
rule(
|
|
3010
1868
|
"CI004",
|
|
3011
1869
|
"Shell spawn",
|
|
@@ -3279,108 +2137,1295 @@ var AUDIT_RULES = [
|
|
|
3279
2137
|
)
|
|
3280
2138
|
];
|
|
3281
2139
|
|
|
3282
|
-
// src/core/skills/audit/scanner.ts
|
|
3283
|
-
var SEVERITY_WEIGHTS = {
|
|
3284
|
-
critical: 25,
|
|
3285
|
-
high: 15,
|
|
3286
|
-
medium: 8,
|
|
3287
|
-
low: 3,
|
|
3288
|
-
info: 0
|
|
3289
|
-
};
|
|
3290
|
-
async function scanFile(filePath, rules) {
|
|
3291
|
-
if (!
|
|
3292
|
-
return { file: filePath, findings: [], score: 100, passed: true };
|
|
2140
|
+
// src/core/skills/audit/scanner.ts
|
|
2141
|
+
var SEVERITY_WEIGHTS = {
|
|
2142
|
+
critical: 25,
|
|
2143
|
+
high: 15,
|
|
2144
|
+
medium: 8,
|
|
2145
|
+
low: 3,
|
|
2146
|
+
info: 0
|
|
2147
|
+
};
|
|
2148
|
+
async function scanFile(filePath, rules) {
|
|
2149
|
+
if (!existsSync10(filePath)) {
|
|
2150
|
+
return { file: filePath, findings: [], score: 100, passed: true };
|
|
2151
|
+
}
|
|
2152
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2153
|
+
const lines = content.split("\n");
|
|
2154
|
+
const activeRules = rules ?? AUDIT_RULES;
|
|
2155
|
+
const findings = [];
|
|
2156
|
+
for (const rule2 of activeRules) {
|
|
2157
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2158
|
+
const line = lines[i] ?? "";
|
|
2159
|
+
const match = line.match(rule2.pattern);
|
|
2160
|
+
if (match) {
|
|
2161
|
+
findings.push({
|
|
2162
|
+
rule: rule2,
|
|
2163
|
+
line: i + 1,
|
|
2164
|
+
column: (match.index ?? 0) + 1,
|
|
2165
|
+
match: match[0],
|
|
2166
|
+
context: line.trim()
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const totalPenalty = findings.reduce(
|
|
2172
|
+
(sum, f) => sum + (SEVERITY_WEIGHTS[f.rule.severity] ?? 0),
|
|
2173
|
+
0
|
|
2174
|
+
);
|
|
2175
|
+
const score = Math.max(0, 100 - totalPenalty);
|
|
2176
|
+
const passed = !findings.some(
|
|
2177
|
+
(f) => f.rule.severity === "critical" || f.rule.severity === "high"
|
|
2178
|
+
);
|
|
2179
|
+
return { file: filePath, findings, score, passed };
|
|
2180
|
+
}
|
|
2181
|
+
async function scanDirectory(dirPath) {
|
|
2182
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2183
|
+
const { join: join7 } = await import("path");
|
|
2184
|
+
if (!existsSync10(dirPath)) return [];
|
|
2185
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
2186
|
+
const results = [];
|
|
2187
|
+
for (const entry of entries) {
|
|
2188
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
2189
|
+
const skillFile = join7(dirPath, entry.name, "SKILL.md");
|
|
2190
|
+
if (existsSync10(skillFile)) {
|
|
2191
|
+
results.push(await scanFile(skillFile));
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return results;
|
|
2196
|
+
}
|
|
2197
|
+
function toSarif(results) {
|
|
2198
|
+
return {
|
|
2199
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
|
2200
|
+
version: "2.1.0",
|
|
2201
|
+
runs: [
|
|
2202
|
+
{
|
|
2203
|
+
tool: {
|
|
2204
|
+
driver: {
|
|
2205
|
+
name: "caamp-audit",
|
|
2206
|
+
version: "0.1.0",
|
|
2207
|
+
rules: AUDIT_RULES.map((r) => ({
|
|
2208
|
+
id: r.id,
|
|
2209
|
+
name: r.name,
|
|
2210
|
+
shortDescription: { text: r.description },
|
|
2211
|
+
defaultConfiguration: {
|
|
2212
|
+
level: r.severity === "critical" || r.severity === "high" ? "error" : "warning"
|
|
2213
|
+
},
|
|
2214
|
+
properties: { category: r.category }
|
|
2215
|
+
}))
|
|
2216
|
+
}
|
|
2217
|
+
},
|
|
2218
|
+
results: results.flatMap(
|
|
2219
|
+
(result) => result.findings.map((f) => ({
|
|
2220
|
+
ruleId: f.rule.id,
|
|
2221
|
+
level: f.rule.severity === "critical" || f.rule.severity === "high" ? "error" : "warning",
|
|
2222
|
+
message: { text: `${f.rule.description}: ${f.match}` },
|
|
2223
|
+
locations: [
|
|
2224
|
+
{
|
|
2225
|
+
physicalLocation: {
|
|
2226
|
+
artifactLocation: { uri: result.file },
|
|
2227
|
+
region: {
|
|
2228
|
+
startLine: f.line,
|
|
2229
|
+
startColumn: f.column
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
]
|
|
2234
|
+
}))
|
|
2235
|
+
)
|
|
2236
|
+
}
|
|
2237
|
+
]
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// src/core/skills/lock.ts
|
|
2242
|
+
import { execFile } from "child_process";
|
|
2243
|
+
import { promisify } from "util";
|
|
2244
|
+
import { simpleGit } from "simple-git";
|
|
2245
|
+
var execFileAsync = promisify(execFile);
|
|
2246
|
+
async function recordSkillInstall(skillName, scopedName, source, sourceType, agents, canonicalPath, isGlobal, projectDir, version) {
|
|
2247
|
+
await updateLockFile((lock) => {
|
|
2248
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2249
|
+
const existing = lock.skills[skillName];
|
|
2250
|
+
lock.skills[skillName] = {
|
|
2251
|
+
name: skillName,
|
|
2252
|
+
scopedName: existing?.scopedName ?? scopedName,
|
|
2253
|
+
source: existing?.source ?? source,
|
|
2254
|
+
sourceType: existing?.sourceType ?? sourceType,
|
|
2255
|
+
version: version ?? existing?.version,
|
|
2256
|
+
installedAt: existing?.installedAt ?? now,
|
|
2257
|
+
updatedAt: now,
|
|
2258
|
+
agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
|
|
2259
|
+
canonicalPath,
|
|
2260
|
+
isGlobal: existing?.isGlobal ?? isGlobal,
|
|
2261
|
+
projectDir: existing?.projectDir ?? projectDir
|
|
2262
|
+
};
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
async function removeSkillFromLock(skillName) {
|
|
2266
|
+
let removed = false;
|
|
2267
|
+
await updateLockFile((lock) => {
|
|
2268
|
+
if (!(skillName in lock.skills)) return;
|
|
2269
|
+
delete lock.skills[skillName];
|
|
2270
|
+
removed = true;
|
|
2271
|
+
});
|
|
2272
|
+
return removed;
|
|
2273
|
+
}
|
|
2274
|
+
async function getTrackedSkills() {
|
|
2275
|
+
const lock = await readLockFile();
|
|
2276
|
+
return lock.skills;
|
|
2277
|
+
}
|
|
2278
|
+
async function fetchLatestSha(repoUrl, ref) {
|
|
2279
|
+
try {
|
|
2280
|
+
const git = simpleGit();
|
|
2281
|
+
const target = ref ?? "HEAD";
|
|
2282
|
+
const args = target === "HEAD" ? [repoUrl, "HEAD"] : ["--refs", repoUrl, target];
|
|
2283
|
+
const result = await git.listRemote(args);
|
|
2284
|
+
const firstLine = result.trim().split("\n")[0];
|
|
2285
|
+
if (!firstLine) return null;
|
|
2286
|
+
const sha = firstLine.split(" ")[0];
|
|
2287
|
+
return sha ?? null;
|
|
2288
|
+
} catch {
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
async function fetchLatestPackageVersion(packageName) {
|
|
2293
|
+
try {
|
|
2294
|
+
const { stdout } = await execFileAsync("npm", ["view", packageName, "version"]);
|
|
2295
|
+
return stdout.trim() || null;
|
|
2296
|
+
} catch {
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
async function checkSkillUpdate(skillName) {
|
|
2301
|
+
const lock = await readLockFile();
|
|
2302
|
+
const entry = lock.skills[skillName];
|
|
2303
|
+
if (!entry) {
|
|
2304
|
+
return { hasUpdate: false, status: "unknown" };
|
|
2305
|
+
}
|
|
2306
|
+
if (entry.sourceType !== "github" && entry.sourceType !== "gitlab" && entry.sourceType !== "library") {
|
|
2307
|
+
return {
|
|
2308
|
+
hasUpdate: false,
|
|
2309
|
+
currentVersion: entry.version,
|
|
2310
|
+
status: "unknown"
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
const parsed = parseSource(entry.source);
|
|
2314
|
+
if (!parsed.owner) {
|
|
2315
|
+
return {
|
|
2316
|
+
hasUpdate: false,
|
|
2317
|
+
currentVersion: entry.version,
|
|
2318
|
+
status: "unknown"
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
if (entry.sourceType === "library") {
|
|
2322
|
+
const packageName = parsed.owner;
|
|
2323
|
+
const latestVersion = await fetchLatestPackageVersion(packageName);
|
|
2324
|
+
if (!latestVersion) {
|
|
2325
|
+
return {
|
|
2326
|
+
hasUpdate: false,
|
|
2327
|
+
currentVersion: entry.version,
|
|
2328
|
+
status: "unknown"
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
const currentVersion2 = entry.version;
|
|
2332
|
+
const hasUpdate2 = !currentVersion2 || currentVersion2 !== latestVersion;
|
|
2333
|
+
return {
|
|
2334
|
+
hasUpdate: hasUpdate2,
|
|
2335
|
+
currentVersion: currentVersion2 ?? "unknown",
|
|
2336
|
+
latestVersion,
|
|
2337
|
+
status: hasUpdate2 ? "update-available" : "up-to-date"
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
if (!parsed.repo) {
|
|
2341
|
+
return {
|
|
2342
|
+
hasUpdate: false,
|
|
2343
|
+
currentVersion: entry.version,
|
|
2344
|
+
status: "unknown"
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
const host = parsed.type === "gitlab" ? "gitlab.com" : "github.com";
|
|
2348
|
+
const repoUrl = `https://${host}/${parsed.owner}/${parsed.repo}.git`;
|
|
2349
|
+
const latestSha = await fetchLatestSha(repoUrl, parsed.ref);
|
|
2350
|
+
if (!latestSha) {
|
|
2351
|
+
return {
|
|
2352
|
+
hasUpdate: false,
|
|
2353
|
+
currentVersion: entry.version,
|
|
2354
|
+
status: "unknown"
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
const currentVersion = entry.version;
|
|
2358
|
+
const hasUpdate = !currentVersion || !latestSha.startsWith(currentVersion.slice(0, 7));
|
|
2359
|
+
return {
|
|
2360
|
+
hasUpdate,
|
|
2361
|
+
currentVersion: currentVersion ?? "unknown",
|
|
2362
|
+
latestVersion: latestSha.slice(0, 12),
|
|
2363
|
+
status: hasUpdate ? "update-available" : "up-to-date"
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
async function checkAllSkillUpdates() {
|
|
2367
|
+
const lock = await readLockFile();
|
|
2368
|
+
const skillNames = Object.keys(lock.skills);
|
|
2369
|
+
const results = {};
|
|
2370
|
+
await Promise.all(
|
|
2371
|
+
skillNames.map(async (name) => {
|
|
2372
|
+
results[name] = await checkSkillUpdate(name);
|
|
2373
|
+
})
|
|
2374
|
+
);
|
|
2375
|
+
return results;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// src/core/network/fetch.ts
|
|
2379
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
2380
|
+
var NetworkError = class extends Error {
|
|
2381
|
+
/** Classification of the failure. */
|
|
2382
|
+
kind;
|
|
2383
|
+
/** URL that was being fetched. */
|
|
2384
|
+
url;
|
|
2385
|
+
/** HTTP status code (only present for `"http"` kind). */
|
|
2386
|
+
status;
|
|
2387
|
+
constructor(message, kind, url, status) {
|
|
2388
|
+
super(message);
|
|
2389
|
+
this.name = "NetworkError";
|
|
2390
|
+
this.kind = kind;
|
|
2391
|
+
this.url = url;
|
|
2392
|
+
this.status = status;
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
function isAbortError(error) {
|
|
2396
|
+
return error instanceof Error && error.name === "AbortError";
|
|
2397
|
+
}
|
|
2398
|
+
async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
2399
|
+
try {
|
|
2400
|
+
return await fetch(url, {
|
|
2401
|
+
...init,
|
|
2402
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2403
|
+
});
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
if (isAbortError(error)) {
|
|
2406
|
+
throw new NetworkError(`Request timed out after ${timeoutMs}ms`, "timeout", url);
|
|
2407
|
+
}
|
|
2408
|
+
throw new NetworkError("Network request failed", "network", url);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
function ensureOkResponse(response, url) {
|
|
2412
|
+
if (!response.ok) {
|
|
2413
|
+
throw new NetworkError(
|
|
2414
|
+
`Request failed with status ${response.status}`,
|
|
2415
|
+
"http",
|
|
2416
|
+
url,
|
|
2417
|
+
response.status
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
return response;
|
|
2421
|
+
}
|
|
2422
|
+
function formatNetworkError(error) {
|
|
2423
|
+
if (error instanceof NetworkError) {
|
|
2424
|
+
if (error.kind === "timeout") {
|
|
2425
|
+
return "Network request timed out. Please check your connection and try again.";
|
|
2426
|
+
}
|
|
2427
|
+
if (error.kind === "http") {
|
|
2428
|
+
return `Marketplace request failed with HTTP ${error.status ?? "unknown"}. Please try again shortly.`;
|
|
2429
|
+
}
|
|
2430
|
+
return "Network request failed. Please check your connection and try again.";
|
|
2431
|
+
}
|
|
2432
|
+
if (error instanceof Error) return error.message;
|
|
2433
|
+
return String(error);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// src/core/marketplace/skillsmp.ts
|
|
2437
|
+
var API_BASE = "https://www.agentskills.in/api/skills";
|
|
2438
|
+
function parseScopedName(value) {
|
|
2439
|
+
const match = value.match(/^@([^/]+)\/([^/]+)$/);
|
|
2440
|
+
if (!match) return null;
|
|
2441
|
+
return {
|
|
2442
|
+
author: match[1],
|
|
2443
|
+
name: match[2]
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
function toResult(skill) {
|
|
2447
|
+
return {
|
|
2448
|
+
name: skill.name,
|
|
2449
|
+
scopedName: skill.scopedName,
|
|
2450
|
+
description: skill.description,
|
|
2451
|
+
author: skill.author,
|
|
2452
|
+
stars: skill.stars,
|
|
2453
|
+
githubUrl: skill.githubUrl,
|
|
2454
|
+
repoFullName: skill.repoFullName,
|
|
2455
|
+
path: skill.path,
|
|
2456
|
+
source: "agentskills.in"
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
var SkillsMPAdapter = class {
|
|
2460
|
+
/** The marketplace identifier used in search results. */
|
|
2461
|
+
name = "agentskills.in";
|
|
2462
|
+
/**
|
|
2463
|
+
* Search for skills by query string.
|
|
2464
|
+
*
|
|
2465
|
+
* @param query - Search query to match against skill names and descriptions.
|
|
2466
|
+
* @param limit - Maximum number of results to return.
|
|
2467
|
+
* @returns Array of marketplace results sorted by stars.
|
|
2468
|
+
*/
|
|
2469
|
+
async search(query, limit = 20) {
|
|
2470
|
+
const params = new URLSearchParams({
|
|
2471
|
+
search: query,
|
|
2472
|
+
limit: String(limit),
|
|
2473
|
+
sortBy: "stars"
|
|
2474
|
+
});
|
|
2475
|
+
const url = `${API_BASE}?${params}`;
|
|
2476
|
+
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
2477
|
+
const data = await response.json();
|
|
2478
|
+
return data.skills.map(toResult);
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Look up a specific skill by its scoped name.
|
|
2482
|
+
*
|
|
2483
|
+
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
2484
|
+
* @returns The matching marketplace result, or `null` if not found.
|
|
2485
|
+
*/
|
|
2486
|
+
async getSkill(scopedName) {
|
|
2487
|
+
const parts = parseScopedName(scopedName);
|
|
2488
|
+
const searchTerms = parts ? [parts.name, `${parts.author} ${parts.name}`, scopedName] : [scopedName];
|
|
2489
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2490
|
+
for (const term of searchTerms) {
|
|
2491
|
+
if (seen.has(term)) continue;
|
|
2492
|
+
seen.add(term);
|
|
2493
|
+
const params = new URLSearchParams({
|
|
2494
|
+
search: term,
|
|
2495
|
+
limit: "50",
|
|
2496
|
+
sortBy: "stars"
|
|
2497
|
+
});
|
|
2498
|
+
const url = `${API_BASE}?${params}`;
|
|
2499
|
+
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
2500
|
+
const data = await response.json();
|
|
2501
|
+
const match = data.skills.find(
|
|
2502
|
+
(s) => s.scopedName === scopedName || `@${s.author}/${s.name}` === scopedName
|
|
2503
|
+
);
|
|
2504
|
+
if (match) {
|
|
2505
|
+
return toResult(match);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return null;
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2512
|
+
// src/core/marketplace/skillssh.ts
|
|
2513
|
+
var API_BASE2 = "https://skills.sh/api";
|
|
2514
|
+
function toResult2(skill) {
|
|
2515
|
+
return {
|
|
2516
|
+
name: skill.name,
|
|
2517
|
+
scopedName: `@${skill.author}/${skill.name}`,
|
|
2518
|
+
description: skill.description,
|
|
2519
|
+
author: skill.author,
|
|
2520
|
+
stars: skill.stars ?? 0,
|
|
2521
|
+
githubUrl: skill.url,
|
|
2522
|
+
repoFullName: skill.repo,
|
|
2523
|
+
path: "",
|
|
2524
|
+
source: "skills.sh"
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
var SkillsShAdapter = class {
|
|
2528
|
+
/** The marketplace identifier used in search results. */
|
|
2529
|
+
name = "skills.sh";
|
|
2530
|
+
/**
|
|
2531
|
+
* Search for skills by query string.
|
|
2532
|
+
*
|
|
2533
|
+
* @param query - Search query to match against skill names.
|
|
2534
|
+
* @param limit - Maximum number of results to return.
|
|
2535
|
+
* @returns Array of marketplace results.
|
|
2536
|
+
*/
|
|
2537
|
+
async search(query, limit = 20) {
|
|
2538
|
+
const params = new URLSearchParams({
|
|
2539
|
+
q: query,
|
|
2540
|
+
limit: String(limit)
|
|
2541
|
+
});
|
|
2542
|
+
const url = `${API_BASE2}/search?${params}`;
|
|
2543
|
+
const response = ensureOkResponse(await fetchWithTimeout(url), url);
|
|
2544
|
+
const data = await response.json();
|
|
2545
|
+
return data.results.map(toResult2);
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Look up a specific skill by its scoped name.
|
|
2549
|
+
*
|
|
2550
|
+
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
2551
|
+
* @returns The matching marketplace result, or `null` if not found.
|
|
2552
|
+
*/
|
|
2553
|
+
async getSkill(scopedName) {
|
|
2554
|
+
const results = await this.search(scopedName, 5);
|
|
2555
|
+
return results.find((r) => r.scopedName === scopedName) ?? null;
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
// src/core/marketplace/client.ts
|
|
2560
|
+
var MarketplaceUnavailableError = class extends Error {
|
|
2561
|
+
/** Per-adapter failure messages. */
|
|
2562
|
+
details;
|
|
2563
|
+
constructor(message, details) {
|
|
2564
|
+
super(message);
|
|
2565
|
+
this.name = "MarketplaceUnavailableError";
|
|
2566
|
+
this.details = details;
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
var MarketplaceClient = class {
|
|
2570
|
+
/** Configured marketplace adapters. */
|
|
2571
|
+
adapters;
|
|
2572
|
+
/**
|
|
2573
|
+
* Create a new marketplace client.
|
|
2574
|
+
*
|
|
2575
|
+
* @param adapters - Custom marketplace adapters (defaults to agentskills.in and skills.sh)
|
|
2576
|
+
*
|
|
2577
|
+
* @example
|
|
2578
|
+
* ```typescript
|
|
2579
|
+
* // Use default adapters
|
|
2580
|
+
* const client = new MarketplaceClient();
|
|
2581
|
+
*
|
|
2582
|
+
* // Use custom adapters
|
|
2583
|
+
* const client = new MarketplaceClient([myAdapter]);
|
|
2584
|
+
* ```
|
|
2585
|
+
*/
|
|
2586
|
+
constructor(adapters) {
|
|
2587
|
+
this.adapters = adapters ?? [new SkillsMPAdapter(), new SkillsShAdapter()];
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Search all marketplaces and return deduplicated, sorted results.
|
|
2591
|
+
*
|
|
2592
|
+
* Queries all adapters in parallel and deduplicates by `scopedName`,
|
|
2593
|
+
* keeping the entry with the highest star count. Results are sorted by
|
|
2594
|
+
* stars descending.
|
|
2595
|
+
*
|
|
2596
|
+
* @param query - Search query string
|
|
2597
|
+
* @param limit - Maximum number of results to return (default: 20)
|
|
2598
|
+
* @returns Deduplicated and sorted marketplace results
|
|
2599
|
+
*
|
|
2600
|
+
* @example
|
|
2601
|
+
* ```typescript
|
|
2602
|
+
* const results = await client.search("code review", 10);
|
|
2603
|
+
* ```
|
|
2604
|
+
*/
|
|
2605
|
+
async search(query, limit = 20) {
|
|
2606
|
+
const settled = await Promise.allSettled(
|
|
2607
|
+
this.adapters.map((adapter) => adapter.search(query, limit))
|
|
2608
|
+
);
|
|
2609
|
+
const flat = [];
|
|
2610
|
+
const failures = [];
|
|
2611
|
+
for (const [index, result] of settled.entries()) {
|
|
2612
|
+
const adapterName = this.adapters[index]?.name ?? "unknown";
|
|
2613
|
+
if (result.status === "fulfilled") {
|
|
2614
|
+
flat.push(...result.value);
|
|
2615
|
+
} else {
|
|
2616
|
+
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
2617
|
+
failures.push(`${adapterName}: ${reason}`);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
if (flat.length === 0 && failures.length > 0) {
|
|
2621
|
+
throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
|
|
2622
|
+
}
|
|
2623
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2624
|
+
for (const result of flat) {
|
|
2625
|
+
const existing = seen.get(result.scopedName);
|
|
2626
|
+
if (!existing || result.stars > existing.stars) {
|
|
2627
|
+
seen.set(result.scopedName, result);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
const deduplicated = Array.from(seen.values());
|
|
2631
|
+
deduplicated.sort((a, b) => b.stars - a.stars);
|
|
2632
|
+
return deduplicated.slice(0, limit);
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Get a specific skill by its scoped name from any marketplace.
|
|
2636
|
+
*
|
|
2637
|
+
* Tries each adapter in order and returns the first match.
|
|
2638
|
+
*
|
|
2639
|
+
* @param scopedName - Scoped skill name (e.g. `"@author/my-skill"`)
|
|
2640
|
+
* @returns The marketplace result, or `null` if not found in any marketplace
|
|
2641
|
+
*
|
|
2642
|
+
* @example
|
|
2643
|
+
* ```typescript
|
|
2644
|
+
* const skill = await client.getSkill("@anthropic/memory");
|
|
2645
|
+
* ```
|
|
2646
|
+
*/
|
|
2647
|
+
async getSkill(scopedName) {
|
|
2648
|
+
const failures = [];
|
|
2649
|
+
for (const adapter of this.adapters) {
|
|
2650
|
+
try {
|
|
2651
|
+
const result = await adapter.getSkill(scopedName);
|
|
2652
|
+
if (result) return result;
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2655
|
+
failures.push(`${adapter.name}: ${reason}`);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
if (failures.length === this.adapters.length && this.adapters.length > 0) {
|
|
2659
|
+
throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
|
|
2660
|
+
}
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
|
|
2665
|
+
// src/core/skills/recommendation.ts
|
|
2666
|
+
var RECOMMENDATION_ERROR_CODES = {
|
|
2667
|
+
QUERY_INVALID: "E_SKILLS_QUERY_INVALID",
|
|
2668
|
+
NO_MATCHES: "E_SKILLS_NO_MATCHES",
|
|
2669
|
+
SOURCE_UNAVAILABLE: "E_SKILLS_SOURCE_UNAVAILABLE",
|
|
2670
|
+
CRITERIA_CONFLICT: "E_SKILLS_CRITERIA_CONFLICT"
|
|
2671
|
+
};
|
|
2672
|
+
var DEFAULT_WEIGHTS = {
|
|
2673
|
+
mustHaveMatch: 10,
|
|
2674
|
+
preferMatch: 4,
|
|
2675
|
+
queryTokenMatch: 3,
|
|
2676
|
+
starsFactor: 2,
|
|
2677
|
+
metadataBoost: 2,
|
|
2678
|
+
modernMarkerBoost: 3,
|
|
2679
|
+
legacyMarkerPenalty: 3,
|
|
2680
|
+
excludePenalty: 25,
|
|
2681
|
+
missingMustHavePenalty: 20
|
|
2682
|
+
};
|
|
2683
|
+
var DEFAULT_MODERN_MARKERS = ["svelte 5", "runes", "lafs", "slsa", "drizzle", "better-auth"];
|
|
2684
|
+
var DEFAULT_LEGACY_MARKERS = [
|
|
2685
|
+
"svelte 3",
|
|
2686
|
+
"jquery",
|
|
2687
|
+
"bower",
|
|
2688
|
+
"legacy",
|
|
2689
|
+
"book.json",
|
|
2690
|
+
"gitbook-cli"
|
|
2691
|
+
];
|
|
2692
|
+
function tokenizeCriteriaValue(value) {
|
|
2693
|
+
return value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
2694
|
+
}
|
|
2695
|
+
function normalizeList(value) {
|
|
2696
|
+
if (value === void 0) return [];
|
|
2697
|
+
if (!(typeof value === "string" || Array.isArray(value))) return [];
|
|
2698
|
+
const source = Array.isArray(value) ? value : [value];
|
|
2699
|
+
const flattened = source.flatMap(
|
|
2700
|
+
(item) => typeof item === "string" ? tokenizeCriteriaValue(item) : []
|
|
2701
|
+
);
|
|
2702
|
+
return Array.from(new Set(flattened)).sort((a, b) => a.localeCompare(b));
|
|
2703
|
+
}
|
|
2704
|
+
function hasAnyCriteriaInput(input) {
|
|
2705
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
2706
|
+
if (query.length > 0) return true;
|
|
2707
|
+
const lists = [input.mustHave, input.prefer, input.exclude];
|
|
2708
|
+
return lists.some((list) => normalizeList(list).length > 0);
|
|
2709
|
+
}
|
|
2710
|
+
function validateRecommendationCriteria(input) {
|
|
2711
|
+
const issues = [];
|
|
2712
|
+
if (input.query !== void 0 && typeof input.query !== "string") {
|
|
2713
|
+
issues.push({
|
|
2714
|
+
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2715
|
+
field: "query",
|
|
2716
|
+
message: "query must be a string"
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
if (input.mustHave !== void 0 && !(typeof input.mustHave === "string" || Array.isArray(input.mustHave))) {
|
|
2720
|
+
issues.push({
|
|
2721
|
+
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2722
|
+
field: "mustHave",
|
|
2723
|
+
message: "mustHave must be a string or string[]"
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
if (input.prefer !== void 0 && !(typeof input.prefer === "string" || Array.isArray(input.prefer))) {
|
|
2727
|
+
issues.push({
|
|
2728
|
+
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2729
|
+
field: "prefer",
|
|
2730
|
+
message: "prefer must be a string or string[]"
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
if (input.exclude !== void 0 && !(typeof input.exclude === "string" || Array.isArray(input.exclude))) {
|
|
2734
|
+
issues.push({
|
|
2735
|
+
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2736
|
+
field: "exclude",
|
|
2737
|
+
message: "exclude must be a string or string[]"
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
const mustHave = normalizeList(input.mustHave);
|
|
2741
|
+
const prefer = normalizeList(input.prefer);
|
|
2742
|
+
const exclude = normalizeList(input.exclude);
|
|
2743
|
+
const conflict = mustHave.some((term) => exclude.includes(term)) || prefer.some((term) => exclude.includes(term));
|
|
2744
|
+
if (conflict) {
|
|
2745
|
+
issues.push({
|
|
2746
|
+
code: RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
|
|
2747
|
+
field: "exclude",
|
|
2748
|
+
message: "criteria terms cannot appear in both prefer/must-have and exclude"
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
if (issues.length === 0 && !hasAnyCriteriaInput(input)) {
|
|
2752
|
+
issues.push({
|
|
2753
|
+
code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
|
|
2754
|
+
field: "query",
|
|
2755
|
+
message: "at least one criteria value is required"
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
return {
|
|
2759
|
+
valid: issues.length === 0,
|
|
2760
|
+
issues
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
function normalizeRecommendationCriteria(input) {
|
|
2764
|
+
const query = (input.query ?? "").trim().toLowerCase();
|
|
2765
|
+
return {
|
|
2766
|
+
query,
|
|
2767
|
+
queryTokens: query ? Array.from(new Set(tokenizeCriteriaValue(query.replace(/\s+/g, ",")))).sort(
|
|
2768
|
+
(a, b) => a.localeCompare(b)
|
|
2769
|
+
) : [],
|
|
2770
|
+
mustHave: normalizeList(input.mustHave),
|
|
2771
|
+
prefer: normalizeList(input.prefer),
|
|
2772
|
+
exclude: normalizeList(input.exclude)
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
function countMatches(haystack, needles) {
|
|
2776
|
+
let count = 0;
|
|
2777
|
+
for (const needle of needles) {
|
|
2778
|
+
if (haystack.includes(needle)) {
|
|
2779
|
+
count += 1;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
return count;
|
|
2783
|
+
}
|
|
2784
|
+
function clampScore(value) {
|
|
2785
|
+
return Number(value.toFixed(6));
|
|
2786
|
+
}
|
|
2787
|
+
function buildSearchText(skill) {
|
|
2788
|
+
return `${skill.name} ${skill.scopedName} ${skill.description} ${skill.author}`.toLowerCase();
|
|
2789
|
+
}
|
|
2790
|
+
function scoreSkillRecommendation(skill, criteria, options = {}) {
|
|
2791
|
+
const weights = { ...DEFAULT_WEIGHTS, ...options.weights };
|
|
2792
|
+
const modernMarkers = (options.modernMarkers ?? DEFAULT_MODERN_MARKERS).map(
|
|
2793
|
+
(marker) => marker.toLowerCase()
|
|
2794
|
+
);
|
|
2795
|
+
const legacyMarkers = (options.legacyMarkers ?? DEFAULT_LEGACY_MARKERS).map(
|
|
2796
|
+
(marker) => marker.toLowerCase()
|
|
2797
|
+
);
|
|
2798
|
+
const text = buildSearchText(skill);
|
|
2799
|
+
const reasons = [];
|
|
2800
|
+
const tradeoffs = [];
|
|
2801
|
+
const mustHaveMatches = countMatches(text, criteria.mustHave);
|
|
2802
|
+
const missingMustHave = Math.max(criteria.mustHave.length - mustHaveMatches, 0);
|
|
2803
|
+
const preferMatches = countMatches(text, criteria.prefer);
|
|
2804
|
+
const queryMatches = countMatches(text, criteria.queryTokens);
|
|
2805
|
+
const excludeMatches = countMatches(text, criteria.exclude);
|
|
2806
|
+
const modernMatches = countMatches(text, modernMarkers);
|
|
2807
|
+
const legacyMatches = countMatches(text, legacyMarkers);
|
|
2808
|
+
const metadataSignal = skill.description.trim().length >= 80 ? 1 : 0;
|
|
2809
|
+
const starsSignal = Math.log10(skill.stars + 1);
|
|
2810
|
+
const sourceConfidence = skill.source === "agentskills.in" ? 1 : skill.source === "skills.sh" ? 0.8 : 0.6;
|
|
2811
|
+
const mustHaveScore = mustHaveMatches * weights.mustHaveMatch - missingMustHave * weights.missingMustHavePenalty;
|
|
2812
|
+
const preferScore = preferMatches * weights.preferMatch;
|
|
2813
|
+
const queryScore = queryMatches * weights.queryTokenMatch;
|
|
2814
|
+
const starsScore = starsSignal * weights.starsFactor;
|
|
2815
|
+
const metadataScore = (metadataSignal + sourceConfidence) * weights.metadataBoost;
|
|
2816
|
+
const modernityScore = modernMatches * weights.modernMarkerBoost - legacyMatches * weights.legacyMarkerPenalty;
|
|
2817
|
+
const exclusionPenalty = excludeMatches * weights.excludePenalty;
|
|
2818
|
+
const hasGitbookTopic = text.includes("gitbook");
|
|
2819
|
+
const hasGitSync = text.includes("git sync") || text.includes("git") && text.includes("sync");
|
|
2820
|
+
const hasApiWorkflow = text.includes("api") && (text.includes("workflow") || text.includes("sync"));
|
|
2821
|
+
const hasLegacyCli = text.includes("gitbook-cli") || text.includes("book.json");
|
|
2822
|
+
const topicScore = (hasGitbookTopic ? 3 : 0) + (hasGitSync ? 2 : 0) + (hasApiWorkflow ? 2 : 0) - (hasLegacyCli ? 4 : 0);
|
|
2823
|
+
const total = clampScore(
|
|
2824
|
+
mustHaveScore + preferScore + queryScore + starsScore + metadataScore + modernityScore + topicScore - exclusionPenalty
|
|
2825
|
+
);
|
|
2826
|
+
if (hasGitbookTopic) reasons.push({ code: "MATCH_TOPIC_GITBOOK" });
|
|
2827
|
+
if (hasGitSync) reasons.push({ code: "HAS_GIT_SYNC" });
|
|
2828
|
+
if (hasApiWorkflow) reasons.push({ code: "HAS_API_WORKFLOW" });
|
|
2829
|
+
if (hasLegacyCli) reasons.push({ code: "PENALTY_LEGACY_CLI" });
|
|
2830
|
+
if (mustHaveMatches > 0)
|
|
2831
|
+
reasons.push({ code: "MUST_HAVE_MATCH", detail: String(mustHaveMatches) });
|
|
2832
|
+
if (missingMustHave > 0)
|
|
2833
|
+
reasons.push({ code: "MISSING_MUST_HAVE", detail: String(missingMustHave) });
|
|
2834
|
+
if (preferMatches > 0) reasons.push({ code: "PREFER_MATCH", detail: String(preferMatches) });
|
|
2835
|
+
if (queryMatches > 0) reasons.push({ code: "QUERY_MATCH", detail: String(queryMatches) });
|
|
2836
|
+
if (starsSignal > 0) reasons.push({ code: "STAR_SIGNAL" });
|
|
2837
|
+
if (metadataSignal > 0) reasons.push({ code: "METADATA_SIGNAL" });
|
|
2838
|
+
if (modernMatches > 0) reasons.push({ code: "MODERN_MARKER", detail: String(modernMatches) });
|
|
2839
|
+
if (legacyMatches > 0) reasons.push({ code: "LEGACY_MARKER", detail: String(legacyMatches) });
|
|
2840
|
+
if (excludeMatches > 0) reasons.push({ code: "EXCLUDE_MATCH", detail: String(excludeMatches) });
|
|
2841
|
+
if (missingMustHave > 0) tradeoffs.push("Missing one or more required criteria terms.");
|
|
2842
|
+
if (excludeMatches > 0) tradeoffs.push("Matches one or more excluded terms.");
|
|
2843
|
+
if (skill.stars < 10) tradeoffs.push("Low quality signal from repository stars.");
|
|
2844
|
+
if (hasLegacyCli) tradeoffs.push("Contains legacy GitBook CLI markers.");
|
|
2845
|
+
const result = {
|
|
2846
|
+
skill,
|
|
2847
|
+
score: total,
|
|
2848
|
+
reasons,
|
|
2849
|
+
tradeoffs,
|
|
2850
|
+
excluded: excludeMatches > 0
|
|
2851
|
+
};
|
|
2852
|
+
if (options.includeDetails) {
|
|
2853
|
+
result.breakdown = {
|
|
2854
|
+
mustHave: clampScore(mustHaveScore),
|
|
2855
|
+
prefer: clampScore(preferScore),
|
|
2856
|
+
query: clampScore(queryScore),
|
|
2857
|
+
stars: clampScore(starsScore),
|
|
2858
|
+
metadata: clampScore(metadataScore),
|
|
2859
|
+
modernity: clampScore(modernityScore),
|
|
2860
|
+
exclusionPenalty: clampScore(exclusionPenalty),
|
|
2861
|
+
total
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
return result;
|
|
2865
|
+
}
|
|
2866
|
+
function recommendSkills(skills, criteriaInput, options = {}) {
|
|
2867
|
+
const validation = validateRecommendationCriteria(criteriaInput);
|
|
2868
|
+
if (!validation.valid) {
|
|
2869
|
+
const first = validation.issues[0];
|
|
2870
|
+
const error = new Error(first?.message ?? "Invalid recommendation criteria");
|
|
2871
|
+
error.code = first?.code;
|
|
2872
|
+
error.issues = validation.issues;
|
|
2873
|
+
throw error;
|
|
2874
|
+
}
|
|
2875
|
+
const criteria = normalizeRecommendationCriteria(criteriaInput);
|
|
2876
|
+
const ranking = skills.map((skill) => scoreSkillRecommendation(skill, criteria, options)).sort((a, b) => {
|
|
2877
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
2878
|
+
if (b.skill.stars !== a.skill.stars) return b.skill.stars - a.skill.stars;
|
|
2879
|
+
return a.skill.scopedName.localeCompare(b.skill.scopedName);
|
|
2880
|
+
});
|
|
2881
|
+
return {
|
|
2882
|
+
criteria,
|
|
2883
|
+
ranking: typeof options.top === "number" ? ranking.slice(0, Math.max(0, options.top)) : ranking
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
var rankSkills = recommendSkills;
|
|
2887
|
+
|
|
2888
|
+
// src/core/skills/recommendation-api.ts
|
|
2889
|
+
function formatSkillRecommendations(result, opts) {
|
|
2890
|
+
const top = result.ranking;
|
|
2891
|
+
if (opts.mode === "human") {
|
|
2892
|
+
if (top.length === 0) return "No recommendations found.";
|
|
2893
|
+
const lines = ["Recommended skills:", ""];
|
|
2894
|
+
for (const [index, entry] of top.entries()) {
|
|
2895
|
+
const marker = index === 0 ? " (Recommended)" : "";
|
|
2896
|
+
lines.push(`${index + 1}) ${entry.skill.scopedName}${marker}`);
|
|
2897
|
+
lines.push(
|
|
2898
|
+
` why: ${entry.reasons.map((reason) => reason.code).join(", ") || "score-based match"}`
|
|
2899
|
+
);
|
|
2900
|
+
lines.push(` tradeoff: ${entry.tradeoffs[0] ?? "none"}`);
|
|
2901
|
+
}
|
|
2902
|
+
lines.push("");
|
|
2903
|
+
lines.push(`CHOOSE: ${top.map((_, index) => index + 1).join(",")}`);
|
|
2904
|
+
return lines.join("\n");
|
|
2905
|
+
}
|
|
2906
|
+
const options = top.map((entry, index) => ({
|
|
2907
|
+
rank: index + 1,
|
|
2908
|
+
scopedName: entry.skill.scopedName,
|
|
2909
|
+
score: entry.score,
|
|
2910
|
+
reasons: entry.reasons,
|
|
2911
|
+
tradeoffs: entry.tradeoffs,
|
|
2912
|
+
...opts.details ? {
|
|
2913
|
+
description: entry.skill.description,
|
|
2914
|
+
source: entry.skill.source,
|
|
2915
|
+
evidence: entry.breakdown ?? null
|
|
2916
|
+
} : {}
|
|
2917
|
+
}));
|
|
2918
|
+
return {
|
|
2919
|
+
query: result.criteria.query,
|
|
2920
|
+
recommended: options[0] ?? null,
|
|
2921
|
+
options
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
async function searchSkills(query, options = {}) {
|
|
2925
|
+
const trimmed = query.trim();
|
|
2926
|
+
if (!trimmed) {
|
|
2927
|
+
const error = new Error("query must be non-empty");
|
|
2928
|
+
error.code = RECOMMENDATION_ERROR_CODES.QUERY_INVALID;
|
|
2929
|
+
throw error;
|
|
2930
|
+
}
|
|
2931
|
+
const client = new MarketplaceClient();
|
|
2932
|
+
try {
|
|
2933
|
+
return await client.search(trimmed, options.limit ?? 20);
|
|
2934
|
+
} catch (error) {
|
|
2935
|
+
const wrapped = new Error(error instanceof Error ? error.message : String(error));
|
|
2936
|
+
wrapped.code = RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
|
|
2937
|
+
throw wrapped;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
async function recommendSkills2(query, criteria, options = {}) {
|
|
2941
|
+
const hits = await searchSkills(query, {
|
|
2942
|
+
limit: options.limit ?? Math.max((options.top ?? 3) * 5, 20)
|
|
2943
|
+
});
|
|
2944
|
+
const ranked = recommendSkills(hits, { ...criteria, query }, options);
|
|
2945
|
+
if (ranked.ranking.length === 0) {
|
|
2946
|
+
const error = new Error("no matches found");
|
|
2947
|
+
error.code = RECOMMENDATION_ERROR_CODES.NO_MATCHES;
|
|
2948
|
+
throw error;
|
|
2949
|
+
}
|
|
2950
|
+
return ranked;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/core/skills/library-loader.ts
|
|
2954
|
+
import { existsSync as existsSync11, readdirSync, readFileSync } from "fs";
|
|
2955
|
+
import { createRequire } from "module";
|
|
2956
|
+
import { basename as basename2, dirname as dirname2, join as join4 } from "path";
|
|
2957
|
+
var require2 = createRequire(import.meta.url);
|
|
2958
|
+
function loadLibraryFromModule(root) {
|
|
2959
|
+
let mod;
|
|
2960
|
+
try {
|
|
2961
|
+
mod = require2(root);
|
|
2962
|
+
} catch {
|
|
2963
|
+
throw new Error(`Failed to load skill library module from ${root}`);
|
|
2964
|
+
}
|
|
2965
|
+
const requiredMethods = [
|
|
2966
|
+
"listSkills",
|
|
2967
|
+
"getSkill",
|
|
2968
|
+
"getSkillPath",
|
|
2969
|
+
"getSkillDir",
|
|
2970
|
+
"readSkillContent",
|
|
2971
|
+
"getCoreSkills",
|
|
2972
|
+
"getSkillsByCategory",
|
|
2973
|
+
"getSkillDependencies",
|
|
2974
|
+
"resolveDependencyTree",
|
|
2975
|
+
"listProfiles",
|
|
2976
|
+
"getProfile",
|
|
2977
|
+
"resolveProfile",
|
|
2978
|
+
"listSharedResources",
|
|
2979
|
+
"getSharedResourcePath",
|
|
2980
|
+
"readSharedResource",
|
|
2981
|
+
"listProtocols",
|
|
2982
|
+
"getProtocolPath",
|
|
2983
|
+
"readProtocol",
|
|
2984
|
+
"validateSkillFrontmatter",
|
|
2985
|
+
"validateAll",
|
|
2986
|
+
"getDispatchMatrix"
|
|
2987
|
+
];
|
|
2988
|
+
for (const method of requiredMethods) {
|
|
2989
|
+
if (typeof mod[method] !== "function") {
|
|
2990
|
+
throw new Error(`Skill library at ${root} does not implement required method: ${method}`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
if (!mod.version || typeof mod.version !== "string") {
|
|
2994
|
+
throw new Error(`Skill library at ${root} is missing 'version' property`);
|
|
2995
|
+
}
|
|
2996
|
+
if (!mod.libraryRoot || typeof mod.libraryRoot !== "string") {
|
|
2997
|
+
throw new Error(`Skill library at ${root} is missing 'libraryRoot' property`);
|
|
2998
|
+
}
|
|
2999
|
+
return mod;
|
|
3000
|
+
}
|
|
3001
|
+
function buildLibraryFromFiles(root) {
|
|
3002
|
+
const catalogPath = join4(root, "skills.json");
|
|
3003
|
+
if (!existsSync11(catalogPath)) {
|
|
3004
|
+
throw new Error(`No skills.json found at ${root}`);
|
|
3005
|
+
}
|
|
3006
|
+
const catalogData = JSON.parse(readFileSync(catalogPath, "utf-8"));
|
|
3007
|
+
const entries = catalogData.skills ?? [];
|
|
3008
|
+
const version = catalogData.version ?? "0.0.0";
|
|
3009
|
+
const manifestPath = join4(root, "skills", "manifest.json");
|
|
3010
|
+
let manifest;
|
|
3011
|
+
if (existsSync11(manifestPath)) {
|
|
3012
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
3013
|
+
} else {
|
|
3014
|
+
manifest = {
|
|
3015
|
+
$schema: "",
|
|
3016
|
+
_meta: {},
|
|
3017
|
+
dispatch_matrix: { by_task_type: {}, by_keyword: {}, by_protocol: {} },
|
|
3018
|
+
skills: []
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
const profilesDir = join4(root, "profiles");
|
|
3022
|
+
const profiles = /* @__PURE__ */ new Map();
|
|
3023
|
+
if (existsSync11(profilesDir)) {
|
|
3024
|
+
for (const file of readdirSync(profilesDir)) {
|
|
3025
|
+
if (!file.endsWith(".json")) continue;
|
|
3026
|
+
try {
|
|
3027
|
+
const profile = JSON.parse(
|
|
3028
|
+
readFileSync(join4(profilesDir, file), "utf-8")
|
|
3029
|
+
);
|
|
3030
|
+
profiles.set(profile.name, profile);
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
3036
|
+
for (const entry of entries) {
|
|
3037
|
+
skillMap.set(entry.name, entry);
|
|
3038
|
+
}
|
|
3039
|
+
function getSkillDir2(name) {
|
|
3040
|
+
const entry = skillMap.get(name);
|
|
3041
|
+
if (entry) {
|
|
3042
|
+
return dirname2(join4(root, entry.path));
|
|
3043
|
+
}
|
|
3044
|
+
return join4(root, "skills", name);
|
|
3045
|
+
}
|
|
3046
|
+
function resolveDeps(names, visited = /* @__PURE__ */ new Set()) {
|
|
3047
|
+
const result = [];
|
|
3048
|
+
for (const name of names) {
|
|
3049
|
+
if (visited.has(name)) continue;
|
|
3050
|
+
visited.add(name);
|
|
3051
|
+
const entry = skillMap.get(name);
|
|
3052
|
+
if (entry && entry.dependencies.length > 0) {
|
|
3053
|
+
result.push(...resolveDeps(entry.dependencies, visited));
|
|
3054
|
+
}
|
|
3055
|
+
result.push(name);
|
|
3056
|
+
}
|
|
3057
|
+
return result;
|
|
3058
|
+
}
|
|
3059
|
+
function resolveProfileByName(name, visited = /* @__PURE__ */ new Set()) {
|
|
3060
|
+
if (visited.has(name)) return [];
|
|
3061
|
+
visited.add(name);
|
|
3062
|
+
const profile = profiles.get(name);
|
|
3063
|
+
if (!profile) return [];
|
|
3064
|
+
let skills = [];
|
|
3065
|
+
if (profile.extends) {
|
|
3066
|
+
skills = resolveProfileByName(profile.extends, visited);
|
|
3067
|
+
}
|
|
3068
|
+
skills.push(...profile.skills);
|
|
3069
|
+
return resolveDeps([...new Set(skills)]);
|
|
3070
|
+
}
|
|
3071
|
+
function discoverFiles(dir, ext) {
|
|
3072
|
+
if (!existsSync11(dir)) return [];
|
|
3073
|
+
return readdirSync(dir).filter((f) => f.endsWith(ext)).map((f) => basename2(f, ext));
|
|
3074
|
+
}
|
|
3075
|
+
const library = {
|
|
3076
|
+
version,
|
|
3077
|
+
libraryRoot: root,
|
|
3078
|
+
skills: entries,
|
|
3079
|
+
manifest,
|
|
3080
|
+
listSkills() {
|
|
3081
|
+
return entries.map((e) => e.name);
|
|
3082
|
+
},
|
|
3083
|
+
getSkill(name) {
|
|
3084
|
+
return skillMap.get(name);
|
|
3085
|
+
},
|
|
3086
|
+
getSkillPath(name) {
|
|
3087
|
+
const entry = skillMap.get(name);
|
|
3088
|
+
if (entry) {
|
|
3089
|
+
return join4(root, entry.path);
|
|
3090
|
+
}
|
|
3091
|
+
return join4(root, "skills", name, "SKILL.md");
|
|
3092
|
+
},
|
|
3093
|
+
getSkillDir: getSkillDir2,
|
|
3094
|
+
readSkillContent(name) {
|
|
3095
|
+
const skillPath = library.getSkillPath(name);
|
|
3096
|
+
if (!existsSync11(skillPath)) {
|
|
3097
|
+
throw new Error(`Skill content not found: ${skillPath}`);
|
|
3098
|
+
}
|
|
3099
|
+
return readFileSync(skillPath, "utf-8");
|
|
3100
|
+
},
|
|
3101
|
+
getCoreSkills() {
|
|
3102
|
+
return entries.filter((e) => e.core);
|
|
3103
|
+
},
|
|
3104
|
+
getSkillsByCategory(category) {
|
|
3105
|
+
return entries.filter((e) => e.category === category);
|
|
3106
|
+
},
|
|
3107
|
+
getSkillDependencies(name) {
|
|
3108
|
+
return skillMap.get(name)?.dependencies ?? [];
|
|
3109
|
+
},
|
|
3110
|
+
resolveDependencyTree(names) {
|
|
3111
|
+
return resolveDeps(names);
|
|
3112
|
+
},
|
|
3113
|
+
listProfiles() {
|
|
3114
|
+
return [...profiles.keys()];
|
|
3115
|
+
},
|
|
3116
|
+
getProfile(name) {
|
|
3117
|
+
return profiles.get(name);
|
|
3118
|
+
},
|
|
3119
|
+
resolveProfile(name) {
|
|
3120
|
+
return resolveProfileByName(name);
|
|
3121
|
+
},
|
|
3122
|
+
listSharedResources() {
|
|
3123
|
+
return discoverFiles(join4(root, "skills", "_shared"), ".md");
|
|
3124
|
+
},
|
|
3125
|
+
getSharedResourcePath(name) {
|
|
3126
|
+
const resourcePath = join4(root, "skills", "_shared", `${name}.md`);
|
|
3127
|
+
return existsSync11(resourcePath) ? resourcePath : void 0;
|
|
3128
|
+
},
|
|
3129
|
+
readSharedResource(name) {
|
|
3130
|
+
const resourcePath = library.getSharedResourcePath(name);
|
|
3131
|
+
if (!resourcePath) return void 0;
|
|
3132
|
+
return readFileSync(resourcePath, "utf-8");
|
|
3133
|
+
},
|
|
3134
|
+
listProtocols() {
|
|
3135
|
+
const rootProtocols = discoverFiles(join4(root, "protocols"), ".md");
|
|
3136
|
+
if (rootProtocols.length > 0) return rootProtocols;
|
|
3137
|
+
return discoverFiles(join4(root, "skills", "protocols"), ".md");
|
|
3138
|
+
},
|
|
3139
|
+
getProtocolPath(name) {
|
|
3140
|
+
const rootPath = join4(root, "protocols", `${name}.md`);
|
|
3141
|
+
if (existsSync11(rootPath)) return rootPath;
|
|
3142
|
+
const skillsPath = join4(root, "skills", "protocols", `${name}.md`);
|
|
3143
|
+
return existsSync11(skillsPath) ? skillsPath : void 0;
|
|
3144
|
+
},
|
|
3145
|
+
readProtocol(name) {
|
|
3146
|
+
const protocolPath = library.getProtocolPath(name);
|
|
3147
|
+
if (!protocolPath) return void 0;
|
|
3148
|
+
return readFileSync(protocolPath, "utf-8");
|
|
3149
|
+
},
|
|
3150
|
+
validateSkillFrontmatter(name) {
|
|
3151
|
+
const entry = skillMap.get(name);
|
|
3152
|
+
if (!entry) {
|
|
3153
|
+
return {
|
|
3154
|
+
valid: false,
|
|
3155
|
+
issues: [{ level: "error", field: "name", message: `Skill not found: ${name}` }]
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
const issues = [];
|
|
3159
|
+
if (!entry.name) {
|
|
3160
|
+
issues.push({ level: "error", field: "name", message: "Missing name" });
|
|
3161
|
+
}
|
|
3162
|
+
if (!entry.description) {
|
|
3163
|
+
issues.push({ level: "error", field: "description", message: "Missing description" });
|
|
3164
|
+
}
|
|
3165
|
+
if (!entry.version) {
|
|
3166
|
+
issues.push({ level: "warn", field: "version", message: "Missing version" });
|
|
3167
|
+
}
|
|
3168
|
+
const skillPath = join4(root, entry.path);
|
|
3169
|
+
if (!existsSync11(skillPath)) {
|
|
3170
|
+
issues.push({
|
|
3171
|
+
level: "error",
|
|
3172
|
+
field: "path",
|
|
3173
|
+
message: `SKILL.md not found at ${entry.path}`
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
return {
|
|
3177
|
+
valid: !issues.some((i) => i.level === "error"),
|
|
3178
|
+
issues
|
|
3179
|
+
};
|
|
3180
|
+
},
|
|
3181
|
+
validateAll() {
|
|
3182
|
+
const results = /* @__PURE__ */ new Map();
|
|
3183
|
+
for (const entry of entries) {
|
|
3184
|
+
results.set(entry.name, library.validateSkillFrontmatter(entry.name));
|
|
3185
|
+
}
|
|
3186
|
+
return results;
|
|
3187
|
+
},
|
|
3188
|
+
getDispatchMatrix() {
|
|
3189
|
+
return manifest.dispatch_matrix;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
return library;
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
// src/core/skills/catalog.ts
|
|
3196
|
+
var catalog_exports = {};
|
|
3197
|
+
__export(catalog_exports, {
|
|
3198
|
+
clearRegisteredLibrary: () => clearRegisteredLibrary,
|
|
3199
|
+
getCoreSkills: () => getCoreSkills,
|
|
3200
|
+
getDispatchMatrix: () => getDispatchMatrix,
|
|
3201
|
+
getLibraryRoot: () => getLibraryRoot,
|
|
3202
|
+
getManifest: () => getManifest,
|
|
3203
|
+
getProfile: () => getProfile,
|
|
3204
|
+
getProtocolPath: () => getProtocolPath,
|
|
3205
|
+
getSharedResourcePath: () => getSharedResourcePath,
|
|
3206
|
+
getSkill: () => getSkill,
|
|
3207
|
+
getSkillDependencies: () => getSkillDependencies,
|
|
3208
|
+
getSkillDir: () => getSkillDir,
|
|
3209
|
+
getSkillPath: () => getSkillPath,
|
|
3210
|
+
getSkills: () => getSkills,
|
|
3211
|
+
getSkillsByCategory: () => getSkillsByCategory,
|
|
3212
|
+
getVersion: () => getVersion,
|
|
3213
|
+
isCatalogAvailable: () => isCatalogAvailable,
|
|
3214
|
+
listProfiles: () => listProfiles,
|
|
3215
|
+
listProtocols: () => listProtocols,
|
|
3216
|
+
listSharedResources: () => listSharedResources,
|
|
3217
|
+
listSkills: () => listSkills,
|
|
3218
|
+
readProtocol: () => readProtocol,
|
|
3219
|
+
readSharedResource: () => readSharedResource,
|
|
3220
|
+
readSkillContent: () => readSkillContent,
|
|
3221
|
+
registerSkillLibrary: () => registerSkillLibrary,
|
|
3222
|
+
registerSkillLibraryFromPath: () => registerSkillLibraryFromPath,
|
|
3223
|
+
resolveDependencyTree: () => resolveDependencyTree,
|
|
3224
|
+
resolveProfile: () => resolveProfile,
|
|
3225
|
+
validateAll: () => validateAll,
|
|
3226
|
+
validateSkillFrontmatter: () => validateSkillFrontmatter
|
|
3227
|
+
});
|
|
3228
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3229
|
+
import { join as join5 } from "path";
|
|
3230
|
+
var _library = null;
|
|
3231
|
+
function registerSkillLibrary(library) {
|
|
3232
|
+
_library = library;
|
|
3233
|
+
}
|
|
3234
|
+
function registerSkillLibraryFromPath(root) {
|
|
3235
|
+
const indexPath = join5(root, "index.js");
|
|
3236
|
+
if (existsSync12(indexPath)) {
|
|
3237
|
+
_library = loadLibraryFromModule(root);
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
_library = buildLibraryFromFiles(root);
|
|
3241
|
+
}
|
|
3242
|
+
function clearRegisteredLibrary() {
|
|
3243
|
+
_library = null;
|
|
3244
|
+
}
|
|
3245
|
+
function discoverLibrary() {
|
|
3246
|
+
const envPath = process.env["CAAMP_SKILL_LIBRARY"];
|
|
3247
|
+
if (envPath && existsSync12(envPath)) {
|
|
3248
|
+
try {
|
|
3249
|
+
const indexPath = join5(envPath, "index.js");
|
|
3250
|
+
if (existsSync12(indexPath)) {
|
|
3251
|
+
return loadLibraryFromModule(envPath);
|
|
3252
|
+
}
|
|
3253
|
+
if (existsSync12(join5(envPath, "skills.json"))) {
|
|
3254
|
+
return buildLibraryFromFiles(envPath);
|
|
3255
|
+
}
|
|
3256
|
+
} catch {
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
return null;
|
|
3260
|
+
}
|
|
3261
|
+
function getLibrary() {
|
|
3262
|
+
if (!_library) {
|
|
3263
|
+
const discovered = discoverLibrary();
|
|
3264
|
+
if (discovered) {
|
|
3265
|
+
_library = discovered;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
if (!_library) {
|
|
3269
|
+
throw new Error(
|
|
3270
|
+
"No skill library registered. Register one with registerSkillLibraryFromPath() or set the CAAMP_SKILL_LIBRARY environment variable."
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
return _library;
|
|
3274
|
+
}
|
|
3275
|
+
function isCatalogAvailable() {
|
|
3276
|
+
try {
|
|
3277
|
+
getLibrary();
|
|
3278
|
+
return true;
|
|
3279
|
+
} catch {
|
|
3280
|
+
return false;
|
|
3293
3281
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3282
|
+
}
|
|
3283
|
+
function getSkills() {
|
|
3284
|
+
return getLibrary().skills;
|
|
3285
|
+
}
|
|
3286
|
+
function getManifest() {
|
|
3287
|
+
return getLibrary().manifest;
|
|
3288
|
+
}
|
|
3289
|
+
function listSkills() {
|
|
3290
|
+
return getLibrary().listSkills();
|
|
3291
|
+
}
|
|
3292
|
+
function getSkill(name) {
|
|
3293
|
+
return getLibrary().getSkill(name);
|
|
3294
|
+
}
|
|
3295
|
+
function getSkillPath(name) {
|
|
3296
|
+
return getLibrary().getSkillPath(name);
|
|
3297
|
+
}
|
|
3298
|
+
function getSkillDir(name) {
|
|
3299
|
+
return getLibrary().getSkillDir(name);
|
|
3300
|
+
}
|
|
3301
|
+
function readSkillContent(name) {
|
|
3302
|
+
return getLibrary().readSkillContent(name);
|
|
3303
|
+
}
|
|
3304
|
+
function getCoreSkills() {
|
|
3305
|
+
return getLibrary().getCoreSkills();
|
|
3306
|
+
}
|
|
3307
|
+
function getSkillsByCategory(category) {
|
|
3308
|
+
return getLibrary().getSkillsByCategory(category);
|
|
3309
|
+
}
|
|
3310
|
+
function getSkillDependencies(name) {
|
|
3311
|
+
return getLibrary().getSkillDependencies(name);
|
|
3312
|
+
}
|
|
3313
|
+
function resolveDependencyTree(names) {
|
|
3314
|
+
return getLibrary().resolveDependencyTree(names);
|
|
3315
|
+
}
|
|
3316
|
+
function listProfiles() {
|
|
3317
|
+
return getLibrary().listProfiles();
|
|
3318
|
+
}
|
|
3319
|
+
function getProfile(name) {
|
|
3320
|
+
return getLibrary().getProfile(name);
|
|
3321
|
+
}
|
|
3322
|
+
function resolveProfile(name) {
|
|
3323
|
+
return getLibrary().resolveProfile(name);
|
|
3324
|
+
}
|
|
3325
|
+
function listSharedResources() {
|
|
3326
|
+
return getLibrary().listSharedResources();
|
|
3327
|
+
}
|
|
3328
|
+
function getSharedResourcePath(name) {
|
|
3329
|
+
return getLibrary().getSharedResourcePath(name);
|
|
3330
|
+
}
|
|
3331
|
+
function readSharedResource(name) {
|
|
3332
|
+
return getLibrary().readSharedResource(name);
|
|
3333
|
+
}
|
|
3334
|
+
function listProtocols() {
|
|
3335
|
+
return getLibrary().listProtocols();
|
|
3336
|
+
}
|
|
3337
|
+
function getProtocolPath(name) {
|
|
3338
|
+
return getLibrary().getProtocolPath(name);
|
|
3339
|
+
}
|
|
3340
|
+
function readProtocol(name) {
|
|
3341
|
+
return getLibrary().readProtocol(name);
|
|
3342
|
+
}
|
|
3343
|
+
function validateSkillFrontmatter(name) {
|
|
3344
|
+
return getLibrary().validateSkillFrontmatter(name);
|
|
3345
|
+
}
|
|
3346
|
+
function validateAll() {
|
|
3347
|
+
return getLibrary().validateAll();
|
|
3348
|
+
}
|
|
3349
|
+
function getDispatchMatrix() {
|
|
3350
|
+
return getLibrary().getDispatchMatrix();
|
|
3351
|
+
}
|
|
3352
|
+
function getVersion() {
|
|
3353
|
+
return getLibrary().version;
|
|
3354
|
+
}
|
|
3355
|
+
function getLibraryRoot() {
|
|
3356
|
+
return getLibrary().libraryRoot;
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
// src/core/skills/discovery.ts
|
|
3360
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3361
|
+
import { readdir, readFile as readFile7 } from "fs/promises";
|
|
3362
|
+
import { join as join6 } from "path";
|
|
3363
|
+
import matter from "gray-matter";
|
|
3364
|
+
async function parseSkillFile(filePath) {
|
|
3365
|
+
try {
|
|
3366
|
+
const content = await readFile7(filePath, "utf-8");
|
|
3367
|
+
const { data } = matter(content);
|
|
3368
|
+
if (!data.name || !data.description) {
|
|
3369
|
+
return null;
|
|
3311
3370
|
}
|
|
3371
|
+
const allowedTools = data["allowed-tools"] ?? data.allowedTools;
|
|
3372
|
+
return {
|
|
3373
|
+
name: String(data.name),
|
|
3374
|
+
description: String(data.description),
|
|
3375
|
+
license: data.license ? String(data.license) : void 0,
|
|
3376
|
+
compatibility: data.compatibility ? String(data.compatibility) : void 0,
|
|
3377
|
+
metadata: data.metadata,
|
|
3378
|
+
allowedTools: typeof allowedTools === "string" ? allowedTools.split(/\s+/) : Array.isArray(allowedTools) ? allowedTools.map(String) : void 0,
|
|
3379
|
+
version: data.version ? String(data.version) : void 0
|
|
3380
|
+
};
|
|
3381
|
+
} catch {
|
|
3382
|
+
return null;
|
|
3312
3383
|
}
|
|
3313
|
-
const totalPenalty = findings.reduce(
|
|
3314
|
-
(sum, f) => sum + (SEVERITY_WEIGHTS[f.rule.severity] ?? 0),
|
|
3315
|
-
0
|
|
3316
|
-
);
|
|
3317
|
-
const score = Math.max(0, 100 - totalPenalty);
|
|
3318
|
-
const passed = !findings.some((f) => f.rule.severity === "critical" || f.rule.severity === "high");
|
|
3319
|
-
return { file: filePath, findings, score, passed };
|
|
3320
3384
|
}
|
|
3321
|
-
async function
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3385
|
+
async function discoverSkill(skillDir) {
|
|
3386
|
+
const skillFile = join6(skillDir, "SKILL.md");
|
|
3387
|
+
if (!existsSync13(skillFile)) return null;
|
|
3388
|
+
const metadata = await parseSkillFile(skillFile);
|
|
3389
|
+
if (!metadata) return null;
|
|
3390
|
+
return {
|
|
3391
|
+
name: metadata.name,
|
|
3392
|
+
scopedName: metadata.name,
|
|
3393
|
+
path: skillDir,
|
|
3394
|
+
metadata
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
async function discoverSkills(rootDir) {
|
|
3398
|
+
if (!existsSync13(rootDir)) return [];
|
|
3399
|
+
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
3400
|
+
const skills = [];
|
|
3327
3401
|
for (const entry of entries) {
|
|
3328
|
-
if (entry.isDirectory()
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3402
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3403
|
+
const skillDir = join6(rootDir, entry.name);
|
|
3404
|
+
const skill = await discoverSkill(skillDir);
|
|
3405
|
+
if (skill) {
|
|
3406
|
+
skills.push(skill);
|
|
3333
3407
|
}
|
|
3334
3408
|
}
|
|
3335
|
-
return
|
|
3409
|
+
return skills;
|
|
3336
3410
|
}
|
|
3337
|
-
function
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
version: "0.1.0",
|
|
3347
|
-
rules: AUDIT_RULES.map((r) => ({
|
|
3348
|
-
id: r.id,
|
|
3349
|
-
name: r.name,
|
|
3350
|
-
shortDescription: { text: r.description },
|
|
3351
|
-
defaultConfiguration: {
|
|
3352
|
-
level: r.severity === "critical" || r.severity === "high" ? "error" : "warning"
|
|
3353
|
-
},
|
|
3354
|
-
properties: { category: r.category }
|
|
3355
|
-
}))
|
|
3356
|
-
}
|
|
3357
|
-
},
|
|
3358
|
-
results: results.flatMap(
|
|
3359
|
-
(result) => result.findings.map((f) => ({
|
|
3360
|
-
ruleId: f.rule.id,
|
|
3361
|
-
level: f.rule.severity === "critical" || f.rule.severity === "high" ? "error" : "warning",
|
|
3362
|
-
message: { text: `${f.rule.description}: ${f.match}` },
|
|
3363
|
-
locations: [
|
|
3364
|
-
{
|
|
3365
|
-
physicalLocation: {
|
|
3366
|
-
artifactLocation: { uri: result.file },
|
|
3367
|
-
region: {
|
|
3368
|
-
startLine: f.line,
|
|
3369
|
-
startColumn: f.column
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
]
|
|
3374
|
-
}))
|
|
3375
|
-
)
|
|
3411
|
+
async function discoverSkillsMulti(dirs) {
|
|
3412
|
+
const all = [];
|
|
3413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3414
|
+
for (const dir of dirs) {
|
|
3415
|
+
const skills = await discoverSkills(dir);
|
|
3416
|
+
for (const skill of skills) {
|
|
3417
|
+
if (!seen.has(skill.name)) {
|
|
3418
|
+
seen.add(skill.name);
|
|
3419
|
+
all.push(skill);
|
|
3376
3420
|
}
|
|
3377
|
-
|
|
3378
|
-
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
return all;
|
|
3379
3424
|
}
|
|
3380
3425
|
|
|
3381
3426
|
// src/core/skills/validator.ts
|
|
3382
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
3383
3427
|
import { existsSync as existsSync14 } from "fs";
|
|
3428
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
3384
3429
|
import matter2 from "gray-matter";
|
|
3385
3430
|
var RESERVED_NAMES = [
|
|
3386
3431
|
"anthropic",
|
|
@@ -3465,7 +3510,11 @@ async function validateSkill(filePath) {
|
|
|
3465
3510
|
}
|
|
3466
3511
|
}
|
|
3467
3512
|
if (!data.description) {
|
|
3468
|
-
issues.push({
|
|
3513
|
+
issues.push({
|
|
3514
|
+
level: "error",
|
|
3515
|
+
field: "description",
|
|
3516
|
+
message: "Missing required field: description"
|
|
3517
|
+
});
|
|
3469
3518
|
} else {
|
|
3470
3519
|
const desc = String(data.description);
|
|
3471
3520
|
if (desc.length > MAX_DESCRIPTION_LENGTH) {
|
|
@@ -3514,15 +3563,15 @@ async function validateSkill(filePath) {
|
|
|
3514
3563
|
}
|
|
3515
3564
|
|
|
3516
3565
|
export {
|
|
3517
|
-
deepMerge,
|
|
3518
|
-
getNestedValue,
|
|
3519
|
-
ensureDir,
|
|
3520
3566
|
setVerbose,
|
|
3521
3567
|
setQuiet,
|
|
3522
3568
|
isVerbose,
|
|
3523
3569
|
isQuiet,
|
|
3524
3570
|
setHuman,
|
|
3525
3571
|
isHuman,
|
|
3572
|
+
deepMerge,
|
|
3573
|
+
getNestedValue,
|
|
3574
|
+
ensureDir,
|
|
3526
3575
|
readConfig,
|
|
3527
3576
|
writeConfig,
|
|
3528
3577
|
removeConfig,
|
|
@@ -3568,8 +3617,25 @@ export {
|
|
|
3568
3617
|
reconcileCleoLock,
|
|
3569
3618
|
parseSource,
|
|
3570
3619
|
isMarketplaceScoped,
|
|
3620
|
+
scanFile,
|
|
3621
|
+
scanDirectory,
|
|
3622
|
+
toSarif,
|
|
3623
|
+
recordSkillInstall,
|
|
3624
|
+
removeSkillFromLock,
|
|
3625
|
+
getTrackedSkills,
|
|
3626
|
+
checkSkillUpdate,
|
|
3627
|
+
checkAllSkillUpdates,
|
|
3571
3628
|
formatNetworkError,
|
|
3572
3629
|
MarketplaceClient,
|
|
3630
|
+
RECOMMENDATION_ERROR_CODES,
|
|
3631
|
+
tokenizeCriteriaValue,
|
|
3632
|
+
validateRecommendationCriteria,
|
|
3633
|
+
normalizeRecommendationCriteria,
|
|
3634
|
+
scoreSkillRecommendation,
|
|
3635
|
+
rankSkills,
|
|
3636
|
+
formatSkillRecommendations,
|
|
3637
|
+
searchSkills,
|
|
3638
|
+
recommendSkills2 as recommendSkills,
|
|
3573
3639
|
loadLibraryFromModule,
|
|
3574
3640
|
buildLibraryFromFiles,
|
|
3575
3641
|
registerSkillLibrary,
|
|
@@ -3586,23 +3652,6 @@ export {
|
|
|
3586
3652
|
discoverSkill,
|
|
3587
3653
|
discoverSkills,
|
|
3588
3654
|
discoverSkillsMulti,
|
|
3589
|
-
recordSkillInstall,
|
|
3590
|
-
removeSkillFromLock,
|
|
3591
|
-
getTrackedSkills,
|
|
3592
|
-
checkSkillUpdate,
|
|
3593
|
-
checkAllSkillUpdates,
|
|
3594
|
-
RECOMMENDATION_ERROR_CODES,
|
|
3595
|
-
tokenizeCriteriaValue,
|
|
3596
|
-
validateRecommendationCriteria,
|
|
3597
|
-
normalizeRecommendationCriteria,
|
|
3598
|
-
scoreSkillRecommendation,
|
|
3599
|
-
rankSkills,
|
|
3600
|
-
formatSkillRecommendations,
|
|
3601
|
-
searchSkills,
|
|
3602
|
-
recommendSkills2 as recommendSkills,
|
|
3603
|
-
scanFile,
|
|
3604
|
-
scanDirectory,
|
|
3605
|
-
toSarif,
|
|
3606
3655
|
validateSkill
|
|
3607
3656
|
};
|
|
3608
|
-
//# sourceMappingURL=chunk-
|
|
3657
|
+
//# sourceMappingURL=chunk-3WKBFXLE.js.map
|