@minniexcode/codex-switch 0.0.9 → 0.0.10

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 (36) hide show
  1. package/README.AI.md +5 -3
  2. package/README.CN.md +25 -3
  3. package/README.md +3 -2
  4. package/dist/app/add-provider.js +0 -11
  5. package/dist/app/bridge.js +0 -1
  6. package/dist/app/edit-provider.js +1 -17
  7. package/dist/app/get-status.js +24 -9
  8. package/dist/app/list-providers.js +0 -1
  9. package/dist/app/run-doctor.js +11 -36
  10. package/dist/app/setup-codex.js +27 -17
  11. package/dist/app/show-config.js +1 -5
  12. package/dist/app/switch-provider.js +5 -20
  13. package/dist/cli/output.js +4 -6
  14. package/dist/cli.js +1 -1
  15. package/dist/commands/handlers.js +192 -39
  16. package/dist/commands/registry.js +7 -5
  17. package/dist/domain/config.js +4 -68
  18. package/dist/domain/providers.js +0 -5
  19. package/dist/domain/runtime-state.js +2 -1
  20. package/dist/domain/setup.js +58 -3
  21. package/dist/interaction/add-interactive.js +55 -1
  22. package/dist/interaction/interactive.js +1 -5
  23. package/dist/runtime/copilot-adapter.js +44 -1
  24. package/dist/runtime/copilot-bridge.js +2 -2
  25. package/dist/runtime/copilot-cli.js +70 -0
  26. package/dist/runtime/copilot-installer.js +49 -2
  27. package/dist/storage/auth-repo.js +28 -77
  28. package/dist/storage/config-repo.js +1 -36
  29. package/dist/storage/runtime-state-repo.js +32 -0
  30. package/docs/Design/codex-switch-copilot-integration-design.md +517 -0
  31. package/docs/Design/codex-switch-v0.0.10-design.md +669 -0
  32. package/docs/PRD/codex-switch-prd-v0.0.10.md +406 -0
  33. package/docs/cli-usage.md +38 -14
  34. package/docs/codex-switch-product-overview.md +2 -2
  35. package/docs/codex-switch-technical-architecture.md +6 -5
  36. package/package.json +1 -1
package/README.AI.md CHANGED
@@ -54,7 +54,8 @@ Shared flags:
54
54
  Operational model:
55
55
 
56
56
  - `providers.json` is the management-state source of truth
57
- - `config.toml` and `auth.json` are runtime mirrors
57
+ - `config.toml` is the managed runtime-routing file
58
+ - `auth.json` is the active auth projection file for direct providers and is also inspected by status/doctor
58
59
  - `backups/latest.json` tracks the latest rollback state
59
60
  - mutating commands should back up first and run under a lightweight file lock
60
61
 
@@ -93,12 +94,13 @@ codexs status --json
93
94
  Current package version in this repository:
94
95
 
95
96
  ```text
96
- 0.0.7
97
+ 0.0.10
97
98
  ```
98
99
 
99
100
  Recent version summary:
100
101
 
101
- - `0.0.7`: command-surface refactor, env_key/auth-mirror model corrections, and the `setup` split into `init` plus `migrate`
102
+ - `0.0.10`: `init` / `migrate` command split finalized, `setup` deprecated, and the managed provider model reduced to static profile plus `base_url` configuration
103
+ - `0.0.7`: command-surface refactor, env_key/auth-mirror model corrections, and the initial `setup` split toward `init` plus `migrate`
102
104
  - `0.0.4`: setup/show/edit/backups list/specific rollback/import merge and clearer CLI semantics
103
105
  - `0.0.3`: interactive TTY flows and improved help
104
106
  - `0.0.2`: mutation orchestration, backups, rollback, locks, drift detection improvements
package/README.CN.md CHANGED
@@ -17,30 +17,43 @@
17
17
 
18
18
  ## 现在可以做什么
19
19
 
20
- 当前 MVP 命令如下:
20
+ 当前命令面如下:
21
21
 
22
22
  ```bash
23
+ codexs init
24
+ codexs migrate
23
25
  codexs list
26
+ codexs show <provider>
24
27
  codexs current
25
- codexs switch <provider>
26
28
  codexs status
29
+ codexs edit <provider>
30
+ codexs switch <provider>
27
31
  codexs import <file>
28
32
  codexs export <file>
29
33
  codexs add <provider>
30
34
  codexs remove <provider>
35
+ codexs backups list
31
36
  codexs doctor
32
37
  codexs rollback
38
+ codexs setup
33
39
  ```
34
40
 
35
41
  对应能力包括:
36
42
 
43
+ - 初始化空的受管 `providers.json`
44
+ - 从已有 `config.toml` adopt 可管理的 runtime profile
37
45
  - 查看本地已管理的 provider
46
+ - 查看单个 provider 详情
38
47
  - 查看当前激活的 profile
48
+ - 查看本地运行态摘要
49
+ - 编辑已有 provider
39
50
  - 安全切换 provider
40
51
  - 导入和导出 provider 映射
41
52
  - 新增和删除 provider
53
+ - 查看备份列表
42
54
  - 检查配置漂移和常见本地问题
43
55
  - 在变更前自动备份,并在失败时回滚
56
+ - 保留 `setup` 作为弃用入口,并引导到 `init` / `migrate`
44
57
 
45
58
  ## 简单用法
46
59
 
@@ -65,6 +78,8 @@ codexs --help
65
78
  典型使用方式:
66
79
 
67
80
  ```bash
81
+ codexs init
82
+ codexs migrate
68
83
  codexs list
69
84
  codexs current
70
85
  codexs add my-provider --profile my-provider --api-key sk-xxx
@@ -111,7 +126,8 @@ codexs status --json
111
126
  存储模型:
112
127
 
113
128
  - `providers.json` 是管理态的单一事实来源
114
- - `config.toml` 和 `auth.json` 是运行态文件
129
+ - `config.toml` 是受管的运行时路由文件
130
+ - `auth.json` 是独立的 Codex 认证状态文件,`status` / `doctor` 只读检查它
115
131
  - `backups/latest.json` 记录最近一次可回滚窗口
116
132
 
117
133
  注意:`providers.json` 可能包含 API key,应视为本地敏感文件。
@@ -128,6 +144,12 @@ codexs status --json
128
144
 
129
145
  ## 最近 3 个版本更新
130
146
 
147
+ ### 0.0.10
148
+
149
+ - 正式拆分 `setup`:新增 `init` 和 `migrate`,`setup` 变为弃用命令
150
+ - 增加 `show`、`edit`、`backups list` 等对当前命令面的整理
151
+ - 清理 provider/runtime 管理语义,CLI 只负责静态 profile 与 `base_url` 层配置
152
+
131
153
  ### 0.0.3
132
154
 
133
155
  - 为 `add`、`switch`、`remove`、`import`、`export`、`rollback` 增加了交互式 TTY 流程
package/README.md CHANGED
@@ -18,7 +18,7 @@ What it does:
18
18
  - Run diagnostics and detect local drift
19
19
  - List backups and roll back to a previous managed state
20
20
 
21
- Current version: `0.0.8`
21
+ Current version: `0.0.10`
22
22
 
23
23
  ## Install
24
24
 
@@ -117,7 +117,8 @@ Managed files:
117
117
  Notes:
118
118
 
119
119
  - `providers.json` is the managed provider registry
120
- - `config.toml` and `auth.json` represent runtime state
120
+ - `config.toml` is the managed runtime-routing file
121
+ - `auth.json` is the active auth projection file; direct-provider switches rewrite `OPENAI_API_KEY`, while `status` and `doctor` inspect its state
121
122
  - mutating commands back up before writing
122
123
  - rollback is available after failed or undesired changes
123
124
 
@@ -41,7 +41,6 @@ const errors_1 = require("../domain/errors");
41
41
  const config_repo_1 = require("../storage/config-repo");
42
42
  const fs_utils_1 = require("../storage/fs-utils");
43
43
  const providers_repo_1 = require("../storage/providers-repo");
44
- const auth_repo_1 = require("../storage/auth-repo");
45
44
  const copilot_installer_1 = require("../runtime/copilot-installer");
46
45
  const run_mutation_1 = require("./run-mutation");
47
46
  /**
@@ -102,14 +101,12 @@ function addProvider(args) {
102
101
  ? {
103
102
  [args.profile]: {
104
103
  baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
105
- envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
106
104
  },
107
105
  }
108
106
  : undefined;
109
107
  if (existingProfile) {
110
108
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
111
109
  }
112
- const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
113
110
  const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
114
111
  const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
115
112
  const next = {
@@ -118,7 +115,6 @@ function addProvider(args) {
118
115
  [args.providerName]: (0, providers_1.cleanProviderRecord)({
119
116
  profile: args.profile,
120
117
  apiKey,
121
- envKey,
122
118
  baseUrl,
123
119
  note: args.note ?? undefined,
124
120
  tags: args.tags,
@@ -134,7 +130,6 @@ function addProvider(args) {
134
130
  files: [
135
131
  { absolutePath: args.providersPath, relativePath: "providers.json" },
136
132
  { absolutePath: args.configPath, relativePath: "config.toml" },
137
- { absolutePath: args.authPath, relativePath: "auth.json" },
138
133
  ],
139
134
  mutate: () => {
140
135
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
@@ -144,15 +139,9 @@ function addProvider(args) {
144
139
  // Persist only the normalized provider payload so later reads are deterministic.
145
140
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
146
141
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
147
- if (document.activeProfile === args.profile) {
148
- const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, next);
149
- const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
150
- (0, auth_repo_1.writeAuthFile)(args.authPath, next.providers[activeProviderName], existingAuth ?? undefined);
151
- }
152
142
  return {
153
143
  provider: args.providerName,
154
144
  profile: args.profile,
155
- envKey,
156
145
  runtimeKind: runtime?.kind ?? null,
157
146
  createdProfileSections: configPlan.createdProfileSections,
158
147
  createdModelProviderSections: configPlan.createdModelProviderSections,
@@ -283,7 +283,6 @@ function persistRecoveredBridgePort(args) {
283
283
  upsertModelProviders: {
284
284
  [args.provider.profile]: {
285
285
  baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(args.provider.runtime),
286
- envKey: (0, config_repo_1.requireRuntimeEnvKey)(document, args.provider.profile),
287
286
  },
288
287
  },
289
288
  });
@@ -7,7 +7,6 @@ const providers_1 = require("../domain/providers");
7
7
  const config_repo_1 = require("../storage/config-repo");
8
8
  const fs_utils_1 = require("../storage/fs-utils");
9
9
  const providers_repo_1 = require("../storage/providers-repo");
10
- const auth_repo_1 = require("../storage/auth-repo");
11
10
  const run_mutation_1 = require("./run-mutation");
12
11
  /**
13
12
  * Updates selected fields on a single managed provider.
@@ -63,23 +62,15 @@ function editProvider(args) {
63
62
  upsertModelProviders = {
64
63
  [newProfile]: {
65
64
  baseUrl: args.baseUrl ?? undefined,
66
- envKey: (0, config_1.buildManagedProfileEnvKey)(newProfile),
67
65
  },
68
66
  };
69
67
  }
70
68
  else {
71
69
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
72
70
  }
73
- const nextEnvKey = args.profile !== undefined && args.profile !== current.profile
74
- ? targetModelProviderSection?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(newProfile)
75
- : current.envKey;
76
- if (nextEnvKey !== current.envKey) {
77
- updatedFields.push("envKey");
78
- }
79
71
  const nextRecord = (0, providers_1.cleanProviderRecord)({
80
72
  profile: newProfile,
81
73
  apiKey: args.apiKey ?? current.apiKey,
82
- envKey: nextEnvKey,
83
74
  baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
84
75
  note: args.note === null ? undefined : args.note ?? current.note,
85
76
  tags: args.tags ?? current.tags,
@@ -90,7 +81,7 @@ function editProvider(args) {
90
81
  ...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
91
82
  },
92
83
  };
93
- if (args.model !== undefined && (targetSection?.model !== args.model) && !updatedFields.includes("model")) {
84
+ if (args.model !== undefined && targetSection?.model !== args.model && !updatedFields.includes("model")) {
94
85
  updatedFields.push("model");
95
86
  }
96
87
  }
@@ -125,7 +116,6 @@ function editProvider(args) {
125
116
  files: [
126
117
  { absolutePath: args.providersPath, relativePath: "providers.json" },
127
118
  { absolutePath: args.configPath, relativePath: "config.toml" },
128
- { absolutePath: args.authPath, relativePath: "auth.json" },
129
119
  ],
130
120
  mutate: () => {
131
121
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
@@ -143,12 +133,6 @@ function editProvider(args) {
143
133
  // Write providers first so the registry and config move together inside the managed backup boundary.
144
134
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
145
135
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
146
- const updatedDocument = (0, config_repo_1.readStructuredConfig)(args.configPath);
147
- if (updatedDocument.activeProfile) {
148
- const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(updatedDocument, nextProviders);
149
- const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
150
- (0, auth_repo_1.writeAuthFile)(args.authPath, nextProviders.providers[activeProviderName], existingAuth ?? undefined);
151
- }
152
136
  return {
153
137
  provider: args.providerName,
154
138
  updatedFields,
@@ -56,7 +56,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
56
56
  const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
57
57
  let configViews = [];
58
58
  let consistencyIssues = [];
59
- const authState = (0, auth_repo_1.readManagedAuthState)(authPath);
59
+ const authState = (0, auth_repo_1.readAuthFileState)(authPath);
60
60
  if (configExists) {
61
61
  const document = (0, config_repo_1.readStructuredConfig)(configPath);
62
62
  currentProfile = document.activeProfile;
@@ -70,20 +70,32 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
70
70
  const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
71
71
  const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
72
72
  const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
73
- const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
73
+ const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)();
74
+ const runtimeState = runtimeStateInspection.state;
74
75
  const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
75
76
  const bridgeProbeTarget = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
76
77
  ? activeProvider
77
78
  : runtimeStateProvider && (0, providers_1.isCopilotBridgeProvider)(runtimeStateProvider)
78
79
  ? runtimeStateProvider
79
80
  : null;
80
- const copilotBridge = bridgeProbeTarget ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget) : runtimeState ? {
81
- ok: false,
82
- runtime: "copilot-bridge",
83
- reason: "failed",
84
- cause: "Copilot bridge runtime state exists but no matching managed Copilot provider is active.",
85
- details: runtimeState,
86
- } : null;
81
+ const copilotBridge = !runtimeStateInspection.valid && runtimeStateInspection.exists
82
+ ? {
83
+ ok: false,
84
+ runtime: "copilot-bridge",
85
+ reason: "failed",
86
+ cause: runtimeStateInspection.parseError ?? "Failed to parse Copilot bridge runtime state.",
87
+ }
88
+ : bridgeProbeTarget
89
+ ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget, runtimeState)
90
+ : runtimeState
91
+ ? {
92
+ ok: false,
93
+ runtime: "copilot-bridge",
94
+ reason: "failed",
95
+ cause: "Copilot bridge runtime state exists but no matching managed Copilot provider is active.",
96
+ details: runtimeState,
97
+ }
98
+ : null;
87
99
  const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
88
100
  ? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
89
101
  ready: false,
@@ -96,6 +108,9 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
96
108
  // Surface unmanaged live state without mutating anything during a read-only status call.
97
109
  warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
98
110
  }
111
+ if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
112
+ warnings.push(`Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`);
113
+ }
99
114
  return {
100
115
  warnings,
101
116
  data: {
@@ -11,7 +11,6 @@ function listProviders(providersPath) {
11
11
  const items = names.map((name) => ({
12
12
  name,
13
13
  profile: providers.providers[name].profile,
14
- envKey: providers.providers[name].envKey,
15
14
  note: providers.providers[name].note ?? null,
16
15
  tags: providers.providers[name].tags ?? [],
17
16
  }));
@@ -102,8 +102,9 @@ async function runDoctor(args) {
102
102
  });
103
103
  }
104
104
  }
105
- const authState = (0, auth_repo_1.readManagedAuthState)(args.authPath);
106
- const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
105
+ const authState = (0, auth_repo_1.readAuthFileState)(args.authPath);
106
+ const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)();
107
+ const runtimeState = runtimeStateInspection.state;
107
108
  if (authState.exists && !authState.valid) {
108
109
  issues.push({
109
110
  code: "AUTH_JSON_INVALID",
@@ -111,34 +112,16 @@ async function runDoctor(args) {
111
112
  file: args.authPath,
112
113
  });
113
114
  }
115
+ if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
116
+ issues.push({
117
+ code: "BRIDGE_STATE_STALE",
118
+ message: `Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`,
119
+ });
120
+ }
114
121
  if (document?.activeProfile && providers) {
115
122
  const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
116
123
  if (matches.length === 1) {
117
124
  const activeProvider = providers.providers[matches[0]];
118
- const payload = authState.payload ?? {};
119
- const actualKeys = authState.managedSecretKeys;
120
- if (authState.authMode !== null && authState.authMode !== "apikey") {
121
- issues.push({
122
- code: "AUTH_JSON_INVALID",
123
- message: `auth.json auth_mode must be "apikey", found "${authState.authMode}".`,
124
- });
125
- }
126
- if (!actualKeys.includes(activeProvider.envKey) || actualKeys.length !== 1) {
127
- issues.push({
128
- code: "AUTH_JSON_ENV_KEY_MISMATCH",
129
- message: `auth.json managed env key does not match active provider "${matches[0]}".`,
130
- provider: matches[0],
131
- expectedEnvKey: activeProvider.envKey,
132
- actualEnvKeys: actualKeys,
133
- });
134
- }
135
- if (payload[activeProvider.envKey] !== activeProvider.apiKey) {
136
- issues.push({
137
- code: "AUTH_JSON_APIKEY_MISMATCH",
138
- message: `auth.json secret value does not match active provider "${matches[0]}".`,
139
- provider: matches[0],
140
- });
141
- }
142
125
  if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
143
126
  const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
144
127
  if (!installStatus.installed) {
@@ -160,7 +143,7 @@ async function runDoctor(args) {
160
143
  ...(normalized.details ?? {}),
161
144
  });
162
145
  }
163
- const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider);
146
+ const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider, runtimeState);
164
147
  if (!bridge.ok) {
165
148
  issues.push({
166
149
  code: mapBridgeDiagnosticCode(bridge.cause),
@@ -255,18 +238,10 @@ function renderConfigIssueMessage(issue) {
255
238
  return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
256
239
  case "MODEL_PROVIDER_BASE_URL_MISSING":
257
240
  return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
258
- case "MODEL_PROVIDER_ENV_KEY_MISSING":
259
- return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing env_key.`;
260
- case "PROVIDER_ENV_KEY_MISMATCH":
261
- return `Provider "${issue.provider}" envKey does not match runtime env_key for profile "${issue.profile}".`;
262
241
  case "ACTIVE_PROVIDER_UNRESOLVED":
263
- return `Active profile "${issue.profile}" maps to multiple providers and cannot determine the current auth mirror owner.`;
242
+ return `Active profile "${issue.profile}" maps to multiple providers, so the active managed provider cannot be resolved uniquely.`;
264
243
  case "AUTH_JSON_INVALID":
265
244
  return String(issue.message ?? issue.reason ?? "auth.json is invalid.");
266
- case "AUTH_JSON_ENV_KEY_MISMATCH":
267
- return `auth.json managed env key does not match provider "${String(issue.provider ?? "")}".`;
268
- case "AUTH_JSON_APIKEY_MISMATCH":
269
- return `auth.json secret does not match provider "${String(issue.provider ?? "")}".`;
270
245
  case "DESTRUCTIVE_REMOVE_BLOCKED":
271
246
  return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
272
247
  default:
@@ -42,7 +42,6 @@ const codex_cli_1 = require("../runtime/codex-cli");
42
42
  const config_repo_1 = require("../storage/config-repo");
43
43
  const fs_utils_1 = require("../storage/fs-utils");
44
44
  const providers_repo_1 = require("../storage/providers-repo");
45
- const auth_repo_1 = require("../storage/auth-repo");
46
45
  const run_doctor_1 = require("./run-doctor");
47
46
  const run_mutation_1 = require("./run-mutation");
48
47
  const MIN_CODEX_VERSION = "0.0.1";
@@ -70,30 +69,46 @@ async function migrateCodex(args) {
70
69
  });
71
70
  }
72
71
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
73
- const profileViews = (0, config_1.buildManagedProfileViews)(document, null);
74
- // Migrate can only adopt unmanaged profiles that already contain enough runtime data to become managed.
75
- const adoptableProfiles = profileViews
76
- .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
77
- .map((view) => view.name)
78
- .sort();
72
+ const currentProviders = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
73
+ const profileViews = (0, config_1.buildManagedProfileViews)(document, currentProviders);
74
+ const adoptability = (0, setup_1.collectMigrateAdoptability)(document, currentProviders);
79
75
  if (profileViews.length === 0) {
80
76
  throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No profiles were found in config.toml.", {
81
77
  file: args.configPath,
82
78
  });
83
79
  }
84
- const invalidAdoptProfiles = args.adoptProfiles.filter((profile) => !adoptableProfiles.includes(profile));
80
+ if (adoptability.adoptableProfiles.length === 0) {
81
+ throw (0, errors_1.cliError)("MIGRATE_NO_ADOPTABLE_PROFILES", "No adoptable profiles were found for migrate.", {
82
+ availableProfiles: adoptability.availableProfiles,
83
+ adoptableProfiles: adoptability.adoptableProfiles,
84
+ blockingReasonsByProfile: adoptability.blockingReasonsByProfile,
85
+ });
86
+ }
87
+ const invalidAdoptProfiles = args.adoptProfiles.filter((profile) => !adoptability.adoptableProfiles.includes(profile));
85
88
  if (invalidAdoptProfiles.length > 0) {
86
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate only adopts unmanaged profiles that already contain model, model_provider, and matching model_providers base_url/env_key.", {
89
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate only adopts unmanaged profiles that already contain model, model_provider, and matching model_providers base_url.", {
87
90
  invalidProfiles: invalidAdoptProfiles.sort(),
88
- adoptableProfiles,
91
+ availableProfiles: adoptability.availableProfiles,
92
+ adoptableProfiles: adoptability.adoptableProfiles,
93
+ blockingReasonsByProfile: adoptability.blockingReasonsByProfile,
89
94
  });
90
95
  }
91
96
  if (args.adoptProfiles.length === 0) {
92
97
  throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires at least one explicit profile to adopt.", {
93
- adoptableProfiles,
98
+ availableProfiles: adoptability.availableProfiles,
99
+ adoptableProfiles: adoptability.adoptableProfiles,
100
+ blockingReasonsByProfile: adoptability.blockingReasonsByProfile,
94
101
  });
95
102
  }
96
- const drafts = (0, setup_1.buildSetupDrafts)(args.adoptProfiles, args.providerDetailsByProfile);
103
+ const runtimeByProfile = profileViews.reduce((accumulator, view) => {
104
+ if (view.source === "unmanaged") {
105
+ accumulator[view.name] = {
106
+ baseUrl: view.baseUrl ?? undefined,
107
+ };
108
+ }
109
+ return accumulator;
110
+ }, {});
111
+ const drafts = (0, setup_1.buildSetupDrafts)(args.adoptProfiles, args.providerDetailsByProfile, runtimeByProfile);
97
112
  const incompleteProfiles = (0, setup_1.findIncompleteSetupProfiles)(drafts);
98
113
  if (incompleteProfiles.length > 0) {
99
114
  throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires complete provider data for every selected profile.", {
@@ -101,7 +116,6 @@ async function migrateCodex(args) {
101
116
  });
102
117
  }
103
118
  (0, fs_utils_1.ensureDir)(args.codexDir);
104
- const currentProviders = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
105
119
  const providersExists = fs.existsSync(args.providersPath);
106
120
  if (providersExists && args.strategy !== "merge" && args.strategy !== "overwrite") {
107
121
  throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists.", {
@@ -123,16 +137,12 @@ async function migrateCodex(args) {
123
137
  files: [
124
138
  { absolutePath: args.providersPath, relativePath: "providers.json" },
125
139
  { absolutePath: args.configPath, relativePath: "config.toml" },
126
- { absolutePath: args.authPath, relativePath: "auth.json" },
127
140
  ],
128
141
  mutate: () => {
129
142
  // migrate currently preserves config structure and only asserts that the file remains writable inside the mutation flow.
130
143
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {});
131
144
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, finalProviders);
132
145
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
133
- const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, finalProviders);
134
- const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
135
- (0, auth_repo_1.writeAuthFile)(args.authPath, finalProviders.providers[activeProviderName], existingAuth ?? undefined);
136
146
  return {
137
147
  codexDir: args.codexDir,
138
148
  strategy: args.strategy,
@@ -31,11 +31,7 @@ function showConfig(args) {
31
31
  selectedProfile,
32
32
  profiles: profiles.map((profile) => ({
33
33
  ...profile,
34
- managedProviderEnvKeys: (0, providers_1.findProvidersByProfile)(providers, profile.name).map((providerName) => ({
35
- providerName,
36
- envKey: providers.providers[providerName].envKey,
37
- matchesRuntime: providers.providers[providerName].envKey === profile.envKey,
38
- })),
34
+ linkedProviderNames: (0, providers_1.findProvidersByProfile)(providers, profile.name),
39
35
  })),
40
36
  },
41
37
  };
@@ -4,14 +4,14 @@ exports.switchProvider = switchProvider;
4
4
  const errors_1 = require("../domain/errors");
5
5
  const providers_1 = require("../domain/providers");
6
6
  const config_repo_1 = require("../storage/config-repo");
7
- const providers_repo_1 = require("../storage/providers-repo");
8
7
  const auth_repo_1 = require("../storage/auth-repo");
8
+ const providers_repo_1 = require("../storage/providers-repo");
9
9
  const copilot_bridge_1 = require("../runtime/copilot-bridge");
10
10
  const copilot_installer_1 = require("../runtime/copilot-installer");
11
11
  const copilot_adapter_1 = require("../runtime/copilot-adapter");
12
12
  const run_mutation_1 = require("./run-mutation");
13
13
  /**
14
- * Switches the active Codex profile and rewrites auth.json for the target provider.
14
+ * Switches the active Codex profile to the target provider.
15
15
  */
16
16
  async function switchProvider(args) {
17
17
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
@@ -22,15 +22,6 @@ async function switchProvider(args) {
22
22
  });
23
23
  }
24
24
  const document = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
25
- const envKey = (0, config_repo_1.requireRuntimeEnvKey)(document, provider.profile);
26
- if (provider.envKey !== envKey) {
27
- throw (0, errors_1.cliError)("PROVIDER_ENV_KEY_MISMATCH", `Provider "${args.providerName}" envKey does not match runtime env_key.`, {
28
- provider: args.providerName,
29
- profile: provider.profile,
30
- providerEnvKey: provider.envKey,
31
- runtimeEnvKey: envKey,
32
- });
33
- }
34
25
  if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
35
26
  const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
36
27
  if (!installStatus.installed) {
@@ -58,8 +49,8 @@ async function switchProvider(args) {
58
49
  latestBackupPath: args.latestBackupPath,
59
50
  operation: "switch",
60
51
  files: [
52
+ { absolutePath: args.providersPath, relativePath: "providers.json" },
61
53
  { absolutePath: args.configPath, relativePath: "config.toml" },
62
- { absolutePath: args.authPath, relativePath: "auth.json" },
63
54
  ],
64
55
  mutate: () => {
65
56
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
@@ -68,7 +59,6 @@ async function switchProvider(args) {
68
59
  ? {
69
60
  [provider.profile]: {
70
61
  baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(nextProvider.runtime),
71
- envKey,
72
62
  },
73
63
  }
74
64
  : undefined,
@@ -82,12 +72,9 @@ async function switchProvider(args) {
82
72
  });
83
73
  }
84
74
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
85
- const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
86
- (0, auth_repo_1.writeAuthFile)(args.authPath, nextProvider, existingAuth ?? undefined);
87
75
  return {
88
76
  provider: args.providerName,
89
77
  profile: nextProvider.profile,
90
- envKey: nextProvider.envKey,
91
78
  portChanged: bridge.portChanged,
92
79
  bridgePort: bridge.port,
93
80
  };
@@ -107,20 +94,18 @@ async function switchProvider(args) {
107
94
  latestBackupPath: args.latestBackupPath,
108
95
  operation: "switch",
109
96
  files: [
110
- { absolutePath: args.configPath, relativePath: "config.toml" },
111
97
  { absolutePath: args.authPath, relativePath: "auth.json" },
98
+ { absolutePath: args.configPath, relativePath: "config.toml" },
112
99
  ],
113
100
  mutate: () => {
114
101
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
115
102
  setActiveProfile: provider.profile,
116
103
  });
117
104
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
118
- const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
119
- (0, auth_repo_1.writeAuthFile)(args.authPath, provider, existingAuth ?? undefined);
105
+ (0, auth_repo_1.writeOpenAiApiKeyAuth)(args.authPath, provider.apiKey);
120
106
  return {
121
107
  provider: args.providerName,
122
108
  profile: provider.profile,
123
- envKey: provider.envKey,
124
109
  };
125
110
  },
126
111
  });
@@ -90,8 +90,7 @@ function renderHumanSuccess(command, data, warnings) {
90
90
  ? ` tags=${provider.tags.join(",")}`
91
91
  : "";
92
92
  const note = provider.note ? ` note=${provider.note}` : "";
93
- const envKey = provider.envKey ? ` envKey=${provider.envKey}` : "";
94
- lines.push(`${provider.name} -> ${provider.profile}${envKey}${tags}${note}`);
93
+ lines.push(`${provider.name} -> ${provider.profile}${tags}${note}`);
95
94
  }
96
95
  }
97
96
  break;
@@ -101,7 +100,6 @@ function renderHumanSuccess(command, data, warnings) {
101
100
  lines.push(`Provider: ${String(data?.providerName ?? "")}`);
102
101
  lines.push(`profile: ${String(provider.profile ?? "")}`);
103
102
  lines.push(`apiKey: ${String(provider.apiKey ?? "")}`);
104
- lines.push(`envKey: ${String(provider.envKey ?? "")}`);
105
103
  if (provider.baseUrl) {
106
104
  lines.push(`baseUrl: ${String(provider.baseUrl)}`);
107
105
  }
@@ -125,14 +123,15 @@ function renderHumanSuccess(command, data, warnings) {
125
123
  lines.push(`activeProviderResolvable: ${String(data?.activeProviderResolvable ?? false)}`);
126
124
  const auth = data?.auth ?? {};
127
125
  lines.push(`authExists: ${String(auth.exists ?? false)}`);
128
- lines.push(`authManagedKeys: ${Array.isArray(auth.managedSecretKeys) ? auth.managedSecretKeys.join(",") : ""}`);
126
+ lines.push(`authValid: ${String(auth.valid ?? false)}`);
127
+ lines.push(`authMode: ${String(auth.authMode ?? "")}`);
129
128
  lines.push(`issues: ${Array.isArray(data?.issues) ? (data?.issues).length : 0}`);
130
129
  break;
131
130
  case "config-show": {
132
131
  lines.push(`activeProfile: ${String(data?.activeProfile ?? "")}`);
133
132
  const profiles = data?.profiles ?? [];
134
133
  for (const profile of profiles) {
135
- lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)} model=${String(profile.model ?? "")} modelProvider=${String(profile.modelProvider ?? "")} baseUrl=${String(profile.baseUrl ?? "")} envKey=${String(profile.envKey ?? "")}`);
134
+ lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)} model=${String(profile.model ?? "")} modelProvider=${String(profile.modelProvider ?? "")} baseUrl=${String(profile.baseUrl ?? "")}`);
136
135
  }
137
136
  break;
138
137
  }
@@ -145,7 +144,6 @@ function renderHumanSuccess(command, data, warnings) {
145
144
  }
146
145
  case "switch":
147
146
  lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
148
- lines.push(`envKey: ${String(data?.envKey ?? "")}`);
149
147
  lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
150
148
  break;
151
149
  case "import":
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ const args_1 = require("./commands/args");
9
9
  const help_1 = require("./commands/help");
10
10
  const errors_1 = require("./domain/errors");
11
11
  const output_1 = require("./cli/output");
12
- const VERSION = "0.0.8";
12
+ const VERSION = require("../package.json").version ?? "0.0.0";
13
13
  /**
14
14
  * Prints the command help text to stdout.
15
15
  */