skillpass 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -492,16 +492,7 @@ function unzipSync(data, opts) {
492
492
  }
493
493
 
494
494
  // src/add.ts
495
- import {
496
- existsSync,
497
- mkdirSync as mkdirSync2,
498
- readdirSync as readdirSync2,
499
- renameSync,
500
- rmdirSync,
501
- rmSync,
502
- statSync as statSync2,
503
- writeFileSync as writeFileSync2
504
- } from "node:fs";
495
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync3, renameSync, rmdirSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
505
496
  import { dirname, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
506
497
 
507
498
  // ../validator/src/load.ts
@@ -15281,10 +15272,10 @@ function parseManifest(input) {
15281
15272
  }
15282
15273
 
15283
15274
  // ../skill-schema/src/report.ts
15284
- var reportFindingSchema = external_exports.strictObject({
15275
+ var reportFindingSchema = external_exports.object({
15285
15276
  code: external_exports.string().min(1),
15286
15277
  message: external_exports.string().min(1),
15287
- location: external_exports.strictObject({
15278
+ location: external_exports.object({
15288
15279
  path: external_exports.string().min(1),
15289
15280
  line: external_exports.number().int().positive().optional(),
15290
15281
  snippet: external_exports.string().optional()
@@ -15313,11 +15304,11 @@ var validationReportSchema = external_exports.strictObject({
15313
15304
  });
15314
15305
 
15315
15306
  // ../skill-schema/src/passport.ts
15316
- var skillPassportSchema = external_exports.strictObject({
15307
+ var skillPassportSchema = external_exports.object({
15317
15308
  schemaVersion: external_exports.literal("0.1"),
15318
15309
  validationStatus: validationStatusSchema,
15319
15310
  riskLevel: riskLevelSchema,
15320
- permissionsSummary: external_exports.strictObject({
15311
+ permissionsSummary: external_exports.object({
15321
15312
  declared: external_exports.array(permissionKeySchema),
15322
15313
  detected: external_exports.array(permissionKeySchema)
15323
15314
  }),
@@ -15338,7 +15329,7 @@ var skillPassportSchema = external_exports.strictObject({
15338
15329
 
15339
15330
  // ../skill-schema/src/ai-review.ts
15340
15331
  var aiReviewVerdictSchema = external_exports.enum(["clear", "caution", "concern"]);
15341
- var aiReviewSchema = external_exports.strictObject({
15332
+ var aiReviewSchema = external_exports.object({
15342
15333
  summary: external_exports.string().min(1).max(600),
15343
15334
  verdict: aiReviewVerdictSchema,
15344
15335
  reasoning: external_exports.string().min(1).max(800),
@@ -15348,14 +15339,7 @@ var aiReviewSchema = external_exports.strictObject({
15348
15339
 
15349
15340
  // ../skill-schema/src/submission.ts
15350
15341
  var SUBMISSION_SOURCE_TYPES = ["github_url", "zip"];
15351
- var SUBMISSION_STATUSES = [
15352
- "draft",
15353
- "validating",
15354
- "passed",
15355
- "warning",
15356
- "failed",
15357
- "published"
15358
- ];
15342
+ var SUBMISSION_STATUSES = ["draft", "validating", "passed", "warning", "failed", "published"];
15359
15343
  var submissionSourceTypeSchema = external_exports.enum(SUBMISSION_SOURCE_TYPES);
15360
15344
  var submissionStatusSchema = external_exports.enum(SUBMISSION_STATUSES);
15361
15345
  var MAX_ZIP_BYTES = 10 * 1024 * 1024;
@@ -15390,9 +15374,14 @@ var publishResultSchema = external_exports.object({
15390
15374
  slug: external_exports.string().min(1),
15391
15375
  version: external_exports.string().min(1)
15392
15376
  });
15377
+ var MAX_SLUG_LENGTH = 60;
15378
+ function slugForSkill(name) {
15379
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
15380
+ return slug || "skill";
15381
+ }
15393
15382
 
15394
15383
  // ../skill-schema/src/public.ts
15395
- var publicSkillSummarySchema = external_exports.strictObject({
15384
+ var publicSkillSummarySchema = external_exports.object({
15396
15385
  slug: external_exports.string().min(1),
15397
15386
  name: external_exports.string().min(1),
15398
15387
  summary: external_exports.string().min(1),
@@ -15422,7 +15411,7 @@ var publicSkillSummarySchema = external_exports.strictObject({
15422
15411
  publishedAt: external_exports.iso.datetime()
15423
15412
  });
15424
15413
  var publicSkillListSchema = external_exports.array(publicSkillSummarySchema);
15425
- var publicSkillVersionSchema = external_exports.strictObject({
15414
+ var publicSkillVersionSchema = external_exports.object({
15426
15415
  version: external_exports.string().min(1),
15427
15416
  validationStatus: validationStatusSchema,
15428
15417
  riskLevel: riskLevelSchema,
@@ -15431,10 +15420,10 @@ var publicSkillVersionSchema = external_exports.strictObject({
15431
15420
  var publicSkillDetailSchema = publicSkillSummarySchema.extend({
15432
15421
  // Full member entries (incl. per-target variants) for a pack; the CLI's
15433
15422
  // per-target install resolution reads exactly this. Null for single skills.
15434
- packMembers: external_exports.array(skillEntrySchema).nullable().optional(),
15423
+ packMembers: external_exports.array(external_exports.object(skillEntrySchema.shape)).nullable().optional(),
15435
15424
  githubRepoUrl: external_exports.string().min(1).nullable(),
15436
15425
  passport: skillPassportSchema,
15437
- maintainerInfo: external_exports.strictObject({
15426
+ maintainerInfo: external_exports.object({
15438
15427
  username: external_exports.string().min(1),
15439
15428
  displayName: external_exports.string().min(1),
15440
15429
  avatarUrl: external_exports.string().min(1)
@@ -15443,18 +15432,18 @@ var publicSkillDetailSchema = publicSkillSummarySchema.extend({
15443
15432
  // The cached AI review for this version's source hash; null until generated.
15444
15433
  aiReview: aiReviewSchema.nullable()
15445
15434
  });
15446
- var publicSkillSourceSchema = external_exports.strictObject({
15435
+ var publicSkillSourceSchema = external_exports.object({
15447
15436
  version: external_exports.string().min(1),
15448
15437
  sourceHash: external_exports.string().min(1),
15449
- files: external_exports.array(external_exports.strictObject({ path: external_exports.string().min(1), content: external_exports.string() }))
15438
+ files: external_exports.array(external_exports.object({ path: external_exports.string().min(1), content: external_exports.string() }))
15450
15439
  });
15451
15440
 
15452
15441
  // ../skill-schema/src/preflight.ts
15453
- var permissionSetDiffSchema = external_exports.strictObject({
15442
+ var permissionSetDiffSchema = external_exports.object({
15454
15443
  added: external_exports.array(permissionKeySchema),
15455
15444
  removed: external_exports.array(permissionKeySchema)
15456
15445
  });
15457
- var publicPreflightSchema = external_exports.strictObject({
15446
+ var publicPreflightSchema = external_exports.object({
15458
15447
  version: external_exports.string().min(1),
15459
15448
  validationStatus: validationStatusSchema,
15460
15449
  riskLevel: riskLevelSchema,
@@ -15462,11 +15451,11 @@ var publicPreflightSchema = external_exports.strictObject({
15462
15451
  sourceVerified: external_exports.boolean(),
15463
15452
  resolvedCommitSha: external_exports.string().min(1).nullable(),
15464
15453
  generatedAt: external_exports.iso.datetime(),
15465
- permissions: external_exports.strictObject({
15454
+ permissions: external_exports.object({
15466
15455
  declared: external_exports.array(permissionKeySchema),
15467
15456
  detected: external_exports.array(permissionKeySchema)
15468
15457
  }),
15469
- diff: external_exports.strictObject({
15458
+ diff: external_exports.object({
15470
15459
  previousVersion: external_exports.string().min(1),
15471
15460
  declared: permissionSetDiffSchema,
15472
15461
  detected: permissionSetDiffSchema
@@ -15546,6 +15535,39 @@ var resolveReportInputSchema = external_exports.strictObject({
15546
15535
  status: external_exports.enum(["reviewed", "actioned"])
15547
15536
  });
15548
15537
 
15538
+ // ../skill-schema/src/maintainer.ts
15539
+ var SKILL_STATUSES = ["published", "draft", "private", "flagged"];
15540
+ var skillStatusSchema = external_exports.enum(SKILL_STATUSES);
15541
+ var maintainerSkillSchema = external_exports.strictObject({
15542
+ slug: external_exports.string().min(1),
15543
+ name: external_exports.string().min(1),
15544
+ status: skillStatusSchema,
15545
+ version: external_exports.string().nullable(),
15546
+ validationStatus: validationStatusSchema.nullable(),
15547
+ riskLevel: riskLevelSchema.nullable(),
15548
+ updatedAt: external_exports.iso.datetime()
15549
+ });
15550
+ var maintainerReportSchema = external_exports.strictObject({
15551
+ id: external_exports.number().int().positive(),
15552
+ skill: external_exports.strictObject({ slug: external_exports.string(), name: external_exports.string() }),
15553
+ reason: external_exports.string(),
15554
+ status: abuseReportStatusSchema,
15555
+ createdAt: external_exports.iso.datetime()
15556
+ });
15557
+
15558
+ // ../skill-schema/src/user.ts
15559
+ var USER_ROLES = ["user", "maintainer", "admin"];
15560
+ var userRoleSchema = external_exports.enum(USER_ROLES);
15561
+ var publicUserSchema = external_exports.object({
15562
+ id: external_exports.number().int().positive(),
15563
+ username: external_exports.string().min(1),
15564
+ displayName: external_exports.string().min(1),
15565
+ avatarUrl: external_exports.string().min(1),
15566
+ role: userRoleSchema,
15567
+ reputation: external_exports.number().int(),
15568
+ createdAt: external_exports.iso.datetime()
15569
+ });
15570
+
15549
15571
  // ../validator/src/rules/permissions.ts
15550
15572
  var PERMISSION_SIGNALS = [
15551
15573
  {
@@ -15600,24 +15622,30 @@ var PERMISSION_SIGNALS = [
15600
15622
  }
15601
15623
  ];
15602
15624
  function detectPermissions(files) {
15603
- const detected = /* @__PURE__ */ new Map();
15625
+ const detected = /* @__PURE__ */ new Set();
15604
15626
  for (const file2 of files) {
15605
- file2.content.split("\n").forEach((line, i2) => {
15627
+ for (const line of file2.content.split("\n")) {
15606
15628
  for (const signal of PERMISSION_SIGNALS) {
15607
15629
  if (!detected.has(signal.permission) && signal.pattern.test(line)) {
15608
- detected.set(signal.permission, {
15609
- permission: signal.permission,
15610
- description: signal.description,
15611
- location: { path: file2.path, line: i2 + 1, snippet: line.trim() }
15612
- });
15630
+ detected.add(signal.permission);
15613
15631
  }
15614
15632
  }
15615
- });
15633
+ }
15616
15634
  }
15617
- return [...detected.values()];
15635
+ return [...detected];
15618
15636
  }
15619
15637
 
15620
15638
  // ../validator/src/load.ts
15639
+ var NUL_SCAN_BYTES = 8e3;
15640
+ function isBinary(bytes) {
15641
+ if (bytes.subarray(0, NUL_SCAN_BYTES).includes(0)) return true;
15642
+ try {
15643
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
15644
+ return false;
15645
+ } catch {
15646
+ return true;
15647
+ }
15648
+ }
15621
15649
  var PackageReadError = class extends Error {
15622
15650
  constructor(dir, cause) {
15623
15651
  super(`cannot read skill package at "${dir}"`, { cause });
@@ -15626,19 +15654,23 @@ var PackageReadError = class extends Error {
15626
15654
  };
15627
15655
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
15628
15656
  function walk(root, rel = "") {
15629
- const files = [];
15657
+ const tree = { files: [], binaries: [] };
15630
15658
  for (const name of readdirSync(join(root, rel)).sort()) {
15631
15659
  const relPath = rel === "" ? name : `${rel}/${name}`;
15632
15660
  const stats = statSync(join(root, relPath));
15633
15661
  if (stats.isDirectory()) {
15634
15662
  if (!SKIP_DIRS.has(name)) {
15635
- files.push(...walk(root, relPath));
15663
+ const sub = walk(root, relPath);
15664
+ tree.files.push(...sub.files);
15665
+ tree.binaries.push(...sub.binaries);
15636
15666
  }
15637
15667
  } else {
15638
- files.push({ path: relPath, content: readFileSync(join(root, relPath), "utf8") });
15668
+ const bytes = readFileSync(join(root, relPath));
15669
+ if (isBinary(bytes)) tree.binaries.push(relPath);
15670
+ else tree.files.push({ path: relPath, content: bytes.toString("utf8") });
15639
15671
  }
15640
15672
  }
15641
- return files;
15673
+ return tree;
15642
15674
  }
15643
15675
  function hashFiles(files) {
15644
15676
  const hash2 = createHash("sha256");
@@ -15665,9 +15697,16 @@ function readManifest(files) {
15665
15697
  if (!result.success) {
15666
15698
  return { state: "invalid", error: result.error };
15667
15699
  }
15668
- return { state: "ok", data: result.data, raw: file2.content };
15700
+ return { state: "ok", data: result.data };
15669
15701
  }
15670
15702
  var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
15703
+ function readDeclaredVersion(frontmatter) {
15704
+ const top = /^version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(frontmatter);
15705
+ if (top) return top[1];
15706
+ const block = /^metadata:\s*\n((?:[ \t]+\S.*(?:\n|$))*)/m.exec(frontmatter);
15707
+ const nested = block && /^[ \t]+version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(block[1]);
15708
+ return nested ? nested[1] : void 0;
15709
+ }
15671
15710
  function readSkillMeta(content) {
15672
15711
  const out = {};
15673
15712
  const fm = FRONTMATTER_RE.exec(content);
@@ -15690,19 +15729,45 @@ function readSkillMeta(content) {
15690
15729
  const text = block[1] === ">" ? gathered.join(" ").replace(/\s+/g, " ").trim() : gathered.join("\n").trim();
15691
15730
  if (text) out[key] = text;
15692
15731
  } else {
15693
- out[key] = kv[2].replace(/^["']|["']$/g, "");
15732
+ const parts = [kv[2]];
15733
+ for (let j = i2 + 1; j < lines.length && /^\s+\S/.test(lines[j]); j++) parts.push(lines[j].trim());
15734
+ out[key] = parts.join(" ").replace(/^["']|["']$/g, "");
15694
15735
  }
15695
15736
  }
15737
+ const version2 = readDeclaredVersion(fm[1]);
15738
+ if (version2) out.version = version2;
15696
15739
  }
15697
15740
  if (!out.description) {
15698
- const prose = body.split("\n").map((line) => line.trim()).find((line) => line && !line.startsWith("#"));
15699
- if (prose) out.description = prose.slice(0, 200);
15741
+ const prose = firstParagraph(body);
15742
+ if (prose) out.description = prose;
15700
15743
  }
15701
15744
  return out;
15702
15745
  }
15703
- function slugify2(value) {
15704
- const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
15705
- return slug || "skill";
15746
+ var HTML_TAG_RE = /<\/?[a-zA-Z][^>]*>/g;
15747
+ var MD_LINK_RE = /\[([^\]]*)\]\([^)]*\)/g;
15748
+ var STRUCTURAL_LINE_RE = /^(#|!\[|\[!\[|---$|\*\*\*$|___$)|^<h[1-6][\s>]/i;
15749
+ var DESCRIPTION_MAX = 200;
15750
+ var cleanInline = (line) => line.replace(HTML_TAG_RE, " ").replace(MD_LINK_RE, "$1").replace(/^>\s?/, "").replace(/\s+/g, " ").trim();
15751
+ function clip(text, max2) {
15752
+ if (text.length <= max2) return text;
15753
+ const head = text.slice(0, max2);
15754
+ const sentenceEnd = Math.max(head.lastIndexOf(". "), head.lastIndexOf("! "), head.lastIndexOf("? "));
15755
+ if (sentenceEnd >= 60) return head.slice(0, sentenceEnd + 1);
15756
+ const wordEnd = head.lastIndexOf(" ");
15757
+ return (wordEnd > 0 ? head.slice(0, wordEnd) : head).replace(/[,;:]+$/, "").trim();
15758
+ }
15759
+ function firstParagraph(text) {
15760
+ const kept = [];
15761
+ for (const raw of text.split("\n")) {
15762
+ const source = raw.trim();
15763
+ const line = STRUCTURAL_LINE_RE.test(source) ? "" : cleanInline(source);
15764
+ if (!line) {
15765
+ if (kept.length > 0) break;
15766
+ continue;
15767
+ }
15768
+ kept.push(line);
15769
+ }
15770
+ return kept.length > 0 ? clip(kept.join(" "), DESCRIPTION_MAX) : void 0;
15706
15771
  }
15707
15772
  function inferTargets(files) {
15708
15773
  const targets = /* @__PURE__ */ new Set();
@@ -15715,11 +15780,12 @@ function inferTargets(files) {
15715
15780
  function inferManifest(files, fallbackName) {
15716
15781
  const skillFile = files.find((f) => f.path === "SKILL.md");
15717
15782
  const meta3 = skillFile ? readSkillMeta(skillFile.content) : {};
15718
- const name = slugify2(meta3.name ?? fallbackName);
15719
- const permissions = [...new Set(detectPermissions(files).map((d) => d.permission))];
15783
+ const name = slugForSkill(meta3.name ?? fallbackName);
15784
+ const permissions = detectPermissions(files);
15720
15785
  return {
15721
15786
  schemaVersion: "0.1",
15722
15787
  name,
15788
+ ...meta3.version && SEMVER_RE.test(meta3.version) ? { version: meta3.version } : {},
15723
15789
  description: meta3.description ?? name,
15724
15790
  targets: inferTargets(files),
15725
15791
  permissions,
@@ -15751,16 +15817,10 @@ function collectMemberFiles(files) {
15751
15817
  }
15752
15818
  function readmeProse(files) {
15753
15819
  const readme = files.find((f) => f.path === "README.md");
15754
- if (!readme) return void 0;
15755
- const prose = readme.content.split("\n").map((line) => line.trim()).find(
15756
- (line) => line && !line.startsWith("#") && !line.startsWith("![") && !line.startsWith("[![") && !line.startsWith("<")
15757
- );
15758
- return prose?.slice(0, 200);
15820
+ return readme ? firstParagraph(readme.content) : void 0;
15759
15821
  }
15760
15822
  function inferNestedManifest(files, fallbackName) {
15761
- const members = collectMemberFiles(files).sort(
15762
- (a, b) => a.folder < b.folder ? -1 : a.folder > b.folder ? 1 : 0
15763
- );
15823
+ const members = collectMemberFiles(files).sort((a, b) => a.folder < b.folder ? -1 : a.folder > b.folder ? 1 : 0);
15764
15824
  if (members.length === 0) {
15765
15825
  return null;
15766
15826
  }
@@ -15770,13 +15830,10 @@ function inferNestedManifest(files, fallbackName) {
15770
15830
  const entryPath = member.claudePath ?? member.agentsPath ?? member.plainPath;
15771
15831
  const file2 = files.find((f) => f.path === entryPath);
15772
15832
  const meta3 = readSkillMeta(file2.content);
15773
- let entryName = slugify2(meta3.name ?? member.folder);
15774
- if (used.has(entryName)) entryName = slugify2(member.folder);
15833
+ let entryName = slugForSkill(meta3.name ?? member.folder);
15834
+ if (used.has(entryName)) entryName = slugForSkill(member.folder);
15775
15835
  used.add(entryName);
15776
- const targets = member.plainPath ? inferTargets(files) : [
15777
- ...member.claudePath ? ["claude-code"] : [],
15778
- ...member.agentsPath ? ["codex"] : []
15779
- ];
15836
+ const targets = member.plainPath ? inferTargets(files) : [...member.claudePath ? ["claude-code"] : [], ...member.agentsPath ? ["codex"] : []];
15780
15837
  for (const target of targets) packTargets.add(target);
15781
15838
  return {
15782
15839
  name: entryName,
@@ -15787,10 +15844,10 @@ function inferNestedManifest(files, fallbackName) {
15787
15844
  };
15788
15845
  });
15789
15846
  const single = entries.length === 1 ? entries[0] : null;
15790
- const permissions = [...new Set(detectPermissions(files).map((d) => d.permission))];
15847
+ const permissions = detectPermissions(files);
15791
15848
  return {
15792
15849
  schemaVersion: "0.1",
15793
- name: single ? single.name : slugify2(fallbackName),
15850
+ name: single ? single.name : slugForSkill(fallbackName),
15794
15851
  description: single ? single.description ?? single.name : readmeProse(files) ?? `A pack of ${entries.length} skills`,
15795
15852
  targets: [...packTargets],
15796
15853
  permissions,
@@ -15820,82 +15877,33 @@ function resolveEntries(dir, manifest, files) {
15820
15877
  ]);
15821
15878
  }
15822
15879
  var byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
15823
- function loadPackageFromFiles(files, name = "package") {
15880
+ function loadPackageFromFiles(files, name = "package", binaries = []) {
15824
15881
  const sorted = [...files].sort(byPath);
15825
15882
  let manifest = readManifest(sorted);
15826
15883
  if (manifest.state === "missing") {
15827
15884
  const data = sorted.some((f) => f.path === "SKILL.md") ? inferManifest(sorted, name) : inferNestedManifest(sorted, name);
15828
15885
  if (data) {
15829
- manifest = { state: "ok", inferred: true, data, raw: JSON.stringify(data, null, 2) };
15886
+ manifest = { state: "ok", inferred: true, data };
15830
15887
  }
15831
15888
  }
15832
15889
  return {
15833
- dir: name,
15834
15890
  files: sorted,
15835
15891
  manifest,
15836
15892
  entries: resolveEntries(name, manifest, sorted),
15837
- sourceHash: hashFiles(sorted)
15893
+ sourceHash: hashFiles(sorted),
15894
+ binaries
15838
15895
  };
15839
15896
  }
15840
15897
  function loadPackage(dir) {
15841
- let files;
15898
+ let tree;
15842
15899
  try {
15843
- files = walk(dir);
15900
+ tree = walk(dir);
15844
15901
  } catch (err2) {
15845
15902
  throw new PackageReadError(dir, err2);
15846
15903
  }
15847
- return loadPackageFromFiles(files, dir);
15904
+ return loadPackageFromFiles(tree.files, dir, tree.binaries);
15848
15905
  }
15849
15906
 
15850
- // ../validator/src/rules/structure.ts
15851
- var structureRule = (pkg) => {
15852
- if (pkg.files.length === 0) {
15853
- return [
15854
- {
15855
- severity: "failure",
15856
- code: "empty-package",
15857
- message: "the package contains no files"
15858
- }
15859
- ];
15860
- }
15861
- const findings = [];
15862
- if (pkg.manifest.state === "missing") {
15863
- findings.push({
15864
- severity: "warning",
15865
- code: "missing-manifest",
15866
- message: "no skill.json manifest; permissions and targets are undeclared"
15867
- });
15868
- } else if (pkg.manifest.state === "invalid") {
15869
- findings.push({
15870
- severity: "failure",
15871
- code: "invalid-manifest",
15872
- message: `skill.json is invalid: ${pkg.manifest.error}`,
15873
- location: { path: "skill.json" }
15874
- });
15875
- }
15876
- for (const entry of pkg.entries) {
15877
- if (!entry.exists) {
15878
- findings.push({
15879
- severity: "failure",
15880
- code: "missing-skill-file",
15881
- message: `skill "${entry.skillName}" references "${entry.path}", which does not exist`,
15882
- location: { path: entry.path }
15883
- });
15884
- } else {
15885
- const file2 = pkg.files.find((f) => f.path === entry.path);
15886
- if (file2 !== void 0 && file2.content.trim() === "") {
15887
- findings.push({
15888
- severity: "failure",
15889
- code: "missing-skill-file",
15890
- message: `skill "${entry.skillName}" entry "${entry.path}" is empty`,
15891
- location: { path: entry.path }
15892
- });
15893
- }
15894
- }
15895
- }
15896
- return findings;
15897
- };
15898
-
15899
15907
  // ../validator/src/rules/content.ts
15900
15908
  var SECRET_PATTERNS = [
15901
15909
  {
@@ -16012,10 +16020,12 @@ var CREDENTIAL_PATTERNS = [
16012
16020
  },
16013
16021
  {
16014
16022
  code: "credential-harvesting",
16015
- // The ::/. lookbehinds skip code identifiers (`Box::leak(secret)`,
16016
- // `.leak(`) so security docs naming anti-patterns don't flag.
16023
+ // The verb has to sit where an instruction puts it (line or sentence
16024
+ // start, or after then/and/to/should/must): narrative like "timing leaks
16025
+ // secret bits" has a noun subject and does not fire. The ::/. lookbehinds
16026
+ // skip code identifiers (`Box::leak(secret)`, `.leak(`).
16017
16027
  pattern: new RegExp(
16018
- String.raw`(?<!\b(?:never|not|don'?t|avoids?|prevents?|stops?)\s)(?<!::)(?<!\.)\b(?:exfiltrates?|steals?|harvests?|dumps?|leaks?)\b[^.\n]{0,60}?${SECRET_NOUN}`,
16028
+ String.raw`(?:^[-*\d.)\s]*|[.:;!?]\s+|\b(?:then|and|or|to|should|must|now|please)\s+)(?<!\b(?:never|not|don'?t|avoids?|prevents?|stops?)\s)(?<!::)(?<!\.)(?:exfiltrate|steal|harvest|dump|leak)s?\b[^.\n]{0,60}?${SECRET_NOUN}`,
16019
16029
  "i"
16020
16030
  ),
16021
16031
  message: "matches a credential-exfiltration phrase"
@@ -16029,36 +16039,143 @@ var CREDENTIAL_PATTERNS = [
16029
16039
  message: "matches a credential-file network-transmission phrase"
16030
16040
  }
16031
16041
  ];
16032
- var advisory = (rows) => rows.map((row2) => ({ ...row2, severity: "warning" }));
16042
+ var advisory = (rows, mentionAware = false) => rows.map((row2) => ({ ...row2, severity: "warning", mentionAware }));
16033
16043
  var ALL_ROWS = [
16034
16044
  ...SECRET_PATTERNS,
16035
- ...advisory(INJECTION_PATTERNS),
16045
+ ...advisory(INJECTION_PATTERNS, true),
16036
16046
  ...advisory(DANGEROUS_PATTERNS),
16037
16047
  ...advisory(MALWARE_PATTERNS),
16038
- ...advisory(CREDENTIAL_PATTERNS)
16048
+ ...advisory(CREDENTIAL_PATTERNS, true)
16039
16049
  ];
16050
+ var MENTION_LEAD_RE = /\b(?:flags?|flagged|detects?|detecting|look(?:ing)?\s+for|watch\s+for|check(?:ing)?\s+for|signs?\s+of|indicators?\s+of|evidence\s+of|red\s+flags?|examples?|e\.g\.|such\s+as|attempts?\s+to|designed\s+to|(?:code|scripts?|files?|comments?|text|instructions?)\s+that)\b/i;
16051
+ var TRANSMIT_VERB_RE = /\b(?:post|send|curl|transmit|exfiltrate)s?\b/i;
16052
+ function matchAnchor(line, match) {
16053
+ if (match[0]) return match.index;
16054
+ const verb = line.search(TRANSMIT_VERB_RE);
16055
+ return verb < 0 ? line.length : verb;
16056
+ }
16057
+ function isMention(line, anchor, inFence) {
16058
+ if (inFence || line.trimStart().startsWith("|")) return true;
16059
+ const before = line.slice(0, anchor);
16060
+ const inside = (mark) => (before.split(mark).length - 1) % 2 === 1;
16061
+ return inside('"') || inside("`") || MENTION_LEAD_RE.test(before);
16062
+ }
16063
+ var FENCE_RE = /^\s*(?:```|~~~)/;
16064
+ var DOCUMENTED_PLACEHOLDERS = ["AKIAIOSFODNN7EXAMPLE", "AKIAI44QH8DHBEXAMPLE"];
16065
+ var withoutPlaceholders = (line) => DOCUMENTED_PLACEHOLDERS.reduce((l, p) => l.replaceAll(p, " "), line);
16066
+ var REDACTIONS = ALL_ROWS.filter((row2) => row2.redact).map(
16067
+ (row2) => new RegExp(row2.pattern.source, `${row2.pattern.flags}g`)
16068
+ );
16069
+ var redactLine = (line) => REDACTIONS.reduce((l, re) => l.replace(re, "[redacted]"), line);
16040
16070
  var contentRule = (pkg) => {
16041
16071
  const findings = [];
16042
16072
  for (const file2 of pkg.files) {
16043
- file2.content.split("\n").forEach((line, i2) => {
16044
- for (const row2 of ALL_ROWS) {
16045
- if (row2.pattern.test(line)) {
16046
- const snippet = row2.redact ? line.trim().replace(row2.pattern, "[redacted]") : line.trim();
16047
- findings.push({
16048
- severity: row2.severity ?? "failure",
16049
- code: row2.code,
16050
- message: row2.message,
16051
- location: { path: file2.path, line: i2 + 1, snippet }
16052
- });
16053
- }
16073
+ let inFence = false;
16074
+ file2.content.split("\n").forEach((rawLine, i2) => {
16075
+ if (FENCE_RE.test(rawLine)) {
16076
+ inFence = !inFence;
16077
+ return;
16054
16078
  }
16079
+ const line = withoutPlaceholders(rawLine);
16080
+ const hits = ALL_ROWS.filter((row2) => {
16081
+ const match = row2.pattern.exec(line);
16082
+ return match !== null && !(row2.mentionAware && isMention(line, matchAnchor(line, match), inFence));
16083
+ });
16084
+ if (hits.length === 0) return;
16085
+ const snippet = redactLine(line).trim();
16086
+ for (const row2 of hits) {
16087
+ findings.push({
16088
+ severity: row2.severity ?? "failure",
16089
+ code: row2.code,
16090
+ message: row2.message,
16091
+ location: { path: file2.path, line: i2 + 1, snippet }
16092
+ });
16093
+ }
16094
+ });
16095
+ }
16096
+ return findings;
16097
+ };
16098
+
16099
+ // ../validator/src/rules/structure.ts
16100
+ var structureRule = (pkg) => {
16101
+ if (pkg.files.length === 0) {
16102
+ return [
16103
+ {
16104
+ severity: "failure",
16105
+ code: "empty-package",
16106
+ message: "the package contains no files"
16107
+ }
16108
+ ];
16109
+ }
16110
+ const findings = [];
16111
+ if (pkg.manifest.state === "missing") {
16112
+ findings.push({
16113
+ severity: "warning",
16114
+ code: "missing-manifest",
16115
+ message: "no skill.json manifest; permissions and targets are undeclared"
16116
+ });
16117
+ } else if (pkg.manifest.state === "invalid") {
16118
+ findings.push({
16119
+ severity: "failure",
16120
+ code: "invalid-manifest",
16121
+ message: `skill.json is invalid: ${pkg.manifest.error}`,
16122
+ location: { path: "skill.json" }
16055
16123
  });
16056
16124
  }
16125
+ for (const path of pkg.binaries) {
16126
+ findings.push({
16127
+ severity: "warning",
16128
+ code: "binary-dropped",
16129
+ message: `"${path}" is not a text file and was left out of the snapshot; installs will not include it`,
16130
+ location: { path }
16131
+ });
16132
+ }
16133
+ for (const entry of pkg.entries) {
16134
+ if (!entry.exists) {
16135
+ findings.push({
16136
+ severity: "failure",
16137
+ code: "missing-skill-file",
16138
+ message: `skill "${entry.skillName}" references "${entry.path}", which does not exist`,
16139
+ location: { path: entry.path }
16140
+ });
16141
+ } else {
16142
+ const file2 = pkg.files.find((f) => f.path === entry.path);
16143
+ if (file2 !== void 0 && file2.content.trim() === "") {
16144
+ findings.push({
16145
+ severity: "failure",
16146
+ code: "missing-skill-file",
16147
+ message: `skill "${entry.skillName}" entry "${entry.path}" is empty`,
16148
+ location: { path: entry.path }
16149
+ });
16150
+ }
16151
+ }
16152
+ }
16057
16153
  return findings;
16058
16154
  };
16059
16155
 
16156
+ // ../validator/package.json
16157
+ var package_default = {
16158
+ name: "validator",
16159
+ version: "0.3.0",
16160
+ private: true,
16161
+ type: "module",
16162
+ exports: {
16163
+ ".": "./src/index.ts"
16164
+ },
16165
+ scripts: {
16166
+ typecheck: "tsc --noEmit"
16167
+ },
16168
+ dependencies: {
16169
+ "skill-schema": "workspace:*"
16170
+ },
16171
+ devDependencies: {
16172
+ "@types/node": "^22.0.0",
16173
+ typescript: "^6.0.3"
16174
+ }
16175
+ };
16176
+
16060
16177
  // ../validator/src/validate.ts
16061
- var ENGINE_VERSION = "0.3.0";
16178
+ var ENGINE_VERSION = package_default.version;
16062
16179
  var RULES = [
16063
16180
  { key: "structure", label: "Check package structure", run: structureRule },
16064
16181
  { key: "content", label: "Scan content for risky patterns", run: contentRule }
@@ -16070,7 +16187,7 @@ function toReportFindings(findings) {
16070
16187
  }
16071
16188
  function buildReport(pkg, findings, opts = {}) {
16072
16189
  const declared = pkg.manifest.state === "ok" ? pkg.manifest.data.permissions : [];
16073
- const detected = detectPermissions(pkg.files).map((d) => d.permission).sort();
16190
+ const detected = [...detectPermissions(pkg.files)].sort();
16074
16191
  const status = findings.some((f) => f.severity === "failure") ? "failed" : findings.some((f) => f.severity === "warning") ? "warning" : "passed";
16075
16192
  const report = {
16076
16193
  schemaVersion: "0.1",
@@ -16121,10 +16238,23 @@ async function getParsed(fetchImpl, url2, schema) {
16121
16238
  try {
16122
16239
  body = await res.json();
16123
16240
  } catch {
16124
- return { ok: false, notFound: false, message: `unexpected response from the API (${res.status})` };
16241
+ return {
16242
+ ok: false,
16243
+ notFound: false,
16244
+ message: `unexpected response from the API (${res.status})`,
16245
+ status: res.status
16246
+ };
16125
16247
  }
16126
16248
  if (typeof body !== "object" || body === null || !("success" in body) || body.success !== true) {
16127
- return res.status === 404 ? { ok: false, notFound: true, message: "not found" } : { ok: false, notFound: false, message: `the API returned an error (${res.status})` };
16249
+ const error51 = body?.error;
16250
+ const apiError = typeof error51 === "string" ? { apiError: error51 } : {};
16251
+ return res.status === 404 ? { ok: false, notFound: true, message: "not found", status: 404, ...apiError } : {
16252
+ ok: false,
16253
+ notFound: false,
16254
+ message: `the API returned an error (${res.status})`,
16255
+ status: res.status,
16256
+ ...apiError
16257
+ };
16128
16258
  }
16129
16259
  const parsed = schema.safeParse(body.data);
16130
16260
  if (!parsed.success) {
@@ -16160,6 +16290,52 @@ async function fetchPreflight(fetchImpl, apiUrl, ref) {
16160
16290
  return { ok: true, slug, detail: detail.data, preflight: preflight.data };
16161
16291
  }
16162
16292
 
16293
+ // src/install.ts
16294
+ import { existsSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
16295
+ var GLOBAL_NEEDS_TARGET = "error: --global needs --target (e.g. --target claude-code)";
16296
+ var TARGET_OR_DIR = "error: pass --target or --dir, not both";
16297
+ function createOutput(emit) {
16298
+ const lines = [];
16299
+ const streamed = emit !== void 0;
16300
+ const push = (...next) => {
16301
+ lines.push(...next);
16302
+ if (next.length > 0) emit?.(next.join("\n"));
16303
+ };
16304
+ const done = (exitCode) => ({ lines, exitCode, streamed });
16305
+ return { push, done };
16306
+ }
16307
+ function isOccupied(path) {
16308
+ return existsSync(path) && (!statSync2(path).isDirectory() || readdirSync2(path).length > 0);
16309
+ }
16310
+ function receiptFor(preflight, packSlug) {
16311
+ return {
16312
+ version: preflight.version,
16313
+ sourceHash: preflight.sourceHash,
16314
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
16315
+ ...packSlug ? { pack: { slug: packSlug, version: preflight.version } } : {}
16316
+ };
16317
+ }
16318
+ function memberFiles(files, sourceDir) {
16319
+ if (sourceDir === ".") {
16320
+ return files;
16321
+ }
16322
+ const prefix = `${sourceDir}/`;
16323
+ return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
16324
+ }
16325
+ function planMembers(installs, files) {
16326
+ return installs.map((m) => ({ ...m, files: memberFiles(files, m.sourceDir) }));
16327
+ }
16328
+ async function confirmRisk(preflight, opts, question, abortLine, push) {
16329
+ if (preflight.riskLevel === "low" || opts.yes) return true;
16330
+ if (!opts.confirmImpl) {
16331
+ push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
16332
+ return false;
16333
+ }
16334
+ if (await opts.confirmImpl(question)) return true;
16335
+ push("", abortLine);
16336
+ return false;
16337
+ }
16338
+
16163
16339
  // src/pack.ts
16164
16340
  import { posix } from "node:path";
16165
16341
  function resolvePackMembers(members, target) {
@@ -16308,10 +16484,7 @@ function renderPreflightReport(detail, preflight, st = PLAIN) {
16308
16484
  ...renderDiff(preflight.diff)
16309
16485
  ];
16310
16486
  if (preflight.blocked) {
16311
- lines.push(
16312
- "",
16313
- st.red(`BLOCKED: ${preflight.blockedReason ?? "this version cannot be downloaded"}`)
16314
- );
16487
+ lines.push("", st.red(`BLOCKED: ${preflight.blockedReason ?? "this version cannot be downloaded"}`));
16315
16488
  }
16316
16489
  return lines;
16317
16490
  }
@@ -16334,37 +16507,39 @@ function renderDiff(diff) {
16334
16507
  // src/targets.ts
16335
16508
  import { homedir } from "node:os";
16336
16509
  import { join as join3, resolve } from "node:path";
16337
- function installAreas(home = homedir()) {
16338
- return {
16339
- "claude-code": { project: join3(".claude", "skills"), global: join3(home, ".claude", "skills") },
16340
- codex: { project: join3(".agents", "skills") }
16341
- };
16510
+ var SHARED = { project: join3(".agents", "skills"), global: join3(".agents", "skills"), layout: "codex" };
16511
+ var INSTALL_TOOLS = {
16512
+ "claude-code": { project: join3(".claude", "skills"), global: join3(".claude", "skills"), layout: "claude-code" },
16513
+ agents: SHARED,
16514
+ codex: SHARED,
16515
+ cursor: SHARED,
16516
+ windsurf: SHARED,
16517
+ "github-copilot": SHARED,
16518
+ "gemini-cli": SHARED,
16519
+ opencode: SHARED,
16520
+ cline: { project: join3(".cline", "skills"), global: join3(".cline", "skills"), layout: "codex" }
16521
+ };
16522
+ var INSTALL_TOOL_NAMES = Object.keys(INSTALL_TOOLS);
16523
+ function lookupTool(name) {
16524
+ return Object.hasOwn(INSTALL_TOOLS, name) ? INSTALL_TOOLS[name] : void 0;
16525
+ }
16526
+ var MAPPED_TARGETS = TARGETS.filter((t) => lookupTool(t) !== void 0);
16527
+ function layoutTarget(tool) {
16528
+ return lookupTool(tool)?.layout;
16529
+ }
16530
+ function declaresTool(declared, tool) {
16531
+ const layout = layoutTarget(tool);
16532
+ return declared.includes(tool) || layout !== void 0 && declared.includes(layout);
16342
16533
  }
16343
- var MAPPED_TARGETS = Object.keys(installAreas());
16344
16534
  function resolveTargetArea(target, global = false, home) {
16345
- if (!TARGETS.includes(target)) {
16346
- return {
16347
- ok: false,
16348
- message: `unknown target "${target}" (known tools: ${TARGETS.join(", ")})`
16349
- };
16350
- }
16351
- const area = installAreas(home)[target];
16352
- if (!area) {
16353
- return {
16354
- ok: false,
16355
- message: `${target} has no standard skills folder yet; use --dir to pick a location`
16356
- };
16357
- }
16358
- if (global) {
16359
- if (!area.global) {
16360
- return {
16361
- ok: false,
16362
- message: `${target} has no user-level skills folder; install per project instead`
16363
- };
16535
+ const tool = lookupTool(target);
16536
+ if (!tool) {
16537
+ if (TARGETS.includes(target)) {
16538
+ return { ok: false, message: `${target} has no standard skills folder yet; use --dir to pick a location` };
16364
16539
  }
16365
- return { ok: true, dir: area.global };
16540
+ return { ok: false, message: `unknown target "${target}" (known tools: ${INSTALL_TOOL_NAMES.join(", ")})` };
16366
16541
  }
16367
- return { ok: true, dir: area.project };
16542
+ return { ok: true, dir: global ? join3(home ?? homedir(), tool.global) : tool.project };
16368
16543
  }
16369
16544
  function resolveTargetDir(target, slug, global = false, home) {
16370
16545
  const area = resolveTargetArea(target, global, home);
@@ -16374,24 +16549,48 @@ function mappableDeclaredTargets(declared) {
16374
16549
  return declared.filter((t) => MAPPED_TARGETS.includes(t));
16375
16550
  }
16376
16551
  function knownAreas(cwd, home) {
16377
- const areas = [];
16378
- const byTarget = installAreas(home);
16379
- for (const tool of MAPPED_TARGETS) {
16380
- const area = byTarget[tool];
16381
- if (!area) {
16382
- continue;
16383
- }
16384
- areas.push({
16385
- tool,
16386
- global: false,
16387
- label: `${tool} project (${area.project})`,
16388
- dir: resolve(cwd, area.project)
16389
- });
16390
- if (area.global) {
16391
- areas.push({ tool, global: true, label: `${tool} user (${area.global})`, dir: area.global });
16552
+ const areas = /* @__PURE__ */ new Map();
16553
+ for (const [name, tool] of Object.entries(INSTALL_TOOLS)) {
16554
+ const scopes = [
16555
+ { global: false, display: tool.project, dir: resolve(cwd, tool.project) },
16556
+ { global: true, display: join3(home ?? homedir(), tool.global), dir: join3(home ?? homedir(), tool.global) }
16557
+ ];
16558
+ for (const scope of scopes) {
16559
+ const existing = areas.get(scope.dir);
16560
+ if (existing) {
16561
+ if (!existing.tools.includes(name)) existing.tools.push(name);
16562
+ existing.project ||= !scope.global;
16563
+ existing.global ||= scope.global;
16564
+ existing.label = areaLabel(existing);
16565
+ continue;
16566
+ }
16567
+ const area = {
16568
+ tools: [name],
16569
+ layout: tool.layout,
16570
+ project: !scope.global,
16571
+ global: scope.global,
16572
+ label: "",
16573
+ dir: scope.dir
16574
+ };
16575
+ area.label = areaLabel(area, scope.display);
16576
+ areas.set(scope.dir, area);
16392
16577
  }
16393
16578
  }
16394
- return areas;
16579
+ return [...areas.values()];
16580
+ }
16581
+ function areaLabel(area, display) {
16582
+ const shown = display ?? area.label.slice(0, area.label.indexOf(" ("));
16583
+ const scopes = [...area.project ? ["project"] : [], ...area.global ? ["user"] : []].join(", ");
16584
+ return `${shown} (${scopes}) - ${area.tools.join(", ")}`;
16585
+ }
16586
+ function resolveArea(target, global, cwd, home) {
16587
+ const resolved = resolveTargetArea(target, global, home);
16588
+ if (!resolved.ok) {
16589
+ return resolved;
16590
+ }
16591
+ const dir = resolve(cwd, resolved.dir);
16592
+ const area = knownAreas(cwd, home).find((a) => a.dir === dir);
16593
+ return area ? { ok: true, area } : { ok: false, message: `no known skills area for ${target}` };
16395
16594
  }
16396
16595
 
16397
16596
  // src/add.ts
@@ -16464,7 +16663,7 @@ function writeTree(files, target) {
16464
16663
  writeFileSync2(filePath, file2.content);
16465
16664
  }
16466
16665
  mkdirSync2(dirname(target), { recursive: true });
16467
- if (existsSync(target)) {
16666
+ if (existsSync2(target)) {
16468
16667
  rmdirSync(target);
16469
16668
  }
16470
16669
  renameSync(tempDir, target);
@@ -16473,309 +16672,247 @@ function writeTree(files, target) {
16473
16672
  throw err2;
16474
16673
  }
16475
16674
  }
16476
- function memberFiles(files, sourceDir) {
16477
- if (sourceDir === ".") {
16478
- return files;
16479
- }
16480
- const prefix = `${sourceDir}/`;
16481
- return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
16675
+ function orderedAreas(declared, cwd, home) {
16676
+ const areas = knownAreas(cwd, home).map((area) => ({
16677
+ ...area,
16678
+ declared: area.tools.some((tool) => declaresTool(declared, tool))
16679
+ }));
16680
+ return [...areas.filter((a) => a.declared), ...areas.filter((a) => !a.declared)];
16482
16681
  }
16483
- function installChoices(declared, slug) {
16484
- const choices = [];
16485
- const ordered = [
16486
- ...MAPPED_TARGETS.filter((t) => declared.includes(t)),
16487
- ...MAPPED_TARGETS.filter((t) => !declared.includes(t))
16488
- ];
16489
- for (const tool of ordered) {
16490
- const note = declared.includes(tool) ? "" : " - not declared by this skill";
16491
- const resolved = resolveTargetDir(tool, slug);
16492
- if (resolved.ok) {
16493
- choices.push({ label: `${tool} skills folder (${resolved.dir})${note}`, dir: resolved.dir });
16494
- }
16495
- const global = resolveTargetDir(tool, slug, true);
16496
- if (global.ok) {
16497
- choices.push({
16498
- label: `${tool} user-level skills folder (${global.dir})${note}`,
16499
- dir: global.dir
16500
- });
16501
- }
16502
- }
16682
+ function installChoices(declared, slug, cwd, home) {
16683
+ const choices = orderedAreas(declared, cwd, home).map((area) => ({
16684
+ label: `${area.label}${area.declared ? "" : " - not declared by this skill"}`,
16685
+ dir: join4(area.dir, slug)
16686
+ }));
16503
16687
  choices.push({ label: `current directory (./${slug})`, dir: slug });
16504
16688
  return choices;
16505
16689
  }
16506
- function packChoices(declared, slug, count) {
16507
- const choices = [];
16508
- const ordered = [
16509
- ...MAPPED_TARGETS.filter((t) => declared.includes(t)),
16510
- ...MAPPED_TARGETS.filter((t) => !declared.includes(t))
16511
- ];
16512
- for (const tool of ordered) {
16513
- const note = declared.includes(tool) ? "" : " - not declared by this pack";
16514
- const project = resolveTargetArea(tool);
16515
- if (project.ok) {
16516
- choices.push({
16517
- label: `${tool} skills folder (${project.dir}) - ${count} skills${note}`,
16518
- tool,
16519
- global: false
16520
- });
16521
- }
16522
- const global = resolveTargetArea(tool, true);
16523
- if (global.ok) {
16524
- choices.push({
16525
- label: `${tool} user-level skills folder (${global.dir}) - ${count} skills${note}`,
16526
- tool,
16527
- global: true
16528
- });
16529
- }
16530
- }
16690
+ function packChoices(declared, slug, count, cwd, home) {
16691
+ const choices = orderedAreas(declared, cwd, home).map((area) => ({
16692
+ label: `${area.label} - ${count} skills${area.declared ? "" : " - not declared by this pack"}`,
16693
+ area
16694
+ }));
16531
16695
  choices.push({ label: `current directory (./${slug}, raw pack source)` });
16532
16696
  return choices;
16533
16697
  }
16534
- async function runAdd(ref, opts = {}) {
16535
- const lines = [];
16536
- const streamed = opts.emit !== void 0;
16537
- const push = (...next) => {
16538
- lines.push(...next);
16539
- if (next.length > 0) {
16540
- opts.emit?.(next.join("\n"));
16541
- }
16542
- };
16543
- const done = (exitCode) => ({ lines, exitCode, streamed });
16544
- if (opts.target && opts.dir) {
16545
- push("error: pass --target or --dir, not both");
16698
+ async function promptIndex(ask, count, push) {
16699
+ while (true) {
16700
+ const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
16701
+ if (answer === "") return 0;
16702
+ const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
16703
+ if (index >= 0 && index < count) return index;
16704
+ push(` answer 1-${count}, or press enter for the default`);
16705
+ }
16706
+ }
16707
+ async function choosePackLocation(ctx, detail, memberCount, prompt) {
16708
+ if (prompt) {
16709
+ const choices = packChoices(detail.targets, ctx.slug, memberCount, ctx.cwd, ctx.home);
16710
+ ctx.push("", "Install location:");
16711
+ choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
16712
+ const chosen = choices[await promptIndex(prompt, choices.length, ctx.push)];
16713
+ return chosen.area ? { area: chosen.area } : { dir: ctx.slug };
16714
+ }
16715
+ const mappable = mappableDeclaredTargets(detail.targets);
16716
+ if (mappable.length > 0) {
16717
+ ctx.push("", `tip: --target ${mappable[0]} installs the ${memberCount} skills into the tool's skills folder`);
16718
+ }
16719
+ return { dir: ctx.slug };
16720
+ }
16721
+ async function chooseSingleLocation(ctx, detail, prompt) {
16722
+ const choices = installChoices(detail.targets, ctx.slug, ctx.cwd, ctx.home);
16723
+ if (prompt && choices.length > 1) {
16724
+ ctx.push("", "Install location:");
16725
+ choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
16726
+ return choices[await promptIndex(prompt, choices.length, ctx.push)].dir;
16727
+ }
16728
+ const mappable = mappableDeclaredTargets(detail.targets);
16729
+ if (mappable.length > 0) {
16730
+ ctx.push("", `tip: --target ${mappable[0]} installs into the tool's skills folder`);
16731
+ }
16732
+ return ctx.slug;
16733
+ }
16734
+ async function installPack(ctx, members, targets) {
16735
+ const { push, done, slug, preflight } = ctx;
16736
+ const resolved = targets.map(({ area, tool }) => ({ area, tool, ...resolvePackMembers(members, area.layout) }));
16737
+ const unsupported = resolved.find((r) => r.installs.length === 0);
16738
+ if (unsupported) {
16739
+ push("", `error: none of this pack's skills support ${unsupported.tool}`);
16546
16740
  return done(2);
16547
16741
  }
16548
- if (opts.global && !opts.target) {
16549
- push("error: --global needs --target (e.g. --target claude-code)");
16742
+ const conflicts = resolved.flatMap((r) => r.installs.map((m) => join4(r.area.dir, m.name))).filter(isOccupied);
16743
+ if (conflicts.length > 0) {
16744
+ push(
16745
+ "",
16746
+ "error: these destinations already exist and are not empty; nothing was installed:",
16747
+ ...conflicts.map((c) => ` ${c}`)
16748
+ );
16550
16749
  return done(2);
16551
16750
  }
16552
- const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
16553
- const apiUrl = resolveApiUrl(opts.apiUrl);
16554
- const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
16555
- if (!fetched.ok) {
16556
- push(...fetched.result.lines);
16557
- return done(fetched.result.exitCode);
16558
- }
16559
- const { slug, detail, preflight } = fetched;
16560
- push(...renderPreflightReport(detail, preflight, opts.style));
16561
- if (preflight.blocked) {
16562
- return done(1);
16751
+ const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
16752
+ if (!download.ok) {
16753
+ push("", `error: ${download.message}`);
16754
+ return done(2);
16563
16755
  }
16564
- const members = detail.packMembers ?? [];
16565
- const isPack = members.length > 0;
16566
- let targetDir = opts.dir;
16567
- let packTool;
16568
- let packGlobal = false;
16569
- if (opts.target) {
16570
- const resolved = isPack ? resolveTargetArea(opts.target, opts.global) : resolveTargetDir(opts.target, slug, opts.global);
16571
- if (!resolved.ok) {
16572
- push("", `error: ${resolved.message}`);
16573
- return done(2);
16574
- }
16575
- if (isPack) {
16576
- packTool = opts.target;
16577
- packGlobal = opts.global ?? false;
16578
- } else {
16579
- targetDir = resolved.dir;
16580
- }
16581
- if (!detail.targets.includes(opts.target)) {
16582
- push("", `warning: this skill does not declare ${opts.target} as a target`);
16583
- }
16756
+ const planned = resolved.map((r) => ({ ...r, plans: planMembers(r.installs, download.files) }));
16757
+ const missing = planned.flatMap((r) => r.plans).find((p) => p.files.length === 0);
16758
+ if (missing) {
16759
+ push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
16760
+ return done(2);
16584
16761
  }
16585
- if (preflight.riskLevel !== "low" && !opts.yes) {
16586
- if (!opts.confirmImpl) {
16587
- push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
16588
- return done(2);
16589
- }
16590
- const confirmed = await opts.confirmImpl(
16591
- `Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
16592
- );
16593
- if (!confirmed) {
16594
- push("", "Install aborted.");
16595
- return done(2);
16762
+ for (const r of planned) {
16763
+ for (const name of r.skipped) {
16764
+ push("", `note: ${name} does not support ${r.tool}; skipped`);
16596
16765
  }
16597
16766
  }
16598
- const prompt = opts.promptImpl;
16599
- const promptIndex = async (ask, count) => {
16600
- while (true) {
16601
- const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
16602
- if (answer === "") {
16603
- return 0;
16604
- }
16605
- const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
16606
- if (index >= 0 && index < count) {
16607
- return index;
16608
- }
16609
- push(` answer 1-${count}, or press enter for the default`);
16610
- }
16611
- };
16612
- if (isPack && packTool === void 0 && targetDir === void 0) {
16613
- if (prompt) {
16614
- const choices = packChoices(detail.targets, slug, members.length);
16615
- push("", "Install location:");
16616
- choices.forEach((choice, i2) => push(` ${i2 + 1}) ${choice.label}`));
16617
- const chosen = choices[await promptIndex(prompt, choices.length)];
16618
- if (chosen.tool) {
16619
- packTool = chosen.tool;
16620
- packGlobal = chosen.global ?? false;
16621
- } else {
16622
- targetDir = slug;
16623
- }
16624
- } else {
16625
- targetDir = slug;
16626
- const mappable = mappableDeclaredTargets(detail.targets);
16627
- if (mappable.length > 0) {
16628
- push(
16629
- "",
16630
- `tip: --target ${mappable[0]} installs the ${members.length} skills into the tool's skills folder`
16631
- );
16767
+ const landed = [];
16768
+ const describe3 = (dir, names) => planned.length > 1 ? `${dir}: ${names.join(", ")}` : names.join(", ");
16769
+ for (const r of planned) {
16770
+ const written = [];
16771
+ try {
16772
+ for (const plan of r.plans) {
16773
+ writeTree(plan.files, join4(r.area.dir, plan.name));
16774
+ written.push(plan.name);
16775
+ recordReceipt(r.area.dir, plan.name, receiptFor(preflight, slug));
16632
16776
  }
16633
- }
16634
- }
16635
- if (packTool !== void 0) {
16636
- const area2 = resolveTargetArea(packTool, packGlobal);
16637
- if (!area2.ok) {
16638
- push("", `error: ${area2.message}`);
16639
- return done(2);
16640
- }
16641
- const { installs, skipped } = resolvePackMembers(members, packTool);
16642
- if (installs.length === 0) {
16643
- push("", `error: none of this pack's skills support ${packTool}`);
16644
- return done(2);
16645
- }
16646
- const areaAbs = resolve2(opts.cwd ?? process.cwd(), area2.dir);
16647
- const conflicts = installs.map((m) => join4(areaAbs, m.name)).filter(
16648
- (dest) => existsSync(dest) && (!statSync2(dest).isDirectory() || readdirSync2(dest).length > 0)
16649
- );
16650
- if (conflicts.length > 0) {
16777
+ } catch {
16778
+ const before = [...landed, ...written.length > 0 ? [describe3(r.area.dir, written)] : []];
16779
+ const where = planned.length > 1 ? ` in ${r.area.dir}` : "";
16651
16780
  push(
16652
16781
  "",
16653
- "error: these destinations already exist and are not empty; nothing was installed:",
16654
- ...conflicts.map((c) => ` ${c}`)
16782
+ `error: could not write ${r.plans[written.length].name}${where}; installed before the failure: ${before.join("; ") || "none"}`
16655
16783
  );
16656
16784
  return done(2);
16657
16785
  }
16658
- const download2 = await downloadVerified(
16659
- fetchImpl,
16660
- apiUrl,
16661
- slug,
16662
- preflight.version,
16663
- preflight.sourceHash
16664
- );
16665
- if (!download2.ok) {
16666
- push("", `error: ${download2.message}`);
16786
+ landed.push(describe3(r.area.dir, written));
16787
+ push("", `Installed ${written.length} skills to ${r.area.dir}`, ` ${written.join(", ")}`);
16788
+ }
16789
+ push("Source hash verified against the Skill Passport.");
16790
+ return done(0);
16791
+ }
16792
+ async function installSingle(ctx, targetDirs) {
16793
+ const { push, done, slug, preflight } = ctx;
16794
+ const targets = targetDirs.map((dir) => resolve2(ctx.cwd, dir));
16795
+ for (const target of targets) {
16796
+ if (!existsSync2(target)) continue;
16797
+ if (!statSync3(target).isDirectory()) {
16798
+ push("", `error: target ${target} already exists and is not a directory`);
16667
16799
  return done(2);
16668
16800
  }
16669
- const plans = installs.map((m) => ({ ...m, files: memberFiles(download2.files, m.sourceDir) }));
16670
- const missing = plans.find((p) => p.files.length === 0);
16671
- if (missing) {
16672
- push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
16801
+ if (readdirSync3(target).length > 0) {
16802
+ push("", `error: target directory ${target} already exists and is not empty`);
16673
16803
  return done(2);
16674
16804
  }
16675
- for (const name of skipped) {
16676
- push("", `note: ${name} does not support ${packTool}; skipped`);
16677
- }
16678
- const written = [];
16805
+ }
16806
+ const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
16807
+ if (!download.ok) {
16808
+ push("", `error: ${download.message}`);
16809
+ return done(2);
16810
+ }
16811
+ const areas = knownAreas(ctx.cwd, ctx.home);
16812
+ const landed = [];
16813
+ for (const target of targets) {
16679
16814
  try {
16680
- for (const plan of plans) {
16681
- writeTree(plan.files, join4(areaAbs, plan.name));
16682
- written.push(plan.name);
16683
- }
16815
+ writeTree(download.files, target);
16684
16816
  } catch {
16685
16817
  push(
16686
16818
  "",
16687
- `error: could not write ${plans[written.length].name}; installed before the failure: ${written.join(", ") || "none"}`
16819
+ landed.length === 0 ? "error: could not write the install; nothing was installed" : `error: could not write ${target}; installed before the failure: ${landed.join(", ")}`
16688
16820
  );
16689
16821
  return done(2);
16690
16822
  }
16691
- for (const name of written) {
16692
- recordReceipt(areaAbs, name, {
16693
- version: preflight.version,
16694
- sourceHash: preflight.sourceHash,
16695
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
16696
- pack: { slug, version: preflight.version }
16697
- });
16823
+ landed.push(target);
16824
+ const area = areas.find((a) => a.dir === dirname(target));
16825
+ if (area) {
16826
+ recordReceipt(area.dir, slug, receiptFor(preflight));
16698
16827
  }
16699
- push(
16700
- "",
16701
- `Installed ${written.length} skills to ${areaAbs}`,
16702
- ` ${written.join(", ")}`,
16703
- "Source hash verified against the Skill Passport."
16704
- );
16705
- return done(0);
16828
+ push("", `Installed ${download.files.length} file(s) to ${target}`);
16706
16829
  }
16707
- if (isPack && opts.dir) {
16708
- push(
16709
- "",
16710
- `note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
16711
- );
16830
+ push("Source hash verified against the Skill Passport.");
16831
+ return done(0);
16832
+ }
16833
+ async function runAdd(ref, opts = {}) {
16834
+ const { push, done } = createOutput(opts.emit);
16835
+ const tools = [...new Set([opts.target ?? []].flat())];
16836
+ if (tools.length > 0 && opts.dir) {
16837
+ push(TARGET_OR_DIR);
16838
+ return done(2);
16712
16839
  }
16713
- if (!targetDir) {
16714
- const choices = installChoices(detail.targets, slug);
16715
- if (prompt && choices.length > 1) {
16716
- push("", "Install location:");
16717
- choices.forEach((choice, i2) => push(` ${i2 + 1}) ${choice.label}`));
16718
- targetDir = choices[await promptIndex(prompt, choices.length)].dir;
16719
- } else {
16720
- targetDir = slug;
16721
- const mappable = mappableDeclaredTargets(detail.targets);
16722
- if (mappable.length > 0) {
16723
- push("", `tip: --target ${mappable[0]} installs into the tool's skills folder`);
16724
- }
16725
- }
16840
+ if (opts.global && tools.length === 0) {
16841
+ push(GLOBAL_NEEDS_TARGET);
16842
+ return done(2);
16726
16843
  }
16727
- const target = resolve2(opts.cwd ?? process.cwd(), targetDir);
16728
- if (existsSync(target)) {
16729
- if (!statSync2(target).isDirectory()) {
16730
- push("", `error: target ${target} already exists and is not a directory`);
16844
+ const cwd = opts.cwd ?? process.cwd();
16845
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
16846
+ const apiUrl = resolveApiUrl(opts.apiUrl);
16847
+ const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
16848
+ if (!fetched.ok) {
16849
+ push(...fetched.result.lines);
16850
+ return done(fetched.result.exitCode);
16851
+ }
16852
+ const { slug, detail, preflight } = fetched;
16853
+ push(...renderPreflightReport(detail, preflight, opts.style));
16854
+ if (preflight.blocked) {
16855
+ return done(1);
16856
+ }
16857
+ const members = detail.packMembers ?? [];
16858
+ const isPack = members.length > 0;
16859
+ const targets = [];
16860
+ for (const tool of tools) {
16861
+ const resolved = resolveArea(tool, opts.global ?? false, cwd, opts.home);
16862
+ if (!resolved.ok) {
16863
+ push("", `error: ${resolved.message}`);
16731
16864
  return done(2);
16732
16865
  }
16733
- if (readdirSync2(target).length > 0) {
16734
- push("", `error: target directory ${target} already exists and is not empty`);
16735
- return done(2);
16866
+ const shared = targets.find((t) => t.area.dir === resolved.area.dir);
16867
+ if (shared) {
16868
+ push("", `note: ${tool} and ${shared.tool} share ${resolved.area.dir}; installing once`);
16869
+ continue;
16870
+ }
16871
+ targets.push({ area: resolved.area, tool });
16872
+ if (!declaresTool(detail.targets, tool)) {
16873
+ push("", `warning: this skill does not declare ${tool} as a target`);
16736
16874
  }
16737
16875
  }
16738
- const download = await downloadVerified(
16739
- fetchImpl,
16740
- apiUrl,
16741
- slug,
16742
- preflight.version,
16743
- preflight.sourceHash
16876
+ const confirmed = await confirmRisk(
16877
+ preflight,
16878
+ opts,
16879
+ `Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
16880
+ "Install aborted.",
16881
+ push
16744
16882
  );
16745
- if (!download.ok) {
16746
- push("", `error: ${download.message}`);
16747
- return done(2);
16748
- }
16749
- try {
16750
- writeTree(download.files, target);
16751
- } catch {
16752
- push("", "error: could not write the install; nothing was installed");
16753
- return done(2);
16883
+ if (!confirmed) return done(2);
16884
+ const ctx = { push, done, fetchImpl, apiUrl, cwd, home: opts.home, slug, preflight };
16885
+ if (isPack) {
16886
+ if (targets.length > 0) {
16887
+ return installPack(ctx, members, targets);
16888
+ }
16889
+ if (opts.dir === void 0) {
16890
+ const location = await choosePackLocation(ctx, detail, members.length, opts.promptImpl);
16891
+ return "area" in location ? installPack(ctx, members, [{ area: location.area, tool: location.area.tools[0] }]) : installSingle(ctx, [location.dir]);
16892
+ }
16893
+ push(
16894
+ "",
16895
+ `note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
16896
+ );
16897
+ return installSingle(ctx, [opts.dir]);
16754
16898
  }
16755
- const area = knownAreas(opts.cwd ?? process.cwd()).find((a) => a.dir === dirname(target));
16756
- if (area) {
16757
- recordReceipt(area.dir, slug, {
16758
- version: preflight.version,
16759
- sourceHash: preflight.sourceHash,
16760
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
16761
- });
16899
+ if (targets.length > 0) {
16900
+ return installSingle(
16901
+ ctx,
16902
+ targets.map((t) => join4(t.area.dir, slug))
16903
+ );
16762
16904
  }
16763
- push(
16764
- "",
16765
- `Installed ${download.files.length} file(s) to ${target}`,
16766
- "Source hash verified against the Skill Passport."
16767
- );
16768
- return done(0);
16905
+ return installSingle(ctx, [opts.dir ?? await chooseSingleLocation(ctx, detail, opts.promptImpl)]);
16769
16906
  }
16770
16907
 
16771
16908
  // src/list.ts
16772
- import { existsSync as existsSync2, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
16909
+ import { existsSync as existsSync3, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
16773
16910
  import { join as join5 } from "node:path";
16774
16911
  function installedIn(dir) {
16775
- if (!existsSync2(dir)) {
16912
+ if (!existsSync3(dir)) {
16776
16913
  return [];
16777
16914
  }
16778
- return readdirSync3(dir).sort().filter((name) => statSync3(join5(dir, name)).isDirectory());
16915
+ return readdirSync4(dir).sort().filter((name) => statSync4(join5(dir, name), { throwIfNoEntry: false })?.isDirectory() ?? false);
16779
16916
  }
16780
16917
  function runList(opts = {}) {
16781
16918
  const cwd = opts.cwd ?? process.cwd();
@@ -16810,11 +16947,7 @@ async function identifyByHash(fetchImpl, apiUrl, slug, dir) {
16810
16947
  } catch {
16811
16948
  return void 0;
16812
16949
  }
16813
- const detail = await getParsed(
16814
- fetchImpl,
16815
- `${apiUrl}/skills/${encodeURIComponent(slug)}`,
16816
- publicSkillDetailSchema
16817
- );
16950
+ const detail = await getParsed(fetchImpl, `${apiUrl}/skills/${encodeURIComponent(slug)}`, publicSkillDetailSchema);
16818
16951
  if (!detail.ok) {
16819
16952
  return void 0;
16820
16953
  }
@@ -16859,9 +16992,7 @@ async function runOutdated(opts = {}) {
16859
16992
  }
16860
16993
  seenPacks.add(receipt.pack.slug);
16861
16994
  tracked += 1;
16862
- const members = Object.values(receipts).filter(
16863
- (r) => r.pack?.slug === receipt.pack?.slug
16864
- ).length;
16995
+ const members = Object.values(receipts).filter((r) => r.pack?.slug === receipt.pack?.slug).length;
16865
16996
  const latest = latestBySlug.get(receipt.pack.slug)?.version;
16866
16997
  if (latest === void 0) {
16867
16998
  rows.push(` ${receipt.pack.slug} ${receipt.pack.version} not in the directory`);
@@ -16869,9 +17000,7 @@ async function runOutdated(opts = {}) {
16869
17000
  rows.push(` ${receipt.pack.slug} ${receipt.pack.version} current (pack, ${members} skills)`);
16870
17001
  } else {
16871
17002
  outdatedCount += 1;
16872
- rows.push(
16873
- ` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`
16874
- );
17003
+ rows.push(` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`);
16875
17004
  }
16876
17005
  continue;
16877
17006
  }
@@ -16927,39 +17056,27 @@ async function runOutdated(opts = {}) {
16927
17056
  }
16928
17057
 
16929
17058
  // src/remove.ts
16930
- import { existsSync as existsSync3, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
17059
+ import { existsSync as existsSync4, rmSync as rmSync2, statSync as statSync5 } from "node:fs";
16931
17060
  import { dirname as dirname2, join as join7, resolve as resolve3 } from "node:path";
16932
17061
  function looksLikeInstalledSkill(dir) {
16933
- return existsSync3(join7(dir, "SKILL.md")) || existsSync3(join7(dir, "skill.json"));
17062
+ return existsSync4(join7(dir, "SKILL.md")) || existsSync4(join7(dir, "skill.json"));
16934
17063
  }
16935
17064
  function candidateDirs(slug, cwd, home) {
16936
- const dirs = [];
16937
- const byTarget = installAreas(home);
16938
- for (const tool of MAPPED_TARGETS) {
16939
- const area = byTarget[tool];
16940
- if (!area) {
16941
- continue;
16942
- }
16943
- dirs.push(resolve3(cwd, join7(area.project, slug)));
16944
- if (area.global) {
16945
- dirs.push(join7(area.global, slug));
16946
- }
16947
- }
16948
- dirs.push(resolve3(cwd, slug));
16949
- return [...new Set(dirs)];
17065
+ const inAreas = knownAreas(cwd, home).map((area) => join7(area.dir, slug));
17066
+ return [.../* @__PURE__ */ new Set([...inAreas, resolve3(cwd, slug)])];
16950
17067
  }
16951
17068
  function runRemove(slug, opts = {}) {
16952
17069
  if (opts.target && opts.dir) {
16953
- return { lines: ["error: pass --target or --dir, not both"], exitCode: 2 };
17070
+ return { lines: [TARGET_OR_DIR], exitCode: 2 };
16954
17071
  }
16955
17072
  if (opts.global && !opts.target) {
16956
- return { lines: ["error: --global needs --target (e.g. --target claude-code)"], exitCode: 2 };
17073
+ return { lines: [GLOBAL_NEEDS_TARGET], exitCode: 2 };
16957
17074
  }
16958
17075
  const cwd = opts.cwd ?? process.cwd();
16959
17076
  if (!opts.dir) {
16960
17077
  let areas = knownAreas(cwd, opts.home);
16961
17078
  if (opts.target) {
16962
- areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
17079
+ areas = areas.filter((a) => a.tools.includes(opts.target) && (opts.global ? a.global : a.project));
16963
17080
  }
16964
17081
  const packHits = areas.map((area) => ({
16965
17082
  area,
@@ -16981,10 +17098,7 @@ function runRemove(slug, opts = {}) {
16981
17098
  removeReceipt(area.dir, name);
16982
17099
  }
16983
17100
  return {
16984
- lines: [
16985
- `Removed pack ${slug} (${members.length} skills) from ${area.dir}`,
16986
- ` ${members.sort().join(", ")}`
16987
- ],
17101
+ lines: [`Removed pack ${slug} (${members.length} skills) from ${area.dir}`, ` ${members.sort().join(", ")}`],
16988
17102
  exitCode: 0
16989
17103
  };
16990
17104
  }
@@ -16999,9 +17113,7 @@ function runRemove(slug, opts = {}) {
16999
17113
  } else if (opts.dir) {
17000
17114
  dir = resolve3(cwd, opts.dir);
17001
17115
  } else {
17002
- const found = candidateDirs(slug, cwd, opts.home).filter(
17003
- (candidate) => existsSync3(candidate)
17004
- );
17116
+ const found = candidateDirs(slug, cwd, opts.home).filter((candidate) => existsSync4(candidate));
17005
17117
  if (found.length === 0) {
17006
17118
  return { lines: [`error: ${slug} is not installed in any known location`], exitCode: 2 };
17007
17119
  }
@@ -17016,10 +17128,10 @@ function runRemove(slug, opts = {}) {
17016
17128
  }
17017
17129
  dir = found[0];
17018
17130
  }
17019
- if (!existsSync3(dir)) {
17131
+ if (!existsSync4(dir)) {
17020
17132
  return { lines: [`error: nothing installed at ${dir}`], exitCode: 2 };
17021
17133
  }
17022
- if (!statSync4(dir).isDirectory()) {
17134
+ if (!statSync5(dir).isDirectory()) {
17023
17135
  return { lines: [`error: ${dir} is not a directory`], exitCode: 2 };
17024
17136
  }
17025
17137
  if (!looksLikeInstalledSkill(dir)) {
@@ -17120,25 +17232,23 @@ async function runSearch(opts = {}) {
17120
17232
  }
17121
17233
  if (opts.category && !CATEGORY_SLUGS.includes(opts.category)) {
17122
17234
  return {
17123
- lines: [
17124
- `error: unknown category "${opts.category}" (known categories: ${CATEGORY_SLUGS.join(", ")})`
17125
- ],
17235
+ lines: [`error: unknown category "${opts.category}" (known categories: ${CATEGORY_SLUGS.join(", ")})`],
17126
17236
  exitCode: 2
17127
17237
  };
17128
17238
  }
17129
17239
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
17130
17240
  const apiUrl = resolveApiUrl(opts.apiUrl);
17241
+ if (opts.ai) {
17242
+ return runAiSearch(opts, fetchImpl, apiUrl);
17243
+ }
17131
17244
  const fetched = await getParsed(fetchImpl, `${apiUrl}/skills`, publicSkillListSchema);
17132
17245
  if (!fetched.ok) {
17133
17246
  return { lines: [`error: ${fetched.message}`], exitCode: 2 };
17134
17247
  }
17135
17248
  const query = (opts.query ?? "").trim().toLowerCase();
17136
- const matches = fetched.data.filter((skill) => {
17137
- if (opts.target && !skill.targets.includes(opts.target)) return false;
17138
- if (opts.category && (skill.category ?? null) !== opts.category) return false;
17139
- if (opts.packs && (skill.packSkills?.length ?? 0) === 0) return false;
17140
- return query === "" || matchesQuery(skill, query);
17141
- });
17249
+ const matches = fetched.data.filter(
17250
+ (skill) => matchesFilters(skill, opts) && (query === "" || matchesQuery(skill, query))
17251
+ );
17142
17252
  if (opts.json) {
17143
17253
  return { lines: [JSON.stringify(matches, null, 2)], exitCode: 0 };
17144
17254
  }
@@ -17150,22 +17260,66 @@ async function runSearch(opts = {}) {
17150
17260
  lines: [
17151
17261
  ...renderRows(matches, opts.width, st),
17152
17262
  "",
17153
- st.dim(
17154
- `${matches.length} of ${fetched.data.length} skills - skillpass report <slug> shows the passport`
17155
- )
17263
+ st.dim(`${matches.length} of ${fetched.data.length} skills - skillpass report <slug> shows the passport`)
17264
+ ],
17265
+ exitCode: 0
17266
+ };
17267
+ }
17268
+ function matchesFilters(skill, opts) {
17269
+ if (opts.target && !skill.targets.includes(opts.target)) return false;
17270
+ if (opts.category && (skill.category ?? null) !== opts.category) return false;
17271
+ if (opts.packs && (skill.packSkills?.length ?? 0) === 0) return false;
17272
+ return true;
17273
+ }
17274
+ var AI_ERRORS = {
17275
+ 429: "AI search is rate limited (20 requests a minute per address); try again in a moment",
17276
+ 503: "AI search is not configured on this API",
17277
+ 502: "AI search is temporarily unavailable; try again shortly"
17278
+ };
17279
+ function aiErrorMessage(outcome) {
17280
+ if (outcome.status !== void 0 && AI_ERRORS[outcome.status]) return AI_ERRORS[outcome.status];
17281
+ if (outcome.status === 400 && outcome.apiError) return outcome.apiError;
17282
+ return outcome.message;
17283
+ }
17284
+ async function runAiSearch(opts, fetchImpl, apiUrl) {
17285
+ const query = (opts.query ?? "").trim();
17286
+ if (query === "") {
17287
+ return { lines: ["error: search --ai needs a query (describe what you need)"], exitCode: 2 };
17288
+ }
17289
+ const fetched = await getParsed(
17290
+ fetchImpl,
17291
+ `${apiUrl}/skills/search?q=${encodeURIComponent(query)}`,
17292
+ publicSkillListSchema
17293
+ );
17294
+ if (!fetched.ok) {
17295
+ return { lines: [`error: ${aiErrorMessage(fetched)}`], exitCode: 2 };
17296
+ }
17297
+ const matches = fetched.data.filter((skill) => matchesFilters(skill, opts));
17298
+ if (opts.json) {
17299
+ return { lines: [JSON.stringify(matches, null, 2)], exitCode: 0 };
17300
+ }
17301
+ if (matches.length === 0) {
17302
+ return { lines: ["No skills match by meaning - try other words"], exitCode: 0 };
17303
+ }
17304
+ const st = opts.style ?? PLAIN;
17305
+ return {
17306
+ lines: [
17307
+ ...renderRows(matches, opts.width, st),
17308
+ "",
17309
+ st.dim(`${matches.length} skills by meaning - skillpass report <slug> shows the passport`)
17156
17310
  ],
17157
17311
  exitCode: 0
17158
17312
  };
17159
17313
  }
17160
17314
 
17161
17315
  // src/update.ts
17162
- import { existsSync as existsSync4, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
17316
+ import { existsSync as existsSync5, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
17163
17317
  import { join as join8 } from "node:path";
17164
17318
  async function locate(slug, opts, fetchImpl, apiUrl) {
17165
17319
  const cwd = opts.cwd ?? process.cwd();
17166
17320
  let areas = knownAreas(cwd, opts.home);
17167
17321
  if (opts.target) {
17168
- areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
17322
+ areas = areas.filter((a) => a.tools.includes(opts.target) && (opts.global ? a.global : a.project));
17169
17323
  }
17170
17324
  const hits = [];
17171
17325
  for (const area of areas) {
@@ -17186,7 +17340,7 @@ async function locate(slug, opts, fetchImpl, apiUrl) {
17186
17340
  hits.push({ area, dir: area.dir, installedVersion: packMember.pack.version, packSlug: slug });
17187
17341
  continue;
17188
17342
  }
17189
- if (existsSync4(dir)) {
17343
+ if (existsSync5(dir)) {
17190
17344
  const identified = await identifyByHash(fetchImpl, apiUrl, slug, dir);
17191
17345
  if (identified !== void 0) {
17192
17346
  hits.push({ area, dir, installedVersion: identified });
@@ -17207,30 +17361,23 @@ function permissionChanges(installed, target) {
17207
17361
  function swapTree(files, dir) {
17208
17362
  const fresh = `${dir}.new-${process.pid}`;
17209
17363
  const aside = `${dir}.old-${process.pid}`;
17364
+ rmSync3(fresh, { recursive: true, force: true });
17210
17365
  writeTree(files, fresh);
17211
- renameSync2(dir, aside);
17212
17366
  try {
17367
+ if (existsSync5(dir)) renameSync2(dir, aside);
17213
17368
  renameSync2(fresh, dir);
17214
17369
  } catch (err2) {
17215
- renameSync2(aside, dir);
17370
+ if (existsSync5(aside) && !existsSync5(dir)) renameSync2(aside, dir);
17216
17371
  rmSync3(fresh, { recursive: true, force: true });
17217
17372
  throw err2;
17218
17373
  }
17219
17374
  rmSync3(aside, { recursive: true, force: true });
17220
17375
  }
17221
17376
  async function runUpdate(ref, opts = {}) {
17222
- const lines = [];
17223
- const streamed = opts.emit !== void 0;
17224
- const push = (...next) => {
17225
- lines.push(...next);
17226
- if (next.length > 0) {
17227
- opts.emit?.(next.join("\n"));
17228
- }
17229
- };
17230
- const done = (exitCode) => ({ lines, exitCode, streamed });
17377
+ const { push, done } = createOutput(opts.emit);
17231
17378
  const st = opts.style ?? PLAIN;
17232
17379
  if (opts.global && !opts.target) {
17233
- push("error: --global needs --target (e.g. --target claude-code)");
17380
+ push(GLOBAL_NEEDS_TARGET);
17234
17381
  return done(2);
17235
17382
  }
17236
17383
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
@@ -17243,9 +17390,7 @@ async function runUpdate(ref, opts = {}) {
17243
17390
  const hits = await locate(slug, opts, fetchImpl, apiUrl);
17244
17391
  const memberHit = hits.find((h) => h.packSlug !== void 0 && h.packSlug !== slug);
17245
17392
  if (memberHit?.packSlug !== void 0) {
17246
- push(
17247
- `error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`
17248
- );
17393
+ push(`error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`);
17249
17394
  return done(2);
17250
17395
  }
17251
17396
  if (hits.length === 0) {
@@ -17280,19 +17425,14 @@ async function runUpdate(ref, opts = {}) {
17280
17425
  if (preflight.blocked) {
17281
17426
  return done(1);
17282
17427
  }
17283
- if (preflight.riskLevel !== "low" && !opts.yes) {
17284
- if (!opts.confirmImpl) {
17285
- push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
17286
- return done(2);
17287
- }
17288
- const confirmed = await opts.confirmImpl(
17289
- `Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
17290
- );
17291
- if (!confirmed) {
17292
- push("", "Update aborted; the installed version was left in place.");
17293
- return done(2);
17294
- }
17295
- }
17428
+ const confirmed = await confirmRisk(
17429
+ preflight,
17430
+ opts,
17431
+ `Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
17432
+ "Update aborted; the installed version was left in place.",
17433
+ push
17434
+ );
17435
+ if (!confirmed) return done(2);
17296
17436
  const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
17297
17437
  if (!download.ok) {
17298
17438
  push("", `error: ${download.message}`);
@@ -17304,11 +17444,7 @@ async function runUpdate(ref, opts = {}) {
17304
17444
  push("", "error: could not replace the install; the installed version was left in place");
17305
17445
  return done(2);
17306
17446
  }
17307
- recordReceipt(installed.area.dir, slug, {
17308
- version: preflight.version,
17309
- sourceHash: preflight.sourceHash,
17310
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
17311
- });
17447
+ recordReceipt(installed.area.dir, slug, receiptFor(preflight));
17312
17448
  push(
17313
17449
  "",
17314
17450
  `Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${installed.dir}`,
@@ -17326,9 +17462,7 @@ async function changesVsInstalled(fetchImpl, apiUrl, slug, installed, target, st
17326
17462
  publicPreflightSchema
17327
17463
  );
17328
17464
  if (!before.ok) {
17329
- return [
17330
- `Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`
17331
- ];
17465
+ return [`Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`];
17332
17466
  }
17333
17467
  const { added, removed } = permissionChanges(before.data.permissions, target.permissions);
17334
17468
  if (added.length === 0 && removed.length === 0) {
@@ -17354,23 +17488,19 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
17354
17488
  if (preflight.blocked) {
17355
17489
  return done(1);
17356
17490
  }
17357
- if (preflight.riskLevel !== "low" && !opts.yes) {
17358
- if (!opts.confirmImpl) {
17359
- push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
17360
- return done(2);
17361
- }
17362
- const confirmed = await opts.confirmImpl(
17363
- `Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
17364
- );
17365
- if (!confirmed) {
17366
- push("", "Update aborted; the installed version was left in place.");
17367
- return done(2);
17368
- }
17369
- }
17491
+ const confirmed = await confirmRisk(
17492
+ preflight,
17493
+ opts,
17494
+ `Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
17495
+ "Update aborted; the installed version was left in place.",
17496
+ push
17497
+ );
17498
+ if (!confirmed) return done(2);
17370
17499
  const areaDir = installed.area.dir;
17371
- const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.tool);
17500
+ const tool = opts.target ?? installed.area.tools[0];
17501
+ const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.layout);
17372
17502
  if (installs.length === 0) {
17373
- push("", `error: none of the new version's skills support ${installed.area.tool}`);
17503
+ push("", `error: none of the new version's skills support ${tool}`);
17374
17504
  return done(2);
17375
17505
  }
17376
17506
  const receipts = readReceipts(areaDir);
@@ -17378,9 +17508,7 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
17378
17508
  const newNames = new Set(installs.map((m) => m.name));
17379
17509
  const toRemove = currentMembers.filter((name) => !newNames.has(name));
17380
17510
  const additions = installs.filter((m) => !currentMembers.includes(m.name));
17381
- const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(
17382
- (dest) => existsSync4(dest) && (!statSync5(dest).isDirectory() || readdirSync4(dest).length > 0)
17383
- );
17511
+ const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(isOccupied);
17384
17512
  if (conflicts.length > 0) {
17385
17513
  push(
17386
17514
  "",
@@ -17394,31 +17522,26 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
17394
17522
  push("", `error: ${download.message}`);
17395
17523
  return done(2);
17396
17524
  }
17397
- const plans = installs.map((m) => ({ ...m, files: memberFiles(download.files, m.sourceDir) }));
17525
+ const plans = planMembers(installs, download.files);
17398
17526
  const missing = plans.find((p) => p.files.length === 0);
17399
17527
  if (missing) {
17400
17528
  push("", `error: the snapshot has no files for "${missing.name}"; nothing was changed`);
17401
17529
  return done(2);
17402
17530
  }
17403
17531
  for (const name of skipped) {
17404
- push("", `note: ${name} does not support ${installed.area.tool}; skipped`);
17532
+ push("", `note: ${name} does not support ${tool}; skipped`);
17405
17533
  }
17406
17534
  const done1 = [];
17407
17535
  try {
17408
17536
  for (const plan of plans) {
17409
17537
  const dest = join8(areaDir, plan.name);
17410
- if (existsSync4(dest)) {
17538
+ if (existsSync5(dest)) {
17411
17539
  swapTree(plan.files, dest);
17412
17540
  } else {
17413
17541
  writeTree(plan.files, dest);
17414
17542
  }
17415
17543
  done1.push(plan.name);
17416
- recordReceipt(areaDir, plan.name, {
17417
- version: preflight.version,
17418
- sourceHash: preflight.sourceHash,
17419
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
17420
- pack: { slug, version: preflight.version }
17421
- });
17544
+ recordReceipt(areaDir, plan.name, receiptFor(preflight, slug));
17422
17545
  }
17423
17546
  } catch {
17424
17547
  push(
@@ -17487,11 +17610,11 @@ var USAGE = [
17487
17610
  "skillpass - validate AI agent skills locally and inspect hosted passports",
17488
17611
  "",
17489
17612
  "Usage:",
17490
- " skillpass search [query] [--target <tool>] [--category <slug>] [--packs] [--json]",
17613
+ " skillpass search [query] [--ai] [--target <tool>] [--category <slug>] [--packs] [--json]",
17491
17614
  " find skills in the directory",
17492
17615
  " skillpass scan <path> [--json] run the validator on a local skill package",
17493
17616
  " skillpass report <slug>[@version] [--json] fetch the hosted passport pre-flight",
17494
- " skillpass add <slug>[@version] [--target <tool> [--global] | --dir <path>] [--yes]",
17617
+ " skillpass add <slug>[@version] [--target <tool>... [--global] | --dir <path>] [--yes]",
17495
17618
  " install a skill through the pre-flight gate",
17496
17619
  " skillpass remove <slug> [--target <tool> [--global] | --dir <path>]",
17497
17620
  " remove an installed skill",
@@ -17502,11 +17625,15 @@ var USAGE = [
17502
17625
  "",
17503
17626
  "Flags:",
17504
17627
  " --json print machine-readable JSON instead of the readable report",
17505
- " --target install into a tool's skills folder: claude-code (.claude/skills),",
17506
- " codex (.agents/skills)",
17507
- " --global with --target claude-code, install to ~/.claude/skills instead",
17628
+ " --target a tool's skills folder: claude-code, cline, or agents (the shared",
17629
+ " .agents/skills read by codex, cursor, windsurf, github-copilot,",
17630
+ " gemini-cli, and opencode; those names work too). add takes it more",
17631
+ " than once to install into several tools",
17632
+ " --global with --target, use the tool's user-level folder (~/.claude/skills,",
17633
+ " ~/.agents/skills, ~/.cline/skills)",
17508
17634
  " --dir install target directory (default ./<slug>)",
17509
17635
  " --yes skip the confirmation prompt for medium+ risk skills",
17636
+ " --ai search by meaning: describe what you need (needs a query)",
17510
17637
  " --category search filter: a directory category slug",
17511
17638
  " --packs search filter: multi-skill packs only",
17512
17639
  " --version print the CLI version",
@@ -17524,7 +17651,7 @@ var COMMAND_FLAGS = {
17524
17651
  list: [],
17525
17652
  outdated: [],
17526
17653
  update: ["--yes", "--target", "--global"],
17527
- search: ["--target", "--category", "--packs", "--json"]
17654
+ search: ["--ai", "--target", "--category", "--packs", "--json"]
17528
17655
  };
17529
17656
  function parseCliArgs(argv) {
17530
17657
  const parsed = {
@@ -17535,6 +17662,7 @@ function parseCliArgs(argv) {
17535
17662
  yes: false,
17536
17663
  global: false,
17537
17664
  packs: false,
17665
+ ai: false,
17538
17666
  seen: []
17539
17667
  };
17540
17668
  for (let i2 = 0; i2 < argv.length; i2++) {
@@ -17551,6 +17679,9 @@ function parseCliArgs(argv) {
17551
17679
  } else if (arg === "--packs") {
17552
17680
  parsed.packs = true;
17553
17681
  parsed.seen.push(arg);
17682
+ } else if (arg === "--ai") {
17683
+ parsed.ai = true;
17684
+ parsed.seen.push(arg);
17554
17685
  } else if (arg === "--dir" || arg === "--target" || arg === "--category") {
17555
17686
  parsed.seen.push(arg);
17556
17687
  const value = argv[i2 + 1];
@@ -17561,7 +17692,7 @@ function parseCliArgs(argv) {
17561
17692
  if (arg === "--dir") {
17562
17693
  parsed.dir = value;
17563
17694
  } else if (arg === "--target") {
17564
- parsed.target = value;
17695
+ parsed.targets = [.../* @__PURE__ */ new Set([...parsed.targets ?? [], value])];
17565
17696
  } else {
17566
17697
  parsed.category = value;
17567
17698
  }
@@ -17611,12 +17742,17 @@ async function run(argv) {
17611
17742
  if (disallowed) {
17612
17743
  return { lines: [`error: ${args.command} does not take ${disallowed}`, "", USAGE], exitCode: 2 };
17613
17744
  }
17745
+ const target = args.targets?.[0];
17746
+ if (args.command !== "add" && (args.targets?.length ?? 0) > 1) {
17747
+ return { lines: [`error: ${args.command} takes one --target`, "", USAGE], exitCode: 2 };
17748
+ }
17614
17749
  if (args.command === "search") {
17615
17750
  return runSearch({
17616
17751
  query: args.positional.join(" "),
17617
- target: args.target,
17752
+ target,
17618
17753
  category: args.category,
17619
17754
  packs: args.packs,
17755
+ ai: args.ai,
17620
17756
  json: args.json,
17621
17757
  width: process.stdout.isTTY ? process.stdout.columns : void 0,
17622
17758
  style: styler(Boolean(process.stdout.isTTY))
@@ -17634,7 +17770,7 @@ async function run(argv) {
17634
17770
  if (!slug) {
17635
17771
  return { lines: ["error: remove needs a skill slug", "", USAGE], exitCode: 2 };
17636
17772
  }
17637
- return runRemove(slug, { target: args.target, dir: args.dir, global: args.global });
17773
+ return runRemove(slug, { target, dir: args.dir, global: args.global });
17638
17774
  }
17639
17775
  if (args.command === "list") {
17640
17776
  if (args.positional.length > 0) {
@@ -17650,7 +17786,7 @@ async function run(argv) {
17650
17786
  const tty = Boolean(process.stdin.isTTY);
17651
17787
  return runUpdate(ref, {
17652
17788
  yes: args.yes,
17653
- target: args.target,
17789
+ target,
17654
17790
  global: args.global,
17655
17791
  style: styler(Boolean(process.stdout.isTTY)),
17656
17792
  confirmImpl: tty ? confirmViaTty : void 0,
@@ -17679,7 +17815,7 @@ async function run(argv) {
17679
17815
  return runAdd(ref, {
17680
17816
  yes: args.yes,
17681
17817
  dir: args.dir,
17682
- target: args.target,
17818
+ target: args.targets,
17683
17819
  global: args.global,
17684
17820
  style: styler(Boolean(process.stdout.isTTY)),
17685
17821
  confirmImpl: tty ? confirmViaTty : void 0,
@@ -17689,10 +17825,17 @@ async function run(argv) {
17689
17825
  }
17690
17826
  return { lines: [`error: unknown command "${args.command}"`, "", USAGE], exitCode: 2 };
17691
17827
  }
17692
- async function main(argv) {
17693
- const result = await run(argv);
17828
+ async function main(argv, runImpl = run) {
17829
+ let result;
17830
+ try {
17831
+ result = await runImpl(argv);
17832
+ } catch (err2) {
17833
+ console.error(`error: ${err2 instanceof Error ? err2.message : String(err2)}`);
17834
+ process.exitCode = 2;
17835
+ return;
17836
+ }
17694
17837
  if (!result.streamed) {
17695
- console.log(result.lines.join("\n"));
17838
+ (result.exitCode === 2 ? console.error : console.log)(result.lines.join("\n"));
17696
17839
  }
17697
17840
  process.exitCode = result.exitCode;
17698
17841
  }