@vima_tech/flywheel 1.1.1 → 1.2.1
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 +201 -59
- package/package.json +4 -2
- package/scripts/flywheel-install.sh +42 -22
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);
|
|
@@ -127,7 +126,7 @@ async function cmdNew(args) {
|
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
let agent = 'claude';
|
|
130
|
-
let skill = '
|
|
129
|
+
let skill = '';
|
|
131
130
|
|
|
132
131
|
for (let i = 1; i < args.length; i++) {
|
|
133
132
|
if (args[i] === '--agent' && args[i + 1]) {
|
|
@@ -146,7 +145,7 @@ async function cmdNew(args) {
|
|
|
146
145
|
console.log(' 项目: ' + projectName);
|
|
147
146
|
console.log(' 目录: ' + targetDir);
|
|
148
147
|
console.log(' Agent: ' + agent);
|
|
149
|
-
console.log(' Skill: ' + skill);
|
|
148
|
+
console.log(' Skill: ' + (skill || '无(后续手动添加)'));
|
|
150
149
|
console.log('');
|
|
151
150
|
|
|
152
151
|
console.log('📦 初始化内核文件...');
|
|
@@ -154,19 +153,26 @@ async function cmdNew(args) {
|
|
|
154
153
|
await initKernel(targetDir, skill);
|
|
155
154
|
console.log(' ✓ 内核安装完成');
|
|
156
155
|
|
|
156
|
+
if (!skill) {
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log(' ℹ 未指定 skill,可后续安装:');
|
|
159
|
+
console.log(' flywheel install req-mining');
|
|
160
|
+
console.log(' flywheel available # 查看可用 skill');
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
console.log('');
|
|
158
164
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
159
165
|
console.log(' ✅ 项目创建完成!');
|
|
160
166
|
console.log('');
|
|
161
167
|
console.log(' 进入目录并启动 ' + agent + ':');
|
|
162
|
-
console.log(' cd ' +
|
|
168
|
+
console.log(' cd ' + projectName + ' && flywheel start');
|
|
163
169
|
console.log('');
|
|
164
|
-
console.log(' 或手动启动:cd ' +
|
|
170
|
+
console.log(' 或手动启动:cd ' + projectName + ' && claude');
|
|
165
171
|
console.log(' 然后输入 /sfw 开始飞轮工作');
|
|
166
172
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
function cmdStart() {
|
|
170
176
|
const agent = detectAgent();
|
|
171
177
|
if (!agent) {
|
|
172
178
|
console.error('❌ 未检测到 Claude Code 或 OpenCode');
|
|
@@ -194,7 +200,130 @@ function detectAgent() {
|
|
|
194
200
|
return null;
|
|
195
201
|
}
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
function findFlywheelProjects(startPath, maxDepth = 3) {
|
|
204
|
+
const projects = [];
|
|
205
|
+
const homeDir = os.homedir();
|
|
206
|
+
const maxDepthParam = maxDepth || 3;
|
|
207
|
+
|
|
208
|
+
function scan(dir, depth) {
|
|
209
|
+
if (depth > maxDepthParam) return;
|
|
210
|
+
try {
|
|
211
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
if (!entry.isDirectory()) continue;
|
|
214
|
+
if (entry.name === '.' || entry.name === '..') continue;
|
|
215
|
+
if (entry.name === 'node_modules') continue;
|
|
216
|
+
|
|
217
|
+
const fullPath = path.join(dir, entry.name);
|
|
218
|
+
const flywheelMarker = path.join(fullPath, '.flywheel');
|
|
219
|
+
|
|
220
|
+
if (fs.existsSync(flywheelMarker)) {
|
|
221
|
+
projects.push(fullPath);
|
|
222
|
+
} else if (fullPath.startsWith(homeDir)) {
|
|
223
|
+
scan(fullPath, depth + 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
scan(startPath, 0);
|
|
230
|
+
return projects;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function updateProjectFramework(projectPath) {
|
|
234
|
+
const baseUrl = 'https://raw.githubusercontent.com/renmengkai/flywheel/main';
|
|
235
|
+
|
|
236
|
+
const kernelFiles = [
|
|
237
|
+
'CLAUDE.md',
|
|
238
|
+
'.claude/commands/flywheel.md', '.claude/commands/skill.md',
|
|
239
|
+
'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
|
|
240
|
+
'scripts/bridge-to-coder.sh', 'scripts/flywheel-install.sh',
|
|
241
|
+
'skills/_kernel/distillation.md',
|
|
242
|
+
'skills/_template/skill.yaml', 'skills/_template/domain.md',
|
|
243
|
+
'skills/_template/feedback-questions.sh',
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
console.log(' 更新框架文件...');
|
|
247
|
+
for (const f of kernelFiles) {
|
|
248
|
+
const dest = path.join(projectPath, f);
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
251
|
+
await downloadFile(baseUrl + '/' + f, dest);
|
|
252
|
+
console.log(' ✓ ' + f);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.log(' ✗ ' + f + ' (失败)');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function cmdUpgrade(args) {
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
262
|
+
console.log(' Flywheel 升级');
|
|
263
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
264
|
+
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log('📦 升级 flywheel CLI...');
|
|
267
|
+
const npmArgs = ['install', '-g', NPM_PKG];
|
|
268
|
+
if (proxy) npmArgs.push('--proxy', proxy);
|
|
269
|
+
const npmSpawn = spawn('npm', npmArgs, { stdio: 'inherit' });
|
|
270
|
+
await new Promise((resolve) => npmSpawn.on('exit', resolve));
|
|
271
|
+
|
|
272
|
+
if (args.length > 0) {
|
|
273
|
+
const targetPath = path.resolve(args[0]);
|
|
274
|
+
if (fs.existsSync(path.join(targetPath, '.flywheel'))) {
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log('📦 更新项目框架: ' + targetPath);
|
|
277
|
+
await updateProjectFramework(targetPath);
|
|
278
|
+
} else {
|
|
279
|
+
console.error('❌ 不是有效的 flywheel 项目: ' + targetPath);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
console.log('');
|
|
283
|
+
console.log('🔍 扫描 flywheel 项目...');
|
|
284
|
+
const projects = findFlywheelProjects(os.homedir());
|
|
285
|
+
|
|
286
|
+
if (projects.length === 0) {
|
|
287
|
+
console.log(' 未找到 flywheel 项目');
|
|
288
|
+
} else {
|
|
289
|
+
console.log(' 找到 ' + projects.length + ' 个项目:');
|
|
290
|
+
projects.forEach((p, i) => console.log(' ' + (i + 1) + '. ' + p));
|
|
291
|
+
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(' 是否更新所有项目框架?(y/N)');
|
|
294
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
295
|
+
const answer = await new Promise((resolve) => rl.question('', resolve));
|
|
296
|
+
rl.close();
|
|
297
|
+
|
|
298
|
+
if (answer.toLowerCase() === 'y') {
|
|
299
|
+
for (const proj of projects) {
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log('📦 更新: ' + proj);
|
|
302
|
+
await updateProjectFramework(proj);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log('');
|
|
309
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
310
|
+
console.log(' ✅ 升级完成');
|
|
311
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let proxy = '';
|
|
315
|
+
const rawArgs = process.argv.slice(2);
|
|
316
|
+
const filteredArgs = [];
|
|
317
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
318
|
+
if (rawArgs[i] === '-x' || rawArgs[i] === '--proxy') {
|
|
319
|
+
proxy = rawArgs[i + 1];
|
|
320
|
+
i++;
|
|
321
|
+
} else {
|
|
322
|
+
filteredArgs.push(rawArgs[i]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const [command, ...restArgs] = filteredArgs;
|
|
198
327
|
|
|
199
328
|
if (!command) {
|
|
200
329
|
console.log(`
|
|
@@ -208,15 +337,21 @@ Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
|
208
337
|
-v|--version 显示版本号
|
|
209
338
|
install|add|i 安装 skill 包
|
|
210
339
|
list|ls 列出已安装的 skill
|
|
211
|
-
update
|
|
340
|
+
update [skill] 更新 skill 包
|
|
212
341
|
distill|dist 手动触发蒸馏
|
|
213
|
-
|
|
342
|
+
upgrade [path] 升级 flywheel 工具,并更新项目框架
|
|
343
|
+
-h|--help 显示帮助
|
|
344
|
+
|
|
345
|
+
选项:
|
|
346
|
+
-x, --proxy <url> 使用代理
|
|
214
347
|
|
|
215
348
|
示例:
|
|
216
349
|
flywheel new my-project
|
|
217
|
-
|
|
218
|
-
flywheel
|
|
219
|
-
flywheel
|
|
350
|
+
flywheel update # 更新所有 skill
|
|
351
|
+
flywheel update req-mining # 更新指定 skill
|
|
352
|
+
flywheel upgrade # 升级工具 + 交互更新项目框架
|
|
353
|
+
flywheel upgrade ./my-project # 更新指定项目框架
|
|
354
|
+
flywheel update -x http://127.0.0.1:7890
|
|
220
355
|
`);
|
|
221
356
|
process.exit(0);
|
|
222
357
|
}
|
|
@@ -226,12 +361,12 @@ switch (command) {
|
|
|
226
361
|
case '--version':
|
|
227
362
|
case '-V':
|
|
228
363
|
case 'version':
|
|
229
|
-
console.log('@vima_tech/flywheel v1.1
|
|
364
|
+
console.log('@vima_tech/flywheel v1.2.1');
|
|
230
365
|
break;
|
|
231
366
|
case 'new':
|
|
232
367
|
case 'create':
|
|
233
368
|
case 'init':
|
|
234
|
-
cmdNew(
|
|
369
|
+
cmdNew(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
235
370
|
break;
|
|
236
371
|
case 'start':
|
|
237
372
|
case 'run':
|
|
@@ -249,45 +384,52 @@ Flywheel CLI — 自成长 AI Agent 飞轮框架
|
|
|
249
384
|
用法: flywheel <command> [args]
|
|
250
385
|
|
|
251
386
|
可用命令:
|
|
252
|
-
new
|
|
253
|
-
start
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
387
|
+
new|create|init 创建新项目
|
|
388
|
+
start|run|launch 启动 Claude Code 或 OpenCode
|
|
389
|
+
-v|--version 显示版本号
|
|
390
|
+
install|add|i 安装 skill 包
|
|
391
|
+
list|ls 列出已安装的 skill
|
|
392
|
+
update [skill] 更新 skill 包
|
|
393
|
+
distill|dist 手动触发蒸馏
|
|
394
|
+
upgrade [path] 升级 flywheel 工具,并更新项目框架
|
|
395
|
+
-h|--help 显示帮助
|
|
396
|
+
|
|
397
|
+
选项:
|
|
398
|
+
-x, --proxy <url> 使用代理
|
|
261
399
|
|
|
262
400
|
示例:
|
|
263
401
|
flywheel new my-project
|
|
264
|
-
|
|
265
|
-
flywheel
|
|
402
|
+
flywheel update # 更新所有 skill
|
|
403
|
+
flywheel update req-mining # 更新指定 skill
|
|
404
|
+
flywheel upgrade # 升级工具 + 交互更新项目框架
|
|
405
|
+
flywheel upgrade ./my-project # 更新指定项目框架
|
|
406
|
+
flywheel update -x http://127.0.0.1:7890
|
|
407
|
+
flywheel available # 查看可安装的 skill
|
|
266
408
|
`);
|
|
267
409
|
break;
|
|
268
410
|
case 'install':
|
|
269
411
|
case 'add':
|
|
270
412
|
case 'i':
|
|
271
|
-
runScript(resolveScript('flywheel-install'), ['add', ...
|
|
413
|
+
runScript(resolveScript('flywheel-install'), ['add', ...restArgs]).catch((e) => process.exit(1));
|
|
414
|
+
break;
|
|
415
|
+
case 'available':
|
|
416
|
+
case 'avail':
|
|
417
|
+
case 'search':
|
|
418
|
+
runScript(resolveScript('flywheel-install'), ['available']).catch((e) => process.exit(1));
|
|
272
419
|
break;
|
|
273
420
|
case 'list':
|
|
274
421
|
case 'ls':
|
|
275
422
|
runScript(resolveScript('flywheel-install'), ['list']).catch((e) => process.exit(1));
|
|
276
423
|
break;
|
|
277
424
|
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
|
-
}
|
|
425
|
+
runScript(resolveScript('flywheel-install'), ['update', ...restArgs]).catch((e) => process.exit(1));
|
|
287
426
|
break;
|
|
288
427
|
case 'distill':
|
|
289
428
|
case 'dist':
|
|
290
|
-
runScript(resolveScript('auto-distill'),
|
|
429
|
+
runScript(resolveScript('auto-distill'), restArgs).catch((e) => process.exit(1));
|
|
430
|
+
break;
|
|
431
|
+
case 'upgrade':
|
|
432
|
+
cmdUpgrade(restArgs).catch((e) => { console.error(e); process.exit(1); });
|
|
291
433
|
break;
|
|
292
434
|
default:
|
|
293
435
|
console.error('未知命令: ' + command);
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vima_tech/flywheel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "自成长 AI Agent 飞轮框架 — 让任何需要积累迭代的 AI 工作流都能自我进化",
|
|
5
5
|
"main": "bin/flywheel.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"flywheel": "./bin/flywheel.js"
|
|
7
|
+
"flywheel": "./bin/flywheel.js",
|
|
8
|
+
"fw": "./bin/flywheel.js",
|
|
9
|
+
"fly": "./bin/flywheel.js"
|
|
8
10
|
},
|
|
9
11
|
"files": [
|
|
10
12
|
"bin/",
|
|
@@ -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() {
|
|
@@ -45,16 +53,15 @@ cmd_available() {
|
|
|
45
53
|
echo "━━━ 可安装 Skill 包 ━━━"
|
|
46
54
|
local available_file="$SCRIPT_DIR/available.json"
|
|
47
55
|
if [ -f "$available_file" ]; then
|
|
48
|
-
python3 -
|
|
49
|
-
import json
|
|
50
|
-
with open(
|
|
56
|
+
python3 -c "
|
|
57
|
+
import json
|
|
58
|
+
with open('$available_file') as f:
|
|
51
59
|
data = json.load(f)
|
|
52
60
|
for s in data.get('skills', []):
|
|
53
|
-
print(f" - {s['name']} v{s['version']} - {s['description']}")
|
|
54
|
-
|
|
55
|
-
"$available_file"
|
|
61
|
+
print(f\" - {s['name']} v{s['version']} - {s['description']}\")
|
|
62
|
+
"
|
|
56
63
|
else
|
|
57
|
-
echo " (无可用 skill
|
|
64
|
+
echo " (无可用 skill)"
|
|
58
65
|
fi
|
|
59
66
|
echo ""
|
|
60
67
|
echo "安装 skill:flywheel install <skill-name>"
|
|
@@ -67,15 +74,13 @@ cmd_add() {
|
|
|
67
74
|
|
|
68
75
|
echo "安装 skill:$skill"
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
if ! curl -fsSL "$BASE_URL/skills/$skill/skill.yaml" -o /dev/null 2>/dev/null; then
|
|
77
|
+
if ! do_curl "$BASE_URL/skills/$skill/skill.yaml" -o /dev/null 2>/dev/null; then
|
|
72
78
|
echo "❌ skill '$skill' 不存在"
|
|
73
79
|
echo " 运行 list 查看可用 skill"
|
|
74
80
|
exit 1
|
|
75
81
|
fi
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
curl -fsSL "$BASE_URL/install.sh" | bash -s -- "$skill"
|
|
83
|
+
curl -fsSL $CURL_PROXY "$BASE_URL/install.sh" | bash -s -- "$skill"
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
# ── update ─────────────────────────────────────────────────────
|
|
@@ -87,37 +92,52 @@ cmd_update() {
|
|
|
87
92
|
else
|
|
88
93
|
for yaml in "$REPO_DIR/skills"/*/skill.yaml; do
|
|
89
94
|
[ -f "$yaml" ] || continue
|
|
90
|
-
|
|
95
|
+
local name=$(basename "$(dirname "$yaml")")
|
|
96
|
+
[ "$name" = "_kernel" ] && continue
|
|
97
|
+
[ "$name" = "_template" ] && continue
|
|
98
|
+
skills+=("$name")
|
|
91
99
|
done
|
|
92
100
|
fi
|
|
93
101
|
|
|
94
102
|
[ ${#skills[@]} -eq 0 ] && { echo "没有已安装的 skill"; exit 0; }
|
|
95
103
|
|
|
96
104
|
echo "━━━ 更新 Skill 包 ━━━"
|
|
105
|
+
|
|
106
|
+
local update_cmds=()
|
|
97
107
|
for skill in "${skills[@]}"; do
|
|
98
|
-
echo ""
|
|
99
|
-
echo "更新 $skill..."
|
|
100
108
|
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
109
|
local local_ver
|
|
104
110
|
local_ver=$(grep '^version:' "$REPO_DIR/skills/$skill/skill.yaml" 2>/dev/null | awk '{print $2}' || echo "0")
|
|
105
111
|
|
|
112
|
+
local remote_ver
|
|
113
|
+
remote_ver=$(do_curl "$yaml_url" 2>/dev/null | grep '^version:' | awk '{print $2}' || echo "?")
|
|
114
|
+
|
|
106
115
|
if [ "$remote_ver" = "$local_ver" ]; then
|
|
107
116
|
echo " ✓ $skill 已是最新版 v$local_ver"
|
|
108
117
|
else
|
|
109
|
-
echo " $local_ver → $remote_ver
|
|
110
|
-
|
|
118
|
+
echo " → $skill v$local_ver → v$remote_ver"
|
|
119
|
+
update_cmds+=("$skill")
|
|
111
120
|
fi
|
|
112
121
|
done
|
|
122
|
+
|
|
123
|
+
if [ ${#update_cmds[@]} -gt 0 ]; then
|
|
124
|
+
echo ""
|
|
125
|
+
echo "开始更新 ${#update_cmds[@]} 个 skill..."
|
|
126
|
+
for skill in "${update_cmds[@]}"; do
|
|
127
|
+
echo ""
|
|
128
|
+
echo "更新 $skill..."
|
|
129
|
+
cmd_add "$skill"
|
|
130
|
+
done
|
|
131
|
+
fi
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
case "$CMD" in
|
|
116
|
-
list)
|
|
117
|
-
add)
|
|
118
|
-
update)
|
|
135
|
+
list) cmd_list ;;
|
|
136
|
+
add) cmd_add "$TARGET" ;;
|
|
137
|
+
update) cmd_update ;;
|
|
138
|
+
available) cmd_available ;;
|
|
119
139
|
*)
|
|
120
|
-
echo "用法:$0 list | add <skill> | update [skill]"
|
|
140
|
+
echo "用法:$0 list | add <skill> | update [skill] | available"
|
|
121
141
|
exit 1
|
|
122
142
|
;;
|
|
123
143
|
esac
|