@zhouhao4221/devflow-skills 0.2.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.
Files changed (83) hide show
  1. package/README.md +57 -235
  2. package/install.js +406 -116
  3. package/package.json +2 -1
  4. package/plugins/api/skills/api/SKILL.md +102 -0
  5. package/plugins/api/skills/api-field-mapper/SKILL.md +95 -0
  6. package/plugins/api/skills/config/SKILL.md +140 -0
  7. package/plugins/api/skills/gen/SKILL.md +345 -0
  8. package/plugins/api/skills/help/SKILL.md +121 -0
  9. package/plugins/api/skills/import/SKILL.md +95 -0
  10. package/plugins/api/skills/map/SKILL.md +152 -0
  11. package/plugins/api/skills/search/SKILL.md +95 -0
  12. package/plugins/diag/skills/audit/SKILL.md +103 -0
  13. package/plugins/diag/skills/diag/SKILL.md +41 -0
  14. package/plugins/diag/skills/diagnose/SKILL.md +167 -0
  15. package/plugins/diag/skills/init/SKILL.md +142 -0
  16. package/plugins/diag/skills/stack-analyzer/SKILL.md +150 -0
  17. package/plugins/pm/skills/ask/SKILL.md +89 -0
  18. package/plugins/pm/skills/brief/SKILL.md +95 -0
  19. package/plugins/pm/skills/export/SKILL.md +93 -0
  20. package/plugins/pm/skills/help/SKILL.md +257 -0
  21. package/plugins/pm/skills/milestone/SKILL.md +102 -0
  22. package/plugins/pm/skills/monthly/SKILL.md +111 -0
  23. package/plugins/pm/skills/plan/SKILL.md +96 -0
  24. package/plugins/pm/skills/pm/SKILL.md +174 -0
  25. package/plugins/pm/skills/progress/SKILL.md +113 -0
  26. package/plugins/pm/skills/report-generator/SKILL.md +104 -0
  27. package/plugins/pm/skills/risk/SKILL.md +223 -0
  28. package/plugins/pm/skills/standup/SKILL.md +96 -0
  29. package/plugins/pm/skills/stats/SKILL.md +158 -0
  30. package/plugins/pm/skills/weekly/SKILL.md +157 -0
  31. package/plugins/req/skills/branch/SKILL.md +447 -0
  32. package/plugins/req/skills/cache/SKILL.md +232 -0
  33. package/plugins/req/skills/changelog/SKILL.md +187 -0
  34. package/plugins/req/skills/changelog-generator/SKILL.md +106 -0
  35. package/plugins/req/skills/code-impact-analyzer/SKILL.md +48 -0
  36. package/plugins/req/skills/commit/SKILL.md +308 -0
  37. package/plugins/req/skills/dev/SKILL.md +229 -0
  38. package/plugins/req/skills/dev-guide/SKILL.md +530 -0
  39. package/plugins/req/skills/do/SKILL.md +191 -0
  40. package/plugins/req/skills/done/SKILL.md +95 -0
  41. package/plugins/req/skills/edit/SKILL.md +187 -0
  42. package/plugins/req/skills/fix/SKILL.md +300 -0
  43. package/plugins/req/skills/help/SKILL.md +136 -0
  44. package/plugins/req/skills/init/SKILL.md +505 -0
  45. package/plugins/req/skills/issue/SKILL.md +237 -0
  46. package/plugins/req/skills/issue-guide/SKILL.md +125 -0
  47. package/plugins/req/skills/migrate/SKILL.md +128 -0
  48. package/plugins/req/skills/modules/SKILL.md +195 -0
  49. package/plugins/req/skills/natural-language-dispatcher/SKILL.md +545 -0
  50. package/plugins/req/skills/new/SKILL.md +172 -0
  51. package/plugins/req/skills/new-quick/SKILL.md +246 -0
  52. package/plugins/req/skills/pr/SKILL.md +157 -0
  53. package/plugins/req/skills/prd/SKILL.md +187 -0
  54. package/plugins/req/skills/prd-analyzer/SKILL.md +131 -0
  55. package/plugins/req/skills/prd-edit/SKILL.md +201 -0
  56. package/plugins/req/skills/projects/SKILL.md +115 -0
  57. package/plugins/req/skills/quick-fix-guide/SKILL.md +51 -0
  58. package/plugins/req/skills/release/SKILL.md +300 -0
  59. package/plugins/req/skills/release-rationale/SKILL.md +213 -0
  60. package/plugins/req/skills/req/SKILL.md +173 -0
  61. package/plugins/req/skills/requirement-analyzer/SKILL.md +274 -0
  62. package/plugins/req/skills/review/SKILL.md +201 -0
  63. package/plugins/req/skills/review-pr/SKILL.md +699 -0
  64. package/plugins/req/skills/show/SKILL.md +302 -0
  65. package/plugins/req/skills/specs/SKILL.md +99 -0
  66. package/plugins/req/skills/split/SKILL.md +164 -0
  67. package/plugins/req/skills/status/SKILL.md +184 -0
  68. package/plugins/req/skills/test/SKILL.md +431 -0
  69. package/plugins/req/skills/test-guide/SKILL.md +304 -0
  70. package/plugins/req/skills/test_new/SKILL.md +417 -0
  71. package/plugins/req/skills/test_regression/SKILL.md +298 -0
  72. package/plugins/req/skills/update/SKILL.md +131 -0
  73. package/plugins/req/skills/update-template/SKILL.md +203 -0
  74. package/plugins/req/skills/upgrade/SKILL.md +178 -0
  75. package/plugins/req/skills/use/SKILL.md +158 -0
  76. package/plugins/req/skills/version-bumper/SKILL.md +113 -0
  77. package/plugins/uat/skills/bug/SKILL.md +153 -0
  78. package/plugins/uat/skills/init/SKILL.md +88 -0
  79. package/plugins/uat/skills/new/SKILL.md +131 -0
  80. package/plugins/uat/skills/report/SKILL.md +48 -0
  81. package/plugins/uat/skills/run/SKILL.md +78 -0
  82. package/plugins/uat/skills/uat/SKILL.md +64 -0
  83. package/plugins/uat/skills/uat-executor/SKILL.md +299 -0
package/install.js CHANGED
@@ -1,152 +1,442 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { spawnSync, execSync } = require('child_process');
3
+ const { spawnSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
- const https = require('https');
8
7
 
9
8
  const VERSION = require('./package.json').version;
10
- const BINARY_NAME = process.platform === 'win32' ? 'devflow-skills.exe' : 'devflow-skills';
11
-
12
- function getPlatform() {
13
- const platform = os.platform();
14
- const arch = os.arch();
15
-
16
- const archMap = { x64: 'amd64', arm64: 'arm64', ia32: '386' };
17
- const mappedArch = archMap[arch] || arch;
18
-
19
- let platformName;
20
- switch (platform) {
21
- case 'linux':
22
- platformName = 'linux';
23
- break;
24
- case 'darwin':
25
- platformName = 'darwin';
26
- break;
27
- case 'win32':
28
- platformName = 'windows';
29
- break;
30
- default:
31
- console.error('不支持的平台: ' + platform);
32
- process.exit(1);
9
+
10
+ // ── tool config ─────────────────────────────────────────────────────────
11
+
12
+ const TOOLS = {
13
+ opencode: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.config/opencode/skills', flat: true },
14
+ codex: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.config/codex/skills', flat: true },
15
+ claude: { projectSkillsDir: 'plugins', globalSkillsDir: 'plugins', flat: false },
16
+ cursor: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.cursor/skills', flat: true },
17
+ copilot: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.github/copilot/skills', flat: true },
18
+ codebuddy: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.config/codebuddy/skills', flat: true },
19
+ windsurf: { projectSkillsDir: '.agents/skills', globalSkillsDir: '.config/windsurf/skills', flat: true },
20
+ };
21
+
22
+ const PLUGIN_ORDER = ['req', 'api', 'pm', 'diag', 'uat'];
23
+
24
+ // ── helpers ─────────────────────────────────────────────────────────────
25
+
26
+ function usage() {
27
+ console.log(`devflow-skills v${VERSION} — AI 技能一键安装/管理工具
28
+
29
+ 用法:
30
+ devflow-skills install --tool <TOOL> [--skill <NAME>...] [--all] [--plugin <NAME>] [--dir <PATH>] [--global] [--symlink]
31
+ devflow-skills list [--plugin <NAME>] [--format text|json] [--filter <STR>]
32
+ devflow-skills uninstall --tool <TOOL> [--skill <NAME>...] [--all] [--dir <PATH>] [--global]
33
+ devflow-skills add <owner/repo> [--tool <TOOL>] [--skill <NAME>...] [--all] [--list] [--dir <PATH>] [--global] [--symlink]
34
+
35
+ 命令:
36
+ install 安装技能到目标 AI 工具目录
37
+ list 列出所有可用技能
38
+ uninstall 从目标 AI 工具目录卸载技能
39
+ add 从 GitHub 仓库克隆并安装技能
40
+
41
+ 支持的 --tool 值: ${Object.keys(TOOLS).join(', ')}
42
+
43
+ 示例:
44
+ npx devflow-skills install --tool opencode --all
45
+ npx devflow-skills install --tool cursor --skill req-dev
46
+ npx devflow-skills install --tool opencode --all -g
47
+ npx devflow-skills install --tool cursor --all --symlink
48
+ npx devflow-skills list --plugin req
49
+ npx devflow-skills list --filter dev
50
+ npx devflow-skills uninstall --tool opencode --all
51
+ npx devflow-skills add zhouhao4221/devflow-skills --tool copilot --all
52
+ npx devflow-skills add zhouhao4221/devflow-skills --list
53
+ `);
54
+ }
55
+
56
+ function fail(msg) {
57
+ console.error('错误:', msg);
58
+ process.exit(1);
59
+ }
60
+
61
+ function ensure(cond, msg) {
62
+ if (!cond) fail(msg);
63
+ }
64
+
65
+ function parseArgs(argv) {
66
+ const positional = [];
67
+ const flags = {};
68
+ for (let i = 0; i < argv.length; i++) {
69
+ const a = argv[i];
70
+ if (a === '-g') {
71
+ flags.global = true;
72
+ continue;
73
+ }
74
+ if (!a.startsWith('--')) { positional.push(a); continue; }
75
+
76
+ const eq = a.indexOf('=');
77
+ const key = eq > -1 ? a.slice(2, eq) : a.slice(2);
78
+ let val = true;
79
+ if (eq > -1) {
80
+ val = a.slice(eq + 1);
81
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
82
+ val = argv[++i];
83
+ }
84
+
85
+ if (flags[key] === undefined) {
86
+ flags[key] = val;
87
+ } else if (Array.isArray(flags[key])) {
88
+ flags[key].push(val);
89
+ } else {
90
+ flags[key] = [flags[key], val];
91
+ }
33
92
  }
93
+ // normalize single-element arrays
94
+ if (flags.skill !== undefined && !Array.isArray(flags.skill)) flags.skill = [flags.skill];
95
+ return { positional, flags };
96
+ }
34
97
 
35
- return `${platformName}_${mappedArch}`;
98
+ function requireTool(flags) {
99
+ ensure(flags.tool, '--tool 是必需参数');
100
+ const cfg = TOOLS[flags.tool];
101
+ ensure(cfg, `不支持的 --tool: ${flags.tool} (支持: ${Object.keys(TOOLS).join(', ')})`);
102
+ return cfg;
36
103
  }
37
104
 
38
- function getCacheDir() {
39
- const dir = path.join(os.homedir(), '.devflow-skills', VERSION);
40
- if (!fs.existsSync(dir)) {
41
- fs.mkdirSync(dir, { recursive: true });
105
+ function requireSelection(flags) {
106
+ const hasAll = !!flags.all;
107
+ const hasSkill = !!(flags.skill && flags.skill.length > 0);
108
+ const hasPlugin = !!flags.plugin;
109
+ ensure(hasAll || hasSkill || hasPlugin, '请指定 --skill、--plugin 或 --all');
110
+ ensure(!(hasAll && hasSkill), '--skill 与 --all 不能同时使用');
111
+ }
112
+
113
+ // ── frontmatter ─────────────────────────────────────────────────────────
114
+
115
+ function parseFrontmatter(content) {
116
+ const lines = content.split('\n');
117
+ if (lines[0].trim() !== '---') return { name: '', description: '' };
118
+ const end = lines.indexOf('---', 1);
119
+ if (end === -1) return { name: '', description: '' };
120
+
121
+ const fm = {};
122
+ let curKey = '';
123
+ for (const line of lines.slice(1, end)) {
124
+ const m = line.match(/^(\w[\w-]*):\s*(.*)/);
125
+ if (m) {
126
+ curKey = m[1];
127
+ const after = m[2].trim();
128
+ fm[curKey] = (after === '|' || after === '>' || after === '|-' || after === '>-') ? '' : after;
129
+ } else if (curKey) {
130
+ const trimmed = line.trimStart();
131
+ if (trimmed && line.length - trimmed.length >= 2) {
132
+ fm[curKey] = fm[curKey] ? fm[curKey] + ' ' + trimmed : trimmed;
133
+ }
134
+ }
42
135
  }
43
- return dir;
136
+ return { name: fm.name || '', description: (fm.description || '').replace(/\n/g, ' ').trim() };
44
137
  }
45
138
 
46
- function downloadBinary(url, dest) {
47
- return new Promise((resolve, reject) => {
48
- const file = fs.createWriteStream(dest, { mode: 0o755 });
49
- https
50
- .get(url, (response) => {
51
- if (response.statusCode === 302 || response.statusCode === 301) {
52
- https.get(response.headers.location, (redirectRes) => {
53
- redirectRes.pipe(file);
54
- file.on('finish', () => {
55
- file.close();
56
- resolve();
57
- });
58
- }).on('error', reject);
59
- return;
60
- }
61
- if (response.statusCode !== 200) {
62
- reject(new Error(`下载失败 (HTTP ${response.statusCode}): ${url}`));
63
- return;
64
- }
65
- response.pipe(file);
66
- file.on('finish', () => {
67
- file.close();
68
- resolve();
69
- });
70
- })
71
- .on('error', reject);
72
- });
139
+ function updateFrontmatterName(content, newName) {
140
+ return content.replace(/^name:\s*.*/m, 'name: ' + newName);
73
141
  }
74
142
 
75
- function getPackageDir() {
76
- return path.dirname(require.resolve('./package.json'));
143
+ // ── skill loading ───────────────────────────────────────────────────────
144
+
145
+ function loadSkills(baseDir) {
146
+ const skills = [];
147
+ const pluginsDir = path.join(baseDir, 'plugins');
148
+ if (!fs.existsSync(pluginsDir)) return skills;
149
+
150
+ for (const plugin of fs.readdirSync(pluginsDir)) {
151
+ const skillsDir = path.join(pluginsDir, plugin, 'skills');
152
+ if (!fs.existsSync(skillsDir)) continue;
153
+ for (const name of fs.readdirSync(skillsDir)) {
154
+ const file = path.join(skillsDir, name, 'SKILL.md');
155
+ if (!fs.existsSync(file)) continue;
156
+ const raw = fs.readFileSync(file, 'utf-8');
157
+ const fm = parseFrontmatter(raw);
158
+ skills.push({ plugin, name, description: fm.description, file, raw });
159
+ }
160
+ }
161
+ skills.sort((a, b) => a.plugin.localeCompare(b.plugin) || a.name.localeCompare(b.name));
162
+ return skills;
77
163
  }
78
164
 
79
- async function getBinary() {
80
- const cacheDir = getCacheDir();
81
- const binaryPath = path.join(cacheDir, BINARY_NAME);
165
+ function resolveSkill(spec, allSkills) {
166
+ const parts = spec.split('-', 2);
167
+ if (parts.length === 2) {
168
+ const m = allSkills.find(s => s.plugin === parts[0] && s.name === parts[1]);
169
+ return m ? [m] : [];
170
+ }
171
+ return allSkills.filter(s => s.name === spec);
172
+ }
82
173
 
83
- if (fs.existsSync(binaryPath)) {
84
- return binaryPath;
174
+ function uniqueResolve(spec, allSkills) {
175
+ const matched = resolveSkill(spec, allSkills);
176
+ ensure(matched.length > 0, `未找到技能 '${spec}',使用 'devflow-skills list' 查看所有可用技能`);
177
+ if (matched.length > 1) {
178
+ for (const s of matched) console.error(` ${s.plugin}-${s.name}: ${s.description}`);
179
+ fail(`技能名 '${spec}' 匹配到多个技能,请使用完整的扁平名 (如 req-dev) 来区分`);
85
180
  }
181
+ return matched[0];
182
+ }
86
183
 
87
- const pkgDir = getPackageDir();
88
- const localBinary = path.join(pkgDir, BINARY_NAME);
89
- if (fs.existsSync(localBinary)) {
90
- process.stderr.write('使用本地构建的二进制。\n');
91
- return localBinary;
184
+ function selectSkills(flags, allSkills) {
185
+ if (flags.all) return allSkills;
186
+ if (flags.plugin) {
187
+ const s = allSkills.filter(s => s.plugin === flags.plugin);
188
+ ensure(s.length > 0, `未找到插件 '${flags.plugin}' 的技能`);
189
+ return s;
92
190
  }
191
+ return flags.skill.map(s => uniqueResolve(s, allSkills));
192
+ }
193
+
194
+ // ── target paths ────────────────────────────────────────────────────────
195
+
196
+ function targetBase(cfg, dir, global) {
197
+ return global ? path.join(os.homedir(), cfg.globalSkillsDir) : path.join(dir, cfg.projectSkillsDir);
198
+ }
199
+
200
+ function targetPath(cfg, dir, global, plugin, name) {
201
+ const base = targetBase(cfg, dir, global);
202
+ return cfg.flat ? path.join(base, plugin + '-' + name) : path.join(base, plugin, 'skills', name);
203
+ }
204
+
205
+ function canonicalBase(dir, global) {
206
+ return global ? path.join(os.homedir(), '.agents', 'skills') : path.join(dir, '.agents', 'skills');
207
+ }
208
+
209
+ function canonicalPath(dir, global, plugin, name) {
210
+ return path.join(canonicalBase(dir, global), plugin + '-' + name);
211
+ }
212
+
213
+ // ── install ─────────────────────────────────────────────────────────────
214
+
215
+ function installOne(cfg, dir, global, symlink, { plugin, name, raw }) {
216
+ if (symlink) return installSymlink(cfg, dir, global, plugin, name, raw);
93
217
 
94
- const platform = getPlatform();
95
- const suffix = process.platform === 'win32' ? '.exe' : '';
96
- const url = `https://github.com/zhouhao4221/devflow-skills/releases/download/v${VERSION}/devflow-skills_${platform}${suffix}`;
218
+ const dest = targetPath(cfg, dir, global, plugin, name);
219
+ fs.mkdirSync(dest, { recursive: true });
220
+ const content = cfg.flat ? updateFrontmatterName(raw, plugin + '-' + name) : raw;
221
+ fs.writeFileSync(path.join(dest, 'SKILL.md'), content);
222
+ }
223
+
224
+ function installSymlink(cfg, dir, global, plugin, name, raw) {
225
+ const canonDir = canonicalPath(dir, global, plugin, name);
226
+ fs.mkdirSync(canonDir, { recursive: true });
227
+ const flatName = plugin + '-' + name;
228
+ const updated = cfg.flat ? updateFrontmatterName(raw, flatName) : raw;
229
+ fs.writeFileSync(path.join(canonDir, 'SKILL.md'), updated);
97
230
 
98
- process.stderr.write(`首次运行,正在下载 devflow-skills v${VERSION} (${platform})...\n`);
231
+ const linkDir = targetPath(cfg, dir, global, plugin, name);
232
+ const linkFile = path.join(linkDir, 'SKILL.md');
233
+ try { fs.unlinkSync(linkDir); } catch (_) {}
234
+ try { fs.unlinkSync(linkFile); } catch (_) {}
235
+ fs.mkdirSync(path.dirname(linkFile), { recursive: true });
99
236
 
100
237
  try {
101
- await downloadBinary(url, binaryPath);
102
- process.stderr.write('下载完成。\n');
103
- } catch (e) {
104
- process.stderr.write('下载失败,尝试本地构建...\n');
238
+ fs.symlinkSync(path.relative(path.dirname(linkFile), path.join(canonDir, 'SKILL.md')), linkFile);
239
+ } catch (_) {
240
+ installOne(cfg, dir, global, false, { plugin, name, raw });
241
+ }
242
+ }
243
+
244
+ function installAll(selected, cfg, dir, global, symlink, sourceLabel) {
245
+ let installed = 0;
246
+ const quiet = selected.length > 10;
247
+ for (const s of selected) {
105
248
  try {
106
- const goBuild = spawnSync('go', ['build', '-o', binaryPath, pkgDir], {
107
- cwd: pkgDir,
108
- stdio: 'inherit',
109
- });
110
- if (goBuild.status === 0 && fs.existsSync(binaryPath)) {
111
- return binaryPath;
112
- }
113
- } catch (_) {}
249
+ installOne(cfg, dir, global, symlink, s);
250
+ installed++;
251
+ if (!quiet || installed <= 10) console.log(` ${s.plugin}-${s.name} (${s.description})`);
252
+ else if (installed === 11) console.log(' ...');
253
+ } catch (e) {
254
+ console.error(`安装 ${s.plugin}-${s.name} 失败:`, e.message);
255
+ }
256
+ }
257
+ const label = sourceLabel ? `从 ${sourceLabel} ` : '';
258
+ console.log(`\n已${label}安装 ${installed} 个技能到 ${targetBase(cfg, dir, global)}/`);
259
+ console.log('下一步: 重启 AI 工具或刷新技能列表即可使用。');
260
+ }
261
+
262
+ function runInstall(flags) {
263
+ const cfg = requireTool(flags);
264
+ requireSelection(flags);
265
+ const selected = selectSkills(flags, loadSkills(__dirname));
266
+ installAll(selected, cfg, flags.dir || '.', !!flags.global, !!flags.symlink);
267
+ }
268
+
269
+ // ── list ────────────────────────────────────────────────────────────────
270
+
271
+ function groupSkills(skills) {
272
+ const groups = {};
273
+ for (const s of skills) {
274
+ if (!groups[s.plugin]) groups[s.plugin] = [];
275
+ groups[s.plugin].push(s);
276
+ }
277
+ return groups;
278
+ }
279
+
280
+ function sortedPlugins(groups, filterPlugin) {
281
+ const order = filterPlugin ? [filterPlugin] : [...PLUGIN_ORDER];
282
+ for (const p of Object.keys(groups).sort()) {
283
+ if (!order.includes(p)) order.push(p);
284
+ }
285
+ return order;
286
+ }
287
+
288
+ function printSkillTable(plugins, groups, indent) {
289
+ for (const p of plugins) {
290
+ if (!groups[p]) continue;
291
+ console.log(`${indent}${p} (${groups[p].length} 个技能):`);
292
+ for (const s of groups[p]) console.log(`${indent} ${s.name.padEnd(20)} ${s.description}`);
293
+ console.log();
294
+ }
295
+ }
296
+
297
+ function runList(flags) {
298
+ let skills = loadSkills(__dirname);
299
+ if (flags.plugin) skills = skills.filter(s => s.plugin === flags.plugin);
300
+ if (flags.filter) {
301
+ const f = flags.filter.toLowerCase();
302
+ skills = skills.filter(s => (s.plugin + '-' + s.name).includes(f) || s.name.includes(f));
303
+ }
304
+
305
+ if (flags.format === 'json') {
306
+ const out = {};
307
+ for (const p of sortedPlugins(groupSkills(skills), flags.plugin)) {
308
+ const g = groupSkills(skills)[p];
309
+ if (!g) continue;
310
+ out[p] = g.map(s => ({ name: s.name, flatName: s.plugin + '-' + s.name, description: s.description }));
311
+ }
312
+ console.log(JSON.stringify(out, null, 2));
313
+ return;
314
+ }
315
+
316
+ const groups = groupSkills(skills);
317
+ for (const p of sortedPlugins(groups, flags.plugin)) {
318
+ if (!groups[p]) continue;
319
+ console.log(`${p} 插件 (${groups[p].length} 个技能):`);
320
+ for (const s of groups[p]) console.log(` ${(s.plugin + '-' + s.name).padEnd(22)} ${s.description}`);
321
+ console.log();
322
+ }
323
+ }
324
+
325
+ // ── uninstall ───────────────────────────────────────────────────────────
326
+
327
+ function runUninstall(flags) {
328
+ const cfg = requireTool(flags);
329
+ const dir = flags.dir || '.';
330
+ const global = !!flags.global;
331
+
332
+ if (flags.all) {
333
+ const base = targetBase(cfg, dir, global);
334
+ const abs = path.resolve(base);
335
+ if (!fs.existsSync(abs)) { console.log(`技能目录不存在: ${abs}\n无需卸载。`); return; }
336
+ fs.rmSync(abs, { recursive: true });
337
+ console.log(`已卸载所有技能,删除目录: ${abs}`);
338
+
339
+ const canon = canonicalBase(dir, global);
340
+ if (fs.existsSync(canon)) console.error(`注意: 规范目录 ${canon} 仍存在,如使用过 --symlink 模式可能需要手动清理`);
341
+ return;
342
+ }
343
+
344
+ ensure(flags.skill && flags.skill.length > 0, '请指定 --skill 或 --all');
114
345
 
115
- process.stderr.write('本地构建失败,尝试使用 go install...\n');
346
+ const allSkills = loadSkills(__dirname);
347
+ let removed = 0;
348
+ for (const spec of flags.skill) {
349
+ const parts = spec.split('-', 2);
350
+ let plugin, name;
351
+ if (parts.length === 2) { plugin = parts[0]; name = parts[1]; }
352
+ else {
353
+ const s = uniqueResolve(spec, allSkills);
354
+ plugin = s.plugin; name = s.name;
355
+ }
356
+
357
+ const tgt = targetPath(cfg, dir, global, plugin, name);
358
+ if (!fs.existsSync(tgt)) { console.log(`未安装: ${plugin}-${name}`); continue; }
116
359
  try {
117
- const goInstall = spawnSync('go', ['install', `github.com/zhouhao4221/devflow-skills@v${VERSION}`], {
118
- stdio: 'inherit',
119
- });
120
- if (goInstall.status !== 0) {
121
- console.error('go install 失败。请确保已安装 Go 1.21+ 或手动下载二进制。');
122
- console.error(`GitHub Releases: https://github.com/zhouhao4221/devflow-skills/releases/tag/v${VERSION}`);
123
- process.exit(1);
124
- }
125
- const goBinPath = path.join(os.homedir(), 'go', 'bin', BINARY_NAME);
126
- if (fs.existsSync(goBinPath)) {
127
- fs.copyFileSync(goBinPath, binaryPath);
128
- fs.chmodSync(binaryPath, 0o755);
129
- return binaryPath;
130
- }
131
- } catch (e2) {
132
- console.error('go install 失败:', e2.message);
133
- process.exit(1);
360
+ fs.rmSync(tgt, { recursive: true });
361
+ console.log(`已卸载: ${plugin}-${name}`);
362
+ removed++;
363
+ } catch (e) {
364
+ console.error(`卸载 ${plugin}-${name} 失败:`, e.message);
134
365
  }
135
366
  }
136
367
 
137
- fs.chmodSync(binaryPath, 0o755);
138
- return binaryPath;
368
+ console.log(removed === 0 ? '没有需要卸载的技能。' : `\n已卸载 ${removed} 个技能。`);
139
369
  }
140
370
 
141
- async function main() {
142
- const binaryPath = await getBinary();
143
- const result = spawnSync(binaryPath, process.argv.slice(2), {
144
- stdio: 'inherit',
145
- });
146
- process.exit(result.status || 0);
371
+ // ── add ─────────────────────────────────────────────────────────────────
372
+
373
+ function fetchRepoSkills(repo) {
374
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'devflow-skills-'));
375
+ const url = `https://github.com/${repo}.git`;
376
+
377
+ console.error(`正在克隆 ${url}...`);
378
+ const r = spawnSync('git', ['clone', '--depth', '1', url, tmpDir], { stdio: 'inherit' });
379
+ if (r.status !== 0) {
380
+ try { fs.rmSync(tmpDir, { recursive: true }); } catch (_) {}
381
+ fail('git clone 失败');
382
+ }
383
+
384
+ const skills = loadSkills(tmpDir);
385
+ try { fs.rmSync(tmpDir, { recursive: true }); } catch (_) {}
386
+ return skills;
147
387
  }
148
388
 
149
- main().catch((err) => {
150
- console.error('运行失败:', err.message);
151
- process.exit(1);
152
- });
389
+ function runAdd(flags, positional) {
390
+ const repo = positional[0];
391
+ ensure(repo, '请指定仓库 (格式: owner/repo)\n示例: devflow-skills add zhouhao4221/devflow-skills --list');
392
+ ensure(repo.includes('/'), '仓库格式无效,请使用 owner/repo 格式');
393
+
394
+ if (flags.list) {
395
+ const skills = fetchRepoSkills(repo);
396
+ console.log(`${repo} 中的可用技能:\n`);
397
+ printSkillTable(sortedPlugins(groupSkills(skills)), groupSkills(skills), ' ');
398
+ console.log(`安装示例:`);
399
+ console.log(` devflow-skills add ${repo} --tool opencode --all`);
400
+ console.log(` devflow-skills add ${repo} --tool cursor --skill <skill-name>`);
401
+ return;
402
+ }
403
+
404
+ const cfg = requireTool(flags);
405
+ requireSelection(flags);
406
+ const selected = selectSkills(flags, fetchRepoSkills(repo));
407
+ installAll(selected, cfg, flags.dir || '.', !!flags.global, !!flags.symlink, repo);
408
+ }
409
+
410
+ // ── version ─────────────────────────────────────────────────────────────
411
+
412
+ function runVersion() {
413
+ console.log(`devflow-skills v${VERSION}`);
414
+ }
415
+
416
+ // ── main ────────────────────────────────────────────────────────────────
417
+
418
+ function main() {
419
+ const argv = process.argv.slice(2);
420
+ if (argv.length === 0) { usage(); process.exit(1); }
421
+
422
+ const cmd = argv[0];
423
+ const rest = argv.slice(1);
424
+
425
+ if (cmd === 'help' || cmd === '-h' || cmd === '--help') { usage(); return; }
426
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') { runVersion(); return; }
427
+
428
+ const { positional, flags } = parseArgs(rest);
429
+
430
+ switch (cmd) {
431
+ case 'install': return runInstall(flags);
432
+ case 'list': return runList(flags);
433
+ case 'uninstall': return runUninstall(flags);
434
+ case 'add': return runAdd(flags, positional);
435
+ default:
436
+ console.error(`未知命令: ${cmd}\n`);
437
+ usage();
438
+ process.exit(1);
439
+ }
440
+ }
441
+
442
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhouhao4221/devflow-skills",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "一键安装/管理 devflow-skills AI 技能包",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "files": [
14
14
  "install.js",
15
+ "plugins/",
15
16
  "package.json"
16
17
  ]
17
18
  }
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: api
3
+ description: |
4
+ API 对接工具 - 列出接口概览和子命令帮助
5
+ ---
6
+
7
+ # API 对接工具
8
+
9
+ 前端 API 对接工具主入口,展示接口概览和可用命令。
10
+
11
+ ## 命令格式
12
+
13
+ ```
14
+ /api [--name=服务名] [--tag=分组名]
15
+ ```
16
+
17
+ ## 子命令
18
+
19
+ | 子命令 | 说明 | 示例 |
20
+ |-------|------|------|
21
+ | (空) | 列出接口概览 | `/api` |
22
+ | `config` | 配置管理 | `/api:config init` |
23
+ | `import` | 导入 Swagger | `/api:import` |
24
+ | `search` | 搜索接口 | `/api:search 用户` |
25
+ | `map` | 字段映射 | `/api:map GET /api/v1/users/{id}` |
26
+ | `gen` | 代码生成 | `/api:gen GET /api/v1/users/{id}` |
27
+
28
+ ## 执行流程
29
+
30
+ ### 无配置时
31
+
32
+ ```
33
+ API 对接工具
34
+
35
+ ⚠️ 尚未初始化配置
36
+
37
+ 快速开始:
38
+ 1. /api:config init — 初始化配置,添加 Swagger 数据源
39
+ 2. /api:import — 导入并解析接口文档
40
+ 3. /api:search <关键词> — 搜索接口
41
+ 4. /api:map <接口> — 查看字段映射
42
+ 5. /api:gen <接口> — 生成类型和请求代码
43
+ ```
44
+
45
+ ### 有配置时
46
+
47
+ 1. 读取 `.api-config.json`
48
+ 2. 对每个数据源调用 `swagger-parser.py`(`mode=summary`)获取接口总数和分组概览。
49
+
50
+ 3. 展示概览:
51
+
52
+ ```
53
+ API 对接工具
54
+
55
+ 数据源:
56
+ 1. 主服务 — http://localhost:8080/swagger/doc.json
57
+ 接口数:42 | 分组:用户管理(8) 订单管理(12) 商品管理(10) 系统设置(6) 其他(6)
58
+
59
+ 2. 支付服务 — ./docs/payment-swagger.json
60
+ 接口数:15 | 分组:支付(8) 退款(4) 对账(3)
61
+
62
+ 常用命令:
63
+ /api:search <关键词> 搜索接口
64
+ /api:map <METHOD> <path> 查看字段映射
65
+ /api:gen <METHOD> <path> 生成代码
66
+ /api:gen --tag=<分组名> 批量生成某分组代码
67
+ /api:config 查看/修改配置
68
+ ```
69
+
70
+ ### 按 Tag 过滤
71
+
72
+ ```
73
+ /api --tag=用户管理
74
+
75
+ 用户管理 — 8 个接口
76
+
77
+
78
+ # 方法 路径 描述
79
+
80
+ 1 GET /api/v1/users 获取用户列表
81
+ 2 POST /api/v1/users 创建用户
82
+ 3 GET /api/v1/users/{id} 获取用户详情
83
+ 4 PUT /api/v1/users/{id} 更新用户信息
84
+ 5 DELETE /api/v1/users/{id} 删除用户
85
+ 6 POST /api/v1/users/{id}/reset 重置密码
86
+ 7 GET /api/v1/users/export 导出用户数据
87
+ 8 POST /api/v1/users/import 导入用户数据
88
+
89
+ ```
90
+
91
+ ### 数据源连接失败
92
+
93
+ 对无法访问的数据源标记状态:
94
+
95
+ ```
96
+ 数据源:
97
+ 1. 主服务 — http://localhost:8080/swagger/doc.json
98
+ ❌ 无法访问,请检查后端服务是否启动
99
+
100
+ 2. 支付服务 — ./docs/payment-swagger.json
101
+ 接口数:15 | 分组:支付(8) 退款(4) 对账(3)
102
+ ```