zcf 3.2.3 → 3.3.0

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 (38) hide show
  1. package/README.md +83 -14
  2. package/dist/chunks/api-providers.mjs +76 -0
  3. package/dist/chunks/claude-code-config-manager.mjs +21 -7
  4. package/dist/chunks/claude-code-incremental-manager.mjs +56 -14
  5. package/dist/chunks/codex-config-switch.mjs +69 -10
  6. package/dist/chunks/codex-provider-manager.mjs +18 -8
  7. package/dist/chunks/codex-uninstaller.mjs +1 -1
  8. package/dist/chunks/commands.mjs +1 -1
  9. package/dist/chunks/features.mjs +637 -0
  10. package/dist/chunks/simple-config.mjs +172 -24
  11. package/dist/cli.mjs +12 -612
  12. package/dist/i18n/locales/en/api.json +6 -1
  13. package/dist/i18n/locales/en/errors.json +1 -0
  14. package/dist/i18n/locales/en/multi-config.json +2 -1
  15. package/dist/i18n/locales/zh-CN/api.json +6 -1
  16. package/dist/i18n/locales/zh-CN/errors.json +1 -0
  17. package/dist/i18n/locales/zh-CN/multi-config.json +2 -1
  18. package/dist/index.d.mts +8 -1
  19. package/dist/index.d.ts +8 -1
  20. package/dist/index.mjs +1 -1
  21. package/package.json +1 -1
  22. package/templates/CLAUDE.md +190 -0
  23. package/templates/claude-code/en/output-styles/engineer-professional.md +2 -1
  24. package/templates/claude-code/en/output-styles/laowang-engineer.md +1 -0
  25. package/templates/claude-code/en/output-styles/nekomata-engineer.md +1 -0
  26. package/templates/claude-code/en/output-styles/ojousama-engineer.md +1 -0
  27. package/templates/claude-code/zh-CN/output-styles/engineer-professional.md +2 -1
  28. package/templates/claude-code/zh-CN/output-styles/laowang-engineer.md +1 -0
  29. package/templates/claude-code/zh-CN/output-styles/nekomata-engineer.md +1 -0
  30. package/templates/claude-code/zh-CN/output-styles/ojousama-engineer.md +1 -0
  31. package/templates/codex/en/system-prompt/engineer-professional.md +2 -1
  32. package/templates/codex/en/system-prompt/laowang-engineer.md +1 -0
  33. package/templates/codex/en/system-prompt/nekomata-engineer.md +1 -0
  34. package/templates/codex/en/system-prompt/ojousama-engineer.md +1 -0
  35. package/templates/codex/zh-CN/system-prompt/engineer-professional.md +2 -1
  36. package/templates/codex/zh-CN/system-prompt/laowang-engineer.md +1 -0
  37. package/templates/codex/zh-CN/system-prompt/nekomata-engineer.md +1 -0
  38. package/templates/codex/zh-CN/system-prompt/ojousama-engineer.md +1 -0
package/README.md CHANGED
@@ -111,14 +111,17 @@ npx zcf ccr --all-lang zh-CN # Configure CCR in Chinese
111
111
  For CI/CD and automated setups, use `--skip-prompt` with parameters:
112
112
 
113
113
  ```bash
114
- # Shorthand version
114
+ # Using API provider preset (v3.3.0+ New - Simplified)
115
+ npx zcf i -s -p 302ai -k "sk-xxx"
116
+
117
+ # Shorthand version (traditional)
115
118
  npx zcf i -s -g zh-CN -t api_key -k "sk-xxx" -u "https://xxx.xxx"
116
119
 
117
- # Complete version
120
+ # Complete version (traditional)
118
121
  npx zcf i --skip-prompt --all-lang zh-CN --api-type api_key --api-key "sk-xxx" --api-url "https://xxx.xxx"
119
122
 
120
- # Shorthand version (with custom models)
121
- npx zcf i -s -t api_key -k "sk-xxx" -M "claude-sonnet-4-5" -F "claude-haiku-4-5"
123
+ # Using provider preset with custom models
124
+ npx zcf i -s -p 302ai -k "sk-xxx" -M "claude-sonnet-4-5" -F "claude-haiku-4-5"
122
125
 
123
126
  # Complete version (with custom models)
124
127
  npx zcf i --skip-prompt \
@@ -129,6 +132,48 @@ npx zcf i --skip-prompt \
129
132
  --api-fast-model "claude-haiku-4-5"
130
133
  ```
131
134
 
135
+ #### 🎯 API Provider Presets (v3.3.0+ New)
136
+
137
+ ZCF now supports API provider presets that automatically configure baseUrl and models, simplifying configuration from 5+ parameters to just 2:
138
+
139
+ **Supported Providers:**
140
+ - `302ai` - [302.AI](https://share.302.ai/gAT9VG) API Service
141
+ - `glm` - GLM (z.ai)
142
+ - `minimax` - MiniMax API Service
143
+ - `kimi` - Kimi (Moonshot AI)
144
+ - `custom` - Custom API endpoint (requires manual URL configuration)
145
+
146
+ **Usage Examples:**
147
+
148
+ ```bash
149
+ # Using 302.AI provider
150
+ npx zcf i --skip-prompt --provider 302ai --api-key "sk-xxx"
151
+ # or shorthand
152
+ npx zcf i -s -p 302ai -k "sk-xxx"
153
+
154
+ # Using GLM provider
155
+ npx zcf i -s -p glm -k "sk-xxx"
156
+
157
+ # Using MiniMax provider
158
+ npx zcf i -s -p minimax -k "sk-xxx"
159
+
160
+ # Using Kimi provider
161
+ npx zcf i -s -p kimi -k "sk-xxx"
162
+
163
+ # Using custom provider (requires URL)
164
+ npx zcf i -s -p custom -k "sk-xxx" -u "https://api.example.com"
165
+
166
+ # For Codex
167
+ npx zcf i -s -T cx -p 302ai -k "sk-xxx"
168
+ ```
169
+
170
+ **Benefits:**
171
+ - ✅ Automatic baseUrl configuration
172
+ - ✅ Automatic authType selection
173
+ - ✅ Automatic model configuration (if available)
174
+ - ✅ Reduces configuration from 5+ parameters to 2
175
+ - ✅ Supports both Claude Code and Codex
176
+
132
177
  #### Non-interactive Mode Parameters
133
178
 
134
179
  When using `--skip-prompt`, the following parameters are available:
@@ -136,16 +181,17 @@ When using `--skip-prompt`, the following parameters are available:
136
181
  | Parameter | Description | Values | Required | Default |
137
182
  | ---------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
138
183
  | `--skip-prompt, -s` | Skip all interactive prompts | - | Yes (for non-interactive mode) | - |
184
+ | `--provider, -p` | API provider preset (v3.3.0+ New) | `302ai`, `glm`, `minimax`, `kimi`, `custom` | No | - (Simplifies configuration by auto-filling baseUrl and models) |
139
185
  | `--lang, -l` | ZCF display language (applies to all commands) | `zh-CN`, `en` | No | `en` or user's saved preference |
140
186
  | `--config-lang, -c` | Configuration language (template files language) | `zh-CN`, `en` | No | `en` |
141
187
  | `--ai-output-lang, -a` | AI output language | `zh-CN`, `en`, custom string | No | `en` |
142
188
  | `--all-lang, -g` | Set all language parameters (applies to all commands) | `zh-CN`, `en`, custom string | No | - (Priority: `--all-lang` > `--lang` > saved user preference > interactive prompt. Custom string sets AI output language to custom while interaction and config languages remain 'en') |
143
189
  | `--config-action, -r` | Config handling | `new`, `backup`, `merge`, `docs-only`, `skip` | No | `backup` |
144
- | `--api-type, -t` | API configuration type | `auth_token`, `api_key`, `ccr_proxy`, `skip` | No | `skip` |
190
+ | `--api-type, -t` | API configuration type | `auth_token`, `api_key`, `ccr_proxy`, `skip` | No | `skip` (auto-set to `api_key` when `--provider` is specified) |
145
191
  | `--api-key, -k` | API key (for both API key and auth token types) | string | Required when `api-type` is not `skip` | - |
146
- | `--api-url, -u` | Custom API URL | URL string | No | official API |
147
- | `--api-model, -M` | Primary API model | string (e.g., `claude-sonnet-4-5`) | No | - |
148
- | `--api-fast-model, -F` | Fast API model (Claude Code only) | string (e.g., `claude-haiku-4-5`) | No | - |
192
+ | `--api-url, -u` | Custom API URL | URL string | No | official API (auto-filled when using `--provider`) |
193
+ | `--api-model, -M` | Primary API model | string (e.g., `claude-sonnet-4-5`) | No | - (auto-filled when using `--provider` if available) |
194
+ | `--api-fast-model, -F` | Fast API model (Claude Code only) | string (e.g., `claude-haiku-4-5`) | No | - (auto-filled when using `--provider` if available) |
149
195
  | `--mcp-services, -m` | MCP services to install (multi-select, comma-separated) | `context7`, `open-websearch`, `spec-workflow`, `mcp-deepwiki`, `Playwright`, `exa`, `serena`, or `skip` for none | No | `all` |
150
196
  | `--workflows, -w` | Workflows to install (multi-select, comma-separated) | `commonTools`, `sixStepsWorkflow`, `featPlanUx`, `gitWorkflow`, `bmadWorkflow`, or `skip` for none | No | `all` |
151
197
  | `--output-styles, -o` | Output styles to install (multi-select, comma-separated) | `engineer-professional`, `nekomata-engineer`, `laowang-engineer`, `ojousama-engineer`, or `skip` for none | No | `all` |
@@ -402,12 +448,35 @@ After configuration:
402
448
 
403
449
  ### 🔐 API Configuration
404
450
 
405
- - Supports two authentication methods:
406
- - **Auth Token**: For tokens obtained via OAuth or browser login
407
- - **API Key**: For API keys from Anthropic Console
408
- - Custom API URL support
409
- - Support for manual configuration later
410
- - Partial modification: Update only needed configuration items (v2.0 new)
451
+ ZCF provides flexible API configuration options for both Claude Code and Codex:
452
+
453
+ **Quick Setup with API Provider Presets (v3.3.0+ New):**
454
+
455
+ Choose from popular API providers with pre-configured settings:
456
+ - **302.AI** - Pay-as-you-go AI service with comprehensive model support
457
+ - **GLM (智谱AI)** - Zhipu AI's GLM models
458
+ - **MiniMax** - MiniMax AI service
459
+ - **Kimi (Moonshot AI)** - Moonshot's Kimi models
460
+ - **Custom Configuration** - Full manual configuration for any provider
461
+
462
+ When using a preset provider, you only need to:
463
+ 1. Select the provider from the list
464
+ 2. Enter your API key
465
+
466
+ The system automatically configures:
467
+ - API base URL
468
+ - Authentication type (API Key or Auth Token)
469
+ - Default models (if applicable)
470
+ - Wire API protocol (for Codex)
471
+
472
+ **Traditional Configuration Methods:**
473
+
474
+ - **Official Login**: Use official authentication system (no API configuration needed)
475
+ - **Auth Token**: For tokens obtained via OAuth or browser login
476
+ - **API Key**: For API keys from Anthropic Console or custom providers
477
+ - **CCR Proxy**: Configure Claude Code Router proxy
478
+ - **Custom API URL**: Support for any compatible API endpoint
479
+ - **Partial Modification**: Update only needed configuration items (v2.0+)
411
480
 
412
481
  ### 💾 Configuration Management
413
482
 
@@ -0,0 +1,76 @@
1
+ const API_PROVIDER_PRESETS = [
2
+ {
3
+ id: "302ai",
4
+ name: "302.AI",
5
+ supportedCodeTools: ["claude-code", "codex"],
6
+ claudeCode: {
7
+ baseUrl: "https://api.302.ai/cc",
8
+ authType: "api_key"
9
+ },
10
+ codex: {
11
+ baseUrl: "https://api.302.ai/v1",
12
+ wireApi: "responses"
13
+ },
14
+ description: "302.AI API Service"
15
+ },
16
+ {
17
+ id: "glm",
18
+ name: "GLM",
19
+ supportedCodeTools: ["claude-code", "codex"],
20
+ claudeCode: {
21
+ baseUrl: "https://open.bigmodel.cn/api/anthropic",
22
+ authType: "auth_token"
23
+ },
24
+ codex: {
25
+ baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
26
+ wireApi: "chat",
27
+ defaultModel: "GLM-4.6"
28
+ },
29
+ description: "GLM (\u667A\u8C31AI)"
30
+ },
31
+ {
32
+ id: "minimax",
33
+ name: "MiniMax",
34
+ supportedCodeTools: ["claude-code", "codex"],
35
+ claudeCode: {
36
+ baseUrl: "https://api.minimaxi.com/anthropic",
37
+ authType: "auth_token",
38
+ defaultModels: ["MiniMax-M2", "MiniMax-M2"]
39
+ },
40
+ codex: {
41
+ baseUrl: "https://api.minimaxi.com/v1",
42
+ wireApi: "chat",
43
+ defaultModel: "MiniMax-M2"
44
+ },
45
+ description: "MiniMax API Service"
46
+ },
47
+ {
48
+ id: "kimi",
49
+ name: "Kimi",
50
+ supportedCodeTools: ["claude-code", "codex"],
51
+ claudeCode: {
52
+ baseUrl: "https://api.moonshot.cn/anthropic",
53
+ authType: "auth_token",
54
+ defaultModels: ["kimi-k2-0905-preview", "kimi-k2-turbo-preview"]
55
+ },
56
+ codex: {
57
+ baseUrl: "https://api.moonshot.cn/v1",
58
+ wireApi: "chat",
59
+ defaultModel: "kimi-k2-0905-preview"
60
+ },
61
+ description: "Kimi (Moonshot AI)"
62
+ }
63
+ ];
64
+ function getApiProviders(codeToolType) {
65
+ return API_PROVIDER_PRESETS.filter(
66
+ (provider) => provider.supportedCodeTools.includes(codeToolType)
67
+ );
68
+ }
69
+ function getProviderPreset(providerId) {
70
+ return API_PROVIDER_PRESETS.find((provider) => provider.id === providerId);
71
+ }
72
+ function getValidProviderIds() {
73
+ return API_PROVIDER_PRESETS.map((provider) => provider.id);
74
+ }
75
+
76
+ export { API_PROVIDER_PRESETS, getApiProviders, getProviderPreset, getValidProviderIds };
@@ -1,6 +1,6 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { join } from 'pathe';
3
- import { j as ZCF_CONFIG_FILE, Z as ZCF_CONFIG_DIR, a8 as ensureDir, a9 as readDefaultTomlConfig, aa as createDefaultTomlConfig, ab as exists, ac as readJsonConfig, ad as writeTomlConfig, S as SETTINGS_FILE, ae as copyFile } from './simple-config.mjs';
3
+ import { j as ZCF_CONFIG_FILE, Z as ZCF_CONFIG_DIR, a9 as ensureDir, aa as readDefaultTomlConfig, ab as createDefaultTomlConfig, ac as exists, ad as readJsonConfig, ae as writeTomlConfig, S as SETTINGS_FILE, af as copyFile } from './simple-config.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:process';
6
6
  import 'ansis';
@@ -182,15 +182,15 @@ class ClaudeCodeConfigManager {
182
182
  * Apply profile settings to Claude Code runtime
183
183
  */
184
184
  static async applyProfileSettings(profile) {
185
- const { ensureI18nInitialized, i18n } = await import('./simple-config.mjs').then(function (n) { return n.a_; });
185
+ const { ensureI18nInitialized, i18n } = await import('./simple-config.mjs').then(function (n) { return n.a$; });
186
186
  ensureI18nInitialized();
187
187
  try {
188
188
  if (!profile) {
189
- const { switchToOfficialLogin } = await import('./simple-config.mjs').then(function (n) { return n.b2; });
189
+ const { switchToOfficialLogin } = await import('./simple-config.mjs').then(function (n) { return n.b3; });
190
190
  switchToOfficialLogin();
191
191
  return;
192
192
  }
193
- const { readJsonConfig: readJsonConfig2, writeJsonConfig } = await import('./simple-config.mjs').then(function (n) { return n.b0; });
193
+ const { readJsonConfig: readJsonConfig2, writeJsonConfig } = await import('./simple-config.mjs').then(function (n) { return n.b1; });
194
194
  const settings = readJsonConfig2(SETTINGS_FILE) || {};
195
195
  if (!settings.env)
196
196
  settings.env = {};
@@ -202,7 +202,7 @@ class ClaudeCodeConfigManager {
202
202
  settings.env.ANTHROPIC_AUTH_TOKEN = profile.apiKey;
203
203
  delete settings.env.ANTHROPIC_API_KEY;
204
204
  } else if (profile.authType === "ccr_proxy") {
205
- const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b3; });
205
+ const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b4; });
206
206
  const ccrConfig = readCcrConfig();
207
207
  if (!ccrConfig) {
208
208
  throw new Error(i18n.t("ccr:ccrNotConfigured") || "CCR proxy configuration not found");
@@ -221,8 +221,18 @@ class ClaudeCodeConfigManager {
221
221
  else
222
222
  delete settings.env.ANTHROPIC_BASE_URL;
223
223
  }
224
+ if (profile.primaryModel) {
225
+ settings.env.ANTHROPIC_MODEL = profile.primaryModel;
226
+ } else {
227
+ delete settings.env.ANTHROPIC_MODEL;
228
+ }
229
+ if (profile.fastModel) {
230
+ settings.env.ANTHROPIC_SMALL_FAST_MODEL = profile.fastModel;
231
+ } else {
232
+ delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
233
+ }
224
234
  writeJsonConfig(SETTINGS_FILE, settings);
225
- const { setPrimaryApiKey, addCompletedOnboarding } = await import('./simple-config.mjs').then(function (n) { return n.b1; });
235
+ const { setPrimaryApiKey, addCompletedOnboarding } = await import('./simple-config.mjs').then(function (n) { return n.b2; });
226
236
  setPrimaryApiKey();
227
237
  addCompletedOnboarding();
228
238
  if (shouldRestartCcr) {
@@ -250,6 +260,10 @@ class ClaudeCodeConfigManager {
250
260
  sanitized.apiKey = profile.apiKey;
251
261
  if (profile.baseUrl)
252
262
  sanitized.baseUrl = profile.baseUrl;
263
+ if (profile.primaryModel)
264
+ sanitized.primaryModel = profile.primaryModel;
265
+ if (profile.fastModel)
266
+ sanitized.fastModel = profile.fastModel;
253
267
  return sanitized;
254
268
  }
255
269
  /**
@@ -575,7 +589,7 @@ class ClaudeCodeConfigManager {
575
589
  */
576
590
  static async syncCcrProfile() {
577
591
  try {
578
- const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b3; });
592
+ const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b4; });
579
593
  const ccrConfig = readCcrConfig();
580
594
  if (!ccrConfig) {
581
595
  await this.ensureCcrProfileExists(ccrConfig);
@@ -1,6 +1,6 @@
1
1
  import ansis from 'ansis';
2
2
  import inquirer from 'inquirer';
3
- import { a4 as ensureI18nInitialized, a5 as i18n, a6 as addNumbersToChoices, a7 as validateApiKey } from './simple-config.mjs';
3
+ import { a5 as ensureI18nInitialized, a6 as i18n, a7 as addNumbersToChoices, a8 as validateApiKey } from './simple-config.mjs';
4
4
  import { ClaudeCodeConfigManager } from './claude-code-config-manager.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:process';
@@ -85,17 +85,40 @@ async function promptContinueAdding() {
85
85
  async function handleAddProfile() {
86
86
  console.log(ansis.cyan(`
87
87
  ${i18n.t("multi-config:addingNewProfile")}`));
88
+ const { getApiProviders } = await import('./api-providers.mjs');
89
+ const providers = getApiProviders("claude-code");
90
+ const providerChoices = [
91
+ { name: i18n.t("api:customProvider"), value: "custom" },
92
+ ...providers.map((p) => ({ name: p.name, value: p.id }))
93
+ ];
94
+ const { selectedProvider } = await inquirer.prompt([{
95
+ type: "list",
96
+ name: "selectedProvider",
97
+ message: i18n.t("api:selectApiProvider"),
98
+ choices: addNumbersToChoices(providerChoices)
99
+ }]);
100
+ let prefilledBaseUrl;
101
+ let prefilledAuthType;
102
+ if (selectedProvider !== "custom") {
103
+ const provider = providers.find((p) => p.id === selectedProvider);
104
+ if (provider?.claudeCode) {
105
+ prefilledBaseUrl = provider.claudeCode.baseUrl;
106
+ prefilledAuthType = provider.claudeCode.authType;
107
+ console.log(ansis.gray(i18n.t("api:providerSelected", { name: provider.name })));
108
+ }
109
+ }
88
110
  const answers = await inquirer.prompt([
89
111
  {
90
112
  type: "input",
91
113
  name: "profileName",
92
114
  message: i18n.t("multi-config:profileNamePrompt"),
115
+ default: selectedProvider !== "custom" ? providers.find((p) => p.id === selectedProvider)?.name : void 0,
93
116
  validate: (input) => {
94
117
  const trimmed = input.trim();
95
118
  if (!trimmed) {
96
119
  return i18n.t("multi-config:profileNameRequired");
97
120
  }
98
- if (!/^[\w\-\s\u4E00-\u9FA5]+$/.test(trimmed)) {
121
+ if (!/^[\w\-\s.\u4E00-\u9FA5]+$/.test(trimmed)) {
99
122
  return i18n.t("multi-config:profileNameInvalid");
100
123
  }
101
124
  return true;
@@ -109,14 +132,16 @@ ${i18n.t("multi-config:addingNewProfile")}`));
109
132
  { name: i18n.t("multi-config:authType.api_key"), value: "api_key" },
110
133
  { name: i18n.t("multi-config:authType.auth_token"), value: "auth_token" }
111
134
  ],
112
- default: "api_key"
135
+ default: prefilledAuthType || "api_key",
136
+ when: () => selectedProvider === "custom"
137
+ // Only ask if custom
113
138
  },
114
139
  {
115
140
  type: "input",
116
141
  name: "baseUrl",
117
142
  message: i18n.t("multi-config:baseUrlPrompt"),
118
- default: "https://api.anthropic.com",
119
- when: (answers2) => answers2.authType !== "ccr_proxy",
143
+ default: prefilledBaseUrl || "https://api.anthropic.com",
144
+ when: (answers2) => selectedProvider === "custom" && answers2.authType !== "ccr_proxy",
120
145
  validate: (input) => {
121
146
  const trimmed = input.trim();
122
147
  if (!trimmed) {
@@ -133,8 +158,8 @@ ${i18n.t("multi-config:addingNewProfile")}`));
133
158
  {
134
159
  type: "input",
135
160
  name: "apiKey",
136
- message: i18n.t("multi-config:apiKeyPrompt"),
137
- when: (answers2) => answers2.authType !== "ccr_proxy",
161
+ message: selectedProvider !== "custom" ? i18n.t("api:enterProviderApiKey", { provider: providers.find((p) => p.id === selectedProvider)?.name || selectedProvider }) : i18n.t("multi-config:apiKeyPrompt"),
162
+ when: (answers2) => selectedProvider === "custom" ? answers2.authType !== "ccr_proxy" : true,
138
163
  validate: (input) => {
139
164
  const trimmed = input.trim();
140
165
  if (!trimmed) {
@@ -146,7 +171,14 @@ ${i18n.t("multi-config:addingNewProfile")}`));
146
171
  }
147
172
  return true;
148
173
  }
149
- },
174
+ }
175
+ ]);
176
+ let modelConfig = null;
177
+ if (selectedProvider === "custom") {
178
+ const { promptCustomModels } = await import('./features.mjs');
179
+ modelConfig = await promptCustomModels();
180
+ }
181
+ const { setAsDefault } = await inquirer.prompt([
150
182
  {
151
183
  type: "confirm",
152
184
  name: "setAsDefault",
@@ -159,11 +191,19 @@ ${i18n.t("multi-config:addingNewProfile")}`));
159
191
  const profile = {
160
192
  id: profileId,
161
193
  name: profileName,
162
- authType: answers.authType
194
+ authType: selectedProvider === "custom" ? answers.authType : prefilledAuthType
163
195
  };
164
- if (answers.authType !== "ccr_proxy") {
196
+ if (profile.authType !== "ccr_proxy") {
165
197
  profile.apiKey = answers.apiKey.trim();
166
- profile.baseUrl = answers.baseUrl.trim();
198
+ profile.baseUrl = selectedProvider === "custom" ? answers.baseUrl.trim() : prefilledBaseUrl;
199
+ }
200
+ if (modelConfig) {
201
+ if (modelConfig.primaryModel.trim()) {
202
+ profile.primaryModel = modelConfig.primaryModel.trim();
203
+ }
204
+ if (modelConfig.fastModel.trim()) {
205
+ profile.fastModel = modelConfig.fastModel.trim();
206
+ }
167
207
  }
168
208
  const existingProfile = ClaudeCodeConfigManager.getProfileByName(profile.name);
169
209
  if (existingProfile) {
@@ -188,14 +228,16 @@ ${i18n.t("multi-config:addingNewProfile")}`));
188
228
  name: profile.name,
189
229
  authType: profile.authType,
190
230
  apiKey: profile.apiKey,
191
- baseUrl: profile.baseUrl
231
+ baseUrl: profile.baseUrl,
232
+ primaryModel: profile.primaryModel,
233
+ fastModel: profile.fastModel
192
234
  });
193
235
  if (updateResult.success) {
194
236
  console.log(ansis.green(i18n.t("multi-config:profileUpdated", { name: profile.name })));
195
237
  if (updateResult.backupPath) {
196
238
  console.log(ansis.gray(i18n.t("common:backupCreated", { path: updateResult.backupPath })));
197
239
  }
198
- if (answers.setAsDefault) {
240
+ if (setAsDefault) {
199
241
  const switchResult = await ClaudeCodeConfigManager.switchProfile(existingProfile.id);
200
242
  if (switchResult.success) {
201
243
  console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: profile.name })));
@@ -213,7 +255,7 @@ ${i18n.t("multi-config:addingNewProfile")}`));
213
255
  if (result.backupPath) {
214
256
  console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
215
257
  }
216
- if (answers.setAsDefault) {
258
+ if (setAsDefault) {
217
259
  const switchResult = await ClaudeCodeConfigManager.switchProfile(runtimeProfile.id);
218
260
  if (switchResult.success) {
219
261
  console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: runtimeProfile.name })));
@@ -1,6 +1,6 @@
1
1
  import ansis from 'ansis';
2
2
  import inquirer from 'inquirer';
3
- import { a4 as ensureI18nInitialized, af as detectConfigManagementMode, a5 as i18n, a6 as addNumbersToChoices } from './simple-config.mjs';
3
+ import { a5 as ensureI18nInitialized, ag as detectConfigManagementMode, a6 as i18n, a7 as addNumbersToChoices } from './simple-config.mjs';
4
4
  import { deleteProviders, editExistingProvider, addProviderToExisting } from './codex-provider-manager.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:process';
@@ -59,16 +59,41 @@ async function configureIncrementalManagement() {
59
59
  }
60
60
  }
61
61
  async function handleAddProvider() {
62
+ const { getApiProviders } = await import('./api-providers.mjs');
63
+ const apiProviders = getApiProviders("codex");
64
+ const providerChoices = [
65
+ { name: i18n.t("api:customProvider"), value: "custom" },
66
+ ...apiProviders.map((p) => ({ name: p.name, value: p.id }))
67
+ ];
68
+ const { selectedProvider } = await inquirer.prompt([{
69
+ type: "list",
70
+ name: "selectedProvider",
71
+ message: i18n.t("api:selectApiProvider"),
72
+ choices: addNumbersToChoices(providerChoices)
73
+ }]);
74
+ let prefilledBaseUrl;
75
+ let prefilledWireApi;
76
+ let prefilledModel;
77
+ if (selectedProvider !== "custom") {
78
+ const provider2 = apiProviders.find((p) => p.id === selectedProvider);
79
+ if (provider2?.codex) {
80
+ prefilledBaseUrl = provider2.codex.baseUrl;
81
+ prefilledWireApi = provider2.codex.wireApi;
82
+ prefilledModel = provider2.codex.defaultModel;
83
+ console.log(ansis.gray(i18n.t("api:providerSelected", { name: provider2.name })));
84
+ }
85
+ }
62
86
  const answers = await inquirer.prompt([
63
87
  {
64
88
  type: "input",
65
89
  name: "providerName",
66
90
  message: i18n.t("codex:providerNamePrompt"),
91
+ default: selectedProvider !== "custom" ? apiProviders.find((p) => p.id === selectedProvider)?.name : void 0,
67
92
  validate: (input) => {
68
93
  const trimmed = input.trim();
69
94
  if (!trimmed)
70
95
  return i18n.t("codex:providerNameRequired");
71
- if (!/^[\w\-\s]+$/.test(trimmed))
96
+ if (!/^[\w\-\s.]+$/.test(trimmed))
72
97
  return i18n.t("codex:providerNameInvalid");
73
98
  return true;
74
99
  }
@@ -77,7 +102,8 @@ async function handleAddProvider() {
77
102
  type: "input",
78
103
  name: "baseUrl",
79
104
  message: i18n.t("codex:providerBaseUrlPrompt"),
80
- default: "https://api.openai.com/v1",
105
+ default: prefilledBaseUrl || "https://api.openai.com/v1",
106
+ when: () => selectedProvider === "custom",
81
107
  validate: (input) => !!input.trim() || i18n.t("codex:providerBaseUrlRequired")
82
108
  },
83
109
  {
@@ -88,30 +114,63 @@ async function handleAddProvider() {
88
114
  { name: i18n.t("codex:protocolResponses"), value: "responses" },
89
115
  { name: i18n.t("codex:protocolChat"), value: "chat" }
90
116
  ],
91
- default: "responses"
117
+ default: prefilledWireApi || "responses",
118
+ when: () => selectedProvider === "custom"
92
119
  },
93
120
  {
94
121
  type: "input",
95
122
  name: "apiKey",
96
- message: i18n.t("codex:providerApiKeyPrompt"),
123
+ message: selectedProvider !== "custom" ? i18n.t("api:enterProviderApiKey", { provider: apiProviders.find((p) => p.id === selectedProvider)?.name || selectedProvider }) : i18n.t("codex:providerApiKeyPrompt"),
97
124
  validate: (input) => !!input.trim() || i18n.t("codex:providerApiKeyRequired")
98
125
  }
99
126
  ]);
100
- const providerId = answers.providerName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-]/g, "");
127
+ const providerId = answers.providerName.trim().toLowerCase().replace(/\s+/g, "-").replace(/\./g, "-").replace(/[^a-z0-9\-]/g, "");
128
+ const managementMode = detectConfigManagementMode();
129
+ const existingProvider = managementMode.providers?.find((p) => p.id === providerId);
130
+ if (existingProvider) {
131
+ const { shouldOverwrite } = await inquirer.prompt([{
132
+ type: "confirm",
133
+ name: "shouldOverwrite",
134
+ message: i18n.t("codex:providerDuplicatePrompt", {
135
+ name: existingProvider.name,
136
+ source: i18n.t("codex:existingConfig")
137
+ }),
138
+ default: false
139
+ }]);
140
+ if (!shouldOverwrite) {
141
+ console.log(ansis.yellow(i18n.t("codex:providerDuplicateSkipped")));
142
+ return;
143
+ }
144
+ }
101
145
  const provider = {
102
146
  id: providerId,
103
147
  name: answers.providerName.trim(),
104
- baseUrl: answers.baseUrl.trim(),
105
- wireApi: answers.wireApi,
148
+ baseUrl: selectedProvider === "custom" ? answers.baseUrl.trim() : prefilledBaseUrl,
149
+ wireApi: selectedProvider === "custom" ? answers.wireApi : prefilledWireApi,
106
150
  envKey: `${providerId.toUpperCase().replace(/-/g, "_")}_API_KEY`,
107
- requiresOpenaiAuth: true
151
+ requiresOpenaiAuth: true,
152
+ model: prefilledModel || "gpt-5-codex"
153
+ // Use provider's default model or fallback
108
154
  };
109
- const result = await addProviderToExisting(provider, answers.apiKey.trim());
155
+ const result = await addProviderToExisting(provider, answers.apiKey.trim(), true);
110
156
  if (result.success) {
111
157
  console.log(ansis.green(i18n.t("codex:providerAdded", { name: result.addedProvider?.name })));
112
158
  if (result.backupPath) {
113
159
  console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
114
160
  }
161
+ const { setAsDefault } = await inquirer.prompt([{
162
+ type: "confirm",
163
+ name: "setAsDefault",
164
+ message: i18n.t("multi-config:setAsDefaultPrompt"),
165
+ default: true
166
+ }]);
167
+ if (setAsDefault) {
168
+ const { switchToProvider } = await import('./simple-config.mjs').then(function (n) { return n.b6; });
169
+ const switched = await switchToProvider(provider.id);
170
+ if (switched) {
171
+ console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: provider.name })));
172
+ }
173
+ }
115
174
  } else {
116
175
  console.log(ansis.red(i18n.t("codex:providerAddFailed", { error: result.error })));
117
176
  }
@@ -1,4 +1,4 @@
1
- import { ag as readCodexConfig, ah as backupCodexComplete, ai as writeCodexConfig, aj as writeAuthFile } from './simple-config.mjs';
1
+ import { ah as readCodexConfig, ai as backupCodexComplete, aj as writeCodexConfig, ak as writeAuthFile } from './simple-config.mjs';
2
2
  import 'node:fs';
3
3
  import 'node:process';
4
4
  import 'ansis';
@@ -26,7 +26,7 @@ const ERROR_MESSAGES = {
26
26
  PROVIDERS_NOT_FOUND: (providers) => `Some providers not found: ${providers.join(", ")}`,
27
27
  CANNOT_DELETE_ALL: "Cannot delete all providers. At least one provider must remain."
28
28
  };
29
- async function addProviderToExisting(provider, apiKey) {
29
+ async function addProviderToExisting(provider, apiKey, allowOverwrite = false) {
30
30
  try {
31
31
  const existingConfig = readCodexConfig();
32
32
  if (!existingConfig) {
@@ -35,8 +35,8 @@ async function addProviderToExisting(provider, apiKey) {
35
35
  error: ERROR_MESSAGES.NO_CONFIG
36
36
  };
37
37
  }
38
- const existingProvider = existingConfig.providers.find((p) => p.id === provider.id);
39
- if (existingProvider) {
38
+ const existingProviderIndex = existingConfig.providers.findIndex((p) => p.id === provider.id);
39
+ if (existingProviderIndex !== -1 && !allowOverwrite) {
40
40
  return {
41
41
  success: false,
42
42
  error: ERROR_MESSAGES.PROVIDER_EXISTS(provider.id)
@@ -49,10 +49,20 @@ async function addProviderToExisting(provider, apiKey) {
49
49
  error: ERROR_MESSAGES.BACKUP_FAILED
50
50
  };
51
51
  }
52
- const updatedConfig = {
53
- ...existingConfig,
54
- providers: [...existingConfig.providers, provider]
55
- };
52
+ let updatedConfig;
53
+ if (existingProviderIndex !== -1) {
54
+ const updatedProviders = [...existingConfig.providers];
55
+ updatedProviders[existingProviderIndex] = provider;
56
+ updatedConfig = {
57
+ ...existingConfig,
58
+ providers: updatedProviders
59
+ };
60
+ } else {
61
+ updatedConfig = {
62
+ ...existingConfig,
63
+ providers: [...existingConfig.providers, provider]
64
+ };
65
+ }
56
66
  writeCodexConfig(updatedConfig);
57
67
  const authEntries = {};
58
68
  authEntries[provider.envKey] = apiKey;
@@ -2,7 +2,7 @@ import { homedir } from 'node:os';
2
2
  import { pathExists } from 'fs-extra';
3
3
  import { join } from 'pathe';
4
4
  import { exec } from 'tinyexec';
5
- import { a5 as i18n } from './simple-config.mjs';
5
+ import { a6 as i18n } from './simple-config.mjs';
6
6
  import { m as moveToTrash } from '../shared/zcf.DGjQxTq_.mjs';
7
7
  import 'node:fs';
8
8
  import 'node:process';
@@ -1,7 +1,7 @@
1
1
  import { exec } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  import ansis from 'ansis';
4
- import { a4 as ensureI18nInitialized, a5 as i18n } from './simple-config.mjs';
4
+ import { a5 as ensureI18nInitialized, a6 as i18n } from './simple-config.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:process';
7
7
  import 'inquirer';