yymaxapi 1.0.101 → 1.0.103
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/yymaxapi.js +333 -228
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -412,6 +412,7 @@ async function promptOpencodeDefaultModelSelection(args = {}, message = '选择
|
|
|
412
412
|
const BACKUP_FILENAME = 'openclaw-default.json.bak';
|
|
413
413
|
const BACKUP_DIR_NAME = 'backups';
|
|
414
414
|
const MAX_BACKUPS = 10;
|
|
415
|
+
const GATEWAY_CLI_NAMES = ['openclaw'];
|
|
415
416
|
const EXTRA_BIN_DIRS = [
|
|
416
417
|
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
417
418
|
path.join(os.homedir(), '.local', 'bin'),
|
|
@@ -1074,6 +1075,55 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
1074
1075
|
return configPath;
|
|
1075
1076
|
}
|
|
1076
1077
|
|
|
1078
|
+
function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id) {
|
|
1079
|
+
const dataDir = getHermesDataDir();
|
|
1080
|
+
const configPath = path.join(dataDir, 'config.yaml');
|
|
1081
|
+
const envPath = getHermesEnvPath();
|
|
1082
|
+
const existingConfigPath = getHermesConfigPath();
|
|
1083
|
+
const wslMirror = getHermesWslMirrorInfo();
|
|
1084
|
+
|
|
1085
|
+
let existingConfigRaw = readTextIfExists(existingConfigPath);
|
|
1086
|
+
if (!existingConfigRaw && wslMirror.sourceConfigPath) {
|
|
1087
|
+
existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
let existingEnvRaw = readTextIfExists(envPath);
|
|
1091
|
+
if (!existingEnvRaw && wslMirror.envPath) {
|
|
1092
|
+
existingEnvRaw = readWslTextFile(wslMirror.envPath);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const normalizedBaseUrl = trimClaudeMessagesSuffix(String(baseUrl || '').trim()).replace(/\/+$/, '');
|
|
1096
|
+
const nextConfigRaw = upsertSimpleYamlConfig(existingConfigRaw, {
|
|
1097
|
+
provider: 'anthropic',
|
|
1098
|
+
model: modelId,
|
|
1099
|
+
base_url: normalizedBaseUrl
|
|
1100
|
+
});
|
|
1101
|
+
const nextEnvRaw = upsertEnvFile(existingEnvRaw, {
|
|
1102
|
+
ANTHROPIC_API_KEY: String(apiKey || '').trim()
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1106
|
+
fs.writeFileSync(configPath, nextConfigRaw, 'utf8');
|
|
1107
|
+
fs.writeFileSync(envPath, nextEnvRaw, 'utf8');
|
|
1108
|
+
|
|
1109
|
+
if (wslMirror.configPath) {
|
|
1110
|
+
try { syncFileToWsl(configPath, wslMirror.configPath); } catch { /* best-effort */ }
|
|
1111
|
+
}
|
|
1112
|
+
if (wslMirror.envPath) {
|
|
1113
|
+
try { syncFileToWsl(envPath, wslMirror.envPath); } catch { /* best-effort */ }
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return {
|
|
1117
|
+
dataDir,
|
|
1118
|
+
configPath,
|
|
1119
|
+
envPath,
|
|
1120
|
+
wslConfigPath: wslMirror.configPath,
|
|
1121
|
+
wslEnvPath: wslMirror.envPath,
|
|
1122
|
+
modelId,
|
|
1123
|
+
baseUrl: normalizedBaseUrl
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1077
1127
|
function syncExternalTools(type, baseUrl, apiKey, extra = {}) {
|
|
1078
1128
|
const synced = [];
|
|
1079
1129
|
try {
|
|
@@ -1151,7 +1201,7 @@ function dockerCmd(cmd) {
|
|
|
1151
1201
|
return _dockerNeedsSudo ? `sudo -n docker ${cmd}` : `docker ${cmd}`;
|
|
1152
1202
|
}
|
|
1153
1203
|
|
|
1154
|
-
// 查找包含 openclaw
|
|
1204
|
+
// 查找包含 openclaw 的运行中容器
|
|
1155
1205
|
function findOpenclawDockerContainers() {
|
|
1156
1206
|
if (_dockerContainerCache !== null) return _dockerContainerCache;
|
|
1157
1207
|
if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
|
|
@@ -1169,7 +1219,7 @@ function findOpenclawDockerContainers() {
|
|
|
1169
1219
|
const status = statusParts.join(' ');
|
|
1170
1220
|
if (!id) continue;
|
|
1171
1221
|
|
|
1172
|
-
// 检查容器内是否有 openclaw
|
|
1222
|
+
// 检查容器内是否有 openclaw(用 command -v 代替 which,兼容精简镜像)
|
|
1173
1223
|
for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
1174
1224
|
const check = safeExec(dockerCmd(`exec ${id} sh -c "command -v ${cli} 2>/dev/null || which ${cli} 2>/dev/null"`), { timeout: 8000 });
|
|
1175
1225
|
if (check.ok && check.output && check.output.trim()) {
|
|
@@ -1343,7 +1393,7 @@ let _wslCliBinaryCache = undefined;
|
|
|
1343
1393
|
|
|
1344
1394
|
function getWslCliBinary() {
|
|
1345
1395
|
if (_wslCliBinaryCache !== undefined) return _wslCliBinaryCache;
|
|
1346
|
-
for (const name of
|
|
1396
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1347
1397
|
try {
|
|
1348
1398
|
const result = execFileSync('wsl', ['bash', '-lc', `which ${name} 2>/dev/null`], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim();
|
|
1349
1399
|
if (result && result.startsWith('/')) {
|
|
@@ -1457,7 +1507,7 @@ function cleanupAgentProcesses() {
|
|
|
1457
1507
|
try {
|
|
1458
1508
|
const { execSync } = require('child_process');
|
|
1459
1509
|
if (process.platform === 'win32') {
|
|
1460
|
-
for (const name of
|
|
1510
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1461
1511
|
try {
|
|
1462
1512
|
execSync(`wmic process where "commandline like '%${name}%agent%' and not commandline like '%gateway%'" call terminate 2>nul`, { stdio: 'ignore', timeout: 10000 });
|
|
1463
1513
|
} catch { /* ignore */ }
|
|
@@ -1468,18 +1518,18 @@ function cleanupAgentProcesses() {
|
|
|
1468
1518
|
// WSL 内的 agent 也要清理
|
|
1469
1519
|
if (detectGatewayEnv() === 'wsl') {
|
|
1470
1520
|
try {
|
|
1471
|
-
execSync('wsl -- bash -lc "pkill -f \'openclaw.*agent\' 2>/dev/null
|
|
1521
|
+
execSync('wsl -- bash -lc "pkill -f \'openclaw.*agent\' 2>/dev/null || true"', { stdio: 'ignore', timeout: 10000 });
|
|
1472
1522
|
} catch { /* ignore */ }
|
|
1473
1523
|
}
|
|
1474
1524
|
} else {
|
|
1475
|
-
for (const name of
|
|
1525
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1476
1526
|
execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
|
|
1477
1527
|
}
|
|
1478
1528
|
}
|
|
1479
1529
|
// Docker 容器内的 agent 也要清理
|
|
1480
1530
|
if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
|
|
1481
1531
|
try {
|
|
1482
|
-
for (const name of
|
|
1532
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
1483
1533
|
safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} sh -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
|
|
1484
1534
|
}
|
|
1485
1535
|
} catch { /* ignore */ }
|
|
@@ -3209,6 +3259,14 @@ function shellQuote(value) {
|
|
|
3209
3259
|
return `"${str.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
3210
3260
|
}
|
|
3211
3261
|
|
|
3262
|
+
function escapeSingleQuotedShell(value) {
|
|
3263
|
+
return String(value || '').replace(/'/g, "'\\''");
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
function buildLoginShellCommand(shellPath, command) {
|
|
3267
|
+
return `${shellPath} -lc '${escapeSingleQuotedShell(command)}'`;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3212
3270
|
function escapeXml(value) {
|
|
3213
3271
|
return String(value)
|
|
3214
3272
|
.replace(/&/g, '&')
|
|
@@ -3252,7 +3310,7 @@ function resolveCliBinary() {
|
|
|
3252
3310
|
}
|
|
3253
3311
|
}
|
|
3254
3312
|
|
|
3255
|
-
const candidates =
|
|
3313
|
+
const candidates = GATEWAY_CLI_NAMES;
|
|
3256
3314
|
const isWin = process.platform === 'win32';
|
|
3257
3315
|
const searchDirs = (process.env.PATH || '').split(path.delimiter).concat(EXTRA_BIN_DIRS);
|
|
3258
3316
|
for (const name of candidates) {
|
|
@@ -3271,30 +3329,6 @@ function resolveCliBinary() {
|
|
|
3271
3329
|
}
|
|
3272
3330
|
}
|
|
3273
3331
|
|
|
3274
|
-
const moltbotRoots = [];
|
|
3275
|
-
if (process.env.MOLTBOT_ROOT) moltbotRoots.push(process.env.MOLTBOT_ROOT);
|
|
3276
|
-
moltbotRoots.push('/opt/moltbot');
|
|
3277
|
-
moltbotRoots.push('/opt/moltbot/app');
|
|
3278
|
-
|
|
3279
|
-
const scriptCandidates = [];
|
|
3280
|
-
for (const root of moltbotRoots) {
|
|
3281
|
-
scriptCandidates.push(
|
|
3282
|
-
path.join(root, 'moltbot.mjs'),
|
|
3283
|
-
path.join(root, 'bin', 'moltbot.mjs'),
|
|
3284
|
-
path.join(root, 'bin', 'moltbot.js'),
|
|
3285
|
-
path.join(root, 'src', 'moltbot.mjs'),
|
|
3286
|
-
path.join(root, 'src', 'moltbot.js')
|
|
3287
|
-
);
|
|
3288
|
-
}
|
|
3289
|
-
|
|
3290
|
-
for (const candidate of scriptCandidates) {
|
|
3291
|
-
try {
|
|
3292
|
-
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
3293
|
-
return candidate;
|
|
3294
|
-
}
|
|
3295
|
-
} catch { }
|
|
3296
|
-
}
|
|
3297
|
-
|
|
3298
3332
|
// Fallback: use login shell to find the binary (loads .zshrc/.bashrc PATH)
|
|
3299
3333
|
for (const name of candidates) {
|
|
3300
3334
|
if (process.platform === 'win32') {
|
|
@@ -3361,8 +3395,7 @@ function getCliMeta() {
|
|
|
3361
3395
|
const cliBinary = resolveCliBinary();
|
|
3362
3396
|
const cliName = cliBinary ? path.basename(cliBinary) : '';
|
|
3363
3397
|
const cliLower = cliName.toLowerCase();
|
|
3364
|
-
const
|
|
3365
|
-
const nodeMajor = isMoltbot ? 24 : 22;
|
|
3398
|
+
const nodeMajor = 22;
|
|
3366
3399
|
return { cliBinary, cliName, nodeMajor };
|
|
3367
3400
|
}
|
|
3368
3401
|
|
|
@@ -3416,6 +3449,177 @@ function getClaudeCodeSettingsPath() {
|
|
|
3416
3449
|
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
3417
3450
|
}
|
|
3418
3451
|
|
|
3452
|
+
function readTextIfExists(filePath) {
|
|
3453
|
+
if (!filePath || !fs.existsSync(filePath)) return '';
|
|
3454
|
+
try {
|
|
3455
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
3456
|
+
} catch {
|
|
3457
|
+
return '';
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function getHermesDataDir() {
|
|
3462
|
+
const envDir = String(process.env.HERMES_DATA_DIR || '').trim();
|
|
3463
|
+
return envDir || path.join(os.homedir(), '.hermes');
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
function getHermesConfigPath() {
|
|
3467
|
+
const dataDir = getHermesDataDir();
|
|
3468
|
+
const candidates = [
|
|
3469
|
+
path.join(dataDir, 'config.yaml'),
|
|
3470
|
+
path.join(dataDir, 'config.yml')
|
|
3471
|
+
];
|
|
3472
|
+
return candidates.find(filePath => fs.existsSync(filePath)) || candidates[0];
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
function getHermesEnvPath() {
|
|
3476
|
+
return path.join(getHermesDataDir(), '.env');
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
function getHermesWslMirrorInfo() {
|
|
3480
|
+
if (process.platform !== 'win32' || !isWslAvailable()) {
|
|
3481
|
+
return {
|
|
3482
|
+
dataDir: null,
|
|
3483
|
+
sourceConfigPath: null,
|
|
3484
|
+
configPath: null,
|
|
3485
|
+
envPath: null
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
const wslHome = getWslHome() || '/root';
|
|
3490
|
+
const dataDir = path.posix.join(wslHome, '.hermes');
|
|
3491
|
+
const sourceConfigPath = findExistingWslFile([
|
|
3492
|
+
path.posix.join(dataDir, 'config.yaml'),
|
|
3493
|
+
path.posix.join(dataDir, 'config.yml')
|
|
3494
|
+
]);
|
|
3495
|
+
|
|
3496
|
+
return {
|
|
3497
|
+
dataDir,
|
|
3498
|
+
sourceConfigPath,
|
|
3499
|
+
configPath: path.posix.join(dataDir, 'config.yaml'),
|
|
3500
|
+
envPath: path.posix.join(dataDir, '.env')
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
function readWslTextFile(filePath) {
|
|
3505
|
+
if (process.platform !== 'win32' || !filePath) return '';
|
|
3506
|
+
const quoted = shellQuote(filePath);
|
|
3507
|
+
const result = safeExec(`wsl -- bash -lc "cat ${quoted} 2>/dev/null"`, { timeout: 10000 });
|
|
3508
|
+
return result.ok ? (result.output || result.stdout || '') : '';
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
function parseSimpleYamlConfig(text) {
|
|
3512
|
+
const config = {};
|
|
3513
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
3514
|
+
const match = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);
|
|
3515
|
+
if (!match) continue;
|
|
3516
|
+
let value = match[2].trim();
|
|
3517
|
+
if (
|
|
3518
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
3519
|
+
|| (value.startsWith('\'') && value.endsWith('\''))
|
|
3520
|
+
) {
|
|
3521
|
+
try {
|
|
3522
|
+
value = JSON.parse(value);
|
|
3523
|
+
} catch {
|
|
3524
|
+
value = value.slice(1, -1);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
config[match[1]] = value;
|
|
3528
|
+
}
|
|
3529
|
+
return config;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
function serializeYamlScalar(value) {
|
|
3533
|
+
return JSON.stringify(String(value ?? ''));
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
function upsertSimpleYamlConfig(text, entries) {
|
|
3537
|
+
const lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
3538
|
+
const normalized = lines.length === 1 && lines[0] === '' ? [] : [...lines];
|
|
3539
|
+
const seen = new Set();
|
|
3540
|
+
|
|
3541
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
3542
|
+
const match = normalized[i].match(/^([A-Za-z0-9_-]+)\s*:\s*.*$/);
|
|
3543
|
+
if (!match) continue;
|
|
3544
|
+
const key = match[1];
|
|
3545
|
+
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
|
|
3546
|
+
normalized[i] = `${key}: ${serializeYamlScalar(entries[key])}`;
|
|
3547
|
+
seen.add(key);
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
for (const key of Object.keys(entries)) {
|
|
3551
|
+
if (!seen.has(key)) {
|
|
3552
|
+
normalized.push(`${key}: ${serializeYamlScalar(entries[key])}`);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
while (normalized.length > 0 && normalized[normalized.length - 1] === '') {
|
|
3557
|
+
normalized.pop();
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
return normalized.join('\n') + '\n';
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
function parseEnvFile(text) {
|
|
3564
|
+
const entries = {};
|
|
3565
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
3566
|
+
const trimmed = line.trim();
|
|
3567
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
3568
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
3569
|
+
if (!match) continue;
|
|
3570
|
+
let value = match[2].trim();
|
|
3571
|
+
if (
|
|
3572
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
3573
|
+
|| (value.startsWith('\'') && value.endsWith('\''))
|
|
3574
|
+
) {
|
|
3575
|
+
try {
|
|
3576
|
+
value = JSON.parse(value);
|
|
3577
|
+
} catch {
|
|
3578
|
+
value = value.slice(1, -1);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
entries[match[1]] = value;
|
|
3582
|
+
}
|
|
3583
|
+
return entries;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
function upsertEnvFile(text, entries) {
|
|
3587
|
+
const lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
3588
|
+
const normalized = lines.length === 1 && lines[0] === '' ? [] : [...lines];
|
|
3589
|
+
const seen = new Set();
|
|
3590
|
+
|
|
3591
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
3592
|
+
const match = normalized[i].match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
3593
|
+
if (!match) continue;
|
|
3594
|
+
const key = match[1];
|
|
3595
|
+
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
|
|
3596
|
+
normalized[i] = `${key}=${JSON.stringify(String(entries[key] ?? ''))}`;
|
|
3597
|
+
seen.add(key);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
for (const key of Object.keys(entries)) {
|
|
3601
|
+
if (!seen.has(key)) {
|
|
3602
|
+
normalized.push(`${key}=${JSON.stringify(String(entries[key] ?? ''))}`);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
while (normalized.length > 0 && normalized[normalized.length - 1] === '') {
|
|
3607
|
+
normalized.pop();
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
return normalized.join('\n') + '\n';
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
function readHermesYamlConfig(configPath = getHermesConfigPath()) {
|
|
3614
|
+
const raw = readTextIfExists(configPath);
|
|
3615
|
+
return {
|
|
3616
|
+
configPath,
|
|
3617
|
+
configured: !!raw || fs.existsSync(configPath),
|
|
3618
|
+
config: parseSimpleYamlConfig(raw),
|
|
3619
|
+
raw
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3419
3623
|
function getOpencodeConfigPath() {
|
|
3420
3624
|
const home = os.homedir();
|
|
3421
3625
|
return process.platform === 'win32'
|
|
@@ -3875,7 +4079,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3875
4079
|
if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway`);
|
|
3876
4080
|
dockerCmds.push(`${container.cli} gateway`);
|
|
3877
4081
|
}
|
|
3878
|
-
for (const name of
|
|
4082
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
3879
4083
|
dockerCmds.push(`${name} gateway`);
|
|
3880
4084
|
}
|
|
3881
4085
|
|
|
@@ -3907,7 +4111,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3907
4111
|
}
|
|
3908
4112
|
} else {
|
|
3909
4113
|
if (daemonResult.reason === 'cli-not-found') {
|
|
3910
|
-
console.log(chalk.red('❌ 未找到 openclaw
|
|
4114
|
+
console.log(chalk.red('❌ 未找到 openclaw 命令,无法自动启动 Gateway'));
|
|
3911
4115
|
} else {
|
|
3912
4116
|
console.log(chalk.red(`❌ 自动启动失败: ${daemonResult.reason}`));
|
|
3913
4117
|
}
|
|
@@ -3951,15 +4155,9 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3951
4155
|
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
3952
4156
|
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
|
|
3953
4157
|
const useNode = cliBinary && nodeInfo && isNodeShebang(cliBinary);
|
|
3954
|
-
const
|
|
3955
|
-
? (useNode ? `"${nodeInfo.path}" "${cliBinary}" gateway` : `"${cliBinary}" gateway`)
|
|
3956
|
-
: null;
|
|
4158
|
+
const candidates = buildGatewayCommands(cliBinary, nodeInfo, useNode, 'start');
|
|
3957
4159
|
|
|
3958
|
-
const candidates
|
|
3959
|
-
if (cliCmd) candidates.push(cliCmd);
|
|
3960
|
-
candidates.push('openclaw gateway', 'clawdbot gateway', 'moltbot gateway');
|
|
3961
|
-
|
|
3962
|
-
for (const cmd of [...new Set(candidates)].filter(Boolean)) {
|
|
4160
|
+
for (const cmd of candidates) {
|
|
3963
4161
|
console.log(chalk.yellow(`⚠️ 尝试启动 Gateway: ${cmd}`));
|
|
3964
4162
|
if (spawnDetached(cmd, env)) {
|
|
3965
4163
|
if (await waitForGateway(port, '127.0.0.1', 10000)) {
|
|
@@ -3972,8 +4170,8 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
|
|
|
3972
4170
|
if (process.platform !== 'win32') {
|
|
3973
4171
|
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
3974
4172
|
if (!fs.existsSync(sh)) continue;
|
|
3975
|
-
for (const
|
|
3976
|
-
const loginShellCmd =
|
|
4173
|
+
for (const cmd of candidates) {
|
|
4174
|
+
const loginShellCmd = buildLoginShellCommand(sh, cmd);
|
|
3977
4175
|
console.log(chalk.yellow(`⚠️ 尝试启动 Gateway: ${loginShellCmd}`));
|
|
3978
4176
|
if (spawnDetached(loginShellCmd, env)) {
|
|
3979
4177
|
if (await waitForGateway(port, '127.0.0.1', 15000)) {
|
|
@@ -5118,183 +5316,88 @@ async function activateCodex(paths, args = {}) {
|
|
|
5118
5316
|
}
|
|
5119
5317
|
}
|
|
5120
5318
|
|
|
5121
|
-
// ============
|
|
5122
|
-
async function
|
|
5123
|
-
console.log(chalk.cyan.bold('\n
|
|
5319
|
+
// ============ 单独配置 Hermes ============
|
|
5320
|
+
async function activateHermes(paths, args = {}) {
|
|
5321
|
+
console.log(chalk.cyan.bold('\n🔧 配置 Hermes\n'));
|
|
5322
|
+
const selectedModel = await promptClaudeModelSelection(args, '选择 Hermes 默认 Claude 模型:');
|
|
5124
5323
|
|
|
5125
|
-
const
|
|
5126
|
-
|
|
5127
|
-
const claudeProviderName = claudeApiConfig.providerName;
|
|
5128
|
-
const codexProviderName = codexApiConfig.providerName;
|
|
5324
|
+
const shouldTest = !(args['no-test'] || args.noTest);
|
|
5325
|
+
let selectedEndpoint = ENDPOINTS[0];
|
|
5129
5326
|
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5327
|
+
if (shouldTest) {
|
|
5328
|
+
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5329
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5330
|
+
const sorted = speedResult.ranked || [];
|
|
5331
|
+
if (sorted.length > 0) {
|
|
5332
|
+
const defaultEp = ENDPOINTS[0];
|
|
5333
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
5334
|
+
type: 'list',
|
|
5335
|
+
name: 'selectedIndex',
|
|
5336
|
+
message: '选择节点:',
|
|
5337
|
+
choices: [
|
|
5338
|
+
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5339
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5340
|
+
...sorted.map((e, i) => ({
|
|
5341
|
+
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5342
|
+
value: i
|
|
5343
|
+
}))
|
|
5344
|
+
]
|
|
5345
|
+
}]);
|
|
5346
|
+
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5347
|
+
} else {
|
|
5348
|
+
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5349
|
+
const { proceed } = await inquirer.prompt([{
|
|
5350
|
+
type: 'confirm', name: 'proceed',
|
|
5351
|
+
message: '仍要使用默认节点配置吗?', default: false
|
|
5352
|
+
}]);
|
|
5353
|
+
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5133
5356
|
|
|
5134
|
-
// 1. CLI 参数
|
|
5135
|
-
const args = parseArgs(process.argv.slice(2));
|
|
5136
5357
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
5358
|
+
let apiKey;
|
|
5137
5359
|
if (directKey) {
|
|
5138
5360
|
apiKey = directKey;
|
|
5139
|
-
keySource = '命令行参数';
|
|
5140
|
-
}
|
|
5141
|
-
|
|
5142
|
-
// 2. 环境变量
|
|
5143
|
-
if (!apiKey) {
|
|
5144
|
-
const envKeys = ['OPENCLAW_CLAUDE_KEY', 'OPENCLAW_CODEX_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY', 'OPENCLAW_API_KEY'];
|
|
5145
|
-
for (const k of envKeys) {
|
|
5146
|
-
if (process.env[k] && process.env[k].trim()) {
|
|
5147
|
-
apiKey = process.env[k].trim();
|
|
5148
|
-
keySource = `环境变量 ${k}`;
|
|
5149
|
-
break;
|
|
5150
|
-
}
|
|
5151
|
-
}
|
|
5152
|
-
}
|
|
5153
|
-
|
|
5154
|
-
// 3. 已有 OpenClaw 配置(云翼 Claude Code 密钥)
|
|
5155
|
-
if (!apiKey) {
|
|
5156
|
-
try {
|
|
5157
|
-
const config = readConfig(paths.openclawConfig);
|
|
5158
|
-
if (config && config.models && config.models.providers) {
|
|
5159
|
-
// 优先取 claude-yunyi 的 key
|
|
5160
|
-
const preferredOrder = [claudeProviderName, codexProviderName];
|
|
5161
|
-
for (const name of preferredOrder) {
|
|
5162
|
-
const p = config.models.providers[name];
|
|
5163
|
-
if (p && p.apiKey && p.apiKey.trim()) {
|
|
5164
|
-
apiKey = p.apiKey.trim();
|
|
5165
|
-
keySource = `已有配置 (${name})`;
|
|
5166
|
-
break;
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
// 其他 provider 的 key
|
|
5170
|
-
if (!apiKey) {
|
|
5171
|
-
for (const [name, p] of Object.entries(config.models.providers)) {
|
|
5172
|
-
if (p.apiKey && p.apiKey.trim()) {
|
|
5173
|
-
apiKey = p.apiKey.trim();
|
|
5174
|
-
keySource = `已有配置 (${name})`;
|
|
5175
|
-
break;
|
|
5176
|
-
}
|
|
5177
|
-
}
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
} catch { /* ignore */ }
|
|
5181
|
-
}
|
|
5182
|
-
|
|
5183
|
-
// 4. 都没有,提示输入
|
|
5184
|
-
if (apiKey) {
|
|
5185
|
-
const masked = apiKey.length > 8 ? apiKey.slice(0, 5) + '***' + apiKey.slice(-3) : '***';
|
|
5186
|
-
console.log(chalk.green(`✓ 已检测到 API Key: ${masked} (来源: ${keySource})`));
|
|
5187
5361
|
} else {
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
}
|
|
5191
|
-
|
|
5192
|
-
// ---- 静默测速选最快节点 ----
|
|
5193
|
-
const speedSpinner = ora({ text: '正在测速选择最快节点...', spinner: 'dots' }).start();
|
|
5194
|
-
let selectedEndpoint = ENDPOINTS[0];
|
|
5195
|
-
try {
|
|
5196
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5197
|
-
if (speedResult.ranked && speedResult.ranked.length > 0) {
|
|
5198
|
-
selectedEndpoint = speedResult.ranked[0];
|
|
5199
|
-
}
|
|
5200
|
-
speedSpinner.succeed(`节点: ${selectedEndpoint.name}`);
|
|
5201
|
-
} catch {
|
|
5202
|
-
speedSpinner.succeed(`节点: ${selectedEndpoint.name} (默认)`);
|
|
5362
|
+
const envKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5363
|
+
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
5203
5364
|
}
|
|
5365
|
+
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
5204
5366
|
|
|
5205
|
-
|
|
5367
|
+
console.log('');
|
|
5206
5368
|
const validation = await validateApiKey(selectedEndpoint.url, apiKey);
|
|
5207
5369
|
if (!validation.valid) {
|
|
5208
|
-
|
|
5370
|
+
const { continueAnyway } = await inquirer.prompt([{
|
|
5371
|
+
type: 'confirm', name: 'continueAnyway',
|
|
5372
|
+
message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
|
|
5373
|
+
}]);
|
|
5374
|
+
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
5209
5375
|
}
|
|
5210
5376
|
|
|
5211
|
-
// ---- 写入两套配置 ----
|
|
5212
|
-
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
5213
|
-
|
|
5214
|
-
// 增量写入: 只添加/更新云翼 provider,保留其他已有配置
|
|
5215
|
-
|
|
5216
|
-
// Claude 侧
|
|
5217
5377
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
5218
|
-
const
|
|
5219
|
-
const
|
|
5220
|
-
|
|
5378
|
+
const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
|
|
5379
|
+
const hermesPaths = writeHermesConfig(claudeBaseUrl, apiKey, selectedModel.id);
|
|
5380
|
+
writeSpinner.succeed('Hermes 配置写入完成');
|
|
5221
5381
|
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
5233
|
-
|
|
5234
|
-
// Codex 侧
|
|
5235
|
-
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
5236
|
-
const codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.4';
|
|
5237
|
-
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.4' };
|
|
5238
|
-
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
5239
|
-
|
|
5240
|
-
config.models.providers[codexProviderName] = {
|
|
5241
|
-
baseUrl: codexBaseUrl,
|
|
5242
|
-
auth: DEFAULT_AUTH_MODE,
|
|
5243
|
-
api: codexApiConfig.api,
|
|
5244
|
-
headers: {},
|
|
5245
|
-
authHeader: codexApiConfig.api.startsWith('openai'),
|
|
5246
|
-
apiKey: apiKey.trim(),
|
|
5247
|
-
models: [{ id: codexModel.id, name: codexModel.name, contextWindow: codexApiConfig.contextWindow, maxTokens: codexApiConfig.maxTokens }]
|
|
5248
|
-
};
|
|
5249
|
-
config.auth.profiles[`${codexProviderName}:default`] = { provider: codexProviderName, mode: 'api_key' };
|
|
5250
|
-
config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
|
|
5251
|
-
|
|
5252
|
-
// 默认主力: Codex, 备用: Claude
|
|
5253
|
-
config.agents.defaults.model.primary = codexModelKey;
|
|
5254
|
-
config.agents.defaults.model.fallbacks = [claudeModelKey];
|
|
5255
|
-
const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
|
|
5256
|
-
force: true,
|
|
5257
|
-
endpointUrl: selectedEndpoint.url,
|
|
5258
|
-
apiKey
|
|
5259
|
-
});
|
|
5260
|
-
|
|
5261
|
-
// ---- 写入 ----
|
|
5262
|
-
const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
|
|
5263
|
-
createTimestampedBackup(paths.openclawConfig, paths.configDir, 'yycode');
|
|
5264
|
-
ensureGatewaySettings(config);
|
|
5265
|
-
cleanupConflictingEnvVars(config, codexBaseUrl, apiKey);
|
|
5266
|
-
writeConfigWithSync(paths, config);
|
|
5267
|
-
updateAuthProfilesWithSync(paths, claudeProviderName, apiKey);
|
|
5268
|
-
updateAuthProfilesWithSync(paths, codexProviderName, apiKey);
|
|
5269
|
-
if (yunyiLayoutResult.applied) {
|
|
5270
|
-
syncManagedYunyiAuthProfiles(paths, config);
|
|
5382
|
+
console.log(chalk.green('\n✅ Hermes 配置完成!'));
|
|
5383
|
+
console.log(chalk.cyan(` Base URL: ${hermesPaths.baseUrl}`));
|
|
5384
|
+
console.log(chalk.gray(` 模型: ${selectedModel.name} (${selectedModel.id})`));
|
|
5385
|
+
console.log(chalk.gray(' Provider: anthropic'));
|
|
5386
|
+
console.log(chalk.gray(' API Key: 已设置'));
|
|
5387
|
+
console.log(chalk.gray('\n 已写入:'));
|
|
5388
|
+
console.log(chalk.gray(` • ${hermesPaths.configPath}`));
|
|
5389
|
+
console.log(chalk.gray(` • ${hermesPaths.envPath}`));
|
|
5390
|
+
if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
|
|
5391
|
+
console.log(chalk.gray(' • 已额外同步到 WSL ~/.hermes'));
|
|
5271
5392
|
}
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
writeSpinner.succeed('配置写入完成');
|
|
5275
|
-
|
|
5276
|
-
// ---- 结果 ----
|
|
5277
|
-
console.log(chalk.green('\n✅ 配置完成!'));
|
|
5278
|
-
console.log(chalk.cyan(` Claude Code: ${claudeModel.name}`));
|
|
5279
|
-
console.log(chalk.cyan(` Codex CLI: ${codexModel.name}`));
|
|
5280
|
-
printYunyiOpenClawSwitchHint(yunyiLayoutResult);
|
|
5281
|
-
console.log('');
|
|
5393
|
+
console.log(chalk.yellow('\n 提示: Hermes Desktop 的“测试连接”按钮当前可能仍按 /v1/models 检测;按钮不绿,不代表 runtime 不能正常对话'));
|
|
5394
|
+
console.log(chalk.yellow(' 建议: 重启 Hermes Desktop,或手动执行 hermes server --port 8787 验证 Hermes runtime'));
|
|
5282
5395
|
}
|
|
5283
5396
|
|
|
5284
|
-
|
|
5285
5397
|
// ============ 主程序 ============
|
|
5286
5398
|
async function main() {
|
|
5287
5399
|
console.clear();
|
|
5288
5400
|
|
|
5289
|
-
// yycode 精简模式:检测到 yycode CLI 时直接走零交互流程
|
|
5290
|
-
const isYYCode = path.basename(process.argv[1] || '').replace(/\.js$/, '') === 'yycode';
|
|
5291
|
-
if (isYYCode) {
|
|
5292
|
-
const paths = getConfigPath();
|
|
5293
|
-
backupOriginalConfig(paths.openclawConfig, paths.configDir);
|
|
5294
|
-
await yycodeQuickSetup(paths);
|
|
5295
|
-
return;
|
|
5296
|
-
}
|
|
5297
|
-
|
|
5298
5401
|
console.log(chalk.cyan.bold('\n🔧 OpenClaw API 配置工具\n'));
|
|
5299
5402
|
|
|
5300
5403
|
const paths = getConfigPath();
|
|
@@ -5318,6 +5421,10 @@ async function main() {
|
|
|
5318
5421
|
await activateClaudeCode(paths, args);
|
|
5319
5422
|
return;
|
|
5320
5423
|
}
|
|
5424
|
+
if (args.preset === 'hermes' || args._.includes('preset-hermes') || args._.includes('hermes-preset')) {
|
|
5425
|
+
await activateHermes(paths, args);
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5321
5428
|
if (args.preset === 'codex' || args._.includes('preset-codex') || args._.includes('codex-preset')) {
|
|
5322
5429
|
await autoActivate(paths, { ...args, primary: 'codex' });
|
|
5323
5430
|
return;
|
|
@@ -5346,6 +5453,7 @@ async function main() {
|
|
|
5346
5453
|
new inquirer.Separator(' -- 一键配置 --'),
|
|
5347
5454
|
{ name: ' 配置 OpenClaw(Claude + Codex)', value: 'auto_activate' },
|
|
5348
5455
|
{ name: ' 配置 Claude Code', value: 'activate_claude_code' },
|
|
5456
|
+
{ name: ' 配置 Hermes', value: 'activate_hermes' },
|
|
5349
5457
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
5350
5458
|
{ name: ' 配置 Codex CLI', value: 'activate_codex' },
|
|
5351
5459
|
new inquirer.Separator(' -- 工具 --'),
|
|
@@ -5389,6 +5497,9 @@ async function main() {
|
|
|
5389
5497
|
case 'activate_claude_code':
|
|
5390
5498
|
await activateClaudeCode(paths);
|
|
5391
5499
|
break;
|
|
5500
|
+
case 'activate_hermes':
|
|
5501
|
+
await activateHermes(paths);
|
|
5502
|
+
break;
|
|
5392
5503
|
case 'activate_opencode':
|
|
5393
5504
|
await activateOpencode(paths);
|
|
5394
5505
|
break;
|
|
@@ -5984,7 +6095,7 @@ async function testConnection(paths, args = {}) {
|
|
|
5984
6095
|
const container = await selectDockerContainer();
|
|
5985
6096
|
if (!container) {
|
|
5986
6097
|
console.log(chalk.red('❌ 未找到包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器'));
|
|
5987
|
-
console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw
|
|
6098
|
+
console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw'));
|
|
5988
6099
|
return;
|
|
5989
6100
|
}
|
|
5990
6101
|
}
|
|
@@ -6024,8 +6135,6 @@ async function testConnection(paths, args = {}) {
|
|
|
6024
6135
|
console.log(chalk.gray(` 然后执行: ${_selectedDockerContainer.cli === 'node' ? `node ${_selectedDockerContainer.cliPath}` : _selectedDockerContainer.cli} gateway`));
|
|
6025
6136
|
} else {
|
|
6026
6137
|
console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
|
|
6027
|
-
console.log(chalk.gray(' 或: clawdbot gateway'));
|
|
6028
|
-
console.log(chalk.gray(' 或: moltbot gateway'));
|
|
6029
6138
|
}
|
|
6030
6139
|
return;
|
|
6031
6140
|
}
|
|
@@ -6035,7 +6144,7 @@ async function testConnection(paths, args = {}) {
|
|
|
6035
6144
|
if (!restartOk) {
|
|
6036
6145
|
console.log(chalk.yellow('⚠️ Gateway 未能通过常规方式重启,当前使用的可能是之前的 Gateway 进程'));
|
|
6037
6146
|
console.log(chalk.yellow(' 新配置可能未生效。如 bot 不回复,请手动重启 Gateway:'));
|
|
6038
|
-
console.log(chalk.gray(' openclaw gateway restart
|
|
6147
|
+
console.log(chalk.gray(' openclaw gateway restart'));
|
|
6039
6148
|
}
|
|
6040
6149
|
|
|
6041
6150
|
// 步骤2: 通过 Gateway 端点测试(优先使用 CLI agent)
|
|
@@ -6160,7 +6269,7 @@ async function testConnection(paths, args = {}) {
|
|
|
6160
6269
|
console.log(chalk.gray(`\n 建议操作:`));
|
|
6161
6270
|
console.log(chalk.gray(` 1) 复制最新地址并重新打开浏览器(不要用旧书签)`));
|
|
6162
6271
|
console.log(chalk.cyan(` http://127.0.0.1:${gatewayPort}/#token=${gatewayToken}`));
|
|
6163
|
-
console.log(chalk.gray(` 2) 执行 Gateway 重启:openclaw gateway restart
|
|
6272
|
+
console.log(chalk.gray(` 2) 执行 Gateway 重启:openclaw gateway restart`));
|
|
6164
6273
|
console.log(chalk.gray(` 3) 若仍 401,检查是否存在多个配置目录(.openclaw 与 .clawdbot)`));
|
|
6165
6274
|
}
|
|
6166
6275
|
|
|
@@ -6170,7 +6279,7 @@ async function testConnection(paths, args = {}) {
|
|
|
6170
6279
|
console.log(chalk.gray(` 如遇问题,尝试更新 Gateway: npm install -g openclaw@latest && openclaw gateway restart`));
|
|
6171
6280
|
}
|
|
6172
6281
|
|
|
6173
|
-
console.log(chalk.gray(`\n 提示: 如果 Gateway 未运行,请执行: openclaw gateway
|
|
6282
|
+
console.log(chalk.gray(`\n 提示: 如果 Gateway 未运行,请执行: openclaw gateway`));
|
|
6174
6283
|
}
|
|
6175
6284
|
} catch (error) {
|
|
6176
6285
|
console.log(chalk.red(`❌ 测试失败: ${error.message}`));
|
|
@@ -6214,7 +6323,7 @@ function buildDockerInnerCmds(container, verb) {
|
|
|
6214
6323
|
if (container.cliPath) cmds.push(`${container.cliPath} ${verb}`);
|
|
6215
6324
|
cmds.push(`${container.cli} ${verb}`);
|
|
6216
6325
|
}
|
|
6217
|
-
for (const name of
|
|
6326
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6218
6327
|
cmds.push(`${name} ${verb}`);
|
|
6219
6328
|
}
|
|
6220
6329
|
return [...new Set(cmds)].filter(Boolean);
|
|
@@ -6247,7 +6356,7 @@ async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
|
6247
6356
|
// 策略 B:杀容器内旧进程 → spawn 启动新 Gateway → 端口探测
|
|
6248
6357
|
if (!silent) console.log(chalk.gray(' Docker 内常规重启未生效,尝试杀进程后重新启动...'));
|
|
6249
6358
|
try {
|
|
6250
|
-
for (const name of
|
|
6359
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6251
6360
|
safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
|
|
6252
6361
|
}
|
|
6253
6362
|
safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
|
|
@@ -6271,7 +6380,7 @@ async function restartGatewayDocker(gatewayPort, silent = false) {
|
|
|
6271
6380
|
async function restartGatewayWsl(gatewayPort, silent = false) {
|
|
6272
6381
|
if (!silent) console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
|
|
6273
6382
|
const wslCli = getWslCliBinary();
|
|
6274
|
-
const names =
|
|
6383
|
+
const names = GATEWAY_CLI_NAMES;
|
|
6275
6384
|
const portWasOpenBefore = await isPortOpen(gatewayPort, '127.0.0.1', 500);
|
|
6276
6385
|
const beforeSignature = portWasOpenBefore ? getGatewayProcessSignature(gatewayPort, 'wsl') : '';
|
|
6277
6386
|
|
|
@@ -6364,13 +6473,11 @@ async function restartGatewayNative(silent = false) {
|
|
|
6364
6473
|
// 策略 C:用 login shell 启动(加载 nvm/fnm 等 PATH)
|
|
6365
6474
|
if (process.platform !== 'win32') {
|
|
6366
6475
|
if (!silent) console.log(chalk.gray(' 尝试通过 login shell 启动...'));
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
if (
|
|
6372
|
-
if (spawnDetached(`${sh} -lc '${name} gateway'`, env)) {
|
|
6373
|
-
launched = true;
|
|
6476
|
+
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
6477
|
+
if (!fs.existsSync(sh)) continue;
|
|
6478
|
+
for (const cmd of startCmds) {
|
|
6479
|
+
const loginShellCmd = buildLoginShellCommand(sh, cmd);
|
|
6480
|
+
if (spawnDetached(loginShellCmd, env)) {
|
|
6374
6481
|
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
6375
6482
|
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (login shell)'));
|
|
6376
6483
|
return true;
|
|
@@ -6383,10 +6490,8 @@ async function restartGatewayNative(silent = false) {
|
|
|
6383
6490
|
|
|
6384
6491
|
// 全部失败,输出诊断
|
|
6385
6492
|
if (!silent) {
|
|
6386
|
-
console.log(chalk.red(
|
|
6493
|
+
console.log(chalk.red('❌ 重启失败: 找不到 openclaw 命令'));
|
|
6387
6494
|
console.log(chalk.gray(` 请手动运行: openclaw gateway restart`));
|
|
6388
|
-
console.log(chalk.gray(` 或: clawdbot gateway restart`));
|
|
6389
|
-
console.log(chalk.gray(` 或: moltbot gateway restart`));
|
|
6390
6495
|
printGatewayDiagnostics(resolved);
|
|
6391
6496
|
}
|
|
6392
6497
|
return false;
|
|
@@ -6404,7 +6509,7 @@ function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
|
6404
6509
|
}
|
|
6405
6510
|
}
|
|
6406
6511
|
|
|
6407
|
-
const names =
|
|
6512
|
+
const names = GATEWAY_CLI_NAMES;
|
|
6408
6513
|
for (const name of names) commands.push(`${name} ${verb}`);
|
|
6409
6514
|
|
|
6410
6515
|
return [...new Set(commands)].filter(Boolean);
|
|
@@ -6421,13 +6526,13 @@ async function killGatewayProcesses(gatewayPort = 18789) {
|
|
|
6421
6526
|
}
|
|
6422
6527
|
}
|
|
6423
6528
|
if (isWslAvailable()) {
|
|
6424
|
-
for (const name of
|
|
6529
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6425
6530
|
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
6426
6531
|
}
|
|
6427
6532
|
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
6428
6533
|
}
|
|
6429
6534
|
} else {
|
|
6430
|
-
for (const name of
|
|
6535
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6431
6536
|
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
6432
6537
|
}
|
|
6433
6538
|
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
@@ -6444,14 +6549,14 @@ function printGatewayDiagnostics(resolved) {
|
|
|
6444
6549
|
console.log(chalk.gray(`\n [诊断] resolveCliBinary = ${resolved || 'null'}`));
|
|
6445
6550
|
const npmPrefix = safeExec('npm prefix -g');
|
|
6446
6551
|
if (npmPrefix.ok) console.log(chalk.gray(` [诊断] npm prefix -g = ${npmPrefix.output}`));
|
|
6447
|
-
for (const name of
|
|
6552
|
+
for (const name of GATEWAY_CLI_NAMES) {
|
|
6448
6553
|
const which = safeExec(process.platform === 'win32' ? `where ${name} 2>nul` : `/bin/zsh -lc "command -v ${name}" 2>/dev/null || /bin/bash -lc "command -v ${name}" 2>/dev/null`);
|
|
6449
6554
|
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
6450
6555
|
}
|
|
6451
6556
|
if (isDockerAvailable()) {
|
|
6452
6557
|
const dContainers = findOpenclawDockerContainers();
|
|
6453
6558
|
if (dContainers.length > 0) {
|
|
6454
|
-
console.log(chalk.gray(
|
|
6559
|
+
console.log(chalk.gray(' [诊断] Docker 容器 (含 openclaw):'));
|
|
6455
6560
|
for (const c of dContainers) {
|
|
6456
6561
|
console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
|
|
6457
6562
|
}
|
|
@@ -6542,7 +6647,7 @@ function testGatewayApi(port, token, model, endpoint = '/v1/chat/completions') {
|
|
|
6542
6647
|
|
|
6543
6648
|
req.on('error', (e) => {
|
|
6544
6649
|
if (e.code === 'ECONNREFUSED') {
|
|
6545
|
-
resolve({ success: false, error: 'Gateway 未运行,请先启动: openclaw gateway
|
|
6650
|
+
resolve({ success: false, error: 'Gateway 未运行,请先启动: openclaw gateway' });
|
|
6546
6651
|
} else {
|
|
6547
6652
|
resolve({ success: false, error: e.message });
|
|
6548
6653
|
}
|
|
@@ -6569,14 +6674,14 @@ function testGatewayViaAgent(model, agentId = '') {
|
|
|
6569
6674
|
const agentCmd = `${wslCli} ${subcommand}`;
|
|
6570
6675
|
cmd = `wsl -- bash -c '${agentCmd.replace(/'/g, "'\\''")}'`;
|
|
6571
6676
|
} else {
|
|
6572
|
-
const agentCmd = `openclaw ${subcommand}
|
|
6677
|
+
const agentCmd = `openclaw ${subcommand}`;
|
|
6573
6678
|
cmd = `wsl -- bash -lc '${agentCmd.replace(/'/g, "'\\''")}'`;
|
|
6574
6679
|
}
|
|
6575
6680
|
execOpts = { timeout: 120000 };
|
|
6576
6681
|
} else {
|
|
6577
6682
|
const { cliBinary, nodeMajor } = getCliMeta();
|
|
6578
6683
|
if (!cliBinary) {
|
|
6579
|
-
resolve({ success: false, usedCli: false, error: '未找到 openclaw
|
|
6684
|
+
resolve({ success: false, usedCli: false, error: '未找到 openclaw 命令' });
|
|
6580
6685
|
return;
|
|
6581
6686
|
}
|
|
6582
6687
|
const nodeInfo = findCompatibleNode(nodeMajor);
|