@skills-hub-ai/mcp 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +3 -3
- 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 +87 -22
- 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":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,cAAc,EAA0B,MAAM,UAAU,CAAC;AAElE,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;IAClE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAgC;IACpD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,MAAM,KAAK,QAAQ;QACxB,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AASD,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,SAAmC,aAAa;IAEhD,YAAY,CAAC,IAAI,CAAC,CAAC;IACnB,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IAElD,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,OAAO,GAAG;QACV,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC;eACf,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC;WACjC,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC;YAC9B,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;;;EAGzC,KAAK,CAAC,YAAY;CACnB,CAAC;IAEA,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;IAEnD,mCAAmC;IACnC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACvC,MAAM,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,KAAK,CAAC,aAAa;QAC5B,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,IAAI;KACnB,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,KAAK,UAAU,mBAAmB,CAChC,KAAwB,EACxB,MAAgC,EAChC,SAAmB,EACnB,UAAU,IAAI,GAAG,EAAU,EAC3B,KAAK,GAAG,CAAC;IAET,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM;QAAE,OAAO;IAE7D,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,SAAS;QACrC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEvB,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skills-hub-ai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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": [
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
-
"@skills-hub-ai/skill-parser": "0.2.
|
|
31
|
+
"@skills-hub-ai/skill-parser": "0.2.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
34
|
+
"@types/node": "^25.5.0",
|
|
35
35
|
"tsx": "^4.0.0",
|
|
36
36
|
"typescript": "^5.7.0",
|
|
37
37
|
"vitest": "^2.0.0"
|
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
|
@@ -19,9 +19,14 @@ vi.mock("@modelcontextprotocol/sdk/server/index.js", () => {
|
|
|
19
19
|
const handlers = new Map<string, (req: unknown) => Promise<unknown>>();
|
|
20
20
|
return {
|
|
21
21
|
Server: vi.fn().mockImplementation(() => ({
|
|
22
|
-
setRequestHandler: vi.fn(
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
setRequestHandler: vi.fn(
|
|
23
|
+
(
|
|
24
|
+
schema: { method: string },
|
|
25
|
+
handler: (req: unknown) => Promise<unknown>,
|
|
26
|
+
) => {
|
|
27
|
+
handlers.set(schema.method, handler);
|
|
28
|
+
},
|
|
29
|
+
),
|
|
25
30
|
connect: vi.fn(),
|
|
26
31
|
_handlers: handlers,
|
|
27
32
|
})),
|
|
@@ -37,7 +42,12 @@ vi.mock("@modelcontextprotocol/sdk/types.js", () => ({
|
|
|
37
42
|
GetPromptRequestSchema: { method: "prompts/get" },
|
|
38
43
|
ListToolsRequestSchema: { method: "tools/list" },
|
|
39
44
|
CallToolRequestSchema: { method: "tools/call" },
|
|
40
|
-
ErrorCode: {
|
|
45
|
+
ErrorCode: {
|
|
46
|
+
InvalidRequest: -32600,
|
|
47
|
+
InvalidParams: -32602,
|
|
48
|
+
MethodNotFound: -32601,
|
|
49
|
+
InternalError: -32603,
|
|
50
|
+
},
|
|
41
51
|
McpError: class McpError extends Error {
|
|
42
52
|
code: number;
|
|
43
53
|
constructor(code: number, message: string) {
|
|
@@ -57,7 +67,11 @@ const mockSearchSkills = vi.mocked(searchSkills);
|
|
|
57
67
|
const mockGetSkillDetail = vi.mocked(getSkillDetail);
|
|
58
68
|
const mockInstallSkill = vi.mocked(installSkill);
|
|
59
69
|
|
|
60
|
-
function makeSkill(
|
|
70
|
+
function makeSkill(
|
|
71
|
+
name: string,
|
|
72
|
+
description: string,
|
|
73
|
+
instructions: string,
|
|
74
|
+
): ParsedSkill {
|
|
61
75
|
return {
|
|
62
76
|
name,
|
|
63
77
|
description,
|
|
@@ -97,9 +111,14 @@ beforeEach(async () => {
|
|
|
97
111
|
handlers = h;
|
|
98
112
|
return {
|
|
99
113
|
Server: vi.fn().mockImplementation(() => ({
|
|
100
|
-
setRequestHandler: vi.fn(
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
setRequestHandler: vi.fn(
|
|
115
|
+
(
|
|
116
|
+
schema: { method: string },
|
|
117
|
+
handler: (req: unknown) => Promise<unknown>,
|
|
118
|
+
) => {
|
|
119
|
+
h.set(schema.method, handler);
|
|
120
|
+
},
|
|
121
|
+
),
|
|
103
122
|
connect: vi.fn(),
|
|
104
123
|
})),
|
|
105
124
|
};
|
|
@@ -114,7 +133,12 @@ beforeEach(async () => {
|
|
|
114
133
|
GetPromptRequestSchema: { method: "prompts/get" },
|
|
115
134
|
ListToolsRequestSchema: { method: "tools/list" },
|
|
116
135
|
CallToolRequestSchema: { method: "tools/call" },
|
|
117
|
-
ErrorCode: {
|
|
136
|
+
ErrorCode: {
|
|
137
|
+
InvalidRequest: -32600,
|
|
138
|
+
InvalidParams: -32602,
|
|
139
|
+
MethodNotFound: -32601,
|
|
140
|
+
InternalError: -32603,
|
|
141
|
+
},
|
|
118
142
|
McpError: class McpError extends Error {
|
|
119
143
|
code: number;
|
|
120
144
|
constructor(code: number, message: string) {
|
|
@@ -138,12 +162,20 @@ describe("MCP server", () => {
|
|
|
138
162
|
|
|
139
163
|
it("listPrompts returns discovered skills", async () => {
|
|
140
164
|
const skills = new Map<string, ParsedSkill>();
|
|
141
|
-
skills.set(
|
|
142
|
-
|
|
165
|
+
skills.set(
|
|
166
|
+
"code-review",
|
|
167
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
168
|
+
);
|
|
169
|
+
skills.set(
|
|
170
|
+
"security-scan",
|
|
171
|
+
makeSkill("Security Scan", "Find vulnerabilities", "Check OWASP"),
|
|
172
|
+
);
|
|
143
173
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
144
174
|
|
|
145
175
|
const handler = handlers.get("prompts/list")!;
|
|
146
|
-
const result = (await handler({})) as {
|
|
176
|
+
const result = (await handler({})) as {
|
|
177
|
+
prompts: Array<{ name: string; description: string }>;
|
|
178
|
+
};
|
|
147
179
|
|
|
148
180
|
expect(result.prompts).toHaveLength(2);
|
|
149
181
|
expect(result.prompts[0].name).toBe("code-review");
|
|
@@ -162,13 +194,22 @@ describe("MCP server", () => {
|
|
|
162
194
|
|
|
163
195
|
it("getPrompt returns skill instructions", async () => {
|
|
164
196
|
const skills = new Map<string, ParsedSkill>();
|
|
165
|
-
skills.set(
|
|
197
|
+
skills.set(
|
|
198
|
+
"code-review",
|
|
199
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
200
|
+
);
|
|
166
201
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
167
202
|
|
|
168
203
|
const handler = handlers.get("prompts/get")!;
|
|
169
204
|
const result = (await handler({
|
|
170
205
|
params: { name: "code-review", arguments: {} },
|
|
171
|
-
})) as {
|
|
206
|
+
})) as {
|
|
207
|
+
description: string;
|
|
208
|
+
messages: Array<{
|
|
209
|
+
role: string;
|
|
210
|
+
content: { type: string; text: string };
|
|
211
|
+
}>;
|
|
212
|
+
};
|
|
172
213
|
|
|
173
214
|
expect(result.description).toBe("Review code quality");
|
|
174
215
|
expect(result.messages).toHaveLength(1);
|
|
@@ -178,15 +219,23 @@ describe("MCP server", () => {
|
|
|
178
219
|
|
|
179
220
|
it("getPrompt appends input argument to instructions", async () => {
|
|
180
221
|
const skills = new Map<string, ParsedSkill>();
|
|
181
|
-
skills.set(
|
|
222
|
+
skills.set(
|
|
223
|
+
"code-review",
|
|
224
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
225
|
+
);
|
|
182
226
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
183
227
|
|
|
184
228
|
const handler = handlers.get("prompts/get")!;
|
|
185
229
|
const result = (await handler({
|
|
186
|
-
params: {
|
|
230
|
+
params: {
|
|
231
|
+
name: "code-review",
|
|
232
|
+
arguments: { input: "Focus on error handling" },
|
|
233
|
+
},
|
|
187
234
|
})) as { messages: Array<{ content: { text: string } }> };
|
|
188
235
|
|
|
189
|
-
expect(result.messages[0].content.text).toBe(
|
|
236
|
+
expect(result.messages[0].content.text).toBe(
|
|
237
|
+
"Review carefully\n\nFocus on error handling",
|
|
238
|
+
);
|
|
190
239
|
});
|
|
191
240
|
|
|
192
241
|
it("getPrompt throws McpError for unknown skill", async () => {
|
|
@@ -196,7 +245,7 @@ describe("MCP server", () => {
|
|
|
196
245
|
|
|
197
246
|
await expect(
|
|
198
247
|
handler({ params: { name: "nonexistent", arguments: {} } }),
|
|
199
|
-
).rejects.toThrow(
|
|
248
|
+
).rejects.toThrow("Unknown skill: nonexistent");
|
|
200
249
|
});
|
|
201
250
|
|
|
202
251
|
it("registers tools/list and tools/call handlers", () => {
|
|
@@ -237,7 +286,12 @@ describe("MCP server", () => {
|
|
|
237
286
|
params: { name: "search_skills", arguments: { query: "code review" } },
|
|
238
287
|
})) as { content: Array<{ text: string }> };
|
|
239
288
|
|
|
240
|
-
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
289
|
+
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
290
|
+
"code review",
|
|
291
|
+
undefined,
|
|
292
|
+
10,
|
|
293
|
+
undefined,
|
|
294
|
+
);
|
|
241
295
|
const parsed = JSON.parse(result.content[0].text);
|
|
242
296
|
expect(parsed).toHaveLength(1);
|
|
243
297
|
expect(parsed[0].slug).toBe("review-code");
|
|
@@ -248,10 +302,18 @@ describe("MCP server", () => {
|
|
|
248
302
|
|
|
249
303
|
const handler = handlers.get("tools/call")!;
|
|
250
304
|
await handler({
|
|
251
|
-
params: {
|
|
305
|
+
params: {
|
|
306
|
+
name: "search_skills",
|
|
307
|
+
arguments: { query: "deploy", organization: "my-org" },
|
|
308
|
+
},
|
|
252
309
|
});
|
|
253
310
|
|
|
254
|
-
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
311
|
+
expect(mockSearchSkills).toHaveBeenCalledWith(
|
|
312
|
+
"deploy",
|
|
313
|
+
undefined,
|
|
314
|
+
10,
|
|
315
|
+
"my-org",
|
|
316
|
+
);
|
|
255
317
|
});
|
|
256
318
|
|
|
257
319
|
it("search_skills throws when query is missing", async () => {
|
|
@@ -311,7 +373,10 @@ describe("MCP server", () => {
|
|
|
311
373
|
|
|
312
374
|
it("list_installed_skills returns discovered skills", async () => {
|
|
313
375
|
const skills = new Map<string, ParsedSkill>();
|
|
314
|
-
skills.set(
|
|
376
|
+
skills.set(
|
|
377
|
+
"code-review",
|
|
378
|
+
makeSkill("Code Review", "Review code quality", "Review carefully"),
|
|
379
|
+
);
|
|
315
380
|
mockDiscoverSkills.mockReturnValue(skills);
|
|
316
381
|
|
|
317
382
|
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
|
}
|