yymaxapi 1.0.34 → 1.0.36

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 +236 -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();
@@ -2767,6 +2941,7 @@ async function main() {
2767
2941
  new inquirer.Separator(' -- 配置模型 --'),
2768
2942
  { name: ' 一键激活', value: 'auto_activate' },
2769
2943
  new inquirer.Separator(' -- 工具 --'),
2944
+ { name: ' 切换模型', value: 'switch_model' },
2770
2945
  { name: ' 测试连接', value: 'test_connection' },
2771
2946
  { name: ' 查看配置', value: 'view_config' },
2772
2947
  { name: ' 恢复默认', value: 'restore' },
@@ -2792,6 +2967,9 @@ async function main() {
2792
2967
  case 'test_connection':
2793
2968
  await testConnection(paths, {});
2794
2969
  break;
2970
+ case 'switch_model':
2971
+ await switchModel(paths);
2972
+ break;
2795
2973
  case 'view_config':
2796
2974
  await viewConfig(paths);
2797
2975
  break;
@@ -2983,6 +3161,64 @@ async function activate(paths, type) {
2983
3161
  console.log(chalk.gray(` API Key: 已设置`));
2984
3162
  }
2985
3163
 
3164
+
3165
+ // ============ 切换模型 ============
3166
+ async function switchModel(paths) {
3167
+ console.log(chalk.cyan('🔄 切换 Claude 模型\n'));
3168
+
3169
+ const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
3170
+ const primary = config.agents?.defaults?.model?.primary || '';
3171
+
3172
+ if (!primary.includes('/')) {
3173
+ console.log(chalk.yellow('⚠️ 请先通过「一键激活」设置主模型'));
3174
+ return;
3175
+ }
3176
+
3177
+ const providerName = primary.split('/')[0];
3178
+ const currentModelId = primary.split('/')[1] || '';
3179
+ const provider = config.models?.providers?.[providerName];
3180
+
3181
+ if (!provider) {
3182
+ console.log(chalk.yellow(`⚠️ 主模型对应的中转站不存在: ${providerName}`));
3183
+ return;
3184
+ }
3185
+
3186
+ const models = [
3187
+ { id: 'claude-opus-4-6', name: 'Claude Opus 4.6' },
3188
+ { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' },
3189
+ ];
3190
+
3191
+ console.log(chalk.gray(`当前模型: ${currentModelId}`));
3192
+ console.log(chalk.gray(`中转节点: ${provider.baseUrl}\n`));
3193
+
3194
+ const { selected } = await inquirer.prompt([{
3195
+ type: 'list',
3196
+ name: 'selected',
3197
+ message: '选择模型:',
3198
+ default: currentModelId,
3199
+ choices: models.map(m => ({
3200
+ name: m.id === currentModelId ? `${m.name} (当前)` : m.name,
3201
+ value: m.id,
3202
+ })),
3203
+ }]);
3204
+
3205
+ if (selected === currentModelId) {
3206
+ console.log(chalk.gray('\n模型未变更'));
3207
+ return;
3208
+ }
3209
+
3210
+ const newPrimary = `${providerName}/${selected}`;
3211
+ config.agents.defaults.model.primary = newPrimary;
3212
+ if (!config.agents.defaults.models[newPrimary]) {
3213
+ config.agents.defaults.models[newPrimary] = { alias: providerName };
3214
+ }
3215
+ writeConfigWithSync(paths, config);
3216
+
3217
+ const selectedName = models.find(m => m.id === selected).name;
3218
+ console.log(chalk.green(`\n✅ 已切换到 ${selectedName}`));
3219
+ console.log(chalk.gray(` ${newPrimary}`));
3220
+ console.log(chalk.yellow('\n💡 切换后建议重启 Gateway: openclaw gateway restart'));
3221
+ }
2986
3222
  // ============ 测试连接 ============
2987
3223
  async function testConnection(paths, args = {}) {
2988
3224
  console.log(chalk.cyan('🧪 测试 OpenClaw Gateway 连接\n'));
@@ -3025,7 +3261,6 @@ async function testConnection(paths, args = {}) {
3025
3261
  console.log(chalk.gray(`中转节点: ${provider.baseUrl}`));
3026
3262
  console.log(chalk.gray(`模型: ${primary}`));
3027
3263
  console.log(chalk.gray(`Gateway: http://localhost:${gatewayPort}\n`));
3028
-
3029
3264
  // 获取 Gateway token
3030
3265
  const gatewayToken = config.gateway?.auth?.token;
3031
3266
  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.36",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {