openclawsetup 2.8.10 → 2.8.12
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 +191 -6
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -208,6 +208,28 @@ function safeFileRead(path) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
function sanitizeAuthProfiles(raw) {
|
|
212
|
+
try {
|
|
213
|
+
const obj = JSON.parse(raw);
|
|
214
|
+
const redact = (o) => {
|
|
215
|
+
if (!o || typeof o !== 'object') return o;
|
|
216
|
+
const copy = Array.isArray(o) ? [...o] : { ...o };
|
|
217
|
+
for (const k of Object.keys(copy)) {
|
|
218
|
+
if (/^(key|token|access|refresh|apiKey)$/i.test(k) && typeof copy[k] === 'string') {
|
|
219
|
+
const v = copy[k];
|
|
220
|
+
copy[k] = v.length > 8 ? `${v.slice(0, 4)}...${v.slice(-4)}` : '<REDACTED>';
|
|
221
|
+
} else if (typeof copy[k] === 'object') {
|
|
222
|
+
copy[k] = redact(copy[k]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return copy;
|
|
226
|
+
};
|
|
227
|
+
return JSON.stringify(redact(obj), null, 2);
|
|
228
|
+
} catch {
|
|
229
|
+
return sanitizeText(raw);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
211
233
|
function runCmdCapture(cmd, timeout = 20000) {
|
|
212
234
|
const result = safeExec(cmd, { timeout });
|
|
213
235
|
if (result.ok) {
|
|
@@ -504,7 +526,7 @@ async function collectEvidencePackage(options = {}) {
|
|
|
504
526
|
const captures = {
|
|
505
527
|
doctor: runCmdCapture(`${cliName} doctor`, quick ? 20000 : 90000),
|
|
506
528
|
status: runCmdCapture(`${cliName} status`, 20000),
|
|
507
|
-
gatewayLogs: runCmdCapture(`${cliName}
|
|
529
|
+
gatewayLogs: runCmdCapture(`${cliName} logs --lines 80`, quick ? 20000 : 45000),
|
|
508
530
|
cliVersion: runCmdCapture(`${cliName} --version`, 15000),
|
|
509
531
|
npmListGlobalOpenclaw: runCmdCapture('npm ls -g openclaw --depth=0', 20000),
|
|
510
532
|
npmListGlobalClawdbot: runCmdCapture('npm ls -g clawdbot --depth=0', 20000),
|
|
@@ -528,16 +550,56 @@ async function collectEvidencePackage(options = {}) {
|
|
|
528
550
|
const configRaw = safeFileRead(snapshot.openclaw.configPath);
|
|
529
551
|
const safeConfigPreview = sanitizeText(truncateText(configRaw, 20000));
|
|
530
552
|
|
|
553
|
+
// Capture auth-profiles.json (critical for 401 debugging)
|
|
554
|
+
const configDir = snapshot.openclaw.configDir || '';
|
|
555
|
+
const authProfilesCandidates = configDir ? [
|
|
556
|
+
join(configDir, 'agents', 'main', 'agent', 'auth-profiles.json'),
|
|
557
|
+
join(configDir, 'agent', 'auth-profiles.json'),
|
|
558
|
+
] : [];
|
|
559
|
+
let authProfilesPath = '';
|
|
560
|
+
let authProfilesRaw = '';
|
|
561
|
+
for (const candidate of authProfilesCandidates) {
|
|
562
|
+
const content = safeFileRead(candidate);
|
|
563
|
+
if (content) {
|
|
564
|
+
authProfilesPath = candidate;
|
|
565
|
+
authProfilesRaw = content;
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Redact key values but preserve structure for debugging
|
|
570
|
+
const safeAuthProfiles = authProfilesRaw ? sanitizeAuthProfiles(authProfilesRaw) : '';
|
|
571
|
+
|
|
572
|
+
// Try to read Gateway log file directly
|
|
573
|
+
const logFileCandidates = configDir ? [
|
|
574
|
+
join(configDir, 'gateway.log'),
|
|
575
|
+
join(configDir, 'logs', 'gateway.log'),
|
|
576
|
+
join(configDir, 'agents', 'main', 'agent', 'gateway.log'),
|
|
577
|
+
] : [];
|
|
578
|
+
let gatewayLogContent = '';
|
|
579
|
+
for (const candidate of logFileCandidates) {
|
|
580
|
+
const content = safeFileRead(candidate);
|
|
581
|
+
if (content) {
|
|
582
|
+
// Take last 5000 chars
|
|
583
|
+
gatewayLogContent = sanitizeText(content.length > 5000 ? content.slice(-5000) : content);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
531
588
|
const summary = {
|
|
532
589
|
meta: {
|
|
533
590
|
generatedAt: new Date().toISOString(),
|
|
534
591
|
durationMs: Date.now() - startedAt,
|
|
535
592
|
quick,
|
|
536
|
-
evidenceVersion: '1.
|
|
593
|
+
evidenceVersion: '1.1.0',
|
|
537
594
|
},
|
|
538
595
|
snapshot,
|
|
539
596
|
tokenOptimizationPreview,
|
|
540
597
|
captures,
|
|
598
|
+
authProfiles: {
|
|
599
|
+
path: authProfilesPath || '(not found)',
|
|
600
|
+
content: safeAuthProfiles || '(empty or missing)',
|
|
601
|
+
},
|
|
602
|
+
gatewayLogFile: gatewayLogContent || '(not found)',
|
|
541
603
|
strongCheck: strongCheckResult
|
|
542
604
|
? {
|
|
543
605
|
issuesCount: strongCheckResult.issues?.length || 0,
|
|
@@ -550,6 +612,9 @@ async function collectEvidencePackage(options = {}) {
|
|
|
550
612
|
|
|
551
613
|
writeFileSync(join(evidenceDir, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
|
|
552
614
|
writeFileSync(join(evidenceDir, 'config.preview.json.txt'), `${safeConfigPreview || '<empty>'}\n`, 'utf8');
|
|
615
|
+
if (safeAuthProfiles) {
|
|
616
|
+
writeFileSync(join(evidenceDir, 'auth-profiles.preview.json.txt'), `${safeAuthProfiles}\n`, 'utf8');
|
|
617
|
+
}
|
|
553
618
|
|
|
554
619
|
const readme = [
|
|
555
620
|
'# OpenClaw 排障证据包',
|
|
@@ -561,6 +626,7 @@ async function collectEvidencePackage(options = {}) {
|
|
|
561
626
|
'## 文件说明',
|
|
562
627
|
'- summary.json: 机器可解析的完整诊断结果(已脱敏)',
|
|
563
628
|
'- config.preview.json.txt: 配置文件预览(已脱敏)',
|
|
629
|
+
'- auth-profiles.preview.json.txt: 认证配置预览(已脱敏,如存在)',
|
|
564
630
|
'',
|
|
565
631
|
'## 发给技术支持',
|
|
566
632
|
'请把整个证据包文件夹(或 zip)发给技术支持即可。',
|
|
@@ -1286,9 +1352,13 @@ async function tryFixPortConflict(cliName, currentPort) {
|
|
|
1286
1352
|
return { ok: false, newPort: currentPort, note: '未找到可用替代端口' };
|
|
1287
1353
|
}
|
|
1288
1354
|
|
|
1289
|
-
|
|
1355
|
+
// 优先用 CLI 设置,超时则直接改配置文件
|
|
1356
|
+
const setResult = safeExec(`${cliName} config set gateway.port ${availablePort}`, { timeout: 10000 });
|
|
1290
1357
|
if (!setResult.ok) {
|
|
1291
|
-
|
|
1358
|
+
const fileSet = setGatewayPortInConfig(availablePort);
|
|
1359
|
+
if (!fileSet.ok) {
|
|
1360
|
+
return { ok: false, newPort: currentPort, note: `端口切换失败: ${fileSet.error}` };
|
|
1361
|
+
}
|
|
1292
1362
|
}
|
|
1293
1363
|
|
|
1294
1364
|
const restartResult = await ensureGatewayRunning(cliName, 'restart');
|
|
@@ -1299,6 +1369,109 @@ async function tryFixPortConflict(cliName, currentPort) {
|
|
|
1299
1369
|
return { ok: true, newPort: availablePort, note: `端口已切换到 ${availablePort}` };
|
|
1300
1370
|
}
|
|
1301
1371
|
|
|
1372
|
+
/**
|
|
1373
|
+
* 直接修改 openclaw.json 中的 gateway.port(不依赖 openclaw CLI)
|
|
1374
|
+
*/
|
|
1375
|
+
function setGatewayPortInConfig(newPort) {
|
|
1376
|
+
const config = getConfigInfo();
|
|
1377
|
+
if (!config.configPath || !existsSync(config.configPath)) {
|
|
1378
|
+
return { ok: false, error: '配置文件不存在' };
|
|
1379
|
+
}
|
|
1380
|
+
try {
|
|
1381
|
+
const raw = readFileSync(config.configPath, 'utf8');
|
|
1382
|
+
const json = JSON.parse(raw);
|
|
1383
|
+
if (!json.gateway) json.gateway = {};
|
|
1384
|
+
json.gateway.port = newPort;
|
|
1385
|
+
writeFileSync(config.configPath, JSON.stringify(json, null, 2), 'utf8');
|
|
1386
|
+
return { ok: true };
|
|
1387
|
+
} catch (e) {
|
|
1388
|
+
return { ok: false, error: e.message };
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* 重置/更改端口:找可用端口 → 写配置 → 重启 Gateway
|
|
1394
|
+
*/
|
|
1395
|
+
async function resetGatewayPort(cliName, requestedPort = null) {
|
|
1396
|
+
const config = getConfigInfo();
|
|
1397
|
+
const currentPort = config.port || DEFAULT_GATEWAY_PORT;
|
|
1398
|
+
|
|
1399
|
+
console.log(colors.cyan('\n🔧 Gateway 端口管理\n'));
|
|
1400
|
+
console.log(colors.gray(` 当前端口: ${currentPort}`));
|
|
1401
|
+
|
|
1402
|
+
// 确定目标端口
|
|
1403
|
+
let targetPort;
|
|
1404
|
+
if (requestedPort && requestedPort !== currentPort) {
|
|
1405
|
+
// 用户指定了端口,检查是否可用
|
|
1406
|
+
const inUse = await isPortInUse(requestedPort);
|
|
1407
|
+
if (inUse) {
|
|
1408
|
+
const processInfo = detectGatewayProcess(requestedPort);
|
|
1409
|
+
if (processInfo.found) {
|
|
1410
|
+
log.error(`端口 ${requestedPort} 已被占用: ${processInfo.processName || '未知'} (PID ${processInfo.pid})`);
|
|
1411
|
+
} else {
|
|
1412
|
+
log.error(`端口 ${requestedPort} 已被占用`);
|
|
1413
|
+
}
|
|
1414
|
+
return { ok: false, port: currentPort };
|
|
1415
|
+
}
|
|
1416
|
+
targetPort = requestedPort;
|
|
1417
|
+
} else {
|
|
1418
|
+
// 自动找可用端口
|
|
1419
|
+
console.log(colors.gray(' 正在扫描可用端口...'));
|
|
1420
|
+
targetPort = await findAvailablePort(DEFAULT_GATEWAY_PORT);
|
|
1421
|
+
if (!targetPort) {
|
|
1422
|
+
log.error('未找到可用端口');
|
|
1423
|
+
return { ok: false, port: currentPort };
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
console.log(colors.cyan(` 目标端口: ${targetPort}`));
|
|
1428
|
+
|
|
1429
|
+
// 先尝试用 CLI 设置(如果可用)
|
|
1430
|
+
const cliSet = safeExec(`${cliName} config set gateway.port ${targetPort}`, { timeout: 10000 });
|
|
1431
|
+
if (!cliSet.ok) {
|
|
1432
|
+
// CLI 不可用,直接改配置文件
|
|
1433
|
+
console.log(colors.gray(' CLI 设置超时,直接修改配置文件...'));
|
|
1434
|
+
const fileSet = setGatewayPortInConfig(targetPort);
|
|
1435
|
+
if (!fileSet.ok) {
|
|
1436
|
+
log.error(`配置写入失败: ${fileSet.error}`);
|
|
1437
|
+
return { ok: false, port: currentPort };
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
log.success(`端口已设置为 ${targetPort}`);
|
|
1442
|
+
|
|
1443
|
+
// 重启 Gateway
|
|
1444
|
+
console.log(colors.gray(' 正在重启 Gateway...'));
|
|
1445
|
+
await ensureGatewayRunning(cliName, 'restart');
|
|
1446
|
+
|
|
1447
|
+
// 等待并验证新端口
|
|
1448
|
+
const maxWait = platform() === 'win32' ? 15000 : 8000;
|
|
1449
|
+
const interval = 1500;
|
|
1450
|
+
let waited = 0;
|
|
1451
|
+
let portReady = false;
|
|
1452
|
+
while (waited < maxWait) {
|
|
1453
|
+
await sleep(interval);
|
|
1454
|
+
waited += interval;
|
|
1455
|
+
const check = getPortCheckOutput(targetPort);
|
|
1456
|
+
if (check.ok && hasListeningState(check.output)) {
|
|
1457
|
+
portReady = true;
|
|
1458
|
+
break;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
if (portReady) {
|
|
1463
|
+
log.success(`Gateway 已在端口 ${targetPort} 上运行`);
|
|
1464
|
+
const token = getDashboardToken(config);
|
|
1465
|
+
if (token) {
|
|
1466
|
+
console.log(colors.cyan(`\n Dashboard: http://127.0.0.1:${targetPort}/?token=${token}`));
|
|
1467
|
+
}
|
|
1468
|
+
return { ok: true, port: targetPort };
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
log.warn('Gateway 重启后端口未就绪,请手动检查');
|
|
1472
|
+
return { ok: false, port: targetPort };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1302
1475
|
function summarizeIssue(level, title, detail, solution, fixCmd = '') {
|
|
1303
1476
|
return { level, title, detail, solution, fixCmd };
|
|
1304
1477
|
}
|
|
@@ -2801,9 +2974,10 @@ async function showInteractiveMenu(existing) {
|
|
|
2801
2974
|
console.log(` ${colors.yellow('9')}. 配置技能`);
|
|
2802
2975
|
console.log(` ${colors.yellow('10')}. 重新安装`);
|
|
2803
2976
|
console.log(` ${colors.yellow('11')}. 完全卸载`);
|
|
2977
|
+
console.log(` ${colors.yellow('12')}. 重置/更改端口`);
|
|
2804
2978
|
console.log(` ${colors.yellow('0')}. 退出`);
|
|
2805
2979
|
|
|
2806
|
-
const choice = await askQuestion('\n请输入选项 (0-
|
|
2980
|
+
const choice = await askQuestion('\n请输入选项 (0-12): ');
|
|
2807
2981
|
|
|
2808
2982
|
switch (choice.trim()) {
|
|
2809
2983
|
case '1':
|
|
@@ -2883,12 +3057,23 @@ async function showInteractiveMenu(existing) {
|
|
|
2883
3057
|
process.exit(0);
|
|
2884
3058
|
}
|
|
2885
3059
|
break;
|
|
3060
|
+
case '12': {
|
|
3061
|
+
const portInput = await askQuestion('输入新端口号(留空自动分配): ');
|
|
3062
|
+
const requestedPort = portInput.trim() ? Number(portInput.trim()) : null;
|
|
3063
|
+
if (requestedPort && (isNaN(requestedPort) || requestedPort < 1024 || requestedPort > 65535)) {
|
|
3064
|
+
log.error('端口号无效,请输入 1024-65535 之间的数字');
|
|
3065
|
+
} else {
|
|
3066
|
+
await resetGatewayPort(cliName, requestedPort);
|
|
3067
|
+
}
|
|
3068
|
+
await waitForEnter('\n按回车返回菜单...');
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
2886
3071
|
case '0':
|
|
2887
3072
|
case '':
|
|
2888
3073
|
console.log(colors.gray('\n再见!'));
|
|
2889
3074
|
process.exit(0);
|
|
2890
3075
|
default:
|
|
2891
|
-
log.warn('无效选项,请输入 0-
|
|
3076
|
+
log.warn('无效选项,请输入 0-12');
|
|
2892
3077
|
}
|
|
2893
3078
|
}
|
|
2894
3079
|
}
|