zcf 2.12.12 → 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.
- package/README.md +92 -4
- package/dist/chunks/codex-config-switch.mjs +419 -0
- package/dist/chunks/codex-uninstaller.mjs +404 -0
- package/dist/chunks/simple-config.mjs +1862 -374
- package/dist/cli.mjs +1416 -194
- package/dist/i18n/locales/en/cli.json +4 -0
- package/dist/i18n/locales/en/codex.json +102 -0
- package/dist/i18n/locales/en/common.json +4 -1
- package/dist/i18n/locales/en/configuration.json +10 -4
- package/dist/i18n/locales/en/language.json +8 -2
- package/dist/i18n/locales/en/mcp.json +4 -3
- package/dist/i18n/locales/en/menu.json +22 -2
- package/dist/i18n/locales/en/uninstall.json +56 -0
- package/dist/i18n/locales/zh-CN/cli.json +4 -0
- package/dist/i18n/locales/zh-CN/codex.json +102 -0
- package/dist/i18n/locales/zh-CN/common.json +4 -1
- package/dist/i18n/locales/zh-CN/configuration.json +10 -4
- package/dist/i18n/locales/zh-CN/language.json +8 -2
- package/dist/i18n/locales/zh-CN/mcp.json +4 -3
- package/dist/i18n/locales/zh-CN/menu.json +22 -2
- package/dist/i18n/locales/zh-CN/uninstall.json +56 -0
- package/dist/index.d.mts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.mjs +2 -1
- package/dist/shared/zcf.DGjQxTq_.mjs +34 -0
- package/package.json +17 -13
- package/templates/{common → claude-code/common}/settings.json +2 -1
- package/templates/codex/common/config.toml +0 -0
- package/templates/codex/en/system-prompt/engineer-professional.md +87 -0
- package/templates/codex/en/system-prompt/laowang-engineer.md +126 -0
- package/templates/codex/en/system-prompt/nekomata-engineer.md +119 -0
- package/templates/codex/en/workflow/sixStep/prompts/workflow.md +211 -0
- package/templates/codex/zh-CN/system-prompt/engineer-professional.md +88 -0
- package/templates/codex/zh-CN/system-prompt/laowang-engineer.md +126 -0
- package/templates/codex/zh-CN/system-prompt/nekomata-engineer.md +119 -0
- package/templates/codex/zh-CN/workflow/sixStep/prompts/workflow.md +211 -0
- /package/templates/{CLAUDE.md → claude-code/CLAUDE.md} +0 -0
- /package/templates/{en → claude-code/en}/output-styles/engineer-professional.md +0 -0
- /package/templates/{en → claude-code/en}/output-styles/laowang-engineer.md +0 -0
- /package/templates/{en → claude-code/en}/output-styles/nekomata-engineer.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/bmad/commands/bmad-init.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/common/agents/get-current-datetime.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/common/agents/init-architect.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/common/commands/init-project.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/git/commands/git-cleanBranches.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/git/commands/git-commit.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/git/commands/git-rollback.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/git/commands/git-worktree.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/plan/agents/planner.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/plan/agents/ui-ux-designer.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/plan/commands/feat.md +0 -0
- /package/templates/{en → claude-code/en}/workflow/sixStep/commands/workflow.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/engineer-professional.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/laowang-engineer.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/output-styles/nekomata-engineer.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/bmad/commands/bmad-init.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/agents/get-current-datetime.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/agents/init-architect.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/common/commands/init-project.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-cleanBranches.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-commit.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-rollback.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/git/commands/git-worktree.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/agents/planner.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/agents/ui-ux-designer.md +0 -0
- /package/templates/{zh-CN → claude-code/zh-CN}/workflow/plan/commands/feat.md +0 -0
- /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
|
|
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
|

|
|
@@ -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
|
|
409
|
+
ZCF - Zero-Config Code Flow
|
|
331
410
|
|
|
332
411
|
? Select ZCF display language / 选择ZCF显示语言:
|
|
333
412
|
❯ 简体中文
|
|
@@ -350,7 +429,7 @@ Select function:
|
|
|
350
429
|
|
|
351
430
|
------------ ZCF ------------
|
|
352
431
|
0. Select display language / 更改显示语言 - Change ZCF interface language
|
|
353
|
-
-.
|
|
432
|
+
-. Uninstall - Remove Claude Code configurations and tools from system
|
|
354
433
|
+. Check updates - Check and update Claude Code, CCR and CCometixLine versions
|
|
355
434
|
Q. Exit
|
|
356
435
|
|
|
@@ -473,6 +552,8 @@ 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 |
|
|
556
|
+
| `zcf uninstall` | - | Interactive uninstall tool for Claude Code configurations and tools |
|
|
476
557
|
| `zcf check-updates` | - | Check and update Claude Code, CCR and CCometixLine versions |
|
|
477
558
|
|
|
478
559
|
#### Common Options
|
|
@@ -523,6 +604,12 @@ npx zcf u -c en # Using short option
|
|
|
523
604
|
|
|
524
605
|
# Run Claude Code usage analysis tool (powered by ccusage)
|
|
525
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
|
|
526
613
|
```
|
|
527
614
|
|
|
528
615
|
## 📁 Project Structure
|
|
@@ -711,11 +798,12 @@ A huge thank you to all our sponsors for their generous support!
|
|
|
711
798
|
- Tc (first sponsor)
|
|
712
799
|
- Argolinhas (first ko-fi sponsor ٩(•̤̀ᵕ•̤́๑))
|
|
713
800
|
- r\*r (first anonymous sponsor🤣)
|
|
801
|
+
- \*\*康 (first KFC sponsor🍗)
|
|
714
802
|
- 16°C coffee (My best friend🤪, offered Claude Code max $200 package)
|
|
715
803
|
|
|
716
804
|
## 📄 License
|
|
717
805
|
|
|
718
|
-
MIT License
|
|
806
|
+
[MIT License](LICENSE)
|
|
719
807
|
|
|
720
808
|
---
|
|
721
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 };
|