yymaxapi 1.0.33 → 1.0.35

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 +215 -6
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -640,6 +640,10 @@ function writeCodexConfig(baseUrl, apiKey) {
640
640
  const configPath = path.join(codexDir, 'config.toml');
641
641
  const marker = '# >>> maxapi codex >>>';
642
642
  const markerEnd = '# <<< maxapi codex <<<';
643
+ const providerKey = 'openclaw-relay';
644
+ // 确保 base_url 以 /v1 结尾(Codex CLI 要求)
645
+ let normalizedUrl = baseUrl.replace(/\/+$/, '');
646
+ if (!normalizedUrl.endsWith('/v1')) normalizedUrl += '/v1';
643
647
  try {
644
648
  let existing = '';
645
649
  if (fs.existsSync(configPath)) {
@@ -650,12 +654,14 @@ function writeCodexConfig(baseUrl, apiKey) {
650
654
  }
651
655
  const section = [
652
656
  marker,
653
- `model = "o4-mini"`,
654
- `provider = "openai"`,
657
+ `model = "gpt-5.3-codex"`,
658
+ `model_provider = "${providerKey}"`,
655
659
  ``,
656
- `[providers.openai]`,
657
- `api_key = "${apiKey}"`,
658
- `base_url = "${baseUrl.replace(/\/+$/, '')}"`,
660
+ `[model_providers.${providerKey}]`,
661
+ `name = "OpenClaw Relay"`,
662
+ `base_url = "${normalizedUrl}"`,
663
+ `wire_api = "responses"`,
664
+ `env_key = "OPENAI_API_KEY"`,
659
665
  markerEnd
660
666
  ].join('\n');
661
667
  const content = existing ? `${existing}\n\n${section}\n` : `${section}\n`;
@@ -2711,9 +2717,183 @@ async function autoActivate(paths, args = {}) {
2711
2717
  }
2712
2718
  }
2713
2719
 
2720
+ // ============ yycode 精简模式(零交互一键配置) ============
2721
+ async function yycodeQuickSetup(paths) {
2722
+ console.log(chalk.cyan.bold('\n⚡ yycode 一键配置\n'));
2723
+
2724
+ const claudeApiConfig = API_CONFIG.claude;
2725
+ const codexApiConfig = API_CONFIG.codex;
2726
+ const claudeProviderName = claudeApiConfig.providerName;
2727
+ const codexProviderName = codexApiConfig.providerName;
2728
+
2729
+ // ---- 探测已有 key ----
2730
+ let apiKey = '';
2731
+ let keySource = '';
2732
+
2733
+ // 1. CLI 参数
2734
+ const args = parseArgs(process.argv.slice(2));
2735
+ const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
2736
+ if (directKey) {
2737
+ apiKey = directKey;
2738
+ keySource = '命令行参数';
2739
+ }
2740
+
2741
+ // 2. 环境变量
2742
+ if (!apiKey) {
2743
+ const envKeys = ['OPENCLAW_CLAUDE_KEY', 'OPENCLAW_CODEX_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY', 'OPENCLAW_API_KEY'];
2744
+ for (const k of envKeys) {
2745
+ if (process.env[k] && process.env[k].trim()) {
2746
+ apiKey = process.env[k].trim();
2747
+ keySource = `环境变量 ${k}`;
2748
+ break;
2749
+ }
2750
+ }
2751
+ }
2752
+
2753
+ // 3. 已有 OpenClaw 配置(云翼 Claude Code 密钥)
2754
+ if (!apiKey) {
2755
+ try {
2756
+ const config = readConfig(paths.openclawConfig);
2757
+ if (config && config.models && config.models.providers) {
2758
+ // 优先取 claude-yunyi 的 key
2759
+ const preferredOrder = [claudeProviderName, codexProviderName];
2760
+ for (const name of preferredOrder) {
2761
+ const p = config.models.providers[name];
2762
+ if (p && p.apiKey && p.apiKey.trim()) {
2763
+ apiKey = p.apiKey.trim();
2764
+ keySource = `已有配置 (${name})`;
2765
+ break;
2766
+ }
2767
+ }
2768
+ // 其他 provider 的 key
2769
+ if (!apiKey) {
2770
+ for (const [name, p] of Object.entries(config.models.providers)) {
2771
+ if (p.apiKey && p.apiKey.trim()) {
2772
+ apiKey = p.apiKey.trim();
2773
+ keySource = `已有配置 (${name})`;
2774
+ break;
2775
+ }
2776
+ }
2777
+ }
2778
+ }
2779
+ } catch { /* ignore */ }
2780
+ }
2781
+
2782
+ // 4. 都没有,提示输入
2783
+ if (apiKey) {
2784
+ const masked = apiKey.length > 8 ? apiKey.slice(0, 5) + '***' + apiKey.slice(-3) : '***';
2785
+ console.log(chalk.green(`✓ 已检测到 API Key: ${masked} (来源: ${keySource})`));
2786
+ } else {
2787
+ apiKey = await promptApiKey('请输入 API Key:', '');
2788
+ if (!apiKey) { console.log(chalk.gray('已取消')); return; }
2789
+ }
2790
+
2791
+ // ---- 静默测速选最快节点 ----
2792
+ const speedSpinner = ora({ text: '正在测速选择最快节点...', spinner: 'dots' }).start();
2793
+ let selectedEndpoint = ENDPOINTS[0];
2794
+ try {
2795
+ const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
2796
+ if (speedResult.ranked && speedResult.ranked.length > 0) {
2797
+ selectedEndpoint = speedResult.ranked[0];
2798
+ }
2799
+ speedSpinner.succeed(`节点: ${selectedEndpoint.name}`);
2800
+ } catch {
2801
+ speedSpinner.succeed(`节点: ${selectedEndpoint.name} (默认)`);
2802
+ }
2803
+
2804
+ // ---- 验证 key ----
2805
+ const validation = await validateApiKey(selectedEndpoint.url, apiKey);
2806
+ if (!validation.valid) {
2807
+ console.log(chalk.yellow('⚠ API Key 验证失败,仍将写入配置'));
2808
+ }
2809
+
2810
+ // ---- 写入两套配置 ----
2811
+ const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
2812
+
2813
+ // 清理旧 provider
2814
+ const existingProviders = Object.keys(config.models.providers || {});
2815
+ const toRemove = existingProviders.filter(n => n !== claudeProviderName && n !== codexProviderName);
2816
+ if (toRemove.length > 0) {
2817
+ pruneProvidersExcept(config, [claudeProviderName, codexProviderName]);
2818
+ pruneAuthProfilesExcept(paths.authProfiles, [claudeProviderName, codexProviderName]);
2819
+ }
2820
+
2821
+ // Claude 侧
2822
+ const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
2823
+ const claudeModelId = 'claude-opus-4-6';
2824
+ const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: 'Claude Opus 4.6' };
2825
+ const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
2826
+
2827
+ config.models.providers[claudeProviderName] = {
2828
+ baseUrl: claudeBaseUrl,
2829
+ auth: DEFAULT_AUTH_MODE,
2830
+ api: claudeApiConfig.api,
2831
+ headers: {},
2832
+ authHeader: false,
2833
+ apiKey: apiKey.trim(),
2834
+ models: [{ id: claudeModel.id, name: claudeModel.name, contextWindow: claudeApiConfig.contextWindow, maxTokens: claudeApiConfig.maxTokens }]
2835
+ };
2836
+ config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
2837
+ config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
2838
+
2839
+ // Codex 侧
2840
+ const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
2841
+ const codexModelId = 'gpt-5.3-codex';
2842
+ const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.3 Codex' };
2843
+ const codexModelKey = `${codexProviderName}/${codexModelId}`;
2844
+
2845
+ config.models.providers[codexProviderName] = {
2846
+ baseUrl: codexBaseUrl,
2847
+ auth: DEFAULT_AUTH_MODE,
2848
+ api: codexApiConfig.api,
2849
+ headers: {},
2850
+ authHeader: false,
2851
+ apiKey: apiKey.trim(),
2852
+ models: [{ id: codexModel.id, name: codexModel.name, contextWindow: codexApiConfig.contextWindow, maxTokens: codexApiConfig.maxTokens }]
2853
+ };
2854
+ config.auth.profiles[`${codexProviderName}:default`] = { provider: codexProviderName, mode: 'api_key' };
2855
+ config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
2856
+
2857
+ // 默认主力: Codex, 备用: Claude
2858
+ config.agents.defaults.model.primary = codexModelKey;
2859
+ config.agents.defaults.model.fallbacks = [claudeModelKey];
2860
+
2861
+ // ---- 写入 ----
2862
+ const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
2863
+ createTimestampedBackup(paths.openclawConfig, paths.configDir, 'yycode');
2864
+ ensureGatewaySettings(config);
2865
+ writeConfigWithSync(paths, config);
2866
+ updateAuthProfiles(paths.authProfiles, claudeProviderName, apiKey);
2867
+ updateAuthProfiles(paths.authProfiles, codexProviderName, apiKey);
2868
+ try { syncExternalTools('claude', claudeBaseUrl, apiKey); } catch { /* ignore */ }
2869
+ try { syncExternalTools('codex', codexBaseUrl, apiKey); } catch { /* ignore */ }
2870
+ writeSpinner.succeed('配置写入完成');
2871
+
2872
+ // ---- 结果 ----
2873
+ console.log(chalk.green('\n✅ 配置完成!'));
2874
+ console.log(chalk.cyan(` Claude Code: ${claudeBaseUrl}`));
2875
+ console.log(chalk.gray(` 模型: ${claudeModel.name}`));
2876
+ console.log(chalk.cyan(` Codex (主): ${codexBaseUrl}`));
2877
+ console.log(chalk.gray(` 模型: ${codexModel.name}`));
2878
+ console.log(chalk.gray(' API Key: 已设置'));
2879
+ console.log(chalk.gray(' 同步: Claude Code settings, Codex CLI config'));
2880
+ console.log('');
2881
+ }
2882
+
2883
+
2714
2884
  // ============ 主程序 ============
2715
2885
  async function main() {
2716
2886
  console.clear();
2887
+
2888
+ // yycode 精简模式:检测到 yycode CLI 时直接走零交互流程
2889
+ const isYYCode = path.basename(process.argv[1] || '').replace(/\.js$/, '') === 'yycode';
2890
+ if (isYYCode) {
2891
+ const paths = getConfigPath();
2892
+ backupOriginalConfig(paths.openclawConfig, paths.configDir);
2893
+ await yycodeQuickSetup(paths);
2894
+ return;
2895
+ }
2896
+
2717
2897
  console.log(chalk.cyan.bold('\n🔧 OpenClaw API 配置工具\n'));
2718
2898
 
2719
2899
  const paths = getConfigPath();
@@ -2989,7 +3169,7 @@ async function testConnection(paths, args = {}) {
2989
3169
  }
2990
3170
 
2991
3171
  // 检查当前激活的是哪个
2992
- const primary = config.agents?.defaults?.model?.primary || '';
3172
+ let primary = config.agents?.defaults?.model?.primary || '';
2993
3173
  if (!primary.includes('/')) {
2994
3174
  console.log(chalk.yellow('⚠️ 请先设置主模型'));
2995
3175
  return;
@@ -3020,6 +3200,35 @@ async function testConnection(paths, args = {}) {
3020
3200
  console.log(chalk.gray(`模型: ${primary}`));
3021
3201
  console.log(chalk.gray(`Gateway: http://localhost:${gatewayPort}\n`));
3022
3202
 
3203
+ // 模型切换(仅 Claude)
3204
+ if (apiType.startsWith('anthropic')) {
3205
+ const currentModelId = primary.split('/')[1] || '';
3206
+ const switchModels = [
3207
+ { id: 'claude-opus-4-6', name: 'Claude Opus 4.6' },
3208
+ { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' },
3209
+ ];
3210
+ const { switchModel } = await inquirer.prompt([{
3211
+ type: 'list',
3212
+ name: 'switchModel',
3213
+ message: '选择测试模型:',
3214
+ default: currentModelId,
3215
+ choices: switchModels.map(m => ({
3216
+ name: m.id === currentModelId ? `${m.name} (当前)` : m.name,
3217
+ value: m.id,
3218
+ })),
3219
+ }]);
3220
+ if (switchModel !== currentModelId) {
3221
+ primary = `${providerName}/${switchModel}`;
3222
+ config.agents.defaults.model.primary = primary;
3223
+ if (!config.agents.defaults.models) config.agents.defaults.models = {};
3224
+ if (!config.agents.defaults.models[primary]) {
3225
+ config.agents.defaults.models[primary] = { alias: providerName };
3226
+ }
3227
+ writeConfigWithSync(paths, config);
3228
+ console.log(chalk.green(` ✅ 已切换到 ${switchModels.find(m => m.id === switchModel).name}\n`));
3229
+ }
3230
+ }
3231
+
3023
3232
  // 获取 Gateway token
3024
3233
  const gatewayToken = config.gateway?.auth?.token;
3025
3234
  if (!gatewayToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {