openclawsetup 2.1.5 → 2.3.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 +374 -18
  2. package/package.json +2 -2
package/bin/cli.mjs CHANGED
@@ -28,9 +28,12 @@ const ERROR_CODES = {
28
28
  PERMISSION_DENIED: { code: 7, message: '权限不足' },
29
29
  NETWORK_ERROR: { code: 8, message: '网络错误' },
30
30
  PTY_UNAVAILABLE: { code: 9, message: '自动模式不可用' },
31
+ PORT_CONFLICT: { code: 10, message: '端口冲突' },
32
+ CONFIG_ERROR: { code: 11, message: '配置文件错误' },
33
+ GATEWAY_NOT_READY: { code: 12, message: 'Gateway 未就绪' },
31
34
  };
32
35
 
33
- function exitWithError(errorType, details = '') {
36
+ function exitWithError(errorType, details = '', autoFix = null) {
34
37
  const err = ERROR_CODES[errorType] || { code: 99, message: '未知错误' };
35
38
  console.log('\n' + '='.repeat(40));
36
39
  console.log(`\x1b[31m❌ 错误: ${err.message}\x1b[0m`);
@@ -38,6 +41,10 @@ function exitWithError(errorType, details = '') {
38
41
  if (details) {
39
42
  console.log(details);
40
43
  }
44
+ if (autoFix) {
45
+ console.log(`\n\x1b[33m💡 自动修复命令:\x1b[0m`);
46
+ console.log(` ${autoFix}`);
47
+ }
41
48
  console.log(`\n错误码: ${err.code}`);
42
49
  process.exit(err.code);
43
50
  }
@@ -78,36 +85,46 @@ function parseArgs() {
78
85
  update: args.includes('--update'),
79
86
  reinstall: args.includes('--reinstall'),
80
87
  uninstall: args.includes('--uninstall'),
88
+ check: args.includes('--check'),
89
+ fix: args.includes('--fix'),
81
90
  manual: args.includes('--manual'),
82
91
  auto: args.includes('--auto'),
83
92
  withModel: args.includes('--with-model'),
84
93
  withChannel: args.includes('--with-channel'),
94
+ quiet: args.includes('--quiet') || args.includes('-q'),
85
95
  help: args.includes('--help') || args.includes('-h'),
86
96
  };
87
97
  }
88
98
 
89
99
  function showHelp() {
90
100
  console.log(`
91
- ${colors.bold('OpenClaw 安装向导')}
101
+ ${colors.bold('OpenClaw 安装向导 v2.3')}
92
102
 
93
- ${colors.cyan('用法:')}
94
- npx openclawsetup 带中文指引的安装
103
+ ${colors.cyan('基础用法:')}
104
+ npx openclawsetup 交互式菜单(已安装)/ 安装向导(未安装)
105
+ npx openclawsetup --check 检查配置和服务状态
106
+ npx openclawsetup --fix 检查并自动修复常见问题
95
107
  npx openclawsetup --update 更新已安装的 OpenClaw
96
108
  npx openclawsetup --reinstall 卸载后重新安装
97
109
  npx openclawsetup --uninstall 卸载 OpenClaw
98
- npx openclawsetup --manual 完全手动模式
99
- npx openclawsetup --auto 强制自动模式
100
110
 
101
- ${colors.cyan('说明:')}
102
- 本工具会调用官方 openclaw onboard 命令
103
- 自动选择推荐配置,按任意键可随时接管
111
+ ${colors.cyan('安装模式:')}
112
+ npx openclawsetup --manual 完全手动模式(自己选择每个选项)
113
+ npx openclawsetup --auto 强制自动模式(脚本化场景)
114
+ npx openclawsetup -q 静默模式(减少输出)
104
115
 
105
116
  ${colors.cyan('高级选项:')}
106
117
  --with-model 检测到模型配置时暂停自动选择
107
118
  --with-channel 检测到渠道配置时暂停自动选择
108
119
 
109
120
  ${colors.cyan('安装后配置模型:')}
110
- npx openclawapi@latest preset-claude
121
+ npx openclawapi@latest preset-claude # 一键配置 Claude
122
+ npx openclawapi@latest # 交互式配置
123
+
124
+ ${colors.cyan('常见问题快速修复:')}
125
+ Gateway 未启动: openclaw gateway start
126
+ 端口被占用: openclaw config set gateway.port 18790
127
+ 配置文件损坏: rm ~/.openclaw/openclaw.json && openclaw onboard
111
128
  `);
112
129
  }
113
130
 
@@ -866,6 +883,341 @@ async function uninstallOpenClaw(existing) {
866
883
  log.success('卸载完成');
867
884
  }
868
885
 
886
+ // ============ 健康检查 ============
887
+
888
+ async function runHealthCheck(cliName, autoFix = false) {
889
+ console.log(colors.bold(colors.cyan('\n🔍 OpenClaw 健康检查\n')));
890
+
891
+ const issues = [];
892
+ const fixed = [];
893
+ const config = getConfigInfo();
894
+
895
+ // 1. 检查配置文件
896
+ console.log(colors.cyan('检查配置文件...'));
897
+ if (!config.configPath || !existsSync(config.configPath)) {
898
+ issues.push({
899
+ level: 'error',
900
+ title: '配置文件不存在',
901
+ detail: '未找到 openclaw.json 配置文件',
902
+ solution: '运行 openclaw onboard 重新配置',
903
+ fixCmd: `${cliName} onboard`,
904
+ });
905
+ } else {
906
+ try {
907
+ const raw = readFileSync(config.configPath, 'utf8');
908
+ JSON.parse(raw);
909
+ log.success('配置文件格式正确');
910
+ } catch (e) {
911
+ const issue = {
912
+ level: 'error',
913
+ title: '配置文件 JSON 格式错误',
914
+ detail: `解析失败: ${e.message}`,
915
+ solution: '备份并重新生成配置文件',
916
+ fixCmd: `mv ${config.configPath} ${config.configPath}.bak && ${cliName} onboard`,
917
+ };
918
+ if (autoFix) {
919
+ console.log(colors.yellow(' 尝试修复配置文件...'));
920
+ const backupPath = `${config.configPath}.bak.${Date.now()}`;
921
+ try {
922
+ const { renameSync } = await import('fs');
923
+ renameSync(config.configPath, backupPath);
924
+ log.success(`已备份损坏的配置到 ${backupPath}`);
925
+ fixed.push('配置文件已备份,请重新运行 onboard');
926
+ } catch {
927
+ issues.push(issue);
928
+ }
929
+ } else {
930
+ issues.push(issue);
931
+ }
932
+ }
933
+ }
934
+
935
+ // 2. 检查 Gateway 进程
936
+ console.log(colors.cyan('检查 Gateway 进程...'));
937
+ const statusResult = safeExec(`${cliName} status`);
938
+ if (statusResult.ok) {
939
+ const output = statusResult.output.toLowerCase();
940
+ if (output.includes('running') || output.includes('active')) {
941
+ log.success('Gateway 进程正在运行');
942
+ } else if (output.includes('stopped') || output.includes('inactive') || output.includes('not running')) {
943
+ const issue = {
944
+ level: 'error',
945
+ title: 'Gateway 未运行',
946
+ detail: 'Gateway 服务已停止',
947
+ solution: `运行 ${cliName} gateway start 启动服务`,
948
+ fixCmd: `${cliName} gateway start`,
949
+ };
950
+ if (autoFix) {
951
+ console.log(colors.yellow(' 尝试启动 Gateway...'));
952
+ const startResult = safeExec(`${cliName} gateway start`);
953
+ if (startResult.ok) {
954
+ log.success('Gateway 已启动');
955
+ fixed.push('Gateway 已自动启动');
956
+ } else {
957
+ issues.push(issue);
958
+ }
959
+ } else {
960
+ issues.push(issue);
961
+ }
962
+ }
963
+ } else {
964
+ issues.push({
965
+ level: 'warning',
966
+ title: '无法获取 Gateway 状态',
967
+ detail: statusResult.error || '状态检查失败',
968
+ solution: `尝试运行 ${cliName} status 查看详情`,
969
+ });
970
+ }
971
+
972
+ // 3. 检查端口监听
973
+ console.log(colors.cyan('检查端口监听...'));
974
+ const port = config.port || 18789;
975
+ const portCheckCmd = platform() === 'win32'
976
+ ? `netstat -an | findstr :${port}`
977
+ : `lsof -i :${port} 2>/dev/null || netstat -tlnp 2>/dev/null | grep :${port}`;
978
+ const portResult = safeExec(portCheckCmd);
979
+
980
+ if (portResult.ok && portResult.output) {
981
+ log.success(`端口 ${port} 正在监听`);
982
+ } else {
983
+ // 检查是否有其他进程占用端口
984
+ const conflictCheck = safeExec(`lsof -i :${port} 2>/dev/null | head -5`);
985
+ if (conflictCheck.ok && conflictCheck.output && !conflictCheck.output.includes('openclaw') && !conflictCheck.output.includes('node')) {
986
+ issues.push({
987
+ level: 'error',
988
+ title: `端口 ${port} 被其他程序占用`,
989
+ detail: conflictCheck.output.slice(0, 100),
990
+ solution: `更换端口: ${cliName} config set gateway.port 18790`,
991
+ fixCmd: `${cliName} config set gateway.port 18790 && ${cliName} gateway restart`,
992
+ });
993
+ } else {
994
+ issues.push({
995
+ level: 'error',
996
+ title: `端口 ${port} 未监听`,
997
+ detail: 'Gateway 端口未开放,服务可能未正常启动',
998
+ solution: `运行 ${cliName} gateway restart`,
999
+ fixCmd: `${cliName} gateway restart`,
1000
+ });
1001
+ }
1002
+ }
1003
+
1004
+ // 4. 检查 API 健康
1005
+ console.log(colors.cyan('检查 API 健康状态...'));
1006
+ const healthResult = safeExec(`curl -s --connect-timeout 3 http://127.0.0.1:${port}/health`);
1007
+ if (healthResult.ok && healthResult.output) {
1008
+ try {
1009
+ const health = JSON.parse(healthResult.output);
1010
+ if (health.status === 'ok' || health.healthy) {
1011
+ log.success('API 健康检查通过');
1012
+ } else {
1013
+ issues.push({
1014
+ level: 'warning',
1015
+ title: 'API 健康状态异常',
1016
+ detail: JSON.stringify(health),
1017
+ solution: `运行 ${cliName} gateway logs 查看日志`,
1018
+ });
1019
+ }
1020
+ } catch {
1021
+ if (healthResult.output.includes('ok') || healthResult.output.includes('healthy')) {
1022
+ log.success('API 健康检查通过');
1023
+ } else {
1024
+ log.warn('API 返回非标准格式');
1025
+ }
1026
+ }
1027
+ } else {
1028
+ issues.push({
1029
+ level: 'error',
1030
+ title: 'API 无响应',
1031
+ detail: '无法连接到 Gateway API',
1032
+ solution: `重启服务: ${cliName} gateway restart`,
1033
+ fixCmd: `${cliName} gateway restart`,
1034
+ });
1035
+ }
1036
+
1037
+ // 5. 检查模型配置
1038
+ console.log(colors.cyan('检查模型配置...'));
1039
+ if (config.raw) {
1040
+ const hasModels = config.raw.includes('"models"') || config.raw.includes('"providers"');
1041
+ const hasApiKey = config.raw.includes('"apiKey"') || config.raw.includes('"api_key"');
1042
+ if (!hasModels && !hasApiKey) {
1043
+ issues.push({
1044
+ level: 'warning',
1045
+ title: '未配置 AI 模型',
1046
+ detail: '配置文件中未找到模型或 API Key 配置',
1047
+ solution: '运行 npx openclawapi@latest 配置模型',
1048
+ fixCmd: 'npx openclawapi@latest',
1049
+ });
1050
+ } else {
1051
+ log.success('已配置模型');
1052
+ }
1053
+ }
1054
+
1055
+ // 6. 运行官方诊断
1056
+ console.log(colors.cyan('运行官方诊断...'));
1057
+ const doctorResult = safeExec(`${cliName} doctor`);
1058
+ if (doctorResult.ok) {
1059
+ const output = doctorResult.output.toLowerCase();
1060
+ if (output.includes('error') || output.includes('fail') || output.includes('问题')) {
1061
+ const issue = {
1062
+ level: 'warning',
1063
+ title: '官方诊断发现问题',
1064
+ detail: doctorResult.output.slice(0, 200),
1065
+ solution: `运行 ${cliName} doctor --fix 尝试自动修复`,
1066
+ fixCmd: `${cliName} doctor --fix`,
1067
+ };
1068
+ if (autoFix) {
1069
+ console.log(colors.yellow(' 运行官方自动修复...'));
1070
+ const fixResult = safeExec(`${cliName} doctor --fix`);
1071
+ if (fixResult.ok) {
1072
+ fixed.push('官方诊断问题已尝试修复');
1073
+ } else {
1074
+ issues.push(issue);
1075
+ }
1076
+ } else {
1077
+ issues.push(issue);
1078
+ }
1079
+ } else {
1080
+ log.success('官方诊断通过');
1081
+ }
1082
+ }
1083
+
1084
+ // 输出结果
1085
+ console.log('\n' + '='.repeat(50));
1086
+
1087
+ if (fixed.length > 0) {
1088
+ console.log(colors.bold(colors.green(`🔧 已自动修复 ${fixed.length} 个问题:`)));
1089
+ fixed.forEach((f, i) => console.log(colors.green(` ${i + 1}. ${f}`)));
1090
+ console.log('');
1091
+ }
1092
+
1093
+ if (issues.length === 0) {
1094
+ console.log(colors.bold(colors.green('✅ 所有检查通过,OpenClaw 运行正常!')));
1095
+ } else {
1096
+ console.log(colors.bold(colors.yellow(`⚠ 发现 ${issues.length} 个问题:`)));
1097
+ console.log('='.repeat(50));
1098
+
1099
+ issues.forEach((issue, i) => {
1100
+ const icon = issue.level === 'error' ? colors.red('❌') : colors.yellow('⚠');
1101
+ console.log(`\n${icon} ${colors.bold(`问题 ${i + 1}: ${issue.title}`)}`);
1102
+ console.log(colors.gray(` ${issue.detail}`));
1103
+ console.log(colors.cyan(` 解决方案: ${issue.solution}`));
1104
+ if (issue.fixCmd) {
1105
+ console.log(colors.yellow(` 快速修复: ${issue.fixCmd}`));
1106
+ }
1107
+ });
1108
+
1109
+ if (!autoFix && issues.some(i => i.fixCmd)) {
1110
+ console.log(colors.cyan('\n💡 提示: 运行 npx openclawsetup --fix 尝试自动修复'));
1111
+ }
1112
+ }
1113
+ console.log('');
1114
+
1115
+ return { issues, fixed };
1116
+ }
1117
+
1118
+ // ============ 交互式菜单 ============
1119
+
1120
+ async function showInteractiveMenu(existing) {
1121
+ const cliName = existing.name || 'openclaw';
1122
+
1123
+ while (true) {
1124
+ console.log(colors.bold(colors.cyan('\n' + '='.repeat(50))));
1125
+ console.log(colors.bold(colors.cyan(' 🦞 OpenClaw 管理菜单')));
1126
+ console.log(colors.bold(colors.cyan('='.repeat(50))));
1127
+
1128
+ showDashboardAccessInfo();
1129
+
1130
+ console.log(colors.cyan('\n请选择操作:'));
1131
+ console.log(` ${colors.yellow('1')}. 检查 - 诊断配置和服务状态`);
1132
+ console.log(` ${colors.yellow('2')}. 修复 - 自动修复常见问题`);
1133
+ console.log(` ${colors.yellow('3')}. 更新 - 更新到最新版本`);
1134
+ console.log(` ${colors.yellow('4')}. 重启 - 重启 Gateway 服务`);
1135
+ console.log(` ${colors.yellow('5')}. 日志 - 查看 Gateway 日志`);
1136
+ console.log(` ${colors.yellow('6')}. 配置模型 - 配置 AI 模型`);
1137
+ console.log(` ${colors.yellow('7')}. 重新安装 - 完全重新安装`);
1138
+ console.log(` ${colors.yellow('8')}. 卸载 - 完全卸载 OpenClaw`);
1139
+ console.log(` ${colors.yellow('0')}. 退出`);
1140
+
1141
+ const choice = await askQuestion('\n请输入选项 (0-8): ');
1142
+
1143
+ switch (choice.trim()) {
1144
+ case '1':
1145
+ await runHealthCheck(cliName, false);
1146
+ await waitForEnter('\n按回车返回菜单...');
1147
+ break;
1148
+ case '2':
1149
+ await runHealthCheck(cliName, true);
1150
+ await waitForEnter('\n按回车返回菜单...');
1151
+ break;
1152
+ case '3':
1153
+ await updateOpenClaw(cliName);
1154
+ await waitForEnter('\n按回车返回菜单...');
1155
+ break;
1156
+ case '4':
1157
+ console.log(colors.cyan('\n重启 Gateway...'));
1158
+ const restartResult = safeExec(`${cliName} gateway restart`);
1159
+ if (restartResult.ok) {
1160
+ log.success('Gateway 已重启');
1161
+ } else {
1162
+ log.error('重启失败: ' + (restartResult.error || ''));
1163
+ }
1164
+ await waitForEnter('\n按回车返回菜单...');
1165
+ break;
1166
+ case '5':
1167
+ console.log(colors.cyan('\n显示最近日志(按 Ctrl+C 退出)...\n'));
1168
+ spawnSync(cliName, ['gateway', 'logs', '--tail', '50'], {
1169
+ stdio: 'inherit',
1170
+ shell: true,
1171
+ });
1172
+ await waitForEnter('\n按回车返回菜单...');
1173
+ break;
1174
+ case '6':
1175
+ console.log(colors.cyan('\n启动模型配置...'));
1176
+ spawnSync('npx', ['openclawapi@latest'], {
1177
+ stdio: 'inherit',
1178
+ shell: true,
1179
+ });
1180
+ await waitForEnter('\n按回车返回菜单...');
1181
+ break;
1182
+ case '7':
1183
+ console.log(colors.yellow('\n即将重新安装 OpenClaw...'));
1184
+ const confirmReinstall = await askQuestion('确认重新安装?(y/N): ');
1185
+ if (confirmReinstall.toLowerCase() === 'y') {
1186
+ await uninstallOpenClaw(existing);
1187
+ const newCliName = await installOpenClaw();
1188
+ await runOnboard(newCliName);
1189
+ showCompletionInfo(newCliName);
1190
+ }
1191
+ break;
1192
+ case '8':
1193
+ console.log(colors.red('\n⚠ 警告:卸载将删除所有配置!'));
1194
+ const confirmUninstall = await askQuestion('确认卸载?(y/N): ');
1195
+ if (confirmUninstall.toLowerCase() === 'y') {
1196
+ await uninstallOpenClaw(existing);
1197
+ console.log(colors.green('\n卸载完成,感谢使用 OpenClaw!'));
1198
+ process.exit(0);
1199
+ }
1200
+ break;
1201
+ case '0':
1202
+ case '':
1203
+ console.log(colors.gray('\n再见!'));
1204
+ process.exit(0);
1205
+ default:
1206
+ log.warn('无效选项,请输入 0-8');
1207
+ }
1208
+ }
1209
+ }
1210
+
1211
+ function askQuestion(prompt) {
1212
+ return new Promise((resolve) => {
1213
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1214
+ rl.question(colors.yellow(prompt), (answer) => {
1215
+ rl.close();
1216
+ resolve(answer);
1217
+ });
1218
+ });
1219
+ }
1220
+
869
1221
  // ============ 主函数 ============
870
1222
 
871
1223
  async function main() {
@@ -888,6 +1240,16 @@ async function main() {
888
1240
  if (existing.installed) {
889
1241
  console.log(colors.green(`\n✓ 检测到已安装: ${existing.name}`));
890
1242
 
1243
+ if (options.check) {
1244
+ await runHealthCheck(existing.name, false);
1245
+ process.exit(0);
1246
+ }
1247
+
1248
+ if (options.fix) {
1249
+ await runHealthCheck(existing.name, true);
1250
+ process.exit(0);
1251
+ }
1252
+
891
1253
  if (options.update) {
892
1254
  await updateOpenClaw(existing.name);
893
1255
  process.exit(0);
@@ -901,14 +1263,8 @@ async function main() {
901
1263
  if (options.reinstall) {
902
1264
  await uninstallOpenClaw(existing);
903
1265
  } else {
904
- console.log(colors.cyan('\n已安装,可选操作:'));
905
- console.log(` 更新: ${colors.yellow('npx openclawsetup --update')}`);
906
- console.log(` 重装: ${colors.yellow('npx openclawsetup --reinstall')}`);
907
- console.log(` 卸载: ${colors.yellow('npx openclawsetup --uninstall')}`);
908
- console.log(` 配置模型: ${colors.yellow('npx openclawapi@latest preset-claude')}`);
909
- showDashboardAccessInfo();
910
- console.log('');
911
- process.exit(0);
1266
+ // 交互式菜单
1267
+ await showInteractiveMenu(existing);
912
1268
  }
913
1269
  }
914
1270
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openclawsetup",
3
- "version": "2.1.5",
4
- "description": "OpenClaw 安装向导 - 带中文指引的官方安装流程",
3
+ "version": "2.3.0",
4
+ "description": "OpenClaw 安装向导 - 智能安装、诊断、自动修复",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "openclawsetup": "bin/cli.mjs"