skillhubs 1.0.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.
Files changed (3) hide show
  1. package/README.md +77 -0
  2. package/index.js +308 -0
  3. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # skillhubs
2
+
3
+ 企业内部 Skills 管理 CLI —— 为 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 提供自定义 Slash Command 的安装与管理。
4
+
5
+ ## 功能概览
6
+
7
+ - **安装/更新/卸载** Skill,自动注册为 Claude Code 的 `/command`
8
+ - **搜索/查看** Registry 中的可用 Skill
9
+ - 支持 Skill 附属资源(scripts、references、assets 目录)
10
+ - 通过环境变量或配置文件灵活指定 Registry 地址
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ npm install -g skillhubs
16
+ ```
17
+
18
+ ## 快速开始
19
+
20
+ ### 1. 配置 Registry 地址
21
+
22
+ ```bash
23
+ skillhubs config set registry https://your-registry.example.com
24
+ ```
25
+
26
+ 或通过环境变量:
27
+
28
+ ```bash
29
+ export SKILL_REGISTRY=https://your-registry.example.com
30
+ ```
31
+
32
+ ### 2. 搜索并安装 Skill
33
+
34
+ ```bash
35
+ skillhubs search <关键词>
36
+ skillhubs add <skill名称>
37
+ ```
38
+
39
+ ### 3. 在 Claude Code 中使用
40
+
41
+ 安装完成后,在 Claude Code 中直接输入:
42
+
43
+ ```
44
+ /<skill名称> 你的输入内容
45
+ ```
46
+
47
+ ## 命令一览
48
+
49
+ | 命令 | 说明 |
50
+ | --- | --- |
51
+ | `skillhubs add <name>` | 安装最新版 Skill |
52
+ | `skillhubs add <name>@<version>` | 安装指定版本 |
53
+ | `skillhubs update <name>` | 更新 Skill 到最新版 |
54
+ | `skillhubs remove <name>` | 卸载 Skill |
55
+ | `skillhubs list` | 列出已安装的 Skill |
56
+ | `skillhubs search <keyword>` | 搜索 Skill |
57
+ | `skillhubs info <name>` | 查看 Skill 详情 |
58
+ | `skillhubs config set registry <url>` | 设置 Registry 地址 |
59
+ | `skillhubs --version` | 查看 CLI 版本 |
60
+
61
+ ## 配置
62
+
63
+ CLI 配置存储在 `~/.skillsrc` 文件中,当前支持的配置项:
64
+
65
+ | 配置项 | 说明 |
66
+ | --- | --- |
67
+ | `registry` | Skills Registry 服务地址 |
68
+
69
+ 配置优先级:环境变量 `SKILL_REGISTRY` > 配置文件 `~/.skillsrc`
70
+
71
+ ## 安装路径
72
+
73
+ Skill 安装后存放于 `~/.claude/commands/` 目录下,Claude Code 会自动识别该目录中的 `.md` 文件作为自定义 Slash Command。
74
+
75
+ ## License
76
+
77
+ MIT
package/index.js ADDED
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const axios = require("axios");
7
+ const yaml = require("js-yaml");
8
+ const unzipper = require("unzipper");
9
+
10
+ const { version: CLI_VERSION } = require("./package.json");
11
+ const [, , command, skillName, ...rest] = process.argv;
12
+
13
+ // ── 配置 ──────────────────────────────────────────────────
14
+ const CONFIG_FILE = path.join(os.homedir(), ".skillsrc");
15
+
16
+ function loadConfig() {
17
+ let cfg = {};
18
+ if (fs.existsSync(CONFIG_FILE)) {
19
+ try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); } catch {}
20
+ }
21
+ return cfg;
22
+ }
23
+
24
+ const config = loadConfig();
25
+ const REGISTRY_URL = process.env.SKILL_REGISTRY || config.registry;
26
+
27
+ if (!REGISTRY_URL && command !== "--version" && command !== "-v" && command !== "--help" && command !== "-h" && command !== "config" && command !== undefined) {
28
+ console.error("错误:未配置 registry 地址");
29
+ console.error("请执行:skillhubs config set registry <企业内网地址>");
30
+ console.error("或设置环境变量:export SKILL_REGISTRY=<企业内网地址>");
31
+ process.exit(1);
32
+ }
33
+
34
+ // Claude Code 读取自定义 slash command 的目录
35
+ const CLAUDE_SKILLS_DIR = path.join(os.homedir(), ".claude", "commands");
36
+
37
+ // ── 解析 YAML frontmatter ─────────────────────────────────
38
+ function parseFrontmatter(content) {
39
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
40
+ if (!match) return { meta: {}, body: content.trim() };
41
+ return {
42
+ meta: yaml.load(match[1]) || {},
43
+ body: match[2].trim(),
44
+ };
45
+ }
46
+
47
+ // ── 帮助信息 ───────────────────────────────────────────────
48
+ function printHelp() {
49
+ console.log(`
50
+ skillhubs CLI v${CLI_VERSION}
51
+
52
+ 用法:
53
+ skillhubs add <name> 安装最新版 skill 到 Claude Code
54
+ skillhubs add <name>@<version> 安装指定版本
55
+ skillhubs update <name> 更新 skill 到最新版
56
+ skillhubs remove <name> 卸载 skill
57
+ skillhubs list 列出已安装的 skill
58
+ skillhubs search <keyword> 搜索 skill
59
+ skillhubs info <name> 查看 skill 详情
60
+ skillhubs config set registry <url> 设置 registry 地址
61
+ skillhubs --version 查看 CLI 版本
62
+
63
+ 安装后在 Claude Code 中使用:
64
+ /<skill-name> 你的输入内容
65
+ `);
66
+ }
67
+
68
+ // ── 安装 ───────────────────────────────────────────────────
69
+ async function addSkill(nameWithVersion) {
70
+ let name = nameWithVersion;
71
+ let targetVersion = null;
72
+
73
+ if (nameWithVersion.includes("@")) {
74
+ [name, targetVersion] = nameWithVersion.split("@");
75
+ }
76
+
77
+ let versions;
78
+ try {
79
+ const { data } = await axios.get(`${REGISTRY_URL}/api/skills/${name}`);
80
+ versions = data;
81
+ } catch {
82
+ console.error(`Skill "${name}" 不存在`);
83
+ process.exit(1);
84
+ }
85
+
86
+ const target = targetVersion
87
+ ? versions.find(v => v.version === targetVersion)
88
+ : versions[0];
89
+
90
+ if (!target) {
91
+ console.error(`版本 ${targetVersion} 不存在`);
92
+ process.exit(1);
93
+ }
94
+
95
+ console.log(`正在安装 ${target.name}@${target.version}...`);
96
+
97
+ // 下载 zip 到临时目录
98
+ const zipPath = path.join(os.tmpdir(), `skillhubs-${target.name}-${target.version}.zip`);
99
+ const writer = fs.createWriteStream(zipPath);
100
+
101
+ const resp = await axios({
102
+ url: `${REGISTRY_URL}/api/skills/${target.name}/${target.version}/download`,
103
+ method: "GET",
104
+ responseType: "stream",
105
+ });
106
+
107
+ resp.data.pipe(writer);
108
+ await new Promise((resolve, reject) => {
109
+ writer.on("finish", resolve);
110
+ writer.on("error", reject);
111
+ });
112
+
113
+ // 解压到临时目录
114
+ const tmpExtract = path.join(os.tmpdir(), `skillhubs-extract-${target.name}-${Date.now()}`);
115
+ await fs.createReadStream(zipPath)
116
+ .pipe(unzipper.Extract({ path: tmpExtract }))
117
+ .promise();
118
+
119
+ // 查找 skill.md(兼容根目录和子目录,如 macOS 手动压缩会多一层文件夹)
120
+ let srcMd = path.join(tmpExtract, "skill.md");
121
+ if (!fs.existsSync(srcMd)) {
122
+ const subDirs = fs.readdirSync(tmpExtract).filter(f =>
123
+ fs.statSync(path.join(tmpExtract, f)).isDirectory()
124
+ );
125
+ for (const dir of subDirs) {
126
+ const candidate = path.join(tmpExtract, dir, "skill.md");
127
+ if (fs.existsSync(candidate)) {
128
+ srcMd = candidate;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ if (!fs.existsSync(srcMd)) {
134
+ console.error("zip 包中缺少 skill.md 文件");
135
+ process.exit(1);
136
+ }
137
+ // 以 skill.md 所在目录作为 skill 根目录(兼容多层结构)
138
+ const skillRoot = path.dirname(srcMd);
139
+
140
+ const content = fs.readFileSync(srcMd, "utf-8");
141
+ const { body } = parseFrontmatter(content);
142
+
143
+ if (!fs.existsSync(CLAUDE_SKILLS_DIR)) {
144
+ fs.mkdirSync(CLAUDE_SKILLS_DIR, { recursive: true });
145
+ }
146
+
147
+ fs.writeFileSync(path.join(CLAUDE_SKILLS_DIR, `${target.name}.md`), body, "utf-8");
148
+
149
+ // 复制可选目录:scripts/、references/、assets/
150
+ const OPTIONAL_DIRS = ["scripts", "references", "assets"];
151
+ const destBase = path.join(CLAUDE_SKILLS_DIR, target.name);
152
+
153
+ for (const dir of OPTIONAL_DIRS) {
154
+ const srcDir = path.join(skillRoot, dir);
155
+ if (fs.existsSync(srcDir)) {
156
+ fs.cpSync(srcDir, path.join(destBase, dir), { recursive: true });
157
+ console.log(` 已复制 ${dir}/ → ${path.join(destBase, dir)}`);
158
+ }
159
+ }
160
+
161
+ // 清理临时文件
162
+ fs.rmSync(tmpExtract, { recursive: true });
163
+ fs.unlinkSync(zipPath);
164
+
165
+ console.log(`✓ ${target.name}@${target.version} 已安装`);
166
+ console.log(` 提示词:${path.join(CLAUDE_SKILLS_DIR, target.name + ".md")}`);
167
+ console.log(` 在 Claude Code 中使用:/${target.name}`);
168
+ }
169
+
170
+ // ── 卸载 ───────────────────────────────────────────────────
171
+ function removeSkill(name) {
172
+ const mdPath = path.join(CLAUDE_SKILLS_DIR, `${name}.md`);
173
+ const dirPath = path.join(CLAUDE_SKILLS_DIR, name);
174
+
175
+ if (!fs.existsSync(mdPath)) {
176
+ console.error(`Skill "${name}" 未安装`);
177
+ process.exit(1);
178
+ }
179
+
180
+ fs.unlinkSync(mdPath);
181
+ if (fs.existsSync(dirPath)) fs.rmSync(dirPath, { recursive: true });
182
+ console.log(`✓ ${name} 已卸载`);
183
+ }
184
+
185
+ // ── 列出已安装 ─────────────────────────────────────────────
186
+ function listSkills() {
187
+ if (!fs.existsSync(CLAUDE_SKILLS_DIR)) {
188
+ console.log("暂无已安装的 skill");
189
+ return;
190
+ }
191
+
192
+ const skills = fs.readdirSync(CLAUDE_SKILLS_DIR)
193
+ .filter(f => f.endsWith(".md") && fs.statSync(path.join(CLAUDE_SKILLS_DIR, f)).isFile());
194
+
195
+ if (!skills.length) { console.log("暂无已安装的 skill"); return; }
196
+
197
+ console.log("已安装的 Skills:\n");
198
+ skills.forEach(f => {
199
+ const name = f.replace(".md", "");
200
+ const hasExtra = fs.existsSync(path.join(CLAUDE_SKILLS_DIR, name));
201
+ console.log(` /${name}${hasExtra ? " [含附属目录]" : ""}`);
202
+ });
203
+ }
204
+
205
+ // ── 搜索 ───────────────────────────────────────────────────
206
+ async function searchSkills(keyword) {
207
+ const { data } = await axios.get(`${REGISTRY_URL}/api/skills`, { params: { search: keyword } });
208
+ if (!data.length) { console.log("没有找到相关 skill"); return; }
209
+
210
+ console.log(`找到 ${data.length} 个 skill:\n`);
211
+ data.forEach(s => {
212
+ console.log(` ${s.name}@${s.version} ${s.description || ""} [${s.author}]`);
213
+ });
214
+ }
215
+
216
+ // ── 查看详情 ───────────────────────────────────────────────
217
+ async function infoSkill(name) {
218
+ let versions;
219
+ try {
220
+ const { data } = await axios.get(`${REGISTRY_URL}/api/skills/${name}`);
221
+ versions = data;
222
+ } catch {
223
+ console.error(`Skill "${name}" 不存在`);
224
+ process.exit(1);
225
+ }
226
+
227
+ const latest = versions[0];
228
+ console.log(`\n${latest.name}`);
229
+ console.log("─".repeat(40));
230
+ console.log(`描述: ${latest.description || "暂无"}`);
231
+ console.log(`作者: ${latest.author || "未知"}`);
232
+ console.log(`最新版本:${latest.version}`);
233
+ console.log(`下载数: ${latest.download_count}`);
234
+ console.log(`标签: ${latest.tags || "无"}`);
235
+ console.log(`\n所有版本:${versions.map(v => v.version).join(", ")}`);
236
+ console.log(`\n安装命令:skillhubs add ${latest.name}`);
237
+ console.log(`使用方式:在 Claude Code 中输入 /${latest.name}\n`);
238
+ }
239
+
240
+ // ── 配置 ───────────────────────────────────────────────────
241
+ function setConfig(key, value) {
242
+ let cfg = {};
243
+ if (fs.existsSync(CONFIG_FILE)) {
244
+ try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); } catch {}
245
+ }
246
+ cfg[key] = value;
247
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
248
+ console.log(`✓ 配置已更新: ${key} = ${value}`);
249
+ }
250
+
251
+ // ── 主入口 ─────────────────────────────────────────────────
252
+ (async () => {
253
+ switch (command) {
254
+ case "add":
255
+ if (!skillName) { console.error("请指定 skill 名称"); process.exit(1); }
256
+ await addSkill(skillName);
257
+ break;
258
+
259
+ case "update":
260
+ if (!skillName) { console.error("请指定 skill 名称"); process.exit(1); }
261
+ await addSkill(skillName);
262
+ break;
263
+
264
+ case "remove":
265
+ case "uninstall":
266
+ if (!skillName) { console.error("请指定 skill 名称"); process.exit(1); }
267
+ removeSkill(skillName);
268
+ break;
269
+
270
+ case "list":
271
+ listSkills();
272
+ break;
273
+
274
+ case "search":
275
+ if (!skillName) { console.error("请指定搜索关键词"); process.exit(1); }
276
+ await searchSkills(skillName);
277
+ break;
278
+
279
+ case "info":
280
+ if (!skillName) { console.error("请指定 skill 名称"); process.exit(1); }
281
+ await infoSkill(skillName);
282
+ break;
283
+
284
+ case "config":
285
+ if (skillName === "set" && rest.length >= 2) {
286
+ setConfig(rest[0], rest[1]);
287
+ } else {
288
+ console.log("用法: skillhubs config set <key> <value>");
289
+ }
290
+ break;
291
+
292
+ case "--version":
293
+ case "-v":
294
+ console.log(CLI_VERSION);
295
+ break;
296
+
297
+ case "--help":
298
+ case "-h":
299
+ case undefined:
300
+ printHelp();
301
+ break;
302
+
303
+ default:
304
+ console.error(`未知命令: ${command}`);
305
+ printHelp();
306
+ process.exit(1);
307
+ }
308
+ })();
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "skillhubs",
3
+ "version": "1.0.0",
4
+ "description": "企业内部 Skills 管理 CLI",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "skillhubs": "index.js"
8
+ },
9
+ "keywords": [
10
+ "ai",
11
+ "skills",
12
+ "enterprise",
13
+ "cli",
14
+ "agent",
15
+ "claude"
16
+ ],
17
+ "license": "MIT",
18
+ "files": [
19
+ "index.js"
20
+ ],
21
+ "dependencies": {
22
+ "axios": "^1.6.0",
23
+ "js-yaml": "^4.1.0",
24
+ "unzipper": "^0.10.11"
25
+ }
26
+ }