@wang121ye/skillmanager 0.0.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/LICENSE +21 -0
- package/README.md +219 -0
- package/manifests/sources.json +38 -0
- package/package.json +50 -0
- package/src/cli.js +179 -0
- package/src/commands/bootstrap.js +153 -0
- package/src/commands/config.js +96 -0
- package/src/commands/select.js +79 -0
- package/src/commands/source.js +103 -0
- package/src/commands/uninstall.js +63 -0
- package/src/commands/update.js +110 -0
- package/src/commands/webui.js +150 -0
- package/src/commands/where.js +23 -0
- package/src/index.js +2 -0
- package/src/lib/concurrency.js +22 -0
- package/src/lib/config.js +75 -0
- package/src/lib/fs.js +33 -0
- package/src/lib/git.js +57 -0
- package/src/lib/http.js +42 -0
- package/src/lib/installed.js +35 -0
- package/src/lib/local-install.js +35 -0
- package/src/lib/manifest.js +84 -0
- package/src/lib/openskills.js +50 -0
- package/src/lib/paths.js +44 -0
- package/src/lib/profiles.js +31 -0
- package/src/lib/scan.js +50 -0
- package/src/lib/source-utils.js +62 -0
- package/src/ui/server.js +321 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ye.wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# skillmanager(`@wang121ye/skillmanager`)
|
|
2
|
+
|
|
3
|
+
跨平台(Windows / Linux / macOS)的 **Agent Skills 管理器**:把「官方 skills + 第三方 skills 仓库 + 你自己的 skills 仓库」统一做 **安装(install)**,并可选用 **Web UI 交互式勾选**来安装子集。
|
|
4
|
+
|
|
5
|
+
本项目 **基于 `openskills`** 实现安装与 `AGENTS.md` 同步。
|
|
6
|
+
|
|
7
|
+
## 安装与使用
|
|
8
|
+
|
|
9
|
+
### 全局安装(推荐)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @wang121ye/skillmanager
|
|
13
|
+
skillmanager install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 直接 npx(无需安装)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @wang121ye/skillmanager install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> 说明:你最初写的 `npx skillmanager bootstrap` 只有在发布“非 scope 包名”时才成立。
|
|
23
|
+
> 现在按 scoped 包发布为 `@wang121ye/skillmanager`,对应 npx 用法是 `npx @wang121ye/skillmanager ...`,但执行的命令名仍然叫 `skillmanager`(由 `bin` 决定)。
|
|
24
|
+
|
|
25
|
+
## 关键能力
|
|
26
|
+
|
|
27
|
+
### 1) 一键安装(默认装“全部来源的全部 skills”)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
skillmanager install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2) 安装时启用 Web UI 选择(默认全选,可批量全选/全不选/反选/搜索)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
skillmanager webui
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
你也可以用某个 profile 名称(会保存选择集到该 profile)。**多数情况下不需要显式传 `--profile`**,直接用默认 profile(通常是 `default` 或你在 `skillmanager config set-default-profile` 里设置的值)即可:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
skillmanager webui --profile laptop
|
|
43
|
+
skillmanager install --profile laptop
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 把 `--profile laptop` 设为默认(推荐)
|
|
47
|
+
|
|
48
|
+
设置一次默认 profile 后,绝大多数命令都可以不写 `--profile`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
skillmanager config set-default-profile laptop
|
|
52
|
+
skillmanager webui
|
|
53
|
+
skillmanager install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
你也可以用环境变量临时覆盖默认 profile:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
SKILLMANAGER_PROFILE=laptop skillmanager webui
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## profile 配置的“上传/下载”(换电脑同步)
|
|
63
|
+
|
|
64
|
+
你可以把某个 profile 上传到一个远端 URL(例如 OSS 对象地址),另一台电脑再 pull 回来。
|
|
65
|
+
|
|
66
|
+
> 安全提示:**开放公共写权限非常危险**,任何人都可以篡改你的 profile。更安全的做法是使用签名 URL、私有桶 + 凭证、或 Git 私有仓库。
|
|
67
|
+
|
|
68
|
+
### 1) 设置远端 URL(只需一次)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
skillmanager config set-remote-profile-url https://<bucket>.<region>.aliyuncs.com/skillmanager_profile.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
你也可以不写入本地配置,改用环境变量(更适合 CI/临时机器):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
SKILLMANAGER_PROFILE_URL=https://<bucket>.<region>.aliyuncs.com/skillmanager_profile.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2) 上传当前 profile
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
skillmanager config push --profile laptop
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3) 新电脑下载并写入本地 profile
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
skillmanager config pull --profile laptop
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3) 安装位置(交给 openskills)
|
|
93
|
+
|
|
94
|
+
- 默认:项目级(`./.claude/skills`)
|
|
95
|
+
- `--global`:全局(`~/.claude/skills`)
|
|
96
|
+
- `--universal`:使用通用目录(`.agent/skills` / `~/.agent/skills`)
|
|
97
|
+
|
|
98
|
+
示例:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
skillmanager install --global --universal
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4) 同步 `AGENTS.md`
|
|
105
|
+
|
|
106
|
+
默认会执行 `openskills sync`(输出到当前目录的 `AGENTS.md`)。
|
|
107
|
+
|
|
108
|
+
- 指定输出文件:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
skillmanager install --output AGENTS.md
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- 跳过同步:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
skillmanager install --no-sync
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 5) dry-run(只打印要装什么,不实际安装)
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
skillmanager install --dry-run
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 更方便地添加/管理第三方仓库
|
|
127
|
+
|
|
128
|
+
以后不需要手动去编辑 `sources.json`,可以直接用命令写入用户配置:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
skillmanager source add https://github.com/obra/superpowers
|
|
132
|
+
skillmanager source add ComposioHQ/awesome-claude-skills
|
|
133
|
+
skillmanager source list
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
禁用/启用/删除:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
skillmanager source disable superpowers
|
|
140
|
+
skillmanager source enable superpowers
|
|
141
|
+
skillmanager source remove superpowers
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> `source add` 支持输入 `owner/repo` 或 GitHub URL(也支持 `git@github.com:owner/repo.git`)。
|
|
145
|
+
|
|
146
|
+
## 更新已安装 skills(无论哪种来源)
|
|
147
|
+
|
|
148
|
+
### 默认更新(推荐)
|
|
149
|
+
|
|
150
|
+
这会调用 `openskills update` 更新所有 openskills 已记录的来源,然后重新 `sync`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
skillmanager update
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 如果你用了 profile 做“子集安装”(Web UI 勾选)
|
|
157
|
+
|
|
158
|
+
因为子集安装是按“本地目录复制安装”(为了兼容 Windows 下 openskills 的本地路径识别问题),`openskills update` 不一定能自动追踪来源;此时用 profile 方式更新最稳:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
skillmanager update --profile laptop
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
需要临时调整选择集:先用 Web UI 更新 profile,再执行 update:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
skillmanager webui --profile laptop
|
|
168
|
+
skillmanager update --profile laptop
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 卸载 skills
|
|
172
|
+
|
|
173
|
+
### 用 Web UI 勾选要卸载的已安装 skills(推荐)
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
skillmanager webui --mode uninstall
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 直接按名称卸载
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
skillmanager uninstall algorithmic-art xlsx
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 清空目标目录(危险操作,需要显式 --all)
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
skillmanager uninstall --all
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Skills 来源配置(官方 / 第三方 / 你自己的)
|
|
192
|
+
|
|
193
|
+
skillmanager 会在第一次运行时,把内置的 `manifests/sources.json` 复制到你的用户配置目录,之后你只需要编辑 **用户配置文件**即可:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
skillmanager paths
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
会打印出类似:
|
|
200
|
+
|
|
201
|
+
- `manifest: C:\Users\<you>\AppData\Roaming\skillmanager\sources.json`(Windows)
|
|
202
|
+
- macOS/Linux 则在 `~/.config/skillmanager/sources.json` 附近
|
|
203
|
+
|
|
204
|
+
`sources.json` 里维护三类来源(你可以继续追加第三方仓库):
|
|
205
|
+
|
|
206
|
+
- 官方:`anthropics/skills`
|
|
207
|
+
- 你的:`wangyendt/wayne-skills`
|
|
208
|
+
- 第三方:自行添加(`enabled: true`)
|
|
209
|
+
|
|
210
|
+
## 发布到 npm(给你未来用)
|
|
211
|
+
|
|
212
|
+
你要发布 scoped 包到 npm,一般是:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm login
|
|
216
|
+
npm publish --access public
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
(你提到的 npm 账号是 `wang121ye`:确保你拥有 `@wang121ye` scope 的发布权限;发布 scoped 包通常需要 `npm publish --access public`,本项目已在 `publishConfig` 里默认设置为 public。)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"sources": [
|
|
4
|
+
{
|
|
5
|
+
"id": "anthropic",
|
|
6
|
+
"name": "Anthropic Official Skills",
|
|
7
|
+
"kind": "git",
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"repo": "https://github.com/anthropics/skills.git",
|
|
10
|
+
"openskillsRef": "anthropics/skills"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "wayne",
|
|
14
|
+
"name": "Wayne Skills",
|
|
15
|
+
"kind": "git",
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"repo": "https://github.com/wangyendt/wayne-skills.git",
|
|
18
|
+
"openskillsRef": "wangyendt/wayne-skills"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "superpowers",
|
|
22
|
+
"name": "Obra Superpowers",
|
|
23
|
+
"kind": "git",
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"repo": "https://github.com/obra/superpowers.git",
|
|
26
|
+
"openskillsRef": "obra/superpowers"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "awesome-claude-skills",
|
|
30
|
+
"name": "Awesome Claude Skills (ComposioHQ)",
|
|
31
|
+
"kind": "git",
|
|
32
|
+
"enabled": true,
|
|
33
|
+
"repo": "https://github.com/ComposioHQ/awesome-claude-skills.git",
|
|
34
|
+
"openskillsRef": "ComposioHQ/awesome-claude-skills"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wang121ye/skillmanager",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Cross-platform agent skill manager (bootstrap + optional Web UI selection) built on top of openskills.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"skillmanager",
|
|
7
|
+
"openskills",
|
|
8
|
+
"claude",
|
|
9
|
+
"cursor",
|
|
10
|
+
"skills",
|
|
11
|
+
"agent"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/wangyendt/skillmanager#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/wangyendt/skillmanager/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/wangyendt/skillmanager.git"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Wayne",
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"bin": {
|
|
28
|
+
"skillmanager": "src/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"main": "src/index.js",
|
|
31
|
+
"files": [
|
|
32
|
+
"src/",
|
|
33
|
+
"manifests/"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.6.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node -e \"console.log('no tests yet')\""
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^13.1.0",
|
|
43
|
+
"express": "^4.21.2",
|
|
44
|
+
"fast-glob": "^3.3.2",
|
|
45
|
+
"gray-matter": "^4.0.3",
|
|
46
|
+
"openskills": "^1.5.0",
|
|
47
|
+
"simple-git": "^3.27.0",
|
|
48
|
+
"undici": "^7.4.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
|
|
4
|
+
const { Command } = require('commander');
|
|
5
|
+
|
|
6
|
+
const { bootstrap } = require('./commands/bootstrap');
|
|
7
|
+
const { selectUi } = require('./commands/select');
|
|
8
|
+
const { where } = require('./commands/where');
|
|
9
|
+
const { update } = require('./commands/update');
|
|
10
|
+
const { uninstall } = require('./commands/uninstall');
|
|
11
|
+
const { webui } = require('./commands/webui');
|
|
12
|
+
const { listSources, addSource, removeSource, setEnabled } = require('./commands/source');
|
|
13
|
+
const { showConfig, setDefaultProfileCmd, setRemoteProfileUrlCmd, pushProfileCmd, pullProfileCmd } = require('./commands/config');
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('skillmanager')
|
|
20
|
+
.description('跨平台 Agent Skills 管理器(基于 openskills)')
|
|
21
|
+
.version(require('../package.json').version);
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('install')
|
|
25
|
+
.description('安装 skills:按 sources 配置安装全部,或用 Web UI 选择子集安装。')
|
|
26
|
+
.option('--global', '安装到全局(默认:当前项目)', false)
|
|
27
|
+
.option('--universal', '使用通用目录 .agent/skills(默认:.claude/skills)', false)
|
|
28
|
+
.option('--output <path>', 'sync 输出文件(默认:AGENTS.md)')
|
|
29
|
+
.option('--no-sync', '跳过 openskills sync', false)
|
|
30
|
+
.option('--dry-run', '仅打印将执行的内容,不实际安装', false)
|
|
31
|
+
.option('--concurrency <n>', '选择模式下的并发扫描数(默认:3)', '3')
|
|
32
|
+
.option('--profile <name>', '选择配置名(默认:来自 config 或 SKILLMANAGER_PROFILE 环境变量)')
|
|
33
|
+
.action(async (opts) => {
|
|
34
|
+
await bootstrap(opts);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('webui')
|
|
39
|
+
.description('打开 Web UI:用于选择并安装(install)或选择并卸载(uninstall)。')
|
|
40
|
+
.option('--mode <install|uninstall>', '模式:install(选择并安装)或 uninstall(选择并卸载)', 'install')
|
|
41
|
+
.option('--profile <name>', '作用:install 模式下使用/保存到的 profile(默认:使用默认 profile)')
|
|
42
|
+
.option('--global', '作用:把目标切到全局目录(~/.claude/skills 或 ~/.agent/skills)', false)
|
|
43
|
+
.option('--universal', '作用:使用 .agent/skills(通用 AGENTS.md 场景;默认是 .claude/skills)', false)
|
|
44
|
+
.option('--output <path>', '作用:sync 输出文件路径(默认:AGENTS.md)')
|
|
45
|
+
.option('--no-sync', '作用:不执行 openskills sync(仅安装/卸载,不更新 AGENTS.md)', false)
|
|
46
|
+
.option('--concurrency <n>', '作用:install 模式下并发拉取/扫描来源仓库,提高速度(默认:3)', '3')
|
|
47
|
+
.action(async (opts) => {
|
|
48
|
+
await webui(opts);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
program
|
|
52
|
+
.command('paths')
|
|
53
|
+
.description('打印 skillmanager 的配置/缓存目录,以及 sources.json 位置。')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
await where();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
program
|
|
59
|
+
.command('update')
|
|
60
|
+
.description('更新 skills,并同步生成/更新 AGENTS.md。')
|
|
61
|
+
.option('--global', '安装到全局(默认:当前项目)', false)
|
|
62
|
+
.option('--universal', '使用通用目录 .agent/skills(默认:.claude/skills)', false)
|
|
63
|
+
.option('--output <path>', 'sync 输出文件(默认:AGENTS.md)')
|
|
64
|
+
.option('--no-sync', '跳过 openskills sync', false)
|
|
65
|
+
.option('--profile <name>', '按 profile 选择集更新(会重新安装选中的 skills)')
|
|
66
|
+
.option('--concurrency <n>', '选择模式下的并发扫描数(默认:3)', '3')
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
await update(opts);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command('uninstall')
|
|
73
|
+
.description('卸载 skills(从 .claude/skills 或 .agent/skills 删除,并可 sync 更新 AGENTS.md)。')
|
|
74
|
+
.argument('[skillNames...]', '要卸载的 skill 名称(目录名),可多个')
|
|
75
|
+
.option('--global', '卸载全局目录(默认:当前项目)', false)
|
|
76
|
+
.option('--universal', '使用通用目录 .agent/skills(默认:.claude/skills)', false)
|
|
77
|
+
.option('--all', '卸载目标目录下所有已安装 skills', false)
|
|
78
|
+
.option('--output <path>', 'sync 输出文件(默认:AGENTS.md)')
|
|
79
|
+
.option('--no-sync', '跳过 openskills sync', false)
|
|
80
|
+
.action(async (skillNames, opts) => {
|
|
81
|
+
await uninstall(opts, skillNames);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const config = program.command('config').description('配置管理:默认 profile、Web UI 选择等。');
|
|
85
|
+
|
|
86
|
+
config
|
|
87
|
+
.command('set-default-profile')
|
|
88
|
+
.description('设置默认 profile(当你不传 --profile 时会用它)。')
|
|
89
|
+
.argument('<name>', 'profile 名称,例如 laptop')
|
|
90
|
+
.action(async (name) => {
|
|
91
|
+
await setDefaultProfileCmd(name);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
config
|
|
95
|
+
.command('set-remote-profile-url')
|
|
96
|
+
.description('设置远端 profile URL(用于 config push/pull)。')
|
|
97
|
+
.argument('<url>', '例如 https://<bucket>.<region>.aliyuncs.com/skillmanager_profile.json')
|
|
98
|
+
.action(async (url) => {
|
|
99
|
+
await setRemoteProfileUrlCmd(url);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
config
|
|
103
|
+
.command('push')
|
|
104
|
+
.description('上传某个 profile 到远端 URL(HTTP PUT)。')
|
|
105
|
+
.option('--profile <name>', 'profile 名称(默认:default)', 'default')
|
|
106
|
+
.option('--url <url>', '远端 URL(可省略,使用 config.remoteProfileUrl 或 SKILLMANAGER_PROFILE_URL)')
|
|
107
|
+
.action(async (opts) => {
|
|
108
|
+
await pushProfileCmd(opts);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
config
|
|
112
|
+
.command('pull')
|
|
113
|
+
.description('从远端 URL 下载 profile 并保存到本地(HTTP GET)。')
|
|
114
|
+
.option('--profile <name>', 'profile 名称(默认:default)', 'default')
|
|
115
|
+
.option('--url <url>', '远端 URL(可省略,使用 config.remoteProfileUrl 或 SKILLMANAGER_PROFILE_URL)')
|
|
116
|
+
.action(async (opts) => {
|
|
117
|
+
await pullProfileCmd(opts);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
config
|
|
121
|
+
.command('show')
|
|
122
|
+
.description('显示配置(含默认 profile)。')
|
|
123
|
+
.action(async () => {
|
|
124
|
+
await showConfig();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const source = program.command('source').description('管理 skills 来源仓库(官方/第三方/自研)。');
|
|
128
|
+
|
|
129
|
+
source
|
|
130
|
+
.command('list')
|
|
131
|
+
.description('列出当前 sources.json 中的来源。')
|
|
132
|
+
.action(async () => {
|
|
133
|
+
await listSources();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
source
|
|
137
|
+
.command('add')
|
|
138
|
+
.description('添加来源:支持 owner/repo、GitHub URL、或 git@... SSH URL。')
|
|
139
|
+
.argument('<repoOrRef>', '例如 ComposioHQ/awesome-claude-skills 或 https://github.com/obra/superpowers')
|
|
140
|
+
.option('--id <id>', '指定来源 id(可选)')
|
|
141
|
+
.option('--name <name>', '显示名称(可选)')
|
|
142
|
+
.option('--ref <openskillsRef>', '指定 openskillsRef(可选,形如 owner/repo)')
|
|
143
|
+
.option('--disabled', '添加为禁用(bootstrap 默认不安装)', false)
|
|
144
|
+
.action(async (repoOrRef, opts) => {
|
|
145
|
+
await addSource(repoOrRef, opts);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
source
|
|
149
|
+
.command('remove')
|
|
150
|
+
.description('删除来源(按 id)。')
|
|
151
|
+
.argument('<id>', '来源 id')
|
|
152
|
+
.action(async (id) => {
|
|
153
|
+
await removeSource(id);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
source
|
|
157
|
+
.command('enable')
|
|
158
|
+
.description('启用来源(按 id)。')
|
|
159
|
+
.argument('<id>', '来源 id')
|
|
160
|
+
.action(async (id) => {
|
|
161
|
+
await setEnabled(id, true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
source
|
|
165
|
+
.command('disable')
|
|
166
|
+
.description('禁用来源(按 id)。')
|
|
167
|
+
.argument('<id>', '来源 id')
|
|
168
|
+
.action(async (id) => {
|
|
169
|
+
await setEnabled(id, false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await program.parseAsync(process.argv);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main().catch((err) => {
|
|
176
|
+
console.error(err?.stack || String(err));
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
});
|
|
179
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const { getAppPaths } = require('../lib/paths');
|
|
2
|
+
const { ensureDir } = require('../lib/fs');
|
|
3
|
+
const { loadSourcesManifest } = require('../lib/manifest');
|
|
4
|
+
const { ensureRepo } = require('../lib/git');
|
|
5
|
+
const { scanSkillsInRepo } = require('../lib/scan');
|
|
6
|
+
const { loadProfile } = require('../lib/profiles');
|
|
7
|
+
const { mapWithConcurrency } = require('../lib/concurrency');
|
|
8
|
+
const { getEffectiveDefaultProfile } = require('../lib/config');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { installSourceRef, syncAgents } = require('../lib/openskills');
|
|
12
|
+
const { installFromLocalSkillDir } = require('../lib/local-install');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
function uniq(arr) {
|
|
16
|
+
return Array.from(new Set(arr));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function bootstrap(opts) {
|
|
20
|
+
const paths = getAppPaths();
|
|
21
|
+
await ensureDir(paths.reposDir);
|
|
22
|
+
await ensureDir(paths.profilesDir);
|
|
23
|
+
|
|
24
|
+
const { sources } = await loadSourcesManifest();
|
|
25
|
+
const enabledSources = sources.filter((s) => s && s.enabled !== false);
|
|
26
|
+
|
|
27
|
+
const profileName = opts?.profile || (await getEffectiveDefaultProfile());
|
|
28
|
+
const existing = await loadProfile({ profilesDir: paths.profilesDir, profileName });
|
|
29
|
+
|
|
30
|
+
const wantsSelection = existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds);
|
|
31
|
+
const globalInstall = !!opts?.global;
|
|
32
|
+
const universal = !!opts?.universal;
|
|
33
|
+
|
|
34
|
+
if (!wantsSelection) {
|
|
35
|
+
// Default path: install everything from each enabled source via openskills directly.
|
|
36
|
+
// This avoids repo scanning and matches “bootstrap 安装所有 skills”的默认体验。
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log(`将从 ${enabledSources.length} 个来源安装(global=${globalInstall}, universal=${universal})…`);
|
|
39
|
+
|
|
40
|
+
if (opts?.dryRun) {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log('\n--dry-run 已启用:将安装以下来源(不执行安装)');
|
|
43
|
+
for (const s of enabledSources) {
|
|
44
|
+
const ref = s.openskillsRef || s.repo;
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.log(`- ${s.name || s.id} (${ref || 'missing-ref'})`);
|
|
47
|
+
}
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.log('\n完成(dry-run)。');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const s of enabledSources) {
|
|
54
|
+
const ref = s.openskillsRef || s.repo;
|
|
55
|
+
if (!ref) continue;
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.log(`\n==> Installing source: ${s.name || s.id} (${ref})`);
|
|
58
|
+
await installSourceRef({ ref, globalInstall, universal });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (opts?.sync !== false) {
|
|
62
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.log('\n完成。');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Selection path: clone repos + scan SKILL.md, then install selected skill dirs.
|
|
71
|
+
const concurrency = Number(opts?.concurrency || process.env.SKILLMANAGER_CONCURRENCY || 3);
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log(`并发扫描:${Math.max(1, concurrency)}(可用 --concurrency 或环境变量 SKILLMANAGER_CONCURRENCY 调整)`);
|
|
74
|
+
|
|
75
|
+
const skillsById = new Map();
|
|
76
|
+
const perSource = await mapWithConcurrency(enabledSources, concurrency, async (s) => {
|
|
77
|
+
try {
|
|
78
|
+
const repoDir = await ensureRepo({ reposDir: paths.reposDir, source: s });
|
|
79
|
+
const skills = await scanSkillsInRepo({
|
|
80
|
+
sourceId: s.id,
|
|
81
|
+
sourceName: s.name || s.id,
|
|
82
|
+
repoDir
|
|
83
|
+
});
|
|
84
|
+
return { source: s, skills };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.warn(`警告:拉取/扫描来源失败,将跳过:${s.name || s.id}`);
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.warn(err?.message || String(err));
|
|
90
|
+
return { source: s, skills: [] };
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (const { skills } of perSource) {
|
|
95
|
+
for (const sk of skills) skillsById.set(sk.id, sk);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const allSkills = Array.from(skillsById.values());
|
|
99
|
+
let selectedIds =
|
|
100
|
+
existing?.selectedSkillIds && Array.isArray(existing.selectedSkillIds)
|
|
101
|
+
? existing.selectedSkillIds
|
|
102
|
+
: allSkills.map((s) => s.id);
|
|
103
|
+
|
|
104
|
+
// 交互式选择已迁移到:skillmanager webui(mode=install)
|
|
105
|
+
|
|
106
|
+
selectedIds = uniq(selectedIds).filter((id) => skillsById.has(id));
|
|
107
|
+
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log(`将安装 ${selectedIds.length} 个 skills(global=${globalInstall}, universal=${universal})…`);
|
|
110
|
+
|
|
111
|
+
if (opts?.dryRun) {
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.log('\n--dry-run 已启用:仅展示前 30 个将安装的 skills(按解析顺序)');
|
|
114
|
+
for (const id of selectedIds.slice(0, 30)) {
|
|
115
|
+
const s = skillsById.get(id);
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
console.log(`- ${s.name} [${s.sourceId}] (${s.id})`);
|
|
118
|
+
}
|
|
119
|
+
if (selectedIds.length > 30) {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.log(`… 还有 ${selectedIds.length - 30} 个`);
|
|
122
|
+
}
|
|
123
|
+
// eslint-disable-next-line no-console
|
|
124
|
+
console.log('\n完成(dry-run)。');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 2) install selected
|
|
129
|
+
for (const id of selectedIds) {
|
|
130
|
+
const skill = skillsById.get(id);
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.log(`\n==> Installing: ${skill.name} (${skill.sourceId})`);
|
|
133
|
+
|
|
134
|
+
// NOTE: On Windows, openskills does not recognize absolute paths like C:\...
|
|
135
|
+
// So for selection-mode we perform a direct local install (copy) ourselves, then rely on openskills sync.
|
|
136
|
+
const folder = universal ? '.agent/skills' : '.claude/skills';
|
|
137
|
+
const targetDir = globalInstall ? path.join(os.homedir(), folder) : path.join(process.cwd(), folder);
|
|
138
|
+
const { targetPath } = await installFromLocalSkillDir({ skillDir: skill.skillDir, targetDir });
|
|
139
|
+
// eslint-disable-next-line no-console
|
|
140
|
+
console.log(`✅ Installed (local copy): ${targetPath}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3) sync AGENTS.md (optional)
|
|
144
|
+
if (opts?.sync !== false) {
|
|
145
|
+
await syncAgents({ output: opts?.output, cwd: process.cwd() });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.log('\n完成。');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { bootstrap };
|
|
153
|
+
|