@vima_tech/flywheel 1.1.0 → 1.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/bin/flywheel.js +211 -59
- package/package.json +1 -1
- package/scripts/flywheel-install.sh +31 -11
package/bin/flywheel.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
const { spawn, execSync } = require('child_process');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const
|
|
5
|
+
const os = require('os');
|
|
6
6
|
|
|
7
|
-
const REPO_DIR = process.env.FLYWHEEL_DIR || path.dirname(path.dirname(__dirname));
|
|
8
7
|
const NPM_PKG = '@vima_tech/flywheel';
|
|
9
8
|
|
|
10
9
|
function resolveScript(name) {
|
|
@@ -15,12 +14,15 @@ function resolveKernelFile(name) {
|
|
|
15
14
|
return path.join(__dirname, '../' + name);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
function runScript(scriptPath, args) {
|
|
17
|
+
function runScript(scriptPath, args, extraEnv = {}) {
|
|
18
|
+
const env = { ...process.env, FLYWHEEL_DIR: process.cwd(), ...extraEnv };
|
|
19
|
+
if (proxy) env.FLYWHEEL_PROXY = proxy;
|
|
20
|
+
|
|
19
21
|
return new Promise((resolve, reject) => {
|
|
20
22
|
const child = spawn('bash', [scriptPath, ...args], {
|
|
21
23
|
cwd: process.cwd(),
|
|
22
24
|
stdio: 'inherit',
|
|
23
|
-
env
|
|
25
|
+
env,
|
|
24
26
|
});
|
|
25
27
|
child.on('exit', (code) => {
|
|
26
28
|
if (code === 0) resolve(0);
|
|
@@ -32,7 +34,9 @@ function runScript(scriptPath, args) {
|
|
|
32
34
|
function downloadFile(url, destPath) {
|
|
33
35
|
return new Promise((resolve, reject) => {
|
|
34
36
|
const file = fs.createWriteStream(destPath);
|
|
35
|
-
|
|
37
|
+
const args = ['-fsSL', url, '-o', destPath];
|
|
38
|
+
if (proxy) args.splice(1, 0, '--proxy', proxy);
|
|
39
|
+
spawn('curl', args, { stdio: 'ignore' })
|
|
36
40
|
.on('exit', (code) => {
|
|
37
41
|
if (code === 0) resolve(0);
|
|
38
42
|
else reject(new Error('curl failed'));
|
|
@@ -41,6 +45,8 @@ function downloadFile(url, destPath) {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
async function initKernel(dir, skill) {
|
|
48
|
+
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/flywheel/main';
|
|
49
|
+
|
|
44
50
|
const kernelFiles = [
|
|
45
51
|
'CLAUDE.md', '.gitignore',
|
|
46
52
|
'.claude/commands/flywheel.md', '.claude/commands/skill.md',
|
|
@@ -52,17 +58,6 @@ async function initKernel(dir, skill) {
|
|
|
52
58
|
'skills/_template/feedback-questions.sh',
|
|
53
59
|
];
|
|
54
60
|
|
|
55
|
-
const optionalFiles = [
|
|
56
|
-
'skills/_template/artifacts.md',
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/flywheel/main';
|
|
60
|
-
const skillFiles = skill ? [
|
|
61
|
-
'skills/' + skill + '/skill.yaml',
|
|
62
|
-
'skills/' + skill + '/domain.md',
|
|
63
|
-
'skills/' + skill + '/feedback-questions.sh',
|
|
64
|
-
] : [];
|
|
65
|
-
|
|
66
61
|
for (const f of kernelFiles) {
|
|
67
62
|
const dest = path.join(dir, f);
|
|
68
63
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
@@ -74,13 +69,12 @@ async function initKernel(dir, skill) {
|
|
|
74
69
|
}
|
|
75
70
|
}
|
|
76
71
|
|
|
77
|
-
for (const f of optionalFiles) {
|
|
78
|
-
const dest = path.join(dir, f);
|
|
79
|
-
const srcPath = resolveKernelFile(f);
|
|
80
|
-
if (fs.existsSync(srcPath)) fs.copyFileSync(srcPath, dest);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
72
|
if (skill) {
|
|
73
|
+
const skillFiles = [
|
|
74
|
+
'skills/' + skill + '/skill.yaml',
|
|
75
|
+
'skills/' + skill + '/domain.md',
|
|
76
|
+
'skills/' + skill + '/feedback-questions.sh',
|
|
77
|
+
];
|
|
84
78
|
for (const f of skillFiles) {
|
|
85
79
|
const dest = path.join(dir, f);
|
|
86
80
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
@@ -106,6 +100,12 @@ async function initKernel(dir, skill) {
|
|
|
106
100
|
fs.writeFileSync(path.join(dir, '.gitignore'), `# flywheel-managed\nprojects/*/\nepisodic-logs/\n.distill-needed/*\n!.distill-needed/.gitkeep\nmemory/.gitkeep\n*.log\n`);
|
|
107
101
|
}
|
|
108
102
|
|
|
103
|
+
fs.writeFileSync(path.join(dir, '.flywheel'), JSON.stringify({
|
|
104
|
+
version: '1.0.0',
|
|
105
|
+
created: new Date().toISOString(),
|
|
106
|
+
skill: skill || 'none'
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
109
|
try {
|
|
110
110
|
execSync('chmod +x ' + path.join(dir, 'scripts/*.sh'), { stdio: 'ignore', shell: true });
|
|
111
111
|
} catch (e) {}
|
|
@@ -118,8 +118,7 @@ async function cmdNew(args) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const projectName = args[0];
|
|
121
|
-
const
|
|
122
|
-
const targetDir = path.join(process.cwd(), dirName);
|
|
121
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
123
122
|
|
|
124
123
|
if (fs.existsSync(targetDir)) {
|
|
125
124
|
console.error('❌ 目录已存在: ' + targetDir);
|
|
@@ -159,14 +158,14 @@ async function cmdNew(args) {
|
|
|
159
158
|
console.log(' ✅ 项目创建完成!');
|
|
160
159
|
console.log('');
|
|
161
160
|
console.log(' 进入目录并启动 ' + agent + ':');
|
|
162
|
-
console.log(' cd ' +
|
|
161
|
+
console.log(' cd ' + projectName + ' && flywheel start');
|
|
163
162
|
console.log('');
|
|
164
|
-
console.log(' 或手动启动:cd ' +
|
|
163
|
+
console.log(' 或手动启动:cd ' + projectName + ' && claude');
|
|
165
164
|
console.log(' 然后输入 /sfw 开始飞轮工作');
|
|
166
165
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
function cmdStart() {
|
|
170
169
|
const agent = detectAgent();
|
|
171
170
|
if (!agent) {
|
|
172
171
|
console.error('❌ 未检测到 Claude Code 或 OpenCode');
|
|
@@ -194,7 +193,130 @@ function detectAgent() {
|
|
|
194
193
|
return null;
|
|
195
194
|
}
|
|
196
195
|
|
|
197
|
-
|
|
196
|
+
function findFlywheelProjects(startPath, maxDepth = 3) {
|
|
197
|
+
const projects = [];
|
|
198
|
+
const homeDir = os.homedir();
|
|
199
|
+
const maxDepthParam = maxDepth || 3;
|
|
200
|
+
|
|
201
|
+
function scan(dir, depth) {
|
|
202
|
+
if (depth > maxDepthParam) return;
|
|
203
|
+
try {
|
|
204
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (!entry.isDirectory()) continue;
|
|
207
|
+
if (entry.name === '.' || entry.name === '..') continue;
|
|
208
|
+
if (entry.name === 'node_modules') continue;
|
|
209
|
+
|
|
210
|
+
const fullPath = path.join(dir, entry.name);
|
|
211
|
+
const flywheelMarker = path.join(fullPath, '.flywheel');
|
|
212
|
+
|
|
213
|
+
if (fs.existsSync(flywheelMarker)) {
|
|
214
|
+
projects.push(fullPath);
|
|
215
|
+
} else if (fullPath.startsWith(homeDir)) {
|
|
216
|
+
scan(fullPath, depth + 1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (e) {}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
scan(startPath, 0);
|
|
223
|
+
return projects;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function updateProjectFramework(projectPath) {
|
|
227
|
+
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/flywheel/main';
|
|
228
|
+
|
|
229
|
+
const kernelFiles = [
|
|
230
|
+
'CLAUDE.md',
|
|
231
|
+
'.claude/commands/flywheel.md', '.claude/commands/skill.md',
|
|
232
|
+
'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
|
|
233
|
+
'scripts/bridge-to-coder.sh', 'scripts/flywheel-install.sh',
|
|
234
|
+
'skills/_kernel/distillation.md',
|
|
235
|
+
'skills/_template/skill.yaml', 'skills/_template/domain.md',
|
|
236
|
+
'skills/_template/feedback-questions.sh',
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
console.log(' 更新框架文件...');
|
|
240
|
+
for (const f of kernelFiles) {
|
|
241
|
+
const dest = path.join(projectPath, f);
|
|
242
|
+
try {
|
|
243
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
244
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
245
|
+
console.log(' ✓ ' + f);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.log(' ✗ ' + f + ' (失败)');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function cmdUpgrade(args) {
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
255
|
+
console.log(' Flywheel 升级');
|
|
256
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
257
|
+
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log('📦 升级 flywheel CLI...');
|
|
260
|
+
const npmArgs = ['install', '-g', NPM_PKG];
|
|
261
|
+
if (proxy) npmArgs.push('--proxy', proxy);
|
|
262
|
+
const npmSpawn = spawn('npm', npmArgs, { stdio: 'inherit' });
|
|
263
|
+
await new Promise((resolve) => npmSpawn.on('exit', resolve));
|
|
264
|
+
|
|
265
|
+
if (args.length > 0) {
|
|
266
|
+
const targetPath = path.resolve(args[0]);
|
|
267
|
+
if (fs.existsSync(path.join(targetPath, '.flywheel'))) {
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log('📦 更新项目框架: ' + targetPath);
|
|
270
|
+
await updateProjectFramework(targetPath);
|
|
271
|
+
} else {
|
|
272
|
+
console.error('❌ 不是有效的 flywheel 项目: ' + targetPath);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log('🔍 扫描 flywheel 项目...');
|
|
277
|
+
const projects = findFlywheelProjects(os.homedir());
|
|
278
|
+
|
|
279
|
+
if (projects.length === 0) {
|
|
280
|
+
console.log(' 未找到 flywheel 项目');
|
|
281
|
+
} else {
|
|
282
|
+
console.log(' 找到 ' + projects.length + ' 个项目:');
|
|
283
|
+
projects.forEach((p, i) => console.log(' ' + (i + 1) + '. ' + p));
|
|
284
|
+
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log(' 是否更新所有项目框架?(y/N)');
|
|
287
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
288
|
+
const answer = await new Promise((resolve) => rl.question('', resolve));
|
|
289
|
+
rl.close();
|
|
290
|
+
|
|
291
|
+
if (answer.toLowerCase() === 'y') {
|
|
292
|
+
for (const proj of projects) {
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log('📦 更新: ' + proj);
|
|
295
|
+
await updateProjectFramework(proj);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
303
|
+
console.log(' ✅ 升级完成');
|
|
304
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let proxy = '';
|
|
308
|
+
const rawArgs = process.argv.slice(2);
|
|
309
|
+
const filteredArgs = [];
|
|
310
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
311
|
+
if (rawArgs[i] === '-x' || rawArgs[i] === '--proxy') {
|
|
312
|
+
proxy = rawArgs[i + 1];
|
|
313
|
+
i++;
|
|
314
|
+
} else {
|
|
315
|
+
filteredArgs.push(rawArgs[i]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const [command, ...restArgs] = filteredArgs;
|
|
198
320
|
|
|
199
321
|
if (!command) {
|
|
200
322
|
console.log(`
|
|
@@ -203,68 +325,98 @@ Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
|
203
325
|
用法: flywheel <command> [args]
|
|
204
326
|
|
|
205
327
|
可用命令:
|
|
206
|
-
new
|
|
207
|
-
start
|
|
208
|
-
version
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
help
|
|
328
|
+
new|create|init 创建新项目
|
|
329
|
+
start|run|launch 启动 Claude Code 或 OpenCode
|
|
330
|
+
-v|--version 显示版本号
|
|
331
|
+
install|add|i 安装 skill 包
|
|
332
|
+
list|ls 列出已安装的 skill
|
|
333
|
+
update [skill] 更新 skill 包
|
|
334
|
+
distill|dist 手动触发蒸馏
|
|
335
|
+
upgrade [path] 升级 flywheel 工具,并更新项目框架
|
|
336
|
+
-h|--help 显示帮助
|
|
337
|
+
|
|
338
|
+
选项:
|
|
339
|
+
-x, --proxy <url> 使用代理
|
|
215
340
|
|
|
216
341
|
示例:
|
|
217
342
|
flywheel new my-project
|
|
218
|
-
|
|
219
|
-
flywheel
|
|
343
|
+
flywheel update # 更新所有 skill
|
|
344
|
+
flywheel update req-mining # 更新指定 skill
|
|
345
|
+
flywheel upgrade # 升级工具 + 交互更新项目框架
|
|
346
|
+
flywheel upgrade ./my-project # 更新指定项目框架
|
|
347
|
+
flywheel update -x http://127.0.0.1:7890
|
|
220
348
|
`);
|
|
221
349
|
process.exit(0);
|
|
222
350
|
}
|
|
223
351
|
|
|
224
352
|
switch (command) {
|
|
353
|
+
case '-v':
|
|
225
354
|
case '--version':
|
|
355
|
+
case '-V':
|
|
226
356
|
case 'version':
|
|
227
|
-
console.log('@vima_tech/flywheel v1.
|
|
357
|
+
console.log('@vima_tech/flywheel v1.2.0');
|
|
358
|
+
break;
|
|
359
|
+
case 'new':
|
|
360
|
+
case 'create':
|
|
361
|
+
case 'init':
|
|
362
|
+
cmdNew(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
363
|
+
break;
|
|
364
|
+
case 'start':
|
|
365
|
+
case 'run':
|
|
366
|
+
case 'launch':
|
|
367
|
+
cmdStart();
|
|
228
368
|
break;
|
|
369
|
+
case '-h':
|
|
370
|
+
case '--help':
|
|
371
|
+
case '-?':
|
|
229
372
|
case 'help':
|
|
373
|
+
case 'h':
|
|
230
374
|
console.log(`
|
|
231
375
|
Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
232
376
|
|
|
233
377
|
用法: flywheel <command> [args]
|
|
234
378
|
|
|
235
379
|
可用命令:
|
|
236
|
-
new
|
|
237
|
-
start
|
|
238
|
-
version
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
380
|
+
new|create|init 创建新项目
|
|
381
|
+
start|run|launch 启动 Claude Code 或 OpenCode
|
|
382
|
+
-v|--version 显示版本号
|
|
383
|
+
install|add|i 安装 skill 包
|
|
384
|
+
list|ls 列出已安装的 skill
|
|
385
|
+
update [skill] 更新 skill 包
|
|
386
|
+
distill|dist 手动触发蒸馏
|
|
387
|
+
upgrade [path] 升级 flywheel 工具,并更新项目框架
|
|
388
|
+
-h|--help 显示帮助
|
|
389
|
+
|
|
390
|
+
选项:
|
|
391
|
+
-x, --proxy <url> 使用代理
|
|
244
392
|
|
|
245
393
|
示例:
|
|
246
|
-
flywheel new my-project
|
|
247
|
-
|
|
248
|
-
flywheel
|
|
394
|
+
flywheel new my-project
|
|
395
|
+
flywheel update # 更新所有 skill
|
|
396
|
+
flywheel update req-mining # 更新指定 skill
|
|
397
|
+
flywheel upgrade # 升级工具 + 交互更新项目框架
|
|
398
|
+
flywheel upgrade ./my-project # 更新指定项目框架
|
|
399
|
+
flywheel update -x http://127.0.0.1:7890
|
|
249
400
|
`);
|
|
250
401
|
break;
|
|
251
402
|
case 'install':
|
|
252
|
-
|
|
403
|
+
case 'add':
|
|
404
|
+
case 'i':
|
|
405
|
+
runScript(resolveScript('flywheel-install'), ['add', ...restArgs]).catch((e) => process.exit(1));
|
|
253
406
|
break;
|
|
254
407
|
case 'list':
|
|
408
|
+
case 'ls':
|
|
255
409
|
runScript(resolveScript('flywheel-install'), ['list']).catch((e) => process.exit(1));
|
|
256
410
|
break;
|
|
257
411
|
case 'update':
|
|
258
|
-
runScript(resolveScript('flywheel-install'), ['update', ...
|
|
412
|
+
runScript(resolveScript('flywheel-install'), ['update', ...restArgs]).catch((e) => process.exit(1));
|
|
259
413
|
break;
|
|
260
414
|
case 'distill':
|
|
261
|
-
|
|
415
|
+
case 'dist':
|
|
416
|
+
runScript(resolveScript('auto-distill'), restArgs).catch((e) => process.exit(1));
|
|
262
417
|
break;
|
|
263
|
-
case '
|
|
264
|
-
|
|
265
|
-
cwd: process.cwd(),
|
|
266
|
-
stdio: 'inherit',
|
|
267
|
-
}).on('exit', (code) => process.exit(code));
|
|
418
|
+
case 'upgrade':
|
|
419
|
+
cmdUpgrade(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
268
420
|
break;
|
|
269
421
|
default:
|
|
270
422
|
console.error('未知命令: ' + command);
|
package/package.json
CHANGED
|
@@ -17,6 +17,14 @@ BRANCH="main"
|
|
|
17
17
|
BASE_URL="https://raw.githubusercontent.com/$REPO/$BRANCH"
|
|
18
18
|
CMD="${1:-list}"
|
|
19
19
|
TARGET="${2:-}"
|
|
20
|
+
PROXY="${FLYWHEEL_PROXY:-}"
|
|
21
|
+
|
|
22
|
+
[ -n "$PROXY" ] && CURL_PROXY="--proxy $PROXY" || CURL_PROXY=""
|
|
23
|
+
|
|
24
|
+
# ── curl helper ─────────────────────────────────────────────────
|
|
25
|
+
do_curl() {
|
|
26
|
+
curl -fsSL $CURL_PROXY "$@"
|
|
27
|
+
}
|
|
20
28
|
|
|
21
29
|
# ── list ───────────────────────────────────────────────────────
|
|
22
30
|
cmd_list() {
|
|
@@ -67,15 +75,13 @@ cmd_add() {
|
|
|
67
75
|
|
|
68
76
|
echo "安装 skill:$skill"
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
if ! curl -fsSL "$BASE_URL/skills/$skill/skill.yaml" -o /dev/null 2>/dev/null; then
|
|
78
|
+
if ! do_curl "$BASE_URL/skills/$skill/skill.yaml" -o /dev/null 2>/dev/null; then
|
|
72
79
|
echo "❌ skill '$skill' 不存在"
|
|
73
80
|
echo " 运行 list 查看可用 skill"
|
|
74
81
|
exit 1
|
|
75
82
|
fi
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
curl -fsSL "$BASE_URL/install.sh" | bash -s -- "$skill"
|
|
84
|
+
curl -fsSL $CURL_PROXY "$BASE_URL/install.sh" | bash -s -- "$skill"
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
# ── update ─────────────────────────────────────────────────────
|
|
@@ -87,29 +93,43 @@ cmd_update() {
|
|
|
87
93
|
else
|
|
88
94
|
for yaml in "$REPO_DIR/skills"/*/skill.yaml; do
|
|
89
95
|
[ -f "$yaml" ] || continue
|
|
90
|
-
|
|
96
|
+
local name=$(basename "$(dirname "$yaml")")
|
|
97
|
+
[ "$name" = "_kernel" ] && continue
|
|
98
|
+
[ "$name" = "_template" ] && continue
|
|
99
|
+
skills+=("$name")
|
|
91
100
|
done
|
|
92
101
|
fi
|
|
93
102
|
|
|
94
103
|
[ ${#skills[@]} -eq 0 ] && { echo "没有已安装的 skill"; exit 0; }
|
|
95
104
|
|
|
96
105
|
echo "━━━ 更新 Skill 包 ━━━"
|
|
106
|
+
|
|
107
|
+
local update_cmds=()
|
|
97
108
|
for skill in "${skills[@]}"; do
|
|
98
|
-
echo ""
|
|
99
|
-
echo "更新 $skill..."
|
|
100
109
|
local yaml_url="$BASE_URL/skills/$skill/skill.yaml"
|
|
101
|
-
local remote_ver
|
|
102
|
-
remote_ver=$(curl -fsSL "$yaml_url" 2>/dev/null | grep '^version:' | awk '{print $2}' || echo "?")
|
|
103
110
|
local local_ver
|
|
104
111
|
local_ver=$(grep '^version:' "$REPO_DIR/skills/$skill/skill.yaml" 2>/dev/null | awk '{print $2}' || echo "0")
|
|
105
112
|
|
|
113
|
+
local remote_ver
|
|
114
|
+
remote_ver=$(do_curl "$yaml_url" 2>/dev/null | grep '^version:' | awk '{print $2}' || echo "?")
|
|
115
|
+
|
|
106
116
|
if [ "$remote_ver" = "$local_ver" ]; then
|
|
107
117
|
echo " ✓ $skill 已是最新版 v$local_ver"
|
|
108
118
|
else
|
|
109
|
-
echo " $local_ver → $remote_ver
|
|
110
|
-
|
|
119
|
+
echo " → $skill v$local_ver → v$remote_ver"
|
|
120
|
+
update_cmds+=("$skill")
|
|
111
121
|
fi
|
|
112
122
|
done
|
|
123
|
+
|
|
124
|
+
if [ ${#update_cmds[@]} -gt 0 ]; then
|
|
125
|
+
echo ""
|
|
126
|
+
echo "开始更新 ${#update_cmds[@]} 个 skill..."
|
|
127
|
+
for skill in "${update_cmds[@]}"; do
|
|
128
|
+
echo ""
|
|
129
|
+
echo "更新 $skill..."
|
|
130
|
+
cmd_add "$skill"
|
|
131
|
+
done
|
|
132
|
+
fi
|
|
113
133
|
}
|
|
114
134
|
|
|
115
135
|
case "$CMD" in
|