guardskills 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/cli.cjs +524 -109
- package/dist/cli.js +524 -109
- package/package.json +2 -1
package/dist/cli.cjs
CHANGED
|
@@ -200,9 +200,13 @@ function enforceSourcePolicy(repoInput, policy) {
|
|
|
200
200
|
|
|
201
201
|
// src/install/skills.ts
|
|
202
202
|
var import_execa = require("execa");
|
|
203
|
-
async function
|
|
203
|
+
async function runProviderInstall(provider, repo, skill) {
|
|
204
|
+
if (provider === "playbooks" && !skill) {
|
|
205
|
+
return 30;
|
|
206
|
+
}
|
|
207
|
+
const args = provider === "skills" ? skill ? ["skills", "add", repo, "--skill", skill] : ["skills", "add", repo] : provider === "playbooks" ? ["playbooks", "add", "skill", repo, "--skill", skill] : provider === "skillkit" ? skill ? ["skillkit", "install", repo, "--skill", skill] : ["skillkit", "install", repo] : skill ? ["openskills", "install", repo, skill] : ["openskills", "install", repo];
|
|
204
208
|
try {
|
|
205
|
-
await (0, import_execa.execa)("npx",
|
|
209
|
+
await (0, import_execa.execa)("npx", args, {
|
|
206
210
|
stdio: "inherit"
|
|
207
211
|
});
|
|
208
212
|
return 0;
|
|
@@ -1067,6 +1071,22 @@ var cliAddOptionsSchema = import_zod2.z.object({
|
|
|
1067
1071
|
maxAuxFiles: import_zod2.z.coerce.number().int().min(1).max(200).optional(),
|
|
1068
1072
|
maxTotalFiles: import_zod2.z.coerce.number().int().min(1).max(400).optional()
|
|
1069
1073
|
});
|
|
1074
|
+
var cliBulkAddOptionsSchema = import_zod2.z.object({
|
|
1075
|
+
config: import_zod2.z.string().optional(),
|
|
1076
|
+
strict: import_zod2.z.boolean().optional(),
|
|
1077
|
+
ci: import_zod2.z.boolean().optional(),
|
|
1078
|
+
json: import_zod2.z.boolean().optional(),
|
|
1079
|
+
yes: import_zod2.z.boolean().optional(),
|
|
1080
|
+
dryRun: import_zod2.z.boolean().optional(),
|
|
1081
|
+
force: import_zod2.z.boolean().optional(),
|
|
1082
|
+
allowUnverifiable: import_zod2.z.boolean().optional(),
|
|
1083
|
+
githubTimeoutMs: import_zod2.z.coerce.number().int().min(1e3).max(12e4).optional(),
|
|
1084
|
+
githubRetries: import_zod2.z.coerce.number().int().min(0).max(6).optional(),
|
|
1085
|
+
githubRetryBaseMs: import_zod2.z.coerce.number().int().min(50).max(5e3).optional(),
|
|
1086
|
+
maxFileBytes: import_zod2.z.coerce.number().int().min(4096).max(5e6).optional(),
|
|
1087
|
+
maxAuxFiles: import_zod2.z.coerce.number().int().min(1).max(200).optional(),
|
|
1088
|
+
maxTotalFiles: import_zod2.z.coerce.number().int().min(1).max(400).optional()
|
|
1089
|
+
});
|
|
1070
1090
|
var effectiveAddOptionsSchema = import_zod2.z.object({
|
|
1071
1091
|
skill: import_zod2.z.string().min(1),
|
|
1072
1092
|
strict: import_zod2.z.boolean(),
|
|
@@ -1186,9 +1206,11 @@ function evaluateGate(level, options) {
|
|
|
1186
1206
|
gateNote: level === "WARNING" ? "WARNING accepted via --yes." : "SAFE to proceed."
|
|
1187
1207
|
};
|
|
1188
1208
|
}
|
|
1189
|
-
async function runAddCommand(repo, rawOptions) {
|
|
1209
|
+
async function runAddCommand(repo, rawOptions, context = {}) {
|
|
1190
1210
|
const cliOptions = cliAddOptionsSchema.parse(rawOptions);
|
|
1191
1211
|
const loadedConfig = loadGuardSkillsConfig(cliOptions.config);
|
|
1212
|
+
const provider = context.provider ?? "skills";
|
|
1213
|
+
const commandName = context.commandName ?? "guardskills add";
|
|
1192
1214
|
const options = resolveEffectiveAddOptions(cliOptions, loadedConfig.config);
|
|
1193
1215
|
enforceSourcePolicy(repo, loadedConfig.config.policy);
|
|
1194
1216
|
enforceOptionPolicy(options, loadedConfig.config);
|
|
@@ -1209,7 +1231,7 @@ async function runAddCommand(repo, rawOptions) {
|
|
|
1209
1231
|
const gate = evaluateGate(decision.level, options);
|
|
1210
1232
|
const configNote = loadedConfig.path ? ` Config: ${loadedConfig.path}` : "";
|
|
1211
1233
|
const report = {
|
|
1212
|
-
command:
|
|
1234
|
+
command: commandName,
|
|
1213
1235
|
repo,
|
|
1214
1236
|
skill: options.skill,
|
|
1215
1237
|
strict: options.strict,
|
|
@@ -1234,14 +1256,353 @@ async function runAddCommand(repo, rawOptions) {
|
|
|
1234
1256
|
if (options.dryRun || options.ci) {
|
|
1235
1257
|
return 0;
|
|
1236
1258
|
}
|
|
1237
|
-
return
|
|
1259
|
+
return runProviderInstall(provider, repo, options.skill);
|
|
1238
1260
|
}
|
|
1239
1261
|
|
|
1240
|
-
// src/commands/
|
|
1262
|
+
// src/commands/openskills-install.ts
|
|
1263
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
1264
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
1265
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
1266
|
+
var import_execa2 = require("execa");
|
|
1241
1267
|
var import_zod3 = require("zod");
|
|
1268
|
+
var ALLOWED_TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
1269
|
+
".md",
|
|
1270
|
+
".txt",
|
|
1271
|
+
".sh",
|
|
1272
|
+
".bash",
|
|
1273
|
+
".zsh",
|
|
1274
|
+
".ps1",
|
|
1275
|
+
".py",
|
|
1276
|
+
".js",
|
|
1277
|
+
".ts",
|
|
1278
|
+
".mjs",
|
|
1279
|
+
".cjs",
|
|
1280
|
+
".json",
|
|
1281
|
+
".yaml",
|
|
1282
|
+
".yml",
|
|
1283
|
+
".toml",
|
|
1284
|
+
".ini",
|
|
1285
|
+
".cfg"
|
|
1286
|
+
]);
|
|
1287
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".turbo"]);
|
|
1288
|
+
var cliOpenSkillsOptionsSchema = import_zod3.z.object({
|
|
1289
|
+
config: import_zod3.z.string().optional(),
|
|
1290
|
+
strict: import_zod3.z.boolean().optional(),
|
|
1291
|
+
ci: import_zod3.z.boolean().optional(),
|
|
1292
|
+
json: import_zod3.z.boolean().optional(),
|
|
1293
|
+
yes: import_zod3.z.boolean().optional(),
|
|
1294
|
+
dryRun: import_zod3.z.boolean().optional(),
|
|
1295
|
+
force: import_zod3.z.boolean().optional(),
|
|
1296
|
+
allowUnverifiable: import_zod3.z.boolean().optional(),
|
|
1297
|
+
githubTimeoutMs: import_zod3.z.coerce.number().int().min(1e3).max(12e4).optional(),
|
|
1298
|
+
githubRetries: import_zod3.z.coerce.number().int().min(0).max(6).optional(),
|
|
1299
|
+
githubRetryBaseMs: import_zod3.z.coerce.number().int().min(50).max(5e3).optional(),
|
|
1300
|
+
maxFileBytes: import_zod3.z.coerce.number().int().min(4096).max(5e6).optional(),
|
|
1301
|
+
maxAuxFiles: import_zod3.z.coerce.number().int().min(1).max(200).optional(),
|
|
1302
|
+
maxTotalFiles: import_zod3.z.coerce.number().int().min(1).max(400).optional()
|
|
1303
|
+
});
|
|
1304
|
+
var DEFAULT_OPTIONS2 = {
|
|
1305
|
+
strict: false,
|
|
1306
|
+
ci: false,
|
|
1307
|
+
json: false,
|
|
1308
|
+
yes: false,
|
|
1309
|
+
dryRun: false,
|
|
1310
|
+
force: false,
|
|
1311
|
+
allowUnverifiable: false,
|
|
1312
|
+
githubTimeoutMs: 15e3,
|
|
1313
|
+
githubRetries: 2,
|
|
1314
|
+
githubRetryBaseMs: 300,
|
|
1315
|
+
maxFileBytes: 25e4,
|
|
1316
|
+
maxAuxFiles: 40,
|
|
1317
|
+
maxTotalFiles: 120
|
|
1318
|
+
};
|
|
1319
|
+
function resolveEffectiveOptions(cliOptions, config) {
|
|
1320
|
+
const defaults = config.defaults ?? {};
|
|
1321
|
+
const resolver = config.resolver ?? {};
|
|
1322
|
+
return {
|
|
1323
|
+
strict: cliOptions.strict ?? defaults.strict ?? DEFAULT_OPTIONS2.strict,
|
|
1324
|
+
ci: cliOptions.ci ?? defaults.ci ?? DEFAULT_OPTIONS2.ci,
|
|
1325
|
+
json: cliOptions.json ?? defaults.json ?? DEFAULT_OPTIONS2.json,
|
|
1326
|
+
yes: cliOptions.yes ?? defaults.yes ?? DEFAULT_OPTIONS2.yes,
|
|
1327
|
+
dryRun: cliOptions.dryRun ?? defaults.dryRun ?? DEFAULT_OPTIONS2.dryRun,
|
|
1328
|
+
force: cliOptions.force ?? defaults.force ?? DEFAULT_OPTIONS2.force,
|
|
1329
|
+
allowUnverifiable: cliOptions.allowUnverifiable ?? defaults.allowUnverifiable ?? DEFAULT_OPTIONS2.allowUnverifiable,
|
|
1330
|
+
githubTimeoutMs: cliOptions.githubTimeoutMs ?? resolver.githubTimeoutMs ?? DEFAULT_OPTIONS2.githubTimeoutMs,
|
|
1331
|
+
githubRetries: cliOptions.githubRetries ?? resolver.githubRetries ?? DEFAULT_OPTIONS2.githubRetries,
|
|
1332
|
+
githubRetryBaseMs: cliOptions.githubRetryBaseMs ?? resolver.githubRetryBaseMs ?? DEFAULT_OPTIONS2.githubRetryBaseMs,
|
|
1333
|
+
maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ?? DEFAULT_OPTIONS2.maxFileBytes,
|
|
1334
|
+
maxAuxFiles: cliOptions.maxAuxFiles ?? resolver.maxAuxFiles ?? DEFAULT_OPTIONS2.maxAuxFiles,
|
|
1335
|
+
maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ?? DEFAULT_OPTIONS2.maxTotalFiles
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
function enforceOptionPolicy2(options, config) {
|
|
1339
|
+
const policy = config.policy;
|
|
1340
|
+
if (!policy) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (options.force && policy.allowForce === false) {
|
|
1344
|
+
throw new GuardSkillsError(
|
|
1345
|
+
"POLICY_VIOLATION",
|
|
1346
|
+
"Policy blocks --force overrides (allowForce=false)."
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
if (options.allowUnverifiable && policy.allowUnverifiableOverride === false) {
|
|
1350
|
+
throw new GuardSkillsError(
|
|
1351
|
+
"POLICY_VIOLATION",
|
|
1352
|
+
"Policy blocks --allow-unverifiable overrides (allowUnverifiableOverride=false)."
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function findSkillDirs(rootDir) {
|
|
1357
|
+
const found = /* @__PURE__ */ new Set();
|
|
1358
|
+
const stack = [{ dir: rootDir, depth: 0 }];
|
|
1359
|
+
while (stack.length > 0) {
|
|
1360
|
+
const current = stack.pop();
|
|
1361
|
+
if (!current) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const skillFile = import_node_path4.default.join(current.dir, "SKILL.md");
|
|
1365
|
+
if (import_node_fs2.default.existsSync(skillFile) && import_node_fs2.default.statSync(skillFile).isFile()) {
|
|
1366
|
+
found.add(current.dir);
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (current.depth >= 8) {
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
let entries;
|
|
1373
|
+
try {
|
|
1374
|
+
entries = import_node_fs2.default.readdirSync(current.dir, { withFileTypes: true });
|
|
1375
|
+
} catch {
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
for (const entry of entries) {
|
|
1379
|
+
if (!entry.isDirectory()) {
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
if (SKIP_DIRS.has(entry.name)) {
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
stack.push({ dir: import_node_path4.default.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
return [...found].sort();
|
|
1389
|
+
}
|
|
1390
|
+
function collectLocalFiles(skillDir, options) {
|
|
1391
|
+
const files = [];
|
|
1392
|
+
const unverifiableReasons = [];
|
|
1393
|
+
const stack = [skillDir];
|
|
1394
|
+
while (stack.length > 0) {
|
|
1395
|
+
const currentDir = stack.pop();
|
|
1396
|
+
if (!currentDir) {
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
let entries;
|
|
1400
|
+
try {
|
|
1401
|
+
entries = import_node_fs2.default.readdirSync(currentDir, { withFileTypes: true });
|
|
1402
|
+
} catch {
|
|
1403
|
+
unverifiableReasons.push(`Cannot read directory: ${currentDir}`);
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
for (const entry of entries) {
|
|
1407
|
+
const fullPath = import_node_path4.default.join(currentDir, entry.name);
|
|
1408
|
+
if (entry.isDirectory()) {
|
|
1409
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
1410
|
+
stack.push(fullPath);
|
|
1411
|
+
}
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (!entry.isFile()) {
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
const relativePath = import_node_path4.default.relative(skillDir, fullPath).replace(/\\/g, "/");
|
|
1418
|
+
const ext = import_node_path4.default.extname(relativePath).toLowerCase();
|
|
1419
|
+
const isSkillFile2 = import_node_path4.default.basename(relativePath).toLowerCase() === "skill.md";
|
|
1420
|
+
if (!isSkillFile2 && !ALLOWED_TEXT_EXTENSIONS2.has(ext)) {
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
if (files.length >= options.maxTotalFiles) {
|
|
1424
|
+
unverifiableReasons.push(
|
|
1425
|
+
`Reached maxTotalFiles=${options.maxTotalFiles}. Remaining files were not scanned.`
|
|
1426
|
+
);
|
|
1427
|
+
return { files, unverifiableReasons };
|
|
1428
|
+
}
|
|
1429
|
+
let sizeBytes = 0;
|
|
1430
|
+
try {
|
|
1431
|
+
sizeBytes = import_node_fs2.default.statSync(fullPath).size;
|
|
1432
|
+
} catch {
|
|
1433
|
+
unverifiableReasons.push(`Cannot stat file: ${relativePath}`);
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
if (sizeBytes > options.maxFileBytes) {
|
|
1437
|
+
unverifiableReasons.push(
|
|
1438
|
+
`Skipped oversized file (${sizeBytes} bytes > ${options.maxFileBytes}): ${relativePath}`
|
|
1439
|
+
);
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
try {
|
|
1443
|
+
files.push({
|
|
1444
|
+
path: relativePath,
|
|
1445
|
+
content: import_node_fs2.default.readFileSync(fullPath, "utf8")
|
|
1446
|
+
});
|
|
1447
|
+
} catch {
|
|
1448
|
+
unverifiableReasons.push(`Cannot read text content: ${relativePath}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return { files, unverifiableReasons };
|
|
1453
|
+
}
|
|
1454
|
+
function resolveSource(source) {
|
|
1455
|
+
const maybePath = import_node_path4.default.resolve(source);
|
|
1456
|
+
if (import_node_fs2.default.existsSync(maybePath)) {
|
|
1457
|
+
return { kind: "local", path: maybePath };
|
|
1458
|
+
}
|
|
1459
|
+
const shorthand = source.match(/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/);
|
|
1460
|
+
if (shorthand) {
|
|
1461
|
+
return { kind: "git", cloneUrl: `https://github.com/${shorthand[1]}/${shorthand[2]}.git` };
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
const parsed = new URL(source);
|
|
1465
|
+
if (parsed.hostname === "github.com" || parsed.hostname === "www.github.com") {
|
|
1466
|
+
return { kind: "git", cloneUrl: source.endsWith(".git") ? source : `${source}.git` };
|
|
1467
|
+
}
|
|
1468
|
+
} catch {
|
|
1469
|
+
}
|
|
1470
|
+
return { kind: "git", cloneUrl: source };
|
|
1471
|
+
}
|
|
1472
|
+
function aggregateLevel(levels) {
|
|
1473
|
+
if (levels.includes("UNVERIFIABLE")) {
|
|
1474
|
+
return "UNVERIFIABLE";
|
|
1475
|
+
}
|
|
1476
|
+
if (levels.includes("CRITICAL")) {
|
|
1477
|
+
return "CRITICAL";
|
|
1478
|
+
}
|
|
1479
|
+
if (levels.includes("UNSAFE")) {
|
|
1480
|
+
return "UNSAFE";
|
|
1481
|
+
}
|
|
1482
|
+
if (levels.includes("WARNING")) {
|
|
1483
|
+
return "WARNING";
|
|
1484
|
+
}
|
|
1485
|
+
return "SAFE";
|
|
1486
|
+
}
|
|
1487
|
+
async function runInteractiveInstallCommand(provider, source, skillName, rawOptions) {
|
|
1488
|
+
if (provider !== "openskills" && provider !== "skills" && provider !== "skillkit") {
|
|
1489
|
+
throw new GuardSkillsError(
|
|
1490
|
+
"INVALID_OPTIONS",
|
|
1491
|
+
`Interactive install flow is not supported for provider '${provider}'.`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
const cliOptions = cliOpenSkillsOptionsSchema.parse(rawOptions);
|
|
1495
|
+
const loadedConfig = loadGuardSkillsConfig(cliOptions.config);
|
|
1496
|
+
const options = resolveEffectiveOptions(cliOptions, loadedConfig.config);
|
|
1497
|
+
enforceSourcePolicy(source, loadedConfig.config.policy);
|
|
1498
|
+
enforceOptionPolicy2(options, loadedConfig.config);
|
|
1499
|
+
const resolvedSource = resolveSource(source);
|
|
1500
|
+
const tempDir = resolvedSource.kind === "git" ? import_node_fs2.default.mkdtempSync(import_node_path4.default.join(import_node_os.default.tmpdir(), `guardskills-${provider}-`)) : null;
|
|
1501
|
+
const scanRoot = resolvedSource.kind === "git" ? tempDir ?? "" : resolvedSource.path;
|
|
1502
|
+
try {
|
|
1503
|
+
if (resolvedSource.kind === "git") {
|
|
1504
|
+
try {
|
|
1505
|
+
await (0, import_execa2.execa)("git", ["clone", "--depth", "1", resolvedSource.cloneUrl, scanRoot], {
|
|
1506
|
+
timeout: options.githubTimeoutMs
|
|
1507
|
+
});
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1510
|
+
throw new GuardSkillsError(
|
|
1511
|
+
"GITHUB_UNKNOWN",
|
|
1512
|
+
`Failed to clone source '${source}' for ${provider} scan: ${message}`,
|
|
1513
|
+
{ cause: error }
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const discovered = findSkillDirs(scanRoot);
|
|
1518
|
+
if (discovered.length === 0) {
|
|
1519
|
+
throw new GuardSkillsError("SKILL_NOT_FOUND", "No SKILL.md files were found in the source.");
|
|
1520
|
+
}
|
|
1521
|
+
const selectedDirs = skillName ? discovered.filter((dir) => import_node_path4.default.basename(dir).toLowerCase() === skillName.toLowerCase()) : discovered;
|
|
1522
|
+
if (selectedDirs.length === 0) {
|
|
1523
|
+
throw new GuardSkillsError(
|
|
1524
|
+
"SKILL_NOT_FOUND",
|
|
1525
|
+
`Skill '${skillName}' was not found. Available: ${discovered.map((dir) => import_node_path4.default.basename(dir)).join(", ")}`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
const levels = [];
|
|
1529
|
+
const summaries = [];
|
|
1530
|
+
for (const skillDir of selectedDirs) {
|
|
1531
|
+
const skill = import_node_path4.default.basename(skillDir);
|
|
1532
|
+
const { files, unverifiableReasons } = collectLocalFiles(skillDir, options);
|
|
1533
|
+
const resolvedSkill = {
|
|
1534
|
+
source: `local:${skillDir}`,
|
|
1535
|
+
owner: "local",
|
|
1536
|
+
repo: "local",
|
|
1537
|
+
defaultBranch: "local",
|
|
1538
|
+
commitSha: "local",
|
|
1539
|
+
skillName: skill,
|
|
1540
|
+
skillDir: skillDir.replace(/\\/g, "/"),
|
|
1541
|
+
skillFilePath: "SKILL.md",
|
|
1542
|
+
files,
|
|
1543
|
+
unverifiableReasons
|
|
1544
|
+
};
|
|
1545
|
+
const scan = scanResolvedSkill(resolvedSkill);
|
|
1546
|
+
const decision = calculateRiskScore(scan.findings, {
|
|
1547
|
+
strict: options.strict,
|
|
1548
|
+
trustCredits: 0,
|
|
1549
|
+
hasUnverifiableContent: scan.hasUnverifiableContent
|
|
1550
|
+
});
|
|
1551
|
+
levels.push(decision.level);
|
|
1552
|
+
summaries.push({ skill, level: decision.level, riskScore: decision.riskScore });
|
|
1553
|
+
}
|
|
1554
|
+
const overall = aggregateLevel(levels);
|
|
1555
|
+
const gate = evaluateGate(overall, { ...options, skill: skillName ?? "ALL_SKILLS" });
|
|
1556
|
+
const label = provider === "openskills" ? "OpenSkills" : provider === "skillkit" ? "skillkit" : "skills.sh";
|
|
1557
|
+
const note = `${gate.gateNote} ${label} flow: ${skillName ? "single skill" : "interactive skill selection"}.`;
|
|
1558
|
+
if (options.json) {
|
|
1559
|
+
console.log(
|
|
1560
|
+
JSON.stringify(
|
|
1561
|
+
{
|
|
1562
|
+
command: `guardskills ${provider} ${provider === "skills" ? "add" : "install"}`,
|
|
1563
|
+
source,
|
|
1564
|
+
skill: skillName ?? null,
|
|
1565
|
+
scannedSkills: summaries.length,
|
|
1566
|
+
overallLevel: overall,
|
|
1567
|
+
skills: summaries,
|
|
1568
|
+
note
|
|
1569
|
+
},
|
|
1570
|
+
null,
|
|
1571
|
+
2
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
} else {
|
|
1575
|
+
console.log(`Command: guardskills ${provider} ${provider === "skills" ? "add" : "install"}`);
|
|
1576
|
+
console.log(`Source: ${source}`);
|
|
1577
|
+
console.log(`Selection: ${skillName ?? "interactive (all scanned)"}`);
|
|
1578
|
+
console.log(`Scanned Skills: ${summaries.length}`);
|
|
1579
|
+
console.log(`Decision: ${overall}`);
|
|
1580
|
+
console.log("Per-skill results:");
|
|
1581
|
+
for (const summary of summaries) {
|
|
1582
|
+
const score = summary.riskScore === null ? "n/a" : summary.riskScore.toFixed(1);
|
|
1583
|
+
console.log(`- ${summary.skill}: ${summary.level} (risk ${score})`);
|
|
1584
|
+
}
|
|
1585
|
+
console.log(`Note: ${note}`);
|
|
1586
|
+
}
|
|
1587
|
+
if (!gate.canInstall) {
|
|
1588
|
+
return gate.exitCode;
|
|
1589
|
+
}
|
|
1590
|
+
if (options.dryRun || options.ci) {
|
|
1591
|
+
return 0;
|
|
1592
|
+
}
|
|
1593
|
+
return runProviderInstall(provider, source, skillName);
|
|
1594
|
+
} finally {
|
|
1595
|
+
if (tempDir) {
|
|
1596
|
+
import_node_fs2.default.rmSync(tempDir, { recursive: true, force: true });
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// src/commands/scan-clawhub.ts
|
|
1602
|
+
var import_zod4 = require("zod");
|
|
1242
1603
|
|
|
1243
1604
|
// src/resolver/clawhub.ts
|
|
1244
|
-
var
|
|
1605
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1245
1606
|
var import_jszip = __toESM(require("jszip"), 1);
|
|
1246
1607
|
var DEFAULT_REGISTRY_BASE_URL = "https://clawhub.ai";
|
|
1247
1608
|
var DEFAULT_ARCHIVE_BASE_URL = "https://auth.clawdhub.com";
|
|
@@ -1249,7 +1610,7 @@ var DEFAULT_REQUEST_TIMEOUT_MS2 = 15e3;
|
|
|
1249
1610
|
var DEFAULT_MAX_FILE_SIZE_BYTES = 25e4;
|
|
1250
1611
|
var DEFAULT_MAX_TOTAL_FILES = 120;
|
|
1251
1612
|
var RETRYABLE_STATUS2 = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1252
|
-
var
|
|
1613
|
+
var ALLOWED_TEXT_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
1253
1614
|
".md",
|
|
1254
1615
|
".txt",
|
|
1255
1616
|
".sh",
|
|
@@ -1491,8 +1852,8 @@ function isLikelyTextFile2(filePath) {
|
|
|
1491
1852
|
if (lower.endsWith("/skill.md") || lower === "skill.md") {
|
|
1492
1853
|
return true;
|
|
1493
1854
|
}
|
|
1494
|
-
const ext =
|
|
1495
|
-
return
|
|
1855
|
+
const ext = import_node_path5.default.posix.extname(lower);
|
|
1856
|
+
return ALLOWED_TEXT_EXTENSIONS3.has(ext);
|
|
1496
1857
|
}
|
|
1497
1858
|
function isBinaryContent2(content) {
|
|
1498
1859
|
return content.includes("\0");
|
|
@@ -1635,7 +1996,7 @@ async function resolveSkillFromArchive(metadata, identifier, options) {
|
|
|
1635
1996
|
defaultBranch: "archive",
|
|
1636
1997
|
commitSha: archiveMeta.version ?? "archive",
|
|
1637
1998
|
skillName: options.skillNameOverride ?? archiveMeta.slug,
|
|
1638
|
-
skillDir:
|
|
1999
|
+
skillDir: import_node_path5.default.posix.dirname(skillFilePath),
|
|
1639
2000
|
skillFilePath,
|
|
1640
2001
|
files,
|
|
1641
2002
|
unverifiableReasons: [...unverifiableReasons],
|
|
@@ -1783,34 +2144,34 @@ async function resolveSkillFromClawHub(identifier, options = {}) {
|
|
|
1783
2144
|
}
|
|
1784
2145
|
|
|
1785
2146
|
// src/commands/scan-clawhub.ts
|
|
1786
|
-
var cliScanClawHubOptionsSchema =
|
|
1787
|
-
config:
|
|
1788
|
-
strict:
|
|
1789
|
-
json:
|
|
1790
|
-
skill:
|
|
1791
|
-
version:
|
|
1792
|
-
clawhubRegistry:
|
|
1793
|
-
githubTimeoutMs:
|
|
1794
|
-
githubRetries:
|
|
1795
|
-
githubRetryBaseMs:
|
|
1796
|
-
maxFileBytes:
|
|
1797
|
-
maxAuxFiles:
|
|
1798
|
-
maxTotalFiles:
|
|
2147
|
+
var cliScanClawHubOptionsSchema = import_zod4.z.object({
|
|
2148
|
+
config: import_zod4.z.string().optional(),
|
|
2149
|
+
strict: import_zod4.z.boolean().optional(),
|
|
2150
|
+
json: import_zod4.z.boolean().optional(),
|
|
2151
|
+
skill: import_zod4.z.string().min(1).optional(),
|
|
2152
|
+
version: import_zod4.z.string().min(1).optional(),
|
|
2153
|
+
clawhubRegistry: import_zod4.z.string().min(1).optional(),
|
|
2154
|
+
githubTimeoutMs: import_zod4.z.coerce.number().int().min(1e3).max(12e4).optional(),
|
|
2155
|
+
githubRetries: import_zod4.z.coerce.number().int().min(0).max(6).optional(),
|
|
2156
|
+
githubRetryBaseMs: import_zod4.z.coerce.number().int().min(50).max(5e3).optional(),
|
|
2157
|
+
maxFileBytes: import_zod4.z.coerce.number().int().min(4096).max(5e6).optional(),
|
|
2158
|
+
maxAuxFiles: import_zod4.z.coerce.number().int().min(1).max(200).optional(),
|
|
2159
|
+
maxTotalFiles: import_zod4.z.coerce.number().int().min(1).max(400).optional()
|
|
1799
2160
|
});
|
|
1800
|
-
var effectiveScanClawHubOptionsSchema =
|
|
1801
|
-
strict:
|
|
1802
|
-
json:
|
|
1803
|
-
skill:
|
|
1804
|
-
version:
|
|
1805
|
-
clawhubRegistry:
|
|
1806
|
-
githubTimeoutMs:
|
|
1807
|
-
githubRetries:
|
|
1808
|
-
githubRetryBaseMs:
|
|
1809
|
-
maxFileBytes:
|
|
1810
|
-
maxAuxFiles:
|
|
1811
|
-
maxTotalFiles:
|
|
2161
|
+
var effectiveScanClawHubOptionsSchema = import_zod4.z.object({
|
|
2162
|
+
strict: import_zod4.z.boolean(),
|
|
2163
|
+
json: import_zod4.z.boolean(),
|
|
2164
|
+
skill: import_zod4.z.string().min(1).optional(),
|
|
2165
|
+
version: import_zod4.z.string().min(1).optional(),
|
|
2166
|
+
clawhubRegistry: import_zod4.z.string().min(1),
|
|
2167
|
+
githubTimeoutMs: import_zod4.z.number().int().min(1e3).max(12e4),
|
|
2168
|
+
githubRetries: import_zod4.z.number().int().min(0).max(6),
|
|
2169
|
+
githubRetryBaseMs: import_zod4.z.number().int().min(50).max(5e3),
|
|
2170
|
+
maxFileBytes: import_zod4.z.number().int().min(4096).max(5e6),
|
|
2171
|
+
maxAuxFiles: import_zod4.z.number().int().min(1).max(200),
|
|
2172
|
+
maxTotalFiles: import_zod4.z.number().int().min(1).max(400)
|
|
1812
2173
|
});
|
|
1813
|
-
var
|
|
2174
|
+
var DEFAULT_OPTIONS3 = {
|
|
1814
2175
|
strict: false,
|
|
1815
2176
|
json: false,
|
|
1816
2177
|
skill: void 0,
|
|
@@ -1858,17 +2219,17 @@ function resolveEffectiveScanClawHubOptions(cliOptions, config) {
|
|
|
1858
2219
|
const defaults = config.defaults ?? {};
|
|
1859
2220
|
const resolver = config.resolver ?? {};
|
|
1860
2221
|
return effectiveScanClawHubOptionsSchema.parse({
|
|
1861
|
-
strict: cliOptions.strict ?? defaults.strict ??
|
|
1862
|
-
json: cliOptions.json ?? defaults.json ??
|
|
1863
|
-
skill: cliOptions.skill ??
|
|
1864
|
-
version: cliOptions.version ??
|
|
1865
|
-
clawhubRegistry: cliOptions.clawhubRegistry ??
|
|
1866
|
-
githubTimeoutMs: cliOptions.githubTimeoutMs ?? resolver.githubTimeoutMs ??
|
|
1867
|
-
githubRetries: cliOptions.githubRetries ?? resolver.githubRetries ??
|
|
1868
|
-
githubRetryBaseMs: cliOptions.githubRetryBaseMs ?? resolver.githubRetryBaseMs ??
|
|
1869
|
-
maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ??
|
|
1870
|
-
maxAuxFiles: cliOptions.maxAuxFiles ?? resolver.maxAuxFiles ??
|
|
1871
|
-
maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ??
|
|
2222
|
+
strict: cliOptions.strict ?? defaults.strict ?? DEFAULT_OPTIONS3.strict,
|
|
2223
|
+
json: cliOptions.json ?? defaults.json ?? DEFAULT_OPTIONS3.json,
|
|
2224
|
+
skill: cliOptions.skill ?? DEFAULT_OPTIONS3.skill,
|
|
2225
|
+
version: cliOptions.version ?? DEFAULT_OPTIONS3.version,
|
|
2226
|
+
clawhubRegistry: cliOptions.clawhubRegistry ?? DEFAULT_OPTIONS3.clawhubRegistry,
|
|
2227
|
+
githubTimeoutMs: cliOptions.githubTimeoutMs ?? resolver.githubTimeoutMs ?? DEFAULT_OPTIONS3.githubTimeoutMs,
|
|
2228
|
+
githubRetries: cliOptions.githubRetries ?? resolver.githubRetries ?? DEFAULT_OPTIONS3.githubRetries,
|
|
2229
|
+
githubRetryBaseMs: cliOptions.githubRetryBaseMs ?? resolver.githubRetryBaseMs ?? DEFAULT_OPTIONS3.githubRetryBaseMs,
|
|
2230
|
+
maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ?? DEFAULT_OPTIONS3.maxFileBytes,
|
|
2231
|
+
maxAuxFiles: cliOptions.maxAuxFiles ?? resolver.maxAuxFiles ?? DEFAULT_OPTIONS3.maxAuxFiles,
|
|
2232
|
+
maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ?? DEFAULT_OPTIONS3.maxTotalFiles
|
|
1872
2233
|
});
|
|
1873
2234
|
}
|
|
1874
2235
|
async function runScanClawHubCommand(identifier, rawOptions) {
|
|
@@ -1930,10 +2291,10 @@ async function runScanClawHubCommand(identifier, rawOptions) {
|
|
|
1930
2291
|
}
|
|
1931
2292
|
|
|
1932
2293
|
// src/commands/scan-local.ts
|
|
1933
|
-
var
|
|
1934
|
-
var
|
|
1935
|
-
var
|
|
1936
|
-
var
|
|
2294
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
2295
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2296
|
+
var import_zod5 = require("zod");
|
|
2297
|
+
var ALLOWED_TEXT_EXTENSIONS4 = /* @__PURE__ */ new Set([
|
|
1937
2298
|
".md",
|
|
1938
2299
|
".txt",
|
|
1939
2300
|
".sh",
|
|
@@ -1952,23 +2313,23 @@ var ALLOWED_TEXT_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
|
1952
2313
|
".ini",
|
|
1953
2314
|
".cfg"
|
|
1954
2315
|
]);
|
|
1955
|
-
var
|
|
1956
|
-
var cliScanLocalOptionsSchema =
|
|
1957
|
-
config:
|
|
1958
|
-
strict:
|
|
1959
|
-
json:
|
|
1960
|
-
skill:
|
|
1961
|
-
maxFileBytes:
|
|
1962
|
-
maxTotalFiles:
|
|
2316
|
+
var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".turbo"]);
|
|
2317
|
+
var cliScanLocalOptionsSchema = import_zod5.z.object({
|
|
2318
|
+
config: import_zod5.z.string().optional(),
|
|
2319
|
+
strict: import_zod5.z.boolean().optional(),
|
|
2320
|
+
json: import_zod5.z.boolean().optional(),
|
|
2321
|
+
skill: import_zod5.z.string().min(1).optional(),
|
|
2322
|
+
maxFileBytes: import_zod5.z.coerce.number().int().min(4096).max(5e6).optional(),
|
|
2323
|
+
maxTotalFiles: import_zod5.z.coerce.number().int().min(1).max(400).optional()
|
|
1963
2324
|
});
|
|
1964
|
-
var effectiveScanLocalOptionsSchema =
|
|
1965
|
-
strict:
|
|
1966
|
-
json:
|
|
1967
|
-
skill:
|
|
1968
|
-
maxFileBytes:
|
|
1969
|
-
maxTotalFiles:
|
|
2325
|
+
var effectiveScanLocalOptionsSchema = import_zod5.z.object({
|
|
2326
|
+
strict: import_zod5.z.boolean(),
|
|
2327
|
+
json: import_zod5.z.boolean(),
|
|
2328
|
+
skill: import_zod5.z.string().min(1).optional(),
|
|
2329
|
+
maxFileBytes: import_zod5.z.number().int().min(4096).max(5e6),
|
|
2330
|
+
maxTotalFiles: import_zod5.z.number().int().min(1).max(400)
|
|
1970
2331
|
});
|
|
1971
|
-
var
|
|
2332
|
+
var DEFAULT_OPTIONS4 = {
|
|
1972
2333
|
strict: false,
|
|
1973
2334
|
json: false,
|
|
1974
2335
|
skill: void 0,
|
|
@@ -1979,30 +2340,30 @@ function toPosixPath(filePath) {
|
|
|
1979
2340
|
return filePath.replace(/\\/g, "/");
|
|
1980
2341
|
}
|
|
1981
2342
|
function getNearbyPathSuggestions(targetPath) {
|
|
1982
|
-
const parent =
|
|
1983
|
-
if (!
|
|
2343
|
+
const parent = import_node_path6.default.dirname(targetPath);
|
|
2344
|
+
if (!import_node_fs3.default.existsSync(parent)) {
|
|
1984
2345
|
return [];
|
|
1985
2346
|
}
|
|
1986
|
-
const needle =
|
|
2347
|
+
const needle = import_node_path6.default.basename(targetPath).toLowerCase();
|
|
1987
2348
|
const suggestions = [];
|
|
1988
|
-
for (const entry of
|
|
2349
|
+
for (const entry of import_node_fs3.default.readdirSync(parent, { withFileTypes: true })) {
|
|
1989
2350
|
if (entry.name.toLowerCase().includes(needle)) {
|
|
1990
|
-
suggestions.push(
|
|
2351
|
+
suggestions.push(import_node_path6.default.join(parent, entry.name));
|
|
1991
2352
|
}
|
|
1992
2353
|
}
|
|
1993
2354
|
return suggestions.slice(0, 5);
|
|
1994
2355
|
}
|
|
1995
2356
|
function isSkillFile(filePath) {
|
|
1996
|
-
return
|
|
2357
|
+
return import_node_path6.default.basename(filePath).toLowerCase() === "skill.md";
|
|
1997
2358
|
}
|
|
1998
2359
|
function isScannableTextFile(filePath) {
|
|
1999
2360
|
if (isSkillFile(filePath)) {
|
|
2000
2361
|
return true;
|
|
2001
2362
|
}
|
|
2002
|
-
const ext =
|
|
2003
|
-
return
|
|
2363
|
+
const ext = import_node_path6.default.extname(filePath).toLowerCase();
|
|
2364
|
+
return ALLOWED_TEXT_EXTENSIONS4.has(ext);
|
|
2004
2365
|
}
|
|
2005
|
-
function
|
|
2366
|
+
function findSkillDirs2(rootDir) {
|
|
2006
2367
|
const found = /* @__PURE__ */ new Set();
|
|
2007
2368
|
const stack = [{ dir: rootDir, depth: 0 }];
|
|
2008
2369
|
let seen = 0;
|
|
@@ -2015,8 +2376,8 @@ function findSkillDirs(rootDir) {
|
|
|
2015
2376
|
if (seen > 5e3) {
|
|
2016
2377
|
break;
|
|
2017
2378
|
}
|
|
2018
|
-
const skillFile =
|
|
2019
|
-
if (
|
|
2379
|
+
const skillFile = import_node_path6.default.join(current.dir, "SKILL.md");
|
|
2380
|
+
if (import_node_fs3.default.existsSync(skillFile) && import_node_fs3.default.statSync(skillFile).isFile()) {
|
|
2020
2381
|
found.add(current.dir);
|
|
2021
2382
|
continue;
|
|
2022
2383
|
}
|
|
@@ -2025,7 +2386,7 @@ function findSkillDirs(rootDir) {
|
|
|
2025
2386
|
}
|
|
2026
2387
|
let entries;
|
|
2027
2388
|
try {
|
|
2028
|
-
entries =
|
|
2389
|
+
entries = import_node_fs3.default.readdirSync(current.dir, { withFileTypes: true });
|
|
2029
2390
|
} catch {
|
|
2030
2391
|
continue;
|
|
2031
2392
|
}
|
|
@@ -2033,10 +2394,10 @@ function findSkillDirs(rootDir) {
|
|
|
2033
2394
|
if (!entry.isDirectory()) {
|
|
2034
2395
|
continue;
|
|
2035
2396
|
}
|
|
2036
|
-
if (
|
|
2397
|
+
if (SKIP_DIRS2.has(entry.name)) {
|
|
2037
2398
|
continue;
|
|
2038
2399
|
}
|
|
2039
|
-
stack.push({ dir:
|
|
2400
|
+
stack.push({ dir: import_node_path6.default.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
2040
2401
|
}
|
|
2041
2402
|
}
|
|
2042
2403
|
return [...found].sort();
|
|
@@ -2045,8 +2406,8 @@ function formatCandidates(candidates) {
|
|
|
2045
2406
|
return candidates.map((candidate) => `- ${toPosixPath(candidate)}`).join("\n");
|
|
2046
2407
|
}
|
|
2047
2408
|
function resolveSkillDirectory(inputPath, preferredSkillName) {
|
|
2048
|
-
const absoluteInput =
|
|
2049
|
-
if (!
|
|
2409
|
+
const absoluteInput = import_node_path6.default.resolve(inputPath);
|
|
2410
|
+
if (!import_node_fs3.default.existsSync(absoluteInput)) {
|
|
2050
2411
|
const suggestions = getNearbyPathSuggestions(absoluteInput);
|
|
2051
2412
|
const suggestionText = suggestions.length > 0 ? `
|
|
2052
2413
|
Nearby paths:
|
|
@@ -2056,7 +2417,7 @@ ${formatCandidates(suggestions)}` : "";
|
|
|
2056
2417
|
`Local path not found: ${toPosixPath(absoluteInput)}${suggestionText}`
|
|
2057
2418
|
);
|
|
2058
2419
|
}
|
|
2059
|
-
const stat =
|
|
2420
|
+
const stat = import_node_fs3.default.statSync(absoluteInput);
|
|
2060
2421
|
if (stat.isFile()) {
|
|
2061
2422
|
if (!isSkillFile(absoluteInput)) {
|
|
2062
2423
|
throw new GuardSkillsError(
|
|
@@ -2065,15 +2426,15 @@ ${formatCandidates(suggestions)}` : "";
|
|
|
2065
2426
|
);
|
|
2066
2427
|
}
|
|
2067
2428
|
return {
|
|
2068
|
-
skillDir:
|
|
2429
|
+
skillDir: import_node_path6.default.dirname(absoluteInput),
|
|
2069
2430
|
note: "Using parent directory of provided SKILL.md file."
|
|
2070
2431
|
};
|
|
2071
2432
|
}
|
|
2072
|
-
const directSkillFile =
|
|
2073
|
-
if (
|
|
2433
|
+
const directSkillFile = import_node_path6.default.join(absoluteInput, "SKILL.md");
|
|
2434
|
+
if (import_node_fs3.default.existsSync(directSkillFile) && import_node_fs3.default.statSync(directSkillFile).isFile()) {
|
|
2074
2435
|
return { skillDir: absoluteInput };
|
|
2075
2436
|
}
|
|
2076
|
-
const discovered =
|
|
2437
|
+
const discovered = findSkillDirs2(absoluteInput);
|
|
2077
2438
|
if (discovered.length === 0) {
|
|
2078
2439
|
throw new GuardSkillsError(
|
|
2079
2440
|
"INVALID_LOCAL_PATH",
|
|
@@ -2082,7 +2443,7 @@ ${formatCandidates(suggestions)}` : "";
|
|
|
2082
2443
|
}
|
|
2083
2444
|
if (preferredSkillName) {
|
|
2084
2445
|
const matches = discovered.filter(
|
|
2085
|
-
(directory) =>
|
|
2446
|
+
(directory) => import_node_path6.default.basename(directory).toLowerCase() === preferredSkillName.toLowerCase()
|
|
2086
2447
|
);
|
|
2087
2448
|
if (matches.length === 1) {
|
|
2088
2449
|
const selected = matches[0];
|
|
@@ -2094,7 +2455,7 @@ ${formatCandidates(suggestions)}` : "";
|
|
|
2094
2455
|
note: `Auto-selected skill '${preferredSkillName}' under the provided path.`
|
|
2095
2456
|
};
|
|
2096
2457
|
}
|
|
2097
|
-
const available = discovered.map((directory) =>
|
|
2458
|
+
const available = discovered.map((directory) => import_node_path6.default.basename(directory));
|
|
2098
2459
|
throw new GuardSkillsError(
|
|
2099
2460
|
"INVALID_LOCAL_PATH",
|
|
2100
2461
|
`Requested --skill '${preferredSkillName}' was not found.
|
|
@@ -2117,7 +2478,7 @@ Available skills: ${available.join(", ")}`
|
|
|
2117
2478
|
${formatCandidates(discovered)}`
|
|
2118
2479
|
);
|
|
2119
2480
|
}
|
|
2120
|
-
function
|
|
2481
|
+
function collectLocalFiles2(skillDir, options) {
|
|
2121
2482
|
const files = [];
|
|
2122
2483
|
const unverifiableReasons = [];
|
|
2123
2484
|
const stack = [skillDir];
|
|
@@ -2128,15 +2489,15 @@ function collectLocalFiles(skillDir, options) {
|
|
|
2128
2489
|
}
|
|
2129
2490
|
let entries;
|
|
2130
2491
|
try {
|
|
2131
|
-
entries =
|
|
2492
|
+
entries = import_node_fs3.default.readdirSync(currentDir, { withFileTypes: true });
|
|
2132
2493
|
} catch {
|
|
2133
2494
|
unverifiableReasons.push(`Cannot read directory: ${toPosixPath(currentDir)}`);
|
|
2134
2495
|
continue;
|
|
2135
2496
|
}
|
|
2136
2497
|
for (const entry of entries) {
|
|
2137
|
-
const fullPath =
|
|
2498
|
+
const fullPath = import_node_path6.default.join(currentDir, entry.name);
|
|
2138
2499
|
if (entry.isDirectory()) {
|
|
2139
|
-
if (!
|
|
2500
|
+
if (!SKIP_DIRS2.has(entry.name)) {
|
|
2140
2501
|
stack.push(fullPath);
|
|
2141
2502
|
}
|
|
2142
2503
|
continue;
|
|
@@ -2144,7 +2505,7 @@ function collectLocalFiles(skillDir, options) {
|
|
|
2144
2505
|
if (!entry.isFile()) {
|
|
2145
2506
|
continue;
|
|
2146
2507
|
}
|
|
2147
|
-
const relativePath = toPosixPath(
|
|
2508
|
+
const relativePath = toPosixPath(import_node_path6.default.relative(skillDir, fullPath));
|
|
2148
2509
|
if (!isScannableTextFile(relativePath)) {
|
|
2149
2510
|
continue;
|
|
2150
2511
|
}
|
|
@@ -2156,7 +2517,7 @@ function collectLocalFiles(skillDir, options) {
|
|
|
2156
2517
|
}
|
|
2157
2518
|
let sizeBytes = 0;
|
|
2158
2519
|
try {
|
|
2159
|
-
sizeBytes =
|
|
2520
|
+
sizeBytes = import_node_fs3.default.statSync(fullPath).size;
|
|
2160
2521
|
} catch {
|
|
2161
2522
|
unverifiableReasons.push(`Cannot stat file: ${relativePath}`);
|
|
2162
2523
|
continue;
|
|
@@ -2170,7 +2531,7 @@ function collectLocalFiles(skillDir, options) {
|
|
|
2170
2531
|
try {
|
|
2171
2532
|
files.push({
|
|
2172
2533
|
path: relativePath,
|
|
2173
|
-
content:
|
|
2534
|
+
content: import_node_fs3.default.readFileSync(fullPath, "utf8")
|
|
2174
2535
|
});
|
|
2175
2536
|
} catch {
|
|
2176
2537
|
unverifiableReasons.push(`Cannot read text content: ${relativePath}`);
|
|
@@ -2183,11 +2544,11 @@ function resolveEffectiveScanLocalOptions(cliOptions, config) {
|
|
|
2183
2544
|
const defaults = config.defaults ?? {};
|
|
2184
2545
|
const resolver = config.resolver ?? {};
|
|
2185
2546
|
return effectiveScanLocalOptionsSchema.parse({
|
|
2186
|
-
strict: cliOptions.strict ?? defaults.strict ??
|
|
2187
|
-
json: cliOptions.json ?? defaults.json ??
|
|
2188
|
-
skill: cliOptions.skill ??
|
|
2189
|
-
maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ??
|
|
2190
|
-
maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ??
|
|
2547
|
+
strict: cliOptions.strict ?? defaults.strict ?? DEFAULT_OPTIONS4.strict,
|
|
2548
|
+
json: cliOptions.json ?? defaults.json ?? DEFAULT_OPTIONS4.json,
|
|
2549
|
+
skill: cliOptions.skill ?? DEFAULT_OPTIONS4.skill,
|
|
2550
|
+
maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ?? DEFAULT_OPTIONS4.maxFileBytes,
|
|
2551
|
+
maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ?? DEFAULT_OPTIONS4.maxTotalFiles
|
|
2191
2552
|
});
|
|
2192
2553
|
}
|
|
2193
2554
|
async function runScanLocalCommand(inputPath, rawOptions) {
|
|
@@ -2195,7 +2556,7 @@ async function runScanLocalCommand(inputPath, rawOptions) {
|
|
|
2195
2556
|
const loadedConfig = loadGuardSkillsConfig(cliOptions.config);
|
|
2196
2557
|
const options = resolveEffectiveScanLocalOptions(cliOptions, loadedConfig.config);
|
|
2197
2558
|
const target = resolveSkillDirectory(inputPath, options.skill);
|
|
2198
|
-
const { files, unverifiableReasons } =
|
|
2559
|
+
const { files, unverifiableReasons } = collectLocalFiles2(target.skillDir, options);
|
|
2199
2560
|
if (files.length === 0) {
|
|
2200
2561
|
throw new GuardSkillsError(
|
|
2201
2562
|
"INVALID_LOCAL_PATH",
|
|
@@ -2208,7 +2569,7 @@ async function runScanLocalCommand(inputPath, rawOptions) {
|
|
|
2208
2569
|
repo: "local",
|
|
2209
2570
|
defaultBranch: "local",
|
|
2210
2571
|
commitSha: "local",
|
|
2211
|
-
skillName: options.skill ??
|
|
2572
|
+
skillName: options.skill ?? import_node_path6.default.basename(target.skillDir),
|
|
2212
2573
|
skillDir: toPosixPath(target.skillDir),
|
|
2213
2574
|
skillFilePath: "SKILL.md",
|
|
2214
2575
|
files,
|
|
@@ -2250,15 +2611,69 @@ async function runScanLocalCommand(inputPath, rawOptions) {
|
|
|
2250
2611
|
}
|
|
2251
2612
|
|
|
2252
2613
|
// src/cli.ts
|
|
2614
|
+
function withCommonAddOptions(command, requireSkill) {
|
|
2615
|
+
const configured = command.option("--config <path>", "Path to guardskills.config.json").option("--strict", "Use stricter risk thresholds").option("--ci", "Deterministic CI mode: scan + gate only, no install handoff").option("--json", "Output machine-readable JSON").option("--yes", "Auto-confirm warnings").option("--dry-run", "Scan only, do not install").option("--force", "Override UNSAFE outcome").option("--allow-unverifiable", "Override UNVERIFIABLE outcome").option("--github-timeout-ms <ms>", "GitHub API request timeout in milliseconds").option("--github-retries <count>", "Retry count for retryable GitHub errors").option("--github-retry-base-ms <ms>", "Base backoff delay for GitHub retries").option("--max-file-bytes <bytes>", "Max file size to scan").option("--max-aux-files <count>", "Max auxiliary files from scripts/src folders").option("--max-total-files <count>", "Max total resolved files to scan");
|
|
2616
|
+
return requireSkill ? configured.requiredOption("--skill <name>", "Skill name to install") : configured.option("--skill <name>", "Skill name to install");
|
|
2617
|
+
}
|
|
2253
2618
|
async function main() {
|
|
2254
2619
|
const program = new import_commander.Command();
|
|
2255
|
-
program.name("guardskills").description("Security wrapper around
|
|
2256
|
-
program.command("add").description("Scan a skill source and conditionally install it via skills CLI").argument("<repo>", "GitHub repository URL or owner/repo")
|
|
2257
|
-
|
|
2620
|
+
program.name("guardskills").description("Security wrapper around skill installation CLIs").version("1.1.0");
|
|
2621
|
+
const legacyAdd = program.command("add").description("Scan a skill source and conditionally install it via skills CLI").argument("<repo>", "GitHub repository URL or owner/repo");
|
|
2622
|
+
withCommonAddOptions(legacyAdd, true).action(
|
|
2623
|
+
async (repo, options) => {
|
|
2624
|
+
const code = await runAddCommand(repo, options);
|
|
2625
|
+
process.exitCode = code;
|
|
2626
|
+
}
|
|
2627
|
+
);
|
|
2628
|
+
const skills = program.command("skills").description("Guarded wrapper for skills.sh install commands");
|
|
2629
|
+
withCommonAddOptions(
|
|
2630
|
+
skills.command("add").description("Scan a skill source and conditionally install it via skills CLI").argument("<repo>", "GitHub repository URL or owner/repo"),
|
|
2631
|
+
false
|
|
2632
|
+
).action(async (repo, options) => {
|
|
2633
|
+
const resolvedSkill = typeof options.skill === "string" ? options.skill : void 0;
|
|
2634
|
+
const code = resolvedSkill ? await runAddCommand(repo, options, {
|
|
2635
|
+
provider: "skills",
|
|
2636
|
+
commandName: "guardskills skills add"
|
|
2637
|
+
}) : await runInteractiveInstallCommand("skills", repo, void 0, options);
|
|
2638
|
+
process.exitCode = code;
|
|
2639
|
+
});
|
|
2640
|
+
const playbooks = program.command("playbooks").description("Guarded wrapper for Playbooks install commands");
|
|
2641
|
+
withCommonAddOptions(
|
|
2642
|
+
playbooks.command("add").description("Scan a skill source and conditionally install it via Playbooks CLI").argument("<resource>", "Playbooks resource type (must be 'skill')").argument("<repo>", "GitHub repository URL or owner/repo"),
|
|
2643
|
+
true
|
|
2644
|
+
).action(async (resource, repo, options) => {
|
|
2645
|
+
if (resource !== "skill") {
|
|
2646
|
+
throw new GuardSkillsError(
|
|
2647
|
+
"INVALID_OPTIONS",
|
|
2648
|
+
`Unsupported playbooks resource '${resource}'. Use: guardskills playbooks add skill <repo> --skill <name>`
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
const code = await runAddCommand(repo, options, {
|
|
2652
|
+
provider: "playbooks",
|
|
2653
|
+
commandName: "guardskills playbooks add skill"
|
|
2654
|
+
});
|
|
2655
|
+
process.exitCode = code;
|
|
2656
|
+
});
|
|
2657
|
+
const openskills = program.command("openskills").description("Guarded wrapper for OpenSkills install commands");
|
|
2658
|
+
withCommonAddOptions(
|
|
2659
|
+
openskills.command("install").description("Scan a skill source and conditionally install it via OpenSkills CLI").argument("<repo>", "GitHub repository URL or owner/repo").argument("[skill]", "Skill name to install"),
|
|
2660
|
+
false
|
|
2661
|
+
).action(async (repo, skill, options) => {
|
|
2662
|
+
const resolvedSkill = skill ?? (typeof options.skill === "string" ? options.skill : void 0);
|
|
2663
|
+
const code = await runInteractiveInstallCommand("openskills", repo, resolvedSkill, options);
|
|
2664
|
+
process.exitCode = code;
|
|
2665
|
+
});
|
|
2666
|
+
const skillkit = program.command("skillkit").description("Guarded wrapper for skillkit install commands");
|
|
2667
|
+
withCommonAddOptions(
|
|
2668
|
+
skillkit.command("install").description("Scan a skill source and conditionally install it via skillkit CLI").argument("<repo>", "GitHub repository URL or owner/repo").argument("[skill]", "Skill name to install"),
|
|
2669
|
+
false
|
|
2670
|
+
).action(async (repo, skill, options) => {
|
|
2671
|
+
const resolvedSkill = skill ?? (typeof options.skill === "string" ? options.skill : void 0);
|
|
2672
|
+
const code = await runInteractiveInstallCommand("skillkit", repo, resolvedSkill, options);
|
|
2258
2673
|
process.exitCode = code;
|
|
2259
2674
|
});
|
|
2260
|
-
program.command("scan-local").description("Scan a local skill folder and print a risk decision").argument("<path>", "Local folder path (or SKILL.md file path)").option("--skill <name>", "Skill directory name when path contains multiple skills").option("--config <path>", "Path to guardskills.config.json").option("--strict", "Use stricter risk thresholds").option("--json", "Output machine-readable JSON").option("--max-file-bytes <bytes>", "Max file size to scan").option("--max-total-files <count>", "Max total files to scan").action(async (
|
|
2261
|
-
const code = await runScanLocalCommand(
|
|
2675
|
+
program.command("scan-local").description("Scan a local skill folder and print a risk decision").argument("<path>", "Local folder path (or SKILL.md file path)").option("--skill <name>", "Skill directory name when path contains multiple skills").option("--config <path>", "Path to guardskills.config.json").option("--strict", "Use stricter risk thresholds").option("--json", "Output machine-readable JSON").option("--max-file-bytes <bytes>", "Max file size to scan").option("--max-total-files <count>", "Max total files to scan").action(async (repo, options) => {
|
|
2676
|
+
const code = await runScanLocalCommand(repo, options);
|
|
2262
2677
|
process.exitCode = code;
|
|
2263
2678
|
});
|
|
2264
2679
|
program.command("scan-clawhub").description("Scan a ClawHub skill package and print a risk decision").argument("<identifier>", "ClawHub package identifier").option("--skill <name>", "Override skill folder name to resolve in source repository").option("--version <version>", "Preferred package version/tag").option("--clawhub-registry <url>", "ClawHub registry base URL").option("--config <path>", "Path to guardskills.config.json").option("--strict", "Use stricter risk thresholds").option("--json", "Output machine-readable JSON").option("--github-timeout-ms <ms>", "Upstream resolver request timeout in milliseconds").option("--github-retries <count>", "Retry count for retryable upstream errors").option("--github-retry-base-ms <ms>", "Base backoff delay for upstream retries").option("--max-file-bytes <bytes>", "Max file size to scan").option("--max-aux-files <count>", "Max auxiliary files from scripts/src folders").option("--max-total-files <count>", "Max total resolved files to scan").action(async (identifier, options) => {
|