@vima_tech/flywheel 1.1.1 → 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 +186 -57
- 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(`
|
|
@@ -208,15 +330,21 @@ Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
|
208
330
|
-v|--version 显示版本号
|
|
209
331
|
install|add|i 安装 skill 包
|
|
210
332
|
list|ls 列出已安装的 skill
|
|
211
|
-
update
|
|
333
|
+
update [skill] 更新 skill 包
|
|
212
334
|
distill|dist 手动触发蒸馏
|
|
213
|
-
|
|
335
|
+
upgrade [path] 升级 flywheel 工具,并更新项目框架
|
|
336
|
+
-h|--help 显示帮助
|
|
337
|
+
|
|
338
|
+
选项:
|
|
339
|
+
-x, --proxy <url> 使用代理
|
|
214
340
|
|
|
215
341
|
示例:
|
|
216
342
|
flywheel new my-project
|
|
217
|
-
|
|
218
|
-
flywheel
|
|
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
|
}
|
|
@@ -226,12 +354,12 @@ switch (command) {
|
|
|
226
354
|
case '--version':
|
|
227
355
|
case '-V':
|
|
228
356
|
case 'version':
|
|
229
|
-
console.log('@vima_tech/flywheel v1.
|
|
357
|
+
console.log('@vima_tech/flywheel v1.2.0');
|
|
230
358
|
break;
|
|
231
359
|
case 'new':
|
|
232
360
|
case 'create':
|
|
233
361
|
case 'init':
|
|
234
|
-
cmdNew(
|
|
362
|
+
cmdNew(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
235
363
|
break;
|
|
236
364
|
case 'start':
|
|
237
365
|
case 'run':
|
|
@@ -249,45 +377,46 @@ Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
|
249
377
|
用法: flywheel <command> [args]
|
|
250
378
|
|
|
251
379
|
可用命令:
|
|
252
|
-
new
|
|
253
|
-
start
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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> 使用代理
|
|
261
392
|
|
|
262
393
|
示例:
|
|
263
394
|
flywheel new my-project
|
|
264
|
-
|
|
265
|
-
flywheel
|
|
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
|
|
266
400
|
`);
|
|
267
401
|
break;
|
|
268
402
|
case 'install':
|
|
269
403
|
case 'add':
|
|
270
404
|
case 'i':
|
|
271
|
-
runScript(resolveScript('flywheel-install'), ['add', ...
|
|
405
|
+
runScript(resolveScript('flywheel-install'), ['add', ...restArgs]).catch((e) => process.exit(1));
|
|
272
406
|
break;
|
|
273
407
|
case 'list':
|
|
274
408
|
case 'ls':
|
|
275
409
|
runScript(resolveScript('flywheel-install'), ['list']).catch((e) => process.exit(1));
|
|
276
410
|
break;
|
|
277
411
|
case 'update':
|
|
278
|
-
|
|
279
|
-
if (args[0] === 'flywheel' || args[0] === 'self') {
|
|
280
|
-
spawn('npm', ['install', '-g', '@vima_tech/flywheel'], {
|
|
281
|
-
cwd: process.cwd(),
|
|
282
|
-
stdio: 'inherit',
|
|
283
|
-
}).on('exit', (code) => process.exit(code));
|
|
284
|
-
} else {
|
|
285
|
-
runScript(resolveScript('flywheel-install'), ['update', ...args]).catch((e) => process.exit(1));
|
|
286
|
-
}
|
|
412
|
+
runScript(resolveScript('flywheel-install'), ['update', ...restArgs]).catch((e) => process.exit(1));
|
|
287
413
|
break;
|
|
288
414
|
case 'distill':
|
|
289
415
|
case 'dist':
|
|
290
|
-
runScript(resolveScript('auto-distill'),
|
|
416
|
+
runScript(resolveScript('auto-distill'), restArgs).catch((e) => process.exit(1));
|
|
417
|
+
break;
|
|
418
|
+
case 'upgrade':
|
|
419
|
+
cmdUpgrade(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
291
420
|
break;
|
|
292
421
|
default:
|
|
293
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
|