@minniexcode/codex-switch 0.1.0 → 0.1.2

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 (68) hide show
  1. package/README.AI.md +141 -110
  2. package/README.CN.md +215 -179
  3. package/README.md +224 -183
  4. package/dist/app/add-provider.js +16 -23
  5. package/dist/app/bridge.js +2 -1
  6. package/dist/app/edit-provider.js +30 -65
  7. package/dist/app/get-current-profile.js +15 -3
  8. package/dist/app/get-status.js +11 -8
  9. package/dist/app/list-config-profiles.js +3 -1
  10. package/dist/app/list-providers.js +10 -4
  11. package/dist/app/remove-provider.js +52 -19
  12. package/dist/app/run-doctor.js +26 -29
  13. package/dist/app/setup-codex.js +3 -3
  14. package/dist/app/show-config.js +3 -1
  15. package/dist/app/switch-provider.js +38 -6
  16. package/dist/cli/output.js +29 -19
  17. package/dist/commands/handlers.js +3 -2
  18. package/dist/commands/help.js +3 -3
  19. package/dist/commands/registry.js +29 -29
  20. package/dist/domain/config.js +293 -209
  21. package/dist/domain/providers.js +8 -0
  22. package/dist/domain/runtime-state.js +15 -15
  23. package/dist/domain/setup.js +3 -1
  24. package/dist/interaction/interactive.js +2 -2
  25. package/dist/runtime/codex-version.js +7 -0
  26. package/dist/runtime/copilot-adapter.js +326 -70
  27. package/dist/runtime/copilot-bridge-worker.js +27 -2
  28. package/dist/runtime/copilot-bridge.js +192 -10
  29. package/dist/runtime/copilot-cli.js +7 -0
  30. package/dist/runtime/copilot-installer.js +59 -1
  31. package/dist/runtime/copilot-sdk-loader.js +4 -1
  32. package/dist/storage/config-repo.js +6 -14
  33. package/docs/Design/codex-switch-v0.1.0-design.md +32 -152
  34. package/docs/Design/codex-switch-v0.1.1-design.md +22 -0
  35. package/docs/Design/codex-switch-v0.1.2-design.md +65 -0
  36. package/docs/PRD/codex-switch-prd-v0.1.0.md +65 -217
  37. package/docs/PRD/codex-switch-prd-v0.1.1.md +26 -0
  38. package/docs/PRD/codex-switch-prd-v0.1.2.md +41 -0
  39. package/docs/Reference/codex-config-reference.md +41 -0
  40. package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
  41. package/docs/Tests/testing.md +1 -1
  42. package/docs/cli-usage.md +290 -223
  43. package/docs/codex-switch-command-design.md +2 -2
  44. package/docs/codex-switch-product-overview.md +18 -13
  45. package/docs/codex-switch-product-research.md +2 -2
  46. package/docs/codex-switch-technical-architecture.md +84 -1115
  47. package/package.json +2 -2
  48. package/docs/Design/codex-switch-copilot-integration-design.md +0 -517
  49. package/docs/Design/codex-switch-v0.0.10-design.md +0 -669
  50. package/docs/Design/codex-switch-v0.0.11-design.md +0 -824
  51. package/docs/Design/codex-switch-v0.0.12-design.md +0 -343
  52. package/docs/Design/codex-switch-v0.0.4-design.md +0 -874
  53. package/docs/Design/codex-switch-v0.0.5-design.md +0 -932
  54. package/docs/Design/codex-switch-v0.0.6-design.md +0 -708
  55. package/docs/Design/codex-switch-v0.0.7-design.md +0 -862
  56. package/docs/Design/codex-switch-v0.0.8-design.md +0 -132
  57. package/docs/Design/codex-switch-v0.0.9-design.md +0 -182
  58. package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +0 -413
  59. package/docs/PRD/codex-switch-prd-v0.0.10.md +0 -406
  60. package/docs/PRD/codex-switch-prd-v0.0.11.md +0 -577
  61. package/docs/PRD/codex-switch-prd-v0.0.12.md +0 -279
  62. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +0 -446
  63. package/docs/PRD/codex-switch-prd-v0.0.8.md +0 -62
  64. package/docs/PRD/codex-switch-prd-v0.0.9.md +0 -166
  65. package/docs/PRD/codex-switch-prd.md +0 -650
  66. package/docs/Tests/test-report-0.0.5.md +0 -163
  67. package/docs/Tests/test-report-0.0.7.md +0 -118
  68. package/docs/Tests/testing-bridge-v0.0.9.md +0 -367
@@ -1,932 +0,0 @@
1
- # codex-switch `0.0.5` 设计文档
2
-
3
- ## 文档信息
4
-
5
- - 文档类型:详细设计文档
6
- - 适用版本:`0.0.5`
7
- - 目标范围:`0.0.4 -> 0.0.5`
8
- - 主对齐 PRD:[`../PRD/codex-switch-prd-v0.1.0.md`](../PRD/codex-switch-prd-v0.1.0.md)
9
- - 远期边界参考:[`../PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md`](../PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md)
10
- - 风格基线:[`codex-switch-v0.0.4-design.md`](./codex-switch-v0.0.4-design.md)
11
-
12
- ## 1. 文档目标
13
-
14
- 这份文档回答的是 `0.0.5` 应该怎样落地,而不是继续讨论长期愿景:
15
-
16
- - `config.toml` 怎样从字符串级切换升级为结构化读取与局部受控写回
17
- - provider 管理命令怎样与 linked profile sections 保持一致
18
- - `config show` / `config list-profiles` 的命令契约和返回边界是什么
19
- - 历史状态、不一致状态、adopt / repair 流程怎样进入现有 CLI
20
- - 当前代码结构上具体改哪些模块、补哪些错误码、补哪些测试
21
-
22
- 目标是让实现阶段不再重复拍板关键技术决策。
23
-
24
- ## 2. 版本定位与设计原则
25
-
26
- ### 2.1 当前基线
27
-
28
- 当前 `0.0.4` 已具备:
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
- - 统一 JSON envelope
36
- - 写操作统一走锁、备份、失败回滚
37
-
38
- 当前实现短板主要在 `config.toml`:
39
-
40
- - 仅支持浅层读取顶层 active `profile`
41
- - profile section 仍靠轻量字符串匹配
42
- - 不能安全维护注释、空行和未受管内容
43
- - provider 写命令没有稳定的 provider-config 双写一致性模型
44
-
45
- ### 2.2 `0.0.5` 的一句话定义
46
-
47
- `0.0.5` 的核心不是再堆一批命令,而是建立 `config.toml` 的结构化读取、provider-linked profile 管理和一致性诊断能力。
48
-
49
- ### 2.3 设计原则
50
-
51
- `0.0.5` 继续沿用现有工程原则:
52
-
53
- - `CLI First`
54
- - `Local First`
55
- - `Safe by Default`
56
- - `AI Friendly`
57
- - `Split State Model`
58
- - `Lightweight Transactions`
59
-
60
- 在此基础上新增四条版本原则:
61
-
62
- - 不把 `config.toml` 提升为 full config editor
63
- - 不新增独立 `repair` 命令
64
- - 不做全量 TOML parse -> stringify 写回
65
- - 所有 provider-config 双写仍必须纳入同一备份与回滚事务
66
-
67
- ## 3. 范围与边界
68
-
69
- ### 3.1 `0.0.5` 范围内
70
-
71
- 本设计覆盖:
72
-
73
- - `config.toml` 结构化读取
74
- - `[profiles.<name>]` 的最小受管写入能力
75
- - 顶层 active `profile` 与 provider-linked profile section 的一致性
76
- - `config show`
77
- - `config list-profiles`
78
- - `add` / `edit` / `remove` / `setup` / `import --merge` 的 config-aware 升级
79
- - `doctor` / `status` 的一致性信号升级
80
- - 多候选 Codex 目录发现与交互选择
81
-
82
- ### 3.2 明确不在 `0.0.5` 范围内
83
-
84
- 下面这些内容不进入本设计:
85
-
86
- - 通用 `config edit` 命令族
87
- - `model` / `base_url` 之外的大 profile schema 首版规格
88
- - 独立 `repair` 命令
89
- - 第三方 auth / extension 集成
90
- - 任意顶层 TOML 键的自由增删改
91
- - 自动化的“全量历史 profile 收编”
92
-
93
- ### 3.3 数据边界
94
-
95
- `0.0.5` 继续坚持状态分层:
96
-
97
- - `providers.json`:provider registry 的单一事实源
98
- - `config.toml`:运行态配置投影,部分受管
99
- - `auth.json`:认证态
100
- - `backups/` + `latest.json`:恢复态
101
-
102
- 不引入数据库,不引入新的长期状态仓库。
103
-
104
- ## 4. `0.0.5` 功能总览
105
-
106
- `0.0.5` 需要完成四件事:
107
-
108
- - 引入结构化 TOML 读取与非破坏性局部 patch 写回能力
109
- - 增加稳定的 config 读取命令,供人类、AI 和脚本消费
110
- - 让 provider 写命令同步维护 linked profile sections
111
- - 补齐历史状态、不一致状态、共享 profile 和安全删除规则
112
-
113
- ## 5. 数据模型设计
114
-
115
- ### 5.1 `ManagedProfileFields`
116
-
117
- `ManagedProfileFields` 表示真正写入 `[profiles.<name>]` 的受管字段。
118
-
119
- `0.0.5` 只正式锁定:
120
-
121
- ```ts
122
- type ManagedProfileFields = {
123
- model: string;
124
- modelProvider: string;
125
- };
126
- ```
127
-
128
- 规则:
129
-
130
- - `model` 和 `model_provider` 是当前唯一正式受管 profile 字段
131
- - 写命令创建 profile section 时,必须同时具备 `model` 和 `model_provider`
132
- - 未提供任一必需字段时,不允许创建新的受管 section
133
- - `apiKey` 继续只保存在 `providers.json`
134
- - `note`、`tags` 等 provider 管理字段不进入 `config.toml`
135
- - `[model_providers.<name>].base_url` 继续由 Codex runtime config 承载,`codex-switch` 首版只读校验,不负责创建
136
-
137
- 字段归属直接锁定如下:
138
-
139
- - `[profiles.<name>]`:`model`、`model_provider`
140
- - `[model_providers.<name>]`:`base_url`
141
- - `providers.json`:`profile`、`apiKey`、可选 `baseUrl`、`note`、`tags`
142
- - `auth.json`:当前激活 provider 对应的运行态认证内容
143
-
144
- 设计原因:
145
-
146
- - 你的使用场景是“中转站”,真正的 runtime 路由由 `profiles.<name>.model_provider -> model_providers.<name>.base_url` 共同定义
147
- - 如果只维护 provider 名称映射,而不校验 `model_provider` 与 `model_providers` 的一致性,切换后就可能出现 profile 名变了但 runtime endpoint 仍落到错误上游的分裂状态
148
- - 因此 `0.0.5` 需要把 `model_provider` 视为 profile runtime projection 的正式字段,并把 `base_url` 作为解析视图与一致性校验的一部分
149
-
150
- ### 5.2 `ManagedProfileView`
151
-
152
- `ManagedProfileView` 是读取命令返回的稳定视图,不等同于持久化结构。
153
-
154
- 建议内部和输出层统一围绕以下字段:
155
-
156
- ```ts
157
- type ManagedProfileView = {
158
- name: string;
159
- managed: boolean;
160
- isActive: boolean;
161
- linkedProviders: string[];
162
- model: string | null;
163
- modelProvider: string | null;
164
- baseUrl: string | null;
165
- managedFields: string[];
166
- source: "managed" | "unmanaged" | "orphaned-reference";
167
- };
168
- ```
169
-
170
- 字段语义:
171
-
172
- - `name`:profile 名
173
- - `managed`:是否至少被一个 provider 引用
174
- - `isActive`:是否为顶层 active profile
175
- - `linkedProviders`:引用该 profile 的 provider 名列表
176
- - `model`:可识别的受管 `model` 值;不存在或不受管时为 `null`
177
- - `modelProvider`:可识别的受管 `model_provider` 值;不存在或不受管时为 `null`
178
- - `baseUrl`:通过 `model_provider -> model_providers.<name>.base_url` 解析出的 runtime endpoint;缺失时为 `null`
179
- - `managedFields`:当前识别到并纳入正式受管的字段名数组;`0.0.5` 只可能为 `[]`、`["model"]`、`["model_provider"]` 或 `["model", "model_provider"]`
180
- - `source`:
181
- - `managed`:section 存在且被 provider 引用
182
- - `unmanaged`:section 存在但没有 provider 引用
183
- - `orphaned-reference`:provider 引用了该 profile,但 `config.toml` 中缺少对应 section
184
-
185
- ### 5.3 `ConfigConsistencyIssue`
186
-
187
- `ConfigConsistencyIssue` 是 `doctor` / `status` 的问题抽象。
188
-
189
- 最少覆盖以下问题类型:
190
-
191
- ```ts
192
- type ConfigConsistencyIssue =
193
- | { code: "ORPHANED_PROFILE_REFERENCE"; profile: string; providers: string[] }
194
- | { code: "UNMANAGED_ACTIVE_PROFILE"; profile: string }
195
- | { code: "SHARED_PROFILE_REFERENCE"; profile: string; providers: string[] }
196
- | { code: "ORPHANED_PROFILE_SECTION"; profile: string }
197
- | { code: "DESTRUCTIVE_REMOVE_BLOCKED"; profile: string; provider: string; activeProfile: string };
198
- ```
199
-
200
- 说明:
201
-
202
- - `ORPHANED_PROFILE_REFERENCE`:`providers.json` 引用了不存在的 profile section
203
- - `UNMANAGED_ACTIVE_PROFILE`:当前 active profile 存在,但没有 provider 映射
204
- - `SHARED_PROFILE_REFERENCE`:多个 provider 指向同一 profile;本身不一定是错误,但必须被识别
205
- - `ORPHANED_PROFILE_SECTION`:`config.toml` 存在 profile section,但没有 provider 引用
206
- - `DESTRUCTIVE_REMOVE_BLOCKED`:删除 provider 会导致 active profile 悬空,必须先切换
207
-
208
- ### 5.4 配置文档内部抽象
209
-
210
- 为了支撑非破坏性写回,`0.0.5` 在 infra 层引入下列内部抽象:
211
-
212
- ```ts
213
- type ParsedConfigDocument = {
214
- rawText: string;
215
- lineEnding: "\n" | "\r\n";
216
- activeProfile: string | null;
217
- profiles: ProfileSectionRef[];
218
- };
219
-
220
- type ProfileSectionRef = {
221
- name: string;
222
- headerStart: number;
223
- sectionStart: number;
224
- sectionEnd: number;
225
- modelValueRange: { start: number; end: number } | null;
226
- baseUrlValueRange: { start: number; end: number } | null;
227
- model: string | null;
228
- baseUrl: string | null;
229
- };
230
-
231
- type ConfigPatchOperation =
232
- | { kind: "replace-range"; start: number; end: number; text: string }
233
- | { kind: "insert-at"; index: number; text: string }
234
- | { kind: "delete-range"; start: number; end: number };
235
-
236
- type ConfigMutationPlan = {
237
- operations: ConfigPatchOperation[];
238
- createdProfileSections: string[];
239
- deletedProfileSections: string[];
240
- updatedProfiles: string[];
241
- switchedActiveProfile: boolean;
242
- };
243
- ```
244
-
245
- 这些抽象只作为实现内部契约,不直接暴露给 CLI 输出。
246
-
247
- ## 6. TOML 处理路线
248
-
249
- ### 6.1 技术决策
250
-
251
- `0.0.5` 直接锁定 TOML 技术路线:
252
-
253
- - 采用 `@toml-tools/parser` 一类的 CST / 结构化 parser 路线
254
- - 目标是获得 section / field 的稳定位置边界
255
- - 最终对原始文本执行局部 patch 写回
256
-
257
- 不采用:
258
-
259
- - 全量 parse -> stringify
260
- - 重新序列化整个 `config.toml`
261
- - 继续只靠字符串正则拼接
262
-
263
- ### 6.2 为什么不做全量 stringify
264
-
265
- 全量 stringify 不符合当前产品目标,因为它会带来下面这些不可接受的副作用:
266
-
267
- - 破坏注释
268
- - 破坏空行
269
- - 改写未受管内容的顺序
270
- - 让用户手工维护的 config 漂移过大
271
-
272
- `0.0.5` 的目标是受控管理 provider-linked sections,而不是接管整份配置文件。
273
-
274
- ### 6.3 Patch 规则
275
-
276
- patch 规则直接锁定:
277
-
278
- - 所有 patch 操作基于原始文本坐标生成
279
- - 应用时按文本区间从后往前执行
280
- - 这样可以避免前面 patch 影响后面 patch 的 offset
281
-
282
- 示例策略:
283
-
284
- 1. 先生成完整 `ConfigMutationPlan`
285
- 2. 将 `replace-range` / `delete-range` / `insert-at` 按起始偏移倒序排序
286
- 3. 统一在一次写回中应用
287
-
288
- ### 6.4 结构化读取边界
289
-
290
- `0.0.5` 结构化识别的范围只包括:
291
-
292
- - 顶层 `profile = "..."`
293
- - `[profiles.<name>]`
294
- - `[model_providers.<name>]`
295
- - `model = "..."`
296
- - `model_provider = "..."`
297
- - `base_url = "..."`
298
-
299
- 其余字段允许原样存在,但:
300
-
301
- - 不在 `0.0.5` 的正式受管写入范围内
302
- - 不要求被完整解析成稳定 schema
303
- - 不允许在写入时被误删或重排
304
-
305
- ## 7. 目录发现与 `setup` 候选策略
306
-
307
- ### 7.1 候选集算法
308
-
309
- `setup` 的目录发现采用保守候选集,不做全盘扫描。
310
-
311
- 优先规则:
312
-
313
- - 若显式传入 `--codex-dir`,只使用该目录
314
- - 否则候选集来自:
315
- - `CODEXS_CODEX_DIR`
316
- - `dev-codex/local-sandbox`,但仅在 `NODE_ENV=development`
317
- - `~/.codex`
318
-
319
- 候选处理:
320
-
321
- - 去重
322
- - 过滤不存在路径
323
- - 保留顺序,形成最终候选列表
324
-
325
- ### 7.2 交互规则
326
-
327
- TTY 模式:
328
-
329
- - 单候选:自动继续
330
- - 多候选:让用户选择现有候选或手动输入
331
- - 无候选:允许手动输入
332
-
333
- 非交互模式:
334
-
335
- - 多候选:返回 `CODEX_DIR_AMBIGUOUS`
336
- - 无候选:返回 `CODEX_DIR_NOT_FOUND`
337
-
338
- ### 7.3 设计取舍
339
-
340
- 这里明确不做:
341
-
342
- - 扫描整个用户目录
343
- - 递归搜索所有潜在 config 路径
344
- - 推断多个工作区下的任意历史目录
345
-
346
- 原因是这类扫描成本高、噪声大、可预测性差,不适合作为稳定 CLI 契约。
347
-
348
- ## 8. 命令设计
349
-
350
- ### 8.1 新增命令面
351
-
352
- `0.0.5` 新增:
353
-
354
- ```bash
355
- codexs config show [profile] [--json] [--codex-dir <path>]
356
- codexs config list-profiles [--json] [--codex-dir <path>]
357
- ```
358
-
359
- ### 8.2 现有命令新增 flags
360
-
361
- `add <provider>` 新增:
362
-
363
- - `--create-profile`
364
- - `--model <name>`
365
- - `--base-url <url>`
366
-
367
- `edit <provider>` 新增:
368
-
369
- - `--create-profile`
370
- - `--model <name>`
371
- - `--base-url <url>`
372
- - 继续支持 `--profile`
373
-
374
- `remove <provider>` 新增:
375
-
376
- - `--switch-to <profile>`
377
-
378
- ### 8.3 不新增命令
379
-
380
- `0.0.5` 明确不新增独立 `repair` 命令。
381
-
382
- repair / adopt 路径通过以下方式承接:
383
-
384
- - `doctor` 暴露问题和建议动作
385
- - `setup` 在交互模式下承接 adopt
386
- - `import --merge` 在交互模式下承接 adopt / repair
387
- - 现有写命令在交互模式下承接必要确认
388
-
389
- ## 9. 读取命令契约
390
-
391
- ### 9.1 `config show`
392
-
393
- #### 用途
394
-
395
- 返回结构化 config 视图,可选聚焦单个 profile。
396
-
397
- #### 命令形态
398
-
399
- ```bash
400
- codexs config show [profile] [--json] [--codex-dir <path>]
401
- ```
402
-
403
- #### 返回边界
404
-
405
- 默认返回 `config.toml` 中全部可识别 profiles,而不是只返回 managed profiles。
406
-
407
- JSON 最小字段:
408
-
409
- ```json
410
- {
411
- "activeProfile": "packycode",
412
- "selectedProfile": null,
413
- "profiles": [
414
- {
415
- "name": "packycode",
416
- "managed": true,
417
- "isActive": true,
418
- "linkedProviders": ["packycode"],
419
- "model": "gpt-5",
420
- "modelProvider": "packycode",
421
- "baseUrl": "https://relay.example.com/v1",
422
- "managedFields": ["model", "model_provider"],
423
- "source": "managed"
424
- }
425
- ]
426
- }
427
- ```
428
-
429
- 若传入 `[profile]`:
430
-
431
- - `selectedProfile` 返回目标 profile
432
- - `profiles` 仍建议保持数组 shape,但只包含目标视图
433
- - 若目标来自 orphaned reference,也允许返回 `source = "orphaned-reference"` 的单条视图
434
-
435
- #### 失败语义
436
-
437
- - 读取失败:`CONFIG_NOT_FOUND` 或 `CONFIG_PARSE_ERROR`
438
- - 指定 profile 不可识别:`PROFILE_NOT_FOUND`
439
-
440
- ### 9.2 `config list-profiles`
441
-
442
- #### 用途
443
-
444
- 返回 profile 的轻量列表视图。
445
-
446
- #### 命令形态
447
-
448
- ```bash
449
- codexs config list-profiles [--json] [--codex-dir <path>]
450
- ```
451
-
452
- #### 返回边界
453
-
454
- 也必须返回全部可识别 profiles,而不是只返回 managed profiles。
455
-
456
- 最小字段:
457
-
458
- - `name`
459
- - `managed`
460
- - `isActive`
461
- - `linkedProviders`
462
- - `model`
463
- - `modelProvider`
464
- - `baseUrl`
465
- - `source`
466
-
467
- 与 `config show` 的差异:
468
-
469
- - `list-profiles` 是轻量列表
470
- - `show` 允许更完整的单 profile 语义和问题上下文
471
-
472
- ## 10. 写命令一致性规则
473
-
474
- ### 10.1 `add <provider>`
475
-
476
- 规则锁定如下:
477
-
478
- - 当目标 profile 已存在时,只建立 provider -> profile 映射
479
- - 当目标 profile 缺失时,只有同时传入 `--create-profile --model <name>` 且 `config.toml` 中已存在同名 `[model_providers.<profile>]` section 时才允许创建 section
480
- - 只传 `--create-profile` 但缺少 `--model`,返回 `MANAGED_PROFILE_FIELDS_MISSING`
481
- - 若缺少同名 `model_providers` section 或其 `base_url`,返回 `PROFILE_NOT_FOUND` 或 `MANAGED_PROFILE_FIELDS_MISSING`
482
- - 不允许写出 provider 指向缺失 profile 的新状态
483
-
484
- ### 10.2 `edit <provider>`
485
-
486
- 规则锁定如下:
487
-
488
- - 改绑到已有 profile:更新 provider 映射并重新计算 active / shared 关系
489
- - 改绑到缺失 profile:只有 `--create-profile --model <name>` 且存在同名 `model_providers` runtime section 时才允许
490
- - 旧 section 不做隐式 rename
491
- - 旧 section 不做隐式 copy
492
- - 旧 section 不做隐式 `model` / `model_provider` 迁移
493
-
494
- 删除旧 section 的规则:
495
-
496
- - 若旧 profile 仍被其他 provider 引用:保留
497
- - 若旧 profile 无其他引用且不是 active profile:可删除
498
- - 若旧 profile 无其他引用但仍是 active profile:必须先切换或交互确认后显式切换
499
-
500
- ### 10.3 `remove <provider>`
501
-
502
- 规则锁定如下:
503
-
504
- - 若删除后 profile 仍被其他 provider 引用:保留 section
505
- - 若删除后已无任何 provider 引用:允许删除 section
506
- - 若会删掉当前 active profile 且这是最后一个引用:必须先 `switch` 或显式 `--switch-to`
507
- - 不允许把 active profile 留成悬空状态
508
-
509
- 这里新增明确错误码:
510
-
511
- - `PROFILE_IN_USE`:对共享 profile 或 active profile 进行危险删除时阻止继续
512
-
513
- ### 10.4 `switch <provider>`
514
-
515
- `switch` 在 `0.0.5` 继续只负责:
516
-
517
- - 修改顶层 active `profile`
518
-
519
- 它不负责:
520
-
521
- - 修复 profile section 内容
522
- - 创建缺失 section
523
- - 迁移 profile schema
524
-
525
- ### 10.5 `setup`
526
-
527
- `setup` 在 `0.0.5` 的要求:
528
-
529
- - 支持多候选目录发现与 TTY 选择
530
- - 支持 adopt 现有 unmanaged profiles
531
- - 不要求一次性把所有历史 profile 全部变为 managed
532
- - 不能制造新的 registry-config 不一致
533
-
534
- 当发现 unmanaged profile 时:
535
-
536
- - 交互模式可选择 adopt 或跳过
537
- - 非交互模式不得静默 adopt;只有在输入已足够明确时才继续
538
-
539
- ### 10.6 `import --merge`
540
-
541
- `import --merge` 继续保持“导入侧覆盖本地同名 provider”的语义。
542
-
543
- 新增一致性规则:
544
-
545
- - 如果导入结果引用缺失 profile,进入与 `add` / `edit` 相同的 create / adopt 规则
546
- - 非交互模式下,无法满足 create 条件则失败
547
- - 交互模式下,可进入 adopt / repair 辅助流
548
- - 最终写入结果不得留下新的 orphaned reference
549
-
550
- ## 11. 写结果契约
551
-
552
- `0.0.5` 要求以下写命令在 JSON `data` 中稳定返回新字段:
553
-
554
- - `add`
555
- - `edit`
556
- - `remove`
557
- - `setup`
558
- - `import --merge`
559
-
560
- 新增字段:
561
-
562
- ```json
563
- {
564
- "createdProfileSections": [],
565
- "deletedProfileSections": [],
566
- "keptSharedProfiles": [],
567
- "switchedActiveProfile": false,
568
- "adoptedProfiles": [],
569
- "repairedProfiles": []
570
- }
571
- ```
572
-
573
- 约束:
574
-
575
- - 未发生时返回空数组或 `false`
576
- - 不用缺省省略字段
577
- - 顶层 envelope 结构不变
578
-
579
- ## 12. 错误语义
580
-
581
- ### 12.1 `0.0.5` 需要新增的错误码
582
-
583
- 设计明确要求实现中新增:
584
-
585
- - `CONFIG_PARSE_ERROR`
586
- - `PROFILE_IN_USE`
587
- - `MANAGED_PROFILE_FIELDS_MISSING`
588
-
589
- 继续使用已有:
590
-
591
- - `CODEX_DIR_NOT_FOUND`
592
- - `CODEX_DIR_AMBIGUOUS`
593
- - `CONFIG_NOT_FOUND`
594
- - `PROFILE_NOT_FOUND`
595
- - `PROVIDERS_NOT_FOUND`
596
- - `PROVIDERS_PARSE_ERROR`
597
- - `BACKUP_FAILED`
598
- - `ROLLBACK_FAILED`
599
-
600
- ### 12.2 错误码语义
601
-
602
- `CONFIG_PARSE_ERROR`:
603
-
604
- - `config.toml` 存在,但结构化读取失败
605
- - 应包含文件路径和 parser 原因
606
-
607
- `PROFILE_IN_USE`:
608
-
609
- - 删除 / 改绑操作会破坏共享 profile 或 active profile 安全约束
610
- - 应包含 `profile`、`provider`、`activeProfile`、`linkedProviders`
611
-
612
- `MANAGED_PROFILE_FIELDS_MISSING`:
613
-
614
- - 需要创建新的受管 profile section,但缺少最小字段
615
- - `0.0.5` 至少用于缺少 `model` 或 `model_provider`
616
-
617
- ### 12.3 明确不进入 `0.0.5` 的错误语义
618
-
619
- `IMPORT_MERGE_CONFLICT` 不进入 `0.0.5`。
620
-
621
- 原因:
622
-
623
- - `import --merge` 继续采用导入侧覆盖策略
624
- - 不引入逐条冲突解决语义
625
-
626
- ## 13. 模块设计与代码落点
627
-
628
- ### 13.1 总体结构
629
-
630
- 保持当前四层结构不变:
631
-
632
- - CLI 层
633
- - Application 层
634
- - Domain 层
635
- - Infrastructure 层
636
-
637
- ### 13.2 `src/domain/config.ts`
638
-
639
- 当前 `src/domain/config.ts` 主要还是字符串 helper。
640
-
641
- `0.0.5` 升级为 config 领域规则入口,负责:
642
-
643
- - active profile 规则
644
- - managed / unmanaged / orphaned 视图拼装
645
- - 写操作前校验
646
- - shared profile 与 destructive remove 规则判断
647
-
648
- 建议新增:
649
-
650
- - `buildManagedProfileViews(...)`
651
- - `collectConfigConsistencyIssues(...)`
652
- - `validateManagedProfileCreation(...)`
653
- - `planProfileLifecycleOutcome(...)`
654
-
655
- ### 13.3 `src/infra/config-repo.ts`
656
-
657
- 当前 `src/infra/config-repo.ts` 主要负责读取 active profile、列出 section 名、改写顶层 profile。
658
-
659
- `0.0.5` 升级为结构化读取 + patch 应用中心,至少暴露:
660
-
661
- - `readStructuredConfig`
662
- - `listStructuredProfiles`
663
- - `planConfigMutation`
664
- - `applyConfigMutation`
665
- - `findCodexDirCandidates`
666
-
667
- 职责包括:
668
-
669
- - 读取原始 `config.toml`
670
- - 通过 CST parser 建立 section / field 边界
671
- - 生成 `ConfigMutationPlan`
672
- - 应用局部 patch
673
- - 保持注释、空行和未受管内容稳定
674
-
675
- 说明:
676
-
677
- - 当前仓库已有 `src/infra/codex-discovery.ts` 的目录发现逻辑
678
- - `0.0.5` 设计上允许把候选目录规则下沉整合进 config-aware 路径解析,但不要求强行删除旧文件
679
- - 实现时可以保留 `codex-discovery.ts` 作为薄封装,底层委托给 `config-repo` 或共用 helper
680
-
681
- ### 13.4 `src/app/`
682
-
683
- 新增:
684
-
685
- - `src/app/show-config.ts`
686
- - `src/app/list-config-profiles.ts`
687
-
688
- 更新:
689
-
690
- - `src/app/add-provider.ts`
691
- - `src/app/edit-provider.ts`
692
- - `src/app/remove-provider.ts`
693
- - `src/app/setup-codex.ts`
694
- - `src/app/import-providers.ts`
695
- - `src/app/get-status.ts`
696
- - `src/app/run-doctor.ts`
697
-
698
- 应用层需要承担:
699
-
700
- - 组合 provider repo 与 config repo
701
- - 组装双写事务输入
702
- - 生成写结果契约字段
703
- - 把 doctor / status 的 issue 转换为稳定输出结构
704
-
705
- ### 13.5 `src/cli.ts`
706
-
707
- 需要新增:
708
-
709
- - `config show` 分派
710
- - `config list-profiles` 分派
711
-
712
- 同时更新现有:
713
-
714
- - `add` 参数接收 `--create-profile`、`--model`、`--base-url`
715
- - `edit` 参数接收 `--create-profile`、`--model`、`--base-url`
716
- - `remove` 参数接收 `--switch-to`
717
- - `setup` 接入多候选目录交互
718
-
719
- ### 13.6 `src/cli/args.ts`
720
-
721
- 需要把新子命令归一化为稳定 command key,方式与现有 `backups list` 类似。
722
-
723
- 建议新增 command key:
724
-
725
- - `config-show`
726
- - `config-list-profiles`
727
-
728
- ### 13.7 `src/cli/help.ts` / `src/cli/output.ts` / `src/cli/interactive.ts`
729
-
730
- 需要增加:
731
-
732
- - 新命令帮助文案
733
- - `config show` / `config list-profiles` 的文本渲染
734
- - `setup` 多候选目录选择交互
735
- - adopt 辅助交互的最小提示
736
-
737
- ## 14. 关键流程时序
738
-
739
- ### 14.1 `config show` 只读流程
740
-
741
- ```text
742
- argv
743
- -> parseArgs
744
- -> executeCommand("config-show")
745
- -> app/show-config
746
- -> infra/config-repo.readStructuredConfig
747
- -> infra/providers-repo.readProvidersFileIfExists
748
- -> domain/config.buildManagedProfileViews
749
- -> output
750
- ```
751
-
752
- ### 14.2 `add` 创建缺失 profile 的双写流程
753
-
754
- ```text
755
- argv
756
- -> parseArgs
757
- -> executeCommand("add")
758
- -> app/add-provider
759
- -> read providers.json + structured config
760
- -> validate create-profile + model/model_provider preconditions
761
- -> domain/config.planProfileLifecycleOutcome
762
- -> infra/config-repo.planConfigMutation
763
- -> app/run-mutation
764
- -> write providers.json + apply config patch
765
- -> success result with createdProfileSections
766
- ```
767
-
768
- ### 14.3 `edit --profile` 重绑定流程
769
-
770
- ```text
771
- argv
772
- -> parseArgs
773
- -> executeCommand("edit")
774
- -> app/edit-provider
775
- -> load provider + structured config
776
- -> resolve new profile target
777
- -> create missing profile only when --create-profile --model is present and matching model_provider runtime section already exists
778
- -> update provider mapping
779
- -> keep or delete old section based on shared/active rules
780
- -> single mutation transaction
781
- -> success result with created/deleted/kept fields
782
- ```
783
-
784
- ### 14.4 `remove --switch-to` 安全删除流程
785
-
786
- ```text
787
- argv
788
- -> parseArgs
789
- -> executeCommand("remove")
790
- -> app/remove-provider
791
- -> load provider + structured config
792
- -> detect whether target profile is last reference and active
793
- -> require explicit switch target when destructive
794
- -> switch active profile first in mutation plan when needed
795
- -> delete provider mapping
796
- -> delete profile section only if no remaining references
797
- -> success result with switchedActiveProfile + deletedProfileSections
798
- ```
799
-
800
- ### 14.5 `setup` 多候选目录 + adopt 流程
801
-
802
- ```text
803
- argv
804
- -> parseArgs
805
- -> executeCommand("setup")
806
- -> findCodexDirCandidates
807
- -> TTY choose candidate or manual path
808
- -> read structured config
809
- -> collect unmanaged profiles and active profile
810
- -> interactive adopt decision when applicable
811
- -> build provider drafts
812
- -> single mutation write
813
- -> run doctor
814
- -> output adopt/repair summary
815
- ```
816
-
817
- ## 15. 兼容、迁移与诊断
818
-
819
- ### 15.1 历史状态识别
820
-
821
- `0.0.5` 需要识别至少四类历史状态:
822
-
823
- - `providers.json` 可用,但 `config.toml` 只有手工维护 profile
824
- - provider 引用了缺失 section
825
- - `config.toml` 存在没有任何 provider 引用的历史 section
826
- - 当前 active profile 指向 unmanaged profile
827
-
828
- ### 15.2 收敛路线
829
-
830
- 在没有独立 `repair` 命令的前提下,收敛路线明确为:
831
-
832
- - `doctor` 识别问题并给出问题码
833
- - `status` 给出浅层信号,不做静默修复
834
- - `setup` 在交互模式下承接 adopt
835
- - `import --merge` 保持显式失败,不为缺失 `model_providers` runtime section 做交互 repair
836
- - 非交互模式遇到不可自动收敛状态时失败,并返回明确原因
837
-
838
- ### 15.3 `doctor` 与 `status` 的责任差异
839
-
840
- `status`:
841
-
842
- - 给出当前 active profile、是否映射、是否存在浅层漂移信号
843
- - 输出面向日常查看
844
-
845
- `doctor`:
846
-
847
- - 给出结构化 `ConfigConsistencyIssue[]`
848
- - 明确问题码、上下文和建议动作
849
- - 输出面向修复和自动化诊断
850
-
851
- ## 16. 测试设计
852
-
853
- ### 16.1 总体原则
854
-
855
- 测试继续采用当前 plain Node specs 模式:
856
-
857
- - 不引入 Jest / Vitest
858
- - fixture 继续放在 `tests/` / `dev-codex/` 现有模式中
859
-
860
- ### 16.2 CLI 测试
861
-
862
- 最少覆盖:
863
-
864
- - `config show` 文本输出
865
- - `config show --json` 输出
866
- - `config list-profiles` 文本输出
867
- - `config list-profiles --json` 输出
868
- - `setup` 单候选目录
869
- - `setup` 多候选目录 + TTY 选择
870
- - `setup` 无候选目录 + TTY 手动输入
871
- - `setup` 多候选目录 + 非交互失败 `CODEX_DIR_AMBIGUOUS`
872
- - `add --create-profile --model` with existing same-named `[model_providers.*]`
873
- - `edit --profile <missing> --create-profile --model` with existing same-named `[model_providers.*]`
874
- - `remove --switch-to`
875
-
876
- ### 16.3 Application 测试
877
-
878
- 最少覆盖:
879
-
880
- - provider + config 双写成功
881
- - provider + config 双写失败整体回滚
882
- - `import --merge` 后 linked section 一致性
883
- - `setup` adopt unmanaged profile
884
-
885
- ### 16.4 Domain / Infra 测试
886
-
887
- 最少覆盖:
888
-
889
- - structured config 读取
890
- - patch 计划生成
891
- - patch 应用后注释、空行、未受管内容保持
892
- - managed / unmanaged / orphaned 视图计算
893
- - `doctor` / `status` issue 计算
894
-
895
- ### 16.5 Fixture 设计
896
-
897
- 最少新增:
898
-
899
- - 带注释和空行的 `config.toml`
900
- - 共享 profile fixture
901
- - orphaned reference fixture
902
- - unmanaged active profile fixture
903
- - 多候选 Codex 目录 fixture
904
-
905
- ## 17. Deferred 到 `0.1.0`
906
-
907
- 下面这些内容作为 `0.0.5 -> 0.1.0` 后续项单列,不混入当前实现:
908
-
909
- - 更大的 profile schema
910
- - 真正的 `config edit` 命令族
911
- - 更强的 repair 自动化
912
- - extensions / auth integration
913
-
914
- ## 18. 验收标准
915
-
916
- `0.0.5` 设计落地后,至少应满足:
917
-
918
- - `config show` 在文本和 `--json` 模式下返回稳定结构
919
- - `config list-profiles` 返回全部可识别 profiles,并通过 `managed` / `source` 区分来源
920
- - `add` / `edit` / `remove` 同步维护 linked profile sections
921
- - 共享 profile 不会因单个 provider 操作被误删
922
- - active profile 不会因删除或重绑定而悬空
923
- - `setup` 的目录发现行为在 TTY / 非交互下可预测
924
- - 结构化 TOML 写回后注释、空行和未受管内容保持稳定
925
- - 双写失败时 `providers.json` 与 `config.toml` 能整体回滚
926
- - `doctor` / `status` 能识别 orphaned reference、unmanaged active profile、shared profile 和 orphaned section
927
-
928
- ## 19. 结论
929
-
930
- `0.0.5` 的本质,是把 `codex-switch` 从“能管理 provider registry 并做浅层 profile 切换”的工具,推进到“对 `config.toml` 有稳定结构化认知、能维护 provider-linked profile、一致性可诊断、双写可回滚”的下一阶段。
931
-
932
- 这一步不要求引入更大的命令体系,也不要求把 `config.toml` 变成通用编辑器;它要求的是把当前最容易产生漂移和隐式破坏的那部分能力,收敛成一套明确、可实现、可测试的实现规格。