@hasna/skills 0.10.73 → 0.10.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mcp.js CHANGED
@@ -23,7 +23,7 @@ var package_default;
23
23
  var init_package = __esm(() => {
24
24
  package_default = {
25
25
  name: "@hasna/skills",
26
- version: "0.10.73",
26
+ version: "0.10.75",
27
27
  description: "Skills library for AI coding agents",
28
28
  type: "module",
29
29
  bin: {
@@ -22021,8 +22021,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
22021
22021
  displayName: manifest.displayName ?? displayName(manifest.name),
22022
22022
  category: manifest.category ?? "Development Tools",
22023
22023
  tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
22024
- inputs: [],
22025
- commands: []
22024
+ inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
22025
+ commands: Array.isArray(manifest.commands) ? manifest.commands : []
22026
22026
  };
22027
22027
  if (!existsSync6(join9(skillPath, "SKILL.md"))) {
22028
22028
  writeFileSync2(join9(skillPath, "SKILL.md"), renderSkillMd(next));
@@ -36452,6 +36452,118 @@ var init_skillinfo = __esm(() => {
36452
36452
  GENERIC_ENV_PATTERN = /\b((?:OPENAI|ANTHROPIC|GEMINI|XAI|ELEVENLABS|DEEPGRAM|REPLICATE|FAL|STABILITY|EXA|FIRECRAWL|TWILIO|SENDGRID|RESEND|SLACK|DISCORD|NOTION|LINEAR|GITHUB|AWS|GOOGLE|CLOUDFLARE|VERCEL|SUPABASE|STRIPE)_[A-Z_]+)\b/g;
36453
36453
  });
36454
36454
 
36455
+ // src/lib/search.ts
36456
+ function editDistance(a, b) {
36457
+ if (a === b)
36458
+ return 0;
36459
+ if (a.length === 0)
36460
+ return b.length;
36461
+ if (b.length === 0)
36462
+ return a.length;
36463
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
36464
+ const curr = new Array(b.length + 1);
36465
+ for (let i = 1;i <= a.length; i++) {
36466
+ curr[0] = i;
36467
+ for (let j = 1;j <= b.length; j++) {
36468
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
36469
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
36470
+ }
36471
+ prev.splice(0, prev.length, ...curr);
36472
+ }
36473
+ return prev[b.length];
36474
+ }
36475
+ function fuzzyMatchScore(word, target) {
36476
+ if (target.includes(word))
36477
+ return 1;
36478
+ const tokens = target.split(/[\s\-_]+/).filter(Boolean);
36479
+ for (const token of tokens) {
36480
+ if (token.startsWith(word))
36481
+ return 0.6;
36482
+ }
36483
+ if (word.length >= 3) {
36484
+ const maxDist = word.length <= 3 ? 1 : 2;
36485
+ for (const token of tokens) {
36486
+ if (Math.abs(token.length - word.length) <= maxDist) {
36487
+ const dist = editDistance(word, token);
36488
+ if (dist <= maxDist)
36489
+ return 0.4;
36490
+ }
36491
+ }
36492
+ }
36493
+ return 0;
36494
+ }
36495
+ function searchSkills(query, registry2 = loadRegistry()) {
36496
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
36497
+ if (words.length === 0)
36498
+ return [];
36499
+ const scored = [];
36500
+ for (const skill of registry2) {
36501
+ const nameLower = skill.name.toLowerCase();
36502
+ const displayNameLower = skill.displayName.toLowerCase();
36503
+ const descriptionLower = skill.description.toLowerCase();
36504
+ const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
36505
+ const tagsCombined = tagsLower.join(" ");
36506
+ let score = 0;
36507
+ let allWordsMatch = true;
36508
+ for (const word of words) {
36509
+ let wordScore = 0;
36510
+ const nameMatch = fuzzyMatchScore(word, nameLower);
36511
+ if (nameMatch > 0)
36512
+ wordScore += 10 * nameMatch;
36513
+ const displayMatch = fuzzyMatchScore(word, displayNameLower);
36514
+ if (displayMatch > 0)
36515
+ wordScore += 7 * displayMatch;
36516
+ const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
36517
+ if (tagMatch > 0)
36518
+ wordScore += 5 * tagMatch;
36519
+ const descMatch = fuzzyMatchScore(word, descriptionLower);
36520
+ if (descMatch > 0)
36521
+ wordScore += 2 * descMatch;
36522
+ if (wordScore === 0) {
36523
+ allWordsMatch = false;
36524
+ break;
36525
+ }
36526
+ score += wordScore;
36527
+ }
36528
+ if (allWordsMatch && score > 0) {
36529
+ scored.push({ skill, score });
36530
+ }
36531
+ }
36532
+ scored.sort((a, b) => b.score - a.score);
36533
+ return scored.map((s) => s.skill);
36534
+ }
36535
+ function findCloseSkillNames(query, names, maxResults = 3) {
36536
+ if (maxResults <= 0)
36537
+ return [];
36538
+ const q = query.trim().toLowerCase();
36539
+ if (q.length === 0)
36540
+ return [];
36541
+ const tokens = q.split("-").filter(Boolean);
36542
+ const ranked = [];
36543
+ if (q.length >= 3) {
36544
+ for (const name of names) {
36545
+ const candidate = name.toLowerCase();
36546
+ const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
36547
+ if (rank !== undefined)
36548
+ ranked.push([name, rank]);
36549
+ }
36550
+ }
36551
+ ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
36552
+ const found = ranked.slice(0, maxResults).map(([name]) => name);
36553
+ if (found.length === maxResults)
36554
+ return found;
36555
+ const taken = new Set(found);
36556
+ const threshold = Math.max(3, Math.floor(q.length / 2));
36557
+ const byDistance = names.filter((name) => !taken.has(name)).map((name) => ({ name, distance: editDistance(q, name.toLowerCase()) })).filter((entry) => entry.distance <= threshold).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name));
36558
+ return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
36559
+ }
36560
+ function findSimilarSkills(query, maxResults = 3, registry2 = loadRegistry()) {
36561
+ return findCloseSkillNames(query, registry2.map((skill) => skill.name), maxResults);
36562
+ }
36563
+ var init_search = __esm(() => {
36564
+ init_registry();
36565
+ });
36566
+
36455
36567
  // src/lib/selected-document.ts
36456
36568
  function readSelectedDocument(entries, file2) {
36457
36569
  const entry = file2 !== undefined ? entries.find((candidate) => candidate.path === file2) : DOCUMENT_PATHS.map((path) => entries.find((candidate) => candidate.path === path && candidate.bytes.length > 0)).find(Boolean);
@@ -36532,11 +36644,30 @@ function exactProfileSelection(spec, profile) {
36532
36644
  const at = spec.lastIndexOf("@");
36533
36645
  const slug = at > 0 ? spec.slice(0, at) : spec;
36534
36646
  const version2 = at > 0 ? spec.slice(at + 1) : undefined;
36535
- const selection = profile.selections.find((entry) => (entry.slug === slug || entry.aliases?.includes(slug)) && (version2 === undefined || version2 === entry.version));
36536
- if (!selection || version2 === "")
36537
- throw new SkillSelectionError("SKILL_NOT_SELECTED", "The requested exact skill version is not selected by this profile or project lock.");
36647
+ const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
36648
+ if (version2 === "")
36649
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
36650
+ if (named.length === 0)
36651
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
36652
+ const selection = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
36653
+ if (!selection)
36654
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
36538
36655
  return selection;
36539
36656
  }
36657
+ function emptyVersionRefusal(spec) {
36658
+ return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
36659
+ }
36660
+ function wrongVersionRefusal(slug, version2, named) {
36661
+ const versions2 = [...new Set(named.map((entry) => entry.version))].sort();
36662
+ const canonical9 = named[0].slug;
36663
+ return `Skill '${slug}' is selected at version '${versions2.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions2[0]}'.`;
36664
+ }
36665
+ function unknownNameRefusal(slug, profile) {
36666
+ const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
36667
+ const close = findCloseSkillNames(slug, names);
36668
+ const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
36669
+ return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
36670
+ }
36540
36671
  async function loadSelectedSkill(spec, profileId, options = {}) {
36541
36672
  const context = await resolveSelectionContext(profileId, options);
36542
36673
  const selection = exactProfileSelection(spec, context.receipt.profile);
@@ -36581,9 +36712,11 @@ async function selectedSkillRequirements(spec, profileId, options = {}) {
36581
36712
  }
36582
36713
  return { ...getSkillRequirementsFromContent(selection.slug, texts, dependencies), selection };
36583
36714
  }
36715
+ var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
36584
36716
  var init_selection_resolver = __esm(() => {
36585
36717
  init_profile_client();
36586
36718
  init_skillinfo();
36719
+ init_search();
36587
36720
  init_selected_document();
36588
36721
  init_selection_cache();
36589
36722
  });
@@ -37726,6 +37859,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
37726
37859
  throw new Error("Retired native materialization changed during discovery verification");
37727
37860
  }
37728
37861
  }
37862
+ function claudeControlSource(source) {
37863
+ return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename12(source.path)) && !source.path.includes(`${sep11}.claude${sep11}plugins${sep11}cache${sep11}`);
37864
+ }
37865
+ function retainedAbsentClaudeRoot(absentRoots, source) {
37866
+ return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
37867
+ }
37729
37868
  function codexInstallationRoots(binding) {
37730
37869
  const proof = binding.codexInstallationInputs;
37731
37870
  if (proof === undefined)
@@ -37921,7 +38060,10 @@ function resolveAgentDiscovery(options) {
37921
38060
  if (!review.sources.some((source) => source.path === canonical9(configPath)))
37922
38061
  throw new Error("Discovery review must include the agent configuration source");
37923
38062
  const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
38063
+ const absentRoots = absentRetiredClaudeRoots(supplied);
37924
38064
  const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
38065
+ if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
38066
+ return source;
37925
38067
  if (agent === "claude" && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.reviewArtifact === undefined && [undefined, "bytes", "path-bytes"].includes(source.hashMode) && isClaudePluginCacheOrphanMarker(source.path)) {
37926
38068
  return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
37927
38069
  }
@@ -37954,6 +38096,11 @@ function resolveAgentDiscovery(options) {
37954
38096
  });
37955
38097
  if (agent === "claude")
37956
38098
  assertReviewedClaudeCacheClosures(home, config2, review, canonical9);
38099
+ for (const root of absentRoots) {
38100
+ safe6(root);
38101
+ if (lstatSync23(root, { throwIfNoEntry: false }))
38102
+ throw new Error("Retired native materialization changed during discovery review");
38103
+ }
37957
38104
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
37958
38105
  const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual5(candidate, source)) === index);
37959
38106
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
@@ -38601,95 +38748,6 @@ var init_registry_types = __esm(() => {
38601
38748
  ];
38602
38749
  });
38603
38750
 
38604
- // src/lib/search.ts
38605
- function editDistance(a, b) {
38606
- if (a === b)
38607
- return 0;
38608
- if (a.length === 0)
38609
- return b.length;
38610
- if (b.length === 0)
38611
- return a.length;
38612
- const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
38613
- const curr = new Array(b.length + 1);
38614
- for (let i = 1;i <= a.length; i++) {
38615
- curr[0] = i;
38616
- for (let j = 1;j <= b.length; j++) {
38617
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
38618
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
38619
- }
38620
- prev.splice(0, prev.length, ...curr);
38621
- }
38622
- return prev[b.length];
38623
- }
38624
- function fuzzyMatchScore(word, target) {
38625
- if (target.includes(word))
38626
- return 1;
38627
- const tokens = target.split(/[\s\-_]+/).filter(Boolean);
38628
- for (const token of tokens) {
38629
- if (token.startsWith(word))
38630
- return 0.6;
38631
- }
38632
- if (word.length >= 3) {
38633
- const maxDist = word.length <= 3 ? 1 : 2;
38634
- for (const token of tokens) {
38635
- if (Math.abs(token.length - word.length) <= maxDist) {
38636
- const dist = editDistance(word, token);
38637
- if (dist <= maxDist)
38638
- return 0.4;
38639
- }
38640
- }
38641
- }
38642
- return 0;
38643
- }
38644
- function searchSkills(query, registry2 = loadRegistry()) {
38645
- const words = query.toLowerCase().split(/\s+/).filter(Boolean);
38646
- if (words.length === 0)
38647
- return [];
38648
- const scored = [];
38649
- for (const skill of registry2) {
38650
- const nameLower = skill.name.toLowerCase();
38651
- const displayNameLower = skill.displayName.toLowerCase();
38652
- const descriptionLower = skill.description.toLowerCase();
38653
- const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
38654
- const tagsCombined = tagsLower.join(" ");
38655
- let score = 0;
38656
- let allWordsMatch = true;
38657
- for (const word of words) {
38658
- let wordScore = 0;
38659
- const nameMatch = fuzzyMatchScore(word, nameLower);
38660
- if (nameMatch > 0)
38661
- wordScore += 10 * nameMatch;
38662
- const displayMatch = fuzzyMatchScore(word, displayNameLower);
38663
- if (displayMatch > 0)
38664
- wordScore += 7 * displayMatch;
38665
- const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
38666
- if (tagMatch > 0)
38667
- wordScore += 5 * tagMatch;
38668
- const descMatch = fuzzyMatchScore(word, descriptionLower);
38669
- if (descMatch > 0)
38670
- wordScore += 2 * descMatch;
38671
- if (wordScore === 0) {
38672
- allWordsMatch = false;
38673
- break;
38674
- }
38675
- score += wordScore;
38676
- }
38677
- if (allWordsMatch && score > 0) {
38678
- scored.push({ skill, score });
38679
- }
38680
- }
38681
- scored.sort((a, b) => b.score - a.score);
38682
- return scored.map((s) => s.skill);
38683
- }
38684
- function findSimilarSkills(query, maxResults = 3, registry2 = loadRegistry()) {
38685
- const q = query.toLowerCase();
38686
- const scored = registry2.map((s) => ({ name: s.name, dist: editDistance(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
38687
- return scored.slice(0, maxResults).map((s) => s.name);
38688
- }
38689
- var init_search = __esm(() => {
38690
- init_registry();
38691
- });
38692
-
38693
38751
  // src/lib/registry.ts
38694
38752
  import { existsSync as existsSync13, readFileSync as readFileSync18, readdirSync as readdirSync10 } from "fs";
38695
38753
  import { join as join33 } from "path";
@@ -46399,7 +46457,11 @@ var exports_selected_run = {};
46399
46457
  __export(exports_selected_run, {
46400
46458
  resolveSelectedRun: () => resolveSelectedRun,
46401
46459
  prepareSelectedSecretBindings: () => prepareSelectedSecretBindings,
46402
- executeSelectedLocal: () => executeSelectedLocal
46460
+ instructionRunRefusal: () => instructionRunRefusal,
46461
+ instructionEntrypointMismatch: () => instructionEntrypointMismatch,
46462
+ instructionCommandEntry: () => instructionCommandEntry,
46463
+ executeSelectedLocal: () => executeSelectedLocal,
46464
+ declaredOperation: () => declaredOperation
46403
46465
  });
46404
46466
  import { createHash as createHash28 } from "crypto";
46405
46467
  import { spawn as spawn2 } from "child_process";
@@ -46412,12 +46474,51 @@ async function resolveSelectedRun(spec, profileId, options = {}) {
46412
46474
  const entries = await readSelectedEntries(selection2, context2, options);
46413
46475
  return { selection: selection2, profileId: context2.receipt.profile.profileId, entries, ...describeEntries(entries), cacheDir: selectionCacheRoot(options) };
46414
46476
  }
46477
+ function declaredOperation(manifest) {
46478
+ const entry = instructionCommandEntry(manifest);
46479
+ if (!entry)
46480
+ return null;
46481
+ const runtime = manifest.runtime;
46482
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
46483
+ return null;
46484
+ if (runtime.runtime !== "bun" && runtime.runtime !== "node" && runtime.runtime !== "python3")
46485
+ return null;
46486
+ if (instructionEntrypointMismatch(manifest))
46487
+ return null;
46488
+ return { entry };
46489
+ }
46490
+ function instructionCommandEntry(manifest) {
46491
+ const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
46492
+ const command = commands[0];
46493
+ if (!command || typeof command !== "object" || typeof command.entry !== "string" || command.entry.length === 0)
46494
+ return null;
46495
+ return command.entry;
46496
+ }
46497
+ function instructionEntrypointMismatch(manifest) {
46498
+ const entry = instructionCommandEntry(manifest);
46499
+ const declared = manifest.runtime;
46500
+ if (!entry || !declared || typeof declared !== "object" || Array.isArray(declared))
46501
+ return null;
46502
+ const runtimeEntrypoint = declared.entrypoint;
46503
+ return typeof runtimeEntrypoint === "string" && runtimeEntrypoint !== entry ? runtimeEntrypoint : null;
46504
+ }
46505
+ function instructionRunRefusal(manifest, isBundledFile) {
46506
+ const declaredEntry = instructionCommandEntry(manifest);
46507
+ if (declaredEntry && !isBundledFile(declaredEntry))
46508
+ return { code: "LOCAL_ENTRYPOINT_MISSING", message: `This selected instruction skill declares its operation entry as ${declaredEntry}, which is not a regular file in the selected bundle; a different runtime.entrypoint never substitutes for it.` };
46509
+ const mismatch = instructionEntrypointMismatch(manifest);
46510
+ if (mismatch)
46511
+ return { code: "LOCAL_ENTRYPOINT_MISMATCH", message: `This selected instruction skill declares its operation entry as ${declaredEntry}, but its runtime.entrypoint is ${mismatch}; the two must agree.` };
46512
+ if (!declaredOperation(manifest))
46513
+ return { code: "INSTRUCTION_SKILL", message: "This selected skill contains instructions. Use skills load instead of skills run." };
46514
+ return null;
46515
+ }
46415
46516
  async function prepareSelectedSecretBindings(selected, cwd = process.cwd()) {
46416
46517
  const entries = await readCachedSelection(selected.selection, { cacheDir: selected.cacheDir });
46417
46518
  if (!entries)
46418
46519
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected execution bundle is not cached.");
46419
46520
  const { kind, manifest } = describeEntries(entries);
46420
- if (kind !== "executable")
46521
+ if (kind !== "executable" && !declaredOperation(manifest))
46421
46522
  throw new SkillSelectionError("INSTRUCTION_SKILL", "Instruction skills do not accept execution secret bindings.");
46422
46523
  const names = declaredSecretNames(manifest.runtime?.env);
46423
46524
  if (!names.length)
@@ -46430,8 +46531,12 @@ async function executeSelectedLocal(selected, options = {}) {
46430
46531
  if (!entries)
46431
46532
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected local execution bundle is not cached.");
46432
46533
  const { kind, manifest, packageJson } = describeEntries(entries);
46433
- if (kind === "instruction")
46434
- throw new SkillSelectionError("INSTRUCTION_SKILL", "This selected skill contains instructions. Use skills load instead of skills run.");
46534
+ const operation = declaredOperation(manifest);
46535
+ if (kind === "instruction") {
46536
+ const refusal3 = instructionRunRefusal(manifest, (path) => entries.some((entry) => entry.path === path));
46537
+ if (refusal3)
46538
+ throw new SkillSelectionError(refusal3.code, refusal3.message);
46539
+ }
46435
46540
  const runtime = manifest.runtime ?? {};
46436
46541
  if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
46437
46542
  throw new SkillSelectionError("INVALID_SKILL_MANIFEST", "The selected runtime declaration is invalid.");
@@ -46450,9 +46555,9 @@ async function executeSelectedLocal(selected, options = {}) {
46450
46555
  const executable = runtimeName === "bun" ? process.execPath : Bun.which(runtimeName);
46451
46556
  if (!executable)
46452
46557
  throw new SkillSelectionError("LOCAL_RUNTIME_UNAVAILABLE", "The selected runtime is not installed on this station.");
46453
- const declaredEntry = runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
46558
+ const declaredEntry = (kind === "instruction" ? operation?.entry : undefined) ?? runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
46454
46559
  if (typeof declaredEntry !== "string" || !entries.some((entry) => entry.path === declaredEntry))
46455
- throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
46560
+ throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", kind === "instruction" ? "The selected instruction skill must declare the operation entrypoint it ships in its manifest, and that declared entry must be a regular file in the selected bundle; a different runtime.entrypoint never substitutes for it." : "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
46456
46561
  const args = options.args ?? [];
46457
46562
  if (args.length > 128 || args.some((arg) => typeof arg !== "string" || arg.length > 16384 || arg.includes("\x00")))
46458
46563
  throw new SkillSelectionError("INVALID_RUN_INPUT", "Local skill arguments exceed their limits.");
@@ -46527,6 +46632,7 @@ async function executeSelectedLocal(selected, options = {}) {
46527
46632
  selection: selection2,
46528
46633
  target: "local",
46529
46634
  runtime: runtimeName,
46635
+ entrypoint: declaredEntry,
46530
46636
  inputDigest: `sha256:${createHash28("sha256").update(input2).digest("hex")}`,
46531
46637
  runDirectory,
46532
46638
  ...executionGrant ? { executionGrant } : {},
@@ -46973,13 +47079,18 @@ function registerOperationTools(server) {
46973
47079
  return mcpError("CONFLICTING_EXECUTION_MODES", "Use remote for the configured server catalog and quote, or target for selected execution, not both.");
46974
47080
  if (managed && !remote) {
46975
47081
  try {
46976
- const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
47082
+ const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2, instructionRunRefusal: instructionRunRefusal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
46977
47083
  const resolved = await resolveSelectedRun2(name, selectedProfileId(), { projectDir: process.cwd() });
46978
- if (resolved.kind === "instruction")
46979
- return mcpError("INSTRUCTION_SKILL", "This selected skill contains instructions. Load it with skills load instead of running it.");
47084
+ if (resolved.kind === "instruction") {
47085
+ const refusal3 = instructionRunRefusal2(resolved.manifest, (path) => resolved.entries.some((entry) => entry.path === path));
47086
+ if (refusal3)
47087
+ return mcpError(refusal3.code, refusal3.message);
47088
+ }
46980
47089
  if (files?.length)
46981
47090
  return mcpError("SELECTED_INPUT_REQUIRED", "Selected executions require their declared JSON input; use the CLI cloud execution input contract for files.");
46982
47091
  if (target === "cloud") {
47092
+ if (resolved.kind === "instruction")
47093
+ return mcpError("INSTRUCTION_SKILL", "Cloud execution requires an executable skill version; run the declared operation locally on a managed station, or load the instructions with skills load.");
46983
47094
  if (args?.length || quoteReceipt || maxCredits !== undefined || maxCostCents !== undefined)
46984
47095
  return mcpError("SELECTED_INPUT_REQUIRED", "The selected cloud execution contract accepts declared JSON input, not legacy run arguments or quote receipts.");
46985
47096
  const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.73",
28
+ version: "0.10.75",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.73",
24143
+ version: "0.10.75",
24144
24144
  description: "Skills library for AI coding agents",
24145
24145
  type: "module",
24146
24146
  bin: {
package/bin/worker.js CHANGED
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24143
24143
  // package.json
24144
24144
  var package_default = {
24145
24145
  name: "@hasna/skills",
24146
- version: "0.10.73",
24146
+ version: "0.10.75",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
@@ -0,0 +1,18 @@
1
+ import type { Command } from "commander";
2
+ import { type SelectionProvenanceReceipt } from "../../lib/selection-provenance.js";
3
+ export interface ProvenanceCommandOptions {
4
+ selectionProfile?: string;
5
+ station?: string;
6
+ session?: string;
7
+ cached?: boolean;
8
+ json?: boolean;
9
+ }
10
+ /**
11
+ * One read-only receipt for the selection chain: selected, activated, applied,
12
+ * pinned and consumed are named separately, and every consumer carries its
13
+ * status and reason. An exit code here only reports a bad request: a read that
14
+ * could not be answered is recorded as `unverified`, because that is the fact
15
+ * the caller came for.
16
+ */
17
+ export declare function provenanceLines(receipt: SelectionProvenanceReceipt): string[];
18
+ export declare function registerProvenance(parent: Command): void;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Stray positionals on a verb that declares none are a phantom verb, not a
3
+ * no-op. Commander's `allowExcessArguments` defaults to true (12.1.0,
4
+ * `lib/command.js:28`), so `skills categories typo` exited 0 with the ordinary
5
+ * output and `skills list typo` printed the whole catalogue: a caller reading
6
+ * the exit code is told the word was understood (BUG e3997558).
7
+ *
8
+ * The default command refuses by hand so it can name the verbs that exist, and
9
+ * `skills mcp` refuses by hand so it can name its three forms. Those two keep
10
+ * their own, better sentences. Every other command that declares no argument of
11
+ * its own is closed here in one pass, so the next command added gets the
12
+ * refusal without its author having to remember the hole exists.
13
+ */
14
+ import type { Command } from "commander";
15
+ /**
16
+ * Opt a command out of the pass because it owns a more precise refusal than
17
+ * commander's. Chainable, and it keeps `allowExcessArguments(true)` so the
18
+ * command still receives the stray word it wants to report.
19
+ */
20
+ export declare function permitStrayArguments(command: Command): Command;
21
+ /**
22
+ * Refuse positionals on every command that declares no argument of its own.
23
+ *
24
+ * A command that declares arguments keeps commander's own arity check, and a
25
+ * command with subcommands never reaches the excess check for a word that names
26
+ * a child (commander dispatches the subcommand first). Commands without an
27
+ * action handler reach it only with zero operands, so the flag is inert there.
28
+ */
29
+ export declare function rejectStrayArguments(command: Command): void;