yymaxapi 1.0.22 → 1.0.23
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 +224 -66
- 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
|
|
|
@@ -2557,10 +2563,10 @@ async function restartGateway() {
|
|
|
2557
2563
|
const wslCmds = wslCli
|
|
2558
2564
|
? [`wsl -- bash -c "${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,85 @@ 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
|
+
} else {
|
|
2616
|
+
// Linux/macOS: pkill gateway 相关进程
|
|
2617
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2618
|
+
safeExec(`pkill -f '${name}.*gateway' 2>/dev/null || true`);
|
|
2619
|
+
}
|
|
2620
|
+
// 备用:通过端口找进程并杀掉
|
|
2621
|
+
const lsof = safeExec(`lsof -ti :${gatewayPort} 2>/dev/null`);
|
|
2622
|
+
if (lsof.ok && lsof.output) {
|
|
2623
|
+
for (const pid of lsof.output.trim().split('\n').filter(Boolean)) {
|
|
2624
|
+
if (/^\d+$/.test(pid.trim())) {
|
|
2625
|
+
safeExec(`kill -9 ${pid.trim()} 2>/dev/null || true`);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
console.log(chalk.gray(' 已尝试清理旧 Gateway 进程'));
|
|
2631
|
+
} catch { /* ignore */ }
|
|
2632
|
+
|
|
2633
|
+
// 2. 等待端口释放
|
|
2634
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2635
|
+
|
|
2636
|
+
// 3. 用已找到的 CLI 路径启动新 Gateway
|
|
2637
|
+
const startCmds = [];
|
|
2638
|
+
|
|
2639
|
+
// 优先用已解析的完整路径
|
|
2640
|
+
if (resolved) {
|
|
2641
|
+
if (useNode && nodeInfo) {
|
|
2642
|
+
startCmds.push(`"${nodeInfo.path}" "${resolved}" gateway`);
|
|
2643
|
+
} else {
|
|
2644
|
+
startCmds.push(`"${resolved}" gateway`);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// Fallback: login shell 方式(加载 nvm 等 PATH)
|
|
2649
|
+
if (process.platform !== 'win32') {
|
|
2650
|
+
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
2651
|
+
startCmds.push(`bash -lc '${name} gateway'`);
|
|
2652
|
+
}
|
|
2653
|
+
} else {
|
|
2654
|
+
startCmds.push('openclaw gateway', 'clawdbot gateway', 'moltbot gateway');
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
for (const cmd of [...new Set(startCmds)].filter(Boolean)) {
|
|
2658
|
+
console.log(chalk.gray(` 尝试启动: ${cmd}`));
|
|
2659
|
+
if (spawnDetached(cmd, env)) {
|
|
2660
|
+
// 等待新 Gateway 启动
|
|
2661
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
|
|
2662
|
+
console.log(chalk.green('✅ Gateway 已强制重启成功'));
|
|
2663
|
+
console.log(chalk.gray(' 现在可以在 Web/Telegram/Discord 等渠道测试对话了'));
|
|
2664
|
+
return true;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
console.log(chalk.red('❌ 强制重启也失败了'));
|
|
2670
|
+
console.log(chalk.gray(' 请手动重启 Gateway:'));
|
|
2671
|
+
console.log(chalk.gray(' 1. 关闭旧进程: pkill -f "gateway" 或 taskkill'));
|
|
2672
|
+
console.log(chalk.gray(' 2. 启动新进程: openclaw gateway / clawdbot gateway / moltbot gateway'));
|
|
2673
|
+
return false;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2587
2676
|
async function restartGatewayNative() {
|
|
2588
2677
|
const { cliBinary: resolved, nodeMajor } = getCliMeta();
|
|
2589
2678
|
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
@@ -2593,16 +2682,16 @@ async function restartGatewayNative() {
|
|
|
2593
2682
|
// 尝试多种命令
|
|
2594
2683
|
const commands = resolved
|
|
2595
2684
|
? [
|
|
2596
|
-
|
|
2597
|
-
|
|
2685
|
+
useNode ? `"${nodeInfo.path}" "${resolved}" gateway restart` : `"${resolved}" gateway restart`
|
|
2686
|
+
]
|
|
2598
2687
|
: [
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2688
|
+
'openclaw gateway restart',
|
|
2689
|
+
'clawdbot gateway restart',
|
|
2690
|
+
'moltbot gateway restart',
|
|
2691
|
+
'npx openclaw gateway restart',
|
|
2692
|
+
'npx clawdbot gateway restart',
|
|
2693
|
+
'npx moltbot gateway restart'
|
|
2694
|
+
];
|
|
2606
2695
|
|
|
2607
2696
|
return new Promise((resolve) => {
|
|
2608
2697
|
let tried = 0;
|
|
@@ -2621,8 +2710,8 @@ async function restartGatewayNative() {
|
|
|
2621
2710
|
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
2711
|
if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
|
|
2623
2712
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2713
|
+
// 尝试强制重启
|
|
2714
|
+
forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
|
|
2626
2715
|
return;
|
|
2627
2716
|
}
|
|
2628
2717
|
|
|
@@ -2635,7 +2724,7 @@ async function restartGatewayNative() {
|
|
|
2635
2724
|
} else {
|
|
2636
2725
|
console.log(chalk.green(`✅ Gateway 已重启`));
|
|
2637
2726
|
console.log(chalk.gray(` 现在可以在 Web/Telegram/Discord 等渠道测试对话了`));
|
|
2638
|
-
resolve();
|
|
2727
|
+
resolve(true);
|
|
2639
2728
|
}
|
|
2640
2729
|
});
|
|
2641
2730
|
};
|
|
@@ -2650,13 +2739,13 @@ function testGatewayApi(port, token, model, endpoint = '/v1/responses') {
|
|
|
2650
2739
|
const isChatCompletions = endpoint.includes('chat/completions');
|
|
2651
2740
|
const postData = isChatCompletions
|
|
2652
2741
|
? JSON.stringify({
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2742
|
+
model: model,
|
|
2743
|
+
messages: [{ role: 'user', content: '你是什么模型?' }]
|
|
2744
|
+
})
|
|
2656
2745
|
: JSON.stringify({
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2746
|
+
model: model,
|
|
2747
|
+
input: '你是什么模型?'
|
|
2748
|
+
});
|
|
2660
2749
|
|
|
2661
2750
|
const options = {
|
|
2662
2751
|
hostname: '127.0.0.1',
|
|
@@ -2768,6 +2857,18 @@ function testGatewayViaAgent(model) {
|
|
|
2768
2857
|
exec(cmd, execOpts, (error, stdout, stderr) => {
|
|
2769
2858
|
// 过滤 stderr 中的 Node.js DeprecationWarning 噪音
|
|
2770
2859
|
const cleanStderr = (stderr || '').replace(/\(node:\d+\) \[DEP\d+\] DeprecationWarning:.*(\n.*trace-deprecation.*)?/g, '').trim();
|
|
2860
|
+
const errStr = cleanStderr.toLowerCase();
|
|
2861
|
+
|
|
2862
|
+
const wslGarbled = errStr.includes('wsl:') && errStr.includes('localhost') && errStr.includes('nat');
|
|
2863
|
+
|
|
2864
|
+
if (gwEnv === 'wsl' && (errStr.includes('/usr/bin/env: ‘node’: no such file') || wslGarbled)) {
|
|
2865
|
+
resolve({
|
|
2866
|
+
success: false,
|
|
2867
|
+
usedCli: true,
|
|
2868
|
+
error: 'WSL 子系统中未安装 Node.js。请使用 `wsl -u root apt install nodejs` 修复环境。\nGateway 可能暂无法在 WSL 内使用 CLI 诊断。'
|
|
2869
|
+
});
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2771
2872
|
|
|
2772
2873
|
if (error) {
|
|
2773
2874
|
// 即使 exec 报错,stdout 中可能仍有有效 JSON(如 CLI 输出了结果但 exit code 非零)
|
|
@@ -2865,9 +2966,9 @@ function sanitizeModelReply(message, options = {}) {
|
|
|
2865
2966
|
|
|
2866
2967
|
const tailPattern = tokens.length
|
|
2867
2968
|
? new RegExp(
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2969
|
+
`[((][^))]*(?:${tokens.join('|')}|[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+)[^))]*[))]\\s*$`,
|
|
2970
|
+
'i'
|
|
2971
|
+
)
|
|
2871
2972
|
: /[((][^))]*[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^))]*[))]\s*$/i;
|
|
2872
2973
|
|
|
2873
2974
|
text = text
|
|
@@ -3308,7 +3409,64 @@ async function restore(paths) {
|
|
|
3308
3409
|
}
|
|
3309
3410
|
}
|
|
3310
3411
|
|
|
3412
|
+
// ============ 环境自检 & 自动安装 ============
|
|
3413
|
+
function ensureGit() {
|
|
3414
|
+
const hasGit = safeExec('git --version');
|
|
3415
|
+
if (hasGit.ok) return;
|
|
3416
|
+
|
|
3417
|
+
console.log(chalk.yellow('\n⚠ 未检测到 Git,正在尝试自动安装...\n'));
|
|
3418
|
+
const platform = os.platform();
|
|
3419
|
+
|
|
3420
|
+
if (platform === 'win32') {
|
|
3421
|
+
// Windows: winget 优先
|
|
3422
|
+
const hasWinget = safeExec('winget --version');
|
|
3423
|
+
if (hasWinget.ok) {
|
|
3424
|
+
console.log(chalk.cyan('→ 通过 winget 安装 Git...'));
|
|
3425
|
+
const result = safeExec('winget install Git.Git --accept-source-agreements --accept-package-agreements --silent', { stdio: 'inherit', timeout: 300000 });
|
|
3426
|
+
if (result.ok) {
|
|
3427
|
+
console.log(chalk.green('✓ Git 安装成功,请重新打开终端后再次运行'));
|
|
3428
|
+
process.exit(0);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
console.log(chalk.red('✗ 自动安装失败,请手动安装 Git: https://git-scm.com'));
|
|
3432
|
+
process.exit(1);
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
if (platform === 'darwin') {
|
|
3436
|
+
// macOS: xcode-select
|
|
3437
|
+
console.log(chalk.cyan('→ 通过 Xcode Command Line Tools 安装 Git...'));
|
|
3438
|
+
const result = safeExec('xcode-select --install', { stdio: 'inherit', timeout: 600000 });
|
|
3439
|
+
if (!result.ok && !result.stderr?.includes('already installed')) {
|
|
3440
|
+
console.log(chalk.yellow(' 请在弹出窗口中点击「安装」,完成后重新运行'));
|
|
3441
|
+
}
|
|
3442
|
+
process.exit(0);
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
// Linux / WSL
|
|
3446
|
+
const pmCmds = [
|
|
3447
|
+
{ check: 'apt-get', install: 'sudo apt-get update -qq && sudo apt-get install -y -qq git' },
|
|
3448
|
+
{ check: 'dnf', install: 'sudo dnf install -y git' },
|
|
3449
|
+
{ check: 'yum', install: 'sudo yum install -y git' },
|
|
3450
|
+
{ check: 'pacman', install: 'sudo pacman -S --noconfirm git' },
|
|
3451
|
+
{ check: 'apk', install: 'sudo apk add git' },
|
|
3452
|
+
{ check: 'zypper', install: 'sudo zypper install -y git' },
|
|
3453
|
+
];
|
|
3454
|
+
for (const pm of pmCmds) {
|
|
3455
|
+
if (safeExec(`command -v ${pm.check}`).ok) {
|
|
3456
|
+
console.log(chalk.cyan(`→ 通过 ${pm.check} 安装 Git...`));
|
|
3457
|
+
const result = safeExec(pm.install, { stdio: 'inherit', timeout: 300000 });
|
|
3458
|
+
if (result.ok && safeExec('git --version').ok) {
|
|
3459
|
+
console.log(chalk.green('✓ Git 安装成功'));
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
console.log(chalk.red('✗ 自动安装失败,请手动安装 Git: https://git-scm.com'));
|
|
3465
|
+
process.exit(1);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3311
3468
|
// 启动
|
|
3469
|
+
ensureGit();
|
|
3312
3470
|
main().catch(error => {
|
|
3313
3471
|
exitWithError('CONFIG_FAILED', error.message, [
|
|
3314
3472
|
'检查网络连接',
|
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.23",
|
|
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",
|