yymaxapi 1.0.22 → 1.0.24
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 +254 -67
- package/install.ps1 +126 -0
- package/install.sh +188 -0
- package/package.json +4 -2
package/bin/yymaxapi.js
CHANGED
|
@@ -507,15 +507,15 @@ function getConfigPath() {
|
|
|
507
507
|
// 将 WSL 配置复制到 Windows 侧,保持同步
|
|
508
508
|
const winDest = path.join(openclawStateDir, path.basename(wp));
|
|
509
509
|
try {
|
|
510
|
-
const content = execFileSync('wsl', ['bash', '-c', `cat '${wp}'`], { encoding: 'utf8', timeout: 10000 });
|
|
510
|
+
const content = execFileSync('wsl', ['bash', '-c', `cat '${wp}'`], { encoding: 'utf8', timeout: 10000, stdio: 'pipe' });
|
|
511
511
|
if (!fs.existsSync(openclawStateDir)) fs.mkdirSync(openclawStateDir, { recursive: true });
|
|
512
512
|
fs.writeFileSync(winDest, content, 'utf8');
|
|
513
513
|
if (!candidates.includes(winDest)) candidates.unshift(winDest);
|
|
514
|
-
} catch {}
|
|
514
|
+
} catch { }
|
|
515
515
|
break;
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
|
-
} catch {}
|
|
518
|
+
} catch { }
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
const defaultConfig = preferMoltbot
|
|
@@ -618,7 +618,7 @@ function isWslAvailable() {
|
|
|
618
618
|
function getWslHome() {
|
|
619
619
|
if (_wslHomeCache !== undefined) return _wslHomeCache;
|
|
620
620
|
try {
|
|
621
|
-
_wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000 }).trim() || null;
|
|
621
|
+
_wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim() || null;
|
|
622
622
|
} catch { _wslHomeCache = null; }
|
|
623
623
|
return _wslHomeCache;
|
|
624
624
|
}
|
|
@@ -630,12 +630,12 @@ function getWslCliBinary() {
|
|
|
630
630
|
if (_wslCliBinaryCache !== undefined) return _wslCliBinaryCache;
|
|
631
631
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
632
632
|
try {
|
|
633
|
-
const result = execFileSync('wsl', ['bash', '-lc', `which ${name} 2>/dev/null`], { encoding: 'utf8', timeout: 5000 }).trim();
|
|
633
|
+
const result = execFileSync('wsl', ['bash', '-lc', `which ${name} 2>/dev/null`], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim();
|
|
634
634
|
if (result && result.startsWith('/')) {
|
|
635
635
|
_wslCliBinaryCache = result;
|
|
636
636
|
return _wslCliBinaryCache;
|
|
637
637
|
}
|
|
638
|
-
} catch {}
|
|
638
|
+
} catch { }
|
|
639
639
|
}
|
|
640
640
|
_wslCliBinaryCache = null;
|
|
641
641
|
return null;
|
|
@@ -663,7 +663,7 @@ function detectGatewayEnv(port = 18789) {
|
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
|
-
} catch {}
|
|
666
|
+
} catch { }
|
|
667
667
|
// Fallback: Windows 没有原生 OpenClaw CLI,但 WSL 里有 → 判定为 WSL
|
|
668
668
|
const nativeCli = resolveCliBinary();
|
|
669
669
|
if (!nativeCli) {
|
|
@@ -705,7 +705,7 @@ function syncConfigToWsl(windowsConfigPath) {
|
|
|
705
705
|
if (!match) return;
|
|
706
706
|
const wslSrc = `/mnt/${match[1].toLowerCase()}/${match[2]}`;
|
|
707
707
|
const wslDest = `${wslHome}/.openclaw/openclaw.json`;
|
|
708
|
-
execFileSync('wsl', ['bash', '-c', `mkdir -p "${wslHome}/.openclaw" && cp "${wslSrc}" "${wslDest}"`], { timeout: 10000, stdio: '
|
|
708
|
+
execFileSync('wsl', ['bash', '-c', `mkdir -p "${wslHome}/.openclaw" && cp "${wslSrc}" "${wslDest}"`], { timeout: 10000, stdio: 'pipe' });
|
|
709
709
|
} catch { /* best-effort */ }
|
|
710
710
|
}
|
|
711
711
|
|
|
@@ -880,7 +880,7 @@ function readAuthStore(authProfilesPath) {
|
|
|
880
880
|
for (const [k, v] of Object.entries(raw)) {
|
|
881
881
|
if (v && typeof v === 'object' && v.type) store.profiles[k] = v;
|
|
882
882
|
}
|
|
883
|
-
} catch {}
|
|
883
|
+
} catch { }
|
|
884
884
|
return store;
|
|
885
885
|
}
|
|
886
886
|
|
|
@@ -1037,7 +1037,7 @@ function resolveCliBinary() {
|
|
|
1037
1037
|
if (fs.existsSync(override) && fs.statSync(override).isFile()) {
|
|
1038
1038
|
return override;
|
|
1039
1039
|
}
|
|
1040
|
-
} catch {}
|
|
1040
|
+
} catch { }
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
1043
|
// Validate that a found binary is a real gateway CLI, not yymaxapi itself
|
|
@@ -1078,7 +1078,7 @@ function resolveCliBinary() {
|
|
|
1078
1078
|
if (fs.existsSync(full) && fs.statSync(full).isFile() && isRealCli(full)) {
|
|
1079
1079
|
return full;
|
|
1080
1080
|
}
|
|
1081
|
-
} catch {}
|
|
1081
|
+
} catch { }
|
|
1082
1082
|
}
|
|
1083
1083
|
}
|
|
1084
1084
|
}
|
|
@@ -1104,7 +1104,7 @@ function resolveCliBinary() {
|
|
|
1104
1104
|
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
1105
1105
|
return candidate;
|
|
1106
1106
|
}
|
|
1107
|
-
} catch {}
|
|
1107
|
+
} catch { }
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
1110
|
// Fallback: use login shell to find the binary (loads .zshrc/.bashrc PATH)
|
|
@@ -1136,7 +1136,7 @@ function resolveCliBinary() {
|
|
|
1136
1136
|
const full = path.join(npmBin, name);
|
|
1137
1137
|
try {
|
|
1138
1138
|
if (fs.existsSync(full) && fs.statSync(full).isFile() && isRealCli(full)) return full;
|
|
1139
|
-
} catch {}
|
|
1139
|
+
} catch { }
|
|
1140
1140
|
}
|
|
1141
1141
|
}
|
|
1142
1142
|
|
|
@@ -1155,14 +1155,14 @@ function resolveCliBinary() {
|
|
|
1155
1155
|
extraSearchDirs.push(path.join(nvmVersionsDir, entry, 'bin'));
|
|
1156
1156
|
}
|
|
1157
1157
|
}
|
|
1158
|
-
} catch {}
|
|
1158
|
+
} catch { }
|
|
1159
1159
|
|
|
1160
1160
|
for (const dir of extraSearchDirs) {
|
|
1161
1161
|
for (const name of candidates) {
|
|
1162
1162
|
const full = path.join(dir, name);
|
|
1163
1163
|
try {
|
|
1164
1164
|
if (fs.existsSync(full) && fs.statSync(full).isFile() && isRealCli(full)) return full;
|
|
1165
|
-
} catch {}
|
|
1165
|
+
} catch { }
|
|
1166
1166
|
}
|
|
1167
1167
|
}
|
|
1168
1168
|
|
|
@@ -1204,7 +1204,7 @@ function findCompatibleNode(minMajor = 22) {
|
|
|
1204
1204
|
for (const entry of entries) {
|
|
1205
1205
|
candidates.push(path.join(nvmVersionsDir, entry, 'bin', 'node'));
|
|
1206
1206
|
}
|
|
1207
|
-
} catch {}
|
|
1207
|
+
} catch { }
|
|
1208
1208
|
}
|
|
1209
1209
|
|
|
1210
1210
|
const seen = new Set();
|
|
@@ -1220,7 +1220,7 @@ function findCompatibleNode(minMajor = 22) {
|
|
|
1220
1220
|
if (major && major >= minMajor) {
|
|
1221
1221
|
return { path: candidate, version: version.trim(), major };
|
|
1222
1222
|
}
|
|
1223
|
-
} catch {}
|
|
1223
|
+
} catch { }
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
1226
|
return null;
|
|
@@ -1350,7 +1350,7 @@ function installMacGatewayDaemon() {
|
|
|
1350
1350
|
const domain = `gui/${uid}`;
|
|
1351
1351
|
const service = `${domain}/${label}`;
|
|
1352
1352
|
|
|
1353
|
-
try { execFileSync('launchctl', ['bootout', service], { stdio: 'ignore' }); } catch {}
|
|
1353
|
+
try { execFileSync('launchctl', ['bootout', service], { stdio: 'ignore' }); } catch { }
|
|
1354
1354
|
execFileSync('launchctl', ['bootstrap', domain, plistPath], { stdio: 'ignore' });
|
|
1355
1355
|
execFileSync('launchctl', ['kickstart', '-k', service], { stdio: 'ignore' });
|
|
1356
1356
|
|
|
@@ -1550,14 +1550,14 @@ async function quickSetup(paths, args = {}) {
|
|
|
1550
1550
|
const { relayType } = initialType
|
|
1551
1551
|
? { relayType: initialType }
|
|
1552
1552
|
: await inquirer.prompt([{
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1553
|
+
type: 'list',
|
|
1554
|
+
name: 'relayType',
|
|
1555
|
+
message: '选择类型:',
|
|
1556
|
+
choices: [
|
|
1557
|
+
{ name: 'Claude', value: 'claude' },
|
|
1558
|
+
{ name: 'Codex / GPT', value: 'codex' }
|
|
1559
|
+
]
|
|
1560
|
+
}]);
|
|
1561
1561
|
|
|
1562
1562
|
const type = relayType;
|
|
1563
1563
|
const typeLabel = type === 'claude' ? 'Claude' : 'Codex';
|
|
@@ -1877,11 +1877,11 @@ async function presetClaude(paths, args = {}) {
|
|
|
1877
1877
|
const shouldTestGateway = args.test !== undefined
|
|
1878
1878
|
? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
|
|
1879
1879
|
: await inquirer.prompt([{
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1880
|
+
type: 'confirm',
|
|
1881
|
+
name: 'testGateway',
|
|
1882
|
+
message: '是否立即通过 OpenClaw Gateway 测试?',
|
|
1883
|
+
default: true
|
|
1884
|
+
}]).then(r => r.testGateway);
|
|
1885
1885
|
|
|
1886
1886
|
if (shouldTestGateway) {
|
|
1887
1887
|
await testConnection(paths, args);
|
|
@@ -2067,11 +2067,11 @@ async function presetCodex(paths, args = {}) {
|
|
|
2067
2067
|
const shouldTestGateway = args.test !== undefined
|
|
2068
2068
|
? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
|
|
2069
2069
|
: await inquirer.prompt([{
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2070
|
+
type: 'confirm',
|
|
2071
|
+
name: 'testGateway',
|
|
2072
|
+
message: '是否立即通过 OpenClaw Gateway 测试?',
|
|
2073
|
+
default: true
|
|
2074
|
+
}]).then(r => r.testGateway);
|
|
2075
2075
|
|
|
2076
2076
|
if (shouldTestGateway) {
|
|
2077
2077
|
await testConnection(paths, args);
|
|
@@ -2405,7 +2405,7 @@ async function testConnection(paths, args = {}) {
|
|
|
2405
2405
|
|
|
2406
2406
|
// 步骤1: 先重启 Gateway 使配置生效
|
|
2407
2407
|
console.log(chalk.cyan('步骤 1/2: 重启 Gateway 使配置生效...'));
|
|
2408
|
-
await restartGateway();
|
|
2408
|
+
const restartOk = await restartGateway();
|
|
2409
2409
|
|
|
2410
2410
|
// 等待 Gateway 启动
|
|
2411
2411
|
const gwSpinner = ora({ text: '等待 Gateway 启动...', spinner: 'dots' }).start();
|
|
@@ -2428,6 +2428,12 @@ async function testConnection(paths, args = {}) {
|
|
|
2428
2428
|
|
|
2429
2429
|
gwSpinner.succeed('Gateway 已启动');
|
|
2430
2430
|
|
|
2431
|
+
if (!restartOk) {
|
|
2432
|
+
console.log(chalk.yellow('⚠️ Gateway 未能通过常规方式重启,当前使用的可能是之前的 Gateway 进程'));
|
|
2433
|
+
console.log(chalk.yellow(' 新配置可能未生效。如 bot 不回复,请手动重启 Gateway:'));
|
|
2434
|
+
console.log(chalk.gray(' openclaw gateway restart 或 clawdbot gateway restart'));
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2431
2437
|
// 步骤2: 通过 Gateway 端点测试(优先使用 CLI agent)
|
|
2432
2438
|
console.log(chalk.cyan(`\n步骤 2/2: 测试 Gateway API 端点...`));
|
|
2433
2439
|
|
|
@@ -2555,12 +2561,12 @@ async function restartGateway() {
|
|
|
2555
2561
|
const wslCli = getWslCliBinary();
|
|
2556
2562
|
return new Promise((resolve) => {
|
|
2557
2563
|
const wslCmds = wslCli
|
|
2558
|
-
? [`wsl -- bash -
|
|
2564
|
+
? [`wsl -- bash -lc "${wslCli} gateway restart"`]
|
|
2559
2565
|
: [
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2566
|
+
'wsl -- bash -lc "openclaw gateway restart"',
|
|
2567
|
+
'wsl -- bash -lc "clawdbot gateway restart"',
|
|
2568
|
+
'wsl -- bash -lc "moltbot gateway restart"',
|
|
2569
|
+
];
|
|
2564
2570
|
let tried = 0;
|
|
2565
2571
|
const tryNext = () => {
|
|
2566
2572
|
if (tried >= wslCmds.length) {
|
|
@@ -2569,11 +2575,15 @@ async function restartGateway() {
|
|
|
2569
2575
|
return;
|
|
2570
2576
|
}
|
|
2571
2577
|
const cmd = wslCmds[tried++];
|
|
2572
|
-
exec(cmd, { timeout: 30000 }, (error) => {
|
|
2573
|
-
|
|
2574
|
-
|
|
2578
|
+
exec(cmd, { timeout: 30000 }, (error, stdout, stderr) => {
|
|
2579
|
+
const errStr = (stderr || '').toLowerCase();
|
|
2580
|
+
const wslGarbled = errStr.includes('wsl:') && errStr.includes('localhost') && errStr.includes('nat');
|
|
2581
|
+
|
|
2582
|
+
if (error || errStr.includes('/usr/bin/env:') || wslGarbled) {
|
|
2583
|
+
tryNext();
|
|
2584
|
+
} else {
|
|
2575
2585
|
console.log(chalk.green('Gateway 已重启 (WSL)'));
|
|
2576
|
-
resolve();
|
|
2586
|
+
resolve(true);
|
|
2577
2587
|
}
|
|
2578
2588
|
});
|
|
2579
2589
|
};
|
|
@@ -2584,6 +2594,103 @@ async function restartGateway() {
|
|
|
2584
2594
|
return restartGatewayNative();
|
|
2585
2595
|
}
|
|
2586
2596
|
|
|
2597
|
+
// 强制重启 Gateway:杀掉旧进程,再用已找到的 CLI 路径启动新 Gateway
|
|
2598
|
+
async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort = 18789) {
|
|
2599
|
+
console.log(chalk.yellow('\n🔄 尝试强制重启 Gateway(杀旧进程 → 启动新进程)...'));
|
|
2600
|
+
|
|
2601
|
+
// 1. 杀掉旧 Gateway 进程
|
|
2602
|
+
try {
|
|
2603
|
+
if (process.platform === 'win32') {
|
|
2604
|
+
// Windows: 通过 taskkill 杀掉占用端口的 node 进程
|
|
2605
|
+
const findPid = safeExec(`netstat -ano | findstr ":${gatewayPort}"`, { timeout: 5000 });
|
|
2606
|
+
if (findPid.ok && findPid.output) {
|
|
2607
|
+
const lines = findPid.output.split('\n').filter(l => l.includes('LISTENING'));
|
|
2608
|
+
for (const line of lines) {
|
|
2609
|
+
const pid = line.trim().split(/\s+/).pop();
|
|
2610
|
+
if (pid && /^\d+$/.test(pid)) {
|
|
2611
|
+
safeExec(`taskkill /F /PID ${pid}`, { timeout: 5000 });
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
// Windows + WSL: 也清理 WSL 内的 gateway 进程
|
|
2616
|
+
if (isWslAvailable()) {
|
|
2617
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2618
|
+
safeExec(`wsl -- bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
|
|
2619
|
+
}
|
|
2620
|
+
safeExec(`wsl -- bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
|
|
2621
|
+
}
|
|
2622
|
+
} else {
|
|
2623
|
+
// Linux/macOS: pkill gateway 相关进程
|
|
2624
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2625
|
+
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
2626
|
+
}
|
|
2627
|
+
// 备用:通过端口找进程并杀掉
|
|
2628
|
+
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
2629
|
+
if (lsof.ok && lsof.output) {
|
|
2630
|
+
for (const pid of lsof.output.trim().split('\n').filter(Boolean)) {
|
|
2631
|
+
if (/^\d+$/.test(pid.trim())) {
|
|
2632
|
+
safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
console.log(chalk.gray(' 已尝试清理旧 Gateway 进程'));
|
|
2638
|
+
} catch { /* ignore */ }
|
|
2639
|
+
|
|
2640
|
+
// 2. 等待端口释放
|
|
2641
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2642
|
+
|
|
2643
|
+
// 3. 用已找到的 CLI 路径启动新 Gateway
|
|
2644
|
+
const startCmds = [];
|
|
2645
|
+
|
|
2646
|
+
// 优先用已解析的完整路径
|
|
2647
|
+
if (resolved) {
|
|
2648
|
+
if (useNode && nodeInfo) {
|
|
2649
|
+
startCmds.push(`"${nodeInfo.path}" "${resolved}" gateway`);
|
|
2650
|
+
} else {
|
|
2651
|
+
startCmds.push(`"${resolved}" gateway`);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
// Fallback: login shell 方式(加载 nvm 等 PATH)
|
|
2656
|
+
if (process.platform !== 'win32') {
|
|
2657
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2658
|
+
startCmds.push(`bash -lc '${name} gateway'`);
|
|
2659
|
+
}
|
|
2660
|
+
} else {
|
|
2661
|
+
// Windows: 先尝试原生命令
|
|
2662
|
+
startCmds.push('openclaw gateway', 'clawdbot gateway', 'moltbot gateway');
|
|
2663
|
+
// Windows + WSL: 也尝试通过 WSL 启动 gateway
|
|
2664
|
+
if (isWslAvailable()) {
|
|
2665
|
+
const wslCli = getWslCliBinary();
|
|
2666
|
+
if (wslCli) {
|
|
2667
|
+
startCmds.push(`wsl -- bash -lc "${wslCli} gateway"`);
|
|
2668
|
+
}
|
|
2669
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2670
|
+
startCmds.push(`wsl -- bash -lc "${name} gateway"`);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
for (const cmd of [...new Set(startCmds)].filter(Boolean)) {
|
|
2676
|
+
console.log(chalk.gray(` 尝试启动: ${cmd}`));
|
|
2677
|
+
if (spawnDetached(cmd, env)) {
|
|
2678
|
+
// 等待新 Gateway 启动
|
|
2679
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
2680
|
+
console.log(chalk.green('✅ Gateway 已强制重启成功'));
|
|
2681
|
+
console.log(chalk.gray(' 现在可以在 Web/Telegram/Discord 等渠道测试对话了'));
|
|
2682
|
+
return true;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
console.log(chalk.red('❌ 强制重启也失败了'));
|
|
2688
|
+
console.log(chalk.gray(' 请手动重启 Gateway:'));
|
|
2689
|
+
console.log(chalk.gray(' 1. 关闭旧进程: pkill -f "gateway" 或 taskkill'));
|
|
2690
|
+
console.log(chalk.gray(' 2. 启动新进程: openclaw gateway / clawdbot gateway / moltbot gateway'));
|
|
2691
|
+
return false;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2587
2694
|
async function restartGatewayNative() {
|
|
2588
2695
|
const { cliBinary: resolved, nodeMajor } = getCliMeta();
|
|
2589
2696
|
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
@@ -2593,16 +2700,27 @@ async function restartGatewayNative() {
|
|
|
2593
2700
|
// 尝试多种命令
|
|
2594
2701
|
const commands = resolved
|
|
2595
2702
|
? [
|
|
2596
|
-
|
|
2597
|
-
|
|
2703
|
+
useNode ? `"${nodeInfo.path}" "${resolved}" gateway restart` : `"${resolved}" gateway restart`
|
|
2704
|
+
]
|
|
2598
2705
|
: [
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2706
|
+
'openclaw gateway restart',
|
|
2707
|
+
'clawdbot gateway restart',
|
|
2708
|
+
'moltbot gateway restart',
|
|
2709
|
+
'npx openclaw gateway restart',
|
|
2710
|
+
'npx clawdbot gateway restart',
|
|
2711
|
+
'npx moltbot gateway restart'
|
|
2712
|
+
];
|
|
2713
|
+
|
|
2714
|
+
// Windows + WSL: 追加 WSL 命令作为额外回退
|
|
2715
|
+
if (process.platform === 'win32' && isWslAvailable()) {
|
|
2716
|
+
const wslCli = getWslCliBinary();
|
|
2717
|
+
if (wslCli) {
|
|
2718
|
+
commands.push(`wsl -- bash -lc "${wslCli} gateway restart"`);
|
|
2719
|
+
}
|
|
2720
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2721
|
+
commands.push(`wsl -- bash -lc "${name} gateway restart"`);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2606
2724
|
|
|
2607
2725
|
return new Promise((resolve) => {
|
|
2608
2726
|
let tried = 0;
|
|
@@ -2621,8 +2739,8 @@ async function restartGatewayNative() {
|
|
|
2621
2739
|
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`);
|
|
2622
2740
|
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
2623
2741
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2742
|
+
// 尝试强制重启
|
|
2743
|
+
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
2626
2744
|
return;
|
|
2627
2745
|
}
|
|
2628
2746
|
|
|
@@ -2635,7 +2753,7 @@ async function restartGatewayNative() {
|
|
|
2635
2753
|
} else {
|
|
2636
2754
|
console.log(chalk.green(`✅ Gateway 已重启`));
|
|
2637
2755
|
console.log(chalk.gray(` 现在可以在 Web/Telegram/Discord 等渠道测试对话了`));
|
|
2638
|
-
resolve();
|
|
2756
|
+
resolve(true);
|
|
2639
2757
|
}
|
|
2640
2758
|
});
|
|
2641
2759
|
};
|
|
@@ -2650,13 +2768,13 @@ function testGatewayApi(port, token, model, endpoint = '/v1/responses') {
|
|
|
2650
2768
|
const isChatCompletions = endpoint.includes('chat/completions');
|
|
2651
2769
|
const postData = isChatCompletions
|
|
2652
2770
|
? JSON.stringify({
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2771
|
+
model: model,
|
|
2772
|
+
messages: [{ role: 'user', content: '你是什么模型?' }]
|
|
2773
|
+
})
|
|
2656
2774
|
: JSON.stringify({
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2775
|
+
model: model,
|
|
2776
|
+
input: '你是什么模型?'
|
|
2777
|
+
});
|
|
2660
2778
|
|
|
2661
2779
|
const options = {
|
|
2662
2780
|
hostname: '127.0.0.1',
|
|
@@ -2768,6 +2886,18 @@ function testGatewayViaAgent(model) {
|
|
|
2768
2886
|
exec(cmd, execOpts, (error, stdout, stderr) => {
|
|
2769
2887
|
// 过滤 stderr 中的 Node.js DeprecationWarning 噪音
|
|
2770
2888
|
const cleanStderr = (stderr || '').replace(/\(node:\d+\) \[DEP\d+\] DeprecationWarning:.*(\n.*trace-deprecation.*)?/g, '').trim();
|
|
2889
|
+
const errStr = cleanStderr.toLowerCase();
|
|
2890
|
+
|
|
2891
|
+
const wslGarbled = errStr.includes('wsl:') && errStr.includes('localhost') && errStr.includes('nat');
|
|
2892
|
+
|
|
2893
|
+
if (gwEnv === 'wsl' && (errStr.includes('/usr/bin/env: ‘node’: no such file') || wslGarbled)) {
|
|
2894
|
+
resolve({
|
|
2895
|
+
success: false,
|
|
2896
|
+
usedCli: true,
|
|
2897
|
+
error: 'WSL 子系统中未安装 Node.js。请使用 `wsl -u root apt install nodejs` 修复环境。\nGateway 可能暂无法在 WSL 内使用 CLI 诊断。'
|
|
2898
|
+
});
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2771
2901
|
|
|
2772
2902
|
if (error) {
|
|
2773
2903
|
// 即使 exec 报错,stdout 中可能仍有有效 JSON(如 CLI 输出了结果但 exit code 非零)
|
|
@@ -2865,9 +2995,9 @@ function sanitizeModelReply(message, options = {}) {
|
|
|
2865
2995
|
|
|
2866
2996
|
const tailPattern = tokens.length
|
|
2867
2997
|
? new RegExp(
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2998
|
+
`[((][^))]*(?:${tokens.join('|')}|[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+)[^))]*[))]\\s*$`,
|
|
2999
|
+
'i'
|
|
3000
|
+
)
|
|
2871
3001
|
: /[((][^))]*[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^))]*[))]\s*$/i;
|
|
2872
3002
|
|
|
2873
3003
|
text = text
|
|
@@ -3308,7 +3438,64 @@ async function restore(paths) {
|
|
|
3308
3438
|
}
|
|
3309
3439
|
}
|
|
3310
3440
|
|
|
3441
|
+
// ============ 环境自检 & 自动安装 ============
|
|
3442
|
+
function ensureGit() {
|
|
3443
|
+
const hasGit = safeExec('git --version');
|
|
3444
|
+
if (hasGit.ok) return;
|
|
3445
|
+
|
|
3446
|
+
console.log(chalk.yellow('\n⚠ 未检测到 Git,正在尝试自动安装...\n'));
|
|
3447
|
+
const platform = os.platform();
|
|
3448
|
+
|
|
3449
|
+
if (platform === 'win32') {
|
|
3450
|
+
// Windows: winget 优先
|
|
3451
|
+
const hasWinget = safeExec('winget --version');
|
|
3452
|
+
if (hasWinget.ok) {
|
|
3453
|
+
console.log(chalk.cyan('→ 通过 winget 安装 Git...'));
|
|
3454
|
+
const result = safeExec('winget install Git.Git --accept-source-agreements --accept-package-agreements --silent', { stdio: 'inherit', timeout: 300000 });
|
|
3455
|
+
if (result.ok) {
|
|
3456
|
+
console.log(chalk.green('✓ Git 安装成功,请重新打开终端后再次运行'));
|
|
3457
|
+
process.exit(0);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
console.log(chalk.red('✗ 自动安装失败,请手动安装 Git: https://git-scm.com'));
|
|
3461
|
+
process.exit(1);
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
if (platform === 'darwin') {
|
|
3465
|
+
// macOS: xcode-select
|
|
3466
|
+
console.log(chalk.cyan('→ 通过 Xcode Command Line Tools 安装 Git...'));
|
|
3467
|
+
const result = safeExec('xcode-select --install', { stdio: 'inherit', timeout: 600000 });
|
|
3468
|
+
if (!result.ok && !result.stderr?.includes('already installed')) {
|
|
3469
|
+
console.log(chalk.yellow(' 请在弹出窗口中点击「安装」,完成后重新运行'));
|
|
3470
|
+
}
|
|
3471
|
+
process.exit(0);
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
// Linux / WSL
|
|
3475
|
+
const pmCmds = [
|
|
3476
|
+
{ check: 'apt-get', install: 'sudo apt-get update -qq && sudo apt-get install -y -qq git' },
|
|
3477
|
+
{ check: 'dnf', install: 'sudo dnf install -y git' },
|
|
3478
|
+
{ check: 'yum', install: 'sudo yum install -y git' },
|
|
3479
|
+
{ check: 'pacman', install: 'sudo pacman -S --noconfirm git' },
|
|
3480
|
+
{ check: 'apk', install: 'sudo apk add git' },
|
|
3481
|
+
{ check: 'zypper', install: 'sudo zypper install -y git' },
|
|
3482
|
+
];
|
|
3483
|
+
for (const pm of pmCmds) {
|
|
3484
|
+
if (safeExec(`command -v ${pm.check}`).ok) {
|
|
3485
|
+
console.log(chalk.cyan(`→ 通过 ${pm.check} 安装 Git...`));
|
|
3486
|
+
const result = safeExec(pm.install, { stdio: 'inherit', timeout: 300000 });
|
|
3487
|
+
if (result.ok && safeExec('git --version').ok) {
|
|
3488
|
+
console.log(chalk.green('✓ Git 安装成功'));
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
console.log(chalk.red('✗ 自动安装失败,请手动安装 Git: https://git-scm.com'));
|
|
3494
|
+
process.exit(1);
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3311
3497
|
// 启动
|
|
3498
|
+
ensureGit();
|
|
3312
3499
|
main().catch(error => {
|
|
3313
3500
|
exitWithError('CONFIG_FAILED', error.message, [
|
|
3314
3501
|
'检查网络连接',
|
package/install.ps1
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# maxapi 一键环境安装脚本(Windows PowerShell)
|
|
3
|
+
# 用法(Node.js 不存在时,先装 Node.js 再 npx):
|
|
4
|
+
# .\install.ps1
|
|
5
|
+
# $env:PACKAGE='llmaxapi'; .\install.ps1
|
|
6
|
+
# ============================================================
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
$PACKAGE = if ($env:PACKAGE) { $env:PACKAGE } else { "yymaxapi" }
|
|
10
|
+
$MIN_NODE_MAJOR = 18
|
|
11
|
+
|
|
12
|
+
# ---------- 颜色输出 ----------
|
|
13
|
+
function Write-Ok($msg) { Write-Host " $msg" -ForegroundColor Green }
|
|
14
|
+
function Write-Info($msg) { Write-Host " $msg" -ForegroundColor Cyan }
|
|
15
|
+
function Write-Warn($msg) { Write-Host " $msg" -ForegroundColor Yellow }
|
|
16
|
+
function Write-Fail($msg) { Write-Host " $msg" -ForegroundColor Red; exit 1 }
|
|
17
|
+
|
|
18
|
+
Write-Host ""
|
|
19
|
+
Write-Host " maxapi 一键环境安装 ($PACKAGE)" -ForegroundColor Cyan
|
|
20
|
+
Write-Host " ========================================" -ForegroundColor Cyan
|
|
21
|
+
Write-Host ""
|
|
22
|
+
|
|
23
|
+
# ---------- 工具函数 ----------
|
|
24
|
+
function Test-Command($cmd) {
|
|
25
|
+
return [bool](Get-Command $cmd -ErrorAction SilentlyContinue)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Get-NodeMajor {
|
|
29
|
+
if (-not (Test-Command "node")) { return 0 }
|
|
30
|
+
$ver = (node -v) -replace '^v','' -split '\.'
|
|
31
|
+
return [int]$ver[0]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Refresh-Path {
|
|
35
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
|
|
36
|
+
[System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Test-HasWinget {
|
|
40
|
+
return [bool](Get-Command "winget" -ErrorAction SilentlyContinue)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ---------- 安装 Git ----------
|
|
44
|
+
function Install-GitIfNeeded {
|
|
45
|
+
if (Test-Command "git") {
|
|
46
|
+
Write-Ok "Git $(git --version) 已安装"
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
Write-Info "安装 Git..."
|
|
50
|
+
if (Test-HasWinget) {
|
|
51
|
+
winget install Git.Git --accept-source-agreements --accept-package-agreements --silent
|
|
52
|
+
Refresh-Path
|
|
53
|
+
} else {
|
|
54
|
+
Write-Warn "未检测到 winget,尝试下载 Git 安装包..."
|
|
55
|
+
$gitUrl = "https://github.com/git-for-windows/git/releases/latest/download/Git-64-bit.exe"
|
|
56
|
+
$gitInstaller = "$env:TEMP\git-installer.exe"
|
|
57
|
+
Invoke-WebRequest -Uri $gitUrl -OutFile $gitInstaller -UseBasicParsing
|
|
58
|
+
Start-Process -FilePath $gitInstaller -ArgumentList "/VERYSILENT","/NORESTART" -Wait
|
|
59
|
+
Remove-Item $gitInstaller -Force -ErrorAction SilentlyContinue
|
|
60
|
+
Refresh-Path
|
|
61
|
+
}
|
|
62
|
+
if (Test-Command "git") { Write-Ok "Git 安装成功" }
|
|
63
|
+
else { Write-Fail "Git 安装失败,请手动安装: https://git-scm.com" }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ---------- 安装 Node.js ----------
|
|
67
|
+
function Install-NodeIfNeeded {
|
|
68
|
+
if ((Get-NodeMajor) -ge $MIN_NODE_MAJOR) {
|
|
69
|
+
Write-Ok "Node.js $(node -v) 已安装"
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (Test-Command "node") {
|
|
73
|
+
Write-Warn "Node.js $(node -v) 版本过低,需要 >= $MIN_NODE_MAJOR"
|
|
74
|
+
}
|
|
75
|
+
Write-Info "安装 Node.js LTS..."
|
|
76
|
+
if (Test-HasWinget) {
|
|
77
|
+
winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent
|
|
78
|
+
Refresh-Path
|
|
79
|
+
} else {
|
|
80
|
+
Write-Info "未检测到 winget,尝试下载 Node.js 安装包..."
|
|
81
|
+
$arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" }
|
|
82
|
+
$nodeUrl = "https://nodejs.org/dist/latest-v22.x/node-v22.0.0-${arch}.msi"
|
|
83
|
+
# 获取实际最新 LTS 版本
|
|
84
|
+
try {
|
|
85
|
+
$versions = Invoke-RestMethod "https://nodejs.org/dist/index.json" -UseBasicParsing
|
|
86
|
+
$lts = ($versions | Where-Object { $_.lts -ne $false } | Select-Object -First 1).version
|
|
87
|
+
$nodeUrl = "https://nodejs.org/dist/${lts}/node-${lts}-${arch}.msi"
|
|
88
|
+
} catch {
|
|
89
|
+
Write-Warn "无法获取最新版本号,使用默认版本"
|
|
90
|
+
}
|
|
91
|
+
$nodeInstaller = "$env:TEMP\node-installer.msi"
|
|
92
|
+
Invoke-WebRequest -Uri $nodeUrl -OutFile $nodeInstaller -UseBasicParsing
|
|
93
|
+
Start-Process msiexec.exe -ArgumentList "/i","$nodeInstaller","/qn","/norestart" -Wait
|
|
94
|
+
Remove-Item $nodeInstaller -Force -ErrorAction SilentlyContinue
|
|
95
|
+
Refresh-Path
|
|
96
|
+
}
|
|
97
|
+
if ((Get-NodeMajor) -ge $MIN_NODE_MAJOR) {
|
|
98
|
+
Write-Ok "Node.js $(node -v) 安装成功"
|
|
99
|
+
} else {
|
|
100
|
+
Write-Fail "Node.js 安装失败,请手动安装: https://nodejs.org"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ========== 主流程 ==========
|
|
105
|
+
|
|
106
|
+
# 修复执行策略(仅当前用户)
|
|
107
|
+
try {
|
|
108
|
+
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force -ErrorAction SilentlyContinue
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
Install-GitIfNeeded
|
|
112
|
+
Install-NodeIfNeeded
|
|
113
|
+
|
|
114
|
+
Write-Host ""
|
|
115
|
+
Write-Host " ========================================" -ForegroundColor Green
|
|
116
|
+
Write-Host " 环境安装完成!" -ForegroundColor Green
|
|
117
|
+
Write-Host " ========================================" -ForegroundColor Green
|
|
118
|
+
Write-Host ""
|
|
119
|
+
Write-Ok "Git: $(git --version)"
|
|
120
|
+
Write-Ok "Node: $(node -v)"
|
|
121
|
+
Write-Ok "npm: $(npm -v)"
|
|
122
|
+
Write-Host ""
|
|
123
|
+
|
|
124
|
+
Write-Info "正在启动 ${PACKAGE}..."
|
|
125
|
+
Write-Host ""
|
|
126
|
+
& npx.cmd "${PACKAGE}@latest"
|
package/install.sh
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================
|
|
3
|
+
# maxapi 一键环境安装脚本(macOS / Linux / WSL)
|
|
4
|
+
# 用法(Node.js 不存在时,先装 Node.js 再 npx):
|
|
5
|
+
# bash install.sh
|
|
6
|
+
# bash install.sh llmaxapi
|
|
7
|
+
# bash install.sh --no-run
|
|
8
|
+
# ============================================================
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# ---------- 配置 ----------
|
|
12
|
+
PACKAGE="${1:-yymaxapi}"
|
|
13
|
+
NO_RUN=false
|
|
14
|
+
MIN_NODE_MAJOR=18
|
|
15
|
+
|
|
16
|
+
for arg in "$@"; do
|
|
17
|
+
case "$arg" in
|
|
18
|
+
--no-run) NO_RUN=true ;;
|
|
19
|
+
yymaxapi|llmaxapi) PACKAGE="$arg" ;;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
# ---------- 颜色 ----------
|
|
24
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'
|
|
25
|
+
YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
26
|
+
|
|
27
|
+
ok() { echo -e "${GREEN}✓${NC} $1"; }
|
|
28
|
+
info() { echo -e "${CYAN}→${NC} $1"; }
|
|
29
|
+
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
30
|
+
fail() { echo -e "${RED}✗${NC} $1"; exit 1; }
|
|
31
|
+
|
|
32
|
+
# ---------- 平台检测 ----------
|
|
33
|
+
OS="$(uname -s)"
|
|
34
|
+
ARCH="$(uname -m)"
|
|
35
|
+
IS_WSL=false
|
|
36
|
+
grep -qiE '(microsoft|wsl)' /proc/version 2>/dev/null && IS_WSL=true
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
echo -e "${CYAN}╔══════════════════════════════════════╗${NC}"
|
|
40
|
+
echo -e "${CYAN}║ maxapi 一键环境安装 (${PACKAGE}) ${NC}"
|
|
41
|
+
echo -e "${CYAN}╚══════════════════════════════════════╝${NC}"
|
|
42
|
+
echo ""
|
|
43
|
+
info "系统: ${OS} ${ARCH}$($IS_WSL && echo ' (WSL)' || true)"
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
# ---------- 工具函数 ----------
|
|
47
|
+
has_cmd() { command -v "$1" &>/dev/null; }
|
|
48
|
+
|
|
49
|
+
node_version_ok() {
|
|
50
|
+
if ! has_cmd node; then return 1; fi
|
|
51
|
+
local ver
|
|
52
|
+
ver="$(node -v | tr -d 'v' | cut -d. -f1)"
|
|
53
|
+
[ "$ver" -ge "$MIN_NODE_MAJOR" ] 2>/dev/null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
detect_pkg_manager() {
|
|
57
|
+
if has_cmd apt-get; then echo "apt"; return; fi
|
|
58
|
+
if has_cmd dnf; then echo "dnf"; return; fi
|
|
59
|
+
if has_cmd yum; then echo "yum"; return; fi
|
|
60
|
+
if has_cmd pacman; then echo "pacman"; return; fi
|
|
61
|
+
if has_cmd apk; then echo "apk"; return; fi
|
|
62
|
+
if has_cmd zypper; then echo "zypper"; return; fi
|
|
63
|
+
if has_cmd brew; then echo "brew"; return; fi
|
|
64
|
+
echo "unknown"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ---------- 安装 Git ----------
|
|
68
|
+
install_git() {
|
|
69
|
+
if has_cmd git; then
|
|
70
|
+
ok "Git $(git --version | awk '{print $3}') 已安装"
|
|
71
|
+
return
|
|
72
|
+
fi
|
|
73
|
+
info "安装 Git..."
|
|
74
|
+
case "$OS" in
|
|
75
|
+
Darwin)
|
|
76
|
+
if has_cmd brew; then
|
|
77
|
+
brew install git
|
|
78
|
+
else
|
|
79
|
+
info "通过 Xcode Command Line Tools 安装..."
|
|
80
|
+
xcode-select --install 2>/dev/null || true
|
|
81
|
+
warn "请在弹出窗口中点击「安装」,完成后重新运行此脚本"
|
|
82
|
+
exit 0
|
|
83
|
+
fi ;;
|
|
84
|
+
Linux)
|
|
85
|
+
local pm; pm="$(detect_pkg_manager)"
|
|
86
|
+
case "$pm" in
|
|
87
|
+
apt) sudo apt-get update -qq && sudo apt-get install -y -qq git ;;
|
|
88
|
+
dnf) sudo dnf install -y git ;;
|
|
89
|
+
yum) sudo yum install -y git ;;
|
|
90
|
+
pacman) sudo pacman -S --noconfirm git ;;
|
|
91
|
+
apk) sudo apk add git ;;
|
|
92
|
+
zypper) sudo zypper install -y git ;;
|
|
93
|
+
*) fail "无法自动安装 Git,请手动安装: https://git-scm.com" ;;
|
|
94
|
+
esac ;;
|
|
95
|
+
*) fail "不支持的操作系统: $OS" ;;
|
|
96
|
+
esac
|
|
97
|
+
has_cmd git && ok "Git 安装成功" || fail "Git 安装失败"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# ---------- 安装 Node.js ----------
|
|
101
|
+
install_node() {
|
|
102
|
+
if node_version_ok; then
|
|
103
|
+
ok "Node.js $(node -v) 已安装"
|
|
104
|
+
return
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if has_cmd node; then
|
|
108
|
+
warn "Node.js $(node -v) 版本过低,需要 >= ${MIN_NODE_MAJOR}"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
info "安装 Node.js (via fnm)..."
|
|
112
|
+
|
|
113
|
+
# fnm: 快速 Node 版本管理器,单二进制,跨平台
|
|
114
|
+
if ! has_cmd fnm; then
|
|
115
|
+
curl -fsSL https://fnm.vercel.app/install | bash -s -- --skip-shell
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# 加载 fnm 到当前 shell
|
|
119
|
+
export FNM_DIR="${FNM_DIR:-$HOME/.local/share/fnm}"
|
|
120
|
+
if [ -d "$FNM_DIR" ]; then
|
|
121
|
+
export PATH="$FNM_DIR:$PATH"
|
|
122
|
+
elif [ -d "$HOME/.fnm" ]; then
|
|
123
|
+
export PATH="$HOME/.fnm:$PATH"
|
|
124
|
+
fi
|
|
125
|
+
eval "$(fnm env)" 2>/dev/null || true
|
|
126
|
+
|
|
127
|
+
fnm install --lts
|
|
128
|
+
fnm use lts-latest
|
|
129
|
+
eval "$(fnm env)" 2>/dev/null || true
|
|
130
|
+
|
|
131
|
+
if node_version_ok; then
|
|
132
|
+
ok "Node.js $(node -v) 安装成功"
|
|
133
|
+
else
|
|
134
|
+
fail "Node.js 安装失败,请手动安装: https://nodejs.org"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# 提示用户添加 fnm 到 shell profile
|
|
138
|
+
local shell_profile=""
|
|
139
|
+
case "${SHELL:-}" in
|
|
140
|
+
*/zsh) shell_profile="$HOME/.zshrc" ;;
|
|
141
|
+
*/bash) shell_profile="$HOME/.bashrc" ;;
|
|
142
|
+
*/fish) shell_profile="$HOME/.config/fish/config.fish" ;;
|
|
143
|
+
esac
|
|
144
|
+
|
|
145
|
+
if [ -n "$shell_profile" ] && ! grep -q 'fnm env' "$shell_profile" 2>/dev/null; then
|
|
146
|
+
warn "建议将以下内容添加到 ${shell_profile}:"
|
|
147
|
+
echo ' eval "$(fnm env --use-on-cd --shell '"${SHELL##*/}"')"'
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# ---------- 安装 curl(极少数 Linux 缺失) ----------
|
|
152
|
+
install_curl() {
|
|
153
|
+
if has_cmd curl; then return; fi
|
|
154
|
+
info "安装 curl..."
|
|
155
|
+
local pm; pm="$(detect_pkg_manager)"
|
|
156
|
+
case "$pm" in
|
|
157
|
+
apt) sudo apt-get update -qq && sudo apt-get install -y -qq curl ;;
|
|
158
|
+
dnf) sudo dnf install -y curl ;;
|
|
159
|
+
yum) sudo yum install -y curl ;;
|
|
160
|
+
pacman) sudo pacman -S --noconfirm curl ;;
|
|
161
|
+
apk) sudo apk add curl ;;
|
|
162
|
+
*) fail "请先手动安装 curl" ;;
|
|
163
|
+
esac
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# ========== 主流程 ==========
|
|
167
|
+
install_curl
|
|
168
|
+
install_git
|
|
169
|
+
install_node
|
|
170
|
+
|
|
171
|
+
echo ""
|
|
172
|
+
echo -e "${GREEN}╔══════════════════════════════════════╗${NC}"
|
|
173
|
+
echo -e "${GREEN}║ 环境安装完成! ${NC}"
|
|
174
|
+
echo -e "${GREEN}╚══════════════════════════════════════╝${NC}"
|
|
175
|
+
echo ""
|
|
176
|
+
ok "Git: $(git --version | awk '{print $3}')"
|
|
177
|
+
ok "Node: $(node -v)"
|
|
178
|
+
ok "npm: $(npm -v)"
|
|
179
|
+
echo ""
|
|
180
|
+
|
|
181
|
+
if [ "$NO_RUN" = true ]; then
|
|
182
|
+
info "跳过运行 ${PACKAGE}(--no-run)"
|
|
183
|
+
info "手动运行: npx ${PACKAGE}@latest"
|
|
184
|
+
else
|
|
185
|
+
info "正在启动 ${PACKAGE}..."
|
|
186
|
+
echo ""
|
|
187
|
+
npx "${PACKAGE}@latest"
|
|
188
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yymaxapi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
4
4
|
"description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
|
|
5
5
|
"main": "bin/yymaxapi.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"bin/",
|
|
27
27
|
"lib/",
|
|
28
28
|
"README.md",
|
|
29
|
-
"config/API节点设置.md"
|
|
29
|
+
"config/API节点设置.md",
|
|
30
|
+
"install.sh",
|
|
31
|
+
"install.ps1"
|
|
30
32
|
],
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"inquirer": "^8.2.5",
|