@minniexcode/codex-switch 0.2.0 → 0.2.2

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 (40) hide show
  1. package/README.AI.md +66 -94
  2. package/README.CN.md +84 -139
  3. package/README.md +91 -151
  4. package/dist/app/add-provider.js +6 -83
  5. package/dist/app/edit-provider.js +9 -29
  6. package/dist/app/get-status.js +1 -77
  7. package/dist/app/list-providers.js +0 -2
  8. package/dist/app/remove-provider.js +3 -5
  9. package/dist/app/run-doctor.js +2 -99
  10. package/dist/app/setup-codex.js +0 -2
  11. package/dist/app/switch-provider.js +1 -74
  12. package/dist/cli/output.js +3 -89
  13. package/dist/commands/handlers.js +20 -172
  14. package/dist/commands/help.js +1 -4
  15. package/dist/commands/registry.js +6 -74
  16. package/dist/domain/config.js +1 -3
  17. package/dist/domain/providers.js +4 -92
  18. package/dist/domain/runtime-state.js +0 -88
  19. package/dist/interaction/add-interactive.js +1 -55
  20. package/dist/interaction/interactive.js +1 -3
  21. package/dist/runtime/codex-probe.js +0 -12
  22. package/dist/storage/codex-paths.js +0 -2
  23. package/docs/Design/codex-switch-v0.2.1-design.md +77 -0
  24. package/docs/PRD/codex-switch-prd-v0.2.1.md +82 -0
  25. package/docs/Tests/testing.md +32 -34
  26. package/docs/cli-usage.md +67 -235
  27. package/docs/codex-switch-command-design.md +1 -1
  28. package/docs/codex-switch-product-overview.md +49 -96
  29. package/docs/codex-switch-technical-architecture.md +37 -52
  30. package/package.json +1 -1
  31. package/dist/app/bridge.js +0 -303
  32. package/dist/runtime/copilot-adapter.js +0 -617
  33. package/dist/runtime/copilot-bridge-worker.js +0 -69
  34. package/dist/runtime/copilot-bridge.js +0 -1351
  35. package/dist/runtime/copilot-cli.js +0 -164
  36. package/dist/runtime/copilot-http-bridge-worker.js +0 -228
  37. package/dist/runtime/copilot-installer.js +0 -231
  38. package/dist/runtime/copilot-sdk-loader.js +0 -62
  39. package/dist/runtime/copilot-token.js +0 -294
  40. package/dist/storage/runtime-state-repo.js +0 -121
@@ -48,7 +48,6 @@ const list_providers_1 = require("../app/list-providers");
48
48
  const remove_provider_1 = require("../app/remove-provider");
49
49
  const rollback_backup_1 = require("../app/rollback-backup");
50
50
  const run_doctor_1 = require("../app/run-doctor");
51
- const bridge_1 = require("../app/bridge");
52
51
  const setup_codex_1 = require("../app/setup-codex");
53
52
  const show_config_1 = require("../app/show-config");
54
53
  const show_provider_1 = require("../app/show-provider");
@@ -60,7 +59,6 @@ const providers_1 = require("../domain/providers");
60
59
  const add_interactive_1 = require("../interaction/add-interactive");
61
60
  const interactive_1 = require("../interaction/interactive");
62
61
  const prompt_1 = require("../interaction/prompt");
63
- const copilot_token_1 = require("../runtime/copilot-token");
64
62
  const config_repo_1 = require("../storage/config-repo");
65
63
  const codex_paths_1 = require("../storage/codex-paths");
66
64
  const providers_repo_1 = require("../storage/providers-repo");
@@ -95,48 +93,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
95
93
  case "current":
96
94
  return (0, get_current_profile_1.getCurrentProfile)(paths.configPath, paths.providersPath);
97
95
  case "status":
98
- return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath, {
99
- runtimeDir: paths.runtimeDir,
100
- runtimesDir: paths.runtimesDir,
101
- toolHomeDir: paths.toolHomeDir,
102
- });
103
- case "bridge-start": {
104
- const providerName = parsed.positionals[0] ?? null;
105
- return (0, bridge_1.startBridge)({
106
- providersPath: paths.providersPath,
107
- configPath: paths.configPath,
108
- runtimeDir: paths.runtimeDir,
109
- runtimesDir: paths.runtimesDir,
110
- toolHomeDir: paths.toolHomeDir,
111
- providerName,
112
- runtime,
113
- json: ctx.options.json,
114
- });
115
- }
116
- case "bridge-stop": {
117
- const providerName = parsed.positionals[0] ?? null;
118
- return (0, bridge_1.stopBridge)({
119
- providersPath: paths.providersPath,
120
- configPath: paths.configPath,
121
- runtimeDir: paths.runtimeDir,
122
- runtimesDir: paths.runtimesDir,
123
- providerName,
124
- runtime,
125
- json: ctx.options.json,
126
- });
127
- }
128
- case "bridge-status": {
129
- const providerName = parsed.positionals[0] ?? null;
130
- return (0, bridge_1.statusBridge)({
131
- providersPath: paths.providersPath,
132
- configPath: paths.configPath,
133
- runtimeDir: paths.runtimeDir,
134
- runtimesDir: paths.runtimesDir,
135
- providerName,
136
- runtime,
137
- json: ctx.options.json,
138
- });
139
- }
96
+ return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath);
140
97
  case "init": {
141
98
  return (0, init_codex_1.initCodex)({
142
99
  toolHomeDir: setupPaths.toolHomeDir,
@@ -146,54 +103,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
146
103
  defaultCodexDir: ctx.options.codexDirExplicit ? setupPaths.codexDir : null,
147
104
  });
148
105
  }
149
- case "login": {
150
- const upstream = (parsed.positionals[0] ?? "").toLowerCase();
151
- if (ctx.options.json || !runtime.isInteractive()) {
152
- throw (0, errors_1.cliError)("COPILOT_LOGIN_REQUIRES_TTY", "login requires an interactive TTY and does not support --json.");
153
- }
154
- if (upstream !== "copilot" && upstream !== "github-copilot") {
155
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", `Unsupported upstream "${parsed.positionals[0] ?? ""}".`, {
156
- supportedUpstreams: ["copilot", "github-copilot"],
157
- });
158
- }
159
- // Check if already authenticated
160
- const existingToken = (0, copilot_token_1.readGithubToken)(paths.toolHomeDir);
161
- if (existingToken) {
162
- try {
163
- await (0, copilot_token_1.exchangeForCopilotToken)(existingToken);
164
- return {
165
- data: {
166
- upstream: "github-copilot",
167
- authReady: true,
168
- loginLaunched: false,
169
- authSource: "github-pat",
170
- },
171
- };
172
- }
173
- catch {
174
- // Token is invalid, proceed with new login
175
- }
176
- }
177
- // Start GitHub OAuth Device Flow
178
- runtime.writeLine("Starting GitHub authentication...");
179
- const deviceFlow = await (0, copilot_token_1.startDeviceFlow)();
180
- runtime.writeLine(`\nPlease visit: ${deviceFlow.verificationUri}`);
181
- runtime.writeLine(`And enter code: ${deviceFlow.userCode}\n`);
182
- runtime.writeLine("Waiting for authorization...");
183
- const githubPat = await (0, copilot_token_1.pollDeviceFlowToken)(deviceFlow.deviceCode, deviceFlow.interval, deviceFlow.expiresIn);
184
- // Validate the token by doing a test exchange
185
- await (0, copilot_token_1.exchangeForCopilotToken)(githubPat);
186
- (0, copilot_token_1.writeGithubToken)(githubPat, paths.toolHomeDir);
187
- runtime.writeLine("GitHub Copilot authentication successful!");
188
- return {
189
- data: {
190
- upstream: "github-copilot",
191
- authReady: true,
192
- loginLaunched: true,
193
- authSource: "github-pat",
194
- },
195
- };
196
- }
197
106
  case "config-show":
198
107
  return (0, show_config_1.showConfig)({
199
108
  configPath: paths.configPath,
@@ -213,11 +122,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
213
122
  if (!providerName) {
214
123
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
215
124
  }
216
- if ((0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk")) {
217
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with switch. Run `codexs login copilot` instead.", {
218
- suggestion: "Run `codexs login copilot` first, then rerun switch without --install-copilot-sdk.",
219
- });
220
- }
221
125
  return (0, switch_provider_1.switchProvider)({
222
126
  codexDir: paths.codexDir,
223
127
  lockPath: paths.lockPath,
@@ -226,9 +130,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
226
130
  configPath: paths.configPath,
227
131
  providersPath: paths.providersPath,
228
132
  authPath: paths.authPath,
229
- runtimeDir: paths.runtimeDir,
230
- runtimesDir: paths.runtimesDir,
231
- toolHomeDir: paths.toolHomeDir,
232
133
  providerName,
233
134
  });
234
135
  }
@@ -289,75 +190,31 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
289
190
  let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
290
191
  let tags = parsed.commandOptions.get("--tag") ?? [];
291
192
  let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
292
- const copilot = (0, args_1.hasFlag)(parsed.commandOptions, "--copilot");
293
- let bridgeHost = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-host", false);
294
- const bridgePortValue = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-port", false);
295
- let bridgeApiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-api-key", false);
296
- const installCopilotSdk = (0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk");
297
- let bridgePort = bridgePortValue ? Number(bridgePortValue) : null;
298
- if (copilot && apiKey) {
299
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--copilot does not allow --api-key. Use --bridge-api-key for the local bridge secret.");
300
- }
301
- if (copilot && installCopilotSdk) {
302
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with add --copilot. Run `codexs login copilot` instead.", {
303
- suggestion: "Run `codexs login copilot` first, then rerun add --copilot.",
304
- });
305
- }
306
- if (bridgePortValue && (!Number.isInteger(bridgePort) || bridgePort === null || bridgePort <= 0)) {
307
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--bridge-port must be a positive integer.");
308
- }
309
- if (!providerName || !profile || (!apiKey && !copilot)) {
193
+ if (!providerName || !profile || !apiKey) {
310
194
  if (ctx.options.json || !runtime.isInteractive()) {
311
- throw (0, add_interactive_1.createNonInteractiveAddError)({ copilot });
312
- }
313
- if (copilot) {
314
- const prompted = await (0, add_interactive_1.collectCopilotAddInput)(runtime, {
315
- providerName,
316
- profile,
317
- model,
318
- note,
319
- tags,
320
- }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]), (candidate) => Boolean((0, config_repo_1.readStructuredConfig)(paths.configPath).profiles.find((profileView) => profileView.name === candidate)), {
321
- bridgeHost,
322
- bridgePort,
323
- bridgeApiKey,
324
- });
325
- providerName = prompted.providerName;
326
- profile = prompted.profile;
327
- model = prompted.model ?? null;
328
- note = prompted.note ?? null;
329
- tags = prompted.tags;
330
- createProfile = createProfile || prompted.createProfile;
331
- baseUrl = null;
332
- bridgeHost = prompted.bridgeHost ?? bridgeHost;
333
- bridgePort = prompted.bridgePort ?? bridgePort;
334
- bridgeApiKey = prompted.bridgeApiKey ?? bridgeApiKey;
335
- }
336
- else {
337
- const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
338
- providerName,
339
- profile,
340
- apiKey,
341
- model,
342
- baseUrl,
343
- note,
344
- tags,
345
- }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]), (candidate) => Boolean((0, config_repo_1.readStructuredConfig)(paths.configPath).profiles.find((profileView) => profileView.name === candidate)));
346
- providerName = prompted.providerName;
347
- profile = prompted.profile;
348
- apiKey = prompted.apiKey;
349
- model = prompted.model ?? null;
350
- baseUrl = prompted.baseUrl ?? null;
351
- note = prompted.note ?? null;
352
- tags = prompted.tags;
353
- createProfile = createProfile || prompted.createProfile;
195
+ throw (0, add_interactive_1.createNonInteractiveAddError)();
354
196
  }
197
+ const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
198
+ providerName,
199
+ profile,
200
+ apiKey,
201
+ model,
202
+ baseUrl,
203
+ note,
204
+ tags,
205
+ }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]), (candidate) => Boolean((0, config_repo_1.readStructuredConfig)(paths.configPath).profiles.find((profileView) => profileView.name === candidate)));
206
+ providerName = prompted.providerName;
207
+ profile = prompted.profile;
208
+ apiKey = prompted.apiKey;
209
+ model = prompted.model ?? null;
210
+ baseUrl = prompted.baseUrl ?? null;
211
+ note = prompted.note ?? null;
212
+ tags = prompted.tags;
213
+ createProfile = createProfile || prompted.createProfile;
355
214
  }
356
215
  return (0, add_provider_1.addProvider)({
357
216
  codexDir: paths.codexDir,
358
- toolHomeDir: paths.toolHomeDir,
359
217
  lockPath: paths.lockPath,
360
- runtimesDir: paths.runtimesDir,
361
218
  backupsDir: paths.backupsDir,
362
219
  latestBackupPath: paths.latestBackupPath,
363
220
  providersPath: paths.providersPath,
@@ -371,10 +228,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
371
228
  note,
372
229
  tags,
373
230
  createProfile,
374
- copilot,
375
- bridgeHost,
376
- bridgePort,
377
- bridgeApiKey,
378
231
  });
379
232
  }
380
233
  case "edit": {
@@ -467,9 +320,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
467
320
  configPath: paths.configPath,
468
321
  providersPath: paths.providersPath,
469
322
  authPath: paths.authPath,
470
- runtimeDir: paths.runtimeDir,
471
- runtimesDir: paths.runtimesDir,
472
- toolHomeDir: paths.toolHomeDir,
473
323
  });
474
324
  case "migrate": {
475
325
  let codexDir = ctx.options.codexDir;
@@ -569,8 +419,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
569
419
  configPath: setupPaths.configPath,
570
420
  providersPath: setupPaths.providersPath,
571
421
  authPath: setupPaths.authPath,
572
- runtimeDir: setupPaths.runtimeDir,
573
- runtimesDir: setupPaths.runtimesDir,
574
422
  backupsDir: setupPaths.backupsDir,
575
423
  latestBackupPath: setupPaths.latestBackupPath,
576
424
  strategy: strategy ?? "overwrite",
@@ -30,8 +30,7 @@ function buildHelpText(commandName) {
30
30
  "codex-switch",
31
31
  "",
32
32
  "Manage and switch local Codex provider/model-provider routing safely.",
33
- "Primary workflows: direct providers use init -> add -> switch -> status -> doctor.",
34
- "Primary workflows: Copilot providers use init -> login copilot -> add --copilot -> switch -> status -> doctor.",
33
+ "Primary workflow: init -> add -> switch -> status -> doctor.",
35
34
  "Advanced adopt flows use migrate only when you already have Codex runtime state to import.",
36
35
  "Deprecated entry: setup still exists only to point callers to init or migrate.",
37
36
  "",
@@ -69,8 +68,6 @@ function buildHelpText(commandName) {
69
68
  " codexs switch packycode",
70
69
  " codexs status",
71
70
  " codexs doctor",
72
- " codexs login copilot",
73
- " codexs add copilot-main --copilot --profile copilot-main --model gpt-5",
74
71
  " codexs migrate",
75
72
  " codexs config show",
76
73
  " codexs backups list",
@@ -41,48 +41,6 @@ exports.COMMANDS = [
41
41
  ],
42
42
  examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
43
43
  },
44
- {
45
- id: "bridge-start",
46
- tokens: ["bridge", "start"],
47
- handler: handlers_1.handleRegisteredCommand,
48
- group: "write",
49
- summary: "Start or reuse the managed Copilot bridge.",
50
- usage: ["codexs bridge start [provider] [--json] [--codex-dir <path>]"],
51
- details: [
52
- "Resolves a Copilot bridge provider by explicit name, active provider, sole provider, or TTY selection.",
53
- "Reuses a healthy bridge for the same provider and replaces a different managed provider when needed.",
54
- "If the preferred port is occupied, automatically selects another free 5-digit port and persists it.",
55
- ],
56
- examples: ["codexs bridge start", "codexs bridge start copilot-main"],
57
- },
58
- {
59
- id: "bridge-stop",
60
- tokens: ["bridge", "stop"],
61
- handler: handlers_1.handleRegisteredCommand,
62
- group: "recovery",
63
- summary: "Stop the managed Copilot bridge.",
64
- usage: ["codexs bridge stop [provider] [--json] [--codex-dir <path>]"],
65
- details: [
66
- "Prefers the runtime-state instance when present and uses an explicit provider as a guard.",
67
- "Clears the runtime-state manifest without mutating providers.json or Codex auth state.",
68
- "Is idempotent when no managed bridge is currently running.",
69
- ],
70
- examples: ["codexs bridge stop", "codexs bridge stop copilot-main"],
71
- },
72
- {
73
- id: "bridge-status",
74
- tokens: ["bridge", "status"],
75
- handler: handlers_1.handleRegisteredCommand,
76
- group: "read",
77
- summary: "Inspect the managed Copilot bridge.",
78
- usage: ["codexs bridge status [provider] [--json] [--codex-dir <path>]"],
79
- details: [
80
- "Reports runtime-state, provider binding, and whether the live worker matches the expected provider.",
81
- "Prefers the runtime-state instance when one is present.",
82
- "Uses an explicit provider as a guard instead of silently switching targets.",
83
- ],
84
- examples: ["codexs bridge status", "codexs bridge status copilot-main"],
85
- },
86
44
  {
87
45
  id: "init",
88
46
  tokens: ["init"],
@@ -95,26 +53,10 @@ exports.COMMANDS = [
95
53
  "Does not create or validate config.toml, auth.json, or the target Codex directory.",
96
54
  "When --codex-dir is passed explicitly and codex-switch.json does not exist yet, init persists it as defaultCodexDir.",
97
55
  "Otherwise init stays scoped to tool-home state and does not persist fallback Codex directory resolution.",
98
- "Use init first for fresh direct-provider or Copilot setups.",
56
+ "Use init first for fresh provider-management setups.",
99
57
  ],
100
58
  examples: ["codexs init", "codexs init --json --codex-dir ~/.codex"],
101
59
  },
102
- {
103
- id: "login",
104
- tokens: ["login"],
105
- handler: handlers_1.handleRegisteredCommand,
106
- group: "write",
107
- summary: "Complete upstream onboarding for interactive providers such as GitHub Copilot.",
108
- usage: ["codexs login <upstream>"],
109
- details: [
110
- "Currently supports copilot and github-copilot as the same upstream.",
111
- "Installs the local Copilot SDK under the tool home when needed, then checks login readiness.",
112
- "When login is not ready, launches the bundled Copilot CLI from the runtime when available, otherwise falls back to PATH, then rechecks before succeeding.",
113
- "Copilot login is shared across the local Copilot runtime, so logging into a different GitHub account replaces the upstream auth used by all Copilot providers.",
114
- "Requires an interactive TTY and does not support --json.",
115
- ],
116
- examples: ["codexs login copilot", "codexs login github-copilot"],
117
- },
118
60
  {
119
61
  id: "migrate",
120
62
  tokens: ["migrate"],
@@ -153,7 +95,7 @@ exports.COMMANDS = [
153
95
  summary: "List managed providers with model-provider routing and current-state hints.",
154
96
  usage: ["codexs list [--json] [--codex-dir <path>]"],
155
97
  details: [
156
- "Reads providers.json and prints provider-to-model-provider mappings together with provider type.",
98
+ "Reads providers.json and prints provider-to-model-provider mappings.",
157
99
  "When the active model_provider is shared by multiple providers, list surfaces the ambiguity instead of inventing one current provider.",
158
100
  "Use --json for machine-readable automation output.",
159
101
  ],
@@ -188,11 +130,10 @@ exports.COMMANDS = [
188
130
  tokens: ["status"],
189
131
  handler: handlers_1.handleRegisteredCommand,
190
132
  group: "read",
191
- summary: "Show tool-home, target-runtime, provider-path, and runtime-health status.",
133
+ summary: "Show target Codex directory, managed route, and health status.",
192
134
  usage: ["codexs status [--json] [--codex-dir <path>]"],
193
135
  details: [
194
- "Reports the target Codex runtime, tool-home storage roles, current model, current model_provider, and whether the live route is mapped.",
195
- "When the active provider uses a local runtime bridge, status also reports bridge, Copilot SDK, and upstream auth state.",
136
+ "Reports the target Codex directory, tool-home root, current model, current model_provider, and whether the live route is mapped.",
196
137
  "Surfaces dual-path config consistency signals without mutating any files.",
197
138
  "Organizes the human-readable view around current state, health impact, and next step.",
198
139
  "Use doctor for deeper diagnostics.",
@@ -224,10 +165,9 @@ exports.COMMANDS = [
224
165
  tokens: ["add"],
225
166
  handler: handlers_1.handleRegisteredCommand,
226
167
  group: "write",
227
- summary: "Add a managed provider for the primary direct or Copilot workflows.",
168
+ summary: "Add a managed provider.",
228
169
  usage: [
229
170
  "codexs add <provider> --profile <model-provider-id> --model <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
230
- "codexs add <provider> --copilot --profile <model-provider-id> --model <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>]",
231
171
  "codexs add [--profile <model-provider-id>] [--model <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
232
172
  ],
233
173
  details: [
@@ -238,14 +178,9 @@ exports.COMMANDS = [
238
178
  "Automation and non-TTY environments must pass all required values explicitly.",
239
179
  "--profile is a CLI alias for the stored model_provider id.",
240
180
  "The command projects only model_providers sections and does not create legacy profiles sections.",
241
- "Use --copilot to create a GitHub Copilot bridge provider backed by the official SDK.",
242
- "Copilot providers require SDK install and login readiness to already be satisfied via codexs login copilot.",
243
- "For Copilot providers, provider apiKey stores only the local bridge secret; upstream GitHub Copilot auth stays shared in the official runtime login.",
244
- "--install-copilot-sdk is kept only as a rejected compatibility flag that points to codexs login copilot.",
245
181
  ],
246
182
  examples: [
247
183
  "codexs add packycode --profile packycode --model gpt-5 --api-key sk-xxx --base-url https://api.example/v1",
248
- "codexs add copilot-main --copilot --profile copilot-main --model gpt-5",
249
184
  "codexs add",
250
185
  ],
251
186
  },
@@ -259,9 +194,7 @@ exports.COMMANDS = [
259
194
  details: [
260
195
  "When <provider> is omitted in a TTY, an interactive provider selector is shown.",
261
196
  "When <provider> is passed explicitly, switch proceeds directly without extra confirmation.",
262
- "Direct providers update the active config profile and rewrite auth.json with auth_mode=apikey plus OPENAI_API_KEY.",
263
- "Copilot bridge providers also rewrite OPENAI_API_KEY to the local bridge secret while managing runtime routing and bridge state.",
264
- "Copilot bridge providers probe the optional official SDK before switching and fail fast if it is missing.",
197
+ "Updates the active config profile and rewrites auth.json with auth_mode=apikey plus OPENAI_API_KEY.",
265
198
  "Switch succeeds only after the managed profile projection is written to the target runtime.",
266
199
  "Backs up config.toml and auth.json and rolls back on failure.",
267
200
  ],
@@ -333,7 +266,6 @@ exports.COMMANDS = [
333
266
  usage: ["codexs doctor [--json] [--codex-dir <path>]"],
334
267
  details: [
335
268
  "Checks the expected config files, provider/model-provider consistency, and Codex CLI availability.",
336
- "Copilot bridge providers add runtime dependency, auth, and bridge health diagnostics.",
337
269
  "Returns structured issues so users and AI agents can act on them.",
338
270
  ],
339
271
  examples: ["codexs doctor", "codexs doctor --json"],
@@ -335,8 +335,7 @@ function collectConfigConsistencyIssues(document, providers) {
335
335
  .sort(([left], [right]) => left.localeCompare(right));
336
336
  if (linkedProviders.length === 1 && activeProviderSection?.baseUrl) {
337
337
  const [providerName, provider] = linkedProviders[0];
338
- if (!provider.runtime &&
339
- typeof provider.baseUrl === "string" &&
338
+ if (typeof provider.baseUrl === "string" &&
340
339
  provider.baseUrl.trim() !== "" &&
341
340
  provider.baseUrl !== activeProviderSection.baseUrl) {
342
341
  issues.push({
@@ -345,7 +344,6 @@ function collectConfigConsistencyIssues(document, providers) {
345
344
  provider: providerName,
346
345
  providerBaseUrl: provider.baseUrl,
347
346
  configBaseUrl: activeProviderSection.baseUrl,
348
- providerType: "direct",
349
347
  });
350
348
  }
351
349
  }
@@ -6,11 +6,7 @@ exports.sortProviders = sortProviders;
6
6
  exports.findProviderByProfile = findProviderByProfile;
7
7
  exports.findProvidersByProfile = findProvidersByProfile;
8
8
  exports.maskSecret = maskSecret;
9
- exports.isRuntimeBackedProvider = isRuntimeBackedProvider;
10
- exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
11
- exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
12
- exports.buildCopilotModelProviderProjection = buildCopilotModelProviderProjection;
13
- exports.buildDirectModelProviderProjection = buildDirectModelProviderProjection;
9
+ exports.buildModelProviderProjection = buildModelProviderProjection;
14
10
  /**
15
11
  * Validates and normalizes unknown JSON into the providers.json domain model.
16
12
  */
@@ -47,14 +43,6 @@ function validateProvidersShape(input) {
47
43
  (!Array.isArray(provider.tags) || provider.tags.some((tag) => typeof tag !== "string"))) {
48
44
  throw new Error(`Provider "${name}" has invalid tags.`);
49
45
  }
50
- if (provider.runtime !== undefined) {
51
- validateProviderRuntime(name, provider.runtime);
52
- const expectedBaseUrl = buildCopilotBridgeBaseUrl(provider.runtime);
53
- if (typeof provider.baseUrl !== "string" || provider.baseUrl.trim() !== expectedBaseUrl) {
54
- throw new Error(`Provider "${name}" baseUrl must match runtime bridge base URL "${expectedBaseUrl}".`);
55
- }
56
- }
57
- // Normalize provider fields during validation so the persisted format stays clean.
58
46
  providers[name] = cleanProviderRecord({
59
47
  profile: provider.profile,
60
48
  apiKey: provider.apiKey,
@@ -62,7 +50,6 @@ function validateProvidersShape(input) {
62
50
  baseUrl: provider.baseUrl,
63
51
  note: provider.note,
64
52
  tags: provider.tags,
65
- runtime: provider.runtime,
66
53
  });
67
54
  }
68
55
  return { providers };
@@ -87,21 +74,6 @@ function cleanProviderRecord(record) {
87
74
  if (record.tags && record.tags.length > 0) {
88
75
  next.tags = record.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
89
76
  }
90
- if (record.runtime) {
91
- const cleanedRuntime = {
92
- kind: record.runtime.kind,
93
- upstream: record.runtime.upstream,
94
- bridgeHost: record.runtime.bridgeHost.trim(),
95
- bridgePort: record.runtime.bridgePort,
96
- bridgePath: record.runtime.bridgePath,
97
- premiumRequests: record.runtime.premiumRequests,
98
- authSource: record.runtime.authSource,
99
- };
100
- if (record.runtime.sdkInstallMode) {
101
- cleanedRuntime.sdkInstallMode = record.runtime.sdkInstallMode;
102
- }
103
- next.runtime = cleanedRuntime;
104
- }
105
77
  return next;
106
78
  }
107
79
  /**
@@ -145,42 +117,12 @@ function maskSecret(value) {
145
117
  return `${value.slice(0, 3)}***${value.slice(-2)}`;
146
118
  }
147
119
  /**
148
- * Returns whether one provider record relies on an auxiliary runtime component.
149
- */
150
- function isRuntimeBackedProvider(provider) {
151
- return Boolean(provider.runtime);
152
- }
153
- /**
154
- * Returns whether one provider uses the GitHub Copilot bridge runtime.
155
- */
156
- function isCopilotBridgeProvider(provider) {
157
- return provider.runtime?.kind === "copilot-http-proxy" || provider.runtime?.kind === "copilot-sdk-bridge";
158
- }
159
- /**
160
- * Builds the canonical local bridge URL for one Copilot runtime provider.
161
- */
162
- function buildCopilotBridgeBaseUrl(runtime) {
163
- return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
164
- }
165
- /**
166
- * Builds the Codex-facing custom model_provider projection for the managed Copilot bridge.
167
- */
168
- function buildCopilotModelProviderProjection(runtime) {
169
- return {
170
- baseUrl: buildCopilotBridgeBaseUrl(runtime),
171
- name: "copilot",
172
- requiresOpenAiAuth: true,
173
- wireApi: "responses",
174
- streamIdleTimeoutMs: 300000,
175
- };
176
- }
177
- /**
178
- * Builds the Codex-facing custom model_provider projection for a direct provider.
120
+ * Builds the Codex-facing custom model_provider projection for a provider.
179
121
  */
180
- function buildDirectModelProviderProjection(profile, baseUrl) {
122
+ function buildModelProviderProjection(profile, baseUrl) {
181
123
  const normalizedBaseUrl = baseUrl.trim();
182
124
  if (!normalizedBaseUrl) {
183
- throw new Error(`Direct model provider "${profile}" requires a non-empty base_url.`);
125
+ throw new Error(`Model provider "${profile}" requires a non-empty base_url.`);
184
126
  }
185
127
  return {
186
128
  baseUrl: normalizedBaseUrl,
@@ -189,33 +131,3 @@ function buildDirectModelProviderProjection(profile, baseUrl) {
189
131
  wireApi: "responses",
190
132
  };
191
133
  }
192
- /**
193
- * Validates one runtime-backed provider block.
194
- */
195
- function validateProviderRuntime(name, runtime) {
196
- if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
197
- throw new Error(`Provider "${name}" has an invalid runtime block.`);
198
- }
199
- const record = runtime;
200
- if (record.kind !== "copilot-http-proxy" && record.kind !== "copilot-sdk-bridge") {
201
- throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
202
- }
203
- if (record.upstream !== "github-copilot") {
204
- throw new Error(`Provider "${name}" has an invalid runtime upstream.`);
205
- }
206
- if (typeof record.bridgeHost !== "string" || record.bridgeHost.trim() === "") {
207
- throw new Error(`Provider "${name}" has an invalid runtime bridgeHost.`);
208
- }
209
- if (typeof record.bridgePort !== "number" || !Number.isInteger(record.bridgePort) || record.bridgePort <= 0) {
210
- throw new Error(`Provider "${name}" has an invalid runtime bridgePort.`);
211
- }
212
- if (record.bridgePath !== "/v1") {
213
- throw new Error(`Provider "${name}" has an invalid runtime bridgePath.`);
214
- }
215
- if (record.premiumRequests !== true) {
216
- throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
217
- }
218
- if (record.authSource !== "github-pat" && record.authSource !== "official-sdk") {
219
- throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
220
- }
221
- }