@minniexcode/codex-switch 0.0.1 → 0.0.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.
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readProvidersFile = readProvidersFile;
37
+ exports.readProvidersFileIfExists = readProvidersFileIfExists;
38
+ exports.writeProvidersFile = writeProvidersFile;
39
+ const fs = __importStar(require("node:fs"));
40
+ const errors_1 = require("../domain/errors");
41
+ const providers_1 = require("../domain/providers");
42
+ const fs_utils_1 = require("./fs-utils");
43
+ /**
44
+ * Reads and validates providers.json from disk.
45
+ */
46
+ function readProvidersFile(providersPath) {
47
+ const raw = (0, fs_utils_1.readRequiredFile)(providersPath, "PROVIDERS_NOT_FOUND", "providers.json");
48
+ try {
49
+ return (0, providers_1.validateProvidersShape)(JSON.parse(raw));
50
+ }
51
+ catch (error) {
52
+ throw (0, errors_1.cliError)("PROVIDERS_PARSE_ERROR", "Failed to parse providers.json.", {
53
+ file: providersPath,
54
+ cause: (0, errors_1.normalizeError)(error).message,
55
+ });
56
+ }
57
+ }
58
+ /**
59
+ * Reads providers.json when it exists, otherwise returns an empty registry.
60
+ */
61
+ function readProvidersFileIfExists(providersPath) {
62
+ return fs.existsSync(providersPath) ? readProvidersFile(providersPath) : { providers: {} };
63
+ }
64
+ /**
65
+ * Persists providers.json using deterministic key ordering.
66
+ */
67
+ function writeProvidersFile(providersPath, providers) {
68
+ (0, fs_utils_1.writeTextFileAtomic)(providersPath, `${JSON.stringify((0, providers_1.sortProviders)(providers), null, 2)}\n`);
69
+ }
@@ -0,0 +1,621 @@
1
+ # codex-switch 命令设计说明
2
+
3
+ ## 文档信息
4
+
5
+ - 文档类型:命令设计文档
6
+ - 适用范围:`codex-switch` MVP
7
+ - 关联文档:
8
+ - [`codex-switch-prd.md`](./codex-switch-prd.md)
9
+ - [`codex-switch-technical-architecture.md`](./codex-switch-technical-architecture.md)
10
+
11
+ ## 1. 文档目标
12
+
13
+ 这份文档把 `codex-switch` 的每个 CLI 命令拆开描述,重点沉淀下面这些内容:
14
+
15
+ - 命令用途
16
+ - 输入参数
17
+ - 成功输出
18
+ - 失败错误码
19
+ - 关键行为语义
20
+ - 对 AI / 自动化调用的注意事项
21
+
22
+ 这份文档是 PRD 的命令规格落地版,也是技术架构文档的命令视角补充。
23
+
24
+ ## 1.1 与 `cc-switch` / `codex-auth` 的命令形态差异
25
+
26
+ 为了帮助后续继续演进,这里先明确三者的命令/交互边界:
27
+
28
+ - `codex-switch`
29
+ - 目标是稳定 CLI 命令
30
+ - 聚焦 provider/profile 切换、导入导出、诊断、回滚
31
+ - `codex-auth`
32
+ - 更偏账号 / auth 管理 CLI
33
+ - 对本项目的参考点是命令组织方式
34
+ - `cc-switch`
35
+ - 更偏 GUI / 桌面管理器
36
+ - 即使内部也有切换逻辑,用户主入口不是命令行,而是桌面界面
37
+
38
+ 这意味着 `codex-switch` 的命令设计原则应继续保持:
39
+
40
+ - 参数显式
41
+ - 输出稳定
42
+ - 能被 AI 和脚本直接消费
43
+ - 不把 GUI 交互假装成 CLI 接口
44
+
45
+ ## 2. 公共命令约定
46
+
47
+ ### 2.1 命令入口
48
+
49
+ 统一命令名:
50
+
51
+ ```bash
52
+ codexs
53
+ ```
54
+
55
+ ### 2.2 公共参数
56
+
57
+ 所有支持的命令共享以下全局参数:
58
+
59
+ - `--json`
60
+ - 返回统一 JSON envelope
61
+ - `--codex-dir <path>`
62
+ - 指定目标工作目录
63
+
64
+ ### 2.3 JSON 输出结构
65
+
66
+ 统一 envelope:
67
+
68
+ ```json
69
+ {
70
+ "ok": true,
71
+ "command": "list",
72
+ "data": {},
73
+ "warnings": [],
74
+ "error": null
75
+ }
76
+ ```
77
+
78
+ 失败时:
79
+
80
+ ```json
81
+ {
82
+ "ok": false,
83
+ "command": "list",
84
+ "data": null,
85
+ "warnings": [],
86
+ "error": {
87
+ "code": "PROVIDERS_NOT_FOUND",
88
+ "message": "providers.json does not exist.",
89
+ "details": {
90
+ "file": "C:\\Users\\name\\.codex\\providers.json"
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### 2.4 固定错误码
97
+
98
+ 当前命令层统一使用:
99
+
100
+ - `CONFIG_NOT_FOUND`
101
+ - `PROVIDERS_NOT_FOUND`
102
+ - `PROVIDERS_PARSE_ERROR`
103
+ - `PROVIDER_NOT_FOUND`
104
+ - `PROFILE_NOT_FOUND`
105
+ - `BACKUP_FAILED`
106
+ - `CODEX_LOGIN_FAILED`
107
+ - `ROLLBACK_FAILED`
108
+ - `INVALID_IMPORT_FILE`
109
+
110
+ ## 3. 命令清单概览
111
+
112
+ ```bash
113
+ codexs list
114
+ codexs current
115
+ codexs switch <provider>
116
+ codexs status
117
+ codexs import <file>
118
+ codexs export <file>
119
+ codexs add <provider>
120
+ codexs remove <provider>
121
+ codexs doctor
122
+ codexs rollback
123
+ ```
124
+
125
+ ## 4. 命令逐项设计
126
+
127
+ ### 4.1 `codexs list`
128
+
129
+ #### 目标
130
+
131
+ 列出当前 `providers.json` 中的 provider 清单。
132
+
133
+ #### 输入
134
+
135
+ ```bash
136
+ codexs list [--json] [--codex-dir <path>]
137
+ ```
138
+
139
+ #### 成功输出
140
+
141
+ 默认输出示意:
142
+
143
+ ```text
144
+ freemodel -> freemodel
145
+ packycode -> packycode tags=daily note=primary
146
+ ```
147
+
148
+ JSON 输出示意:
149
+
150
+ ```json
151
+ {
152
+ "ok": true,
153
+ "command": "list",
154
+ "data": {
155
+ "providers": [
156
+ {
157
+ "name": "freemodel",
158
+ "profile": "freemodel",
159
+ "note": null,
160
+ "tags": []
161
+ }
162
+ ],
163
+ "count": 1
164
+ },
165
+ "warnings": [],
166
+ "error": null
167
+ }
168
+ ```
169
+
170
+ #### 失败错误码
171
+
172
+ - `PROVIDERS_NOT_FOUND`
173
+ - `PROVIDERS_PARSE_ERROR`
174
+
175
+ #### AI 调用建议
176
+
177
+ - 优先使用 `--json`
178
+ - 不要依赖默认输出格式做机器解析
179
+
180
+ ### 4.2 `codexs current`
181
+
182
+ #### 目标
183
+
184
+ 返回当前 `config.toml` 顶层 `profile`。
185
+
186
+ #### 输入
187
+
188
+ ```bash
189
+ codexs current [--json] [--codex-dir <path>]
190
+ ```
191
+
192
+ #### 成功输出
193
+
194
+ 默认:
195
+
196
+ ```text
197
+ Current profile: packycode
198
+ ```
199
+
200
+ JSON:
201
+
202
+ ```json
203
+ {
204
+ "ok": true,
205
+ "command": "current",
206
+ "data": {
207
+ "profile": "packycode"
208
+ },
209
+ "warnings": [],
210
+ "error": null
211
+ }
212
+ ```
213
+
214
+ #### 失败错误码
215
+
216
+ - `CONFIG_NOT_FOUND`
217
+ - `PROFILE_NOT_FOUND`
218
+
219
+ ### 4.3 `codexs status`
220
+
221
+ #### 目标
222
+
223
+ 给出本地配置的浅状态概览。
224
+
225
+ #### 输入
226
+
227
+ ```bash
228
+ codexs status [--json] [--codex-dir <path>]
229
+ ```
230
+
231
+ #### 当前返回字段
232
+
233
+ - `codexDir`
234
+ - `configExists`
235
+ - `providersExists`
236
+ - `currentProfile`
237
+ - `currentProfileMapped`
238
+ - `provider`
239
+
240
+ #### 成功输出
241
+
242
+ JSON 示例:
243
+
244
+ ```json
245
+ {
246
+ "ok": true,
247
+ "command": "status",
248
+ "data": {
249
+ "codexDir": "C:\\Users\\name\\.codex",
250
+ "configExists": true,
251
+ "providersExists": true,
252
+ "currentProfile": "packycode",
253
+ "currentProfileMapped": true,
254
+ "provider": "packycode"
255
+ },
256
+ "warnings": [],
257
+ "error": null
258
+ }
259
+ ```
260
+
261
+ #### 设计说明
262
+
263
+ - `status` 是概览,不做深度建议
264
+ - 深度问题检测交给 `doctor`
265
+
266
+ ### 4.4 `codexs switch <provider>`
267
+
268
+ #### 目标
269
+
270
+ 切换到指定 provider,并默认同步更新登录状态。
271
+
272
+ #### 输入
273
+
274
+ ```bash
275
+ codexs switch <provider> [--no-login] [--json] [--codex-dir <path>]
276
+ ```
277
+
278
+ #### 前置校验
279
+
280
+ - `providers.json` 必须存在
281
+ - provider 必须存在
282
+ - provider 对应的 `profile` 必须存在于 `config.toml`
283
+
284
+ #### 执行步骤
285
+
286
+ 1. 读取 provider 数据
287
+ 2. 校验 profile
288
+ 3. 备份 `config.toml`
289
+ 4. 如果存在,备份 `auth.json`
290
+ 5. 更新顶层 `profile`
291
+ 6. 默认执行 `codex login --with-api-key`
292
+ 7. 成功则保存为最近一次备份
293
+ 8. 失败则按 manifest 回滚
294
+
295
+ #### 成功输出
296
+
297
+ JSON 示例:
298
+
299
+ ```json
300
+ {
301
+ "ok": true,
302
+ "command": "switch",
303
+ "data": {
304
+ "provider": "freemodel",
305
+ "profile": "freemodel",
306
+ "loginPerformed": true,
307
+ "backupPath": "C:\\Users\\name\\.codex\\backups\\20260511-221550-switch"
308
+ },
309
+ "warnings": [],
310
+ "error": null
311
+ }
312
+ ```
313
+
314
+ #### 失败错误码
315
+
316
+ - `PROVIDER_NOT_FOUND`
317
+ - `PROFILE_NOT_FOUND`
318
+ - `BACKUP_FAILED`
319
+ - `CODEX_LOGIN_FAILED`
320
+ - `ROLLBACK_FAILED`
321
+
322
+ #### 注意事项
323
+
324
+ - `--no-login` 仅跳过登录,不跳过备份和 profile 修改
325
+ - 登录失败时当前实现会附带 `rollbackApplied: true`
326
+
327
+ ### 4.5 `codexs import <file>`
328
+
329
+ #### 目标
330
+
331
+ 整体替换当前 `providers.json`。
332
+
333
+ #### 输入
334
+
335
+ ```bash
336
+ codexs import <file> [--json] [--codex-dir <path>]
337
+ ```
338
+
339
+ #### 行为语义
340
+
341
+ - 只支持整体替换
342
+ - 不支持 merge
343
+ - 写入前备份当前 `providers.json`
344
+
345
+ #### 成功输出
346
+
347
+ ```json
348
+ {
349
+ "ok": true,
350
+ "command": "import",
351
+ "data": {
352
+ "importedProviders": ["imported"],
353
+ "backupPath": "C:\\Users\\name\\.codex\\backups\\20260511-221457-import"
354
+ },
355
+ "warnings": [],
356
+ "error": null
357
+ }
358
+ ```
359
+
360
+ #### 失败错误码
361
+
362
+ - `INVALID_IMPORT_FILE`
363
+ - `BACKUP_FAILED`
364
+ - `ROLLBACK_FAILED`
365
+
366
+ ### 4.6 `codexs export <file>`
367
+
368
+ #### 目标
369
+
370
+ 导出当前 `providers.json` 到指定位置。
371
+
372
+ #### 输入
373
+
374
+ ```bash
375
+ codexs export <file> [--force] [--json] [--codex-dir <path>]
376
+ ```
377
+
378
+ #### 行为语义
379
+
380
+ - 默认不覆盖已有文件
381
+ - 传 `--force` 后允许覆盖
382
+
383
+ #### 成功输出
384
+
385
+ ```json
386
+ {
387
+ "ok": true,
388
+ "command": "export",
389
+ "data": {
390
+ "exportedTo": "C:\\path\\providers-export.json",
391
+ "count": 3
392
+ },
393
+ "warnings": [],
394
+ "error": null
395
+ }
396
+ ```
397
+
398
+ #### 失败错误码
399
+
400
+ - `INVALID_IMPORT_FILE`
401
+ - `PROVIDERS_NOT_FOUND`
402
+ - `PROVIDERS_PARSE_ERROR`
403
+
404
+ ### 4.7 `codexs add <provider>`
405
+
406
+ #### 目标
407
+
408
+ 新增一条 provider 记录。
409
+
410
+ #### 输入
411
+
412
+ ```bash
413
+ codexs add <provider> \
414
+ --profile <name> \
415
+ --api-key <key> \
416
+ [--base-url <url>] \
417
+ [--note <text>] \
418
+ [--tag <tag>] \
419
+ [--json] \
420
+ [--codex-dir <path>]
421
+ ```
422
+
423
+ #### 行为语义
424
+
425
+ - provider 名必须唯一
426
+ - 写入前备份旧 `providers.json`
427
+ - 当前不支持交互式模式
428
+
429
+ #### 成功输出
430
+
431
+ ```json
432
+ {
433
+ "ok": true,
434
+ "command": "add",
435
+ "data": {
436
+ "provider": "temp",
437
+ "profile": "freemodel",
438
+ "backupPath": "C:\\Users\\name\\.codex\\backups\\20260511-221457-add"
439
+ },
440
+ "warnings": [],
441
+ "error": null
442
+ }
443
+ ```
444
+
445
+ #### 失败错误码
446
+
447
+ - `INVALID_IMPORT_FILE`
448
+ - `BACKUP_FAILED`
449
+ - `ROLLBACK_FAILED`
450
+
451
+ ### 4.8 `codexs remove <provider>`
452
+
453
+ #### 目标
454
+
455
+ 删除一条 provider 记录。
456
+
457
+ #### 输入
458
+
459
+ ```bash
460
+ codexs remove <provider> --force [--json] [--codex-dir <path>]
461
+ ```
462
+
463
+ #### 行为语义
464
+
465
+ - 必须显式传入 `--force`
466
+ - 先备份再删除
467
+
468
+ #### 失败错误码
469
+
470
+ - `PROVIDER_NOT_FOUND`
471
+ - `INVALID_IMPORT_FILE`
472
+ - `BACKUP_FAILED`
473
+ - `ROLLBACK_FAILED`
474
+
475
+ ### 4.9 `codexs doctor`
476
+
477
+ #### 目标
478
+
479
+ 返回结构化问题列表,而不是只给一个总体状态。
480
+
481
+ #### 输入
482
+
483
+ ```bash
484
+ codexs doctor [--json] [--codex-dir <path>]
485
+ ```
486
+
487
+ #### 当前诊断项
488
+
489
+ - `config.toml` 是否存在
490
+ - `providers.json` 是否存在
491
+ - `providers.json` 是否可解析
492
+ - provider 的 profile 是否存在
493
+ - `codex` CLI 是否可执行
494
+
495
+ #### 当前返回结构
496
+
497
+ ```json
498
+ {
499
+ "ok": true,
500
+ "command": "doctor",
501
+ "data": {
502
+ "healthy": false,
503
+ "issues": [
504
+ {
505
+ "code": "CODEX_LOGIN_FAILED",
506
+ "message": "codex CLI is not available.",
507
+ "cause": "spawnSync codex EPERM"
508
+ }
509
+ ],
510
+ "codexDir": "C:\\Users\\name\\.codex"
511
+ },
512
+ "warnings": ["doctor found 1 issue(s)"],
513
+ "error": null
514
+ }
515
+ ```
516
+
517
+ #### 说明
518
+
519
+ - 当前实现把 codex CLI 缺失归到 `CODEX_LOGIN_FAILED`
520
+ - 后续可按需要拆分更细错误码
521
+
522
+ ### 4.10 `codexs rollback`
523
+
524
+ #### 目标
525
+
526
+ 恢复最近一次备份对应的文件状态。
527
+
528
+ #### 输入
529
+
530
+ ```bash
531
+ codexs rollback [--json] [--codex-dir <path>]
532
+ ```
533
+
534
+ #### 行为语义
535
+
536
+ - 读取 `backups/latest.json`
537
+ - 恢复 manifest 中记录的文件
538
+ - 如果最近一次备份包含 `auth.json`,一并恢复
539
+
540
+ #### 成功输出
541
+
542
+ ```json
543
+ {
544
+ "ok": true,
545
+ "command": "rollback",
546
+ "data": {
547
+ "restoredFiles": ["config.toml", "auth.json"],
548
+ "backupPath": "C:\\Users\\name\\.codex\\backups\\20260511-221550-switch"
549
+ },
550
+ "warnings": [],
551
+ "error": null
552
+ }
553
+ ```
554
+
555
+ #### 失败错误码
556
+
557
+ - `ROLLBACK_FAILED`
558
+
559
+ ## 5. 默认输出与敏感信息策略
560
+
561
+ 默认输出遵循:
562
+
563
+ - 不打印完整 `apiKey`
564
+ - 不打印无关调试信息
565
+ - 成功时尽量只返回命令核心结果
566
+
567
+ JSON 输出也遵循相同策略:
568
+
569
+ - `error.details` 不应包含完整 `apiKey`
570
+ - 主要返回路径、命令对象、回滚状态等可操作信息
571
+
572
+ ## 6. AI / 自动化调用建议
573
+
574
+ 对 AI 代理或自动化脚本,推荐以下调用约定:
575
+
576
+ - 一律使用 `--json`
577
+ - 严格按 `error.code` 判断失败类型
578
+ - 对 `switch` 命令关注:
579
+ - `provider`
580
+ - `profile`
581
+ - `loginPerformed`
582
+ - `backupPath`
583
+ - `rollbackApplied`
584
+ - 对 `doctor` 命令关注:
585
+ - `healthy`
586
+ - `issues[]`
587
+
588
+ 推荐模式:
589
+
590
+ 1. 先调用 `status --json`
591
+ 2. 再调用 `doctor --json`
592
+ 3. 满足前置条件后调用 `switch --json`
593
+ 4. 失败时根据错误码决定是否提示用户手动执行 `rollback`
594
+
595
+ ## 7. 后续命令演进建议
596
+
597
+ 如果继续扩展命令面,建议新增命令时遵守下面三条:
598
+
599
+ - 不破坏当前 JSON envelope
600
+ - 不复用语义不匹配的错误码
601
+ - 所有写命令默认纳入备份与回滚模型
602
+
603
+ 未来候选命令:
604
+
605
+ - `codexs show <provider>`
606
+ - `codexs edit <provider>`
607
+ - `codexs backups list`
608
+ - `codexs rollback <backup-id>`
609
+ - `codexs import --merge`
610
+
611
+ ## 8. 结论
612
+
613
+ `codex-switch` 当前命令设计已经具备下面几个工程特征:
614
+
615
+ - 命令面稳定
616
+ - 参数风格统一
617
+ - JSON 输出可机器解析
618
+ - 写操作具备安全语义
619
+ - 错误码已可作为 AI 调用契约
620
+
621
+ 这意味着它已经不再只是“能切换配置的脚本集合”,而是一套具备持续演进空间的 CLI 命令体系。
@@ -8,6 +8,8 @@
8
8
  - 版本范围:MVP / CLI First
9
9
  - 文档定位:正式 PRD
10
10
  - 对应研究稿:[`codex-switch-product-research.md`](./codex-switch-product-research.md)
11
+ - 对应技术架构:[`codex-switch-technical-architecture.md`](./codex-switch-technical-architecture.md)
12
+ - 对应命令设计:[`codex-switch-command-design.md`](./codex-switch-command-design.md)
11
13
 
12
14
  ## 一句话定义
13
15
 
@@ -10,6 +10,8 @@
10
10
 
11
11
  - 研究输入稿:[`codex-switch-product-research.md`](./codex-switch-product-research.md)
12
12
  - 正式 PRD:[`codex-switch-prd.md`](./codex-switch-prd.md)
13
+ - 技术架构设计:[`codex-switch-technical-architecture.md`](./codex-switch-technical-architecture.md)
14
+ - 命令设计说明:[`codex-switch-command-design.md`](./codex-switch-command-design.md)
13
15
 
14
16
  ## 产品概述
15
17