yymaxapi 1.0.31 → 1.0.33
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 +197 -52
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -979,7 +979,7 @@ function detectGatewayEnv(port = 18789) {
|
|
|
979
979
|
}
|
|
980
980
|
}
|
|
981
981
|
} catch { }
|
|
982
|
-
// Fallback: Windows 没有原生 OpenClaw CLI
|
|
982
|
+
// Fallback: Windows 没有原生 OpenClaw CLI → 检查 WSL / Docker
|
|
983
983
|
const nativeCli = resolveCliBinary();
|
|
984
984
|
if (!nativeCli) {
|
|
985
985
|
const wslCli = getWslCliBinary();
|
|
@@ -993,6 +993,12 @@ function detectGatewayEnv(port = 18789) {
|
|
|
993
993
|
_gwEnvCache = 'docker';
|
|
994
994
|
return 'docker';
|
|
995
995
|
}
|
|
996
|
+
// Windows 没有原生 CLI,WSL 可用但未找到具体 CLI 路径
|
|
997
|
+
// 仍然判定为 WSL(Gateway 大概率在 WSL 里,CLI 可能不在 bash -l 的 PATH 中)
|
|
998
|
+
if (isWslAvailable()) {
|
|
999
|
+
_gwEnvCache = 'wsl';
|
|
1000
|
+
return 'wsl';
|
|
1001
|
+
}
|
|
996
1002
|
}
|
|
997
1003
|
_gwEnvCache = 'native';
|
|
998
1004
|
return 'native';
|
|
@@ -2501,60 +2507,207 @@ async function presetCodex(paths, args = {}) {
|
|
|
2501
2507
|
}
|
|
2502
2508
|
}
|
|
2503
2509
|
|
|
2504
|
-
// ============
|
|
2505
|
-
async function autoActivate(paths) {
|
|
2506
|
-
console.log(chalk.cyan.bold('\n🚀
|
|
2510
|
+
// ============ 一键激活(同时配置 Claude Code + Codex) ============
|
|
2511
|
+
async function autoActivate(paths, args = {}) {
|
|
2512
|
+
console.log(chalk.cyan.bold('\n🚀 一键激活(同时配置 Claude Code + Codex)\n'));
|
|
2507
2513
|
|
|
2508
|
-
const
|
|
2509
|
-
|
|
2514
|
+
const claudeApiConfig = API_CONFIG.claude;
|
|
2515
|
+
const codexApiConfig = API_CONFIG.codex;
|
|
2516
|
+
const claudeProviderName = claudeApiConfig.providerName;
|
|
2517
|
+
const codexProviderName = codexApiConfig.providerName;
|
|
2510
2518
|
|
|
2511
|
-
//
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2519
|
+
// ---- 测速选节点 ----
|
|
2520
|
+
const shouldTest = !(args['no-test'] || args.noTest);
|
|
2521
|
+
let selectedEndpoint = ENDPOINTS[0];
|
|
2514
2522
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2523
|
+
if (shouldTest) {
|
|
2524
|
+
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
2525
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
2517
2526
|
|
|
2527
|
+
const sorted = speedResult.ranked || [];
|
|
2528
|
+
if (sorted.length > 0) {
|
|
2529
|
+
const defaultEp = ENDPOINTS[0];
|
|
2530
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
2531
|
+
type: 'list',
|
|
2532
|
+
name: 'selectedIndex',
|
|
2533
|
+
message: '选择节点:',
|
|
2534
|
+
choices: [
|
|
2535
|
+
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
2536
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
2537
|
+
...sorted.map((e, i) => ({
|
|
2538
|
+
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
2539
|
+
value: i
|
|
2540
|
+
}))
|
|
2541
|
+
]
|
|
2542
|
+
}]);
|
|
2543
|
+
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
2544
|
+
if (speedResult.usedFallback) {
|
|
2545
|
+
console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
|
|
2546
|
+
}
|
|
2547
|
+
} else {
|
|
2548
|
+
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
2549
|
+
const { proceed } = await inquirer.prompt([{
|
|
2550
|
+
type: 'confirm', name: 'proceed',
|
|
2551
|
+
message: '仍要写入默认节点配置吗?', default: false
|
|
2552
|
+
}]);
|
|
2553
|
+
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// ---- API Key ----
|
|
2558
|
+
const apiKeyEnvFallbacks = [
|
|
2559
|
+
'OPENCLAW_CLAUDE_KEY',
|
|
2560
|
+
'OPENCLAW_CODEX_KEY',
|
|
2561
|
+
'CLAUDE_API_KEY',
|
|
2562
|
+
'OPENAI_API_KEY',
|
|
2563
|
+
'OPENCLAW_API_KEY'
|
|
2564
|
+
];
|
|
2565
|
+
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
2566
|
+
let apiKey;
|
|
2567
|
+
if (directKey) {
|
|
2568
|
+
apiKey = directKey;
|
|
2569
|
+
} else {
|
|
2570
|
+
const envKey = getApiKeyFromArgs({}, apiKeyEnvFallbacks);
|
|
2571
|
+
apiKey = await promptApiKey('请输入 API Key(同时用于 Claude Code 和 Codex):', envKey || '');
|
|
2572
|
+
}
|
|
2573
|
+
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
2574
|
+
|
|
2575
|
+
// ---- 验证 ----
|
|
2576
|
+
console.log('');
|
|
2577
|
+
const validation = await validateApiKey(selectedEndpoint.url, apiKey);
|
|
2518
2578
|
if (!validation.valid) {
|
|
2519
2579
|
const { continueAnyway } = await inquirer.prompt([{
|
|
2520
2580
|
type: 'confirm', name: 'continueAnyway',
|
|
2521
|
-
message: 'API Key
|
|
2581
|
+
message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
|
|
2522
2582
|
}]);
|
|
2523
2583
|
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
2524
2584
|
}
|
|
2525
2585
|
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
const hasClaude = CLAUDE_MODELS && CLAUDE_MODELS.length > 0;
|
|
2540
|
-
const hasCodex = CODEX_MODELS && CODEX_MODELS.length > 0;
|
|
2541
|
-
const typeChoices = [];
|
|
2542
|
-
if (hasClaude) typeChoices.push({ name: 'Claude', value: 'claude' });
|
|
2543
|
-
if (hasCodex) typeChoices.push({ name: 'Codex (GPT)', value: 'codex' });
|
|
2544
|
-
if (typeChoices.length === 0) { targetType = 'claude'; }
|
|
2545
|
-
else if (typeChoices.length === 1) { targetType = typeChoices[0].value; }
|
|
2546
|
-
else {
|
|
2547
|
-
const { picked } = await inquirer.prompt([{
|
|
2548
|
-
type: 'list', name: 'picked', message: '无法自动识别服务类型,请选择:', choices: typeChoices
|
|
2549
|
-
}]);
|
|
2550
|
-
targetType = picked;
|
|
2551
|
-
}
|
|
2586
|
+
// ---- 构建配置 ----
|
|
2587
|
+
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
2588
|
+
|
|
2589
|
+
const existingProviders = Object.keys(config.models.providers || {});
|
|
2590
|
+
const toRemove = existingProviders.filter(n => n !== claudeProviderName && n !== codexProviderName);
|
|
2591
|
+
if (toRemove.length > 0 && !args.force) {
|
|
2592
|
+
const { overwrite } = await inquirer.prompt([{
|
|
2593
|
+
type: 'confirm',
|
|
2594
|
+
name: 'overwrite',
|
|
2595
|
+
message: `检测到已有中转配置: ${existingProviders.join(', ')},将替换为 ${claudeProviderName} + ${codexProviderName}。是否继续?`,
|
|
2596
|
+
default: false
|
|
2597
|
+
}]);
|
|
2598
|
+
if (!overwrite) { console.log(chalk.gray('已取消')); return; }
|
|
2552
2599
|
}
|
|
2553
2600
|
|
|
2554
|
-
if (
|
|
2555
|
-
|
|
2601
|
+
if (toRemove.length > 0) {
|
|
2602
|
+
pruneProvidersExcept(config, [claudeProviderName, codexProviderName]);
|
|
2603
|
+
pruneAuthProfilesExcept(paths.authProfiles, [claudeProviderName, codexProviderName]);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// Claude 侧
|
|
2607
|
+
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
2608
|
+
const claudeModelId = 'claude-opus-4-6';
|
|
2609
|
+
const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: 'Claude Opus 4.6' };
|
|
2610
|
+
const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
|
|
2611
|
+
|
|
2612
|
+
config.models.providers[claudeProviderName] = {
|
|
2613
|
+
baseUrl: claudeBaseUrl,
|
|
2614
|
+
auth: DEFAULT_AUTH_MODE,
|
|
2615
|
+
api: claudeApiConfig.api,
|
|
2616
|
+
headers: {},
|
|
2617
|
+
authHeader: false,
|
|
2618
|
+
apiKey: apiKey.trim(),
|
|
2619
|
+
models: [{
|
|
2620
|
+
id: claudeModel.id,
|
|
2621
|
+
name: claudeModel.name,
|
|
2622
|
+
contextWindow: claudeApiConfig.contextWindow,
|
|
2623
|
+
maxTokens: claudeApiConfig.maxTokens
|
|
2624
|
+
}]
|
|
2625
|
+
};
|
|
2626
|
+
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
2627
|
+
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
2628
|
+
|
|
2629
|
+
// Codex 侧
|
|
2630
|
+
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2631
|
+
const codexModelId = 'gpt-5.3-codex';
|
|
2632
|
+
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.3 Codex' };
|
|
2633
|
+
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2634
|
+
|
|
2635
|
+
config.models.providers[codexProviderName] = {
|
|
2636
|
+
baseUrl: codexBaseUrl,
|
|
2637
|
+
auth: DEFAULT_AUTH_MODE,
|
|
2638
|
+
api: codexApiConfig.api,
|
|
2639
|
+
headers: {},
|
|
2640
|
+
authHeader: false,
|
|
2641
|
+
apiKey: apiKey.trim(),
|
|
2642
|
+
models: [{
|
|
2643
|
+
id: codexModel.id,
|
|
2644
|
+
name: codexModel.name,
|
|
2645
|
+
contextWindow: codexApiConfig.contextWindow,
|
|
2646
|
+
maxTokens: codexApiConfig.maxTokens
|
|
2647
|
+
}]
|
|
2648
|
+
};
|
|
2649
|
+
config.auth.profiles[`${codexProviderName}:default`] = { provider: codexProviderName, mode: 'api_key' };
|
|
2650
|
+
config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
|
|
2651
|
+
|
|
2652
|
+
// ---- 选择主力 ----
|
|
2653
|
+
let primaryType = 'codex';
|
|
2654
|
+
if (args.primary === 'claude') {
|
|
2655
|
+
primaryType = 'claude';
|
|
2656
|
+
} else if (args.primary === 'codex') {
|
|
2657
|
+
primaryType = 'codex';
|
|
2556
2658
|
} else {
|
|
2557
|
-
|
|
2659
|
+
const { picked } = await inquirer.prompt([{
|
|
2660
|
+
type: 'list',
|
|
2661
|
+
name: 'picked',
|
|
2662
|
+
message: '选择主力工具(默认启动哪个):',
|
|
2663
|
+
choices: [
|
|
2664
|
+
{ name: 'Codex (GPT)', value: 'codex' },
|
|
2665
|
+
{ name: 'Claude Code', value: 'claude' }
|
|
2666
|
+
]
|
|
2667
|
+
}]);
|
|
2668
|
+
primaryType = picked;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
const primaryModelKey = primaryType === 'claude' ? claudeModelKey : codexModelKey;
|
|
2672
|
+
config.agents.defaults.model.primary = primaryModelKey;
|
|
2673
|
+
const fallbackModelKey = primaryType === 'claude' ? codexModelKey : claudeModelKey;
|
|
2674
|
+
config.agents.defaults.model.fallbacks = [fallbackModelKey];
|
|
2675
|
+
|
|
2676
|
+
// ---- 写入配置 ----
|
|
2677
|
+
const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
|
|
2678
|
+
createTimestampedBackup(paths.openclawConfig, paths.configDir, 'unified');
|
|
2679
|
+
ensureGatewaySettings(config);
|
|
2680
|
+
writeConfigWithSync(paths, config);
|
|
2681
|
+
updateAuthProfiles(paths.authProfiles, claudeProviderName, apiKey);
|
|
2682
|
+
updateAuthProfiles(paths.authProfiles, codexProviderName, apiKey);
|
|
2683
|
+
const extSynced = [];
|
|
2684
|
+
try { syncExternalTools('claude', claudeBaseUrl, apiKey); extSynced.push('Claude Code settings'); } catch { /* ignore */ }
|
|
2685
|
+
try { syncExternalTools('codex', codexBaseUrl, apiKey); extSynced.push('Codex CLI config'); } catch { /* ignore */ }
|
|
2686
|
+
writeSpinner.succeed('配置写入完成');
|
|
2687
|
+
|
|
2688
|
+
// ---- 输出结果 ----
|
|
2689
|
+
const primaryTag = primaryType === 'claude' ? ' (主)' : '';
|
|
2690
|
+
const codexTag = primaryType === 'codex' ? ' (主)' : '';
|
|
2691
|
+
console.log(chalk.green('\n✅ 一键激活完成!'));
|
|
2692
|
+
console.log(chalk.cyan(` Claude Code${primaryTag}: ${claudeBaseUrl}`));
|
|
2693
|
+
console.log(chalk.gray(` 模型: ${claudeModel.name}`));
|
|
2694
|
+
console.log(chalk.cyan(` Codex${codexTag}: ${codexBaseUrl}`));
|
|
2695
|
+
console.log(chalk.gray(` 模型: ${codexModel.name}`));
|
|
2696
|
+
console.log(chalk.gray(' API Key: 已设置'));
|
|
2697
|
+
if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
|
|
2698
|
+
|
|
2699
|
+
// ---- 测试连接 ----
|
|
2700
|
+
const shouldTestGateway = args.test !== undefined
|
|
2701
|
+
? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
|
|
2702
|
+
: await inquirer.prompt([{
|
|
2703
|
+
type: 'confirm',
|
|
2704
|
+
name: 'testGateway',
|
|
2705
|
+
message: '是否立即通过 OpenClaw Gateway 测试?',
|
|
2706
|
+
default: true
|
|
2707
|
+
}]).then(r => r.testGateway);
|
|
2708
|
+
|
|
2709
|
+
if (shouldTestGateway) {
|
|
2710
|
+
await testConnection(paths, args);
|
|
2558
2711
|
}
|
|
2559
2712
|
}
|
|
2560
2713
|
|
|
@@ -2577,11 +2730,11 @@ async function main() {
|
|
|
2577
2730
|
return;
|
|
2578
2731
|
}
|
|
2579
2732
|
if (args.preset === 'claude' || args._.includes('preset-claude') || args._.includes('claude-preset')) {
|
|
2580
|
-
await
|
|
2733
|
+
await autoActivate(paths, { ...args, primary: 'claude' });
|
|
2581
2734
|
return;
|
|
2582
2735
|
}
|
|
2583
2736
|
if (args.preset === 'codex' || args._.includes('preset-codex') || args._.includes('codex-preset')) {
|
|
2584
|
-
await
|
|
2737
|
+
await autoActivate(paths, { ...args, primary: 'codex' });
|
|
2585
2738
|
return;
|
|
2586
2739
|
}
|
|
2587
2740
|
if (args._.includes('test')) {
|
|
@@ -2606,9 +2759,7 @@ async function main() {
|
|
|
2606
2759
|
loop: false,
|
|
2607
2760
|
choices: [
|
|
2608
2761
|
new inquirer.Separator(' -- 配置模型 --'),
|
|
2609
|
-
{ name: '
|
|
2610
|
-
{ name: ' 激活 Claude', value: 'activate_claude' },
|
|
2611
|
-
{ name: ' 激活 Codex (GPT)', value: 'activate_codex' },
|
|
2762
|
+
{ name: ' 一键激活', value: 'auto_activate' },
|
|
2612
2763
|
new inquirer.Separator(' -- 工具 --'),
|
|
2613
2764
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
2614
2765
|
{ name: ' 查看配置', value: 'view_config' },
|
|
@@ -2632,12 +2783,6 @@ async function main() {
|
|
|
2632
2783
|
case 'auto_activate':
|
|
2633
2784
|
await autoActivate(paths);
|
|
2634
2785
|
break;
|
|
2635
|
-
case 'activate_claude':
|
|
2636
|
-
await presetClaude(paths, {});
|
|
2637
|
-
break;
|
|
2638
|
-
case 'activate_codex':
|
|
2639
|
-
await presetCodex(paths, {});
|
|
2640
|
-
break;
|
|
2641
2786
|
case 'test_connection':
|
|
2642
2787
|
await testConnection(paths, {});
|
|
2643
2788
|
break;
|