@minniexcode/codex-switch 0.0.12 → 0.1.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.
- package/README.AI.md +37 -6
- package/README.CN.md +45 -11
- package/README.md +45 -13
- package/dist/app/add-provider.js +22 -24
- package/dist/app/edit-provider.js +34 -55
- package/dist/app/get-current-profile.js +15 -3
- package/dist/app/get-status.js +11 -8
- package/dist/app/list-config-profiles.js +3 -1
- package/dist/app/list-providers.js +10 -4
- package/dist/app/remove-provider.js +52 -19
- package/dist/app/run-doctor.js +29 -28
- package/dist/app/setup-codex.js +3 -3
- package/dist/app/show-config.js +3 -1
- package/dist/app/switch-provider.js +36 -5
- package/dist/cli/output.js +36 -18
- package/dist/commands/handlers.js +2 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +35 -30
- package/dist/domain/config.js +250 -185
- package/dist/domain/providers.js +23 -0
- package/dist/domain/runtime-state.js +15 -15
- package/dist/domain/setup.js +3 -1
- package/dist/interaction/interactive.js +2 -2
- package/dist/runtime/codex-version.js +7 -0
- package/dist/storage/config-repo.js +6 -14
- package/docs/Design/codex-switch-v0.1.0-design.md +152 -0
- package/docs/Design/codex-switch-v0.1.1-design.md +33 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +217 -205
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/Tests/testing.md +31 -78
- package/docs/cli-usage.md +86 -27
- package/docs/codex-switch-command-design.md +649 -649
- package/docs/codex-switch-product-overview.md +81 -80
- package/docs/codex-switch-technical-architecture.md +1115 -1115
- package/package.json +51 -51
|
@@ -1,1115 +1,1115 @@
|
|
|
1
|
-
# codex-switch 技术架构设计
|
|
2
|
-
|
|
3
|
-
> 状态说明:这份文档是历史跨版本参考,不是当前 release contract。
|
|
4
|
-
> 当前事实源请改看 [`docs/cli-usage.md`](./cli-usage.md)、[`docs/PRD/codex-switch-prd-v0.
|
|
5
|
-
|
|
6
|
-
## 文档信息
|
|
7
|
-
|
|
8
|
-
- 文档类型:技术架构设计文档
|
|
9
|
-
- 适用范围:`codex-switch` MVP / CLI First
|
|
10
|
-
- 对应产品文档:
|
|
11
|
-
- [`codex-switch-product-overview.md`](./codex-switch-product-overview.md)
|
|
12
|
-
- [`codex-switch-product-research.md`](./codex-switch-product-research.md)
|
|
13
|
-
- [`PRD/codex-switch-prd.md`](./PRD/codex-switch-prd.md)
|
|
14
|
-
- [`codex-switch-command-design.md`](./codex-switch-command-design.md)
|
|
15
|
-
|
|
16
|
-
## 1. 文档目标
|
|
17
|
-
|
|
18
|
-
这份文档用于把 `codex-switch` 当前已经实现的技术架构、代码组织、核心流程和设计取舍完整沉淀下来,作为后续维护、扩展和协作开发的基线。
|
|
19
|
-
|
|
20
|
-
它回答的不是“产品要做什么”,而是下面这些工程问题:
|
|
21
|
-
|
|
22
|
-
- 当前代码是如何分层的
|
|
23
|
-
- 每一层分别负责什么、不负责什么
|
|
24
|
-
- CLI 命令是如何被解析、执行和输出的
|
|
25
|
-
- 配置文件、备份、回滚和错误码是如何落到代码里的
|
|
26
|
-
- 模块之间的依赖关系是什么
|
|
27
|
-
- 核心流程的时序是什么
|
|
28
|
-
- 测试如何组织
|
|
29
|
-
- 后续如果新增命令、扩展数据模型或做 GUI / MCP / daemon,应沿着什么方向演进
|
|
30
|
-
|
|
31
|
-
## 2. 设计输入与参考
|
|
32
|
-
|
|
33
|
-
### 2.1 当前项目输入
|
|
34
|
-
|
|
35
|
-
`codex-switch` 的架构设计主要由以下输入共同约束:
|
|
36
|
-
|
|
37
|
-
- 产品目标:本地优先、默认安全、对 AI 友好的 provider/profile 切换 CLI
|
|
38
|
-
- PRD 约束:固定命令面、统一 JSON 输出、固定错误码、写操作必须备份、失败必须回滚
|
|
39
|
-
- 技术路线:Node.js + TypeScript
|
|
40
|
-
- 使用对象:本地 `~/.codex/` 目录,而不是云端控制面
|
|
41
|
-
|
|
42
|
-
### 2.2 对 `codex-auth` 的借鉴
|
|
43
|
-
|
|
44
|
-
根据 `codex-auth` 当前公开 README 和命令文档入口,它是一个已经产品化的 CLI-first 工具,优势主要在下面几类工程实践:
|
|
45
|
-
|
|
46
|
-
- 命令家族清晰:`list` / `login` / `switch` / `remove` / `status` / `import` / `config`
|
|
47
|
-
- npm 全局分发路径成熟,同时支持 `npx`
|
|
48
|
-
- 明确区分交互式能力和自动化可调用能力
|
|
49
|
-
- 文档按命令组织,降低学习和调用成本
|
|
50
|
-
|
|
51
|
-
`codex-switch` 借鉴的是这些“CLI 产品化方法”,但没有复制它的多账号体系、远程 usage 刷新、后台 auto-switch 等更重的职责。
|
|
52
|
-
|
|
53
|
-
### 2.3 对 `cc-switch` 的借鉴
|
|
54
|
-
|
|
55
|
-
根据 `cc-switch` 当前公开 README,它是一个跨平台的桌面 All-in-One 管理器,目标覆盖:
|
|
56
|
-
|
|
57
|
-
- Claude Code
|
|
58
|
-
- Codex
|
|
59
|
-
- Gemini CLI
|
|
60
|
-
- OpenCode
|
|
61
|
-
- OpenClaw
|
|
62
|
-
- Hermes Agent
|
|
63
|
-
|
|
64
|
-
其公开设计信息显示:
|
|
65
|
-
|
|
66
|
-
- 技术栈是 `React + TypeScript + Tauri + Rust`
|
|
67
|
-
- 前端与后端通过 Tauri IPC 通信
|
|
68
|
-
- 后端采用明确的分层:`Commands -> Services -> Database/Models`
|
|
69
|
-
- 数据层采用 SQLite 作为单一事实源,并配合 JSON 做设备级配置
|
|
70
|
-
- 具备桌面 GUI、系统托盘、云同步、用量统计、Session Manager、Proxy 等更重能力
|
|
71
|
-
|
|
72
|
-
对 `codex-switch` 的借鉴点主要有:
|
|
73
|
-
|
|
74
|
-
- 清晰的分层表达
|
|
75
|
-
- 文档中直接公开架构总览和项目结构
|
|
76
|
-
- 命令/服务/数据层职责拆分明确
|
|
77
|
-
- 把“管理器”能力和“切换器”能力区分成不同模块
|
|
78
|
-
|
|
79
|
-
不直接照搬的点也很明确:
|
|
80
|
-
|
|
81
|
-
- `cc-switch` 是桌面应用优先,`codex-switch` 是 CLI-first
|
|
82
|
-
- `cc-switch` 的职责明显更广,包含云同步、会话管理、代理模式、统计面板等
|
|
83
|
-
- `cc-switch` 的 SQLite + GUI 方案适合 All-in-One manager,不适合当前这个轻量本地切换工具的 MVP
|
|
84
|
-
|
|
85
|
-
因此,`codex-switch` 在架构上更适合借鉴它的“分层清晰度”和“工程表达方式”,而不是直接复制它的技术栈和能力范围。
|
|
86
|
-
|
|
87
|
-
## 3. 架构总览
|
|
88
|
-
|
|
89
|
-
### 3.1 核心设计原则
|
|
90
|
-
|
|
91
|
-
当前实现严格围绕下面五个原则组织:
|
|
92
|
-
|
|
93
|
-
- `CLI First`
|
|
94
|
-
- 核心能力全部通过命令完成
|
|
95
|
-
- `Local First`
|
|
96
|
-
- 主对象是本地文件,不依赖远程控制面
|
|
97
|
-
- `Safe by Default`
|
|
98
|
-
- 所有写操作先备份,失败后回滚
|
|
99
|
-
- `AI Friendly`
|
|
100
|
-
- 关键命令支持统一 JSON envelope
|
|
101
|
-
- `Low Coupling`
|
|
102
|
-
- 参数解析、业务编排、文件访问、错误模型彼此解耦
|
|
103
|
-
- `Split State Model`
|
|
104
|
-
- `providers.json` 是管理态事实源,`config.toml` 是运行态路由文件,`auth.json` 是 direct provider 的认证投影
|
|
105
|
-
- `Lightweight Transactions`
|
|
106
|
-
- 单次写操作要有锁、备份、回滚边界
|
|
107
|
-
|
|
108
|
-
### 3.2 分层结构
|
|
109
|
-
|
|
110
|
-
当前代码采用 4 层结构:
|
|
111
|
-
|
|
112
|
-
```text
|
|
113
|
-
CLI 层
|
|
114
|
-
负责参数解析、命令分发、输出渲染、进程退出语义
|
|
115
|
-
|
|
116
|
-
Application 层
|
|
117
|
-
负责单个用例的编排流程,例如 switch / import / rollback
|
|
118
|
-
|
|
119
|
-
Domain 层
|
|
120
|
-
负责数据模型、规则校验、错误码和纯函数逻辑
|
|
121
|
-
|
|
122
|
-
Infrastructure 层
|
|
123
|
-
负责文件系统、路径、备份持久化、子进程调用
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
这四层的边界是当前架构最重要的维护基础。
|
|
127
|
-
|
|
128
|
-
当前实现还明确区分三类状态对象:
|
|
129
|
-
|
|
130
|
-
- 管理态单一事实源:`providers.json`
|
|
131
|
-
- 运行态路由:`config.toml`
|
|
132
|
-
- 当前 active direct provider 的认证投影:`auth.json`
|
|
133
|
-
- 回滚态:`backups/latest.json` 和对应 manifest
|
|
134
|
-
|
|
135
|
-
这意味着未来即使引入 GUI / MCP / HTTP 适配层,核心同步目标仍然是 runtime files,而不是把 runtime files 本身当成长期管理数据库。
|
|
136
|
-
|
|
137
|
-
`0.0.6` 的实现边界还需要区分“逻辑主层”和“兼容层”:
|
|
138
|
-
|
|
139
|
-
- 逻辑主层已经迁移到 `src/commands/`、`src/interaction/`、`src/storage/`、`src/runtime/`
|
|
140
|
-
- `src/cli/` 和 `src/infra/` 当前主要承担兼容 re-export 与入口收敛职责
|
|
141
|
-
- 这意味着 `0.0.6` 的完成标准是“边界和契约已经统一”,而不是“所有旧路径文件都物理删除”
|
|
142
|
-
- 后续版本可以在兼容窗口结束后继续删除旧 facade,但这不属于 `0.0.6` 的必须交付范围
|
|
143
|
-
|
|
144
|
-
### 3.3 模块依赖图
|
|
145
|
-
|
|
146
|
-
当前代码依赖关系可以抽象为:
|
|
147
|
-
|
|
148
|
-
```text
|
|
149
|
-
+----------------------+
|
|
150
|
-
| CLI Layer |
|
|
151
|
-
| cli.ts / cli/* |
|
|
152
|
-
+----------+-----------+
|
|
153
|
-
|
|
|
154
|
-
v
|
|
155
|
-
+----------------------+
|
|
156
|
-
| Application Layer |
|
|
157
|
-
| app/* use cases |
|
|
158
|
-
+----------+-----------+
|
|
159
|
-
|
|
|
160
|
-
+--------------+--------------+
|
|
161
|
-
| |
|
|
162
|
-
v v
|
|
163
|
-
+----------------------+ +----------------------+
|
|
164
|
-
| Domain Layer | | Infrastructure Layer |
|
|
165
|
-
| rules / schema / err | | fs / path / process |
|
|
166
|
-
+----------------------+ +----------------------+
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
依赖方向约束:
|
|
170
|
-
|
|
171
|
-
- `cli` 可以依赖 `app`、`domain`、`infra`
|
|
172
|
-
- `app` 可以依赖 `domain`、`infra`
|
|
173
|
-
- `infra` 可以依赖 `domain`
|
|
174
|
-
- `domain` 不依赖其他项目层
|
|
175
|
-
|
|
176
|
-
这保证领域规则不会反向耦合到 CLI 或具体文件系统实现。
|
|
177
|
-
|
|
178
|
-
### 3.4 命令调用生命周期
|
|
179
|
-
|
|
180
|
-
所有命令大体遵循同一个生命周期:
|
|
181
|
-
|
|
182
|
-
```text
|
|
183
|
-
argv
|
|
184
|
-
-> parseArgs
|
|
185
|
-
-> create CommandContext
|
|
186
|
-
-> executeCommand
|
|
187
|
-
-> app use case
|
|
188
|
-
-> infra/domain collaboration
|
|
189
|
-
-> CommandResult / CliErrorShape
|
|
190
|
-
-> output renderer
|
|
191
|
-
-> stdout/stderr + exitCode
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
这条链路的工程价值在于:
|
|
195
|
-
|
|
196
|
-
- 参数解析和业务行为分离
|
|
197
|
-
- 成功输出和失败输出统一渲染
|
|
198
|
-
- 业务层不直接操作终端
|
|
199
|
-
- CLI 层测试可以只验证 envelope 和渲染,而不重测业务细节
|
|
200
|
-
|
|
201
|
-
## 4. 代码目录与职责
|
|
202
|
-
|
|
203
|
-
当前目录结构如下:
|
|
204
|
-
|
|
205
|
-
```text
|
|
206
|
-
src/
|
|
207
|
-
cli.ts
|
|
208
|
-
commands/
|
|
209
|
-
interaction/
|
|
210
|
-
app/
|
|
211
|
-
cli/
|
|
212
|
-
domain/
|
|
213
|
-
runtime/
|
|
214
|
-
infra/
|
|
215
|
-
storage/
|
|
216
|
-
|
|
217
|
-
tests/
|
|
218
|
-
app.spec.js
|
|
219
|
-
cli.spec.js
|
|
220
|
-
domain.spec.js
|
|
221
|
-
helpers.js
|
|
222
|
-
run-tests.js
|
|
223
|
-
|
|
224
|
-
scripts/
|
|
225
|
-
build.cjs
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 4.1 `src/cli.ts`
|
|
229
|
-
|
|
230
|
-
入口文件,只做三件事:
|
|
231
|
-
|
|
232
|
-
- 接收 `process.argv`
|
|
233
|
-
- 调用参数解析
|
|
234
|
-
- 根据命令分发到对应 use case,并调用输出渲染
|
|
235
|
-
|
|
236
|
-
它不直接做文件读写,不直接写业务逻辑,也不直接实现备份或校验规则。
|
|
237
|
-
|
|
238
|
-
### 4.2 `src/commands/`
|
|
239
|
-
|
|
240
|
-
这一层是 `0.0.6` 新增的命令表面层,负责把“公开 CLI 形态”收敛为单一 registry。
|
|
241
|
-
|
|
242
|
-
它承担的职责是:
|
|
243
|
-
|
|
244
|
-
- 定义每个命令的公开 token 形态,例如 `config show`、`config list-profiles`、`backups list`
|
|
245
|
-
- 统一保存 `summary`、`usage`、`details`、`examples`
|
|
246
|
-
- 绑定 command handler,供 dispatch 直接执行
|
|
247
|
-
- 让 help、解析和 dispatch 共享同一份事实源
|
|
248
|
-
|
|
249
|
-
它不负责:
|
|
250
|
-
|
|
251
|
-
- 具体文件读写
|
|
252
|
-
- prompt 交互细节
|
|
253
|
-
- human output 渲染
|
|
254
|
-
|
|
255
|
-
### 4.3 `src/interaction/`
|
|
256
|
-
|
|
257
|
-
这一层是 `0.0.6` 中显式抽出的交互层,负责所有 CLI 级 prompt 组合逻辑。
|
|
258
|
-
|
|
259
|
-
它承担的职责是:
|
|
260
|
-
|
|
261
|
-
- 判断哪些路径允许交互
|
|
262
|
-
- 组合 provider 选择、确认、rollback 预览等交互动作
|
|
263
|
-
- 组织 add/edit/setup 中的渐进式输入收集
|
|
264
|
-
|
|
265
|
-
它不负责:
|
|
266
|
-
|
|
267
|
-
- 业务状态变更
|
|
268
|
-
- 文件系统写入
|
|
269
|
-
- 运行时探测
|
|
270
|
-
|
|
271
|
-
`setup` 是这一层边界最强的命令之一。在 `0.0.6` 中,它的 adopt profile 选择和 provider 详情输入仍然是交互式 contract,因此非交互路径会显式失败,而不是隐式进入空输入分支。
|
|
272
|
-
|
|
273
|
-
### 4.4 `src/storage/`
|
|
274
|
-
|
|
275
|
-
这一层是 `0.0.6` 的文件和状态访问层,负责把以前分散在 `infra/` 的能力收口成稳定存储边界。
|
|
276
|
-
|
|
277
|
-
它承担的职责是:
|
|
278
|
-
|
|
279
|
-
- `config.toml` / `providers.json` / backup manifest / lock file 的读写
|
|
280
|
-
- Codex home 目录路径展开
|
|
281
|
-
- 原子写入、备份、回滚、锁定
|
|
282
|
-
|
|
283
|
-
`src/infra/` 在当前版本主要保留兼容 re-export,以便逐步迁移,不再作为新的业务入口继续扩张。
|
|
284
|
-
|
|
285
|
-
### 4.5 `src/runtime/`
|
|
286
|
-
|
|
287
|
-
这一层是 `0.0.6` 新增的运行时边界,负责本地 Codex CLI 的外部依赖探测和登录调用。
|
|
288
|
-
|
|
289
|
-
它承担的职责是:
|
|
290
|
-
|
|
291
|
-
- Codex 可用性检查
|
|
292
|
-
- Codex 版本检查
|
|
293
|
-
- Codex login 调用
|
|
294
|
-
- 未来可扩展为第三方 runtime adapter 的能力边界
|
|
295
|
-
|
|
296
|
-
### 4.6 `src/cli/`
|
|
297
|
-
|
|
298
|
-
#### `src/cli/args.ts`
|
|
299
|
-
|
|
300
|
-
负责:
|
|
301
|
-
|
|
302
|
-
- 解析全局参数 `--json`、`--codex-dir`
|
|
303
|
-
- 解析命令名、位置参数和命令选项
|
|
304
|
-
- 提供 `hasFlag` / `getSingleOption` 这种小型解析辅助函数
|
|
305
|
-
|
|
306
|
-
不负责:
|
|
307
|
-
|
|
308
|
-
- 命令实际行为
|
|
309
|
-
- 文件访问
|
|
310
|
-
- 输出格式化
|
|
311
|
-
|
|
312
|
-
#### `src/cli/prompt.ts`
|
|
313
|
-
|
|
314
|
-
负责:
|
|
315
|
-
|
|
316
|
-
- 对 `inquirer` 做轻量封装
|
|
317
|
-
- 提供 `input` / `password` / `select` / `confirm` 四类 typed 交互能力
|
|
318
|
-
- 把 prompt 取消统一转换成稳定 CLI 错误
|
|
319
|
-
|
|
320
|
-
不负责:
|
|
321
|
-
|
|
322
|
-
- 业务判断某个命令是否应该进入交互
|
|
323
|
-
- 直接读写 provider/config 文件
|
|
324
|
-
|
|
325
|
-
#### `src/cli/interactive.ts`
|
|
326
|
-
|
|
327
|
-
负责:
|
|
328
|
-
|
|
329
|
-
- 统一判定何时允许交互
|
|
330
|
-
- 组合 provider 选择、危险确认、rollback 摘要展示等 CLI 级辅助逻辑
|
|
331
|
-
- 保持命令分支对交互的接入方式一致
|
|
332
|
-
|
|
333
|
-
#### `src/cli/output.ts`
|
|
334
|
-
|
|
335
|
-
负责:
|
|
336
|
-
|
|
337
|
-
- 渲染统一 JSON envelope
|
|
338
|
-
- 渲染默认的人类可读输出
|
|
339
|
-
- 渲染失败输出和错误码
|
|
340
|
-
|
|
341
|
-
当前输出层还额外抽出了:
|
|
342
|
-
|
|
343
|
-
- `renderSuccess`
|
|
344
|
-
- `renderFailure`
|
|
345
|
-
|
|
346
|
-
这两个纯渲染入口的作用是让 CLI 层本身也能被测试,而不依赖真实子进程。
|
|
347
|
-
|
|
348
|
-
### 4.7 `src/app/`
|
|
349
|
-
|
|
350
|
-
这一层是应用服务 / 用例编排层。每个文件基本对应一个命令或一个用例:
|
|
351
|
-
|
|
352
|
-
- `list-providers.ts`
|
|
353
|
-
- `get-current-profile.ts`
|
|
354
|
-
- `get-status.ts`
|
|
355
|
-
- `switch-provider.ts`
|
|
356
|
-
- `import-providers.ts`
|
|
357
|
-
- `export-providers.ts`
|
|
358
|
-
- `add-provider.ts`
|
|
359
|
-
- `remove-provider.ts`
|
|
360
|
-
- `run-doctor.ts`
|
|
361
|
-
- `rollback-latest.ts`
|
|
362
|
-
- `run-mutation.ts`
|
|
363
|
-
|
|
364
|
-
这一层的职责是:
|
|
365
|
-
|
|
366
|
-
- 组合多个 infra 能力
|
|
367
|
-
- 串起校验、备份、写入、回滚、输出数据准备
|
|
368
|
-
- 为所有写操作提供统一 orchestration contract
|
|
369
|
-
- 保证命令行为满足 PRD 语义
|
|
370
|
-
|
|
371
|
-
它不关心:
|
|
372
|
-
|
|
373
|
-
- 终端输出长什么样
|
|
374
|
-
- `stdout` / `stderr` 怎么写
|
|
375
|
-
- argv 怎么解析
|
|
376
|
-
|
|
377
|
-
### 4.8 `src/domain/`
|
|
378
|
-
|
|
379
|
-
#### `errors.ts`
|
|
380
|
-
|
|
381
|
-
定义当前 MVP 固定错误码:
|
|
382
|
-
|
|
383
|
-
- `CONFIG_NOT_FOUND`
|
|
384
|
-
- `PROVIDERS_NOT_FOUND`
|
|
385
|
-
- `PROVIDERS_PARSE_ERROR`
|
|
386
|
-
- `PROVIDER_NOT_FOUND`
|
|
387
|
-
- `PROFILE_NOT_FOUND`
|
|
388
|
-
- `BACKUP_FAILED`
|
|
389
|
-
- `CODEX_LOGIN_FAILED`
|
|
390
|
-
- `ROLLBACK_FAILED`
|
|
391
|
-
- `LOCK_CONFLICT`
|
|
392
|
-
- `LIVE_STATE_DRIFT`
|
|
393
|
-
- `INVALID_IMPORT_FILE`
|
|
394
|
-
|
|
395
|
-
并提供:
|
|
396
|
-
|
|
397
|
-
- `cliError`
|
|
398
|
-
- `normalizeError`
|
|
399
|
-
|
|
400
|
-
这是整个错误语义统一的基础。
|
|
401
|
-
|
|
402
|
-
#### `providers.ts`
|
|
403
|
-
|
|
404
|
-
负责 `providers.json` 的领域模型和规则:
|
|
405
|
-
|
|
406
|
-
- `ProviderRecord`
|
|
407
|
-
- `ProvidersFile`
|
|
408
|
-
- `validateProvidersShape`
|
|
409
|
-
- `cleanProviderRecord`
|
|
410
|
-
- `sortProviders`
|
|
411
|
-
- `findProviderByProfile`
|
|
412
|
-
|
|
413
|
-
这里是当前 provider 数据契约的唯一可信实现位置。
|
|
414
|
-
|
|
415
|
-
#### `config.ts`
|
|
416
|
-
|
|
417
|
-
负责 `config.toml` 相关的纯逻辑:
|
|
418
|
-
|
|
419
|
-
- 读取顶层 `profile`
|
|
420
|
-
- 提取 `[profiles.xxx]` 名称
|
|
421
|
-
- 替换顶层 `profile`
|
|
422
|
-
|
|
423
|
-
它只做字符串级和结构级处理,不做真实文件 IO。
|
|
424
|
-
|
|
425
|
-
#### `backup.ts`
|
|
426
|
-
|
|
427
|
-
定义备份 manifest 的结构:
|
|
428
|
-
|
|
429
|
-
- `FileBackupEntry`
|
|
430
|
-
- `BackupManifest`
|
|
431
|
-
|
|
432
|
-
### 4.5 `src/infra/`
|
|
433
|
-
|
|
434
|
-
#### `codex-paths.ts`
|
|
435
|
-
|
|
436
|
-
负责路径集中管理:
|
|
437
|
-
|
|
438
|
-
- `resolveCodexDir`
|
|
439
|
-
- `createCodexPaths`
|
|
440
|
-
|
|
441
|
-
它把下面这些路径收敛为一个统一对象:
|
|
442
|
-
|
|
443
|
-
- `config.toml`
|
|
444
|
-
- `providers.json`
|
|
445
|
-
- `auth.json`
|
|
446
|
-
- `backups/`
|
|
447
|
-
- `backups/latest.json`
|
|
448
|
-
|
|
449
|
-
这样上层命令不再自己拼路径。
|
|
450
|
-
|
|
451
|
-
#### `providers-repo.ts`
|
|
452
|
-
|
|
453
|
-
负责 `providers.json` 的文件级读写:
|
|
454
|
-
|
|
455
|
-
- 读取文件
|
|
456
|
-
- 解析 JSON
|
|
457
|
-
- 套用 domain schema 校验
|
|
458
|
-
- 序列化写回
|
|
459
|
-
|
|
460
|
-
#### `config-repo.ts`
|
|
461
|
-
|
|
462
|
-
负责 `config.toml` 的文件级访问:
|
|
463
|
-
|
|
464
|
-
- 读取配置文件
|
|
465
|
-
- 获取当前 profile
|
|
466
|
-
- 校验 profile 是否存在
|
|
467
|
-
- 更新顶层 profile
|
|
468
|
-
|
|
469
|
-
#### `backup-repo.ts`
|
|
470
|
-
|
|
471
|
-
负责备份和恢复落盘:
|
|
472
|
-
|
|
473
|
-
- 创建按时间戳命名的备份目录
|
|
474
|
-
- 保存 `manifest.json`
|
|
475
|
-
- 记录 `latest.json`
|
|
476
|
-
- 依据 manifest 恢复文件
|
|
477
|
-
|
|
478
|
-
#### `lock-repo.ts`
|
|
479
|
-
|
|
480
|
-
负责轻量并发控制:
|
|
481
|
-
|
|
482
|
-
- 在 `~/.codex/.codex-switch.lock` 上建立单操作锁
|
|
483
|
-
- 并发写入时快速失败
|
|
484
|
-
- 为 CLI、脚本和 AI agent 并发调用提供最小安全边界
|
|
485
|
-
|
|
486
|
-
#### `codex-cli.ts`
|
|
487
|
-
|
|
488
|
-
负责调用真实 `codex` CLI:
|
|
489
|
-
|
|
490
|
-
- `runCodexLogin`
|
|
491
|
-
- `checkCodexAvailable`
|
|
492
|
-
|
|
493
|
-
这里还提供了测试友好的可注入实现:
|
|
494
|
-
|
|
495
|
-
- `setCodexSpawnImplementation`
|
|
496
|
-
- `resetCodexSpawnImplementation`
|
|
497
|
-
|
|
498
|
-
这样应用层测试可以验证 `switch` 和 `doctor` 的行为,而不用依赖本机一定装好了真正的 `codex`。
|
|
499
|
-
|
|
500
|
-
#### `fs-utils.ts`
|
|
501
|
-
|
|
502
|
-
负责通用文件工具:
|
|
503
|
-
|
|
504
|
-
- `ensureDir`
|
|
505
|
-
- `writeTextFileAtomic`
|
|
506
|
-
- `readRequiredFile`
|
|
507
|
-
- 输出细节格式化
|
|
508
|
-
|
|
509
|
-
## 5. 关键数据模型
|
|
510
|
-
|
|
511
|
-
### 5.1 `providers.json`
|
|
512
|
-
|
|
513
|
-
当前系统围绕下面这个结构工作:
|
|
514
|
-
|
|
515
|
-
```json
|
|
516
|
-
{
|
|
517
|
-
"providers": {
|
|
518
|
-
"packycode": {
|
|
519
|
-
"profile": "packycode",
|
|
520
|
-
"apiKey": "sk-xxx",
|
|
521
|
-
"baseUrl": "https://example.com/v1",
|
|
522
|
-
"note": "primary free model route",
|
|
523
|
-
"tags": ["free", "daily"]
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
#### 字段语义
|
|
530
|
-
|
|
531
|
-
- `profile`
|
|
532
|
-
- 必填
|
|
533
|
-
- 必须映射到 `config.toml` 中已存在的 profile
|
|
534
|
-
- `apiKey`
|
|
535
|
-
- 必填
|
|
536
|
-
- 用于 `codex login --with-api-key`
|
|
537
|
-
- `baseUrl`
|
|
538
|
-
- 选填
|
|
539
|
-
- 当前版本只存储在 `providers.json`;`config show` 中展示的 runtime `baseUrl` 由 `model_provider -> model_providers.*.base_url` 解析
|
|
540
|
-
- `note`
|
|
541
|
-
- 选填
|
|
542
|
-
- 面向人类和 AI 的说明字段
|
|
543
|
-
- `tags`
|
|
544
|
-
- 选填
|
|
545
|
-
- 预留给未来筛选与推荐
|
|
546
|
-
|
|
547
|
-
从建模角度看,`providers.json` 是管理态 SSOT。未来如果支持 backfill,也应是显式命令把受控的 live 信息反向写回 registry,而不是默认把 runtime file 当成事实源。
|
|
548
|
-
|
|
549
|
-
### 5.2 `config.toml`
|
|
550
|
-
|
|
551
|
-
当前实现只关心两类信息:
|
|
552
|
-
|
|
553
|
-
- 顶层 `profile = "..."`
|
|
554
|
-
- `[profiles.xxx]` 段是否存在
|
|
555
|
-
|
|
556
|
-
当前版本不负责:
|
|
557
|
-
|
|
558
|
-
- 创建新的 profile section
|
|
559
|
-
- 深度解析 profile 内部更多字段
|
|
560
|
-
- 管理 base URL 或模型等 profile 内容
|
|
561
|
-
|
|
562
|
-
它在当前架构里是 direct provider 的认证投影文件,不承担 provider 选择职责,也不作为 provider registry 的事实源。
|
|
563
|
-
|
|
564
|
-
### 5.3 `auth.json`
|
|
565
|
-
|
|
566
|
-
当前实现不直接建模其内部字段,但它有明确架构角色:
|
|
567
|
-
|
|
568
|
-
- 属于 direct provider 的认证投影
|
|
569
|
-
- 在存在时纳入备份与回滚
|
|
570
|
-
- 不是 provider registry 的事实源
|
|
571
|
-
|
|
572
|
-
### 5.4 备份 manifest
|
|
573
|
-
|
|
574
|
-
当前备份 manifest 记录:
|
|
575
|
-
|
|
576
|
-
- 备份创建时间
|
|
577
|
-
- 备份原因
|
|
578
|
-
- 根目录
|
|
579
|
-
- 备份目录
|
|
580
|
-
- 每个文件的相对路径
|
|
581
|
-
- 原文件是否存在
|
|
582
|
-
- 备份文件名
|
|
583
|
-
|
|
584
|
-
这使得回滚不依赖目录猜测,而是依赖显式清单恢复。
|
|
585
|
-
|
|
586
|
-
## 6. 核心命令工作流
|
|
587
|
-
|
|
588
|
-
### 6.1 `list`
|
|
589
|
-
|
|
590
|
-
流程:
|
|
591
|
-
|
|
592
|
-
1. CLI 解析参数
|
|
593
|
-
2. `listProviders` 调用 `readProvidersFile`
|
|
594
|
-
3. 按名称排序并返回 provider 列表
|
|
595
|
-
4. 输出层渲染 JSON 或人类可读格式
|
|
596
|
-
|
|
597
|
-
失败点:
|
|
598
|
-
|
|
599
|
-
- 文件不存在:`PROVIDERS_NOT_FOUND`
|
|
600
|
-
- JSON 结构不合法:`PROVIDERS_PARSE_ERROR`
|
|
601
|
-
|
|
602
|
-
### 6.2 `current`
|
|
603
|
-
|
|
604
|
-
流程:
|
|
605
|
-
|
|
606
|
-
1. 读取 `config.toml`
|
|
607
|
-
2. 解析顶层 `profile`
|
|
608
|
-
3. 输出当前 profile
|
|
609
|
-
|
|
610
|
-
失败点:
|
|
611
|
-
|
|
612
|
-
- 文件不存在:`CONFIG_NOT_FOUND`
|
|
613
|
-
- 没有顶层 profile:`PROFILE_NOT_FOUND`
|
|
614
|
-
|
|
615
|
-
### 6.3 `switch <provider>`
|
|
616
|
-
|
|
617
|
-
这是当前最关键的命令,也是架构设计的中心流程。
|
|
618
|
-
|
|
619
|
-
流程如下:
|
|
620
|
-
|
|
621
|
-
1. CLI 层在 TTY 且 `<provider>` 缺失时,先用 selector 选 provider
|
|
622
|
-
2. 获取单操作写锁
|
|
623
|
-
3. 读取并解析 `providers.json`
|
|
624
|
-
4. 校验目标 provider 是否存在
|
|
625
|
-
5. 读取 `config.toml`
|
|
626
|
-
6. 校验 provider 对应的 `profile` 在配置中存在
|
|
627
|
-
7. 创建备份:
|
|
628
|
-
- `config.toml`
|
|
629
|
-
- `auth.json` 仅在历史备份清单已包含时由 rollback 兼容恢复
|
|
630
|
-
8. 更新顶层 `profile`
|
|
631
|
-
9. 如果未传 `--no-login`,执行 `codex login --with-api-key`
|
|
632
|
-
10. 成功后把这次备份记录为 `latest.json`
|
|
633
|
-
11. 若任何步骤失败,按 manifest 回滚
|
|
634
|
-
12. 释放写锁
|
|
635
|
-
|
|
636
|
-
#### 为什么 `switch` 必须由应用层编排
|
|
637
|
-
|
|
638
|
-
因为它同时跨越了:
|
|
639
|
-
|
|
640
|
-
- provider 仓储
|
|
641
|
-
- config 仓储
|
|
642
|
-
- 备份系统
|
|
643
|
-
- 外部 CLI 调用
|
|
644
|
-
- 失败回滚语义
|
|
645
|
-
|
|
646
|
-
这是典型的应用层编排问题,不能直接塞回 CLI 文件里。
|
|
647
|
-
|
|
648
|
-
#### `switch` 时序图
|
|
649
|
-
|
|
650
|
-
```text
|
|
651
|
-
User/Agent
|
|
652
|
-
-> CLI parseArgs
|
|
653
|
-
-> executeCommand("switch")
|
|
654
|
-
-> app/switchProvider
|
|
655
|
-
-> app/runMutation
|
|
656
|
-
-> providers-repo.readProvidersFile
|
|
657
|
-
-> config-repo.ensureProfileExists
|
|
658
|
-
-> backup-repo.createBackup
|
|
659
|
-
-> config-repo.updateTopLevelProfile
|
|
660
|
-
-> codex-cli.runCodexLogin (unless --no-login)
|
|
661
|
-
-> backup-repo.saveLatestManifest
|
|
662
|
-
-> CLI output renderer
|
|
663
|
-
|
|
664
|
-
failure path:
|
|
665
|
-
-> codex-cli.runCodexLogin throws
|
|
666
|
-
-> backup-repo.restoreManifest
|
|
667
|
-
-> app throws normalized error with rollbackApplied=true
|
|
668
|
-
-> CLI output renderer
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
#### `switch` 事务边界
|
|
672
|
-
|
|
673
|
-
从实现角度看,`switch` 的“事务”并不是数据库事务,而是一个文件系统级补偿事务:
|
|
674
|
-
|
|
675
|
-
- 事务开始点:
|
|
676
|
-
- 备份成功生成 manifest
|
|
677
|
-
- 事务提交点:
|
|
678
|
-
- 配置更新成功且登录成功
|
|
679
|
-
- `latest.json` 被更新为本次备份
|
|
680
|
-
- 事务补偿点:
|
|
681
|
-
- 发生异常时按 manifest 恢复文件
|
|
682
|
-
|
|
683
|
-
这是当前版本最关键的安全设计。
|
|
684
|
-
|
|
685
|
-
### 6.4 `status`
|
|
686
|
-
|
|
687
|
-
这是浅状态概览,不是深诊断。
|
|
688
|
-
|
|
689
|
-
当前输出包含:
|
|
690
|
-
|
|
691
|
-
- `codexDir`
|
|
692
|
-
- `configExists`
|
|
693
|
-
- `providersExists`
|
|
694
|
-
- `currentProfile`
|
|
695
|
-
- `currentProfileMapped`
|
|
696
|
-
- `provider`
|
|
697
|
-
- `liveState`
|
|
698
|
-
- `storage`
|
|
699
|
-
|
|
700
|
-
### 6.5 `doctor`
|
|
701
|
-
|
|
702
|
-
这是显式问题检测。
|
|
703
|
-
|
|
704
|
-
当前至少检查:
|
|
705
|
-
|
|
706
|
-
- `config.toml` 是否存在
|
|
707
|
-
- `providers.json` 是否存在
|
|
708
|
-
- `providers.json` 是否可解析
|
|
709
|
-
- provider 映射的 profile 是否存在
|
|
710
|
-
- 当前 live profile 是否已经脱离 `providers.json` 映射
|
|
711
|
-
- `codex` CLI 是否可执行
|
|
712
|
-
|
|
713
|
-
当前实现里,如果 `codex` 不可执行,会返回:
|
|
714
|
-
|
|
715
|
-
- `CODEX_LOGIN_FAILED`
|
|
716
|
-
|
|
717
|
-
从语义上看这足够满足 PRD 的“CLI 是否可执行”检查要求,但后续若要更精细,可以单独引入一个更专门的 `CODEX_CLI_NOT_FOUND` 类型。
|
|
718
|
-
|
|
719
|
-
### 6.6 `import`
|
|
720
|
-
|
|
721
|
-
流程:
|
|
722
|
-
|
|
723
|
-
1. CLI 层保留显式路径参数
|
|
724
|
-
2. TTY 中写入前先确认
|
|
725
|
-
3. 读取外部文件
|
|
726
|
-
4. 校验 JSON 和 schema
|
|
727
|
-
5. 备份当前 `providers.json`
|
|
728
|
-
6. 整体替换写入
|
|
729
|
-
7. 写失败则恢复旧文件
|
|
730
|
-
|
|
731
|
-
当前明确不支持 merge import。
|
|
732
|
-
|
|
733
|
-
### 6.7 `export`
|
|
734
|
-
|
|
735
|
-
流程:
|
|
736
|
-
|
|
737
|
-
1. 读取当前 `providers.json`
|
|
738
|
-
2. 检查目标文件是否存在
|
|
739
|
-
3. TTY 中若文件已存在且未传 `--force`,先确认是否覆盖
|
|
740
|
-
4. 默认拒绝覆盖
|
|
741
|
-
5. 传入 `--force` 或确认覆盖时允许写入
|
|
742
|
-
|
|
743
|
-
### 6.8 `add`
|
|
744
|
-
|
|
745
|
-
流程:
|
|
746
|
-
|
|
747
|
-
1. CLI 层仅在缺失必填字段且当前是 TTY 时进入交互
|
|
748
|
-
2. provider 名在 prompt 阶段尽早做重名检查
|
|
749
|
-
3. profile 优先从 `config.toml` 里解析出的现有 profile 列表选择
|
|
750
|
-
4. apiKey 通过隐藏输入采集并二次确认
|
|
751
|
-
5. 读取现有 provider 集合
|
|
752
|
-
6. 校验重名
|
|
753
|
-
7. 备份旧文件
|
|
754
|
-
8. 追加一条 provider 记录
|
|
755
|
-
9. 写回 `providers.json`
|
|
756
|
-
|
|
757
|
-
### 6.9 `remove`
|
|
758
|
-
|
|
759
|
-
流程:
|
|
760
|
-
|
|
761
|
-
1. CLI 层在 TTY 且缺少 provider 时可先选择 provider
|
|
762
|
-
2. CLI 层在 TTY 中始终做删除确认
|
|
763
|
-
3. 非 TTY 或 `--json` 场景继续要求显式 `--force`
|
|
764
|
-
4. 校验 provider 存在
|
|
765
|
-
5. 备份 `providers.json`
|
|
766
|
-
6. 删除目标 provider
|
|
767
|
-
7. 写回
|
|
768
|
-
|
|
769
|
-
### 6.10 `rollback`
|
|
770
|
-
|
|
771
|
-
流程:
|
|
772
|
-
|
|
773
|
-
1. TTY 中先读取 `backups/latest.json`
|
|
774
|
-
2. 展示备份目录和待恢复文件摘要
|
|
775
|
-
3. 请求确认
|
|
776
|
-
4. 加载最近一次 manifest
|
|
777
|
-
5. 按 manifest 恢复文件
|
|
778
|
-
6. 返回恢复文件列表和备份目录
|
|
779
|
-
|
|
780
|
-
#### `rollback` 时序图
|
|
781
|
-
|
|
782
|
-
```text
|
|
783
|
-
CLI
|
|
784
|
-
-> executeCommand("rollback")
|
|
785
|
-
-> app/rollbackLatest
|
|
786
|
-
-> backup-repo.loadLatestManifest
|
|
787
|
-
-> backup-repo.restoreManifest
|
|
788
|
-
-> output renderer
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
## 7. 输出设计
|
|
792
|
-
|
|
793
|
-
### 7.1 统一 JSON envelope
|
|
794
|
-
|
|
795
|
-
关键命令支持统一结构:
|
|
796
|
-
|
|
797
|
-
```json
|
|
798
|
-
{
|
|
799
|
-
"ok": true,
|
|
800
|
-
"command": "switch",
|
|
801
|
-
"data": {},
|
|
802
|
-
"warnings": [],
|
|
803
|
-
"error": null
|
|
804
|
-
}
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
成功和失败的 envelope 都由 `src/cli/output.ts` 统一渲染。
|
|
808
|
-
|
|
809
|
-
### 7.2 人类可读输出
|
|
810
|
-
|
|
811
|
-
人类模式下,输出尽量保持:
|
|
812
|
-
|
|
813
|
-
- 短
|
|
814
|
-
- 稳定
|
|
815
|
-
- 无敏感值
|
|
816
|
-
- 可快速扫描
|
|
817
|
-
|
|
818
|
-
当前并未做彩色输出、表格对齐或交互式 UI,这符合 MVP 的工程约束。
|
|
819
|
-
|
|
820
|
-
## 8. 错误处理设计
|
|
821
|
-
|
|
822
|
-
### 8.1 统一错误码
|
|
823
|
-
|
|
824
|
-
错误码集中定义在 `src/domain/errors.ts`,所有层最终都要收敛到这一套固定代码上。
|
|
825
|
-
|
|
826
|
-
这样做的意义是:
|
|
827
|
-
|
|
828
|
-
- AI 自动化更稳定
|
|
829
|
-
- CLI 输出更一致
|
|
830
|
-
- 测试断言更明确
|
|
831
|
-
- 后续做 GUI / HTTP / MCP 适配时不用重建错误体系
|
|
832
|
-
|
|
833
|
-
### 8.2 应用层回滚策略
|
|
834
|
-
|
|
835
|
-
当前回滚策略不是“失败就把目录整体还原”,而是:
|
|
836
|
-
|
|
837
|
-
- 在写操作之前先生成明确的 manifest
|
|
838
|
-
- 失败后按 manifest 恢复受影响文件
|
|
839
|
-
|
|
840
|
-
这样更安全,也更容易扩展到未来多文件事务。
|
|
841
|
-
|
|
842
|
-
当前进一步把“事务”固定为单操作窗口:
|
|
843
|
-
|
|
844
|
-
- 先拿锁
|
|
845
|
-
- 再备份
|
|
846
|
-
- 再变更
|
|
847
|
-
- 失败则补偿恢复
|
|
848
|
-
- 最后释放锁
|
|
849
|
-
|
|
850
|
-
### 8.3 错误传播路径
|
|
851
|
-
|
|
852
|
-
当前错误传播采用“低层抛错,高层归一”的方式:
|
|
853
|
-
|
|
854
|
-
```text
|
|
855
|
-
infra/domain throws cliError(...)
|
|
856
|
-
-> app catch/augment if needed
|
|
857
|
-
-> cli normalizeError(...)
|
|
858
|
-
-> output layer renderFailure(...)
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
其中应用层只在两类场景下增强错误:
|
|
862
|
-
|
|
863
|
-
- 需要补充上下文细节
|
|
864
|
-
- 需要把回滚结果附加到错误上
|
|
865
|
-
|
|
866
|
-
## 9. 测试架构
|
|
867
|
-
|
|
868
|
-
### 9.1 当前测试目录
|
|
869
|
-
|
|
870
|
-
```text
|
|
871
|
-
tests/
|
|
872
|
-
domain.spec.js
|
|
873
|
-
app.spec.js
|
|
874
|
-
cli.spec.js
|
|
875
|
-
helpers.js
|
|
876
|
-
run-tests.js
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
### 9.2 为什么当前没有直接使用 `node --test`
|
|
880
|
-
|
|
881
|
-
当前运行环境下,Node 内建 test runner 会触发 worker / spawn 路径上的 `EPERM` 限制,因此当前采用:
|
|
882
|
-
|
|
883
|
-
- 标准 `tests/` 目录结构
|
|
884
|
-
- 串行测试 runner `tests/run-tests.js`
|
|
885
|
-
|
|
886
|
-
这不是长期唯一方案,但在当前环境里是务实且稳定的选择。
|
|
887
|
-
|
|
888
|
-
### 9.3 当前测试覆盖
|
|
889
|
-
|
|
890
|
-
#### `domain.spec.js`
|
|
891
|
-
|
|
892
|
-
覆盖:
|
|
893
|
-
|
|
894
|
-
- profile 解析
|
|
895
|
-
- profile section 提取
|
|
896
|
-
- top-level profile 替换
|
|
897
|
-
- provider schema 校验
|
|
898
|
-
- provider 清洗与 profile 反查
|
|
899
|
-
|
|
900
|
-
#### `app.spec.js`
|
|
901
|
-
|
|
902
|
-
覆盖:
|
|
903
|
-
|
|
904
|
-
- `list/current/status`
|
|
905
|
-
- `add/export/import/remove`
|
|
906
|
-
- `switch` 成功路径
|
|
907
|
-
- `switch` 登录失败回滚
|
|
908
|
-
- `rollback`
|
|
909
|
-
- `doctor`
|
|
910
|
-
- `PROVIDER_NOT_FOUND`
|
|
911
|
-
- `PROFILE_NOT_FOUND`
|
|
912
|
-
- `INVALID_IMPORT_FILE`
|
|
913
|
-
- `PROVIDERS_NOT_FOUND`
|
|
914
|
-
- `PROVIDERS_PARSE_ERROR`
|
|
915
|
-
|
|
916
|
-
#### `cli.spec.js`
|
|
917
|
-
|
|
918
|
-
覆盖:
|
|
919
|
-
|
|
920
|
-
- argv 解析
|
|
921
|
-
- JSON success envelope
|
|
922
|
-
- JSON failure envelope
|
|
923
|
-
- `CONFIG_NOT_FOUND` 的 CLI 层失败渲染
|
|
924
|
-
|
|
925
|
-
### 9.4 测试分层策略
|
|
926
|
-
|
|
927
|
-
当前测试策略遵循:
|
|
928
|
-
|
|
929
|
-
- domain 层测纯规则
|
|
930
|
-
- app 层测命令行为和回滚语义
|
|
931
|
-
- cli 层测参数解析和输出契约
|
|
932
|
-
|
|
933
|
-
这样可以避免所有测试都退化成“起一个子进程跑命令”的高成本方式,也避免纯单元测试完全失去真实业务价值。
|
|
934
|
-
|
|
935
|
-
## 10. 打包与发布设计
|
|
936
|
-
|
|
937
|
-
### 10.1 构建脚本
|
|
938
|
-
|
|
939
|
-
当前构建脚本在:
|
|
940
|
-
|
|
941
|
-
- `scripts/build.cjs`
|
|
942
|
-
|
|
943
|
-
它先清理 `dist/`,再执行 `tsc`。这样可以避免旧编译产物残留进 npm 包。
|
|
944
|
-
|
|
945
|
-
### 10.2 npm 包入口
|
|
946
|
-
|
|
947
|
-
当前 npm 二进制入口定义在 `package.json`:
|
|
948
|
-
|
|
949
|
-
```json
|
|
950
|
-
"bin": {
|
|
951
|
-
"codexs": "dist/cli.js"
|
|
952
|
-
}
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
这保证:
|
|
956
|
-
|
|
957
|
-
- 全局安装后可执行 `codexs`
|
|
958
|
-
- `npx @minniexcode/codex-switch ...` 可以走同一入口
|
|
959
|
-
|
|
960
|
-
### 10.3 打包验证
|
|
961
|
-
|
|
962
|
-
当前工程已经通过 `npm pack --dry-run` 验证:
|
|
963
|
-
|
|
964
|
-
- `dist/` 中只包含当前源码对应的编译产物
|
|
965
|
-
- `docs/`、`README.md`、`LICENSE` 和 `package.json` 进入包
|
|
966
|
-
- 已删除的旧构建文件不会混进 tarball
|
|
967
|
-
|
|
968
|
-
### 10.4 为什么构建脚本单独放在 `scripts/build.cjs`
|
|
969
|
-
|
|
970
|
-
最初如果仅使用 `tsc`,旧的编译产物可能残留在 `dist/` 并混入 npm 包。单独脚本的意义是:
|
|
971
|
-
|
|
972
|
-
- 显式清理 `dist/`
|
|
973
|
-
- 再调用 TypeScript 编译
|
|
974
|
-
- 让打包输出和当前源码状态严格一致
|
|
975
|
-
|
|
976
|
-
## 11. 架构优点
|
|
977
|
-
|
|
978
|
-
当前架构的主要优点有:
|
|
979
|
-
|
|
980
|
-
- 命令行为和文件系统副作用解耦
|
|
981
|
-
- 输出层与业务层解耦
|
|
982
|
-
- 错误码统一
|
|
983
|
-
- 备份/回滚是独立能力,不被嵌入某一个命令实现里
|
|
984
|
-
- 测试可以分别打到 domain / app / cli 三层
|
|
985
|
-
- 后续新增命令时,能复用现有 repository、错误码和输出体系
|
|
986
|
-
|
|
987
|
-
## 12. 与 `codex-auth` / `cc-switch` 的架构对比
|
|
988
|
-
|
|
989
|
-
### 12.1 与 `codex-auth` 的对比
|
|
990
|
-
|
|
991
|
-
`codex-auth` 更接近已经产品化的 CLI 账号管理器,而 `codex-switch` 更聚焦 provider/profile 切换:
|
|
992
|
-
|
|
993
|
-
- 相同点:
|
|
994
|
-
- 都是 CLI-first
|
|
995
|
-
- 都适合 npm 分发
|
|
996
|
-
- 都适合自动化和 AI 调用
|
|
997
|
-
- 不同点:
|
|
998
|
-
- `codex-auth` 的主对象是账号 / auth
|
|
999
|
-
- `codex-switch` 的主对象是 provider/profile 和本地配置
|
|
1000
|
-
|
|
1001
|
-
因此 `codex-auth` 对本项目的主要参考价值在于“命令产品化”,不是数据模型本身。
|
|
1002
|
-
|
|
1003
|
-
### 12.2 与 `cc-switch` 的对比
|
|
1004
|
-
|
|
1005
|
-
`cc-switch` 是桌面 All-in-One manager,`codex-switch` 是轻量 CLI 工具:
|
|
1006
|
-
|
|
1007
|
-
| 维度 | `codex-switch` | `cc-switch` |
|
|
1008
|
-
| --- | --- | --- |
|
|
1009
|
-
| 产品形态 | CLI | Desktop App |
|
|
1010
|
-
| 核心对象 | provider/profile + 本地配置 | 多工具统一管理平台 |
|
|
1011
|
-
| 技术栈 | Node.js + TypeScript | React + TypeScript + Tauri + Rust |
|
|
1012
|
-
| 主数据层 | 本地文件 + 备份 manifest | SQLite + JSON |
|
|
1013
|
-
| 核心能力 | 切换、导入导出、诊断、回滚 | GUI 管理、云同步、代理、统计、会话管理 |
|
|
1014
|
-
| 适用场景 | 轻量切换、自动化、AI 调用 | 可视化统一管理 |
|
|
1015
|
-
|
|
1016
|
-
这个对比说明:
|
|
1017
|
-
|
|
1018
|
-
- `cc-switch` 值得借鉴的是架构表达方式和模块化意识
|
|
1019
|
-
- `codex-switch` 当前不应该为了“完整平台化”而抬高技术复杂度
|
|
1020
|
-
|
|
1021
|
-
## 13. 当前限制与工程债
|
|
1022
|
-
|
|
1023
|
-
当前版本仍有一些明确边界:
|
|
1024
|
-
|
|
1025
|
-
### 13.1 TOML 处理仍是轻量字符串策略
|
|
1026
|
-
|
|
1027
|
-
当前没有引入完整 TOML parser,而是:
|
|
1028
|
-
|
|
1029
|
-
- 解析顶层 `profile`
|
|
1030
|
-
- 识别 `[profiles.xxx]`
|
|
1031
|
-
- 替换顶层 `profile`
|
|
1032
|
-
|
|
1033
|
-
这对 MVP 足够,但如果未来要修改更多 TOML 字段,建议引入真正的 TOML AST 级处理。
|
|
1034
|
-
|
|
1035
|
-
### 13.2 `doctor` 的错误码还不够细
|
|
1036
|
-
|
|
1037
|
-
当前 `codex` CLI 不可执行会复用:
|
|
1038
|
-
|
|
1039
|
-
- `CODEX_LOGIN_FAILED`
|
|
1040
|
-
|
|
1041
|
-
从 MVP 可用性上没问题,但长期更推荐拆成专门的 CLI availability 错误。
|
|
1042
|
-
|
|
1043
|
-
### 13.3 当前交互式命令层范围仍然受控
|
|
1044
|
-
|
|
1045
|
-
当前 CLI 仍以显式参数模式为主,但已经把 `inquirer` 交互扩展到了高频写命令:
|
|
1046
|
-
|
|
1047
|
-
- `add` 缺失必填字段时的渐进式提问
|
|
1048
|
-
- `switch` 的 provider 选择
|
|
1049
|
-
- `remove` 的 provider 选择与确认
|
|
1050
|
-
- `import` / `export` 的危险确认
|
|
1051
|
-
- `rollback` 的恢复确认
|
|
1052
|
-
|
|
1053
|
-
当前仍然没有:
|
|
1054
|
-
|
|
1055
|
-
- 路径向导式 import/export
|
|
1056
|
-
- TUI 状态面板
|
|
1057
|
-
- 脱离显式参数契约的自动化交互
|
|
1058
|
-
|
|
1059
|
-
这继续保持了 CLI-first 和自动化优先的主体边界。
|
|
1060
|
-
|
|
1061
|
-
### 13.4 尚未引入持久化审计日志
|
|
1062
|
-
|
|
1063
|
-
当前只有:
|
|
1064
|
-
|
|
1065
|
-
- 备份文件
|
|
1066
|
-
- `latest.json`
|
|
1067
|
-
|
|
1068
|
-
没有单独的操作日志或事件审计记录。
|
|
1069
|
-
|
|
1070
|
-
## 14. 后续演进建议
|
|
1071
|
-
|
|
1072
|
-
### 14.1 短期
|
|
1073
|
-
|
|
1074
|
-
- 为 `doctor` 拆出更细的 issue code
|
|
1075
|
-
- 增加 CLI 级更多命令覆盖测试
|
|
1076
|
-
- 清理测试夹具和本地 cache 目录的仓库管理策略
|
|
1077
|
-
- 为文档增加命令级技术说明页
|
|
1078
|
-
|
|
1079
|
-
### 14.2 中期
|
|
1080
|
-
|
|
1081
|
-
- 引入真正的 TOML parser
|
|
1082
|
-
- 支持更细粒度的 provider 编辑命令
|
|
1083
|
-
- 支持 `import --merge` 等增强模式
|
|
1084
|
-
- 增加更显式的备份历史查看与指定版本恢复
|
|
1085
|
-
|
|
1086
|
-
### 14.3 长期
|
|
1087
|
-
|
|
1088
|
-
- 增加 GUI / TUI 壳层,但复用同一 application/domain/infra
|
|
1089
|
-
- 增加 MCP 或 HTTP 适配层,让 AI 工具和桌面前端复用核心逻辑
|
|
1090
|
-
- 增加多设备 / 多工作区隔离策略
|
|
1091
|
-
|
|
1092
|
-
如果未来确实需要进入桌面形态,`cc-switch` 的做法提供了一个现实参考:
|
|
1093
|
-
|
|
1094
|
-
- 前端:React + TS
|
|
1095
|
-
- 壳层:Tauri
|
|
1096
|
-
- 本地系统能力:Rust backend
|
|
1097
|
-
|
|
1098
|
-
但这应该是 `codex-switch` 在 CLI 版本稳定之后的下一阶段选择,而不是当前 MVP 的前提。
|
|
1099
|
-
|
|
1100
|
-
## 15. 结论
|
|
1101
|
-
|
|
1102
|
-
`codex-switch` 当前的技术架构已经从“单脚本/单文件 CLI”进入了一个可维护、可测试、可扩展的工程化形态。
|
|
1103
|
-
|
|
1104
|
-
它的核心技术结论可以归纳为:
|
|
1105
|
-
|
|
1106
|
-
- 使用 TypeScript + Node.js 做 CLI-first MVP 是合理的
|
|
1107
|
-
- 以 `cli / app / domain / infra` 四层作为长期架构边界是合理的
|
|
1108
|
-
- 以 `providers.json`、`config.toml`、`backups/` 为核心对象的本地事务式切换方案是合理的
|
|
1109
|
-
- 以统一错误码、统一 JSON envelope 和显式备份 manifest 支撑 AI 调用与安全回滚,是当前版本最有价值的工程资产
|
|
1110
|
-
|
|
1111
|
-
如果后续继续演进,这份架构最需要被保护的不是“具体文件名”,而是这三件事:
|
|
1112
|
-
|
|
1113
|
-
- 分层边界不能塌回单文件 CLI
|
|
1114
|
-
- 错误码和输出契约不能随意漂移
|
|
1115
|
-
- 写操作必须持续保持“先备份、后修改、失败回滚”的默认安全语义
|
|
1
|
+
# codex-switch 技术架构设计
|
|
2
|
+
|
|
3
|
+
> 状态说明:这份文档是历史跨版本参考,不是当前 release contract。
|
|
4
|
+
> 当前事实源请改看 [`docs/cli-usage.md`](./cli-usage.md)、[`docs/PRD/codex-switch-prd-v0.1.1.md`](./PRD/codex-switch-prd-v0.1.1.md)、[`docs/Design/codex-switch-v0.1.1-design.md`](./Design/codex-switch-v0.1.1-design.md)。
|
|
5
|
+
|
|
6
|
+
## 文档信息
|
|
7
|
+
|
|
8
|
+
- 文档类型:技术架构设计文档
|
|
9
|
+
- 适用范围:`codex-switch` MVP / CLI First
|
|
10
|
+
- 对应产品文档:
|
|
11
|
+
- [`codex-switch-product-overview.md`](./codex-switch-product-overview.md)
|
|
12
|
+
- [`codex-switch-product-research.md`](./codex-switch-product-research.md)
|
|
13
|
+
- [`PRD/codex-switch-prd.md`](./PRD/codex-switch-prd.md)
|
|
14
|
+
- [`codex-switch-command-design.md`](./codex-switch-command-design.md)
|
|
15
|
+
|
|
16
|
+
## 1. 文档目标
|
|
17
|
+
|
|
18
|
+
这份文档用于把 `codex-switch` 当前已经实现的技术架构、代码组织、核心流程和设计取舍完整沉淀下来,作为后续维护、扩展和协作开发的基线。
|
|
19
|
+
|
|
20
|
+
它回答的不是“产品要做什么”,而是下面这些工程问题:
|
|
21
|
+
|
|
22
|
+
- 当前代码是如何分层的
|
|
23
|
+
- 每一层分别负责什么、不负责什么
|
|
24
|
+
- CLI 命令是如何被解析、执行和输出的
|
|
25
|
+
- 配置文件、备份、回滚和错误码是如何落到代码里的
|
|
26
|
+
- 模块之间的依赖关系是什么
|
|
27
|
+
- 核心流程的时序是什么
|
|
28
|
+
- 测试如何组织
|
|
29
|
+
- 后续如果新增命令、扩展数据模型或做 GUI / MCP / daemon,应沿着什么方向演进
|
|
30
|
+
|
|
31
|
+
## 2. 设计输入与参考
|
|
32
|
+
|
|
33
|
+
### 2.1 当前项目输入
|
|
34
|
+
|
|
35
|
+
`codex-switch` 的架构设计主要由以下输入共同约束:
|
|
36
|
+
|
|
37
|
+
- 产品目标:本地优先、默认安全、对 AI 友好的 provider/profile 切换 CLI
|
|
38
|
+
- PRD 约束:固定命令面、统一 JSON 输出、固定错误码、写操作必须备份、失败必须回滚
|
|
39
|
+
- 技术路线:Node.js + TypeScript
|
|
40
|
+
- 使用对象:本地 `~/.codex/` 目录,而不是云端控制面
|
|
41
|
+
|
|
42
|
+
### 2.2 对 `codex-auth` 的借鉴
|
|
43
|
+
|
|
44
|
+
根据 `codex-auth` 当前公开 README 和命令文档入口,它是一个已经产品化的 CLI-first 工具,优势主要在下面几类工程实践:
|
|
45
|
+
|
|
46
|
+
- 命令家族清晰:`list` / `login` / `switch` / `remove` / `status` / `import` / `config`
|
|
47
|
+
- npm 全局分发路径成熟,同时支持 `npx`
|
|
48
|
+
- 明确区分交互式能力和自动化可调用能力
|
|
49
|
+
- 文档按命令组织,降低学习和调用成本
|
|
50
|
+
|
|
51
|
+
`codex-switch` 借鉴的是这些“CLI 产品化方法”,但没有复制它的多账号体系、远程 usage 刷新、后台 auto-switch 等更重的职责。
|
|
52
|
+
|
|
53
|
+
### 2.3 对 `cc-switch` 的借鉴
|
|
54
|
+
|
|
55
|
+
根据 `cc-switch` 当前公开 README,它是一个跨平台的桌面 All-in-One 管理器,目标覆盖:
|
|
56
|
+
|
|
57
|
+
- Claude Code
|
|
58
|
+
- Codex
|
|
59
|
+
- Gemini CLI
|
|
60
|
+
- OpenCode
|
|
61
|
+
- OpenClaw
|
|
62
|
+
- Hermes Agent
|
|
63
|
+
|
|
64
|
+
其公开设计信息显示:
|
|
65
|
+
|
|
66
|
+
- 技术栈是 `React + TypeScript + Tauri + Rust`
|
|
67
|
+
- 前端与后端通过 Tauri IPC 通信
|
|
68
|
+
- 后端采用明确的分层:`Commands -> Services -> Database/Models`
|
|
69
|
+
- 数据层采用 SQLite 作为单一事实源,并配合 JSON 做设备级配置
|
|
70
|
+
- 具备桌面 GUI、系统托盘、云同步、用量统计、Session Manager、Proxy 等更重能力
|
|
71
|
+
|
|
72
|
+
对 `codex-switch` 的借鉴点主要有:
|
|
73
|
+
|
|
74
|
+
- 清晰的分层表达
|
|
75
|
+
- 文档中直接公开架构总览和项目结构
|
|
76
|
+
- 命令/服务/数据层职责拆分明确
|
|
77
|
+
- 把“管理器”能力和“切换器”能力区分成不同模块
|
|
78
|
+
|
|
79
|
+
不直接照搬的点也很明确:
|
|
80
|
+
|
|
81
|
+
- `cc-switch` 是桌面应用优先,`codex-switch` 是 CLI-first
|
|
82
|
+
- `cc-switch` 的职责明显更广,包含云同步、会话管理、代理模式、统计面板等
|
|
83
|
+
- `cc-switch` 的 SQLite + GUI 方案适合 All-in-One manager,不适合当前这个轻量本地切换工具的 MVP
|
|
84
|
+
|
|
85
|
+
因此,`codex-switch` 在架构上更适合借鉴它的“分层清晰度”和“工程表达方式”,而不是直接复制它的技术栈和能力范围。
|
|
86
|
+
|
|
87
|
+
## 3. 架构总览
|
|
88
|
+
|
|
89
|
+
### 3.1 核心设计原则
|
|
90
|
+
|
|
91
|
+
当前实现严格围绕下面五个原则组织:
|
|
92
|
+
|
|
93
|
+
- `CLI First`
|
|
94
|
+
- 核心能力全部通过命令完成
|
|
95
|
+
- `Local First`
|
|
96
|
+
- 主对象是本地文件,不依赖远程控制面
|
|
97
|
+
- `Safe by Default`
|
|
98
|
+
- 所有写操作先备份,失败后回滚
|
|
99
|
+
- `AI Friendly`
|
|
100
|
+
- 关键命令支持统一 JSON envelope
|
|
101
|
+
- `Low Coupling`
|
|
102
|
+
- 参数解析、业务编排、文件访问、错误模型彼此解耦
|
|
103
|
+
- `Split State Model`
|
|
104
|
+
- `providers.json` 是管理态事实源,`config.toml` 是运行态路由文件,`auth.json` 是 direct provider 的认证投影
|
|
105
|
+
- `Lightweight Transactions`
|
|
106
|
+
- 单次写操作要有锁、备份、回滚边界
|
|
107
|
+
|
|
108
|
+
### 3.2 分层结构
|
|
109
|
+
|
|
110
|
+
当前代码采用 4 层结构:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
CLI 层
|
|
114
|
+
负责参数解析、命令分发、输出渲染、进程退出语义
|
|
115
|
+
|
|
116
|
+
Application 层
|
|
117
|
+
负责单个用例的编排流程,例如 switch / import / rollback
|
|
118
|
+
|
|
119
|
+
Domain 层
|
|
120
|
+
负责数据模型、规则校验、错误码和纯函数逻辑
|
|
121
|
+
|
|
122
|
+
Infrastructure 层
|
|
123
|
+
负责文件系统、路径、备份持久化、子进程调用
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
这四层的边界是当前架构最重要的维护基础。
|
|
127
|
+
|
|
128
|
+
当前实现还明确区分三类状态对象:
|
|
129
|
+
|
|
130
|
+
- 管理态单一事实源:`providers.json`
|
|
131
|
+
- 运行态路由:`config.toml`
|
|
132
|
+
- 当前 active direct provider 的认证投影:`auth.json`
|
|
133
|
+
- 回滚态:`backups/latest.json` 和对应 manifest
|
|
134
|
+
|
|
135
|
+
这意味着未来即使引入 GUI / MCP / HTTP 适配层,核心同步目标仍然是 runtime files,而不是把 runtime files 本身当成长期管理数据库。
|
|
136
|
+
|
|
137
|
+
`0.0.6` 的实现边界还需要区分“逻辑主层”和“兼容层”:
|
|
138
|
+
|
|
139
|
+
- 逻辑主层已经迁移到 `src/commands/`、`src/interaction/`、`src/storage/`、`src/runtime/`
|
|
140
|
+
- `src/cli/` 和 `src/infra/` 当前主要承担兼容 re-export 与入口收敛职责
|
|
141
|
+
- 这意味着 `0.0.6` 的完成标准是“边界和契约已经统一”,而不是“所有旧路径文件都物理删除”
|
|
142
|
+
- 后续版本可以在兼容窗口结束后继续删除旧 facade,但这不属于 `0.0.6` 的必须交付范围
|
|
143
|
+
|
|
144
|
+
### 3.3 模块依赖图
|
|
145
|
+
|
|
146
|
+
当前代码依赖关系可以抽象为:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
+----------------------+
|
|
150
|
+
| CLI Layer |
|
|
151
|
+
| cli.ts / cli/* |
|
|
152
|
+
+----------+-----------+
|
|
153
|
+
|
|
|
154
|
+
v
|
|
155
|
+
+----------------------+
|
|
156
|
+
| Application Layer |
|
|
157
|
+
| app/* use cases |
|
|
158
|
+
+----------+-----------+
|
|
159
|
+
|
|
|
160
|
+
+--------------+--------------+
|
|
161
|
+
| |
|
|
162
|
+
v v
|
|
163
|
+
+----------------------+ +----------------------+
|
|
164
|
+
| Domain Layer | | Infrastructure Layer |
|
|
165
|
+
| rules / schema / err | | fs / path / process |
|
|
166
|
+
+----------------------+ +----------------------+
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
依赖方向约束:
|
|
170
|
+
|
|
171
|
+
- `cli` 可以依赖 `app`、`domain`、`infra`
|
|
172
|
+
- `app` 可以依赖 `domain`、`infra`
|
|
173
|
+
- `infra` 可以依赖 `domain`
|
|
174
|
+
- `domain` 不依赖其他项目层
|
|
175
|
+
|
|
176
|
+
这保证领域规则不会反向耦合到 CLI 或具体文件系统实现。
|
|
177
|
+
|
|
178
|
+
### 3.4 命令调用生命周期
|
|
179
|
+
|
|
180
|
+
所有命令大体遵循同一个生命周期:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
argv
|
|
184
|
+
-> parseArgs
|
|
185
|
+
-> create CommandContext
|
|
186
|
+
-> executeCommand
|
|
187
|
+
-> app use case
|
|
188
|
+
-> infra/domain collaboration
|
|
189
|
+
-> CommandResult / CliErrorShape
|
|
190
|
+
-> output renderer
|
|
191
|
+
-> stdout/stderr + exitCode
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
这条链路的工程价值在于:
|
|
195
|
+
|
|
196
|
+
- 参数解析和业务行为分离
|
|
197
|
+
- 成功输出和失败输出统一渲染
|
|
198
|
+
- 业务层不直接操作终端
|
|
199
|
+
- CLI 层测试可以只验证 envelope 和渲染,而不重测业务细节
|
|
200
|
+
|
|
201
|
+
## 4. 代码目录与职责
|
|
202
|
+
|
|
203
|
+
当前目录结构如下:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
src/
|
|
207
|
+
cli.ts
|
|
208
|
+
commands/
|
|
209
|
+
interaction/
|
|
210
|
+
app/
|
|
211
|
+
cli/
|
|
212
|
+
domain/
|
|
213
|
+
runtime/
|
|
214
|
+
infra/
|
|
215
|
+
storage/
|
|
216
|
+
|
|
217
|
+
tests/
|
|
218
|
+
app.spec.js
|
|
219
|
+
cli.spec.js
|
|
220
|
+
domain.spec.js
|
|
221
|
+
helpers.js
|
|
222
|
+
run-tests.js
|
|
223
|
+
|
|
224
|
+
scripts/
|
|
225
|
+
build.cjs
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 4.1 `src/cli.ts`
|
|
229
|
+
|
|
230
|
+
入口文件,只做三件事:
|
|
231
|
+
|
|
232
|
+
- 接收 `process.argv`
|
|
233
|
+
- 调用参数解析
|
|
234
|
+
- 根据命令分发到对应 use case,并调用输出渲染
|
|
235
|
+
|
|
236
|
+
它不直接做文件读写,不直接写业务逻辑,也不直接实现备份或校验规则。
|
|
237
|
+
|
|
238
|
+
### 4.2 `src/commands/`
|
|
239
|
+
|
|
240
|
+
这一层是 `0.0.6` 新增的命令表面层,负责把“公开 CLI 形态”收敛为单一 registry。
|
|
241
|
+
|
|
242
|
+
它承担的职责是:
|
|
243
|
+
|
|
244
|
+
- 定义每个命令的公开 token 形态,例如 `config show`、`config list-profiles`、`backups list`
|
|
245
|
+
- 统一保存 `summary`、`usage`、`details`、`examples`
|
|
246
|
+
- 绑定 command handler,供 dispatch 直接执行
|
|
247
|
+
- 让 help、解析和 dispatch 共享同一份事实源
|
|
248
|
+
|
|
249
|
+
它不负责:
|
|
250
|
+
|
|
251
|
+
- 具体文件读写
|
|
252
|
+
- prompt 交互细节
|
|
253
|
+
- human output 渲染
|
|
254
|
+
|
|
255
|
+
### 4.3 `src/interaction/`
|
|
256
|
+
|
|
257
|
+
这一层是 `0.0.6` 中显式抽出的交互层,负责所有 CLI 级 prompt 组合逻辑。
|
|
258
|
+
|
|
259
|
+
它承担的职责是:
|
|
260
|
+
|
|
261
|
+
- 判断哪些路径允许交互
|
|
262
|
+
- 组合 provider 选择、确认、rollback 预览等交互动作
|
|
263
|
+
- 组织 add/edit/setup 中的渐进式输入收集
|
|
264
|
+
|
|
265
|
+
它不负责:
|
|
266
|
+
|
|
267
|
+
- 业务状态变更
|
|
268
|
+
- 文件系统写入
|
|
269
|
+
- 运行时探测
|
|
270
|
+
|
|
271
|
+
`setup` 是这一层边界最强的命令之一。在 `0.0.6` 中,它的 adopt profile 选择和 provider 详情输入仍然是交互式 contract,因此非交互路径会显式失败,而不是隐式进入空输入分支。
|
|
272
|
+
|
|
273
|
+
### 4.4 `src/storage/`
|
|
274
|
+
|
|
275
|
+
这一层是 `0.0.6` 的文件和状态访问层,负责把以前分散在 `infra/` 的能力收口成稳定存储边界。
|
|
276
|
+
|
|
277
|
+
它承担的职责是:
|
|
278
|
+
|
|
279
|
+
- `config.toml` / `providers.json` / backup manifest / lock file 的读写
|
|
280
|
+
- Codex home 目录路径展开
|
|
281
|
+
- 原子写入、备份、回滚、锁定
|
|
282
|
+
|
|
283
|
+
`src/infra/` 在当前版本主要保留兼容 re-export,以便逐步迁移,不再作为新的业务入口继续扩张。
|
|
284
|
+
|
|
285
|
+
### 4.5 `src/runtime/`
|
|
286
|
+
|
|
287
|
+
这一层是 `0.0.6` 新增的运行时边界,负责本地 Codex CLI 的外部依赖探测和登录调用。
|
|
288
|
+
|
|
289
|
+
它承担的职责是:
|
|
290
|
+
|
|
291
|
+
- Codex 可用性检查
|
|
292
|
+
- Codex 版本检查
|
|
293
|
+
- Codex login 调用
|
|
294
|
+
- 未来可扩展为第三方 runtime adapter 的能力边界
|
|
295
|
+
|
|
296
|
+
### 4.6 `src/cli/`
|
|
297
|
+
|
|
298
|
+
#### `src/cli/args.ts`
|
|
299
|
+
|
|
300
|
+
负责:
|
|
301
|
+
|
|
302
|
+
- 解析全局参数 `--json`、`--codex-dir`
|
|
303
|
+
- 解析命令名、位置参数和命令选项
|
|
304
|
+
- 提供 `hasFlag` / `getSingleOption` 这种小型解析辅助函数
|
|
305
|
+
|
|
306
|
+
不负责:
|
|
307
|
+
|
|
308
|
+
- 命令实际行为
|
|
309
|
+
- 文件访问
|
|
310
|
+
- 输出格式化
|
|
311
|
+
|
|
312
|
+
#### `src/cli/prompt.ts`
|
|
313
|
+
|
|
314
|
+
负责:
|
|
315
|
+
|
|
316
|
+
- 对 `inquirer` 做轻量封装
|
|
317
|
+
- 提供 `input` / `password` / `select` / `confirm` 四类 typed 交互能力
|
|
318
|
+
- 把 prompt 取消统一转换成稳定 CLI 错误
|
|
319
|
+
|
|
320
|
+
不负责:
|
|
321
|
+
|
|
322
|
+
- 业务判断某个命令是否应该进入交互
|
|
323
|
+
- 直接读写 provider/config 文件
|
|
324
|
+
|
|
325
|
+
#### `src/cli/interactive.ts`
|
|
326
|
+
|
|
327
|
+
负责:
|
|
328
|
+
|
|
329
|
+
- 统一判定何时允许交互
|
|
330
|
+
- 组合 provider 选择、危险确认、rollback 摘要展示等 CLI 级辅助逻辑
|
|
331
|
+
- 保持命令分支对交互的接入方式一致
|
|
332
|
+
|
|
333
|
+
#### `src/cli/output.ts`
|
|
334
|
+
|
|
335
|
+
负责:
|
|
336
|
+
|
|
337
|
+
- 渲染统一 JSON envelope
|
|
338
|
+
- 渲染默认的人类可读输出
|
|
339
|
+
- 渲染失败输出和错误码
|
|
340
|
+
|
|
341
|
+
当前输出层还额外抽出了:
|
|
342
|
+
|
|
343
|
+
- `renderSuccess`
|
|
344
|
+
- `renderFailure`
|
|
345
|
+
|
|
346
|
+
这两个纯渲染入口的作用是让 CLI 层本身也能被测试,而不依赖真实子进程。
|
|
347
|
+
|
|
348
|
+
### 4.7 `src/app/`
|
|
349
|
+
|
|
350
|
+
这一层是应用服务 / 用例编排层。每个文件基本对应一个命令或一个用例:
|
|
351
|
+
|
|
352
|
+
- `list-providers.ts`
|
|
353
|
+
- `get-current-profile.ts`
|
|
354
|
+
- `get-status.ts`
|
|
355
|
+
- `switch-provider.ts`
|
|
356
|
+
- `import-providers.ts`
|
|
357
|
+
- `export-providers.ts`
|
|
358
|
+
- `add-provider.ts`
|
|
359
|
+
- `remove-provider.ts`
|
|
360
|
+
- `run-doctor.ts`
|
|
361
|
+
- `rollback-latest.ts`
|
|
362
|
+
- `run-mutation.ts`
|
|
363
|
+
|
|
364
|
+
这一层的职责是:
|
|
365
|
+
|
|
366
|
+
- 组合多个 infra 能力
|
|
367
|
+
- 串起校验、备份、写入、回滚、输出数据准备
|
|
368
|
+
- 为所有写操作提供统一 orchestration contract
|
|
369
|
+
- 保证命令行为满足 PRD 语义
|
|
370
|
+
|
|
371
|
+
它不关心:
|
|
372
|
+
|
|
373
|
+
- 终端输出长什么样
|
|
374
|
+
- `stdout` / `stderr` 怎么写
|
|
375
|
+
- argv 怎么解析
|
|
376
|
+
|
|
377
|
+
### 4.8 `src/domain/`
|
|
378
|
+
|
|
379
|
+
#### `errors.ts`
|
|
380
|
+
|
|
381
|
+
定义当前 MVP 固定错误码:
|
|
382
|
+
|
|
383
|
+
- `CONFIG_NOT_FOUND`
|
|
384
|
+
- `PROVIDERS_NOT_FOUND`
|
|
385
|
+
- `PROVIDERS_PARSE_ERROR`
|
|
386
|
+
- `PROVIDER_NOT_FOUND`
|
|
387
|
+
- `PROFILE_NOT_FOUND`
|
|
388
|
+
- `BACKUP_FAILED`
|
|
389
|
+
- `CODEX_LOGIN_FAILED`
|
|
390
|
+
- `ROLLBACK_FAILED`
|
|
391
|
+
- `LOCK_CONFLICT`
|
|
392
|
+
- `LIVE_STATE_DRIFT`
|
|
393
|
+
- `INVALID_IMPORT_FILE`
|
|
394
|
+
|
|
395
|
+
并提供:
|
|
396
|
+
|
|
397
|
+
- `cliError`
|
|
398
|
+
- `normalizeError`
|
|
399
|
+
|
|
400
|
+
这是整个错误语义统一的基础。
|
|
401
|
+
|
|
402
|
+
#### `providers.ts`
|
|
403
|
+
|
|
404
|
+
负责 `providers.json` 的领域模型和规则:
|
|
405
|
+
|
|
406
|
+
- `ProviderRecord`
|
|
407
|
+
- `ProvidersFile`
|
|
408
|
+
- `validateProvidersShape`
|
|
409
|
+
- `cleanProviderRecord`
|
|
410
|
+
- `sortProviders`
|
|
411
|
+
- `findProviderByProfile`
|
|
412
|
+
|
|
413
|
+
这里是当前 provider 数据契约的唯一可信实现位置。
|
|
414
|
+
|
|
415
|
+
#### `config.ts`
|
|
416
|
+
|
|
417
|
+
负责 `config.toml` 相关的纯逻辑:
|
|
418
|
+
|
|
419
|
+
- 读取顶层 `profile`
|
|
420
|
+
- 提取 `[profiles.xxx]` 名称
|
|
421
|
+
- 替换顶层 `profile`
|
|
422
|
+
|
|
423
|
+
它只做字符串级和结构级处理,不做真实文件 IO。
|
|
424
|
+
|
|
425
|
+
#### `backup.ts`
|
|
426
|
+
|
|
427
|
+
定义备份 manifest 的结构:
|
|
428
|
+
|
|
429
|
+
- `FileBackupEntry`
|
|
430
|
+
- `BackupManifest`
|
|
431
|
+
|
|
432
|
+
### 4.5 `src/infra/`
|
|
433
|
+
|
|
434
|
+
#### `codex-paths.ts`
|
|
435
|
+
|
|
436
|
+
负责路径集中管理:
|
|
437
|
+
|
|
438
|
+
- `resolveCodexDir`
|
|
439
|
+
- `createCodexPaths`
|
|
440
|
+
|
|
441
|
+
它把下面这些路径收敛为一个统一对象:
|
|
442
|
+
|
|
443
|
+
- `config.toml`
|
|
444
|
+
- `providers.json`
|
|
445
|
+
- `auth.json`
|
|
446
|
+
- `backups/`
|
|
447
|
+
- `backups/latest.json`
|
|
448
|
+
|
|
449
|
+
这样上层命令不再自己拼路径。
|
|
450
|
+
|
|
451
|
+
#### `providers-repo.ts`
|
|
452
|
+
|
|
453
|
+
负责 `providers.json` 的文件级读写:
|
|
454
|
+
|
|
455
|
+
- 读取文件
|
|
456
|
+
- 解析 JSON
|
|
457
|
+
- 套用 domain schema 校验
|
|
458
|
+
- 序列化写回
|
|
459
|
+
|
|
460
|
+
#### `config-repo.ts`
|
|
461
|
+
|
|
462
|
+
负责 `config.toml` 的文件级访问:
|
|
463
|
+
|
|
464
|
+
- 读取配置文件
|
|
465
|
+
- 获取当前 profile
|
|
466
|
+
- 校验 profile 是否存在
|
|
467
|
+
- 更新顶层 profile
|
|
468
|
+
|
|
469
|
+
#### `backup-repo.ts`
|
|
470
|
+
|
|
471
|
+
负责备份和恢复落盘:
|
|
472
|
+
|
|
473
|
+
- 创建按时间戳命名的备份目录
|
|
474
|
+
- 保存 `manifest.json`
|
|
475
|
+
- 记录 `latest.json`
|
|
476
|
+
- 依据 manifest 恢复文件
|
|
477
|
+
|
|
478
|
+
#### `lock-repo.ts`
|
|
479
|
+
|
|
480
|
+
负责轻量并发控制:
|
|
481
|
+
|
|
482
|
+
- 在 `~/.codex/.codex-switch.lock` 上建立单操作锁
|
|
483
|
+
- 并发写入时快速失败
|
|
484
|
+
- 为 CLI、脚本和 AI agent 并发调用提供最小安全边界
|
|
485
|
+
|
|
486
|
+
#### `codex-cli.ts`
|
|
487
|
+
|
|
488
|
+
负责调用真实 `codex` CLI:
|
|
489
|
+
|
|
490
|
+
- `runCodexLogin`
|
|
491
|
+
- `checkCodexAvailable`
|
|
492
|
+
|
|
493
|
+
这里还提供了测试友好的可注入实现:
|
|
494
|
+
|
|
495
|
+
- `setCodexSpawnImplementation`
|
|
496
|
+
- `resetCodexSpawnImplementation`
|
|
497
|
+
|
|
498
|
+
这样应用层测试可以验证 `switch` 和 `doctor` 的行为,而不用依赖本机一定装好了真正的 `codex`。
|
|
499
|
+
|
|
500
|
+
#### `fs-utils.ts`
|
|
501
|
+
|
|
502
|
+
负责通用文件工具:
|
|
503
|
+
|
|
504
|
+
- `ensureDir`
|
|
505
|
+
- `writeTextFileAtomic`
|
|
506
|
+
- `readRequiredFile`
|
|
507
|
+
- 输出细节格式化
|
|
508
|
+
|
|
509
|
+
## 5. 关键数据模型
|
|
510
|
+
|
|
511
|
+
### 5.1 `providers.json`
|
|
512
|
+
|
|
513
|
+
当前系统围绕下面这个结构工作:
|
|
514
|
+
|
|
515
|
+
```json
|
|
516
|
+
{
|
|
517
|
+
"providers": {
|
|
518
|
+
"packycode": {
|
|
519
|
+
"profile": "packycode",
|
|
520
|
+
"apiKey": "sk-xxx",
|
|
521
|
+
"baseUrl": "https://example.com/v1",
|
|
522
|
+
"note": "primary free model route",
|
|
523
|
+
"tags": ["free", "daily"]
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
#### 字段语义
|
|
530
|
+
|
|
531
|
+
- `profile`
|
|
532
|
+
- 必填
|
|
533
|
+
- 必须映射到 `config.toml` 中已存在的 profile
|
|
534
|
+
- `apiKey`
|
|
535
|
+
- 必填
|
|
536
|
+
- 用于 `codex login --with-api-key`
|
|
537
|
+
- `baseUrl`
|
|
538
|
+
- 选填
|
|
539
|
+
- 当前版本只存储在 `providers.json`;`config show` 中展示的 runtime `baseUrl` 由 `model_provider -> model_providers.*.base_url` 解析
|
|
540
|
+
- `note`
|
|
541
|
+
- 选填
|
|
542
|
+
- 面向人类和 AI 的说明字段
|
|
543
|
+
- `tags`
|
|
544
|
+
- 选填
|
|
545
|
+
- 预留给未来筛选与推荐
|
|
546
|
+
|
|
547
|
+
从建模角度看,`providers.json` 是管理态 SSOT。未来如果支持 backfill,也应是显式命令把受控的 live 信息反向写回 registry,而不是默认把 runtime file 当成事实源。
|
|
548
|
+
|
|
549
|
+
### 5.2 `config.toml`
|
|
550
|
+
|
|
551
|
+
当前实现只关心两类信息:
|
|
552
|
+
|
|
553
|
+
- 顶层 `profile = "..."`
|
|
554
|
+
- `[profiles.xxx]` 段是否存在
|
|
555
|
+
|
|
556
|
+
当前版本不负责:
|
|
557
|
+
|
|
558
|
+
- 创建新的 profile section
|
|
559
|
+
- 深度解析 profile 内部更多字段
|
|
560
|
+
- 管理 base URL 或模型等 profile 内容
|
|
561
|
+
|
|
562
|
+
它在当前架构里是 direct provider 的认证投影文件,不承担 provider 选择职责,也不作为 provider registry 的事实源。
|
|
563
|
+
|
|
564
|
+
### 5.3 `auth.json`
|
|
565
|
+
|
|
566
|
+
当前实现不直接建模其内部字段,但它有明确架构角色:
|
|
567
|
+
|
|
568
|
+
- 属于 direct provider 的认证投影
|
|
569
|
+
- 在存在时纳入备份与回滚
|
|
570
|
+
- 不是 provider registry 的事实源
|
|
571
|
+
|
|
572
|
+
### 5.4 备份 manifest
|
|
573
|
+
|
|
574
|
+
当前备份 manifest 记录:
|
|
575
|
+
|
|
576
|
+
- 备份创建时间
|
|
577
|
+
- 备份原因
|
|
578
|
+
- 根目录
|
|
579
|
+
- 备份目录
|
|
580
|
+
- 每个文件的相对路径
|
|
581
|
+
- 原文件是否存在
|
|
582
|
+
- 备份文件名
|
|
583
|
+
|
|
584
|
+
这使得回滚不依赖目录猜测,而是依赖显式清单恢复。
|
|
585
|
+
|
|
586
|
+
## 6. 核心命令工作流
|
|
587
|
+
|
|
588
|
+
### 6.1 `list`
|
|
589
|
+
|
|
590
|
+
流程:
|
|
591
|
+
|
|
592
|
+
1. CLI 解析参数
|
|
593
|
+
2. `listProviders` 调用 `readProvidersFile`
|
|
594
|
+
3. 按名称排序并返回 provider 列表
|
|
595
|
+
4. 输出层渲染 JSON 或人类可读格式
|
|
596
|
+
|
|
597
|
+
失败点:
|
|
598
|
+
|
|
599
|
+
- 文件不存在:`PROVIDERS_NOT_FOUND`
|
|
600
|
+
- JSON 结构不合法:`PROVIDERS_PARSE_ERROR`
|
|
601
|
+
|
|
602
|
+
### 6.2 `current`
|
|
603
|
+
|
|
604
|
+
流程:
|
|
605
|
+
|
|
606
|
+
1. 读取 `config.toml`
|
|
607
|
+
2. 解析顶层 `profile`
|
|
608
|
+
3. 输出当前 profile
|
|
609
|
+
|
|
610
|
+
失败点:
|
|
611
|
+
|
|
612
|
+
- 文件不存在:`CONFIG_NOT_FOUND`
|
|
613
|
+
- 没有顶层 profile:`PROFILE_NOT_FOUND`
|
|
614
|
+
|
|
615
|
+
### 6.3 `switch <provider>`
|
|
616
|
+
|
|
617
|
+
这是当前最关键的命令,也是架构设计的中心流程。
|
|
618
|
+
|
|
619
|
+
流程如下:
|
|
620
|
+
|
|
621
|
+
1. CLI 层在 TTY 且 `<provider>` 缺失时,先用 selector 选 provider
|
|
622
|
+
2. 获取单操作写锁
|
|
623
|
+
3. 读取并解析 `providers.json`
|
|
624
|
+
4. 校验目标 provider 是否存在
|
|
625
|
+
5. 读取 `config.toml`
|
|
626
|
+
6. 校验 provider 对应的 `profile` 在配置中存在
|
|
627
|
+
7. 创建备份:
|
|
628
|
+
- `config.toml`
|
|
629
|
+
- `auth.json` 仅在历史备份清单已包含时由 rollback 兼容恢复
|
|
630
|
+
8. 更新顶层 `profile`
|
|
631
|
+
9. 如果未传 `--no-login`,执行 `codex login --with-api-key`
|
|
632
|
+
10. 成功后把这次备份记录为 `latest.json`
|
|
633
|
+
11. 若任何步骤失败,按 manifest 回滚
|
|
634
|
+
12. 释放写锁
|
|
635
|
+
|
|
636
|
+
#### 为什么 `switch` 必须由应用层编排
|
|
637
|
+
|
|
638
|
+
因为它同时跨越了:
|
|
639
|
+
|
|
640
|
+
- provider 仓储
|
|
641
|
+
- config 仓储
|
|
642
|
+
- 备份系统
|
|
643
|
+
- 外部 CLI 调用
|
|
644
|
+
- 失败回滚语义
|
|
645
|
+
|
|
646
|
+
这是典型的应用层编排问题,不能直接塞回 CLI 文件里。
|
|
647
|
+
|
|
648
|
+
#### `switch` 时序图
|
|
649
|
+
|
|
650
|
+
```text
|
|
651
|
+
User/Agent
|
|
652
|
+
-> CLI parseArgs
|
|
653
|
+
-> executeCommand("switch")
|
|
654
|
+
-> app/switchProvider
|
|
655
|
+
-> app/runMutation
|
|
656
|
+
-> providers-repo.readProvidersFile
|
|
657
|
+
-> config-repo.ensureProfileExists
|
|
658
|
+
-> backup-repo.createBackup
|
|
659
|
+
-> config-repo.updateTopLevelProfile
|
|
660
|
+
-> codex-cli.runCodexLogin (unless --no-login)
|
|
661
|
+
-> backup-repo.saveLatestManifest
|
|
662
|
+
-> CLI output renderer
|
|
663
|
+
|
|
664
|
+
failure path:
|
|
665
|
+
-> codex-cli.runCodexLogin throws
|
|
666
|
+
-> backup-repo.restoreManifest
|
|
667
|
+
-> app throws normalized error with rollbackApplied=true
|
|
668
|
+
-> CLI output renderer
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### `switch` 事务边界
|
|
672
|
+
|
|
673
|
+
从实现角度看,`switch` 的“事务”并不是数据库事务,而是一个文件系统级补偿事务:
|
|
674
|
+
|
|
675
|
+
- 事务开始点:
|
|
676
|
+
- 备份成功生成 manifest
|
|
677
|
+
- 事务提交点:
|
|
678
|
+
- 配置更新成功且登录成功
|
|
679
|
+
- `latest.json` 被更新为本次备份
|
|
680
|
+
- 事务补偿点:
|
|
681
|
+
- 发生异常时按 manifest 恢复文件
|
|
682
|
+
|
|
683
|
+
这是当前版本最关键的安全设计。
|
|
684
|
+
|
|
685
|
+
### 6.4 `status`
|
|
686
|
+
|
|
687
|
+
这是浅状态概览,不是深诊断。
|
|
688
|
+
|
|
689
|
+
当前输出包含:
|
|
690
|
+
|
|
691
|
+
- `codexDir`
|
|
692
|
+
- `configExists`
|
|
693
|
+
- `providersExists`
|
|
694
|
+
- `currentProfile`
|
|
695
|
+
- `currentProfileMapped`
|
|
696
|
+
- `provider`
|
|
697
|
+
- `liveState`
|
|
698
|
+
- `storage`
|
|
699
|
+
|
|
700
|
+
### 6.5 `doctor`
|
|
701
|
+
|
|
702
|
+
这是显式问题检测。
|
|
703
|
+
|
|
704
|
+
当前至少检查:
|
|
705
|
+
|
|
706
|
+
- `config.toml` 是否存在
|
|
707
|
+
- `providers.json` 是否存在
|
|
708
|
+
- `providers.json` 是否可解析
|
|
709
|
+
- provider 映射的 profile 是否存在
|
|
710
|
+
- 当前 live profile 是否已经脱离 `providers.json` 映射
|
|
711
|
+
- `codex` CLI 是否可执行
|
|
712
|
+
|
|
713
|
+
当前实现里,如果 `codex` 不可执行,会返回:
|
|
714
|
+
|
|
715
|
+
- `CODEX_LOGIN_FAILED`
|
|
716
|
+
|
|
717
|
+
从语义上看这足够满足 PRD 的“CLI 是否可执行”检查要求,但后续若要更精细,可以单独引入一个更专门的 `CODEX_CLI_NOT_FOUND` 类型。
|
|
718
|
+
|
|
719
|
+
### 6.6 `import`
|
|
720
|
+
|
|
721
|
+
流程:
|
|
722
|
+
|
|
723
|
+
1. CLI 层保留显式路径参数
|
|
724
|
+
2. TTY 中写入前先确认
|
|
725
|
+
3. 读取外部文件
|
|
726
|
+
4. 校验 JSON 和 schema
|
|
727
|
+
5. 备份当前 `providers.json`
|
|
728
|
+
6. 整体替换写入
|
|
729
|
+
7. 写失败则恢复旧文件
|
|
730
|
+
|
|
731
|
+
当前明确不支持 merge import。
|
|
732
|
+
|
|
733
|
+
### 6.7 `export`
|
|
734
|
+
|
|
735
|
+
流程:
|
|
736
|
+
|
|
737
|
+
1. 读取当前 `providers.json`
|
|
738
|
+
2. 检查目标文件是否存在
|
|
739
|
+
3. TTY 中若文件已存在且未传 `--force`,先确认是否覆盖
|
|
740
|
+
4. 默认拒绝覆盖
|
|
741
|
+
5. 传入 `--force` 或确认覆盖时允许写入
|
|
742
|
+
|
|
743
|
+
### 6.8 `add`
|
|
744
|
+
|
|
745
|
+
流程:
|
|
746
|
+
|
|
747
|
+
1. CLI 层仅在缺失必填字段且当前是 TTY 时进入交互
|
|
748
|
+
2. provider 名在 prompt 阶段尽早做重名检查
|
|
749
|
+
3. profile 优先从 `config.toml` 里解析出的现有 profile 列表选择
|
|
750
|
+
4. apiKey 通过隐藏输入采集并二次确认
|
|
751
|
+
5. 读取现有 provider 集合
|
|
752
|
+
6. 校验重名
|
|
753
|
+
7. 备份旧文件
|
|
754
|
+
8. 追加一条 provider 记录
|
|
755
|
+
9. 写回 `providers.json`
|
|
756
|
+
|
|
757
|
+
### 6.9 `remove`
|
|
758
|
+
|
|
759
|
+
流程:
|
|
760
|
+
|
|
761
|
+
1. CLI 层在 TTY 且缺少 provider 时可先选择 provider
|
|
762
|
+
2. CLI 层在 TTY 中始终做删除确认
|
|
763
|
+
3. 非 TTY 或 `--json` 场景继续要求显式 `--force`
|
|
764
|
+
4. 校验 provider 存在
|
|
765
|
+
5. 备份 `providers.json`
|
|
766
|
+
6. 删除目标 provider
|
|
767
|
+
7. 写回
|
|
768
|
+
|
|
769
|
+
### 6.10 `rollback`
|
|
770
|
+
|
|
771
|
+
流程:
|
|
772
|
+
|
|
773
|
+
1. TTY 中先读取 `backups/latest.json`
|
|
774
|
+
2. 展示备份目录和待恢复文件摘要
|
|
775
|
+
3. 请求确认
|
|
776
|
+
4. 加载最近一次 manifest
|
|
777
|
+
5. 按 manifest 恢复文件
|
|
778
|
+
6. 返回恢复文件列表和备份目录
|
|
779
|
+
|
|
780
|
+
#### `rollback` 时序图
|
|
781
|
+
|
|
782
|
+
```text
|
|
783
|
+
CLI
|
|
784
|
+
-> executeCommand("rollback")
|
|
785
|
+
-> app/rollbackLatest
|
|
786
|
+
-> backup-repo.loadLatestManifest
|
|
787
|
+
-> backup-repo.restoreManifest
|
|
788
|
+
-> output renderer
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
## 7. 输出设计
|
|
792
|
+
|
|
793
|
+
### 7.1 统一 JSON envelope
|
|
794
|
+
|
|
795
|
+
关键命令支持统一结构:
|
|
796
|
+
|
|
797
|
+
```json
|
|
798
|
+
{
|
|
799
|
+
"ok": true,
|
|
800
|
+
"command": "switch",
|
|
801
|
+
"data": {},
|
|
802
|
+
"warnings": [],
|
|
803
|
+
"error": null
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
成功和失败的 envelope 都由 `src/cli/output.ts` 统一渲染。
|
|
808
|
+
|
|
809
|
+
### 7.2 人类可读输出
|
|
810
|
+
|
|
811
|
+
人类模式下,输出尽量保持:
|
|
812
|
+
|
|
813
|
+
- 短
|
|
814
|
+
- 稳定
|
|
815
|
+
- 无敏感值
|
|
816
|
+
- 可快速扫描
|
|
817
|
+
|
|
818
|
+
当前并未做彩色输出、表格对齐或交互式 UI,这符合 MVP 的工程约束。
|
|
819
|
+
|
|
820
|
+
## 8. 错误处理设计
|
|
821
|
+
|
|
822
|
+
### 8.1 统一错误码
|
|
823
|
+
|
|
824
|
+
错误码集中定义在 `src/domain/errors.ts`,所有层最终都要收敛到这一套固定代码上。
|
|
825
|
+
|
|
826
|
+
这样做的意义是:
|
|
827
|
+
|
|
828
|
+
- AI 自动化更稳定
|
|
829
|
+
- CLI 输出更一致
|
|
830
|
+
- 测试断言更明确
|
|
831
|
+
- 后续做 GUI / HTTP / MCP 适配时不用重建错误体系
|
|
832
|
+
|
|
833
|
+
### 8.2 应用层回滚策略
|
|
834
|
+
|
|
835
|
+
当前回滚策略不是“失败就把目录整体还原”,而是:
|
|
836
|
+
|
|
837
|
+
- 在写操作之前先生成明确的 manifest
|
|
838
|
+
- 失败后按 manifest 恢复受影响文件
|
|
839
|
+
|
|
840
|
+
这样更安全,也更容易扩展到未来多文件事务。
|
|
841
|
+
|
|
842
|
+
当前进一步把“事务”固定为单操作窗口:
|
|
843
|
+
|
|
844
|
+
- 先拿锁
|
|
845
|
+
- 再备份
|
|
846
|
+
- 再变更
|
|
847
|
+
- 失败则补偿恢复
|
|
848
|
+
- 最后释放锁
|
|
849
|
+
|
|
850
|
+
### 8.3 错误传播路径
|
|
851
|
+
|
|
852
|
+
当前错误传播采用“低层抛错,高层归一”的方式:
|
|
853
|
+
|
|
854
|
+
```text
|
|
855
|
+
infra/domain throws cliError(...)
|
|
856
|
+
-> app catch/augment if needed
|
|
857
|
+
-> cli normalizeError(...)
|
|
858
|
+
-> output layer renderFailure(...)
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
其中应用层只在两类场景下增强错误:
|
|
862
|
+
|
|
863
|
+
- 需要补充上下文细节
|
|
864
|
+
- 需要把回滚结果附加到错误上
|
|
865
|
+
|
|
866
|
+
## 9. 测试架构
|
|
867
|
+
|
|
868
|
+
### 9.1 当前测试目录
|
|
869
|
+
|
|
870
|
+
```text
|
|
871
|
+
tests/
|
|
872
|
+
domain.spec.js
|
|
873
|
+
app.spec.js
|
|
874
|
+
cli.spec.js
|
|
875
|
+
helpers.js
|
|
876
|
+
run-tests.js
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
### 9.2 为什么当前没有直接使用 `node --test`
|
|
880
|
+
|
|
881
|
+
当前运行环境下,Node 内建 test runner 会触发 worker / spawn 路径上的 `EPERM` 限制,因此当前采用:
|
|
882
|
+
|
|
883
|
+
- 标准 `tests/` 目录结构
|
|
884
|
+
- 串行测试 runner `tests/run-tests.js`
|
|
885
|
+
|
|
886
|
+
这不是长期唯一方案,但在当前环境里是务实且稳定的选择。
|
|
887
|
+
|
|
888
|
+
### 9.3 当前测试覆盖
|
|
889
|
+
|
|
890
|
+
#### `domain.spec.js`
|
|
891
|
+
|
|
892
|
+
覆盖:
|
|
893
|
+
|
|
894
|
+
- profile 解析
|
|
895
|
+
- profile section 提取
|
|
896
|
+
- top-level profile 替换
|
|
897
|
+
- provider schema 校验
|
|
898
|
+
- provider 清洗与 profile 反查
|
|
899
|
+
|
|
900
|
+
#### `app.spec.js`
|
|
901
|
+
|
|
902
|
+
覆盖:
|
|
903
|
+
|
|
904
|
+
- `list/current/status`
|
|
905
|
+
- `add/export/import/remove`
|
|
906
|
+
- `switch` 成功路径
|
|
907
|
+
- `switch` 登录失败回滚
|
|
908
|
+
- `rollback`
|
|
909
|
+
- `doctor`
|
|
910
|
+
- `PROVIDER_NOT_FOUND`
|
|
911
|
+
- `PROFILE_NOT_FOUND`
|
|
912
|
+
- `INVALID_IMPORT_FILE`
|
|
913
|
+
- `PROVIDERS_NOT_FOUND`
|
|
914
|
+
- `PROVIDERS_PARSE_ERROR`
|
|
915
|
+
|
|
916
|
+
#### `cli.spec.js`
|
|
917
|
+
|
|
918
|
+
覆盖:
|
|
919
|
+
|
|
920
|
+
- argv 解析
|
|
921
|
+
- JSON success envelope
|
|
922
|
+
- JSON failure envelope
|
|
923
|
+
- `CONFIG_NOT_FOUND` 的 CLI 层失败渲染
|
|
924
|
+
|
|
925
|
+
### 9.4 测试分层策略
|
|
926
|
+
|
|
927
|
+
当前测试策略遵循:
|
|
928
|
+
|
|
929
|
+
- domain 层测纯规则
|
|
930
|
+
- app 层测命令行为和回滚语义
|
|
931
|
+
- cli 层测参数解析和输出契约
|
|
932
|
+
|
|
933
|
+
这样可以避免所有测试都退化成“起一个子进程跑命令”的高成本方式,也避免纯单元测试完全失去真实业务价值。
|
|
934
|
+
|
|
935
|
+
## 10. 打包与发布设计
|
|
936
|
+
|
|
937
|
+
### 10.1 构建脚本
|
|
938
|
+
|
|
939
|
+
当前构建脚本在:
|
|
940
|
+
|
|
941
|
+
- `scripts/build.cjs`
|
|
942
|
+
|
|
943
|
+
它先清理 `dist/`,再执行 `tsc`。这样可以避免旧编译产物残留进 npm 包。
|
|
944
|
+
|
|
945
|
+
### 10.2 npm 包入口
|
|
946
|
+
|
|
947
|
+
当前 npm 二进制入口定义在 `package.json`:
|
|
948
|
+
|
|
949
|
+
```json
|
|
950
|
+
"bin": {
|
|
951
|
+
"codexs": "dist/cli.js"
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
这保证:
|
|
956
|
+
|
|
957
|
+
- 全局安装后可执行 `codexs`
|
|
958
|
+
- `npx @minniexcode/codex-switch ...` 可以走同一入口
|
|
959
|
+
|
|
960
|
+
### 10.3 打包验证
|
|
961
|
+
|
|
962
|
+
当前工程已经通过 `npm pack --dry-run` 验证:
|
|
963
|
+
|
|
964
|
+
- `dist/` 中只包含当前源码对应的编译产物
|
|
965
|
+
- `docs/`、`README.md`、`LICENSE` 和 `package.json` 进入包
|
|
966
|
+
- 已删除的旧构建文件不会混进 tarball
|
|
967
|
+
|
|
968
|
+
### 10.4 为什么构建脚本单独放在 `scripts/build.cjs`
|
|
969
|
+
|
|
970
|
+
最初如果仅使用 `tsc`,旧的编译产物可能残留在 `dist/` 并混入 npm 包。单独脚本的意义是:
|
|
971
|
+
|
|
972
|
+
- 显式清理 `dist/`
|
|
973
|
+
- 再调用 TypeScript 编译
|
|
974
|
+
- 让打包输出和当前源码状态严格一致
|
|
975
|
+
|
|
976
|
+
## 11. 架构优点
|
|
977
|
+
|
|
978
|
+
当前架构的主要优点有:
|
|
979
|
+
|
|
980
|
+
- 命令行为和文件系统副作用解耦
|
|
981
|
+
- 输出层与业务层解耦
|
|
982
|
+
- 错误码统一
|
|
983
|
+
- 备份/回滚是独立能力,不被嵌入某一个命令实现里
|
|
984
|
+
- 测试可以分别打到 domain / app / cli 三层
|
|
985
|
+
- 后续新增命令时,能复用现有 repository、错误码和输出体系
|
|
986
|
+
|
|
987
|
+
## 12. 与 `codex-auth` / `cc-switch` 的架构对比
|
|
988
|
+
|
|
989
|
+
### 12.1 与 `codex-auth` 的对比
|
|
990
|
+
|
|
991
|
+
`codex-auth` 更接近已经产品化的 CLI 账号管理器,而 `codex-switch` 更聚焦 provider/profile 切换:
|
|
992
|
+
|
|
993
|
+
- 相同点:
|
|
994
|
+
- 都是 CLI-first
|
|
995
|
+
- 都适合 npm 分发
|
|
996
|
+
- 都适合自动化和 AI 调用
|
|
997
|
+
- 不同点:
|
|
998
|
+
- `codex-auth` 的主对象是账号 / auth
|
|
999
|
+
- `codex-switch` 的主对象是 provider/profile 和本地配置
|
|
1000
|
+
|
|
1001
|
+
因此 `codex-auth` 对本项目的主要参考价值在于“命令产品化”,不是数据模型本身。
|
|
1002
|
+
|
|
1003
|
+
### 12.2 与 `cc-switch` 的对比
|
|
1004
|
+
|
|
1005
|
+
`cc-switch` 是桌面 All-in-One manager,`codex-switch` 是轻量 CLI 工具:
|
|
1006
|
+
|
|
1007
|
+
| 维度 | `codex-switch` | `cc-switch` |
|
|
1008
|
+
| --- | --- | --- |
|
|
1009
|
+
| 产品形态 | CLI | Desktop App |
|
|
1010
|
+
| 核心对象 | provider/profile + 本地配置 | 多工具统一管理平台 |
|
|
1011
|
+
| 技术栈 | Node.js + TypeScript | React + TypeScript + Tauri + Rust |
|
|
1012
|
+
| 主数据层 | 本地文件 + 备份 manifest | SQLite + JSON |
|
|
1013
|
+
| 核心能力 | 切换、导入导出、诊断、回滚 | GUI 管理、云同步、代理、统计、会话管理 |
|
|
1014
|
+
| 适用场景 | 轻量切换、自动化、AI 调用 | 可视化统一管理 |
|
|
1015
|
+
|
|
1016
|
+
这个对比说明:
|
|
1017
|
+
|
|
1018
|
+
- `cc-switch` 值得借鉴的是架构表达方式和模块化意识
|
|
1019
|
+
- `codex-switch` 当前不应该为了“完整平台化”而抬高技术复杂度
|
|
1020
|
+
|
|
1021
|
+
## 13. 当前限制与工程债
|
|
1022
|
+
|
|
1023
|
+
当前版本仍有一些明确边界:
|
|
1024
|
+
|
|
1025
|
+
### 13.1 TOML 处理仍是轻量字符串策略
|
|
1026
|
+
|
|
1027
|
+
当前没有引入完整 TOML parser,而是:
|
|
1028
|
+
|
|
1029
|
+
- 解析顶层 `profile`
|
|
1030
|
+
- 识别 `[profiles.xxx]`
|
|
1031
|
+
- 替换顶层 `profile`
|
|
1032
|
+
|
|
1033
|
+
这对 MVP 足够,但如果未来要修改更多 TOML 字段,建议引入真正的 TOML AST 级处理。
|
|
1034
|
+
|
|
1035
|
+
### 13.2 `doctor` 的错误码还不够细
|
|
1036
|
+
|
|
1037
|
+
当前 `codex` CLI 不可执行会复用:
|
|
1038
|
+
|
|
1039
|
+
- `CODEX_LOGIN_FAILED`
|
|
1040
|
+
|
|
1041
|
+
从 MVP 可用性上没问题,但长期更推荐拆成专门的 CLI availability 错误。
|
|
1042
|
+
|
|
1043
|
+
### 13.3 当前交互式命令层范围仍然受控
|
|
1044
|
+
|
|
1045
|
+
当前 CLI 仍以显式参数模式为主,但已经把 `inquirer` 交互扩展到了高频写命令:
|
|
1046
|
+
|
|
1047
|
+
- `add` 缺失必填字段时的渐进式提问
|
|
1048
|
+
- `switch` 的 provider 选择
|
|
1049
|
+
- `remove` 的 provider 选择与确认
|
|
1050
|
+
- `import` / `export` 的危险确认
|
|
1051
|
+
- `rollback` 的恢复确认
|
|
1052
|
+
|
|
1053
|
+
当前仍然没有:
|
|
1054
|
+
|
|
1055
|
+
- 路径向导式 import/export
|
|
1056
|
+
- TUI 状态面板
|
|
1057
|
+
- 脱离显式参数契约的自动化交互
|
|
1058
|
+
|
|
1059
|
+
这继续保持了 CLI-first 和自动化优先的主体边界。
|
|
1060
|
+
|
|
1061
|
+
### 13.4 尚未引入持久化审计日志
|
|
1062
|
+
|
|
1063
|
+
当前只有:
|
|
1064
|
+
|
|
1065
|
+
- 备份文件
|
|
1066
|
+
- `latest.json`
|
|
1067
|
+
|
|
1068
|
+
没有单独的操作日志或事件审计记录。
|
|
1069
|
+
|
|
1070
|
+
## 14. 后续演进建议
|
|
1071
|
+
|
|
1072
|
+
### 14.1 短期
|
|
1073
|
+
|
|
1074
|
+
- 为 `doctor` 拆出更细的 issue code
|
|
1075
|
+
- 增加 CLI 级更多命令覆盖测试
|
|
1076
|
+
- 清理测试夹具和本地 cache 目录的仓库管理策略
|
|
1077
|
+
- 为文档增加命令级技术说明页
|
|
1078
|
+
|
|
1079
|
+
### 14.2 中期
|
|
1080
|
+
|
|
1081
|
+
- 引入真正的 TOML parser
|
|
1082
|
+
- 支持更细粒度的 provider 编辑命令
|
|
1083
|
+
- 支持 `import --merge` 等增强模式
|
|
1084
|
+
- 增加更显式的备份历史查看与指定版本恢复
|
|
1085
|
+
|
|
1086
|
+
### 14.3 长期
|
|
1087
|
+
|
|
1088
|
+
- 增加 GUI / TUI 壳层,但复用同一 application/domain/infra
|
|
1089
|
+
- 增加 MCP 或 HTTP 适配层,让 AI 工具和桌面前端复用核心逻辑
|
|
1090
|
+
- 增加多设备 / 多工作区隔离策略
|
|
1091
|
+
|
|
1092
|
+
如果未来确实需要进入桌面形态,`cc-switch` 的做法提供了一个现实参考:
|
|
1093
|
+
|
|
1094
|
+
- 前端:React + TS
|
|
1095
|
+
- 壳层:Tauri
|
|
1096
|
+
- 本地系统能力:Rust backend
|
|
1097
|
+
|
|
1098
|
+
但这应该是 `codex-switch` 在 CLI 版本稳定之后的下一阶段选择,而不是当前 MVP 的前提。
|
|
1099
|
+
|
|
1100
|
+
## 15. 结论
|
|
1101
|
+
|
|
1102
|
+
`codex-switch` 当前的技术架构已经从“单脚本/单文件 CLI”进入了一个可维护、可测试、可扩展的工程化形态。
|
|
1103
|
+
|
|
1104
|
+
它的核心技术结论可以归纳为:
|
|
1105
|
+
|
|
1106
|
+
- 使用 TypeScript + Node.js 做 CLI-first MVP 是合理的
|
|
1107
|
+
- 以 `cli / app / domain / infra` 四层作为长期架构边界是合理的
|
|
1108
|
+
- 以 `providers.json`、`config.toml`、`backups/` 为核心对象的本地事务式切换方案是合理的
|
|
1109
|
+
- 以统一错误码、统一 JSON envelope 和显式备份 manifest 支撑 AI 调用与安全回滚,是当前版本最有价值的工程资产
|
|
1110
|
+
|
|
1111
|
+
如果后续继续演进,这份架构最需要被保护的不是“具体文件名”,而是这三件事:
|
|
1112
|
+
|
|
1113
|
+
- 分层边界不能塌回单文件 CLI
|
|
1114
|
+
- 错误码和输出契约不能随意漂移
|
|
1115
|
+
- 写操作必须持续保持“先备份、后修改、失败回滚”的默认安全语义
|