@mcp-s/skills 1.0.5 → 1.3.1

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/dist/index.js DELETED
@@ -1,495 +0,0 @@
1
- #!/usr/bin/env node
2
- import { program } from "commander";
3
- import * as p from "@clack/prompts";
4
- import chalk from "chalk";
5
- import { homedir } from "os";
6
- import { fetchSkills } from "./api.js";
7
- import { detectInstalledAgents, agents } from "./agents.js";
8
- import { installSkillForAgent, isSkillInstalled, getCanonicalPath, } from "./installer.js";
9
- import { getOrCreateAuth, refreshToken, authenticate, saveAuthConfig, } from "./auth.js";
10
- function shortenPath(fullPath, cwd) {
11
- const home = homedir();
12
- if (fullPath.startsWith(home)) {
13
- return fullPath.replace(home, "~");
14
- }
15
- if (fullPath.startsWith(cwd)) {
16
- return "." + fullPath.slice(cwd.length);
17
- }
18
- return fullPath;
19
- }
20
- function formatList(items, maxShow = 5) {
21
- if (items.length <= maxShow) {
22
- return items.join(", ");
23
- }
24
- const shown = items.slice(0, maxShow);
25
- const remaining = items.length - maxShow;
26
- return `${shown.join(", ")} +${remaining} more`;
27
- }
28
- function buildBaseUrl(org, baseUrl) {
29
- if (baseUrl) {
30
- return baseUrl;
31
- }
32
- if (org) {
33
- return `https://${org}.mcp-s.com/skills`;
34
- }
35
- throw new Error("Either --org or --base-url must be provided");
36
- }
37
- program
38
- .name("skills")
39
- .description("Install skills onto coding agents from MCP-S server (Claude Code, Cline, Codex, Cursor, and more)")
40
- .version("1.0.0")
41
- .option("-o, --org <org>", "Organization name (builds URL as https://<org>.mcp-s.com/skills)")
42
- .option("-b, --base-url <url>", "Full base URL of the MCP-S skills server")
43
- .option("-g, --global", "Install skill globally (user-level) instead of project-level")
44
- .option("-a, --agent <agents...>", "Specify agents to install to (claude-code, cline, codex, cursor, and more)")
45
- .option("-s, --skill <skills...>", "Specify skill slugs to install (skip selection prompt)")
46
- .option("-l, --list", "List available skills without installing")
47
- .option("-y, --yes", "Skip confirmation prompts")
48
- .option("--all", "Install all skills to all agents without any prompts (implies -y -g)")
49
- .option("-k, --key <userAccessKey>", "Your MCP-S user access key (found in your MCP-S dashboard)")
50
- .configureOutput({
51
- outputError: (str, write) => {
52
- if (str.includes("error: required option")) {
53
- console.log();
54
- console.log(chalk.bgRed.white.bold(" ERROR ") +
55
- " " +
56
- chalk.red("Missing required option: --org or --base-url"));
57
- console.log();
58
- console.log(chalk.dim(" Usage:"));
59
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--org <org>")} ${chalk.dim("[options]")}`);
60
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--base-url <url>")} ${chalk.dim("[options]")}`);
61
- console.log();
62
- console.log(chalk.dim(" Examples:"));
63
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--org mycompany")}`);
64
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--base-url https://custom.example.com/skills")}`);
65
- console.log();
66
- console.log(chalk.dim(" Run") +
67
- ` ${chalk.cyan("npx @mcp-s/skills --help")}` +
68
- chalk.dim(" for more information."));
69
- console.log();
70
- }
71
- else {
72
- write(str);
73
- }
74
- },
75
- })
76
- .action(async (options) => {
77
- await main(options);
78
- });
79
- program.parse();
80
- async function main(options) {
81
- var _a;
82
- // Validate that either org or baseUrl is provided
83
- if (!options.org && !options.baseUrl) {
84
- console.log();
85
- console.log(chalk.bgRed.white.bold(" ERROR ") +
86
- " " +
87
- chalk.red("Missing required option: --org or --base-url"));
88
- console.log();
89
- console.log(chalk.dim(" Usage:"));
90
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--org <org>")} ${chalk.dim("[options]")}`);
91
- console.log(` ${chalk.cyan("npx @mcp-s/skills")} ${chalk.yellow("--base-url <url>")} ${chalk.dim("[options]")}`);
92
- console.log();
93
- process.exit(1);
94
- }
95
- if (options.all) {
96
- options.yes = true;
97
- options.global = true;
98
- }
99
- console.log();
100
- p.intro(chalk.bgCyan.black(" MCP-S Skills "));
101
- const spinner = p.spinner();
102
- const baseUrl = buildBaseUrl(options.org, options.baseUrl);
103
- try {
104
- // Get or create authentication
105
- spinner.start("Checking authentication...");
106
- let auth;
107
- try {
108
- auth = await getOrCreateAuth(options.org, options.key);
109
- }
110
- catch (error) {
111
- spinner.stop(chalk.red("Authentication required"));
112
- if (error instanceof Error && error.message.includes("No user access key")) {
113
- p.log.error(error.message);
114
- p.log.info(`You can find your user access key in your MCP-S dashboard at ${chalk.cyan("https://<org>.mcp-s.com/settings")}`);
115
- process.exit(1);
116
- }
117
- throw error;
118
- }
119
- spinner.stop(auth.isNew
120
- ? "New authentication created"
121
- : "Using existing authentication");
122
- // Try to fetch skills
123
- spinner.start("Fetching skills from server...");
124
- let skills;
125
- let needsAuth = false;
126
- try {
127
- skills = await fetchSkills(baseUrl, auth);
128
- }
129
- catch (error) {
130
- if (error &&
131
- typeof error === "object" &&
132
- "status" in error &&
133
- error.status === 401) {
134
- needsAuth = true;
135
- skills = [];
136
- }
137
- else {
138
- throw error;
139
- }
140
- }
141
- // Handle authentication if needed
142
- if (needsAuth || auth.isNew) {
143
- spinner.stop("Authentication required");
144
- p.log.info("You need to authenticate to access skills.");
145
- // Generate new token and authenticate
146
- const newAuth = await refreshToken(options.org, options.key);
147
- auth = Object.assign(Object.assign({}, newAuth), { isNew: true });
148
- const { url, opened } = await authenticate({
149
- baseUrl,
150
- userAccessKey: auth.userAccessKey,
151
- userOTT: auth.userOTT,
152
- openBrowser: true,
153
- });
154
- if (opened) {
155
- p.log.info("Opening authentication page in your browser...");
156
- }
157
- else {
158
- p.log.info("Please open this URL in your browser to authenticate:");
159
- p.log.message(chalk.cyan(url));
160
- }
161
- // Wait for user confirmation
162
- const confirmed = await p.confirm({
163
- message: "Have you completed authentication in the browser?",
164
- });
165
- if (p.isCancel(confirmed) || !confirmed) {
166
- p.cancel("Authentication cancelled");
167
- process.exit(0);
168
- }
169
- // Save the auth config
170
- await saveAuthConfig({
171
- userAccessKey: auth.userAccessKey,
172
- userOTT: auth.userOTT,
173
- org: options.org,
174
- });
175
- // Retry fetching skills
176
- spinner.start("Fetching skills from server...");
177
- try {
178
- skills = await fetchSkills(baseUrl, auth);
179
- }
180
- catch (error) {
181
- if (error &&
182
- typeof error === "object" &&
183
- "status" in error &&
184
- error.status === 401) {
185
- spinner.stop(chalk.red("Authentication failed"));
186
- p.log.error("Authentication failed. Please try again or check your credentials.");
187
- process.exit(1);
188
- }
189
- throw error;
190
- }
191
- }
192
- spinner.stop(`Found ${skills.length} skill${skills.length !== 1 ? "s" : ""}`);
193
- if (skills.length === 0) {
194
- p.log.warn("No skills available on this server.");
195
- p.outro(chalk.yellow("Nothing to install."));
196
- process.exit(0);
197
- }
198
- // List mode
199
- if (options.list) {
200
- console.log();
201
- p.log.step(chalk.bold("Available Skills"));
202
- for (const skill of skills) {
203
- p.log.message(` ${chalk.cyan(skill.slug || skill.name)} - ${chalk.dim(skill.description)}`);
204
- }
205
- console.log();
206
- p.outro("Run without --list to install");
207
- process.exit(0);
208
- }
209
- // Select skills
210
- let selectedSkills;
211
- if (options.skill && options.skill.length > 0) {
212
- // Filter by specified skill slugs
213
- selectedSkills = skills.filter((s) => options.skill.includes(s.slug) || options.skill.includes(s.name));
214
- if (selectedSkills.length === 0) {
215
- p.log.error(`No matching skills found for: ${options.skill.join(", ")}`);
216
- p.log.info(`Available skills: ${skills.map((s) => s.slug || s.name).join(", ")}`);
217
- process.exit(1);
218
- }
219
- }
220
- else if (options.all) {
221
- selectedSkills = skills;
222
- }
223
- else {
224
- // Interactive selection
225
- const skillChoices = skills.map((s) => ({
226
- value: s,
227
- label: s.name,
228
- hint: s.description,
229
- }));
230
- const selected = await p.multiselect({
231
- message: "Select skills to install",
232
- options: skillChoices,
233
- required: true,
234
- });
235
- if (p.isCancel(selected)) {
236
- p.cancel("Installation cancelled");
237
- process.exit(0);
238
- }
239
- selectedSkills = selected;
240
- }
241
- p.log.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${chalk.cyan(selectedSkills.map((s) => s.slug || s.name).join(", "))}`);
242
- // Select agents
243
- let targetAgents;
244
- const validAgents = Object.keys(agents);
245
- if (options.agent && options.agent.length > 0) {
246
- const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
247
- if (invalidAgents.length > 0) {
248
- p.log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
249
- p.log.info(`Valid agents: ${validAgents.join(", ")}`);
250
- process.exit(1);
251
- }
252
- targetAgents = options.agent;
253
- }
254
- else {
255
- spinner.start("Detecting installed agents...");
256
- const installedAgents = await detectInstalledAgents();
257
- spinner.stop(`Detected ${installedAgents.length} agent${installedAgents.length !== 1 ? "s" : ""}`);
258
- if (installedAgents.length === 0) {
259
- if (options.yes) {
260
- targetAgents = validAgents;
261
- p.log.info("Installing to all agents (none detected)");
262
- }
263
- else {
264
- p.log.warn("No coding agents detected. You can still install skills.");
265
- const allAgentChoices = Object.entries(agents).map(([key, config]) => ({
266
- value: key,
267
- label: config.displayName,
268
- }));
269
- const selected = await p.multiselect({
270
- message: "Select agents to install skills to",
271
- options: allAgentChoices,
272
- required: true,
273
- initialValues: Object.keys(agents),
274
- });
275
- if (p.isCancel(selected)) {
276
- p.cancel("Installation cancelled");
277
- process.exit(0);
278
- }
279
- targetAgents = selected;
280
- }
281
- }
282
- else if (installedAgents.length === 1 || options.yes) {
283
- targetAgents = installedAgents;
284
- if (installedAgents.length === 1) {
285
- const firstAgent = installedAgents[0];
286
- p.log.info(`Installing to: ${chalk.cyan(agents[firstAgent].displayName)}`);
287
- }
288
- else {
289
- p.log.info(`Installing to: ${installedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ")}`);
290
- }
291
- }
292
- else {
293
- const agentChoices = installedAgents.map((a) => ({
294
- value: a,
295
- label: agents[a].displayName,
296
- hint: `${options.global ? agents[a].globalSkillsDir : agents[a].skillsDir}`,
297
- }));
298
- const selected = await p.multiselect({
299
- message: "Select agents to install skills to",
300
- options: agentChoices,
301
- required: true,
302
- initialValues: installedAgents,
303
- });
304
- if (p.isCancel(selected)) {
305
- p.cancel("Installation cancelled");
306
- process.exit(0);
307
- }
308
- targetAgents = selected;
309
- }
310
- }
311
- // Installation scope
312
- let installGlobally = (_a = options.global) !== null && _a !== void 0 ? _a : false;
313
- if (options.global === undefined && !options.yes) {
314
- const scope = await p.select({
315
- message: "Installation scope",
316
- options: [
317
- {
318
- value: false,
319
- label: "Project",
320
- hint: "Install in current directory (committed with your project)",
321
- },
322
- {
323
- value: true,
324
- label: "Global",
325
- hint: "Install in home directory (available across all projects)",
326
- },
327
- ],
328
- });
329
- if (p.isCancel(scope)) {
330
- p.cancel("Installation cancelled");
331
- process.exit(0);
332
- }
333
- installGlobally = scope;
334
- }
335
- // Installation method
336
- let installMode = "symlink";
337
- if (!options.yes) {
338
- const modeChoice = await p.select({
339
- message: "Installation method",
340
- options: [
341
- {
342
- value: "symlink",
343
- label: "Symlink (Recommended)",
344
- hint: "Single source of truth, easy updates",
345
- },
346
- {
347
- value: "copy",
348
- label: "Copy to all agents",
349
- hint: "Independent copies for each agent",
350
- },
351
- ],
352
- });
353
- if (p.isCancel(modeChoice)) {
354
- p.cancel("Installation cancelled");
355
- process.exit(0);
356
- }
357
- installMode = modeChoice;
358
- }
359
- // Check for existing installations
360
- const cwd = process.cwd();
361
- const overwriteStatus = new Map();
362
- for (const skill of selectedSkills) {
363
- const skillStatus = new Map();
364
- for (const agent of targetAgents) {
365
- skillStatus.set(agent, await isSkillInstalled(skill.slug || skill.name, agent, {
366
- global: installGlobally,
367
- }));
368
- }
369
- overwriteStatus.set(skill.slug || skill.name, skillStatus);
370
- }
371
- // Show summary
372
- const summaryLines = [];
373
- const agentNames = targetAgents.map((a) => agents[a].displayName);
374
- for (const skill of selectedSkills) {
375
- const skillName = skill.slug || skill.name;
376
- if (installMode === "symlink") {
377
- const canonicalPath = getCanonicalPath(skillName, {
378
- global: installGlobally,
379
- });
380
- const shortCanonical = shortenPath(canonicalPath, cwd);
381
- summaryLines.push(`${chalk.cyan(skillName)}`);
382
- summaryLines.push(` ${chalk.dim(shortCanonical)}`);
383
- summaryLines.push(` ${chalk.dim("symlink →")} ${formatList(agentNames)}`);
384
- }
385
- else {
386
- summaryLines.push(`${chalk.cyan(skillName)}`);
387
- summaryLines.push(` ${chalk.dim("copy →")} ${formatList(agentNames)}`);
388
- }
389
- const skillOverwrites = overwriteStatus.get(skillName);
390
- const overwriteAgents = targetAgents
391
- .filter((a) => skillOverwrites === null || skillOverwrites === void 0 ? void 0 : skillOverwrites.get(a))
392
- .map((a) => agents[a].displayName);
393
- if (overwriteAgents.length > 0) {
394
- summaryLines.push(` ${chalk.yellow("overwrites:")} ${formatList(overwriteAgents)}`);
395
- }
396
- }
397
- console.log();
398
- p.note(summaryLines.join("\n"), "Installation Summary");
399
- // Confirm
400
- if (!options.yes) {
401
- const confirmed = await p.confirm({
402
- message: "Proceed with installation?",
403
- });
404
- if (p.isCancel(confirmed) || !confirmed) {
405
- p.cancel("Installation cancelled");
406
- process.exit(0);
407
- }
408
- }
409
- // Install
410
- spinner.start("Installing skills...");
411
- const results = [];
412
- for (const skill of selectedSkills) {
413
- for (const agent of targetAgents) {
414
- const result = await installSkillForAgent(skill, agent, {
415
- global: installGlobally,
416
- mode: installMode,
417
- });
418
- results.push(Object.assign({ skill: skill.slug || skill.name, agent: agents[agent].displayName }, result));
419
- }
420
- }
421
- spinner.stop("Installation complete");
422
- console.log();
423
- const successful = results.filter((r) => r.success);
424
- const failed = results.filter((r) => !r.success);
425
- if (successful.length > 0) {
426
- const resultLines = [];
427
- // Group by skill
428
- const skillGroups = new Map();
429
- for (const r of successful) {
430
- if (!skillGroups.has(r.skill)) {
431
- skillGroups.set(r.skill, []);
432
- }
433
- skillGroups.get(r.skill).push(r);
434
- }
435
- for (const [skillName, skillResults] of skillGroups) {
436
- const firstResult = skillResults[0];
437
- if (firstResult.mode === "copy") {
438
- resultLines.push(`${chalk.green("✓")} ${skillName} ${chalk.dim("(copied)")}`);
439
- for (const r of skillResults) {
440
- const shortPath = shortenPath(r.path, cwd);
441
- resultLines.push(` ${chalk.dim("→")} ${r.agent}: ${shortPath}`);
442
- }
443
- }
444
- else {
445
- if (firstResult.canonicalPath) {
446
- const shortPath = shortenPath(firstResult.canonicalPath, cwd);
447
- resultLines.push(`${chalk.green("✓")} ${skillName}`);
448
- resultLines.push(` ${chalk.dim(shortPath)}`);
449
- }
450
- else {
451
- resultLines.push(`${chalk.green("✓")} ${skillName}`);
452
- }
453
- const symlinked = skillResults
454
- .filter((r) => !r.symlinkFailed)
455
- .map((r) => r.agent);
456
- const copied = skillResults
457
- .filter((r) => r.symlinkFailed)
458
- .map((r) => r.agent);
459
- if (symlinked.length > 0) {
460
- resultLines.push(` ${chalk.dim("symlink →")} ${formatList(symlinked)}`);
461
- }
462
- if (copied.length > 0) {
463
- resultLines.push(` ${chalk.yellow("copied →")} ${formatList(copied)}`);
464
- }
465
- }
466
- }
467
- const skillCount = skillGroups.size;
468
- const agentCount = new Set(successful.map((r) => r.agent)).size;
469
- const title = chalk.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
470
- p.note(resultLines.join("\n"), title);
471
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
472
- if (symlinkFailures.length > 0) {
473
- const copiedAgentNames = [
474
- ...new Set(symlinkFailures.map((r) => r.agent)),
475
- ];
476
- p.log.warn(chalk.yellow(`Symlinks failed for: ${formatList(copiedAgentNames)}`));
477
- p.log.message(chalk.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
478
- }
479
- }
480
- if (failed.length > 0) {
481
- console.log();
482
- p.log.error(chalk.red(`Failed to install ${failed.length}`));
483
- for (const r of failed) {
484
- p.log.message(` ${chalk.red("✗")} ${r.skill} → ${r.agent}: ${chalk.dim(r.error)}`);
485
- }
486
- }
487
- console.log();
488
- p.outro(chalk.green("Done!"));
489
- }
490
- catch (error) {
491
- spinner.stop(chalk.red("Error"));
492
- p.log.error(chalk.red(error instanceof Error ? error.message : "Unknown error"));
493
- process.exit(1);
494
- }
495
- }