@minniexcode/codex-switch 0.0.3 → 0.0.5

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 (44) hide show
  1. package/README.AI.md +8 -3
  2. package/README.md +160 -91
  3. package/dist/app/add-provider.js +32 -1
  4. package/dist/app/edit-provider.js +137 -0
  5. package/dist/app/get-status.js +9 -2
  6. package/dist/app/import-providers.js +47 -2
  7. package/dist/app/list-backups.js +17 -0
  8. package/dist/app/list-config-profiles.js +29 -0
  9. package/dist/app/remove-provider.js +34 -2
  10. package/dist/app/rollback-backup.js +30 -0
  11. package/dist/app/run-doctor.js +22 -21
  12. package/dist/app/setup-codex.js +155 -0
  13. package/dist/app/show-config.js +34 -0
  14. package/dist/app/show-provider.js +22 -0
  15. package/dist/app/switch-provider.js +5 -2
  16. package/dist/cli/add-interactive.js +25 -31
  17. package/dist/cli/args.js +19 -5
  18. package/dist/cli/help.js +109 -14
  19. package/dist/cli/interactive.js +123 -8
  20. package/dist/cli/output.js +56 -1
  21. package/dist/cli/prompt.js +19 -2
  22. package/dist/cli.js +250 -13
  23. package/dist/domain/backups.js +103 -0
  24. package/dist/domain/config.js +471 -39
  25. package/dist/domain/errors.js +3 -3
  26. package/dist/domain/providers.js +10 -0
  27. package/dist/domain/setup.js +30 -0
  28. package/dist/infra/backup-repo.js +65 -6
  29. package/dist/infra/codex-cli.js +79 -2
  30. package/dist/infra/codex-discovery.js +10 -0
  31. package/dist/infra/codex-paths.js +14 -1
  32. package/dist/infra/config-repo.js +102 -9
  33. package/dist/infra/providers-repo.js +29 -0
  34. package/docs/Design/codex-switch-v0.0.4-design.md +874 -0
  35. package/docs/Design/codex-switch-v0.0.5-design.md +922 -0
  36. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +308 -0
  37. package/docs/PRD/codex-switch-prd-v0.1.0.md +343 -0
  38. package/docs/{codex-switch-prd.md → PRD/codex-switch-prd.md} +9 -5
  39. package/docs/cli-usage.md +580 -0
  40. package/docs/codex-switch-command-design.md +1 -1
  41. package/docs/codex-switch-product-overview.md +1 -1
  42. package/docs/codex-switch-product-research.md +2 -2
  43. package/docs/codex-switch-technical-architecture.md +1 -1
  44. package/package.json +1 -1
package/README.AI.md CHANGED
@@ -17,16 +17,20 @@ Primary goals:
17
17
  ## Main Command Surface
18
18
 
19
19
  ```bash
20
+ codexs setup
20
21
  codexs list
22
+ codexs show <provider>
21
23
  codexs current
22
- codexs switch <provider>
23
24
  codexs status
25
+ codexs edit <provider>
26
+ codexs switch <provider>
24
27
  codexs import <file>
25
28
  codexs export <file>
26
29
  codexs add <provider>
27
30
  codexs remove <provider>
31
+ codexs backups list
28
32
  codexs doctor
29
- codexs rollback
33
+ codexs rollback [backup-id]
30
34
  ```
31
35
 
32
36
  Shared flags:
@@ -88,11 +92,12 @@ codexs status --json
88
92
  Current package version in this repository:
89
93
 
90
94
  ```text
91
- 0.0.3
95
+ 0.0.4
92
96
  ```
93
97
 
94
98
  Recent version summary:
95
99
 
100
+ - `0.0.4`: setup/show/edit/backups list/specific rollback/import merge and clearer CLI semantics
96
101
  - `0.0.3`: interactive TTY flows and improved help
97
102
  - `0.0.2`: mutation orchestration, backups, rollback, locks, drift detection improvements
98
103
  - `0.0.1`: initial TypeScript CLI and baseline docs
package/README.md CHANGED
@@ -1,113 +1,173 @@
1
1
  # @minniexcode/codex-switch
2
2
 
3
- `@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider/profile configuration safely.
3
+ `codex-switch` is a local-first CLI for managing and switching Codex provider/profile configuration safely.
4
4
 
5
- It is built for people who use multiple Codex providers, API keys, or profiles and want a repeatable way to switch between them without manually editing files under `~/.codex/`.
5
+ `codex-switch` 是一个本地优先的 CLI,用来安全地管理和切换 Codex provider/profile 配置。
6
6
 
7
- ## What This Repository Is For
7
+ It is designed for users who work with multiple Codex providers, API keys, or profiles and want a repeatable, backup-first workflow instead of manually editing files under `~/.codex/`.
8
8
 
9
- This repository contains the CLI implementation, package metadata, and product documents for `codex-switch`.
9
+ 它面向同时维护多个 Codex provider、API key profile 的用户,目标是用可重复、先备份再写入的方式替代手动修改 `~/.codex/` 下的文件。
10
10
 
11
- The project focuses on a simple idea:
11
+ ## Overview | 简介
12
12
 
13
- - keep Codex profile switching local
14
- - back up config before writes
15
- - roll back on failure
16
- - support both humans in a terminal and AI/automation workflows
13
+ What it does:
17
14
 
18
- ## What It Can Do
15
+ - Initialize `providers.json` from an existing Codex directory
16
+ - List, show, add, edit, and remove provider records
17
+ - Switch the active provider/profile safely
18
+ - Import and export provider definitions
19
+ - Run diagnostics and detect local drift
20
+ - List backups and rollback to a previous managed state
19
21
 
20
- Current MVP command set:
22
+ 它可以完成的事情:
21
23
 
22
- ```bash
23
- codexs list
24
- codexs current
25
- codexs switch <provider>
26
- codexs status
27
- codexs import <file>
28
- codexs export <file>
29
- codexs add <provider>
30
- codexs remove <provider>
31
- codexs doctor
32
- codexs rollback
33
- ```
24
+ - 从现有 Codex 目录初始化 `providers.json`
25
+ - 查看、新增、编辑、删除 provider 记录
26
+ - 安全切换当前激活的 provider/profile
27
+ - 导入和导出 provider 配置
28
+ - 运行诊断并识别本地配置漂移
29
+ - 查看备份并回滚到之前的受管状态
34
30
 
35
- What that means in practice:
31
+ Current version: `0.0.4`
36
32
 
37
- - list locally managed providers
38
- - show the current active profile
39
- - switch to another provider safely
40
- - import and export provider mappings
41
- - add or remove provider records
42
- - detect config drift and common local issues
43
- - back up managed files before mutation and roll back when needed
33
+ 当前版本:`0.0.4`
44
34
 
45
- ## Quick Start
46
-
47
- ### For Humans
35
+ ## Install | 安装
48
36
 
49
37
  Install globally:
50
38
 
51
- ```text
39
+ ```bash
52
40
  npm install -g @minniexcode/codex-switch
53
41
  ```
54
42
 
55
- Or run without installing globally:
43
+ Or run directly:
56
44
 
57
- ```text
45
+ ```bash
58
46
  npx @minniexcode/codex-switch --help
59
47
  ```
60
48
 
61
- Check the CLI:
49
+ 全局安装:
62
50
 
63
- ```text
51
+ ```bash
52
+ npm install -g @minniexcode/codex-switch
53
+ ```
54
+
55
+ 或者直接运行:
56
+
57
+ ```bash
58
+ npx @minniexcode/codex-switch --help
59
+ ```
60
+
61
+ CLI entry:
62
+
63
+ ```bash
64
64
  codexs --help
65
65
  ```
66
66
 
67
- Typical usage:
67
+ 命令入口:
68
68
 
69
- ```text
69
+ ```bash
70
+ codexs --help
71
+ ```
72
+
73
+ ## Quick Start | 快速开始
74
+
75
+ Take over an existing Codex directory:
76
+
77
+ ```bash
78
+ codexs setup
79
+ ```
80
+
81
+ 接管当前已有的 Codex 目录:
82
+
83
+ ```bash
84
+ codexs setup
85
+ ```
86
+
87
+ Inspect managed providers:
88
+
89
+ ```bash
70
90
  codexs list
71
- codexs current
91
+ codexs show my-provider
92
+ ```
93
+
94
+ 查看已管理的 provider:
95
+
96
+ ```bash
97
+ codexs list
98
+ codexs show my-provider
99
+ ```
100
+
101
+ Add and switch:
102
+
103
+ ```bash
72
104
  codexs add my-provider --profile my-provider --api-key sk-xxx
73
105
  codexs switch my-provider
74
- codexs status
75
106
  ```
76
107
 
77
- ### For LLM Agents
108
+ 新增并切换:
109
+
110
+ ```bash
111
+ codexs add my-provider --profile my-provider --api-key sk-xxx
112
+ codexs switch my-provider
113
+ ```
78
114
 
79
- Read this first:
115
+ Check runtime state:
80
116
 
81
- ```text
82
- ./README.AI.md
117
+ ```bash
118
+ codexs current
119
+ codexs status
120
+ codexs doctor
83
121
  ```
84
122
 
85
- Then install and use the project by following the agent-specific instructions in that file.
123
+ 检查当前运行状态:
124
+
125
+ ```bash
126
+ codexs current
127
+ codexs status
128
+ codexs doctor
129
+ ```
86
130
 
87
- Reference:
131
+ ## Common Commands | 常用命令
88
132
 
89
- - [AI README](./README.AI.md)
133
+ ```bash
134
+ codexs setup
135
+ codexs list
136
+ codexs show <provider>
137
+ codexs current
138
+ codexs status
139
+ codexs add <provider> --profile <name> --api-key <key>
140
+ codexs edit <provider> [--profile <name>] [--api-key <key>]
141
+ codexs switch <provider>
142
+ codexs remove <provider>
143
+ codexs import <file> [--merge]
144
+ codexs export <file>
145
+ codexs backups list
146
+ codexs rollback [backup-id]
147
+ codexs doctor
148
+ ```
90
149
 
91
- Shared flags:
150
+ Command help:
92
151
 
93
- ```text
94
- --json
95
- --codex-dir <path>
152
+ ```bash
153
+ codexs help switch
154
+ codexs help setup
96
155
  ```
97
156
 
98
- ## Interactive Use
157
+ 命令帮助:
158
+
159
+ ```bash
160
+ codexs help switch
161
+ codexs help setup
162
+ ```
99
163
 
100
- The CLI supports both explicit commands and guided terminal flows.
164
+ ## How It Works | 工作方式
101
165
 
102
- - `codexs add` prompts for missing required values in a real TTY
103
- - `codexs switch` can show a provider selector when no provider is passed
104
- - `codexs remove` supports interactive selection and confirmation
105
- - `import`, `export`, and `rollback` ask for confirmation in interactive mode
106
- - `--json` remains non-interactive for scripts and agents
166
+ By default, `codex-switch` operates on `~/.codex/`, and you can override the target with `--codex-dir`.
107
167
 
108
- ## Files It Manages
168
+ `codex-switch` 默认围绕 `~/.codex/` 工作,也可以通过 `--codex-dir` 指向其他目录。
109
169
 
110
- `codex-switch` is designed around files under `~/.codex/`:
170
+ Managed files:
111
171
 
112
172
  ```text
113
173
  ~/.codex/
@@ -117,46 +177,55 @@ The CLI supports both explicit commands and guided terminal flows.
117
177
  backups/
118
178
  ```
119
179
 
120
- Storage model:
180
+ 说明:
121
181
 
122
- - `providers.json` is the management source of truth
123
- - `config.toml` and `auth.json` are runtime state
124
- - `backups/latest.json` tracks the latest rollback window
182
+ - `providers.json` is the managed provider registry
183
+ - `config.toml` and `auth.json` represent runtime state
184
+ - mutating commands back up before writing
185
+ - rollback is available after failed or undesired changes
125
186
 
126
- `providers.json` may contain API keys, so it should be treated as a local secret.
187
+ - `providers.json` 是受管理的 provider 注册表
188
+ - `config.toml` 和 `auth.json` 代表当前运行态
189
+ - 所有写操作都会先备份再写入
190
+ - 变更失败或结果不符合预期时可以回滚
127
191
 
128
- ## Documentation
192
+ ## Automation | 自动化
129
193
 
130
- User-oriented project docs:
194
+ This CLI supports both human TTY use and non-interactive automation.
131
195
 
132
- - [Chinese README](./README.CN.md)
133
- - [AI README](./README.AI.md)
134
- - [Product Overview](./docs/codex-switch-product-overview.md)
135
- - [Product Research](./docs/codex-switch-product-research.md)
136
- - [PRD](./docs/codex-switch-prd.md)
137
- - [Technical Architecture](./docs/codex-switch-technical-architecture.md)
138
- - [Command Design](./docs/codex-switch-command-design.md)
196
+ 这个 CLI 同时支持人工交互和非交互自动化。
139
197
 
140
- ## Latest 3 Versions
198
+ Recommended global flags:
141
199
 
142
- ### 0.0.3
200
+ ```bash
201
+ --json
202
+ --codex-dir <path>
203
+ --help
204
+ --version
205
+ ```
143
206
 
144
- - Added interactive TTY flows for high-frequency commands such as `add`, `switch`, `remove`, `import`, `export`, and `rollback`
145
- - Improved help output and command-specific guidance
146
- - Expanded CLI test coverage for interactive and argument handling behavior
207
+ 建议:
147
208
 
148
- ### 0.0.2
209
+ - use `--json` for stable machine-readable output
210
+ - pass all required arguments explicitly in scripts or CI
211
+ - use `--codex-dir <path>` for sandbox or test environments
149
212
 
150
- - Added mutation orchestration with backup-first writes, rollback handling, and single-process locking
151
- - Improved `status` and `doctor` so they can detect runtime drift more clearly
152
- - Strengthened repository and domain layers for safer config operations
213
+ - 在脚本或 CI 中使用 `--json` 获取稳定输出
214
+ - 在非交互环境中显式传入所有必需参数
215
+ - 在测试环境中优先配合 `--codex-dir <path>` 使用
153
216
 
154
- ### 0.0.1
217
+ ## Documentation | 文档
155
218
 
156
- - Shipped the initial TypeScript CLI implementation
157
- - Added the core MVP commands and file-based provider management model
158
- - Added the first full set of product, architecture, and command design docs
219
+ - [Detailed CLI Usage](./docs/cli-usage.md)
220
+ - [详细 CLI 使用文档](./docs/cli-usage.md)
221
+ - [Changelog](./CHANGELOG.md)
222
+ - [更新日志](./CHANGELOG.md)
223
+ - [AI README](./README.AI.md)
224
+ - [中文 README(历史版)](./README.CN.md)
225
+ - [Product Overview](./docs/codex-switch-product-overview.md)
226
+ - [Technical Architecture](./docs/codex-switch-technical-architecture.md)
227
+ - [0.0.4 Design Doc](./docs/codex-switch-v0.0.4-design.md)
159
228
 
160
- ## License
229
+ ## License | 许可证
161
230
 
162
231
  MIT
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addProvider = addProvider;
4
+ const config_1 = require("../domain/config");
4
5
  const providers_1 = require("../domain/providers");
5
6
  const errors_1 = require("../domain/errors");
7
+ const config_repo_1 = require("../infra/config-repo");
6
8
  const fs_utils_1 = require("../infra/fs-utils");
7
9
  const providers_repo_1 = require("../infra/providers-repo");
8
10
  const run_mutation_1 = require("./run-mutation");
@@ -15,6 +17,22 @@ function addProvider(args) {
15
17
  if (providers.providers[args.providerName]) {
16
18
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
17
19
  }
20
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
21
+ const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
22
+ if (!existingProfile && !args.createProfile) {
23
+ throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${args.profile}" does not exist in config.toml.`, {
24
+ profile: args.profile,
25
+ provider: args.providerName,
26
+ });
27
+ }
28
+ const upsertProfiles = !existingProfile && args.createProfile
29
+ ? {
30
+ [args.profile]: (0, config_1.validateManagedProfileCreation)(args.profile, {
31
+ model: args.model ?? undefined,
32
+ baseUrl: args.baseUrl ?? undefined,
33
+ }),
34
+ }
35
+ : undefined;
18
36
  const next = {
19
37
  providers: {
20
38
  ...providers.providers,
@@ -32,13 +50,26 @@ function addProvider(args) {
32
50
  backupsDir: args.backupsDir,
33
51
  latestBackupPath: args.latestBackupPath,
34
52
  operation: "add",
35
- files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
53
+ files: [
54
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
55
+ { absolutePath: args.configPath, relativePath: "config.toml" },
56
+ ],
36
57
  mutate: () => {
58
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
59
+ upsertProfiles,
60
+ });
37
61
  // Persist only the normalized provider payload so later reads are deterministic.
38
62
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
63
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
39
64
  return {
40
65
  provider: args.providerName,
41
66
  profile: args.profile,
67
+ createdProfileSections: configPlan.createdProfileSections,
68
+ deletedProfileSections: configPlan.deletedProfileSections,
69
+ keptSharedProfiles: [],
70
+ switchedActiveProfile: configPlan.switchedActiveProfile,
71
+ adoptedProfiles: [],
72
+ repairedProfiles: [],
42
73
  };
43
74
  },
44
75
  });
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.editProvider = editProvider;
4
+ const errors_1 = require("../domain/errors");
5
+ const config_1 = require("../domain/config");
6
+ const providers_1 = require("../domain/providers");
7
+ const config_repo_1 = require("../infra/config-repo");
8
+ const fs_utils_1 = require("../infra/fs-utils");
9
+ const providers_repo_1 = require("../infra/providers-repo");
10
+ const run_mutation_1 = require("./run-mutation");
11
+ /**
12
+ * Updates selected fields on a single managed provider.
13
+ */
14
+ function editProvider(args) {
15
+ (0, fs_utils_1.ensureDir)(args.codexDir);
16
+ const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
17
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
18
+ const current = providers.providers[args.providerName];
19
+ if (!current) {
20
+ throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`, {
21
+ provider: args.providerName,
22
+ file: args.providersPath,
23
+ });
24
+ }
25
+ const updatedFields = [];
26
+ const nextRecord = (0, providers_1.cleanProviderRecord)({
27
+ profile: args.profile ?? current.profile,
28
+ apiKey: args.apiKey ?? current.apiKey,
29
+ baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
30
+ note: args.note === null ? undefined : args.note ?? current.note,
31
+ tags: args.tags ?? current.tags,
32
+ });
33
+ if (args.profile !== undefined && args.profile !== current.profile) {
34
+ updatedFields.push("profile");
35
+ }
36
+ if (args.apiKey !== undefined && args.apiKey !== current.apiKey) {
37
+ updatedFields.push("apiKey");
38
+ }
39
+ if (args.baseUrl !== undefined && (args.baseUrl ?? undefined) !== current.baseUrl) {
40
+ updatedFields.push("baseUrl");
41
+ }
42
+ if (args.note !== undefined && (args.note ?? undefined) !== current.note) {
43
+ updatedFields.push("note");
44
+ }
45
+ if (args.tags !== undefined) {
46
+ updatedFields.push("tags");
47
+ }
48
+ const oldProfile = current.profile;
49
+ const newProfile = nextRecord.profile;
50
+ const targetSection = document.profiles.find((profile) => profile.name === newProfile) ?? null;
51
+ const targetProfileExists = Boolean(targetSection);
52
+ let upsertProfiles;
53
+ if (!targetProfileExists) {
54
+ if (!args.createProfile) {
55
+ throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${newProfile}" does not exist in config.toml.`, {
56
+ profile: newProfile,
57
+ provider: args.providerName,
58
+ });
59
+ }
60
+ upsertProfiles = {
61
+ [newProfile]: (0, config_1.validateManagedProfileCreation)(newProfile, {
62
+ model: args.model ?? undefined,
63
+ baseUrl: args.baseUrl ?? undefined,
64
+ }),
65
+ };
66
+ }
67
+ else if (args.model !== undefined || args.baseUrl !== undefined) {
68
+ upsertProfiles = {
69
+ [newProfile]: {
70
+ ...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
71
+ ...(args.baseUrl !== undefined && args.baseUrl !== null ? { baseUrl: args.baseUrl } : {}),
72
+ },
73
+ };
74
+ if (args.model !== undefined && (targetSection?.model !== args.model) && !updatedFields.includes("model")) {
75
+ updatedFields.push("model");
76
+ }
77
+ if (args.baseUrl !== undefined && targetSection?.baseUrl !== args.baseUrl && !updatedFields.includes("baseUrl")) {
78
+ updatedFields.push("baseUrl");
79
+ }
80
+ }
81
+ const remainingLinksByProfile = new Map();
82
+ for (const [name, provider] of Object.entries(providers.providers)) {
83
+ if (name === args.providerName) {
84
+ continue;
85
+ }
86
+ const list = remainingLinksByProfile.get(provider.profile) ?? [];
87
+ list.push(name);
88
+ remainingLinksByProfile.set(provider.profile, list);
89
+ }
90
+ if (newProfile !== oldProfile) {
91
+ const list = remainingLinksByProfile.get(newProfile) ?? [];
92
+ list.push(args.providerName);
93
+ remainingLinksByProfile.set(newProfile, list);
94
+ }
95
+ const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
96
+ providerName: args.providerName,
97
+ oldProfile,
98
+ newProfile,
99
+ activeProfile: document.activeProfile,
100
+ remainingLinksByProfile,
101
+ switchToProfile: args.switchToProfile ?? null,
102
+ });
103
+ return (0, run_mutation_1.runMutation)({
104
+ codexDir: args.codexDir,
105
+ backupsDir: args.backupsDir,
106
+ latestBackupPath: args.latestBackupPath,
107
+ operation: "edit",
108
+ files: [
109
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
110
+ { absolutePath: args.configPath, relativePath: "config.toml" },
111
+ ],
112
+ mutate: () => {
113
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
114
+ upsertProfiles,
115
+ deleteProfiles: lifecycle.deletedProfileSections,
116
+ setActiveProfile: lifecycle.nextActiveProfile,
117
+ });
118
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, {
119
+ providers: {
120
+ ...providers.providers,
121
+ [args.providerName]: nextRecord,
122
+ },
123
+ });
124
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
125
+ return {
126
+ provider: args.providerName,
127
+ updatedFields,
128
+ createdProfileSections: configPlan.createdProfileSections,
129
+ deletedProfileSections: configPlan.deletedProfileSections,
130
+ keptSharedProfiles: lifecycle.keptSharedProfiles,
131
+ switchedActiveProfile: lifecycle.switchedActiveProfile,
132
+ adoptedProfiles: [],
133
+ repairedProfiles: [],
134
+ };
135
+ },
136
+ });
137
+ }
@@ -37,6 +37,7 @@ exports.getStatus = getStatus;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const config_1 = require("../domain/config");
39
39
  const runtime_state_1 = require("../domain/runtime-state");
40
+ const config_repo_1 = require("../infra/config-repo");
40
41
  const providers_repo_1 = require("../infra/providers-repo");
41
42
  /**
42
43
  * Reports the current on-disk runtime state and how it maps back to managed providers.
@@ -47,9 +48,13 @@ function getStatus(codexDir, configPath, providersPath) {
47
48
  let currentProfile = null;
48
49
  const warnings = [];
49
50
  const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
51
+ let configViews = [];
52
+ let consistencyIssues = [];
50
53
  if (configExists) {
51
- const configContent = fs.readFileSync(configPath, "utf8");
52
- currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
54
+ const document = (0, config_repo_1.readStructuredConfig)(configPath);
55
+ currentProfile = document.activeProfile;
56
+ configViews = (0, config_1.buildManagedProfileViews)(document, providers);
57
+ consistencyIssues = (0, config_1.collectConfigConsistencyIssues)(document, providers);
53
58
  if (!currentProfile) {
54
59
  warnings.push("config.toml exists but has no top-level profile.");
55
60
  }
@@ -70,6 +75,8 @@ function getStatus(codexDir, configPath, providersPath) {
70
75
  currentProfileMapped: liveState.profileMapped,
71
76
  provider: liveState.mappedProvider,
72
77
  liveState,
78
+ configProfiles: configViews,
79
+ issues: consistencyIssues,
73
80
  },
74
81
  };
75
82
  }
@@ -36,8 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.importProviders = importProviders;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const path = __importStar(require("node:path"));
39
+ const config_1 = require("../domain/config");
39
40
  const providers_1 = require("../domain/providers");
40
41
  const errors_1 = require("../domain/errors");
42
+ const config_repo_1 = require("../infra/config-repo");
41
43
  const fs_utils_1 = require("../infra/fs-utils");
42
44
  const providers_repo_1 = require("../infra/providers-repo");
43
45
  const run_mutation_1 = require("./run-mutation");
@@ -58,16 +60,59 @@ function importProviders(args) {
58
60
  });
59
61
  }
60
62
  (0, fs_utils_1.ensureDir)(args.codexDir);
63
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
61
64
  return (0, run_mutation_1.runMutation)({
62
65
  codexDir: args.codexDir,
63
66
  backupsDir: args.backupsDir,
64
67
  latestBackupPath: args.latestBackupPath,
65
68
  operation: "import",
66
- files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
69
+ files: [
70
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
71
+ { absolutePath: args.configPath, relativePath: "config.toml" },
72
+ ],
67
73
  mutate: () => {
68
- (0, providers_repo_1.writeProvidersFile)(args.providersPath, imported);
74
+ const current = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
75
+ const next = args.merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
76
+ const currentViews = (0, config_1.buildManagedProfileViews)(document, current);
77
+ const nextViews = (0, config_1.buildManagedProfileViews)(document, next);
78
+ const adoptedProfiles = currentViews
79
+ .filter((view) => view.source === "unmanaged" && view.linkedProviders.length === 0)
80
+ .filter((view) => nextViews.some((nextView) => nextView.name === view.name && nextView.managed))
81
+ .map((view) => view.name)
82
+ .sort();
83
+ const missingViews = nextViews.filter((view) => view.source === "orphaned-reference");
84
+ const repairedProfiles = [];
85
+ const upsertProfiles = missingViews.reduce((accumulator, view) => {
86
+ const repair = args.repairProfiles?.[view.name];
87
+ if (!repair) {
88
+ throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", "Import would create provider references to missing config profiles that need model and base_url.", {
89
+ profilesNeedingRepair: missingViews.map((entry) => entry.name).sort(),
90
+ });
91
+ }
92
+ accumulator[view.name] = (0, config_1.validateManagedProfileCreation)(view.name, repair);
93
+ repairedProfiles.push(view.name);
94
+ return accumulator;
95
+ }, {});
96
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
97
+ upsertProfiles,
98
+ });
99
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
100
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
101
+ const replacedProviders = args.merge
102
+ ? Object.keys(imported.providers).filter((name) => current.providers[name]).sort()
103
+ : [];
69
104
  return {
105
+ mode: args.merge ? "merge" : "replace",
70
106
  importedProviders: Object.keys(imported.providers).sort(),
107
+ importedCount: Object.keys(imported.providers).length,
108
+ mergedCount: Object.keys(next.providers).length,
109
+ replacedProviders,
110
+ createdProfileSections: configPlan.createdProfileSections,
111
+ deletedProfileSections: [],
112
+ keptSharedProfiles: nextViews.filter((view) => view.linkedProviders.length > 1).map((view) => view.name),
113
+ switchedActiveProfile: false,
114
+ adoptedProfiles,
115
+ repairedProfiles: repairedProfiles.sort(),
71
116
  };
72
117
  },
73
118
  });
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listBackupEntries = listBackupEntries;
4
+ const backup_repo_1 = require("../infra/backup-repo");
5
+ /**
6
+ * Lists backup manifests available under the managed Codex backups directory.
7
+ */
8
+ function listBackupEntries(backupsDir) {
9
+ const result = (0, backup_repo_1.listBackups)(backupsDir);
10
+ return {
11
+ data: {
12
+ backups: result.backups,
13
+ count: result.backups.length,
14
+ },
15
+ warnings: result.warnings,
16
+ };
17
+ }