@minniexcode/codex-switch 0.0.9 → 0.0.11

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 +52 -13
  2. package/README.CN.md +94 -39
  3. package/README.md +75 -33
  4. package/dist/app/add-provider.js +29 -26
  5. package/dist/app/bridge.js +15 -15
  6. package/dist/app/edit-provider.js +2 -18
  7. package/dist/app/get-status.js +35 -13
  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 +0 -1
  11. package/dist/app/remove-provider.js +1 -1
  12. package/dist/app/run-doctor.js +21 -39
  13. package/dist/app/run-mutation.js +3 -2
  14. package/dist/app/setup-codex.js +30 -18
  15. package/dist/app/show-config.js +1 -5
  16. package/dist/app/switch-provider.js +16 -33
  17. package/dist/cli/output.js +4 -6
  18. package/dist/cli.js +35 -3
  19. package/dist/commands/args.js +2 -2
  20. package/dist/commands/dispatch.js +40 -0
  21. package/dist/commands/handlers.js +202 -84
  22. package/dist/commands/help.js +2 -0
  23. package/dist/commands/registry.js +33 -12
  24. package/dist/domain/backups.js +4 -4
  25. package/dist/domain/config.js +102 -61
  26. package/dist/domain/providers.js +12 -5
  27. package/dist/domain/runtime-state.js +81 -4
  28. package/dist/domain/setup.js +58 -3
  29. package/dist/interaction/add-interactive.js +55 -1
  30. package/dist/interaction/interactive.js +1 -5
  31. package/dist/runtime/copilot-adapter.js +56 -13
  32. package/dist/runtime/copilot-bridge.js +392 -44
  33. package/dist/runtime/copilot-cli.js +142 -0
  34. package/dist/runtime/copilot-installer.js +59 -11
  35. package/dist/runtime/copilot-sdk-loader.js +5 -5
  36. package/dist/storage/auth-repo.js +28 -77
  37. package/dist/storage/backup-repo.js +4 -4
  38. package/dist/storage/codex-paths.js +34 -8
  39. package/dist/storage/config-repo.js +1 -36
  40. package/dist/storage/lock-repo.js +2 -4
  41. package/dist/storage/runtime-state-repo.js +43 -10
  42. package/dist/storage/tool-config-repo.js +111 -0
  43. package/docs/Design/codex-switch-copilot-integration-design.md +517 -0
  44. package/docs/Design/codex-switch-v0.0.10-design.md +669 -0
  45. package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
  46. package/docs/PRD/codex-switch-prd-v0.0.10.md +406 -0
  47. package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
  48. package/docs/cli-usage.md +166 -271
  49. package/docs/codex-switch-product-overview.md +2 -2
  50. package/docs/codex-switch-technical-architecture.md +6 -5
  51. package/package.json +1 -1
@@ -41,13 +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 auth_repo_1 = require("../storage/auth-repo");
44
+ const copilot_adapter_1 = require("../runtime/copilot-adapter");
45
45
  const copilot_installer_1 = require("../runtime/copilot-installer");
46
46
  const run_mutation_1 = require("./run-mutation");
47
47
  /**
48
48
  * Adds a new provider record to the managed providers registry.
49
49
  */
50
- function addProvider(args) {
50
+ async function addProvider(args) {
51
51
  (0, fs_utils_1.ensureDir)(args.codexDir);
52
52
  const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
53
53
  if (providers.providers[args.providerName]) {
@@ -68,17 +68,26 @@ function addProvider(args) {
68
68
  }
69
69
  : undefined;
70
70
  if (args.copilot) {
71
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
71
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
72
72
  if (!installStatus.installed) {
73
- if (!args.installCopilotSdk) {
74
- throw (0, errors_1.cliError)(args.interactive ? "COPILOT_SDK_MISSING" : "COPILOT_SDK_INSTALL_REQUIRES_TTY", args.interactive
75
- ? "The optional Copilot SDK runtime is not installed. Re-run with --install-copilot-sdk or confirm installation interactively."
76
- : "The optional Copilot SDK runtime is not installed. Pass --install-copilot-sdk when running non-interactively.", {
77
- installDir: installStatus.installDir,
78
- 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.",
79
88
  });
80
89
  }
81
- (0, copilot_installer_1.installCopilotSdk)();
90
+ throw error;
82
91
  }
83
92
  }
84
93
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
@@ -98,18 +107,20 @@ function addProvider(args) {
98
107
  }),
99
108
  }
100
109
  : undefined;
101
- const upsertModelProviders = !existingModelProvider && args.createProfile
110
+ const upsertModelProviders = args.copilot
102
111
  ? {
103
- [args.profile]: {
104
- baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
105
- envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
106
- },
112
+ [args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
107
113
  }
108
- : undefined;
114
+ : !existingModelProvider && args.createProfile
115
+ ? {
116
+ [args.profile]: {
117
+ baseUrl: args.baseUrl ?? undefined,
118
+ },
119
+ }
120
+ : undefined;
109
121
  if (existingProfile) {
110
122
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
111
123
  }
112
- const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
113
124
  const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
114
125
  const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
115
126
  const next = {
@@ -118,7 +129,6 @@ function addProvider(args) {
118
129
  [args.providerName]: (0, providers_1.cleanProviderRecord)({
119
130
  profile: args.profile,
120
131
  apiKey,
121
- envKey,
122
132
  baseUrl,
123
133
  note: args.note ?? undefined,
124
134
  tags: args.tags,
@@ -127,14 +137,13 @@ function addProvider(args) {
127
137
  },
128
138
  };
129
139
  return (0, run_mutation_1.runMutation)({
130
- codexDir: args.codexDir,
140
+ lockPath: args.lockPath,
131
141
  backupsDir: args.backupsDir,
132
142
  latestBackupPath: args.latestBackupPath,
133
143
  operation: "add",
134
144
  files: [
135
145
  { absolutePath: args.providersPath, relativePath: "providers.json" },
136
146
  { absolutePath: args.configPath, relativePath: "config.toml" },
137
- { absolutePath: args.authPath, relativePath: "auth.json" },
138
147
  ],
139
148
  mutate: () => {
140
149
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
@@ -144,15 +153,9 @@ function addProvider(args) {
144
153
  // Persist only the normalized provider payload so later reads are deterministic.
145
154
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
146
155
  (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
156
  return {
153
157
  provider: args.providerName,
154
158
  profile: args.profile,
155
- envKey,
156
159
  runtimeKind: runtime?.kind ?? null,
157
160
  createdProfileSections: configPlan.createdProfileSections,
158
161
  createdModelProviderSections: configPlan.createdModelProviderSections,
@@ -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,10 +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
- envKey: (0, config_repo_1.requireRuntimeEnvKey)(document, args.provider.profile),
287
- },
287
+ [args.provider.profile]: (0, providers_1.buildCopilotModelProviderProjection)(args.provider.runtime),
288
288
  },
289
289
  });
290
290
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
@@ -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
  }
@@ -118,14 +109,13 @@ function editProvider(args) {
118
109
  switchToProfile: args.switchToProfile ?? null,
119
110
  });
120
111
  return (0, run_mutation_1.runMutation)({
121
- codexDir: args.codexDir,
112
+ lockPath: args.lockPath,
122
113
  backupsDir: args.backupsDir,
123
114
  latestBackupPath: args.latestBackupPath,
124
115
  operation: "edit",
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,
@@ -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;
@@ -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;
@@ -69,23 +69,35 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
69
69
  const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
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
- const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
73
- const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
72
+ const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)(options?.runtimesDir);
73
+ const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(options?.runtimeDir);
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, options?.runtimeDir)
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
- ? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
100
+ ? await (0, copilot_adapter_1.readCopilotAuthState)(options?.runtimesDir).catch((error) => ({
89
101
  ready: false,
90
102
  source: "official-sdk",
91
103
  mode: "session",
@@ -96,11 +108,21 @@ 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: {
102
117
  codexDir,
103
- storage: (0, runtime_state_1.getStorageRoles)(),
118
+ storage: (0, runtime_state_1.getStorageRoles)({
119
+ codexDir,
120
+ providersPath,
121
+ configPath,
122
+ authPath,
123
+ runtimeDir: options?.runtimeDir,
124
+ runtimesDir: options?.runtimesDir,
125
+ }),
104
126
  configExists,
105
127
  providersExists,
106
128
  currentProfile,
@@ -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
  }
@@ -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
  }));
@@ -34,7 +34,7 @@ function removeProvider(args) {
34
34
  switchToProfile: args.switchToProfile ?? null,
35
35
  });
36
36
  return (0, run_mutation_1.runMutation)({
37
- codexDir: args.codexDir,
37
+ lockPath: args.lockPath,
38
38
  backupsDir: args.backupsDir,
39
39
  latestBackupPath: args.latestBackupPath,
40
40
  operation: "remove",
@@ -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)(args.runtimeDir);
107
+ const runtimeState = runtimeStateInspection.state;
107
108
  if (authState.exists && !authState.valid) {
108
109
  issues.push({
109
110
  code: "AUTH_JSON_INVALID",
@@ -111,36 +112,18 @@ 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
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
126
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
144
127
  if (!installStatus.installed) {
145
128
  issues.push({
146
129
  code: "COPILOT_SDK_MISSING",
@@ -150,7 +133,7 @@ async function runDoctor(args) {
150
133
  });
151
134
  }
152
135
  try {
153
- await (0, copilot_adapter_1.readCopilotAuthState)();
136
+ await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
154
137
  }
155
138
  catch (error) {
156
139
  const normalized = (0, errors_1.normalizeError)(error);
@@ -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, args.runtimeDir);
164
147
  if (!bridge.ok) {
165
148
  issues.push({
166
149
  code: mapBridgeDiagnosticCode(bridge.cause),
@@ -215,7 +198,14 @@ async function runDoctor(args) {
215
198
  healthy: issues.length === 0,
216
199
  issues,
217
200
  codexDir: args.codexDir,
218
- storage: (0, runtime_state_1.getStorageRoles)(),
201
+ storage: (0, runtime_state_1.getStorageRoles)({
202
+ codexDir: args.codexDir,
203
+ providersPath: args.providersPath,
204
+ configPath: args.configPath,
205
+ authPath: args.authPath,
206
+ runtimeDir: args.runtimeDir,
207
+ runtimesDir: args.runtimesDir,
208
+ }),
219
209
  liveState: drift,
220
210
  auth: authState,
221
211
  },
@@ -255,18 +245,10 @@ function renderConfigIssueMessage(issue) {
255
245
  return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
256
246
  case "MODEL_PROVIDER_BASE_URL_MISSING":
257
247
  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
248
  case "ACTIVE_PROVIDER_UNRESOLVED":
263
- return `Active profile "${issue.profile}" maps to multiple providers and cannot determine the current auth mirror owner.`;
249
+ return `Active profile "${issue.profile}" maps to multiple providers, so the active managed provider cannot be resolved uniquely.`;
264
250
  case "AUTH_JSON_INVALID":
265
251
  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
252
  case "DESTRUCTIVE_REMOVE_BLOCKED":
271
253
  return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
272
254
  default:
@@ -8,8 +8,9 @@ const lock_repo_1 = require("../storage/lock-repo");
8
8
  * Runs a write operation under a lock with automatic backup and rollback handling.
9
9
  */
10
10
  function runMutation(args) {
11
- return (0, lock_repo_1.withCodexLock)(args.codexDir, args.operation, () => {
12
- const backup = (0, backup_repo_1.createBackup)(args.codexDir, args.backupsDir, args.operation, args.files);
11
+ const lockPath = args.lockPath ?? require("node:path").join(args.codexDir ?? process.cwd(), ".codex-switch.lock");
12
+ return (0, lock_repo_1.withCodexLock)(lockPath, args.operation, () => {
13
+ const backup = (0, backup_repo_1.createBackup)(args.backupsDir, args.operation, args.files);
13
14
  try {
14
15
  const data = args.mutate({ backup });
15
16
  // Record the successful backup only after the mutation completes.