@minniexcode/codex-switch 0.0.4 → 0.0.6

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 (64) hide show
  1. package/README.md +35 -97
  2. package/dist/app/add-provider.js +40 -3
  3. package/dist/app/edit-provider.js +76 -3
  4. package/dist/app/export-providers.js +2 -2
  5. package/dist/app/get-current-profile.js +1 -1
  6. package/dist/app/get-status.js +10 -3
  7. package/dist/app/import-providers.js +47 -3
  8. package/dist/app/list-backups.js +1 -1
  9. package/dist/app/list-config-profiles.js +30 -0
  10. package/dist/app/list-providers.js +1 -1
  11. package/dist/app/remove-provider.js +35 -3
  12. package/dist/app/rollback-backup.js +1 -1
  13. package/dist/app/rollback-latest.js +1 -1
  14. package/dist/app/run-doctor.js +44 -26
  15. package/dist/app/run-mutation.js +2 -2
  16. package/dist/app/setup-codex.js +37 -20
  17. package/dist/app/show-config.js +34 -0
  18. package/dist/app/show-provider.js +1 -1
  19. package/dist/app/switch-provider.js +8 -5
  20. package/dist/cli/add-interactive.js +7 -106
  21. package/dist/cli/args.js +5 -126
  22. package/dist/cli/help.js +5 -276
  23. package/dist/cli/interactive.js +16 -171
  24. package/dist/cli/output.js +23 -1
  25. package/dist/cli/prompt.js +3 -108
  26. package/dist/cli.js +10 -315
  27. package/dist/commands/args.js +132 -0
  28. package/dist/commands/dispatch.js +16 -0
  29. package/dist/commands/handlers.js +391 -0
  30. package/dist/commands/help.js +119 -0
  31. package/dist/commands/registry.js +291 -0
  32. package/dist/commands/types.js +2 -0
  33. package/dist/domain/config.js +548 -39
  34. package/dist/infra/backup-repo.js +8 -208
  35. package/dist/infra/codex-cli.js +8 -113
  36. package/dist/infra/codex-discovery.js +3 -41
  37. package/dist/infra/codex-paths.js +5 -69
  38. package/dist/infra/config-repo.js +161 -9
  39. package/dist/infra/fs-utils.js +7 -95
  40. package/dist/infra/lock-repo.js +3 -97
  41. package/dist/infra/providers-repo.js +7 -96
  42. package/dist/interaction/add-interactive.js +108 -0
  43. package/dist/interaction/interactive.js +216 -0
  44. package/dist/interaction/prompt.js +110 -0
  45. package/dist/runtime/codex-cli.js +130 -0
  46. package/dist/runtime/codex-probe.js +50 -0
  47. package/dist/runtime/types.js +2 -0
  48. package/dist/storage/backup-repo.js +210 -0
  49. package/dist/storage/codex-paths.js +71 -0
  50. package/dist/storage/config-repo.js +208 -0
  51. package/dist/storage/fs-utils.js +97 -0
  52. package/dist/storage/lock-repo.js +99 -0
  53. package/dist/storage/providers-repo.js +98 -0
  54. package/docs/Design/codex-switch-v0.0.5-design.md +932 -0
  55. package/docs/Design/codex-switch-v0.0.6-design.md +708 -0
  56. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +340 -0
  57. package/docs/PRD/codex-switch-prd-v0.1.0.md +215 -291
  58. package/docs/PRD/codex-switch-prd.md +1 -1
  59. package/docs/cli-usage.md +2 -1
  60. package/docs/codex-switch-technical-architecture.md +73 -4
  61. package/docs/test-report-0.0.5.md +163 -0
  62. package/docs/testing.md +131 -0
  63. package/package.json +1 -1
  64. /package/docs/{codex-switch-v0.0.4-design.md → Design/codex-switch-v0.0.4-design.md} +0 -0
package/README.md CHANGED
@@ -1,14 +1,12 @@
1
- # @minniexcode/codex-switch
1
+ ## @minniexcode/codex-switch
2
2
 
3
3
  `codex-switch` is a local-first CLI for managing and switching Codex provider/profile configuration safely.
4
4
 
5
- `codex-switch` 是一个本地优先的 CLI,用来安全地管理和切换 Codex 的 provider/profile 配置。
6
-
7
5
  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
6
 
9
- 它面向同时维护多个 Codex provider、API key 或 profile 的用户,目标是用可重复、先备份再写入的方式替代手动修改 `~/.codex/` 下的文件。
7
+ 中文版: [README.CN.md](./README.CN.md)
10
8
 
11
- ## Overview | 简介
9
+ ## Overview
12
10
 
13
11
  What it does:
14
12
 
@@ -17,22 +15,11 @@ What it does:
17
15
  - Switch the active provider/profile safely
18
16
  - Import and export provider definitions
19
17
  - Run diagnostics and detect local drift
20
- - List backups and rollback to a previous managed state
21
-
22
- 它可以完成的事情:
23
-
24
- - 从现有 Codex 目录初始化 `providers.json`
25
- - 查看、新增、编辑、删除 provider 记录
26
- - 安全切换当前激活的 provider/profile
27
- - 导入和导出 provider 配置
28
- - 运行诊断并识别本地配置漂移
29
- - 查看备份并回滚到之前的受管状态
30
-
31
- Current version: `0.0.4`
18
+ - List backups and roll back to a previous managed state
32
19
 
33
- 当前版本:`0.0.4`
20
+ Current version: `0.0.6`
34
21
 
35
- ## Install | 安装
22
+ ## Install
36
23
 
37
24
  Install globally:
38
25
 
@@ -46,31 +33,13 @@ Or run directly:
46
33
  npx @minniexcode/codex-switch --help
47
34
  ```
48
35
 
49
- 全局安装:
50
-
51
- ```bash
52
- npm install -g @minniexcode/codex-switch
53
- ```
54
-
55
- 或者直接运行:
56
-
57
- ```bash
58
- npx @minniexcode/codex-switch --help
59
- ```
60
-
61
36
  CLI entry:
62
37
 
63
38
  ```bash
64
39
  codexs --help
65
40
  ```
66
41
 
67
- 命令入口:
68
-
69
- ```bash
70
- codexs --help
71
- ```
72
-
73
- ## Quick Start | 快速开始
42
+ ## Quick Start
74
43
 
75
44
  Take over an existing Codex directory:
76
45
 
@@ -78,12 +47,6 @@ Take over an existing Codex directory:
78
47
  codexs setup
79
48
  ```
80
49
 
81
- 接管当前已有的 Codex 目录:
82
-
83
- ```bash
84
- codexs setup
85
- ```
86
-
87
50
  Inspect managed providers:
88
51
 
89
52
  ```bash
@@ -91,13 +54,6 @@ codexs list
91
54
  codexs show my-provider
92
55
  ```
93
56
 
94
- 查看已管理的 provider:
95
-
96
- ```bash
97
- codexs list
98
- codexs show my-provider
99
- ```
100
-
101
57
  Add and switch:
102
58
 
103
59
  ```bash
@@ -105,13 +61,6 @@ codexs add my-provider --profile my-provider --api-key sk-xxx
105
61
  codexs switch my-provider
106
62
  ```
107
63
 
108
- 新增并切换:
109
-
110
- ```bash
111
- codexs add my-provider --profile my-provider --api-key sk-xxx
112
- codexs switch my-provider
113
- ```
114
-
115
64
  Check runtime state:
116
65
 
117
66
  ```bash
@@ -120,15 +69,7 @@ codexs status
120
69
  codexs doctor
121
70
  ```
122
71
 
123
- 检查当前运行状态:
124
-
125
- ```bash
126
- codexs current
127
- codexs status
128
- codexs doctor
129
- ```
130
-
131
- ## Common Commands | 常用命令
72
+ ## Common Commands
132
73
 
133
74
  ```bash
134
75
  codexs setup
@@ -154,19 +95,10 @@ codexs help switch
154
95
  codexs help setup
155
96
  ```
156
97
 
157
- 命令帮助:
158
-
159
- ```bash
160
- codexs help switch
161
- codexs help setup
162
- ```
163
-
164
- ## How It Works | 工作方式
98
+ ## How It Works
165
99
 
166
100
  By default, `codex-switch` operates on `~/.codex/`, and you can override the target with `--codex-dir`.
167
101
 
168
- `codex-switch` 默认围绕 `~/.codex/` 工作,也可以通过 `--codex-dir` 指向其他目录。
169
-
170
102
  Managed files:
171
103
 
172
104
  ```text
@@ -177,23 +109,19 @@ Managed files:
177
109
  backups/
178
110
  ```
179
111
 
180
- 说明:
112
+ Notes:
181
113
 
182
114
  - `providers.json` is the managed provider registry
183
115
  - `config.toml` and `auth.json` represent runtime state
184
116
  - mutating commands back up before writing
185
117
  - rollback is available after failed or undesired changes
186
118
 
187
- - `providers.json` 是受管理的 provider 注册表
188
- - `config.toml` 和 `auth.json` 代表当前运行态
189
- - 所有写操作都会先备份再写入
190
- - 变更失败或结果不符合预期时可以回滚
191
-
192
- ## Automation | 自动化
119
+ ## Automation
193
120
 
194
121
  This CLI supports both human TTY use and non-interactive automation.
195
122
 
196
- 这个 CLI 同时支持人工交互和非交互自动化。
123
+ Current exception:
124
+ - `setup` in `0.0.6` is intentionally TTY-only for adopt initialization. It requires interactive profile selection and provider detail collection, and non-interactive/`--json` runs fail fast with a structured error.
197
125
 
198
126
  Recommended global flags:
199
127
 
@@ -204,28 +132,38 @@ Recommended global flags:
204
132
  --version
205
133
  ```
206
134
 
207
- 建议:
135
+ Recommendations:
208
136
 
209
137
  - use `--json` for stable machine-readable output
210
138
  - pass all required arguments explicitly in scripts or CI
211
139
  - use `--codex-dir <path>` for sandbox or test environments
212
140
 
213
- - 在脚本或 CI 中使用 `--json` 获取稳定输出
214
- - 在非交互环境中显式传入所有必需参数
215
- - 在测试环境中优先配合 `--codex-dir <path>` 使用
141
+ ## Testing
216
142
 
217
- ## Documentation | 文档
143
+ Build and test locally:
218
144
 
219
- - [Detailed CLI Usage](./docs/cli-usage.md)
220
- - [详细 CLI 使用文档](./docs/cli-usage.md)
221
- - [Changelog](./CHANGELOG.md)
222
- - [更新日志](./CHANGELOG.md)
145
+ ```bash
146
+ npm run build
147
+ npm test
148
+ ```
149
+
150
+ The repository includes a development fixture under `dev-codex/local-sandbox` plus dedicated test docs:
151
+
152
+ - [Testing Guide](./docs/testing.md)
153
+ - [Test Report for 0.0.5](./docs/test-report-0.0.5.md)
154
+
155
+ ## Documentation
156
+
157
+ - [Chinese README](./README.CN.md)
223
158
  - [AI README](./README.AI.md)
224
- - [中文 README(历史版)](./README.CN.md)
159
+ - [Detailed CLI Usage](./docs/cli-usage.md)
160
+ - [Testing Guide](./docs/testing.md)
161
+ - [Test Report for 0.0.5](./docs/test-report-0.0.5.md)
225
162
  - [Product Overview](./docs/codex-switch-product-overview.md)
226
163
  - [Technical Architecture](./docs/codex-switch-technical-architecture.md)
227
- - [0.0.4 Design Doc](./docs/codex-switch-v0.0.4-design.md)
164
+ - [Design Doc 0.0.5](./docs/Design/codex-switch-v0.0.5-design.md)
165
+ - [Design Doc 0.0.4](./docs/Design/codex-switch-v0.0.4-design.md)
228
166
 
229
- ## License | 许可证
167
+ ## License
230
168
 
231
169
  MIT
@@ -1,10 +1,12 @@
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");
6
- const fs_utils_1 = require("../infra/fs-utils");
7
- const providers_repo_1 = require("../infra/providers-repo");
7
+ const config_repo_1 = require("../storage/config-repo");
8
+ const fs_utils_1 = require("../storage/fs-utils");
9
+ const providers_repo_1 = require("../storage/providers-repo");
8
10
  const run_mutation_1 = require("./run-mutation");
9
11
  /**
10
12
  * Adds a new provider record to the managed providers registry.
@@ -15,6 +17,28 @@ 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
+ modelProvider: args.profile,
33
+ }),
34
+ }
35
+ : undefined;
36
+ if (!existingProfile && args.createProfile) {
37
+ (0, config_repo_1.requireModelProviderRuntimeSection)(document, args.profile);
38
+ }
39
+ if (existingProfile) {
40
+ (0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
41
+ }
18
42
  const next = {
19
43
  providers: {
20
44
  ...providers.providers,
@@ -32,13 +56,26 @@ function addProvider(args) {
32
56
  backupsDir: args.backupsDir,
33
57
  latestBackupPath: args.latestBackupPath,
34
58
  operation: "add",
35
- files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
59
+ files: [
60
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
61
+ { absolutePath: args.configPath, relativePath: "config.toml" },
62
+ ],
36
63
  mutate: () => {
64
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
65
+ upsertProfiles,
66
+ });
37
67
  // Persist only the normalized provider payload so later reads are deterministic.
38
68
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
69
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
39
70
  return {
40
71
  provider: args.providerName,
41
72
  profile: args.profile,
73
+ createdProfileSections: configPlan.createdProfileSections,
74
+ deletedProfileSections: configPlan.deletedProfileSections,
75
+ keptSharedProfiles: [],
76
+ switchedActiveProfile: configPlan.switchedActiveProfile,
77
+ adoptedProfiles: [],
78
+ repairedProfiles: [],
42
79
  };
43
80
  },
44
81
  });
@@ -2,9 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.editProvider = editProvider;
4
4
  const errors_1 = require("../domain/errors");
5
+ const config_1 = require("../domain/config");
5
6
  const providers_1 = require("../domain/providers");
6
- const fs_utils_1 = require("../infra/fs-utils");
7
- const providers_repo_1 = require("../infra/providers-repo");
7
+ const config_repo_1 = require("../storage/config-repo");
8
+ const fs_utils_1 = require("../storage/fs-utils");
9
+ const providers_repo_1 = require("../storage/providers-repo");
8
10
  const run_mutation_1 = require("./run-mutation");
9
11
  /**
10
12
  * Updates selected fields on a single managed provider.
@@ -12,6 +14,7 @@ const run_mutation_1 = require("./run-mutation");
12
14
  function editProvider(args) {
13
15
  (0, fs_utils_1.ensureDir)(args.codexDir);
14
16
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
17
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
15
18
  const current = providers.providers[args.providerName];
16
19
  if (!current) {
17
20
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`, {
@@ -42,22 +45,92 @@ function editProvider(args) {
42
45
  if (args.tags !== undefined) {
43
46
  updatedFields.push("tags");
44
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
+ modelProvider: newProfile,
64
+ }),
65
+ };
66
+ (0, config_repo_1.requireModelProviderRuntimeSection)(document, newProfile);
67
+ }
68
+ else {
69
+ (0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
70
+ }
71
+ if (targetProfileExists && args.model !== undefined) {
72
+ upsertProfiles = {
73
+ [newProfile]: {
74
+ ...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
75
+ },
76
+ };
77
+ if (args.model !== undefined && (targetSection?.model !== args.model) && !updatedFields.includes("model")) {
78
+ updatedFields.push("model");
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
+ });
45
103
  return (0, run_mutation_1.runMutation)({
46
104
  codexDir: args.codexDir,
47
105
  backupsDir: args.backupsDir,
48
106
  latestBackupPath: args.latestBackupPath,
49
107
  operation: "edit",
50
- files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
108
+ files: [
109
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
110
+ { absolutePath: args.configPath, relativePath: "config.toml" },
111
+ ],
51
112
  mutate: () => {
113
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
114
+ upsertProfiles,
115
+ deleteProfiles: lifecycle.deletedProfileSections,
116
+ setActiveProfile: lifecycle.nextActiveProfile,
117
+ });
52
118
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, {
53
119
  providers: {
54
120
  ...providers.providers,
55
121
  [args.providerName]: nextRecord,
56
122
  },
57
123
  });
124
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
58
125
  return {
59
126
  provider: args.providerName,
60
127
  updatedFields,
128
+ createdProfileSections: configPlan.createdProfileSections,
129
+ deletedProfileSections: configPlan.deletedProfileSections,
130
+ keptSharedProfiles: lifecycle.keptSharedProfiles,
131
+ switchedActiveProfile: lifecycle.switchedActiveProfile,
132
+ adoptedProfiles: [],
133
+ repairedProfiles: [],
61
134
  };
62
135
  },
63
136
  });
@@ -37,8 +37,8 @@ exports.exportProviders = exportProviders;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const path = __importStar(require("node:path"));
39
39
  const errors_1 = require("../domain/errors");
40
- const fs_utils_1 = require("../infra/fs-utils");
41
- const providers_repo_1 = require("../infra/providers-repo");
40
+ const fs_utils_1 = require("../storage/fs-utils");
41
+ const providers_repo_1 = require("../storage/providers-repo");
42
42
  /**
43
43
  * Exports the current providers registry to a user-specified file.
44
44
  */
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCurrentProfile = getCurrentProfile;
4
- const config_repo_1 = require("../infra/config-repo");
4
+ const config_repo_1 = require("../storage/config-repo");
5
5
  /**
6
6
  * Returns the currently active top-level Codex profile.
7
7
  */
@@ -37,7 +37,8 @@ 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 providers_repo_1 = require("../infra/providers-repo");
40
+ const config_repo_1 = require("../storage/config-repo");
41
+ const providers_repo_1 = require("../storage/providers-repo");
41
42
  /**
42
43
  * Reports the current on-disk runtime state and how it maps back to managed providers.
43
44
  */
@@ -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,10 +36,12 @@ 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");
41
- const fs_utils_1 = require("../infra/fs-utils");
42
- const providers_repo_1 = require("../infra/providers-repo");
42
+ const config_repo_1 = require("../storage/config-repo");
43
+ const fs_utils_1 = require("../storage/fs-utils");
44
+ const providers_repo_1 = require("../storage/providers-repo");
43
45
  const run_mutation_1 = require("./run-mutation");
44
46
  /**
45
47
  * Imports provider definitions from an external JSON file into the managed registry.
@@ -58,16 +60,52 @@ 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
74
  const current = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
69
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 sourceView = currentViews.find((entry) => entry.name === view.name) ?? null;
87
+ if (!sourceView?.model) {
88
+ throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", "Import would create provider references to missing config profiles that need model and matching model_provider sections.", {
89
+ profilesNeedingRepair: missingViews.map((entry) => entry.name).sort(),
90
+ });
91
+ }
92
+ if (sourceView.modelProvider !== view.name || !sourceView.baseUrl) {
93
+ throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", "Import would create provider references to missing config profiles without matching model_provider runtime sections.", {
94
+ profilesNeedingRepair: missingViews.map((entry) => entry.name).sort(),
95
+ });
96
+ }
97
+ accumulator[view.name] = (0, config_1.validateManagedProfileCreation)(view.name, {
98
+ model: sourceView.model,
99
+ modelProvider: view.name,
100
+ });
101
+ repairedProfiles.push(view.name);
102
+ return accumulator;
103
+ }, {});
104
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
105
+ upsertProfiles,
106
+ });
70
107
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
108
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
71
109
  const replacedProviders = args.merge
72
110
  ? Object.keys(imported.providers).filter((name) => current.providers[name]).sort()
73
111
  : [];
@@ -77,6 +115,12 @@ function importProviders(args) {
77
115
  importedCount: Object.keys(imported.providers).length,
78
116
  mergedCount: Object.keys(next.providers).length,
79
117
  replacedProviders,
118
+ createdProfileSections: configPlan.createdProfileSections,
119
+ deletedProfileSections: [],
120
+ keptSharedProfiles: nextViews.filter((view) => view.linkedProviders.length > 1).map((view) => view.name),
121
+ switchedActiveProfile: false,
122
+ adoptedProfiles,
123
+ repairedProfiles: repairedProfiles.sort(),
80
124
  };
81
125
  },
82
126
  });
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listBackupEntries = listBackupEntries;
4
- const backup_repo_1 = require("../infra/backup-repo");
4
+ const backup_repo_1 = require("../storage/backup-repo");
5
5
  /**
6
6
  * Lists backup manifests available under the managed Codex backups directory.
7
7
  */
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listConfigProfilesView = listConfigProfilesView;
4
+ const config_1 = require("../domain/config");
5
+ const config_repo_1 = require("../storage/config-repo");
6
+ const providers_repo_1 = require("../storage/providers-repo");
7
+ /**
8
+ * Returns the lightweight config profile listing.
9
+ */
10
+ function listConfigProfilesView(args) {
11
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
12
+ const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
13
+ const profiles = (0, config_1.buildManagedProfileViews)(document, providers).map((profile) => ({
14
+ name: profile.name,
15
+ managed: profile.managed,
16
+ isActive: profile.isActive,
17
+ linkedProviders: profile.linkedProviders,
18
+ model: profile.model,
19
+ modelProvider: profile.modelProvider,
20
+ baseUrl: profile.baseUrl,
21
+ source: profile.source,
22
+ }));
23
+ return {
24
+ data: {
25
+ activeProfile: document.activeProfile,
26
+ profiles,
27
+ count: profiles.length,
28
+ },
29
+ };
30
+ }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listProviders = listProviders;
4
- const providers_repo_1 = require("../infra/providers-repo");
4
+ const providers_repo_1 = require("../storage/providers-repo");
5
5
  /**
6
6
  * Returns the sorted list of configured providers for display.
7
7
  */
@@ -2,29 +2,61 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.removeProvider = removeProvider;
4
4
  const errors_1 = require("../domain/errors");
5
- const providers_repo_1 = require("../infra/providers-repo");
5
+ const config_1 = require("../domain/config");
6
+ const config_repo_1 = require("../storage/config-repo");
7
+ const providers_repo_1 = require("../storage/providers-repo");
6
8
  const run_mutation_1 = require("./run-mutation");
7
9
  /**
8
10
  * Removes a provider from the managed providers registry.
9
11
  */
10
12
  function removeProvider(args) {
11
13
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
12
- if (!providers.providers[args.providerName]) {
14
+ const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
15
+ const current = providers.providers[args.providerName];
16
+ if (!current) {
13
17
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`);
14
18
  }
15
19
  const nextProviders = { ...providers.providers };
16
20
  // Delete against a copied object so the original parsed state stays untouched.
17
21
  delete nextProviders[args.providerName];
22
+ const remainingLinksByProfile = new Map();
23
+ for (const [name, provider] of Object.entries(nextProviders)) {
24
+ const list = remainingLinksByProfile.get(provider.profile) ?? [];
25
+ list.push(name);
26
+ remainingLinksByProfile.set(provider.profile, list);
27
+ }
28
+ const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
29
+ providerName: args.providerName,
30
+ oldProfile: current.profile,
31
+ newProfile: null,
32
+ activeProfile: document.activeProfile,
33
+ remainingLinksByProfile,
34
+ switchToProfile: args.switchToProfile ?? null,
35
+ });
18
36
  return (0, run_mutation_1.runMutation)({
19
37
  codexDir: args.codexDir,
20
38
  backupsDir: args.backupsDir,
21
39
  latestBackupPath: args.latestBackupPath,
22
40
  operation: "remove",
23
- files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
41
+ files: [
42
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
43
+ { absolutePath: args.configPath, relativePath: "config.toml" },
44
+ ],
24
45
  mutate: () => {
46
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
47
+ deleteProfiles: lifecycle.deletedProfileSections,
48
+ setActiveProfile: lifecycle.nextActiveProfile,
49
+ });
25
50
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: nextProviders });
51
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
26
52
  return {
27
53
  provider: args.providerName,
54
+ createdProfileSections: configPlan.createdProfileSections,
55
+ deletedProfileSections: configPlan.deletedProfileSections,
56
+ keptSharedProfiles: lifecycle.keptSharedProfiles,
57
+ switchedActiveProfile: lifecycle.switchedActiveProfile,
58
+ adoptedProfiles: [],
59
+ repairedProfiles: [],
28
60
  };
29
61
  },
30
62
  });