@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/README.AI.md +105 -0
- package/README.CN.md +160 -0
- package/README.md +100 -131
- package/dist/cli/add-interactive.js +114 -0
- package/dist/cli/args.js +36 -9
- package/dist/cli/help.js +220 -0
- package/dist/cli/interactive.js +114 -0
- package/dist/cli/prompt.js +93 -0
- package/dist/cli.js +76 -47
- package/docs/codex-switch-command-design.md +29 -4
- package/docs/codex-switch-prd.md +22 -3
- package/docs/codex-switch-technical-architecture.md +78 -37
- package/package.json +4 -1
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
|
|
22
|
-
const
|
|
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(`${
|
|
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 (
|
|
62
|
-
|
|
44
|
+
if (parsed.versionRequested) {
|
|
45
|
+
printVersion();
|
|
63
46
|
process.exit(0);
|
|
64
47
|
}
|
|
65
|
-
if (parsed.
|
|
66
|
-
|
|
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
|
|
70
|
-
|
|
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
|
-
|
|
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
|
|
137
|
+
force,
|
|
135
138
|
});
|
|
136
139
|
}
|
|
137
140
|
case "add": {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!profile || !apiKey) {
|
|
145
|
-
|
|
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
|
|
156
|
-
note
|
|
157
|
-
tags
|
|
174
|
+
baseUrl,
|
|
175
|
+
note,
|
|
176
|
+
tags,
|
|
158
177
|
});
|
|
159
178
|
}
|
|
160
179
|
case "remove": {
|
|
161
|
-
|
|
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,
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
package/docs/codex-switch-prd.md
CHANGED
|
@@ -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
|
-
-
|
|
387
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
529
|
-
3.
|
|
530
|
-
4.
|
|
531
|
-
5.
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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.
|
|
630
|
-
3.
|
|
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.
|
|
666
|
+
3. TTY 中若文件已存在且未传 `--force`,先确认是否覆盖
|
|
667
|
+
4. 默认拒绝覆盖
|
|
668
|
+
5. 传入 `--force` 或确认覆盖时允许写入
|
|
644
669
|
|
|
645
670
|
### 6.8 `add`
|
|
646
671
|
|
|
647
672
|
流程:
|
|
648
673
|
|
|
649
|
-
1.
|
|
650
|
-
2.
|
|
651
|
-
3.
|
|
652
|
-
4.
|
|
653
|
-
5.
|
|
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.
|
|
660
|
-
2.
|
|
661
|
-
3.
|
|
662
|
-
4.
|
|
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.
|
|
670
|
-
2.
|
|
671
|
-
3.
|
|
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
|
-
-
|
|
942
|
-
- 向导式 add/import
|
|
982
|
+
- 路径向导式 import/export
|
|
943
983
|
- TUI 状态面板
|
|
984
|
+
- 脱离显式参数契约的自动化交互
|
|
944
985
|
|
|
945
|
-
|
|
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.
|
|
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
|
}
|