openclawsetup 2.1.0 → 2.1.2
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/README.md +20 -7
- package/bin/cli.mjs +379 -11
- package/package.json +4 -1
- package//344/275/277/347/224/250/350/257/264/346/230/216.md +27 -4
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ OpenClaw 智能安装向导 - 调用官方 `openclaw onboard` 交互界面,自
|
|
|
7
7
|
- **真实体验**:调用官方 `openclaw onboard` 命令,用户看到完整的原版安装界面
|
|
8
8
|
- **智能自动化**:自动选择推荐配置,无需手动操作
|
|
9
9
|
- **可观看过程**:用户可以看到每一步的选择过程,了解发生了什么
|
|
10
|
+
- **随时接管**:按任意键立刻切换为手动操作
|
|
10
11
|
- **手动模式**:`--manual` 参数可切换到完全手动模式
|
|
11
12
|
- **三系统支持**:macOS、Linux、Windows
|
|
12
13
|
|
|
@@ -32,13 +33,13 @@ npx openclawsetup@latest
|
|
|
32
33
|
|
|
33
34
|
## 安装模式
|
|
34
35
|
|
|
35
|
-
###
|
|
36
|
+
### 智能模式(默认,自动应答)
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
39
|
npx openclawsetup@latest
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
自动完成以下选择(按任意键可接管):
|
|
42
43
|
- ✓ 选择 QuickStart 模式
|
|
43
44
|
- ✓ 跳过模型配置(后续用 `npx openclawapi` 配置)
|
|
44
45
|
- ✓ 跳过渠道配置(后续用 `npx openclawdc` 或 `npx openclaw-chat-cn@latest feishu` 配置)
|
|
@@ -53,12 +54,22 @@ npx openclawsetup@latest --manual
|
|
|
53
54
|
|
|
54
55
|
完全交互,自己选择所有配置项。
|
|
55
56
|
|
|
57
|
+
### 强制自动模式
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx openclawsetup@latest --auto
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
自动应答不可用时将直接退出(适合脚本化场景)。
|
|
64
|
+
|
|
56
65
|
## 命令行参数
|
|
57
66
|
|
|
58
67
|
| 参数 | 说明 |
|
|
59
68
|
|------|------|
|
|
60
69
|
| `--manual` | 手动模式,不自动选择 |
|
|
61
|
-
| `--
|
|
70
|
+
| `--auto` | 强制自动模式(不可用则退出) |
|
|
71
|
+
| `--with-model` | 检测到模型配置时暂停自动选择 |
|
|
72
|
+
| `--with-channel` | 检测到渠道配置时暂停自动选择 |
|
|
62
73
|
| `--update` | 检查并更新已安装的 OpenClaw |
|
|
63
74
|
| `--reinstall` | 卸载后重新安装(清除配置) |
|
|
64
75
|
| `--help, -h` | 显示帮助信息 |
|
|
@@ -132,11 +143,11 @@ openclaw doctor
|
|
|
132
143
|
## 工作原理
|
|
133
144
|
|
|
134
145
|
1. 安装 `openclaw` npm 包
|
|
135
|
-
2.
|
|
136
|
-
3.
|
|
137
|
-
4.
|
|
146
|
+
2. 优先检测官方 `openclaw onboard` 的非交互参数(可用时直接自动完成)
|
|
147
|
+
3. 如不支持,则使用 `node-pty` 创建伪终端进行自动应答(按任意键接管)
|
|
148
|
+
4. 若自动应答不可用,退回手动模式
|
|
138
149
|
|
|
139
|
-
##
|
|
150
|
+
## 自动应答规则(兜底)
|
|
140
151
|
|
|
141
152
|
| 提示类型 | 自动选择 |
|
|
142
153
|
|---------|---------|
|
|
@@ -149,6 +160,8 @@ openclaw doctor
|
|
|
149
160
|
| Daemon/Service | 安装 (y) |
|
|
150
161
|
| UI 选择 | Web Dashboard |
|
|
151
162
|
|
|
163
|
+
> 使用 `--with-model` / `--with-channel` 时,自动模式会在对应步骤暂停并交给用户操作。
|
|
164
|
+
|
|
152
165
|
## 卸载
|
|
153
166
|
|
|
154
167
|
```bash
|
package/bin/cli.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* OpenClaw 安装向导
|
|
4
4
|
*
|
|
5
|
-
* 调用官方 openclaw onboard
|
|
6
|
-
*
|
|
5
|
+
* 调用官方 openclaw onboard,自动选择推荐配置
|
|
6
|
+
* 用户可随时按任意键接管,切换为手动操作
|
|
7
7
|
*
|
|
8
8
|
* 用法:
|
|
9
9
|
* npx openclawsetup # 带中文指引的安装
|
|
@@ -51,6 +51,10 @@ function parseArgs() {
|
|
|
51
51
|
return {
|
|
52
52
|
update: args.includes('--update'),
|
|
53
53
|
reinstall: args.includes('--reinstall'),
|
|
54
|
+
manual: args.includes('--manual'),
|
|
55
|
+
auto: args.includes('--auto'),
|
|
56
|
+
withModel: args.includes('--with-model'),
|
|
57
|
+
withChannel: args.includes('--with-channel'),
|
|
54
58
|
help: args.includes('--help') || args.includes('-h'),
|
|
55
59
|
};
|
|
56
60
|
}
|
|
@@ -63,10 +67,16 @@ ${colors.cyan('用法:')}
|
|
|
63
67
|
npx openclawsetup 带中文指引的安装
|
|
64
68
|
npx openclawsetup --update 更新已安装的 OpenClaw
|
|
65
69
|
npx openclawsetup --reinstall 卸载后重新安装
|
|
70
|
+
npx openclawsetup --manual 完全手动模式
|
|
71
|
+
npx openclawsetup --auto 强制自动模式
|
|
66
72
|
|
|
67
73
|
${colors.cyan('说明:')}
|
|
68
74
|
本工具会调用官方 openclaw onboard 命令
|
|
69
|
-
|
|
75
|
+
自动选择推荐配置,按任意键可随时接管
|
|
76
|
+
|
|
77
|
+
${colors.cyan('高级选项:')}
|
|
78
|
+
--with-model 检测到模型配置时暂停自动选择
|
|
79
|
+
--with-channel 检测到渠道配置时暂停自动选择
|
|
70
80
|
|
|
71
81
|
${colors.cyan('安装后配置模型:')}
|
|
72
82
|
npx openclawapi@latest preset-claude
|
|
@@ -137,7 +147,7 @@ function showInstallGuide() {
|
|
|
137
147
|
console.log(colors.bold(colors.cyan('='.repeat(60))));
|
|
138
148
|
|
|
139
149
|
console.log(colors.yellow('\n接下来会运行官方 openclaw onboard 命令'));
|
|
140
|
-
console.log(colors.yellow('
|
|
150
|
+
console.log(colors.yellow('自动模式会按以下推荐选择(可随时接管):\n'));
|
|
141
151
|
|
|
142
152
|
console.log(colors.cyan('┌─────────────────────────────────────────────────────────┐'));
|
|
143
153
|
console.log(colors.cyan('│') + colors.bold(' 步骤 1: 安全确认') + colors.cyan(' │'));
|
|
@@ -167,6 +177,91 @@ function showInstallGuide() {
|
|
|
167
177
|
console.log(colors.gray('如果不确定,选择 Skip 或直接回车通常是安全的\n'));
|
|
168
178
|
}
|
|
169
179
|
|
|
180
|
+
function stripAnsi(input) {
|
|
181
|
+
return input
|
|
182
|
+
.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
|
|
183
|
+
.replace(/\x1b\][^\x07]*\x07/g, '')
|
|
184
|
+
.replace(/\r/g, '\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getOnboardHelp(cliName) {
|
|
188
|
+
const candidates = [
|
|
189
|
+
`${cliName} onboard --help`,
|
|
190
|
+
`${cliName} onboard -h`,
|
|
191
|
+
`${cliName} help onboard`,
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
for (const cmd of candidates) {
|
|
195
|
+
const result = safeExec(cmd, { stdio: 'pipe' });
|
|
196
|
+
const output = result.ok ? result.output : '';
|
|
197
|
+
if (output && output.length > 20) {
|
|
198
|
+
return output;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildOnboardArgsFromHelp(helpText, options) {
|
|
205
|
+
const help = helpText.toLowerCase();
|
|
206
|
+
const args = [];
|
|
207
|
+
const enabled = [];
|
|
208
|
+
|
|
209
|
+
const pickFlag = (flags) => flags.find((flag) => help.includes(flag));
|
|
210
|
+
|
|
211
|
+
const yesFlag = pickFlag(['--yes', '--assume-yes', '--accept', '--agree']);
|
|
212
|
+
if (yesFlag) {
|
|
213
|
+
args.push(yesFlag);
|
|
214
|
+
enabled.push('yes');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const installDaemonFlag = pickFlag(['--install-daemon', '--daemon']);
|
|
218
|
+
if (installDaemonFlag) {
|
|
219
|
+
args.push(installDaemonFlag);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (help.includes('--quickstart')) {
|
|
223
|
+
args.push('--quickstart');
|
|
224
|
+
enabled.push('quickstart');
|
|
225
|
+
} else if (help.includes('--mode') && (help.includes('quickstart') || help.includes('quick start'))) {
|
|
226
|
+
args.push('--mode', 'quickstart');
|
|
227
|
+
enabled.push('quickstart');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!options.withModel) {
|
|
231
|
+
const skipModelFlag = pickFlag(['--skip-model', '--no-model', '--skip-provider']);
|
|
232
|
+
if (skipModelFlag) {
|
|
233
|
+
args.push(skipModelFlag);
|
|
234
|
+
enabled.push('skip-model');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!options.withChannel) {
|
|
239
|
+
const skipChannelFlag = pickFlag(['--skip-channel', '--no-channel', '--skip-channels']);
|
|
240
|
+
if (skipChannelFlag) {
|
|
241
|
+
args.push(skipChannelFlag);
|
|
242
|
+
enabled.push('skip-channel');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (help.includes('--ui') && (help.includes('web') || help.includes('dashboard'))) {
|
|
247
|
+
args.push('--ui', 'web');
|
|
248
|
+
enabled.push('ui-web');
|
|
249
|
+
} else if (help.includes('--web')) {
|
|
250
|
+
args.push('--web');
|
|
251
|
+
enabled.push('ui-web');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const autoCapable = enabled.length > 0 || Boolean(yesFlag);
|
|
255
|
+
return { args, enabled, autoCapable };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getOnboardCommand(cliName, onboardArgs = ['onboard', '--install-daemon']) {
|
|
259
|
+
if (platform() === 'win32') {
|
|
260
|
+
return { file: 'cmd.exe', args: ['/c', cliName, ...onboardArgs] };
|
|
261
|
+
}
|
|
262
|
+
return { file: cliName, args: onboardArgs };
|
|
263
|
+
}
|
|
264
|
+
|
|
170
265
|
function waitForEnter(message) {
|
|
171
266
|
return new Promise((resolve) => {
|
|
172
267
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -247,18 +342,291 @@ async function runOnboard(cliName) {
|
|
|
247
342
|
console.log(colors.gray('以下是官方 openclaw onboard 界面:'));
|
|
248
343
|
console.log(colors.gray('-'.repeat(60) + '\n'));
|
|
249
344
|
|
|
250
|
-
|
|
251
|
-
const
|
|
345
|
+
const options = parseArgs();
|
|
346
|
+
const preferAuto = !options.manual;
|
|
347
|
+
let usedAuto = false;
|
|
348
|
+
|
|
349
|
+
if (preferAuto) {
|
|
350
|
+
const flagResult = runOnboardFlags(cliName, options);
|
|
351
|
+
if (flagResult.ran) {
|
|
352
|
+
usedAuto = true;
|
|
353
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
354
|
+
if (!flagResult.ok) {
|
|
355
|
+
log.warn(`onboard 退出码: ${flagResult.exitCode}`);
|
|
356
|
+
log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const autoResult = await runOnboardAuto(cliName, options);
|
|
360
|
+
if (autoResult.ok) {
|
|
361
|
+
usedAuto = true;
|
|
362
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
363
|
+
if (autoResult.exitCode !== 0) {
|
|
364
|
+
log.warn(`onboard 退出码: ${autoResult.exitCode}`);
|
|
365
|
+
log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
|
|
366
|
+
}
|
|
367
|
+
} else if (options.auto) {
|
|
368
|
+
log.error('自动模式不可用,已退出');
|
|
369
|
+
log.hint(autoResult.reason || '请尝试 --manual');
|
|
370
|
+
process.exit(1);
|
|
371
|
+
} else {
|
|
372
|
+
log.warn('自动模式不可用,已切换为手动模式');
|
|
373
|
+
log.hint(autoResult.reason || '未能启用自动应答');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!usedAuto) {
|
|
379
|
+
const manualResult = runOnboardManual(cliName);
|
|
380
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
381
|
+
if (manualResult.status !== 0) {
|
|
382
|
+
log.warn(`onboard 退出码: ${manualResult.status}`);
|
|
383
|
+
log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function runOnboardManual(cliName) {
|
|
389
|
+
const { file, args } = getOnboardCommand(cliName);
|
|
390
|
+
return spawnSync(file, args, {
|
|
252
391
|
stdio: 'inherit',
|
|
253
|
-
shell: true,
|
|
254
392
|
});
|
|
393
|
+
}
|
|
255
394
|
|
|
256
|
-
|
|
395
|
+
function runOnboardFlags(cliName, options) {
|
|
396
|
+
const help = getOnboardHelp(cliName);
|
|
397
|
+
if (!help) {
|
|
398
|
+
return { ran: false, reason: '未检测到 onboard 帮助信息' };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const { args, enabled, autoCapable } = buildOnboardArgsFromHelp(help, options);
|
|
402
|
+
if (!autoCapable) {
|
|
403
|
+
return { ran: false, reason: '未检测到可用的非交互参数' };
|
|
404
|
+
}
|
|
257
405
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
log.hint(
|
|
406
|
+
log.info('检测到官方非交互参数,优先使用原生方式安装');
|
|
407
|
+
if (enabled.length) {
|
|
408
|
+
log.hint(`已启用: ${enabled.join(', ')}`);
|
|
261
409
|
}
|
|
410
|
+
|
|
411
|
+
const { file, args: spawnArgs } = getOnboardCommand(cliName, ['onboard', ...args]);
|
|
412
|
+
const result = spawnSync(file, spawnArgs, {
|
|
413
|
+
stdio: 'inherit',
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return { ran: true, ok: result.status === 0, exitCode: result.status ?? 1 };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function createAutoResponder(term, options) {
|
|
420
|
+
const lastSent = new Map();
|
|
421
|
+
let autoStopped = false;
|
|
422
|
+
let pausedNoticeShown = false;
|
|
423
|
+
|
|
424
|
+
const cooldownMs = 1200;
|
|
425
|
+
const shouldRespond = (id) => {
|
|
426
|
+
const now = Date.now();
|
|
427
|
+
const last = lastSent.get(id) || 0;
|
|
428
|
+
if (now - last < cooldownMs) return false;
|
|
429
|
+
lastSent.set(id, now);
|
|
430
|
+
return true;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const stopAuto = (reason) => {
|
|
434
|
+
if (autoStopped) return;
|
|
435
|
+
autoStopped = true;
|
|
436
|
+
log.warn(reason);
|
|
437
|
+
log.hint('已进入手动模式');
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const send = (id, payload) => {
|
|
441
|
+
if (!shouldRespond(id)) return false;
|
|
442
|
+
term.write(payload);
|
|
443
|
+
return true;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
return (rawText) => {
|
|
447
|
+
if (autoStopped) return;
|
|
448
|
+
const text = rawText.toLowerCase();
|
|
449
|
+
const tail = text.slice(-800);
|
|
450
|
+
|
|
451
|
+
if (tail.includes('do you want to continue') || tail.includes('continue?') || tail.includes('是否继续')) {
|
|
452
|
+
if (tail.includes('yes') && tail.includes('no')) {
|
|
453
|
+
send('confirm-select', '\x1b[A\x1b[D\r');
|
|
454
|
+
} else {
|
|
455
|
+
send('confirm', 'y\r');
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if ((tail.includes('quick start') || tail.includes('quickstart')) && (tail.includes('advanced') || tail.includes('custom'))) {
|
|
461
|
+
send('setup', '\r');
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const modelPrompt =
|
|
466
|
+
tail.includes('model provider') ||
|
|
467
|
+
tail.includes('model providers') ||
|
|
468
|
+
tail.includes('select a model') ||
|
|
469
|
+
tail.includes('模型') && tail.includes('提供商');
|
|
470
|
+
|
|
471
|
+
if (modelPrompt) {
|
|
472
|
+
if (options.withModel) {
|
|
473
|
+
stopAuto('检测到模型配置提示,已暂停自动选择(--with-model)');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
send('model-skip', tail.includes('skip') || tail.includes('跳过') ? 's\r' : '\r');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const apiKeyPrompt =
|
|
481
|
+
tail.includes('api key') ||
|
|
482
|
+
tail.includes('apikey') ||
|
|
483
|
+
(tail.includes('key') && tail.includes('请输入'));
|
|
484
|
+
|
|
485
|
+
if (apiKeyPrompt) {
|
|
486
|
+
if (options.withModel) {
|
|
487
|
+
stopAuto('检测到 API Key 配置,已暂停自动选择(--with-model)');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
send('api-skip', tail.includes('skip') || tail.includes('跳过') ? 's\r' : '\r');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const channelPrompt =
|
|
495
|
+
tail.includes('channel') ||
|
|
496
|
+
tail.includes('discord') ||
|
|
497
|
+
tail.includes('telegram') ||
|
|
498
|
+
tail.includes('feishu') ||
|
|
499
|
+
(tail.includes('渠道') && (tail.includes('选择') || tail.includes('配置')));
|
|
500
|
+
|
|
501
|
+
if (channelPrompt) {
|
|
502
|
+
if (options.withChannel) {
|
|
503
|
+
stopAuto('检测到渠道配置提示,已暂停自动选择(--with-channel)');
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
send('channel-skip', 's\r');
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (tail.includes('skills') || tail.includes('skill')) {
|
|
511
|
+
send('skills-skip', 's\r');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const daemonPrompt =
|
|
516
|
+
tail.includes('daemon') ||
|
|
517
|
+
tail.includes('service') && (tail.includes('install') || tail.includes('enable')) ||
|
|
518
|
+
(tail.includes('后台') && tail.includes('服务')) ||
|
|
519
|
+
tail.includes('开机') && tail.includes('自启');
|
|
520
|
+
|
|
521
|
+
if (daemonPrompt) {
|
|
522
|
+
send('daemon-yes', 'y\r');
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const uiPrompt =
|
|
527
|
+
tail.includes('dashboard') ||
|
|
528
|
+
tail.includes('web ui') ||
|
|
529
|
+
tail.includes('web dashboard') ||
|
|
530
|
+
(tail.includes('ui') && tail.includes('选择')) ||
|
|
531
|
+
(tail.includes('界面') && tail.includes('选择'));
|
|
532
|
+
|
|
533
|
+
if (uiPrompt) {
|
|
534
|
+
send('ui-web', tail.includes('web') || tail.includes('dashboard') ? '\r' : 'w\r');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (tail.includes('port') && (tail.includes('gateway') || tail.includes('端口'))) {
|
|
539
|
+
send('port-default', '\r');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (!pausedNoticeShown && (options.withModel || options.withChannel)) {
|
|
544
|
+
pausedNoticeShown = true;
|
|
545
|
+
log.hint('检测到 --with-model / --with-channel,将在相关步骤暂停自动选择');
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function runOnboardAuto(cliName, options) {
|
|
551
|
+
let ptyModule;
|
|
552
|
+
try {
|
|
553
|
+
ptyModule = await import('node-pty');
|
|
554
|
+
} catch (e) {
|
|
555
|
+
return { ok: false, reason: 'node-pty 依赖不可用,请尝试 --manual' };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const pty = ptyModule.default ?? ptyModule;
|
|
559
|
+
const spawn = pty?.spawn;
|
|
560
|
+
if (typeof spawn !== 'function') {
|
|
561
|
+
return { ok: false, reason: 'node-pty 未正确加载,请尝试 --manual' };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const { file, args } = getOnboardCommand(cliName);
|
|
565
|
+
const cols = process.stdout.columns || 80;
|
|
566
|
+
const rows = process.stdout.rows || 24;
|
|
567
|
+
const env = { ...process.env, FORCE_COLOR: '1' };
|
|
568
|
+
|
|
569
|
+
let term;
|
|
570
|
+
try {
|
|
571
|
+
term = spawn(file, args, {
|
|
572
|
+
name: 'xterm-256color',
|
|
573
|
+
cols,
|
|
574
|
+
rows,
|
|
575
|
+
cwd: process.cwd(),
|
|
576
|
+
env,
|
|
577
|
+
});
|
|
578
|
+
} catch (e) {
|
|
579
|
+
return { ok: false, reason: `启动 PTY 失败: ${e.message}` };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const autoResponder = createAutoResponder(term, options);
|
|
583
|
+
let buffer = '';
|
|
584
|
+
let manualOverride = false;
|
|
585
|
+
|
|
586
|
+
log.info('自动模式已开启,按任意键可接管');
|
|
587
|
+
|
|
588
|
+
const stdin = process.stdin;
|
|
589
|
+
const canReadInput = Boolean(stdin.isTTY);
|
|
590
|
+
const restoreRaw = canReadInput ? stdin.isRaw : false;
|
|
591
|
+
|
|
592
|
+
const onUserData = (data) => {
|
|
593
|
+
if (!manualOverride) {
|
|
594
|
+
manualOverride = true;
|
|
595
|
+
log.warn('已接管,自动模式停止');
|
|
596
|
+
}
|
|
597
|
+
term.write(data.toString('utf8'));
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
if (canReadInput && typeof stdin.setRawMode === 'function') {
|
|
601
|
+
stdin.setRawMode(true);
|
|
602
|
+
}
|
|
603
|
+
if (canReadInput) {
|
|
604
|
+
stdin.on('data', onUserData);
|
|
605
|
+
stdin.resume();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
term.onData((data) => {
|
|
609
|
+
process.stdout.write(data);
|
|
610
|
+
if (manualOverride) return;
|
|
611
|
+
buffer += stripAnsi(data);
|
|
612
|
+
if (buffer.length > 4000) {
|
|
613
|
+
buffer = buffer.slice(-4000);
|
|
614
|
+
}
|
|
615
|
+
autoResponder(buffer);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
return await new Promise((resolve) => {
|
|
619
|
+
term.onExit(({ exitCode }) => {
|
|
620
|
+
if (canReadInput) {
|
|
621
|
+
stdin.off('data', onUserData);
|
|
622
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
623
|
+
stdin.setRawMode(Boolean(restoreRaw));
|
|
624
|
+
}
|
|
625
|
+
stdin.pause();
|
|
626
|
+
}
|
|
627
|
+
resolve({ ok: true, exitCode });
|
|
628
|
+
});
|
|
629
|
+
});
|
|
262
630
|
}
|
|
263
631
|
|
|
264
632
|
// ============ 更新 ============
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclawsetup",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "OpenClaw 安装向导 - 带中文指引的官方安装流程",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"openclawsetup": "bin/cli.mjs"
|
|
8
8
|
},
|
|
9
|
+
"optionalDependencies": {
|
|
10
|
+
"node-pty": "^1.0.0"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"start": "node bin/cli.mjs"
|
|
11
14
|
},
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
npx openclawsetup@latest
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
你会看到官方的 `openclaw onboard`
|
|
13
|
+
你会看到官方的 `openclaw onboard` 界面,但所有选项会自动选择推荐配置(按任意键可接管)。
|
|
14
|
+
默认优先使用官方提供的非交互参数,若版本不支持则使用自动应答兜底。
|
|
14
15
|
|
|
15
16
|
### 方式二:手动模式
|
|
16
17
|
|
|
@@ -20,7 +21,15 @@ npx openclawsetup@latest
|
|
|
20
21
|
npx openclawsetup@latest --manual
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
###
|
|
24
|
+
### 方式三:强制自动模式
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx openclawsetup@latest --auto
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
自动应答不可用时将直接退出(适合脚本化场景)。
|
|
31
|
+
|
|
32
|
+
### 方式四:一键脚本
|
|
24
33
|
|
|
25
34
|
自动检测并安装 Node.js,无需任何前置条件。
|
|
26
35
|
|
|
@@ -49,6 +58,10 @@ irm https://unpkg.com/openclawsetup@latest/install.ps1 | iex
|
|
|
49
58
|
| Daemon/Service | 安装 (y) |
|
|
50
59
|
| UI 选择 | Web Dashboard |
|
|
51
60
|
|
|
61
|
+
> 需要在安装过程中配置模型或渠道时,可使用:
|
|
62
|
+
> - `--with-model`:检测到模型配置步骤时暂停自动选择
|
|
63
|
+
> - `--with-channel`:检测到渠道配置步骤时暂停自动选择
|
|
64
|
+
|
|
52
65
|
**为什么跳过模型和渠道?**
|
|
53
66
|
- 模型配置:后续用 `npx openclawapi` 单独配置,更灵活
|
|
54
67
|
- 渠道配置:后续用 `npx openclawdc`(Discord)或 `npx openclaw-chat-cn@latest feishu`(飞书)配置
|
|
@@ -142,13 +155,23 @@ curl -fsSL https://unpkg.com/openclawsetup@latest/install.sh | bash
|
|
|
142
155
|
|
|
143
156
|
### 自动选择没有生效
|
|
144
157
|
**现象**:安装过程中需要手动输入
|
|
145
|
-
|
|
158
|
+
**原因**:可能是终端环境问题、无 TTY,或自动应答依赖不可用(node-pty 未安装成功)
|
|
146
159
|
**解决**:
|
|
147
160
|
1. 使用手动模式:
|
|
148
161
|
```bash
|
|
149
162
|
npx openclawsetup@latest --manual
|
|
150
163
|
```
|
|
151
|
-
2.
|
|
164
|
+
2. 需要脚本化时使用强制自动模式(不可用会退出):
|
|
165
|
+
```bash
|
|
166
|
+
npx openclawsetup@latest --auto
|
|
167
|
+
```
|
|
168
|
+
3. 在 Linux 服务器上补齐编译依赖后再试(Ubuntu 示例):
|
|
169
|
+
```bash
|
|
170
|
+
sudo apt-get update
|
|
171
|
+
sudo apt-get install -y build-essential python3 make g++ pkg-config
|
|
172
|
+
npx openclawsetup@latest --auto
|
|
173
|
+
```
|
|
174
|
+
4. 或直接运行官方命令:
|
|
152
175
|
```bash
|
|
153
176
|
npm install -g openclaw@latest
|
|
154
177
|
openclaw onboard --install-daemon
|