@skills-hub-ai/mcp 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 +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -1
- package/dist/api.js.map +1 -1
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js +1 -4
- package/dist/discover.js.map +1 -1
- package/dist/index.js +39 -14
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +31 -0
- package/dist/installer.js.map +1 -1
- package/package.json +20 -9
- package/src/api.test.ts +64 -17
- package/src/api.ts +11 -3
- package/src/discover.test.ts +10 -3
- package/src/discover.ts +1 -4
- package/src/index.test.ts +100 -31
- package/src/index.ts +101 -53
- package/src/installer.test.ts +73 -21
- package/src/installer.ts +47 -2
package/README.md
CHANGED
|
@@ -38,10 +38,10 @@ Add to your Cursor MCP settings (`~/.cursor/mcp.json`):
|
|
|
38
38
|
|
|
39
39
|
The server scans these directories for installed skills:
|
|
40
40
|
|
|
41
|
-
| Tool
|
|
42
|
-
|
|
41
|
+
| Tool | Directory |
|
|
42
|
+
| ----------- | ------------------- |
|
|
43
43
|
| Claude Code | `~/.claude/skills/` |
|
|
44
|
-
| Cursor
|
|
44
|
+
| Cursor | `~/.cursor/skills/` |
|
|
45
45
|
|
|
46
46
|
Each skill directory should contain a `SKILL.md` file (installed via `skills-hub install <slug>`).
|
|
47
47
|
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE;QACX,QAAQ,EAAE,KAAK,CAAC;YACd,KAAK,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;aAAE,CAAC;YACnE,SAAS,EAAE,MAAM,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;SACrB,CAAC,CAAC;KACJ,GAAG,IAAI,CAAC;CACV;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,SAAK,EACV,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgB9B;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAS7E"}
|
package/dist/api.js
CHANGED
|
@@ -11,7 +11,11 @@ function fetchWithTimeout(url) {
|
|
|
11
11
|
return fetch(url, { signal: controller.signal, headers }).finally(() => clearTimeout(timer));
|
|
12
12
|
}
|
|
13
13
|
export async function searchSkills(query, category, limit = 10, org) {
|
|
14
|
-
const params = new URLSearchParams({
|
|
14
|
+
const params = new URLSearchParams({
|
|
15
|
+
q: query,
|
|
16
|
+
limit: String(limit),
|
|
17
|
+
sort: "relevance",
|
|
18
|
+
});
|
|
15
19
|
if (category)
|
|
16
20
|
params.set("category", category);
|
|
17
21
|
if (org)
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,2BAA2B,CAAC;AAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC;AACzD,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/D,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,SAAS,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,2BAA2B,CAAC;AAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC;AACzD,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/D,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,SAAS,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CACrE,YAAY,CAAC,KAAK,CAAC,CACpB,CAAC;AACJ,CAAC;AAqCD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,QAAiB,EACjB,KAAK,GAAG,EAAE,EACV,GAAY;IAEZ,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,CAAC,EAAE,KAAK;QACR,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;QACpB,IAAI,EAAE,WAAW;KAClB,CAAC,CAAC;IACH,IAAI,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC/C,IAAI,GAAG;QAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEhC,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,OAAO,kBAAkB,MAAM,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmC,CAAC;IAClE,OAAO,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAChC,GAAG,OAAO,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CACvD,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsB,CAAC;AACjD,CAAC"}
|
package/dist/discover.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAuC7E,oDAAoD;AACpD,wBAAgB,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAczD;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,IAAI,IAAI,CAGjC"}
|
package/dist/discover.js
CHANGED
|
@@ -8,10 +8,7 @@ let cacheTime = 0;
|
|
|
8
8
|
/** Directories where skills may be installed */
|
|
9
9
|
function getSkillPaths() {
|
|
10
10
|
const home = homedir();
|
|
11
|
-
return [
|
|
12
|
-
join(home, ".claude", "skills"),
|
|
13
|
-
join(home, ".cursor", "skills"),
|
|
14
|
-
];
|
|
11
|
+
return [join(home, ".claude", "skills"), join(home, ".cursor", "skills")];
|
|
15
12
|
}
|
|
16
13
|
/** Scan a single skills directory and collect parsed skills */
|
|
17
14
|
function scanDir(dir, out) {
|
package/dist/discover.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,YAAY,EAAoB,MAAM,6BAA6B,CAAC;AAE7E,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,IAAI,KAAK,GAAoC,IAAI,CAAC;AAClD,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,gDAAgD;AAChD,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,YAAY,EAAoB,MAAM,6BAA6B,CAAC;AAE7E,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,IAAI,KAAK,GAAoC,IAAI,CAAC;AAClD,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,gDAAgD;AAChD,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,+DAA+D;AAC/D,SAAS,OAAO,CAAC,GAAW,EAAE,GAA6B;IACzD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAE7B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,qCAAqC;QAElE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QAErC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;QAC5C,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,GAAG,MAAM,CAAC;IACf,SAAS,GAAG,GAAG,CAAC;IAChB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU;IACxB,KAAK,GAAG,IAAI,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;AAChB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -54,10 +54,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
54
54
|
inputSchema: {
|
|
55
55
|
type: "object",
|
|
56
56
|
properties: {
|
|
57
|
-
query: {
|
|
58
|
-
|
|
57
|
+
query: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Search query (e.g. 'code review', 'testing React')",
|
|
60
|
+
},
|
|
61
|
+
category: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Filter by category slug (e.g. 'review', 'test', 'build')",
|
|
64
|
+
},
|
|
59
65
|
limit: { type: "number", description: "Max results (default 10)" },
|
|
60
|
-
organization: {
|
|
66
|
+
organization: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Organization slug to include org-private skills in results (requires authentication)",
|
|
69
|
+
},
|
|
61
70
|
},
|
|
62
71
|
required: ["query"],
|
|
63
72
|
},
|
|
@@ -68,7 +77,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
68
77
|
inputSchema: {
|
|
69
78
|
type: "object",
|
|
70
79
|
properties: {
|
|
71
|
-
slug: {
|
|
80
|
+
slug: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Skill slug (e.g. 'review-code')",
|
|
83
|
+
},
|
|
72
84
|
},
|
|
73
85
|
required: ["slug"],
|
|
74
86
|
},
|
|
@@ -80,7 +92,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
80
92
|
type: "object",
|
|
81
93
|
properties: {
|
|
82
94
|
slug: { type: "string", description: "Skill slug to install" },
|
|
83
|
-
target: {
|
|
95
|
+
target: {
|
|
96
|
+
type: "string",
|
|
97
|
+
enum: ["claude-code", "cursor"],
|
|
98
|
+
description: "Install target (default: claude-code)",
|
|
99
|
+
},
|
|
84
100
|
},
|
|
85
101
|
required: ["slug"],
|
|
86
102
|
},
|
|
@@ -106,7 +122,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
106
122
|
const limit = Math.min(Math.max(args?.limit || 10, 1), 100);
|
|
107
123
|
const results = await searchSkills(query, args?.category, limit, args?.organization);
|
|
108
124
|
return {
|
|
109
|
-
content: [
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
110
127
|
type: "text",
|
|
111
128
|
text: JSON.stringify(results.map((s) => ({
|
|
112
129
|
slug: s.slug,
|
|
@@ -119,7 +136,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
119
136
|
isComposition: s.isComposition,
|
|
120
137
|
tags: s.tags,
|
|
121
138
|
})), null, 2),
|
|
122
|
-
}
|
|
139
|
+
},
|
|
140
|
+
],
|
|
123
141
|
};
|
|
124
142
|
}
|
|
125
143
|
case "get_skill_detail": {
|
|
@@ -128,7 +146,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
128
146
|
throw new McpError(ErrorCode.InvalidParams, "slug is required");
|
|
129
147
|
const detail = await getSkillDetail(slug);
|
|
130
148
|
return {
|
|
131
|
-
content: [
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
132
151
|
type: "text",
|
|
133
152
|
text: JSON.stringify({
|
|
134
153
|
slug: detail.slug,
|
|
@@ -143,9 +162,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
143
162
|
tags: detail.tags,
|
|
144
163
|
isComposition: detail.isComposition,
|
|
145
164
|
composition: detail.composition,
|
|
146
|
-
instructionsPreview: detail.instructions?.slice(0, 500) +
|
|
165
|
+
instructionsPreview: detail.instructions?.slice(0, 500) +
|
|
166
|
+
(detail.instructions?.length > 500 ? "..." : ""),
|
|
147
167
|
}, null, 2),
|
|
148
|
-
}
|
|
168
|
+
},
|
|
169
|
+
],
|
|
149
170
|
};
|
|
150
171
|
}
|
|
151
172
|
case "install_skill": {
|
|
@@ -155,10 +176,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
155
176
|
const target = args?.target || "claude-code";
|
|
156
177
|
const result = await installSkill(slug, target);
|
|
157
178
|
return {
|
|
158
|
-
content: [
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
159
181
|
type: "text",
|
|
160
182
|
text: JSON.stringify(result, null, 2),
|
|
161
|
-
}
|
|
183
|
+
},
|
|
184
|
+
],
|
|
162
185
|
};
|
|
163
186
|
}
|
|
164
187
|
case "list_installed_skills": {
|
|
@@ -171,10 +194,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
171
194
|
category: skill.category || null,
|
|
172
195
|
}));
|
|
173
196
|
return {
|
|
174
|
-
content: [
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
175
199
|
type: "text",
|
|
176
200
|
text: JSON.stringify(list, null, 2),
|
|
177
|
-
}
|
|
201
|
+
},
|
|
202
|
+
],
|
|
178
203
|
};
|
|
179
204
|
}
|
|
180
205
|
default:
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,SAAS,EACT,QAAQ,GACT,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,EACxC,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChE,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;IAC5D,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAEhC,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5D,IAAI,EAAE,IAAI;YACV,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE;gBACT;oBACE,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,uCAAuC;oBACpD,QAAQ,EAAE,KAAK;iBAChB;aACF;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IACjE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IACjD,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,kBAAkB,IAAI,6BAA6B,IAAI,kBAAkB,CAC1E,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC;IAC9B,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;QAChB,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;aACzC;SACF;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,0EAA0E;AAC1E,wEAAwE;AAExE,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,EAAE;QACL;YACE,IAAI,EAAE,eAAe;YACrB,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/installer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wEAAwE;AACxE,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,aAAa,GAAG,QAAwB,GAC/C,OAAO,CAAC,aAAa,CAAC,CA6CxB"}
|
package/dist/installer.js
CHANGED
|
@@ -21,6 +21,12 @@ function getSkillsDir(target) {
|
|
|
21
21
|
? join(home, ".cursor", "skills")
|
|
22
22
|
: join(home, ".claude", "skills");
|
|
23
23
|
}
|
|
24
|
+
function getCommandsDir(target) {
|
|
25
|
+
const home = homedir();
|
|
26
|
+
return target === "cursor"
|
|
27
|
+
? join(home, ".cursor", "skills")
|
|
28
|
+
: join(home, ".claude", "commands");
|
|
29
|
+
}
|
|
24
30
|
/** Install a single skill from the API to the local skills directory */
|
|
25
31
|
export async function installSkill(slug, target = "claude-code") {
|
|
26
32
|
validateSlug(slug);
|
|
@@ -37,6 +43,19 @@ category: ${yamlEscape(skill.category.slug)}
|
|
|
37
43
|
${skill.instructions}
|
|
38
44
|
`;
|
|
39
45
|
writeFileSync(join(skillDir, "SKILL.md"), content);
|
|
46
|
+
// Register as slash command (Claude Code reads from ~/.claude/commands/)
|
|
47
|
+
if (target === "claude-code") {
|
|
48
|
+
const commandsDir = getCommandsDir(target);
|
|
49
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
50
|
+
const commandContent = `name: ${yamlEscape(skill.name)}
|
|
51
|
+
description: ${yamlEscape(skill.description)}
|
|
52
|
+
version: ${yamlEscape(skill.latestVersion)}
|
|
53
|
+
category: ${yamlEscape(skill.category.slug)}
|
|
54
|
+
|
|
55
|
+
${skill.instructions}
|
|
56
|
+
`;
|
|
57
|
+
writeFileSync(join(commandsDir, `${slug}.md`), commandContent);
|
|
58
|
+
}
|
|
40
59
|
// Install composition dependencies
|
|
41
60
|
const deps = [];
|
|
42
61
|
if (skill.composition?.children.length) {
|
|
@@ -82,6 +101,18 @@ category: ${yamlEscape(childDetail.category.slug)}
|
|
|
82
101
|
${childDetail.instructions}
|
|
83
102
|
`;
|
|
84
103
|
writeFileSync(join(childDir, "SKILL.md"), content);
|
|
104
|
+
// Register child as slash command too
|
|
105
|
+
if (target === "claude-code") {
|
|
106
|
+
const commandsDir = getCommandsDir(target);
|
|
107
|
+
const commandContent = `name: ${yamlEscape(childDetail.name)}
|
|
108
|
+
description: ${yamlEscape(childDetail.description)}
|
|
109
|
+
version: ${yamlEscape(childDetail.latestVersion)}
|
|
110
|
+
category: ${yamlEscape(childDetail.category.slug)}
|
|
111
|
+
|
|
112
|
+
${childDetail.instructions}
|
|
113
|
+
`;
|
|
114
|
+
writeFileSync(join(commandsDir, `${childSlug}.md`), commandContent);
|
|
115
|
+
}
|
|
85
116
|
installed.push(childSlug);
|
|
86
117
|
// Recurse if this child is also a composition
|
|
87
118
|
await installDependencies(childDetail, target, installed, visited, depth + 1);
|
package/dist/installer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skills-hub-ai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "MCP server for skills-hub.ai — serve installed skills as prompts in any MCP-compatible AI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
8
8
|
"model-context-protocol",
|
|
9
|
+
"mcp-server",
|
|
9
10
|
"claude-code",
|
|
11
|
+
"claude-desktop",
|
|
12
|
+
"claude-skills",
|
|
13
|
+
"agent-skills",
|
|
14
|
+
"skill-md",
|
|
15
|
+
"ai-skills",
|
|
10
16
|
"cursor",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"prompts"
|
|
17
|
+
"cline",
|
|
18
|
+
"continue",
|
|
19
|
+
"ai-prompts",
|
|
20
|
+
"developer-tools",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"ai-search",
|
|
23
|
+
"skill-registry",
|
|
24
|
+
"skills-hub"
|
|
14
25
|
],
|
|
15
26
|
"repository": {
|
|
16
27
|
"type": "git",
|
|
@@ -27,14 +38,14 @@
|
|
|
27
38
|
"skills-hub-mcp": "dist/index.js"
|
|
28
39
|
},
|
|
29
40
|
"dependencies": {
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
31
|
-
"@skills-hub-ai/skill-parser": "0.2.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
+
"@skills-hub-ai/skill-parser": "0.2.2"
|
|
32
43
|
},
|
|
33
44
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^25.6.0",
|
|
35
46
|
"tsx": "^4.0.0",
|
|
36
|
-
"typescript": "^
|
|
37
|
-
"vitest": "^
|
|
47
|
+
"typescript": "^6.0.3",
|
|
48
|
+
"vitest": "^4.1.5"
|
|
38
49
|
},
|
|
39
50
|
"scripts": {
|
|
40
51
|
"dev": "tsx src/index.ts",
|
package/src/api.test.ts
CHANGED
|
@@ -18,7 +18,9 @@ describe("auth header", () => {
|
|
|
18
18
|
await searchSkills("test");
|
|
19
19
|
|
|
20
20
|
const opts = mockFetch.mock.calls[0][1] as RequestInit;
|
|
21
|
-
expect(
|
|
21
|
+
expect(
|
|
22
|
+
(opts.headers as Record<string, string>)["Authorization"],
|
|
23
|
+
).toBeUndefined();
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
it("omits Authorization header on getSkillDetail when token not set", async () => {
|
|
@@ -30,7 +32,9 @@ describe("auth header", () => {
|
|
|
30
32
|
await getSkillDetail("test-skill");
|
|
31
33
|
|
|
32
34
|
const opts = mockFetch.mock.calls[0][1] as RequestInit;
|
|
33
|
-
expect(
|
|
35
|
+
expect(
|
|
36
|
+
(opts.headers as Record<string, string>)["Authorization"],
|
|
37
|
+
).toBeUndefined();
|
|
34
38
|
});
|
|
35
39
|
});
|
|
36
40
|
|
|
@@ -38,17 +42,35 @@ describe("searchSkills", () => {
|
|
|
38
42
|
it("calls search API with query", async () => {
|
|
39
43
|
mockFetch.mockResolvedValue({
|
|
40
44
|
ok: true,
|
|
41
|
-
json: () =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
json: () =>
|
|
46
|
+
Promise.resolve({
|
|
47
|
+
data: [
|
|
48
|
+
{
|
|
49
|
+
slug: "review-code",
|
|
50
|
+
name: "Review Code",
|
|
51
|
+
description: "AI code review",
|
|
52
|
+
qualityScore: 92,
|
|
53
|
+
installCount: 150,
|
|
54
|
+
avgRating: 4.8,
|
|
55
|
+
latestVersion: "1.0.0",
|
|
56
|
+
category: { name: "Review", slug: "review" },
|
|
57
|
+
isComposition: false,
|
|
58
|
+
tags: ["review"],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}),
|
|
46
62
|
});
|
|
47
63
|
|
|
48
64
|
const results = await searchSkills("code review");
|
|
49
65
|
|
|
50
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
51
|
-
|
|
66
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
67
|
+
expect.stringContaining("/api/v1/search?"),
|
|
68
|
+
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
|
69
|
+
);
|
|
70
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
71
|
+
expect.stringContaining("q=code+review"),
|
|
72
|
+
expect.anything(),
|
|
73
|
+
);
|
|
52
74
|
expect(results).toHaveLength(1);
|
|
53
75
|
expect(results[0].slug).toBe("review-code");
|
|
54
76
|
});
|
|
@@ -61,7 +83,10 @@ describe("searchSkills", () => {
|
|
|
61
83
|
|
|
62
84
|
await searchSkills("test", "build");
|
|
63
85
|
|
|
64
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
86
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
87
|
+
expect.stringContaining("category=build"),
|
|
88
|
+
expect.anything(),
|
|
89
|
+
);
|
|
65
90
|
});
|
|
66
91
|
|
|
67
92
|
it("passes limit", async () => {
|
|
@@ -72,11 +97,18 @@ describe("searchSkills", () => {
|
|
|
72
97
|
|
|
73
98
|
await searchSkills("test", undefined, 5);
|
|
74
99
|
|
|
75
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
100
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
101
|
+
expect.stringContaining("limit=5"),
|
|
102
|
+
expect.anything(),
|
|
103
|
+
);
|
|
76
104
|
});
|
|
77
105
|
|
|
78
106
|
it("throws on API error", async () => {
|
|
79
|
-
mockFetch.mockResolvedValue({
|
|
107
|
+
mockFetch.mockResolvedValue({
|
|
108
|
+
ok: false,
|
|
109
|
+
status: 500,
|
|
110
|
+
statusText: "Internal Server Error",
|
|
111
|
+
});
|
|
80
112
|
|
|
81
113
|
await expect(searchSkills("fail")).rejects.toThrow("Search failed: 500");
|
|
82
114
|
});
|
|
@@ -99,7 +131,10 @@ describe("searchSkills", () => {
|
|
|
99
131
|
|
|
100
132
|
await searchSkills("testing", undefined, 10, "my-org");
|
|
101
133
|
|
|
102
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
134
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
135
|
+
expect.stringContaining("org=my-org"),
|
|
136
|
+
expect.anything(),
|
|
137
|
+
);
|
|
103
138
|
});
|
|
104
139
|
|
|
105
140
|
it("omits org query parameter when not provided", async () => {
|
|
@@ -140,15 +175,24 @@ describe("getSkillDetail", () => {
|
|
|
140
175
|
|
|
141
176
|
const result = await getSkillDetail("review-code");
|
|
142
177
|
|
|
143
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
178
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
179
|
+
expect.stringContaining("/api/v1/skills/review-code"),
|
|
180
|
+
expect.anything(),
|
|
181
|
+
);
|
|
144
182
|
expect(result.slug).toBe("review-code");
|
|
145
183
|
expect(result.instructions).toBe("Review the code");
|
|
146
184
|
});
|
|
147
185
|
|
|
148
186
|
it("throws on not found", async () => {
|
|
149
|
-
mockFetch.mockResolvedValue({
|
|
187
|
+
mockFetch.mockResolvedValue({
|
|
188
|
+
ok: false,
|
|
189
|
+
status: 404,
|
|
190
|
+
statusText: "Not Found",
|
|
191
|
+
});
|
|
150
192
|
|
|
151
|
-
await expect(getSkillDetail("nonexistent")).rejects.toThrow(
|
|
193
|
+
await expect(getSkillDetail("nonexistent")).rejects.toThrow(
|
|
194
|
+
"Skill not found: nonexistent",
|
|
195
|
+
);
|
|
152
196
|
});
|
|
153
197
|
|
|
154
198
|
it("URL-encodes the slug", async () => {
|
|
@@ -159,6 +203,9 @@ describe("getSkillDetail", () => {
|
|
|
159
203
|
|
|
160
204
|
await getSkillDetail("my-skill");
|
|
161
205
|
|
|
162
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
206
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
207
|
+
expect.stringContaining("/api/v1/skills/my-skill"),
|
|
208
|
+
expect.anything(),
|
|
209
|
+
);
|
|
163
210
|
});
|
|
164
211
|
});
|
package/src/api.ts
CHANGED
|
@@ -9,7 +9,9 @@ function fetchWithTimeout(url: string): Promise<Response> {
|
|
|
9
9
|
if (API_TOKEN) {
|
|
10
10
|
headers["Authorization"] = `ApiKey ${API_TOKEN}`;
|
|
11
11
|
}
|
|
12
|
-
return fetch(url, { signal: controller.signal, headers }).finally(() =>
|
|
12
|
+
return fetch(url, { signal: controller.signal, headers }).finally(() =>
|
|
13
|
+
clearTimeout(timer),
|
|
14
|
+
);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface SkillSearchResult {
|
|
@@ -53,7 +55,11 @@ export async function searchSkills(
|
|
|
53
55
|
limit = 10,
|
|
54
56
|
org?: string,
|
|
55
57
|
): Promise<SkillSearchResult[]> {
|
|
56
|
-
const params = new URLSearchParams({
|
|
58
|
+
const params = new URLSearchParams({
|
|
59
|
+
q: query,
|
|
60
|
+
limit: String(limit),
|
|
61
|
+
sort: "relevance",
|
|
62
|
+
});
|
|
57
63
|
if (category) params.set("category", category);
|
|
58
64
|
if (org) params.set("org", org);
|
|
59
65
|
|
|
@@ -67,7 +73,9 @@ export async function searchSkills(
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
export async function getSkillDetail(slug: string): Promise<SkillDetailResult> {
|
|
70
|
-
const res = await fetchWithTimeout(
|
|
76
|
+
const res = await fetchWithTimeout(
|
|
77
|
+
`${API_URL}/api/v1/skills/${encodeURIComponent(slug)}`,
|
|
78
|
+
);
|
|
71
79
|
if (!res.ok) {
|
|
72
80
|
throw new Error(`Skill not found: ${slug} (${res.status})`);
|
|
73
81
|
}
|
package/src/discover.test.ts
CHANGED
|
@@ -36,7 +36,11 @@ Check for OWASP top 10 vulnerabilities.
|
|
|
36
36
|
`;
|
|
37
37
|
|
|
38
38
|
function makeDirent(name: string): ReturnType<typeof readdirSync>[0] {
|
|
39
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
isDirectory: () => true,
|
|
42
|
+
isFile: () => false,
|
|
43
|
+
} as unknown as ReturnType<typeof readdirSync>[0];
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
beforeEach(() => {
|
|
@@ -58,7 +62,9 @@ describe("discoverSkills", () => {
|
|
|
58
62
|
expect(skills.size).toBe(1);
|
|
59
63
|
expect(skills.has("code-review")).toBe(true);
|
|
60
64
|
expect(skills.get("code-review")!.name).toBe("Code Review");
|
|
61
|
-
expect(skills.get("code-review")!.instructions).toContain(
|
|
65
|
+
expect(skills.get("code-review")!.instructions).toContain(
|
|
66
|
+
"Review the code carefully",
|
|
67
|
+
);
|
|
62
68
|
});
|
|
63
69
|
|
|
64
70
|
it("discovers skills from cursor skills directory", () => {
|
|
@@ -80,7 +86,8 @@ describe("discoverSkills", () => {
|
|
|
80
86
|
mockReaddirSync.mockImplementation((dir) => {
|
|
81
87
|
const s = String(dir);
|
|
82
88
|
if (s.includes(".claude")) return [makeDirent("code-review")];
|
|
83
|
-
if (s.includes(".cursor"))
|
|
89
|
+
if (s.includes(".cursor"))
|
|
90
|
+
return [makeDirent("code-review"), makeDirent("security-scan")];
|
|
84
91
|
return [];
|
|
85
92
|
});
|
|
86
93
|
mockReadFileSync.mockImplementation((p) => {
|
package/src/discover.ts
CHANGED
|
@@ -11,10 +11,7 @@ let cacheTime = 0;
|
|
|
11
11
|
/** Directories where skills may be installed */
|
|
12
12
|
function getSkillPaths(): string[] {
|
|
13
13
|
const home = homedir();
|
|
14
|
-
return [
|
|
15
|
-
join(home, ".claude", "skills"),
|
|
16
|
-
join(home, ".cursor", "skills"),
|
|
17
|
-
];
|
|
14
|
+
return [join(home, ".claude", "skills"), join(home, ".cursor", "skills")];
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
/** Scan a single skills directory and collect parsed skills */
|
package/src/index.test.ts
CHANGED
|
@@ -18,18 +18,25 @@ vi.mock("./installer.js", () => ({
|
|
|
18
18
|
vi.mock("@modelcontextprotocol/sdk/server/index.js", () => {
|
|
19
19
|
const handlers = new Map<string, (req: unknown) => Promise<unknown>>();
|
|
20
20
|
return {
|
|
21
|
-
Server: vi.fn().mockImplementation(()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
Server: vi.fn().mockImplementation(function () {
|
|
22
|
+
return {
|
|
23
|
+
setRequestHandler: vi.fn(
|
|
24
|
+
function (
|
|
25
|
+
schema: { method: string },
|
|
26
|
+
handler: (req: unknown) => Promise<unknown>,
|
|
27
|
+
) {
|
|
28
|
+
handlers.set(schema.method, handler);
|
|
29
|
+
},
|
|
30
|
+
),
|
|
31
|
+
connect: vi.fn(),
|
|
32
|
+
_handlers: handlers,
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
28
35
|
};
|
|
29
36
|
});
|
|
30
37
|
|
|
31
38
|
vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({
|
|
32
|
-
StdioServerTransport: vi.fn(),
|
|
39
|
+
StdioServerTransport: vi.fn().mockImplementation(function () { return {}; }),
|
|
33
40
|
}));
|
|
34
41
|
|
|
35
42
|
vi.mock("@modelcontextprotocol/sdk/types.js", () => ({
|
|
@@ -37,7 +44,12 @@ vi.mock("@modelcontextprotocol/sdk/types.js", () => ({
|
|
|
37
44
|
GetPromptRequestSchema: { method: "prompts/get" },
|
|
38
45
|
ListToolsRequestSchema: { method: "tools/list" },
|
|
39
46
|
CallToolRequestSchema: { method: "tools/call" },
|
|
40
|
-
ErrorCode: {
|
|
47
|
+
ErrorCode: {
|
|
48
|
+
InvalidRequest: -32600,
|
|
49
|
+
InvalidParams: -32602,
|
|
50
|
+
MethodNotFound: -32601,
|
|
51
|
+
InternalError: -32603,
|
|
52
|
+
},
|
|
41
53
|
McpError: class McpError extends Error {
|
|
42
54
|
code: number;
|
|
43
55
|
constructor(code: number, message: string) {
|
|
@@ -57,7 +69,11 @@ const mockSearchSkills = vi.mocked(searchSkills);
|
|
|
57
69
|
const mockGetSkillDetail = vi.mocked(getSkillDetail);
|
|
58
70
|
const mockInstallSkill = vi.mocked(installSkill);
|
|
59
71
|
|
|
60
|
-
function makeSkill(
|
|
72
|
+
function makeSkill(
|
|
73
|
+
name: string,
|
|
74
|
+
description: string,
|
|
75
|
+
instructions: string,
|
|
76
|
+
): ParsedSkill {
|
|
61
77
|
return {
|
|
62
78
|
name,
|
|
63
79
|
description,
|
|
@@ -96,17 +112,24 @@ beforeEach(async () => {
|
|
|
96
112
|
const h = new Map<string, (req: unknown) => Promise<unknown>>();
|
|
97
113
|
handlers = h;
|
|
98
114
|
return {
|
|
99
|
-
Server: vi.fn().mockImplementation(()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
Server: vi.fn().mockImplementation(function () {
|
|
116
|
+
return {
|
|
117
|
+
setRequestHandler: vi.fn(
|
|
118
|
+
function (
|
|
119
|
+
schema: { method: string },
|
|
120
|
+
handler: (req: unknown) => Promise<unknown>,
|
|
121
|
+
) {
|
|
122
|
+
h.set(schema.method, handler);
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
connect: vi.fn(),
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
105
128
|
};
|
|
106
129
|
});
|
|
107
130
|
|
|
108
131
|
vi.doMock("@modelcontextprotocol/sdk/server/stdio.js", () => ({
|
|
109
|
-
StdioServerTransport: vi.fn(),
|
|
132
|
+
StdioServerTransport: vi.fn().mockImplementation(function () { return {}; }),
|
|
110
133
|
}));
|
|
111
134
|
|
|
112
135
|
vi.doMock("@modelcontextprotocol/sdk/types.js", () => ({
|
|
@@ -114,7 +137,12 @@ beforeEach(async () => {
|
|
|
114
137
|
GetPromptRequestSchema: { method: "prompts/get" },
|
|
115
138
|
ListToolsRequestSchema: { method: "tools/list" },
|
|
116
139
|
CallToolRequestSchema: { method: "tools/call" },
|
|
117
|
-
ErrorCode: {
|
|
140
|
+
ErrorCode: {
|
|
141
|
+
InvalidRequest: -32600,
|
|
142
|
+
InvalidParams: -32602,
|
|
143
|
+
MethodNotFound: -32601,
|
|
144
|
+
InternalError: -32603,
|
|
145
|
+
},
|
|
118
146
|
McpError: class McpError extends Error {
|
|
119
147
|
code: number;
|
|
120
148
|
constructor(code: number, message: string) {
|
|
@@ -138,12 +166,20 @@ describe("MCP server", () => {
|
|
|
138
166
|
|
|
139
167
|
it("listPrompts returns discovered skills", async () => {
|
|
140
168
|
const skills = new Map<string, ParsedSkill>();
|
|
141
|
-
skills.set(
|
|
142
|
-
|
|
169
|
+
skills.set(
|
|
170
|
+
"code-review",
|
|
171
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
172
|
+
);
|
|
173
|
+
skills.set(
|
|
174
|
+
"security-scan",
|
|
175
|
+
makeSkill("Security Scan", "Find vulnerabilities", "Check OWASP"),
|
|
176
|
+
);
|
|
143
177
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
144
178
|
|
|
145
179
|
const handler = handlers.get("prompts/list")!;
|
|
146
|
-
const result = (await handler({})) as {
|
|
180
|
+
const result = (await handler({})) as {
|
|
181
|
+
prompts: Array<{ name: string; description: string }>;
|
|
182
|
+
};
|
|
147
183
|
|
|
148
184
|
expect(result.prompts).toHaveLength(2);
|
|
149
185
|
expect(result.prompts[0].name).toBe("code-review");
|
|
@@ -162,13 +198,22 @@ describe("MCP server", () => {
|
|
|
162
198
|
|
|
163
199
|
it("getPrompt returns skill instructions", async () => {
|
|
164
200
|
const skills = new Map<string, ParsedSkill>();
|
|
165
|
-
skills.set(
|
|
201
|
+
skills.set(
|
|
202
|
+
"code-review",
|
|
203
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
204
|
+
);
|
|
166
205
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
167
206
|
|
|
168
207
|
const handler = handlers.get("prompts/get")!;
|
|
169
208
|
const result = (await handler({
|
|
170
209
|
params: { name: "code-review", arguments: {} },
|
|
171
|
-
})) as {
|
|
210
|
+
})) as {
|
|
211
|
+
description: string;
|
|
212
|
+
messages: Array<{
|
|
213
|
+
role: string;
|
|
214
|
+
content: { type: string; text: string };
|
|
215
|
+
}>;
|
|
216
|
+
};
|
|
172
217
|
|
|
173
218
|
expect(result.description).toBe("Review code quality");
|
|
174
219
|
expect(result.messages).toHaveLength(1);
|
|
@@ -178,15 +223,23 @@ describe("MCP server", () => {
|
|
|
178
223
|
|
|
179
224
|
it("getPrompt appends input argument to instructions", async () => {
|
|
180
225
|
const skills = new Map<string, ParsedSkill>();
|
|
181
|
-
skills.set(
|
|
226
|
+
skills.set(
|
|
227
|
+
"code-review",
|
|
228
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
229
|
+
);
|
|
182
230
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
183
231
|
|
|
184
232
|
const handler = handlers.get("prompts/get")!;
|
|
185
233
|
const result = (await handler({
|
|
186
|
-
params: {
|
|
234
|
+
params: {
|
|
235
|
+
name: "code-review",
|
|
236
|
+
arguments: { input: "Focus on error handling" },
|
|
237
|
+
},
|
|
187
238
|
})) as { messages: Array<{ content: { text: string } }> };
|
|
188
239
|
|
|
189
|
-
expect(result.messages[0].content.text).toBe(
|
|
240
|
+
expect(result.messages[0].content.text).toBe(
|
|
241
|
+
"Review carefully\n\nFocus on error handling",
|
|
242
|
+
);
|
|
190
243
|
});
|
|
191
244
|
|
|
192
245
|
it("getPrompt throws McpError for unknown skill", async () => {
|
|
@@ -196,7 +249,7 @@ describe("MCP server", () => {
|
|
|
196
249
|
|
|
197
250
|
await expect(
|
|
198
251
|
handler({ params: { name: "nonexistent", arguments: {} } }),
|
|
199
|
-
).rejects.toThrow(
|
|
252
|
+
).rejects.toThrow("Unknown skill: nonexistent");
|
|
200
253
|
});
|
|
201
254
|
|
|
202
255
|
it("registers tools/list and tools/call handlers", () => {
|
|
@@ -237,7 +290,12 @@ describe("MCP server", () => {
|
|
|
237
290
|
params: { name: "search_skills", arguments: { query: "code review" } },
|
|
238
291
|
})) as { content: Array<{ text: string }> };
|
|
239
292
|
|
|
240
|
-
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
293
|
+
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
294
|
+
"code review",
|
|
295
|
+
undefined,
|
|
296
|
+
10,
|
|
297
|
+
undefined,
|
|
298
|
+
);
|
|
241
299
|
const parsed = JSON.parse(result.content[0].text);
|
|
242
300
|
expect(parsed).toHaveLength(1);
|
|
243
301
|
expect(parsed[0].slug).toBe("review-code");
|
|
@@ -248,10 +306,18 @@ describe("MCP server", () => {
|
|
|
248
306
|
|
|
249
307
|
const handler = handlers.get("tools/call")!;
|
|
250
308
|
await handler({
|
|
251
|
-
params: {
|
|
309
|
+
params: {
|
|
310
|
+
name: "search_skills",
|
|
311
|
+
arguments: { query: "deploy", organization: "my-org" },
|
|
312
|
+
},
|
|
252
313
|
});
|
|
253
314
|
|
|
254
|
-
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
315
|
+
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
316
|
+
"deploy",
|
|
317
|
+
undefined,
|
|
318
|
+
10,
|
|
319
|
+
"my-org",
|
|
320
|
+
);
|
|
255
321
|
});
|
|
256
322
|
|
|
257
323
|
it("search_skills throws when query is missing", async () => {
|
|
@@ -311,7 +377,10 @@ describe("MCP server", () => {
|
|
|
311
377
|
|
|
312
378
|
it("list_installed_skills returns discovered skills", async () => {
|
|
313
379
|
const skills = new Map<string, ParsedSkill>();
|
|
314
|
-
skills.set(
|
|
380
|
+
skills.set(
|
|
381
|
+
"code-review",
|
|
382
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
383
|
+
);
|
|
315
384
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
316
385
|
|
|
317
386
|
const handler = handlers.get("tools/call")!;
|
package/src/index.ts
CHANGED
|
@@ -73,37 +73,58 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
73
73
|
tools: [
|
|
74
74
|
{
|
|
75
75
|
name: "search_skills",
|
|
76
|
-
description:
|
|
76
|
+
description:
|
|
77
|
+
"Search the skills-hub.ai catalog for skills matching a query. Supports org-scoped search to include organization-private skills (requires SKILLS_HUB_API_TOKEN).",
|
|
77
78
|
inputSchema: {
|
|
78
79
|
type: "object" as const,
|
|
79
80
|
properties: {
|
|
80
|
-
query: {
|
|
81
|
-
|
|
81
|
+
query: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Search query (e.g. 'code review', 'testing React')",
|
|
84
|
+
},
|
|
85
|
+
category: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description:
|
|
88
|
+
"Filter by category slug (e.g. 'review', 'test', 'build')",
|
|
89
|
+
},
|
|
82
90
|
limit: { type: "number", description: "Max results (default 10)" },
|
|
83
|
-
organization: {
|
|
91
|
+
organization: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description:
|
|
94
|
+
"Organization slug to include org-private skills in results (requires authentication)",
|
|
95
|
+
},
|
|
84
96
|
},
|
|
85
97
|
required: ["query"],
|
|
86
98
|
},
|
|
87
99
|
},
|
|
88
100
|
{
|
|
89
101
|
name: "get_skill_detail",
|
|
90
|
-
description:
|
|
102
|
+
description:
|
|
103
|
+
"Get full details about a skill including instructions, composition children, and metadata",
|
|
91
104
|
inputSchema: {
|
|
92
105
|
type: "object" as const,
|
|
93
106
|
properties: {
|
|
94
|
-
slug: {
|
|
107
|
+
slug: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Skill slug (e.g. 'review-code')",
|
|
110
|
+
},
|
|
95
111
|
},
|
|
96
112
|
required: ["slug"],
|
|
97
113
|
},
|
|
98
114
|
},
|
|
99
115
|
{
|
|
100
116
|
name: "install_skill",
|
|
101
|
-
description:
|
|
117
|
+
description:
|
|
118
|
+
"Install a skill from skills-hub.ai to the local skills directory",
|
|
102
119
|
inputSchema: {
|
|
103
120
|
type: "object" as const,
|
|
104
121
|
properties: {
|
|
105
122
|
slug: { type: "string", description: "Skill slug to install" },
|
|
106
|
-
target: {
|
|
123
|
+
target: {
|
|
124
|
+
type: "string",
|
|
125
|
+
enum: ["claude-code", "cursor"],
|
|
126
|
+
description: "Install target (default: claude-code)",
|
|
127
|
+
},
|
|
107
128
|
},
|
|
108
129
|
required: ["slug"],
|
|
109
130
|
},
|
|
@@ -126,63 +147,88 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
126
147
|
switch (name) {
|
|
127
148
|
case "search_skills": {
|
|
128
149
|
const query = args?.query as string;
|
|
129
|
-
if (!query)
|
|
150
|
+
if (!query)
|
|
151
|
+
throw new McpError(ErrorCode.InvalidParams, "query is required");
|
|
130
152
|
const limit = Math.min(Math.max((args?.limit as number) || 10, 1), 100);
|
|
131
|
-
const results = await searchSkills(
|
|
153
|
+
const results = await searchSkills(
|
|
154
|
+
query,
|
|
155
|
+
args?.category as string | undefined,
|
|
156
|
+
limit,
|
|
157
|
+
args?.organization as string | undefined,
|
|
158
|
+
);
|
|
132
159
|
return {
|
|
133
|
-
content: [
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text" as const,
|
|
163
|
+
text: JSON.stringify(
|
|
164
|
+
results.map((s) => ({
|
|
165
|
+
slug: s.slug,
|
|
166
|
+
name: s.name,
|
|
167
|
+
description: s.description,
|
|
168
|
+
qualityScore: s.qualityScore,
|
|
169
|
+
installCount: s.installCount,
|
|
170
|
+
avgRating: s.avgRating,
|
|
171
|
+
category: s.category.name,
|
|
172
|
+
isComposition: s.isComposition,
|
|
173
|
+
tags: s.tags,
|
|
174
|
+
})),
|
|
175
|
+
null,
|
|
176
|
+
2,
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
147
180
|
};
|
|
148
181
|
}
|
|
149
182
|
|
|
150
183
|
case "get_skill_detail": {
|
|
151
184
|
const slug = args?.slug as string;
|
|
152
|
-
if (!slug)
|
|
185
|
+
if (!slug)
|
|
186
|
+
throw new McpError(ErrorCode.InvalidParams, "slug is required");
|
|
153
187
|
const detail = await getSkillDetail(slug);
|
|
154
188
|
return {
|
|
155
|
-
content: [
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: "text" as const,
|
|
192
|
+
text: JSON.stringify(
|
|
193
|
+
{
|
|
194
|
+
slug: detail.slug,
|
|
195
|
+
name: detail.name,
|
|
196
|
+
description: detail.description,
|
|
197
|
+
qualityScore: detail.qualityScore,
|
|
198
|
+
installCount: detail.installCount,
|
|
199
|
+
avgRating: detail.avgRating,
|
|
200
|
+
latestVersion: detail.latestVersion,
|
|
201
|
+
category: detail.category,
|
|
202
|
+
platforms: detail.platforms,
|
|
203
|
+
tags: detail.tags,
|
|
204
|
+
isComposition: detail.isComposition,
|
|
205
|
+
composition: detail.composition,
|
|
206
|
+
instructionsPreview:
|
|
207
|
+
detail.instructions?.slice(0, 500) +
|
|
208
|
+
(detail.instructions?.length > 500 ? "..." : ""),
|
|
209
|
+
},
|
|
210
|
+
null,
|
|
211
|
+
2,
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
],
|
|
173
215
|
};
|
|
174
216
|
}
|
|
175
217
|
|
|
176
218
|
case "install_skill": {
|
|
177
219
|
const slug = args?.slug as string;
|
|
178
|
-
if (!slug)
|
|
179
|
-
|
|
220
|
+
if (!slug)
|
|
221
|
+
throw new McpError(ErrorCode.InvalidParams, "slug is required");
|
|
222
|
+
const target =
|
|
223
|
+
(args?.target as "claude-code" | "cursor") || "claude-code";
|
|
180
224
|
const result = await installSkill(slug, target);
|
|
181
225
|
return {
|
|
182
|
-
content: [
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: "text" as const,
|
|
229
|
+
text: JSON.stringify(result, null, 2),
|
|
230
|
+
},
|
|
231
|
+
],
|
|
186
232
|
};
|
|
187
233
|
}
|
|
188
234
|
|
|
@@ -196,10 +242,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
196
242
|
category: skill.category || null,
|
|
197
243
|
}));
|
|
198
244
|
return {
|
|
199
|
-
content: [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: "text" as const,
|
|
248
|
+
text: JSON.stringify(list, null, 2),
|
|
249
|
+
},
|
|
250
|
+
],
|
|
203
251
|
};
|
|
204
252
|
}
|
|
205
253
|
|
package/src/installer.test.ts
CHANGED
|
@@ -46,7 +46,10 @@ describe("installSkill", () => {
|
|
|
46
46
|
|
|
47
47
|
const result = await installSkill("review-code");
|
|
48
48
|
|
|
49
|
-
expect(mockMkdirSync).toHaveBeenCalledWith(
|
|
49
|
+
expect(mockMkdirSync).toHaveBeenCalledWith(
|
|
50
|
+
expect.stringContaining("review-code"),
|
|
51
|
+
{ recursive: true },
|
|
52
|
+
);
|
|
50
53
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
51
54
|
expect.stringContaining("SKILL.md"),
|
|
52
55
|
expect.stringContaining("name: Review Code"),
|
|
@@ -56,26 +59,36 @@ describe("installSkill", () => {
|
|
|
56
59
|
expect(result.dependencies).toEqual([]);
|
|
57
60
|
});
|
|
58
61
|
|
|
59
|
-
it("writes
|
|
62
|
+
it("writes SKILL.md and command file", async () => {
|
|
60
63
|
mockGetSkillDetail.mockResolvedValue(baseSkill);
|
|
61
64
|
|
|
62
65
|
await installSkill("review-code");
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(
|
|
69
|
-
expect(
|
|
67
|
+
// SKILL.md (with frontmatter fences) + command .md (without fences)
|
|
68
|
+
expect(mockWriteFileSync).toHaveBeenCalledTimes(2);
|
|
69
|
+
|
|
70
|
+
const skillContent = mockWriteFileSync.mock.calls[0][1] as string;
|
|
71
|
+
expect(skillContent).toContain("---");
|
|
72
|
+
expect(skillContent).toContain("name: Review Code");
|
|
73
|
+
expect(skillContent).toContain("Review all code changes carefully.");
|
|
74
|
+
|
|
75
|
+
const commandPath = mockWriteFileSync.mock.calls[1][0] as string;
|
|
76
|
+
const commandContent = mockWriteFileSync.mock.calls[1][1] as string;
|
|
77
|
+
expect(commandPath).toContain("commands/review-code.md");
|
|
78
|
+
expect(commandContent).not.toContain("---");
|
|
79
|
+
expect(commandContent).toContain("name: Review Code");
|
|
80
|
+
expect(commandContent).toContain("Review all code changes carefully.");
|
|
70
81
|
});
|
|
71
82
|
|
|
72
|
-
it("installs to cursor directory
|
|
83
|
+
it("installs to cursor directory without command file", async () => {
|
|
73
84
|
mockGetSkillDetail.mockResolvedValue(baseSkill);
|
|
74
85
|
|
|
75
86
|
const result = await installSkill("review-code", "cursor");
|
|
76
87
|
|
|
77
88
|
expect(result.path).toContain(".cursor");
|
|
78
89
|
expect(result.path).toContain("skills");
|
|
90
|
+
// Cursor only writes SKILL.md, no command file
|
|
91
|
+
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
|
|
79
92
|
});
|
|
80
93
|
|
|
81
94
|
it("installs composition dependencies", async () => {
|
|
@@ -86,14 +99,36 @@ describe("installSkill", () => {
|
|
|
86
99
|
isComposition: true,
|
|
87
100
|
composition: {
|
|
88
101
|
children: [
|
|
89
|
-
{
|
|
90
|
-
|
|
102
|
+
{
|
|
103
|
+
skill: { slug: "lint-check", name: "Lint Check", qualityScore: 85 },
|
|
104
|
+
sortOrder: 0,
|
|
105
|
+
isParallel: false,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
skill: {
|
|
109
|
+
slug: "test-coverage",
|
|
110
|
+
name: "Test Coverage",
|
|
111
|
+
qualityScore: 80,
|
|
112
|
+
},
|
|
113
|
+
sortOrder: 1,
|
|
114
|
+
isParallel: false,
|
|
115
|
+
},
|
|
91
116
|
],
|
|
92
117
|
},
|
|
93
118
|
};
|
|
94
119
|
|
|
95
|
-
const childSkill1 = {
|
|
96
|
-
|
|
120
|
+
const childSkill1 = {
|
|
121
|
+
...baseSkill,
|
|
122
|
+
slug: "lint-check",
|
|
123
|
+
name: "Lint Check",
|
|
124
|
+
instructions: "Run lint",
|
|
125
|
+
};
|
|
126
|
+
const childSkill2 = {
|
|
127
|
+
...baseSkill,
|
|
128
|
+
slug: "test-coverage",
|
|
129
|
+
name: "Test Coverage",
|
|
130
|
+
instructions: "Check coverage",
|
|
131
|
+
};
|
|
97
132
|
|
|
98
133
|
mockGetSkillDetail
|
|
99
134
|
.mockResolvedValueOnce(parentSkill)
|
|
@@ -103,8 +138,8 @@ describe("installSkill", () => {
|
|
|
103
138
|
const result = await installSkill("full-review");
|
|
104
139
|
|
|
105
140
|
expect(result.dependencies).toEqual(["lint-check", "test-coverage"]);
|
|
106
|
-
// Parent + 2 children = 3 writes
|
|
107
|
-
expect(mockWriteFileSync).toHaveBeenCalledTimes(
|
|
141
|
+
// Parent + 2 children = 3 SKILL.md + 3 command files = 6 writes
|
|
142
|
+
expect(mockWriteFileSync).toHaveBeenCalledTimes(6);
|
|
108
143
|
});
|
|
109
144
|
|
|
110
145
|
it("skips already-installed children", async () => {
|
|
@@ -114,7 +149,11 @@ describe("installSkill", () => {
|
|
|
114
149
|
isComposition: true,
|
|
115
150
|
composition: {
|
|
116
151
|
children: [
|
|
117
|
-
{
|
|
152
|
+
{
|
|
153
|
+
skill: { slug: "lint-check", name: "Lint Check", qualityScore: 85 },
|
|
154
|
+
sortOrder: 0,
|
|
155
|
+
isParallel: false,
|
|
156
|
+
},
|
|
118
157
|
],
|
|
119
158
|
},
|
|
120
159
|
};
|
|
@@ -126,9 +165,9 @@ describe("installSkill", () => {
|
|
|
126
165
|
|
|
127
166
|
const result = await installSkill("full-review");
|
|
128
167
|
|
|
129
|
-
// Only parent installed, child skipped
|
|
168
|
+
// Only parent installed (SKILL.md + command), child skipped
|
|
130
169
|
expect(result.dependencies).toEqual([]);
|
|
131
|
-
expect(mockWriteFileSync).toHaveBeenCalledTimes(
|
|
170
|
+
expect(mockWriteFileSync).toHaveBeenCalledTimes(2);
|
|
132
171
|
});
|
|
133
172
|
|
|
134
173
|
it("continues if a child fails", async () => {
|
|
@@ -138,13 +177,26 @@ describe("installSkill", () => {
|
|
|
138
177
|
isComposition: true,
|
|
139
178
|
composition: {
|
|
140
179
|
children: [
|
|
141
|
-
{
|
|
142
|
-
|
|
180
|
+
{
|
|
181
|
+
skill: { slug: "broken-skill", name: "Broken", qualityScore: null },
|
|
182
|
+
sortOrder: 0,
|
|
183
|
+
isParallel: false,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
skill: { slug: "good-skill", name: "Good", qualityScore: 90 },
|
|
187
|
+
sortOrder: 1,
|
|
188
|
+
isParallel: false,
|
|
189
|
+
},
|
|
143
190
|
],
|
|
144
191
|
},
|
|
145
192
|
};
|
|
146
193
|
|
|
147
|
-
const goodSkill = {
|
|
194
|
+
const goodSkill = {
|
|
195
|
+
...baseSkill,
|
|
196
|
+
slug: "good-skill",
|
|
197
|
+
name: "Good",
|
|
198
|
+
instructions: "Do good",
|
|
199
|
+
};
|
|
148
200
|
|
|
149
201
|
mockGetSkillDetail
|
|
150
202
|
.mockResolvedValueOnce(parentSkill)
|
package/src/installer.ts
CHANGED
|
@@ -24,6 +24,13 @@ function getSkillsDir(target: "claude-code" | "cursor"): string {
|
|
|
24
24
|
: join(home, ".claude", "skills");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function getCommandsDir(target: "claude-code" | "cursor"): string {
|
|
28
|
+
const home = homedir();
|
|
29
|
+
return target === "cursor"
|
|
30
|
+
? join(home, ".cursor", "skills")
|
|
31
|
+
: join(home, ".claude", "commands");
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export interface InstallResult {
|
|
28
35
|
slug: string;
|
|
29
36
|
version: string;
|
|
@@ -54,6 +61,20 @@ ${skill.instructions}
|
|
|
54
61
|
|
|
55
62
|
writeFileSync(join(skillDir, "SKILL.md"), content);
|
|
56
63
|
|
|
64
|
+
// Register as slash command (Claude Code reads from ~/.claude/commands/)
|
|
65
|
+
if (target === "claude-code") {
|
|
66
|
+
const commandsDir = getCommandsDir(target);
|
|
67
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
68
|
+
const commandContent = `name: ${yamlEscape(skill.name)}
|
|
69
|
+
description: ${yamlEscape(skill.description)}
|
|
70
|
+
version: ${yamlEscape(skill.latestVersion)}
|
|
71
|
+
category: ${yamlEscape(skill.category.slug)}
|
|
72
|
+
|
|
73
|
+
${skill.instructions}
|
|
74
|
+
`;
|
|
75
|
+
writeFileSync(join(commandsDir, `${slug}.md`), commandContent);
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
// Install composition dependencies
|
|
58
79
|
const deps: string[] = [];
|
|
59
80
|
if (skill.composition?.children.length) {
|
|
@@ -85,7 +106,11 @@ async function installDependencies(
|
|
|
85
106
|
if (visited.has(childSlug)) continue;
|
|
86
107
|
visited.add(childSlug);
|
|
87
108
|
|
|
88
|
-
try {
|
|
109
|
+
try {
|
|
110
|
+
validateSlug(childSlug);
|
|
111
|
+
} catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
89
114
|
|
|
90
115
|
// Skip if already installed locally
|
|
91
116
|
if (existsSync(join(skillsDir, childSlug, "SKILL.md"))) continue;
|
|
@@ -105,10 +130,30 @@ category: ${yamlEscape(childDetail.category.slug)}
|
|
|
105
130
|
${childDetail.instructions}
|
|
106
131
|
`;
|
|
107
132
|
writeFileSync(join(childDir, "SKILL.md"), content);
|
|
133
|
+
|
|
134
|
+
// Register child as slash command too
|
|
135
|
+
if (target === "claude-code") {
|
|
136
|
+
const commandsDir = getCommandsDir(target);
|
|
137
|
+
const commandContent = `name: ${yamlEscape(childDetail.name)}
|
|
138
|
+
description: ${yamlEscape(childDetail.description)}
|
|
139
|
+
version: ${yamlEscape(childDetail.latestVersion)}
|
|
140
|
+
category: ${yamlEscape(childDetail.category.slug)}
|
|
141
|
+
|
|
142
|
+
${childDetail.instructions}
|
|
143
|
+
`;
|
|
144
|
+
writeFileSync(join(commandsDir, `${childSlug}.md`), commandContent);
|
|
145
|
+
}
|
|
146
|
+
|
|
108
147
|
installed.push(childSlug);
|
|
109
148
|
|
|
110
149
|
// Recurse if this child is also a composition
|
|
111
|
-
await installDependencies(
|
|
150
|
+
await installDependencies(
|
|
151
|
+
childDetail,
|
|
152
|
+
target,
|
|
153
|
+
installed,
|
|
154
|
+
visited,
|
|
155
|
+
depth + 1,
|
|
156
|
+
);
|
|
112
157
|
} catch {
|
|
113
158
|
// Skip failed children, don't block the parent install
|
|
114
159
|
}
|