@minniexcode/codex-switch 0.0.10 → 0.0.12

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 (51) hide show
  1. package/README.AI.md +68 -73
  2. package/README.CN.md +108 -111
  3. package/README.md +87 -80
  4. package/dist/app/add-provider.js +29 -15
  5. package/dist/app/bridge.js +15 -14
  6. package/dist/app/edit-provider.js +1 -1
  7. package/dist/app/get-status.js +21 -9
  8. package/dist/app/import-providers.js +1 -1
  9. package/dist/app/init-codex.js +13 -14
  10. package/dist/app/list-providers.js +48 -1
  11. package/dist/app/remove-provider.js +1 -1
  12. package/dist/app/run-doctor.js +12 -5
  13. package/dist/app/run-mutation.js +3 -2
  14. package/dist/app/setup-codex.js +3 -1
  15. package/dist/app/switch-provider.js +11 -13
  16. package/dist/cli/output.js +145 -18
  17. package/dist/cli.js +34 -2
  18. package/dist/commands/args.js +2 -2
  19. package/dist/commands/dispatch.js +40 -0
  20. package/dist/commands/handlers.js +130 -161
  21. package/dist/commands/help.js +11 -5
  22. package/dist/commands/registry.js +42 -20
  23. package/dist/domain/backups.js +4 -4
  24. package/dist/domain/config.js +110 -5
  25. package/dist/domain/providers.js +12 -0
  26. package/dist/domain/runtime-state.js +111 -13
  27. package/dist/infra/config-repo.js +16 -206
  28. package/dist/interaction/interactive.js +16 -6
  29. package/dist/runtime/copilot-adapter.js +12 -12
  30. package/dist/runtime/copilot-bridge.js +394 -45
  31. package/dist/runtime/copilot-cli.js +84 -12
  32. package/dist/runtime/copilot-installer.js +10 -9
  33. package/dist/runtime/copilot-sdk-loader.js +5 -5
  34. package/dist/storage/backup-repo.js +4 -4
  35. package/dist/storage/codex-paths.js +34 -8
  36. package/dist/storage/config-repo.js +0 -23
  37. package/dist/storage/lock-repo.js +2 -4
  38. package/dist/storage/runtime-state-repo.js +14 -13
  39. package/dist/storage/tool-config-repo.js +111 -0
  40. package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
  41. package/docs/Design/codex-switch-v0.0.12-design.md +343 -0
  42. package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
  43. package/docs/PRD/codex-switch-prd-v0.0.12.md +279 -0
  44. package/docs/PRD/codex-switch-prd-v0.1.0.md +125 -237
  45. package/docs/Tests/testing.md +39 -112
  46. package/docs/cli-usage.md +135 -565
  47. package/docs/codex-switch-command-design.md +3 -0
  48. package/docs/codex-switch-product-overview.md +52 -207
  49. package/docs/codex-switch-technical-architecture.md +3 -0
  50. package/package.json +1 -1
  51. package/dist/app/rollback-latest.js +0 -26
package/README.md CHANGED
@@ -1,136 +1,149 @@
1
- ## @minniexcode/codex-switch
1
+ # @minniexcode/codex-switch
2
2
 
3
- `codex-switch` is a local-first CLI for managing and switching Codex provider/profile configuration safely.
3
+ `@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and profile configuration safely.
4
4
 
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/`.
5
+ It keeps `codex-switch` tool state separate from the target Codex runtime, so provider management, backup flow, and runtime projection stay explicit instead of relying on manual file edits.
6
6
 
7
- 中文版: [README.CN.md](./README.CN.md)
7
+ Chinese version: [README.CN.md](./README.CN.md)
8
8
 
9
- ## Overview
9
+ ## Version
10
10
 
11
- What it does:
11
+ Current package version: `0.0.12`
12
12
 
13
- - Initialize an empty managed `providers.json`
14
- - Migrate unmanaged runtime profiles from an existing Codex directory
15
- - List, show, add, edit, and remove provider records
16
- - Switch the active provider/profile safely
17
- - Import and export provider definitions
18
- - Run diagnostics and detect local drift
19
- - List backups and roll back to a previous managed state
20
-
21
- Current version: `0.0.10`
13
+ This repository is still on a development-version line. The current release focuses on making the primary workflows, help text, and operational boundaries consistent with the implementation.
22
14
 
23
15
  ## Install
24
16
 
25
- Install globally:
26
-
27
17
  ```bash
28
18
  npm install -g @minniexcode/codex-switch
29
19
  ```
30
20
 
31
- Or run directly:
21
+ Run without a global install:
32
22
 
33
23
  ```bash
34
24
  npx @minniexcode/codex-switch --help
35
25
  ```
36
26
 
37
- CLI entry:
27
+ Built CLI entrypoint:
38
28
 
39
29
  ```bash
40
30
  codexs --help
41
31
  ```
42
32
 
43
- ## Quick Start
33
+ ## Primary Workflows
44
34
 
45
- Take over an existing Codex directory:
35
+ Direct provider workflow:
46
36
 
47
37
  ```bash
48
38
  codexs init
49
- codexs migrate
39
+ codexs add my-provider --profile my-provider --api-key sk-xxx
40
+ codexs switch my-provider
41
+ codexs status
42
+ codexs doctor
50
43
  ```
51
44
 
52
- Inspect managed providers:
45
+ GitHub Copilot workflow:
53
46
 
54
47
  ```bash
55
- codexs list
56
- codexs show my-provider
48
+ codexs init
49
+ codexs login copilot
50
+ codexs add copilot-main --copilot --profile copilot-main
51
+ codexs switch copilot-main
52
+ codexs status
53
+ codexs doctor
57
54
  ```
58
55
 
59
- Add and switch:
56
+ Notes:
60
57
 
61
- ```bash
62
- codexs add my-provider --profile my-provider --api-key sk-xxx
63
- codexs switch my-provider
64
- ```
58
+ - `init` prepares the `codex-switch` tool home and managed state.
59
+ - `login copilot` handles upstream Copilot onboarding and auth readiness.
60
+ - `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
61
+ - `status` is the main read command after switching.
62
+ - `doctor` is the main repair-oriented diagnostic command.
63
+
64
+ ## Advanced Adopt Workflow
65
65
 
66
- Check runtime state:
66
+ Use `migrate` only when you already have Codex runtime state that should be adopted into managed `providers.json` state:
67
67
 
68
68
  ```bash
69
- codexs current
70
- codexs status
71
- codexs doctor
69
+ codexs init
70
+ codexs migrate
72
71
  ```
73
72
 
74
- ## Common Commands
73
+ `migrate` is an advanced adopt helper. It is not the default first step for a fresh install.
74
+
75
+ ## Command Surface
75
76
 
76
77
  ```bash
77
78
  codexs init
79
+ codexs login copilot
78
80
  codexs migrate
79
81
  codexs list
80
82
  codexs show <provider>
81
83
  codexs current
82
84
  codexs status
85
+ codexs config show [profile]
86
+ codexs config list-profiles
83
87
  codexs add <provider> --profile <name> --api-key <key>
84
- codexs edit <provider> [--profile <name>] [--api-key <key>]
88
+ codexs add <provider> --copilot --profile <name>
89
+ codexs edit <provider>
85
90
  codexs switch <provider>
86
- codexs remove <provider>
87
- codexs import <file> [--merge]
88
- codexs export <file>
91
+ codexs remove <provider> [--force] [--switch-to <profile>]
92
+ codexs import <file>
93
+ codexs export <file> [--force]
94
+ codexs bridge start [provider]
95
+ codexs bridge status [provider]
96
+ codexs bridge stop [provider]
89
97
  codexs backups list
90
98
  codexs rollback [backup-id]
91
99
  codexs doctor
92
100
  ```
93
101
 
94
- Command help:
102
+ `setup` still exists only as a deprecated compatibility entry that points callers to `init` or `migrate`.
95
103
 
96
- ```bash
97
- codexs help switch
98
- codexs help init
99
- codexs help migrate
100
- codexs help setup
101
- ```
104
+ ## Runtime Model
105
+
106
+ `codex-switch` uses a dual-path model.
102
107
 
103
- ## How It Works
108
+ Tool home:
104
109
 
105
- By default, `codex-switch` operates on `~/.codex/`, and you can override the target with `--codex-dir`.
110
+ ```text
111
+ ~/.config/codex-switch/
112
+ codex-switch.json
113
+ providers.json
114
+ backups/
115
+ runtime/
116
+ runtimes/
117
+ ```
106
118
 
107
- Managed files:
119
+ Target Codex runtime:
108
120
 
109
121
  ```text
110
122
  ~/.codex/
111
123
  config.toml
112
124
  auth.json
113
- providers.json
114
- backups/
115
125
  ```
116
126
 
117
- Notes:
127
+ Key points:
118
128
 
119
- - `providers.json` is the managed provider registry
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
122
- - mutating commands back up before writing
123
- - rollback is available after failed or undesired changes
129
+ - `providers.json` is the managed provider registry and lives under the tool home.
130
+ - `codex-switch.json` stores tool-level metadata such as `defaultCodexDir`.
131
+ - `config.toml` remains the active runtime routing file in the target Codex directory.
132
+ - `auth.json` remains the active auth projection file in the target Codex directory.
133
+ - Direct providers rewrite `OPENAI_API_KEY` into the active runtime projection.
134
+ - Copilot providers keep upstream GitHub authentication in the official Copilot runtime while `codex-switch` manages local bridge state and routing.
124
135
 
125
- ## Automation
136
+ Path controls:
126
137
 
127
- This CLI supports both human TTY use and non-interactive automation.
138
+ - `--codex-dir <path>` targets a specific Codex runtime directory.
139
+ - `CODEXS_CODEX_DIR` provides the default target runtime when `--codex-dir` is not passed.
140
+ - `CODEXS_HOME` overrides the tool home location.
128
141
 
129
- Current exceptions:
130
- - `init` is automation-friendly and idempotent, but still returns a structured error in non-interactive or `--json` mode when the resolved target directory does not exist.
131
- - `migrate` remains 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.
142
+ ## Automation Notes
132
143
 
133
- Recommended global flags:
144
+ This CLI supports both human TTY usage and non-interactive automation.
145
+
146
+ Global flags:
134
147
 
135
148
  ```bash
136
149
  --json
@@ -139,37 +152,31 @@ Recommended global flags:
139
152
  --version
140
153
  ```
141
154
 
142
- Recommendations:
143
-
144
- - use `--json` for stable machine-readable output
145
- - pass all required arguments explicitly in scripts or CI
146
- - use `--codex-dir <path>` for sandbox or test environments
155
+ Operational limits:
147
156
 
148
- ## Testing
157
+ - `login copilot` requires a real TTY and does not support `--json`.
158
+ - `migrate` still depends on interactive profile selection and provider-detail collection in this release.
159
+ - Automation should pass explicit arguments and prefer `--json` for stable parsing.
149
160
 
150
- Build and test locally:
161
+ ## Local Development
151
162
 
152
163
  ```bash
153
164
  npm run build
154
165
  npm test
166
+ npx tsc --noEmit
167
+ node dist/cli.js --help
168
+ npm pack --dry-run
155
169
  ```
156
170
 
157
- The repository includes a development fixture under `dev-codex/local-sandbox` plus dedicated test docs:
158
-
159
- - [Testing Guide](./docs/testing.md)
160
- - [Test Report for 0.0.5](./docs/test-report-0.0.5.md)
161
-
162
171
  ## Documentation
163
172
 
164
173
  - [Chinese README](./README.CN.md)
165
174
  - [AI README](./README.AI.md)
166
175
  - [Detailed CLI Usage](./docs/cli-usage.md)
167
- - [Testing Guide](./docs/testing.md)
168
- - [Test Report for 0.0.5](./docs/test-report-0.0.5.md)
176
+ - [Testing Guide](./docs/Tests/testing.md)
169
177
  - [Product Overview](./docs/codex-switch-product-overview.md)
170
- - [Technical Architecture](./docs/codex-switch-technical-architecture.md)
171
- - [Design Doc 0.0.5](./docs/Design/codex-switch-v0.0.5-design.md)
172
- - [Design Doc 0.0.4](./docs/Design/codex-switch-v0.0.4-design.md)
178
+ - [PRD 0.0.12](./docs/PRD/codex-switch-prd-v0.0.12.md)
179
+ - [Release Gate PRD 0.1.0](./docs/PRD/codex-switch-prd-v0.1.0.md)
173
180
 
174
181
  ## License
175
182
 
@@ -41,12 +41,13 @@ 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 copilot_adapter_1 = require("../runtime/copilot-adapter");
44
45
  const copilot_installer_1 = require("../runtime/copilot-installer");
45
46
  const run_mutation_1 = require("./run-mutation");
46
47
  /**
47
48
  * Adds a new provider record to the managed providers registry.
48
49
  */
49
- function addProvider(args) {
50
+ async function addProvider(args) {
50
51
  (0, fs_utils_1.ensureDir)(args.codexDir);
51
52
  const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
52
53
  if (providers.providers[args.providerName]) {
@@ -67,17 +68,26 @@ function addProvider(args) {
67
68
  }
68
69
  : undefined;
69
70
  if (args.copilot) {
70
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
71
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
71
72
  if (!installStatus.installed) {
72
- if (!args.installCopilotSdk) {
73
- throw (0, errors_1.cliError)(args.interactive ? "COPILOT_SDK_MISSING" : "COPILOT_SDK_INSTALL_REQUIRES_TTY", args.interactive
74
- ? "The optional Copilot SDK runtime is not installed. Re-run with --install-copilot-sdk or confirm installation interactively."
75
- : "The optional Copilot SDK runtime is not installed. Pass --install-copilot-sdk when running non-interactively.", {
76
- installDir: installStatus.installDir,
77
- packageName: installStatus.packageName,
73
+ throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed. Run `codexs login copilot` first.", {
74
+ installDir: installStatus.installDir,
75
+ packageName: installStatus.packageName,
76
+ suggestion: "Run `codexs login copilot` to install the Copilot SDK and complete login.",
77
+ });
78
+ }
79
+ try {
80
+ await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
81
+ }
82
+ catch (error) {
83
+ const normalized = (0, errors_1.normalizeError)(error);
84
+ if (normalized.code === "COPILOT_AUTH_REQUIRED") {
85
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "Copilot authentication is required before a Copilot provider can be added.", {
86
+ ...(normalized.details ?? {}),
87
+ suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
78
88
  });
79
89
  }
80
- (0, copilot_installer_1.installCopilotSdk)();
90
+ throw error;
81
91
  }
82
92
  }
83
93
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
@@ -97,13 +107,17 @@ function addProvider(args) {
97
107
  }),
98
108
  }
99
109
  : undefined;
100
- const upsertModelProviders = !existingModelProvider && args.createProfile
110
+ const upsertModelProviders = args.copilot
101
111
  ? {
102
- [args.profile]: {
103
- baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
104
- },
112
+ [args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
105
113
  }
106
- : undefined;
114
+ : !existingModelProvider && args.createProfile
115
+ ? {
116
+ [args.profile]: {
117
+ baseUrl: args.baseUrl ?? undefined,
118
+ },
119
+ }
120
+ : undefined;
107
121
  if (existingProfile) {
108
122
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
109
123
  }
@@ -123,7 +137,7 @@ function addProvider(args) {
123
137
  },
124
138
  };
125
139
  return (0, run_mutation_1.runMutation)({
126
- codexDir: args.codexDir,
140
+ lockPath: args.lockPath,
127
141
  backupsDir: args.backupsDir,
128
142
  latestBackupPath: args.latestBackupPath,
129
143
  operation: "add",
@@ -22,13 +22,14 @@ async function startBridge(args) {
22
22
  requestedProviderName: args.providerName ?? null,
23
23
  providers,
24
24
  configPath: args.configPath,
25
+ runtimeDir: args.runtimeDir,
25
26
  runtime: args.runtime,
26
27
  json: args.json,
27
28
  commandName: "start",
28
29
  preferRuntimeState: false,
29
30
  });
30
- await requireBridgeRuntimeReadiness();
31
- const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider);
31
+ await requireBridgeRuntimeReadiness(args.runtimesDir);
32
+ const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir);
32
33
  const nextProvider = bridge.portChanged ? rewriteBridgeProviderPort(target.provider, bridge.port) : target.provider;
33
34
  if (bridge.portChanged) {
34
35
  try {
@@ -43,7 +44,7 @@ async function startBridge(args) {
43
44
  }
44
45
  catch (error) {
45
46
  if (!bridge.reused) {
46
- (0, copilot_bridge_1.stopCopilotBridge)();
47
+ (0, copilot_bridge_1.stopCopilotBridge)(args.runtimeDir);
47
48
  }
48
49
  throw error;
49
50
  }
@@ -66,7 +67,7 @@ async function startBridge(args) {
66
67
  */
67
68
  async function stopBridge(args) {
68
69
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
69
- const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
70
+ const state = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
70
71
  if (!state && !args.providerName) {
71
72
  return {
72
73
  data: {
@@ -90,6 +91,7 @@ async function stopBridge(args) {
90
91
  requestedProviderName: args.providerName ?? null,
91
92
  providers,
92
93
  configPath: args.configPath,
94
+ runtimeDir: args.runtimeDir,
93
95
  runtime: args.runtime,
94
96
  json: args.json,
95
97
  commandName: "stop",
@@ -101,7 +103,7 @@ async function stopBridge(args) {
101
103
  requestedProvider: args.providerName,
102
104
  });
103
105
  }
104
- (0, copilot_bridge_1.stopCopilotBridge)();
106
+ (0, copilot_bridge_1.stopCopilotBridge)(args.runtimeDir);
105
107
  return {
106
108
  data: {
107
109
  provider: target.providerName,
@@ -115,18 +117,19 @@ async function stopBridge(args) {
115
117
  */
116
118
  async function statusBridge(args) {
117
119
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
118
- const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
120
+ const state = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
119
121
  const target = await resolveBridgeTarget({
120
122
  requestedProviderName: args.providerName ?? null,
121
123
  providers,
122
124
  configPath: args.configPath,
125
+ runtimeDir: args.runtimeDir,
123
126
  runtime: args.runtime,
124
127
  json: args.json,
125
128
  commandName: "status",
126
129
  preferRuntimeState: true,
127
130
  });
128
131
  const provider = target.provider;
129
- const runtimeStatus = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(provider);
132
+ const runtimeStatus = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(provider, state, args.runtimeDir);
130
133
  const expectedBaseUrl = (0, providers_1.buildCopilotBridgeBaseUrl)(provider.runtime);
131
134
  if (args.providerName && state?.provider && state.provider !== args.providerName) {
132
135
  throw (0, errors_1.cliError)("BRIDGE_PROVIDER_MISMATCH", `Bridge runtime state belongs to "${state.provider}" not "${args.providerName}".`, {
@@ -154,7 +157,7 @@ async function resolveBridgeTarget(args) {
154
157
  return resolveNamedBridgeProvider(args.providers, args.requestedProviderName);
155
158
  }
156
159
  if (args.preferRuntimeState) {
157
- const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
160
+ const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
158
161
  if (runtimeState?.provider && args.providers.providers[runtimeState.provider]) {
159
162
  return resolveNamedBridgeProvider(args.providers, runtimeState.provider);
160
163
  }
@@ -238,15 +241,15 @@ async function promptForCopilotBridgeSelection(runtime, targets, commandName) {
238
241
  /**
239
242
  * Verifies that the local Copilot bridge prerequisites are available before startup.
240
243
  */
241
- async function requireBridgeRuntimeReadiness() {
242
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
244
+ async function requireBridgeRuntimeReadiness(runtimesDir) {
245
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
243
246
  if (!installStatus.installed) {
244
247
  throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
245
248
  installDir: installStatus.installDir,
246
249
  packageName: installStatus.packageName,
247
250
  });
248
251
  }
249
- await (0, copilot_adapter_1.readCopilotAuthState)();
252
+ await (0, copilot_adapter_1.readCopilotAuthState)(runtimesDir);
250
253
  }
251
254
  /**
252
255
  * Rewrites one Copilot bridge provider record with a recovered runtime port.
@@ -281,9 +284,7 @@ function persistRecoveredBridgePort(args) {
281
284
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
282
285
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
283
286
  upsertModelProviders: {
284
- [args.provider.profile]: {
285
- baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(args.provider.runtime),
286
- },
287
+ [args.provider.profile]: (0, providers_1.buildCopilotModelProviderProjection)(args.provider.runtime),
287
288
  },
288
289
  });
289
290
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
@@ -109,7 +109,7 @@ function editProvider(args) {
109
109
  switchToProfile: args.switchToProfile ?? null,
110
110
  });
111
111
  return (0, run_mutation_1.runMutation)({
112
- codexDir: args.codexDir,
112
+ lockPath: args.lockPath,
113
113
  backupsDir: args.backupsDir,
114
114
  latestBackupPath: args.latestBackupPath,
115
115
  operation: "edit",
@@ -48,7 +48,7 @@ const runtime_state_repo_1 = require("../storage/runtime-state-repo");
48
48
  /**
49
49
  * Reports the current on-disk runtime state and how it maps back to managed providers.
50
50
  */
51
- async function getStatus(codexDir, configPath, providersPath, authPath) {
51
+ async function getStatus(codexDir, configPath, providersPath, authPath, options) {
52
52
  const configExists = fs.existsSync(configPath);
53
53
  const providersExists = fs.existsSync(providersPath);
54
54
  let currentProfile = null;
@@ -67,10 +67,12 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
67
67
  }
68
68
  }
69
69
  const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
70
- const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
71
- const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
72
- const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
73
- const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)();
70
+ const activeProviderCandidates = liveState.mappedProviders;
71
+ const activeProvider = liveState.providerResolvable && providers && liveState.mappedProvider
72
+ ? providers.providers[liveState.mappedProvider]
73
+ : null;
74
+ const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)(options?.runtimesDir);
75
+ const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(options?.runtimeDir);
74
76
  const runtimeState = runtimeStateInspection.state;
75
77
  const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
76
78
  const bridgeProbeTarget = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
@@ -86,7 +88,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
86
88
  cause: runtimeStateInspection.parseError ?? "Failed to parse Copilot bridge runtime state.",
87
89
  }
88
90
  : bridgeProbeTarget
89
- ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget, runtimeState)
91
+ ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget, runtimeState, options?.runtimeDir)
90
92
  : runtimeState
91
93
  ? {
92
94
  ok: false,
@@ -97,7 +99,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
97
99
  }
98
100
  : null;
99
101
  const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
100
- ? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
102
+ ? await (0, copilot_adapter_1.readCopilotAuthState)(options?.runtimesDir).catch((error) => ({
101
103
  ready: false,
102
104
  source: "official-sdk",
103
105
  mode: "session",
@@ -108,6 +110,9 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
108
110
  // Surface unmanaged live state without mutating anything during a read-only status call.
109
111
  warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
110
112
  }
113
+ if (liveState.reason === "shared-profile") {
114
+ warnings.push(`Current config profile "${currentProfile}" is shared by multiple providers in providers.json, so the active provider cannot be resolved uniquely.`);
115
+ }
111
116
  if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
112
117
  warnings.push(`Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`);
113
118
  }
@@ -115,13 +120,20 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
115
120
  warnings,
116
121
  data: {
117
122
  codexDir,
118
- storage: (0, runtime_state_1.getStorageRoles)(),
123
+ storage: (0, runtime_state_1.getStorageRoles)({
124
+ codexDir,
125
+ providersPath,
126
+ configPath,
127
+ authPath,
128
+ runtimeDir: options?.runtimeDir,
129
+ runtimesDir: options?.runtimesDir,
130
+ }),
119
131
  configExists,
120
132
  providersExists,
121
133
  currentProfile,
122
134
  currentProfileMapped: liveState.profileMapped,
123
135
  provider: liveState.mappedProvider,
124
- activeProviderResolvable: activeProviderCandidates.length === 1,
136
+ activeProviderResolvable: liveState.providerResolvable,
125
137
  activeProviderCandidates,
126
138
  runtimeProvider: activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? activeProvider.runtime?.kind ?? null : null,
127
139
  copilotSdk: {
@@ -62,7 +62,7 @@ function importProviders(args) {
62
62
  (0, fs_utils_1.ensureDir)(args.codexDir);
63
63
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
64
64
  return (0, run_mutation_1.runMutation)({
65
- codexDir: args.codexDir,
65
+ lockPath: args.lockPath,
66
66
  backupsDir: args.backupsDir,
67
67
  latestBackupPath: args.latestBackupPath,
68
68
  operation: "import",
@@ -35,34 +35,33 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.initCodex = initCodex;
37
37
  const fs = __importStar(require("node:fs"));
38
- const errors_1 = require("../domain/errors");
39
38
  const fs_utils_1 = require("../storage/fs-utils");
40
39
  const providers_repo_1 = require("../storage/providers-repo");
40
+ const tool_config_repo_1 = require("../storage/tool-config-repo");
41
41
  /**
42
- * Initializes a Codex directory for managed providers.json usage without requiring live Codex state.
42
+ * Initializes the codex-switch tool home without requiring target Codex runtime files.
43
43
  */
44
44
  function initCodex(args) {
45
- const codexDirExists = fs.existsSync(args.codexDir);
46
- if (!codexDirExists && !args.createCodexDir) {
47
- throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
48
- codexDir: args.codexDir,
49
- });
50
- }
51
- if (!codexDirExists) {
52
- (0, fs_utils_1.ensureDir)(args.codexDir);
45
+ const toolHomeExists = fs.existsSync(args.toolHomeDir);
46
+ if (!toolHomeExists) {
47
+ (0, fs_utils_1.ensureDir)(args.toolHomeDir);
53
48
  }
49
+ const toolConfigExists = fs.existsSync(args.toolConfigPath);
50
+ const ensuredConfig = (0, tool_config_repo_1.ensureToolConfig)(args.toolConfigPath, args.version, args.defaultCodexDir ?? "");
54
51
  const providersExists = fs.existsSync(args.providersPath);
55
52
  if (!providersExists) {
56
53
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: {} });
57
54
  }
58
55
  return {
59
56
  data: {
60
- codexDir: args.codexDir,
61
- createdCodexDir: !codexDirExists,
57
+ toolHomeDir: args.toolHomeDir,
58
+ toolConfigPath: args.toolConfigPath,
59
+ providersPath: args.providersPath,
60
+ createdToolHomeDir: !toolHomeExists,
61
+ createdToolConfigFile: ensuredConfig.created && !toolConfigExists,
62
62
  createdProvidersFile: !providersExists,
63
+ toolConfigAlreadyExisted: toolConfigExists,
63
64
  providersAlreadyExisted: providersExists,
64
- configExists: fs.existsSync(args.configPath),
65
- authExists: fs.existsSync(args.authPath),
66
65
  },
67
66
  };
68
67
  }