yymaxapi 1.0.32 → 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.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +190 -51
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -2507,60 +2507,207 @@ async function presetCodex(paths, args = {}) {
2507
2507
  }
2508
2508
  }
2509
2509
 
2510
- // ============ 一键激活(自动识别服务类型) ============
2511
- async function autoActivate(paths) {
2512
- console.log(chalk.cyan.bold('\n🚀 一键激活(自动识别服务类型)\n'));
2510
+ // ============ 一键激活(同时配置 Claude Code + Codex) ============
2511
+ async function autoActivate(paths, args = {}) {
2512
+ console.log(chalk.cyan.bold('\n🚀 一键激活(同时配置 Claude Code + Codex)\n'));
2513
2513
 
2514
- const apiKey = await promptApiKey('请输入 API Key:', '');
2515
- if (!apiKey) { console.log(chalk.gray('已取消')); return; }
2514
+ const claudeApiConfig = API_CONFIG.claude;
2515
+ const codexApiConfig = API_CONFIG.codex;
2516
+ const claudeProviderName = claudeApiConfig.providerName;
2517
+ const codexProviderName = codexApiConfig.providerName;
2516
2518
 
2517
- // 用第一个节点验证,获取 service_type
2518
- const nodeUrl = ENDPOINTS[0] ? ENDPOINTS[0].url : '';
2519
- if (!nodeUrl) { console.log(chalk.red('没有可用节点')); return; }
2519
+ // ---- 测速选节点 ----
2520
+ const shouldTest = !(args['no-test'] || args.noTest);
2521
+ let selectedEndpoint = ENDPOINTS[0];
2520
2522
 
2521
- console.log('');
2522
- const validation = await validateApiKey(nodeUrl, apiKey);
2523
+ if (shouldTest) {
2524
+ console.log(chalk.cyan('📡 开始测速节点...\n'));
2525
+ const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
2523
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);
2524
2578
  if (!validation.valid) {
2525
2579
  const { continueAnyway } = await inquirer.prompt([{
2526
2580
  type: 'confirm', name: 'continueAnyway',
2527
- message: 'API Key 验证失败,是否手动选择服务类型继续?', default: false
2581
+ message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
2528
2582
  }]);
2529
2583
  if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
2530
2584
  }
2531
2585
 
2532
- const serviceType = (validation.data && validation.data.service_type || '').toLowerCase();
2533
- let targetType = '';
2534
-
2535
- if (serviceType.includes('claude') || serviceType.includes('anthropic')) {
2536
- targetType = 'claude';
2537
- console.log(chalk.cyan(`\n识别为 Claude 服务,进入 Claude 配置流程...\n`));
2538
- } else if (serviceType.includes('codex') || serviceType.includes('gpt') || serviceType.includes('openai')) {
2539
- targetType = 'codex';
2540
- console.log(chalk.cyan(`\n识别为 Codex 服务,进入 Codex 配置流程...\n`));
2541
- }
2542
-
2543
- if (!targetType) {
2544
- // 无法自动识别,让用户选
2545
- const hasClaude = CLAUDE_MODELS && CLAUDE_MODELS.length > 0;
2546
- const hasCodex = CODEX_MODELS && CODEX_MODELS.length > 0;
2547
- const typeChoices = [];
2548
- if (hasClaude) typeChoices.push({ name: 'Claude', value: 'claude' });
2549
- if (hasCodex) typeChoices.push({ name: 'Codex (GPT)', value: 'codex' });
2550
- if (typeChoices.length === 0) { targetType = 'claude'; }
2551
- else if (typeChoices.length === 1) { targetType = typeChoices[0].value; }
2552
- else {
2553
- const { picked } = await inquirer.prompt([{
2554
- type: 'list', name: 'picked', message: '无法自动识别服务类型,请选择:', choices: typeChoices
2555
- }]);
2556
- targetType = picked;
2557
- }
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; }
2599
+ }
2600
+
2601
+ if (toRemove.length > 0) {
2602
+ pruneProvidersExcept(config, [claudeProviderName, codexProviderName]);
2603
+ pruneAuthProfilesExcept(paths.authProfiles, [claudeProviderName, codexProviderName]);
2558
2604
  }
2559
2605
 
2560
- if (targetType === 'claude') {
2561
- await presetClaude(paths, { 'api-key': apiKey });
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';
2562
2658
  } else {
2563
- await presetCodex(paths, { 'api-key': apiKey });
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);
2564
2711
  }
2565
2712
  }
2566
2713
 
@@ -2583,11 +2730,11 @@ async function main() {
2583
2730
  return;
2584
2731
  }
2585
2732
  if (args.preset === 'claude' || args._.includes('preset-claude') || args._.includes('claude-preset')) {
2586
- await presetClaude(paths, args);
2733
+ await autoActivate(paths, { ...args, primary: 'claude' });
2587
2734
  return;
2588
2735
  }
2589
2736
  if (args.preset === 'codex' || args._.includes('preset-codex') || args._.includes('codex-preset')) {
2590
- await presetCodex(paths, args);
2737
+ await autoActivate(paths, { ...args, primary: 'codex' });
2591
2738
  return;
2592
2739
  }
2593
2740
  if (args._.includes('test')) {
@@ -2612,9 +2759,7 @@ async function main() {
2612
2759
  loop: false,
2613
2760
  choices: [
2614
2761
  new inquirer.Separator(' -- 配置模型 --'),
2615
- { name: ' 一键激活(自动识别)', value: 'auto_activate' },
2616
- { name: ' 激活 Claude', value: 'activate_claude' },
2617
- { name: ' 激活 Codex (GPT)', value: 'activate_codex' },
2762
+ { name: ' 一键激活', value: 'auto_activate' },
2618
2763
  new inquirer.Separator(' -- 工具 --'),
2619
2764
  { name: ' 测试连接', value: 'test_connection' },
2620
2765
  { name: ' 查看配置', value: 'view_config' },
@@ -2638,12 +2783,6 @@ async function main() {
2638
2783
  case 'auto_activate':
2639
2784
  await autoActivate(paths);
2640
2785
  break;
2641
- case 'activate_claude':
2642
- await presetClaude(paths, {});
2643
- break;
2644
- case 'activate_codex':
2645
- await presetCodex(paths, {});
2646
- break;
2647
2786
  case 'test_connection':
2648
2787
  await testConnection(paths, {});
2649
2788
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.32",
3
+ "version": "1.0.33",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {