@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 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 内容包 @rockyskills/rw-skills](https://www.npmjs.com/package/@rockyskills/rw-skills)
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
- - [安装文档](#安装文档):环境要求、npm 安装、GitHub 安装、安装范围和检查方法。
10
+ - [安装文档](#安装文档):环境要求、直接安装、多 Agent 支持、安装范围和检查方法。
11
11
  - [使用文档](#使用文档):调用方式、输入资料、使用示例和预期产出。
12
12
  - [更新与卸载](#更新与卸载)
13
13
  - [新增 Skill](#新增-skill)
@@ -27,9 +27,11 @@ Skill 名称用于安装参数和调用。中文名称用于阅读说明。它
27
27
 
28
28
  ### 1. 环境要求
29
29
 
30
- - 使用 Codex。其他助手的支持范围见 [Vercel Labs skills](https://github.com/vercel-labs/skills),本仓库目前验证的是 Codex 安装。
31
- - Node.js `>=22.20.0`,以及可用的 `npm`、`npx`。
32
- - 能访问 npm registry。使用 GitHub 安装方式时,也需要能访问 GitHub。
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
- 安装使用第三方工具 `skills@1.7.2`。`@rockyskills/rw-skills` 是包含 Skill、介绍和模板的内容包,没有可执行入口。`npm install @rockyskills/rw-skills` 只下载包,不能代替下面的 Skill 安装步骤。
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
- ### 2. 从 npm 安装全部 Skill
58
+ ### 3. 安装全部 Skill
44
59
 
45
- 先取得 npm 当前最新版本的下载地址:
60
+ 在目标项目根目录安装到 Codex:
46
61
 
47
62
  ```bash
48
- rw_skills_tarball="$(npm view @rockyskills/rw-skills dist.tarball --registry=https://registry.npmjs.org/)"
63
+ npx --yes @rockyskills/rw-skills@0.2.0 install --agent codex
49
64
  ```
50
65
 
51
- 然后将全部 Skill 全局安装到 Codex,所有项目均可使用:
66
+ 全局安装到 Claude Code:
52
67
 
53
68
  ```bash
54
- npx --yes skills@1.7.2 add "$rw_skills_tarball" \
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
- 全局目录为 `~/.agents/skills/`。`--skill '*'` 表示安装内容包中的全部 Skill,单引号用于防止 shell 展开星号。当前包只有 `traceable-development`,以后新增 Skill 时不需要逐个填写名称。
59
-
60
- 如果只需要当前项目使用,在该项目根目录执行下面的命令:
72
+ 同时安装到 Codex、Claude Code 和 Cursor:
61
73
 
62
74
  ```bash
63
- npx --yes skills@1.7.2 add "$rw_skills_tarball" \
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
- 项目目录为 `.agents/skills/`。以上命令中的下载地址变量需在同一个终端中设置。安装前检查同名 Skill;有自定义内容时先备份,再决定是否替换。
78
+ 默认安装包中的全部 Skill。`--agent` 必填,支持逗号分隔或重复传入;`--global` 可选,不传则安装到当前项目。安装器复制完整 Skill 目录、辅助文件和 MIT 许可证,不创建符号链接,不修改 Agent 配置。
68
79
 
69
- ### 3. 只安装一个 Skill
80
+ `npx --yes` 只自动确认下载这个 npm 包。同名 Skill 目录或链接已存在时,安装器仍会停止。请先备份并将旧目录移出 Agent 的 Skill 目录,再重新安装。
70
81
 
71
- 使用清单中的名称替换 `--skill '*'`。例如全局安装可追溯开发协作:
82
+ ### 4. 只安装一个 Skill
72
83
 
73
84
  ```bash
74
- npx --yes skills@1.7.2 add "$rw_skills_tarball" \
75
- --skill traceable-development --agent codex --global --copy --yes
85
+ npx --yes @rockyskills/rw-skills@0.2.0 install \
86
+ --agent cursor --skill traceable-development
76
87
  ```
77
88
 
78
- 需要固定版本时,取得指定版本的地址即可。例如已经发布的 `0.1.0`:
89
+ `--skill` 的名称来自本页完整清单,可以重复传入以选择多个 Skill。未知名称会报错,不会安装到其他位置。
90
+
91
+ 查看本包可安装的 Skill:
79
92
 
80
93
  ```bash
81
- rw_skills_tarball="$(npm view @rockyskills/rw-skills@0.1.0 dist.tarball --registry=https://registry.npmjs.org/)"
94
+ npx --yes @rockyskills/rw-skills@0.2.0 list
82
95
  ```
83
96
 
84
- ### 4. 从 GitHub 安装
85
-
86
- 先列出仓库中的全部 Skill:
97
+ 查看命令帮助:
87
98
 
88
99
  ```bash
89
- npx --yes skills@1.7.2 add rockywu/rw-skills --list
100
+ npx --yes @rockyskills/rw-skills@0.2.0 --help
90
101
  ```
91
102
 
92
- 全局安装全部 Skill:
103
+ `list` 列出当前包的 Skill,不扫描本机已经安装的其他 Skill。
104
+
105
+ ### 5. 不通过 npm 安装
106
+
107
+ 从 GitHub 克隆仓库后,也可以直接运行仓库中的安装入口。项目安装前,先切换到目标项目目录,再用实际克隆路径替换以下路径:
93
108
 
94
109
  ```bash
95
- npx --yes skills@1.7.2 add rockywu/rw-skills \
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
- 仅安装到当前项目时去掉 `--global`。仅安装单个 Skill 时,将 `'*'` 替换为清单中的 Skill 名称。
113
+ 全局安装同样增加 `--global`。这个入口只使用 Node.js 标准库,无需先运行 `npm install`。
114
+
115
+ 也可以手动复制完整的 `traceable-development/` 到上表对应目录,并保留 `references/` 和仓库的 MIT `LICENSE`。
100
116
 
101
- ### 5. 检查安装结果
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
- 项目安装时,检查当前项目的 `.agents/skills/traceable-development/`。
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
- 在 Codex 对话中输入 `$traceable-development`,然后说明目标、范围和约束。在支持 Skill 选择器的界面,也可以选择该 Skill 后发送任务。
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
- `--copy` 会保存本地副本,npm 或 GitHub 内容更新后不会自动同步。更新时重新取得最新下载地址,并重新运行所选安装命令;自定义内容先备份。
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
- 卸载项目版本时,在目标项目根目录执行,并去掉 `--global`。卸载前确认其中没有需要保留的自定义内容。
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` 组织。更新已发布的 README 需要发布新的 npm 版本,GitHub 的修改不会自动更新已有 npm 版本的 README。
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
+ }
@@ -1,75 +1,46 @@
1
- # npm 分发说明
1
+ # npm 发布说明
2
2
 
3
- ## 包与安装方式
3
+ 包名:`@rockyskills/rw-skills`。组织:`rockyskills`。许可证:MIT。
4
4
 
5
- 包名:`@rockyskills/rw-skills`。发布组织:`rockyskills`。首版:`0.1.0`。
5
+ `0.1.x` 只包含 Skill 内容。从 `0.2.0` 起,包包含自己的 `rw-skills` 安装入口。安装器使用 Node.js 标准库,直接复制到 Agent 的标准 Skill 目录,不依赖第三方安装工具。
6
6
 
7
- 一个纯内容包分发本仓库的全部 Skill。安装时复用 [Vercel Labs skills](https://github.com/vercel-labs/skills),不维护自己的安装器。
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@1.7.2 add \
18
- https://registry.npmjs.org/@rockyskills/rw-skills/-/rw-skills-0.1.0.tgz \
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
- 全局安装增加 `--global`。`skills@1.7.2` 要求 Node.js `>=22.20.0`。
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
- 1. 确认版本与包内容,运行 `npm pack --dry-run` 检查白名单。
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
- 6. 查询 registry 中的真实版本和下载地址:
31
+ 需要账号拥有 `rockyskills` 组织的发布权限。按 npm 提示在浏览器完成本人认证和 2FA。已有版本不能覆盖,后续发布需增加版本号。
32
+
33
+ 发布后确认:
47
34
 
48
35
  ```bash
49
- npm view @rockyskills/rw-skills version dist.tarball \
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
- 7. 从查询返回的下载地址在临时项目安装,并比较 `SKILL.md` 和模板与已提交版本的内容。
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:创建和发布公开 scope 包](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages)
72
- - [Vercel Labs skills:安装方式与参数](https://github.com/vercel-labs/skills)
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
- OpenAI 官方建议将面向其他用户的可复用 Skill 打包为插件。npm 内容包提供文件分发,不会自动进入 ChatGPT 或 Codex 的插件目录。
46
+ 本包通过 npm 分发本地 Skill 文件,不会自动进入 Agent 的插件目录。
package/package.json CHANGED
@@ -1,8 +1,19 @@
1
1
  {
2
2
  "name": "@rockyskills/rw-skills",
3
- "version": "0.1.1",
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
+ });