openclawsetup 2.8.2 → 2.8.4

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 +134 -17
  2. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -856,16 +856,78 @@ function getDashboardToken(config) {
856
856
  return extractAuthToken(config?.raw || '') || '';
857
857
  }
858
858
 
859
+ function maskToken(token = '') {
860
+ if (!token || token === '<未配置>' || token === '<你的token>') return token;
861
+ const text = String(token);
862
+ if (text.length <= 8) return '***';
863
+ return `${text.slice(0, 4)}...${text.slice(-4)}`;
864
+ }
865
+
859
866
  function parseStatusOutput(statusOutput) {
860
867
  const text = (statusOutput || '').toLowerCase();
861
- const runningKeywords = ['running', 'active', '已运行', '运行中', '在线'];
862
- const stoppedKeywords = ['stopped', 'inactive', 'not running', '未运行', '停止'];
868
+ const stoppedRegex = [
869
+ /\bnot\s+running\b/,
870
+ /\bstopped\b/,
871
+ /\binactive\b/,
872
+ /\boffline\b/,
873
+ ];
874
+ const runningRegex = [
875
+ /\brunning\b/,
876
+ /\bactive\b/,
877
+ /\bonline\b/,
878
+ ];
879
+ const stoppedKeywords = ['未运行', '已停止', '停止', '离线'];
880
+ const runningKeywords = ['已运行', '运行中', '在线'];
863
881
 
864
- if (runningKeywords.some((word) => text.includes(word))) return 'running';
882
+ if (stoppedRegex.some((pattern) => pattern.test(text))) return 'stopped';
865
883
  if (stoppedKeywords.some((word) => text.includes(word))) return 'stopped';
884
+ if (runningRegex.some((pattern) => pattern.test(text))) return 'running';
885
+ if (runningKeywords.some((word) => text.includes(word))) return 'running';
866
886
  return 'unknown';
867
887
  }
868
888
 
889
+ function isCommandTimeout(result) {
890
+ if (!result || result.ok) return false;
891
+ const raw = `${result.error || ''} ${result.stderr || ''}`.toLowerCase();
892
+ return raw.includes('timed out') || raw.includes('etimedout') || raw.includes('sigterm') || raw.includes('killed');
893
+ }
894
+
895
+ function probeGatewayStatus(cliName, timeout = STATUS_CMD_TIMEOUT_MS) {
896
+ const attempts = [
897
+ `${cliName} status`,
898
+ `${cliName} gateway status`,
899
+ ];
900
+
901
+ let state = 'unknown';
902
+ let output = '';
903
+ let timedOut = false;
904
+ let error = '';
905
+
906
+ for (const cmd of attempts) {
907
+ const result = safeExec(cmd, { timeout });
908
+ if (isCommandTimeout(result)) {
909
+ timedOut = true;
910
+ continue;
911
+ }
912
+ if (!result.ok) {
913
+ if (!error) error = result.stderr || result.error || '';
914
+ continue;
915
+ }
916
+ if (!result.output) continue;
917
+
918
+ output = result.output;
919
+ const parsed = parseStatusOutput(result.output);
920
+ if (parsed === 'running') {
921
+ return { state: 'running', output, timedOut, error };
922
+ }
923
+ if (parsed === 'stopped') {
924
+ state = 'stopped';
925
+ }
926
+ }
927
+
928
+ return { state, output, timedOut, error };
929
+ }
930
+
869
931
  function getPortCheckOutput(port, timeout = PORT_CHECK_TIMEOUT_MS) {
870
932
  const cmd = platform() === 'win32'
871
933
  ? `netstat -ano | findstr :${port}`
@@ -1094,6 +1156,39 @@ function selectHealthPort(config, strongMode = false) {
1094
1156
  return strongMode ? STRONG_PORT_CANDIDATES[0] : DEFAULT_GATEWAY_PORT;
1095
1157
  }
1096
1158
 
1159
+ function parsePortFromText(raw = '') {
1160
+ const matches = String(raw).match(/\b(\d{2,5})\b/g) || [];
1161
+ for (const item of matches) {
1162
+ const port = Number(item);
1163
+ if (Number.isInteger(port) && port > 0 && port <= 65535) {
1164
+ return port;
1165
+ }
1166
+ }
1167
+ return null;
1168
+ }
1169
+
1170
+ function getRuntimeGatewayPort(cliName, timeout = STATUS_CMD_TIMEOUT_MS) {
1171
+ if (!cliName) return null;
1172
+ const result = safeExec(`${cliName} config get gateway.port`, { timeout });
1173
+ if (!result.ok || !result.output) return null;
1174
+ return parsePortFromText(result.output);
1175
+ }
1176
+
1177
+ function resolveHealthPort(cliName, config, strongMode = false) {
1178
+ const runtimePort = getRuntimeGatewayPort(cliName);
1179
+ if (runtimePort) return { port: runtimePort, source: 'runtime' };
1180
+
1181
+ const candidate = Number(config?.port || DEFAULT_GATEWAY_PORT);
1182
+ if (Number.isInteger(candidate) && candidate > 0) {
1183
+ return { port: candidate, source: 'config' };
1184
+ }
1185
+
1186
+ return {
1187
+ port: strongMode ? STRONG_PORT_CANDIDATES[0] : DEFAULT_GATEWAY_PORT,
1188
+ source: 'default',
1189
+ };
1190
+ }
1191
+
1097
1192
  async function tryFixPortConflict(cliName, currentPort) {
1098
1193
  const availablePort = await findAvailablePort(currentPort);
1099
1194
  if (!availablePort || availablePort === currentPort) {
@@ -1375,12 +1470,18 @@ function getOnboardHelp(cliName) {
1375
1470
  return '';
1376
1471
  }
1377
1472
 
1473
+ function extractOnboardFlags(helpText = '') {
1474
+ const matches = helpText.toLowerCase().match(/--[a-z0-9][a-z0-9-]*/g) || [];
1475
+ return new Set(matches);
1476
+ }
1477
+
1378
1478
  function buildOnboardArgsFromHelp(helpText, options) {
1379
1479
  const help = helpText.toLowerCase();
1480
+ const flagSet = extractOnboardFlags(helpText);
1380
1481
  const args = [];
1381
1482
  const enabled = [];
1382
1483
 
1383
- const pickFlag = (flags) => flags.find((flag) => help.includes(flag));
1484
+ const pickFlag = (flags) => flags.find((flag) => flagSet.has(flag));
1384
1485
 
1385
1486
  const yesFlag = pickFlag(['--yes', '--assume-yes', '--accept', '--agree']);
1386
1487
  if (yesFlag) {
@@ -1393,10 +1494,10 @@ function buildOnboardArgsFromHelp(helpText, options) {
1393
1494
  args.push(installDaemonFlag);
1394
1495
  }
1395
1496
 
1396
- if (help.includes('--quickstart')) {
1497
+ if (flagSet.has('--quickstart')) {
1397
1498
  args.push('--quickstart');
1398
1499
  enabled.push('quickstart');
1399
- } else if (help.includes('--mode') && (help.includes('quickstart') || help.includes('quick start'))) {
1500
+ } else if (flagSet.has('--mode') && (help.includes('quickstart') || help.includes('quick start'))) {
1400
1501
  args.push('--mode', 'quickstart');
1401
1502
  enabled.push('quickstart');
1402
1503
  }
@@ -1417,10 +1518,10 @@ function buildOnboardArgsFromHelp(helpText, options) {
1417
1518
  }
1418
1519
  }
1419
1520
 
1420
- if (help.includes('--ui') && (help.includes('web') || help.includes('dashboard'))) {
1521
+ if (flagSet.has('--ui') && (help.includes('web') || help.includes('dashboard'))) {
1421
1522
  args.push('--ui', 'web');
1422
1523
  enabled.push('ui-web');
1423
- } else if (help.includes('--web')) {
1524
+ } else if (flagSet.has('--web')) {
1424
1525
  args.push('--web');
1425
1526
  enabled.push('ui-web');
1426
1527
  }
@@ -1848,8 +1949,9 @@ function showCompletionInfo(cliName) {
1848
1949
  function showDashboardAccessInfo() {
1849
1950
  const config = getConfigInfo();
1850
1951
  const port = config.port || 18789;
1851
- const token = getDashboardToken(config) || '<你的token>';
1852
- const dashboardUrl = `http://127.0.0.1:${port}/?token=${token}`;
1952
+ const tokenRaw = getDashboardToken(config) || '<你的token>';
1953
+ const tokenMasked = maskToken(tokenRaw);
1954
+ const dashboardUrl = `http://127.0.0.1:${port}/?token=${tokenMasked}`;
1853
1955
 
1854
1956
  if (detectVps()) {
1855
1957
  const serverIp = getServerIp() || '<服务器IP>';
@@ -2026,16 +2128,27 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2026
2128
 
2027
2129
  // 3. 检查 Gateway 进程
2028
2130
  console.log(colors.cyan('检查 Gateway 进程...'));
2029
- const statusResult = safeExec(`${activeCli} status`);
2030
- const statusState = statusResult.ok ? parseStatusOutput(statusResult.output) : 'unknown';
2031
-
2032
- if (statusResult.ok && statusState === 'running') {
2131
+ const statusProbe = probeGatewayStatus(activeCli, STATUS_CMD_TIMEOUT_MS);
2132
+ const statusState = statusProbe.state;
2133
+ const statusPortResolved = resolveHealthPort(activeCli, config, strongMode);
2134
+ const portForStatus = statusPortResolved.port;
2135
+ const statusPortResult = getPortCheckOutput(portForStatus, PORT_CHECK_TIMEOUT_MS);
2136
+ const statusPortListening = Boolean(statusPortResult.ok && statusPortResult.output);
2137
+
2138
+ if (statusState === 'running' || (statusState !== 'running' && statusPortListening)) {
2033
2139
  log.success('Gateway 进程正在运行');
2140
+ if (statusProbe.timedOut) {
2141
+ log.hint('状态命令超时,已按端口监听判定为运行中');
2142
+ } else if (statusState !== 'running' && statusPortListening) {
2143
+ log.hint('状态命令返回非运行,但端口可访问(兼容判定)');
2144
+ }
2034
2145
  } else {
2035
2146
  const issue = summarizeIssue(
2036
2147
  'error',
2037
2148
  'Gateway 未运行',
2038
- statusResult.ok ? 'Gateway 状态为停止/未知' : (statusResult.stderr || statusResult.error || '状态检查失败'),
2149
+ statusProbe.output
2150
+ ? 'Gateway 状态为停止/未知'
2151
+ : (statusProbe.error || '状态检查失败'),
2039
2152
  `运行 ${activeCli} gateway start 启动服务`,
2040
2153
  `${activeCli} gateway start`,
2041
2154
  );
@@ -2056,7 +2169,7 @@ async function runHealthCheck(cliName, autoFix = false, strongMode = false) {
2056
2169
 
2057
2170
  // 4. 检查端口监听
2058
2171
  config = getConfigInfo();
2059
- let port = selectHealthPort(config, strongMode);
2172
+ let port = resolveHealthPort(activeCli, config, strongMode).port;
2060
2173
  console.log(colors.cyan(`检查端口监听 (${port})...`));
2061
2174
  let portHealthy = false;
2062
2175
 
@@ -2337,7 +2450,8 @@ function testModelChat(cliName, timeoutMs = MODEL_CHAT_TIMEOUT_MS) {
2337
2450
 
2338
2451
  async function showStatusInfo(cliName) {
2339
2452
  const config = getConfigInfo();
2340
- const port = config.port || 18789;
2453
+ const portResolved = resolveHealthPort(cliName, config, false);
2454
+ const port = portResolved.port;
2341
2455
  const token = getDashboardToken(config) || '<未配置>';
2342
2456
  const dashboardUrl = `http://127.0.0.1:${port}/?token=${token}`;
2343
2457
 
@@ -2378,6 +2492,9 @@ async function showStatusInfo(cliName) {
2378
2492
  if (statusTimedOut) {
2379
2493
  console.log(colors.yellow(' ⚠ 状态命令超时,已自动切换端口探测模式'));
2380
2494
  }
2495
+ if (portResolved.source === 'runtime' && Number(config?.port) !== Number(port)) {
2496
+ console.log(colors.gray(` … 运行时端口为 ${port}(与本地配置文件不一致)`));
2497
+ }
2381
2498
 
2382
2499
  if (statusState === 'running') {
2383
2500
  console.log(colors.green(' ✓ Gateway 服务正在运行'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawsetup",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "description": "OpenClaw 安装向导 - 智能安装、诊断、自动修复",
5
5
  "type": "module",
6
6
  "bin": {