openclawsetup 1.0.6 → 1.0.8
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/cli.mjs +256 -56
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { execSync, spawn } from 'child_process';
|
|
21
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
21
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
|
22
22
|
import { homedir, platform } from 'os';
|
|
23
23
|
import { join } from 'path';
|
|
24
24
|
import { randomBytes } from 'crypto';
|
|
25
|
+
import { createInterface } from 'readline';
|
|
25
26
|
|
|
26
27
|
// 解析命令行参数
|
|
27
28
|
function parseArgs() {
|
|
@@ -31,6 +32,8 @@ function parseArgs() {
|
|
|
31
32
|
token: process.env.GATEWAY_TOKEN || '',
|
|
32
33
|
skipDaemon: process.env.SKIP_DAEMON === 'true' || process.env.SKIP_DAEMON === '1',
|
|
33
34
|
skipStart: false,
|
|
35
|
+
update: false,
|
|
36
|
+
reinstall: false,
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -47,6 +50,12 @@ function parseArgs() {
|
|
|
47
50
|
case '--skip-start':
|
|
48
51
|
result.skipStart = true;
|
|
49
52
|
break;
|
|
53
|
+
case '--update':
|
|
54
|
+
result.update = true;
|
|
55
|
+
break;
|
|
56
|
+
case '--reinstall':
|
|
57
|
+
result.reinstall = true;
|
|
58
|
+
break;
|
|
50
59
|
case '--help':
|
|
51
60
|
case '-h':
|
|
52
61
|
showHelp();
|
|
@@ -69,6 +78,8 @@ OpenClaw 一键安装工具
|
|
|
69
78
|
--token <token> 指定 Gateway Token (默认: 自动生成)
|
|
70
79
|
--skip-daemon 跳过服务持久化配置
|
|
71
80
|
--skip-start 安装后不启动服务
|
|
81
|
+
--update 检查并更新已安装的 OpenClaw
|
|
82
|
+
--reinstall 卸载后重新安装(会清除配置)
|
|
72
83
|
--help, -h 显示帮助信息
|
|
73
84
|
|
|
74
85
|
环境变量:
|
|
@@ -80,6 +91,12 @@ OpenClaw 一键安装工具
|
|
|
80
91
|
# 默认安装
|
|
81
92
|
npx openclawsetup
|
|
82
93
|
|
|
94
|
+
# 检查并更新
|
|
95
|
+
npx openclawsetup --update
|
|
96
|
+
|
|
97
|
+
# 卸载后重新安装
|
|
98
|
+
npx openclawsetup --reinstall
|
|
99
|
+
|
|
83
100
|
# 指定端口和 Token
|
|
84
101
|
npx openclawsetup --port 8080 --token mytoken123
|
|
85
102
|
|
|
@@ -167,6 +184,160 @@ function checkNodeVersion() {
|
|
|
167
184
|
return true;
|
|
168
185
|
}
|
|
169
186
|
|
|
187
|
+
// 交互式提示用户选择
|
|
188
|
+
function promptChoice(question, choices) {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
// 检测是否是交互式终端
|
|
191
|
+
if (!process.stdin.isTTY) {
|
|
192
|
+
// 非交互式环境(如 curl | bash),显示选项但不等待输入
|
|
193
|
+
console.log(colors.cyan(`\n${question}`));
|
|
194
|
+
choices.forEach((choice, i) => {
|
|
195
|
+
console.log(` ${colors.yellow(i + 1)}. ${choice.label}`);
|
|
196
|
+
});
|
|
197
|
+
console.log(` ${colors.yellow(0)}. 退出`);
|
|
198
|
+
console.log(colors.gray('\n检测到非交互式环境,请直接运行以下命令进行操作:'));
|
|
199
|
+
console.log(colors.yellow(` npx openclawsetup --update`) + colors.gray(' # 检查并更新'));
|
|
200
|
+
console.log(colors.yellow(` npx openclawsetup --reinstall`) + colors.gray(' # 卸载后重新安装'));
|
|
201
|
+
resolve(null);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const rl = createInterface({
|
|
206
|
+
input: process.stdin,
|
|
207
|
+
output: process.stdout,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
console.log(colors.cyan(`\n${question}`));
|
|
211
|
+
choices.forEach((choice, i) => {
|
|
212
|
+
console.log(` ${colors.yellow(i + 1)}. ${choice.label}`);
|
|
213
|
+
});
|
|
214
|
+
console.log(` ${colors.yellow(0)}. 退出`);
|
|
215
|
+
|
|
216
|
+
rl.question(colors.cyan('\n请输入选项编号: '), (answer) => {
|
|
217
|
+
rl.close();
|
|
218
|
+
const num = parseInt(answer, 10);
|
|
219
|
+
if (num === 0 || isNaN(num) || num > choices.length) {
|
|
220
|
+
resolve(null);
|
|
221
|
+
} else {
|
|
222
|
+
resolve(choices[num - 1].value);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 检查并更新 OpenClaw
|
|
229
|
+
async function checkAndUpdate(cliName) {
|
|
230
|
+
console.log(colors.cyan('\n检查更新中...'));
|
|
231
|
+
|
|
232
|
+
// 获取当前版本
|
|
233
|
+
const currentResult = safeExec(`${cliName} --version`);
|
|
234
|
+
const currentVersion = currentResult.ok ? currentResult.output : '未知';
|
|
235
|
+
console.log(` 当前版本: ${colors.yellow(currentVersion)}`);
|
|
236
|
+
|
|
237
|
+
// 更新到最新版本
|
|
238
|
+
log.info('正在更新到最新版本...');
|
|
239
|
+
const updateResult = await execWithProgress(
|
|
240
|
+
`npm install -g ${cliName}@latest`,
|
|
241
|
+
`正在更新 ${cliName}...`,
|
|
242
|
+
{ timeout: 300000 }
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (updateResult.ok) {
|
|
246
|
+
const newResult = safeExec(`${cliName} --version`);
|
|
247
|
+
const newVersion = newResult.ok ? newResult.output : '未知';
|
|
248
|
+
if (newVersion !== currentVersion) {
|
|
249
|
+
log.success(`更新完成: ${currentVersion} → ${newVersion}`);
|
|
250
|
+
} else {
|
|
251
|
+
log.success(`已是最新版本: ${newVersion}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 重启 Gateway
|
|
255
|
+
console.log(colors.cyan('\n重启 Gateway 服务...'));
|
|
256
|
+
const restartResult = safeExec(`${cliName} gateway restart`);
|
|
257
|
+
if (restartResult.ok) {
|
|
258
|
+
log.success('Gateway 已重启');
|
|
259
|
+
} else {
|
|
260
|
+
log.warn('Gateway 重启失败,请手动重启: ' + cliName + ' gateway restart');
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
log.error('更新失败: ' + (updateResult.error || '未知错误'));
|
|
264
|
+
log.hint('可以手动更新: npm install -g ' + cliName + '@latest');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 卸载并重新安装
|
|
269
|
+
async function uninstallAndReinstall(existing, options) {
|
|
270
|
+
console.log(colors.cyan('\n开始卸载...'));
|
|
271
|
+
|
|
272
|
+
// 停止服务
|
|
273
|
+
log.info('停止 Gateway 服务...');
|
|
274
|
+
safeExec(`${existing.name} gateway stop`);
|
|
275
|
+
|
|
276
|
+
// 卸载 npm 包
|
|
277
|
+
log.info('卸载 npm 包...');
|
|
278
|
+
const uninstallResult = await execWithProgress(
|
|
279
|
+
`npm uninstall -g ${existing.name}`,
|
|
280
|
+
`正在卸载 ${existing.name}...`,
|
|
281
|
+
{ timeout: 60000 }
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (uninstallResult.ok) {
|
|
285
|
+
log.success('npm 包已卸载');
|
|
286
|
+
} else {
|
|
287
|
+
log.warn('npm 卸载可能未完成: ' + (uninstallResult.error || ''));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 删除配置目录
|
|
291
|
+
if (existing.configDir && existsSync(existing.configDir)) {
|
|
292
|
+
log.info('删除配置目录...');
|
|
293
|
+
try {
|
|
294
|
+
rmSync(existing.configDir, { recursive: true, force: true });
|
|
295
|
+
log.success('配置目录已删除');
|
|
296
|
+
} catch (e) {
|
|
297
|
+
log.warn('配置目录删除失败: ' + e.message);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 删除服务持久化配置
|
|
302
|
+
const os = platform();
|
|
303
|
+
if (os === 'darwin') {
|
|
304
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'com.openclaw.gateway.plist');
|
|
305
|
+
if (existsSync(plistPath)) {
|
|
306
|
+
safeExec('launchctl unload ' + plistPath);
|
|
307
|
+
try {
|
|
308
|
+
rmSync(plistPath);
|
|
309
|
+
log.success('macOS 服务配置已删除');
|
|
310
|
+
} catch (e) {}
|
|
311
|
+
}
|
|
312
|
+
} else if (os === 'linux') {
|
|
313
|
+
safeExec('systemctl --user stop openclaw');
|
|
314
|
+
safeExec('systemctl --user disable openclaw');
|
|
315
|
+
const servicePath = join(homedir(), '.config', 'systemd', 'user', 'openclaw.service');
|
|
316
|
+
if (existsSync(servicePath)) {
|
|
317
|
+
try {
|
|
318
|
+
rmSync(servicePath);
|
|
319
|
+
log.success('Linux 服务配置已删除');
|
|
320
|
+
} catch (e) {}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(colors.green('\n✓ 卸载完成,开始重新安装...\n'));
|
|
325
|
+
|
|
326
|
+
// 重新安装
|
|
327
|
+
const cliName = await installOpenClaw();
|
|
328
|
+
const config = createConfig(options);
|
|
329
|
+
|
|
330
|
+
if (!options.skipDaemon) {
|
|
331
|
+
setupDaemon(cliName);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!options.skipStart) {
|
|
335
|
+
startService(cliName);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { cliName, config };
|
|
339
|
+
}
|
|
340
|
+
|
|
170
341
|
// 检测现有安装
|
|
171
342
|
function detectExistingInstall() {
|
|
172
343
|
const home = homedir();
|
|
@@ -535,6 +706,57 @@ function startService(cliName) {
|
|
|
535
706
|
return false;
|
|
536
707
|
}
|
|
537
708
|
|
|
709
|
+
// 显示安装完成信息
|
|
710
|
+
function showCompletionInfo(cliName, config, isVPS) {
|
|
711
|
+
console.log(colors.bold(colors.green('\n========================================')));
|
|
712
|
+
console.log(colors.bold(colors.green('✅ OpenClaw 安装完成!')));
|
|
713
|
+
console.log(colors.bold(colors.green('========================================')));
|
|
714
|
+
|
|
715
|
+
console.log(colors.cyan('\n配置信息:'));
|
|
716
|
+
console.log(` 配置目录: ${colors.yellow(config.configDir)}`);
|
|
717
|
+
console.log(` Gateway 端口: ${colors.yellow(config.port)}`);
|
|
718
|
+
console.log(` Gateway Token: ${colors.yellow(config.token)}`);
|
|
719
|
+
|
|
720
|
+
// 根据环境显示不同的访问说明
|
|
721
|
+
if (isVPS) {
|
|
722
|
+
console.log(colors.cyan('\n📡 Dashboard 访问 (云服务器):'));
|
|
723
|
+
console.log(colors.gray(' Gateway 默认绑定 127.0.0.1,外部无法直接访问(安全设计)'));
|
|
724
|
+
console.log('');
|
|
725
|
+
console.log(colors.yellow(' 方式一:SSH 隧道(推荐,安全)'));
|
|
726
|
+
console.log(colors.gray(' 在本地电脑执行以下命令,保持终端窗口打开:'));
|
|
727
|
+
console.log(` ssh -N -L ${config.port}:127.0.0.1:${config.port} root@<服务器IP>`);
|
|
728
|
+
console.log(colors.gray(' 然后在本地浏览器访问:'));
|
|
729
|
+
console.log(` ${colors.green(`http://127.0.0.1:${config.port}/?token=${config.token}`)}`);
|
|
730
|
+
console.log('');
|
|
731
|
+
console.log(colors.yellow(' 方式二:直接暴露端口(不推荐,有安全风险)'));
|
|
732
|
+
console.log(colors.gray(' 1. 修改配置文件 ~/.openclaw/openclaw.json'));
|
|
733
|
+
console.log(colors.gray(' 将 "bind": "loopback" 改为 "bind": "all"'));
|
|
734
|
+
console.log(colors.gray(' 2. 在云服务器控制台开放端口 ' + config.port));
|
|
735
|
+
console.log(colors.gray(' - 腾讯云:安全组 → 入站规则 → 添加 TCP ' + config.port));
|
|
736
|
+
console.log(colors.gray(' - 阿里云:安全组 → 配置规则 → 添加 TCP ' + config.port));
|
|
737
|
+
console.log(colors.gray(' 3. 重启 Gateway:' + cliName + ' gateway restart'));
|
|
738
|
+
console.log(colors.gray(' 4. 访问:http://<服务器IP>:' + config.port + '/?token=...'));
|
|
739
|
+
} else {
|
|
740
|
+
console.log(colors.cyan('\nDashboard 访问:'));
|
|
741
|
+
console.log(` ${colors.yellow(`http://127.0.0.1:${config.port}/?token=${config.token}`)}`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
console.log(colors.cyan('\n下一步 - 配置 AI 模型:'));
|
|
745
|
+
console.log(` ${colors.yellow('npx openclawapi@latest preset-claude')}`);
|
|
746
|
+
|
|
747
|
+
console.log(colors.cyan('\n常用命令:'));
|
|
748
|
+
console.log(` 查看状态: ${colors.yellow(`${cliName} status`)}`);
|
|
749
|
+
console.log(` 查看日志: ${colors.yellow(`${cliName} gateway logs`)}`);
|
|
750
|
+
console.log(` 重启服务: ${colors.yellow(`${cliName} gateway restart`)}`);
|
|
751
|
+
console.log(` 诊断问题: ${colors.yellow(`${cliName} doctor`)}`);
|
|
752
|
+
|
|
753
|
+
console.log(colors.cyan('\n配置聊天渠道:'));
|
|
754
|
+
console.log(` Discord: ${colors.yellow('npx openclawdc')}`);
|
|
755
|
+
console.log(` 飞书: ${colors.yellow('npx openclawfs')}`);
|
|
756
|
+
|
|
757
|
+
console.log('');
|
|
758
|
+
}
|
|
759
|
+
|
|
538
760
|
// 检测是否在云服务器/VPS 上运行
|
|
539
761
|
function detectVPS() {
|
|
540
762
|
// 检查常见的云服务器特征
|
|
@@ -626,11 +848,37 @@ async function main() {
|
|
|
626
848
|
console.log(` Discord: ${colors.yellow('npx openclawdc')}`);
|
|
627
849
|
console.log(` 飞书: ${colors.yellow('npx openclawfs')}`);
|
|
628
850
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
851
|
+
// 如果指定了 --update 或 --reinstall 参数,直接执行
|
|
852
|
+
if (options.update) {
|
|
853
|
+
await checkAndUpdate(existing.name);
|
|
854
|
+
console.log('');
|
|
855
|
+
process.exit(0);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (options.reinstall) {
|
|
859
|
+
const result = await uninstallAndReinstall(existing, options);
|
|
860
|
+
showCompletionInfo(result.cliName, result.config, isVPS);
|
|
861
|
+
process.exit(0);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 交互式选择
|
|
865
|
+
const choice = await promptChoice('请选择操作:', [
|
|
866
|
+
{ label: '检查并更新 OpenClaw', value: 'update' },
|
|
867
|
+
{ label: '卸载后重新安装(会清除配置)', value: 'reinstall' },
|
|
868
|
+
]);
|
|
869
|
+
|
|
870
|
+
if (choice === 'update') {
|
|
871
|
+
await checkAndUpdate(existing.name);
|
|
872
|
+
console.log('');
|
|
873
|
+
process.exit(0);
|
|
874
|
+
} else if (choice === 'reinstall') {
|
|
875
|
+
const result = await uninstallAndReinstall(existing, options);
|
|
876
|
+
showCompletionInfo(result.cliName, result.config, isVPS);
|
|
877
|
+
process.exit(0);
|
|
878
|
+
} else {
|
|
879
|
+
console.log(colors.gray('\n已退出'));
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
634
882
|
}
|
|
635
883
|
|
|
636
884
|
// 安装 CLI
|
|
@@ -653,57 +901,9 @@ async function main() {
|
|
|
653
901
|
log.info('跳过服务启动 (--skip-start)');
|
|
654
902
|
}
|
|
655
903
|
|
|
656
|
-
// 检测是否在 VPS
|
|
904
|
+
// 检测是否在 VPS 上并显示完成信息
|
|
657
905
|
const isVPS = detectVPS();
|
|
658
|
-
|
|
659
|
-
// 完成
|
|
660
|
-
console.log(colors.bold(colors.green('\n========================================')));
|
|
661
|
-
console.log(colors.bold(colors.green('✅ OpenClaw 安装完成!')));
|
|
662
|
-
console.log(colors.bold(colors.green('========================================')));
|
|
663
|
-
|
|
664
|
-
console.log(colors.cyan('\n配置信息:'));
|
|
665
|
-
console.log(` 配置目录: ${colors.yellow(config.configDir)}`);
|
|
666
|
-
console.log(` Gateway 端口: ${colors.yellow(config.port)}`);
|
|
667
|
-
console.log(` Gateway Token: ${colors.yellow(config.token)}`);
|
|
668
|
-
|
|
669
|
-
// 根据环境显示不同的访问说明
|
|
670
|
-
if (isVPS) {
|
|
671
|
-
console.log(colors.cyan('\n📡 Dashboard 访问 (云服务器):'));
|
|
672
|
-
console.log(colors.gray(' Gateway 默认绑定 127.0.0.1,外部无法直接访问(安全设计)'));
|
|
673
|
-
console.log('');
|
|
674
|
-
console.log(colors.yellow(' 方式一:SSH 隧道(推荐,安全)'));
|
|
675
|
-
console.log(colors.gray(' 在本地电脑执行以下命令,保持终端窗口打开:'));
|
|
676
|
-
console.log(` ssh -N -L ${config.port}:127.0.0.1:${config.port} root@<服务器IP>`);
|
|
677
|
-
console.log(colors.gray(' 然后在本地浏览器访问:'));
|
|
678
|
-
console.log(` ${colors.green(`http://127.0.0.1:${config.port}/?token=${config.token}`)}`);
|
|
679
|
-
console.log('');
|
|
680
|
-
console.log(colors.yellow(' 方式二:直接暴露端口(不推荐,有安全风险)'));
|
|
681
|
-
console.log(colors.gray(' 1. 修改配置文件 ~/.openclaw/openclaw.json'));
|
|
682
|
-
console.log(colors.gray(' 将 "bind": "loopback" 改为 "bind": "all"'));
|
|
683
|
-
console.log(colors.gray(' 2. 在云服务器控制台开放端口 ' + config.port));
|
|
684
|
-
console.log(colors.gray(' - 腾讯云:安全组 → 入站规则 → 添加 TCP ' + config.port));
|
|
685
|
-
console.log(colors.gray(' - 阿里云:安全组 → 配置规则 → 添加 TCP ' + config.port));
|
|
686
|
-
console.log(colors.gray(' 3. 重启 Gateway:' + cliName + ' gateway restart'));
|
|
687
|
-
console.log(colors.gray(' 4. 访问:http://<服务器IP>:' + config.port + '/?token=...'));
|
|
688
|
-
} else {
|
|
689
|
-
console.log(colors.cyan('\nDashboard 访问:'));
|
|
690
|
-
console.log(` ${colors.yellow(`http://127.0.0.1:${config.port}/?token=${config.token}`)}`);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
console.log(colors.cyan('\n下一步 - 配置 AI 模型:'));
|
|
694
|
-
console.log(` ${colors.yellow('npx openclawapi@latest preset-claude')}`);
|
|
695
|
-
|
|
696
|
-
console.log(colors.cyan('\n常用命令:'));
|
|
697
|
-
console.log(` 查看状态: ${colors.yellow(`${cliName} status`)}`);
|
|
698
|
-
console.log(` 查看日志: ${colors.yellow(`${cliName} gateway logs`)}`);
|
|
699
|
-
console.log(` 重启服务: ${colors.yellow(`${cliName} gateway restart`)}`);
|
|
700
|
-
console.log(` 诊断问题: ${colors.yellow(`${cliName} doctor`)}`);
|
|
701
|
-
|
|
702
|
-
console.log(colors.cyan('\n配置聊天渠道:'));
|
|
703
|
-
console.log(` Discord: ${colors.yellow('npx openclawdc')}`);
|
|
704
|
-
console.log(` 飞书: ${colors.yellow('npx openclawfs')}`);
|
|
705
|
-
|
|
706
|
-
console.log('');
|
|
906
|
+
showCompletionInfo(cliName, config, isVPS);
|
|
707
907
|
}
|
|
708
908
|
|
|
709
909
|
main().catch((e) => {
|