@bman654/clodex 2.7.0 → 2.8.0

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.
package/README.md CHANGED
@@ -30,7 +30,7 @@ clodex claude # 5. launch Claude Code on an OpenAI model
30
30
  ```
31
31
 
32
32
  1. **Install** — puts the `clodex` command on your PATH.
33
- 2. **Sign in** — opens a device-code OAuth flow for your ChatGPT/Codex plan; the token is stored in your OS credential store. (API-key users: `clodex providers add` instead.)
33
+ 2. **Sign in** — opens a device-code OAuth flow for your ChatGPT/Codex plan; the token is stored in your OS credential store. If your workspace admin has disabled device code authorization, add `--browser` to sign in through your browser instead. (API-key users: `clodex providers add` instead.)
34
34
  3. **Pick models** — an interactive manager for favorites (max 20) and short aliases like `sol` so you do not need to type the long names. Favorites drive the `/model` switch menu, proxy-mode routing, and the patcher.
35
35
  4. **Patch** *(optional but recommended for proxy mode)* — bakes your favorites and aliases into the Claude Code binary so they pass model validation, appear in `/model`, and report their real context windows. Re-run after each `claude` update; `clodex patch --restore` undoes it. This step is required if you want to use clodex-routed models as subagents via the Agent tool.
36
36
  5. **Launch** — starts Claude Code bridged to the model you choose.
@@ -273,7 +273,7 @@ Two things worth knowing about the numbers:
273
273
  | --- | --- |
274
274
  | *(none)* | Provider hub wizard |
275
275
  | `add` | Add OpenAI or OpenCode Go with an API key, or sign in with ChatGPT |
276
- | `auth openai` | Sign in with ChatGPT/Codex-plan OAuth (device code) |
276
+ | `auth openai` | Sign in with ChatGPT/Codex-plan OAuth (device code; `--browser` for workspaces that disable device codes) |
277
277
  | `list` | Show configured providers |
278
278
  | `remove <id>` | Remove a provider by id |
279
279
  | `refresh-models [id]` | Update cached model lists |
@@ -1708,6 +1708,7 @@ export {
1708
1708
  getInstalledClaudeVersion,
1709
1709
  tcpListenerUrlHost,
1710
1710
  waitForTcpListenerCandidate,
1711
+ waitForTcpListener,
1711
1712
  listenTcpServer,
1712
1713
  isDiscoveryDisabled,
1713
1714
  registerServerRuntimeState,
@@ -1722,4 +1723,4 @@ export {
1722
1723
  wrapperRequiresServer,
1723
1724
  computeWrapperEnv
1724
1725
  };
1725
- //# sourceMappingURL=chunk-WZXTVKFX.js.map
1726
+ //# sourceMappingURL=chunk-J7WXOD2K.js.map
@@ -7,7 +7,7 @@ import {
7
7
  readLiveServerRuntimeStates,
8
8
  waitForTcpListenerCandidate,
9
9
  wrapperRequiresServer
10
- } from "./chunk-WZXTVKFX.js";
10
+ } from "./chunk-J7WXOD2K.js";
11
11
 
12
12
  // src/claude-wrapper.ts
13
13
  import { spawn } from "child_process";
package/dist/cli.js CHANGED
@@ -47,11 +47,12 @@ import {
47
47
  storeActiveOAuthAccount,
48
48
  tcpListenerUrlHost,
49
49
  unregisterServerRuntimeState,
50
+ waitForTcpListener,
50
51
  withCredentialMutationLock,
51
52
  withProviderMutationLock,
52
53
  withRegistryWriteLock,
53
54
  withRegistryWriteLockSync
54
- } from "./chunk-WZXTVKFX.js";
55
+ } from "./chunk-J7WXOD2K.js";
55
56
 
56
57
  // src/cli.ts
57
58
  import pc13 from "picocolors";
@@ -204,6 +205,13 @@ function printOAuthStepsPanel(title, providerLabel2) {
204
205
  `${pc.white("3. Approve access for ")}${fmtProvider(providerLabel2)}`
205
206
  ]);
206
207
  }
208
+ function printOAuthBrowserPanel(title, providerLabel2) {
209
+ printPanel(pc.cyan(title), [
210
+ `${pc.white("1. Sign in on the page that opens in your browser")}`,
211
+ `${pc.white("2. Approve access for ")}${fmtProvider(providerLabel2)}`,
212
+ `${pc.white("3. Return to this terminal")}`
213
+ ]);
214
+ }
207
215
  function printNetworkWarningPanel() {
208
216
  printPanel(pc.yellow("Network mode"), [
209
217
  `${pc.yellow(pc.bold("Anyone on your network"))}${pc.white(" who knows the password can use this server through your account.")}`
@@ -374,7 +382,7 @@ import { join } from "path";
374
382
  // package.json
375
383
  var package_default = {
376
384
  name: "@bman654/clodex",
377
- version: "2.7.0",
385
+ version: "2.8.0",
378
386
  publishConfig: {
379
387
  access: "public"
380
388
  },
@@ -821,6 +829,24 @@ function supportsNativeOAuth(providerId) {
821
829
  }
822
830
 
823
831
  // src/oauth/pkce.ts
832
+ function generateRandomString(length) {
833
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
834
+ return Array.from(crypto.getRandomValues(new Uint8Array(length))).map((b) => chars[b % chars.length]).join("");
835
+ }
836
+ function base64UrlEncode(buffer) {
837
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
838
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
839
+ }
840
+ async function generatePkce() {
841
+ const verifier = generateRandomString(64);
842
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
843
+ return { verifier, challenge: base64UrlEncode(hash) };
844
+ }
845
+ function generateOAuthState() {
846
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
847
+ const binary = String.fromCharCode(...bytes);
848
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
849
+ }
824
850
  function positiveSecondsToMs(value, defaultMs) {
825
851
  const seconds = Number(value);
826
852
  return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : defaultMs;
@@ -871,11 +897,112 @@ async function postOAuthRefresh(url, body, options) {
871
897
  }
872
898
  }
873
899
 
900
+ // src/oauth/callback-server.ts
901
+ import http from "http";
902
+ var SUCCESS_HTML = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Authorized</title></head>
903
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0">
904
+ <div style="text-align:center;padding:2rem;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)">
905
+ <div style="color:#22c55e;font-size:2.5rem">&#10003;</div>
906
+ <h1 style="margin:.5rem 0">Authentication successful</h1>
907
+ <p style="color:#666">You can close this tab and return to the terminal.</p>
908
+ </div></body></html>`;
909
+ var FAILURE_HTML = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Sign-in failed</title></head>
910
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0">
911
+ <div style="text-align:center;padding:2rem;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)">
912
+ <div style="color:#ef4444;font-size:2.5rem">&#10007;</div>
913
+ <h1 style="margin:.5rem 0">Sign-in failed</h1>
914
+ <p style="color:#666">Return to the terminal for details.</p>
915
+ </div></body></html>`;
916
+ var LOOPBACK_PROBE_TIMEOUT_MS = 250;
917
+ async function isLoopbackPortTaken(port) {
918
+ const probes = await Promise.all(["127.0.0.1", "::1"].map(
919
+ (host) => waitForTcpListener(host, port, LOOPBACK_PROBE_TIMEOUT_MS).catch(() => false)
920
+ ));
921
+ return probes.some(Boolean);
922
+ }
923
+ async function startCallbackServer(options) {
924
+ let codeResolve;
925
+ let codeReject;
926
+ let buffered;
927
+ const { path, redirectHost } = options;
928
+ const server = http.createServer((req, res) => {
929
+ const u = new URL(req.url ?? "/", "http://localhost");
930
+ if (u.pathname !== path) {
931
+ res.writeHead(404);
932
+ res.end();
933
+ return;
934
+ }
935
+ const code = u.searchParams.get("code") ?? "";
936
+ const state = u.searchParams.get("state") ?? "";
937
+ const error = u.searchParams.get("error") ?? "";
938
+ if (options.expectedState !== void 0 && state !== options.expectedState) {
939
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
940
+ res.end("Invalid OAuth state");
941
+ return;
942
+ }
943
+ const failed = Boolean(error) || !code;
944
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
945
+ res.end(failed ? FAILURE_HTML : SUCCESS_HTML);
946
+ const params = { code, state, error: error || void 0 };
947
+ if (codeResolve) codeResolve(params);
948
+ else buffered ??= params;
949
+ });
950
+ const ports = options.ports?.length ? options.ports : [0];
951
+ let address;
952
+ let lastError;
953
+ for (const port of ports) {
954
+ if (port !== 0 && redirectHost === "localhost" && await isLoopbackPortTaken(port)) {
955
+ const busy = new Error(`listen EADDRINUSE: address already in use localhost:${port}`);
956
+ busy.code = "EADDRINUSE";
957
+ lastError = busy;
958
+ continue;
959
+ }
960
+ try {
961
+ address = await listenTcpServer(server, port, redirectHost);
962
+ break;
963
+ } catch (error) {
964
+ lastError = error;
965
+ }
966
+ }
967
+ if (!address) throw lastError ?? new Error("OAuth callback server could not bind");
968
+ return {
969
+ port: address.port,
970
+ redirectUri: `http://${redirectHost}:${address.port}${path}`,
971
+ waitForCallback(timeoutMs = 3e5) {
972
+ return new Promise((resolve3, reject) => {
973
+ if (buffered) {
974
+ resolve3(buffered);
975
+ buffered = void 0;
976
+ return;
977
+ }
978
+ const timer = setTimeout(
979
+ () => reject(new Error("OAuth timeout \u2014 browser closed without completing sign-in")),
980
+ timeoutMs
981
+ );
982
+ codeResolve = (params) => {
983
+ clearTimeout(timer);
984
+ resolve3(params);
985
+ };
986
+ codeReject = (err) => {
987
+ clearTimeout(timer);
988
+ reject(err);
989
+ };
990
+ });
991
+ },
992
+ close() {
993
+ server.close();
994
+ codeReject?.(new Error("Server closed"));
995
+ }
996
+ };
997
+ }
998
+
874
999
  // src/oauth/openai.ts
875
1000
  var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
876
1001
  var ISSUER = "https://auth.openai.com";
877
1002
  var OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
878
1003
  var DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1e3;
1004
+ var BROWSER_CALLBACK_PORTS = [1455, 1457];
1005
+ var BROWSER_CALLBACK_PATH = "/auth/callback";
879
1006
  function extractOpenAiAccountId(tokens) {
880
1007
  const token = tokens.id_token ?? tokens.access_token;
881
1008
  if (!token) return void 0;
@@ -963,6 +1090,74 @@ async function refreshOpenAiAccessToken(refreshToken) {
963
1090
  }
964
1091
  );
965
1092
  }
1093
+ function buildOpenAiAuthorizeUrl(redirectUri, challenge, state) {
1094
+ const qs = new URLSearchParams({
1095
+ response_type: "code",
1096
+ client_id: CLIENT_ID,
1097
+ redirect_uri: redirectUri,
1098
+ scope: "openid profile email offline_access",
1099
+ code_challenge: challenge,
1100
+ code_challenge_method: "S256",
1101
+ id_token_add_organizations: "true",
1102
+ codex_cli_simplified_flow: "true",
1103
+ state
1104
+ });
1105
+ return `${ISSUER}/oauth/authorize?${qs.toString()}`;
1106
+ }
1107
+ async function exchangeOpenAiAuthorizationCode(code, redirectUri, codeVerifier) {
1108
+ return postOAuthRefresh(
1109
+ `${ISSUER}/oauth/token`,
1110
+ new URLSearchParams({
1111
+ grant_type: "authorization_code",
1112
+ code,
1113
+ redirect_uri: redirectUri,
1114
+ client_id: CLIENT_ID,
1115
+ code_verifier: codeVerifier
1116
+ }),
1117
+ {
1118
+ contentType: "form",
1119
+ errorPrefix: "OpenAI token exchange failed",
1120
+ includeStatus: true
1121
+ }
1122
+ );
1123
+ }
1124
+ async function runOpenAiBrowserFlow(onAuthorizeUrl, opts) {
1125
+ const { verifier, challenge } = await generatePkce();
1126
+ const state = generateOAuthState();
1127
+ const ports = opts?.ports ?? BROWSER_CALLBACK_PORTS;
1128
+ let server;
1129
+ try {
1130
+ server = await startCallbackServer({
1131
+ ports,
1132
+ path: BROWSER_CALLBACK_PATH,
1133
+ redirectHost: "localhost",
1134
+ expectedState: state
1135
+ });
1136
+ } catch (error) {
1137
+ if (error.code === "EADDRINUSE") {
1138
+ throw new Error(
1139
+ `Ports ${ports.join(" and ")} are in use \u2014 close any other OpenAI sign-in (e.g. codex login) and try again.`
1140
+ );
1141
+ }
1142
+ throw new Error(
1143
+ `Could not start the OAuth callback listener: ${error instanceof Error ? error.message : String(error)}`,
1144
+ { cause: error }
1145
+ );
1146
+ }
1147
+ try {
1148
+ onAuthorizeUrl({ url: buildOpenAiAuthorizeUrl(server.redirectUri, challenge, state) });
1149
+ const params = await server.waitForCallback(opts?.timeoutMs);
1150
+ if (params.error) throw new Error(`OpenAI sign-in failed: ${params.error}`);
1151
+ if (!params.code) throw new Error("OpenAI sign-in returned no authorization code");
1152
+ if (params.state !== state) {
1153
+ throw new Error("OpenAI sign-in returned a mismatched state \u2014 try again");
1154
+ }
1155
+ const tokens = await exchangeOpenAiAuthorizationCode(params.code, server.redirectUri, verifier);
1156
+ return { tokens, accountId: extractOpenAiAccountId(tokens) };
1157
+ } finally {
1158
+ server.close();
1159
+ }
1160
+ }
966
1161
  async function runOpenAiDeviceCodeFlow(onDeviceCode, opts) {
967
1162
  const deviceData = await requestOpenAiDeviceCode();
968
1163
  onDeviceCode({ url: openAiDeviceCodeUrl(), userCode: deviceData.user_code });
@@ -14226,6 +14421,25 @@ async function runNativeDeviceCode(providerId) {
14226
14421
  throw err;
14227
14422
  }
14228
14423
  }
14424
+ async function runNativeBrowserSignIn(providerId) {
14425
+ const label = PROVIDER_DISPLAY[providerId];
14426
+ printOAuthBrowserPanel(`${label} \u2014 Sign in`, label);
14427
+ const spinner5 = p3.spinner();
14428
+ spinner5.start("Opening your browser...");
14429
+ try {
14430
+ const { tokens, accountId } = await runOpenAiBrowserFlow(({ url }) => {
14431
+ spinner5.stop("");
14432
+ p3.log.info(`If the browser did not open, visit: ${pc4.cyan(url)}`);
14433
+ openBrowser(url);
14434
+ spinner5.start("Waiting for sign-in in your browser...");
14435
+ });
14436
+ spinner5.stop(pc4.green("Signed in to OpenAI ChatGPT"));
14437
+ return tokensToStoredCredential(tokens, void 0, accountId);
14438
+ } catch (err) {
14439
+ spinner5.stop("");
14440
+ throw err;
14441
+ }
14442
+ }
14229
14443
  async function upsertOAuthAccountSlot(registryId, account, authRef, expectedAuthRef) {
14230
14444
  return withRegistryWriteLock(async () => {
14231
14445
  const registry = loadRegistryStrict();
@@ -14429,7 +14643,7 @@ async function authenticateProvider(providerId, options = {}) {
14429
14643
  `Credential store is unavailable${storeDiagMsg ? `: ${storeDiagMsg}` : ""}. Set CLODEX_CREDENTIAL_HELPER to an absolute path to an external credential helper and try again.`
14430
14644
  );
14431
14645
  }
14432
- const cred = await runNativeDeviceCode(providerId);
14646
+ const cred = options.method === "browser" ? await runNativeBrowserSignIn(providerId) : await runNativeDeviceCode(providerId);
14433
14647
  const persisted = await persistNativeOAuthCredential(providerId, cred, accountName);
14434
14648
  const refreshSpinner = p3.spinner();
14435
14649
  refreshSpinner.start("Refreshing model list...");
@@ -14468,11 +14682,17 @@ function providerAuthHelpText() {
14468
14682
 
14469
14683
  ${pc4.bold("Usage:")}
14470
14684
  clodex providers auth openai
14685
+ clodex providers auth openai --browser
14471
14686
  clodex providers auth openai --account work
14472
14687
 
14473
14688
  ${pc4.bold("Device code (works on SSH/VPS):")}
14474
14689
  openai ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
14475
14690
 
14691
+ ${pc4.bold("Browser sign-in:")}
14692
+ --browser sign in through your browser instead of a device code \u2014 use this
14693
+ when your workspace admin has disabled device code authorization.
14694
+ Needs a local browser, so it does not work over plain SSH.
14695
+
14476
14696
  ${pc4.bold("Named accounts:")}
14477
14697
  --account <name> store an additional ChatGPT account under a named slot
14478
14698
  (the default sign-in is untouched). Select one at launch:
@@ -14830,6 +15050,7 @@ function parseProvidersArgs(args) {
14830
15050
  for (let i = 0; i < rest.length; i++) {
14831
15051
  const arg = rest[i];
14832
15052
  if (arg === "--native") authMethod = "native";
15053
+ else if (arg === "--browser") authMethod = "browser";
14833
15054
  else if (arg === "--account") {
14834
15055
  const value = rest[i + 1];
14835
15056
  if (!value || value.startsWith("-")) {
@@ -14870,11 +15091,12 @@ ${pc6.bold("Usage:")}
14870
15091
  clodex providers remove <id>
14871
15092
  clodex providers refresh-models [id]
14872
15093
  clodex providers auth openai
15094
+ clodex providers auth openai --browser
14873
15095
 
14874
15096
  ${pc6.bold("Subcommands:")}
14875
15097
  (none) Provider hub wizard
14876
15098
  add Add a built-in provider or sign in with ChatGPT
14877
- auth Sign in with ChatGPT/Codex-plan OAuth (device code)
15099
+ auth Sign in with ChatGPT/Codex-plan OAuth (device code, or --browser)
14878
15100
  list Show configured providers
14879
15101
  remove Remove a provider by id
14880
15102
  refresh-models Update cached model lists`;
@@ -14999,6 +15221,21 @@ function shouldOfferAccountSwitch(provider) {
14999
15221
  function providerLabel(name, modelCount, enabled) {
15000
15222
  return `${fmtEnabledStar(enabled)} ${fmtProvider(name)} ${pc6.dim(`(${modelCount} model${modelCount === 1 ? "" : "s"})`)}`;
15001
15223
  }
15224
+ async function promptOAuthMethod() {
15225
+ const choice = await p5.select({
15226
+ message: "How do you want to sign in?",
15227
+ initialValue: "native",
15228
+ options: [
15229
+ { value: "native", label: "Device code", hint: "works everywhere, including SSH/VPS" },
15230
+ { value: "browser", label: "Browser", hint: "for workspaces that disable device code authorization" }
15231
+ ]
15232
+ });
15233
+ if (p5.isCancel(choice)) {
15234
+ p5.cancel("Cancelled.");
15235
+ return null;
15236
+ }
15237
+ return choice;
15238
+ }
15002
15239
  async function runProvidersAuthWithCleanupState(providerId, method, cleanupState, account) {
15003
15240
  try {
15004
15241
  const result = await authenticateProvider(providerId, { method, account });
@@ -15161,7 +15398,7 @@ async function runProvidersAddWithCleanupState(cleanupState) {
15161
15398
  options.push({
15162
15399
  value: "oauth",
15163
15400
  label: "Sign in with ChatGPT (Plus/Pro plan)",
15164
- hint: "OAuth device code \u2014 no API key needed"
15401
+ hint: "OAuth (device code or browser) \u2014 no API key needed"
15165
15402
  });
15166
15403
  }
15167
15404
  for (const template of listRegistryAddableTemplates(providers)) {
@@ -15184,7 +15421,9 @@ async function runProvidersAddWithCleanupState(cleanupState) {
15184
15421
  return 0;
15185
15422
  }
15186
15423
  if (choice === "oauth") {
15187
- return runProvidersAuthWithCleanupState("openai", void 0, cleanupState);
15424
+ const method = await promptOAuthMethod();
15425
+ if (method === null) return 0;
15426
+ return runProvidersAuthWithCleanupState("openai", method, cleanupState);
15188
15427
  }
15189
15428
  if (typeof choice === "string" && choice.startsWith("api:")) {
15190
15429
  return runTemplateAddFlow(choice.slice("api:".length), cleanupState);
@@ -15324,7 +15563,9 @@ async function runProviderDetail(id) {
15324
15563
  return "back";
15325
15564
  }
15326
15565
  if (action === "auth") {
15327
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, void 0, state));
15566
+ const method = await promptOAuthMethod();
15567
+ if (method === null) return "back";
15568
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, method, state));
15328
15569
  return "back";
15329
15570
  }
15330
15571
  if (action === "account") {
@@ -15419,7 +15660,7 @@ async function runProvidersHub() {
15419
15660
  }
15420
15661
  const configuredIds = new Set(entries.map((entry) => entry.id));
15421
15662
  if (listVisibleOAuthTemplates(configuredIds).length > 0) {
15422
- options.push({ value: "auth-menu", label: "\u2192 Sign in with ChatGPT (OAuth)", hint: "device code" });
15663
+ options.push({ value: "auth-menu", label: "\u2192 Sign in with ChatGPT (OAuth)", hint: "device code or browser" });
15423
15664
  } else if (configuredIds.has("openai-oauth")) {
15424
15665
  options.push({
15425
15666
  value: "auth-account",
@@ -15447,7 +15688,9 @@ async function runProvidersHub() {
15447
15688
  continue;
15448
15689
  }
15449
15690
  if (choice === "auth-menu") {
15450
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state));
15691
+ const method = await promptOAuthMethod();
15692
+ if (method === null) continue;
15693
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", method, state));
15451
15694
  continue;
15452
15695
  }
15453
15696
  if (choice === "auth-account") {
@@ -15464,7 +15707,9 @@ async function runProvidersHub() {
15464
15707
  }
15465
15708
  });
15466
15709
  if (p5.isCancel(name)) continue;
15467
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state, String(name)));
15710
+ const accountMethod = await promptOAuthMethod();
15711
+ if (accountMethod === null) continue;
15712
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", accountMethod, state, String(name)));
15468
15713
  continue;
15469
15714
  }
15470
15715
  if (typeof choice === "string" && choice.startsWith("provider:")) {
@@ -15520,7 +15765,7 @@ async function runFirstRunWizard(_trace = false) {
15520
15765
  {
15521
15766
  value: "oauth",
15522
15767
  label: pc7.cyan("Sign in with ChatGPT (Plus/Pro plan)"),
15523
- hint: "OAuth device code \u2014 uses your ChatGPT/Codex plan"
15768
+ hint: "OAuth (device code or browser) \u2014 uses your ChatGPT/Codex plan"
15524
15769
  },
15525
15770
  {
15526
15771
  value: "apikey",
@@ -15533,7 +15778,14 @@ async function runFirstRunWizard(_trace = false) {
15533
15778
  p6.cancel("Cancelled.");
15534
15779
  return "cancel";
15535
15780
  }
15536
- const code = choice === "oauth" ? await runProvidersAuth("openai") : await runProvidersAdd();
15781
+ let code;
15782
+ if (choice === "oauth") {
15783
+ const method = await promptOAuthMethod();
15784
+ if (method === null) return "cancel";
15785
+ code = await runProvidersAuth("openai", method);
15786
+ } else {
15787
+ code = await runProvidersAdd();
15788
+ }
15537
15789
  if (code !== 0) return "cancel";
15538
15790
  if (await needsFirstRunSetup()) return "cancel";
15539
15791
  p6.log.success("OpenAI provider ready \u2014 picking a model next.");
@@ -16660,7 +16912,7 @@ import pc10 from "picocolors";
16660
16912
  import * as p9 from "@clack/prompts";
16661
16913
 
16662
16914
  // src/http-proxy/server.ts
16663
- import * as http from "http";
16915
+ import * as http2 from "http";
16664
16916
  import * as https from "https";
16665
16917
  import * as net from "net";
16666
16918
  import { randomUUID as randomUUID6 } from "crypto";
@@ -17097,7 +17349,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
17097
17349
  upstream.end(rawBody);
17098
17350
  });
17099
17351
  }
17100
- function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.request, adapterAgent, lifecycle, isLocalShutdown = () => false) {
17352
+ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.request, adapterAgent, lifecycle, isLocalShutdown = () => false) {
17101
17353
  return new Promise((resolve3) => {
17102
17354
  const startedAt = Date.now();
17103
17355
  let lastActivityAt = startedAt;
@@ -17298,7 +17550,7 @@ function forwardPlainHttp(req, res) {
17298
17550
  res.end("HTTP proxy requests must use an absolute URL");
17299
17551
  return;
17300
17552
  }
17301
- const transport = target.protocol === "https:" ? https : http;
17553
+ const transport = target.protocol === "https:" ? https : http2;
17302
17554
  const upstream = transport.request({
17303
17555
  protocol: target.protocol,
17304
17556
  hostname: target.hostname,
@@ -17349,7 +17601,7 @@ async function startHttpProxy(options) {
17349
17601
  options.modelAliases
17350
17602
  );
17351
17603
  }
17352
- const adapterAgent = adapter ? new http.Agent({ keepAlive: true }) : void 0;
17604
+ const adapterAgent = adapter ? new http2.Agent({ keepAlive: true }) : void 0;
17353
17605
  let shuttingDown = false;
17354
17606
  const mitmServer = https.createServer({
17355
17607
  key: certificates.serverKey,
@@ -17505,7 +17757,7 @@ async function startHttpProxy(options) {
17505
17757
  );
17506
17758
  });
17507
17759
  const sockets = /* @__PURE__ */ new Set();
17508
- const proxyServer = http.createServer(forwardPlainHttp);
17760
+ const proxyServer = http2.createServer(forwardPlainHttp);
17509
17761
  proxyServer.on("connection", (socket) => {
17510
17762
  sockets.add(socket);
17511
17763
  socket.once("close", () => sockets.delete(socket));