@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
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.COMMON_TAG_CHOICES = void 0;
4
4
  exports.collectAddInput = collectAddInput;
5
+ exports.collectCopilotAddInput = collectCopilotAddInput;
5
6
  exports.createNonInteractiveAddError = createNonInteractiveAddError;
6
7
  exports.promptTags = promptTags;
7
8
  const errors_1 = require("../domain/errors");
@@ -37,10 +38,51 @@ async function collectAddInput(runtime, defaults, providerExists, profileExists)
37
38
  tags,
38
39
  };
39
40
  }
41
+ /**
42
+ * Collects Copilot add command inputs interactively when required values are missing.
43
+ */
44
+ async function collectCopilotAddInput(runtime, defaults, providerExists, profileExists, options) {
45
+ runtime.writeLine("Interactive add mode");
46
+ runtime.writeLine("Provide the missing Copilot provider fields. Press Enter to keep optional values empty.");
47
+ const providerName = defaults.providerName
48
+ ? normalizeRequiredValue(defaults.providerName)
49
+ : await promptProviderName(runtime, providerExists);
50
+ const profile = defaults.profile ? normalizeRequiredValue(defaults.profile) : await promptRequiredValue(runtime, "Profile");
51
+ const createProfile = !profileExists(profile);
52
+ const model = createProfile
53
+ ? defaults.model
54
+ ? normalizeRequiredValue(defaults.model)
55
+ : await promptRequiredValue(runtime, `Model for new profile "${profile}"`)
56
+ : defaults.model ?? null;
57
+ const note = defaults.note ?? normalizeOptionalValue(await runtime.inputText("Note (optional)"));
58
+ const tags = defaults.tags.length > 0 ? defaults.tags : await promptTags(runtime);
59
+ const bridgeHost = options?.bridgeHost ?? normalizeOptionalValue(await runtime.inputText("Bridge host (optional)", { defaultValue: "127.0.0.1" }));
60
+ const bridgePortText = options?.bridgePort !== undefined && options.bridgePort !== null
61
+ ? String(options.bridgePort)
62
+ : await runtime.inputText("Bridge port (optional)", { defaultValue: "41415" });
63
+ const bridgePort = normalizeBridgePort(runtime, bridgePortText);
64
+ const bridgeApiKey = options?.bridgeApiKey ?? normalizeOptionalValue(await runtime.inputSecret("Bridge API key (optional)"));
65
+ return {
66
+ providerName,
67
+ profile,
68
+ createProfile,
69
+ model,
70
+ note,
71
+ tags,
72
+ bridgeApiKey,
73
+ bridgeHost,
74
+ bridgePort,
75
+ };
76
+ }
40
77
  /**
41
78
  * Throws a consistent error when interactive add is unavailable.
42
79
  */
43
- function createNonInteractiveAddError() {
80
+ function createNonInteractiveAddError(options) {
81
+ if (options?.copilot) {
82
+ return (0, errors_1.cliError)("INVALID_ARGUMENT", "add --copilot requires <provider> and --profile when running without an interactive TTY.", {
83
+ suggestion: "Run in a terminal TTY or pass <provider>, --profile, and any optional Copilot bridge flags explicitly.",
84
+ });
85
+ }
44
86
  return (0, errors_1.cliError)("INVALID_ARGUMENT", "add requires <provider>, --profile, and --api-key when running without an interactive TTY.", {
45
87
  suggestion: "Run in a terminal TTY or pass all required values explicitly.",
46
88
  });
@@ -90,6 +132,18 @@ function normalizeOptionalValue(value) {
90
132
  const normalized = value.trim();
91
133
  return normalized === "" ? null : normalized;
92
134
  }
135
+ function normalizeBridgePort(runtime, value) {
136
+ const normalized = value.trim();
137
+ if (normalized === "") {
138
+ return null;
139
+ }
140
+ const parsed = Number(normalized);
141
+ if (Number.isInteger(parsed) && parsed > 0) {
142
+ return parsed;
143
+ }
144
+ runtime.writeLine("Bridge port must be a positive integer. Falling back to the default port.");
145
+ return null;
146
+ }
93
147
  async function promptTags(runtime, defaults = []) {
94
148
  const defaultPresetTags = defaults.filter(isCommonTag);
95
149
  return runtime.selectMany("Select tags (optional)", exports.COMMON_TAG_CHOICES.map((tag) => ({ value: tag, label: tag })), { defaultValues: defaultPresetTags });
@@ -222,7 +222,7 @@ async function chooseSetupProfiles(runtime, profiles) {
222
222
  return runtime.selectMany("Choose unmanaged config profiles to adopt into providers.json.", profiles.map((profile) => ({
223
223
  value: profile.name,
224
224
  label: profile.name,
225
- hint: `${profile.model} | ${profile.baseUrl} | ${profile.envKey}`,
225
+ hint: `${profile.model} | ${profile.baseUrl}`,
226
226
  })));
227
227
  }
228
228
  /**
@@ -235,9 +235,6 @@ async function collectSetupProviderDetails(runtime, profiles, defaultsByProfile
235
235
  const providerName = (await runtime.inputText(`Provider name for profile "${profile}"`, {
236
236
  defaultValue: defaults.providerName ?? profile,
237
237
  })).trim();
238
- if (defaults.envKey) {
239
- runtime.writeLine(`Runtime env key for "${profile}": ${defaults.envKey}`);
240
- }
241
238
  const apiKey = await promptRequiredSecret(runtime, `API key for profile "${profile}"`, defaults.apiKey?.trim() || undefined);
242
239
  const baseUrl = (await runtime.inputText(`Base URL note for profile "${profile}" (optional)`, {
243
240
  defaultValue: defaults.baseUrl ?? "",
@@ -249,7 +246,6 @@ async function collectSetupProviderDetails(runtime, profiles, defaultsByProfile
249
246
  result[profile] = {
250
247
  providerName: providerName || defaults.providerName || profile,
251
248
  apiKey,
252
- envKey: defaults.envKey,
253
249
  baseUrl: baseUrl || defaults.baseUrl || undefined,
254
250
  note: note || defaults.note || undefined,
255
251
  // Empty selections are omitted so downstream setup validation can distinguish unset from explicit data.
@@ -114,7 +114,7 @@ async function createCopilotSession() {
114
114
  throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a supported createSession API.", {});
115
115
  }
116
116
  try {
117
- const session = (await Promise.resolve(createSession({})));
117
+ const session = (await Promise.resolve(createSession(createSessionOptions(sdk))));
118
118
  return {
119
119
  sdk,
120
120
  client,
@@ -122,11 +122,30 @@ async function createCopilotSession() {
122
122
  };
123
123
  }
124
124
  catch (error) {
125
+ if (classifyCopilotSessionError(error) === "unsupported") {
126
+ throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a compatible permission-handling session API.", {
127
+ cause: error instanceof Error ? error.message : String(error),
128
+ });
129
+ }
125
130
  throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "Copilot authentication is required before the local bridge can be used.", {
126
131
  cause: error instanceof Error ? error.message : String(error),
127
132
  });
128
133
  }
129
134
  }
135
+ /**
136
+ * Builds the session options used consistently across auth probes and request execution.
137
+ */
138
+ function createSessionOptions(sdk) {
139
+ const approveAll = resolveApproveAll(sdk);
140
+ if (approveAll) {
141
+ return {
142
+ onPermissionRequest: (request) => approveAll(request),
143
+ };
144
+ }
145
+ return {
146
+ onPermissionRequest: () => true,
147
+ };
148
+ }
130
149
  function createCopilotClient(sdk) {
131
150
  const ClientCtor = resolveConstructor(sdk, "CopilotClient");
132
151
  if (!ClientCtor) {
@@ -146,6 +165,16 @@ async function stopCopilotClient(client) {
146
165
  await Promise.resolve(client.stop());
147
166
  }
148
167
  }
168
+ /**
169
+ * Distinguishes true auth failures from SDK API-shape mismatches.
170
+ */
171
+ function classifyCopilotSessionError(error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ if (/onPermissionRequest/i.test(message) || /permission/i.test(message)) {
174
+ return "unsupported";
175
+ }
176
+ return "auth";
177
+ }
149
178
  function resolveCallable(target, name) {
150
179
  if (!target) {
151
180
  return null;
@@ -171,3 +200,17 @@ function resolveConstructor(target, name) {
171
200
  }
172
201
  return null;
173
202
  }
203
+ /**
204
+ * Resolves the SDK-provided permission helper when available.
205
+ */
206
+ function resolveApproveAll(target) {
207
+ const direct = target.approveAll;
208
+ if (typeof direct === "function") {
209
+ return direct;
210
+ }
211
+ const nestedDefault = target.default;
212
+ if (nestedDefault && typeof nestedDefault.approveAll === "function") {
213
+ return nestedDefault.approveAll;
214
+ }
215
+ return null;
216
+ }
@@ -65,8 +65,8 @@ function resetCopilotBridgeSpawnImplementation() {
65
65
  /**
66
66
  * Returns the last known Copilot bridge runtime status.
67
67
  */
68
- async function probeCopilotBridgeRuntime(provider) {
69
- const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
68
+ async function probeCopilotBridgeRuntime(provider, persistedState) {
69
+ const state = persistedState === undefined ? (0, runtime_state_repo_1.readCopilotBridgeState)() : persistedState;
70
70
  if (state && (!provider || !(0, providers_1.isCopilotBridgeProvider)(provider))) {
71
71
  return {
72
72
  ok: false,
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setCopilotCliSpawnImplementation = setCopilotCliSpawnImplementation;
4
+ exports.resetCopilotCliSpawnImplementation = resetCopilotCliSpawnImplementation;
5
+ exports.checkCopilotCliAvailable = checkCopilotCliAvailable;
6
+ exports.runCopilotLogin = runCopilotLogin;
7
+ const node_child_process_1 = require("node:child_process");
8
+ let spawnImplementation = node_child_process_1.spawnSync;
9
+ /**
10
+ * Overrides the spawn implementation for Copilot CLI tests.
11
+ */
12
+ function setCopilotCliSpawnImplementation(spawnLike) {
13
+ spawnImplementation = spawnLike;
14
+ }
15
+ /**
16
+ * Restores the default spawn implementation after tests.
17
+ */
18
+ function resetCopilotCliSpawnImplementation() {
19
+ spawnImplementation = node_child_process_1.spawnSync;
20
+ }
21
+ /**
22
+ * Checks whether the GitHub Copilot CLI is available on PATH.
23
+ */
24
+ function checkCopilotCliAvailable() {
25
+ const invocation = getCopilotInvocation(["--help"]);
26
+ const result = spawnImplementation(invocation.command, invocation.args, {
27
+ stdio: "pipe",
28
+ encoding: "utf8",
29
+ shell: false,
30
+ });
31
+ if (result.error || result.status !== 0) {
32
+ return {
33
+ ok: false,
34
+ cause: result.error?.message ?? (result.stderr.trim() || "Unknown failure"),
35
+ };
36
+ }
37
+ return { ok: true };
38
+ }
39
+ /**
40
+ * Launches the official `copilot login` flow in the current terminal.
41
+ */
42
+ function runCopilotLogin(options) {
43
+ const args = ["login"];
44
+ if (options?.host) {
45
+ args.push("--hostname", options.host);
46
+ }
47
+ const invocation = getCopilotInvocation(args);
48
+ const result = spawnImplementation(invocation.command, invocation.args, {
49
+ stdio: "inherit",
50
+ shell: false,
51
+ });
52
+ if (result.error || result.status !== 0) {
53
+ throw new Error(result.error?.message ?? `copilot login exited with status ${String(result.status)}`);
54
+ }
55
+ }
56
+ /**
57
+ * Resolves a cross-platform invocation for the Copilot CLI.
58
+ */
59
+ function getCopilotInvocation(args) {
60
+ if (process.platform === "win32") {
61
+ return {
62
+ command: process.env.ComSpec || "cmd.exe",
63
+ args: ["/d", "/s", "/c", ["copilot", ...args].join(" ")],
64
+ };
65
+ }
66
+ return {
67
+ command: "copilot",
68
+ args,
69
+ };
70
+ }
@@ -107,19 +107,66 @@ function installCopilotSdk() {
107
107
  if (!fs.existsSync(packageJsonPath)) {
108
108
  fs.writeFileSync(packageJsonPath, `${JSON.stringify({ name: "codex-switch-copilot-runtime", private: true, version: "0.0.0" }, null, 2)}\n`, "utf8");
109
109
  }
110
- const command = process.platform === "win32" ? "npm.cmd" : "npm";
111
- const result = spawnImplementation(command, ["install", "--no-save", `${COPILOT_SDK_PACKAGE}@${COPILOT_SDK_VERSION}`], {
110
+ const installCommand = resolveNpmInstallCommand();
111
+ const result = spawnImplementation(installCommand.command, installCommand.args, {
112
112
  cwd: installDir,
113
113
  stdio: "pipe",
114
114
  encoding: "utf8",
115
115
  shell: false,
116
116
  });
117
+ if (result.error) {
118
+ throw (0, errors_1.cliError)("COPILOT_SDK_INSTALL_FAILED", "Failed to install the optional Copilot SDK runtime.", {
119
+ installDir,
120
+ packageName: COPILOT_SDK_PACKAGE,
121
+ cause: result.error.message,
122
+ errorCode: result.error.code ?? null,
123
+ command: installCommand.command,
124
+ args: installCommand.args,
125
+ });
126
+ }
117
127
  if (result.status !== 0) {
118
128
  throw (0, errors_1.cliError)("COPILOT_SDK_INSTALL_FAILED", "Failed to install the optional Copilot SDK runtime.", {
119
129
  installDir,
120
130
  packageName: COPILOT_SDK_PACKAGE,
121
131
  cause: result.stderr || result.stdout || `npm exited with status ${String(result.status)}`,
132
+ command: installCommand.command,
133
+ args: installCommand.args,
122
134
  });
123
135
  }
124
136
  return probeCopilotSdkInstall();
125
137
  }
138
+ /**
139
+ * Resolves a stable npm install invocation for the optional Copilot SDK runtime.
140
+ */
141
+ function resolveNpmInstallCommand() {
142
+ const installArgs = ["install", "--no-save", `${COPILOT_SDK_PACKAGE}@${COPILOT_SDK_VERSION}`];
143
+ const npmCliPath = resolveNpmCliPath();
144
+ if (npmCliPath) {
145
+ return {
146
+ command: process.execPath,
147
+ args: [npmCliPath, ...installArgs],
148
+ };
149
+ }
150
+ return {
151
+ command: process.platform === "win32" ? "npm.cmd" : "npm",
152
+ args: installArgs,
153
+ };
154
+ }
155
+ /**
156
+ * Finds a locally available npm CLI script near the active Node runtime.
157
+ */
158
+ function resolveNpmCliPath() {
159
+ const execDir = path.dirname(process.execPath);
160
+ const candidates = [
161
+ process.env.npm_execpath,
162
+ path.join(execDir, "node_modules", "npm", "bin", "npm-cli.js"),
163
+ path.join(execDir, "..", "node_modules", "npm", "bin", "npm-cli.js"),
164
+ path.join(execDir, "..", "..", "node_modules", "npm", "bin", "npm-cli.js"),
165
+ ];
166
+ for (const candidate of candidates) {
167
+ if (candidate && fs.existsSync(candidate)) {
168
+ return path.resolve(candidate);
169
+ }
170
+ }
171
+ return null;
172
+ }
@@ -34,15 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.readAuthFileIfExists = readAuthFileIfExists;
37
- exports.buildManagedAuthPayload = buildManagedAuthPayload;
38
- exports.buildManagedAuthJson = buildManagedAuthJson;
39
- exports.writeAuthFile = writeAuthFile;
40
- exports.extractManagedAuthFingerprint = extractManagedAuthFingerprint;
41
- exports.readManagedAuthState = readManagedAuthState;
37
+ exports.readAuthFileState = readAuthFileState;
38
+ exports.writeOpenAiApiKeyAuth = writeOpenAiApiKeyAuth;
42
39
  const fs = __importStar(require("node:fs"));
43
40
  const errors_1 = require("../domain/errors");
44
- const fs_utils_1 = require("./fs-utils");
45
- const LEGACY_MANAGED_SECRET_KEYS = new Set(["api_key"]);
46
41
  /**
47
42
  * Reads auth.json when it exists and returns null otherwise.
48
43
  */
@@ -61,84 +56,27 @@ function readAuthFileIfExists(authPath) {
61
56
  }
62
57
  }
63
58
  /**
64
- * Builds the stable managed auth payload for one provider.
59
+ * Reads auth.json into a neutral file-state summary for status and doctor.
65
60
  */
66
- function buildManagedAuthPayload(provider) {
67
- return {
68
- auth_mode: "apikey",
69
- [provider.envKey]: provider.apiKey,
70
- };
71
- }
72
- /**
73
- * Builds the next auth.json object while preserving unmanaged metadata.
74
- */
75
- function buildManagedAuthJson(provider, existingAuthJson) {
76
- const nextManaged = buildManagedAuthPayload(provider);
77
- const result = {};
78
- if (existingAuthJson && typeof existingAuthJson === "object" && !Array.isArray(existingAuthJson)) {
79
- for (const [key, value] of Object.entries(existingAuthJson)) {
80
- if (key === "auth_mode" || LEGACY_MANAGED_SECRET_KEYS.has(key) || looksLikeManagedSecretKey(key)) {
81
- continue;
82
- }
83
- result[key] = value;
84
- }
85
- }
86
- result.auth_mode = nextManaged.auth_mode;
87
- result[provider.envKey] = provider.apiKey;
88
- return result;
89
- }
90
- /**
91
- * Writes auth.json atomically using the managed mirror strategy.
92
- */
93
- function writeAuthFile(authPath, provider, existingAuthJson) {
94
- (0, fs_utils_1.writeTextFileAtomic)(authPath, `${JSON.stringify(buildManagedAuthJson(provider, existingAuthJson), null, 2)}\n`);
95
- }
96
- /**
97
- * Extracts a lightweight fingerprint used by doctor/status.
98
- */
99
- function extractManagedAuthFingerprint(input) {
100
- if (!input || typeof input !== "object" || Array.isArray(input)) {
101
- return {
102
- authMode: null,
103
- managedSecretKeys: [],
104
- payload: null,
105
- };
106
- }
107
- const payload = input;
108
- const authMode = typeof payload.auth_mode === "string" ? payload.auth_mode : null;
109
- const managedSecretKeys = Object.keys(payload)
110
- .filter((key) => key !== "auth_mode" && looksLikeManagedSecretKey(key))
111
- .sort();
112
- return {
113
- authMode,
114
- managedSecretKeys,
115
- payload,
116
- };
117
- }
118
- /**
119
- * Reads auth.json into a doctor-friendly managed state summary.
120
- */
121
- function readManagedAuthState(authPath) {
61
+ function readAuthFileState(authPath) {
122
62
  if (!fs.existsSync(authPath)) {
123
63
  return {
124
64
  exists: false,
125
65
  valid: false,
126
66
  parseError: null,
127
67
  authMode: null,
128
- managedSecretKeys: [],
129
- payload: null,
130
68
  };
131
69
  }
132
70
  try {
133
71
  const payload = readAuthFileIfExists(authPath);
134
- const fingerprint = extractManagedAuthFingerprint(payload);
72
+ const authMode = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.auth_mode === "string"
73
+ ? String(payload.auth_mode)
74
+ : null;
135
75
  return {
136
76
  exists: true,
137
- valid: Boolean(fingerprint.payload),
77
+ valid: Boolean(payload && typeof payload === "object" && !Array.isArray(payload)),
138
78
  parseError: null,
139
- authMode: fingerprint.authMode,
140
- managedSecretKeys: fingerprint.managedSecretKeys,
141
- payload: fingerprint.payload,
79
+ authMode,
142
80
  };
143
81
  }
144
82
  catch (error) {
@@ -147,14 +85,27 @@ function readManagedAuthState(authPath) {
147
85
  valid: false,
148
86
  parseError: (0, errors_1.normalizeError)(error).message,
149
87
  authMode: null,
150
- managedSecretKeys: [],
151
- payload: null,
152
88
  };
153
89
  }
154
90
  }
155
- function looksLikeManagedSecretKey(key) {
156
- if (LEGACY_MANAGED_SECRET_KEYS.has(key)) {
157
- return true;
91
+ /**
92
+ * Writes the active direct-provider auth projection expected by Codex.
93
+ * Invalid or missing existing auth.json content is replaced with a minimal valid object.
94
+ */
95
+ function writeOpenAiApiKeyAuth(authPath, apiKey) {
96
+ let next = {};
97
+ if (fs.existsSync(authPath)) {
98
+ try {
99
+ const payload = JSON.parse(fs.readFileSync(authPath, "utf8"));
100
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
101
+ next = { ...payload };
102
+ }
103
+ }
104
+ catch {
105
+ next = {};
106
+ }
158
107
  }
159
- return /^[A-Z0-9_]+$/.test(key);
108
+ next.auth_mode = "apikey";
109
+ next.OPENAI_API_KEY = apiKey;
110
+ fs.writeFileSync(authPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
160
111
  }
@@ -40,7 +40,6 @@ exports.listConfigProfiles = listConfigProfiles;
40
40
  exports.ensureProfileExists = ensureProfileExists;
41
41
  exports.requireManagedProfileRuntime = requireManagedProfileRuntime;
42
42
  exports.requireModelProviderRuntimeSection = requireModelProviderRuntimeSection;
43
- exports.requireRuntimeEnvKey = requireRuntimeEnvKey;
44
43
  exports.resolveActiveProviderName = resolveActiveProviderName;
45
44
  exports.updateTopLevelProfile = updateTopLevelProfile;
46
45
  exports.createConfigMutationPlan = createConfigMutationPlan;
@@ -143,13 +142,6 @@ function requireManagedProfileRuntime(document, providers, profile) {
143
142
  missingFields: ["base_url"],
144
143
  });
145
144
  }
146
- if (!modelProviderSection.envKey) {
147
- throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${view.modelProvider}" requires env_key.`, {
148
- profile,
149
- modelProvider: view.modelProvider,
150
- missingFields: ["env_key"],
151
- });
152
- }
153
145
  return view;
154
146
  }
155
147
  /**
@@ -170,33 +162,6 @@ function requireModelProviderRuntimeSection(document, profile) {
170
162
  missingFields: ["base_url"],
171
163
  });
172
164
  }
173
- if (!modelProviderSection.envKey) {
174
- throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
175
- profile,
176
- modelProvider: profile,
177
- missingFields: ["env_key"],
178
- });
179
- }
180
- }
181
- /**
182
- * Returns the runtime env_key for one profile or throws a typed error.
183
- */
184
- function requireRuntimeEnvKey(document, profile) {
185
- const modelProviderSection = document.modelProviders.find((entry) => entry.name === profile);
186
- if (!modelProviderSection) {
187
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${profile}" does not exist in config.toml.`, {
188
- profile,
189
- modelProvider: profile,
190
- });
191
- }
192
- if (!modelProviderSection.envKey) {
193
- throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
194
- profile,
195
- modelProvider: profile,
196
- missingFields: ["env_key"],
197
- });
198
- }
199
- return modelProviderSection.envKey;
200
165
  }
201
166
  /**
202
167
  * Resolves the current active provider and requires the mapping to be unique.
@@ -212,7 +177,7 @@ function resolveActiveProviderName(document, providers) {
212
177
  });
213
178
  }
214
179
  if (matches.length > 1) {
215
- throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers.`, {
180
+ throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers, so the active managed provider is ambiguous.`, {
216
181
  profile: document.activeProfile,
217
182
  providers: matches,
218
183
  });
@@ -35,11 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
37
37
  exports.readCopilotBridgeState = readCopilotBridgeState;
38
+ exports.inspectCopilotBridgeState = inspectCopilotBridgeState;
38
39
  exports.writeCopilotBridgeState = writeCopilotBridgeState;
39
40
  exports.clearCopilotBridgeState = clearCopilotBridgeState;
40
41
  const fs = __importStar(require("node:fs"));
41
42
  const os = __importStar(require("node:os"));
42
43
  const path = __importStar(require("node:path"));
44
+ const errors_1 = require("../domain/errors");
43
45
  const fs_utils_1 = require("./fs-utils");
44
46
  /**
45
47
  * Returns the user-level runtime state file used by Copilot bridge helpers.
@@ -61,6 +63,36 @@ function readCopilotBridgeState() {
61
63
  }
62
64
  return JSON.parse(fs.readFileSync(statePath, "utf8"));
63
65
  }
66
+ /**
67
+ * Safely inspects the runtime-state file for status/doctor style read paths.
68
+ */
69
+ function inspectCopilotBridgeState() {
70
+ const statePath = getCopilotBridgeStatePath();
71
+ if (!fs.existsSync(statePath)) {
72
+ return {
73
+ exists: false,
74
+ valid: false,
75
+ parseError: null,
76
+ state: null,
77
+ };
78
+ }
79
+ try {
80
+ return {
81
+ exists: true,
82
+ valid: true,
83
+ parseError: null,
84
+ state: readCopilotBridgeState(),
85
+ };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ exists: true,
90
+ valid: false,
91
+ parseError: (0, errors_1.normalizeError)(error).message,
92
+ state: null,
93
+ };
94
+ }
95
+ }
64
96
  /**
65
97
  * Persists the Copilot bridge state manifest.
66
98
  */