ring-skills-mcp 1.0.3 → 1.0.5

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.
Files changed (2) hide show
  1. package/dist/index.js +126 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -52,19 +52,97 @@ async function fetchSkills(host, search = "", pageSize = 5000, token = "") {
52
52
  throw new Error("Failed to fetch Skills list: Unknown error");
53
53
  }
54
54
  }
55
+ function parseGitUrl(gitUrl) {
56
+ // GitHub URL pattern: https://github.com/{owner}/{repo}/tree/{branch}/{path}
57
+ const githubMatch = gitUrl.match(/https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
58
+ if (githubMatch) {
59
+ const skillPath = githubMatch[4];
60
+ const skillName = skillPath.split("/").pop() || "";
61
+ return {
62
+ type: "github",
63
+ host: "github.com",
64
+ owner: githubMatch[1],
65
+ repo: githubMatch[2],
66
+ branch: githubMatch[3],
67
+ skillPath,
68
+ skillName: skillName.toLowerCase(),
69
+ };
70
+ }
71
+ // GitLab URL pattern: https://{host}/{owner}/{repo}/-/tree/{branch}/{path}
72
+ const gitlabMatch = gitUrl.match(/https?:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/-\/tree\/([^\/]+)\/(.+)/);
73
+ if (gitlabMatch) {
74
+ const skillPath = gitlabMatch[5];
75
+ const skillName = skillPath.split("/").pop() || "";
76
+ return {
77
+ type: "gitlab",
78
+ host: gitlabMatch[1],
79
+ owner: gitlabMatch[2],
80
+ repo: gitlabMatch[3],
81
+ branch: gitlabMatch[4],
82
+ skillPath,
83
+ skillName: skillName.toLowerCase(),
84
+ };
85
+ }
86
+ // Fallback: try to extract skill name from URL
87
+ const skillMatch = gitUrl.match(/\/skills\/([^\/\s]+)/);
88
+ if (skillMatch) {
89
+ return {
90
+ type: "unknown",
91
+ host: "",
92
+ owner: "",
93
+ repo: "",
94
+ branch: "main",
95
+ skillPath: "",
96
+ skillName: skillMatch[1].toLowerCase(),
97
+ };
98
+ }
99
+ return null;
100
+ }
55
101
  /**
56
- * Extract skill name from gitUrl
57
- * Example: "https://github.com/anthropics/skills/tree/main/skills/webapp-testing" -> "webapp-testing"
102
+ * Build degit source path from git URL info
58
103
  */
59
- function extractSkillNameFromGitUrl(gitUrl) {
60
- // Match pattern: .../skills/{skill-name} or .../skills/{skill-name}/...
61
- const match = gitUrl.match(/\/skills\/([^\/\s]+)/);
62
- return match ? match[1].toLowerCase() : null;
104
+ function buildDegitSource(urlInfo) {
105
+ if (urlInfo.type === "github") {
106
+ // GitHub: owner/repo/path#branch
107
+ return `${urlInfo.owner}/${urlInfo.repo}/${urlInfo.skillPath}#${urlInfo.branch}`;
108
+ }
109
+ else if (urlInfo.type === "gitlab") {
110
+ // GitLab (self-hosted): host:owner/repo/path#branch
111
+ return `${urlInfo.host}:${urlInfo.owner}/${urlInfo.repo}/${urlInfo.skillPath}#${urlInfo.branch}`;
112
+ }
113
+ return "";
63
114
  }
64
115
  /**
65
- * Install Skill to specified project
116
+ * Install Skill to specified project using gitUrl
66
117
  */
67
- async function installSkill(skillName, projectPath, targetDir = "skills", repo = DEFAULT_SKILLS_REPO) {
118
+ async function installSkillFromGitUrl(gitUrl, projectPath, targetDir = ".claude/skills") {
119
+ const urlInfo = parseGitUrl(gitUrl);
120
+ if (!urlInfo) {
121
+ throw new Error(`Could not parse git URL: ${gitUrl}`);
122
+ }
123
+ const skillName = urlInfo.skillName;
124
+ const destPath = path.join(projectPath, targetDir, skillName);
125
+ const sourcePath = buildDegitSource(urlInfo);
126
+ if (!sourcePath) {
127
+ throw new Error(`Could not build degit source from URL: ${gitUrl}`);
128
+ }
129
+ const command = `npx degit ${sourcePath} ${destPath}`;
130
+ try {
131
+ const { stdout, stderr } = await execAsync(command);
132
+ const output = stdout || stderr || "Installation completed";
133
+ return `✅ Skill "${skillName}" has been successfully installed to ${destPath}\n\nSource: ${sourcePath}\n${output}`;
134
+ }
135
+ catch (error) {
136
+ if (error instanceof Error) {
137
+ throw new Error(`Failed to install Skill: ${error.message}`);
138
+ }
139
+ throw new Error("Failed to install Skill: Unknown error");
140
+ }
141
+ }
142
+ /**
143
+ * Install Skill to specified project using skill name and repo
144
+ */
145
+ async function installSkillFromRepo(skillName, projectPath, targetDir = ".claude/skills", repo = DEFAULT_SKILLS_REPO) {
68
146
  // Convert skill name to lowercase
69
147
  const normalizedName = skillName.toLowerCase();
70
148
  // Build full installation path
@@ -164,7 +242,7 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
164
242
  targetDir: z
165
243
  .string()
166
244
  .optional()
167
- .describe("Installation target directory, default: skills"),
245
+ .describe("Installation target directory, default: .claude/skills"),
168
246
  repo: z
169
247
  .string()
170
248
  .optional()
@@ -176,23 +254,32 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
176
254
  content: [
177
255
  {
178
256
  type: "text",
179
- text: "❌ Please provide project path (projectPath).\n\nYou can provide the project root directory path of the currently opened file in IDE, skill will be installed to the skills directory of that project.\n\nExample: /Users/xxx/my-project",
257
+ text: "❌ Please provide project path (projectPath).\n\nYou can provide the project root directory path of the currently opened file in IDE, skill will be installed to the .claude/skills directory of that project.\n\nExample: /Users/xxx/my-project",
180
258
  },
181
259
  ],
182
260
  isError: true,
183
261
  };
184
262
  }
185
- // Determine the actual skill name to use
186
- let actualSkillName = null;
187
263
  // Priority: gitUrl > skillName
188
264
  if (gitUrl) {
189
- actualSkillName = extractSkillNameFromGitUrl(gitUrl);
190
- if (!actualSkillName) {
265
+ // Install from gitUrl (supports GitHub and GitLab)
266
+ try {
267
+ const result = await installSkillFromGitUrl(gitUrl, projectPath, targetDir || ".claude/skills");
191
268
  return {
192
269
  content: [
193
270
  {
194
271
  type: "text",
195
- text: `❌ Error: Could not extract skill name from gitUrl: ${gitUrl}\n\nExpected format: https://github.com/org/repo/tree/main/skills/{skill-name}`,
272
+ text: result,
273
+ },
274
+ ],
275
+ };
276
+ }
277
+ catch (error) {
278
+ return {
279
+ content: [
280
+ {
281
+ type: "text",
282
+ text: `❌ Error: ${error instanceof Error ? error.message : "Unknown error"}`,
196
283
  },
197
284
  ],
198
285
  isError: true,
@@ -200,9 +287,31 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
200
287
  }
201
288
  }
202
289
  else if (skillName) {
203
- actualSkillName = skillName;
290
+ // Install from repo using skill name
291
+ try {
292
+ const result = await installSkillFromRepo(skillName, projectPath, targetDir || ".claude/skills", repo || DEFAULT_SKILLS_REPO);
293
+ return {
294
+ content: [
295
+ {
296
+ type: "text",
297
+ text: result,
298
+ },
299
+ ],
300
+ };
301
+ }
302
+ catch (error) {
303
+ return {
304
+ content: [
305
+ {
306
+ type: "text",
307
+ text: `❌ Error: ${error instanceof Error ? error.message : "Unknown error"}`,
308
+ },
309
+ ],
310
+ isError: true,
311
+ };
312
+ }
204
313
  }
205
- if (!actualSkillName) {
314
+ else {
206
315
  return {
207
316
  content: [
208
317
  {
@@ -213,28 +322,6 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
213
322
  isError: true,
214
323
  };
215
324
  }
216
- try {
217
- const result = await installSkill(actualSkillName, projectPath, targetDir || "skills", repo || DEFAULT_SKILLS_REPO);
218
- return {
219
- content: [
220
- {
221
- type: "text",
222
- text: result,
223
- },
224
- ],
225
- };
226
- }
227
- catch (error) {
228
- return {
229
- content: [
230
- {
231
- type: "text",
232
- text: `❌ Error: ${error instanceof Error ? error.message : "Unknown error"}`,
233
- },
234
- ],
235
- isError: true,
236
- };
237
- }
238
325
  });
239
326
  // Start server
240
327
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ring-skills-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "MCP service for fetching and installing company Skills",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",