@rockyskills/rw-skills 0.1.1 → 0.2.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 +65 -57
- package/bin/rw-skills.mjs +139 -0
- package/docs/npm-distribution.md +23 -52
- package/package.json +14 -1
- package/test/install.test.mjs +104 -0
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
中文 AI Agent Skill 仓库。将开发协作方法写成可安装、可调用的 Skill,供 AI 编程助手使用。
|
|
4
4
|
|
|
5
|
-
[GitHub 仓库](https://github.com/rockywu/rw-skills) · [npm
|
|
5
|
+
[GitHub 仓库](https://github.com/rockywu/rw-skills) · [npm 包 @rockyskills/rw-skills](https://www.npmjs.com/package/@rockyskills/rw-skills)
|
|
6
6
|
|
|
7
7
|
## 文档目录
|
|
8
8
|
|
|
9
9
|
- [工程中的全部 Skill](#工程中的全部-skill)
|
|
10
|
-
- [安装文档](#安装文档)
|
|
10
|
+
- [安装文档](#安装文档):环境要求、直接安装、多 Agent 支持、安装范围和检查方法。
|
|
11
11
|
- [使用文档](#使用文档):调用方式、输入资料、使用示例和预期产出。
|
|
12
12
|
- [更新与卸载](#更新与卸载)
|
|
13
13
|
- [新增 Skill](#新增-skill)
|
|
@@ -27,9 +27,11 @@ Skill 名称用于安装参数和调用。中文名称用于阅读说明。它
|
|
|
27
27
|
|
|
28
28
|
### 1. 环境要求
|
|
29
29
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
30
|
+
- Node.js `>=20`,以及可用的 `npm`、`npx`。
|
|
31
|
+
- 使用下表中支持 Agent Skills 的本地 Agent 版本。
|
|
32
|
+
- 通过 npm 安装时,能访问 npm registry。
|
|
33
|
+
|
|
34
|
+
从 **`0.2.0` 起**,本包提供自己的安装命令,使用 Node.js 标准库,没有第三方安装依赖。旧版 `0.1.x` 只有 Skill 内容,不包含这个入口。以下 npm 命令需要先发布 `0.2.0`。
|
|
33
35
|
|
|
34
36
|
检查环境:
|
|
35
37
|
|
|
@@ -38,94 +40,106 @@ node --version
|
|
|
38
40
|
npm --version
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
### 2. 支持的 Agent 与安装目录
|
|
44
|
+
|
|
45
|
+
| Agent | `--agent` 参数 | 项目目录 | 全局目录 | 官方说明 |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| Codex | `codex` | `.agents/skills/` | `~/.agents/skills/` | [文档](https://developers.openai.com/codex/skills/) |
|
|
48
|
+
| Claude Code | `claude` | `.claude/skills/` | `~/.claude/skills/` | [文档](https://code.claude.com/docs/en/skills) |
|
|
49
|
+
| Cursor | `cursor` | `.cursor/skills/` | `~/.cursor/skills/` | [文档](https://cursor.com/docs/context/skills) |
|
|
50
|
+
| Gemini CLI | `gemini` | `.gemini/skills/` | `~/.gemini/skills/` | [文档](https://geminicli.com/docs/cli/skills/) |
|
|
51
|
+
| OpenCode | `opencode` | `.opencode/skills/` | `~/.config/opencode/skills/` | [文档](https://opencode.ai/docs/skills/) |
|
|
52
|
+
| GitHub Copilot | `copilot` | `.github/skills/` | `~/.copilot/skills/` | [文档](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills) |
|
|
53
|
+
|
|
54
|
+
全局目录中的 `~` 表示当前用户的主目录。项目目录相对于执行命令时的当前目录,请在目标项目根目录运行。全局安装面向本地 Agent;云端使用需要按 Agent 的官方规则配置。
|
|
55
|
+
|
|
56
|
+
目录规则已按官方文档核对,六种 Agent 的项目与全局文件安装均使用临时目录验证。尚未逐一启动六种 Agent 验证任务执行效果。部分 Agent 也会扫描其他 Agent 的兼容目录,重复安装可能出现重复条目;按自己实际使用的 Agent 选择目标即可。
|
|
42
57
|
|
|
43
|
-
###
|
|
58
|
+
### 3. 安装全部 Skill
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
在目标项目根目录安装到 Codex:
|
|
46
61
|
|
|
47
62
|
```bash
|
|
48
|
-
|
|
63
|
+
npx --yes @rockyskills/rw-skills@0.2.0 install --agent codex
|
|
49
64
|
```
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
全局安装到 Claude Code:
|
|
52
67
|
|
|
53
68
|
```bash
|
|
54
|
-
npx --yes skills@
|
|
55
|
-
--skill '*' --agent codex --global --copy --yes
|
|
69
|
+
npx --yes @rockyskills/rw-skills@0.2.0 install --agent claude --global
|
|
56
70
|
```
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
如果只需要当前项目使用,在该项目根目录执行下面的命令:
|
|
72
|
+
同时安装到 Codex、Claude Code 和 Cursor:
|
|
61
73
|
|
|
62
74
|
```bash
|
|
63
|
-
npx --yes skills@
|
|
64
|
-
--skill '*' --agent codex --copy --yes
|
|
75
|
+
npx --yes @rockyskills/rw-skills@0.2.0 install --agent codex,claude,cursor --global
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
默认安装包中的全部 Skill。`--agent` 必填,支持逗号分隔或重复传入;`--global` 可选,不传则安装到当前项目。安装器复制完整 Skill 目录、辅助文件和 MIT 许可证,不创建符号链接,不修改 Agent 配置。
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
`npx --yes` 只自动确认下载这个 npm 包。同名 Skill 目录或链接已存在时,安装器仍会停止。请先备份并将旧目录移出 Agent 的 Skill 目录,再重新安装。
|
|
70
81
|
|
|
71
|
-
|
|
82
|
+
### 4. 只安装一个 Skill
|
|
72
83
|
|
|
73
84
|
```bash
|
|
74
|
-
npx --yes skills@
|
|
75
|
-
--skill traceable-development
|
|
85
|
+
npx --yes @rockyskills/rw-skills@0.2.0 install \
|
|
86
|
+
--agent cursor --skill traceable-development
|
|
76
87
|
```
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
`--skill` 的名称来自本页完整清单,可以重复传入以选择多个 Skill。未知名称会报错,不会安装到其他位置。
|
|
90
|
+
|
|
91
|
+
查看本包可安装的 Skill:
|
|
79
92
|
|
|
80
93
|
```bash
|
|
81
|
-
|
|
94
|
+
npx --yes @rockyskills/rw-skills@0.2.0 list
|
|
82
95
|
```
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
先列出仓库中的全部 Skill:
|
|
97
|
+
查看命令帮助:
|
|
87
98
|
|
|
88
99
|
```bash
|
|
89
|
-
npx --yes
|
|
100
|
+
npx --yes @rockyskills/rw-skills@0.2.0 --help
|
|
90
101
|
```
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
`list` 列出当前包的 Skill,不扫描本机已经安装的其他 Skill。
|
|
104
|
+
|
|
105
|
+
### 5. 不通过 npm 安装
|
|
106
|
+
|
|
107
|
+
从 GitHub 克隆仓库后,也可以直接运行仓库中的安装入口。项目安装前,先切换到目标项目目录,再用实际克隆路径替换以下路径:
|
|
93
108
|
|
|
94
109
|
```bash
|
|
95
|
-
|
|
96
|
-
--skill '*' --agent codex --global --copy --yes
|
|
110
|
+
node /path/to/rw-skills/bin/rw-skills.mjs install --agent codex
|
|
97
111
|
```
|
|
98
112
|
|
|
99
|
-
|
|
113
|
+
全局安装同样增加 `--global`。这个入口只使用 Node.js 标准库,无需先运行 `npm install`。
|
|
114
|
+
|
|
115
|
+
也可以手动复制完整的 `traceable-development/` 到上表对应目录,并保留 `references/` 和仓库的 MIT `LICENSE`。
|
|
100
116
|
|
|
101
|
-
###
|
|
117
|
+
### 6. 检查安装结果
|
|
102
118
|
|
|
103
|
-
|
|
119
|
+
例如 Codex 全局安装后,应至少存在以下文件:
|
|
104
120
|
|
|
105
121
|
```text
|
|
106
122
|
~/.agents/skills/traceable-development/SKILL.md
|
|
107
123
|
~/.agents/skills/traceable-development/references/templates.md
|
|
124
|
+
~/.agents/skills/traceable-development/LICENSE
|
|
108
125
|
```
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
也可以使用安装工具查看已安装清单:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# 全局安装清单
|
|
116
|
-
npx --yes skills@1.7.2 list --global --agent codex
|
|
117
|
-
|
|
118
|
-
# 当前项目安装清单
|
|
119
|
-
npx --yes skills@1.7.2 list --agent codex
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Codex 会自动发现新增 Skill。如果列表中没有出现,可以重启 Codex。手动安装时,也必须复制完整的 Skill 目录,保留 `references/`,不能只复制 `SKILL.md`。
|
|
127
|
+
其他 Agent 检查上表对应目录。重新打开 Agent 会话并查看它提供的 Skill 清单;未出现时核对版本是否支持 Skills,以及所选安装范围。
|
|
123
128
|
|
|
124
129
|
## 使用文档
|
|
125
130
|
|
|
126
131
|
### 1. 调用 Skill
|
|
127
132
|
|
|
128
|
-
|
|
133
|
+
安装完成后,按所用 Agent 的调用方式发送任务:
|
|
134
|
+
|
|
135
|
+
| Agent | 调用方式 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| Codex | 输入 `$traceable-development`,再描述任务 |
|
|
138
|
+
| Claude Code | 输入 `/traceable-development`,再描述任务 |
|
|
139
|
+
| Cursor | 在 Agent 聊天输入 `/`,搜索并选择 `traceable-development` |
|
|
140
|
+
| Gemini CLI、OpenCode、GitHub Copilot | 明确要求使用 `traceable-development` 处理开发计划;在 Agent 的 Skill 功能中核对是否已发现 |
|
|
141
|
+
|
|
142
|
+
以下示例采用 Codex 的 `$traceable-development`。其他 Agent 替换为其调用方式,任务要求可以保持一致。
|
|
129
143
|
|
|
130
144
|
安装 Skill 不等于调用它。调用 Skill 也不等于授权推送、合并或发布;需要执行这些操作时,应在任务中说明。
|
|
131
145
|
|
|
@@ -178,15 +192,9 @@ $traceable-development
|
|
|
178
192
|
|
|
179
193
|
## 更新与卸载
|
|
180
194
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
卸载全局的可追溯开发协作 Skill:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
npx --yes skills@1.7.2 remove traceable-development --agent codex --global
|
|
187
|
-
```
|
|
195
|
+
安装保存的是本地副本,发布新版本不会自动同步。更新时先将原 Skill 文件夹移出 Agent 的 Skill 目录进行备份,再运行新版本的安装命令。安装器不会覆盖旧内容。
|
|
188
196
|
|
|
189
|
-
|
|
197
|
+
卸载时,备份自定义内容后,在上表对应目录中删除本仓库的 Skill 文件夹。例如 Codex 全局版本对应 `~/.agents/skills/traceable-development/`。不要删除同目录下其他来源的 Skill。
|
|
190
198
|
|
|
191
199
|
## 新增 Skill
|
|
192
200
|
|
|
@@ -203,7 +211,7 @@ skill-name/
|
|
|
203
211
|
|
|
204
212
|
## 发布说明
|
|
205
213
|
|
|
206
|
-
npm 包归属 `rockyskills`
|
|
214
|
+
npm 包归属 `rockyskills` 组织。当前安装器准备为 `0.2.0`。更新已发布的 README 或安装器需要发布新的 npm 版本,GitHub 修改不会自动更新已有 npm 版本。
|
|
207
215
|
|
|
208
216
|
发布配置与流程见 [npm 分发说明](https://github.com/rockywu/rw-skills/blob/main/docs/npm-distribution.md)。已发布的同版本不能覆盖。
|
|
209
217
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve, relative } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
|
|
8
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
export const agents = {
|
|
10
|
+
codex: { project: '.agents/skills', global: '.agents/skills' },
|
|
11
|
+
claude: { project: '.claude/skills', global: '.claude/skills' },
|
|
12
|
+
cursor: { project: '.cursor/skills', global: '.cursor/skills' },
|
|
13
|
+
gemini: { project: '.gemini/skills', global: '.gemini/skills' },
|
|
14
|
+
opencode: { project: '.opencode/skills', global: '.config/opencode/skills' },
|
|
15
|
+
copilot: { project: '.github/skills', global: '.copilot/skills' },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function skillDirectory(agent, global, cwd, home) {
|
|
19
|
+
if (!Object.hasOwn(agents, agent)) throw new Error(`未知 Agent:${agent}。支持:${Object.keys(agents).join(', ')}`);
|
|
20
|
+
return resolve(global ? home : cwd, agents[agent][global ? 'global' : 'project']);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function catalog() {
|
|
24
|
+
return readdirSync(packageRoot, { withFileTypes: true })
|
|
25
|
+
.filter(entry => entry.isDirectory() && existsSync(join(packageRoot, entry.name, 'SKILL.md')))
|
|
26
|
+
.map(entry => {
|
|
27
|
+
const source = join(packageRoot, entry.name);
|
|
28
|
+
const text = readFileSync(join(source, 'SKILL.md'), 'utf8');
|
|
29
|
+
const name = text.match(/^name:\s*([a-z0-9-]+)\s*$/m)?.[1];
|
|
30
|
+
if (!name || name !== entry.name || name.length > 64) throw new Error(`Skill 名称与目录不一致:${entry.name}`);
|
|
31
|
+
return { name, source, description: text.match(/^description:\s*(.+)$/m)?.[1] ?? '' };
|
|
32
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 拒绝经由符号链接写入,避免修改链接指向的其他 Skill 或目录。
|
|
36
|
+
function checkDirectory(path, anchor) {
|
|
37
|
+
let current = anchor;
|
|
38
|
+
for (const part of relative(anchor, path).split(/[\\/]/)) {
|
|
39
|
+
current = join(current, part);
|
|
40
|
+
try {
|
|
41
|
+
const stat = lstatSync(current);
|
|
42
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`目标目录不是普通目录:${current}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.code !== 'ENOENT') throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const help = `rw-skills:直接安装本包中的中文 Skill,无第三方安装依赖。
|
|
50
|
+
|
|
51
|
+
用法:
|
|
52
|
+
npx @rockyskills/rw-skills list
|
|
53
|
+
npx @rockyskills/rw-skills install --agent codex
|
|
54
|
+
npx @rockyskills/rw-skills install --agent codex,claude,cursor --global
|
|
55
|
+
npx @rockyskills/rw-skills install --agent claude --skill traceable-development
|
|
56
|
+
|
|
57
|
+
选项:
|
|
58
|
+
--agent <名称> 必填;用逗号分隔多个 Agent,也可重复此选项
|
|
59
|
+
--skill <名称> 可选;默认安装全部,可重复此选项
|
|
60
|
+
--global 安装到用户目录;默认安装到当前项目
|
|
61
|
+
--help 查看帮助
|
|
62
|
+
|
|
63
|
+
支持:${Object.keys(agents).join(', ')}
|
|
64
|
+
已有同名 Skill 时停止,不覆盖。更新前请将旧目录移到 Skill 目录以外备份。
|
|
65
|
+
list 列出本包可安装的 Skill。`;
|
|
66
|
+
|
|
67
|
+
export function main(args, cwd = process.cwd(), home = homedir()) {
|
|
68
|
+
const { values, positionals } = parseArgs({ args, allowPositionals: true, options: {
|
|
69
|
+
agent: { type: 'string', multiple: true },
|
|
70
|
+
skill: { type: 'string', multiple: true },
|
|
71
|
+
global: { type: 'boolean', default: false },
|
|
72
|
+
help: { type: 'boolean', default: false },
|
|
73
|
+
} });
|
|
74
|
+
if (values.help || args.length === 0) { console.log(help); return; }
|
|
75
|
+
if (positionals.length !== 1 || !['install', 'list'].includes(positionals[0])) throw new Error('请使用 install 或 list;--help 查看帮助。');
|
|
76
|
+
const skills = catalog();
|
|
77
|
+
if (skills.length === 0) throw new Error('包中没有可安装的 Skill。');
|
|
78
|
+
if (positionals[0] === 'list') {
|
|
79
|
+
if (values.agent || values.skill || values.global) throw new Error('list 仅列出本包的 Skill,不接受安装选项。');
|
|
80
|
+
for (const skill of skills) console.log(`${skill.name}\n ${skill.description}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const selectedAgents = [...new Set((values.agent ?? []).flatMap(value => value.split(',')))];
|
|
84
|
+
if (!selectedAgents.length) throw new Error('请通过 --agent 指定目标 Agent。');
|
|
85
|
+
const requested = new Set(values.skill ?? []);
|
|
86
|
+
for (const name of requested) {
|
|
87
|
+
if (!skills.some(skill => skill.name === name)) throw new Error(`未知 Skill:${name}。使用 list 查看清单。`);
|
|
88
|
+
}
|
|
89
|
+
const selectedSkills = requested.size ? skills.filter(skill => requested.has(skill.name)) : skills;
|
|
90
|
+
const targets = selectedAgents.flatMap(agent => {
|
|
91
|
+
const base = skillDirectory(agent, values.global, cwd, home);
|
|
92
|
+
checkDirectory(base, values.global ? home : cwd);
|
|
93
|
+
return selectedSkills.map(skill => ({ agent, base, skill, destination: join(base, skill.name) }));
|
|
94
|
+
});
|
|
95
|
+
// 先检查全部目标,参数错误或已有目录时不产生部分安装。
|
|
96
|
+
for (const target of targets) {
|
|
97
|
+
try {
|
|
98
|
+
lstatSync(target.destination);
|
|
99
|
+
throw new Error(`已存在:${target.destination}。请先备份并移走旧目录,再安装。`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code !== 'ENOENT') throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const installed = [];
|
|
105
|
+
try {
|
|
106
|
+
for (const target of targets) {
|
|
107
|
+
mkdirSync(target.base, { recursive: true });
|
|
108
|
+
const staging = mkdtempSync(join(target.base, '.rw-skills-'));
|
|
109
|
+
try {
|
|
110
|
+
cpSync(target.skill.source, staging, {
|
|
111
|
+
recursive: true,
|
|
112
|
+
filter: path => !['.DS_Store', '.git', 'node_modules'].includes(path.split(/[\\/]/).at(-1)),
|
|
113
|
+
});
|
|
114
|
+
if (!existsSync(join(staging, 'LICENSE'))) cpSync(join(packageRoot, 'LICENSE'), join(staging, 'LICENSE'));
|
|
115
|
+
// 复制期间可能有另一进程创建目标,重新检查以保护已有内容。
|
|
116
|
+
try {
|
|
117
|
+
lstatSync(target.destination);
|
|
118
|
+
throw new Error(`安装期间目标已被创建:${target.destination}`);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error.code !== 'ENOENT') throw error;
|
|
121
|
+
}
|
|
122
|
+
renameSync(staging, target.destination);
|
|
123
|
+
installed.push(target.destination);
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(staging, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
console.log(`已安装 ${target.skill.name} → ${target.agent}: ${target.destination}`);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
for (const path of installed) rmSync(path, { recursive: true, force: true });
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
console.log('请在 Agent 中查看 Skill 清单;未发现时重新打开会话。');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
137
|
+
try { main(process.argv.slice(2)); }
|
|
138
|
+
catch (error) { console.error(`安装失败:${error.message}`); process.exitCode = 1; }
|
|
139
|
+
}
|
package/docs/npm-distribution.md
CHANGED
|
@@ -1,75 +1,46 @@
|
|
|
1
|
-
# npm
|
|
1
|
+
# npm 发布说明
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
包名:`@rockyskills/rw-skills`。组织:`rockyskills`。许可证:MIT。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`0.1.x` 只包含 Skill 内容。从 `0.2.0` 起,包包含自己的 `rw-skills` 安装入口。安装器使用 Node.js 标准库,直接复制到 Agent 的标准 Skill 目录,不依赖第三方安装工具。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| 方式 | 内容来源 | 安装命令 |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| GitHub 仓库 | 仓库当前内容 | `npx --yes skills@1.7.2 add rockywu/rw-skills` |
|
|
12
|
-
| npm 固定版本 | registry 的版本归档 | 使用下面的 `.tgz` 下载地址 |
|
|
13
|
-
|
|
14
|
-
安装到当前项目的 Codex:
|
|
7
|
+
## 安装入口
|
|
15
8
|
|
|
16
9
|
```bash
|
|
17
|
-
npx --yes skills@
|
|
18
|
-
|
|
19
|
-
--skill traceable-development --agent codex --copy --yes
|
|
10
|
+
npx --yes @rockyskills/rw-skills@0.2.0 list
|
|
11
|
+
npx --yes @rockyskills/rw-skills@0.2.0 install --agent codex,claude,cursor --global
|
|
20
12
|
```
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`npm install` 只下载 npm 包,不会自动把 Skill 放进 Codex 的发现目录。内容包没有 `bin` 或 `postinstall`,不支持直接执行 `npx @rockyskills/rw-skills`,也不会在 npm 安装时修改用户的 Skill 目录。
|
|
25
|
-
|
|
26
|
-
## 包配置
|
|
27
|
-
|
|
28
|
-
以根目录的 [package.json](../package.json) 为准。文件白名单包含 MIT 许可证、仓库介绍、分发说明、Skill 介绍、`SKILL.md` 和模板,排除 `.DS_Store`、Git 文件和临时归档。
|
|
29
|
-
|
|
30
|
-
仓库及内容包采用 [MIT License](../LICENSE),`package.json` 的 `license` 为 `MIT`,包内包含 `LICENSE`。新增 Skill 时同步扩展白名单。
|
|
14
|
+
完整支持清单、安装范围、冲突处理和调用示例见 [README](../README.md)。`npm install` 下载包,不自动写入 Skill 目录。安装必须通过明确的 `install` 命令执行。
|
|
31
15
|
|
|
32
|
-
|
|
16
|
+
## 发布前检查
|
|
33
17
|
|
|
34
|
-
|
|
18
|
+
1. 核对 `package.json` 的版本、MIT 许可证、`bin` 入口和 `engines`。
|
|
19
|
+
2. 执行 `npm test`。测试使用临时目录,不修改真实 Agent 目录。
|
|
20
|
+
3. 执行 `npm pack --dry-run`。核对包内包含 `bin/`、测试、`LICENSE`、README、全部 Skill 及辅助文件。`prepack` 会运行测试。
|
|
21
|
+
4. 执行 `npm pack`,在临时项目用生成的 `.tgz` 运行 CLI 并验证安装。
|
|
22
|
+
5. 提交并推送 GitHub,再公开发布。新增 Skill 时同步更新 README 完整清单及发布文件白名单。
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
2. 用 `npm pack` 生成归档;从归档执行安装,检查 Skill 和辅助文件完整性。
|
|
38
|
-
3. 提交仓库,再推送到 GitHub 的 `main` 分支。不要把与发布无关的本地改动加入提交。
|
|
39
|
-
4. 用 `npm whoami --registry=https://registry.npmjs.org/` 检查账号,用 `npm org ls rockyskills --json --registry=https://registry.npmjs.org/` 核对组织权限。
|
|
40
|
-
5. 在 npm 要求的本人认证可完成时,执行发布:
|
|
24
|
+
## 发布与认证
|
|
41
25
|
|
|
42
26
|
```bash
|
|
27
|
+
npm whoami --registry=https://registry.npmjs.org/
|
|
43
28
|
npm publish --access public --registry=https://registry.npmjs.org/
|
|
44
29
|
```
|
|
45
30
|
|
|
46
|
-
|
|
31
|
+
需要账号拥有 `rockyskills` 组织的发布权限。按 npm 提示在浏览器完成本人认证和 2FA。已有版本不能覆盖,后续发布需增加版本号。
|
|
32
|
+
|
|
33
|
+
发布后确认:
|
|
47
34
|
|
|
48
35
|
```bash
|
|
49
|
-
npm view @rockyskills/rw-skills version
|
|
50
|
-
--registry=https://registry.npmjs.org/
|
|
36
|
+
npm view @rockyskills/rw-skills version bin license --registry=https://registry.npmjs.org/
|
|
51
37
|
```
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
后续发布必须增加版本号。同一个已发布版本不能覆盖。Git 推送成功不等于 npm 已发布,npm 已发布也不等于实际任务效果已经验收。
|
|
56
|
-
|
|
57
|
-
## 已完成的准备验证(2026-10-13,Asia/Shanghai)
|
|
58
|
-
|
|
59
|
-
- GitHub 仓库 `rockywu/rw-skills` 为公开仓库。
|
|
60
|
-
- 现有 Skill 为 `traceable-development`,入口和模板文件齐全。
|
|
61
|
-
- Codex 内置安装脚本、GitHub 远程安装、本地目录安装和本地 HTTP 下载 `.tgz` 安装均已在临时目录验证成功。
|
|
62
|
-
- 已安装的 `SKILL.md` 和模板内容与仓库一致。
|
|
63
|
-
- 试验 npm 内容包的白名单检查通过,未包含 `.DS_Store` 或 Git 文件。
|
|
64
|
-
- 本机 Node.js 为 `24.19.0`,npm 为 `11.17.0`,满足安装工具要求。
|
|
65
|
-
- npm 命令行账号已验证为 `rockywu`,在 `rockyskills` 组织中具有 owner 权限。
|
|
66
|
-
|
|
67
|
-
以上记录证明安装文件完整性,不代表 Skill 的实际任务效果验收。真实 registry 的发布结果与发布后安装结果,以本次执行输出为准。
|
|
39
|
+
随后按 README 命令从真实 registry 安装到临时项目,并核对 `SKILL.md`、模板和 MIT 许可证。Git 推送成功、npm 发布成功、文件安装通过和 Agent 实际任务效果是不同的验证结果,应分别记录。
|
|
68
40
|
|
|
69
41
|
## 参考
|
|
70
42
|
|
|
71
|
-
- [npm
|
|
72
|
-
- [
|
|
73
|
-
- [OpenAI:Skill 结构、发现目录和插件分发建议](https://developers.openai.com/codex/skills/)
|
|
43
|
+
- [npm:发布公开 scope 包](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages)
|
|
44
|
+
- [npm:package.json 的 bin 入口](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#bin)
|
|
74
45
|
|
|
75
|
-
|
|
46
|
+
本包通过 npm 分发本地 Skill 文件,不会自动进入 Agent 的插件目录。
|
package/package.json
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rockyskills/rw-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "中文 AI Agent Skills:开发计划、任务依赖与进度追溯",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rw-skills": "bin/rw-skills.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test",
|
|
15
|
+
"prepack": "npm test"
|
|
16
|
+
},
|
|
6
17
|
"author": "rockywu",
|
|
7
18
|
"repository": {
|
|
8
19
|
"type": "git",
|
|
@@ -15,6 +26,8 @@
|
|
|
15
26
|
"files": [
|
|
16
27
|
"README.md",
|
|
17
28
|
"LICENSE",
|
|
29
|
+
"bin/",
|
|
30
|
+
"test/",
|
|
18
31
|
"docs/npm-distribution.md",
|
|
19
32
|
"traceable-development/**/*.md"
|
|
20
33
|
],
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { main } from '../bin/rw-skills.mjs';
|
|
9
|
+
|
|
10
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
const expected = {
|
|
12
|
+
codex: ['.agents/skills', '.agents/skills'],
|
|
13
|
+
claude: ['.claude/skills', '.claude/skills'],
|
|
14
|
+
cursor: ['.cursor/skills', '.cursor/skills'],
|
|
15
|
+
gemini: ['.gemini/skills', '.gemini/skills'],
|
|
16
|
+
opencode: ['.opencode/skills', '.config/opencode/skills'],
|
|
17
|
+
copilot: ['.github/skills', '.copilot/skills'],
|
|
18
|
+
};
|
|
19
|
+
function workspace(t) {
|
|
20
|
+
const dir = mkdtempSync(join(tmpdir(), 'rw-skills-test-'));
|
|
21
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
22
|
+
const cwd = join(dir, 'project'); const home = join(dir, 'user');
|
|
23
|
+
mkdirSync(cwd); mkdirSync(home);
|
|
24
|
+
return { cwd, home };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const [agent, paths] of Object.entries(expected)) {
|
|
28
|
+
for (const global of [false, true]) {
|
|
29
|
+
test(`${agent}: ${global ? '用户' : '项目'}安装保持 Skill、引用文件和 MIT 许可`, t => {
|
|
30
|
+
const { cwd, home } = workspace(t);
|
|
31
|
+
main(['install', '--agent', agent, ...(global ? ['--global'] : [])], cwd, home);
|
|
32
|
+
const dest = join(global ? home : cwd, paths[global ? 1 : 0], 'traceable-development');
|
|
33
|
+
for (const rel of ['SKILL.md', 'references/templates.md', 'README.md']) {
|
|
34
|
+
assert.deepEqual(readFileSync(join(dest, rel)), readFileSync(join(root, 'traceable-development', rel)));
|
|
35
|
+
}
|
|
36
|
+
assert.deepEqual(readFileSync(join(dest, 'LICENSE')), readFileSync(join(root, 'LICENSE')));
|
|
37
|
+
assert.equal(existsSync(join(dest, '.DS_Store')), false);
|
|
38
|
+
assert.equal(existsSync(join(global ? cwd : home, paths[global ? 0 : 1])), false);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('已有 Skill 保持不变,冲突时其他 Agent 不发生部分安装', t => {
|
|
44
|
+
const { cwd, home } = workspace(t);
|
|
45
|
+
const existing = join(cwd, '.cursor/skills/traceable-development');
|
|
46
|
+
mkdirSync(existing, { recursive: true });
|
|
47
|
+
writeFileSync(join(existing, 'SKILL.md'), '用户的自定义内容');
|
|
48
|
+
assert.throws(() => main(['install', '--agent', 'codex,cursor'], cwd, home), /已存在/);
|
|
49
|
+
assert.equal(readFileSync(join(existing, 'SKILL.md'), 'utf8'), '用户的自定义内容');
|
|
50
|
+
assert.equal(existsSync(join(cwd, '.agents')), false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('拒绝路径穿越、未知 Agent 和未知 Skill,且不写入', t => {
|
|
54
|
+
const { cwd, home } = workspace(t);
|
|
55
|
+
for (const args of [
|
|
56
|
+
['install'], ['install', '--agent', 'codex,other'],
|
|
57
|
+
['install', '--agent', 'codex', '--skill', '../outside'],
|
|
58
|
+
['install', '--agent', 'codex', '--unknown'],
|
|
59
|
+
['install', '--agent', 'codex', '--global', 'false'],
|
|
60
|
+
]) assert.throws(() => main(args, cwd, home));
|
|
61
|
+
assert.equal(existsSync(join(cwd, '.agents')), false);
|
|
62
|
+
assert.equal(existsSync(join(home, '.agents')), false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('父目录符号链接不会将安装写入其他目录', t => {
|
|
66
|
+
const { cwd, home } = workspace(t);
|
|
67
|
+
symlinkSync(home, join(cwd, '.claude'), 'dir');
|
|
68
|
+
assert.throws(() => main(['install', '--agent', 'claude'], cwd, home), /不是普通目录/);
|
|
69
|
+
assert.equal(existsSync(join(home, 'skills')), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('已有悬空 Skill 链接也被保留', t => {
|
|
73
|
+
const { cwd, home } = workspace(t);
|
|
74
|
+
const base = join(cwd, '.agents/skills'); mkdirSync(base, { recursive: true });
|
|
75
|
+
symlinkSync(join(home, 'missing'), join(base, 'traceable-development'), 'dir');
|
|
76
|
+
assert.throws(() => main(['install', '--agent', 'codex'], cwd, home), /已存在/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('多 Agent、重复参数和选定 Skill 能同时安装', t => {
|
|
80
|
+
const { cwd, home } = workspace(t);
|
|
81
|
+
main(['install', '--agent', 'claude,cursor', '--agent', 'claude', '--skill', 'traceable-development'], cwd, home);
|
|
82
|
+
for (const name of ['.claude', '.cursor']) assert.ok(existsSync(join(cwd, name, 'skills/traceable-development/SKILL.md')));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('npm 可执行入口可查看帮助与清单,错误命令返回非零状态', t => {
|
|
86
|
+
const { cwd } = workspace(t);
|
|
87
|
+
for (const args of [[], ['--help'], ['list']]) {
|
|
88
|
+
const result = spawnSync(process.execPath, [join(root, 'bin/rw-skills.mjs'), ...args], { cwd, encoding: 'utf8' });
|
|
89
|
+
assert.equal(result.status, 0, result.stderr);
|
|
90
|
+
assert.match(result.stdout, /traceable-development/);
|
|
91
|
+
}
|
|
92
|
+
const result = spawnSync(process.execPath, [join(root, 'bin/rw-skills.mjs'), 'install', '--agent', 'invalid'], { cwd, encoding: 'utf8' });
|
|
93
|
+
assert.notEqual(result.status, 0); assert.match(result.stderr, /未知 Agent/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('npm bin 符号链接入口实际执行安装', t => {
|
|
97
|
+
const { cwd } = workspace(t);
|
|
98
|
+
const bin = join(cwd, 'rw-skills');
|
|
99
|
+
symlinkSync(join(root, 'bin/rw-skills.mjs'), bin, 'file');
|
|
100
|
+
const result = spawnSync(process.execPath, [bin, 'install', '--agent', 'codex'], { cwd, encoding: 'utf8' });
|
|
101
|
+
assert.equal(result.status, 0, result.stderr);
|
|
102
|
+
assert.match(result.stdout, /已安装/);
|
|
103
|
+
assert.deepEqual(readFileSync(join(cwd, '.agents/skills/traceable-development/SKILL.md')), readFileSync(join(root, 'traceable-development/SKILL.md')));
|
|
104
|
+
});
|