@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/README.md +399 -96
- package/ThirdPartyNoticeText.txt +171 -0
- package/bin/cli.mjs +14 -0
- package/dist/_chunks/libs/@clack/core.mjs +767 -0
- package/dist/_chunks/libs/@clack/prompts.mjs +275 -0
- package/dist/_chunks/libs/@kwsites/file-exists.mjs +562 -0
- package/dist/_chunks/libs/@kwsites/promise-deferred.mjs +37 -0
- package/dist/_chunks/libs/bluebird.mjs +4141 -0
- package/dist/_chunks/libs/core-util-is.mjs +65 -0
- package/dist/_chunks/libs/duplexer2.mjs +1726 -0
- package/dist/_chunks/libs/esprima.mjs +5338 -0
- package/dist/_chunks/libs/extend-shallow.mjs +31 -0
- package/dist/_chunks/libs/fs-extra.mjs +1801 -0
- package/dist/_chunks/libs/gray-matter.mjs +2596 -0
- package/dist/_chunks/libs/node-int64.mjs +103 -0
- package/dist/_chunks/libs/simple-git.mjs +3584 -0
- package/dist/_chunks/libs/unzipper.mjs +945 -0
- package/dist/_chunks/libs/xdg-basedir.mjs +14 -0
- package/dist/_chunks/rolldown-runtime.mjs +24 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +4258 -0
- package/package.json +85 -27
- package/dist/agents.js +0 -246
- package/dist/api.js +0 -98
- package/dist/auth.js +0 -117
- package/dist/index.js +0 -495
- package/dist/installer.js +0 -231
- package/dist/types.js +0 -1
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
|
-
}
|