zcf 2.12.13 → 3.0.1

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 +94 -4
  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 +1869 -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,7 +10,9 @@
10
10
 
11
11
  [中文](README_zh-CN.md) | **English** | [日本語](README_ja-JP.md) | [Changelog](CHANGELOG.md)
12
12
 
13
- > Zero-config, one-click setup for Claude Code with bilingual support, intelligent agent system and personalized AI assistant
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
+
15
+ > Zero-config, one-click setup for Claude Code & Codex with bilingual support, intelligent agent system and personalized AI assistant
14
16
 
15
17
  ![Rendering](./src/assets/screenshot-en.webp)
16
18
 
@@ -126,6 +128,83 @@ 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
+ - **⚠️ Important**: Due to Codex prompt limitations, `/workflow` commands cannot pass parameters directly. Use `/workflow` first, then provide task description in follow-up message
178
+
179
+ 4. **MCP Services**: Full compatibility with existing MCP services including:
180
+ - Context7, Open WebSearch, Spec Workflow
181
+ - DeepWiki, Playwright, EXA search
182
+ - Automatic service configuration with API key management
183
+
184
+ **File Locations:**
185
+
186
+ - Configuration: `~/.codex/config.toml`
187
+ - Authentication: `~/.codex/auth.json`
188
+ - System Prompts: `~/.codex/AGENTS.md`
189
+ - Workflows: `~/.codex/prompts/`
190
+ - Backups: `~/.codex/backup/`
191
+
192
+ **Command Line Operations:**
193
+
194
+ Dedicated command line tool for Codex (v3.0.0+ New):
195
+
196
+ ```bash
197
+ # Codex API provider switching
198
+ npx zcf config-switch # Interactive provider selection
199
+ npx zcf cs # Using alias
200
+ npx zcf cs provider-name # Direct switch to specified provider
201
+ npx zcf cs --list # List all available providers
202
+ ```
203
+
204
+ **Migration Between Tools:**
205
+
206
+ 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.
207
+
129
208
  #### 🎨 AI Output Styles (v2.12+ New Feature)
130
209
 
131
210
  ZCF now supports customizable AI output styles to personalize your Claude Code experience:
@@ -224,6 +303,7 @@ After CCR setup, ZCF automatically configures Claude Code to use CCR as the API
224
303
 
225
304
  **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
305
 
306
+
227
307
  #### 📊 CCometixLine Support (Status Bar Tool) (v2.9.9+ New)
228
308
 
229
309
  [CCometixLine](https://github.com/Haleclipse/CCometixLine) is a high-performance Rust-based statusline tool that provides:
@@ -270,6 +350,8 @@ After configuration:
270
350
  - `/feat <task description>` - Start new feature development, divided into plan and UI phases
271
351
  - `/workflow <task description>` - Execute complete development workflow, not automated, starts with multiple solution options, asks for user feedback at each step, allows plan modifications, maximum control
272
352
 
353
+ > **⚠️ Important Note for Codex Users**: Due to Codex functionality limitations, prompts cannot pass parameters directly. When using `/workflow`, you need to send the workflow command first, wait for AI response, then send your task description in a separate message.
354
+
273
355
  > **PS**:
274
356
  >
275
357
  > - Both feat and workflow have their advantages, try both to compare
@@ -327,7 +409,7 @@ After configuration:
327
409
  ```bash
328
410
  $ npx zcf
329
411
 
330
- ZCF - Zero-Config Claude-Code Flow
412
+ ZCF - Zero-Config Code Flow
331
413
 
332
414
  ? Select ZCF display language / 选择ZCF显示语言:
333
415
  ❯ 简体中文
@@ -473,6 +555,7 @@ Enter your choice: _
473
555
  | `zcf update` | `zcf u` | Update workflow-related md files with backup |
474
556
  | `zcf ccu` | - | Run Claude Code usage analysis tool - [ccusage](https://github.com/ryoppippi/ccusage) |
475
557
  | `zcf ccr` | - | Open CCR (Claude Code Router) management menu |
558
+ | `zcf config-switch` | `zcf cs` | Codex API provider switching tool - Switch between official login and custom providers |
476
559
  | `zcf uninstall` | - | Interactive uninstall tool for Claude Code configurations and tools |
477
560
  | `zcf check-updates` | - | Check and update Claude Code, CCR and CCometixLine versions |
478
561
 
@@ -524,6 +607,12 @@ npx zcf u -c en # Using short option
524
607
 
525
608
  # Run Claude Code usage analysis tool (powered by ccusage)
526
609
  npx zcf ccu # Daily usage (default), or use: monthly, session, blocks
610
+
611
+ # Codex API provider switching (v3.0.0+ New)
612
+ npx zcf config-switch # Interactive provider selection
613
+ npx zcf cs # Using alias
614
+ npx zcf cs provider-name # Direct switch to specified provider
615
+ npx zcf cs --list # List all available providers
527
616
  ```
528
617
 
529
618
  ## 📁 Project Structure
@@ -712,11 +801,12 @@ A huge thank you to all our sponsors for their generous support!
712
801
  - Tc (first sponsor)
713
802
  - Argolinhas (first ko-fi sponsor ٩(•̤̀ᵕ•̤́๑))
714
803
  - r\*r (first anonymous sponsor🤣)
804
+ - \*\*康 (first KFC sponsor🍗)
715
805
  - 16°C coffee (My best friend🤪, offered Claude Code max $200 package)
716
806
 
717
807
  ## 📄 License
718
808
 
719
- MIT License
809
+ [MIT License](LICENSE)
720
810
 
721
811
  ---
722
812
 
@@ -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 };