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.
Files changed (4) hide show
  1. package/README.md +24 -5
  2. package/dist/cli.cjs +524 -109
  3. package/dist/cli.js +524 -109
  4. 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 runSkillsInstall(repo, skill) {
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", ["skills", "add", repo, "--skill", skill], {
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: "guardskills add",
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 runSkillsInstall(repo, options.skill);
1259
+ return runProviderInstall(provider, repo, options.skill);
1238
1260
  }
1239
1261
 
1240
- // src/commands/scan-clawhub.ts
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 import_node_path4 = __toESM(require("path"), 1);
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 ALLOWED_TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
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 = import_node_path4.default.posix.extname(lower);
1495
- return ALLOWED_TEXT_EXTENSIONS2.has(ext);
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: import_node_path4.default.posix.dirname(skillFilePath),
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 = import_zod3.z.object({
1787
- config: import_zod3.z.string().optional(),
1788
- strict: import_zod3.z.boolean().optional(),
1789
- json: import_zod3.z.boolean().optional(),
1790
- skill: import_zod3.z.string().min(1).optional(),
1791
- version: import_zod3.z.string().min(1).optional(),
1792
- clawhubRegistry: import_zod3.z.string().min(1).optional(),
1793
- githubTimeoutMs: import_zod3.z.coerce.number().int().min(1e3).max(12e4).optional(),
1794
- githubRetries: import_zod3.z.coerce.number().int().min(0).max(6).optional(),
1795
- githubRetryBaseMs: import_zod3.z.coerce.number().int().min(50).max(5e3).optional(),
1796
- maxFileBytes: import_zod3.z.coerce.number().int().min(4096).max(5e6).optional(),
1797
- maxAuxFiles: import_zod3.z.coerce.number().int().min(1).max(200).optional(),
1798
- maxTotalFiles: import_zod3.z.coerce.number().int().min(1).max(400).optional()
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 = import_zod3.z.object({
1801
- strict: import_zod3.z.boolean(),
1802
- json: import_zod3.z.boolean(),
1803
- skill: import_zod3.z.string().min(1).optional(),
1804
- version: import_zod3.z.string().min(1).optional(),
1805
- clawhubRegistry: import_zod3.z.string().min(1),
1806
- githubTimeoutMs: import_zod3.z.number().int().min(1e3).max(12e4),
1807
- githubRetries: import_zod3.z.number().int().min(0).max(6),
1808
- githubRetryBaseMs: import_zod3.z.number().int().min(50).max(5e3),
1809
- maxFileBytes: import_zod3.z.number().int().min(4096).max(5e6),
1810
- maxAuxFiles: import_zod3.z.number().int().min(1).max(200),
1811
- maxTotalFiles: import_zod3.z.number().int().min(1).max(400)
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 DEFAULT_OPTIONS2 = {
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 ?? DEFAULT_OPTIONS2.strict,
1862
- json: cliOptions.json ?? defaults.json ?? DEFAULT_OPTIONS2.json,
1863
- skill: cliOptions.skill ?? DEFAULT_OPTIONS2.skill,
1864
- version: cliOptions.version ?? DEFAULT_OPTIONS2.version,
1865
- clawhubRegistry: cliOptions.clawhubRegistry ?? DEFAULT_OPTIONS2.clawhubRegistry,
1866
- githubTimeoutMs: cliOptions.githubTimeoutMs ?? resolver.githubTimeoutMs ?? DEFAULT_OPTIONS2.githubTimeoutMs,
1867
- githubRetries: cliOptions.githubRetries ?? resolver.githubRetries ?? DEFAULT_OPTIONS2.githubRetries,
1868
- githubRetryBaseMs: cliOptions.githubRetryBaseMs ?? resolver.githubRetryBaseMs ?? DEFAULT_OPTIONS2.githubRetryBaseMs,
1869
- maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ?? DEFAULT_OPTIONS2.maxFileBytes,
1870
- maxAuxFiles: cliOptions.maxAuxFiles ?? resolver.maxAuxFiles ?? DEFAULT_OPTIONS2.maxAuxFiles,
1871
- maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ?? DEFAULT_OPTIONS2.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 import_node_fs2 = __toESM(require("fs"), 1);
1934
- var import_node_path5 = __toESM(require("path"), 1);
1935
- var import_zod4 = require("zod");
1936
- var ALLOWED_TEXT_EXTENSIONS3 = /* @__PURE__ */ new Set([
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 SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".turbo"]);
1956
- var cliScanLocalOptionsSchema = import_zod4.z.object({
1957
- config: import_zod4.z.string().optional(),
1958
- strict: import_zod4.z.boolean().optional(),
1959
- json: import_zod4.z.boolean().optional(),
1960
- skill: import_zod4.z.string().min(1).optional(),
1961
- maxFileBytes: import_zod4.z.coerce.number().int().min(4096).max(5e6).optional(),
1962
- maxTotalFiles: import_zod4.z.coerce.number().int().min(1).max(400).optional()
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 = import_zod4.z.object({
1965
- strict: import_zod4.z.boolean(),
1966
- json: import_zod4.z.boolean(),
1967
- skill: import_zod4.z.string().min(1).optional(),
1968
- maxFileBytes: import_zod4.z.number().int().min(4096).max(5e6),
1969
- maxTotalFiles: import_zod4.z.number().int().min(1).max(400)
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 DEFAULT_OPTIONS3 = {
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 = import_node_path5.default.dirname(targetPath);
1983
- if (!import_node_fs2.default.existsSync(parent)) {
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 = import_node_path5.default.basename(targetPath).toLowerCase();
2347
+ const needle = import_node_path6.default.basename(targetPath).toLowerCase();
1987
2348
  const suggestions = [];
1988
- for (const entry of import_node_fs2.default.readdirSync(parent, { withFileTypes: true })) {
2349
+ for (const entry of import_node_fs3.default.readdirSync(parent, { withFileTypes: true })) {
1989
2350
  if (entry.name.toLowerCase().includes(needle)) {
1990
- suggestions.push(import_node_path5.default.join(parent, entry.name));
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 import_node_path5.default.basename(filePath).toLowerCase() === "skill.md";
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 = import_node_path5.default.extname(filePath).toLowerCase();
2003
- return ALLOWED_TEXT_EXTENSIONS3.has(ext);
2363
+ const ext = import_node_path6.default.extname(filePath).toLowerCase();
2364
+ return ALLOWED_TEXT_EXTENSIONS4.has(ext);
2004
2365
  }
2005
- function findSkillDirs(rootDir) {
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 = import_node_path5.default.join(current.dir, "SKILL.md");
2019
- if (import_node_fs2.default.existsSync(skillFile) && import_node_fs2.default.statSync(skillFile).isFile()) {
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 = import_node_fs2.default.readdirSync(current.dir, { withFileTypes: true });
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 (SKIP_DIRS.has(entry.name)) {
2397
+ if (SKIP_DIRS2.has(entry.name)) {
2037
2398
  continue;
2038
2399
  }
2039
- stack.push({ dir: import_node_path5.default.join(current.dir, entry.name), depth: current.depth + 1 });
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 = import_node_path5.default.resolve(inputPath);
2049
- if (!import_node_fs2.default.existsSync(absoluteInput)) {
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 = import_node_fs2.default.statSync(absoluteInput);
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: import_node_path5.default.dirname(absoluteInput),
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 = import_node_path5.default.join(absoluteInput, "SKILL.md");
2073
- if (import_node_fs2.default.existsSync(directSkillFile) && import_node_fs2.default.statSync(directSkillFile).isFile()) {
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 = findSkillDirs(absoluteInput);
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) => import_node_path5.default.basename(directory).toLowerCase() === preferredSkillName.toLowerCase()
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) => import_node_path5.default.basename(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 collectLocalFiles(skillDir, options) {
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 = import_node_fs2.default.readdirSync(currentDir, { withFileTypes: true });
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 = import_node_path5.default.join(currentDir, entry.name);
2498
+ const fullPath = import_node_path6.default.join(currentDir, entry.name);
2138
2499
  if (entry.isDirectory()) {
2139
- if (!SKIP_DIRS.has(entry.name)) {
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(import_node_path5.default.relative(skillDir, fullPath));
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 = import_node_fs2.default.statSync(fullPath).size;
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: import_node_fs2.default.readFileSync(fullPath, "utf8")
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 ?? DEFAULT_OPTIONS3.strict,
2187
- json: cliOptions.json ?? defaults.json ?? DEFAULT_OPTIONS3.json,
2188
- skill: cliOptions.skill ?? DEFAULT_OPTIONS3.skill,
2189
- maxFileBytes: cliOptions.maxFileBytes ?? resolver.maxFileBytes ?? DEFAULT_OPTIONS3.maxFileBytes,
2190
- maxTotalFiles: cliOptions.maxTotalFiles ?? resolver.maxTotalFiles ?? DEFAULT_OPTIONS3.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 } = collectLocalFiles(target.skillDir, options);
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 ?? import_node_path5.default.basename(target.skillDir),
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 skills add").version("1.1.0");
2256
- program.command("add").description("Scan a skill source and conditionally install it via skills CLI").argument("<repo>", "GitHub repository URL or owner/repo").requiredOption("--skill <name>", "Skill name to install").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").action(async (repo, options) => {
2257
- const code = await runAddCommand(repo, options);
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 (inputPath, options) => {
2261
- const code = await runScanLocalCommand(inputPath, options);
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) => {