skillcaddy 0.27.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/CHANGELOG.md +497 -0
- package/CONTEXT.md +149 -0
- package/LICENSE +21 -0
- package/README.md +499 -0
- package/README_CN.md +482 -0
- package/collection-metadata/github/mattpocock.json +16 -0
- package/lib/canonicalJson.js +11 -0
- package/lib/claudeStore.js +135 -0
- package/lib/collectionEnablement.js +115 -0
- package/lib/collectionSetup.js +199 -0
- package/lib/enablePlan.js +116 -0
- package/lib/enablementScope.js +8 -0
- package/lib/hermesStore.js +21 -0
- package/lib/libraryImage.js +1785 -0
- package/lib/managerInstall.js +109 -0
- package/lib/managerSourceWorkflow.js +234 -0
- package/lib/projectActions.js +24 -0
- package/lib/projectPath.js +51 -0
- package/lib/roots.js +99 -0
- package/lib/skillLinkStore.js +68 -0
- package/lib/skillMetadata.js +191 -0
- package/lib/skillStore.js +541 -0
- package/lib/sourceAcquisition.js +475 -0
- package/lib/sourceAcquisitionAdapter.js +47 -0
- package/lib/sourceAcquisitionError.js +18 -0
- package/lib/sourceAcquisitionPolicy.js +139 -0
- package/lib/sourceArchive.js +363 -0
- package/lib/sourceArchiveWorkspace.js +36 -0
- package/lib/sourceGit.js +95 -0
- package/lib/sourceGitAcquisitionAdapter.js +145 -0
- package/lib/sourceGitBatchUpdate.js +168 -0
- package/lib/sourceGitRepair.js +227 -0
- package/lib/sourceGitStaging.js +139 -0
- package/lib/sourceGitUpgradeAdapter.js +583 -0
- package/lib/sourceGitUrl.js +170 -0
- package/lib/sourceHttp.js +302 -0
- package/lib/sourceLocal.js +200 -0
- package/lib/sourceLocalAcquisitionAdapter.js +107 -0
- package/lib/sourceManager.js +242 -0
- package/lib/sourceMigration.js +172 -0
- package/lib/sourceMigrationIssue.js +6 -0
- package/lib/sourcePath.js +6 -0
- package/lib/sourcePolicy.js +53 -0
- package/lib/sourceRecord.js +295 -0
- package/lib/sourceRegistry.js +155 -0
- package/lib/sourceRemoteArchiveAcquisitionAdapter.js +147 -0
- package/lib/sourceRemoteFileAcquisitionAdapter.js +95 -0
- package/lib/sourceRemoteFilePolicy.js +62 -0
- package/lib/sourceRemoteFileWorkspace.js +55 -0
- package/lib/sourceSkillDiscovery.js +117 -0
- package/lib/sourceTree.js +185 -0
- package/lib/sourceUpgrade.js +596 -0
- package/lib/sourceUpgradeAdapter.js +41 -0
- package/lib/sourceUpgradePolicy.js +170 -0
- package/lib/sourceUpgradeTransaction.js +202 -0
- package/lib/sourceValidation.js +120 -0
- package/lib/sourceWorkspace.js +37 -0
- package/lib/specGate.js +235 -0
- package/lib/tuiActions.js +169 -0
- package/lib/tuiInstall.js +133 -0
- package/lib/tuiLayout.js +94 -0
- package/lib/version.js +17 -0
- package/lib/webManager.js +370 -0
- package/package.json +74 -0
- package/public/agentsUi.js +138 -0
- package/public/app.js +1431 -0
- package/public/claude.css +2 -0
- package/public/claudeUi.js +119 -0
- package/public/emptyState.js +12 -0
- package/public/enabled.css +303 -0
- package/public/favicon.svg +5 -0
- package/public/index.html +199 -0
- package/public/skillcaddy_CN.png +0 -0
- package/public/skillcaddy_EN.png +0 -0
- package/public/styles.css +1611 -0
- package/scripts/cli.js +647 -0
- package/scripts/cron-sync-update-git.sh +128 -0
- package/scripts/pull-github.sh +22 -0
- package/scripts/source.js +638 -0
- package/scripts/tui.js +1058 -0
- package/server.js +196 -0
- package/skills/skillcaddy-manager/SKILL.md +93 -0
- package/skills/skillcaddy-manager/agents/openai.yaml +7 -0
- package/skills/skillcaddy-manager/references/METADATA.md +75 -0
- package/skills/skillcaddy-manager/references/OPERATIONS.md +315 -0
- package/skills/skillcaddy-manager/references/RECOMMENDATION_GUIDE.md +51 -0
- package/skills/skillcaddy-manager/references/featured-skills.json +564 -0
- package/skills/skillcaddy-manager/references/skill-platforms.json +48 -0
- package/skills/skillcaddy-manager/scripts/check-conflicts.cjs +117 -0
- package/skills/skillcaddy-manager/scripts/check-global-skills.cjs +169 -0
- package/skills/skillcaddy-manager/scripts/translate-skill-notes.cjs +297 -0
- package/skills/skillcaddy-manager/scripts/version-manager.cjs +146 -0
- package/skills/skillcaddy-manager/scripts/view-recommendations.cjs +279 -0
- package/skills/skillcaddy-manager/skillcaddy.json +9 -0
- package/skills/spec-pipeline/spec-direction-gate/SKILL.md +119 -0
- package/skills/spec-pipeline/spec-direction-gate/check-approval.py +110 -0
- package/skills/spec-pipeline/spec-draft-intake/SKILL.md +116 -0
- package/skills/spec-pipeline/spec-role-contract/SKILL.md +113 -0
- package/skills/spec-pipeline/spec-role-contract/check-roles.py +108 -0
package/README_CN.md
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# Skillcaddy
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [中文](README_CN.md)
|
|
4
|
+
|
|
5
|
+
本地 AI Skills 中央库 + 按作用域软链接启用。一个原件库目录装下所有 skill 源,按需 symlink 到项目、用户共享的 Agents 目录或 Hermes。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 为什么是 Skillcaddy?
|
|
10
|
+
|
|
11
|
+
如果你在多个项目里同时用 Claude Code / Codex / OpenCode / Pi,迟早会遇到这些场景:
|
|
12
|
+
|
|
13
|
+
- 同一个 skill 散落在三个 repo 里,各自略微漂移
|
|
14
|
+
- 新开项目就要把 skill 复制一遍,分不清哪个版本才是最新
|
|
15
|
+
- 上游 skill 更新了,本地副本却落后几周
|
|
16
|
+
- 需要 Claude Code 专属入口,同时还要保留 agents 列表干净
|
|
17
|
+
- archived 的 skill 不小心又被启用了——因为没人设门禁
|
|
18
|
+
|
|
19
|
+
Skillcaddy 用一个原件库目录作单一事实来源,用明确的项目/全局/Hermes symlink 作用域作投递机制,把这些问题一次性解决。
|
|
20
|
+
|
|
21
|
+
- **单一事实来源** — 一个原件库目录汇总 `official / github / personal / archived`;仓库自带的 `skills/` 随程序发布
|
|
22
|
+
- **原件库与安装形态无关** — 原件库默认在 `~/.skillcaddy/library`,重装或升级 Skillcaddy 不会动到你已装好的 skill
|
|
23
|
+
- **零项目污染** — 启用靠 symlink 落到 `.agents/skills/`,绝不复制
|
|
24
|
+
- **项目或全局作用域** — 项目链接留在项目内;全局链接只写入 `~/.agents/skills/`
|
|
25
|
+
- **独立 Hermes 作用域** — `official`/`github`/`personal` 来源都可以显式链接到 `~/.hermes/skills/`;不跟随 `HERMES_HOME`,也不会改变项目/全局作用域
|
|
26
|
+
- **天生多 Agent 友好** — 一个 symlink 同时被 Claude Code / Codex / OpenCode / Pi 走各自标准路径识别
|
|
27
|
+
- **独立管理启用 / 禁用** — agents 列与 Claude Code 列各自跟踪
|
|
28
|
+
- **安全默认** — 禁用只删 symlink;`archived/` 必须显式点名才启用
|
|
29
|
+
- **便于贡献** — 新 skill 放在 `skills/<name>/`,配 `SKILL.md` + `agents/openai.yaml`,随本仓库发布
|
|
30
|
+
|
|
31
|
+
不管你是独自维护多个 repo 的个人开发者、想统一 skill 资产的小团队,还是想发布可复用 skill 的作者——契约都一样:skill 属于你的库,不属于任何单一项目。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx skillcaddy # 免安装直接跑
|
|
37
|
+
npm install -g skillcaddy # 装成全局命令
|
|
38
|
+
brew install skillcaddy # macOS / Linuxbrew
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
也可以用 shell 安装脚本:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
curl -fsSL https://raw.githubusercontent.com/chenweil/skillcaddy/main/scripts/install.sh | bash
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
或者从源码运行:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/chenweil/skillcaddy.git
|
|
51
|
+
cd skillcaddy
|
|
52
|
+
npm install
|
|
53
|
+
npm start
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
需要 Node.js >= 20。Web 管理器默认固定使用 `http://127.0.0.1:4173`。在页面里填写目标项目路径,启用/禁用 skill。如果该端口临时被占用,可以用 `PORT=<其他端口> npm start` 临时覆盖。
|
|
57
|
+
|
|
58
|
+
### 原件库放在哪里
|
|
59
|
+
|
|
60
|
+
Skillcaddy 把**代码根**(程序本身:`package.json`、`server.js`、仓库自带的 `skills/`)和**原件库根**(你的 skill:`official / github / personal / archived`、source 注册表、skill metadata)分开了。
|
|
61
|
+
|
|
62
|
+
- **clone 形态**:原件库仍在 clone 目录里,和以前完全一致。
|
|
63
|
+
- **发行形态**(`npx` / `npm i -g` / `brew`):原件库在 `~/.skillcaddy/library`,重装或升级程序不会碰它。
|
|
64
|
+
- `SKILLCADDY_ROOT` 或 `--root <目录>` 可以指向任意其它原件库目录;`scripts/pull-github.sh` 用的就是这个变量。
|
|
65
|
+
|
|
66
|
+
`skillcaddy -a` 会同时报告两个根,TUI 摘要里也会打印原件库根。
|
|
67
|
+
|
|
68
|
+
### 随仓库提供的 spec-pipeline 技能
|
|
69
|
+
|
|
70
|
+
本仓库在 `skills/spec-pipeline/` 下提供三个技能,实现 [`docs/research/0037`](docs/research/0037-spec-doc-pipeline-gaps-improvement-plan.md) 描述的方向确认关卡。技能内容已入版本控制,clone 后即存在;但**项目级启用链接不会随 clone 到达**,这是设计如此:ADR 0011 Decision 4 明确把项目级启用排除在库镜像之外(`lib/libraryImage.js` 只携带 `global` 和 `hermes` 三元组)。每个 checkout 重建一次即可:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run enable:spec-pipeline
|
|
74
|
+
# 只预览,不做任何改动
|
|
75
|
+
npm run enable:spec-pipeline -- --dry-run
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
然后检查本仓库内所有 spec 是否通过关卡:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm run check:specs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
两个命令都可重复执行。`check:specs` 同时是 `npm test` 的一环;没有 `specs/` 目录的仓库判为通过,加 `--strict` 可反转这一判定。
|
|
85
|
+
|
|
86
|
+
Web 原件库中的项目、全局和 Hermes 启用彼此独立。全局启用 skill 后,项目操作仍然可用:如果当前项目需要自己的 setup、Claude Code 同步或明确的项目级优先关系,仍可把同一个 skill 添加到当前项目。库级操作会分别显示项目/全局/Hermes 启用进度;关键词搜索自动展开命中的库,来源和标签筛选则保持折叠,方便紧凑浏览。已启用面板把项目 Agents 与 Claude Code 并列为项目通道,把全局与 Hermes 放在下面的整行通道;新增的共享搜索可以按名称或来源快速定位已启用 skill,不必在长列表中滚动查找。
|
|
87
|
+
|
|
88
|
+
### 全局 CLI / TUI 命令
|
|
89
|
+
|
|
90
|
+
发行形态安装后 `skillcaddy` 命令已经就位。用 clone 开发时,把这个 clone 链到全局(本地 `npm link`):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run install:cli
|
|
94
|
+
npm run check:cli
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
安装后,`skillcaddy` 同时提供 CLI 和 TUI。旧的 `install:tui` / `check:tui` 仍保留为兼容别名;也可以在仓库内直接运行 `npm run tui -- install cli`,或进入 TUI 后选择 `11. 安装/检查全局 CLI + TUI 命令`。
|
|
98
|
+
|
|
99
|
+
安装一次本地 CLI/TUI 命令后,也可以从任意项目直接使用统一入口管理 Web 和 skill:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
skillcaddy start [projectPath] # 启动 Web 管理器
|
|
103
|
+
skillcaddy stop # 停止 Skillcaddy 自己启动的 Web
|
|
104
|
+
skillcaddy restart [projectPath] # 重启 Web 管理器
|
|
105
|
+
skillcaddy -v # 查看版本
|
|
106
|
+
skillcaddy -h # 查看帮助
|
|
107
|
+
skillcaddy -u [projectPath] # 安全更新已登记的 Git skill 源
|
|
108
|
+
skillcaddy -a [projectPath] # 分析项目状态、诊断和推荐
|
|
109
|
+
skillcaddy enable <skill-id> --project <dir> [--alias <name>]
|
|
110
|
+
skillcaddy enable <skill-id> --global [--alias <name>]
|
|
111
|
+
skillcaddy enable <skill-id> --hermes [--alias <name>]
|
|
112
|
+
skillcaddy disable <alias> --project <dir>
|
|
113
|
+
skillcaddy disable <alias> --global
|
|
114
|
+
skillcaddy disable <alias> --hermes
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`-a` 是只读汇报,不会创建原件库目录、安装、启用或写入 metadata;`-u` 复用已有的 fast-forward-only Git 源更新流程,必须显式提供当前项目路径,同时总会检查全局链接。`start` 会复用已经运行的 Web,`stop/restart` 只处理能确认由 Skillcaddy 管理的进程,不会按端口误杀外部服务。`--root` 选择原件库根,对所有命令生效(包括 Web 生命周期);Web 进程始终运行提供可执行文件的代码根。未带参数的 `skillcaddy` 仍然进入 TUI。
|
|
118
|
+
|
|
119
|
+
在无法验证进程归属的平台上,Web stop/restart 会安全失败,不会猜测并终止进程。
|
|
120
|
+
|
|
121
|
+
如果暂时不想安装全局命令,仓库内也可以直接运行同一入口:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm run tui -- start --no-open
|
|
125
|
+
npm run tui -- stop
|
|
126
|
+
npm run tui -- -a /path/to/project
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 终端 UI(TUI)
|
|
130
|
+
|
|
131
|
+
启动交互式终端管理器:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm run tui -- /path/to/project
|
|
135
|
+
# 或显式指定原件库根目录
|
|
136
|
+
npm run tui -- --root ~/.skillcaddy/library /path/to/project
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
如果希望在任意项目中使用这份 clone 的原件库,只需安装一次本地 CLI/TUI 命令:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run install:cli
|
|
143
|
+
npm run check:cli
|
|
144
|
+
|
|
145
|
+
cd /path/to/another-project
|
|
146
|
+
skillcaddy
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`install:cli` 使用本地 `npm link`,链接的是当前 clone 而不是下载一个包。全局 `skillcaddy` 命令始终链接到当前 clone;这个 clone 仍是中央原件库,后续通过 `git pull` 更新即可。不带参数运行 `skillcaddy` 会管理当前目录,也可以用 `skillcaddy /path/to/project` 指定其它项目。如果全局同名 package 已链接到另一份 clone,安装会停止,不会覆盖。
|
|
150
|
+
|
|
151
|
+
TUI 提供完整的键盘驱动界面,无需浏览器:
|
|
152
|
+
|
|
153
|
+
- **查看已启用 skill** — Agents 和 Claude Code 双列并排展示
|
|
154
|
+
- **浏览/搜索 skill 库** — 关键词搜索、来源过滤、库级钻取
|
|
155
|
+
- **启用 skill** — 创建 `.agents/skills/` 软链接并自动同步 Claude Code
|
|
156
|
+
- **全局启用 skill** — 不依赖项目,在 `~/.agents/skills/` 创建共享链接
|
|
157
|
+
- **独立 Hermes 启用** — 选项 14 只把 `official`/`github`/`personal` skill 显式链接到 `~/.hermes/skills/`
|
|
158
|
+
- **清理已启用 skill** — 仅删除项目软链接,原件安全
|
|
159
|
+
- **管理全局 skill** — 选项 13 查看、启用并安全清理当前原件库在 `~/.agents/skills/` 拥有的全局链接
|
|
160
|
+
- **同步 Claude Code** — 一键同步 `.claude/skills/` 与 `.agents/skills/`
|
|
161
|
+
- **编辑 metadata** — 内联编辑备注、tags、一键加入开关
|
|
162
|
+
- **查看诊断建议** — 检测重复名、断链、来源漂移等问题
|
|
163
|
+
- **跟踪库初始化** — 区分“已启用软链接”和“项目配置已就绪”,并引导交互式 setup
|
|
164
|
+
- **处理重复名** — 使用建议名称或自定义名称启用指定来源的 skill,不重命名原件
|
|
165
|
+
- **刷新项目** — 重载状态、切换项目路径
|
|
166
|
+
- **更新 GitHub 源** — 批量 fast-forward pull `github/` 仓库
|
|
167
|
+
- **批量生成中文 note** — 交互式流程(选项 10),为仅有英文 `description`、缺少中文 `note` 的 skill 补全中文介绍
|
|
168
|
+
- **安装/检查全局命令** — 选项 11,安装当前 clone 提供的 CLI + TUI 命令
|
|
169
|
+
- **按地址获取远程 Source** — 选项 12,将完整 HTTPS/SSH/SCP Git 仓库、公开 HTTP(S) ZIP 或稳定的 HTTP(S) `/SKILL.md` 直链加入中央原件库
|
|
170
|
+
|
|
171
|
+
浏览库时改为紧凑分页表格展示(`n`/`p` 翻页,`a` 一键加入该库)。skill 介绍优先使用 metadata 中的 `note`,而非原始英文 `description`。
|
|
172
|
+
|
|
173
|
+
菜单导航使用数字键(1-14)选择操作,`/关键词` 搜索,`b` 返回,`q` 退出。适合终端快速操作或无头环境使用。
|
|
174
|
+
|
|
175
|
+
如果希望 AI Agent 在任意项目里都能使用仓库自带的 `skillcaddy-manager`,首次安装后执行一次:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm run install:manager
|
|
179
|
+
npm run check:manager
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
这会创建受 Skillcaddy 管理的全局软链接:`~/.agents/skills/skillcaddy-manager` 指向本仓库的 `skills/skillcaddy-manager`。如果目标位置已经存在文件、目录,或指向其它位置的软链接,命令会停止,不会覆盖。
|
|
183
|
+
|
|
184
|
+
也可以通过 URL 直接传递项目路径:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
http://127.0.0.1:4173/?projectPath=<encoded-project-path>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
页面会立即加载该项目,把最近使用的项目路径保存在浏览器本地历史中,并支持通过库标题旁的 `+` 一次启用该库内可用的全部 skills。已启用面板会按 Agents、Claude Code、全局 Agents、Hermes 四个通道展示,并用一个共享搜索框过滤所有已启用条目。如果误点启用了整个库,可以用库标题旁的 `×` 同时清理该库在 Agents 和 Claude Code 中的启用入口。
|
|
191
|
+
|
|
192
|
+
## Skill 元数据
|
|
193
|
+
|
|
194
|
+
`SKILL.md` 仍然是给 Agent 使用的契约。面向人的备注和分类由 Skillcaddy 存到 `.skillcaddy/metadata/.../skillcaddy.json`,避免污染外部原件库:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"note": "适合在代码修改前后做执行协议检查,避免漏测和误改。",
|
|
199
|
+
"tags": ["Developer Tools", "Quality", "Workflow"],
|
|
200
|
+
"autoEnable": true
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
运行时扫描与 Web UI 只从本地 sidecar 存储读取 metadata。Tags 会显示为过滤 tab 和 pill 标签,备注会显示在每个 skill 卡片上。把 `autoEnable` 设为 `false` 后,废弃或有风险的 skill 会被库级一键加入跳过,但仍可单独手动启用。这样既不污染上游原件库,也能让大型本地 skill 库更容易浏览。
|
|
205
|
+
|
|
206
|
+
从 v0.15.0 开始,正常运行时扫描会忽略旧的 `<skill-dir>/skillcaddy.json`。迁移命令仍会显式发现这些文件,保留至 v0.15.x,并在 v0.16.0 删除。可先预检,再显式执行迁移:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npm run migrate:metadata
|
|
210
|
+
npm run migrate:metadata -- --yes
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
执行迁移会写入等价的 sidecar metadata,并保留 legacy 文件用于回滚;写入 sidecar 后运行时才会使用迁移后的 metadata。
|
|
214
|
+
|
|
215
|
+
## 库初始化生命周期
|
|
216
|
+
|
|
217
|
+
部分 collection 在启用 skills 后,还需要针对每个项目执行一次 setup。Skillcaddy 把契约保存在第三方 clone 之外的 `collection-metadata/<source>/<collection>.json`,并通过 `/api/state` 把状态报告为 `missing`、`partial`、`ready` 或 `invalid`。全局启用不会运行或要求项目 setup。
|
|
218
|
+
|
|
219
|
+
库级一键启用通过 `scope`(`project`、`global` 或 `hermes`)调用 `POST /api/enable-plan` 和 `POST /api/enable-collection`。项目作用域会纳入声明的 setup 指引;全局作用域只创建 `~/.agents/skills` 链接;Hermes 作用域接受 `official`、`github`、`personal` 来源并创建 `~/.hermes/skills/` 直接链接。统一生命周期会把每个候选 skill 分类为已启用、已存在、已跳过或失败。项目配置不完整时只显示为“待配置”,不会被宣称为“已就绪”;交互式 setup 不会静默运行,collection metadata 也不能携带可执行 shell 命令。
|
|
220
|
+
|
|
221
|
+
## 平台兼容性
|
|
222
|
+
|
|
223
|
+
| 平台 | 状态 | 备注 |
|
|
224
|
+
|------|------|------|
|
|
225
|
+
| macOS | ✅ 完全支持 | 原生目录符号链接 |
|
|
226
|
+
| Linux | ✅ 完全支持 | 原生目录符号链接 |
|
|
227
|
+
| Windows | ⚠️ 需额外配置 | 详见下方说明 |
|
|
228
|
+
|
|
229
|
+
### Windows 前置条件
|
|
230
|
+
|
|
231
|
+
Skillcaddy 通过 Node.js 的 `fs.symlink(..., 'dir')` 创建目录符号链接。在 Windows 上这一调用需要满足下列条件之一,否则会抛出 `EPERM`:
|
|
232
|
+
|
|
233
|
+
1. **开启"开发人员模式"**(推荐)
|
|
234
|
+
- 设置 → 隐私和安全 → 开发者选项 → **开发人员模式**
|
|
235
|
+
- 适用于 Windows 10 创意者更新(1703)及以上
|
|
236
|
+
2. **以管理员身份运行**
|
|
237
|
+
- 用管理员终端执行 `npm start`
|
|
238
|
+
|
|
239
|
+
### Windows 已知限制
|
|
240
|
+
|
|
241
|
+
- `readlink` 返回的目标路径可能带有 `\\?\` 前缀或反斜杠,可能影响"别名重复指向"检测(`enableSkill` 中的 `existingTarget !== resolvedSkillPath` 判断)。
|
|
242
|
+
- NTFS 默认大小写不敏感,但代码按大小写敏感比较别名(一般不影响日常使用,但同义大小写的别名会被视为两个 skill)。
|
|
243
|
+
- 没有针对 Windows 的路径规整、junction fallback 或复制降级。
|
|
244
|
+
|
|
245
|
+
### 计划中的兼容改进(未实现)
|
|
246
|
+
|
|
247
|
+
为了让 Windows 用户开箱即用,后续会引入以下策略,但**当前版本均未实现**:
|
|
248
|
+
|
|
249
|
+
- **平台分支**:检测到 `process.platform === 'win32'` 时优先使用 junction(`fs.symlink(target, path, 'junction')`),junction 不需要开发者模式。
|
|
250
|
+
- **失败降级**:捕获 `EPERM` 后递归复制 skill 内容到 `.agents/skills/`,并在 `disableSkill` 中改为删除真实目录。
|
|
251
|
+
- **路径规整**:`resolveLinkTarget` 去掉 `\\?\` 前缀、统一分隔符、Windows 下做大小写不敏感比较。
|
|
252
|
+
- **README Windows 段**:补充 PowerShell 命令、磁盘格式要求(NTFS)、junction 与 symlink 的取舍说明。
|
|
253
|
+
|
|
254
|
+
## 架构示意
|
|
255
|
+
|
|
256
|
+

|
|
257
|
+
|
|
258
|
+
**核心设计**:
|
|
259
|
+
- `.agents/skills` 是跨 Agent 标准路径,所有 Agent 都能识别
|
|
260
|
+
- `.claude/skills` 为 Claude Code 专用,但通过二次软链接指向 `.agents/skills`
|
|
261
|
+
- Skillcaddy 的全局作用域只管理 `~/.agents/skills/`;全局 Claude 目录只被 Agent 扫描,本管理器不会写入
|
|
262
|
+
- 一次启用,多 Agent 共享;禁用时只删软链接,原件安全
|
|
263
|
+
|
|
264
|
+
## 目录约定
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
skillcaddy/ # 代码根
|
|
268
|
+
├── lib/ # 管理器代码
|
|
269
|
+
├── public/ # Web UI
|
|
270
|
+
├── scripts/ # 入口与维护脚本(如 pull-github.sh)
|
|
271
|
+
├── skills/ # 仓库自带的 skill(随程序发布;本仓库内有 skillcaddy-manager)
|
|
272
|
+
├── collection-metadata/ # 合集初始化契约
|
|
273
|
+
├── server.js
|
|
274
|
+
└── test/
|
|
275
|
+
|
|
276
|
+
~/.skillcaddy/library/ # 原件库根(发行形态)
|
|
277
|
+
├── official/ # 官方/上游 skill
|
|
278
|
+
├── github/ # 从 GitHub 克隆来的 skill
|
|
279
|
+
├── personal/ # 个人原创 skill
|
|
280
|
+
├── archived/ # 停用的 skill
|
|
281
|
+
└── .skillcaddy/ # source 注册表 + skill metadata sidecar
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
四个外部 skill 源目录(`official/github/personal/archived`)已加入 `.gitignore`。请使用下面的受管 source 命令,不再手工选择目标目录。`skills/` 是仓库自带的源,与本项目一同发布,不在 `.gitignore` 内。
|
|
285
|
+
|
|
286
|
+
## 受管 source 获取
|
|
287
|
+
|
|
288
|
+
首个版本通过仓库内的 npm 命令提供:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# 只读清单与检查
|
|
292
|
+
npm run source -- list
|
|
293
|
+
|
|
294
|
+
# 搬迁中央库,包括离线来源和用户级启用
|
|
295
|
+
npm run source -- image export /path/to/library.tar.gz
|
|
296
|
+
npm run source -- image import /path/to/library.tar.gz --dry-run
|
|
297
|
+
npm run source -- image import /path/to/library.tar.gz --yes
|
|
298
|
+
npm run source -- inspect github/example/toolbox
|
|
299
|
+
|
|
300
|
+
# 先预览,再获取 Git 仓库、公开 HTTP(S) ZIP、直接 SKILL.md、
|
|
301
|
+
# 本地 ZIP 或本地目录
|
|
302
|
+
npm run source -- add <input>
|
|
303
|
+
npm run source -- add <input> --yes
|
|
304
|
+
|
|
305
|
+
# 直接 SKILL.md 必须显式指定 name,并安装到 official/<name>/
|
|
306
|
+
npm run source -- add https://example.com/SKILL.md --name example --yes
|
|
307
|
+
|
|
308
|
+
# 替换一个已登记 source;Archive/Local 更新需要新 input,
|
|
309
|
+
# Git/Remote file 可复用 registry 中的 origin
|
|
310
|
+
npm run source -- update <source-id> [input] --project /path/to/project
|
|
311
|
+
npm run source -- update <source-id> [input] --allow-breaking --yes --project /path/to/project
|
|
312
|
+
|
|
313
|
+
# 通过同一安全路径更新全部已登记 Git source
|
|
314
|
+
npm run source -- update-git --project /path/to/project
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`add` 与 `update` 是不同操作。重复添加完全相同的内容会成功且不改动;source identity 或目标目录冲突会停止,不能因此获得替换权限。Archive/Local 新 source 的命名冲突可用 `--name` 或 `--namespace` 解决。Remote file 必须提供 `--name`,不接受 `--namespace`;只有 source registry 已存在对应 identity 时才能使用 `update`。Remote file 更新可省略 input 以复用已登记 origin,也可提供新的稳定 URL 来迁移 origin。
|
|
318
|
+
|
|
319
|
+
CLI exit category 保持稳定:`0` 表示成功或 identical no-op,`1` 表示一般获取/更新失败,`2` 表示参数错误,`3` 表示 identity 未解析或冲突,`4` 表示 breaking replacement 缺少显式授权。
|
|
320
|
+
|
|
321
|
+
获取只改变中央库,不创建项目软链接、不执行外部代码、不运行 setup,也不调用 runtime preflight。启用已获取的 skill 仍使用现有 Web、TUI 或项目链接 API。只有用户明确要求“获取并启用”时,才先获取、重新扫描 state、按完整 ID 解析指定 skill,并且只启用该项。
|
|
322
|
+
|
|
323
|
+
未知 setup readiness 不产生通用提醒或门禁。已声明的 setup contract 可以在启用后给出非阻塞提醒。IMA API key 等专有凭据与 setup 继续由发布方 skill 的 runtime preflight 负责。
|
|
324
|
+
|
|
325
|
+
### 接管现有中央库
|
|
326
|
+
|
|
327
|
+
迁移保留 source 的物理路径和现有项目链接。先预览:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
npm run source -- migrate
|
|
331
|
+
npm run source -- migrate --yes
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Library image 导出要求所有 bucket 内容均已登记、非 Git 来源的完整性基线未漂移。当原件库根本身是 git 仓库时,额外要求工作区干净且已推送;原件库根是普通目录时,这项仓库状态检查会被跳过。镜像携带完整嵌套 Git 仓库及用户级启用,不包含项目级启用与 `skillcaddy-manager`。导入不会覆盖内容不同的来源或已有 alias;部分提交失败时会报告 committed、uncommitted、unattempted,重跑同一镜像即可继续。参见[实现合同](docs/LIBRARY_IMAGE_SPEC.md)及[验证说明](docs/LIBRARY_IMAGE_IMPLEMENTATION.md)。
|
|
335
|
+
|
|
336
|
+
执行命令只在 `.skillcaddy/sources/` 写入 sidecar 记录;有歧义的 source 会保持 unresolved,不会猜测。需要额外恢复保障时,可在执行前复制该 registry 目录;恢复副本即可还原 registry 状态,不移动中央库内容。失败的 add/update 会自动清理或回滚;操作中断后,先运行 `npm run source -- list` 和 `npm run source -- inspect <source-id>` 核查,再重试。
|
|
337
|
+
|
|
338
|
+
首个版本通过 CLI 支持完整 Git 仓库、公开 HTTP(S) ZIP、稳定 HTTP(S) `/SKILL.md` 直链、本地 ZIP 和本地目录。TUI 选项 12 也支持通过 HTTPS/SSH/SCP Git 获取完整仓库、公开 HTTP(S) ZIP 和稳定的 HTTP(S) `/SKILL.md` 直链,但只写入中央原件库,不创建项目链接、不运行 setup,也不调用 runtime preflight。Web 仍不支持 source 获取、替换或删除;TUI 不支持 source 替换或删除,自动选择最新版和非 ZIP archive 也仍未提供。clone-backed 全局 `skillcaddy` 命令现在还提供 Web 生命周期、只读分析和已登记 Git 源更新入口,但仍复用现有 source 管理边界。Remote file 只获取一个 `SKILL.md`,不会跟随相对引用或获取 companion files;此类 skill 应改用 ZIP、Local 或完整 Git source。
|
|
339
|
+
|
|
340
|
+
随本仓库发布(仅在贡献 Skillcaddy 本身时使用):
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
skills/<skill-name>/
|
|
344
|
+
├── SKILL.md
|
|
345
|
+
└── agents/
|
|
346
|
+
└── openai.yaml # Codex / OpenCode 元数据(可选)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`skills/` 下的 skill 在扫描时会被标记为 `source: 'local'`、`id: 'local/<name>'`,行为与其他源完全一致——可被启用到任意项目的 `.agents/skills/`。
|
|
350
|
+
|
|
351
|
+
管理器启动时会自动扫描所有源目录(`official / github / personal / archived / skills`),无需重启服务即可在 UI 中看到新增 skill。
|
|
352
|
+
|
|
353
|
+
## 更新 Git source
|
|
354
|
+
|
|
355
|
+
通过统一的 fast-forward-only 安全路径更新所有已登记 Git 源。脏工作树会自动跳过;会影响当前项目或全局已知链接的 breaking 更新会被阻止:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npm run source -- update-git --project /path/to/project
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`--project` 是必填项,用于检查目标项目的 `.agents/skills/` 软链接;全局
|
|
362
|
+
`~/.agents/skills/` 链接总会一并扫描,`~/.hermes/skills/` 也会纳入检查。breaking 更新会分别报告项目、全局和 Hermes
|
|
363
|
+
链接;`--allow-breaking` 只授权替换原件,不会自动删除或重新绑定受影响链接,受影响链接可能因此断开。
|
|
364
|
+
|
|
365
|
+
如果你手动执行了 `git pull`,导致已登记 Git clone 前进但 registry 没有同步,
|
|
366
|
+
可以用显式 repair 流程采纳当前状态:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
npm run source -- repair github/<owner>/<repo> --project /path/to/project
|
|
370
|
+
npm run source -- repair github/<owner>/<repo> --project /path/to/project --yes
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
repair 不会 pull、覆盖、stash 或 reset 当前 clone;只有干净、位于登记 ref、
|
|
374
|
+
远端一致、相对 registry 为 fast-forward 且通过 source 校验的 clone 才会被采纳。
|
|
375
|
+
如果会影响当前项目链接,仍需显式加 `--allow-breaking`;dirty clone 只提醒,
|
|
376
|
+
不会自动处理。
|
|
377
|
+
|
|
378
|
+
## 本项目自带的 skills
|
|
379
|
+
|
|
380
|
+
### skillcaddy-manager
|
|
381
|
+
|
|
382
|
+
让 Agent(尤其是 Codex)知道如何正确使用 Skillcaddy 本身:
|
|
383
|
+
|
|
384
|
+
- 列出当前可用的 skill(含来源 / 集合 / 别名 / 路径)
|
|
385
|
+
- 启用 / 禁用某个 skill,或对一个 source / collection 批量操作
|
|
386
|
+
- 同步 Claude Code 入口
|
|
387
|
+
- 更新 GitHub 源的本地克隆
|
|
388
|
+
- 健康检查(断链、别名冲突、archvied 误启用)
|
|
389
|
+
- 检测冲突并要求用户确认
|
|
390
|
+
|
|
391
|
+
**安全规则**:只在明确选择的项目、全局 `.agents/skills` 或 Hermes `~/.hermes/skills` 作用域操作;不删中央原件;不接管普通目录或外部链接;Hermes 只接受 `official`/`github`/`personal`;不动 `archived/` 除非显式点名;任何状态变更前先给 dry-run 摘要。
|
|
392
|
+
|
|
393
|
+
**触发方式**:`agents/openai.yaml` 设置了 `allow_implicit_invocation: true`,Agent 看到相关请求时会自动加载。
|
|
394
|
+
|
|
395
|
+
## 启用 / 禁用
|
|
396
|
+
|
|
397
|
+
**启用**:在选定作用域创建软链接,指向中央库中的 skill 原件。项目作用域还会 best-effort 同步 Claude Code;全局作用域只写入 `~/.agents/skills/`;Hermes 作用域只处理 eligible 来源。
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
<project>/.agents/skills/<alias> -> <skillcaddy>/<source>/<skill>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
~/.agents/skills/<alias> -> <skillcaddy>/<source>/<skill>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**同步 Claude**:为 Claude Code 创建 `.claude/skills/` 入口,每个 skill 软链接指向 `.agents/skills/`。
|
|
408
|
+
|
|
409
|
+
```text
|
|
410
|
+
<project>/.claude/skills/<alias> -> ../../.agents/skills/<alias>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**禁用**:删除选定作用域的软链接,不删原件。清理全局链接前会确认目标属于当前 Skillcaddy 原件库;外部链接和普通目录保持只读。
|
|
414
|
+
|
|
415
|
+
**为什么是两层软链接?**
|
|
416
|
+
- `.agents/skills` 是 Agent Skills 标准,Codex / OpenCode / Pi 都能识别
|
|
417
|
+
- `.claude/skills` 让 Claude Code 也能使用,且支持单独管理(部分启用/禁用)
|
|
418
|
+
- 全局启用明确只面向 Agents,不会写入 `~/.claude/skills`
|
|
419
|
+
- 一次启用,多 Agent 共享;禁用不影响原件,安全可靠
|
|
420
|
+
|
|
421
|
+
## 推荐系统
|
|
422
|
+
|
|
423
|
+
Skillcaddy 内置推荐系统,帮助用户发现和选择合适的 skills。
|
|
424
|
+
|
|
425
|
+
### 快速查看推荐
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
node skills/skillcaddy-manager/scripts/view-recommendations.cjs onboarding
|
|
429
|
+
node skills/skillcaddy-manager/scripts/view-recommendations.cjs scenario new-project
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 推荐原则
|
|
433
|
+
|
|
434
|
+
- **先分析再推荐**: 先看当前库、当前项目和已有 skill
|
|
435
|
+
- **平台优先**: 空库时先推荐发现平台,不要固定推某个库
|
|
436
|
+
- **场景分流**: mattpocock + lencx 只用于明确开发场景,不是空白默认态
|
|
437
|
+
- **重叠审查**: 启用前根据库名、能力清单和功能描述识别可能重复的库
|
|
438
|
+
- **全局检测**: 检测全局 skills 目录,建议统一管理
|
|
439
|
+
|
|
440
|
+
### 空库默认推荐
|
|
441
|
+
|
|
442
|
+
当用户库里还没有任何 skill 时,默认推荐是:
|
|
443
|
+
|
|
444
|
+
1. 先看平台入口: `skillsmp`、`skills.sh`
|
|
445
|
+
2. 再判断当前需求属于开发、写作、研究还是设计
|
|
446
|
+
3. 场景明确后再选 starter 库
|
|
447
|
+
|
|
448
|
+
### 开发场景 starter
|
|
449
|
+
|
|
450
|
+
**开发流程黄金组合:**
|
|
451
|
+
|
|
452
|
+
1. **mattpocock/skills** (工作流套件)
|
|
453
|
+
- Setup: 由 `setup-matt-pocock-skills` 引导项目配置并跟踪就绪状态
|
|
454
|
+
- 包含: TDD、领域建模、调试、实现、质询
|
|
455
|
+
|
|
456
|
+
2. **lencx/skills** (项目把控)
|
|
457
|
+
- coding-protocol: 防止 AI 误改
|
|
458
|
+
- keel: 架构治理
|
|
459
|
+
|
|
460
|
+
### 相关脚本
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
node skills/skillcaddy-manager/scripts/check-conflicts.cjs <collection-id> --against <installed-id,installed-id>
|
|
464
|
+
node skills/skillcaddy-manager/scripts/check-global-skills.cjs
|
|
465
|
+
node skills/skillcaddy-manager/scripts/version-manager.cjs check
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
详细文档请查看 [Skillcaddy manager 推荐分支](skills/skillcaddy-manager/references/RECOMMENDATION_GUIDE.md)。
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
## 测试
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
npm test
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## 链接
|
|
478
|
+
灵感来源于公众号作者:[思想原型](https://mp.weixin.qq.com/s/HOj99MT5F4ecSAFkvJfVvQ)
|
|
479
|
+
|
|
480
|
+
[Linux Do](https://linux.do/)
|
|
481
|
+
[浅谈 AI 编程](https://mp.weixin.qq.com/s/f-NIkyxIuA8vjAUDp1bh5w)
|
|
482
|
+
[深度思考:架构腐朽 & Loop Engineering](https://mp.weixin.qq.com/s/wINKSDQCroWBvf29h567zA)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"setup": {
|
|
4
|
+
"requirement": "required",
|
|
5
|
+
"scope": "project",
|
|
6
|
+
"mode": "interactive",
|
|
7
|
+
"skillId": "github/mattpocock/skills/engineering/setup-matt-pocock-skills",
|
|
8
|
+
"appliesTo": [
|
|
9
|
+
"github/mattpocock/skills/engineering/*"
|
|
10
|
+
],
|
|
11
|
+
"requiredArtifacts": [
|
|
12
|
+
"docs/agents/issue-tracker.md",
|
|
13
|
+
"docs/agents/domain.md"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function canonicalJson(value) {
|
|
2
|
+
if (Array.isArray(value)) {
|
|
3
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
4
|
+
}
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
return `{${Object.keys(value).sort().map((key) =>
|
|
7
|
+
`${JSON.stringify(key)}:${canonicalJson(value[key])}`
|
|
8
|
+
).join(',')}}`;
|
|
9
|
+
}
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { lstat, mkdir, readdir, readlink, rm, symlink } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizeProjectPath } from './projectPath.js';
|
|
4
|
+
|
|
5
|
+
export async function syncClaudeSkills(projectPath) {
|
|
6
|
+
const resolvedProjectPath = normalizeProjectPath(projectPath);
|
|
7
|
+
const agentsSkills = getAgentsSkillsDir(resolvedProjectPath);
|
|
8
|
+
const claudeSkills = getClaudeSkillsPath(resolvedProjectPath);
|
|
9
|
+
|
|
10
|
+
await mkdir(agentsSkills, { recursive: true });
|
|
11
|
+
await mkdir(path.dirname(claudeSkills), { recursive: true });
|
|
12
|
+
|
|
13
|
+
if (await linkPathExists(claudeSkills)) {
|
|
14
|
+
const stat = await lstat(claudeSkills);
|
|
15
|
+
if (stat.isSymbolicLink()) {
|
|
16
|
+
const target = await readlink(claudeSkills);
|
|
17
|
+
const resolvedTarget = path.resolve(path.dirname(claudeSkills), target);
|
|
18
|
+
if (resolvedTarget === path.resolve(agentsSkills)) {
|
|
19
|
+
// 旧版:.claude/skills 是指向 .agents/skills 的目录级软链接。迁移为真实目录 + per-skill 软链接。
|
|
20
|
+
await rm(claudeSkills);
|
|
21
|
+
} else {
|
|
22
|
+
// 指向其他位置的自定义软链接,不动
|
|
23
|
+
return { ok: true, linkPath: claudeSkills, targetPath: target, unchanged: true };
|
|
24
|
+
}
|
|
25
|
+
} else if (!stat.isDirectory()) {
|
|
26
|
+
throw new Error(`Claude skills 入口已存在但不是目录:${claudeSkills}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await mkdir(claudeSkills, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const agentsEntries = await safeReaddir(agentsSkills);
|
|
33
|
+
let changed = false;
|
|
34
|
+
|
|
35
|
+
for (const entry of agentsEntries) {
|
|
36
|
+
const alias = entry.name;
|
|
37
|
+
const linkPath = path.join(claudeSkills, alias);
|
|
38
|
+
if (await linkPathExists(linkPath)) continue;
|
|
39
|
+
await symlink(path.join('..', '..', '.agents', 'skills', alias), linkPath, 'dir');
|
|
40
|
+
changed = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { ok: true, linkPath: claudeSkills, targetPath: agentsSkills, unchanged: !changed };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getClaudeStatus(projectPath) {
|
|
47
|
+
const claudeSkills = getClaudeSkillsPath(normalizeProjectPath(projectPath));
|
|
48
|
+
if (!(await linkPathExists(claudeSkills))) {
|
|
49
|
+
return { exists: false, isSymlink: false, linkPath: claudeSkills, targetPath: null, skills: [] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stat = await lstat(claudeSkills);
|
|
53
|
+
const isSymlink = stat.isSymbolicLink();
|
|
54
|
+
return {
|
|
55
|
+
exists: true,
|
|
56
|
+
isSymlink,
|
|
57
|
+
linkPath: claudeSkills,
|
|
58
|
+
targetPath: isSymlink ? await readlink(claudeSkills) : null,
|
|
59
|
+
skills: await scanClaudeSkills(claudeSkills)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function unlinkClaudeSkills(projectPath) {
|
|
64
|
+
const status = await getClaudeStatus(projectPath);
|
|
65
|
+
if (!status.exists) return { ok: true, ...status, removed: false };
|
|
66
|
+
|
|
67
|
+
if (status.isSymlink) {
|
|
68
|
+
await rm(status.linkPath);
|
|
69
|
+
return { ok: true, ...status, removed: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const entries = await safeReaddir(status.linkPath);
|
|
73
|
+
if (entries.length === 0) return { ok: true, ...status, removed: false };
|
|
74
|
+
await Promise.all(entries.map((entry) => rm(path.join(status.linkPath, entry.name))));
|
|
75
|
+
return { ok: true, ...status, removed: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function unlinkClaudeSkill(input) {
|
|
79
|
+
const projectPath = normalizeProjectPath(input.projectPath);
|
|
80
|
+
const alias = normalizeAlias(input.alias);
|
|
81
|
+
const linkPath = path.join(getClaudeSkillsPath(projectPath), alias);
|
|
82
|
+
|
|
83
|
+
if (!(await linkPathExists(linkPath))) return { ok: true, alias, linkPath, removed: false };
|
|
84
|
+
const stat = await lstat(linkPath);
|
|
85
|
+
if (!stat.isSymbolicLink()) throw new Error(`拒绝删除非软链接:${linkPath}`);
|
|
86
|
+
|
|
87
|
+
await rm(linkPath);
|
|
88
|
+
return { ok: true, alias, linkPath, removed: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function scanClaudeSkills(claudeSkills) {
|
|
92
|
+
const entries = (await safeReaddir(claudeSkills)).sort((left, right) => left.name.localeCompare(right.name));
|
|
93
|
+
return Promise.all(entries.map((entry) => buildClaudeSkill(claudeSkills, entry.name)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function buildClaudeSkill(claudeSkills, alias) {
|
|
97
|
+
const linkPath = path.join(claudeSkills, alias);
|
|
98
|
+
const stat = await lstat(linkPath);
|
|
99
|
+
if (!stat.isSymbolicLink()) return { alias, linkPath, targetPath: null, isSymlink: false, exists: true };
|
|
100
|
+
|
|
101
|
+
const targetPath = path.resolve(path.dirname(linkPath), await readlink(linkPath));
|
|
102
|
+
return { alias, linkPath, targetPath, isSymlink: true, exists: await linkPathExists(targetPath) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function safeReaddir(dir) {
|
|
106
|
+
try {
|
|
107
|
+
return await readdir(dir, { withFileTypes: true });
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getAgentsSkillsDir(projectPath) {
|
|
114
|
+
return path.join(normalizeProjectPath(projectPath), '.agents', 'skills');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getClaudeSkillsPath(projectPath) {
|
|
118
|
+
return path.join(normalizeProjectPath(projectPath), '.claude', 'skills');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function linkPathExists(targetPath) {
|
|
122
|
+
try {
|
|
123
|
+
await lstat(targetPath);
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeAlias(value) {
|
|
131
|
+
if (!value || typeof value !== 'string') throw new Error('alias 不能为空');
|
|
132
|
+
const alias = value.trim();
|
|
133
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(alias)) throw new Error('alias 只能包含字母、数字、点、下划线和短横线');
|
|
134
|
+
return alias;
|
|
135
|
+
}
|