@minniexcode/codex-switch 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.AI.md +5 -2
  2. package/README.md +44 -100
  3. package/dist/app/add-provider.js +28 -4
  4. package/dist/app/edit-provider.js +47 -19
  5. package/dist/app/export-providers.js +2 -2
  6. package/dist/app/get-current-profile.js +1 -1
  7. package/dist/app/get-status.js +10 -3
  8. package/dist/app/import-providers.js +15 -7
  9. package/dist/app/init-codex.js +68 -0
  10. package/dist/app/list-backups.js +1 -1
  11. package/dist/app/list-config-profiles.js +3 -2
  12. package/dist/app/list-providers.js +2 -1
  13. package/dist/app/remove-provider.js +2 -2
  14. package/dist/app/rollback-backup.js +1 -1
  15. package/dist/app/rollback-latest.js +1 -1
  16. package/dist/app/run-doctor.js +83 -6
  17. package/dist/app/run-mutation.js +2 -2
  18. package/dist/app/setup-codex.js +21 -12
  19. package/dist/app/show-config.js +11 -3
  20. package/dist/app/show-provider.js +1 -1
  21. package/dist/app/switch-provider.js +16 -9
  22. package/dist/cli/add-interactive.js +7 -104
  23. package/dist/cli/args.js +6 -135
  24. package/dist/cli/help.js +8 -313
  25. package/dist/cli/interactive.js +17 -225
  26. package/dist/cli/output.js +21 -6
  27. package/dist/cli/prompt.js +4 -106
  28. package/dist/cli.js +10 -404
  29. package/dist/commands/args.js +132 -0
  30. package/dist/commands/dispatch.js +16 -0
  31. package/dist/commands/handlers.js +460 -0
  32. package/dist/commands/help.js +120 -0
  33. package/dist/commands/registry.js +351 -0
  34. package/dist/commands/types.js +2 -0
  35. package/dist/domain/config.js +235 -21
  36. package/dist/domain/providers.js +16 -2
  37. package/dist/domain/setup.js +1 -0
  38. package/dist/infra/backup-repo.js +9 -206
  39. package/dist/infra/codex-cli.js +9 -126
  40. package/dist/infra/codex-paths.js +6 -67
  41. package/dist/infra/config-repo.js +59 -0
  42. package/dist/infra/fs-utils.js +8 -93
  43. package/dist/infra/lock-repo.js +4 -95
  44. package/dist/infra/providers-repo.js +8 -94
  45. package/dist/interaction/add-interactive.js +99 -0
  46. package/dist/interaction/interactive.js +289 -0
  47. package/dist/interaction/prompt.js +110 -0
  48. package/dist/runtime/codex-cli.js +130 -0
  49. package/dist/runtime/codex-probe.js +57 -0
  50. package/dist/runtime/types.js +2 -0
  51. package/dist/storage/auth-repo.js +160 -0
  52. package/dist/storage/backup-repo.js +210 -0
  53. package/dist/storage/codex-paths.js +71 -0
  54. package/dist/storage/config-repo.js +266 -0
  55. package/dist/storage/fs-utils.js +97 -0
  56. package/dist/storage/lock-repo.js +99 -0
  57. package/dist/storage/providers-repo.js +98 -0
  58. package/docs/Design/codex-switch-v0.0.5-design.md +32 -22
  59. package/docs/Design/codex-switch-v0.0.6-design.md +708 -0
  60. package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
  61. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +227 -89
  62. package/docs/PRD/codex-switch-prd-v0.1.0.md +200 -226
  63. package/docs/PRD/codex-switch-prd.md +1 -1
  64. package/docs/Reference/codex-config-reference.md +604 -0
  65. package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
  66. package/docs/cli-usage.md +78 -29
  67. package/docs/codex-switch-technical-architecture.md +73 -4
  68. package/docs/test-report-0.0.5.md +163 -0
  69. package/docs/test-report-0.0.7.md +118 -0
  70. package/docs/testing.md +151 -0
  71. package/package.json +1 -1
@@ -0,0 +1,862 @@
1
+ # codex-switch `0.0.7` 设计文档
2
+
3
+ ## 文档信息
4
+
5
+ - 文档类型:详细设计文档
6
+ - 适用版本:`0.0.7`
7
+ - 目标范围:`0.0.6 -> 0.0.7`
8
+ - 主对齐 PRD:[`../PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md`](../PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md)
9
+ - 当前实现基线:[`codex-switch-v0.0.6-design.md`](./codex-switch-v0.0.6-design.md)
10
+ - 风格基线:[`codex-switch-v0.0.5-design.md`](./codex-switch-v0.0.5-design.md)
11
+
12
+ ## 1. 文档目标
13
+
14
+ 这份文档回答的是 `0.0.7` 应该怎样落地,而不是继续讨论长期愿景:
15
+
16
+ - `providers.json`、`config.toml`、`auth.json` 的职责边界应该怎样正式纠偏
17
+ - 当前实现从“`providers.json.apiKey + config.toml api_key + codex login`”迁移到“`providers.json.profile/apiKey/envKey + config.toml env_key + auth.json mirror`”时,哪些行为必须定死
18
+ - `setup`、`add`、`edit`、`switch`、`doctor`、`status` 的契约应该怎样收口
19
+ - `auth.json` 的受管镜像模型、历史受管键清理策略和回滚事务边界是什么
20
+ - 当前代码结构上具体改哪些模块、补哪些错误码、补哪些 fixture 与测试
21
+
22
+ 目标是让实现阶段不再重复拍板 provider secret 应该落在哪、`env_key` 怎样派生、切换时怎样同步认证态这些关键技术决策。
23
+
24
+ ## 2. 版本定位与设计原则
25
+
26
+ ### 2.1 当前基线
27
+
28
+ 当前 `0.0.6` 已具备:
29
+
30
+ - provider registry 管理:`list`、`show`、`add`、`edit`、`remove`
31
+ - 运行态切换:`current`、`switch`
32
+ - 导入导出:`import`、`import --merge`、`export`
33
+ - 初始化与诊断:`setup`、`status`、`doctor`
34
+ - 备份恢复:`backups list`、`rollback`
35
+ - `config show` / `config list-profiles`
36
+ - 命令表面、应用用例、存储访问、运行时集成的基础分层
37
+ - 写操作统一走锁、备份、失败回滚
38
+
39
+ 当前主要问题已经不是“缺命令”,而是 provider 配置语义仍然存在错误假设:
40
+
41
+ - 把 `config.toml [model_providers.*].api_key` 当作可依赖的 provider secret 来源
42
+ - 把 `codex login` 当作 `switch` 的主线切换机制
43
+ - `providers.json` 尚未正式收纳 `envKey`
44
+ - `auth.json` 仍未被定义为“当前 active provider 的受管认证镜像”
45
+
46
+ ### 2.2 `0.0.7` 的一句话定义
47
+
48
+ `0.0.7` 的核心不是增加新命令,而是完成 Provider Configuration Correction:移除 `config.toml api_key` 假设,锁定 `env_key` 驱动的运行态路由与认证镜像模型,并让 provider registry、runtime config、auth mirror 三方一致性成为正式默认能力。
49
+
50
+ ### 2.3 设计原则
51
+
52
+ `0.0.7` 继续沿用现有工程原则:
53
+
54
+ - `CLI First`
55
+ - `Local First`
56
+ - `Safe by Default`
57
+ - `AI Friendly`
58
+ - `Split State Model`
59
+ - `Lightweight Transactions`
60
+
61
+ 在此基础上新增五条版本原则:
62
+
63
+ - 不再把 provider secret 写入 `config.toml`
64
+ - 不再把 `codex login` 作为 `switch` 主线依赖
65
+ - `envKey` 来自 runtime `env_key`,不是独立自由输入源
66
+ - `auth.json` 只镜像当前 active provider,不升级为多 provider secret 仓库
67
+ - 历史旧状态允许被识别和报告,但不承诺无条件兼容运行
68
+
69
+ ## 3. 范围与边界
70
+
71
+ ### 3.1 `0.0.7` 范围内
72
+
73
+ 本设计覆盖:
74
+
75
+ - `providers.json` 正式 schema 收口为 `profile + apiKey + envKey`
76
+ - `config.toml` 运行态字段正式收口到 `base_url + env_key`
77
+ - `auth.json` 的受管认证镜像读写与诊断
78
+ - `setup` / `add` / `edit` / `switch` / `doctor` / `status` / `show` / `list` / `config show` 的语义重构
79
+ - 切换与 setup 成功后的 config-auth 双写与事务回滚
80
+ - 共享 profile 下 active provider 唯一判定规则
81
+ - fixture、测试、过渡态识别与诊断信号升级
82
+
83
+ ### 3.2 明确不在 `0.0.7` 范围内
84
+
85
+ 下面这些内容不进入本设计:
86
+
87
+ - 多 auth mode 作为正式受管能力
88
+ - `requires_openai_auth`、command auth、网页登录等其他认证路线
89
+ - 把 `auth.json` 扩展为多 provider 缓存仓库
90
+ - 通用 `config edit` 命令族
91
+ - 自动修复所有历史不一致状态的独立 `repair` 命令
92
+ - 第三方 auth adapter 的产品化接口
93
+
94
+ ### 3.3 数据边界
95
+
96
+ `0.0.7` 直接锁定三个核心文件的职责边界:
97
+
98
+ - `providers.json`:管理态事实源,保存 provider registry,包括 `profile`、`apiKey`、`envKey`
99
+ - `config.toml`:运行态路由事实源,保存 active `profile`、`[profiles.*]` 与 `[model_providers.*].base_url/env_key`
100
+ - `auth.json`:当前 active provider 的认证运行态镜像,不是 registry,也不是长期多 provider secret 仓库
101
+
102
+ 附加约束:
103
+
104
+ - `config.toml [model_providers.*].api_key` 不再是合法受管字段
105
+ - `providers.json` 中的 `envKey` 必须与 runtime `env_key` 一致
106
+ - `auth.json` 只对当前 provider 投影,不保存其他 provider 的 managed secret
107
+
108
+ ## 4. `0.0.7` 功能总览
109
+
110
+ `0.0.7` 需要完成五件事:
111
+
112
+ - 锁定 provider registry、runtime config、auth mirror 三层的数据模型
113
+ - 让 `setup` / `add` / `edit` / `switch` 围绕 `env_key` 而不是 `api_key` 运转
114
+ - 新增 `auth.json` 的受管镜像写入、回滚和诊断能力
115
+ - 扩展 `doctor` / `status` / `show` / `list` / `config show` 的可观测性
116
+ - 明确旧 fixture、旧测试和旧运行态的识别策略,避免“半兼容、半失真”的过渡实现
117
+
118
+ ## 5. 数据模型设计
119
+
120
+ ### 5.1 `providers.json` 正式 schema
121
+
122
+ `0.0.7` 锁定正式 provider schema 为:
123
+
124
+ ```json
125
+ {
126
+ "providers": {
127
+ "packycode": {
128
+ "profile": "packycode",
129
+ "apiKey": "sk-xxx",
130
+ "envKey": "PACKYCODE_API_KEY",
131
+ "baseUrl": "https://example.com/v1",
132
+ "note": "primary route",
133
+ "tags": ["paid"]
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ 约束:
140
+
141
+ - `profile` 必填
142
+ - `apiKey` 必填
143
+ - `envKey` 必填
144
+ - `baseUrl` 仍可作为 registry 元数据保留,但不是 runtime 真值来源
145
+ - `note`、`tags` 保持原有管理属性
146
+
147
+ 产品语义:
148
+
149
+ - `profile` 表示 provider 指向的 runtime profile 名
150
+ - `apiKey` 表示当前 provider 的 managed secret
151
+ - `envKey` 表示切换后写入 `auth.json` 的目标键名,且必须对应 `config.toml [model_providers.<profile>].env_key`
152
+
153
+ ### 5.2 `config.toml` 运行态模型
154
+
155
+ `0.0.7` 下 `config.toml` 继续是“部分受管的 runtime projection”,但正式字段范围收口如下:
156
+
157
+ - 顶层 `profile = "..."`:当前 active profile
158
+ - `[profiles.<name>]`:至少受管 `model`、`model_provider`
159
+ - `[model_providers.<name>].base_url`:runtime 路由地址
160
+ - `[model_providers.<name>].env_key`:runtime 认证环境变量键名
161
+
162
+ 明确禁止:
163
+
164
+ - `[model_providers.<name>].api_key`
165
+ - 把 provider secret 持久化到 `config.toml`
166
+
167
+ 设计原因:
168
+
169
+ - runtime 路由应该由 `profile -> model_provider -> model_providers.<name>` 链路表达
170
+ - runtime secret 键名应该由 `env_key` 表达
171
+ - secret 值本身只允许由 `providers.json` 和 `auth.json` 承担,不再和 `config.toml` 混放
172
+
173
+ ### 5.3 `auth.json` 运行态镜像模型
174
+
175
+ `auth.json` 在 `0.0.7` 中被正式定义为当前 active provider 的受管认证镜像:
176
+
177
+ ```json
178
+ {
179
+ "auth_mode": "apikey",
180
+ "PACKYCODE_API_KEY": "sk-xxx"
181
+ }
182
+ ```
183
+
184
+ 约束:
185
+
186
+ - `auth_mode` 固定写入 `apikey`
187
+ - secret 键名来自当前 provider 的 `envKey`
188
+ - secret 值来自当前 provider 的 `apiKey`
189
+ - `auth.json` 不作为 provider registry 使用
190
+ - `auth.json` 不持有 inactive provider 的 managed secret
191
+
192
+ ### 5.4 `ManagedProfileView`
193
+
194
+ `ManagedProfileView` 继续是读取命令使用的稳定视图,但 `0.0.7` 起必须补充 `envKey` 解析结果:
195
+
196
+ ```ts
197
+ type ManagedProfileView = {
198
+ name: string;
199
+ managed: boolean;
200
+ isActive: boolean;
201
+ linkedProviders: string[];
202
+ model: string | null;
203
+ modelProvider: string | null;
204
+ baseUrl: string | null;
205
+ envKey: string | null;
206
+ managedFields: string[];
207
+ source: "managed" | "unmanaged" | "orphaned-reference";
208
+ };
209
+ ```
210
+
211
+ 新增语义:
212
+
213
+ - `envKey` 表示通过 `model_provider -> model_providers.<name>.env_key` 解析出的 runtime env key
214
+ - `envKey = null` 时,表示该 profile 当前不能作为完整受管 provider runtime 使用
215
+
216
+ ### 5.5 `ConfigConsistencyIssue`
217
+
218
+ `0.0.7` 的诊断抽象要在 `0.0.6` 既有问题类型基础上增加 env key 和 auth mirror 相关 issue:
219
+
220
+ ```ts
221
+ type ConfigConsistencyIssue =
222
+ | { code: "ORPHANED_PROFILE_REFERENCE"; profile: string; providers: string[] }
223
+ | { code: "UNMANAGED_ACTIVE_PROFILE"; profile: string }
224
+ | { code: "SHARED_PROFILE_REFERENCE"; profile: string; providers: string[] }
225
+ | { code: "ORPHANED_PROFILE_SECTION"; profile: string }
226
+ | { code: "DESTRUCTIVE_REMOVE_BLOCKED"; profile: string; provider: string; activeProfile: string }
227
+ | { code: "MODEL_PROVIDER_ENV_KEY_MISSING"; profile: string; modelProvider: string }
228
+ | { code: "PROVIDER_ENV_KEY_MISMATCH"; provider: string; profile: string; providerEnvKey: string; runtimeEnvKey: string | null }
229
+ | { code: "AUTH_JSON_INVALID"; reason: string }
230
+ | { code: "AUTH_JSON_ENV_KEY_MISMATCH"; provider: string; expectedEnvKey: string; actualEnvKeys: string[] }
231
+ | { code: "AUTH_JSON_APIKEY_MISMATCH"; provider: string }
232
+ | { code: "ACTIVE_PROVIDER_UNRESOLVED"; profile: string; providers: string[] };
233
+ ```
234
+
235
+ 说明:
236
+
237
+ - `MODEL_PROVIDER_ENV_KEY_MISSING`:runtime `model_providers.<name>` 缺失 `env_key`
238
+ - `PROVIDER_ENV_KEY_MISMATCH`:provider registry 的 `envKey` 与 runtime `env_key` 不一致
239
+ - `AUTH_JSON_INVALID`:`auth.json` 非法、无法解析、结构缺关键字段或存在冲突 managed secret
240
+ - `AUTH_JSON_ENV_KEY_MISMATCH`:当前 `auth.json` 写入的 managed env key 与 active provider 不一致
241
+ - `AUTH_JSON_APIKEY_MISMATCH`:当前 `auth.json` 中的 secret 值与 active provider 的 `apiKey` 不一致
242
+ - `ACTIVE_PROVIDER_UNRESOLVED`:当前 active profile 无法唯一反推 provider
243
+
244
+ ## 6. 运行态镜像与一致性规则
245
+
246
+ ### 6.1 三方一致性主规则
247
+
248
+ `0.0.7` 直接锁定以下一致性规则:
249
+
250
+ - provider 的 `profile` 必须指向存在的 runtime profile
251
+ - runtime profile 必须具备 `model`、`model_provider`
252
+ - `profiles.<name>.model_provider` 必须能解析到同名 `[model_providers.<name>]`
253
+ - runtime `model_providers.<name>` 必须具备 `base_url` 和 `env_key`
254
+ - provider 的 `envKey` 必须等于 runtime `env_key`
255
+ - 当前 active provider 的 `apiKey` / `envKey` 必须完整镜像到 `auth.json`
256
+
257
+ ### 6.2 `envKey` 派生规则
258
+
259
+ `envKey` 不是自由主输入源,而是 runtime 派生字段:
260
+
261
+ - 来源:`config.toml [model_providers.<profile>].env_key`
262
+ - 用途:持久化写回 `providers.json`,并作为 `auth.json` 键名
263
+ - 常规路径下,`add` / `edit` 不把 `--env-key` 作为主要用户输入面
264
+
265
+ 设计要求:
266
+
267
+ - `add` / `edit` / `setup adopt` 必须从 runtime 读取 `env_key`
268
+ - 如果 runtime 缺失 `env_key`,写操作失败,而不是静默继承旧值
269
+ - `edit` 仅更新 `note` / `tags` / `apiKey` 时,可保持原 `envKey`,但 `doctor` 仍需校验其与 runtime 是否一致
270
+
271
+ ### 6.3 共享 profile 与当前 provider 唯一判定
272
+
273
+ 共享 profile 在 `0.0.7` 仍然允许存在,但规则必须更明确:
274
+
275
+ - 多个 provider 可以共享同一 `profile`
276
+ - `auth.json` 只镜像当前 provider,因此每次需要写 auth mirror 时,当前 provider 必须可唯一判定
277
+ - 若仅凭 active profile 不能唯一确定 provider,则不能隐式猜测
278
+
279
+ 处理策略:
280
+
281
+ - `switch <provider>`:输入就是 provider name,因此总能唯一定位
282
+ - `setup` 完成 provider registry 初始化后,如果需要同步当前 active auth mirror,而 active profile 映射多个 provider:
283
+ - 交互模式:必须显式询问“哪个 provider 代表当前 active profile”
284
+ - 非交互模式:必须失败并报 `ACTIVE_PROVIDER_UNRESOLVED`
285
+
286
+ ### 6.4 `auth.json` 受管键集合
287
+
288
+ 设计中正式引入“受管键集合”概念:
289
+
290
+ - 当前受管固定键:`auth_mode`
291
+ - 当前受管 secret 键:当前 provider 的 `envKey`
292
+ - 历史受管 secret 键:之前由 `codex-switch` 写入的 `api_key` 或其他旧 managed env key
293
+
294
+ 分类规则:
295
+
296
+ - 受管键:`auth_mode` + 当前 provider `envKey` + 历史受管 secret 键
297
+ - 非受管键:其他未知对象或元信息,例如 `user`、`last_login`
298
+
299
+ 写入策略:
300
+
301
+ - 保留非受管元字段
302
+ - 移除旧的 managed secret 字段
303
+ - 移除旧的固定 `api_key` managed 字段
304
+ - 最终写回 `auth_mode = apikey` + 当前 provider `envKey = apiKey`
305
+
306
+ ### 6.5 `auth.json` helper 能力
307
+
308
+ 内部建议锁定以下 helper 目标接口:
309
+
310
+ ```ts
311
+ type ManagedAuthPayload = {
312
+ auth_mode: "apikey";
313
+ secretKey: string;
314
+ secretValue: string;
315
+ };
316
+
317
+ function buildManagedAuthPayload(provider: ProviderRecord): ManagedAuthPayload;
318
+ function writeAuthMirror(authPath: string, provider: ProviderRecord, existingAuthJson?: unknown): void;
319
+ function readManagedAuthState(authPath: string): ManagedAuthState;
320
+ ```
321
+
322
+ 同时建议在 storage 层增加等价能力模块,例如 `src/storage/auth-repo.ts`,至少承载:
323
+
324
+ - `readAuthFileIfExists`
325
+ - `writeAuthFile`
326
+ - `buildManagedAuthJson`
327
+ - `extractManagedAuthFingerprint`
328
+
329
+ 设计要求:
330
+
331
+ - `auth.json` 写入属于 storage,不属于 runtime
332
+ - 因为它是本地文件镜像,不是外部 CLI 调用
333
+
334
+ ## 7. 命令详细设计
335
+
336
+ ### 7.1 `setup`
337
+
338
+ `setup` 的 adoptable profile 条件收紧为同时满足:
339
+
340
+ - 有 `[profiles.<name>]`
341
+ - 有 `model`
342
+ - 有 `model_provider`
343
+ - `model_provider === profileName`
344
+ - 有同名 `[model_providers.<name>]`
345
+ - 有 `base_url`
346
+ - 有 `env_key`
347
+
348
+ `setup` 从 runtime 读取:
349
+
350
+ - `profile`
351
+ - `baseUrl`
352
+ - `envKey`
353
+
354
+ `setup` 只补问:
355
+
356
+ - `providerName`
357
+ - `apiKey`
358
+ - `note`
359
+ - `tags`
360
+
361
+ 说明:
362
+
363
+ - `baseUrl` 可以作为回显和 registry 元数据写回来源
364
+ - `envKey` 只读自 runtime,不作为常规补问字段
365
+
366
+ 成功路径:
367
+
368
+ - 写入或合并 `providers.json`
369
+ - 解析当前 active profile 所对应的当前 provider
370
+ - 立即同步当前 provider 到 `auth.json`
371
+ - 最后运行 `doctor` 或复用同等级一致性校验,确保初始状态可诊断
372
+
373
+ 特殊规则:
374
+
375
+ - 若当前 active profile 无法唯一映射到 provider:
376
+ - 交互模式补问选择 provider
377
+ - 非交互模式失败
378
+
379
+ ### 7.2 `add`
380
+
381
+ `add` 仍要求:
382
+
383
+ - `providerName`
384
+ - `profile`
385
+ - `apiKey`
386
+
387
+ `add` 的 `envKey` 规则:
388
+
389
+ - 不作为显式主要输入
390
+ - 从 `config.toml [model_providers.<profile>].env_key` 读取
391
+ - 若对应 runtime section 缺失 `env_key`,则失败
392
+
393
+ `baseUrl` 规则:
394
+
395
+ - `--base-url` 仍可作为 registry 元数据
396
+ - 但它不代替 runtime `base_url`
397
+ - `doctor` 可继续校验 registry `baseUrl` 与 runtime `base_url` 的偏差,但 runtime 真值仍以 `config.toml` 为准
398
+
399
+ ### 7.3 `edit`
400
+
401
+ `edit` 的核心行为调整:
402
+
403
+ - `apiKey` 仍可编辑
404
+ - `note`、`tags` 仍可编辑
405
+ - `profile` 可编辑
406
+
407
+ 当 `profile` 变更时:
408
+
409
+ - 新的 `envKey` 必须从目标 profile runtime section 重新解析并覆盖
410
+ - 不允许用户把 provider 挂到缺少 `env_key` 的 runtime section
411
+
412
+ 当仅更新 `note` / `tags` / `apiKey` 时:
413
+
414
+ - 原有 `envKey` 保持
415
+ - `doctor` 仍负责识别与 runtime `env_key` 的不一致
416
+
417
+ ### 7.4 `switch`
418
+
419
+ `switch` 输入仍然是 provider name,而不是 profile。
420
+
421
+ `0.0.7` 起行为固定为:
422
+
423
+ - 更新 active profile
424
+ - 重写 `auth.json`
425
+
426
+ 不再保留的主线语义:
427
+
428
+ - `runCodexLogin`
429
+ - “切换后再走外部 login 才算完成”
430
+
431
+ `--no-login` 处理建议:
432
+
433
+ - 设计层不再把它当作有效主线契约
434
+ - 可在实现中保留为 deprecated parse 兼容项,但不得再影响核心行为
435
+
436
+ 切换成功的最小定义:
437
+
438
+ - `config.toml` 顶层 active profile 已更新
439
+ - `auth.json` 已镜像当前 provider 的 `auth_mode + envKey/apiKey`
440
+
441
+ ### 7.5 `show` / `list`
442
+
443
+ 这两个命令继续展示 provider registry,但输出应新增:
444
+
445
+ - `envKey`
446
+
447
+ 输出约束:
448
+
449
+ - 默认文本输出继续掩码 `apiKey`
450
+ - `envKey` 不需要掩码
451
+ - 如有 `baseUrl`,继续作为 registry 元数据展示
452
+
453
+ ### 7.6 `config show`
454
+
455
+ `config show` 继续展示:
456
+
457
+ - active profile
458
+ - managed profile / runtime baseUrl
459
+
460
+ `0.0.7` 起新增展示:
461
+
462
+ - runtime `envKey`
463
+ - 如能解析 provider link,可展示 managed provider `envKey` 与 runtime `env_key` 是否一致
464
+
465
+ ### 7.7 `doctor`
466
+
467
+ `doctor` 需要新增三类诊断面:
468
+
469
+ - provider profile 是否存在且可解析 runtime section
470
+ - runtime `env_key` 是否存在
471
+ - `auth.json` 当前镜像是否与 active provider 一致
472
+
473
+ 新增 issue 类型至少包括:
474
+
475
+ - `MODEL_PROVIDER_ENV_KEY_MISSING`
476
+ - `PROVIDER_ENV_KEY_MISMATCH`
477
+ - `AUTH_JSON_INVALID`
478
+ - `AUTH_JSON_ENV_KEY_MISMATCH`
479
+ - `AUTH_JSON_APIKEY_MISMATCH`
480
+ - `ACTIVE_PROVIDER_UNRESOLVED`
481
+
482
+ 设计要求:
483
+
484
+ - `doctor` 可以识别旧 `config.toml api_key`、旧 `providers.json` 无 `envKey`、旧 `auth.json api_key` 结构
485
+ - 但这些旧状态以“发现问题并报告”为目标,而不是继续被当作健康运行态
486
+
487
+ ### 7.8 `status`
488
+
489
+ `status` 继续做轻量摘要,但要补充当前 auth mirror 概况:
490
+
491
+ - 当前 active profile
492
+ - 当前 active provider 是否可唯一判定
493
+ - runtime `env_key` 是否可解析
494
+ - `auth.json` 是否看起来与当前 active provider 一致
495
+
496
+ 约束:
497
+
498
+ - `status` 只输出摘要,不展开完整 doctor 级问题列表
499
+
500
+ ## 8. 错误语义
501
+
502
+ `0.0.7` 需要明确几类新失败语义:
503
+
504
+ - runtime profile 存在,但缺少 `model_provider` 或对应 `model_providers.<name>`
505
+ - runtime `model_providers.<name>` 缺少 `env_key`
506
+ - provider registry 中缺少 `envKey`
507
+ - 当前 active profile 无法唯一映射到 provider
508
+ - `auth.json` 无法解析或存在冲突 managed secret
509
+
510
+ 建议新增或收敛的错误码语义:
511
+
512
+ - `MODEL_PROVIDER_ENV_KEY_MISSING`
513
+ - `PROVIDER_ENV_KEY_REQUIRED`
514
+ - `ACTIVE_PROVIDER_UNRESOLVED`
515
+ - `AUTH_JSON_INVALID`
516
+ - `AUTH_JSON_SYNC_FAILED`
517
+
518
+ 错误处理要求:
519
+
520
+ - 非交互 `setup` 在缺 secret 或无法唯一判定 active provider 时必须失败
521
+ - `switch` 在 config 写入或 auth mirror 写入任一失败时必须整体失败并回滚
522
+ - `doctor` 不因历史旧状态崩溃,而应结构化产出问题列表
523
+
524
+ ## 9. 模块设计与代码落点
525
+
526
+ ### 9.1 `src/domain/providers.ts`
527
+
528
+ 本模块需要收口到 `0.0.7` 的正式 provider schema:
529
+
530
+ - `ProviderRecord` 增加 `envKey: string`
531
+ - validation / normalization 强制校验 `envKey`
532
+ - 旧 provider 数据缺失 `envKey` 时,应在读取或 doctor 路径上被识别为异常状态
533
+
534
+ ### 9.2 `src/domain/config.ts`
535
+
536
+ 本模块需要完成以下调整:
537
+
538
+ - `ModelProviderSectionRef` 改为正式解析 `env_key`
539
+ - 删除 `api_key` 相关字段的正式读取模型
540
+ - `ManagedProfileView` 增加 `envKey`
541
+ - `ConfigConsistencyIssue` 增加 env key / auth mirror 相关 issue code
542
+
543
+ ### 9.3 `src/storage/config-repo.ts`
544
+
545
+ storage 层要把 `env_key` 视为正式前置条件:
546
+
547
+ - `requireModelProviderRuntimeSection`
548
+ - `requireManagedProfileRuntime`
549
+
550
+ 这些 helper 在 `0.0.7` 中应明确要求:
551
+
552
+ - `base_url` 存在
553
+ - `env_key` 存在
554
+
555
+ ### 9.4 `src/storage/auth-repo.ts`
556
+
557
+ 建议新增或等价落点能力模块,职责锁定为:
558
+
559
+ - `readAuthFileIfExists`
560
+ - `writeAuthFile`
561
+ - `buildManagedAuthJson`
562
+ - `extractManagedAuthFingerprint`
563
+
564
+ 原因:
565
+
566
+ - `auth.json` 写入是本地文件镜像操作
567
+ - 它属于 storage concern,而不是 runtime CLI 调用
568
+
569
+ ### 9.5 `src/app/setup-codex.ts`
570
+
571
+ 需要调整:
572
+
573
+ - `providerDetailsByProfile` shape 增加 `envKey`
574
+ - adopt 校验改为依赖 `env_key`
575
+ - setup 成功后同步 `auth.json`
576
+ - 当 active profile 对应多个 provider 时,走显式选择或失败语义
577
+
578
+ ### 9.6 `src/app/switch-provider.ts`
579
+
580
+ 需要调整:
581
+
582
+ - 删除 `runCodexLogin` 主路径
583
+ - 切换改为 `config.toml + auth.json` 双写事务
584
+ - 失败时回滚两个文件
585
+
586
+ ### 9.7 `src/app/run-doctor.ts`
587
+
588
+ 需要新增:
589
+
590
+ - auth mirror 检查
591
+ - provider `envKey` 与 runtime `env_key` 的一致性检查
592
+ - 当前 active provider 是否可唯一判定的检查
593
+
594
+ ### 9.8 `src/commands/handlers.ts`
595
+
596
+ 命令处理层需要收口:
597
+
598
+ - `setup` adopt 数据从 config 读取 `envKey`
599
+ - `add` / `edit` 不再围绕 `config api_key default` 设计
600
+ - `switch` 不再向核心 use case 传递 `noLogin`
601
+
602
+ ### 9.9 `src/interaction/interactive.ts`
603
+
604
+ 交互层需要调整:
605
+
606
+ - `collectSetupProviderDetails` 改成只补 `providerName`、`apiKey`、`baseUrl?`、`note`、`tags`
607
+ - prompt hint 中可显示 runtime `envKey`
608
+ - 共享 profile 场景下增加 active provider 选择能力
609
+
610
+ ### 9.10 `src/cli/output.ts`
611
+
612
+ 输出层需要调整:
613
+
614
+ - `show` / `list` / `switch` 输出增加 `envKey`
615
+ - 删除 `loginPerformed` 之类的切换结果表达
616
+ - `status` / `config show` 增加 auth mirror 摘要和 runtime `envKey`
617
+
618
+ ### 9.11 `src/runtime/codex-cli.ts`
619
+
620
+ 运行时集成层需要收口:
621
+
622
+ - `runCodexLogin` 不再是 `switch` 主路径依赖
623
+ - 仍可保留 runtime probe / version check 能力
624
+
625
+ ## 10. 关键流程时序
626
+
627
+ ### 10.1 `setup`
628
+
629
+ 建议时序:
630
+
631
+ ```text
632
+ setup
633
+ -> choose codex dir
634
+ -> parse config.toml
635
+ -> find adoptable profiles with base_url + env_key
636
+ -> collect providerName/apiKey/note/tags
637
+ -> write providers.json
638
+ -> resolve current active provider
639
+ -> write auth.json mirror
640
+ -> run doctor
641
+ ```
642
+
643
+ 关键点:
644
+
645
+ - adopt 只依赖 runtime `base_url + env_key`
646
+ - 不再从 config 读取 `api_key`
647
+ - 当前 active provider 无法唯一定位时,不允许静默挑一个
648
+
649
+ ### 10.2 `add` / `edit`
650
+
651
+ 建议时序:
652
+
653
+ ```text
654
+ add/edit
655
+ -> read providers.json
656
+ -> resolve target profile
657
+ -> parse config.toml runtime section
658
+ -> require env_key
659
+ -> derive envKey from runtime
660
+ -> write providers.json
661
+ -> if current active provider affected and needs sync, update auth.json
662
+ ```
663
+
664
+ 关键点:
665
+
666
+ - `profile` 变更时必须重新解析 `envKey`
667
+ - 只改 `apiKey` 且目标是当前 active provider 时,应同步 auth mirror
668
+
669
+ ### 10.3 `switch`
670
+
671
+ 建议时序:
672
+
673
+ ```text
674
+ switch <provider>
675
+ -> read providers.json
676
+ -> resolve provider by name
677
+ -> validate profile + runtime env_key
678
+ -> backup config.toml + auth.json
679
+ -> update top-level profile in config.toml
680
+ -> rewrite auth.json with auth_mode + envKey/apiKey
681
+ -> success
682
+ -> on failure rollback both files
683
+ ```
684
+
685
+ 关键点:
686
+
687
+ - `switch` 不再调用 `codex login`
688
+ - 成功标准是 config-auth 双写完成
689
+ - 任一文件写失败都必须触发事务级回滚
690
+
691
+ ### 10.4 `doctor`
692
+
693
+ 建议时序:
694
+
695
+ ```text
696
+ doctor
697
+ -> read providers.json
698
+ -> parse config.toml
699
+ -> read auth.json
700
+ -> resolve active profile
701
+ -> resolve active provider if possible
702
+ -> compare providers/config/auth consistency
703
+ -> emit issue list
704
+ ```
705
+
706
+ 关键点:
707
+
708
+ - `doctor` 同时检查 providers、config、auth 三方状态
709
+ - 对旧结构做识别,不把旧结构误报为健康状态
710
+
711
+ ## 11. fixture 与测试设计
712
+
713
+ ### 11.1 fixture 迁移
714
+
715
+ `dev-codex/local-sandbox` 需要整体迁移到 `0.0.7` 模型:
716
+
717
+ - `config.toml` 改用 `env_key`
718
+ - `providers.json` 增加 `envKey`
719
+ - `auth.json` 改成 `auth_mode + <envKey>`
720
+
721
+ 目标是让主开发 fixture 能健康通过:
722
+
723
+ - `config show`
724
+ - `list`
725
+ - `current`
726
+ - `doctor`
727
+
728
+ ### 11.2 测试重写范围
729
+
730
+ 测试层至少要覆盖以下变更:
731
+
732
+ - `tests/workflows.spec.js`
733
+ - `tests/interaction.spec.js`
734
+ - `tests/commands.spec.js`
735
+ - 现有 domain / app 相关 spec
736
+
737
+ 具体关注点:
738
+
739
+ - fixture 不再含 `config.toml api_key`
740
+ - setup 回归从“继承 config api_key 默认值”改成“从 config 读取 `env_key` 并补问 `apiKey`”
741
+ - switch 回归从“login rollback”改成“auth mirror rollback”
742
+
743
+ ### 11.3 Domain 测试
744
+
745
+ 至少新增或重写:
746
+
747
+ - provider schema 强制 `envKey`
748
+ - config parser 解析 `env_key`,不再依赖 `api_key`
749
+ - doctor issue code 覆盖 env key / auth mirror 不一致
750
+
751
+ ### 11.4 Application 测试
752
+
753
+ 至少新增或重写:
754
+
755
+ - `setup` adopt 合法 `env_key` profile
756
+ - `add` / `edit` 从 runtime 派生 `envKey`
757
+ - `switch` 写入 `auth.json` 并在失败时回滚
758
+ - 当前 active provider 受影响时,`edit apiKey` 后同步 auth mirror
759
+
760
+ ### 11.5 CLI / interaction 测试
761
+
762
+ 至少新增或重写:
763
+
764
+ - `setup` 仅补问 `apiKey`
765
+ - `collectSetupProviderDetails` / `collectEditInput` 的字段与默认值逻辑
766
+ - `show` / `list` / `switch` 输出新增 `envKey`
767
+ - 非交互 `setup` 在缺 secret 或无法唯一判定 active provider 时失败
768
+
769
+ ### 11.6 共享 profile 场景测试
770
+
771
+ 必须显式覆盖:
772
+
773
+ - 共享 profile 下 `switch <provider>` 仍可唯一写出对应 `auth.json`
774
+ - 仅凭 active profile 无法唯一反推 provider 时,`setup` 同步当前 auth 必须交互补问或非交互失败
775
+
776
+ ## 12. 迁移与过渡态识别
777
+
778
+ `0.0.7` 的目标是纠偏,不是对历史错误模型继续背书。
779
+
780
+ 因此本版明确采用“识别并报告”的过渡策略,而不是“无限兼容”策略。
781
+
782
+ ### 12.1 需要识别的旧状态
783
+
784
+ 当前 repo 内与用户本地运行态都可能存在:
785
+
786
+ - `config.toml` 仍含 `api_key`
787
+ - `providers.json` 缺 `envKey`
788
+ - `auth.json` 仍是旧 `api_key` 结构
789
+ - 旧 fixture、旧测试仍围绕 login 语义断言
790
+
791
+ ### 12.2 识别策略
792
+
793
+ `doctor` / `status` 需要能识别并报告:
794
+
795
+ - runtime 仍依赖 `api_key`
796
+ - provider record 缺 `envKey`
797
+ - `auth.json` 仍是旧 managed `api_key` 结构
798
+ - active profile 无法唯一映射到 provider
799
+ - runtime 缺 `env_key`
800
+
801
+ ### 12.3 非兼容边界
802
+
803
+ `0.0.7` 不要求:
804
+
805
+ - 旧 `config.toml api_key` 状态继续被当作健康写路径
806
+ - 旧 `providers.json` 无 `envKey` 状态继续被静默补全后成功运行
807
+ - 旧 `auth.json api_key` 状态继续被视为正确镜像
808
+
809
+ 换句话说:
810
+
811
+ - 允许发现
812
+ - 允许诊断
813
+ - 不承诺继续以旧模型成功完成所有写操作
814
+
815
+ ## 13. 验收标准
816
+
817
+ `0.0.7` 完成时,至少满足以下验收条件:
818
+
819
+ - `providers.json` 新增 provider 时必须持久化 `envKey`
820
+ - `add` / `edit` / `setup` 都从 runtime `env_key` 派生 `envKey`
821
+ - `switch` 成功后,`config.toml` active profile 与 `auth.json` 镜像同时更新
822
+ - `switch` 任一阶段失败时,`config.toml` 与 `auth.json` 均可回滚
823
+ - `doctor` 能识别 `MODEL_PROVIDER_ENV_KEY_MISSING`
824
+ - `doctor` 能识别 `PROVIDER_ENV_KEY_MISMATCH`
825
+ - `doctor` 能识别 `AUTH_JSON_INVALID`
826
+ - `doctor` 能识别 `AUTH_JSON_ENV_KEY_MISMATCH`
827
+ - `doctor` 能识别 `AUTH_JSON_APIKEY_MISMATCH`
828
+ - `doctor` 能识别 `ACTIVE_PROVIDER_UNRESOLVED`
829
+ - `show` / `list` / `config show` / `status` 能输出 `envKey` 或其摘要
830
+ - 新 fixture 健康通过主读命令验证
831
+
832
+ 建议固定的验收式场景:
833
+
834
+ - `env_key = OPENAI_API_KEY`
835
+ - `env_key = PACKYCODE_API_KEY`
836
+ - active profile 映射单 provider
837
+ - active profile 映射多 provider
838
+ - `auth.json` 旧结构存在 `api_key`
839
+ - runtime 缺 `env_key`
840
+
841
+ ## 14. Deferred
842
+
843
+ 以下内容明确延后,不进入 `0.0.7`:
844
+
845
+ - 多 auth mode 管理
846
+ - `requires_openai_auth` 等其他认证模型
847
+ - 远端 secret store 或系统 keychain
848
+ - 自动迁移历史旧状态的一键修复器
849
+ - 以 provider 为维度缓存多个 inactive auth mirror
850
+
851
+ ## 15. 结论
852
+
853
+ `0.0.7` 不是一次“再加几个命令”的版本,而是一次配置语义纠偏版本。
854
+
855
+ 它的核心成果应当是:
856
+
857
+ - `providers.json` 成为 provider secret 与 `envKey` 的管理态事实源
858
+ - `config.toml` 回到 runtime route 与 `env_key` 的职责边界
859
+ - `auth.json` 成为当前 active provider 的单一受管认证镜像
860
+ - `setup`、`add`、`edit`、`switch`、`doctor` 围绕这一模型形成稳定且可回滚的实现
861
+
862
+ 只有先把这条主线收口,后续 `0.1.0` 的稳定契约、第三方 auth 扩展和 runtime integration 边界才有可靠基础。