@minniexcode/codex-switch 0.0.2 → 0.0.3

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/dist/cli.js CHANGED
@@ -15,37 +15,20 @@ const rollback_latest_1 = require("./app/rollback-latest");
15
15
  const run_doctor_1 = require("./app/run-doctor");
16
16
  const switch_provider_1 = require("./app/switch-provider");
17
17
  const errors_1 = require("./domain/errors");
18
+ const providers_repo_1 = require("./infra/providers-repo");
18
19
  const codex_paths_1 = require("./infra/codex-paths");
19
20
  const args_1 = require("./cli/args");
21
+ const add_interactive_1 = require("./cli/add-interactive");
22
+ const help_1 = require("./cli/help");
23
+ const interactive_1 = require("./cli/interactive");
20
24
  const output_1 = require("./cli/output");
21
- const VERSION = "0.0.2";
22
- const HELP_TEXT = `codex-switch
23
-
24
- Usage:
25
- codexs <command> [options]
26
-
27
- Commands:
28
- codexs list
29
- codexs current
30
- codexs switch <provider> [--no-login]
31
- codexs status
32
- codexs import <file>
33
- codexs export <file> [--force]
34
- codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]
35
- codexs remove <provider> --force
36
- codexs doctor
37
- codexs rollback
38
-
39
- Global options:
40
- --json
41
- --codex-dir <path>
42
- --help
43
- --version`;
25
+ const prompt_1 = require("./cli/prompt");
26
+ const VERSION = "0.0.3";
44
27
  /**
45
28
  * Prints the command help text to stdout.
46
29
  */
47
- function printHelp() {
48
- process.stdout.write(`${HELP_TEXT}\n`);
30
+ function printHelp(commandName) {
31
+ process.stdout.write(`${(0, help_1.buildHelpText)(commandName)}\n`);
49
32
  }
50
33
  /**
51
34
  * Prints the current CLI version to stdout.
@@ -58,16 +41,22 @@ function printVersion() {
58
41
  */
59
42
  function main() {
60
43
  const parsed = (0, args_1.parseArgs)(process.argv.slice(2));
61
- if (!parsed.command) {
62
- printHelp();
44
+ if (parsed.versionRequested) {
45
+ printVersion();
63
46
  process.exit(0);
64
47
  }
65
- if (parsed.command === "help") {
66
- printHelp();
48
+ if (parsed.helpRequested) {
49
+ if (parsed.helpTarget && !(0, help_1.isKnownCommandName)(parsed.helpTarget)) {
50
+ (0, output_1.outputFailure)({ command: "help", options: parsed.globalOptions }, (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Unknown help topic: ${parsed.helpTarget}`, {
51
+ availableCommands: (0, help_1.buildHelpText)(parsed.helpTarget).split("\n").slice(2),
52
+ }));
53
+ return;
54
+ }
55
+ printHelp(parsed.helpTarget);
67
56
  process.exit(0);
68
57
  }
69
- if (parsed.command === "version") {
70
- printVersion();
58
+ if (!parsed.command) {
59
+ printHelp();
71
60
  process.exit(0);
72
61
  }
73
62
  const ctx = {
@@ -85,7 +74,7 @@ function main() {
85
74
  /**
86
75
  * Dispatches a parsed CLI command into the application layer.
87
76
  */
88
- async function executeCommand(ctx, parsed) {
77
+ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRuntime)()) {
89
78
  const paths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
90
79
  switch (ctx.command) {
91
80
  case "list":
@@ -95,7 +84,10 @@ async function executeCommand(ctx, parsed) {
95
84
  case "status":
96
85
  return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
97
86
  case "switch": {
98
- const providerName = parsed.positionals[0];
87
+ let providerName = parsed.positionals[0] ?? null;
88
+ if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
89
+ providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to switch to");
90
+ }
99
91
  if (!providerName) {
100
92
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
101
93
  }
@@ -115,6 +107,9 @@ async function executeCommand(ctx, parsed) {
115
107
  if (!sourceFile) {
116
108
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing import file path.");
117
109
  }
110
+ if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
111
+ await (0, interactive_1.confirmImport)(runtime, sourceFile);
112
+ }
118
113
  return (0, import_providers_1.importProviders)({
119
114
  codexDir: paths.codexDir,
120
115
  backupsDir: paths.backupsDir,
@@ -128,21 +123,45 @@ async function executeCommand(ctx, parsed) {
128
123
  if (!targetFile) {
129
124
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing export file path.");
130
125
  }
126
+ let force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
127
+ if (!force && (0, interactive_1.canPrompt)(runtime, ctx.options.json) && (0, interactive_1.exportTargetExists)(targetFile)) {
128
+ const confirmed = await (0, interactive_1.confirmExportOverwrite)(runtime, targetFile);
129
+ if (!confirmed) {
130
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Export cancelled.");
131
+ }
132
+ force = true;
133
+ }
131
134
  return (0, export_providers_1.exportProviders)({
132
135
  providersPath: paths.providersPath,
133
136
  targetFile,
134
- force: (0, args_1.hasFlag)(parsed.commandOptions, "--force"),
137
+ force,
135
138
  });
136
139
  }
137
140
  case "add": {
138
- const providerName = parsed.positionals[0];
139
- if (!providerName) {
140
- throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing provider name for add command.");
141
- }
142
- const profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
143
- const apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
144
- if (!profile || !apiKey) {
145
- throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "add requires --profile and --api-key.");
141
+ let providerName = parsed.positionals[0] ?? null;
142
+ let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
143
+ let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
144
+ let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false);
145
+ let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
146
+ let tags = parsed.commandOptions.get("--tag") ?? [];
147
+ if (!providerName || !profile || !apiKey) {
148
+ if (ctx.options.json || !runtime.isInteractive()) {
149
+ throw (0, add_interactive_1.createNonInteractiveAddError)();
150
+ }
151
+ const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
152
+ providerName,
153
+ profile,
154
+ apiKey,
155
+ baseUrl,
156
+ note,
157
+ tags,
158
+ }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]), paths.configPath);
159
+ providerName = prompted.providerName;
160
+ profile = prompted.profile;
161
+ apiKey = prompted.apiKey;
162
+ baseUrl = prompted.baseUrl ?? null;
163
+ note = prompted.note ?? null;
164
+ tags = prompted.tags;
146
165
  }
147
166
  return (0, add_provider_1.addProvider)({
148
167
  codexDir: paths.codexDir,
@@ -152,19 +171,26 @@ async function executeCommand(ctx, parsed) {
152
171
  providerName,
153
172
  profile,
154
173
  apiKey,
155
- baseUrl: (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false),
156
- note: (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false),
157
- tags: parsed.commandOptions.get("--tag") ?? [],
174
+ baseUrl,
175
+ note,
176
+ tags,
158
177
  });
159
178
  }
160
179
  case "remove": {
161
- const providerName = parsed.positionals[0];
180
+ let providerName = parsed.positionals[0] ?? null;
181
+ const force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
182
+ if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
183
+ providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to remove");
184
+ }
162
185
  if (!providerName) {
163
186
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for remove command.");
164
187
  }
165
- if (!(0, args_1.hasFlag)(parsed.commandOptions, "--force")) {
188
+ if (!force && !(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
166
189
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "remove requires --force.");
167
190
  }
191
+ if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
192
+ await (0, interactive_1.confirmProviderRemoval)(runtime, providerName);
193
+ }
168
194
  return (0, remove_provider_1.removeProvider)({
169
195
  codexDir: paths.codexDir,
170
196
  backupsDir: paths.backupsDir,
@@ -180,6 +206,9 @@ async function executeCommand(ctx, parsed) {
180
206
  providersPath: paths.providersPath,
181
207
  });
182
208
  case "rollback":
209
+ if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
210
+ await (0, interactive_1.confirmRollback)(runtime, paths.latestBackupPath);
211
+ }
183
212
  return (0, rollback_latest_1.rollbackLatest)(paths.latestBackupPath);
184
213
  default:
185
214
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Unknown command: ${ctx.command}`);
@@ -40,7 +40,7 @@
40
40
  - 参数显式
41
41
  - 输出稳定
42
42
  - 能被 AI 和脚本直接消费
43
- - 不把 GUI 交互假装成 CLI 接口
43
+ - 交互只作为 TTY 中的人类增强层,而不是自动化契约
44
44
 
45
45
  ## 2. 公共命令约定
46
46
 
@@ -107,6 +107,13 @@ codexs
107
107
  - `ROLLBACK_FAILED`
108
108
  - `INVALID_IMPORT_FILE`
109
109
 
110
+ ### 2.5 渐进式交互约定
111
+
112
+ - `--json` 一律禁用交互
113
+ - 非 TTY 一律不进入交互
114
+ - 交互主要服务于人类高频写命令,不改变自动化显式参数契约
115
+ - 用户取消 prompt、`Ctrl+C`、或确认选择否时,不应产生任何文件写入
116
+
110
117
  ## 3. 命令清单概览
111
118
 
112
119
  ```bash
@@ -275,6 +282,12 @@ JSON 示例:
275
282
  codexs switch <provider> [--no-login] [--json] [--codex-dir <path>]
276
283
  ```
277
284
 
285
+ #### 交互行为
286
+
287
+ - 当 `<provider>` 缺失且当前是 TTY 时,先从 `providers.json` 读取 provider 列表,再用选择器选择 provider
288
+ - 当 `<provider>` 已显式传入时,不额外确认
289
+ - `--no-login` 仍保持显式 flag,不进入提问
290
+
278
291
  #### 前置校验
279
292
 
280
293
  - `providers.json` 必须存在
@@ -341,6 +354,8 @@ codexs import <file> [--json] [--codex-dir <path>]
341
354
  - 只支持整体替换
342
355
  - 不支持 merge
343
356
  - 写入前备份当前 `providers.json`
357
+ - 路径参数必须显式传入
358
+ - TTY 中实际替换前增加一次确认
344
359
 
345
360
  #### 成功输出
346
361
 
@@ -379,6 +394,8 @@ codexs export <file> [--force] [--json] [--codex-dir <path>]
379
394
 
380
395
  - 默认不覆盖已有文件
381
396
  - 传 `--force` 后允许覆盖
397
+ - 路径参数必须显式传入
398
+ - 当目标文件已存在、当前是 TTY 且未传 `--force` 时,可通过确认后继续覆盖
382
399
 
383
400
  #### 成功输出
384
401
 
@@ -424,7 +441,11 @@ codexs add <provider> \
424
441
 
425
442
  - provider 名必须唯一
426
443
  - 写入前备份旧 `providers.json`
427
- - 当前不支持交互式模式
444
+ - 显式参数模式保持可用
445
+ - 当缺少必填字段且 stdin/stdout 都是 TTY 时,允许渐进式提问
446
+ - `--json` 或非 TTY 场景下仍要求显式传入必填参数
447
+ - `profile` 优先从 `config.toml` 已有 profile 列表里选择,获取失败时退回文本输入
448
+ - `apiKey` 使用隐藏输入并要求二次确认
428
449
 
429
450
  #### 成功输出
430
451
 
@@ -457,12 +478,15 @@ codexs add <provider> \
457
478
  #### 输入
458
479
 
459
480
  ```bash
460
- codexs remove <provider> --force [--json] [--codex-dir <path>]
481
+ codexs remove <provider> [--force] [--json] [--codex-dir <path>]
461
482
  ```
462
483
 
463
484
  #### 行为语义
464
485
 
465
- - 必须显式传入 `--force`
486
+ - TTY 中若缺少 `<provider>`,允许先选择 provider
487
+ - TTY 中始终需要确认,确认文案必须带 provider 名
488
+ - TTY 中即使未传 `--force`,也可通过确认完成删除
489
+ - 非 TTY 或 `--json` 场景下仍要求显式 `<provider> --force`
466
490
  - 先备份再删除
467
491
 
468
492
  #### 失败错误码
@@ -536,6 +560,7 @@ codexs rollback [--json] [--codex-dir <path>]
536
560
  - 读取 `backups/latest.json`
537
561
  - 恢复 manifest 中记录的文件
538
562
  - 如果最近一次备份包含 `auth.json`,一并恢复
563
+ - TTY 中执行前会展示备份目录和受影响文件摘要,并请求确认
539
564
 
540
565
  #### 成功输出
541
566
 
@@ -99,6 +99,10 @@ AI 需要通过 `status` 或 `doctor --json` 判断本地配置是否完整、
99
99
 
100
100
  关键命令必须提供稳定、可解析的 `--json` 输出。
101
101
 
102
+ ### Progressive For Humans
103
+
104
+ 高频人类写命令可以在 TTY 中提供渐进式交互,但这层体验不能改变自动化的显式参数契约。
105
+
102
106
  ### Local First
103
107
 
104
108
  MVP 的核心对象是本地文件与本机配置,不依赖云端控制面。
@@ -278,6 +282,12 @@ MVP 固定包含以下命令:
278
282
  - 位置参数:`<provider>`
279
283
  - 可选参数:`--no-login`
280
284
 
285
+ 交互边界:
286
+
287
+ - 当 `<provider>` 缺失且运行在 TTY 中时,允许用户从 provider 列表中选择
288
+ - `--json` 或非 TTY 场景下仍要求显式 provider
289
+ - 当 provider 已显式传入时,不做额外确认
290
+
281
291
  前置校验:
282
292
 
283
293
  - `providers.json` 存在且可解析
@@ -346,6 +356,8 @@ MVP 固定包含以下命令:
346
356
  说明:
347
357
 
348
358
  - MVP 不做 merge 导入,固定为整体替换
359
+ - 路径参数保持显式,不做路径向导
360
+ - TTY 中在真正覆盖前增加确认
349
361
 
350
362
  ### `codexs export <file>`
351
363
 
@@ -361,6 +373,8 @@ MVP 固定包含以下命令:
361
373
 
362
374
  - 默认不覆盖已有文件
363
375
  - 只有传入 `--force` 时才允许覆盖
376
+ - TTY 中若目标已存在且未传 `--force`,允许通过确认覆盖
377
+ - 非 TTY 与 `--json` 保持显式 `--force` 契约
364
378
 
365
379
  ### `codexs add <provider>`
366
380
 
@@ -383,8 +397,11 @@ MVP 固定包含以下命令:
383
397
 
384
398
  范围限制:
385
399
 
386
- - MVP 只支持显式参数模式
387
- - 不提供交互式 wizard
400
+ - 显式参数模式仍是自动化主路径
401
+ - `add` 缺少必填参数且运行在 TTY 中时,允许进入渐进式交互录入
402
+ - `--json` 或非 TTY 场景下仍要求显式传参
403
+ - `profile` 优先展示 `config.toml` 中现有 profile 供选择
404
+ - `apiKey` 以隐藏输入采集并要求二次确认
388
405
 
389
406
  ### `codexs remove <provider>`
390
407
 
@@ -406,7 +423,8 @@ MVP 固定包含以下命令:
406
423
 
407
424
  范围限制:
408
425
 
409
- - 自动化调用场景下,要求显式传 `--force`
426
+ - TTY 中可选择 provider,并通过确认代替 `--force`
427
+ - 自动化调用场景下,仍要求显式传 `--force`
410
428
 
411
429
  ### `codexs doctor`
412
430
 
@@ -442,6 +460,7 @@ MVP 固定包含以下命令:
442
460
  说明:
443
461
 
444
462
  - `rollback` 进入 MVP,因为它直接支撑“默认安全”的产品承诺
463
+ - TTY 中在恢复前展示将被恢复的文件摘要,并请求确认
445
464
 
446
465
  ## 输出契约
447
466
 
@@ -236,6 +236,27 @@ scripts/
236
236
  - 文件访问
237
237
  - 输出格式化
238
238
 
239
+ #### `src/cli/prompt.ts`
240
+
241
+ 负责:
242
+
243
+ - 对 `inquirer` 做轻量封装
244
+ - 提供 `input` / `password` / `select` / `confirm` 四类 typed 交互能力
245
+ - 把 prompt 取消统一转换成稳定 CLI 错误
246
+
247
+ 不负责:
248
+
249
+ - 业务判断某个命令是否应该进入交互
250
+ - 直接读写 provider/config 文件
251
+
252
+ #### `src/cli/interactive.ts`
253
+
254
+ 负责:
255
+
256
+ - 统一判定何时允许交互
257
+ - 组合 provider 选择、危险确认、rollback 摘要展示等 CLI 级辅助逻辑
258
+ - 保持命令分支对交互的接入方式一致
259
+
239
260
  #### `src/cli/output.ts`
240
261
 
241
262
  负责:
@@ -524,19 +545,20 @@ scripts/
524
545
 
525
546
  流程如下:
526
547
 
527
- 1. 获取单操作写锁
528
- 2. 读取并解析 `providers.json`
529
- 3. 校验目标 provider 是否存在
530
- 4. 读取 `config.toml`
531
- 5. 校验 provider 对应的 `profile` 在配置中存在
532
- 6. 创建备份:
548
+ 1. CLI 层在 TTY 且 `<provider>` 缺失时,先用 selector 选 provider
549
+ 2. 获取单操作写锁
550
+ 3. 读取并解析 `providers.json`
551
+ 4. 校验目标 provider 是否存在
552
+ 5. 读取 `config.toml`
553
+ 6. 校验 provider 对应的 `profile` 在配置中存在
554
+ 7. 创建备份:
533
555
  - `config.toml`
534
556
  - `auth.json`(如果存在)
535
- 7. 更新顶层 `profile`
536
- 8. 如果未传 `--no-login`,执行 `codex login --with-api-key`
537
- 9. 成功后把这次备份记录为 `latest.json`
538
- 10. 若任何步骤失败,按 manifest 回滚
539
- 11. 释放写锁
557
+ 8. 更新顶层 `profile`
558
+ 9. 如果未传 `--no-login`,执行 `codex login --with-api-key`
559
+ 10. 成功后把这次备份记录为 `latest.json`
560
+ 11. 若任何步骤失败,按 manifest 回滚
561
+ 12. 释放写锁
540
562
 
541
563
  #### 为什么 `switch` 必须由应用层编排
542
564
 
@@ -625,11 +647,13 @@ failure path:
625
647
 
626
648
  流程:
627
649
 
628
- 1. 读取外部文件
629
- 2. 校验 JSON 和 schema
630
- 3. 备份当前 `providers.json`
631
- 4. 整体替换写入
632
- 5. 写失败则恢复旧文件
650
+ 1. CLI 层保留显式路径参数
651
+ 2. TTY 中写入前先确认
652
+ 3. 读取外部文件
653
+ 4. 校验 JSON 和 schema
654
+ 5. 备份当前 `providers.json`
655
+ 6. 整体替换写入
656
+ 7. 写失败则恢复旧文件
633
657
 
634
658
  当前明确不支持 merge import。
635
659
 
@@ -639,37 +663,46 @@ failure path:
639
663
 
640
664
  1. 读取当前 `providers.json`
641
665
  2. 检查目标文件是否存在
642
- 3. 默认拒绝覆盖
643
- 4. 传入 `--force` 时允许覆盖
666
+ 3. TTY 中若文件已存在且未传 `--force`,先确认是否覆盖
667
+ 4. 默认拒绝覆盖
668
+ 5. 传入 `--force` 或确认覆盖时允许写入
644
669
 
645
670
  ### 6.8 `add`
646
671
 
647
672
  流程:
648
673
 
649
- 1. 读取现有 provider 集合
650
- 2. 校验重名
651
- 3. 备份旧文件
652
- 4. 追加一条 provider 记录
653
- 5. 写回 `providers.json`
674
+ 1. CLI 层仅在缺失必填字段且当前是 TTY 时进入交互
675
+ 2. provider 名在 prompt 阶段尽早做重名检查
676
+ 3. profile 优先从 `config.toml` 里解析出的现有 profile 列表选择
677
+ 4. apiKey 通过隐藏输入采集并二次确认
678
+ 5. 读取现有 provider 集合
679
+ 6. 校验重名
680
+ 7. 备份旧文件
681
+ 8. 追加一条 provider 记录
682
+ 9. 写回 `providers.json`
654
683
 
655
684
  ### 6.9 `remove`
656
685
 
657
686
  流程:
658
687
 
659
- 1. 校验 provider 存在
660
- 2. 要求显式 `--force`
661
- 3. 备份 `providers.json`
662
- 4. 删除目标 provider
663
- 5. 写回
688
+ 1. CLI 层在 TTY 且缺少 provider 时可先选择 provider
689
+ 2. CLI 层在 TTY 中始终做删除确认
690
+ 3. TTY 或 `--json` 场景继续要求显式 `--force`
691
+ 4. 校验 provider 存在
692
+ 5. 备份 `providers.json`
693
+ 6. 删除目标 provider
694
+ 7. 写回
664
695
 
665
696
  ### 6.10 `rollback`
666
697
 
667
698
  流程:
668
699
 
669
- 1. 读取 `backups/latest.json`
670
- 2. 加载最近一次 manifest
671
- 3. 按 manifest 恢复文件
672
- 4. 返回恢复文件列表和备份目录
700
+ 1. TTY 中先读取 `backups/latest.json`
701
+ 2. 展示备份目录和待恢复文件摘要
702
+ 3. 请求确认
703
+ 4. 加载最近一次 manifest
704
+ 5. 按 manifest 恢复文件
705
+ 6. 返回恢复文件列表和备份目录
673
706
 
674
707
  #### `rollback` 时序图
675
708
 
@@ -934,15 +967,23 @@ tests/
934
967
 
935
968
  从 MVP 可用性上没问题,但长期更推荐拆成专门的 CLI availability 错误。
936
969
 
937
- ### 13.3 当前没有真正的交互式命令层
970
+ ### 13.3 当前交互式命令层范围仍然受控
971
+
972
+ 当前 CLI 仍以显式参数模式为主,但已经把 `inquirer` 交互扩展到了高频写命令:
973
+
974
+ - `add` 缺失必填字段时的渐进式提问
975
+ - `switch` 的 provider 选择
976
+ - `remove` 的 provider 选择与确认
977
+ - `import` / `export` 的危险确认
978
+ - `rollback` 的恢复确认
938
979
 
939
- 现在所有命令都是显式参数模式,没有:
980
+ 当前仍然没有:
940
981
 
941
- - 交互式 provider 选择
942
- - 向导式 add/import
982
+ - 路径向导式 import/export
943
983
  - TUI 状态面板
984
+ - 脱离显式参数契约的自动化交互
944
985
 
945
- 这符合 PRD 的 MVP 边界。
986
+ 这继续保持了 CLI-first 和自动化优先的主体边界。
946
987
 
947
988
  ### 13.4 尚未引入持久化审计日志
948
989
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minniexcode/codex-switch",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Local-first CLI for managing and switching Codex provider/profile configuration.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -44,5 +44,8 @@
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.12.3",
46
46
  "typescript": "^5.9.3"
47
+ },
48
+ "dependencies": {
49
+ "inquirer": "^13.4.3"
47
50
  }
48
51
  }