@hasna/skills 0.1.5 → 0.1.6
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 +297 -39
- package/bin/mcp.js +69 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +70 -18
- package/dist/lib/registry.d.ts +8 -0
- package/dist/lib/skillinfo.d.ts +9 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1878,7 +1878,7 @@ var package_default;
|
|
|
1878
1878
|
var init_package = __esm(() => {
|
|
1879
1879
|
package_default = {
|
|
1880
1880
|
name: "@hasna/skills",
|
|
1881
|
-
version: "0.1.
|
|
1881
|
+
version: "0.1.6",
|
|
1882
1882
|
description: "Skills library for AI coding agents",
|
|
1883
1883
|
type: "module",
|
|
1884
1884
|
bin: {
|
|
@@ -1957,6 +1957,45 @@ var init_package = __esm(() => {
|
|
|
1957
1957
|
function getSkillsByCategory(category) {
|
|
1958
1958
|
return SKILLS.filter((s) => s.category === category);
|
|
1959
1959
|
}
|
|
1960
|
+
function editDistance(a, b) {
|
|
1961
|
+
if (a === b)
|
|
1962
|
+
return 0;
|
|
1963
|
+
if (a.length === 0)
|
|
1964
|
+
return b.length;
|
|
1965
|
+
if (b.length === 0)
|
|
1966
|
+
return a.length;
|
|
1967
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1968
|
+
const curr = new Array(b.length + 1);
|
|
1969
|
+
for (let i = 1;i <= a.length; i++) {
|
|
1970
|
+
curr[0] = i;
|
|
1971
|
+
for (let j = 1;j <= b.length; j++) {
|
|
1972
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1973
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
1974
|
+
}
|
|
1975
|
+
prev.splice(0, prev.length, ...curr);
|
|
1976
|
+
}
|
|
1977
|
+
return prev[b.length];
|
|
1978
|
+
}
|
|
1979
|
+
function fuzzyMatchScore(word, target) {
|
|
1980
|
+
if (target.includes(word))
|
|
1981
|
+
return 1;
|
|
1982
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
1983
|
+
for (const token of tokens) {
|
|
1984
|
+
if (token.startsWith(word))
|
|
1985
|
+
return 0.6;
|
|
1986
|
+
}
|
|
1987
|
+
if (word.length >= 3) {
|
|
1988
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
1989
|
+
for (const token of tokens) {
|
|
1990
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
1991
|
+
const dist = editDistance(word, token);
|
|
1992
|
+
if (dist <= maxDist)
|
|
1993
|
+
return 0.4;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return 0;
|
|
1998
|
+
}
|
|
1960
1999
|
function searchSkills(query) {
|
|
1961
2000
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1962
2001
|
if (words.length === 0)
|
|
@@ -1967,30 +2006,28 @@ function searchSkills(query) {
|
|
|
1967
2006
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
1968
2007
|
const descriptionLower = skill.description.toLowerCase();
|
|
1969
2008
|
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
2009
|
+
const tagsCombined = tagsLower.join(" ");
|
|
1970
2010
|
let score = 0;
|
|
1971
2011
|
let allWordsMatch = true;
|
|
1972
2012
|
for (const word of words) {
|
|
1973
|
-
let
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
if (
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
if (
|
|
1987
|
-
score += 2;
|
|
1988
|
-
wordMatched = true;
|
|
1989
|
-
}
|
|
1990
|
-
if (!wordMatched) {
|
|
2013
|
+
let wordScore = 0;
|
|
2014
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
2015
|
+
if (nameMatch > 0)
|
|
2016
|
+
wordScore += 10 * nameMatch;
|
|
2017
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
2018
|
+
if (displayMatch > 0)
|
|
2019
|
+
wordScore += 7 * displayMatch;
|
|
2020
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
2021
|
+
if (tagMatch > 0)
|
|
2022
|
+
wordScore += 5 * tagMatch;
|
|
2023
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
2024
|
+
if (descMatch > 0)
|
|
2025
|
+
wordScore += 2 * descMatch;
|
|
2026
|
+
if (wordScore === 0) {
|
|
1991
2027
|
allWordsMatch = false;
|
|
1992
2028
|
break;
|
|
1993
2029
|
}
|
|
2030
|
+
score += wordScore;
|
|
1994
2031
|
}
|
|
1995
2032
|
if (allWordsMatch && score > 0) {
|
|
1996
2033
|
scored.push({ skill, score });
|
|
@@ -5395,6 +5432,93 @@ async function runSkill(name, args, options = {}) {
|
|
|
5395
5432
|
const exitCode = await proc.exited;
|
|
5396
5433
|
return { exitCode };
|
|
5397
5434
|
}
|
|
5435
|
+
function detectProjectSkills(cwd = process.cwd()) {
|
|
5436
|
+
const pkgPath = join2(cwd, "package.json");
|
|
5437
|
+
if (!existsSync2(pkgPath)) {
|
|
5438
|
+
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5439
|
+
const recommended2 = alwaysRecommend.map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5440
|
+
return { detected: [], recommended: recommended2 };
|
|
5441
|
+
}
|
|
5442
|
+
let pkg;
|
|
5443
|
+
try {
|
|
5444
|
+
pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5445
|
+
} catch {
|
|
5446
|
+
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5447
|
+
const recommended2 = alwaysRecommend.map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5448
|
+
return { detected: [], recommended: recommended2 };
|
|
5449
|
+
}
|
|
5450
|
+
const allDeps = {
|
|
5451
|
+
...pkg.dependencies,
|
|
5452
|
+
...pkg.devDependencies
|
|
5453
|
+
};
|
|
5454
|
+
const depNames = Object.keys(allDeps);
|
|
5455
|
+
const detected = [];
|
|
5456
|
+
const recommendedNames = new Set;
|
|
5457
|
+
for (const name of ["implementation-plan", "write", "deepresearch"]) {
|
|
5458
|
+
recommendedNames.add(name);
|
|
5459
|
+
}
|
|
5460
|
+
const frontendDeps = ["next", "react", "vue", "svelte", "nuxt", "@nuxtjs/nuxt"];
|
|
5461
|
+
for (const dep of frontendDeps) {
|
|
5462
|
+
if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
|
|
5463
|
+
detected.push(dep);
|
|
5464
|
+
for (const name of ["image", "generate-favicon", "seo-brief-builder"]) {
|
|
5465
|
+
recommendedNames.add(name);
|
|
5466
|
+
}
|
|
5467
|
+
break;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
const backendDeps = ["express", "fastify", "hono", "koa", "@hono/hono"];
|
|
5471
|
+
for (const dep of backendDeps) {
|
|
5472
|
+
if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
|
|
5473
|
+
detected.push(dep);
|
|
5474
|
+
for (const name of ["api-test-suite", "apidocs"]) {
|
|
5475
|
+
recommendedNames.add(name);
|
|
5476
|
+
}
|
|
5477
|
+
break;
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
const aiDeps = ["@anthropic-ai/sdk", "openai", "@openai/openai", "anthropic"];
|
|
5481
|
+
for (const dep of aiDeps) {
|
|
5482
|
+
if (depNames.includes(dep)) {
|
|
5483
|
+
detected.push(dep);
|
|
5484
|
+
for (const name of ["deepresearch", "webcrawling"]) {
|
|
5485
|
+
recommendedNames.add(name);
|
|
5486
|
+
}
|
|
5487
|
+
break;
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
if (depNames.includes("stripe")) {
|
|
5491
|
+
detected.push("stripe");
|
|
5492
|
+
recommendedNames.add("invoice");
|
|
5493
|
+
}
|
|
5494
|
+
const emailDeps = ["nodemailer", "@sendgrid/mail", "@sendgrid/client"];
|
|
5495
|
+
for (const dep of emailDeps) {
|
|
5496
|
+
if (depNames.includes(dep)) {
|
|
5497
|
+
detected.push(dep);
|
|
5498
|
+
for (const name of ["gmail", "email-campaign"]) {
|
|
5499
|
+
recommendedNames.add(name);
|
|
5500
|
+
}
|
|
5501
|
+
break;
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
const testDeps = ["vitest", "jest", "mocha", "@jest/core"];
|
|
5505
|
+
for (const dep of testDeps) {
|
|
5506
|
+
if (depNames.includes(dep)) {
|
|
5507
|
+
detected.push(dep);
|
|
5508
|
+
recommendedNames.add("api-test-suite");
|
|
5509
|
+
break;
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
if (depNames.includes("typescript")) {
|
|
5513
|
+
detected.push("typescript");
|
|
5514
|
+
for (const name of ["scaffold-project", "deploy"]) {
|
|
5515
|
+
recommendedNames.add(name);
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
const uniqueDetected = Array.from(new Set(detected));
|
|
5519
|
+
const recommended = Array.from(recommendedNames).map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5520
|
+
return { detected: uniqueDetected, recommended };
|
|
5521
|
+
}
|
|
5398
5522
|
function generateSkillMd(name) {
|
|
5399
5523
|
const meta = getSkill(name);
|
|
5400
5524
|
if (!meta)
|
|
@@ -34796,6 +34920,19 @@ var init_mcp2 = __esm(() => {
|
|
|
34796
34920
|
}));
|
|
34797
34921
|
return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
|
|
34798
34922
|
});
|
|
34923
|
+
server.registerTool("list_tags", {
|
|
34924
|
+
title: "List Tags",
|
|
34925
|
+
description: "List all unique tags across all skills with their occurrence counts"
|
|
34926
|
+
}, async () => {
|
|
34927
|
+
const tagCounts = new Map;
|
|
34928
|
+
for (const skill of SKILLS) {
|
|
34929
|
+
for (const tag of skill.tags) {
|
|
34930
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
34931
|
+
}
|
|
34932
|
+
}
|
|
34933
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
34934
|
+
return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
|
|
34935
|
+
});
|
|
34799
34936
|
server.registerTool("get_requirements", {
|
|
34800
34937
|
title: "Get Requirements",
|
|
34801
34938
|
description: "Get environment variables, system dependencies, and npm dependencies for a skill",
|
|
@@ -34959,6 +35096,16 @@ function createFetchHandler(options) {
|
|
|
34959
35096
|
}));
|
|
34960
35097
|
return json2(counts);
|
|
34961
35098
|
}
|
|
35099
|
+
if (path === "/api/tags" && method === "GET") {
|
|
35100
|
+
const tagCounts = new Map;
|
|
35101
|
+
for (const skill of SKILLS) {
|
|
35102
|
+
for (const tag of skill.tags) {
|
|
35103
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35104
|
+
}
|
|
35105
|
+
}
|
|
35106
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
35107
|
+
return json2(sorted);
|
|
35108
|
+
}
|
|
34962
35109
|
if (path === "/api/skills/search" && method === "GET") {
|
|
34963
35110
|
const query = url2.searchParams.get("q") || "";
|
|
34964
35111
|
if (!query.trim())
|
|
@@ -36416,7 +36563,7 @@ Skills installed to .skills/`));
|
|
|
36416
36563
|
process.exitCode = 1;
|
|
36417
36564
|
}
|
|
36418
36565
|
});
|
|
36419
|
-
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show only installed skills", false).option("--json", "Output as JSON", false).description("List available or installed skills").action((options) => {
|
|
36566
|
+
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show only installed skills", false).option("-t, --tags <tags>", "Filter by comma-separated tags (OR logic, case-insensitive)").option("--json", "Output as JSON", false).description("List available or installed skills").action((options) => {
|
|
36420
36567
|
if (options.installed) {
|
|
36421
36568
|
const installed = getInstalledSkills();
|
|
36422
36569
|
if (options.json) {
|
|
@@ -36435,6 +36582,7 @@ Installed skills (${installed.length}):
|
|
|
36435
36582
|
}
|
|
36436
36583
|
return;
|
|
36437
36584
|
}
|
|
36585
|
+
const tagFilter = options.tags ? options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean) : null;
|
|
36438
36586
|
if (options.category) {
|
|
36439
36587
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36440
36588
|
if (!category) {
|
|
@@ -36443,7 +36591,10 @@ Installed skills (${installed.length}):
|
|
|
36443
36591
|
process.exitCode = 1;
|
|
36444
36592
|
return;
|
|
36445
36593
|
}
|
|
36446
|
-
|
|
36594
|
+
let skills = getSkillsByCategory(category);
|
|
36595
|
+
if (tagFilter) {
|
|
36596
|
+
skills = skills.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36597
|
+
}
|
|
36447
36598
|
if (options.json) {
|
|
36448
36599
|
console.log(JSON.stringify(skills, null, 2));
|
|
36449
36600
|
return;
|
|
@@ -36456,6 +36607,20 @@ ${category} (${skills.length}):
|
|
|
36456
36607
|
}
|
|
36457
36608
|
return;
|
|
36458
36609
|
}
|
|
36610
|
+
if (tagFilter) {
|
|
36611
|
+
const skills = SKILLS.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36612
|
+
if (options.json) {
|
|
36613
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
36614
|
+
return;
|
|
36615
|
+
}
|
|
36616
|
+
console.log(chalk2.bold(`
|
|
36617
|
+
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
36618
|
+
`));
|
|
36619
|
+
for (const s of skills) {
|
|
36620
|
+
console.log(` ${chalk2.cyan(s.name)} ${chalk2.dim(`[${s.category}]`)} - ${s.description}`);
|
|
36621
|
+
}
|
|
36622
|
+
return;
|
|
36623
|
+
}
|
|
36459
36624
|
if (options.json) {
|
|
36460
36625
|
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36461
36626
|
return;
|
|
@@ -36472,7 +36637,7 @@ Available skills (${SKILLS.length}):
|
|
|
36472
36637
|
console.log();
|
|
36473
36638
|
}
|
|
36474
36639
|
});
|
|
36475
|
-
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("-c, --category <category>", "Filter results by category").description("Search for skills").action((query, options) => {
|
|
36640
|
+
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("-c, --category <category>", "Filter results by category").option("-t, --tags <tags>", "Filter results by comma-separated tags (OR logic, case-insensitive)").description("Search for skills").action((query, options) => {
|
|
36476
36641
|
let results = searchSkills(query);
|
|
36477
36642
|
if (options.category) {
|
|
36478
36643
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
@@ -36484,6 +36649,10 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36484
36649
|
}
|
|
36485
36650
|
results = results.filter((s) => s.category === category);
|
|
36486
36651
|
}
|
|
36652
|
+
if (options.tags) {
|
|
36653
|
+
const tagFilter = options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
36654
|
+
results = results.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36655
|
+
}
|
|
36487
36656
|
if (options.json) {
|
|
36488
36657
|
console.log(JSON.stringify(results, null, 2));
|
|
36489
36658
|
return;
|
|
@@ -36628,10 +36797,76 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
36628
36797
|
}
|
|
36629
36798
|
process.exitCode = result.exitCode;
|
|
36630
36799
|
});
|
|
36631
|
-
program2.command("init").option("--json", "Output as JSON", false).description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
36800
|
+
program2.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and install recommended skills for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
36632
36801
|
const cwd = process.cwd();
|
|
36802
|
+
if (options.for) {
|
|
36803
|
+
let agents;
|
|
36804
|
+
try {
|
|
36805
|
+
agents = resolveAgents(options.for);
|
|
36806
|
+
} catch (err) {
|
|
36807
|
+
console.error(chalk2.red(err.message));
|
|
36808
|
+
process.exitCode = 1;
|
|
36809
|
+
return;
|
|
36810
|
+
}
|
|
36811
|
+
const { detected, recommended } = detectProjectSkills(cwd);
|
|
36812
|
+
if (options.json) {
|
|
36813
|
+
const installResults2 = [];
|
|
36814
|
+
for (const skill of recommended) {
|
|
36815
|
+
for (const agent of agents) {
|
|
36816
|
+
const result = installSkillForAgent(skill.name, {
|
|
36817
|
+
agent,
|
|
36818
|
+
scope: options.scope
|
|
36819
|
+
}, generateSkillMd);
|
|
36820
|
+
installResults2.push({ ...result, agent, scope: options.scope });
|
|
36821
|
+
}
|
|
36822
|
+
}
|
|
36823
|
+
console.log(JSON.stringify({
|
|
36824
|
+
detected,
|
|
36825
|
+
recommended: recommended.map((s) => s.name),
|
|
36826
|
+
installed: installResults2
|
|
36827
|
+
}, null, 2));
|
|
36828
|
+
return;
|
|
36829
|
+
}
|
|
36830
|
+
if (detected.length > 0) {
|
|
36831
|
+
console.log(chalk2.bold(`
|
|
36832
|
+
Detected project technologies:`));
|
|
36833
|
+
for (const tech of detected) {
|
|
36834
|
+
console.log(` ${chalk2.cyan(tech)}`);
|
|
36835
|
+
}
|
|
36836
|
+
} else {
|
|
36837
|
+
console.log(chalk2.dim(`
|
|
36838
|
+
No specific project dependencies detected`));
|
|
36839
|
+
}
|
|
36840
|
+
console.log(chalk2.bold(`
|
|
36841
|
+
Recommended skills (${recommended.length}):`));
|
|
36842
|
+
for (const skill of recommended) {
|
|
36843
|
+
console.log(` ${chalk2.cyan(skill.name)} - ${skill.description}`);
|
|
36844
|
+
}
|
|
36845
|
+
console.log(chalk2.bold(`
|
|
36846
|
+
Installing recommended skills for ${options.for} (${options.scope})...
|
|
36847
|
+
`));
|
|
36848
|
+
const installResults = [];
|
|
36849
|
+
for (const skill of recommended) {
|
|
36850
|
+
for (const agent of agents) {
|
|
36851
|
+
const result = installSkillForAgent(skill.name, {
|
|
36852
|
+
agent,
|
|
36853
|
+
scope: options.scope
|
|
36854
|
+
}, generateSkillMd);
|
|
36855
|
+
installResults.push({ ...result, agent });
|
|
36856
|
+
const label = `${skill.name} \u2192 ${agent} (${options.scope})`;
|
|
36857
|
+
if (result.success) {
|
|
36858
|
+
console.log(chalk2.green(`\u2713 ${label}`));
|
|
36859
|
+
} else {
|
|
36860
|
+
console.log(chalk2.red(`\u2717 ${label}: ${result.error}`));
|
|
36861
|
+
}
|
|
36862
|
+
}
|
|
36863
|
+
}
|
|
36864
|
+
if (installResults.some((r) => !r.success)) {
|
|
36865
|
+
process.exitCode = 1;
|
|
36866
|
+
}
|
|
36867
|
+
}
|
|
36633
36868
|
const installed = getInstalledSkills();
|
|
36634
|
-
if (installed.length === 0) {
|
|
36869
|
+
if (installed.length === 0 && !options.for) {
|
|
36635
36870
|
if (options.json) {
|
|
36636
36871
|
console.log(JSON.stringify({ skills: [], envVars: 0, gitignoreUpdated: false }));
|
|
36637
36872
|
} else {
|
|
@@ -36639,6 +36874,8 @@ program2.command("init").option("--json", "Output as JSON", false).description("
|
|
|
36639
36874
|
}
|
|
36640
36875
|
return;
|
|
36641
36876
|
}
|
|
36877
|
+
if (installed.length === 0)
|
|
36878
|
+
return;
|
|
36642
36879
|
const envMap = new Map;
|
|
36643
36880
|
for (const name of installed) {
|
|
36644
36881
|
const reqs = getSkillRequirements(name);
|
|
@@ -36712,25 +36949,27 @@ ${gitignoreEntry}
|
|
|
36712
36949
|
console.log(chalk2.dim(" .skills/ already in .gitignore"));
|
|
36713
36950
|
}
|
|
36714
36951
|
}
|
|
36715
|
-
if (options.
|
|
36716
|
-
|
|
36717
|
-
|
|
36718
|
-
|
|
36719
|
-
|
|
36720
|
-
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36952
|
+
if (!options.for) {
|
|
36953
|
+
if (options.json) {
|
|
36954
|
+
console.log(JSON.stringify({
|
|
36955
|
+
skills: installed,
|
|
36956
|
+
envVars: envVarCount,
|
|
36957
|
+
gitignoreUpdated
|
|
36958
|
+
}, null, 2));
|
|
36959
|
+
} else {
|
|
36960
|
+
if (envMap.size > 0) {
|
|
36961
|
+
console.log(chalk2.bold(`
|
|
36724
36962
|
Skill environment requirements:`));
|
|
36725
|
-
|
|
36726
|
-
|
|
36727
|
-
|
|
36728
|
-
|
|
36963
|
+
for (const name of installed) {
|
|
36964
|
+
const reqs = getSkillRequirements(name);
|
|
36965
|
+
if (reqs?.envVars.length) {
|
|
36966
|
+
console.log(` ${chalk2.cyan(name)}: ${reqs.envVars.join(", ")}`);
|
|
36967
|
+
}
|
|
36729
36968
|
}
|
|
36730
36969
|
}
|
|
36731
|
-
|
|
36732
|
-
console.log(chalk2.bold(`
|
|
36970
|
+
console.log(chalk2.bold(`
|
|
36733
36971
|
Initialized for ${installed.length} installed skill(s)`));
|
|
36972
|
+
}
|
|
36734
36973
|
}
|
|
36735
36974
|
});
|
|
36736
36975
|
program2.command("remove").alias("rm").argument("<skill>", "Skill to remove").option("--json", "Output as JSON", false).option("--for <agent>", "Remove from agent: claude, codex, gemini, or all").option("--scope <scope>", "Remove scope: global or project", "global").option("--dry-run", "Print what would happen without actually removing", false).description("Remove an installed skill").action((skill, options) => {
|
|
@@ -36872,6 +37111,25 @@ Categories:
|
|
|
36872
37111
|
console.log(` ${name} (${count})`);
|
|
36873
37112
|
}
|
|
36874
37113
|
});
|
|
37114
|
+
program2.command("tags").option("--json", "Output as JSON", false).description("List all unique tags with counts").action((options) => {
|
|
37115
|
+
const tagCounts = new Map;
|
|
37116
|
+
for (const skill of SKILLS) {
|
|
37117
|
+
for (const tag of skill.tags) {
|
|
37118
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
37119
|
+
}
|
|
37120
|
+
}
|
|
37121
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
37122
|
+
if (options.json) {
|
|
37123
|
+
console.log(JSON.stringify(sorted, null, 2));
|
|
37124
|
+
return;
|
|
37125
|
+
}
|
|
37126
|
+
console.log(chalk2.bold(`
|
|
37127
|
+
Tags:
|
|
37128
|
+
`));
|
|
37129
|
+
for (const { name, count } of sorted) {
|
|
37130
|
+
console.log(` ${chalk2.cyan(name)} (${count})`);
|
|
37131
|
+
}
|
|
37132
|
+
});
|
|
36875
37133
|
program2.command("mcp").option("--register <agent>", "Register MCP server with agent: claude, codex, gemini, or all").description("Start MCP server (stdio) or register with an agent").action(async (options) => {
|
|
36876
37134
|
if (options.register) {
|
|
36877
37135
|
const agents = options.register === "all" ? ["claude", "codex", "gemini"] : [options.register];
|
package/bin/mcp.js
CHANGED
|
@@ -28599,7 +28599,7 @@ class StdioServerTransport {
|
|
|
28599
28599
|
// package.json
|
|
28600
28600
|
var package_default = {
|
|
28601
28601
|
name: "@hasna/skills",
|
|
28602
|
-
version: "0.1.
|
|
28602
|
+
version: "0.1.6",
|
|
28603
28603
|
description: "Skills library for AI coding agents",
|
|
28604
28604
|
type: "module",
|
|
28605
28605
|
bin: {
|
|
@@ -30113,6 +30113,45 @@ var SKILLS = [
|
|
|
30113
30113
|
function getSkillsByCategory(category) {
|
|
30114
30114
|
return SKILLS.filter((s) => s.category === category);
|
|
30115
30115
|
}
|
|
30116
|
+
function editDistance(a, b) {
|
|
30117
|
+
if (a === b)
|
|
30118
|
+
return 0;
|
|
30119
|
+
if (a.length === 0)
|
|
30120
|
+
return b.length;
|
|
30121
|
+
if (b.length === 0)
|
|
30122
|
+
return a.length;
|
|
30123
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
30124
|
+
const curr = new Array(b.length + 1);
|
|
30125
|
+
for (let i = 1;i <= a.length; i++) {
|
|
30126
|
+
curr[0] = i;
|
|
30127
|
+
for (let j = 1;j <= b.length; j++) {
|
|
30128
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
30129
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
30130
|
+
}
|
|
30131
|
+
prev.splice(0, prev.length, ...curr);
|
|
30132
|
+
}
|
|
30133
|
+
return prev[b.length];
|
|
30134
|
+
}
|
|
30135
|
+
function fuzzyMatchScore(word, target) {
|
|
30136
|
+
if (target.includes(word))
|
|
30137
|
+
return 1;
|
|
30138
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
30139
|
+
for (const token of tokens) {
|
|
30140
|
+
if (token.startsWith(word))
|
|
30141
|
+
return 0.6;
|
|
30142
|
+
}
|
|
30143
|
+
if (word.length >= 3) {
|
|
30144
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
30145
|
+
for (const token of tokens) {
|
|
30146
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
30147
|
+
const dist = editDistance(word, token);
|
|
30148
|
+
if (dist <= maxDist)
|
|
30149
|
+
return 0.4;
|
|
30150
|
+
}
|
|
30151
|
+
}
|
|
30152
|
+
}
|
|
30153
|
+
return 0;
|
|
30154
|
+
}
|
|
30116
30155
|
function searchSkills(query) {
|
|
30117
30156
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
30118
30157
|
if (words.length === 0)
|
|
@@ -30123,30 +30162,28 @@ function searchSkills(query) {
|
|
|
30123
30162
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
30124
30163
|
const descriptionLower = skill.description.toLowerCase();
|
|
30125
30164
|
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
30165
|
+
const tagsCombined = tagsLower.join(" ");
|
|
30126
30166
|
let score = 0;
|
|
30127
30167
|
let allWordsMatch = true;
|
|
30128
30168
|
for (const word of words) {
|
|
30129
|
-
let
|
|
30130
|
-
|
|
30131
|
-
|
|
30132
|
-
|
|
30133
|
-
|
|
30134
|
-
if (
|
|
30135
|
-
|
|
30136
|
-
|
|
30137
|
-
|
|
30138
|
-
|
|
30139
|
-
|
|
30140
|
-
|
|
30141
|
-
|
|
30142
|
-
if (
|
|
30143
|
-
score += 2;
|
|
30144
|
-
wordMatched = true;
|
|
30145
|
-
}
|
|
30146
|
-
if (!wordMatched) {
|
|
30169
|
+
let wordScore = 0;
|
|
30170
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
30171
|
+
if (nameMatch > 0)
|
|
30172
|
+
wordScore += 10 * nameMatch;
|
|
30173
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
30174
|
+
if (displayMatch > 0)
|
|
30175
|
+
wordScore += 7 * displayMatch;
|
|
30176
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
30177
|
+
if (tagMatch > 0)
|
|
30178
|
+
wordScore += 5 * tagMatch;
|
|
30179
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
30180
|
+
if (descMatch > 0)
|
|
30181
|
+
wordScore += 2 * descMatch;
|
|
30182
|
+
if (wordScore === 0) {
|
|
30147
30183
|
allWordsMatch = false;
|
|
30148
30184
|
break;
|
|
30149
30185
|
}
|
|
30186
|
+
score += wordScore;
|
|
30150
30187
|
}
|
|
30151
30188
|
if (allWordsMatch && score > 0) {
|
|
30152
30189
|
scored.push({ skill, score });
|
|
@@ -30709,6 +30746,19 @@ server.registerTool("list_categories", {
|
|
|
30709
30746
|
}));
|
|
30710
30747
|
return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
|
|
30711
30748
|
});
|
|
30749
|
+
server.registerTool("list_tags", {
|
|
30750
|
+
title: "List Tags",
|
|
30751
|
+
description: "List all unique tags across all skills with their occurrence counts"
|
|
30752
|
+
}, async () => {
|
|
30753
|
+
const tagCounts = new Map;
|
|
30754
|
+
for (const skill of SKILLS) {
|
|
30755
|
+
for (const tag of skill.tags) {
|
|
30756
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
30757
|
+
}
|
|
30758
|
+
}
|
|
30759
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
30760
|
+
return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
|
|
30761
|
+
});
|
|
30712
30762
|
server.registerTool("get_requirements", {
|
|
30713
30763
|
title: "Get Requirements",
|
|
30714
30764
|
description: "Get environment variables, system dependencies, and npm dependencies for a skill",
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* Or use the interactive CLI:
|
|
8
8
|
* skills
|
|
9
9
|
*/
|
|
10
|
-
export { SKILLS, CATEGORIES, getSkill, getSkillsByCategory, searchSkills, type SkillMeta, type Category, } from "./lib/registry.js";
|
|
10
|
+
export { SKILLS, CATEGORIES, getSkill, getSkillsByCategory, searchSkills, getSkillsByTag, getAllTags, type SkillMeta, type Category, } from "./lib/registry.js";
|
|
11
11
|
export { installSkill, installSkills, installSkillForAgent, removeSkillForAgent, getInstalledSkills, removeSkill, skillExists, getSkillPath, getAgentSkillsDir, getAgentSkillPath, AGENT_TARGETS, type InstallResult, type InstallOptions, type AgentTarget, type AgentScope, type AgentInstallOptions, } from "./lib/installer.js";
|
|
12
12
|
export { getSkillDocs, getSkillBestDoc, getSkillRequirements, runSkill, generateEnvExample, generateSkillMd, type SkillDocs, type SkillRequirements, } from "./lib/skillinfo.js";
|
package/dist/index.js
CHANGED
|
@@ -1439,6 +1439,45 @@ var SKILLS = [
|
|
|
1439
1439
|
function getSkillsByCategory(category) {
|
|
1440
1440
|
return SKILLS.filter((s) => s.category === category);
|
|
1441
1441
|
}
|
|
1442
|
+
function editDistance(a, b) {
|
|
1443
|
+
if (a === b)
|
|
1444
|
+
return 0;
|
|
1445
|
+
if (a.length === 0)
|
|
1446
|
+
return b.length;
|
|
1447
|
+
if (b.length === 0)
|
|
1448
|
+
return a.length;
|
|
1449
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1450
|
+
const curr = new Array(b.length + 1);
|
|
1451
|
+
for (let i = 1;i <= a.length; i++) {
|
|
1452
|
+
curr[0] = i;
|
|
1453
|
+
for (let j = 1;j <= b.length; j++) {
|
|
1454
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1455
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
1456
|
+
}
|
|
1457
|
+
prev.splice(0, prev.length, ...curr);
|
|
1458
|
+
}
|
|
1459
|
+
return prev[b.length];
|
|
1460
|
+
}
|
|
1461
|
+
function fuzzyMatchScore(word, target) {
|
|
1462
|
+
if (target.includes(word))
|
|
1463
|
+
return 1;
|
|
1464
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
1465
|
+
for (const token of tokens) {
|
|
1466
|
+
if (token.startsWith(word))
|
|
1467
|
+
return 0.6;
|
|
1468
|
+
}
|
|
1469
|
+
if (word.length >= 3) {
|
|
1470
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
1471
|
+
for (const token of tokens) {
|
|
1472
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
1473
|
+
const dist = editDistance(word, token);
|
|
1474
|
+
if (dist <= maxDist)
|
|
1475
|
+
return 0.4;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return 0;
|
|
1480
|
+
}
|
|
1442
1481
|
function searchSkills(query) {
|
|
1443
1482
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1444
1483
|
if (words.length === 0)
|
|
@@ -1449,30 +1488,28 @@ function searchSkills(query) {
|
|
|
1449
1488
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
1450
1489
|
const descriptionLower = skill.description.toLowerCase();
|
|
1451
1490
|
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
1491
|
+
const tagsCombined = tagsLower.join(" ");
|
|
1452
1492
|
let score = 0;
|
|
1453
1493
|
let allWordsMatch = true;
|
|
1454
1494
|
for (const word of words) {
|
|
1455
|
-
let
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
if (
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
if (
|
|
1469
|
-
score += 2;
|
|
1470
|
-
wordMatched = true;
|
|
1471
|
-
}
|
|
1472
|
-
if (!wordMatched) {
|
|
1495
|
+
let wordScore = 0;
|
|
1496
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
1497
|
+
if (nameMatch > 0)
|
|
1498
|
+
wordScore += 10 * nameMatch;
|
|
1499
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
1500
|
+
if (displayMatch > 0)
|
|
1501
|
+
wordScore += 7 * displayMatch;
|
|
1502
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
1503
|
+
if (tagMatch > 0)
|
|
1504
|
+
wordScore += 5 * tagMatch;
|
|
1505
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
1506
|
+
if (descMatch > 0)
|
|
1507
|
+
wordScore += 2 * descMatch;
|
|
1508
|
+
if (wordScore === 0) {
|
|
1473
1509
|
allWordsMatch = false;
|
|
1474
1510
|
break;
|
|
1475
1511
|
}
|
|
1512
|
+
score += wordScore;
|
|
1476
1513
|
}
|
|
1477
1514
|
if (allWordsMatch && score > 0) {
|
|
1478
1515
|
scored.push({ skill, score });
|
|
@@ -1484,6 +1521,19 @@ function searchSkills(query) {
|
|
|
1484
1521
|
function getSkill(name) {
|
|
1485
1522
|
return SKILLS.find((s) => s.name === name);
|
|
1486
1523
|
}
|
|
1524
|
+
function getSkillsByTag(tag) {
|
|
1525
|
+
const needle = tag.toLowerCase();
|
|
1526
|
+
return SKILLS.filter((s) => s.tags.some((t) => t.toLowerCase().includes(needle)));
|
|
1527
|
+
}
|
|
1528
|
+
function getAllTags() {
|
|
1529
|
+
const tagSet = new Set;
|
|
1530
|
+
for (const skill of SKILLS) {
|
|
1531
|
+
for (const tag of skill.tags) {
|
|
1532
|
+
tagSet.add(tag.toLowerCase());
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return Array.from(tagSet).sort();
|
|
1536
|
+
}
|
|
1487
1537
|
// src/lib/installer.ts
|
|
1488
1538
|
import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, readFileSync } from "fs";
|
|
1489
1539
|
import { join, dirname } from "path";
|
|
@@ -1957,6 +2007,7 @@ export {
|
|
|
1957
2007
|
installSkills,
|
|
1958
2008
|
installSkillForAgent,
|
|
1959
2009
|
installSkill,
|
|
2010
|
+
getSkillsByTag,
|
|
1960
2011
|
getSkillsByCategory,
|
|
1961
2012
|
getSkillRequirements,
|
|
1962
2013
|
getSkillPath,
|
|
@@ -1964,6 +2015,7 @@ export {
|
|
|
1964
2015
|
getSkillBestDoc,
|
|
1965
2016
|
getSkill,
|
|
1966
2017
|
getInstalledSkills,
|
|
2018
|
+
getAllTags,
|
|
1967
2019
|
getAgentSkillsDir,
|
|
1968
2020
|
getAgentSkillPath,
|
|
1969
2021
|
generateSkillMd,
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -15,3 +15,11 @@ export declare const SKILLS: SkillMeta[];
|
|
|
15
15
|
export declare function getSkillsByCategory(category: Category): SkillMeta[];
|
|
16
16
|
export declare function searchSkills(query: string): SkillMeta[];
|
|
17
17
|
export declare function getSkill(name: string): SkillMeta | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Return all skills whose tags include a partial case-insensitive match for `tag`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSkillsByTag(tag: string): SkillMeta[];
|
|
22
|
+
/**
|
|
23
|
+
* Return all unique tags across every skill, sorted alphabetically.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getAllTags(): string[];
|
package/dist/lib/skillinfo.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill info - reads docs, requirements, and metadata from skill source
|
|
3
3
|
*/
|
|
4
|
+
import { type SkillMeta } from "./registry.js";
|
|
4
5
|
export interface SkillDocs {
|
|
5
6
|
skillMd: string | null;
|
|
6
7
|
readme: string | null;
|
|
@@ -33,6 +34,14 @@ export declare function runSkill(name: string, args: string[], options?: {
|
|
|
33
34
|
exitCode: number;
|
|
34
35
|
error?: string;
|
|
35
36
|
}>;
|
|
37
|
+
export interface DetectedProjectSkills {
|
|
38
|
+
detected: string[];
|
|
39
|
+
recommended: SkillMeta[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect project type from package.json and recommend relevant skills
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectProjectSkills(cwd?: string): DetectedProjectSkills;
|
|
36
45
|
/**
|
|
37
46
|
* Generate a .env.example from installed skills
|
|
38
47
|
*/
|