openclawsetup 2.0.3 → 2.1.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/README.md +17 -4
- package/bin/cli.mjs +452 -319
- package/package.json +5 -2
- package//344/275/277/347/224/250/350/257/264/346/230/216.md +20 -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,7 +143,7 @@ openclaw doctor
|
|
|
132
143
|
## 工作原理
|
|
133
144
|
|
|
134
145
|
1. 安装 `openclaw` npm 包
|
|
135
|
-
2. 使用 `
|
|
146
|
+
2. 使用 `node-pty` 创建跨平台伪终端(Windows 使用 ConPTY)
|
|
136
147
|
3. 监听输出,识别交互提示后自动发送预设答案
|
|
137
148
|
4. 用户看到完整的原版界面 + 自动选择过程
|
|
138
149
|
|
|
@@ -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
|
@@ -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,
|
|
15
|
-
import { existsSync,
|
|
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
|
-
|
|
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
|
-
|
|
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,62 @@ 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'),
|
|
54
|
+
manual: args.includes('--manual'),
|
|
55
|
+
auto: args.includes('--auto'),
|
|
56
|
+
withModel: args.includes('--with-model'),
|
|
57
|
+
withChannel: args.includes('--with-channel'),
|
|
125
58
|
help: args.includes('--help') || args.includes('-h'),
|
|
126
|
-
skipModel: !args.includes('--with-model'), // 默认跳过模型配置
|
|
127
59
|
};
|
|
128
60
|
}
|
|
129
61
|
|
|
130
62
|
function showHelp() {
|
|
131
63
|
console.log(`
|
|
132
|
-
${colors.bold('OpenClaw
|
|
64
|
+
${colors.bold('OpenClaw 安装向导')}
|
|
133
65
|
|
|
134
66
|
${colors.cyan('用法:')}
|
|
135
|
-
npx openclawsetup
|
|
136
|
-
npx openclawsetup --manual 手动模式(完全交互,自己选择)
|
|
67
|
+
npx openclawsetup 带中文指引的安装
|
|
137
68
|
npx openclawsetup --update 更新已安装的 OpenClaw
|
|
138
69
|
npx openclawsetup --reinstall 卸载后重新安装
|
|
70
|
+
npx openclawsetup --manual 完全手动模式
|
|
71
|
+
npx openclawsetup --auto 强制自动模式
|
|
139
72
|
|
|
140
|
-
${colors.cyan('
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
--update 检查并更新
|
|
144
|
-
--reinstall 重新安装(清除配置)
|
|
145
|
-
--help, -h 显示帮助
|
|
73
|
+
${colors.cyan('说明:')}
|
|
74
|
+
本工具会调用官方 openclaw onboard 命令
|
|
75
|
+
自动选择推荐配置,按任意键可随时接管
|
|
146
76
|
|
|
147
|
-
${colors.cyan('
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
✓ 跳过渠道配置(后续用 npx openclawdc 或 npx openclaw-chat-cn@latest feishu 配置)
|
|
151
|
-
✓ 安装后台服务(开机自启)
|
|
152
|
-
✓ 选择 Web Dashboard
|
|
77
|
+
${colors.cyan('高级选项:')}
|
|
78
|
+
--with-model 检测到模型配置时暂停自动选择
|
|
79
|
+
--with-channel 检测到渠道配置时暂停自动选择
|
|
153
80
|
|
|
154
81
|
${colors.cyan('安装后配置模型:')}
|
|
155
82
|
npx openclawapi@latest preset-claude
|
|
156
83
|
`);
|
|
157
84
|
}
|
|
158
85
|
|
|
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 };
|
|
86
|
+
// ============ 环境检测 ============
|
|
167
87
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
}
|
|
88
|
+
function checkNodeVersion() {
|
|
89
|
+
const version = process.version;
|
|
90
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
91
|
+
if (major < 18) {
|
|
92
|
+
log.error(`Node.js 版本过低: ${version},需要 18+`);
|
|
93
|
+
console.log(colors.cyan('\n解决方案:'));
|
|
94
|
+
console.log(' 1. 访问 https://nodejs.org/ 下载最新版本');
|
|
95
|
+
console.log(' 2. 或使用 nvm: nvm install 22');
|
|
96
|
+
process.exit(1);
|
|
184
97
|
}
|
|
185
|
-
|
|
186
|
-
return result;
|
|
98
|
+
return true;
|
|
187
99
|
}
|
|
188
100
|
|
|
189
|
-
// ============ 安装 OpenClaw ============
|
|
190
|
-
|
|
191
|
-
// 检测是否需要 sudo
|
|
192
101
|
function needsSudo() {
|
|
193
102
|
const os = platform();
|
|
194
103
|
if (os === 'win32' || os === 'darwin') return false;
|
|
195
104
|
|
|
196
|
-
// Linux: 检查 /usr/lib/node_modules 是否可写
|
|
197
105
|
try {
|
|
198
106
|
const testDir = '/usr/lib/node_modules';
|
|
199
107
|
if (existsSync(testDir)) {
|
|
@@ -206,178 +114,408 @@ function needsSudo() {
|
|
|
206
114
|
return true;
|
|
207
115
|
}
|
|
208
116
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
117
|
+
function detectExistingInstall() {
|
|
118
|
+
const home = homedir();
|
|
119
|
+
const openclawDir = join(home, '.openclaw');
|
|
120
|
+
const clawdbotDir = join(home, '.clawdbot');
|
|
121
|
+
|
|
122
|
+
if (existsSync(openclawDir)) {
|
|
123
|
+
return { installed: true, configDir: openclawDir, name: 'openclaw' };
|
|
124
|
+
}
|
|
125
|
+
if (existsSync(clawdbotDir)) {
|
|
126
|
+
return { installed: true, configDir: clawdbotDir, name: 'clawdbot' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const openclawResult = safeExec('openclaw --version');
|
|
130
|
+
if (openclawResult.ok) {
|
|
131
|
+
return { installed: true, name: 'openclaw', version: openclawResult.output };
|
|
214
132
|
}
|
|
215
|
-
|
|
133
|
+
|
|
134
|
+
const clawdbotResult = safeExec('clawdbot --version');
|
|
135
|
+
if (clawdbotResult.ok) {
|
|
136
|
+
return { installed: true, name: 'clawdbot', version: clawdbotResult.output };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { installed: false };
|
|
216
140
|
}
|
|
217
141
|
|
|
218
|
-
|
|
219
|
-
|
|
142
|
+
// ============ 安装指引 ============
|
|
143
|
+
|
|
144
|
+
function showInstallGuide() {
|
|
145
|
+
console.log(colors.bold(colors.cyan('\n' + '='.repeat(60))));
|
|
146
|
+
console.log(colors.bold(colors.cyan(' 📖 OpenClaw 安装指引')));
|
|
147
|
+
console.log(colors.bold(colors.cyan('='.repeat(60))));
|
|
148
|
+
|
|
149
|
+
console.log(colors.yellow('\n接下来会运行官方 openclaw onboard 命令'));
|
|
150
|
+
console.log(colors.yellow('自动模式会按以下推荐选择(可随时接管):\n'));
|
|
151
|
+
|
|
152
|
+
console.log(colors.cyan('┌─────────────────────────────────────────────────────────┐'));
|
|
153
|
+
console.log(colors.cyan('│') + colors.bold(' 步骤 1: 安全确认') + colors.cyan(' │'));
|
|
154
|
+
console.log(colors.cyan('│') + ' 看到 "Do you want to continue?" 时 ' + colors.cyan('│'));
|
|
155
|
+
console.log(colors.cyan('│') + colors.green(' → 输入 y 然后回车') + ' ' + colors.cyan('│'));
|
|
156
|
+
console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
|
|
157
|
+
console.log(colors.cyan('│') + colors.bold(' 步骤 2: Setup 模式') + colors.cyan(' │'));
|
|
158
|
+
console.log(colors.cyan('│') + ' 看到 "Quick Start" 和 "Advanced" 选项时 ' + colors.cyan('│'));
|
|
159
|
+
console.log(colors.cyan('│') + colors.green(' → 选择 Quick Start(通常是第 1 个)') + ' ' + colors.cyan('│'));
|
|
160
|
+
console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
|
|
161
|
+
console.log(colors.cyan('│') + colors.bold(' 步骤 3: Model Provider') + colors.cyan(' │'));
|
|
162
|
+
console.log(colors.cyan('│') + ' 看到选择 AI 模型提供商时 ' + colors.cyan('│'));
|
|
163
|
+
console.log(colors.cyan('│') + colors.green(' → 选择 Skip 或按 s 跳过') + ' ' + colors.cyan('│'));
|
|
164
|
+
console.log(colors.cyan('│') + colors.gray(' (后续用 npx openclawapi 单独配置更方便)') + ' ' + colors.cyan('│'));
|
|
165
|
+
console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
|
|
166
|
+
console.log(colors.cyan('│') + colors.bold(' 步骤 4: Channel 配置') + colors.cyan(' │'));
|
|
167
|
+
console.log(colors.cyan('│') + ' 看到选择聊天渠道(Telegram/Discord 等)时 ' + colors.cyan('│'));
|
|
168
|
+
console.log(colors.cyan('│') + colors.green(' → 选择 Skip 或按 s 跳过') + ' ' + colors.cyan('│'));
|
|
169
|
+
console.log(colors.cyan('│') + colors.gray(' (后续用 npx openclawdc 等单独配置)') + ' ' + colors.cyan('│'));
|
|
170
|
+
console.log(colors.cyan('├─────────────────────────────────────────────────────────┤'));
|
|
171
|
+
console.log(colors.cyan('│') + colors.bold(' 步骤 5: Daemon/Service') + colors.cyan(' │'));
|
|
172
|
+
console.log(colors.cyan('│') + ' 看到是否安装后台服务时 ' + colors.cyan('│'));
|
|
173
|
+
console.log(colors.cyan('│') + colors.green(' → 输入 y 确认安装(开机自启)') + ' ' + colors.cyan('│'));
|
|
174
|
+
console.log(colors.cyan('└─────────────────────────────────────────────────────────┘'));
|
|
175
|
+
|
|
176
|
+
console.log(colors.gray('\n其他选项可以直接回车使用默认值'));
|
|
177
|
+
console.log(colors.gray('如果不确定,选择 Skip 或直接回车通常是安全的\n'));
|
|
178
|
+
}
|
|
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 getOnboardCommand(cliName) {
|
|
188
|
+
if (platform() === 'win32') {
|
|
189
|
+
return { file: 'cmd.exe', args: ['/c', cliName, 'onboard', '--install-daemon'] };
|
|
190
|
+
}
|
|
191
|
+
return { file: cliName, args: ['onboard', '--install-daemon'] };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function waitForEnter(message) {
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
197
|
+
rl.question(colors.yellow(message), () => {
|
|
198
|
+
rl.close();
|
|
199
|
+
resolve();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============ 安装 OpenClaw ============
|
|
220
205
|
|
|
206
|
+
async function installOpenClaw() {
|
|
221
207
|
const useSudo = needsSudo();
|
|
208
|
+
|
|
209
|
+
console.log(colors.bold(colors.cyan('\n[1/2] 安装 OpenClaw CLI\n')));
|
|
210
|
+
|
|
222
211
|
if (useSudo) {
|
|
223
|
-
log.hint('
|
|
212
|
+
log.hint('Linux 系统需要 sudo 权限安装全局包');
|
|
213
|
+
console.log(colors.yellow('\n请运行以下命令:'));
|
|
214
|
+
console.log(colors.green(' sudo npm install -g openclaw@latest\n'));
|
|
215
|
+
|
|
216
|
+
await waitForEnter('安装完成后按回车继续...');
|
|
217
|
+
|
|
218
|
+
// 验证安装
|
|
219
|
+
const check = safeExec('openclaw --version');
|
|
220
|
+
if (!check.ok) {
|
|
221
|
+
log.error('未检测到 openclaw 命令,请确认安装成功');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
log.success(`OpenClaw 已安装: ${check.output}`);
|
|
225
|
+
return 'openclaw';
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const child = spawn(cmd, args, {
|
|
229
|
-
stdio: 'inherit',
|
|
230
|
-
shell: true,
|
|
231
|
-
});
|
|
228
|
+
// macOS 或有权限的 Linux:直接安装
|
|
229
|
+
console.log(colors.gray('正在安装 openclaw...\n'));
|
|
232
230
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
});
|
|
231
|
+
const result = spawnSync('npm', ['install', '-g', 'openclaw@latest'], {
|
|
232
|
+
stdio: 'inherit',
|
|
233
|
+
shell: true,
|
|
255
234
|
});
|
|
256
|
-
}
|
|
257
235
|
|
|
258
|
-
|
|
236
|
+
if (result.status === 0) {
|
|
237
|
+
log.success('OpenClaw CLI 安装完成');
|
|
238
|
+
return 'openclaw';
|
|
239
|
+
}
|
|
259
240
|
|
|
260
|
-
|
|
261
|
-
log.
|
|
241
|
+
// 尝试 clawdbot
|
|
242
|
+
log.warn('openclaw 安装失败,尝试 clawdbot...');
|
|
243
|
+
const fallback = spawnSync('npm', ['install', '-g', 'clawdbot@latest'], {
|
|
244
|
+
stdio: 'inherit',
|
|
245
|
+
shell: true,
|
|
246
|
+
});
|
|
262
247
|
|
|
263
|
-
if (
|
|
264
|
-
log.
|
|
265
|
-
|
|
266
|
-
console.log(colors.dim(' 智能模式:自动选择推荐配置,您可以观看安装过程\n'));
|
|
248
|
+
if (fallback.status === 0) {
|
|
249
|
+
log.success('clawdbot 安装完成');
|
|
250
|
+
return 'clawdbot';
|
|
267
251
|
}
|
|
268
252
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
253
|
+
log.error('安装失败');
|
|
254
|
+
console.log(colors.cyan('\n解决方案:'));
|
|
255
|
+
console.log(' 1. 检查网络连接');
|
|
256
|
+
console.log(' 2. 手动安装: npm install -g openclaw@latest');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ============ 运行 Onboard ============
|
|
261
|
+
|
|
262
|
+
async function runOnboard(cliName) {
|
|
263
|
+
console.log(colors.bold(colors.cyan('\n[2/2] 运行配置向导\n')));
|
|
264
|
+
|
|
265
|
+
// 显示指引
|
|
266
|
+
showInstallGuide();
|
|
267
|
+
|
|
268
|
+
await waitForEnter('准备好了吗?按回车开始配置...');
|
|
269
|
+
|
|
270
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
271
|
+
console.log(colors.gray('以下是官方 openclaw onboard 界面:'));
|
|
272
|
+
console.log(colors.gray('-'.repeat(60) + '\n'));
|
|
273
|
+
|
|
274
|
+
const options = parseArgs();
|
|
275
|
+
const preferAuto = !options.manual;
|
|
276
|
+
let usedAuto = false;
|
|
277
|
+
|
|
278
|
+
if (preferAuto) {
|
|
279
|
+
const autoResult = await runOnboardAuto(cliName, options);
|
|
280
|
+
if (autoResult.ok) {
|
|
281
|
+
usedAuto = true;
|
|
282
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
283
|
+
if (autoResult.exitCode !== 0) {
|
|
284
|
+
log.warn(`onboard 退出码: ${autoResult.exitCode}`);
|
|
285
|
+
log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
|
|
286
|
+
}
|
|
287
|
+
} else if (options.auto) {
|
|
288
|
+
log.error('自动模式不可用,已退出');
|
|
289
|
+
log.hint(autoResult.reason || '请尝试 --manual');
|
|
290
|
+
process.exit(1);
|
|
296
291
|
} else {
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
});
|
|
292
|
+
log.warn('自动模式不可用,已切换为手动模式');
|
|
293
|
+
log.hint(autoResult.reason || '未能启用自动应答');
|
|
306
294
|
}
|
|
295
|
+
}
|
|
307
296
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
297
|
+
if (!usedAuto) {
|
|
298
|
+
const manualResult = runOnboardManual(cliName);
|
|
299
|
+
console.log(colors.gray('\n' + '-'.repeat(60)));
|
|
300
|
+
if (manualResult.status !== 0) {
|
|
301
|
+
log.warn(`onboard 退出码: ${manualResult.status}`);
|
|
302
|
+
log.hint('如果配置未完成,可以手动运行: ' + cliName + ' onboard');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function runOnboardManual(cliName) {
|
|
308
|
+
const { file, args } = getOnboardCommand(cliName);
|
|
309
|
+
return spawnSync(file, args, {
|
|
310
|
+
stdio: 'inherit',
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function createAutoResponder(term, options) {
|
|
315
|
+
const lastSent = new Map();
|
|
316
|
+
let autoStopped = false;
|
|
317
|
+
let pausedNoticeShown = false;
|
|
318
|
+
|
|
319
|
+
const cooldownMs = 1200;
|
|
320
|
+
const shouldRespond = (id) => {
|
|
321
|
+
const now = Date.now();
|
|
322
|
+
const last = lastSent.get(id) || 0;
|
|
323
|
+
if (now - last < cooldownMs) return false;
|
|
324
|
+
lastSent.set(id, now);
|
|
325
|
+
return true;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const stopAuto = (reason) => {
|
|
329
|
+
if (autoStopped) return;
|
|
330
|
+
autoStopped = true;
|
|
331
|
+
log.warn(reason);
|
|
332
|
+
log.hint('已进入手动模式');
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const send = (id, payload) => {
|
|
336
|
+
if (!shouldRespond(id)) return false;
|
|
337
|
+
term.write(payload);
|
|
338
|
+
return true;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (rawText) => {
|
|
342
|
+
if (autoStopped) return;
|
|
343
|
+
const text = rawText.toLowerCase();
|
|
344
|
+
const tail = text.slice(-800);
|
|
345
|
+
|
|
346
|
+
if (tail.includes('do you want to continue') || tail.includes('continue?') || tail.includes('是否继续')) {
|
|
347
|
+
send('confirm', 'y\r');
|
|
317
348
|
return;
|
|
318
349
|
}
|
|
319
350
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
}
|
|
351
|
+
if ((tail.includes('quick start') || tail.includes('quickstart')) && (tail.includes('advanced') || tail.includes('custom'))) {
|
|
352
|
+
send('setup', '\r');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const modelPrompt =
|
|
357
|
+
tail.includes('model provider') ||
|
|
358
|
+
tail.includes('model providers') ||
|
|
359
|
+
tail.includes('select a model') ||
|
|
360
|
+
tail.includes('模型') && tail.includes('提供商');
|
|
361
|
+
|
|
362
|
+
if (modelPrompt) {
|
|
363
|
+
if (options.withModel) {
|
|
364
|
+
stopAuto('检测到模型配置提示,已暂停自动选择(--with-model)');
|
|
365
|
+
return;
|
|
353
366
|
}
|
|
367
|
+
send('model-skip', tail.includes('skip') || tail.includes('跳过') ? 's\r' : '\r');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const apiKeyPrompt =
|
|
372
|
+
tail.includes('api key') ||
|
|
373
|
+
tail.includes('apikey') ||
|
|
374
|
+
(tail.includes('key') && tail.includes('请输入'));
|
|
354
375
|
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
|
|
376
|
+
if (apiKeyPrompt) {
|
|
377
|
+
if (options.withModel) {
|
|
378
|
+
stopAuto('检测到 API Key 配置,已暂停自动选择(--with-model)');
|
|
379
|
+
return;
|
|
358
380
|
}
|
|
359
|
-
|
|
381
|
+
send('api-skip', tail.includes('skip') || tail.includes('跳过') ? 's\r' : '\r');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
360
384
|
|
|
361
|
-
|
|
362
|
-
|
|
385
|
+
const channelPrompt =
|
|
386
|
+
tail.includes('channel') ||
|
|
387
|
+
tail.includes('discord') ||
|
|
388
|
+
tail.includes('telegram') ||
|
|
389
|
+
tail.includes('feishu') ||
|
|
390
|
+
(tail.includes('渠道') && (tail.includes('选择') || tail.includes('配置')));
|
|
391
|
+
|
|
392
|
+
if (channelPrompt) {
|
|
393
|
+
if (options.withChannel) {
|
|
394
|
+
stopAuto('检测到渠道配置提示,已暂停自动选择(--with-channel)');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
send('channel-skip', 's\r');
|
|
398
|
+
return;
|
|
363
399
|
}
|
|
364
|
-
|
|
365
|
-
|
|
400
|
+
|
|
401
|
+
if (tail.includes('skills') || tail.includes('skill')) {
|
|
402
|
+
send('skills-skip', 's\r');
|
|
403
|
+
return;
|
|
366
404
|
}
|
|
367
405
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
406
|
+
const daemonPrompt =
|
|
407
|
+
tail.includes('daemon') ||
|
|
408
|
+
tail.includes('service') && (tail.includes('install') || tail.includes('enable')) ||
|
|
409
|
+
(tail.includes('后台') && tail.includes('服务')) ||
|
|
410
|
+
tail.includes('开机') && tail.includes('自启');
|
|
411
|
+
|
|
412
|
+
if (daemonPrompt) {
|
|
413
|
+
send('daemon-yes', 'y\r');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const uiPrompt =
|
|
418
|
+
tail.includes('dashboard') ||
|
|
419
|
+
tail.includes('web ui') ||
|
|
420
|
+
tail.includes('web dashboard') ||
|
|
421
|
+
(tail.includes('ui') && tail.includes('选择')) ||
|
|
422
|
+
(tail.includes('界面') && tail.includes('选择'));
|
|
423
|
+
|
|
424
|
+
if (uiPrompt) {
|
|
425
|
+
send('ui-web', tail.includes('web') || tail.includes('dashboard') ? '\r' : 'w\r');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (tail.includes('port') && (tail.includes('gateway') || tail.includes('端口'))) {
|
|
430
|
+
send('port-default', '\r');
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!pausedNoticeShown && (options.withModel || options.withChannel)) {
|
|
435
|
+
pausedNoticeShown = true;
|
|
436
|
+
log.hint('检测到 --with-model / --with-channel,将在相关步骤暂停自动选择');
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function runOnboardAuto(cliName, options) {
|
|
442
|
+
let ptyModule;
|
|
443
|
+
try {
|
|
444
|
+
ptyModule = await import('node-pty');
|
|
445
|
+
} catch (e) {
|
|
446
|
+
return { ok: false, reason: 'node-pty 依赖不可用,请尝试 --manual' };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const pty = ptyModule.default ?? ptyModule;
|
|
450
|
+
const spawn = pty?.spawn;
|
|
451
|
+
if (typeof spawn !== 'function') {
|
|
452
|
+
return { ok: false, reason: 'node-pty 未正确加载,请尝试 --manual' };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const { file, args } = getOnboardCommand(cliName);
|
|
456
|
+
const cols = process.stdout.columns || 80;
|
|
457
|
+
const rows = process.stdout.rows || 24;
|
|
458
|
+
const env = { ...process.env, FORCE_COLOR: '1' };
|
|
459
|
+
|
|
460
|
+
let term;
|
|
461
|
+
try {
|
|
462
|
+
term = spawn(file, args, {
|
|
463
|
+
name: 'xterm-256color',
|
|
464
|
+
cols,
|
|
465
|
+
rows,
|
|
466
|
+
cwd: process.cwd(),
|
|
467
|
+
env,
|
|
377
468
|
});
|
|
469
|
+
} catch (e) {
|
|
470
|
+
return { ok: false, reason: `启动 PTY 失败: ${e.message}` };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const autoResponder = createAutoResponder(term, options);
|
|
474
|
+
let buffer = '';
|
|
475
|
+
let manualOverride = false;
|
|
476
|
+
|
|
477
|
+
log.info('自动模式已开启,按任意键可接管');
|
|
478
|
+
|
|
479
|
+
const stdin = process.stdin;
|
|
480
|
+
const canReadInput = Boolean(stdin.isTTY);
|
|
481
|
+
const restoreRaw = canReadInput ? stdin.isRaw : false;
|
|
482
|
+
|
|
483
|
+
const onUserData = (data) => {
|
|
484
|
+
if (!manualOverride) {
|
|
485
|
+
manualOverride = true;
|
|
486
|
+
log.warn('已接管,自动模式停止');
|
|
487
|
+
}
|
|
488
|
+
term.write(data.toString('utf8'));
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
if (canReadInput && typeof stdin.setRawMode === 'function') {
|
|
492
|
+
stdin.setRawMode(true);
|
|
493
|
+
}
|
|
494
|
+
if (canReadInput) {
|
|
495
|
+
stdin.on('data', onUserData);
|
|
496
|
+
stdin.resume();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
term.onData((data) => {
|
|
500
|
+
process.stdout.write(data);
|
|
501
|
+
if (manualOverride) return;
|
|
502
|
+
buffer += stripAnsi(data);
|
|
503
|
+
if (buffer.length > 4000) {
|
|
504
|
+
buffer = buffer.slice(-4000);
|
|
505
|
+
}
|
|
506
|
+
autoResponder(buffer);
|
|
507
|
+
});
|
|
378
508
|
|
|
379
|
-
|
|
380
|
-
|
|
509
|
+
return await new Promise((resolve) => {
|
|
510
|
+
term.onExit(({ exitCode }) => {
|
|
511
|
+
if (canReadInput) {
|
|
512
|
+
stdin.off('data', onUserData);
|
|
513
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
514
|
+
stdin.setRawMode(Boolean(restoreRaw));
|
|
515
|
+
}
|
|
516
|
+
stdin.pause();
|
|
517
|
+
}
|
|
518
|
+
resolve({ ok: true, exitCode });
|
|
381
519
|
});
|
|
382
520
|
});
|
|
383
521
|
}
|
|
@@ -385,53 +523,48 @@ async function runOnboardWithAutoResponse(cliName, options) {
|
|
|
385
523
|
// ============ 更新 ============
|
|
386
524
|
|
|
387
525
|
async function updateOpenClaw(cliName) {
|
|
388
|
-
log.
|
|
526
|
+
console.log(colors.bold(colors.cyan('\n检查更新...\n')));
|
|
389
527
|
|
|
390
528
|
const currentResult = safeExec(`${cliName} --version`);
|
|
391
529
|
const currentVersion = currentResult.ok ? currentResult.output : '未知';
|
|
392
|
-
console.log(
|
|
530
|
+
console.log(`当前版本: ${colors.yellow(currentVersion)}`);
|
|
393
531
|
|
|
394
|
-
|
|
395
|
-
|
|
532
|
+
const useSudo = needsSudo();
|
|
533
|
+
|
|
534
|
+
if (useSudo) {
|
|
535
|
+
console.log(colors.yellow('\n请运行以下命令更新:'));
|
|
536
|
+
console.log(colors.green(` sudo npm install -g ${cliName}@latest`));
|
|
537
|
+
console.log(colors.green(` ${cliName} gateway restart\n`));
|
|
538
|
+
} else {
|
|
539
|
+
console.log(colors.gray('\n正在更新...\n'));
|
|
540
|
+
spawnSync('npm', ['install', '-g', `${cliName}@latest`], {
|
|
396
541
|
stdio: 'inherit',
|
|
397
542
|
shell: true,
|
|
398
543
|
});
|
|
399
544
|
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
}
|
|
545
|
+
const newResult = safeExec(`${cliName} --version`);
|
|
546
|
+
const newVersion = newResult.ok ? newResult.output : '未知';
|
|
409
547
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
log.hint(`手动更新: npm install -g ${cliName}@latest`);
|
|
421
|
-
}
|
|
422
|
-
resolve();
|
|
423
|
-
});
|
|
424
|
-
});
|
|
548
|
+
if (newVersion !== currentVersion) {
|
|
549
|
+
log.success(`更新完成: ${currentVersion} → ${newVersion}`);
|
|
550
|
+
} else {
|
|
551
|
+
log.success(`已是最新版本: ${newVersion}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
console.log(colors.gray('\n重启 Gateway...'));
|
|
555
|
+
safeExec(`${cliName} gateway restart`);
|
|
556
|
+
log.success('Gateway 已重启');
|
|
557
|
+
}
|
|
425
558
|
}
|
|
426
559
|
|
|
427
560
|
// ============ 完成信息 ============
|
|
428
561
|
|
|
429
562
|
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('
|
|
563
|
+
console.log(colors.bold(colors.green('\n' + '='.repeat(60))));
|
|
564
|
+
console.log(colors.bold(colors.green(' ✅ OpenClaw 安装完成!')));
|
|
565
|
+
console.log(colors.bold(colors.green('='.repeat(60))));
|
|
433
566
|
|
|
434
|
-
console.log(colors.cyan('\n下一步 - 配置 AI
|
|
567
|
+
console.log(colors.cyan('\n下一步 - 配置 AI 模型(必须):'));
|
|
435
568
|
console.log(` ${colors.yellow('npx openclawapi@latest preset-claude')}`);
|
|
436
569
|
|
|
437
570
|
console.log(colors.cyan('\n常用命令:'));
|
|
@@ -440,7 +573,7 @@ function showCompletionInfo(cliName) {
|
|
|
440
573
|
console.log(` 重启服务: ${colors.yellow(`${cliName} gateway restart`)}`);
|
|
441
574
|
console.log(` 诊断问题: ${colors.yellow(`${cliName} doctor`)}`);
|
|
442
575
|
|
|
443
|
-
console.log(colors.cyan('\n
|
|
576
|
+
console.log(colors.cyan('\n配置聊天渠道(可选):'));
|
|
444
577
|
console.log(` Discord: ${colors.yellow('npx openclawdc')}`);
|
|
445
578
|
console.log(` 飞书: ${colors.yellow('npx openclaw-chat-cn@latest feishu')}`);
|
|
446
579
|
|
|
@@ -457,7 +590,7 @@ async function main() {
|
|
|
457
590
|
process.exit(0);
|
|
458
591
|
}
|
|
459
592
|
|
|
460
|
-
console.log(colors.bold(colors.cyan('\n🦞 OpenClaw
|
|
593
|
+
console.log(colors.bold(colors.cyan('\n🦞 OpenClaw 安装向导\n')));
|
|
461
594
|
|
|
462
595
|
// 检查 Node 版本
|
|
463
596
|
checkNodeVersion();
|
|
@@ -477,10 +610,18 @@ async function main() {
|
|
|
477
610
|
if (options.reinstall) {
|
|
478
611
|
log.info('\n卸载现有安装...');
|
|
479
612
|
safeExec(`${existing.name} gateway stop`);
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
log.
|
|
613
|
+
|
|
614
|
+
if (needsSudo()) {
|
|
615
|
+
console.log(colors.yellow('\n请运行以下命令卸载:'));
|
|
616
|
+
console.log(colors.green(` sudo npm uninstall -g ${existing.name}`));
|
|
617
|
+
console.log(colors.green(` rm -rf ~/.openclaw ~/.clawdbot\n`));
|
|
618
|
+
await waitForEnter('卸载完成后按回车继续...');
|
|
619
|
+
} else {
|
|
620
|
+
spawnSync('npm', ['uninstall', '-g', existing.name], { stdio: 'inherit', shell: true });
|
|
621
|
+
if (existing.configDir && existsSync(existing.configDir)) {
|
|
622
|
+
rmSync(existing.configDir, { recursive: true, force: true });
|
|
623
|
+
}
|
|
624
|
+
log.success('卸载完成');
|
|
484
625
|
}
|
|
485
626
|
} else {
|
|
486
627
|
console.log(colors.cyan('\n已安装,可选操作:'));
|
|
@@ -492,22 +633,14 @@ async function main() {
|
|
|
492
633
|
}
|
|
493
634
|
}
|
|
494
635
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const cliName = await installOpenClaw();
|
|
498
|
-
|
|
499
|
-
// 运行 onboard
|
|
500
|
-
await runOnboardWithAutoResponse(cliName, options);
|
|
636
|
+
// 安装 CLI
|
|
637
|
+
const cliName = await installOpenClaw();
|
|
501
638
|
|
|
502
|
-
|
|
503
|
-
|
|
639
|
+
// 运行 onboard
|
|
640
|
+
await runOnboard(cliName);
|
|
504
641
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
'检查网络连接',
|
|
508
|
-
'手动安装: npm install -g openclaw && openclaw onboard --install-daemon',
|
|
509
|
-
]);
|
|
510
|
-
}
|
|
642
|
+
// 显示完成信息
|
|
643
|
+
showCompletionInfo(cliName);
|
|
511
644
|
}
|
|
512
645
|
|
|
513
646
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclawsetup",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "OpenClaw
|
|
3
|
+
"version": "2.1.1",
|
|
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,7 @@
|
|
|
10
10
|
npx openclawsetup@latest
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
你会看到官方的 `openclaw onboard`
|
|
13
|
+
你会看到官方的 `openclaw onboard` 界面,但所有选项会自动选择推荐配置(按任意键可接管)。
|
|
14
14
|
|
|
15
15
|
### 方式二:手动模式
|
|
16
16
|
|
|
@@ -20,7 +20,15 @@ npx openclawsetup@latest
|
|
|
20
20
|
npx openclawsetup@latest --manual
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### 方式三:强制自动模式
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx openclawsetup@latest --auto
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
自动应答不可用时将直接退出(适合脚本化场景)。
|
|
30
|
+
|
|
31
|
+
### 方式四:一键脚本
|
|
24
32
|
|
|
25
33
|
自动检测并安装 Node.js,无需任何前置条件。
|
|
26
34
|
|
|
@@ -49,6 +57,10 @@ irm https://unpkg.com/openclawsetup@latest/install.ps1 | iex
|
|
|
49
57
|
| Daemon/Service | 安装 (y) |
|
|
50
58
|
| UI 选择 | Web Dashboard |
|
|
51
59
|
|
|
60
|
+
> 需要在安装过程中配置模型或渠道时,可使用:
|
|
61
|
+
> - `--with-model`:检测到模型配置步骤时暂停自动选择
|
|
62
|
+
> - `--with-channel`:检测到渠道配置步骤时暂停自动选择
|
|
63
|
+
|
|
52
64
|
**为什么跳过模型和渠道?**
|
|
53
65
|
- 模型配置:后续用 `npx openclawapi` 单独配置,更灵活
|
|
54
66
|
- 渠道配置:后续用 `npx openclawdc`(Discord)或 `npx openclaw-chat-cn@latest feishu`(飞书)配置
|
|
@@ -142,13 +154,17 @@ curl -fsSL https://unpkg.com/openclawsetup@latest/install.sh | bash
|
|
|
142
154
|
|
|
143
155
|
### 自动选择没有生效
|
|
144
156
|
**现象**:安装过程中需要手动输入
|
|
145
|
-
|
|
157
|
+
**原因**:可能是终端环境问题或自动应答依赖不可用
|
|
146
158
|
**解决**:
|
|
147
159
|
1. 使用手动模式:
|
|
148
160
|
```bash
|
|
149
161
|
npx openclawsetup@latest --manual
|
|
150
162
|
```
|
|
151
|
-
2.
|
|
163
|
+
2. 需要脚本化时使用强制自动模式(不可用会退出):
|
|
164
|
+
```bash
|
|
165
|
+
npx openclawsetup@latest --auto
|
|
166
|
+
```
|
|
167
|
+
3. 或直接运行官方命令:
|
|
152
168
|
```bash
|
|
153
169
|
npm install -g openclaw@latest
|
|
154
170
|
openclaw onboard --install-daemon
|