openclawsetup 2.0.3 → 2.1.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 (2) hide show
  1. package/bin/cli.mjs +204 -330
  2. package/package.json +2 -2
package/bin/cli.mjs CHANGED
@@ -1,58 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * OpenClaw 智能安装向导
3
+ * OpenClaw 安装向导
4
4
  *
5
- * 调用官方 openclaw onboard 交互界面,自动完成推荐配置
6
- * 用户可以看到完整的原版安装过程,但无需手动选择
5
+ * 调用官方 openclaw onboard,在关键步骤前提供中文指引
6
+ * 用户看到完整的原版安装过程,我们只提供选择建议
7
7
  *
8
8
  * 用法:
9
- * npx openclawsetup # 智能安装(自动选择推荐配置)
10
- * npx openclawsetup --manual # 手动模式(完全交互)
9
+ * npx openclawsetup # 带中文指引的安装
11
10
  * npx openclawsetup --update # 更新已安装的 OpenClaw
12
11
  */
13
12
 
14
- import { execSync, spawn } from 'child_process';
15
- import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync, accessSync, constants as fsConstants } from 'fs';
13
+ import { execSync, spawnSync } from 'child_process';
14
+ import { existsSync, accessSync, constants as fsConstants, rmSync } from 'fs';
16
15
  import { homedir, platform } from 'os';
17
16
  import { join } from 'path';
18
17
  import { createInterface } from 'readline';
19
18
 
20
- // ============ 配置 ============
21
-
22
- // 自动应答规则:识别关键词 -> 自动输入
23
- // 顺序很重要:先匹配的先执行
24
- const AUTO_RESPONSES = [
25
- // 安全确认
26
- { match: /continue\?|accept|agree|proceed|\(y\/n\)|\[y\/N\]|\[Y\/n\]/i, response: 'y', delay: 300, desc: '安全确认' },
27
-
28
- // Setup 模式选择 - QuickStart
29
- { match: /quick\s*start|setup\s*mode|choose.*mode/i, response: '1', delay: 500, desc: '选择 QuickStart' },
30
-
31
- // Model Provider - 跳过(用户后续用 openclawapi 配置)
32
- { match: /provider|anthropic|openai|select.*model|choose.*model/i, response: 's', delay: 500, desc: '跳过模型配置' },
33
-
34
- // API Key - 跳过
35
- { match: /api\s*key|enter.*key|paste.*key|setup.*token/i, response: '', delay: 300, desc: '跳过 API Key', skip: true },
36
-
37
- // Channel 配置 - 跳过
38
- { match: /channel|telegram|discord|whatsapp|slack|skip.*channel/i, response: 's', delay: 500, desc: '跳过渠道配置' },
39
-
40
- // Skills - 跳过
41
- { match: /skill|install.*skill|skip.*skill/i, response: 's', delay: 500, desc: '跳过 Skills' },
42
-
43
- // Daemon/Service - 安装
44
- { match: /daemon|service|background|auto.*start|launchd|systemd/i, response: 'y', delay: 300, desc: '安装后台服务' },
45
-
46
- // UI 选择 - Web Dashboard
47
- { match: /interface|dashboard|tui|web.*ui|control.*ui/i, response: '1', delay: 500, desc: '选择 Web Dashboard' },
48
-
49
- // 名称/称呼 - 使用默认
50
- { match: /name|call\s*you|address/i, response: '', delay: 300, desc: '使用默认名称' },
51
-
52
- // 通用确认
53
- { match: /press\s*enter|continue|next|\[enter\]/i, response: '', delay: 200, desc: '继续' },
54
- ];
55
-
56
19
  // ============ 工具函数 ============
57
20
 
58
21
  const colors = {
@@ -62,7 +25,7 @@ const colors = {
62
25
  cyan: (s) => `\x1b[36m${s}\x1b[0m`,
63
26
  bold: (s) => `\x1b[1m${s}\x1b[0m`,
64
27
  gray: (s) => `\x1b[90m${s}\x1b[0m`,
65
- dim: (s) => `\x1b[2m${s}\x1b[0m`,
28
+ bgYellow: (s) => `\x1b[43m\x1b[30m${s}\x1b[0m`,
66
29
  };
67
30
 
68
31
  const log = {
@@ -71,30 +34,9 @@ const log = {
71
34
  warn: (msg) => console.log(colors.yellow(`⚠ ${msg}`)),
72
35
  error: (msg) => console.log(colors.red(`✗ ${msg}`)),
73
36
  hint: (msg) => console.log(colors.gray(` 提示: ${msg}`)),
74
- auto: (msg) => console.log(colors.dim(` [自动] ${msg}`)),
75
- };
76
-
77
- const ERROR_CODES = {
78
- NODE_VERSION: { code: 1, message: 'Node.js 版本过低' },
79
- NPM_INSTALL_FAILED: { code: 3, message: 'npm 安装失败' },
80
- ONBOARD_FAILED: { code: 4, message: 'onboard 执行失败' },
81
- NOT_TTY: { code: 5, message: '需要交互式终端' },
37
+ guide: (msg) => console.log(colors.bgYellow(` 📖 ${msg} `)),
82
38
  };
83
39
 
84
- function exitWithError(errorType, details = '', solutions = []) {
85
- const err = ERROR_CODES[errorType] || { code: 99, message: '未知错误' };
86
- console.log(colors.bold(colors.red('\n========================================')));
87
- console.log(colors.bold(colors.red(`❌ 错误: ${err.message}`)));
88
- console.log(colors.bold(colors.red('========================================')));
89
- if (details) console.log(colors.gray(` ${details}`));
90
- if (solutions.length > 0) {
91
- console.log(colors.cyan('\n解决方案:'));
92
- solutions.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
93
- }
94
- console.log(colors.gray(`\n错误码: ${err.code}`));
95
- process.exit(err.code);
96
- }
97
-
98
40
  function safeExec(cmd, options = {}) {
99
41
  try {
100
42
  const output = execSync(cmd, { encoding: 'utf8', stdio: 'pipe', ...options });
@@ -104,96 +46,52 @@ function safeExec(cmd, options = {}) {
104
46
  }
105
47
  }
106
48
 
107
- function checkNodeVersion() {
108
- const version = process.version;
109
- const major = parseInt(version.slice(1).split('.')[0], 10);
110
- if (major < 18) {
111
- exitWithError('NODE_VERSION', `当前版本: ${version},需要 Node.js 18+`, [
112
- '升级 Node.js: https://nodejs.org/',
113
- '使用 nvm: nvm install 22',
114
- ]);
115
- }
116
- return true;
117
- }
118
-
119
49
  function parseArgs() {
120
50
  const args = process.argv.slice(2);
121
51
  return {
122
- manual: args.includes('--manual'),
123
52
  update: args.includes('--update'),
124
53
  reinstall: args.includes('--reinstall'),
125
54
  help: args.includes('--help') || args.includes('-h'),
126
- skipModel: !args.includes('--with-model'), // 默认跳过模型配置
127
55
  };
128
56
  }
129
57
 
130
58
  function showHelp() {
131
59
  console.log(`
132
- ${colors.bold('OpenClaw 智能安装向导')}
60
+ ${colors.bold('OpenClaw 安装向导')}
133
61
 
134
62
  ${colors.cyan('用法:')}
135
- npx openclawsetup 智能安装(自动选择推荐配置)
136
- npx openclawsetup --manual 手动模式(完全交互,自己选择)
63
+ npx openclawsetup 带中文指引的安装
137
64
  npx openclawsetup --update 更新已安装的 OpenClaw
138
65
  npx openclawsetup --reinstall 卸载后重新安装
139
66
 
140
- ${colors.cyan('选项:')}
141
- --manual 完全手动模式,不自动选择
142
- --with-model 安装时配置模型(默认跳过,后续用 openclawapi 配置)
143
- --update 检查并更新
144
- --reinstall 重新安装(清除配置)
145
- --help, -h 显示帮助
146
-
147
- ${colors.cyan('智能安装会自动:')}
148
- ✓ 选择 QuickStart 模式
149
- ✓ 跳过模型配置(后续用 npx openclawapi 配置)
150
- ✓ 跳过渠道配置(后续用 npx openclawdc 或 npx openclaw-chat-cn@latest feishu 配置)
151
- ✓ 安装后台服务(开机自启)
152
- ✓ 选择 Web Dashboard
67
+ ${colors.cyan('说明:')}
68
+ 本工具会调用官方 openclaw onboard 命令
69
+ 在关键步骤前提供中文指引,告诉你该如何选择
153
70
 
154
71
  ${colors.cyan('安装后配置模型:')}
155
72
  npx openclawapi@latest preset-claude
156
73
  `);
157
74
  }
158
75
 
159
- // ============ 检测已安装 ============
160
-
161
- function detectExistingInstall() {
162
- const home = homedir();
163
- const openclawDir = join(home, '.openclaw');
164
- const clawdbotDir = join(home, '.clawdbot');
165
-
166
- let result = { installed: false };
76
+ // ============ 环境检测 ============
167
77
 
168
- if (existsSync(openclawDir) || existsSync(clawdbotDir)) {
169
- result = {
170
- installed: true,
171
- configDir: existsSync(openclawDir) ? openclawDir : clawdbotDir,
172
- name: existsSync(openclawDir) ? 'openclaw' : 'clawdbot',
173
- };
174
- } else {
175
- const openclawResult = safeExec('openclaw --version');
176
- if (openclawResult.ok) {
177
- result = { installed: true, name: 'openclaw', version: openclawResult.output };
178
- } else {
179
- const clawdbotResult = safeExec('clawdbot --version');
180
- if (clawdbotResult.ok) {
181
- result = { installed: true, name: 'clawdbot', version: clawdbotResult.output };
182
- }
183
- }
78
+ function checkNodeVersion() {
79
+ const version = process.version;
80
+ const major = parseInt(version.slice(1).split('.')[0], 10);
81
+ if (major < 18) {
82
+ log.error(`Node.js 版本过低: ${version},需要 18+`);
83
+ console.log(colors.cyan('\n解决方案:'));
84
+ console.log(' 1. 访问 https://nodejs.org/ 下载最新版本');
85
+ console.log(' 2. 或使用 nvm: nvm install 22');
86
+ process.exit(1);
184
87
  }
185
-
186
- return result;
88
+ return true;
187
89
  }
188
90
 
189
- // ============ 安装 OpenClaw ============
190
-
191
- // 检测是否需要 sudo
192
91
  function needsSudo() {
193
92
  const os = platform();
194
93
  if (os === 'win32' || os === 'darwin') return false;
195
94
 
196
- // Linux: 检查 /usr/lib/node_modules 是否可写
197
95
  try {
198
96
  const testDir = '/usr/lib/node_modules';
199
97
  if (existsSync(testDir)) {
@@ -206,232 +104,208 @@ function needsSudo() {
206
104
  return true;
207
105
  }
208
106
 
209
- // 获取 npm 安装命令
210
- function getNpmInstallCmd(pkg) {
211
- const useSudo = needsSudo();
212
- if (useSudo) {
213
- return { cmd: 'sudo', args: ['npm', 'install', '-g', pkg] };
107
+ function detectExistingInstall() {
108
+ const home = homedir();
109
+ const openclawDir = join(home, '.openclaw');
110
+ const clawdbotDir = join(home, '.clawdbot');
111
+
112
+ if (existsSync(openclawDir)) {
113
+ return { installed: true, configDir: openclawDir, name: 'openclaw' };
114
+ }
115
+ if (existsSync(clawdbotDir)) {
116
+ return { installed: true, configDir: clawdbotDir, name: 'clawdbot' };
117
+ }
118
+
119
+ const openclawResult = safeExec('openclaw --version');
120
+ if (openclawResult.ok) {
121
+ return { installed: true, name: 'openclaw', version: openclawResult.output };
214
122
  }
215
- return { cmd: 'npm', args: ['install', '-g', pkg] };
123
+
124
+ const clawdbotResult = safeExec('clawdbot --version');
125
+ if (clawdbotResult.ok) {
126
+ return { installed: true, name: 'clawdbot', version: clawdbotResult.output };
127
+ }
128
+
129
+ return { installed: false };
216
130
  }
217
131
 
218
- async function installOpenClaw() {
219
- log.info('\n[1/2] 安装 OpenClaw CLI...\n');
132
+ // ============ 安装指引 ============
133
+
134
+ function showInstallGuide() {
135
+ console.log(colors.bold(colors.cyan('\n' + '='.repeat(60))));
136
+ console.log(colors.bold(colors.cyan(' 📖 OpenClaw 安装指引')));
137
+ console.log(colors.bold(colors.cyan('='.repeat(60))));
138
+
139
+ console.log(colors.yellow('\n接下来会运行官方 openclaw onboard 命令'));
140
+ console.log(colors.yellow('请按照以下指引选择:\n'));
141
+
142
+ console.log(colors.cyan('┌─────────────────────────────────────────────────────────┐'));
143
+ console.log(colors.cyan('│') + colors.bold(' 步骤 1: 安全确认') + colors.cyan(' │'));
144
+ console.log(colors.cyan('│') + ' 看到 "Do you want to continue?" 时 ' + colors.cyan('│'));
145
+ console.log(colors.cyan('│') + colors.green(' → 输入 y 然后回车') + ' ' + colors.cyan('│'));
146
+ console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
147
+ console.log(colors.cyan('│') + colors.bold(' 步骤 2: Setup 模式') + colors.cyan(' │'));
148
+ console.log(colors.cyan('│') + ' 看到 "Quick Start" 和 "Advanced" 选项时 ' + colors.cyan('│'));
149
+ console.log(colors.cyan('│') + colors.green(' → 选择 Quick Start(通常是第 1 个)') + ' ' + colors.cyan('│'));
150
+ console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
151
+ console.log(colors.cyan('│') + colors.bold(' 步骤 3: Model Provider') + colors.cyan(' │'));
152
+ console.log(colors.cyan('│') + ' 看到选择 AI 模型提供商时 ' + colors.cyan('│'));
153
+ console.log(colors.cyan('│') + colors.green(' → 选择 Skip 或按 s 跳过') + ' ' + colors.cyan('│'));
154
+ console.log(colors.cyan('│') + colors.gray(' (后续用 npx openclawapi 单独配置更方便)') + ' ' + colors.cyan('│'));
155
+ console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
156
+ console.log(colors.cyan('│') + colors.bold(' 步骤 4: Channel 配置') + colors.cyan(' │'));
157
+ console.log(colors.cyan('│') + ' 看到选择聊天渠道(Telegram/Discord 等)时 ' + colors.cyan('│'));
158
+ console.log(colors.cyan('│') + colors.green(' → 选择 Skip 或按 s 跳过') + ' ' + colors.cyan('│'));
159
+ console.log(colors.cyan('│') + colors.gray(' (后续用 npx openclawdc 等单独配置)') + ' ' + colors.cyan('│'));
160
+ console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
161
+ console.log(colors.cyan('│') + colors.bold(' 步骤 5: Daemon/Service') + colors.cyan(' │'));
162
+ console.log(colors.cyan('│') + ' 看到是否安装后台服务时 ' + colors.cyan('│'));
163
+ console.log(colors.cyan('│') + colors.green(' → 输入 y 确认安装(开机自启)') + ' ' + colors.cyan('│'));
164
+ console.log(colors.cyan('└─────────────────────────────────────────────────────────┘'));
165
+
166
+ console.log(colors.gray('\n其他选项可以直接回车使用默认值'));
167
+ console.log(colors.gray('如果不确定,选择 Skip 或直接回车通常是安全的\n'));
168
+ }
169
+
170
+ function waitForEnter(message) {
171
+ return new Promise((resolve) => {
172
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
173
+ rl.question(colors.yellow(message), () => {
174
+ rl.close();
175
+ resolve();
176
+ });
177
+ });
178
+ }
220
179
 
180
+ // ============ 安装 OpenClaw ============
181
+
182
+ async function installOpenClaw() {
221
183
  const useSudo = needsSudo();
184
+
185
+ console.log(colors.bold(colors.cyan('\n[1/2] 安装 OpenClaw CLI\n')));
186
+
222
187
  if (useSudo) {
223
- log.hint('检测到需要 sudo 权限,可能需要输入密码\n');
188
+ log.hint('Linux 系统需要 sudo 权限安装全局包');
189
+ console.log(colors.yellow('\n请运行以下命令:'));
190
+ console.log(colors.green(' sudo npm install -g openclaw@latest\n'));
191
+
192
+ await waitForEnter('安装完成后按回车继续...');
193
+
194
+ // 验证安装
195
+ const check = safeExec('openclaw --version');
196
+ if (!check.ok) {
197
+ log.error('未检测到 openclaw 命令,请确认安装成功');
198
+ process.exit(1);
199
+ }
200
+ log.success(`OpenClaw 已安装: ${check.output}`);
201
+ return 'openclaw';
224
202
  }
225
203
 
226
- return new Promise((resolve, reject) => {
227
- const { cmd, args } = getNpmInstallCmd('openclaw@latest');
228
- const child = spawn(cmd, args, {
229
- stdio: 'inherit',
230
- shell: true,
231
- });
204
+ // macOS 或有权限的 Linux:直接安装
205
+ console.log(colors.gray('正在安装 openclaw...\n'));
232
206
 
233
- child.on('close', (code) => {
234
- if (code === 0) {
235
- log.success('OpenClaw CLI 安装完成');
236
- resolve('openclaw');
237
- } else {
238
- // 尝试 clawdbot
239
- log.warn('openclaw 安装失败,尝试 clawdbot...');
240
- const { cmd: cmd2, args: args2 } = getNpmInstallCmd('clawdbot@latest');
241
- const fallback = spawn(cmd2, args2, {
242
- stdio: 'inherit',
243
- shell: true,
244
- });
245
- fallback.on('close', (code2) => {
246
- if (code2 === 0) {
247
- log.success('clawdbot 安装完成');
248
- resolve('clawdbot');
249
- } else {
250
- reject(new Error('npm 安装失败'));
251
- }
252
- });
253
- }
254
- });
207
+ const result = spawnSync('npm', ['install', '-g', 'openclaw@latest'], {
208
+ stdio: 'inherit',
209
+ shell: true,
255
210
  });
256
- }
257
211
 
258
- // ============ 自动化 Onboard ============
212
+ if (result.status === 0) {
213
+ log.success('OpenClaw CLI 安装完成');
214
+ return 'openclaw';
215
+ }
259
216
 
260
- async function runOnboardWithAutoResponse(cliName, options) {
261
- log.info('\n[2/2] 运行配置向导...\n');
217
+ // 尝试 clawdbot
218
+ log.warn('openclaw 安装失败,尝试 clawdbot...');
219
+ const fallback = spawnSync('npm', ['install', '-g', 'clawdbot@latest'], {
220
+ stdio: 'inherit',
221
+ shell: true,
222
+ });
262
223
 
263
- if (options.manual) {
264
- log.hint('手动模式:请自行完成所有选择\n');
265
- } else {
266
- console.log(colors.dim(' 智能模式:自动选择推荐配置,您可以观看安装过程\n'));
224
+ if (fallback.status === 0) {
225
+ log.success('clawdbot 安装完成');
226
+ return 'clawdbot';
267
227
  }
268
228
 
269
- return new Promise((resolve, reject) => {
270
- // 使用 script 命令来伪造 TTY(Unix)或直接 spawn(可能不完美)
271
- const os = platform();
272
- let child;
273
-
274
- // 设置终端尺寸环境变量
275
- const termEnv = {
276
- ...process.env,
277
- FORCE_COLOR: '1',
278
- TERM: 'xterm-256color',
279
- COLUMNS: process.stdout.columns?.toString() || '120',
280
- LINES: process.stdout.rows?.toString() || '40',
281
- };
282
-
283
- if (os === 'win32') {
284
- // Windows: 直接 spawn,可能没有完整的交互体验
285
- child = spawn(cliName, ['onboard', '--install-daemon'], {
286
- stdio: options.manual ? 'inherit' : ['pipe', 'pipe', 'pipe'],
287
- shell: true,
288
- env: termEnv,
289
- });
290
- } else if (os === 'darwin') {
291
- // macOS: script -q /dev/null command args...
292
- child = spawn('script', ['-q', '/dev/null', cliName, 'onboard', '--install-daemon'], {
293
- stdio: options.manual ? 'inherit' : ['pipe', 'pipe', 'pipe'],
294
- env: termEnv,
295
- });
296
- } else {
297
- // Linux: script -q -c "command args..." /dev/null
298
- // 使用 stty 设置终端尺寸
299
- const cols = process.stdout.columns || 120;
300
- const rows = process.stdout.rows || 40;
301
- const cmd = `stty cols ${cols} rows ${rows} 2>/dev/null; ${cliName} onboard --install-daemon`;
302
- child = spawn('script', ['-q', '-c', cmd, '/dev/null'], {
303
- stdio: options.manual ? 'inherit' : ['pipe', 'pipe', 'pipe'],
304
- env: termEnv,
305
- });
306
- }
229
+ log.error('安装失败');
230
+ console.log(colors.cyan('\n解决方案:'));
231
+ console.log(' 1. 检查网络连接');
232
+ console.log(' 2. 手动安装: npm install -g openclaw@latest');
233
+ process.exit(1);
234
+ }
307
235
 
308
- if (options.manual) {
309
- // 手动模式:完全交互
310
- child.on('close', (code) => {
311
- if (code === 0) {
312
- resolve();
313
- } else {
314
- reject(new Error(`onboard 退出码: ${code}`));
315
- }
316
- });
317
- return;
318
- }
236
+ // ============ 运行 Onboard ============
319
237
 
320
- // 智能模式:监听输出并自动应答
321
- let buffer = '';
322
- let lastResponseTime = 0;
323
- const RESPONSE_COOLDOWN = 1000; // 防止重复响应
324
-
325
- const processOutput = (data) => {
326
- const text = data.toString();
327
- process.stdout.write(text); // 显示原版输出
328
- buffer += text;
329
-
330
- // 检查是否需要自动应答
331
- const now = Date.now();
332
- if (now - lastResponseTime < RESPONSE_COOLDOWN) return;
333
-
334
- for (const rule of AUTO_RESPONSES) {
335
- if (rule.match.test(buffer)) {
336
- if (rule.skip) {
337
- // 跳过这个提示,不发送任何内容
338
- buffer = '';
339
- return;
340
- }
341
-
342
- lastResponseTime = now;
343
- setTimeout(() => {
344
- if (child.stdin && !child.stdin.destroyed) {
345
- log.auto(rule.desc);
346
- child.stdin.write(rule.response + '\n');
347
- }
348
- }, rule.delay);
349
-
350
- buffer = ''; // 清空缓冲区,避免重复匹配
351
- break;
352
- }
353
- }
238
+ async function runOnboard(cliName) {
239
+ console.log(colors.bold(colors.cyan('\n[2/2] 运行配置向导\n')));
354
240
 
355
- // 防止缓冲区过大
356
- if (buffer.length > 2000) {
357
- buffer = buffer.slice(-1000);
358
- }
359
- };
241
+ // 显示指引
242
+ showInstallGuide();
360
243
 
361
- if (child.stdout) {
362
- child.stdout.on('data', processOutput);
363
- }
364
- if (child.stderr) {
365
- child.stderr.on('data', processOutput);
366
- }
244
+ await waitForEnter('准备好了吗?按回车开始配置...');
367
245
 
368
- child.on('close', (code) => {
369
- console.log(''); // 换行
370
- if (code === 0) {
371
- resolve();
372
- } else {
373
- // 非零退出码不一定是错误,可能是用户中断
374
- log.warn(`onboard 退出码: ${code}`);
375
- resolve();
376
- }
377
- });
246
+ console.log(colors.gray('\n' + '-'.repeat(60)));
247
+ console.log(colors.gray('以下是官方 openclaw onboard 界面:'));
248
+ console.log(colors.gray('-'.repeat(60) + '\n'));
378
249
 
379
- child.on('error', (err) => {
380
- reject(err);
381
- });
250
+ // 直接运行官方 onboard,完全交互
251
+ const result = spawnSync(cliName, ['onboard', '--install-daemon'], {
252
+ stdio: 'inherit',
253
+ shell: true,
382
254
  });
255
+
256
+ console.log(colors.gray('\n' + '-'.repeat(60)));
257
+
258
+ if (result.status !== 0) {
259
+ log.warn(`onboard 退出码: ${result.status}`);
260
+ log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
261
+ }
383
262
  }
384
263
 
385
264
  // ============ 更新 ============
386
265
 
387
266
  async function updateOpenClaw(cliName) {
388
- log.info('\n检查更新...\n');
267
+ console.log(colors.bold(colors.cyan('\n检查更新...\n')));
389
268
 
390
269
  const currentResult = safeExec(`${cliName} --version`);
391
270
  const currentVersion = currentResult.ok ? currentResult.output : '未知';
392
- console.log(` 当前版本: ${colors.yellow(currentVersion)}`);
271
+ console.log(`当前版本: ${colors.yellow(currentVersion)}`);
393
272
 
394
- return new Promise((resolve) => {
395
- const child = spawn('npm', ['install', '-g', `${cliName}@latest`], {
273
+ const useSudo = needsSudo();
274
+
275
+ if (useSudo) {
276
+ console.log(colors.yellow('\n请运行以下命令更新:'));
277
+ console.log(colors.green(` sudo npm install -g ${cliName}@latest`));
278
+ console.log(colors.green(` ${cliName} gateway restart\n`));
279
+ } else {
280
+ console.log(colors.gray('\n正在更新...\n'));
281
+ spawnSync('npm', ['install', '-g', `${cliName}@latest`], {
396
282
  stdio: 'inherit',
397
283
  shell: true,
398
284
  });
399
285
 
400
- child.on('close', (code) => {
401
- if (code === 0) {
402
- const newResult = safeExec(`${cliName} --version`);
403
- const newVersion = newResult.ok ? newResult.output : '未知';
404
- if (newVersion !== currentVersion) {
405
- log.success(`更新完成: ${currentVersion} → ${newVersion}`);
406
- } else {
407
- log.success(`已是最新版本: ${newVersion}`);
408
- }
286
+ const newResult = safeExec(`${cliName} --version`);
287
+ const newVersion = newResult.ok ? newResult.output : '未知';
409
288
 
410
- // 重启 Gateway
411
- log.info('\n重启 Gateway...');
412
- const restart = safeExec(`${cliName} gateway restart`);
413
- if (restart.ok) {
414
- log.success('Gateway 已重启');
415
- } else {
416
- log.hint(`手动重启: ${cliName} gateway restart`);
417
- }
418
- } else {
419
- log.error('更新失败');
420
- log.hint(`手动更新: npm install -g ${cliName}@latest`);
421
- }
422
- resolve();
423
- });
424
- });
289
+ if (newVersion !== currentVersion) {
290
+ log.success(`更新完成: ${currentVersion} → ${newVersion}`);
291
+ } else {
292
+ log.success(`已是最新版本: ${newVersion}`);
293
+ }
294
+
295
+ console.log(colors.gray('\n重启 Gateway...'));
296
+ safeExec(`${cliName} gateway restart`);
297
+ log.success('Gateway 已重启');
298
+ }
425
299
  }
426
300
 
427
301
  // ============ 完成信息 ============
428
302
 
429
303
  function showCompletionInfo(cliName) {
430
- console.log(colors.bold(colors.green('\n========================================')));
431
- console.log(colors.bold(colors.green('✅ OpenClaw 安装完成!')));
432
- console.log(colors.bold(colors.green('========================================')));
304
+ console.log(colors.bold(colors.green('\n' + '='.repeat(60))));
305
+ console.log(colors.bold(colors.green(' ✅ OpenClaw 安装完成!')));
306
+ console.log(colors.bold(colors.green('='.repeat(60))));
433
307
 
434
- console.log(colors.cyan('\n下一步 - 配置 AI 模型:'));
308
+ console.log(colors.cyan('\n下一步 - 配置 AI 模型(必须):'));
435
309
  console.log(` ${colors.yellow('npx openclawapi@latest preset-claude')}`);
436
310
 
437
311
  console.log(colors.cyan('\n常用命令:'));
@@ -440,7 +314,7 @@ function showCompletionInfo(cliName) {
440
314
  console.log(` 重启服务: ${colors.yellow(`${cliName} gateway restart`)}`);
441
315
  console.log(` 诊断问题: ${colors.yellow(`${cliName} doctor`)}`);
442
316
 
443
- console.log(colors.cyan('\n配置聊天渠道:'));
317
+ console.log(colors.cyan('\n配置聊天渠道(可选):'));
444
318
  console.log(` Discord: ${colors.yellow('npx openclawdc')}`);
445
319
  console.log(` 飞书: ${colors.yellow('npx openclaw-chat-cn@latest feishu')}`);
446
320
 
@@ -457,7 +331,7 @@ async function main() {
457
331
  process.exit(0);
458
332
  }
459
333
 
460
- console.log(colors.bold(colors.cyan('\n🦞 OpenClaw 智能安装向导\n')));
334
+ console.log(colors.bold(colors.cyan('\n🦞 OpenClaw 安装向导\n')));
461
335
 
462
336
  // 检查 Node 版本
463
337
  checkNodeVersion();
@@ -477,10 +351,18 @@ async function main() {
477
351
  if (options.reinstall) {
478
352
  log.info('\n卸载现有安装...');
479
353
  safeExec(`${existing.name} gateway stop`);
480
- safeExec(`npm uninstall -g ${existing.name}`);
481
- if (existing.configDir && existsSync(existing.configDir)) {
482
- rmSync(existing.configDir, { recursive: true, force: true });
483
- log.success('配置已清除');
354
+
355
+ if (needsSudo()) {
356
+ console.log(colors.yellow('\n请运行以下命令卸载:'));
357
+ console.log(colors.green(` sudo npm uninstall -g ${existing.name}`));
358
+ console.log(colors.green(` rm -rf ~/.openclaw ~/.clawdbot\n`));
359
+ await waitForEnter('卸载完成后按回车继续...');
360
+ } else {
361
+ spawnSync('npm', ['uninstall', '-g', existing.name], { stdio: 'inherit', shell: true });
362
+ if (existing.configDir && existsSync(existing.configDir)) {
363
+ rmSync(existing.configDir, { recursive: true, force: true });
364
+ }
365
+ log.success('卸载完成');
484
366
  }
485
367
  } else {
486
368
  console.log(colors.cyan('\n已安装,可选操作:'));
@@ -492,22 +374,14 @@ async function main() {
492
374
  }
493
375
  }
494
376
 
495
- try {
496
- // 安装 CLI
497
- const cliName = await installOpenClaw();
498
-
499
- // 运行 onboard
500
- await runOnboardWithAutoResponse(cliName, options);
377
+ // 安装 CLI
378
+ const cliName = await installOpenClaw();
501
379
 
502
- // 显示完成信息
503
- showCompletionInfo(cliName);
380
+ // 运行 onboard
381
+ await runOnboard(cliName);
504
382
 
505
- } catch (err) {
506
- exitWithError('ONBOARD_FAILED', err.message, [
507
- '检查网络连接',
508
- '手动安装: npm install -g openclaw && openclaw onboard --install-daemon',
509
- ]);
510
- }
383
+ // 显示完成信息
384
+ showCompletionInfo(cliName);
511
385
  }
512
386
 
513
387
  main().catch((e) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openclawsetup",
3
- "version": "2.0.3",
4
- "description": "OpenClaw 智能安装向导 - 调用官方交互界面,自动选择推荐配置",
3
+ "version": "2.1.0",
4
+ "description": "OpenClaw 安装向导 - 带中文指引的官方安装流程",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "openclawsetup": "bin/cli.mjs"