@bman654/clodex 2.7.0 → 2.8.1

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/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.1",
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 });
@@ -3224,7 +3419,7 @@ function savedStopsAfter(current, assignments) {
3224
3419
  }
3225
3420
 
3226
3421
  // src/patch-transforms.ts
3227
- var PATCH_TRANSFORMS_VERSION = 8;
3422
+ var PATCH_TRANSFORMS_VERSION = 9;
3228
3423
  var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
3229
3424
  var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
3230
3425
  function projectNativeEffort(effort) {
@@ -3427,7 +3622,7 @@ function applyClodexPatches(source, config) {
3427
3622
  }).join("; ");
3428
3623
  applyOnce(
3429
3624
  "PATCH 4: Agent tool model description",
3430
- /(describe\(`Optional model override for this agent[^`]*?)(`\))/,
3625
+ /(describe\(`Optional model override for this agent(?:[^`\\]|\\.)*?)(`)/,
3431
3626
  (_m, body, close) => body.includes("Additional custom models") ? body + close : body + " Additional custom models: " + listing + "." + close,
3432
3627
  { required: false, noopIsSkip: true }
3433
3628
  );
@@ -3974,7 +4169,7 @@ function captureBuiltInPatchProofs(source, config, results) {
3974
4169
  );
3975
4170
  addPattern(
3976
4171
  "PATCH 4: Agent tool model description",
3977
- /describe\(`Optional model override for this agent[^`]*?`\)/
4172
+ /describe\(`Optional model override for this agent(?:[^`\\]|\\.)*?`/
3978
4173
  );
3979
4174
  const aliases = configuredAliases(config);
3980
4175
  if (protectsResult(results, "PATCH 6: alias resolver switch")) {
@@ -6605,6 +6800,9 @@ var BUN_OFFSETS_BYTES = 32;
6605
6800
  var TAIL_SCAN_BYTES = 16 * 1024 * 1024;
6606
6801
  var MODULE_STRUCT_BYTES_CURRENT = 52;
6607
6802
  var MODULE_STRUCT_BYTES_LEGACY = 36;
6803
+ var CONTENTS_FIELD_AT = 8;
6804
+ var LOADER_FROM_END = 3;
6805
+ var BUN_JAVASCRIPT_LOADER = 1;
6608
6806
  var MAX_MODULE_NAME_BYTES = 4096;
6609
6807
  var MIN_SHIMMABLE_NAME_BYTES = 7;
6610
6808
  function tweakccRecognizesModuleName(name) {
@@ -6659,6 +6857,8 @@ function parseBunModuleNamesAtUnchecked(fd, offsetsAt) {
6659
6857
  if (!modules) return null;
6660
6858
  const names = [];
6661
6859
  const nameOffsets = [];
6860
+ const contents = [];
6861
+ const loaders = [];
6662
6862
  for (let index = 0; index < moduleCount; index++) {
6663
6863
  const nameOffset = modules.readUInt32LE(index * structBytes);
6664
6864
  const nameLength = modules.readUInt32LE(index * structBytes + 4);
@@ -6670,8 +6870,23 @@ function parseBunModuleNamesAtUnchecked(fd, offsetsAt) {
6670
6870
  if (!/^[\x20-\x7e]+$/.test(name)) return null;
6671
6871
  names.push(name);
6672
6872
  nameOffsets.push(blobAt + nameOffset);
6873
+ contents.push({
6874
+ offset: modules.readUInt32LE(index * structBytes + CONTENTS_FIELD_AT),
6875
+ length: modules.readUInt32LE(index * structBytes + CONTENTS_FIELD_AT + 4)
6876
+ });
6877
+ loaders.push(modules.readUInt8(index * structBytes + structBytes - LOADER_FROM_END));
6673
6878
  }
6674
- return { names, entryPointId, offsets: nameOffsets };
6879
+ return {
6880
+ names,
6881
+ entryPointId,
6882
+ offsets: nameOffsets,
6883
+ contents,
6884
+ loaders,
6885
+ blobAt,
6886
+ modulesAt: blobAt + modulesOffset,
6887
+ structBytes,
6888
+ byteCount: Number(byteCount)
6889
+ };
6675
6890
  }
6676
6891
  function findStandIn(fd, fileSize, marker) {
6677
6892
  const needle = Buffer.from(marker);
@@ -6717,6 +6932,70 @@ function isMachO(fd) {
6717
6932
  const value = magic.readUInt32BE(0);
6718
6933
  return value === 4277009102 || value === 4277009103 || value === 3472551422 || value === 3489328638 || value === 3405691582 || value === 3405691583;
6719
6934
  }
6935
+ function readBunModuleTable(path) {
6936
+ const fd = openSync2(path, "r");
6937
+ try {
6938
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6939
+ if (!parsed) return null;
6940
+ const { names, loaders, contents, entryPointId, blobAt, modulesAt, structBytes, byteCount } = parsed;
6941
+ return { names, loaders, contents, entryPointId, blobAt, modulesAt, structBytes, byteCount };
6942
+ } finally {
6943
+ closeSync2(fd);
6944
+ }
6945
+ }
6946
+ function readBunJavaScriptModules(path) {
6947
+ const fd = openSync2(path, "r");
6948
+ try {
6949
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6950
+ if (!parsed) return null;
6951
+ const modules = [];
6952
+ for (let index = 0; index < parsed.names.length; index++) {
6953
+ if (parsed.loaders[index] !== BUN_JAVASCRIPT_LOADER) continue;
6954
+ const range = parsed.contents[index];
6955
+ if (range.offset < 0 || range.length < 0 || range.offset + range.length > parsed.byteCount) {
6956
+ return null;
6957
+ }
6958
+ const bytes = range.length === 0 ? Buffer.alloc(0) : readAt(fd, range.length, parsed.blobAt + range.offset);
6959
+ if (!bytes) return null;
6960
+ modules.push({
6961
+ index,
6962
+ name: parsed.names[index],
6963
+ source: bytes.toString("utf8"),
6964
+ byteLength: bytes.length
6965
+ });
6966
+ }
6967
+ return modules;
6968
+ } finally {
6969
+ closeSync2(fd);
6970
+ }
6971
+ }
6972
+ function repointBunModuleContents(path, edits) {
6973
+ if (edits.length === 0) return;
6974
+ const fd = openSync2(path, "r+");
6975
+ try {
6976
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6977
+ if (!parsed) throw new Error(`cannot read the Bun module table of ${path}`);
6978
+ for (const { index, range } of edits) {
6979
+ if (index < 0 || index >= parsed.names.length) {
6980
+ throw new Error(`module ${index} is outside the ${parsed.names.length}-module table of ${path}`);
6981
+ }
6982
+ if (range.offset < 0 || range.length < 0 || range.offset + range.length > parsed.byteCount) {
6983
+ throw new Error(
6984
+ `module ${index} would point at ${range.offset}+${range.length}, outside the ${parsed.byteCount}-byte blob of ${path}`
6985
+ );
6986
+ }
6987
+ const at = parsed.modulesAt + index * parsed.structBytes + CONTENTS_FIELD_AT;
6988
+ const pair = Buffer.alloc(8);
6989
+ pair.writeUInt32LE(range.offset, 0);
6990
+ pair.writeUInt32LE(range.length, 4);
6991
+ if (writeSync2(fd, pair, 0, pair.length, at) !== pair.length) {
6992
+ throw new Error(`short write repointing module ${index} of ${path}`);
6993
+ }
6994
+ }
6995
+ } finally {
6996
+ closeSync2(fd);
6997
+ }
6998
+ }
6720
6999
  function shimEntryModuleName(path) {
6721
7000
  const fd = openSync2(path, "r+");
6722
7001
  try {
@@ -6749,7 +7028,17 @@ function restoreEntryModuleName(path, shim, { resign }) {
6749
7028
  }
6750
7029
  writeNameBytes(fd, shim.original, offset);
6751
7030
  restoreEveryStandIn(fd, statSync4(path).size, shim);
6752
- machO = resign && isMachO(fd);
7031
+ machO = resign;
7032
+ } finally {
7033
+ closeSync2(fd);
7034
+ }
7035
+ if (machO) resignMachOBinary(path);
7036
+ }
7037
+ function resignMachOBinary(path) {
7038
+ const fd = openSync2(path, "r");
7039
+ let machO;
7040
+ try {
7041
+ machO = isMachO(fd);
6753
7042
  } finally {
6754
7043
  closeSync2(fd);
6755
7044
  }
@@ -6758,6 +7047,99 @@ function restoreEntryModuleName(path, shim, { resign }) {
6758
7047
  }
6759
7048
  }
6760
7049
 
7050
+ // src/bun-bundle.ts
7051
+ function writableModuleIndex(path) {
7052
+ const table = readBunModuleTable(path);
7053
+ if (!table) return null;
7054
+ const index = table.names.findIndex(tweakccRecognizesModuleName);
7055
+ return index < 0 ? null : index;
7056
+ }
7057
+ var BUNDLE_MODULE_SEPARATOR = '\n/*clodex:module-boundary`;{}"]*/\n';
7058
+ function readClaudeBundle(path) {
7059
+ const modules = readBunJavaScriptModules(path);
7060
+ if (!modules || modules.length === 0) return null;
7061
+ if (modules.some((module) => module.source.includes(BUNDLE_MODULE_SEPARATOR))) return null;
7062
+ if (modules.some((module) => !roundTripsAsUtf8(module.source, module.byteLength))) return null;
7063
+ return { modules, source: modules.map((module) => module.source).join(BUNDLE_MODULE_SEPARATOR) };
7064
+ }
7065
+ function roundTripsAsUtf8(source, byteLength) {
7066
+ return Buffer.byteLength(source, "utf8") === byteLength && !source.includes("\uFFFD");
7067
+ }
7068
+ function splitBundleSource(bundle, patched) {
7069
+ const parts = patched.split(BUNDLE_MODULE_SEPARATOR);
7070
+ if (parts.length !== bundle.modules.length) {
7071
+ throw new Error(
7072
+ `the patched bundle has ${parts.length} module boundaries, expected ${bundle.modules.length}`
7073
+ );
7074
+ }
7075
+ return parts;
7076
+ }
7077
+ function planBundleWrite(bundle, patchedSources, writableIndex) {
7078
+ if (patchedSources.length !== bundle.modules.length) {
7079
+ throw new Error(
7080
+ `expected ${bundle.modules.length} patched module sources, got ${patchedSources.length}`
7081
+ );
7082
+ }
7083
+ const writable = bundle.modules.findIndex((module) => module.index === writableIndex);
7084
+ if (writable < 0) {
7085
+ throw new Error(`module ${writableIndex} is not one of this binary's JavaScript modules`);
7086
+ }
7087
+ const changed = bundle.modules.map((module, at) => at).filter((at) => at !== writable && patchedSources[at] !== bundle.modules[at].source);
7088
+ if (changed.length === 0) return { content: patchedSources[writable], repoints: [] };
7089
+ const order = [writable, ...changed];
7090
+ const repoints = [];
7091
+ let content = "";
7092
+ let start = 0;
7093
+ for (const at of order) {
7094
+ const source = patchedSources[at];
7095
+ const length = Buffer.byteLength(source, "utf8");
7096
+ repoints.push({ index: bundle.modules[at].index, start, length, expected: source });
7097
+ content += source + "\0";
7098
+ start += length + 1;
7099
+ }
7100
+ return { content, repoints };
7101
+ }
7102
+ function applyBundleWritePlan(path, plan) {
7103
+ const table = readBunModuleTable(path);
7104
+ if (!table) {
7105
+ if (plan.repoints.length === 0) return;
7106
+ throw new Error(`cannot read the Bun module table of ${path} after repacking`);
7107
+ }
7108
+ const recognized = table.names.filter(tweakccRecognizesModuleName);
7109
+ if (recognized.length !== 1) {
7110
+ throw new Error(
7111
+ `${path} has ${recognized.length} modules tweakcc would write to; the patch was planned around exactly one`
7112
+ );
7113
+ }
7114
+ if (plan.repoints.length === 0) return;
7115
+ const writable = table.names.findIndex(tweakccRecognizesModuleName);
7116
+ if (writable !== plan.repoints[0].index) {
7117
+ throw new Error(
7118
+ `${path} exposes module ${writable} to tweakcc, but the patch was planned around module ${plan.repoints[0].index}`
7119
+ );
7120
+ }
7121
+ const written = table.contents[writable];
7122
+ const expectedBytes = Buffer.byteLength(plan.content, "utf8");
7123
+ if (written.length !== expectedBytes) {
7124
+ throw new Error(
7125
+ `${path} holds ${written.length} bytes where the ${expectedBytes} bytes clodex wrote should be; refusing to repoint its modules`
7126
+ );
7127
+ }
7128
+ const edits = plan.repoints.map((repoint) => ({
7129
+ index: repoint.index,
7130
+ range: { offset: written.offset + repoint.start, length: repoint.length }
7131
+ }));
7132
+ repointBunModuleContents(path, edits);
7133
+ const after = readBunJavaScriptModules(path);
7134
+ if (!after) throw new Error(`cannot re-read the modules of ${path} after repointing them`);
7135
+ const byIndex = new Map(after.map((module) => [module.index, module.source]));
7136
+ for (const repoint of plan.repoints) {
7137
+ if (byIndex.get(repoint.index) !== repoint.expected) {
7138
+ throw new Error(`module ${repoint.index} of ${path} did not read back as the patched source`);
7139
+ }
7140
+ }
7141
+ }
7142
+
6761
7143
  // src/patch-backup.ts
6762
7144
  import { createHash as createHash5 } from "crypto";
6763
7145
  import { existsSync as existsSync4, readFileSync as readFileSync7, readdirSync, statSync as statSync5 } from "fs";
@@ -12109,9 +12491,15 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
12109
12491
  copyFileSync(from, candidatePath);
12110
12492
  const shim = shimEntryModuleName(candidatePath);
12111
12493
  const installation = await tryDetectInstallation({ path: candidatePath });
12112
- const source = await readContent(installation);
12494
+ const bundle = readClaudeBundle(candidatePath);
12495
+ if (!bundle && installation.kind === "native") {
12496
+ p2.log.warn(
12497
+ `Could not read ${candidatePath} as a Bun module table, so only the module tweakcc names is being patched. On Claude Code 2.1.242 and later that module is a stub holding none of the code clodex patches, and the patch below will fail at its first required site \u2014 the cause is this read, not a changed anchor.`
12498
+ );
12499
+ }
12500
+ const source = bundle ? bundle.source : await readContent(installation);
12113
12501
  if (shim) restoreEntryModuleName(candidatePath, shim, { resign: false });
12114
- return { installation, source };
12502
+ return { installation, source, bundle };
12115
12503
  };
12116
12504
  const facts = collectPristineFacts({
12117
12505
  version,
@@ -12204,8 +12592,25 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
12204
12592
  }
12205
12593
  results = [...results, ...local.results];
12206
12594
  const writeShim = shimEntryModuleName(candidatePath);
12207
- await writeContent(loaded.installation, local.content);
12595
+ let repointed = false;
12596
+ if (loaded.bundle) {
12597
+ const writable = writableModuleIndex(candidatePath);
12598
+ if (writable === null) {
12599
+ throw new Error("no module of the patch candidate carries a name tweakcc can write to");
12600
+ }
12601
+ const plan2 = planBundleWrite(
12602
+ loaded.bundle,
12603
+ splitBundleSource(loaded.bundle, local.content),
12604
+ writable
12605
+ );
12606
+ await writeContent(loaded.installation, plan2.content);
12607
+ applyBundleWritePlan(candidatePath, plan2);
12608
+ repointed = plan2.repoints.length > 0;
12609
+ } else {
12610
+ await writeContent(loaded.installation, local.content);
12611
+ }
12208
12612
  if (writeShim) restoreEntryModuleName(candidatePath, writeShim, { resign: true });
12613
+ else if (repointed) resignMachOBinary(candidatePath);
12209
12614
  patchedSize = statSync6(candidatePath).size;
12210
12615
  patchedSha256 = sha256File(candidatePath);
12211
12616
  renameSync2(candidatePath, binaryPath);
@@ -14226,6 +14631,25 @@ async function runNativeDeviceCode(providerId) {
14226
14631
  throw err;
14227
14632
  }
14228
14633
  }
14634
+ async function runNativeBrowserSignIn(providerId) {
14635
+ const label = PROVIDER_DISPLAY[providerId];
14636
+ printOAuthBrowserPanel(`${label} \u2014 Sign in`, label);
14637
+ const spinner5 = p3.spinner();
14638
+ spinner5.start("Opening your browser...");
14639
+ try {
14640
+ const { tokens, accountId } = await runOpenAiBrowserFlow(({ url }) => {
14641
+ spinner5.stop("");
14642
+ p3.log.info(`If the browser did not open, visit: ${pc4.cyan(url)}`);
14643
+ openBrowser(url);
14644
+ spinner5.start("Waiting for sign-in in your browser...");
14645
+ });
14646
+ spinner5.stop(pc4.green("Signed in to OpenAI ChatGPT"));
14647
+ return tokensToStoredCredential(tokens, void 0, accountId);
14648
+ } catch (err) {
14649
+ spinner5.stop("");
14650
+ throw err;
14651
+ }
14652
+ }
14229
14653
  async function upsertOAuthAccountSlot(registryId, account, authRef, expectedAuthRef) {
14230
14654
  return withRegistryWriteLock(async () => {
14231
14655
  const registry = loadRegistryStrict();
@@ -14429,7 +14853,7 @@ async function authenticateProvider(providerId, options = {}) {
14429
14853
  `Credential store is unavailable${storeDiagMsg ? `: ${storeDiagMsg}` : ""}. Set CLODEX_CREDENTIAL_HELPER to an absolute path to an external credential helper and try again.`
14430
14854
  );
14431
14855
  }
14432
- const cred = await runNativeDeviceCode(providerId);
14856
+ const cred = options.method === "browser" ? await runNativeBrowserSignIn(providerId) : await runNativeDeviceCode(providerId);
14433
14857
  const persisted = await persistNativeOAuthCredential(providerId, cred, accountName);
14434
14858
  const refreshSpinner = p3.spinner();
14435
14859
  refreshSpinner.start("Refreshing model list...");
@@ -14468,11 +14892,17 @@ function providerAuthHelpText() {
14468
14892
 
14469
14893
  ${pc4.bold("Usage:")}
14470
14894
  clodex providers auth openai
14895
+ clodex providers auth openai --browser
14471
14896
  clodex providers auth openai --account work
14472
14897
 
14473
14898
  ${pc4.bold("Device code (works on SSH/VPS):")}
14474
14899
  openai ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
14475
14900
 
14901
+ ${pc4.bold("Browser sign-in:")}
14902
+ --browser sign in through your browser instead of a device code \u2014 use this
14903
+ when your workspace admin has disabled device code authorization.
14904
+ Needs a local browser, so it does not work over plain SSH.
14905
+
14476
14906
  ${pc4.bold("Named accounts:")}
14477
14907
  --account <name> store an additional ChatGPT account under a named slot
14478
14908
  (the default sign-in is untouched). Select one at launch:
@@ -14830,6 +15260,7 @@ function parseProvidersArgs(args) {
14830
15260
  for (let i = 0; i < rest.length; i++) {
14831
15261
  const arg = rest[i];
14832
15262
  if (arg === "--native") authMethod = "native";
15263
+ else if (arg === "--browser") authMethod = "browser";
14833
15264
  else if (arg === "--account") {
14834
15265
  const value = rest[i + 1];
14835
15266
  if (!value || value.startsWith("-")) {
@@ -14870,11 +15301,12 @@ ${pc6.bold("Usage:")}
14870
15301
  clodex providers remove <id>
14871
15302
  clodex providers refresh-models [id]
14872
15303
  clodex providers auth openai
15304
+ clodex providers auth openai --browser
14873
15305
 
14874
15306
  ${pc6.bold("Subcommands:")}
14875
15307
  (none) Provider hub wizard
14876
15308
  add Add a built-in provider or sign in with ChatGPT
14877
- auth Sign in with ChatGPT/Codex-plan OAuth (device code)
15309
+ auth Sign in with ChatGPT/Codex-plan OAuth (device code, or --browser)
14878
15310
  list Show configured providers
14879
15311
  remove Remove a provider by id
14880
15312
  refresh-models Update cached model lists`;
@@ -14999,6 +15431,21 @@ function shouldOfferAccountSwitch(provider) {
14999
15431
  function providerLabel(name, modelCount, enabled) {
15000
15432
  return `${fmtEnabledStar(enabled)} ${fmtProvider(name)} ${pc6.dim(`(${modelCount} model${modelCount === 1 ? "" : "s"})`)}`;
15001
15433
  }
15434
+ async function promptOAuthMethod() {
15435
+ const choice = await p5.select({
15436
+ message: "How do you want to sign in?",
15437
+ initialValue: "native",
15438
+ options: [
15439
+ { value: "native", label: "Device code", hint: "works everywhere, including SSH/VPS" },
15440
+ { value: "browser", label: "Browser", hint: "for workspaces that disable device code authorization" }
15441
+ ]
15442
+ });
15443
+ if (p5.isCancel(choice)) {
15444
+ p5.cancel("Cancelled.");
15445
+ return null;
15446
+ }
15447
+ return choice;
15448
+ }
15002
15449
  async function runProvidersAuthWithCleanupState(providerId, method, cleanupState, account) {
15003
15450
  try {
15004
15451
  const result = await authenticateProvider(providerId, { method, account });
@@ -15161,7 +15608,7 @@ async function runProvidersAddWithCleanupState(cleanupState) {
15161
15608
  options.push({
15162
15609
  value: "oauth",
15163
15610
  label: "Sign in with ChatGPT (Plus/Pro plan)",
15164
- hint: "OAuth device code \u2014 no API key needed"
15611
+ hint: "OAuth (device code or browser) \u2014 no API key needed"
15165
15612
  });
15166
15613
  }
15167
15614
  for (const template of listRegistryAddableTemplates(providers)) {
@@ -15184,7 +15631,9 @@ async function runProvidersAddWithCleanupState(cleanupState) {
15184
15631
  return 0;
15185
15632
  }
15186
15633
  if (choice === "oauth") {
15187
- return runProvidersAuthWithCleanupState("openai", void 0, cleanupState);
15634
+ const method = await promptOAuthMethod();
15635
+ if (method === null) return 0;
15636
+ return runProvidersAuthWithCleanupState("openai", method, cleanupState);
15188
15637
  }
15189
15638
  if (typeof choice === "string" && choice.startsWith("api:")) {
15190
15639
  return runTemplateAddFlow(choice.slice("api:".length), cleanupState);
@@ -15324,7 +15773,9 @@ async function runProviderDetail(id) {
15324
15773
  return "back";
15325
15774
  }
15326
15775
  if (action === "auth") {
15327
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, void 0, state));
15776
+ const method = await promptOAuthMethod();
15777
+ if (method === null) return "back";
15778
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, method, state));
15328
15779
  return "back";
15329
15780
  }
15330
15781
  if (action === "account") {
@@ -15419,7 +15870,7 @@ async function runProvidersHub() {
15419
15870
  }
15420
15871
  const configuredIds = new Set(entries.map((entry) => entry.id));
15421
15872
  if (listVisibleOAuthTemplates(configuredIds).length > 0) {
15422
- options.push({ value: "auth-menu", label: "\u2192 Sign in with ChatGPT (OAuth)", hint: "device code" });
15873
+ options.push({ value: "auth-menu", label: "\u2192 Sign in with ChatGPT (OAuth)", hint: "device code or browser" });
15423
15874
  } else if (configuredIds.has("openai-oauth")) {
15424
15875
  options.push({
15425
15876
  value: "auth-account",
@@ -15447,7 +15898,9 @@ async function runProvidersHub() {
15447
15898
  continue;
15448
15899
  }
15449
15900
  if (choice === "auth-menu") {
15450
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state));
15901
+ const method = await promptOAuthMethod();
15902
+ if (method === null) continue;
15903
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", method, state));
15451
15904
  continue;
15452
15905
  }
15453
15906
  if (choice === "auth-account") {
@@ -15464,7 +15917,9 @@ async function runProvidersHub() {
15464
15917
  }
15465
15918
  });
15466
15919
  if (p5.isCancel(name)) continue;
15467
- await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state, String(name)));
15920
+ const accountMethod = await promptOAuthMethod();
15921
+ if (accountMethod === null) continue;
15922
+ await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", accountMethod, state, String(name)));
15468
15923
  continue;
15469
15924
  }
15470
15925
  if (typeof choice === "string" && choice.startsWith("provider:")) {
@@ -15520,7 +15975,7 @@ async function runFirstRunWizard(_trace = false) {
15520
15975
  {
15521
15976
  value: "oauth",
15522
15977
  label: pc7.cyan("Sign in with ChatGPT (Plus/Pro plan)"),
15523
- hint: "OAuth device code \u2014 uses your ChatGPT/Codex plan"
15978
+ hint: "OAuth (device code or browser) \u2014 uses your ChatGPT/Codex plan"
15524
15979
  },
15525
15980
  {
15526
15981
  value: "apikey",
@@ -15533,7 +15988,14 @@ async function runFirstRunWizard(_trace = false) {
15533
15988
  p6.cancel("Cancelled.");
15534
15989
  return "cancel";
15535
15990
  }
15536
- const code = choice === "oauth" ? await runProvidersAuth("openai") : await runProvidersAdd();
15991
+ let code;
15992
+ if (choice === "oauth") {
15993
+ const method = await promptOAuthMethod();
15994
+ if (method === null) return "cancel";
15995
+ code = await runProvidersAuth("openai", method);
15996
+ } else {
15997
+ code = await runProvidersAdd();
15998
+ }
15537
15999
  if (code !== 0) return "cancel";
15538
16000
  if (await needsFirstRunSetup()) return "cancel";
15539
16001
  p6.log.success("OpenAI provider ready \u2014 picking a model next.");
@@ -16660,7 +17122,7 @@ import pc10 from "picocolors";
16660
17122
  import * as p9 from "@clack/prompts";
16661
17123
 
16662
17124
  // src/http-proxy/server.ts
16663
- import * as http from "http";
17125
+ import * as http2 from "http";
16664
17126
  import * as https from "https";
16665
17127
  import * as net from "net";
16666
17128
  import { randomUUID as randomUUID6 } from "crypto";
@@ -17097,7 +17559,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
17097
17559
  upstream.end(rawBody);
17098
17560
  });
17099
17561
  }
17100
- function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.request, adapterAgent, lifecycle, isLocalShutdown = () => false) {
17562
+ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.request, adapterAgent, lifecycle, isLocalShutdown = () => false) {
17101
17563
  return new Promise((resolve3) => {
17102
17564
  const startedAt = Date.now();
17103
17565
  let lastActivityAt = startedAt;
@@ -17298,7 +17760,7 @@ function forwardPlainHttp(req, res) {
17298
17760
  res.end("HTTP proxy requests must use an absolute URL");
17299
17761
  return;
17300
17762
  }
17301
- const transport = target.protocol === "https:" ? https : http;
17763
+ const transport = target.protocol === "https:" ? https : http2;
17302
17764
  const upstream = transport.request({
17303
17765
  protocol: target.protocol,
17304
17766
  hostname: target.hostname,
@@ -17349,7 +17811,7 @@ async function startHttpProxy(options) {
17349
17811
  options.modelAliases
17350
17812
  );
17351
17813
  }
17352
- const adapterAgent = adapter ? new http.Agent({ keepAlive: true }) : void 0;
17814
+ const adapterAgent = adapter ? new http2.Agent({ keepAlive: true }) : void 0;
17353
17815
  let shuttingDown = false;
17354
17816
  const mitmServer = https.createServer({
17355
17817
  key: certificates.serverKey,
@@ -17505,7 +17967,7 @@ async function startHttpProxy(options) {
17505
17967
  );
17506
17968
  });
17507
17969
  const sockets = /* @__PURE__ */ new Set();
17508
- const proxyServer = http.createServer(forwardPlainHttp);
17970
+ const proxyServer = http2.createServer(forwardPlainHttp);
17509
17971
  proxyServer.on("connection", (socket) => {
17510
17972
  sockets.add(socket);
17511
17973
  socket.once("close", () => sockets.delete(socket));