zcf 2.12.13 → 3.0.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 (67) hide show
  1. package/README.md +90 -3
  2. package/dist/chunks/codex-config-switch.mjs +419 -0
  3. package/dist/chunks/codex-uninstaller.mjs +404 -0
  4. package/dist/chunks/simple-config.mjs +1861 -374
  5. package/dist/cli.mjs +672 -206
  6. package/dist/i18n/locales/en/cli.json +1 -0
  7. package/dist/i18n/locales/en/codex.json +102 -0
  8. package/dist/i18n/locales/en/common.json +4 -1
  9. package/dist/i18n/locales/en/configuration.json +10 -4
  10. package/dist/i18n/locales/en/language.json +8 -2
  11. package/dist/i18n/locales/en/mcp.json +4 -3
  12. package/dist/i18n/locales/en/menu.json +20 -0
  13. package/dist/i18n/locales/en/uninstall.json +0 -4
  14. package/dist/i18n/locales/zh-CN/cli.json +1 -0
  15. package/dist/i18n/locales/zh-CN/codex.json +102 -0
  16. package/dist/i18n/locales/zh-CN/common.json +4 -1
  17. package/dist/i18n/locales/zh-CN/configuration.json +10 -4
  18. package/dist/i18n/locales/zh-CN/language.json +8 -2
  19. package/dist/i18n/locales/zh-CN/mcp.json +4 -3
  20. package/dist/i18n/locales/zh-CN/menu.json +20 -0
  21. package/dist/i18n/locales/zh-CN/uninstall.json +0 -4
  22. package/dist/index.d.mts +11 -3
  23. package/dist/index.d.ts +11 -3
  24. package/dist/index.mjs +2 -1
  25. package/dist/shared/zcf.DGjQxTq_.mjs +34 -0
  26. package/package.json +11 -10
  27. package/templates/{common → claude-code/common}/settings.json +2 -1
  28. package/templates/codex/common/config.toml +0 -0
  29. package/templates/codex/en/system-prompt/engineer-professional.md +87 -0
  30. package/templates/codex/en/system-prompt/laowang-engineer.md +126 -0
  31. package/templates/codex/en/system-prompt/nekomata-engineer.md +119 -0
  32. package/templates/codex/en/workflow/sixStep/prompts/workflow.md +211 -0
  33. package/templates/codex/zh-CN/system-prompt/engineer-professional.md +88 -0
  34. package/templates/codex/zh-CN/system-prompt/laowang-engineer.md +126 -0
  35. package/templates/codex/zh-CN/system-prompt/nekomata-engineer.md +119 -0
  36. package/templates/codex/zh-CN/workflow/sixStep/prompts/workflow.md +211 -0
  37. /package/templates/{CLAUDE.md → claude-code/CLAUDE.md} +0 -0
  38. /package/templates/{en → claude-code/en}/output-styles/engineer-professional.md +0 -0
  39. /package/templates/{en → claude-code/en}/output-styles/laowang-engineer.md +0 -0
  40. /package/templates/{en → claude-code/en}/output-styles/nekomata-engineer.md +0 -0
  41. /package/templates/{en → claude-code/en}/workflow/bmad/commands/bmad-init.md +0 -0
  42. /package/templates/{en → claude-code/en}/workflow/common/agents/get-current-datetime.md +0 -0
  43. /package/templates/{en → claude-code/en}/workflow/common/agents/init-architect.md +0 -0
  44. /package/templates/{en → claude-code/en}/workflow/common/commands/init-project.md +0 -0
  45. /package/templates/{en → claude-code/en}/workflow/git/commands/git-cleanBranches.md +0 -0
  46. /package/templates/{en → claude-code/en}/workflow/git/commands/git-commit.md +0 -0
  47. /package/templates/{en → claude-code/en}/workflow/git/commands/git-rollback.md +0 -0
  48. /package/templates/{en → claude-code/en}/workflow/git/commands/git-worktree.md +0 -0
  49. /package/templates/{en → claude-code/en}/workflow/plan/agents/planner.md +0 -0
  50. /package/templates/{en → claude-code/en}/workflow/plan/agents/ui-ux-designer.md +0 -0
  51. /package/templates/{en → claude-code/en}/workflow/plan/commands/feat.md +0 -0
  52. /package/templates/{en → claude-code/en}/workflow/sixStep/commands/workflow.md +0 -0
  53. /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/engineer-professional.md +0 -0
  54. /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/laowang-engineer.md +0 -0
  55. /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/nekomata-engineer.md +0 -0
  56. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/bmad/commands/bmad-init.md +0 -0
  57. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/agents/get-current-datetime.md +0 -0
  58. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/agents/init-architect.md +0 -0
  59. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/commands/init-project.md +0 -0
  60. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-cleanBranches.md +0 -0
  61. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-commit.md +0 -0
  62. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-rollback.md +0 -0
  63. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-worktree.md +0 -0
  64. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/agents/planner.md +0 -0
  65. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/agents/ui-ux-designer.md +0 -0
  66. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/commands/feat.md +0 -0
  67. /package/templates/{zh-CN → claude-code/zh-CN}/workflow/sixStep/commands/workflow.md +0 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ZCF - Zero-Config Claude-Code Flow
1
+ # ZCF - Zero-Config Code Flow
2
2
 
3
3
  [![npm version][npm-version-src]][npm-version-href]
4
4
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
@@ -10,6 +10,8 @@
10
10
 
11
11
  [中文](README_zh-CN.md) | **English** | [日本語](README_ja-JP.md) | [Changelog](CHANGELOG.md)
12
12
 
13
+ **✨ Quick Links**: [Codex Support](#-codex-support-v300-new) | [BMad Workflow](#-bmad-workflow-v27-new-feature) | [Spec Workflow](#-spec-workflow-v2124-new-feature) | [Open Web Search](#-open-web-search-v2129-new-feature) | [CCR Router](#-ccr-claude-code-router-support-v28-enhanced) | [CCometixLine](#-ccometixline-support-status-bar-tool-v299-new) | [Output Styles](#-ai-output-styles-v212-new-feature)
14
+
13
15
  > Zero-config, one-click setup for Claude Code with bilingual support, intelligent agent system and personalized AI assistant
14
16
 
15
17
  ![Rendering](./src/assets/screenshot-en.webp)
@@ -126,6 +128,82 @@ When using `--skip-prompt`, the following parameters are available:
126
128
  | `--default-output-style, -d` | Default output style | Same as output styles plus built-in: `default`, `explanatory`, `learning` | No | `engineer-professional` |
127
129
  | `--install-cometix-line, -x` | Install CCometixLine statusline tool | `true`, `false` | No | `true` |
128
130
 
131
+ #### 🤖 Codex Support (v3.0.0+ New)
132
+
133
+ [Codex](https://www.npmjs.com/package/@openai/codex) is OpenAI's official code generation CLI tool. ZCF now supports complete Codex integration with the same configuration convenience as Claude Code.
134
+
135
+ **Key Features:**
136
+
137
+ - **Unified Tool Management**: Switch between Claude Code and Codex seamlessly through ZCF menu
138
+ - **Intelligent Configuration**: Automatic Codex CLI installation, API provider setup, and MCP service integration
139
+ - **Comprehensive Backup System**: All configuration changes include timestamped backups with recovery capabilities
140
+ - **Multi-Provider Support**: Configure multiple API providers (OpenAI, custom endpoints) with easy switching
141
+ - **System Prompt Integration**: Install professional AI personalities (Engineer, Nekomata Engineer, Laowang Engineer)
142
+ - **Workflow Templates**: Import structured development workflows optimized for code generation tasks
143
+ - **Advanced Uninstaller**: Selective removal of Codex components with conflict resolution
144
+
145
+ **Getting Started with Codex:**
146
+
147
+ Switch to Codex mode in ZCF main menu:
148
+ ```bash
149
+ npx zcf → Select S # Switch between Claude Code and Codex
150
+ ```
151
+
152
+ Or access Codex features directly:
153
+ ```bash
154
+ # Full Codex initialization
155
+ npx zcf → Select 1 (after switching to Codex mode)
156
+
157
+ # Individual Codex configuration
158
+ npx zcf → Select 3 # Configure Codex API providers
159
+ npx zcf → Select 4 # Configure Codex MCP services
160
+ ```
161
+
162
+ **Configuration Options:**
163
+
164
+ 1. **API Provider Configuration**:
165
+ - **Official Login**: Use OpenAI's official authentication system
166
+ - **Custom Providers**: Configure multiple API endpoints with provider switching
167
+ - **Incremental Management**: Add, edit, or remove providers without affecting existing configuration
168
+
169
+ 2. **System Prompt Styles**:
170
+ - **Engineer Professional**: SOLID, KISS, DRY, YAGNI principles for robust code
171
+ - **Nekomata Engineer**: Cute catgirl engineer with rigorous technical standards
172
+ - **Laowang Engineer**: Grumpy tech style that never tolerates substandard code
173
+
174
+ 3. **Workflow Integration**:
175
+ - **Six-Step Workflow**: Structured development process from research to optimization
176
+ - **Custom Workflows**: Import and configure task-specific development templates
177
+
178
+ 4. **MCP Services**: Full compatibility with existing MCP services including:
179
+ - Context7, Open WebSearch, Spec Workflow
180
+ - DeepWiki, Playwright, EXA search
181
+ - Automatic service configuration with API key management
182
+
183
+ **File Locations:**
184
+
185
+ - Configuration: `~/.codex/config.toml`
186
+ - Authentication: `~/.codex/auth.json`
187
+ - System Prompts: `~/.codex/AGENTS.md`
188
+ - Workflows: `~/.codex/prompts/`
189
+ - Backups: `~/.codex/backup/`
190
+
191
+ **Command Line Operations:**
192
+
193
+ Dedicated command line tool for Codex (v3.0.0+ New):
194
+
195
+ ```bash
196
+ # Codex API provider switching
197
+ npx zcf config-switch # Interactive provider selection
198
+ npx zcf cs # Using alias
199
+ npx zcf cs provider-name # Direct switch to specified provider
200
+ npx zcf cs --list # List all available providers
201
+ ```
202
+
203
+ **Migration Between Tools:**
204
+
205
+ ZCF allows seamless switching between Claude Code and Codex while preserving your preferences and workflow configurations. Both tools share the same MCP services and workflow templates for consistent development experience.
206
+
129
207
  #### 🎨 AI Output Styles (v2.12+ New Feature)
130
208
 
131
209
  ZCF now supports customizable AI output styles to personalize your Claude Code experience:
@@ -224,6 +302,7 @@ After CCR setup, ZCF automatically configures Claude Code to use CCR as the API
224
302
 
225
303
  **Important Notice for v2.9.9 Users**: If you previously installed CCometixLine using ZCF v2.9.9, please rerun the installation process to ensure that the CCometixLine configuration is correctly added. Run `npx zcf` -> `Select L` -> `Select 1` to add the CCometixLine configuration.
226
304
 
305
+
227
306
  #### 📊 CCometixLine Support (Status Bar Tool) (v2.9.9+ New)
228
307
 
229
308
  [CCometixLine](https://github.com/Haleclipse/CCometixLine) is a high-performance Rust-based statusline tool that provides:
@@ -327,7 +406,7 @@ After configuration:
327
406
  ```bash
328
407
  $ npx zcf
329
408
 
330
- ZCF - Zero-Config Claude-Code Flow
409
+ ZCF - Zero-Config Code Flow
331
410
 
332
411
  ? Select ZCF display language / 选择ZCF显示语言:
333
412
  ❯ 简体中文
@@ -473,6 +552,7 @@ Enter your choice: _
473
552
  | `zcf update` | `zcf u` | Update workflow-related md files with backup |
474
553
  | `zcf ccu` | - | Run Claude Code usage analysis tool - [ccusage](https://github.com/ryoppippi/ccusage) |
475
554
  | `zcf ccr` | - | Open CCR (Claude Code Router) management menu |
555
+ | `zcf config-switch` | `zcf cs` | Codex API provider switching tool - Switch between official login and custom providers |
476
556
  | `zcf uninstall` | - | Interactive uninstall tool for Claude Code configurations and tools |
477
557
  | `zcf check-updates` | - | Check and update Claude Code, CCR and CCometixLine versions |
478
558
 
@@ -524,6 +604,12 @@ npx zcf u -c en # Using short option
524
604
 
525
605
  # Run Claude Code usage analysis tool (powered by ccusage)
526
606
  npx zcf ccu # Daily usage (default), or use: monthly, session, blocks
607
+
608
+ # Codex API provider switching (v3.0.0+ New)
609
+ npx zcf config-switch # Interactive provider selection
610
+ npx zcf cs # Using alias
611
+ npx zcf cs provider-name # Direct switch to specified provider
612
+ npx zcf cs --list # List all available providers
527
613
  ```
528
614
 
529
615
  ## 📁 Project Structure
@@ -712,11 +798,12 @@ A huge thank you to all our sponsors for their generous support!
712
798
  - Tc (first sponsor)
713
799
  - Argolinhas (first ko-fi sponsor ٩(•̤̀ᵕ•̤́๑))
714
800
  - r\*r (first anonymous sponsor🤣)
801
+ - \*\*康 (first KFC sponsor🍗)
715
802
  - 16°C coffee (My best friend🤪, offered Claude Code max $200 package)
716
803
 
717
804
  ## 📄 License
718
805
 
719
- MIT License
806
+ [MIT License](LICENSE)
720
807
 
721
808
  ---
722
809
 
@@ -0,0 +1,419 @@
1
+ import ansis from 'ansis';
2
+ import inquirer from 'inquirer';
3
+ import { X as readCodexConfig, Y as backupCodexComplete, _ as writeCodexConfig, $ as writeAuthFile, a0 as ensureI18nInitialized, a1 as detectConfigManagementMode, W as i18n, a2 as addNumbersToChoices } from './simple-config.mjs';
4
+ import 'node:fs';
5
+ import 'node:process';
6
+ import 'node:child_process';
7
+ import 'node:os';
8
+ import 'node:util';
9
+ import 'dayjs';
10
+ import 'pathe';
11
+ import 'node:url';
12
+ import 'ora';
13
+ import 'semver';
14
+ import 'smol-toml';
15
+ import 'tinyexec';
16
+ import 'node:fs/promises';
17
+ import 'i18next';
18
+ import 'i18next-fs-backend';
19
+
20
+ const ERROR_MESSAGES = {
21
+ NO_CONFIG: "No existing configuration found",
22
+ BACKUP_FAILED: "Failed to create backup",
23
+ PROVIDER_EXISTS: (id) => `Provider with ID "${id}" already exists`,
24
+ PROVIDER_NOT_FOUND: (id) => `Provider with ID "${id}" not found`,
25
+ NO_PROVIDERS_SPECIFIED: "No providers specified for deletion",
26
+ PROVIDERS_NOT_FOUND: (providers) => `Some providers not found: ${providers.join(", ")}`,
27
+ CANNOT_DELETE_ALL: "Cannot delete all providers. At least one provider must remain."
28
+ };
29
+ async function addProviderToExisting(provider, apiKey) {
30
+ try {
31
+ const existingConfig = readCodexConfig();
32
+ if (!existingConfig) {
33
+ return {
34
+ success: false,
35
+ error: ERROR_MESSAGES.NO_CONFIG
36
+ };
37
+ }
38
+ const existingProvider = existingConfig.providers.find((p) => p.id === provider.id);
39
+ if (existingProvider) {
40
+ return {
41
+ success: false,
42
+ error: ERROR_MESSAGES.PROVIDER_EXISTS(provider.id)
43
+ };
44
+ }
45
+ const backupPath = backupCodexComplete();
46
+ if (!backupPath) {
47
+ return {
48
+ success: false,
49
+ error: ERROR_MESSAGES.BACKUP_FAILED
50
+ };
51
+ }
52
+ const updatedConfig = {
53
+ ...existingConfig,
54
+ providers: [...existingConfig.providers, provider]
55
+ };
56
+ writeCodexConfig(updatedConfig);
57
+ const authEntries = {};
58
+ authEntries[provider.envKey] = apiKey;
59
+ writeAuthFile(authEntries);
60
+ return {
61
+ success: true,
62
+ backupPath,
63
+ addedProvider: provider
64
+ };
65
+ } catch (error) {
66
+ return {
67
+ success: false,
68
+ error: error instanceof Error ? error.message : "Unknown error"
69
+ };
70
+ }
71
+ }
72
+ async function editExistingProvider(providerId, updates) {
73
+ try {
74
+ const existingConfig = readCodexConfig();
75
+ if (!existingConfig) {
76
+ return {
77
+ success: false,
78
+ error: ERROR_MESSAGES.NO_CONFIG
79
+ };
80
+ }
81
+ const providerIndex = existingConfig.providers.findIndex((p) => p.id === providerId);
82
+ if (providerIndex === -1) {
83
+ return {
84
+ success: false,
85
+ error: ERROR_MESSAGES.PROVIDER_NOT_FOUND(providerId)
86
+ };
87
+ }
88
+ const backupPath = backupCodexComplete();
89
+ if (!backupPath) {
90
+ return {
91
+ success: false,
92
+ error: ERROR_MESSAGES.BACKUP_FAILED
93
+ };
94
+ }
95
+ const updatedProvider = {
96
+ ...existingConfig.providers[providerIndex],
97
+ ...updates.name && { name: updates.name },
98
+ ...updates.baseUrl && { baseUrl: updates.baseUrl },
99
+ ...updates.wireApi && { wireApi: updates.wireApi }
100
+ };
101
+ const updatedProviders = [...existingConfig.providers];
102
+ updatedProviders[providerIndex] = updatedProvider;
103
+ const updatedConfig = {
104
+ ...existingConfig,
105
+ providers: updatedProviders
106
+ };
107
+ writeCodexConfig(updatedConfig);
108
+ if (updates.apiKey) {
109
+ const authEntries = {};
110
+ authEntries[updatedProvider.envKey] = updates.apiKey;
111
+ writeAuthFile(authEntries);
112
+ }
113
+ return {
114
+ success: true,
115
+ backupPath,
116
+ updatedProvider
117
+ };
118
+ } catch (error) {
119
+ return {
120
+ success: false,
121
+ error: error instanceof Error ? error.message : "Unknown error"
122
+ };
123
+ }
124
+ }
125
+ async function deleteProviders(providerIds) {
126
+ try {
127
+ const existingConfig = readCodexConfig();
128
+ if (!existingConfig) {
129
+ return {
130
+ success: false,
131
+ error: ERROR_MESSAGES.NO_CONFIG
132
+ };
133
+ }
134
+ if (!providerIds || providerIds.length === 0) {
135
+ return {
136
+ success: false,
137
+ error: ERROR_MESSAGES.NO_PROVIDERS_SPECIFIED
138
+ };
139
+ }
140
+ const notFoundProviders = providerIds.filter(
141
+ (id) => !existingConfig.providers.some((p) => p.id === id)
142
+ );
143
+ if (notFoundProviders.length > 0) {
144
+ return {
145
+ success: false,
146
+ error: ERROR_MESSAGES.PROVIDERS_NOT_FOUND(notFoundProviders)
147
+ };
148
+ }
149
+ const remainingProviders = existingConfig.providers.filter(
150
+ (p) => !providerIds.includes(p.id)
151
+ );
152
+ if (remainingProviders.length === 0) {
153
+ return {
154
+ success: false,
155
+ error: ERROR_MESSAGES.CANNOT_DELETE_ALL
156
+ };
157
+ }
158
+ const backupPath = backupCodexComplete();
159
+ if (!backupPath) {
160
+ return {
161
+ success: false,
162
+ error: ERROR_MESSAGES.BACKUP_FAILED
163
+ };
164
+ }
165
+ let newDefaultProvider = existingConfig.modelProvider;
166
+ if (providerIds.includes(existingConfig.modelProvider || "")) {
167
+ newDefaultProvider = remainingProviders[0].id;
168
+ }
169
+ const updatedConfig = {
170
+ ...existingConfig,
171
+ modelProvider: newDefaultProvider,
172
+ providers: remainingProviders
173
+ };
174
+ writeCodexConfig(updatedConfig);
175
+ const result = {
176
+ success: true,
177
+ backupPath,
178
+ deletedProviders: providerIds,
179
+ remainingProviders
180
+ };
181
+ if (newDefaultProvider !== existingConfig.modelProvider) {
182
+ result.newDefaultProvider = newDefaultProvider || void 0;
183
+ }
184
+ return result;
185
+ } catch (error) {
186
+ return {
187
+ success: false,
188
+ error: error instanceof Error ? error.message : "Unknown error"
189
+ };
190
+ }
191
+ }
192
+
193
+ async function configureIncrementalManagement() {
194
+ ensureI18nInitialized();
195
+ const managementMode = detectConfigManagementMode();
196
+ if (managementMode.mode !== "management" || !managementMode.hasProviders) {
197
+ console.log(ansis.yellow(i18n.t("codex:noExistingProviders")));
198
+ return;
199
+ }
200
+ console.log(ansis.cyan(i18n.t("codex:incrementalManagementTitle")));
201
+ console.log(ansis.gray(i18n.t("codex:currentProviderCount", { count: managementMode.providerCount })));
202
+ if (managementMode.currentProvider) {
203
+ console.log(ansis.gray(i18n.t("codex:currentDefaultProvider", { provider: managementMode.currentProvider })));
204
+ }
205
+ const choices = [
206
+ { name: i18n.t("codex:addProvider"), value: "add" },
207
+ { name: i18n.t("codex:editProvider"), value: "edit" },
208
+ { name: i18n.t("codex:deleteProvider"), value: "delete" },
209
+ { name: i18n.t("common:back"), value: "back" }
210
+ ];
211
+ const { action } = await inquirer.prompt([{
212
+ type: "list",
213
+ name: "action",
214
+ message: i18n.t("codex:selectAction"),
215
+ choices: addNumbersToChoices(choices)
216
+ }]);
217
+ if (!action || action === "back") {
218
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
219
+ return;
220
+ }
221
+ switch (action) {
222
+ case "add":
223
+ await handleAddProvider();
224
+ break;
225
+ case "edit":
226
+ await handleEditProvider(managementMode.providers);
227
+ break;
228
+ case "delete":
229
+ await handleDeleteProvider(managementMode.providers);
230
+ break;
231
+ }
232
+ }
233
+ async function handleAddProvider() {
234
+ const answers = await inquirer.prompt([
235
+ {
236
+ type: "input",
237
+ name: "providerName",
238
+ message: i18n.t("codex:providerNamePrompt"),
239
+ validate: (input) => {
240
+ const trimmed = input.trim();
241
+ if (!trimmed)
242
+ return i18n.t("codex:providerNameRequired");
243
+ if (!/^[\w\-\s]+$/.test(trimmed))
244
+ return i18n.t("codex:providerNameInvalid");
245
+ return true;
246
+ }
247
+ },
248
+ {
249
+ type: "input",
250
+ name: "baseUrl",
251
+ message: i18n.t("codex:providerBaseUrlPrompt"),
252
+ default: "https://api.openai.com/v1",
253
+ validate: (input) => !!input.trim() || i18n.t("codex:providerBaseUrlRequired")
254
+ },
255
+ {
256
+ type: "list",
257
+ name: "wireApi",
258
+ message: i18n.t("codex:providerProtocolPrompt"),
259
+ choices: [
260
+ { name: i18n.t("codex:protocolResponses"), value: "responses" },
261
+ { name: i18n.t("codex:protocolChat"), value: "chat" }
262
+ ],
263
+ default: "responses"
264
+ },
265
+ {
266
+ type: "password",
267
+ name: "apiKey",
268
+ message: i18n.t("codex:providerApiKeyPrompt") + i18n.t("common:inputHidden"),
269
+ validate: (input) => !!input.trim() || i18n.t("codex:providerApiKeyRequired")
270
+ }
271
+ ]);
272
+ const providerId = answers.providerName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-]/g, "");
273
+ const provider = {
274
+ id: providerId,
275
+ name: answers.providerName.trim(),
276
+ baseUrl: answers.baseUrl.trim(),
277
+ wireApi: answers.wireApi,
278
+ envKey: `${providerId.toUpperCase().replace(/-/g, "_")}_API_KEY`,
279
+ requiresOpenaiAuth: true
280
+ };
281
+ const result = await addProviderToExisting(provider, answers.apiKey.trim());
282
+ if (result.success) {
283
+ console.log(ansis.green(i18n.t("codex:providerAdded", { name: result.addedProvider?.name })));
284
+ if (result.backupPath) {
285
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
286
+ }
287
+ } else {
288
+ console.log(ansis.red(i18n.t("codex:providerAddFailed", { error: result.error })));
289
+ }
290
+ }
291
+ async function handleEditProvider(providers) {
292
+ const choices = providers.map((provider2) => ({
293
+ name: `${provider2.name} (${provider2.baseUrl})`,
294
+ value: provider2.id
295
+ }));
296
+ const { selectedProviderId } = await inquirer.prompt([{
297
+ type: "list",
298
+ name: "selectedProviderId",
299
+ message: i18n.t("codex:selectProviderToEdit"),
300
+ choices: addNumbersToChoices(choices)
301
+ }]);
302
+ if (!selectedProviderId) {
303
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
304
+ return;
305
+ }
306
+ const provider = providers.find((p) => p.id === selectedProviderId);
307
+ if (!provider) {
308
+ console.log(ansis.red(i18n.t("codex:providerNotFound")));
309
+ return;
310
+ }
311
+ const answers = await inquirer.prompt([
312
+ {
313
+ type: "input",
314
+ name: "providerName",
315
+ message: i18n.t("codex:providerNamePrompt"),
316
+ default: provider.name,
317
+ validate: (input) => {
318
+ const trimmed = input.trim();
319
+ if (!trimmed)
320
+ return i18n.t("codex:providerNameRequired");
321
+ if (!/^[\w\-\s]+$/.test(trimmed))
322
+ return i18n.t("codex:providerNameInvalid");
323
+ return true;
324
+ }
325
+ },
326
+ {
327
+ type: "input",
328
+ name: "baseUrl",
329
+ message: i18n.t("codex:providerBaseUrlPrompt"),
330
+ default: provider.baseUrl,
331
+ validate: (input) => !!input.trim() || i18n.t("codex:providerBaseUrlRequired")
332
+ },
333
+ {
334
+ type: "list",
335
+ name: "wireApi",
336
+ message: i18n.t("codex:providerProtocolPrompt"),
337
+ choices: [
338
+ { name: i18n.t("codex:protocolResponses"), value: "responses" },
339
+ { name: i18n.t("codex:protocolChat"), value: "chat" }
340
+ ],
341
+ default: provider.wireApi
342
+ },
343
+ {
344
+ type: "password",
345
+ name: "apiKey",
346
+ message: i18n.t("codex:providerApiKeyPrompt") + i18n.t("common:inputHidden"),
347
+ validate: (input) => !!input.trim() || i18n.t("codex:providerApiKeyRequired")
348
+ }
349
+ ]);
350
+ const updates = {
351
+ name: answers.providerName.trim(),
352
+ baseUrl: answers.baseUrl.trim(),
353
+ wireApi: answers.wireApi,
354
+ apiKey: answers.apiKey.trim()
355
+ };
356
+ const result = await editExistingProvider(selectedProviderId, updates);
357
+ if (result.success) {
358
+ console.log(ansis.green(i18n.t("codex:providerUpdated", { name: result.updatedProvider?.name })));
359
+ if (result.backupPath) {
360
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
361
+ }
362
+ } else {
363
+ console.log(ansis.red(i18n.t("codex:providerUpdateFailed", { error: result.error })));
364
+ }
365
+ }
366
+ async function handleDeleteProvider(providers) {
367
+ const choices = providers.map((provider) => ({
368
+ name: `${provider.name} (${provider.baseUrl})`,
369
+ value: provider.id
370
+ }));
371
+ const { selectedProviderIds } = await inquirer.prompt({
372
+ type: "checkbox",
373
+ name: "selectedProviderIds",
374
+ message: i18n.t("codex:selectProvidersToDelete"),
375
+ choices,
376
+ validate: (input) => {
377
+ const selected = input;
378
+ if (!selected || selected.length === 0) {
379
+ return i18n.t("codex:selectAtLeastOne");
380
+ }
381
+ if (selected.length === providers.length) {
382
+ return i18n.t("codex:cannotDeleteAll");
383
+ }
384
+ return true;
385
+ }
386
+ });
387
+ if (!selectedProviderIds || selectedProviderIds.length === 0) {
388
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
389
+ return;
390
+ }
391
+ const selectedNames = selectedProviderIds.map(
392
+ (id) => providers.find((p) => p.id === id)?.name || id
393
+ ).join(", ");
394
+ const { confirmDelete } = await inquirer.prompt([{
395
+ type: "confirm",
396
+ name: "confirmDelete",
397
+ message: i18n.t("codex:confirmDeleteProviders", { providers: selectedNames }),
398
+ default: false
399
+ }]);
400
+ if (!confirmDelete) {
401
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
402
+ return;
403
+ }
404
+ const result = await deleteProviders(selectedProviderIds);
405
+ if (result.success) {
406
+ console.log(ansis.green(i18n.t("codex:providersDeleted", { count: selectedProviderIds.length })));
407
+ if (result.newDefaultProvider) {
408
+ console.log(ansis.cyan(i18n.t("codex:newDefaultProvider", { provider: result.newDefaultProvider })));
409
+ }
410
+ if (result.backupPath) {
411
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
412
+ }
413
+ } else {
414
+ console.log(ansis.red(i18n.t("codex:providersDeleteFailed", { error: result.error })));
415
+ }
416
+ }
417
+ const codexConfigSwitch = { configureIncrementalManagement };
418
+
419
+ export { configureIncrementalManagement, codexConfigSwitch as default };