@hasna/skills 0.1.6 → 0.1.8
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 +682 -50
- package/bin/mcp.js +146 -1
- 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.8",
|
|
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",
|
|
@@ -34776,6 +34782,9 @@ var init_stdio2 = __esm(() => {
|
|
|
34776
34782
|
|
|
34777
34783
|
// src/mcp/index.ts
|
|
34778
34784
|
var exports_mcp = {};
|
|
34785
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
34786
|
+
import { join as join3 } from "path";
|
|
34787
|
+
import { homedir as homedir2 } from "os";
|
|
34779
34788
|
async function main() {
|
|
34780
34789
|
const transport = new StdioServerTransport;
|
|
34781
34790
|
await server.connect(transport);
|
|
@@ -34877,6 +34886,52 @@ var init_mcp2 = __esm(() => {
|
|
|
34877
34886
|
isError: !result.success
|
|
34878
34887
|
};
|
|
34879
34888
|
});
|
|
34889
|
+
server.registerTool("install_category", {
|
|
34890
|
+
title: "Install Category",
|
|
34891
|
+
description: "Install all skills in a category. Optionally install for a specific agent (claude, codex, gemini, or all) with a given scope.",
|
|
34892
|
+
inputSchema: {
|
|
34893
|
+
category: exports_external.string().describe("Category name (case-insensitive, e.g. 'Event Management')"),
|
|
34894
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
34895
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
34896
|
+
}
|
|
34897
|
+
}, async ({ category, for: agentArg, scope }) => {
|
|
34898
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
34899
|
+
if (!matchedCategory) {
|
|
34900
|
+
return {
|
|
34901
|
+
content: [{ type: "text", text: `Unknown category: ${category}. Available: ${CATEGORIES.join(", ")}` }],
|
|
34902
|
+
isError: true
|
|
34903
|
+
};
|
|
34904
|
+
}
|
|
34905
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
34906
|
+
const names = categorySkills.map((s) => s.name);
|
|
34907
|
+
if (agentArg) {
|
|
34908
|
+
let agents;
|
|
34909
|
+
try {
|
|
34910
|
+
agents = resolveAgents(agentArg);
|
|
34911
|
+
} catch (err) {
|
|
34912
|
+
return {
|
|
34913
|
+
content: [{ type: "text", text: err.message }],
|
|
34914
|
+
isError: true
|
|
34915
|
+
};
|
|
34916
|
+
}
|
|
34917
|
+
const results2 = [];
|
|
34918
|
+
for (const name of names) {
|
|
34919
|
+
for (const a of agents) {
|
|
34920
|
+
const r = installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd);
|
|
34921
|
+
results2.push({ ...r, agent: a, scope: scope || "global" });
|
|
34922
|
+
}
|
|
34923
|
+
}
|
|
34924
|
+
return {
|
|
34925
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results: results2 }, null, 2) }],
|
|
34926
|
+
isError: results2.some((r) => !r.success)
|
|
34927
|
+
};
|
|
34928
|
+
}
|
|
34929
|
+
const results = names.map((name) => installSkill(name));
|
|
34930
|
+
return {
|
|
34931
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results }, null, 2) }],
|
|
34932
|
+
isError: results.some((r) => !r.success)
|
|
34933
|
+
};
|
|
34934
|
+
});
|
|
34880
34935
|
server.registerTool("remove_skill", {
|
|
34881
34936
|
title: "Remove Skill",
|
|
34882
34937
|
description: "Remove an installed skill. Without --for, removes from .skills/. With --for, removes from agent skill directory.",
|
|
@@ -34969,6 +35024,94 @@ var init_mcp2 = __esm(() => {
|
|
|
34969
35024
|
content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: name }, null, 2) }]
|
|
34970
35025
|
};
|
|
34971
35026
|
});
|
|
35027
|
+
server.registerTool("export_skills", {
|
|
35028
|
+
title: "Export Skills",
|
|
35029
|
+
description: "Export the list of currently installed skills as a JSON payload that can be imported elsewhere"
|
|
35030
|
+
}, async () => {
|
|
35031
|
+
const skills = getInstalledSkills();
|
|
35032
|
+
const payload = {
|
|
35033
|
+
version: 1,
|
|
35034
|
+
skills,
|
|
35035
|
+
timestamp: new Date().toISOString()
|
|
35036
|
+
};
|
|
35037
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
35038
|
+
});
|
|
35039
|
+
server.registerTool("import_skills", {
|
|
35040
|
+
title: "Import Skills",
|
|
35041
|
+
description: "Install a list of skills from an export payload. Supports agent-specific installs via the 'for' parameter.",
|
|
35042
|
+
inputSchema: {
|
|
35043
|
+
skills: exports_external.array(exports_external.string()).describe("List of skill names to install"),
|
|
35044
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
35045
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
35046
|
+
}
|
|
35047
|
+
}, async ({ skills: skillList, for: agentArg, scope }) => {
|
|
35048
|
+
if (!skillList || skillList.length === 0) {
|
|
35049
|
+
return { content: [{ type: "text", text: JSON.stringify({ imported: 0, results: [] }, null, 2) }] };
|
|
35050
|
+
}
|
|
35051
|
+
const results = [];
|
|
35052
|
+
if (agentArg) {
|
|
35053
|
+
let agents;
|
|
35054
|
+
try {
|
|
35055
|
+
agents = resolveAgents(agentArg);
|
|
35056
|
+
} catch (err) {
|
|
35057
|
+
return {
|
|
35058
|
+
content: [{ type: "text", text: err.message }],
|
|
35059
|
+
isError: true
|
|
35060
|
+
};
|
|
35061
|
+
}
|
|
35062
|
+
for (const name of skillList) {
|
|
35063
|
+
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
35064
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35065
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35066
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35067
|
+
}
|
|
35068
|
+
} else {
|
|
35069
|
+
for (const name of skillList) {
|
|
35070
|
+
const result = installSkill(name);
|
|
35071
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35072
|
+
}
|
|
35073
|
+
}
|
|
35074
|
+
const imported = results.filter((r) => r.success).length;
|
|
35075
|
+
const hasErrors = results.some((r) => !r.success);
|
|
35076
|
+
return {
|
|
35077
|
+
content: [{ type: "text", text: JSON.stringify({ imported, total: skillList.length, results }, null, 2) }],
|
|
35078
|
+
isError: hasErrors
|
|
35079
|
+
};
|
|
35080
|
+
});
|
|
35081
|
+
server.registerTool("whoami", {
|
|
35082
|
+
title: "Skills Whoami",
|
|
35083
|
+
description: "Show setup summary: package version, installed skills, agent configurations, skills directory location, and working directory"
|
|
35084
|
+
}, async () => {
|
|
35085
|
+
const version2 = package_default.version;
|
|
35086
|
+
const cwd = process.cwd();
|
|
35087
|
+
const installed = getInstalledSkills();
|
|
35088
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
35089
|
+
const agents = [];
|
|
35090
|
+
for (const agent of agentNames) {
|
|
35091
|
+
const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
|
|
35092
|
+
const exists = existsSync3(agentSkillsPath);
|
|
35093
|
+
let skillCount = 0;
|
|
35094
|
+
if (exists) {
|
|
35095
|
+
try {
|
|
35096
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => {
|
|
35097
|
+
const full = join3(agentSkillsPath, f);
|
|
35098
|
+
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
35099
|
+
}).length;
|
|
35100
|
+
} catch {}
|
|
35101
|
+
}
|
|
35102
|
+
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
35103
|
+
}
|
|
35104
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
35105
|
+
const result = {
|
|
35106
|
+
version: version2,
|
|
35107
|
+
installedCount: installed.length,
|
|
35108
|
+
installed,
|
|
35109
|
+
agents,
|
|
35110
|
+
skillsDir,
|
|
35111
|
+
cwd
|
|
35112
|
+
};
|
|
35113
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35114
|
+
});
|
|
34972
35115
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
34973
35116
|
description: "Full list of all available skills as JSON"
|
|
34974
35117
|
}, async () => ({
|
|
@@ -35004,15 +35147,15 @@ __export(exports_serve, {
|
|
|
35004
35147
|
startServer: () => startServer,
|
|
35005
35148
|
createFetchHandler: () => createFetchHandler
|
|
35006
35149
|
});
|
|
35007
|
-
import { existsSync as
|
|
35008
|
-
import { join as
|
|
35150
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
35151
|
+
import { join as join4, dirname as dirname2, extname } from "path";
|
|
35009
35152
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35010
35153
|
function getPackageJson() {
|
|
35011
35154
|
try {
|
|
35012
35155
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35013
35156
|
for (const rel of ["../..", ".."]) {
|
|
35014
|
-
const pkgPath =
|
|
35015
|
-
if (
|
|
35157
|
+
const pkgPath = join4(scriptDir, rel, "package.json");
|
|
35158
|
+
if (existsSync4(pkgPath)) {
|
|
35016
35159
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
35017
35160
|
return { version: pkg.version || "unknown", name: pkg.name || "skills" };
|
|
35018
35161
|
}
|
|
@@ -35024,20 +35167,20 @@ function resolveDashboardDir() {
|
|
|
35024
35167
|
const candidates = [];
|
|
35025
35168
|
try {
|
|
35026
35169
|
const scriptDir = dirname2(fileURLToPath2(import.meta.url));
|
|
35027
|
-
candidates.push(
|
|
35028
|
-
candidates.push(
|
|
35170
|
+
candidates.push(join4(scriptDir, "..", "dashboard", "dist"));
|
|
35171
|
+
candidates.push(join4(scriptDir, "..", "..", "dashboard", "dist"));
|
|
35029
35172
|
} catch {}
|
|
35030
35173
|
if (process.argv[1]) {
|
|
35031
35174
|
const mainDir = dirname2(process.argv[1]);
|
|
35032
|
-
candidates.push(
|
|
35033
|
-
candidates.push(
|
|
35175
|
+
candidates.push(join4(mainDir, "..", "dashboard", "dist"));
|
|
35176
|
+
candidates.push(join4(mainDir, "..", "..", "dashboard", "dist"));
|
|
35034
35177
|
}
|
|
35035
|
-
candidates.push(
|
|
35178
|
+
candidates.push(join4(process.cwd(), "dashboard", "dist"));
|
|
35036
35179
|
for (const candidate of candidates) {
|
|
35037
|
-
if (
|
|
35180
|
+
if (existsSync4(candidate))
|
|
35038
35181
|
return candidate;
|
|
35039
35182
|
}
|
|
35040
|
-
return
|
|
35183
|
+
return join4(process.cwd(), "dashboard", "dist");
|
|
35041
35184
|
}
|
|
35042
35185
|
function json2(data, status = 200) {
|
|
35043
35186
|
return new Response(JSON.stringify(data), {
|
|
@@ -35071,7 +35214,7 @@ function getAllSkillsWithStatus() {
|
|
|
35071
35214
|
});
|
|
35072
35215
|
}
|
|
35073
35216
|
function serveStaticFile(filePath) {
|
|
35074
|
-
if (!
|
|
35217
|
+
if (!existsSync4(filePath))
|
|
35075
35218
|
return null;
|
|
35076
35219
|
const ext = extname(filePath);
|
|
35077
35220
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -35081,7 +35224,7 @@ function serveStaticFile(filePath) {
|
|
|
35081
35224
|
}
|
|
35082
35225
|
function createFetchHandler(options) {
|
|
35083
35226
|
const dashboardDir = options?.dashboardDir ?? resolveDashboardDir();
|
|
35084
|
-
const dashboardExists = options?.dashboardExists ??
|
|
35227
|
+
const dashboardExists = options?.dashboardExists ?? existsSync4(dashboardDir);
|
|
35085
35228
|
return async function fetchHandler(req) {
|
|
35086
35229
|
const url2 = new URL(req.url);
|
|
35087
35230
|
const path = url2.pathname;
|
|
@@ -35195,6 +35338,43 @@ function createFetchHandler(options) {
|
|
|
35195
35338
|
return json2(result, result.success ? 200 : 400);
|
|
35196
35339
|
}
|
|
35197
35340
|
}
|
|
35341
|
+
if (path === "/api/skills/install-category" && method === "POST") {
|
|
35342
|
+
let body = {};
|
|
35343
|
+
try {
|
|
35344
|
+
const text = await req.text();
|
|
35345
|
+
if (text)
|
|
35346
|
+
body = JSON.parse(text);
|
|
35347
|
+
} catch {}
|
|
35348
|
+
if (!body.category) {
|
|
35349
|
+
return json2({ error: "Missing required field: category" }, 400);
|
|
35350
|
+
}
|
|
35351
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === body.category.toLowerCase());
|
|
35352
|
+
if (!matchedCategory) {
|
|
35353
|
+
return json2({ error: `Unknown category: ${body.category}. Available: ${CATEGORIES.join(", ")}` }, 400);
|
|
35354
|
+
}
|
|
35355
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
35356
|
+
const names = categorySkills.map((s) => s.name);
|
|
35357
|
+
if (body.for) {
|
|
35358
|
+
try {
|
|
35359
|
+
const agents = resolveAgents(body.for);
|
|
35360
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35361
|
+
const results = [];
|
|
35362
|
+
for (const name of names) {
|
|
35363
|
+
for (const agent of agents) {
|
|
35364
|
+
results.push(installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
const allSuccess = results.every((r) => r.success);
|
|
35368
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35369
|
+
} catch (e) {
|
|
35370
|
+
return json2({ success: false, error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35371
|
+
}
|
|
35372
|
+
} else {
|
|
35373
|
+
const results = names.map((name) => installSkill(name));
|
|
35374
|
+
const allSuccess = results.every((r) => r.success);
|
|
35375
|
+
return json2({ category: matchedCategory, count: names.length, success: allSuccess, results }, allSuccess ? 200 : 207);
|
|
35376
|
+
}
|
|
35377
|
+
}
|
|
35198
35378
|
const removeMatch = path.match(/^\/api\/skills\/([^/]+)\/remove$/);
|
|
35199
35379
|
if (removeMatch && method === "POST") {
|
|
35200
35380
|
const name = removeMatch[1];
|
|
@@ -35207,6 +35387,50 @@ function createFetchHandler(options) {
|
|
|
35207
35387
|
const pkg = getPackageJson();
|
|
35208
35388
|
return json2({ version: pkg.version, name: pkg.name });
|
|
35209
35389
|
}
|
|
35390
|
+
if (path === "/api/export" && method === "GET") {
|
|
35391
|
+
const skills = getInstalledSkills();
|
|
35392
|
+
return json2({
|
|
35393
|
+
version: 1,
|
|
35394
|
+
skills,
|
|
35395
|
+
timestamp: new Date().toISOString()
|
|
35396
|
+
});
|
|
35397
|
+
}
|
|
35398
|
+
if (path === "/api/import" && method === "POST") {
|
|
35399
|
+
let body = {};
|
|
35400
|
+
try {
|
|
35401
|
+
const text = await req.text();
|
|
35402
|
+
if (text)
|
|
35403
|
+
body = JSON.parse(text);
|
|
35404
|
+
} catch {
|
|
35405
|
+
return json2({ error: "Invalid JSON body" }, 400);
|
|
35406
|
+
}
|
|
35407
|
+
if (!Array.isArray(body.skills)) {
|
|
35408
|
+
return json2({ error: 'Body must include "skills" array' }, 400);
|
|
35409
|
+
}
|
|
35410
|
+
const skillList = body.skills;
|
|
35411
|
+
const results = [];
|
|
35412
|
+
if (body.for) {
|
|
35413
|
+
try {
|
|
35414
|
+
const agents = resolveAgents(body.for);
|
|
35415
|
+
const scope = body.scope === "project" ? "project" : "global";
|
|
35416
|
+
for (const name of skillList) {
|
|
35417
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope }, generateSkillMd));
|
|
35418
|
+
const success2 = agentResults.every((r) => r.success);
|
|
35419
|
+
const errors4 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
35420
|
+
results.push({ skill: name, success: success2, ...errors4.length > 0 ? { error: errors4.join("; ") } : {} });
|
|
35421
|
+
}
|
|
35422
|
+
} catch (e) {
|
|
35423
|
+
return json2({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
|
|
35424
|
+
}
|
|
35425
|
+
} else {
|
|
35426
|
+
for (const name of skillList) {
|
|
35427
|
+
const result = installSkill(name);
|
|
35428
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
35429
|
+
}
|
|
35430
|
+
}
|
|
35431
|
+
const imported = results.filter((r) => r.success).length;
|
|
35432
|
+
return json2({ imported, total: skillList.length, results }, imported === skillList.length ? 200 : 207);
|
|
35433
|
+
}
|
|
35210
35434
|
if (path === "/api/self-update" && method === "POST") {
|
|
35211
35435
|
try {
|
|
35212
35436
|
const pkg = getPackageJson();
|
|
@@ -35236,12 +35460,12 @@ function createFetchHandler(options) {
|
|
|
35236
35460
|
}
|
|
35237
35461
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
35238
35462
|
if (path !== "/") {
|
|
35239
|
-
const filePath =
|
|
35463
|
+
const filePath = join4(dashboardDir, path);
|
|
35240
35464
|
const res2 = serveStaticFile(filePath);
|
|
35241
35465
|
if (res2)
|
|
35242
35466
|
return res2;
|
|
35243
35467
|
}
|
|
35244
|
-
const indexPath =
|
|
35468
|
+
const indexPath = join4(dashboardDir, "index.html");
|
|
35245
35469
|
const res = serveStaticFile(indexPath);
|
|
35246
35470
|
if (res)
|
|
35247
35471
|
return res;
|
|
@@ -35252,7 +35476,7 @@ function createFetchHandler(options) {
|
|
|
35252
35476
|
async function startServer(port = 0, options) {
|
|
35253
35477
|
const shouldOpen = options?.open ?? true;
|
|
35254
35478
|
const dashboardDir = resolveDashboardDir();
|
|
35255
|
-
const dashboardExists =
|
|
35479
|
+
const dashboardExists = existsSync4(dashboardDir);
|
|
35256
35480
|
if (!dashboardExists) {
|
|
35257
35481
|
console.error(`
|
|
35258
35482
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -35332,8 +35556,8 @@ var {
|
|
|
35332
35556
|
// src/cli/index.tsx
|
|
35333
35557
|
init_package();
|
|
35334
35558
|
import chalk2 from "chalk";
|
|
35335
|
-
import { existsSync as
|
|
35336
|
-
import { join as
|
|
35559
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync2, appendFileSync, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
35560
|
+
import { join as join5 } from "path";
|
|
35337
35561
|
|
|
35338
35562
|
// src/cli/components/App.tsx
|
|
35339
35563
|
import { useState as useState7 } from "react";
|
|
@@ -36463,20 +36687,33 @@ var program2 = new Command;
|
|
|
36463
36687
|
program2.name("skills").description("Install AI agent skills for your project").version(package_default.version).option("--verbose", "Enable verbose logging", false).enablePositionalOptions();
|
|
36464
36688
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive skill browser (TUI)").action(() => {
|
|
36465
36689
|
if (!isTTY) {
|
|
36466
|
-
console.log(
|
|
36467
|
-
`);
|
|
36468
|
-
console.log(" skills list List available skills");
|
|
36469
|
-
console.log(" skills search <q> Search skills");
|
|
36470
|
-
console.log(" skills install <n> Install a skill");
|
|
36471
|
-
console.log(" skills info <n> Show skill details");
|
|
36472
|
-
console.log(" skills serve Start web dashboard");
|
|
36473
|
-
console.log(` skills --help Show all commands
|
|
36474
|
-
`);
|
|
36690
|
+
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36475
36691
|
process.exit(0);
|
|
36476
36692
|
}
|
|
36477
36693
|
render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
|
|
36478
36694
|
});
|
|
36479
|
-
program2.command("install").alias("add").argument("
|
|
36695
|
+
program2.command("install").alias("add").argument("[skills...]", "Skills to install").option("-o, --overwrite", "Overwrite existing skills", false).option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Print what would happen without actually installing", false).option("--category <category>", "Install all skills in a category (case-insensitive)").description("Install one or more skills").action((skills, options) => {
|
|
36696
|
+
if (skills.length === 0 && !options.category) {
|
|
36697
|
+
console.error("error: missing required argument 'skills' or --category option");
|
|
36698
|
+
process.exitCode = 1;
|
|
36699
|
+
return;
|
|
36700
|
+
}
|
|
36701
|
+
if (options.category) {
|
|
36702
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
36703
|
+
if (!matchedCategory) {
|
|
36704
|
+
console.error(`Unknown category: ${options.category}`);
|
|
36705
|
+
console.error(`Available: ${CATEGORIES.join(", ")}`);
|
|
36706
|
+
process.exitCode = 1;
|
|
36707
|
+
return;
|
|
36708
|
+
}
|
|
36709
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
36710
|
+
skills = categorySkills.map((s) => s.name);
|
|
36711
|
+
if (!options.json) {
|
|
36712
|
+
console.log(chalk2.bold(`
|
|
36713
|
+
Installing ${skills.length} skills from "${matchedCategory}"...
|
|
36714
|
+
`));
|
|
36715
|
+
}
|
|
36716
|
+
}
|
|
36480
36717
|
const results = [];
|
|
36481
36718
|
if (options.for) {
|
|
36482
36719
|
let agents;
|
|
@@ -36563,7 +36800,8 @@ Skills installed to .skills/`));
|
|
|
36563
36800
|
process.exitCode = 1;
|
|
36564
36801
|
}
|
|
36565
36802
|
});
|
|
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) => {
|
|
36803
|
+
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).option("--brief", "One line per skill: name \u2014 description [category]", false).description("List available or installed skills").action((options) => {
|
|
36804
|
+
const brief = options.brief && !options.json;
|
|
36567
36805
|
if (options.installed) {
|
|
36568
36806
|
const installed = getInstalledSkills();
|
|
36569
36807
|
if (options.json) {
|
|
@@ -36574,6 +36812,12 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
36574
36812
|
console.log(chalk2.dim("No skills installed"));
|
|
36575
36813
|
return;
|
|
36576
36814
|
}
|
|
36815
|
+
if (brief) {
|
|
36816
|
+
for (const name of installed) {
|
|
36817
|
+
console.log(name);
|
|
36818
|
+
}
|
|
36819
|
+
return;
|
|
36820
|
+
}
|
|
36577
36821
|
console.log(chalk2.bold(`
|
|
36578
36822
|
Installed skills (${installed.length}):
|
|
36579
36823
|
`));
|
|
@@ -36599,6 +36843,12 @@ Installed skills (${installed.length}):
|
|
|
36599
36843
|
console.log(JSON.stringify(skills, null, 2));
|
|
36600
36844
|
return;
|
|
36601
36845
|
}
|
|
36846
|
+
if (brief) {
|
|
36847
|
+
for (const s of skills) {
|
|
36848
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36849
|
+
}
|
|
36850
|
+
return;
|
|
36851
|
+
}
|
|
36602
36852
|
console.log(chalk2.bold(`
|
|
36603
36853
|
${category} (${skills.length}):
|
|
36604
36854
|
`));
|
|
@@ -36613,6 +36863,12 @@ ${category} (${skills.length}):
|
|
|
36613
36863
|
console.log(JSON.stringify(skills, null, 2));
|
|
36614
36864
|
return;
|
|
36615
36865
|
}
|
|
36866
|
+
if (brief) {
|
|
36867
|
+
for (const s of skills) {
|
|
36868
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36869
|
+
}
|
|
36870
|
+
return;
|
|
36871
|
+
}
|
|
36616
36872
|
console.log(chalk2.bold(`
|
|
36617
36873
|
Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
36618
36874
|
`));
|
|
@@ -36625,6 +36881,16 @@ Skills matching tags [${tagFilter.join(", ")}] (${skills.length}):
|
|
|
36625
36881
|
console.log(JSON.stringify(SKILLS, null, 2));
|
|
36626
36882
|
return;
|
|
36627
36883
|
}
|
|
36884
|
+
if (brief) {
|
|
36885
|
+
const sorted = [...SKILLS].sort((a, b) => {
|
|
36886
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
36887
|
+
return catCmp !== 0 ? catCmp : a.name.localeCompare(b.name);
|
|
36888
|
+
});
|
|
36889
|
+
for (const s of sorted) {
|
|
36890
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36891
|
+
}
|
|
36892
|
+
return;
|
|
36893
|
+
}
|
|
36628
36894
|
console.log(chalk2.bold(`
|
|
36629
36895
|
Available skills (${SKILLS.length}):
|
|
36630
36896
|
`));
|
|
@@ -36637,7 +36903,7 @@ Available skills (${SKILLS.length}):
|
|
|
36637
36903
|
console.log();
|
|
36638
36904
|
}
|
|
36639
36905
|
});
|
|
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) => {
|
|
36906
|
+
program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("--brief", "One line per skill: name \u2014 description [category]", 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) => {
|
|
36641
36907
|
let results = searchSkills(query);
|
|
36642
36908
|
if (options.category) {
|
|
36643
36909
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
@@ -36653,6 +36919,7 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36653
36919
|
const tagFilter = options.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
36654
36920
|
results = results.filter((s) => s.tags.some((tag) => tagFilter.includes(tag.toLowerCase())));
|
|
36655
36921
|
}
|
|
36922
|
+
const brief = options.brief && !options.json;
|
|
36656
36923
|
if (options.json) {
|
|
36657
36924
|
console.log(JSON.stringify(results, null, 2));
|
|
36658
36925
|
return;
|
|
@@ -36661,6 +36928,12 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
|
|
|
36661
36928
|
console.log(chalk2.dim(`No skills found for "${query}"`));
|
|
36662
36929
|
return;
|
|
36663
36930
|
}
|
|
36931
|
+
if (brief) {
|
|
36932
|
+
for (const s of results) {
|
|
36933
|
+
console.log(`${s.name} \u2014 ${s.description} [${s.category}]`);
|
|
36934
|
+
}
|
|
36935
|
+
return;
|
|
36936
|
+
}
|
|
36664
36937
|
console.log(chalk2.bold(`
|
|
36665
36938
|
Found ${results.length} skill(s):
|
|
36666
36939
|
`));
|
|
@@ -36669,7 +36942,7 @@ Found ${results.length} skill(s):
|
|
|
36669
36942
|
console.log(` ${s.description}`);
|
|
36670
36943
|
}
|
|
36671
36944
|
});
|
|
36672
|
-
program2.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).description("Show details about a specific skill").action((name, options) => {
|
|
36945
|
+
program2.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).description("Show details about a specific skill").action((name, options) => {
|
|
36673
36946
|
const skill = getSkill(name);
|
|
36674
36947
|
if (!skill) {
|
|
36675
36948
|
console.error(`Skill '${name}' not found`);
|
|
@@ -36681,6 +36954,10 @@ program2.command("info").argument("<skill>", "Skill name").option("--json", "Out
|
|
|
36681
36954
|
console.log(JSON.stringify({ ...skill, ...reqs }, null, 2));
|
|
36682
36955
|
return;
|
|
36683
36956
|
}
|
|
36957
|
+
if (options.brief) {
|
|
36958
|
+
console.log(`${skill.name} \u2014 ${skill.description} [${skill.category}] (tags: ${skill.tags.join(", ")})`);
|
|
36959
|
+
return;
|
|
36960
|
+
}
|
|
36684
36961
|
console.log(`
|
|
36685
36962
|
${chalk2.bold(skill.displayName)}`);
|
|
36686
36963
|
console.log(`${skill.description}`);
|
|
@@ -36911,7 +37188,7 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36911
37188
|
const envContent = lines.join(`
|
|
36912
37189
|
`) + `
|
|
36913
37190
|
`;
|
|
36914
|
-
const envPath =
|
|
37191
|
+
const envPath = join5(cwd, ".env.example");
|
|
36915
37192
|
writeFileSync2(envPath, envContent);
|
|
36916
37193
|
envVarCount = envMap.size;
|
|
36917
37194
|
if (!options.json) {
|
|
@@ -36922,10 +37199,10 @@ Installing recommended skills for ${options.for} (${options.scope})...
|
|
|
36922
37199
|
console.log(chalk2.dim(" No environment variables detected across installed skills"));
|
|
36923
37200
|
}
|
|
36924
37201
|
}
|
|
36925
|
-
const gitignorePath =
|
|
37202
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
36926
37203
|
const gitignoreEntry = ".skills/";
|
|
36927
37204
|
let gitignoreContent = "";
|
|
36928
|
-
if (
|
|
37205
|
+
if (existsSync5(gitignorePath)) {
|
|
36929
37206
|
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
36930
37207
|
}
|
|
36931
37208
|
let gitignoreUpdated = false;
|
|
@@ -37035,12 +37312,12 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37035
37312
|
}
|
|
37036
37313
|
function collectFiles(dir, base = "") {
|
|
37037
37314
|
const files = new Set;
|
|
37038
|
-
if (!
|
|
37315
|
+
if (!existsSync5(dir))
|
|
37039
37316
|
return files;
|
|
37040
|
-
for (const entry of
|
|
37041
|
-
const full =
|
|
37317
|
+
for (const entry of readdirSync4(dir)) {
|
|
37318
|
+
const full = join5(dir, entry);
|
|
37042
37319
|
const rel = base ? `${base}/${entry}` : entry;
|
|
37043
|
-
if (
|
|
37320
|
+
if (statSync3(full).isDirectory()) {
|
|
37044
37321
|
for (const f of collectFiles(full, rel))
|
|
37045
37322
|
files.add(f);
|
|
37046
37323
|
} else {
|
|
@@ -37052,7 +37329,7 @@ program2.command("update").argument("[skills...]", "Skills to update (default: a
|
|
|
37052
37329
|
const updateResults = [];
|
|
37053
37330
|
for (const name of toUpdate) {
|
|
37054
37331
|
const skillName = normalizeSkillName(name);
|
|
37055
|
-
const destPath =
|
|
37332
|
+
const destPath = join5(process.cwd(), ".skills", skillName);
|
|
37056
37333
|
const beforeFiles = collectFiles(destPath);
|
|
37057
37334
|
const result = installSkill(name, { overwrite: true });
|
|
37058
37335
|
const afterFiles = collectFiles(destPath);
|
|
@@ -37133,7 +37410,7 @@ Tags:
|
|
|
37133
37410
|
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) => {
|
|
37134
37411
|
if (options.register) {
|
|
37135
37412
|
const agents = options.register === "all" ? ["claude", "codex", "gemini"] : [options.register];
|
|
37136
|
-
const binPath =
|
|
37413
|
+
const binPath = join5(import.meta.dir, "..", "mcp", "index.ts");
|
|
37137
37414
|
for (const agent of agents) {
|
|
37138
37415
|
if (agent === "claude") {
|
|
37139
37416
|
try {
|
|
@@ -37147,8 +37424,8 @@ program2.command("mcp").option("--register <agent>", "Register MCP server with a
|
|
|
37147
37424
|
console.log(chalk2.yellow(`Manual registration: claude mcp add skills -- bun run ${binPath}`));
|
|
37148
37425
|
}
|
|
37149
37426
|
} else if (agent === "codex") {
|
|
37150
|
-
const { homedir:
|
|
37151
|
-
const configPath =
|
|
37427
|
+
const { homedir: homedir3 } = await import("os");
|
|
37428
|
+
const configPath = join5(homedir3(), ".codex", "config.toml");
|
|
37152
37429
|
console.log(chalk2.bold(`
|
|
37153
37430
|
Add to ${configPath}:`));
|
|
37154
37431
|
console.log(chalk2.dim(`[mcp_servers.skills]
|
|
@@ -37156,8 +37433,8 @@ command = "bun"
|
|
|
37156
37433
|
args = ["run", "${binPath}"]`));
|
|
37157
37434
|
console.log(chalk2.green(`\u2713 Codex MCP config shown above`));
|
|
37158
37435
|
} else if (agent === "gemini") {
|
|
37159
|
-
const { homedir:
|
|
37160
|
-
const configPath =
|
|
37436
|
+
const { homedir: homedir3 } = await import("os");
|
|
37437
|
+
const configPath = join5(homedir3(), ".gemini", "settings.json");
|
|
37161
37438
|
console.log(chalk2.bold(`
|
|
37162
37439
|
Add to ${configPath} mcpServers:`));
|
|
37163
37440
|
console.log(chalk2.dim(JSON.stringify({
|
|
@@ -37211,12 +37488,15 @@ program2.command("completion").argument("<shell>", "Shell type: bash, zsh, or fi
|
|
|
37211
37488
|
"remove",
|
|
37212
37489
|
"update",
|
|
37213
37490
|
"categories",
|
|
37491
|
+
"tags",
|
|
37214
37492
|
"mcp",
|
|
37215
37493
|
"serve",
|
|
37216
37494
|
"init",
|
|
37217
37495
|
"self-update",
|
|
37218
37496
|
"completion",
|
|
37219
|
-
"outdated"
|
|
37497
|
+
"outdated",
|
|
37498
|
+
"doctor",
|
|
37499
|
+
"auth"
|
|
37220
37500
|
];
|
|
37221
37501
|
const skillNames = SKILLS.map((s) => s.name);
|
|
37222
37502
|
const categoryNames = CATEGORIES.map((c) => c);
|
|
@@ -37339,6 +37619,117 @@ _skills "$@"
|
|
|
37339
37619
|
process.exitCode = 1;
|
|
37340
37620
|
}
|
|
37341
37621
|
});
|
|
37622
|
+
program2.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export installed skills to JSON for sharing or backup").action((_options) => {
|
|
37623
|
+
const skills = getInstalledSkills();
|
|
37624
|
+
const payload = {
|
|
37625
|
+
version: 1,
|
|
37626
|
+
skills,
|
|
37627
|
+
timestamp: new Date().toISOString()
|
|
37628
|
+
};
|
|
37629
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
37630
|
+
});
|
|
37631
|
+
program2.command("import").argument("<file>", "JSON file to import (use - for stdin)").option("--json", "Output results as JSON", false).option("--for <agent>", "Install for agent: claude, codex, gemini, or all").option("--scope <scope>", "Install scope: global or project", "global").option("--dry-run", "Show what would be installed without actually installing", false).description("Import and install skills from a JSON export file").action(async (file2, options) => {
|
|
37632
|
+
let raw;
|
|
37633
|
+
try {
|
|
37634
|
+
if (file2 === "-") {
|
|
37635
|
+
raw = await new Response(process.stdin).text();
|
|
37636
|
+
} else {
|
|
37637
|
+
if (!existsSync5(file2)) {
|
|
37638
|
+
console.error(chalk2.red(`File not found: ${file2}`));
|
|
37639
|
+
process.exitCode = 1;
|
|
37640
|
+
return;
|
|
37641
|
+
}
|
|
37642
|
+
raw = readFileSync4(file2, "utf-8");
|
|
37643
|
+
}
|
|
37644
|
+
} catch (err) {
|
|
37645
|
+
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
37646
|
+
process.exitCode = 1;
|
|
37647
|
+
return;
|
|
37648
|
+
}
|
|
37649
|
+
let payload;
|
|
37650
|
+
try {
|
|
37651
|
+
payload = JSON.parse(raw);
|
|
37652
|
+
} catch {
|
|
37653
|
+
console.error(chalk2.red("Invalid JSON in import file"));
|
|
37654
|
+
process.exitCode = 1;
|
|
37655
|
+
return;
|
|
37656
|
+
}
|
|
37657
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.skills)) {
|
|
37658
|
+
console.error(chalk2.red('Invalid format: expected { "version": 1, "skills": [...] }'));
|
|
37659
|
+
process.exitCode = 1;
|
|
37660
|
+
return;
|
|
37661
|
+
}
|
|
37662
|
+
const skillList = payload.skills;
|
|
37663
|
+
const total = skillList.length;
|
|
37664
|
+
if (total === 0) {
|
|
37665
|
+
if (options.json) {
|
|
37666
|
+
console.log(JSON.stringify({ imported: 0, results: [] }));
|
|
37667
|
+
} else {
|
|
37668
|
+
console.log(chalk2.dim("No skills to import"));
|
|
37669
|
+
}
|
|
37670
|
+
return;
|
|
37671
|
+
}
|
|
37672
|
+
if (options.dryRun) {
|
|
37673
|
+
if (options.json) {
|
|
37674
|
+
console.log(JSON.stringify({ dryRun: true, skills: skillList }));
|
|
37675
|
+
} else {
|
|
37676
|
+
console.log(chalk2.bold(`
|
|
37677
|
+
[dry-run] Would install ${total} skill(s):
|
|
37678
|
+
`));
|
|
37679
|
+
for (let i = 0;i < total; i++) {
|
|
37680
|
+
const target = options.for ? ` for ${options.for} (${options.scope})` : " to .skills/";
|
|
37681
|
+
console.log(chalk2.dim(` [${i + 1}/${total}] ${skillList[i]}${target}`));
|
|
37682
|
+
}
|
|
37683
|
+
}
|
|
37684
|
+
return;
|
|
37685
|
+
}
|
|
37686
|
+
const results = [];
|
|
37687
|
+
if (options.for) {
|
|
37688
|
+
let agents;
|
|
37689
|
+
try {
|
|
37690
|
+
agents = resolveAgents(options.for);
|
|
37691
|
+
} catch (err) {
|
|
37692
|
+
console.error(chalk2.red(err.message));
|
|
37693
|
+
process.exitCode = 1;
|
|
37694
|
+
return;
|
|
37695
|
+
}
|
|
37696
|
+
for (let i = 0;i < total; i++) {
|
|
37697
|
+
const name = skillList[i];
|
|
37698
|
+
if (!options.json) {
|
|
37699
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37700
|
+
}
|
|
37701
|
+
const agentResults = agents.map((agent) => installSkillForAgent(name, { agent, scope: options.scope }, generateSkillMd));
|
|
37702
|
+
const success2 = agentResults.every((r) => r.success);
|
|
37703
|
+
results.push({ skill: name, success: success2, agentResults });
|
|
37704
|
+
if (!options.json) {
|
|
37705
|
+
console.log(success2 ? " done" : " failed");
|
|
37706
|
+
}
|
|
37707
|
+
}
|
|
37708
|
+
} else {
|
|
37709
|
+
for (let i = 0;i < total; i++) {
|
|
37710
|
+
const name = skillList[i];
|
|
37711
|
+
if (!options.json) {
|
|
37712
|
+
process.stdout.write(`[${i + 1}/${total}] Installing ${name}...`);
|
|
37713
|
+
}
|
|
37714
|
+
const result = installSkill(name);
|
|
37715
|
+
results.push(result);
|
|
37716
|
+
if (!options.json) {
|
|
37717
|
+
console.log(result.success ? " done" : " failed");
|
|
37718
|
+
}
|
|
37719
|
+
}
|
|
37720
|
+
}
|
|
37721
|
+
if (options.json) {
|
|
37722
|
+
console.log(JSON.stringify({ imported: results.filter((r) => r.success).length, total, results }));
|
|
37723
|
+
} else {
|
|
37724
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
37725
|
+
const failed = total - succeeded;
|
|
37726
|
+
console.log(chalk2.bold(`
|
|
37727
|
+
Imported ${succeeded}/${total} skill(s)${failed > 0 ? ` (${failed} failed)` : ""}`));
|
|
37728
|
+
}
|
|
37729
|
+
if (results.some((r) => !r.success)) {
|
|
37730
|
+
process.exitCode = 1;
|
|
37731
|
+
}
|
|
37732
|
+
});
|
|
37342
37733
|
program2.command("doctor").option("--json", "Output as JSON", false).description("Check environment variables for installed skills").action((options) => {
|
|
37343
37734
|
const installed = getInstalledSkills();
|
|
37344
37735
|
if (installed.length === 0) {
|
|
@@ -37377,6 +37768,247 @@ Skills Doctor (${installed.length} installed):
|
|
|
37377
37768
|
}
|
|
37378
37769
|
}
|
|
37379
37770
|
});
|
|
37771
|
+
program2.command("auth").argument("[skill]", "Skill name (omit to check all installed skills)").option("--set <assignment>", "Set an env var in .env file (format: KEY=VALUE)").option("--json", "Output as JSON", false).description("Show auth/env var status for a skill or all installed skills").action((name, options) => {
|
|
37772
|
+
const cwd = process.cwd();
|
|
37773
|
+
const envFilePath = join5(cwd, ".env");
|
|
37774
|
+
if (options.set) {
|
|
37775
|
+
const eqIdx = options.set.indexOf("=");
|
|
37776
|
+
if (eqIdx === -1) {
|
|
37777
|
+
console.error(chalk2.red(`Invalid format for --set. Expected KEY=VALUE, got: ${options.set}`));
|
|
37778
|
+
process.exitCode = 1;
|
|
37779
|
+
return;
|
|
37780
|
+
}
|
|
37781
|
+
const key = options.set.slice(0, eqIdx).trim();
|
|
37782
|
+
const value = options.set.slice(eqIdx + 1);
|
|
37783
|
+
if (!key) {
|
|
37784
|
+
console.error(chalk2.red("Key cannot be empty"));
|
|
37785
|
+
process.exitCode = 1;
|
|
37786
|
+
return;
|
|
37787
|
+
}
|
|
37788
|
+
let existing = "";
|
|
37789
|
+
if (existsSync5(envFilePath)) {
|
|
37790
|
+
existing = readFileSync4(envFilePath, "utf-8");
|
|
37791
|
+
}
|
|
37792
|
+
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
37793
|
+
let updated;
|
|
37794
|
+
if (keyPattern.test(existing)) {
|
|
37795
|
+
updated = existing.replace(keyPattern, `${key}=${value}`);
|
|
37796
|
+
} else {
|
|
37797
|
+
updated = existing.endsWith(`
|
|
37798
|
+
`) || existing === "" ? existing + `${key}=${value}
|
|
37799
|
+
` : existing + `
|
|
37800
|
+
${key}=${value}
|
|
37801
|
+
`;
|
|
37802
|
+
}
|
|
37803
|
+
writeFileSync2(envFilePath, updated, "utf-8");
|
|
37804
|
+
console.log(chalk2.green(`Set ${key} in ${envFilePath}`));
|
|
37805
|
+
return;
|
|
37806
|
+
}
|
|
37807
|
+
if (name) {
|
|
37808
|
+
const reqs = getSkillRequirements(name);
|
|
37809
|
+
if (!reqs) {
|
|
37810
|
+
console.error(`Skill '${name}' not found`);
|
|
37811
|
+
process.exitCode = 1;
|
|
37812
|
+
return;
|
|
37813
|
+
}
|
|
37814
|
+
const envVars = reqs.envVars.map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37815
|
+
if (options.json) {
|
|
37816
|
+
console.log(JSON.stringify({ skill: name, envVars }, null, 2));
|
|
37817
|
+
return;
|
|
37818
|
+
}
|
|
37819
|
+
console.log(chalk2.bold(`
|
|
37820
|
+
Auth status for ${name}:
|
|
37821
|
+
`));
|
|
37822
|
+
if (envVars.length === 0) {
|
|
37823
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37824
|
+
} else {
|
|
37825
|
+
for (const v of envVars) {
|
|
37826
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37827
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37828
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37829
|
+
}
|
|
37830
|
+
}
|
|
37831
|
+
return;
|
|
37832
|
+
}
|
|
37833
|
+
const installed = getInstalledSkills();
|
|
37834
|
+
if (installed.length === 0) {
|
|
37835
|
+
if (options.json) {
|
|
37836
|
+
console.log(JSON.stringify([]));
|
|
37837
|
+
} else {
|
|
37838
|
+
console.log("No skills installed");
|
|
37839
|
+
}
|
|
37840
|
+
return;
|
|
37841
|
+
}
|
|
37842
|
+
const report = [];
|
|
37843
|
+
for (const skillName of installed) {
|
|
37844
|
+
const reqs = getSkillRequirements(skillName);
|
|
37845
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({ name: v, set: !!process.env[v] }));
|
|
37846
|
+
report.push({ skill: skillName, envVars });
|
|
37847
|
+
}
|
|
37848
|
+
if (options.json) {
|
|
37849
|
+
console.log(JSON.stringify(report, null, 2));
|
|
37850
|
+
return;
|
|
37851
|
+
}
|
|
37852
|
+
console.log(chalk2.bold(`
|
|
37853
|
+
Auth status (${installed.length} installed skills):
|
|
37854
|
+
`));
|
|
37855
|
+
for (const entry of report) {
|
|
37856
|
+
console.log(chalk2.bold(` ${entry.skill}`));
|
|
37857
|
+
if (entry.envVars.length === 0) {
|
|
37858
|
+
console.log(chalk2.dim(" No environment variables required"));
|
|
37859
|
+
} else {
|
|
37860
|
+
for (const v of entry.envVars) {
|
|
37861
|
+
const icon = v.set ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
37862
|
+
const status = v.set ? chalk2.green("set") : chalk2.red("missing");
|
|
37863
|
+
console.log(` ${icon} ${v.name} (${status})`);
|
|
37864
|
+
}
|
|
37865
|
+
}
|
|
37866
|
+
}
|
|
37867
|
+
});
|
|
37868
|
+
program2.command("whoami").option("--json", "Output as JSON", false).description("Show setup summary: version, installed skills, agent configs, and paths").action((options) => {
|
|
37869
|
+
const { homedir: homedir3 } = __require("os");
|
|
37870
|
+
const version2 = package_default.version;
|
|
37871
|
+
const cwd = process.cwd();
|
|
37872
|
+
const installed = getInstalledSkills();
|
|
37873
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
37874
|
+
const agentConfigs = [];
|
|
37875
|
+
for (const agent of agentNames) {
|
|
37876
|
+
const agentSkillsPath = join5(homedir3(), `.${agent}`, "skills");
|
|
37877
|
+
const exists = existsSync5(agentSkillsPath);
|
|
37878
|
+
let skillCount = 0;
|
|
37879
|
+
if (exists) {
|
|
37880
|
+
try {
|
|
37881
|
+
skillCount = readdirSync4(agentSkillsPath).filter((f) => {
|
|
37882
|
+
const full = join5(agentSkillsPath, f);
|
|
37883
|
+
return f.startsWith("skill-") && statSync3(full).isDirectory();
|
|
37884
|
+
}).length;
|
|
37885
|
+
} catch {}
|
|
37886
|
+
}
|
|
37887
|
+
agentConfigs.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
37888
|
+
}
|
|
37889
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
37890
|
+
if (options.json) {
|
|
37891
|
+
console.log(JSON.stringify({
|
|
37892
|
+
version: version2,
|
|
37893
|
+
installedCount: installed.length,
|
|
37894
|
+
installed,
|
|
37895
|
+
agents: agentConfigs,
|
|
37896
|
+
skillsDir,
|
|
37897
|
+
cwd
|
|
37898
|
+
}, null, 2));
|
|
37899
|
+
return;
|
|
37900
|
+
}
|
|
37901
|
+
console.log(chalk2.bold(`
|
|
37902
|
+
skills v${version2}
|
|
37903
|
+
`));
|
|
37904
|
+
console.log(`${chalk2.dim("Working directory:")} ${cwd}`);
|
|
37905
|
+
console.log(`${chalk2.dim("Skills directory:")} ${skillsDir}`);
|
|
37906
|
+
console.log();
|
|
37907
|
+
if (installed.length === 0) {
|
|
37908
|
+
console.log(chalk2.dim("No skills installed in current project"));
|
|
37909
|
+
} else {
|
|
37910
|
+
console.log(chalk2.bold(`Installed skills (${installed.length}):`));
|
|
37911
|
+
for (const name of installed) {
|
|
37912
|
+
console.log(` ${chalk2.cyan(name)}`);
|
|
37913
|
+
}
|
|
37914
|
+
}
|
|
37915
|
+
console.log();
|
|
37916
|
+
console.log(chalk2.bold("Agent configurations:"));
|
|
37917
|
+
for (const cfg of agentConfigs) {
|
|
37918
|
+
if (cfg.exists) {
|
|
37919
|
+
console.log(` ${chalk2.green("\u2713")} ${cfg.agent} \u2014 ${cfg.skillCount} skill(s) at ${cfg.path}`);
|
|
37920
|
+
} else {
|
|
37921
|
+
console.log(` ${chalk2.dim("\u2717")} ${cfg.agent} \u2014 not configured`);
|
|
37922
|
+
}
|
|
37923
|
+
}
|
|
37924
|
+
});
|
|
37925
|
+
program2.command("test").argument("[skill]", "Skill name to test (omit to test all installed skills)").option("--json", "Output results as JSON", false).description("Test skill readiness: env vars, system deps, and npm deps").action(async (skillArg, options) => {
|
|
37926
|
+
let skillNames;
|
|
37927
|
+
if (skillArg) {
|
|
37928
|
+
const registryName = skillArg.startsWith("skill-") ? skillArg.replace("skill-", "") : skillArg;
|
|
37929
|
+
const skill = getSkill(registryName);
|
|
37930
|
+
if (!skill) {
|
|
37931
|
+
if (options.json) {
|
|
37932
|
+
console.log(JSON.stringify({ error: `Skill '${skillArg}' not found` }));
|
|
37933
|
+
} else {
|
|
37934
|
+
console.error(chalk2.red(`Skill '${skillArg}' not found`));
|
|
37935
|
+
}
|
|
37936
|
+
process.exitCode = 1;
|
|
37937
|
+
return;
|
|
37938
|
+
}
|
|
37939
|
+
skillNames = [registryName];
|
|
37940
|
+
} else {
|
|
37941
|
+
skillNames = getInstalledSkills();
|
|
37942
|
+
if (skillNames.length === 0) {
|
|
37943
|
+
if (options.json) {
|
|
37944
|
+
console.log(JSON.stringify([]));
|
|
37945
|
+
} else {
|
|
37946
|
+
console.log(chalk2.dim("No skills installed. Run: skills install <name>"));
|
|
37947
|
+
}
|
|
37948
|
+
return;
|
|
37949
|
+
}
|
|
37950
|
+
}
|
|
37951
|
+
const results = [];
|
|
37952
|
+
for (const name of skillNames) {
|
|
37953
|
+
const reqs = getSkillRequirements(name);
|
|
37954
|
+
const envVars = (reqs?.envVars ?? []).map((v) => ({
|
|
37955
|
+
name: v,
|
|
37956
|
+
set: !!process.env[v]
|
|
37957
|
+
}));
|
|
37958
|
+
const systemDeps = (reqs?.systemDeps ?? []).map((dep) => {
|
|
37959
|
+
const proc = Bun.spawnSync(["which", dep]);
|
|
37960
|
+
return { name: dep, available: proc.exitCode === 0 };
|
|
37961
|
+
});
|
|
37962
|
+
const npmDeps = Object.entries(reqs?.dependencies ?? {}).map(([pkgName, version2]) => ({
|
|
37963
|
+
name: pkgName,
|
|
37964
|
+
version: version2
|
|
37965
|
+
}));
|
|
37966
|
+
const ready = envVars.every((v) => v.set) && systemDeps.every((d) => d.available);
|
|
37967
|
+
results.push({ skill: name, envVars, systemDeps, npmDeps, ready });
|
|
37968
|
+
}
|
|
37969
|
+
if (options.json) {
|
|
37970
|
+
console.log(JSON.stringify(results, null, 2));
|
|
37971
|
+
return;
|
|
37972
|
+
}
|
|
37973
|
+
const allReady = results.every((r) => r.ready);
|
|
37974
|
+
console.log(chalk2.bold(`
|
|
37975
|
+
Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
|
|
37976
|
+
`));
|
|
37977
|
+
for (const result of results) {
|
|
37978
|
+
const readyLabel = result.ready ? chalk2.green("ready") : chalk2.red("not ready");
|
|
37979
|
+
console.log(chalk2.bold(` ${result.skill}`) + chalk2.dim(` [${readyLabel}]`));
|
|
37980
|
+
if (result.envVars.length === 0 && result.systemDeps.length === 0) {
|
|
37981
|
+
console.log(chalk2.dim(" No requirements"));
|
|
37982
|
+
}
|
|
37983
|
+
for (const v of result.envVars) {
|
|
37984
|
+
if (v.set) {
|
|
37985
|
+
console.log(` ${chalk2.green("\u2713")} ${v.name}`);
|
|
37986
|
+
} else {
|
|
37987
|
+
console.log(` ${chalk2.red("\u2717")} ${v.name} ${chalk2.dim("(missing)")}`);
|
|
37988
|
+
}
|
|
37989
|
+
}
|
|
37990
|
+
for (const dep of result.systemDeps) {
|
|
37991
|
+
if (dep.available) {
|
|
37992
|
+
console.log(` ${chalk2.green("\u2713")} ${dep.name} ${chalk2.dim("(system)")}`);
|
|
37993
|
+
} else {
|
|
37994
|
+
console.log(` ${chalk2.red("\u2717")} ${dep.name} ${chalk2.dim("(not installed)")}`);
|
|
37995
|
+
}
|
|
37996
|
+
}
|
|
37997
|
+
if (result.npmDeps.length > 0) {
|
|
37998
|
+
console.log(chalk2.dim(` npm: ${result.npmDeps.map((d) => d.name).join(", ")}`));
|
|
37999
|
+
}
|
|
38000
|
+
}
|
|
38001
|
+
console.log();
|
|
38002
|
+
if (allReady) {
|
|
38003
|
+
console.log(chalk2.green(`All ${results.length} skill(s) ready`));
|
|
38004
|
+
} else {
|
|
38005
|
+
const notReady = results.filter((r) => !r.ready).length;
|
|
38006
|
+
console.log(chalk2.yellow(`${notReady} skill(s) not ready`));
|
|
38007
|
+
}
|
|
38008
|
+
if (!allReady) {
|
|
38009
|
+
process.exitCode = 1;
|
|
38010
|
+
}
|
|
38011
|
+
});
|
|
37380
38012
|
program2.command("outdated").option("--json", "Output as JSON", false).description("Check for outdated installed skills").action((options) => {
|
|
37381
38013
|
const installed = getInstalledSkills();
|
|
37382
38014
|
if (installed.length === 0) {
|
|
@@ -37392,17 +38024,17 @@ program2.command("outdated").option("--json", "Output as JSON", false).descripti
|
|
|
37392
38024
|
const upToDate = [];
|
|
37393
38025
|
for (const name of installed) {
|
|
37394
38026
|
const skillName = normalizeSkillName(name);
|
|
37395
|
-
const installedPkgPath =
|
|
38027
|
+
const installedPkgPath = join5(cwd, ".skills", skillName, "package.json");
|
|
37396
38028
|
let installedVersion = "unknown";
|
|
37397
|
-
if (
|
|
38029
|
+
if (existsSync5(installedPkgPath)) {
|
|
37398
38030
|
try {
|
|
37399
38031
|
installedVersion = JSON.parse(readFileSync4(installedPkgPath, "utf-8")).version || "unknown";
|
|
37400
38032
|
} catch {}
|
|
37401
38033
|
}
|
|
37402
38034
|
const registryPath = getSkillPath(name);
|
|
37403
|
-
const registryPkgPath =
|
|
38035
|
+
const registryPkgPath = join5(registryPath, "package.json");
|
|
37404
38036
|
let registryVersion = "unknown";
|
|
37405
|
-
if (
|
|
38037
|
+
if (existsSync5(registryPkgPath)) {
|
|
37406
38038
|
try {
|
|
37407
38039
|
registryVersion = JSON.parse(readFileSync4(registryPkgPath, "utf-8")).version || "unknown";
|
|
37408
38040
|
} catch {}
|
package/bin/mcp.js
CHANGED
|
@@ -28596,10 +28596,15 @@ class StdioServerTransport {
|
|
|
28596
28596
|
});
|
|
28597
28597
|
}
|
|
28598
28598
|
}
|
|
28599
|
+
|
|
28600
|
+
// src/mcp/index.ts
|
|
28601
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
28602
|
+
import { join as join3 } from "path";
|
|
28603
|
+
import { homedir as homedir2 } from "os";
|
|
28599
28604
|
// package.json
|
|
28600
28605
|
var package_default = {
|
|
28601
28606
|
name: "@hasna/skills",
|
|
28602
|
-
version: "0.1.
|
|
28607
|
+
version: "0.1.8",
|
|
28603
28608
|
description: "Skills library for AI coding agents",
|
|
28604
28609
|
type: "module",
|
|
28605
28610
|
bin: {
|
|
@@ -28640,6 +28645,12 @@ var package_default = {
|
|
|
28640
28645
|
"typescript",
|
|
28641
28646
|
"bun",
|
|
28642
28647
|
"claude",
|
|
28648
|
+
"codex",
|
|
28649
|
+
"gemini",
|
|
28650
|
+
"mcp",
|
|
28651
|
+
"model-context-protocol",
|
|
28652
|
+
"open-source",
|
|
28653
|
+
"skill-library",
|
|
28643
28654
|
"automation"
|
|
28644
28655
|
],
|
|
28645
28656
|
author: "Hasna",
|
|
@@ -30703,6 +30714,52 @@ server.registerTool("install_skill", {
|
|
|
30703
30714
|
isError: !result.success
|
|
30704
30715
|
};
|
|
30705
30716
|
});
|
|
30717
|
+
server.registerTool("install_category", {
|
|
30718
|
+
title: "Install Category",
|
|
30719
|
+
description: "Install all skills in a category. Optionally install for a specific agent (claude, codex, gemini, or all) with a given scope.",
|
|
30720
|
+
inputSchema: {
|
|
30721
|
+
category: exports_external.string().describe("Category name (case-insensitive, e.g. 'Event Management')"),
|
|
30722
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
30723
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
30724
|
+
}
|
|
30725
|
+
}, async ({ category, for: agentArg, scope }) => {
|
|
30726
|
+
const matchedCategory = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
30727
|
+
if (!matchedCategory) {
|
|
30728
|
+
return {
|
|
30729
|
+
content: [{ type: "text", text: `Unknown category: ${category}. Available: ${CATEGORIES.join(", ")}` }],
|
|
30730
|
+
isError: true
|
|
30731
|
+
};
|
|
30732
|
+
}
|
|
30733
|
+
const categorySkills = getSkillsByCategory(matchedCategory);
|
|
30734
|
+
const names = categorySkills.map((s) => s.name);
|
|
30735
|
+
if (agentArg) {
|
|
30736
|
+
let agents;
|
|
30737
|
+
try {
|
|
30738
|
+
agents = resolveAgents(agentArg);
|
|
30739
|
+
} catch (err) {
|
|
30740
|
+
return {
|
|
30741
|
+
content: [{ type: "text", text: err.message }],
|
|
30742
|
+
isError: true
|
|
30743
|
+
};
|
|
30744
|
+
}
|
|
30745
|
+
const results2 = [];
|
|
30746
|
+
for (const name of names) {
|
|
30747
|
+
for (const a of agents) {
|
|
30748
|
+
const r = installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd);
|
|
30749
|
+
results2.push({ ...r, agent: a, scope: scope || "global" });
|
|
30750
|
+
}
|
|
30751
|
+
}
|
|
30752
|
+
return {
|
|
30753
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results: results2 }, null, 2) }],
|
|
30754
|
+
isError: results2.some((r) => !r.success)
|
|
30755
|
+
};
|
|
30756
|
+
}
|
|
30757
|
+
const results = names.map((name) => installSkill(name));
|
|
30758
|
+
return {
|
|
30759
|
+
content: [{ type: "text", text: JSON.stringify({ category: matchedCategory, count: names.length, results }, null, 2) }],
|
|
30760
|
+
isError: results.some((r) => !r.success)
|
|
30761
|
+
};
|
|
30762
|
+
});
|
|
30706
30763
|
server.registerTool("remove_skill", {
|
|
30707
30764
|
title: "Remove Skill",
|
|
30708
30765
|
description: "Remove an installed skill. Without --for, removes from .skills/. With --for, removes from agent skill directory.",
|
|
@@ -30795,6 +30852,94 @@ server.registerTool("run_skill", {
|
|
|
30795
30852
|
content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: name }, null, 2) }]
|
|
30796
30853
|
};
|
|
30797
30854
|
});
|
|
30855
|
+
server.registerTool("export_skills", {
|
|
30856
|
+
title: "Export Skills",
|
|
30857
|
+
description: "Export the list of currently installed skills as a JSON payload that can be imported elsewhere"
|
|
30858
|
+
}, async () => {
|
|
30859
|
+
const skills = getInstalledSkills();
|
|
30860
|
+
const payload = {
|
|
30861
|
+
version: 1,
|
|
30862
|
+
skills,
|
|
30863
|
+
timestamp: new Date().toISOString()
|
|
30864
|
+
};
|
|
30865
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
30866
|
+
});
|
|
30867
|
+
server.registerTool("import_skills", {
|
|
30868
|
+
title: "Import Skills",
|
|
30869
|
+
description: "Install a list of skills from an export payload. Supports agent-specific installs via the 'for' parameter.",
|
|
30870
|
+
inputSchema: {
|
|
30871
|
+
skills: exports_external.array(exports_external.string()).describe("List of skill names to install"),
|
|
30872
|
+
for: exports_external.string().optional().describe("Agent target: claude, codex, gemini, or all"),
|
|
30873
|
+
scope: exports_external.string().optional().describe("Install scope: global or project (default: global)")
|
|
30874
|
+
}
|
|
30875
|
+
}, async ({ skills: skillList, for: agentArg, scope }) => {
|
|
30876
|
+
if (!skillList || skillList.length === 0) {
|
|
30877
|
+
return { content: [{ type: "text", text: JSON.stringify({ imported: 0, results: [] }, null, 2) }] };
|
|
30878
|
+
}
|
|
30879
|
+
const results = [];
|
|
30880
|
+
if (agentArg) {
|
|
30881
|
+
let agents;
|
|
30882
|
+
try {
|
|
30883
|
+
agents = resolveAgents(agentArg);
|
|
30884
|
+
} catch (err) {
|
|
30885
|
+
return {
|
|
30886
|
+
content: [{ type: "text", text: err.message }],
|
|
30887
|
+
isError: true
|
|
30888
|
+
};
|
|
30889
|
+
}
|
|
30890
|
+
for (const name of skillList) {
|
|
30891
|
+
const agentResults = agents.map((a) => installSkillForAgent(name, { agent: a, scope: scope || "global" }, generateSkillMd));
|
|
30892
|
+
const success2 = agentResults.every((r) => r.success);
|
|
30893
|
+
const errors3 = agentResults.filter((r) => !r.success).map((r) => r.error).filter(Boolean);
|
|
30894
|
+
results.push({ skill: name, success: success2, ...errors3.length > 0 ? { error: errors3.join("; ") } : {} });
|
|
30895
|
+
}
|
|
30896
|
+
} else {
|
|
30897
|
+
for (const name of skillList) {
|
|
30898
|
+
const result = installSkill(name);
|
|
30899
|
+
results.push({ skill: result.skill, success: result.success, ...result.error ? { error: result.error } : {} });
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
const imported = results.filter((r) => r.success).length;
|
|
30903
|
+
const hasErrors = results.some((r) => !r.success);
|
|
30904
|
+
return {
|
|
30905
|
+
content: [{ type: "text", text: JSON.stringify({ imported, total: skillList.length, results }, null, 2) }],
|
|
30906
|
+
isError: hasErrors
|
|
30907
|
+
};
|
|
30908
|
+
});
|
|
30909
|
+
server.registerTool("whoami", {
|
|
30910
|
+
title: "Skills Whoami",
|
|
30911
|
+
description: "Show setup summary: package version, installed skills, agent configurations, skills directory location, and working directory"
|
|
30912
|
+
}, async () => {
|
|
30913
|
+
const version2 = package_default.version;
|
|
30914
|
+
const cwd = process.cwd();
|
|
30915
|
+
const installed = getInstalledSkills();
|
|
30916
|
+
const agentNames = ["claude", "codex", "gemini"];
|
|
30917
|
+
const agents = [];
|
|
30918
|
+
for (const agent of agentNames) {
|
|
30919
|
+
const agentSkillsPath = join3(homedir2(), `.${agent}`, "skills");
|
|
30920
|
+
const exists = existsSync3(agentSkillsPath);
|
|
30921
|
+
let skillCount = 0;
|
|
30922
|
+
if (exists) {
|
|
30923
|
+
try {
|
|
30924
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => {
|
|
30925
|
+
const full = join3(agentSkillsPath, f);
|
|
30926
|
+
return f.startsWith("skill-") && statSync2(full).isDirectory();
|
|
30927
|
+
}).length;
|
|
30928
|
+
} catch {}
|
|
30929
|
+
}
|
|
30930
|
+
agents.push({ agent, path: agentSkillsPath, exists, skillCount });
|
|
30931
|
+
}
|
|
30932
|
+
const skillsDir = getSkillPath("image").replace(/[/\\][^/\\]*$/, "");
|
|
30933
|
+
const result = {
|
|
30934
|
+
version: version2,
|
|
30935
|
+
installedCount: installed.length,
|
|
30936
|
+
installed,
|
|
30937
|
+
agents,
|
|
30938
|
+
skillsDir,
|
|
30939
|
+
cwd
|
|
30940
|
+
};
|
|
30941
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
30942
|
+
});
|
|
30798
30943
|
server.registerResource("Skills Registry", "skills://registry", {
|
|
30799
30944
|
description: "Full list of all available skills as JSON"
|
|
30800
30945
|
}, async () => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|