@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/README.md +77 -0
- package/bin/index.js +684 -142
- package/bin/mcp.js +215 -104
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/provenance.d.ts +18 -0
- package/dist/cli/stray-arguments.d.ts +29 -0
- package/dist/index.js +206 -111
- package/dist/lib/portable-skills-files.d.ts +10 -0
- package/dist/lib/search.d.ts +18 -0
- package/dist/lib/selected-run.d.ts +45 -0
- package/dist/lib/selection-provenance.d.ts +108 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +172 -33
- package/docs/plugin-admission.md +13 -9
- package/docs/skill-standard.md +18 -4
- package/package.json +1 -1
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.
|
|
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
|
|
36536
|
-
if (
|
|
36537
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
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
|
-
|
|
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
|
-
|
|
46434
|
-
|
|
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
|
-
|
|
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
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.
|
|
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.
|
|
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;
|