@hasna/skills 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/bin/index.js +307 -49
- package/bin/mcp.js +75 -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 +7 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npx @hasna/skills
|
|
|
10
10
|
|
|
11
11
|
- **202 ready-to-use skills** across development, business, content, data, media, design, and more
|
|
12
12
|
- **Interactive TUI** -- browse by category, search, and install from the terminal
|
|
13
|
-
- **MCP server** --
|
|
13
|
+
- **MCP server** -- 10 tools and 2 resources for AI agent integration
|
|
14
14
|
- **HTTP dashboard** -- React web UI to browse, search, install, and manage skills
|
|
15
15
|
- **Agent-aware installs** -- copies SKILL.md to `~/.claude/skills/`, `~/.codex/skills/`, or `~/.gemini/skills/`
|
|
16
16
|
- **Auto-generated index** -- `.skills/index.ts` is updated on every install for easy imports
|
|
@@ -52,6 +52,12 @@ skills run image --prompt "a sunset over mountains"
|
|
|
52
52
|
|
|
53
53
|
# Start the web dashboard
|
|
54
54
|
skills serve
|
|
55
|
+
|
|
56
|
+
# Smart init: detect project type and install skills for Claude
|
|
57
|
+
skills init --for claude
|
|
58
|
+
|
|
59
|
+
# Browse skills by tag
|
|
60
|
+
skills list --tags api,testing
|
|
55
61
|
```
|
|
56
62
|
|
|
57
63
|
## Categories
|
|
@@ -84,23 +90,24 @@ skills serve
|
|
|
84
90
|
| `skills install <names...>` | `skills add` | Install one or more skills to `.skills/` |
|
|
85
91
|
| `skills install <name> --for <agent>` | | Install SKILL.md for claude, codex, gemini, or all |
|
|
86
92
|
| `skills remove <name>` | `skills rm` | Remove an installed skill |
|
|
87
|
-
| `skills list` | `skills ls` | List all available skills |
|
|
93
|
+
| `skills list` | `skills ls` | List all available skills (supports `--tags` to filter by tags) |
|
|
88
94
|
| `skills list --category <cat>` | | List skills in a category |
|
|
89
95
|
| `skills list --installed` | | List installed skills |
|
|
90
|
-
| `skills search <query>` | | Search skills by name, description, or tags |
|
|
96
|
+
| `skills search <query>` | | Search skills by name, description, or tags (supports `--tags` to filter by tags) |
|
|
91
97
|
| `skills info <name>` | | Show skill metadata, requirements, and env vars |
|
|
92
98
|
| `skills docs <name>` | | Show skill documentation (SKILL.md/README.md/CLAUDE.md) |
|
|
93
99
|
| `skills requires <name>` | | Show env vars, system deps, and npm dependencies |
|
|
94
100
|
| `skills run <name> [args...]` | | Run a skill directly |
|
|
95
101
|
| `skills categories` | | List all categories with counts |
|
|
96
|
-
| `skills
|
|
102
|
+
| `skills tags` | | List all tags with skill counts |
|
|
103
|
+
| `skills init` | | Initialize project, detect deps, and optionally install for agents |
|
|
97
104
|
| `skills update [names...]` | | Update installed skills (reinstall with overwrite) |
|
|
98
105
|
| `skills serve` | | Start the HTTP dashboard (auto-assigns free port) |
|
|
99
106
|
| `skills mcp` | | Start the MCP server on stdio |
|
|
100
107
|
| `skills mcp --register <agent>` | | Register MCP server with claude, codex, gemini, or all |
|
|
101
108
|
| `skills self-update` | | Update `@hasna/skills` to the latest version |
|
|
102
109
|
|
|
103
|
-
All list/search/info commands support `--json` for machine-readable output.
|
|
110
|
+
All list/search/info commands support `--json` for machine-readable output. Search uses fuzzy matching -- typos and abbreviations are tolerated.
|
|
104
111
|
|
|
105
112
|
## MCP Server
|
|
106
113
|
|
|
@@ -133,7 +140,7 @@ Add to your MCP config:
|
|
|
133
140
|
}
|
|
134
141
|
```
|
|
135
142
|
|
|
136
|
-
### Tools (
|
|
143
|
+
### Tools (10)
|
|
137
144
|
|
|
138
145
|
| Tool | Description |
|
|
139
146
|
|------|-------------|
|
|
@@ -144,6 +151,7 @@ Add to your MCP config:
|
|
|
144
151
|
| `install_skill` | Install a skill (full source to `.skills/` or SKILL.md to agent dir) |
|
|
145
152
|
| `remove_skill` | Remove an installed skill |
|
|
146
153
|
| `list_categories` | List all categories with skill counts |
|
|
154
|
+
| `list_tags` | List all skill tags with counts |
|
|
147
155
|
| `get_requirements` | Get env vars, system deps, and npm dependencies |
|
|
148
156
|
| `run_skill` | Run a skill by name with optional arguments |
|
|
149
157
|
|
|
@@ -190,6 +198,7 @@ The dashboard server also exposes a REST API:
|
|
|
190
198
|
| `/api/skills/:name/docs` | GET | Raw documentation text |
|
|
191
199
|
| `/api/skills/:name/install` | POST | Install a skill |
|
|
192
200
|
| `/api/skills/:name/remove` | POST | Remove a skill |
|
|
201
|
+
| `/api/tags` | GET | All tags with skill counts |
|
|
193
202
|
| `/api/version` | GET | Current package version |
|
|
194
203
|
| `/api/self-update` | POST | Update to latest version |
|
|
195
204
|
|
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.7",
|
|
1882
1882
|
description: "Skills library for AI coding agents",
|
|
1883
1883
|
type: "module",
|
|
1884
1884
|
bin: {
|
|
@@ -1919,6 +1919,12 @@ var init_package = __esm(() => {
|
|
|
1919
1919
|
"typescript",
|
|
1920
1920
|
"bun",
|
|
1921
1921
|
"claude",
|
|
1922
|
+
"codex",
|
|
1923
|
+
"gemini",
|
|
1924
|
+
"mcp",
|
|
1925
|
+
"model-context-protocol",
|
|
1926
|
+
"open-source",
|
|
1927
|
+
"skill-library",
|
|
1922
1928
|
"automation"
|
|
1923
1929
|
],
|
|
1924
1930
|
author: "Hasna",
|
|
@@ -1957,6 +1963,45 @@ var init_package = __esm(() => {
|
|
|
1957
1963
|
function getSkillsByCategory(category) {
|
|
1958
1964
|
return SKILLS.filter((s) => s.category === category);
|
|
1959
1965
|
}
|
|
1966
|
+
function editDistance(a, b) {
|
|
1967
|
+
if (a === b)
|
|
1968
|
+
return 0;
|
|
1969
|
+
if (a.length === 0)
|
|
1970
|
+
return b.length;
|
|
1971
|
+
if (b.length === 0)
|
|
1972
|
+
return a.length;
|
|
1973
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1974
|
+
const curr = new Array(b.length + 1);
|
|
1975
|
+
for (let i = 1;i <= a.length; i++) {
|
|
1976
|
+
curr[0] = i;
|
|
1977
|
+
for (let j = 1;j <= b.length; j++) {
|
|
1978
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1979
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
1980
|
+
}
|
|
1981
|
+
prev.splice(0, prev.length, ...curr);
|
|
1982
|
+
}
|
|
1983
|
+
return prev[b.length];
|
|
1984
|
+
}
|
|
1985
|
+
function fuzzyMatchScore(word, target) {
|
|
1986
|
+
if (target.includes(word))
|
|
1987
|
+
return 1;
|
|
1988
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
1989
|
+
for (const token of tokens) {
|
|
1990
|
+
if (token.startsWith(word))
|
|
1991
|
+
return 0.6;
|
|
1992
|
+
}
|
|
1993
|
+
if (word.length >= 3) {
|
|
1994
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
1995
|
+
for (const token of tokens) {
|
|
1996
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
1997
|
+
const dist = editDistance(word, token);
|
|
1998
|
+
if (dist <= maxDist)
|
|
1999
|
+
return 0.4;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return 0;
|
|
2004
|
+
}
|
|
1960
2005
|
function searchSkills(query) {
|
|
1961
2006
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1962
2007
|
if (words.length === 0)
|
|
@@ -1967,30 +2012,28 @@ function searchSkills(query) {
|
|
|
1967
2012
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
1968
2013
|
const descriptionLower = skill.description.toLowerCase();
|
|
1969
2014
|
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
2015
|
+
const tagsCombined = tagsLower.join(" ");
|
|
1970
2016
|
let score = 0;
|
|
1971
2017
|
let allWordsMatch = true;
|
|
1972
2018
|
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) {
|
|
2019
|
+
let wordScore = 0;
|
|
2020
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
2021
|
+
if (nameMatch > 0)
|
|
2022
|
+
wordScore += 10 * nameMatch;
|
|
2023
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
2024
|
+
if (displayMatch > 0)
|
|
2025
|
+
wordScore += 7 * displayMatch;
|
|
2026
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
2027
|
+
if (tagMatch > 0)
|
|
2028
|
+
wordScore += 5 * tagMatch;
|
|
2029
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
2030
|
+
if (descMatch > 0)
|
|
2031
|
+
wordScore += 2 * descMatch;
|
|
2032
|
+
if (wordScore === 0) {
|
|
1991
2033
|
allWordsMatch = false;
|
|
1992
2034
|
break;
|
|
1993
2035
|
}
|
|
2036
|
+
score += wordScore;
|
|
1994
2037
|
}
|
|
1995
2038
|
if (allWordsMatch && score > 0) {
|
|
1996
2039
|
scored.push({ skill, score });
|
|
@@ -5395,6 +5438,93 @@ async function runSkill(name, args, options = {}) {
|
|
|
5395
5438
|
const exitCode = await proc.exited;
|
|
5396
5439
|
return { exitCode };
|
|
5397
5440
|
}
|
|
5441
|
+
function detectProjectSkills(cwd = process.cwd()) {
|
|
5442
|
+
const pkgPath = join2(cwd, "package.json");
|
|
5443
|
+
if (!existsSync2(pkgPath)) {
|
|
5444
|
+
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5445
|
+
const recommended2 = alwaysRecommend.map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5446
|
+
return { detected: [], recommended: recommended2 };
|
|
5447
|
+
}
|
|
5448
|
+
let pkg;
|
|
5449
|
+
try {
|
|
5450
|
+
pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5451
|
+
} catch {
|
|
5452
|
+
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
5453
|
+
const recommended2 = alwaysRecommend.map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5454
|
+
return { detected: [], recommended: recommended2 };
|
|
5455
|
+
}
|
|
5456
|
+
const allDeps = {
|
|
5457
|
+
...pkg.dependencies,
|
|
5458
|
+
...pkg.devDependencies
|
|
5459
|
+
};
|
|
5460
|
+
const depNames = Object.keys(allDeps);
|
|
5461
|
+
const detected = [];
|
|
5462
|
+
const recommendedNames = new Set;
|
|
5463
|
+
for (const name of ["implementation-plan", "write", "deepresearch"]) {
|
|
5464
|
+
recommendedNames.add(name);
|
|
5465
|
+
}
|
|
5466
|
+
const frontendDeps = ["next", "react", "vue", "svelte", "nuxt", "@nuxtjs/nuxt"];
|
|
5467
|
+
for (const dep of frontendDeps) {
|
|
5468
|
+
if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
|
|
5469
|
+
detected.push(dep);
|
|
5470
|
+
for (const name of ["image", "generate-favicon", "seo-brief-builder"]) {
|
|
5471
|
+
recommendedNames.add(name);
|
|
5472
|
+
}
|
|
5473
|
+
break;
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
const backendDeps = ["express", "fastify", "hono", "koa", "@hono/hono"];
|
|
5477
|
+
for (const dep of backendDeps) {
|
|
5478
|
+
if (depNames.some((d) => d === dep || d.startsWith(`${dep}/`))) {
|
|
5479
|
+
detected.push(dep);
|
|
5480
|
+
for (const name of ["api-test-suite", "apidocs"]) {
|
|
5481
|
+
recommendedNames.add(name);
|
|
5482
|
+
}
|
|
5483
|
+
break;
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
const aiDeps = ["@anthropic-ai/sdk", "openai", "@openai/openai", "anthropic"];
|
|
5487
|
+
for (const dep of aiDeps) {
|
|
5488
|
+
if (depNames.includes(dep)) {
|
|
5489
|
+
detected.push(dep);
|
|
5490
|
+
for (const name of ["deepresearch", "webcrawling"]) {
|
|
5491
|
+
recommendedNames.add(name);
|
|
5492
|
+
}
|
|
5493
|
+
break;
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
if (depNames.includes("stripe")) {
|
|
5497
|
+
detected.push("stripe");
|
|
5498
|
+
recommendedNames.add("invoice");
|
|
5499
|
+
}
|
|
5500
|
+
const emailDeps = ["nodemailer", "@sendgrid/mail", "@sendgrid/client"];
|
|
5501
|
+
for (const dep of emailDeps) {
|
|
5502
|
+
if (depNames.includes(dep)) {
|
|
5503
|
+
detected.push(dep);
|
|
5504
|
+
for (const name of ["gmail", "email-campaign"]) {
|
|
5505
|
+
recommendedNames.add(name);
|
|
5506
|
+
}
|
|
5507
|
+
break;
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
const testDeps = ["vitest", "jest", "mocha", "@jest/core"];
|
|
5511
|
+
for (const dep of testDeps) {
|
|
5512
|
+
if (depNames.includes(dep)) {
|
|
5513
|
+
detected.push(dep);
|
|
5514
|
+
recommendedNames.add("api-test-suite");
|
|
5515
|
+
break;
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
if (depNames.includes("typescript")) {
|
|
5519
|
+
detected.push("typescript");
|
|
5520
|
+
for (const name of ["scaffold-project", "deploy"]) {
|
|
5521
|
+
recommendedNames.add(name);
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
const uniqueDetected = Array.from(new Set(detected));
|
|
5525
|
+
const recommended = Array.from(recommendedNames).map((name) => SKILLS.find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
5526
|
+
return { detected: uniqueDetected, recommended };
|
|
5527
|
+
}
|
|
5398
5528
|
function generateSkillMd(name) {
|
|
5399
5529
|
const meta = getSkill(name);
|
|
5400
5530
|
if (!meta)
|
|
@@ -34796,6 +34926,19 @@ var init_mcp2 = __esm(() => {
|
|
|
34796
34926
|
}));
|
|
34797
34927
|
return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
|
|
34798
34928
|
});
|
|
34929
|
+
server.registerTool("list_tags", {
|
|
34930
|
+
title: "List Tags",
|
|
34931
|
+
description: "List all unique tags across all skills with their occurrence counts"
|
|
34932
|
+
}, async () => {
|
|
34933
|
+
const tagCounts = new Map;
|
|
34934
|
+
for (const skill of SKILLS) {
|
|
34935
|
+
for (const tag of skill.tags) {
|
|
34936
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
34937
|
+
}
|
|
34938
|
+
}
|
|
34939
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
34940
|
+
return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
|
|
34941
|
+
});
|
|
34799
34942
|
server.registerTool("get_requirements", {
|
|
34800
34943
|
title: "Get Requirements",
|
|
34801
34944
|
description: "Get environment variables, system dependencies, and npm dependencies for a skill",
|
|
@@ -34959,6 +35102,16 @@ function createFetchHandler(options) {
|
|
|
34959
35102
|
}));
|
|
34960
35103
|
return json2(counts);
|
|
34961
35104
|
}
|
|
35105
|
+
if (path === "/api/tags" && method === "GET") {
|
|
35106
|
+
const tagCounts = new Map;
|
|
35107
|
+
for (const skill of SKILLS) {
|
|
35108
|
+
for (const tag of skill.tags) {
|
|
35109
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
35110
|
+
}
|
|
35111
|
+
}
|
|
35112
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
35113
|
+
return json2(sorted);
|
|
35114
|
+
}
|
|
34962
35115
|
if (path === "/api/skills/search" && method === "GET") {
|
|
34963
35116
|
const query = url2.searchParams.get("q") || "";
|
|
34964
35117
|
if (!query.trim())
|
|
@@ -36316,15 +36469,7 @@ var program2 = new Command;
|
|
|
36316
36469
|
program2.name("skills").description("Install AI agent skills for your project").version(package_default.version).option("--verbose", "Enable verbose logging", false).enablePositionalOptions();
|
|
36317
36470
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive skill browser (TUI)").action(() => {
|
|
36318
36471
|
if (!isTTY) {
|
|
36319
|
-
console.log(
|
|
36320
|
-
`);
|
|
36321
|
-
console.log(" skills list List available skills");
|
|
36322
|
-
console.log(" skills search <q> Search skills");
|
|
36323
|
-
console.log(" skills install <n> Install a skill");
|
|
36324
|
-
console.log(" skills info <n> Show skill details");
|
|
36325
|
-
console.log(" skills serve Start web dashboard");
|
|
36326
|
-
console.log(` skills --help Show all commands
|
|
36327
|
-
`);
|
|
36472
|
+
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36328
36473
|
process.exit(0);
|
|
36329
36474
|
}
|
|
36330
36475
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
@@ -36416,7 +36561,7 @@ Skills installed to .skills/`));
|
|
|
36416
36561
|
process.exitCode = 1;
|
|
36417
36562
|
}
|
|
36418
36563
|
});
|
|
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) => {
|
|
36564
|
+
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
36565
|
if (options.installed) {
|
|
36421
36566
|
const installed = getInstalledSkills();
|
|
36422
36567
|
if (options.json) {
|
|
@@ -36435,6 +36580,7 @@ Installed skills (${installed.length}):
|
|
|
36435
36580
|
}
|
|
36436
36581
|
return;
|
|
36437
36582
|
}
|
|
36583
|
+
const tagFilter = options.tags ? options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean) : null;
|
|
36438
36584
|
if (options.category) {
|
|
36439
36585
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36440
36586
|
if (!category) {
|
|
@@ -36443,7 +36589,10 @@ Installed skills (${installed.length}):
|
|
|
36443
36589
|
process.exitCode = 1;
|
|
36444
36590
|
return;
|
|
36445
36591
|
}
|
|
36446
|
-
|
|
36592
|
+
let skills = getSkillsByCategory(category);
|
|
36593
|
+
if (tagFilter) {
|
|
36594
|
+
skills = skills.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36595
|
+
}
|
|
36447
36596
|
if (options.json) {
|
|
36448
36597
|
console.log(JSON.stringify(skills, null, 2));
|
|
36449
36598
|
return;
|
|
@@ -36456,6 +36605,20 @@ ${category} (${skills.length}):
|
|
|
36456
36605
|
}
|
|
36457
36606
|
return;
|
|
36458
36607
|
}
|
|
36608
|
+
if (tagFilter) {
|
|
36609
|
+
const skills = SKILLS.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36610
|
+
if (options.json) {
|
|
36611
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
36612
|
+
return;
|
|
36613
|
+
}
|
|
36614
|
+
console.log(chalk2.bold(`
|
|
36615
|
+
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
36616
|
+
`));
|
|
36617
|
+
for (const s of skills) {
|
|
36618
|
+
console.log(` ${chalk2.cyan(s.name)} ${chalk2.dim(`[${s.category}]`)} - ${s.description}`);
|
|
36619
|
+
}
|
|
36620
|
+
return;
|
|
36621
|
+
}
|
|
36459
36622
|
if (options.json) {
|
|
36460
36623
|
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36461
36624
|
return;
|
|
@@ -36472,7 +36635,7 @@ Available skills (${SKILLS.length}):
|
|
|
36472
36635
|
console.log();
|
|
36473
36636
|
}
|
|
36474
36637
|
});
|
|
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) => {
|
|
36638
|
+
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
36639
|
let results = searchSkills(query);
|
|
36477
36640
|
if (options.category) {
|
|
36478
36641
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
@@ -36484,6 +36647,10 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36484
36647
|
}
|
|
36485
36648
|
results = results.filter((s) => s.category === category);
|
|
36486
36649
|
}
|
|
36650
|
+
if (options.tags) {
|
|
36651
|
+
const tagFilter = options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
36652
|
+
results = results.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36653
|
+
}
|
|
36487
36654
|
if (options.json) {
|
|
36488
36655
|
console.log(JSON.stringify(results, null, 2));
|
|
36489
36656
|
return;
|
|
@@ -36628,10 +36795,76 @@ program2.command("run").argument("<skill>", "Skill name").argument("[args...]",
|
|
|
36628
36795
|
}
|
|
36629
36796
|
process.exitCode = result.exitCode;
|
|
36630
36797
|
});
|
|
36631
|
-
program2.command("init").option("--json", "Output as JSON", false).description("Initialize project for installed skills (.env.example, .gitignore)").action((options) => {
|
|
36798
|
+
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
36799
|
const cwd = process.cwd();
|
|
36800
|
+
if (options.for) {
|
|
36801
|
+
let agents;
|
|
36802
|
+
try {
|
|
36803
|
+
agents = resolveAgents(options.for);
|
|
36804
|
+
} catch (err) {
|
|
36805
|
+
console.error(chalk2.red(err.message));
|
|
36806
|
+
process.exitCode = 1;
|
|
36807
|
+
return;
|
|
36808
|
+
}
|
|
36809
|
+
const { detected, recommended } = detectProjectSkills(cwd);
|
|
36810
|
+
if (options.json) {
|
|
36811
|
+
const installResults2 = [];
|
|
36812
|
+
for (const skill of recommended) {
|
|
36813
|
+
for (const agent of agents) {
|
|
36814
|
+
const result = installSkillForAgent(skill.name, {
|
|
36815
|
+
agent,
|
|
36816
|
+
scope: options.scope
|
|
36817
|
+
}, generateSkillMd);
|
|
36818
|
+
installResults2.push({ ...result, agent, scope: options.scope });
|
|
36819
|
+
}
|
|
36820
|
+
}
|
|
36821
|
+
console.log(JSON.stringify({
|
|
36822
|
+
detected,
|
|
36823
|
+
recommended: recommended.map((s) => s.name),
|
|
36824
|
+
installed: installResults2
|
|
36825
|
+
}, null, 2));
|
|
36826
|
+
return;
|
|
36827
|
+
}
|
|
36828
|
+
if (detected.length > 0) {
|
|
36829
|
+
console.log(chalk2.bold(`
|
|
36830
|
+
Detected project technologies:`));
|
|
36831
|
+
for (const tech of detected) {
|
|
36832
|
+
console.log(` ${chalk2.cyan(tech)}`);
|
|
36833
|
+
}
|
|
36834
|
+
} else {
|
|
36835
|
+
console.log(chalk2.dim(`
|
|
36836
|
+
No specific project dependencies detected`));
|
|
36837
|
+
}
|
|
36838
|
+
console.log(chalk2.bold(`
|
|
36839
|
+
Recommended skills (${recommended.length}):`));
|
|
36840
|
+
for (const skill of recommended) {
|
|
36841
|
+
console.log(` ${chalk2.cyan(skill.name)} - ${skill.description}`);
|
|
36842
|
+
}
|
|
36843
|
+
console.log(chalk2.bold(`
|
|
36844
|
+
Installing recommended skills for ${options.for} (${options.scope})...
|
|
36845
|
+
`));
|
|
36846
|
+
const installResults = [];
|
|
36847
|
+
for (const skill of recommended) {
|
|
36848
|
+
for (const agent of agents) {
|
|
36849
|
+
const result = installSkillForAgent(skill.name, {
|
|
36850
|
+
agent,
|
|
36851
|
+
scope: options.scope
|
|
36852
|
+
}, generateSkillMd);
|
|
36853
|
+
installResults.push({ ...result, agent });
|
|
36854
|
+
const label = `${skill.name} \u2192 ${agent} (${options.scope})`;
|
|
36855
|
+
if (result.success) {
|
|
36856
|
+
console.log(chalk2.green(`\u2713 ${label}`));
|
|
36857
|
+
} else {
|
|
36858
|
+
console.log(chalk2.red(`\u2717 ${label}: ${result.error}`));
|
|
36859
|
+
}
|
|
36860
|
+
}
|
|
36861
|
+
}
|
|
36862
|
+
if (installResults.some((r) => !r.success)) {
|
|
36863
|
+
process.exitCode = 1;
|
|
36864
|
+
}
|
|
36865
|
+
}
|
|
36633
36866
|
const installed = getInstalledSkills();
|
|
36634
|
-
if (installed.length === 0) {
|
|
36867
|
+
if (installed.length === 0 && !options.for) {
|
|
36635
36868
|
if (options.json) {
|
|
36636
36869
|
console.log(JSON.stringify({ skills: [], envVars: 0, gitignoreUpdated: false }));
|
|
36637
36870
|
} else {
|
|
@@ -36639,6 +36872,8 @@ program2.command("init").option("--json", "Output as JSON", false).description("
|
|
|
36639
36872
|
}
|
|
36640
36873
|
return;
|
|
36641
36874
|
}
|
|
36875
|
+
if (installed.length === 0)
|
|
36876
|
+
return;
|
|
36642
36877
|
const envMap = new Map;
|
|
36643
36878
|
for (const name of installed) {
|
|
36644
36879
|
const reqs = getSkillRequirements(name);
|
|
@@ -36712,25 +36947,27 @@ ${gitignoreEntry}
|
|
|
36712
36947
|
console.log(chalk2.dim(" .skills/ already in .gitignore"));
|
|
36713
36948
|
}
|
|
36714
36949
|
}
|
|
36715
|
-
if (options.
|
|
36716
|
-
|
|
36717
|
-
|
|
36718
|
-
|
|
36719
|
-
|
|
36720
|
-
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36950
|
+
if (!options.for) {
|
|
36951
|
+
if (options.json) {
|
|
36952
|
+
console.log(JSON.stringify({
|
|
36953
|
+
skills: installed,
|
|
36954
|
+
envVars: envVarCount,
|
|
36955
|
+
gitignoreUpdated
|
|
36956
|
+
}, null, 2));
|
|
36957
|
+
} else {
|
|
36958
|
+
if (envMap.size > 0) {
|
|
36959
|
+
console.log(chalk2.bold(`
|
|
36724
36960
|
Skill environment requirements:`));
|
|
36725
|
-
|
|
36726
|
-
|
|
36727
|
-
|
|
36728
|
-
|
|
36961
|
+
for (const name of installed) {
|
|
36962
|
+
const reqs = getSkillRequirements(name);
|
|
36963
|
+
if (reqs?.envVars.length) {
|
|
36964
|
+
console.log(` ${chalk2.cyan(name)}: ${reqs.envVars.join(", ")}`);
|
|
36965
|
+
}
|
|
36729
36966
|
}
|
|
36730
36967
|
}
|
|
36731
|
-
|
|
36732
|
-
console.log(chalk2.bold(`
|
|
36968
|
+
console.log(chalk2.bold(`
|
|
36733
36969
|
Initialized for ${installed.length} installed skill(s)`));
|
|
36970
|
+
}
|
|
36734
36971
|
}
|
|
36735
36972
|
});
|
|
36736
36973
|
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 +37109,25 @@ Categories:
|
|
|
36872
37109
|
console.log(` ${name} (${count})`);
|
|
36873
37110
|
}
|
|
36874
37111
|
});
|
|
37112
|
+
program2.command("tags").option("--json", "Output as JSON", false).description("List all unique tags with counts").action((options) => {
|
|
37113
|
+
const tagCounts = new Map;
|
|
37114
|
+
for (const skill of SKILLS) {
|
|
37115
|
+
for (const tag of skill.tags) {
|
|
37116
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
37117
|
+
}
|
|
37118
|
+
}
|
|
37119
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
37120
|
+
if (options.json) {
|
|
37121
|
+
console.log(JSON.stringify(sorted, null, 2));
|
|
37122
|
+
return;
|
|
37123
|
+
}
|
|
37124
|
+
console.log(chalk2.bold(`
|
|
37125
|
+
Tags:
|
|
37126
|
+
`));
|
|
37127
|
+
for (const { name, count } of sorted) {
|
|
37128
|
+
console.log(` ${chalk2.cyan(name)} (${count})`);
|
|
37129
|
+
}
|
|
37130
|
+
});
|
|
36875
37131
|
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
37132
|
if (options.register) {
|
|
36877
37133
|
const agents = options.register === "all" ? ["claude", "codex", "gemini"] : [options.register];
|
|
@@ -36953,12 +37209,14 @@ program2.command("completion").argument("<shell>", "Shell type: bash, zsh, or fi
|
|
|
36953
37209
|
"remove",
|
|
36954
37210
|
"update",
|
|
36955
37211
|
"categories",
|
|
37212
|
+
"tags",
|
|
36956
37213
|
"mcp",
|
|
36957
37214
|
"serve",
|
|
36958
37215
|
"init",
|
|
36959
37216
|
"self-update",
|
|
36960
37217
|
"completion",
|
|
36961
|
-
"outdated"
|
|
37218
|
+
"outdated",
|
|
37219
|
+
"doctor"
|
|
36962
37220
|
];
|
|
36963
37221
|
const skillNames = SKILLS.map((s) => s.name);
|
|
36964
37222
|
const categoryNames = CATEGORIES.map((c) => c);
|
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.7",
|
|
28603
28603
|
description: "Skills library for AI coding agents",
|
|
28604
28604
|
type: "module",
|
|
28605
28605
|
bin: {
|
|
@@ -28640,6 +28640,12 @@ var package_default = {
|
|
|
28640
28640
|
"typescript",
|
|
28641
28641
|
"bun",
|
|
28642
28642
|
"claude",
|
|
28643
|
+
"codex",
|
|
28644
|
+
"gemini",
|
|
28645
|
+
"mcp",
|
|
28646
|
+
"model-context-protocol",
|
|
28647
|
+
"open-source",
|
|
28648
|
+
"skill-library",
|
|
28643
28649
|
"automation"
|
|
28644
28650
|
],
|
|
28645
28651
|
author: "Hasna",
|
|
@@ -30113,6 +30119,45 @@ var SKILLS = [
|
|
|
30113
30119
|
function getSkillsByCategory(category) {
|
|
30114
30120
|
return SKILLS.filter((s) => s.category === category);
|
|
30115
30121
|
}
|
|
30122
|
+
function editDistance(a, b) {
|
|
30123
|
+
if (a === b)
|
|
30124
|
+
return 0;
|
|
30125
|
+
if (a.length === 0)
|
|
30126
|
+
return b.length;
|
|
30127
|
+
if (b.length === 0)
|
|
30128
|
+
return a.length;
|
|
30129
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
30130
|
+
const curr = new Array(b.length + 1);
|
|
30131
|
+
for (let i = 1;i <= a.length; i++) {
|
|
30132
|
+
curr[0] = i;
|
|
30133
|
+
for (let j = 1;j <= b.length; j++) {
|
|
30134
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
30135
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
30136
|
+
}
|
|
30137
|
+
prev.splice(0, prev.length, ...curr);
|
|
30138
|
+
}
|
|
30139
|
+
return prev[b.length];
|
|
30140
|
+
}
|
|
30141
|
+
function fuzzyMatchScore(word, target) {
|
|
30142
|
+
if (target.includes(word))
|
|
30143
|
+
return 1;
|
|
30144
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
30145
|
+
for (const token of tokens) {
|
|
30146
|
+
if (token.startsWith(word))
|
|
30147
|
+
return 0.6;
|
|
30148
|
+
}
|
|
30149
|
+
if (word.length >= 3) {
|
|
30150
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
30151
|
+
for (const token of tokens) {
|
|
30152
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
30153
|
+
const dist = editDistance(word, token);
|
|
30154
|
+
if (dist <= maxDist)
|
|
30155
|
+
return 0.4;
|
|
30156
|
+
}
|
|
30157
|
+
}
|
|
30158
|
+
}
|
|
30159
|
+
return 0;
|
|
30160
|
+
}
|
|
30116
30161
|
function searchSkills(query) {
|
|
30117
30162
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
30118
30163
|
if (words.length === 0)
|
|
@@ -30123,30 +30168,28 @@ function searchSkills(query) {
|
|
|
30123
30168
|
const displayNameLower = skill.displayName.toLowerCase();
|
|
30124
30169
|
const descriptionLower = skill.description.toLowerCase();
|
|
30125
30170
|
const tagsLower = skill.tags.map((t) => t.toLowerCase());
|
|
30171
|
+
const tagsCombined = tagsLower.join(" ");
|
|
30126
30172
|
let score = 0;
|
|
30127
30173
|
let allWordsMatch = true;
|
|
30128
30174
|
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) {
|
|
30175
|
+
let wordScore = 0;
|
|
30176
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
30177
|
+
if (nameMatch > 0)
|
|
30178
|
+
wordScore += 10 * nameMatch;
|
|
30179
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
30180
|
+
if (displayMatch > 0)
|
|
30181
|
+
wordScore += 7 * displayMatch;
|
|
30182
|
+
const tagMatch = Math.max(...tagsLower.map((t) => fuzzyMatchScore(word, t)), fuzzyMatchScore(word, tagsCombined));
|
|
30183
|
+
if (tagMatch > 0)
|
|
30184
|
+
wordScore += 5 * tagMatch;
|
|
30185
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
30186
|
+
if (descMatch > 0)
|
|
30187
|
+
wordScore += 2 * descMatch;
|
|
30188
|
+
if (wordScore === 0) {
|
|
30147
30189
|
allWordsMatch = false;
|
|
30148
30190
|
break;
|
|
30149
30191
|
}
|
|
30192
|
+
score += wordScore;
|
|
30150
30193
|
}
|
|
30151
30194
|
if (allWordsMatch && score > 0) {
|
|
30152
30195
|
scored.push({ skill, score });
|
|
@@ -30709,6 +30752,19 @@ server.registerTool("list_categories", {
|
|
|
30709
30752
|
}));
|
|
30710
30753
|
return { content: [{ type: "text", text: JSON.stringify(cats, null, 2) }] };
|
|
30711
30754
|
});
|
|
30755
|
+
server.registerTool("list_tags", {
|
|
30756
|
+
title: "List Tags",
|
|
30757
|
+
description: "List all unique tags across all skills with their occurrence counts"
|
|
30758
|
+
}, async () => {
|
|
30759
|
+
const tagCounts = new Map;
|
|
30760
|
+
for (const skill of SKILLS) {
|
|
30761
|
+
for (const tag of skill.tags) {
|
|
30762
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
30763
|
+
}
|
|
30764
|
+
}
|
|
30765
|
+
const sorted = Array.from(tagCounts.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({ name, count }));
|
|
30766
|
+
return { content: [{ type: "text", text: JSON.stringify(sorted, null, 2) }] };
|
|
30767
|
+
});
|
|
30712
30768
|
server.registerTool("get_requirements", {
|
|
30713
30769
|
title: "Get Requirements",
|
|
30714
30770
|
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
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Skills library for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,12 @@
|
|
|
41
41
|
"typescript",
|
|
42
42
|
"bun",
|
|
43
43
|
"claude",
|
|
44
|
+
"codex",
|
|
45
|
+
"gemini",
|
|
46
|
+
"mcp",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"open-source",
|
|
49
|
+
"skill-library",
|
|
44
50
|
"automation"
|
|
45
51
|
],
|
|
46
52
|
"author": "Hasna",
|