@nathan33/nao-skill 0.1.0 → 0.3.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 +8 -5
- package/bin/nao-skill.js +151 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,20 +139,23 @@ PM 是任务状态权威,以状态机驱动执行:
|
|
|
139
139
|
.agents/scripts/ui-tokens-check.sh <repo> # 扫 UI 硬编码色值(绕过 Design Tokens)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
##
|
|
142
|
+
## 安装与升级(npm 发布)
|
|
143
143
|
|
|
144
|
-
发布为 **`@nathan33/nao-skill
|
|
144
|
+
发布为 **`@nathan33/nao-skill`**,CLI `nao-skill` 一键接入项目:
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
147
|
npm install -g @nathan33/nao-skill # 全局安装 CLI
|
|
148
148
|
# 或在任意项目里直接:npx @nathan33/nao-skill install
|
|
149
149
|
|
|
150
|
-
nao-skill install #
|
|
151
|
-
nao-skill install /path/to/proj --force # 指定目录;--force
|
|
150
|
+
nao-skill install # 安装/合并到当前目录(保守,不覆盖已有)
|
|
151
|
+
nao-skill install /path/to/proj --force # 指定目录;--force 备份后覆盖同名
|
|
152
|
+
nao-skill update # 升级已有安装(源优先 + 清理废弃 + 留自定义)
|
|
152
153
|
```
|
|
153
154
|
|
|
155
|
+
- **install(保守合并)**:目标已有同名文件默认保留项目既有;`--force` 覆盖;写入 `.agents/.nao-version` 版本标记。
|
|
156
|
+
- **update(升级)**:机制文件源优先覆盖(项目定制应放 `AGENTS.md`)、备份清理已知废弃路径(如旧版 `skills/checklists/`,防 skill 冲突)到 `.agents/.nao-obsolete/`、保留项目自定义文件。
|
|
157
|
+
- **旧版检测**:install 发现版本落后或废弃路径 → 提示运行 `update`。
|
|
154
158
|
- **安装内容**:`.agents/` 全套(角色卡 / 技能 / 交付清单 / 协议 / roles.yaml / 工具链 / 模板)+ 生成 `AGENTS.md`(已存在则提示按 §七 合并,不覆盖)。
|
|
155
|
-
- **合并策略**:目标已有同名文件默认保留项目既有(避免覆盖自定义);`--force` 备份后覆盖。
|
|
156
159
|
- **发布**:`npm publish --access public`(scope 包需 `--access public`)。
|
|
157
160
|
|
|
158
161
|
## 目录结构
|
package/bin/nao-skill.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* bash .agents/scripts/nao-fleet.sh check / ensure arch rd-fe ...
|
|
18
18
|
* 角色卡经 fleet 拉起时 --append-system-prompt 注入;checklists 按需读取
|
|
19
19
|
*/
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
20
21
|
import {
|
|
21
22
|
cpSync,
|
|
22
23
|
existsSync,
|
|
@@ -34,6 +35,11 @@ const PKG = JSON.parse(readFileSync(new URL('../package.json', import.meta.url),
|
|
|
34
35
|
const SRC = fileURLToPath(new URL('../.agents', import.meta.url));
|
|
35
36
|
const TEMPLATE_AGENTS = fileURLToPath(new URL('../.agents/templates/AGENTS.md.example', import.meta.url));
|
|
36
37
|
|
|
38
|
+
// 已知废弃路径(相对项目根):旧版遗留,update 时备份后移除
|
|
39
|
+
const OBSOLETE = ['.agents/skills/checklists'];
|
|
40
|
+
// 安装版本标记(install/update 写入,供下次检测升级)
|
|
41
|
+
const VERSION_FILE = '.agents/.nao-version';
|
|
42
|
+
|
|
37
43
|
const log = (...a) => console.log('[nao-skill]', ...a);
|
|
38
44
|
const warn = (...a) => console.error('[nao-skill]', ...a);
|
|
39
45
|
|
|
@@ -59,6 +65,67 @@ function copyDir(src, dst, force) {
|
|
|
59
65
|
return { copied, skipped, overwritten };
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
// 已知 pi 插件(nao 舰队生态),name → npm 包
|
|
69
|
+
const PI_PLUGINS = {
|
|
70
|
+
intercom: { pkg: 'pi-intercom', desc: '多会话协作(舰队机制核心依赖)' },
|
|
71
|
+
'ask-me': { pkg: 'pi-ask-me', desc: '提问/访谈工具' },
|
|
72
|
+
subagents: { pkg: 'pi-subagents', desc: '子代理委派' },
|
|
73
|
+
'web-access': { pkg: 'pi-web-access', desc: '网页访问' },
|
|
74
|
+
codegraph: { pkg: '@sean_pedersen/pi-codegraph', desc: 'CodeGraph 代码定位(pi 集成)' },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function piSettingsPath() {
|
|
78
|
+
return join(process.env.HOME || process.env.USERPROFILE || '', '.pi', 'agent', 'settings.json');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readPackages() {
|
|
82
|
+
try {
|
|
83
|
+
const d = JSON.parse(readFileSync(piSettingsPath(), 'utf8'));
|
|
84
|
+
return Array.isArray(d.packages) ? d.packages : [];
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function installedPlugins() {
|
|
91
|
+
const pkgs = readPackages();
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const [name, { pkg }] of Object.entries(PI_PLUGINS)) {
|
|
94
|
+
out[name] = pkgs.includes(`npm:${pkg}`) || pkgs.includes(pkg);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function pluginsList() {
|
|
100
|
+
const installed = installedPlugins();
|
|
101
|
+
log(`pi 插件状态(settings: ${piSettingsPath()})`);
|
|
102
|
+
for (const [name, { pkg, desc }] of Object.entries(PI_PLUGINS)) {
|
|
103
|
+
console.log(` ${installed[name] ? '✓' : '·'} ${name.padEnd(11)} ${pkg.padEnd(26)} ${desc}${installed[name] ? '' : '(缺失)'}`);
|
|
104
|
+
}
|
|
105
|
+
console.log('\n安装: nao-skill plugins install <name...> | plugins install-all');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function pluginsInstall(names, all = false) {
|
|
109
|
+
const targets = all ? Object.keys(PI_PLUGINS) : names;
|
|
110
|
+
const todo = [];
|
|
111
|
+
for (const n of targets) {
|
|
112
|
+
const p = PI_PLUGINS[n];
|
|
113
|
+
if (!p) { warn(`未知插件: ${n}(可用: ${Object.keys(PI_PLUGINS).join(', ')})`); continue; }
|
|
114
|
+
if (installedPlugins()[n]) { log(`已安装: ${n}`); continue; }
|
|
115
|
+
todo.push([n, p]);
|
|
116
|
+
}
|
|
117
|
+
if (!todo.length) { log('全部插件已就绪。'); return; }
|
|
118
|
+
for (const [n, p] of todo) {
|
|
119
|
+
log(`安装 ${n} → pi install npm:${p.pkg} ...`);
|
|
120
|
+
try {
|
|
121
|
+
execFileSync('pi', ['install', `npm:${p.pkg}`], { stdio: 'inherit' });
|
|
122
|
+
log(`✓ ${n} 安装完成(重启 pi 会话后生效)`);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
warn(`✗ ${n} 安装失败: ${e.message}(确认 pi 已安装且在 PATH)`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
62
129
|
function install(target, force) {
|
|
63
130
|
if (!existsSync(SRC)) {
|
|
64
131
|
warn(`包内 .agents 缺失:${SRC}(发布物损坏?)`);
|
|
@@ -77,6 +144,16 @@ function install(target, force) {
|
|
|
77
144
|
}
|
|
78
145
|
}
|
|
79
146
|
|
|
147
|
+
// 版本标记 + 升级/废弃检测
|
|
148
|
+
const prev = readInstalledVersion(target);
|
|
149
|
+
writeFileSync(join(target, VERSION_FILE), PKG.version);
|
|
150
|
+
if (prev && prev !== PKG.version) {
|
|
151
|
+
warn(`检测到旧版安装(v${prev} → v${PKG.version}):建议运行 nao-skill update 升级机制文件。`);
|
|
152
|
+
}
|
|
153
|
+
for (const p of detectObsolete(target)) {
|
|
154
|
+
warn(`发现废弃路径 ${p}(旧版遗留,可能导致 skill 冲突):建议运行 nao-skill update 清理。`);
|
|
155
|
+
}
|
|
156
|
+
|
|
80
157
|
const agentsMd = join(target, 'AGENTS.md');
|
|
81
158
|
if (!existsSync(agentsMd)) {
|
|
82
159
|
let tpl = readFileSync(TEMPLATE_AGENTS, 'utf8');
|
|
@@ -95,11 +172,55 @@ function install(target, force) {
|
|
|
95
172
|
log(' 角色卡:.agents/prompts/ · 技能:.agents/skills/ · 交付清单:.agents/checklists/');
|
|
96
173
|
}
|
|
97
174
|
|
|
175
|
+
function update(target) {
|
|
176
|
+
// 源优先升级:机制文件以包为权威(项目定制应放 AGENTS.md,不在机制文件里改)
|
|
177
|
+
const dst = join(target, '.agents');
|
|
178
|
+
if (!existsSync(dst)) {
|
|
179
|
+
log('目标无 .agents/,直接完整安装。');
|
|
180
|
+
return install(target, false);
|
|
181
|
+
}
|
|
182
|
+
let overwritten = 0;
|
|
183
|
+
for (const e of readdirSync(SRC)) {
|
|
184
|
+
const s = join(SRC, e);
|
|
185
|
+
const d = join(dst, e);
|
|
186
|
+
rmSync(d, { recursive: true, force: true });
|
|
187
|
+
cpSync(s, d, { recursive: true });
|
|
188
|
+
overwritten++;
|
|
189
|
+
}
|
|
190
|
+
// 清理已知废弃路径(先备份到 .agents/.nao-obsolete/)
|
|
191
|
+
for (const p of OBSOLETE) {
|
|
192
|
+
const full = join(target, p);
|
|
193
|
+
if (existsSync(full)) {
|
|
194
|
+
const bakDir = join(dst, '.nao-obsolete');
|
|
195
|
+
mkdirSync(bakDir, { recursive: true });
|
|
196
|
+
cpSync(full, join(bakDir, `${basename(p)}-${Date.now()}`), { recursive: true });
|
|
197
|
+
rmSync(full, { recursive: true, force: true });
|
|
198
|
+
log(`废弃路径已备份并移除: ${p}(备份于 .agents/.nao-obsolete/)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// 项目自定义文件保留(源没有的不动)
|
|
202
|
+
writeFileSync(join(target, VERSION_FILE), PKG.version);
|
|
203
|
+
log(`✔ 升级完成:机制文件以 v${PKG.version} 为准,项目自定义已保留。`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function readInstalledVersion(target) {
|
|
207
|
+
try { return readFileSync(join(target, VERSION_FILE), 'utf8').trim(); } catch { return null; }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function detectObsolete(target) {
|
|
211
|
+
return OBSOLETE.filter((p) => existsSync(join(target, p)));
|
|
212
|
+
}
|
|
213
|
+
|
|
98
214
|
function help() {
|
|
99
215
|
console.log(`nao-skill v${PKG.version} — nao 多角色 AI 开发舰队安装器
|
|
100
216
|
|
|
101
217
|
用法:
|
|
102
218
|
nao-skill install [dir] [--force] 安装/合并 .agents/ 到目标项目(默认当前目录)
|
|
219
|
+
nao-skill install --plugins 项目接入时顺带安装 pi 舰队插件
|
|
220
|
+
nao-skill update [dir] 升级已有安装:源优先覆盖 + 清理废弃路径 + 保留项目自定义
|
|
221
|
+
nao-skill plugins list 列出已知 pi 插件与安装状态
|
|
222
|
+
nao-skill plugins install <名...> 安装指定插件(intercom/ask-me/subagents/web-access/codegraph)
|
|
223
|
+
nao-skill plugins install-all 安装全部舰队插件
|
|
103
224
|
nao-skill --version 显示版本
|
|
104
225
|
nao-skill --help 显示本帮助
|
|
105
226
|
|
|
@@ -124,18 +245,47 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
124
245
|
} else if (cmd === 'install' || cmd === 'i') {
|
|
125
246
|
let target = process.cwd();
|
|
126
247
|
let force = false;
|
|
248
|
+
let withPlugins = false;
|
|
127
249
|
const rest = args.slice(1);
|
|
128
250
|
for (let i = 0; i < rest.length; i++) {
|
|
129
251
|
if (rest[i] === '--force' || rest[i] === '-f') force = true;
|
|
252
|
+
else if (rest[i] === '--plugins') withPlugins = true;
|
|
130
253
|
else if (rest[i] === '--target') target = resolve(rest[++i]);
|
|
131
254
|
else if (!rest[i].startsWith('-')) target = resolve(rest[i]);
|
|
132
255
|
else { warn(`未知参数: ${rest[i]}`); process.exit(2); }
|
|
133
256
|
}
|
|
134
|
-
if (!
|
|
257
|
+
if (!statIsDir(target)) {
|
|
135
258
|
warn(`目标目录无效: ${target}`);
|
|
136
259
|
process.exit(2);
|
|
137
260
|
}
|
|
138
261
|
install(target, force);
|
|
262
|
+
if (withPlugins) pluginsInstall([], true);
|
|
263
|
+
} else if (cmd === 'plugins' || cmd === 'plugin') {
|
|
264
|
+
const sub = args[1];
|
|
265
|
+
const rest = args.slice(2);
|
|
266
|
+
if (sub === 'list' || sub === 'ls' || !sub) {
|
|
267
|
+
pluginsList();
|
|
268
|
+
} else if (sub === 'install' || sub === 'add') {
|
|
269
|
+
pluginsInstall(rest);
|
|
270
|
+
} else if (sub === 'install-all' || sub === 'installAll') {
|
|
271
|
+
pluginsInstall([], true);
|
|
272
|
+
} else {
|
|
273
|
+
warn(`未知插件命令: ${sub}(可用: list / install / install-all)`);
|
|
274
|
+
process.exit(2);
|
|
275
|
+
}
|
|
276
|
+
} else if (cmd === 'update' || cmd === 'u') {
|
|
277
|
+
let target = process.cwd();
|
|
278
|
+
const rest = args.slice(1);
|
|
279
|
+
for (let i = 0; i < rest.length; i++) {
|
|
280
|
+
if (rest[i] === '--target') target = resolve(rest[++i]);
|
|
281
|
+
else if (!rest[i].startsWith('-')) target = resolve(rest[i]);
|
|
282
|
+
else { warn(`未知参数: ${rest[i]}`); process.exit(2); }
|
|
283
|
+
}
|
|
284
|
+
if (!statIsDir(target)) {
|
|
285
|
+
warn(`目标目录无效: ${target}`);
|
|
286
|
+
process.exit(2);
|
|
287
|
+
}
|
|
288
|
+
update(target);
|
|
139
289
|
} else {
|
|
140
290
|
warn(`未知命令: ${cmd}(可用: install)`);
|
|
141
291
|
process.exit(2);
|