yymaxapi 1.0.34 → 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 +204 -1
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -2717,9 +2717,183 @@ async function autoActivate(paths, args = {}) {
2717
2717
  }
2718
2718
  }
2719
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
+
2720
2884
  // ============ 主程序 ============
2721
2885
  async function main() {
2722
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
+
2723
2897
  console.log(chalk.cyan.bold('\n🔧 OpenClaw API 配置工具\n'));
2724
2898
 
2725
2899
  const paths = getConfigPath();
@@ -2995,7 +3169,7 @@ async function testConnection(paths, args = {}) {
2995
3169
  }
2996
3170
 
2997
3171
  // 检查当前激活的是哪个
2998
- const primary = config.agents?.defaults?.model?.primary || '';
3172
+ let primary = config.agents?.defaults?.model?.primary || '';
2999
3173
  if (!primary.includes('/')) {
3000
3174
  console.log(chalk.yellow('⚠️ 请先设置主模型'));
3001
3175
  return;
@@ -3026,6 +3200,35 @@ async function testConnection(paths, args = {}) {
3026
3200
  console.log(chalk.gray(`模型: ${primary}`));
3027
3201
  console.log(chalk.gray(`Gateway: http://localhost:${gatewayPort}\n`));
3028
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
+
3029
3232
  // 获取 Gateway token
3030
3233
  const gatewayToken = config.gateway?.auth?.token;
3031
3234
  if (!gatewayToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {