@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 +2 -2
- package/dist/{chunk-WZXTVKFX.js → chunk-J7WXOD2K.js} +2 -1
- package/dist/claude-wrapper.js +1 -1
- package/dist/cli.js +269 -17
- package/dist/cli.js.map +1 -1
- package/docs/credential-helpers.md +50 -2
- package/package.json +1 -1
- /package/dist/{chunk-WZXTVKFX.js.map → chunk-J7WXOD2K.js.map} +0 -0
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-
|
|
1726
|
+
//# sourceMappingURL=chunk-J7WXOD2K.js.map
|
package/dist/claude-wrapper.js
CHANGED
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-
|
|
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.
|
|
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">✓</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">✗</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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 :
|
|
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
|
|
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 =
|
|
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));
|