skillhub 0.1.0

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 ADDED
@@ -0,0 +1,121 @@
1
+ # @skillhub/cli
2
+
3
+ Command-line tool for installing and managing AI Agent skills from [SkillHub](https://skills.palebluedot.live).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Install globally
9
+ npm install -g @skillhub/cli
10
+
11
+ # Or use directly with npx
12
+ npx @skillhub/cli
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Search for skills
18
+
19
+ ```bash
20
+ skillhub search pdf
21
+ skillhub search "code review" --platform claude --limit 20
22
+ ```
23
+
24
+ ### Install a skill
25
+
26
+ ```bash
27
+ skillhub install anthropics/skills/pdf
28
+ skillhub install obra/superpowers/brainstorming --platform codex
29
+ skillhub install anthropics/skills/docx --project # Install in current project
30
+ ```
31
+
32
+ ### List installed skills
33
+
34
+ ```bash
35
+ skillhub list
36
+ skillhub list --platform claude
37
+ ```
38
+
39
+ ### Update skills
40
+
41
+ ```bash
42
+ skillhub update anthropics/skills/pdf # Update specific skill
43
+ skillhub update --all # Update all installed skills
44
+ ```
45
+
46
+ ### Uninstall a skill
47
+
48
+ ```bash
49
+ skillhub uninstall pdf
50
+ skillhub uninstall brainstorming --platform codex
51
+ ```
52
+
53
+ ### Configuration
54
+
55
+ ```bash
56
+ skillhub config --list # Show all config
57
+ skillhub config --get defaultPlatform # Get specific value
58
+ skillhub config --set defaultPlatform=claude # Set value
59
+ ```
60
+
61
+ ## Platform Support
62
+
63
+ SkillHub CLI supports multiple AI agent platforms:
64
+
65
+ | Platform | Flag | Install Path |
66
+ |----------|------|--------------|
67
+ | Claude | `--platform claude` | `~/.claude/skills/` |
68
+ | OpenAI Codex | `--platform codex` | `~/.codex/skills/` |
69
+ | GitHub Copilot | `--platform copilot` | `~/.github/skills/` |
70
+ | Cursor | `--platform cursor` | `~/.cursor/skills/` |
71
+ | Windsurf | `--platform windsurf` | `~/.windsurf/skills/` |
72
+
73
+ Default platform: `claude`
74
+
75
+ ## Options
76
+
77
+ ### Global Options
78
+
79
+ - `--platform <name>` - Target platform (claude, codex, copilot, cursor, windsurf)
80
+ - `--project` - Install in current project instead of user directory
81
+ - `--force` - Overwrite existing installation
82
+ - `--help` - Show help information
83
+ - `--version` - Show version number
84
+
85
+ ### Environment Variables
86
+
87
+ - `SKILLHUB_API_URL` - Override API endpoint (default: https://skills.palebluedot.live/api)
88
+ - `GITHUB_TOKEN` - GitHub token for API rate limits (optional)
89
+
90
+ ## Configuration File
91
+
92
+ Configuration is stored in `~/.skillhub/config.json`:
93
+
94
+ ```json
95
+ {
96
+ "defaultPlatform": "claude",
97
+ "apiUrl": "https://skills.palebluedot.live/api",
98
+ "githubToken": "ghp_..."
99
+ }
100
+ ```
101
+
102
+ ## Examples
103
+
104
+ ```bash
105
+ # Search and install a skill
106
+ skillhub search "document processing"
107
+ skillhub install anthropics/skills/pdf
108
+
109
+ # Install skill for Codex
110
+ skillhub install obra/superpowers/brainstorming --platform codex
111
+
112
+ # Update all installed skills
113
+ skillhub update --all
114
+
115
+ # Check what's installed
116
+ skillhub list
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,88 @@
1
+ // src/utils/paths.ts
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import fs from "fs-extra";
5
+ var PLATFORM_PATHS = {
6
+ claude: {
7
+ user: ".claude/skills",
8
+ project: ".claude/skills"
9
+ },
10
+ codex: {
11
+ user: ".codex/skills",
12
+ project: ".codex/skills"
13
+ },
14
+ copilot: {
15
+ user: ".github/skills",
16
+ project: ".github/skills"
17
+ },
18
+ cursor: {
19
+ user: ".cursor/skills",
20
+ project: ".cursor/skills"
21
+ },
22
+ windsurf: {
23
+ user: ".windsurf/skills",
24
+ project: ".windsurf/skills"
25
+ }
26
+ };
27
+ function getSkillsPath(platform, project = false) {
28
+ const home = os.homedir();
29
+ const cwd = process.cwd();
30
+ const paths = PLATFORM_PATHS[platform];
31
+ if (project) {
32
+ return path.join(cwd, paths.project);
33
+ }
34
+ return path.join(home, paths.user);
35
+ }
36
+ function getSkillPath(platform, skillName, project = false) {
37
+ const basePath = getSkillsPath(platform, project);
38
+ return path.join(basePath, skillName);
39
+ }
40
+ async function ensureSkillsDir(platform, project = false) {
41
+ const skillsPath = getSkillsPath(platform, project);
42
+ await fs.ensureDir(skillsPath);
43
+ return skillsPath;
44
+ }
45
+ async function isSkillInstalled(platform, skillName, project = false) {
46
+ const skillPath = getSkillPath(platform, skillName, project);
47
+ return fs.pathExists(skillPath);
48
+ }
49
+ async function detectPlatform() {
50
+ const cwd = process.cwd();
51
+ for (const platform of Object.keys(PLATFORM_PATHS)) {
52
+ const configPath = path.join(cwd, `.${platform}`);
53
+ if (await fs.pathExists(configPath)) {
54
+ return platform;
55
+ }
56
+ }
57
+ if (await fs.pathExists(path.join(cwd, ".github"))) {
58
+ return "copilot";
59
+ }
60
+ return null;
61
+ }
62
+ function getConfigPath() {
63
+ const home = os.homedir();
64
+ return path.join(home, ".skillhub", "config.json");
65
+ }
66
+ async function loadConfig() {
67
+ const configPath = getConfigPath();
68
+ if (await fs.pathExists(configPath)) {
69
+ return fs.readJson(configPath);
70
+ }
71
+ return {};
72
+ }
73
+ async function saveConfig(config) {
74
+ const configPath = getConfigPath();
75
+ await fs.ensureDir(path.dirname(configPath));
76
+ await fs.writeJson(configPath, config, { spaces: 2 });
77
+ }
78
+
79
+ export {
80
+ getSkillsPath,
81
+ getSkillPath,
82
+ ensureSkillsDir,
83
+ isSkillInstalled,
84
+ detectPlatform,
85
+ getConfigPath,
86
+ loadConfig,
87
+ saveConfig
88
+ };
package/dist/index.js ADDED
@@ -0,0 +1,571 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureSkillsDir,
4
+ getConfigPath,
5
+ getSkillPath,
6
+ getSkillsPath,
7
+ isSkillInstalled,
8
+ loadConfig,
9
+ saveConfig
10
+ } from "./chunk-YXEKBJKF.js";
11
+
12
+ // src/index.ts
13
+ import { Command } from "commander";
14
+ import chalk5 from "chalk";
15
+
16
+ // src/commands/install.ts
17
+ import fs from "fs-extra";
18
+ import * as path from "path";
19
+ import chalk from "chalk";
20
+ import ora from "ora";
21
+ import { parseSkillMd } from "skillhub-core";
22
+
23
+ // src/utils/api.ts
24
+ var API_BASE_URL = process.env.SKILLHUB_API_URL || "https://skills.palebluedot.live/api";
25
+ async function searchSkills(query, options = {}) {
26
+ const params = new URLSearchParams({
27
+ q: query,
28
+ limit: String(options.limit || 10),
29
+ page: String(options.page || 1)
30
+ });
31
+ if (options.platform) {
32
+ params.set("platform", options.platform);
33
+ }
34
+ const response = await fetch(`${API_BASE_URL}/skills?${params}`);
35
+ if (!response.ok) {
36
+ throw new Error(`API error: ${response.status}`);
37
+ }
38
+ return response.json();
39
+ }
40
+ async function getSkill(id) {
41
+ const response = await fetch(`${API_BASE_URL}/skills/${encodeURIComponent(id)}`);
42
+ if (response.status === 404) {
43
+ return null;
44
+ }
45
+ if (!response.ok) {
46
+ throw new Error(`API error: ${response.status}`);
47
+ }
48
+ return response.json();
49
+ }
50
+ async function trackInstall(skillId, platform, method = "cli") {
51
+ try {
52
+ await fetch(`${API_BASE_URL}/skills/${encodeURIComponent(skillId)}/install`, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({ platform, method })
56
+ });
57
+ } catch {
58
+ }
59
+ }
60
+
61
+ // src/utils/github.ts
62
+ import { Octokit } from "@octokit/rest";
63
+ var octokit = null;
64
+ function getOctokit() {
65
+ if (!octokit) {
66
+ octokit = new Octokit({
67
+ auth: process.env.GITHUB_TOKEN,
68
+ userAgent: "SkillHub-CLI/1.0"
69
+ });
70
+ }
71
+ return octokit;
72
+ }
73
+ async function fetchSkillContent(owner, repo, skillPath, branch = "main") {
74
+ const client = getOctokit();
75
+ const skillMdPath = skillPath ? `${skillPath}/SKILL.md` : "SKILL.md";
76
+ const skillMdResponse = await client.repos.getContent({
77
+ owner,
78
+ repo,
79
+ path: skillMdPath,
80
+ ref: branch
81
+ });
82
+ if (!("content" in skillMdResponse.data)) {
83
+ throw new Error("SKILL.md not found");
84
+ }
85
+ const skillMd = Buffer.from(skillMdResponse.data.content, "base64").toString("utf-8");
86
+ const scripts = [];
87
+ try {
88
+ const scriptsPath = skillPath ? `${skillPath}/scripts` : "scripts";
89
+ const scriptsResponse = await client.repos.getContent({
90
+ owner,
91
+ repo,
92
+ path: scriptsPath,
93
+ ref: branch
94
+ });
95
+ if (Array.isArray(scriptsResponse.data)) {
96
+ for (const file of scriptsResponse.data) {
97
+ if (file.type === "file") {
98
+ const fileResponse = await client.repos.getContent({
99
+ owner,
100
+ repo,
101
+ path: file.path,
102
+ ref: branch
103
+ });
104
+ if ("content" in fileResponse.data) {
105
+ scripts.push({
106
+ name: file.name,
107
+ content: Buffer.from(fileResponse.data.content, "base64").toString("utf-8")
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
113
+ } catch {
114
+ }
115
+ const references = [];
116
+ try {
117
+ const refsPath = skillPath ? `${skillPath}/references` : "references";
118
+ const refsResponse = await client.repos.getContent({
119
+ owner,
120
+ repo,
121
+ path: refsPath,
122
+ ref: branch
123
+ });
124
+ if (Array.isArray(refsResponse.data)) {
125
+ for (const file of refsResponse.data) {
126
+ if (file.type === "file" && file.size && file.size < 1e5) {
127
+ const fileResponse = await client.repos.getContent({
128
+ owner,
129
+ repo,
130
+ path: file.path,
131
+ ref: branch
132
+ });
133
+ if ("content" in fileResponse.data) {
134
+ references.push({
135
+ name: file.name,
136
+ content: Buffer.from(fileResponse.data.content, "base64").toString("utf-8")
137
+ });
138
+ }
139
+ }
140
+ }
141
+ }
142
+ } catch {
143
+ }
144
+ return {
145
+ skillMd,
146
+ scripts,
147
+ references,
148
+ assets: []
149
+ // TODO: handle binary assets
150
+ };
151
+ }
152
+ async function getDefaultBranch(owner, repo) {
153
+ const client = getOctokit();
154
+ const response = await client.repos.get({ owner, repo });
155
+ return response.data.default_branch;
156
+ }
157
+
158
+ // src/commands/install.ts
159
+ async function install(skillId, options) {
160
+ const spinner = ora("Fetching skill information...").start();
161
+ try {
162
+ const parts = skillId.split("/");
163
+ if (parts.length < 2) {
164
+ spinner.fail("Invalid skill ID format. Use: owner/repo or owner/repo/skill-name");
165
+ process.exit(1);
166
+ }
167
+ const [owner, repo, ...rest] = parts;
168
+ const skillPath = rest.join("/");
169
+ const skillInfo = await getSkill(skillId);
170
+ let skillName;
171
+ let branch = "main";
172
+ if (skillInfo) {
173
+ skillName = skillInfo.name;
174
+ spinner.text = `Found skill: ${chalk.cyan(skillName)}`;
175
+ } else {
176
+ spinner.text = "Skill not in registry, fetching from GitHub...";
177
+ branch = await getDefaultBranch(owner, repo);
178
+ skillName = skillPath || repo;
179
+ }
180
+ const installed = await isSkillInstalled(options.platform, skillName, options.project);
181
+ if (installed && !options.force) {
182
+ spinner.fail(
183
+ `Skill ${chalk.cyan(skillName)} is already installed. Use ${chalk.yellow("--force")} to overwrite.`
184
+ );
185
+ process.exit(1);
186
+ }
187
+ await ensureSkillsDir(options.platform, options.project);
188
+ spinner.text = "Downloading skill files...";
189
+ const content = await fetchSkillContent(owner, repo, skillPath, branch);
190
+ const parsed = parseSkillMd(content.skillMd);
191
+ if (!parsed.validation.isValid) {
192
+ spinner.warn("Skill has validation issues:");
193
+ for (const error of parsed.validation.errors) {
194
+ console.log(chalk.yellow(` - ${error.message}`));
195
+ }
196
+ }
197
+ const actualName = parsed.metadata.name || skillName;
198
+ const installPath = getSkillPath(options.platform, actualName, options.project);
199
+ if (installed && options.force) {
200
+ await fs.remove(installPath);
201
+ }
202
+ spinner.text = "Installing skill...";
203
+ await fs.ensureDir(installPath);
204
+ await fs.writeFile(path.join(installPath, "SKILL.md"), content.skillMd);
205
+ await fs.writeJson(path.join(installPath, ".skillhub.json"), {
206
+ skillId,
207
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
208
+ platform: options.platform,
209
+ version: parsed.metadata.version || null
210
+ });
211
+ if (content.scripts.length > 0) {
212
+ const scriptsDir = path.join(installPath, "scripts");
213
+ await fs.ensureDir(scriptsDir);
214
+ for (const script of content.scripts) {
215
+ const scriptPath = path.join(scriptsDir, script.name);
216
+ await fs.writeFile(scriptPath, script.content);
217
+ await fs.chmod(scriptPath, "755");
218
+ }
219
+ }
220
+ if (content.references.length > 0) {
221
+ const refsDir = path.join(installPath, "references");
222
+ await fs.ensureDir(refsDir);
223
+ for (const ref of content.references) {
224
+ await fs.writeFile(path.join(refsDir, ref.name), ref.content);
225
+ }
226
+ }
227
+ await trackInstall(skillId, options.platform, "cli");
228
+ spinner.succeed(`Skill ${chalk.green(actualName)} installed successfully!`);
229
+ console.log();
230
+ console.log(chalk.dim(`Path: ${installPath}`));
231
+ console.log();
232
+ if (parsed.metadata.description) {
233
+ console.log(chalk.dim(parsed.metadata.description));
234
+ console.log();
235
+ }
236
+ console.log(chalk.yellow("Usage:"));
237
+ console.log(
238
+ ` This skill will be automatically activated when your ${getPlatformName(options.platform)} agent recognizes it's relevant.`
239
+ );
240
+ if (content.scripts.length > 0) {
241
+ console.log();
242
+ console.log(chalk.dim(`Scripts: ${content.scripts.map((s) => s.name).join(", ")}`));
243
+ }
244
+ } catch (error) {
245
+ spinner.fail("Installation failed");
246
+ console.error(chalk.red(error.message));
247
+ process.exit(1);
248
+ }
249
+ }
250
+ function getPlatformName(platform) {
251
+ const names = {
252
+ claude: "Claude",
253
+ codex: "OpenAI Codex",
254
+ copilot: "GitHub Copilot",
255
+ cursor: "Cursor",
256
+ windsurf: "Windsurf"
257
+ };
258
+ return names[platform];
259
+ }
260
+
261
+ // src/commands/search.ts
262
+ import chalk2 from "chalk";
263
+ import ora2 from "ora";
264
+ async function search(query, options) {
265
+ const spinner = ora2("Searching skills...").start();
266
+ try {
267
+ const limit = parseInt(options.limit || "10");
268
+ const result = await searchSkills(query, {
269
+ platform: options.platform,
270
+ limit
271
+ });
272
+ spinner.stop();
273
+ if (result.skills.length === 0) {
274
+ console.log(chalk2.yellow("No skills found."));
275
+ console.log(chalk2.dim("Try a different search term or check the spelling."));
276
+ return;
277
+ }
278
+ console.log(chalk2.bold(`Found ${result.pagination.total} skills:
279
+ `));
280
+ console.log(
281
+ chalk2.dim("\u2500".repeat(80))
282
+ );
283
+ for (const skill of result.skills) {
284
+ const verified = skill.isVerified ? chalk2.green("\u2713") : " ";
285
+ const security = getSecurityBadge(skill.securityScore);
286
+ console.log(
287
+ `${verified} ${chalk2.cyan(skill.name.padEnd(25))} ${security} \u2B50 ${formatNumber(skill.githubStars).padStart(6)} \u2B07 ${formatNumber(skill.downloadCount).padStart(6)}`
288
+ );
289
+ console.log(
290
+ ` ${chalk2.dim(skill.description.slice(0, 70))}${skill.description.length > 70 ? "..." : ""}`
291
+ );
292
+ console.log(
293
+ ` ${chalk2.dim("Platforms:")} ${skill.compatibility.platforms.join(", ")}`
294
+ );
295
+ console.log(chalk2.dim("\u2500".repeat(80)));
296
+ }
297
+ console.log();
298
+ console.log(chalk2.dim(`Install with: ${chalk2.white("npx skillhub install <skill-id>")}`));
299
+ if (result.pagination.total > result.skills.length) {
300
+ console.log(
301
+ chalk2.dim(`Showing ${result.skills.length} of ${result.pagination.total}. Use --limit to see more.`)
302
+ );
303
+ }
304
+ } catch (error) {
305
+ spinner.fail("Search failed");
306
+ console.error(chalk2.red(error.message));
307
+ process.exit(1);
308
+ }
309
+ }
310
+ function formatNumber(num) {
311
+ if (num >= 1e3) {
312
+ return (num / 1e3).toFixed(1) + "k";
313
+ }
314
+ return num.toString();
315
+ }
316
+ function getSecurityBadge(score) {
317
+ if (score >= 90) return chalk2.green("\u25CF\u25CF\u25CF\u25CF\u25CF");
318
+ if (score >= 70) return chalk2.yellow("\u25CF\u25CF\u25CF\u25CF\u25CB");
319
+ if (score >= 50) return chalk2.yellow("\u25CF\u25CF\u25CF\u25CB\u25CB");
320
+ if (score >= 30) return chalk2.red("\u25CF\u25CF\u25CB\u25CB\u25CB");
321
+ return chalk2.red("\u25CF\u25CB\u25CB\u25CB\u25CB");
322
+ }
323
+
324
+ // src/commands/list.ts
325
+ import fs2 from "fs-extra";
326
+ import * as path2 from "path";
327
+ import chalk3 from "chalk";
328
+ import { parseSkillMd as parseSkillMd2 } from "skillhub-core";
329
+ var ALL_PLATFORMS = ["claude", "codex", "copilot", "cursor", "windsurf"];
330
+ async function list(options) {
331
+ const platforms = options.platform ? [options.platform] : ALL_PLATFORMS;
332
+ let totalSkills = 0;
333
+ for (const platform of platforms) {
334
+ const skills = await getInstalledSkills(platform);
335
+ if (skills.length === 0) {
336
+ continue;
337
+ }
338
+ totalSkills += skills.length;
339
+ console.log(chalk3.bold(`
340
+ ${getPlatformName2(platform)} (${skills.length} skills):`));
341
+ console.log(chalk3.dim("\u2500".repeat(60)));
342
+ for (const skill of skills) {
343
+ const version = skill.version ? chalk3.dim(`v${skill.version}`) : "";
344
+ console.log(` ${chalk3.cyan(skill.name.padEnd(25))} ${version}`);
345
+ if (skill.description) {
346
+ console.log(` ${chalk3.dim(skill.description.slice(0, 55))}${skill.description.length > 55 ? "..." : ""}`);
347
+ }
348
+ }
349
+ }
350
+ if (totalSkills === 0) {
351
+ console.log(chalk3.yellow("\nNo skills installed."));
352
+ console.log(chalk3.dim("Search for skills with: npx skillhub search <query>"));
353
+ console.log(chalk3.dim("Install a skill with: npx skillhub install <skill-id>"));
354
+ } else {
355
+ console.log(chalk3.dim(`
356
+ ${totalSkills} total skill(s) installed.`));
357
+ }
358
+ }
359
+ async function getInstalledSkills(platform) {
360
+ const skillsPath = getSkillsPath(platform);
361
+ const skills = [];
362
+ if (!await fs2.pathExists(skillsPath)) {
363
+ return skills;
364
+ }
365
+ const entries = await fs2.readdir(skillsPath, { withFileTypes: true });
366
+ for (const entry of entries) {
367
+ if (!entry.isDirectory()) {
368
+ continue;
369
+ }
370
+ const skillPath = path2.join(skillsPath, entry.name);
371
+ const skillMdPath = path2.join(skillPath, "SKILL.md");
372
+ if (!await fs2.pathExists(skillMdPath)) {
373
+ continue;
374
+ }
375
+ try {
376
+ const content = await fs2.readFile(skillMdPath, "utf-8");
377
+ const parsed = parseSkillMd2(content);
378
+ skills.push({
379
+ name: parsed.metadata.name || entry.name,
380
+ description: parsed.metadata.description,
381
+ version: parsed.metadata.version,
382
+ path: skillPath
383
+ });
384
+ } catch {
385
+ skills.push({
386
+ name: entry.name,
387
+ path: skillPath
388
+ });
389
+ }
390
+ }
391
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
392
+ }
393
+ function getPlatformName2(platform) {
394
+ const names = {
395
+ claude: "Claude",
396
+ codex: "OpenAI Codex",
397
+ copilot: "GitHub Copilot",
398
+ cursor: "Cursor",
399
+ windsurf: "Windsurf"
400
+ };
401
+ return names[platform];
402
+ }
403
+
404
+ // src/commands/config.ts
405
+ import chalk4 from "chalk";
406
+ async function config(options) {
407
+ const currentConfig = await loadConfig();
408
+ if (options.list || !options.set && !options.get) {
409
+ console.log(chalk4.bold("SkillHub CLI Configuration:\n"));
410
+ console.log(chalk4.dim(`Config file: ${getConfigPath()}
411
+ `));
412
+ if (Object.keys(currentConfig).length === 0) {
413
+ console.log(chalk4.yellow("No configuration set."));
414
+ console.log(chalk4.dim("\nAvailable settings:"));
415
+ console.log(chalk4.dim(" defaultPlatform - Default platform for installations (claude, codex, copilot)"));
416
+ console.log(chalk4.dim(" apiUrl - SkillHub API URL"));
417
+ console.log(chalk4.dim(" githubToken - GitHub personal access token for private repos"));
418
+ return;
419
+ }
420
+ for (const [key, value] of Object.entries(currentConfig)) {
421
+ const displayValue = key.toLowerCase().includes("token") ? maskSecret(String(value)) : String(value);
422
+ console.log(` ${chalk4.cyan(key)}: ${displayValue}`);
423
+ }
424
+ return;
425
+ }
426
+ if (options.get) {
427
+ const value = currentConfig[options.get];
428
+ if (value === void 0) {
429
+ console.log(chalk4.yellow(`Config '${options.get}' is not set.`));
430
+ return;
431
+ }
432
+ const displayValue = options.get.toLowerCase().includes("token") ? maskSecret(String(value)) : String(value);
433
+ console.log(displayValue);
434
+ return;
435
+ }
436
+ if (options.set) {
437
+ const [key, ...valueParts] = options.set.split("=");
438
+ const value = valueParts.join("=");
439
+ if (!key || value === void 0) {
440
+ console.error(chalk4.red("Invalid format. Use: --set key=value"));
441
+ process.exit(1);
442
+ }
443
+ currentConfig[key] = value;
444
+ await saveConfig(currentConfig);
445
+ const displayValue = key.toLowerCase().includes("token") ? maskSecret(value) : value;
446
+ console.log(chalk4.green(`Set ${chalk4.cyan(key)} = ${displayValue}`));
447
+ }
448
+ }
449
+ function maskSecret(value) {
450
+ if (value.length <= 8) {
451
+ return "*".repeat(value.length);
452
+ }
453
+ return value.slice(0, 4) + "*".repeat(value.length - 8) + value.slice(-4);
454
+ }
455
+
456
+ // src/index.ts
457
+ var VERSION = "0.1.0";
458
+ var program = new Command();
459
+ program.name("skillhub").description("CLI for managing AI Agent skills").version(VERSION);
460
+ program.command("install <skill-id>").description("Install a skill from the registry").option("-p, --platform <platform>", "Target platform (claude, codex, copilot, cursor, windsurf)", "claude").option("--project", "Install in the current project instead of globally").option("-f, --force", "Overwrite existing skill").action(async (skillId, options) => {
461
+ await install(skillId, {
462
+ platform: options.platform,
463
+ project: options.project,
464
+ force: options.force
465
+ });
466
+ });
467
+ program.command("search <query>").description("Search for skills in the registry").option("-p, --platform <platform>", "Filter by platform").option("-l, --limit <number>", "Number of results", "10").action(async (query, options) => {
468
+ await search(query, options);
469
+ });
470
+ program.command("list").description("List installed skills").option("-p, --platform <platform>", "Filter by platform").action(async (options) => {
471
+ await list(options);
472
+ });
473
+ program.command("config").description("Manage CLI configuration").option("--set <key=value>", "Set a configuration value").option("--get <key>", "Get a configuration value").option("--list", "List all configuration values").action(async (options) => {
474
+ await config(options);
475
+ });
476
+ program.command("uninstall <skill-name>").description("Uninstall a skill").option("-p, --platform <platform>", "Target platform", "claude").option("--project", "Uninstall from project instead of globally").action(async (skillName, options) => {
477
+ const fs3 = await import("fs-extra");
478
+ const { getSkillPath: getSkillPath2, isSkillInstalled: isSkillInstalled2 } = await import("./paths-BVI5WSHE.js");
479
+ const installed = await isSkillInstalled2(options.platform, skillName, options.project);
480
+ if (!installed) {
481
+ console.log(chalk5.yellow(`Skill ${skillName} is not installed.`));
482
+ process.exit(1);
483
+ }
484
+ const skillPath = getSkillPath2(options.platform, skillName, options.project);
485
+ await fs3.default.remove(skillPath);
486
+ console.log(chalk5.green(`Skill ${skillName} uninstalled successfully.`));
487
+ });
488
+ program.command("update [skill-name]").description("Update installed skills").option("-p, --platform <platform>", "Target platform", "claude").option("--all", "Update all installed skills").action(async (skillName, options) => {
489
+ const fsExtra = await import("fs-extra");
490
+ const pathModule = await import("path");
491
+ const { getSkillsPath: getSkillsPath2, getSkillPath: getSkillPath2 } = await import("./paths-BVI5WSHE.js");
492
+ const ALL_PLATFORMS2 = ["claude", "codex", "copilot", "cursor", "windsurf"];
493
+ if (options.all) {
494
+ console.log(chalk5.cyan("\nUpdating all installed skills...\n"));
495
+ const platforms = options.platform ? [options.platform] : ALL_PLATFORMS2;
496
+ let updated = 0;
497
+ let failed = 0;
498
+ let skipped = 0;
499
+ for (const platform of platforms) {
500
+ const skillsPath = getSkillsPath2(platform);
501
+ if (!await fsExtra.default.pathExists(skillsPath)) {
502
+ continue;
503
+ }
504
+ const entries = await fsExtra.default.readdir(skillsPath, { withFileTypes: true });
505
+ for (const entry of entries) {
506
+ if (!entry.isDirectory()) continue;
507
+ const skillPath2 = pathModule.join(skillsPath, entry.name);
508
+ const metadataPath2 = pathModule.join(skillPath2, ".skillhub.json");
509
+ if (!await fsExtra.default.pathExists(metadataPath2)) {
510
+ console.log(chalk5.yellow(` Skipping ${entry.name}: No metadata (reinstall with CLI to enable updates)`));
511
+ skipped++;
512
+ continue;
513
+ }
514
+ try {
515
+ const metadata = await fsExtra.default.readJson(metadataPath2);
516
+ const skillId = metadata.skillId;
517
+ if (!skillId) {
518
+ console.log(chalk5.yellow(` Skipping ${entry.name}: No skill ID in metadata`));
519
+ skipped++;
520
+ continue;
521
+ }
522
+ console.log(chalk5.dim(` Updating ${entry.name}...`));
523
+ await install(skillId, {
524
+ platform,
525
+ force: true
526
+ });
527
+ updated++;
528
+ } catch (error) {
529
+ console.log(chalk5.red(` Failed to update ${entry.name}: ${error.message}`));
530
+ failed++;
531
+ }
532
+ }
533
+ }
534
+ console.log();
535
+ console.log(chalk5.green(`Updated: ${updated}`));
536
+ if (failed > 0) console.log(chalk5.red(`Failed: ${failed}`));
537
+ if (skipped > 0) console.log(chalk5.yellow(`Skipped: ${skipped}`));
538
+ return;
539
+ }
540
+ if (!skillName) {
541
+ console.log(chalk5.red("Please specify a skill name or use --all."));
542
+ process.exit(1);
543
+ }
544
+ const skillPath = getSkillPath2(options.platform, skillName);
545
+ const metadataPath = pathModule.join(skillPath, ".skillhub.json");
546
+ if (!await fsExtra.default.pathExists(metadataPath)) {
547
+ console.log(chalk5.yellow(`Skill ${skillName} was not installed via CLI.`));
548
+ console.log(chalk5.dim("To update, reinstall with: npx skillhub install <skill-id> --force"));
549
+ process.exit(1);
550
+ }
551
+ try {
552
+ const metadata = await fsExtra.default.readJson(metadataPath);
553
+ const skillId = metadata.skillId;
554
+ if (!skillId) {
555
+ console.log(chalk5.red("No skill ID found in metadata."));
556
+ process.exit(1);
557
+ }
558
+ console.log(chalk5.dim(`Updating ${skillName}...`));
559
+ await install(skillId, {
560
+ platform: options.platform,
561
+ force: true
562
+ });
563
+ } catch (error) {
564
+ console.log(chalk5.red(`Failed to update: ${error.message}`));
565
+ process.exit(1);
566
+ }
567
+ });
568
+ program.parse();
569
+ if (!process.argv.slice(2).length) {
570
+ program.help();
571
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ detectPlatform,
3
+ ensureSkillsDir,
4
+ getConfigPath,
5
+ getSkillPath,
6
+ getSkillsPath,
7
+ isSkillInstalled,
8
+ loadConfig,
9
+ saveConfig
10
+ } from "./chunk-YXEKBJKF.js";
11
+ export {
12
+ detectPlatform,
13
+ ensureSkillsDir,
14
+ getConfigPath,
15
+ getSkillPath,
16
+ getSkillsPath,
17
+ isSkillInstalled,
18
+ loadConfig,
19
+ saveConfig
20
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "skillhub",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for managing AI Agent skills - search, install, and update skills for Claude, Codex, Copilot, and more",
5
+ "author": "SkillHub Contributors",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/anthropics/skillhub.git",
10
+ "directory": "apps/cli"
11
+ },
12
+ "homepage": "https://skills.palebluedot.live",
13
+ "type": "module",
14
+ "bin": {
15
+ "skillhub": "./dist/index.js"
16
+ },
17
+ "files": ["dist"],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm --target node20 --clean",
20
+ "dev": "tsx src/index.ts",
21
+ "start": "node dist/index.js",
22
+ "lint": "eslint src/",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "vitest",
25
+ "test:run": "vitest run"
26
+ },
27
+ "dependencies": {
28
+ "skillhub-core": "^0.1.0",
29
+ "@octokit/rest": "^20.0.2",
30
+ "chalk": "^5.3.0",
31
+ "commander": "^11.1.0",
32
+ "fs-extra": "^11.2.0",
33
+ "ora": "^8.0.1",
34
+ "prompts": "^2.4.2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/fs-extra": "^11.0.4",
38
+ "@types/node": "^20.10.0",
39
+ "@types/prompts": "^2.4.9",
40
+ "tsup": "^8.0.1",
41
+ "tsx": "^4.7.0",
42
+ "typescript": "^5.3.0",
43
+ "vitest": "^1.2.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "keywords": ["ai", "agent", "skills", "cli", "claude", "codex", "copilot"]
49
+ }