@hasna/skills 0.10.74 → 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/index.js +169 -98
- package/bin/mcp.js +137 -93
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/stray-arguments.d.ts +29 -0
- package/dist/index.js +133 -90
- package/dist/lib/search.d.ts +18 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +50 -7
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36888,7 +36888,7 @@ var package_default;
|
|
|
36888
36888
|
var init_package = __esm(() => {
|
|
36889
36889
|
package_default = {
|
|
36890
36890
|
name: "@hasna/skills",
|
|
36891
|
-
version: "0.10.
|
|
36891
|
+
version: "0.10.75",
|
|
36892
36892
|
description: "Skills library for AI coding agents",
|
|
36893
36893
|
type: "module",
|
|
36894
36894
|
bin: {
|
|
@@ -60988,6 +60988,118 @@ var init_skillinfo = __esm(() => {
|
|
|
60988
60988
|
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;
|
|
60989
60989
|
});
|
|
60990
60990
|
|
|
60991
|
+
// src/lib/search.ts
|
|
60992
|
+
function editDistance(a, b) {
|
|
60993
|
+
if (a === b)
|
|
60994
|
+
return 0;
|
|
60995
|
+
if (a.length === 0)
|
|
60996
|
+
return b.length;
|
|
60997
|
+
if (b.length === 0)
|
|
60998
|
+
return a.length;
|
|
60999
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
61000
|
+
const curr = new Array(b.length + 1);
|
|
61001
|
+
for (let i = 1;i <= a.length; i++) {
|
|
61002
|
+
curr[0] = i;
|
|
61003
|
+
for (let j = 1;j <= b.length; j++) {
|
|
61004
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
61005
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
61006
|
+
}
|
|
61007
|
+
prev.splice(0, prev.length, ...curr);
|
|
61008
|
+
}
|
|
61009
|
+
return prev[b.length];
|
|
61010
|
+
}
|
|
61011
|
+
function fuzzyMatchScore(word, target) {
|
|
61012
|
+
if (target.includes(word))
|
|
61013
|
+
return 1;
|
|
61014
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
61015
|
+
for (const token of tokens) {
|
|
61016
|
+
if (token.startsWith(word))
|
|
61017
|
+
return 0.6;
|
|
61018
|
+
}
|
|
61019
|
+
if (word.length >= 3) {
|
|
61020
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
61021
|
+
for (const token of tokens) {
|
|
61022
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
61023
|
+
const dist = editDistance(word, token);
|
|
61024
|
+
if (dist <= maxDist)
|
|
61025
|
+
return 0.4;
|
|
61026
|
+
}
|
|
61027
|
+
}
|
|
61028
|
+
}
|
|
61029
|
+
return 0;
|
|
61030
|
+
}
|
|
61031
|
+
function searchSkills(query, registry = loadRegistry()) {
|
|
61032
|
+
const words2 = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
61033
|
+
if (words2.length === 0)
|
|
61034
|
+
return [];
|
|
61035
|
+
const scored = [];
|
|
61036
|
+
for (const skill of registry) {
|
|
61037
|
+
const nameLower = skill.name.toLowerCase();
|
|
61038
|
+
const displayNameLower = skill.displayName.toLowerCase();
|
|
61039
|
+
const descriptionLower = skill.description.toLowerCase();
|
|
61040
|
+
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
61041
|
+
const tagsCombined = tagsLower.join(" ");
|
|
61042
|
+
let score = 0;
|
|
61043
|
+
let allWordsMatch = true;
|
|
61044
|
+
for (const word of words2) {
|
|
61045
|
+
let wordScore = 0;
|
|
61046
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
61047
|
+
if (nameMatch > 0)
|
|
61048
|
+
wordScore += 10 * nameMatch;
|
|
61049
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
61050
|
+
if (displayMatch > 0)
|
|
61051
|
+
wordScore += 7 * displayMatch;
|
|
61052
|
+
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
61053
|
+
if (tagMatch > 0)
|
|
61054
|
+
wordScore += 5 * tagMatch;
|
|
61055
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
61056
|
+
if (descMatch > 0)
|
|
61057
|
+
wordScore += 2 * descMatch;
|
|
61058
|
+
if (wordScore === 0) {
|
|
61059
|
+
allWordsMatch = false;
|
|
61060
|
+
break;
|
|
61061
|
+
}
|
|
61062
|
+
score += wordScore;
|
|
61063
|
+
}
|
|
61064
|
+
if (allWordsMatch && score > 0) {
|
|
61065
|
+
scored.push({ skill, score });
|
|
61066
|
+
}
|
|
61067
|
+
}
|
|
61068
|
+
scored.sort((a, b) => b.score - a.score);
|
|
61069
|
+
return scored.map((s) => s.skill);
|
|
61070
|
+
}
|
|
61071
|
+
function findCloseSkillNames(query, names, maxResults = 3) {
|
|
61072
|
+
if (maxResults <= 0)
|
|
61073
|
+
return [];
|
|
61074
|
+
const q = query.trim().toLowerCase();
|
|
61075
|
+
if (q.length === 0)
|
|
61076
|
+
return [];
|
|
61077
|
+
const tokens = q.split("-").filter(Boolean);
|
|
61078
|
+
const ranked = [];
|
|
61079
|
+
if (q.length >= 3) {
|
|
61080
|
+
for (const name of names) {
|
|
61081
|
+
const candidate = name.toLowerCase();
|
|
61082
|
+
const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
|
|
61083
|
+
if (rank !== undefined)
|
|
61084
|
+
ranked.push([name, rank]);
|
|
61085
|
+
}
|
|
61086
|
+
}
|
|
61087
|
+
ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
|
|
61088
|
+
const found = ranked.slice(0, maxResults).map(([name]) => name);
|
|
61089
|
+
if (found.length === maxResults)
|
|
61090
|
+
return found;
|
|
61091
|
+
const taken = new Set(found);
|
|
61092
|
+
const threshold = Math.max(3, Math.floor(q.length / 2));
|
|
61093
|
+
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));
|
|
61094
|
+
return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
|
|
61095
|
+
}
|
|
61096
|
+
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
61097
|
+
return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
|
|
61098
|
+
}
|
|
61099
|
+
var init_search = __esm(() => {
|
|
61100
|
+
init_registry();
|
|
61101
|
+
});
|
|
61102
|
+
|
|
60991
61103
|
// src/lib/selected-document.ts
|
|
60992
61104
|
function readSelectedDocument(entries, file) {
|
|
60993
61105
|
const entry = file !== undefined ? entries.find((candidate) => candidate.path === file) : DOCUMENT_PATHS.map((path) => entries.find((candidate) => candidate.path === path && candidate.bytes.length > 0)).find(Boolean);
|
|
@@ -61108,11 +61220,30 @@ function exactProfileSelection(spec, profile) {
|
|
|
61108
61220
|
const at2 = spec.lastIndexOf("@");
|
|
61109
61221
|
const slug = at2 > 0 ? spec.slice(0, at2) : spec;
|
|
61110
61222
|
const version = at2 > 0 ? spec.slice(at2 + 1) : undefined;
|
|
61111
|
-
const
|
|
61112
|
-
if (
|
|
61113
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
61223
|
+
const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
|
|
61224
|
+
if (version === "")
|
|
61225
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
|
|
61226
|
+
if (named.length === 0)
|
|
61227
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
|
|
61228
|
+
const selection = version === undefined ? named[0] : named.find((entry) => entry.version === version);
|
|
61229
|
+
if (!selection)
|
|
61230
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version, named));
|
|
61114
61231
|
return selection;
|
|
61115
61232
|
}
|
|
61233
|
+
function emptyVersionRefusal(spec) {
|
|
61234
|
+
return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
|
|
61235
|
+
}
|
|
61236
|
+
function wrongVersionRefusal(slug, version, named) {
|
|
61237
|
+
const versions = [...new Set(named.map((entry) => entry.version))].sort();
|
|
61238
|
+
const canonical9 = named[0].slug;
|
|
61239
|
+
return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
|
|
61240
|
+
}
|
|
61241
|
+
function unknownNameRefusal(slug, profile) {
|
|
61242
|
+
const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
|
|
61243
|
+
const close = findCloseSkillNames(slug, names);
|
|
61244
|
+
const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
61245
|
+
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}`;
|
|
61246
|
+
}
|
|
61116
61247
|
async function loadSelectedSkill(spec, profileId, options = {}) {
|
|
61117
61248
|
const context = await resolveSelectionContext(profileId, options);
|
|
61118
61249
|
const selection = exactProfileSelection(spec, context.receipt.profile);
|
|
@@ -61157,9 +61288,11 @@ async function selectedSkillRequirements(spec, profileId, options = {}) {
|
|
|
61157
61288
|
}
|
|
61158
61289
|
return { ...getSkillRequirementsFromContent(selection.slug, texts, dependencies), selection };
|
|
61159
61290
|
}
|
|
61291
|
+
var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
|
|
61160
61292
|
var init_selection_resolver = __esm(() => {
|
|
61161
61293
|
init_profile_client();
|
|
61162
61294
|
init_skillinfo();
|
|
61295
|
+
init_search();
|
|
61163
61296
|
init_selected_document();
|
|
61164
61297
|
init_selection_cache();
|
|
61165
61298
|
});
|
|
@@ -63981,95 +64114,6 @@ var init_registry_types = __esm(() => {
|
|
|
63981
64114
|
BASIC_SKILL_NAMES = [];
|
|
63982
64115
|
});
|
|
63983
64116
|
|
|
63984
|
-
// src/lib/search.ts
|
|
63985
|
-
function editDistance(a, b) {
|
|
63986
|
-
if (a === b)
|
|
63987
|
-
return 0;
|
|
63988
|
-
if (a.length === 0)
|
|
63989
|
-
return b.length;
|
|
63990
|
-
if (b.length === 0)
|
|
63991
|
-
return a.length;
|
|
63992
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
63993
|
-
const curr = new Array(b.length + 1);
|
|
63994
|
-
for (let i = 1;i <= a.length; i++) {
|
|
63995
|
-
curr[0] = i;
|
|
63996
|
-
for (let j = 1;j <= b.length; j++) {
|
|
63997
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
63998
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
63999
|
-
}
|
|
64000
|
-
prev.splice(0, prev.length, ...curr);
|
|
64001
|
-
}
|
|
64002
|
-
return prev[b.length];
|
|
64003
|
-
}
|
|
64004
|
-
function fuzzyMatchScore(word, target) {
|
|
64005
|
-
if (target.includes(word))
|
|
64006
|
-
return 1;
|
|
64007
|
-
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
64008
|
-
for (const token of tokens) {
|
|
64009
|
-
if (token.startsWith(word))
|
|
64010
|
-
return 0.6;
|
|
64011
|
-
}
|
|
64012
|
-
if (word.length >= 3) {
|
|
64013
|
-
const maxDist = word.length <= 3 ? 1 : 2;
|
|
64014
|
-
for (const token of tokens) {
|
|
64015
|
-
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
64016
|
-
const dist = editDistance(word, token);
|
|
64017
|
-
if (dist <= maxDist)
|
|
64018
|
-
return 0.4;
|
|
64019
|
-
}
|
|
64020
|
-
}
|
|
64021
|
-
}
|
|
64022
|
-
return 0;
|
|
64023
|
-
}
|
|
64024
|
-
function searchSkills(query, registry = loadRegistry()) {
|
|
64025
|
-
const words2 = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
64026
|
-
if (words2.length === 0)
|
|
64027
|
-
return [];
|
|
64028
|
-
const scored = [];
|
|
64029
|
-
for (const skill of registry) {
|
|
64030
|
-
const nameLower = skill.name.toLowerCase();
|
|
64031
|
-
const displayNameLower = skill.displayName.toLowerCase();
|
|
64032
|
-
const descriptionLower = skill.description.toLowerCase();
|
|
64033
|
-
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
64034
|
-
const tagsCombined = tagsLower.join(" ");
|
|
64035
|
-
let score = 0;
|
|
64036
|
-
let allWordsMatch = true;
|
|
64037
|
-
for (const word of words2) {
|
|
64038
|
-
let wordScore = 0;
|
|
64039
|
-
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
64040
|
-
if (nameMatch > 0)
|
|
64041
|
-
wordScore += 10 * nameMatch;
|
|
64042
|
-
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
64043
|
-
if (displayMatch > 0)
|
|
64044
|
-
wordScore += 7 * displayMatch;
|
|
64045
|
-
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
64046
|
-
if (tagMatch > 0)
|
|
64047
|
-
wordScore += 5 * tagMatch;
|
|
64048
|
-
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
64049
|
-
if (descMatch > 0)
|
|
64050
|
-
wordScore += 2 * descMatch;
|
|
64051
|
-
if (wordScore === 0) {
|
|
64052
|
-
allWordsMatch = false;
|
|
64053
|
-
break;
|
|
64054
|
-
}
|
|
64055
|
-
score += wordScore;
|
|
64056
|
-
}
|
|
64057
|
-
if (allWordsMatch && score > 0) {
|
|
64058
|
-
scored.push({ skill, score });
|
|
64059
|
-
}
|
|
64060
|
-
}
|
|
64061
|
-
scored.sort((a, b) => b.score - a.score);
|
|
64062
|
-
return scored.map((s) => s.skill);
|
|
64063
|
-
}
|
|
64064
|
-
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
64065
|
-
const q = query.toLowerCase();
|
|
64066
|
-
const scored = registry.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);
|
|
64067
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
64068
|
-
}
|
|
64069
|
-
var init_search = __esm(() => {
|
|
64070
|
-
init_registry();
|
|
64071
|
-
});
|
|
64072
|
-
|
|
64073
64117
|
// src/lib/registry.ts
|
|
64074
64118
|
var exports_registry = {};
|
|
64075
64119
|
__export(exports_registry, {
|
|
@@ -88771,6 +88815,24 @@ function optionPrefix(program2, args) {
|
|
|
88771
88815
|
return { indices, separator: -1 };
|
|
88772
88816
|
}
|
|
88773
88817
|
|
|
88818
|
+
// src/cli/stray-arguments.ts
|
|
88819
|
+
function permitStrayArguments(command) {
|
|
88820
|
+
selfRefusing.add(command);
|
|
88821
|
+
return command.allowExcessArguments(true);
|
|
88822
|
+
}
|
|
88823
|
+
function rejectStrayArguments(command) {
|
|
88824
|
+
for (const child2 of command.commands) {
|
|
88825
|
+
if (child2.registeredArguments.length === 0 && !selfRefusing.has(child2)) {
|
|
88826
|
+
child2.allowExcessArguments(false);
|
|
88827
|
+
}
|
|
88828
|
+
rejectStrayArguments(child2);
|
|
88829
|
+
}
|
|
88830
|
+
}
|
|
88831
|
+
var selfRefusing;
|
|
88832
|
+
var init_stray_arguments = __esm(() => {
|
|
88833
|
+
selfRefusing = new WeakSet;
|
|
88834
|
+
});
|
|
88835
|
+
|
|
88774
88836
|
// src/lib/revision.ts
|
|
88775
88837
|
import { createHash as createHash33 } from "crypto";
|
|
88776
88838
|
function revisionIdOf(content) {
|
|
@@ -90820,7 +90882,13 @@ function handleReadRefusal(error61) {
|
|
|
90820
90882
|
async function handleInfoError(name, options, error61) {
|
|
90821
90883
|
if (isSkillsFleetCredentialError(error61))
|
|
90822
90884
|
return handleReadRefusal(error61);
|
|
90823
|
-
|
|
90885
|
+
let remote = options.remote === true;
|
|
90886
|
+
if (!remote) {
|
|
90887
|
+
try {
|
|
90888
|
+
remote = (await requireSkillsReadAccess()).mode === "hosted";
|
|
90889
|
+
} catch {}
|
|
90890
|
+
}
|
|
90891
|
+
const notFound = await resolveRemoteNotFound(name, remote, error61.message);
|
|
90824
90892
|
if (options.json)
|
|
90825
90893
|
console.log(JSON.stringify(notFound));
|
|
90826
90894
|
else
|
|
@@ -117271,14 +117339,14 @@ function registerRuntime(parent) {
|
|
|
117271
117339
|
const exportsCommand = parent.command("exports").description("Inspect or open skill run exports");
|
|
117272
117340
|
exportsCommand.command("open").argument("<run-id>", "Run id").option("--json", "Output as JSON", false).description("Open the export directory for a run").action((runId2, options) => handleExportsOpen(runId2, options));
|
|
117273
117341
|
exportsCommand.command("download").argument("<run-id>", "Remote run id").option("--json", "Output as JSON", false).description("Download remote run artifacts into .skills/exports").action((runId2, options) => handleExportsDownload(runId2, options));
|
|
117274
|
-
parent.command("mcp").option("--register <agent>", "Register MCP server with agent").option("--json", "Output registration result as JSON", false).
|
|
117342
|
+
permitStrayArguments(parent.command("mcp").option("--register <agent>", "Register MCP server with agent").option("--json", "Output registration result as JSON", false).description("Start MCP server (stdio) or register with an agent").action(async (options, command) => {
|
|
117275
117343
|
const stray = command.args[0];
|
|
117276
117344
|
if (stray !== undefined) {
|
|
117277
117345
|
console.error(source_default.red(`error: unknown argument '${stray}'. 'skills mcp' takes no positional arguments. ` + `Valid forms: 'skills mcp' (start the MCP stdio server), ` + `'skills mcp --register <agent>', 'skills mcp --register all'`));
|
|
117278
117346
|
process.exit(1);
|
|
117279
117347
|
}
|
|
117280
117348
|
await handleMcp(options);
|
|
117281
|
-
});
|
|
117349
|
+
}));
|
|
117282
117350
|
const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
|
|
117283
117351
|
setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
|
|
117284
117352
|
parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--reviewed-lock <path>", "Install from this private absolute package-lock.json path without resolving dependency ranges").option("--reviewed-lock-sha256 <sha256>", "Exact reviewed SHA-256 of the supplied package-lock.json").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
|
|
@@ -118373,6 +118441,7 @@ var init_runtime = __esm(() => {
|
|
|
118373
118441
|
init_execution_secrets();
|
|
118374
118442
|
init_context();
|
|
118375
118443
|
init_registry();
|
|
118444
|
+
init_stray_arguments();
|
|
118376
118445
|
init_skillinfo();
|
|
118377
118446
|
init_skill_contract();
|
|
118378
118447
|
init_blog_article();
|
|
@@ -134114,6 +134183,7 @@ init_registry();
|
|
|
134114
134183
|
init_discovery();
|
|
134115
134184
|
init_fleet_credentials();
|
|
134116
134185
|
init_read_access();
|
|
134186
|
+
init_stray_arguments();
|
|
134117
134187
|
var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
|
|
134118
134188
|
var isTTY2 = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
|
|
134119
134189
|
var program2 = new Command;
|
|
@@ -134123,7 +134193,7 @@ program2.hook("preAction", () => {
|
|
|
134123
134193
|
recordEnvironmentProfile(process.env.HASNA_PROFILE);
|
|
134124
134194
|
selectCliSkillsCredentialProfile(profile);
|
|
134125
134195
|
});
|
|
134126
|
-
program2.command("interactive", { isDefault: true }).alias("i").
|
|
134196
|
+
permitStrayArguments(program2.command("interactive", { isDefault: true }).alias("i").description("Interactive skill browser (TUI)").action(async (_options, command) => {
|
|
134127
134197
|
const stray = command.args[0];
|
|
134128
134198
|
if (stray !== undefined) {
|
|
134129
134199
|
const verbs = program2.commands.map((c) => c.name()).filter((n2) => n2 !== "interactive").sort();
|
|
@@ -134150,7 +134220,7 @@ program2.command("interactive", { isDefault: true }).alias("i").allowExcessArgum
|
|
|
134150
134220
|
render_default(/* @__PURE__ */ jsx_dev_runtime8.jsxDEV(App2, {
|
|
134151
134221
|
initialAccess: access
|
|
134152
134222
|
}, undefined, false, undefined, this));
|
|
134153
|
-
});
|
|
134223
|
+
}));
|
|
134154
134224
|
var { registerInstall: registerInstall2 } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
134155
134225
|
registerInstall2(program2);
|
|
134156
134226
|
var { registerBrowse: registerBrowse2 } = await Promise.resolve().then(() => (init_list(), exports_list));
|
|
@@ -134212,6 +134282,7 @@ registerStorage2(program2);
|
|
|
134212
134282
|
var { registerRegistryReconcile: registerRegistryReconcile2 } = await Promise.resolve().then(() => (init_registry_reconcile2(), exports_registry_reconcile));
|
|
134213
134283
|
registerRegistryReconcile2(program2);
|
|
134214
134284
|
registerEventsCommands(program2, { source: "skills" });
|
|
134285
|
+
rejectStrayArguments(program2);
|
|
134215
134286
|
var cliArgs = process.argv.slice(2);
|
|
134216
134287
|
var colorFlag = optionPrefix(program2, cliArgs).indices.find((index) => cliArgs[index] === "--no-color");
|
|
134217
134288
|
if (colorFlag !== undefined) {
|
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: {
|
|
@@ -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
|
});
|
|
@@ -38615,95 +38748,6 @@ var init_registry_types = __esm(() => {
|
|
|
38615
38748
|
];
|
|
38616
38749
|
});
|
|
38617
38750
|
|
|
38618
|
-
// src/lib/search.ts
|
|
38619
|
-
function editDistance(a, b) {
|
|
38620
|
-
if (a === b)
|
|
38621
|
-
return 0;
|
|
38622
|
-
if (a.length === 0)
|
|
38623
|
-
return b.length;
|
|
38624
|
-
if (b.length === 0)
|
|
38625
|
-
return a.length;
|
|
38626
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
38627
|
-
const curr = new Array(b.length + 1);
|
|
38628
|
-
for (let i = 1;i <= a.length; i++) {
|
|
38629
|
-
curr[0] = i;
|
|
38630
|
-
for (let j = 1;j <= b.length; j++) {
|
|
38631
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
38632
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
38633
|
-
}
|
|
38634
|
-
prev.splice(0, prev.length, ...curr);
|
|
38635
|
-
}
|
|
38636
|
-
return prev[b.length];
|
|
38637
|
-
}
|
|
38638
|
-
function fuzzyMatchScore(word, target) {
|
|
38639
|
-
if (target.includes(word))
|
|
38640
|
-
return 1;
|
|
38641
|
-
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
38642
|
-
for (const token of tokens) {
|
|
38643
|
-
if (token.startsWith(word))
|
|
38644
|
-
return 0.6;
|
|
38645
|
-
}
|
|
38646
|
-
if (word.length >= 3) {
|
|
38647
|
-
const maxDist = word.length <= 3 ? 1 : 2;
|
|
38648
|
-
for (const token of tokens) {
|
|
38649
|
-
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
38650
|
-
const dist = editDistance(word, token);
|
|
38651
|
-
if (dist <= maxDist)
|
|
38652
|
-
return 0.4;
|
|
38653
|
-
}
|
|
38654
|
-
}
|
|
38655
|
-
}
|
|
38656
|
-
return 0;
|
|
38657
|
-
}
|
|
38658
|
-
function searchSkills(query, registry2 = loadRegistry()) {
|
|
38659
|
-
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
38660
|
-
if (words.length === 0)
|
|
38661
|
-
return [];
|
|
38662
|
-
const scored = [];
|
|
38663
|
-
for (const skill of registry2) {
|
|
38664
|
-
const nameLower = skill.name.toLowerCase();
|
|
38665
|
-
const displayNameLower = skill.displayName.toLowerCase();
|
|
38666
|
-
const descriptionLower = skill.description.toLowerCase();
|
|
38667
|
-
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
38668
|
-
const tagsCombined = tagsLower.join(" ");
|
|
38669
|
-
let score = 0;
|
|
38670
|
-
let allWordsMatch = true;
|
|
38671
|
-
for (const word of words) {
|
|
38672
|
-
let wordScore = 0;
|
|
38673
|
-
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
38674
|
-
if (nameMatch > 0)
|
|
38675
|
-
wordScore += 10 * nameMatch;
|
|
38676
|
-
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
38677
|
-
if (displayMatch > 0)
|
|
38678
|
-
wordScore += 7 * displayMatch;
|
|
38679
|
-
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
38680
|
-
if (tagMatch > 0)
|
|
38681
|
-
wordScore += 5 * tagMatch;
|
|
38682
|
-
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
38683
|
-
if (descMatch > 0)
|
|
38684
|
-
wordScore += 2 * descMatch;
|
|
38685
|
-
if (wordScore === 0) {
|
|
38686
|
-
allWordsMatch = false;
|
|
38687
|
-
break;
|
|
38688
|
-
}
|
|
38689
|
-
score += wordScore;
|
|
38690
|
-
}
|
|
38691
|
-
if (allWordsMatch && score > 0) {
|
|
38692
|
-
scored.push({ skill, score });
|
|
38693
|
-
}
|
|
38694
|
-
}
|
|
38695
|
-
scored.sort((a, b) => b.score - a.score);
|
|
38696
|
-
return scored.map((s) => s.skill);
|
|
38697
|
-
}
|
|
38698
|
-
function findSimilarSkills(query, maxResults = 3, registry2 = loadRegistry()) {
|
|
38699
|
-
const q = query.toLowerCase();
|
|
38700
|
-
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);
|
|
38701
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
38702
|
-
}
|
|
38703
|
-
var init_search = __esm(() => {
|
|
38704
|
-
init_registry();
|
|
38705
|
-
});
|
|
38706
|
-
|
|
38707
38751
|
// src/lib/registry.ts
|
|
38708
38752
|
import { existsSync as existsSync13, readFileSync as readFileSync18, readdirSync as readdirSync10 } from "fs";
|
|
38709
38753
|
import { join as join33 } from "path";
|
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,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;
|
package/dist/index.js
CHANGED
|
@@ -15144,7 +15144,7 @@ import { basename as basename13, dirname as dirname24, isAbsolute as isAbsolute2
|
|
|
15144
15144
|
import { homedir as homedir5 } from "os";
|
|
15145
15145
|
import { isAbsolute as isAbsolute25, join as join36, resolve as resolve24 } from "path";
|
|
15146
15146
|
// package.json
|
|
15147
|
-
var version = "0.10.
|
|
15147
|
+
var version = "0.10.75";
|
|
15148
15148
|
var package_default = {
|
|
15149
15149
|
name: "@hasna/skills",
|
|
15150
15150
|
version,
|
|
@@ -22727,6 +22727,115 @@ function readIfExists(path) {
|
|
|
22727
22727
|
return null;
|
|
22728
22728
|
}
|
|
22729
22729
|
|
|
22730
|
+
// src/lib/search.ts
|
|
22731
|
+
function editDistance(a, b) {
|
|
22732
|
+
if (a === b)
|
|
22733
|
+
return 0;
|
|
22734
|
+
if (a.length === 0)
|
|
22735
|
+
return b.length;
|
|
22736
|
+
if (b.length === 0)
|
|
22737
|
+
return a.length;
|
|
22738
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
22739
|
+
const curr = new Array(b.length + 1);
|
|
22740
|
+
for (let i = 1;i <= a.length; i++) {
|
|
22741
|
+
curr[0] = i;
|
|
22742
|
+
for (let j = 1;j <= b.length; j++) {
|
|
22743
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
22744
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
22745
|
+
}
|
|
22746
|
+
prev.splice(0, prev.length, ...curr);
|
|
22747
|
+
}
|
|
22748
|
+
return prev[b.length];
|
|
22749
|
+
}
|
|
22750
|
+
function fuzzyMatchScore(word, target) {
|
|
22751
|
+
if (target.includes(word))
|
|
22752
|
+
return 1;
|
|
22753
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
22754
|
+
for (const token of tokens) {
|
|
22755
|
+
if (token.startsWith(word))
|
|
22756
|
+
return 0.6;
|
|
22757
|
+
}
|
|
22758
|
+
if (word.length >= 3) {
|
|
22759
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
22760
|
+
for (const token of tokens) {
|
|
22761
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
22762
|
+
const dist = editDistance(word, token);
|
|
22763
|
+
if (dist <= maxDist)
|
|
22764
|
+
return 0.4;
|
|
22765
|
+
}
|
|
22766
|
+
}
|
|
22767
|
+
}
|
|
22768
|
+
return 0;
|
|
22769
|
+
}
|
|
22770
|
+
function searchSkills(query, registry = loadRegistry()) {
|
|
22771
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
22772
|
+
if (words.length === 0)
|
|
22773
|
+
return [];
|
|
22774
|
+
const scored = [];
|
|
22775
|
+
for (const skill of registry) {
|
|
22776
|
+
const nameLower = skill.name.toLowerCase();
|
|
22777
|
+
const displayNameLower = skill.displayName.toLowerCase();
|
|
22778
|
+
const descriptionLower = skill.description.toLowerCase();
|
|
22779
|
+
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
22780
|
+
const tagsCombined = tagsLower.join(" ");
|
|
22781
|
+
let score = 0;
|
|
22782
|
+
let allWordsMatch = true;
|
|
22783
|
+
for (const word of words) {
|
|
22784
|
+
let wordScore = 0;
|
|
22785
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
22786
|
+
if (nameMatch > 0)
|
|
22787
|
+
wordScore += 10 * nameMatch;
|
|
22788
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
22789
|
+
if (displayMatch > 0)
|
|
22790
|
+
wordScore += 7 * displayMatch;
|
|
22791
|
+
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
22792
|
+
if (tagMatch > 0)
|
|
22793
|
+
wordScore += 5 * tagMatch;
|
|
22794
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
22795
|
+
if (descMatch > 0)
|
|
22796
|
+
wordScore += 2 * descMatch;
|
|
22797
|
+
if (wordScore === 0) {
|
|
22798
|
+
allWordsMatch = false;
|
|
22799
|
+
break;
|
|
22800
|
+
}
|
|
22801
|
+
score += wordScore;
|
|
22802
|
+
}
|
|
22803
|
+
if (allWordsMatch && score > 0) {
|
|
22804
|
+
scored.push({ skill, score });
|
|
22805
|
+
}
|
|
22806
|
+
}
|
|
22807
|
+
scored.sort((a, b) => b.score - a.score);
|
|
22808
|
+
return scored.map((s) => s.skill);
|
|
22809
|
+
}
|
|
22810
|
+
function findCloseSkillNames(query, names, maxResults = 3) {
|
|
22811
|
+
if (maxResults <= 0)
|
|
22812
|
+
return [];
|
|
22813
|
+
const q = query.trim().toLowerCase();
|
|
22814
|
+
if (q.length === 0)
|
|
22815
|
+
return [];
|
|
22816
|
+
const tokens = q.split("-").filter(Boolean);
|
|
22817
|
+
const ranked = [];
|
|
22818
|
+
if (q.length >= 3) {
|
|
22819
|
+
for (const name of names) {
|
|
22820
|
+
const candidate = name.toLowerCase();
|
|
22821
|
+
const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
|
|
22822
|
+
if (rank !== undefined)
|
|
22823
|
+
ranked.push([name, rank]);
|
|
22824
|
+
}
|
|
22825
|
+
}
|
|
22826
|
+
ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
|
|
22827
|
+
const found = ranked.slice(0, maxResults).map(([name]) => name);
|
|
22828
|
+
if (found.length === maxResults)
|
|
22829
|
+
return found;
|
|
22830
|
+
const taken = new Set(found);
|
|
22831
|
+
const threshold = Math.max(3, Math.floor(q.length / 2));
|
|
22832
|
+
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));
|
|
22833
|
+
return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
|
|
22834
|
+
}
|
|
22835
|
+
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
22836
|
+
return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
|
|
22837
|
+
}
|
|
22838
|
+
|
|
22730
22839
|
// src/lib/selected-document.ts
|
|
22731
22840
|
var DOCUMENT_PATHS = ["SKILL.md", "README.md", "CLAUDE.md"];
|
|
22732
22841
|
function readSelectedDocument(entries, file) {
|
|
@@ -22843,11 +22952,31 @@ function exactProfileSelection(spec, profile) {
|
|
|
22843
22952
|
const at = spec.lastIndexOf("@");
|
|
22844
22953
|
const slug = at > 0 ? spec.slice(0, at) : spec;
|
|
22845
22954
|
const version2 = at > 0 ? spec.slice(at + 1) : undefined;
|
|
22846
|
-
const
|
|
22847
|
-
if (
|
|
22848
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
22955
|
+
const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
|
|
22956
|
+
if (version2 === "")
|
|
22957
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
|
|
22958
|
+
if (named.length === 0)
|
|
22959
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
|
|
22960
|
+
const selection = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
|
|
22961
|
+
if (!selection)
|
|
22962
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
|
|
22849
22963
|
return selection;
|
|
22850
22964
|
}
|
|
22965
|
+
var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
|
|
22966
|
+
function emptyVersionRefusal(spec) {
|
|
22967
|
+
return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
|
|
22968
|
+
}
|
|
22969
|
+
function wrongVersionRefusal(slug, version2, named) {
|
|
22970
|
+
const versions = [...new Set(named.map((entry) => entry.version))].sort();
|
|
22971
|
+
const canonical9 = named[0].slug;
|
|
22972
|
+
return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
|
|
22973
|
+
}
|
|
22974
|
+
function unknownNameRefusal(slug, profile) {
|
|
22975
|
+
const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
|
|
22976
|
+
const close = findCloseSkillNames(slug, names);
|
|
22977
|
+
const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
22978
|
+
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}`;
|
|
22979
|
+
}
|
|
22851
22980
|
|
|
22852
22981
|
// src/lib/selected-manifest.ts
|
|
22853
22982
|
function jsonEntry(entries, path) {
|
|
@@ -25354,92 +25483,6 @@ var CATEGORIES = [
|
|
|
25354
25483
|
"Event Management"
|
|
25355
25484
|
];
|
|
25356
25485
|
var BASIC_SKILL_NAMES = [];
|
|
25357
|
-
|
|
25358
|
-
// src/lib/search.ts
|
|
25359
|
-
function editDistance(a, b) {
|
|
25360
|
-
if (a === b)
|
|
25361
|
-
return 0;
|
|
25362
|
-
if (a.length === 0)
|
|
25363
|
-
return b.length;
|
|
25364
|
-
if (b.length === 0)
|
|
25365
|
-
return a.length;
|
|
25366
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
25367
|
-
const curr = new Array(b.length + 1);
|
|
25368
|
-
for (let i = 1;i <= a.length; i++) {
|
|
25369
|
-
curr[0] = i;
|
|
25370
|
-
for (let j = 1;j <= b.length; j++) {
|
|
25371
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
25372
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
25373
|
-
}
|
|
25374
|
-
prev.splice(0, prev.length, ...curr);
|
|
25375
|
-
}
|
|
25376
|
-
return prev[b.length];
|
|
25377
|
-
}
|
|
25378
|
-
function fuzzyMatchScore(word, target) {
|
|
25379
|
-
if (target.includes(word))
|
|
25380
|
-
return 1;
|
|
25381
|
-
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
25382
|
-
for (const token of tokens) {
|
|
25383
|
-
if (token.startsWith(word))
|
|
25384
|
-
return 0.6;
|
|
25385
|
-
}
|
|
25386
|
-
if (word.length >= 3) {
|
|
25387
|
-
const maxDist = word.length <= 3 ? 1 : 2;
|
|
25388
|
-
for (const token of tokens) {
|
|
25389
|
-
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
25390
|
-
const dist = editDistance(word, token);
|
|
25391
|
-
if (dist <= maxDist)
|
|
25392
|
-
return 0.4;
|
|
25393
|
-
}
|
|
25394
|
-
}
|
|
25395
|
-
}
|
|
25396
|
-
return 0;
|
|
25397
|
-
}
|
|
25398
|
-
function searchSkills(query, registry = loadRegistry()) {
|
|
25399
|
-
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
25400
|
-
if (words.length === 0)
|
|
25401
|
-
return [];
|
|
25402
|
-
const scored = [];
|
|
25403
|
-
for (const skill of registry) {
|
|
25404
|
-
const nameLower = skill.name.toLowerCase();
|
|
25405
|
-
const displayNameLower = skill.displayName.toLowerCase();
|
|
25406
|
-
const descriptionLower = skill.description.toLowerCase();
|
|
25407
|
-
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
25408
|
-
const tagsCombined = tagsLower.join(" ");
|
|
25409
|
-
let score = 0;
|
|
25410
|
-
let allWordsMatch = true;
|
|
25411
|
-
for (const word of words) {
|
|
25412
|
-
let wordScore = 0;
|
|
25413
|
-
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
25414
|
-
if (nameMatch > 0)
|
|
25415
|
-
wordScore += 10 * nameMatch;
|
|
25416
|
-
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
25417
|
-
if (displayMatch > 0)
|
|
25418
|
-
wordScore += 7 * displayMatch;
|
|
25419
|
-
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
25420
|
-
if (tagMatch > 0)
|
|
25421
|
-
wordScore += 5 * tagMatch;
|
|
25422
|
-
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
25423
|
-
if (descMatch > 0)
|
|
25424
|
-
wordScore += 2 * descMatch;
|
|
25425
|
-
if (wordScore === 0) {
|
|
25426
|
-
allWordsMatch = false;
|
|
25427
|
-
break;
|
|
25428
|
-
}
|
|
25429
|
-
score += wordScore;
|
|
25430
|
-
}
|
|
25431
|
-
if (allWordsMatch && score > 0) {
|
|
25432
|
-
scored.push({ skill, score });
|
|
25433
|
-
}
|
|
25434
|
-
}
|
|
25435
|
-
scored.sort((a, b) => b.score - a.score);
|
|
25436
|
-
return scored.map((s) => s.skill);
|
|
25437
|
-
}
|
|
25438
|
-
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
25439
|
-
const q = query.toLowerCase();
|
|
25440
|
-
const scored = registry.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);
|
|
25441
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
25442
|
-
}
|
|
25443
25486
|
// src/lib/registry.ts
|
|
25444
25487
|
function isBasicSkillName(name) {
|
|
25445
25488
|
return BASIC_SKILL_NAMES.includes(name);
|
package/dist/lib/search.d.ts
CHANGED
|
@@ -11,6 +11,24 @@ export declare function editDistance(a: string, b: string): number;
|
|
|
11
11
|
* Search skills by name, description, and tags using fuzzy matching.
|
|
12
12
|
*/
|
|
13
13
|
export declare function searchSkills(query: string, registry?: SkillMeta[]): SkillMeta[];
|
|
14
|
+
/**
|
|
15
|
+
* Bounded close canonical names for a refused name, strongest evidence first:
|
|
16
|
+
*
|
|
17
|
+
* 0. the exact name;
|
|
18
|
+
* 1. a hyphen-delimited containment — the candidate owns the query as a whole
|
|
19
|
+
* leading or trailing segment (`fleet-refresh` → `instructions-fleet-refresh`);
|
|
20
|
+
* 2. a multi-word token subset of the candidate;
|
|
21
|
+
* 3. the edit-distance rule that was the only signal before.
|
|
22
|
+
*
|
|
23
|
+
* Containment is what edit distance cannot see, and it is the case that cost
|
|
24
|
+
* real time: the canonical name that owns a shorter name as its trailing
|
|
25
|
+
* segment is 13 edits from the short name, so a caller naming the procedure by
|
|
26
|
+
* its last words was told the procedure did not exist (Agents Audit A27). The
|
|
27
|
+
* distance pass still runs and fills any slots containment leaves, so a
|
|
28
|
+
* distance-only miss — including every query under three characters — keeps the
|
|
29
|
+
* answer it has always had.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findCloseSkillNames(query: string, names: readonly string[], maxResults?: number): string[];
|
|
14
32
|
/**
|
|
15
33
|
* Find skills with names similar to the given query (for "did you mean?" suggestions).
|
|
16
34
|
*/
|
|
@@ -49,7 +49,7 @@ var __require = import.meta.require;
|
|
|
49
49
|
// src/runtime-prerequisites.ts
|
|
50
50
|
import { isAbsolute as isAbsolute7 } from "path";
|
|
51
51
|
// package.json
|
|
52
|
-
var version = "0.10.
|
|
52
|
+
var version = "0.10.75";
|
|
53
53
|
var package_default = {
|
|
54
54
|
name: "@hasna/skills",
|
|
55
55
|
version,
|
package/dist/sdk/index.js
CHANGED
|
@@ -32371,7 +32371,7 @@ var init_event_sink = __esm(() => {
|
|
|
32371
32371
|
init_dist();
|
|
32372
32372
|
});
|
|
32373
32373
|
// package.json
|
|
32374
|
-
var version = "0.10.
|
|
32374
|
+
var version = "0.10.75";
|
|
32375
32375
|
var package_default = {
|
|
32376
32376
|
name: "@hasna/skills",
|
|
32377
32377
|
version,
|
|
@@ -51913,10 +51913,33 @@ function searchSkills(query, registry = loadRegistry()) {
|
|
|
51913
51913
|
scored.sort((a, b) => b.score - a.score);
|
|
51914
51914
|
return scored.map((s) => s.skill);
|
|
51915
51915
|
}
|
|
51916
|
+
function findCloseSkillNames(query, names, maxResults = 3) {
|
|
51917
|
+
if (maxResults <= 0)
|
|
51918
|
+
return [];
|
|
51919
|
+
const q = query.trim().toLowerCase();
|
|
51920
|
+
if (q.length === 0)
|
|
51921
|
+
return [];
|
|
51922
|
+
const tokens = q.split("-").filter(Boolean);
|
|
51923
|
+
const ranked = [];
|
|
51924
|
+
if (q.length >= 3) {
|
|
51925
|
+
for (const name of names) {
|
|
51926
|
+
const candidate2 = name.toLowerCase();
|
|
51927
|
+
const rank = candidate2 === q ? 0 : candidate2.endsWith(`-${q}`) || candidate2.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate2.split("-").includes(token)) ? 2 : undefined;
|
|
51928
|
+
if (rank !== undefined)
|
|
51929
|
+
ranked.push([name, rank]);
|
|
51930
|
+
}
|
|
51931
|
+
}
|
|
51932
|
+
ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
|
|
51933
|
+
const found = ranked.slice(0, maxResults).map(([name]) => name);
|
|
51934
|
+
if (found.length === maxResults)
|
|
51935
|
+
return found;
|
|
51936
|
+
const taken = new Set(found);
|
|
51937
|
+
const threshold = Math.max(3, Math.floor(q.length / 2));
|
|
51938
|
+
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));
|
|
51939
|
+
return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
|
|
51940
|
+
}
|
|
51916
51941
|
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
51917
|
-
|
|
51918
|
-
const scored = registry.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);
|
|
51919
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
51942
|
+
return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
|
|
51920
51943
|
}
|
|
51921
51944
|
// src/lib/registry.ts
|
|
51922
51945
|
function isBasicSkillName(name) {
|
|
@@ -52937,11 +52960,31 @@ function exactProfileSelection(spec, profile) {
|
|
|
52937
52960
|
const at = spec.lastIndexOf("@");
|
|
52938
52961
|
const slug = at > 0 ? spec.slice(0, at) : spec;
|
|
52939
52962
|
const version2 = at > 0 ? spec.slice(at + 1) : undefined;
|
|
52940
|
-
const
|
|
52941
|
-
if (
|
|
52942
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
52963
|
+
const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
|
|
52964
|
+
if (version2 === "")
|
|
52965
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
|
|
52966
|
+
if (named.length === 0)
|
|
52967
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
|
|
52968
|
+
const selection2 = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
|
|
52969
|
+
if (!selection2)
|
|
52970
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
|
|
52943
52971
|
return selection2;
|
|
52944
52972
|
}
|
|
52973
|
+
var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
|
|
52974
|
+
function emptyVersionRefusal(spec) {
|
|
52975
|
+
return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
|
|
52976
|
+
}
|
|
52977
|
+
function wrongVersionRefusal(slug, version2, named) {
|
|
52978
|
+
const versions = [...new Set(named.map((entry) => entry.version))].sort();
|
|
52979
|
+
const canonical9 = named[0].slug;
|
|
52980
|
+
return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
|
|
52981
|
+
}
|
|
52982
|
+
function unknownNameRefusal(slug, profile) {
|
|
52983
|
+
const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
|
|
52984
|
+
const close = findCloseSkillNames(slug, names);
|
|
52985
|
+
const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
52986
|
+
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}`;
|
|
52987
|
+
}
|
|
52945
52988
|
|
|
52946
52989
|
// src/lib/selected-run.ts
|
|
52947
52990
|
async function resolveSelectedRun(spec, profileId, options = {}) {
|