@bman654/clodex 2.17.0 → 2.18.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/dist/cli.js CHANGED
@@ -74,7 +74,7 @@ import {
74
74
  withRegistryWriteLockSync,
75
75
  writeParentNoticeLines,
76
76
  writeParentNoticeLinesSync
77
- } from "./chunk-5TG736TX.js";
77
+ } from "./chunk-YGEPQOPA.js";
78
78
 
79
79
  // src/cli.ts
80
80
  import pc13 from "picocolors";
@@ -249,7 +249,7 @@ function printFavoritesOnlyPanel() {
249
249
 
250
250
  // src/cli.ts
251
251
  import * as p13 from "@clack/prompts";
252
- import { realpathSync as realpathSync3 } from "fs";
252
+ import { realpathSync as realpathSync4 } from "fs";
253
253
  import { fileURLToPath as fileURLToPath2 } from "url";
254
254
 
255
255
  // src/launch.ts
@@ -346,7 +346,7 @@ import { join } from "path";
346
346
  // package.json
347
347
  var package_default = {
348
348
  name: "@bman654/clodex",
349
- version: "2.17.0",
349
+ version: "2.18.0",
350
350
  publishConfig: {
351
351
  access: "public"
352
352
  },
@@ -3186,7 +3186,7 @@ function pricingBoundaryWarning(modelLabel, limits, resolved) {
3186
3186
  const boundary = positiveInteger(limits.pricingBoundary);
3187
3187
  if (boundary === void 0 || !resolved.crossesPricingBoundary) return null;
3188
3188
  const note = limits.pricingBoundaryNote?.trim();
3189
- return `${modelLabel}: a ${withThousands(resolved.effective)}-token window can grow past the ${withThousands(boundary)}-token pricing boundary.` + (note ? ` ${note}` : "") + " Use the standard stop to stay under it.";
3189
+ return `${modelLabel}: a ${withThousands(resolved.effective)}-token window can grow past the ${withThousands(boundary)}-token pricing boundary.` + (note ? ` ${note}` : "") + " Choose a smaller context stop to reduce the chance of higher-rate requests.";
3190
3190
  }
3191
3191
  function contextClampNotice(modelLabel, resolved) {
3192
3192
  if (resolved.clampedFrom === void 0) return null;
@@ -3867,7 +3867,7 @@ import {
3867
3867
  existsSync as existsSync4,
3868
3868
  mkdtempSync,
3869
3869
  mkdirSync as mkdirSync5,
3870
- readFileSync as readFileSync9,
3870
+ readFileSync as readFileSync10,
3871
3871
  renameSync as renameSync2,
3872
3872
  rmSync,
3873
3873
  statSync as statSync7,
@@ -3875,10 +3875,10 @@ import {
3875
3875
  writeFileSync as writeFileSync5,
3876
3876
  openSync as openSync8,
3877
3877
  closeSync as closeSync8,
3878
- realpathSync as realpathSync2
3878
+ realpathSync as realpathSync3
3879
3879
  } from "fs";
3880
- import { homedir as homedir2 } from "os";
3881
- import { basename, dirname as dirname5, join as join7 } from "path";
3880
+ import { homedir as homedir3 } from "os";
3881
+ import { basename, dirname as dirname5, join as join8 } from "path";
3882
3882
  import pc3 from "picocolors";
3883
3883
  import * as p2 from "@clack/prompts";
3884
3884
 
@@ -5979,6 +5979,69 @@ function deriveBrand(family) {
5979
5979
  return "Other";
5980
5980
  }
5981
5981
 
5982
+ // src/registry/server-text.ts
5983
+ function isControlCode(code) {
5984
+ return code <= 31 || code >= 127 && code <= 159;
5985
+ }
5986
+ function hasControlChars(value) {
5987
+ for (let i = 0; i < value.length; i++) {
5988
+ if (isControlCode(value.charCodeAt(i))) return true;
5989
+ }
5990
+ return false;
5991
+ }
5992
+ function printableServerText(value) {
5993
+ let out = "";
5994
+ for (const ch of value) out += isControlCode(ch.charCodeAt(0)) ? " " : ch;
5995
+ return out.trim();
5996
+ }
5997
+
5998
+ // src/codex-client-version.ts
5999
+ var VERSION2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$(?![\s\S])/;
6000
+ function readCodexClientVersion(value) {
6001
+ return typeof value === "string" && VERSION2.test(value) ? value : void 0;
6002
+ }
6003
+ function compareCodexClientVersions(a, b) {
6004
+ const left = VERSION2.exec(a);
6005
+ const right = VERSION2.exec(b);
6006
+ if (!left || !right) return void 0;
6007
+ for (let i = 1; i <= 3; i++) {
6008
+ const x = BigInt(left[i]);
6009
+ const y = BigInt(right[i]);
6010
+ if (x !== y) return x < y ? -1 : 1;
6011
+ }
6012
+ const preA = left[4];
6013
+ const preB = right[4];
6014
+ if (preA === preB) return 0;
6015
+ if (preA === void 0) return 1;
6016
+ if (preB === void 0) return -1;
6017
+ const partsA = preA.split(".");
6018
+ const partsB = preB.split(".");
6019
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
6020
+ const x = partsA[i];
6021
+ const y = partsB[i];
6022
+ if (x === void 0) return -1;
6023
+ if (y === void 0) return 1;
6024
+ if (x === y) continue;
6025
+ const numericX = /^\d+$/.test(x);
6026
+ const numericY = /^\d+$/.test(y);
6027
+ if (numericX && numericY) return BigInt(x) < BigInt(y) ? -1 : 1;
6028
+ if (numericX !== numericY) return numericX ? -1 : 1;
6029
+ return x < y ? -1 : 1;
6030
+ }
6031
+ return 0;
6032
+ }
6033
+ function requiresNewerCodexClient(model) {
6034
+ if (!model.useResponsesLite) return false;
6035
+ const minimum = readCodexClientVersion(model.minimalClientVersion);
6036
+ return minimum !== void 0 && compareCodexClientVersions(minimum, CODEX_RESPONSES_LITE_VERSION) === 1;
6037
+ }
6038
+ function codexClientVersionWarning(models) {
6039
+ const unavailable = models.filter(requiresNewerCodexClient);
6040
+ if (unavailable.length === 0) return void 0;
6041
+ const details = unavailable.map((model) => `${printableServerText(model.id)} (requires ${model.minimalClientVersion})`).join(", ");
6042
+ return `Hidden ChatGPT-plan models: ${details}. clodex sends Codex client version ${CODEX_RESPONSES_LITE_VERSION} for Responses-Lite; their catalog minimum exceeds this version. Update clodex to a release supporting their required version to use them.`;
6043
+ }
6044
+
5982
6045
  // src/data/openai-oauth-models.ts
5983
6046
  var GPT_HIGH_CONTEXT_PRICING_BOUNDARY = 272e3;
5984
6047
  var GPT_HIGH_CONTEXT_PRICING_NOTE = "Above it, OpenAI prices the full request at 2x input and 1.5x output.";
@@ -5993,17 +6056,17 @@ var OPENAI_OAUTH_MODEL_SEEDS = [
5993
6056
  // context window, but the Codex client is served a smaller one, and this path is
5994
6057
  // Codex-only. All three cards publish a 128,000 output limit and the same 272K
5995
6058
  // pricing boundary as the GPT-5.6 family.
5996
- { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5997
- { id: "gpt-6-sol", name: "GPT-6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5998
- { id: "gpt-6-luna", name: "GPT-6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6059
+ { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.153.0" },
6060
+ { id: "gpt-6-sol", name: "GPT-6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.155.0" },
6061
+ { id: "gpt-6-luna", name: "GPT-6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.155.0" },
5999
6062
  // "An alias for our flagship general-purpose models, with safeguards calibrated
6000
6063
  // for defensive cybersecurity work" — access is gated on a separate opt-in
6001
6064
  // program, so most installs will never see this id in their catalog.
6002
- { id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6065
+ { id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.144.0" },
6003
6066
  // GPT-5.6 family (Sol / Terra / Luna)
6004
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
6005
- { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
6006
- { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6067
+ { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, minimalClientVersion: "0.144.0" },
6068
+ { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, minimalClientVersion: "0.144.0" },
6069
+ { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.144.0" },
6007
6070
  // GPT-5.5 family (Pro)
6008
6071
  { id: "gpt-5.5", name: "GPT-5.5", contextWindow: 272e3, maxOutputTokens: 128e3, reasoning: true },
6009
6072
  // GPT-5.4 family
@@ -6045,12 +6108,16 @@ function applyOAuthSeedContextMetadata(models) {
6045
6108
  const pricing = openAiPricingMetadata(model.id);
6046
6109
  return {
6047
6110
  ...model,
6111
+ contextWindow: model.contextWindow ?? seed?.contextWindow,
6048
6112
  maxContextWindow: model.maxContextWindow ?? seed?.maxContextWindow,
6049
6113
  effectiveContextPercent: migratedEffectiveContextPercent(model.effectiveContextPercent),
6050
6114
  pricingBoundary: model.pricingBoundary ?? seed?.pricingBoundary ?? pricing.pricingBoundary,
6051
6115
  pricingBoundaryNote: model.pricingBoundaryNote ?? seed?.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
6052
6116
  maxOutputTokens: model.maxOutputTokens ?? seed?.maxOutputTokens,
6053
- reasoning: seed?.reasoning ?? model.reasoning
6117
+ reasoning: seed?.reasoning ?? model.reasoning,
6118
+ useResponsesLite: model.useResponsesLite ?? seed?.useResponsesLite,
6119
+ preferWebSockets: model.preferWebSockets ?? seed?.preferWebSockets,
6120
+ minimalClientVersion: model.minimalClientVersion ?? seed?.minimalClientVersion
6054
6121
  };
6055
6122
  });
6056
6123
  }
@@ -6074,7 +6141,8 @@ function buildOpenAiOAuthModels() {
6074
6141
  npm: "@ai-sdk/openai",
6075
6142
  reasoning: seed.reasoning,
6076
6143
  useResponsesLite: seed.useResponsesLite,
6077
- preferWebSockets: seed.preferWebSockets
6144
+ preferWebSockets: seed.preferWebSockets,
6145
+ minimalClientVersion: seed.minimalClientVersion
6078
6146
  };
6079
6147
  });
6080
6148
  }
@@ -6104,22 +6172,6 @@ function normalizeGoogleDisplayName(rawName, bareId) {
6104
6172
  return stripGoogleModelPrefix(trimmed);
6105
6173
  }
6106
6174
 
6107
- // src/registry/server-text.ts
6108
- function isControlCode(code) {
6109
- return code <= 31 || code >= 127 && code <= 159;
6110
- }
6111
- function hasControlChars(value) {
6112
- for (let i = 0; i < value.length; i++) {
6113
- if (isControlCode(value.charCodeAt(i))) return true;
6114
- }
6115
- return false;
6116
- }
6117
- function printableServerText(value) {
6118
- let out = "";
6119
- for (const ch of value) out += isControlCode(ch.charCodeAt(0)) ? " " : ch;
6120
- return out.trim();
6121
- }
6122
-
6123
6175
  // src/trace-log.ts
6124
6176
  import {
6125
6177
  chmodSync as chmodSync3,
@@ -6590,6 +6642,7 @@ function parseModelList(body, npm, allowBareArray) {
6590
6642
  family,
6591
6643
  brand: deriveBrand(family),
6592
6644
  contextWindow,
6645
+ ...npm === "@ai-sdk/openai" ? openAiPricingMetadata(id) : {},
6593
6646
  cost,
6594
6647
  isFree: isFreeStatus(freeStatus),
6595
6648
  freeStatus,
@@ -6607,6 +6660,7 @@ function materializeTemplateModel(template, model, baseUrl) {
6607
6660
  const { id, upstreamModelId: normalizedUpstream } = normalizeGoogleModelId(model.id, npm);
6608
6661
  const family = model.family ?? (id.split(/[-/:]/)[0] ?? id);
6609
6662
  const freeStatus = model.freeStatus ?? classifyFreeStatus({ model });
6663
+ const pricing = npm === "@ai-sdk/openai" ? openAiPricingMetadata(model.upstreamModelId ?? id) : {};
6610
6664
  return {
6611
6665
  ...model,
6612
6666
  id,
@@ -6615,6 +6669,8 @@ function materializeTemplateModel(template, model, baseUrl) {
6615
6669
  family,
6616
6670
  brand: model.brand ?? deriveBrand(family),
6617
6671
  contextWindow: model.contextWindow ?? lookupKnownContextWindow(id),
6672
+ pricingBoundary: model.pricingBoundary ?? pricing.pricingBoundary,
6673
+ pricingBoundaryNote: model.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
6618
6674
  isFree: model.isFree ?? isFreeStatus(freeStatus),
6619
6675
  freeStatus,
6620
6676
  modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
@@ -6807,8 +6863,21 @@ function resolveEndpoint(npm, apiUrl) {
6807
6863
  }
6808
6864
  function projectProviderCachedModels(provider) {
6809
6865
  const cached = provider.modelsCache?.models ?? [];
6810
- if (isChatGptOAuthProvider(provider)) return applyOAuthSeedContextMetadata(cached);
6811
- if (!isRetainedOpenCodeGoProvider(provider)) return cached;
6866
+ if (isChatGptOAuthProvider(provider)) {
6867
+ return applyOAuthSeedContextMetadata(cached).filter((model) => !requiresNewerCodexClient(model));
6868
+ }
6869
+ if (!isRetainedOpenCodeGoProvider(provider)) {
6870
+ if (!cached.some((model) => (model.npm ?? provider.api.npm) === "@ai-sdk/openai")) return cached;
6871
+ return cached.map((model) => {
6872
+ if ((model.npm ?? provider.api.npm) !== "@ai-sdk/openai") return model;
6873
+ const pricing = openAiPricingMetadata(model.upstreamModelId ?? model.id);
6874
+ return {
6875
+ ...model,
6876
+ pricingBoundary: model.pricingBoundary ?? pricing.pricingBoundary,
6877
+ pricingBoundaryNote: model.pricingBoundaryNote ?? pricing.pricingBoundaryNote
6878
+ };
6879
+ });
6880
+ }
6812
6881
  const template = retainedOpenCodeGoTemplate();
6813
6882
  return template ? applyTemplateModelMetadata(template, cached) : [];
6814
6883
  }
@@ -6962,8 +7031,162 @@ function materializeRegistry(registry, resolveCredential, opts) {
6962
7031
  return result;
6963
7032
  }
6964
7033
 
7034
+ // src/editor-extension-version.ts
7035
+ import { execFileSync, execSync } from "child_process";
7036
+ import { readdirSync, readFileSync as readFileSync7, realpathSync } from "fs";
7037
+ import { homedir as homedir2 } from "os";
7038
+ import { join as join6 } from "path";
7039
+ var CLAUDE_EXTENSION_ID = "anthropic.claude-code";
7040
+ var EDITOR_EXTENSION_ROOTS = [
7041
+ { editor: "VS Code", dir: ".vscode" },
7042
+ { editor: "VS Code Insiders", dir: ".vscode-insiders" },
7043
+ { editor: "VS Code (remote server)", dir: ".vscode-server" },
7044
+ { editor: "VSCodium", dir: ".vscode-oss" },
7045
+ { editor: "Cursor", dir: ".cursor" },
7046
+ { editor: "Windsurf", dir: ".windsurf" }
7047
+ ];
7048
+ var CHECKED_EDITORS_DESCRIPTION = EDITOR_EXTENSION_ROOTS.map((root) => root.editor).join(", ");
7049
+ function releaseKey(version) {
7050
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
7051
+ return match ? match.slice(1).map(Number) : null;
7052
+ }
7053
+ function compareVersionKeys(a, b) {
7054
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
7055
+ const diff = (a[i] ?? 0) - (b[i] ?? 0);
7056
+ if (diff !== 0) return diff;
7057
+ }
7058
+ return 0;
7059
+ }
7060
+ function newestVersion(versions) {
7061
+ let best = null;
7062
+ for (const version of versions) {
7063
+ const key = releaseKey(version);
7064
+ if (key && (!best || compareVersionKeys(key, best.key) > 0)) best = { version, key };
7065
+ }
7066
+ return best?.version ?? null;
7067
+ }
7068
+ var EXTENSION_DIR_PATTERN = /^anthropic\.claude-code-(\d+\.\d+\.\d+)(?:-[a-z0-9]+)*$/i;
7069
+ function readJson(path) {
7070
+ try {
7071
+ return JSON.parse(readFileSync7(path, "utf8"));
7072
+ } catch {
7073
+ return void 0;
7074
+ }
7075
+ }
7076
+ function entryDirectoryName(entry) {
7077
+ if (typeof entry.relativeLocation === "string") return entry.relativeLocation;
7078
+ const location = entry.location;
7079
+ const raw = typeof location === "string" ? location : location && typeof location === "object" ? location.fsPath ?? location.path : void 0;
7080
+ if (typeof raw !== "string") return null;
7081
+ const parts = raw.split(/[\\/]/).filter(Boolean);
7082
+ return parts.at(-1) ?? null;
7083
+ }
7084
+ function readClaudeExtensionVersion(extensionsDir) {
7085
+ let names;
7086
+ try {
7087
+ names = readdirSync(extensionsDir);
7088
+ } catch {
7089
+ return null;
7090
+ }
7091
+ const present = new Set(names);
7092
+ const listed = readJson(join6(extensionsDir, "extensions.json"));
7093
+ if (Array.isArray(listed)) {
7094
+ const versions2 = [];
7095
+ for (const item of listed) {
7096
+ if (!item || typeof item !== "object") continue;
7097
+ const entry = item;
7098
+ const identifier = entry.identifier;
7099
+ if (typeof identifier?.id !== "string" || identifier.id.toLowerCase() !== CLAUDE_EXTENSION_ID) continue;
7100
+ if (typeof entry.version !== "string") continue;
7101
+ const dirName = entryDirectoryName(entry);
7102
+ if (dirName !== null && !present.has(dirName)) continue;
7103
+ versions2.push(entry.version);
7104
+ }
7105
+ return newestVersion(versions2);
7106
+ }
7107
+ const obsoleteRaw = readJson(join6(extensionsDir, ".obsolete"));
7108
+ const obsolete = obsoleteRaw && typeof obsoleteRaw === "object" && !Array.isArray(obsoleteRaw) ? new Set(Object.entries(obsoleteRaw).filter(([, flagged]) => flagged).map(([name]) => name)) : /* @__PURE__ */ new Set();
7109
+ const versions = [];
7110
+ for (const name of names) {
7111
+ if (obsolete.has(name)) continue;
7112
+ const match = EXTENSION_DIR_PATTERN.exec(name);
7113
+ if (match) versions.push(match[1]);
7114
+ }
7115
+ return newestVersion(versions);
7116
+ }
7117
+ function findInstalledClaudeExtensions(home = homedir2()) {
7118
+ const found = [];
7119
+ for (const root of EDITOR_EXTENSION_ROOTS) {
7120
+ const extensionsDir = join6(home, root.dir, "extensions");
7121
+ const version = readClaudeExtensionVersion(extensionsDir);
7122
+ if (version) found.push({ editor: root.editor, extensionsDir, version });
7123
+ }
7124
+ return found;
7125
+ }
7126
+ var NPM_ROOT_TIMEOUT_MS = 1e4;
7127
+ function readNpmGlobalRoot(env = process.env) {
7128
+ try {
7129
+ const options = {
7130
+ env,
7131
+ encoding: "utf8",
7132
+ stdio: ["ignore", "pipe", "ignore"],
7133
+ timeout: NPM_ROOT_TIMEOUT_MS
7134
+ };
7135
+ const output = process.platform === "win32" ? execSync("npm root -g", options) : execFileSync("npm", ["root", "-g"], options);
7136
+ const root = output.trim().split(/\r?\n/).at(-1)?.trim();
7137
+ return root || null;
7138
+ } catch {
7139
+ return null;
7140
+ }
7141
+ }
7142
+ function comparablePath(path) {
7143
+ let real = path;
7144
+ try {
7145
+ real = realpathSync(path);
7146
+ } catch {
7147
+ }
7148
+ const normalized = real.replace(/\\/g, "/").replace(/\/+$/, "");
7149
+ return /^[a-z]:\//i.test(normalized) ? normalized.toLowerCase() : normalized;
7150
+ }
7151
+ function claudeInstallCommand(binaryPath, version, npmGlobalRoot) {
7152
+ const path = comparablePath(binaryPath);
7153
+ if (/\/\.local\/share\/claude\/versions\/[^/]+$/.test(path)) return `claude install ${version}`;
7154
+ if (/\/node_modules\/@anthropic-ai\/claude-code/.test(path)) {
7155
+ const root = npmGlobalRoot();
7156
+ const packagePrefix = root === null ? null : `${comparablePath(root)}/@anthropic-ai/claude-code`;
7157
+ const underGlobalPackage = packagePrefix !== null && path.startsWith(packagePrefix) && ["/", "-"].includes(path.charAt(packagePrefix.length));
7158
+ if (underGlobalPackage) return `npm install -g @anthropic-ai/claude-code@${version}`;
7159
+ }
7160
+ return null;
7161
+ }
7162
+ function describeExtensionDrift(extension, patched, npmGlobalRoot) {
7163
+ const extensionKey = releaseKey(extension.version);
7164
+ const patchedKey = releaseKey(patched.version);
7165
+ if (!extensionKey || !patchedKey) return null;
7166
+ const order = compareVersionKeys(extensionKey, patchedKey);
7167
+ if (order === 0) return null;
7168
+ const { editor, version } = extension;
7169
+ const install = claudeInstallCommand(patched.binaryPath, version, npmGlobalRoot);
7170
+ const lines = [
7171
+ `${editor}'s Claude Code extension is ${version}, but the Claude Code clodex patched is ${patched.version} (${patched.binaryPath}).`,
7172
+ `When ${editor} launches Claude Code through clodex-claude (claudeCode.claudeProcessWrapper), its chats run the extension's own bundled ${version} binary and clodex models will not appear in its model picker.`,
7173
+ ...install ? ["To fix it, run:", ` ${install}`, " clodex patch"] : [`To fix it, update the Claude Code at ${patched.binaryPath} to ${version} with the tool that installed it, then run:`, " clodex patch"]
7174
+ ];
7175
+ if (order < 0) lines.push(`Or update the extension in ${editor} to ${patched.version}.`);
7176
+ return lines.join("\n");
7177
+ }
7178
+ function claudeExtensionDriftWarnings(patched, home = homedir2(), npmGlobalRoot = () => readNpmGlobalRoot()) {
7179
+ let root = null;
7180
+ const cachedRoot = () => (root ??= { value: npmGlobalRoot() }).value;
7181
+ try {
7182
+ return findInstalledClaudeExtensions(home).map((extension) => describeExtensionDrift(extension, patched, cachedRoot)).filter((warning) => warning !== null);
7183
+ } catch {
7184
+ return [];
7185
+ }
7186
+ }
7187
+
6965
7188
  // src/npm-shim.ts
6966
- import { readFileSync as readFileSync7, realpathSync, statSync as statSync4 } from "fs";
7189
+ import { readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
6967
7190
  import { dirname as dirname3, resolve as resolve2 } from "path";
6968
7191
  var MAX_LAUNCHER_BYTES = 64 * 1024;
6969
7192
  var MAX_LAUNCHER_HOPS = 4;
@@ -6986,7 +7209,7 @@ function launcherKind(shimPath) {
6986
7209
  function readLauncherText(path) {
6987
7210
  try {
6988
7211
  if (statSync4(path).size > MAX_LAUNCHER_BYTES) return null;
6989
- return readFileSync7(path, "utf8");
7212
+ return readFileSync8(path, "utf8");
6990
7213
  } catch {
6991
7214
  return null;
6992
7215
  }
@@ -7043,7 +7266,7 @@ function resolveThroughNpmShims(startPath) {
7043
7266
  }
7044
7267
  let hopped;
7045
7268
  try {
7046
- hopped = realpathSync(reading.target);
7269
+ hopped = realpathSync2(reading.target);
7047
7270
  } catch {
7048
7271
  return {
7049
7272
  ok: false,
@@ -7068,13 +7291,13 @@ import {
7068
7291
  closeSync as closeSync2,
7069
7292
  fstatSync,
7070
7293
  openSync as openSync2,
7071
- readFileSync as readFileSync8,
7294
+ readFileSync as readFileSync9,
7072
7295
  readSync,
7073
7296
  statSync as statSync5
7074
7297
  } from "fs";
7075
7298
  import { createRequire } from "module";
7076
7299
  import { arch } from "os";
7077
- import { dirname as dirname4, join as join6 } from "path";
7300
+ import { dirname as dirname4, join as join7 } from "path";
7078
7301
  var MAX_PLACEHOLDER_BYTES = 64 * 1024;
7079
7302
  var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
7080
7303
  var MISSING_BINARY_MARKER = "claude native binary not installed";
@@ -7101,11 +7324,11 @@ function getPlatformKey() {
7101
7324
  }
7102
7325
  function inspectNativePackage(binaryPath) {
7103
7326
  const packageRoot2 = dirname4(dirname4(binaryPath));
7104
- const packageJsonPath = join6(packageRoot2, "package.json");
7105
- const installScriptPath = join6(packageRoot2, "install.cjs");
7327
+ const packageJsonPath = join7(packageRoot2, "package.json");
7328
+ const installScriptPath = join7(packageRoot2, "install.cjs");
7106
7329
  let manifest;
7107
7330
  try {
7108
- manifest = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
7331
+ manifest = JSON.parse(readFileSync9(packageJsonPath, "utf8"));
7109
7332
  if (manifest.name !== CLAUDE_PACKAGE || !statSync5(installScriptPath).isFile()) {
7110
7333
  return { nativePackageState: "unknown", installScriptPath: null };
7111
7334
  }
@@ -7159,7 +7382,7 @@ function inspectClaudeNativeBinaryPlaceholder(path) {
7159
7382
 
7160
7383
  // src/bun-entry-module.ts
7161
7384
  import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync6, writeSync } from "fs";
7162
- import { execFileSync } from "child_process";
7385
+ import { execFileSync as execFileSync2 } from "child_process";
7163
7386
  var BUN_TRAILER = Buffer.from("\n---- Bun! ----\n");
7164
7387
  var BUN_OFFSETS_BYTES = 32;
7165
7388
  var TAIL_SCAN_BYTES = 16 * 1024 * 1024;
@@ -7415,7 +7638,7 @@ function resignMachOBinary(path) {
7415
7638
  closeSync3(fd);
7416
7639
  }
7417
7640
  if (machO && process.platform === "darwin") {
7418
- execFileSync("codesign", ["-s", "-", "-f", path], { stdio: "ignore" });
7641
+ execFileSync2("codesign", ["-s", "-", "-f", path], { stdio: "ignore" });
7419
7642
  }
7420
7643
  }
7421
7644
 
@@ -11705,7 +11928,7 @@ function withThousands2(value) {
11705
11928
  return value.toLocaleString("en-US");
11706
11929
  }
11707
11930
  function formatPricingBoundaryWarning(modelLabel, boundary, inputTokens) {
11708
- return `clodex: ${modelLabel} request counted ${withThousands2(inputTokens)} input tokens, above the ${withThousands2(boundary)}-token pricing boundary. The provider prices the full request at a higher rate above this point. Set this model to the standard context stop to stay under it.`;
11931
+ return `clodex: ${modelLabel} request counted ${withThousands2(inputTokens)} input tokens, above the ${withThousands2(boundary)}-token pricing boundary. The provider prices the full request at a higher rate above this point. Choose a smaller context stop to reduce the chance of higher-rate requests.`;
11709
11932
  }
11710
11933
  function reportPricingBoundaryCrossing(observation, emit = emitParentNotice) {
11711
11934
  const { modelKey, modelLabel, pricingBoundary, inputTokens } = observation;
@@ -12515,15 +12738,10 @@ function annotateToolNames(messages) {
12515
12738
  }
12516
12739
  }
12517
12740
  function thinkingToSdkPart(block, npm) {
12518
- const text6 = block.thinking ?? "";
12519
- if (npm === "@ai-sdk/openai" && !block.signature && !text6.trim()) return null;
12520
- const part = { type: "reasoning", text: text6 };
12521
- if (block.signature) {
12522
- if (npm === "@ai-sdk/google") {
12523
- part.providerOptions = { google: { thoughtSignature: block.signature } };
12524
- } else if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/openai-compatible") {
12525
- part.providerOptions = { openai: { reasoningEncryptedContent: block.signature } };
12526
- }
12741
+ if (npm === "@ai-sdk/openai") return null;
12742
+ const part = { type: "reasoning", text: block.thinking ?? "" };
12743
+ if (block.signature && npm === "@ai-sdk/google") {
12744
+ part.providerOptions = { google: { thoughtSignature: block.signature } };
12527
12745
  }
12528
12746
  return part;
12529
12747
  }
@@ -14062,7 +14280,7 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
14062
14280
 
14063
14281
  // src/patcher.ts
14064
14282
  function getPatchLockPath() {
14065
- return join7(getAppHome(), "patch.lock");
14283
+ return join8(getAppHome(), "patch.lock");
14066
14284
  }
14067
14285
  function writePatchManifest(manifest, path = getPatchManifestPath()) {
14068
14286
  mkdirSync5(getAppHome(), { recursive: true, mode: 448 });
@@ -14219,7 +14437,7 @@ function pidIsAlive(pid) {
14219
14437
  function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14220
14438
  const now = opts.now ?? Date.now();
14221
14439
  const isAlive = opts.isAlive ?? pidIsAlive;
14222
- mkdirSync5(join7(lockPath, ".."), { recursive: true, mode: 448 });
14440
+ mkdirSync5(join8(lockPath, ".."), { recursive: true, mode: 448 });
14223
14441
  for (let attempt = 0; attempt < 2; attempt++) {
14224
14442
  try {
14225
14443
  const fd = openSync8(lockPath, "wx");
@@ -14235,7 +14453,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14235
14453
  } catch {
14236
14454
  let stale = false;
14237
14455
  try {
14238
- const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
14456
+ const existing = JSON.parse(readFileSync10(lockPath, "utf8"));
14239
14457
  stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
14240
14458
  } catch {
14241
14459
  stale = true;
@@ -14257,7 +14475,7 @@ function takeIgnoredLaunchOverride() {
14257
14475
  }
14258
14476
  function resolveClaudeBinaryForPatch() {
14259
14477
  const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"]?.trim() || null;
14260
- const nativeSymlink = join7(homedir2(), ".local", "bin", "claude");
14478
+ const nativeSymlink = join8(homedir3(), ".local", "bin", "claude");
14261
14479
  if (envOverride && !existsSync4(envOverride)) {
14262
14480
  return { ok: false, reason: "patch-target-missing", declaredPath: envOverride };
14263
14481
  }
@@ -14265,7 +14483,7 @@ function resolveClaudeBinaryForPatch() {
14265
14483
  if (!source) return { ok: false, reason: "binary-not-found" };
14266
14484
  let resolved;
14267
14485
  try {
14268
- resolved = realpathSync2(source);
14486
+ resolved = realpathSync3(source);
14269
14487
  } catch {
14270
14488
  return { ok: false, reason: "binary-not-found" };
14271
14489
  }
@@ -14285,8 +14503,8 @@ function resolveClaudeBinaryForPatch() {
14285
14503
  let launchOverrideResolved = null;
14286
14504
  if (launchOverride) {
14287
14505
  try {
14288
- const followedOverride = resolveThroughNpmShims(realpathSync2(launchOverride));
14289
- launchOverrideResolved = followedOverride.ok ? followedOverride.path : realpathSync2(launchOverride);
14506
+ const followedOverride = resolveThroughNpmShims(realpathSync3(launchOverride));
14507
+ launchOverrideResolved = followedOverride.ok ? followedOverride.path : realpathSync3(launchOverride);
14290
14508
  } catch {
14291
14509
  launchOverrideResolved = launchOverride;
14292
14510
  }
@@ -14425,8 +14643,8 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14425
14643
  try {
14426
14644
  mkdirSync5(backupDir(), { recursive: true });
14427
14645
  const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
14428
- candidateDir = mkdtempSync(join7(dirname5(binaryPath), ".clodex-patch-"));
14429
- const candidatePath = join7(candidateDir, basename(binaryPath));
14646
+ candidateDir = mkdtempSync(join8(dirname5(binaryPath), ".clodex-patch-"));
14647
+ const candidatePath = join8(candidateDir, basename(binaryPath));
14430
14648
  const seedCandidate = async (from) => {
14431
14649
  copyFileSync(from, candidatePath);
14432
14650
  const shim = shimEntryModuleName(candidatePath);
@@ -14713,6 +14931,9 @@ function runRestoreCommand(target) {
14713
14931
  p2.log.success(`Restored pristine claude ${version} from ${plan.backupPath}.`);
14714
14932
  return 0;
14715
14933
  }
14934
+ function warnOnEditorExtensionDrift(binaryPath, version) {
14935
+ for (const warning of claudeExtensionDriftWarnings({ binaryPath, version })) p2.log.warn(warning);
14936
+ }
14716
14937
  async function runPatchCommand(opts = {}) {
14717
14938
  const target = resolveClaudeBinaryForPatch();
14718
14939
  const ignoredOverride = takeIgnoredLaunchOverride();
@@ -14758,6 +14979,7 @@ async function runPatchCommand(opts = {}) {
14758
14979
  });
14759
14980
  if (state === "current") {
14760
14981
  p2.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
14982
+ warnOnEditorExtensionDrift(binaryPath, version);
14761
14983
  return 0;
14762
14984
  }
14763
14985
  const release = tryAcquirePatchLock();
@@ -14793,6 +15015,7 @@ async function runPatchCommand(opts = {}) {
14793
15015
  if (!opts.trace) {
14794
15016
  for (const line of outcome.detailLines ?? []) p2.log.info(pc3.dim(line));
14795
15017
  }
15018
+ warnOnEditorExtensionDrift(binaryPath, version);
14796
15019
  return 0;
14797
15020
  } finally {
14798
15021
  release();
@@ -15166,7 +15389,7 @@ import {
15166
15389
  lstatSync,
15167
15390
  mkdirSync as mkdirSync6,
15168
15391
  openSync as openSync9,
15169
- readFileSync as readFileSync10,
15392
+ readFileSync as readFileSync11,
15170
15393
  renameSync as renameSync3,
15171
15394
  unlinkSync as unlinkSync4,
15172
15395
  writeFileSync as writeFileSync6
@@ -15273,7 +15496,7 @@ function readJournalUnlocked(path) {
15273
15496
  if (opened.size > MAX_JOURNAL_BYTES) {
15274
15497
  throw new Error("Credential cleanup journal is too large.");
15275
15498
  }
15276
- return parseJournal(JSON.parse(readFileSync10(fd, "utf8")));
15499
+ return parseJournal(JSON.parse(readFileSync11(fd, "utf8")));
15277
15500
  } catch (error) {
15278
15501
  const message = error instanceof Error ? error.message : String(error);
15279
15502
  throw new Error(`Could not read credential cleanup journal: ${message}`);
@@ -16340,7 +16563,8 @@ function readCapabilityFlags(m) {
16340
16563
  const bool = (v) => typeof v === "boolean" ? v : void 0;
16341
16564
  return {
16342
16565
  useResponsesLite: bool(m["use_responses_lite"]),
16343
- preferWebSockets: bool(m["prefer_websockets"])
16566
+ preferWebSockets: bool(m["prefer_websockets"]),
16567
+ minimalClientVersion: readCodexClientVersion(m["minimal_client_version"])
16344
16568
  };
16345
16569
  }
16346
16570
  function parseOpenAiModelEntries(body) {
@@ -16377,7 +16601,8 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16377
16601
  effectiveContextPercent: entry.effective_context_window_percent ?? seed.effectiveContextPercent,
16378
16602
  maxOutputTokens: entry.max_output_tokens ?? seed.maxOutputTokens,
16379
16603
  useResponsesLite: entry.useResponsesLite ?? seed.useResponsesLite,
16380
- preferWebSockets: entry.preferWebSockets ?? seed.preferWebSockets
16604
+ preferWebSockets: entry.preferWebSockets ?? seed.preferWebSockets,
16605
+ minimalClientVersion: entry.minimalClientVersion ?? seed.minimalClientVersion
16381
16606
  };
16382
16607
  }
16383
16608
  const { id } = entry;
@@ -16412,7 +16637,8 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16412
16637
  // so a wrong `true` here costs an unusable menu entry, not a 400.
16413
16638
  reasoning: codexCatalog ? true : modelPrefersResponsesApi(id),
16414
16639
  useResponsesLite: entry.useResponsesLite,
16415
- preferWebSockets: entry.preferWebSockets
16640
+ preferWebSockets: entry.preferWebSockets,
16641
+ minimalClientVersion: entry.minimalClientVersion
16416
16642
  };
16417
16643
  }
16418
16644
  async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
@@ -16473,6 +16699,10 @@ async function refreshOpenAiOAuthModels(accessToken) {
16473
16699
  credentialRejected: credentialFailure !== void 0
16474
16700
  };
16475
16701
  }
16702
+ function warningSuffix(models) {
16703
+ const warning = codexClientVersionWarning(applyOAuthSeedContextMetadata(models));
16704
+ return warning ? ` ${warning}` : "";
16705
+ }
16476
16706
  async function refreshApiListProvider(provider, apiKey) {
16477
16707
  const npm = provider.api.npm ?? "@ai-sdk/openai-compatible";
16478
16708
  const retained = isRetainedOpenCodeGoProvider(provider);
@@ -16682,7 +16912,7 @@ async function refreshProviderModels(providerId, apiKey, registry, credentialSna
16682
16912
  if (oauthResult.source === "seed" && cachedModelCount(cacheProvider) > 0) {
16683
16913
  return skipWithCachedModels(
16684
16914
  cacheProvider,
16685
- `Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with clodex's built-in fallback list. Try refreshing again later.`
16915
+ `Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with clodex's built-in fallback list. Try refreshing again later.` + warningSuffix(cacheProvider.modelsCache.models)
16686
16916
  );
16687
16917
  }
16688
16918
  if (oauthResult.source === "seed") {
@@ -16776,7 +17006,7 @@ async function refreshProviderModels(providerId, apiKey, registry, credentialSna
16776
17006
  ok: true,
16777
17007
  modelCount: enriched.length,
16778
17008
  previousModelCount: hadPreviousRefresh ? previousModelCount : void 0,
16779
- reason: oauthFallbackReason
17009
+ reason: isChatGptOAuthProvider(provider) ? [oauthFallbackReason, codexClientVersionWarning(models)].filter(Boolean).join(" ") || void 0 : void 0
16780
17010
  };
16781
17011
  } catch (err) {
16782
17012
  return {
@@ -17133,6 +17363,7 @@ async function authenticateProvider(providerId, options = {}) {
17133
17363
  refreshSpinner.stop(`Models not refreshed${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
17134
17364
  } else if (refreshResult.ok) {
17135
17365
  refreshSpinner.stop("Models refreshed");
17366
+ if (refreshResult.reason) p4.log.warn(refreshResult.reason);
17136
17367
  } else {
17137
17368
  refreshSpinner.stop(`Could not refresh models${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
17138
17369
  }
@@ -18812,7 +19043,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
18812
19043
  }
18813
19044
  async function handleAnthropicMessages(req, res, options, modelCache, plog) {
18814
19045
  const clientAbort = watchClientDisconnect(res);
18815
- const body = await readJson(req);
19046
+ const body = await readJson2(req);
18816
19047
  if (!body) {
18817
19048
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
18818
19049
  return;
@@ -19082,7 +19313,7 @@ data: ${JSON.stringify({
19082
19313
  }
19083
19314
  async function handleAnthropicCountTokens(req, res, options, plog) {
19084
19315
  const clientAbort = watchClientDisconnect(res);
19085
- const body = await readJson(req);
19316
+ const body = await readJson2(req);
19086
19317
  if (!body) {
19087
19318
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
19088
19319
  return;
@@ -19151,7 +19382,7 @@ async function handleAnthropicCountTokens(req, res, options, plog) {
19151
19382
  }
19152
19383
  async function handleOpenAIChatCompletions(req, res, options, modelCache, plog) {
19153
19384
  const clientAbort = watchClientDisconnect(res);
19154
- const body = await readJson(req);
19385
+ const body = await readJson2(req);
19155
19386
  if (!body) {
19156
19387
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
19157
19388
  return;
@@ -19358,7 +19589,7 @@ function getResponseModelId(bodyModel, model, options) {
19358
19589
  if (typeof bodyModel === "string" && options.aliasNames?.has(bodyModel)) return bodyModel;
19359
19590
  return options.gateway?.maskGatewayIds ? gatewayDisplayName(model, options.gateway) : typeof bodyModel === "string" ? bodyModel : model.id;
19360
19591
  }
19361
- async function readJson(req) {
19592
+ async function readJson2(req) {
19362
19593
  try {
19363
19594
  const raw = await readBody(req);
19364
19595
  return raw ? JSON.parse(raw) : {};
@@ -19502,8 +19733,8 @@ import { createBrotliDecompress, createGunzip, createInflate } from "zlib";
19502
19733
 
19503
19734
  // src/http-proxy/ca.ts
19504
19735
  import { randomBytes as randomBytes2 } from "crypto";
19505
- import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
19506
- import { dirname as dirname7, join as join8, resolve as resolve3 } from "path";
19736
+ import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
19737
+ import { dirname as dirname7, join as join9, resolve as resolve3 } from "path";
19507
19738
  import forge from "node-forge";
19508
19739
  var CERT_DIR = "http-proxy";
19509
19740
  var CA_CERT_FILE = "clodex-ca.pem";
@@ -19518,14 +19749,14 @@ function serialNumber() {
19518
19749
  return bytes.toString("hex");
19519
19750
  }
19520
19751
  function certPaths() {
19521
- const dir = join8(getAppHome(), CERT_DIR);
19752
+ const dir = join9(getAppHome(), CERT_DIR);
19522
19753
  return {
19523
19754
  dir,
19524
- caCert: join8(dir, CA_CERT_FILE),
19525
- caKey: join8(dir, CA_KEY_FILE),
19526
- serverCert: join8(dir, SERVER_CERT_FILE),
19527
- serverKey: join8(dir, SERVER_KEY_FILE),
19528
- version: join8(dir, CERT_VERSION_FILE)
19755
+ caCert: join9(dir, CA_CERT_FILE),
19756
+ caKey: join9(dir, CA_KEY_FILE),
19757
+ serverCert: join9(dir, SERVER_CERT_FILE),
19758
+ serverKey: join9(dir, SERVER_KEY_FILE),
19759
+ version: join9(dir, CERT_VERSION_FILE)
19529
19760
  };
19530
19761
  }
19531
19762
  function writePrivate(path, value) {
@@ -19578,8 +19809,8 @@ function generateCertificates(paths) {
19578
19809
  }
19579
19810
  function storedCertificatesAreCurrent(paths) {
19580
19811
  try {
19581
- const ca = forge.pki.certificateFromPem(readFileSync11(paths.caCert, "utf8"));
19582
- const server = forge.pki.certificateFromPem(readFileSync11(paths.serverCert, "utf8"));
19812
+ const ca = forge.pki.certificateFromPem(readFileSync12(paths.caCert, "utf8"));
19813
+ const server = forge.pki.certificateFromPem(readFileSync12(paths.serverCert, "utf8"));
19583
19814
  const now = Date.now();
19584
19815
  const renewalBuffer = 7 * 24 * 60 * 60 * 1e3;
19585
19816
  return ca.validity.notBefore.getTime() <= now && ca.validity.notAfter.getTime() > now + renewalBuffer && server.validity.notBefore.getTime() <= now && server.validity.notAfter.getTime() > now + renewalBuffer && ca.verify(ca) && ca.verify(server);
@@ -19590,13 +19821,13 @@ function storedCertificatesAreCurrent(paths) {
19590
19821
  function ensureHttpProxyCertificates() {
19591
19822
  const paths = certPaths();
19592
19823
  const required = [paths.caCert, paths.caKey, paths.serverCert, paths.serverKey, paths.version];
19593
- const current = required.every(existsSync6) && readFileSync11(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
19824
+ const current = required.every(existsSync6) && readFileSync12(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
19594
19825
  if (!current) generateCertificates(paths);
19595
19826
  return {
19596
19827
  caCertPath: paths.caCert,
19597
- caCert: readFileSync11(paths.caCert, "utf8"),
19598
- serverCert: readFileSync11(paths.serverCert, "utf8"),
19599
- serverKey: readFileSync11(paths.serverKey, "utf8")
19828
+ caCert: readFileSync12(paths.caCert, "utf8"),
19829
+ serverCert: readFileSync12(paths.serverCert, "utf8"),
19830
+ serverKey: readFileSync12(paths.serverKey, "utf8")
19600
19831
  };
19601
19832
  }
19602
19833
  function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarning) {
@@ -19610,7 +19841,7 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
19610
19841
  let additionalCa;
19611
19842
  try {
19612
19843
  if (resolve3(additionalCaCertPath) === resolve3(relayCaCertPath)) return relayCaCertPath;
19613
- additionalCa = readFileSync11(additionalCaCertPath, "utf8").trim();
19844
+ additionalCa = readFileSync12(additionalCaCertPath, "utf8").trim();
19614
19845
  } catch (err) {
19615
19846
  return warn(
19616
19847
  `NODE_EXTRA_CA_CERTS=${additionalCaCertPath} cannot be read (${err instanceof Error ? err.message : String(err)}), so it is not part of the proxy CA bundle. Where node reports this itself it says only "Ignoring extra certs ... load failed", without naming the variable. Clear or correct it.`
@@ -19620,8 +19851,8 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
19620
19851
  return warn(`NODE_EXTRA_CA_CERTS=${additionalCaCertPath} is empty, so it adds nothing.`);
19621
19852
  }
19622
19853
  try {
19623
- const relayCa = readFileSync11(relayCaCertPath, "utf8").trimEnd();
19624
- const combinedPath = join8(dirname7(relayCaCertPath), "combined-ca.pem");
19854
+ const relayCa = readFileSync12(relayCaCertPath, "utf8").trimEnd();
19855
+ const combinedPath = join9(dirname7(relayCaCertPath), "combined-ca.pem");
19625
19856
  writePublic(combinedPath, `${relayCa}
19626
19857
  ${additionalCa}
19627
19858
  `);
@@ -21502,9 +21733,9 @@ function planLaunchWizard(opts) {
21502
21733
 
21503
21734
  // src/vscode-launcher.ts
21504
21735
  import { spawnSync as spawnSync2 } from "child_process";
21505
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync, readFileSync as readFileSync12, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
21506
- import { tmpdir } from "os";
21507
- import { dirname as dirname8, join as join9 } from "path";
21736
+ import { existsSync as existsSync7, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, readFileSync as readFileSync13, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
21737
+ import { homedir as homedir4, tmpdir } from "os";
21738
+ import { dirname as dirname8, join as join10 } from "path";
21508
21739
  import { fileURLToPath } from "url";
21509
21740
  var LAUNCHER_EXE_NAME = "clodex-claude.exe";
21510
21741
  var VSCODE_WRAPPER_SETTING = "claudeCode.claudeProcessWrapper";
@@ -21513,15 +21744,15 @@ var WRAPPER_PATH_PLACEHOLDER = '"__CLODEX_WRAPPER_SCRIPT_PATH__"';
21513
21744
  var LAUNCHER_SOURCE_FILE = "clodex-claude-launcher.cs";
21514
21745
  var UTF8_BOM = "\uFEFF";
21515
21746
  var CSC_ARGS = ["/nologo", "/target:exe", "/platform:anycpu", "/optimize+", `/out:${LAUNCHER_EXE_NAME}`, LAUNCHER_SOURCE_FILE];
21516
- var packageRoot = join9(dirname8(fileURLToPath(import.meta.url)), "..");
21747
+ var packageRoot = join10(dirname8(fileURLToPath(import.meta.url)), "..");
21517
21748
  function defaultLauncherInputs() {
21518
21749
  return {
21519
- launcherSourcePath: join9(packageRoot, "launcher", LAUNCHER_SOURCE_FILE),
21520
- wrapperScriptPath: join9(packageRoot, "dist", "claude-wrapper.js")
21750
+ launcherSourcePath: join10(packageRoot, "launcher", LAUNCHER_SOURCE_FILE),
21751
+ wrapperScriptPath: join10(packageRoot, "dist", "claude-wrapper.js")
21521
21752
  };
21522
21753
  }
21523
21754
  function launcherInstallPath(env = process.env) {
21524
- return join9(getAppHome(env), "bin", LAUNCHER_EXE_NAME);
21755
+ return join10(getAppHome(env), "bin", LAUNCHER_EXE_NAME);
21525
21756
  }
21526
21757
  function csharpStringLiteral(value) {
21527
21758
  let literal = '"';
@@ -21547,30 +21778,23 @@ function renderLauncherSource(template, paths) {
21547
21778
  }
21548
21779
  function cscSearchRoots(env = process.env) {
21549
21780
  const windir = env.WINDIR?.trim() || env.SystemRoot?.trim() || "C:\\Windows";
21550
- const dotnet = join9(windir, "Microsoft.NET");
21551
- return [join9(dotnet, "Framework64"), join9(dotnet, "Framework")];
21781
+ const dotnet = join10(windir, "Microsoft.NET");
21782
+ return [join10(dotnet, "Framework64"), join10(dotnet, "Framework")];
21552
21783
  }
21553
21784
  function frameworkVersionKey(name) {
21554
21785
  return name.slice(1).split(".").map((part) => Number.parseInt(part, 10) || 0);
21555
21786
  }
21556
- function compareVersionKeys(a, b) {
21557
- for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
21558
- const diff = (a[i] ?? 0) - (b[i] ?? 0);
21559
- if (diff !== 0) return diff;
21560
- }
21561
- return 0;
21562
- }
21563
21787
  function findCsc(env = process.env) {
21564
21788
  for (const root of cscSearchRoots(env)) {
21565
21789
  let entries;
21566
21790
  try {
21567
- entries = readdirSync(root);
21791
+ entries = readdirSync2(root);
21568
21792
  } catch {
21569
21793
  continue;
21570
21794
  }
21571
21795
  const candidates = entries.filter((name) => /^v4\.\d+(\.\d+)*$/i.test(name)).sort((a, b) => compareVersionKeys(frameworkVersionKey(b), frameworkVersionKey(a)));
21572
21796
  for (const version of candidates) {
21573
- const csc = join9(root, version, "csc.exe");
21797
+ const csc = join10(root, version, "csc.exe");
21574
21798
  try {
21575
21799
  if (statSync8(csc).isFile()) return csc;
21576
21800
  } catch {
@@ -21629,7 +21853,7 @@ function installVscodeLauncher(opts = {}) {
21629
21853
  }
21630
21854
  let source;
21631
21855
  try {
21632
- source = renderLauncherSource(readFileSync12(launcherSourcePath, "utf8"), { nodePath, wrapperScriptPath });
21856
+ source = renderLauncherSource(readFileSync13(launcherSourcePath, "utf8"), { nodePath, wrapperScriptPath });
21633
21857
  } catch (err) {
21634
21858
  return { ok: false, reason: "missing-input", message: `could not prepare the launcher source: ${describeError(err)}` };
21635
21859
  }
@@ -21637,7 +21861,7 @@ function installVscodeLauncher(opts = {}) {
21637
21861
  const tempRoot = opts.tempRoot ?? tmpdir();
21638
21862
  let scratch;
21639
21863
  try {
21640
- scratch = mkdtempSync2(join9(tempRoot, "clodex-vscode-launcher-"));
21864
+ scratch = mkdtempSync2(join10(tempRoot, "clodex-vscode-launcher-"));
21641
21865
  } catch (err) {
21642
21866
  return { ok: false, reason: "install-failed", message: `could not create a scratch directory under ${tempRoot}: ${describeError(err)}` };
21643
21867
  }
@@ -21652,9 +21876,9 @@ function installVscodeLauncher(opts = {}) {
21652
21876
  }
21653
21877
  function buildAndPublish(step) {
21654
21878
  const { scratch, cscPath, exePath } = step;
21655
- writeFileSync8(join9(scratch, LAUNCHER_SOURCE_FILE), UTF8_BOM + step.source, "utf8");
21879
+ writeFileSync8(join10(scratch, LAUNCHER_SOURCE_FILE), UTF8_BOM + step.source, "utf8");
21656
21880
  const compiled = step.compile(cscPath, CSC_ARGS, scratch);
21657
- const builtExe = join9(scratch, LAUNCHER_EXE_NAME);
21881
+ const builtExe = join10(scratch, LAUNCHER_EXE_NAME);
21658
21882
  if (compiled.error || compiled.status !== 0 || !existsSync7(builtExe)) {
21659
21883
  const output = [compiled.stdout, compiled.stderr].map((text6) => text6.trim()).filter(Boolean).join("\n");
21660
21884
  return {
@@ -21709,16 +21933,27 @@ function successReport(result) {
21709
21933
  "install: the paths above are compiled into the executable."
21710
21934
  ];
21711
21935
  }
21936
+ function launcherExtensionDriftWarnings(opts = {}) {
21937
+ const env = opts.env ?? process.env;
21938
+ const manifest = readPatchManifest(getPatchManifestPath(env));
21939
+ if (!manifest || typeof manifest.claudeVersion !== "string") return [];
21940
+ return claudeExtensionDriftWarnings(
21941
+ { binaryPath: manifest.binaryPath, version: manifest.claudeVersion },
21942
+ opts.homeDir ?? homedir4(),
21943
+ () => readNpmGlobalRoot(env)
21944
+ );
21945
+ }
21712
21946
  function runInstallVscodeLauncherCommand(opts = {}) {
21713
21947
  const result = installVscodeLauncher(opts);
21714
21948
  if (result.warning) console.error(`clodex: warning: ${result.warning}`);
21715
21949
  if (!result.ok) {
21716
21950
  console.error(`clodex: ${result.message}`);
21717
21951
  if (result.detail) console.error(result.detail);
21718
- return 1;
21952
+ } else {
21953
+ console.log(successReport(result).join("\n"));
21719
21954
  }
21720
- console.log(successReport(result).join("\n"));
21721
- return 0;
21955
+ for (const drift of launcherExtensionDriftWarnings(opts)) console.error(`clodex: warning: ${drift}`);
21956
+ return result.ok ? 0 : 1;
21722
21957
  }
21723
21958
 
21724
21959
  // src/cli.ts
@@ -22052,8 +22287,10 @@ ${pc13.bold("Options:")}
22052
22287
  --trace Write debug logs to ~/.clodex/logs/ and show errors on exit
22053
22288
  --fast Request Codex fast mode (service_tier=priority) on ChatGPT-OAuth models
22054
22289
  (equivalent to CLODEX_SERVICE_TIER=fast; warns if the SDK omits it)
22055
- --provider Boot provider id (skip wizard when paired with --model or in print mode)
22056
- --model Boot model id (skip wizard when paired with --provider or in print mode)
22290
+ --provider Endpoint mode only: boot provider id (skip wizard when paired with
22291
+ --model or in print mode)
22292
+ --model Endpoint mode only: boot model id (skip wizard when paired with
22293
+ --provider or in print mode)
22057
22294
  --context <model=stop> use a different share of a model's window for this
22058
22295
  launch only (standard, max, default, or a token count). Nothing is
22059
22296
  saved. It reaches Claude Code through the exported catalog, so a
@@ -22094,7 +22331,7 @@ ${pc13.bold("Examples:")}
22094
22331
  clodex claude --trace --resume abc-123
22095
22332
  clodex claude --endpoint
22096
22333
  clodex claude --endpoint --save-mode
22097
- clodex claude --provider openai-oauth --model gpt-6-sol
22334
+ clodex claude --endpoint --provider openai-oauth --model gpt-6-sol
22098
22335
  clodex claude -- --print "hello"
22099
22336
  clodex claude -- --dangerously-skip-permissions`;
22100
22337
  }
@@ -22216,8 +22453,11 @@ ${pc13.bold("How it works:")}
22216
22453
 
22217
22454
  ${pc13.bold("Context stops and cost:")}
22218
22455
  Some providers price large prompts in bands: GPT-5.6 and GPT-6 bill the full request at
22219
- a higher rate above 272,000 input tokens. The standard stop stays under that
22220
- line, so max is opt-in and reports the boundary when you select it.
22456
+ a higher rate above 272,000 input tokens. For ChatGPT/Codex-plan models whose
22457
+ catalog reports a 272,000-token window, standard stays at that line and a
22458
+ larger window is opt-in. OpenAI API-key models retain their full window, so
22459
+ standard may already cross the boundary. Choose a smaller numeric stop such
22460
+ as 250k to limit exposure to the higher rate.
22221
22461
 
22222
22462
  ${pc13.bold("Examples:")}
22223
22463
  clodex favorites
@@ -22254,7 +22494,13 @@ ${pc13.bold("Behavior:")}
22254
22494
  Local patches execute after the built-ins as an all-or-none set. Enabling
22255
22495
  them executes trusted JavaScript from local-patches.mjs with your full user
22256
22496
  permissions. Local failures are reported but never block the built-ins.
22257
- Run clodex patch again after every claude update.`;
22497
+ Run clodex patch again after every claude update.
22498
+
22499
+ An editor launching Claude Code through clodex-claude runs this patched
22500
+ install only when its Claude Code extension is the same build. When an
22501
+ installed extension is a different version, clodex patch warns and prints the
22502
+ commands that align them. Editors checked:
22503
+ ${CHECKED_EDITORS_DESCRIPTION}`;
22258
22504
  }
22259
22505
  function installVscodeLauncherHelpText() {
22260
22506
  return `${pc13.bold("clodex install-vscode-launcher")} v${VERSION}
@@ -22284,6 +22530,8 @@ ${pc13.bold("Behavior:")}
22284
22530
  As on macOS and Linux, clodex-claude then runs your clodex-patched install in
22285
22531
  place of the bundled claude.exe when the two builds match, so clodex models
22286
22532
  appear in the extension's model picker; re-run clodex patch after updates.
22533
+ Like clodex patch, it warns when an installed Claude Code extension is a
22534
+ different version from the Claude Code clodex last patched.
22287
22535
 
22288
22536
  The paths to node.exe and to the clodex install are compiled in: re-run this
22289
22537
  command after switching Node versions or moving the clodex install.
@@ -23234,7 +23482,7 @@ Error: ${resolved.error}
23234
23482
  function isCliEntryPoint() {
23235
23483
  if (!process.argv[1]) return false;
23236
23484
  try {
23237
- return realpathSync3(fileURLToPath2(import.meta.url)) === realpathSync3(process.argv[1]);
23485
+ return realpathSync4(fileURLToPath2(import.meta.url)) === realpathSync4(process.argv[1]);
23238
23486
  } catch {
23239
23487
  return false;
23240
23488
  }