@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 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 readline = require('readline');
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: { ...process.env, FLYWHEEL_DIR: process.cwd() },
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
- spawn('curl', ['-fsSL', url, '-o', destPath], { stdio: 'ignore' })
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 dirName = projectName;
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 = 'req-mining';
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 ' + dirName + ' && flywheel start');
168
+ console.log(' cd ' + projectName + ' && flywheel start');
163
169
  console.log('');
164
- console.log(' 或手动启动:cd ' + dirName + ' && claude');
170
+ console.log(' 或手动启动:cd ' + projectName + ' && claude');
165
171
  console.log(' 然后输入 /sfw 开始飞轮工作');
166
172
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
167
173
  }
168
174
 
169
- async function cmdStart() {
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
- const [, , command, ...args] = process.argv;
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|upgrade 更新 flywheel 或 skill
340
+ update [skill] 更新 skill
212
341
  distill|dist 手动触发蒸馏
213
- -h|--help|h 显示帮助
342
+ upgrade [path] 升级 flywheel 工具,并更新项目框架
343
+ -h|--help 显示帮助
344
+
345
+ 选项:
346
+ -x, --proxy <url> 使用代理
214
347
 
215
348
  示例:
216
349
  flywheel new my-project
217
- cd my-project && flywheel start
218
- flywheel install req-mining
219
- flywheel update flywheel # 更新 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.0');
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(args).catch((e) => { console.error(e); process.exit(1); });
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 <name> 创建新项目 (new|create|init)
253
- start 启动 Claude Code 或 OpenCode (start|run|launch)
254
- version 显示版本号 (-v|--version)
255
- update 更新 flywheel 到最新版本
256
- install <skill> 安装 skill 包 (install|add|i)
257
- list 列出已安装的 skill (list|ls)
258
- update skill 更新 skill 包 (update|upgrade)
259
- distill [project] 手动触发蒸馏 (distill|dist)
260
- help 显示帮助 (-h|--help)
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
- cd my-project && flywheel start
265
- flywheel install req-mining
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', ...args]).catch((e) => process.exit(1));
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
- case 'upgrade':
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'), args).catch((e) => process.exit(1));
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.1.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 - << 'PYEOF'
49
- import json, sys
50
- with open(sys.argv[1]) as f:
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
- PYEOF
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
- # 检查 skill 是否存在
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
- # 使用主安装器的 install_skill 逻辑
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
- skills+=("$(basename "$(dirname "$yaml")")")
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
- cmd_add "$skill"
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) cmd_list ;;
117
- add) cmd_add "$TARGET" ;;
118
- update) cmd_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