@bman654/clodex 2.1.8 → 2.2.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
@@ -9,6 +9,7 @@ import {
9
9
  getCredentialMutationLockPath,
10
10
  getCredentialStateRoot,
11
11
  getInstalledClaudeVersion,
12
+ getLocalPatchesPath,
12
13
  getLogsPath,
13
14
  getSavedServerPassword,
14
15
  getServerExposedProviders,
@@ -39,7 +40,7 @@ import {
39
40
  withProviderMutationLock,
40
41
  withRegistryWriteLock,
41
42
  withRegistryWriteLockSync
42
- } from "./chunk-VQOX3AXE.js";
43
+ } from "./chunk-W4SVDQCZ.js";
43
44
 
44
45
  // src/cli.ts
45
46
  import pc13 from "picocolors";
@@ -217,7 +218,7 @@ import { join } from "path";
217
218
  // package.json
218
219
  var package_default = {
219
220
  name: "@bman654/clodex",
220
- version: "2.1.8",
221
+ version: "2.2.0",
221
222
  publishConfig: {
222
223
  access: "public"
223
224
  },
@@ -273,7 +274,7 @@ var package_default = {
273
274
  open: "11.0.0",
274
275
  picocolors: "1.1.1",
275
276
  tweakcc: "4.3.0",
276
- undici: "7.28.0",
277
+ undici: "7.29.0",
277
278
  ws: "8.21.0"
278
279
  },
279
280
  devDependencies: {
@@ -4523,20 +4524,28 @@ function continuationMismatchSummary(entry, payload, log12) {
4523
4524
  const details = continuationMismatchDetails(entry, payload, log12, true);
4524
4525
  return `full_items=${details.fullItems} expected_prefix_items=${details.expectedPrefixItems} first_mismatch=${details.firstMismatch} expected=${details.expectedKind} actual=${details.actualKind}`;
4525
4526
  }
4526
- function continuationMatch(entry, payload) {
4527
+ function canonicalItemStrings(items) {
4528
+ return items.map((item) => canonicalJson(normalizeToolCallJson([item])));
4529
+ }
4530
+ function isStrictPrefix(head, client) {
4531
+ if (client.length <= head.length) return false;
4532
+ for (let index = 0; index < head.length; index += 1) {
4533
+ if (head[index] !== client[index]) return false;
4534
+ }
4535
+ return true;
4536
+ }
4537
+ function continuationMatch(entry, payload, clientItems) {
4527
4538
  if (!entry.responseId || !entry.requestInput || !entry.expectedAssistant) return void 0;
4528
4539
  const full = inputArray(payload);
4529
- const exactPrefix = [...entry.requestInput, ...entry.expectedAssistant];
4530
- if (full.length > exactPrefix.length && arraysEqual(full.slice(0, exactPrefix.length), exactPrefix)) {
4531
- return { delta: full.slice(exactPrefix.length), mode: "exact" };
4540
+ entry.canonicalPrefix ??= canonicalItemStrings([...entry.requestInput, ...entry.expectedAssistant]);
4541
+ if (isStrictPrefix(entry.canonicalPrefix, clientItems)) {
4542
+ return { delta: full.slice(entry.canonicalPrefix.length), mode: "exact" };
4532
4543
  }
4533
4544
  const echoedAssistant = entry.expectedAssistant.filter((item) => conversationItemKind(item) !== "reasoning");
4534
4545
  if (echoedAssistant.length === entry.expectedAssistant.length) return void 0;
4535
- const echoablePrefix = [...entry.requestInput, ...echoedAssistant];
4536
- if (full.length <= echoablePrefix.length || !arraysEqual(full.slice(0, echoablePrefix.length), echoablePrefix)) {
4537
- return void 0;
4538
- }
4539
- return { delta: full.slice(echoablePrefix.length), mode: "omitted_reasoning" };
4546
+ entry.canonicalEchoablePrefix ??= canonicalItemStrings([...entry.requestInput, ...echoedAssistant]);
4547
+ if (!isStrictPrefix(entry.canonicalEchoablePrefix, clientItems)) return void 0;
4548
+ return { delta: full.slice(entry.canonicalEchoablePrefix.length), mode: "omitted_reasoning" };
4540
4549
  }
4541
4550
  function eventType(event) {
4542
4551
  return event && typeof event === "object" && typeof event.type === "string" ? event.type : void 0;
@@ -5156,6 +5165,8 @@ function handleSocketMessage(entry, data) {
5156
5165
  entry.responseId = ctx.responseId;
5157
5166
  entry.requestInput = inputArray(ctx.originalPayload);
5158
5167
  entry.expectedAssistant = expectedAssistantItems(ctx);
5168
+ entry.canonicalPrefix = void 0;
5169
+ entry.canonicalEchoablePrefix = void 0;
5159
5170
  entry.promptFieldHashes = ctx.promptFieldHashes;
5160
5171
  entry.instructionsSnapshot = ctx.instructionsSnapshot;
5161
5172
  entry.lastUsedAt = now;
@@ -5318,7 +5329,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
5318
5329
  const evictions = cleanupExpiredConnections(now);
5319
5330
  const candidates = partitionKey ? connectionEntries(partitionKey) : [];
5320
5331
  const idleCandidates = candidates.filter((entry) => !entry.inFlight);
5321
- const matches = idleCandidates.map((entry) => ({ entry, match: continuationMatch(entry, payload) })).filter((candidate) => candidate.match !== void 0).sort((left, right) => left.match.delta.length - right.match.delta.length || (left.match.mode === right.match.mode ? 0 : left.match.mode === "exact" ? -1 : 1));
5332
+ const clientItems = idleCandidates.length ? canonicalItemStrings(inputArray(payload)) : [];
5333
+ const matches = idleCandidates.map((entry) => ({ entry, match: continuationMatch(entry, payload, clientItems) })).filter((candidate) => candidate.match !== void 0).sort((left, right) => left.match.delta.length - right.match.delta.length || (left.match.mode === right.match.mode ? 0 : left.match.mode === "exact" ? -1 : 1));
5322
5334
  let selected = matches[0]?.entry;
5323
5335
  const selectedMatch = matches[0]?.match;
5324
5336
  const selectedDelta = selectedMatch?.delta;
@@ -13906,16 +13918,16 @@ function planLaunchWizard(opts) {
13906
13918
  }
13907
13919
 
13908
13920
  // src/patcher.ts
13909
- import { createHash as createHash8 } from "crypto";
13921
+ import { createHash as createHash9 } from "crypto";
13910
13922
  import {
13911
13923
  copyFileSync,
13912
13924
  existsSync as existsSync7,
13913
13925
  mkdtempSync,
13914
13926
  mkdirSync as mkdirSync7,
13915
- readFileSync as readFileSync9,
13927
+ readFileSync as readFileSync10,
13916
13928
  renameSync as renameSync3,
13917
13929
  rmSync,
13918
- statSync as statSync4,
13930
+ statSync as statSync5,
13919
13931
  unlinkSync as unlinkSync4,
13920
13932
  writeFileSync as writeFileSync7,
13921
13933
  openSync as openSync4,
@@ -13927,9 +13939,380 @@ import { basename, dirname as dirname5, join as join8 } from "path";
13927
13939
  import pc12 from "picocolors";
13928
13940
  import * as p11 from "@clack/prompts";
13929
13941
 
13930
- // src/patch-backup.ts
13942
+ // src/local-patches.ts
13931
13943
  import { createHash as createHash7 } from "crypto";
13932
- import { existsSync as existsSync6, readFileSync as readFileSync8, readdirSync, statSync as statSync3 } from "fs";
13944
+ import { readFileSync as readFileSync8, statSync as statSync3 } from "fs";
13945
+ var LOCAL_PATCH_API_VERSION = 1;
13946
+ var LOCAL_PATCH_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
13947
+ var MAX_LOCAL_PATCH_BYTES = 1024 * 1024;
13948
+ var RESERVED_MARKER_PREFIX = "/*ccpatch:";
13949
+ var LOCAL_MARKER_PREFIX = "/*clodex-local:";
13950
+ var MAX_ERROR_LENGTH = 300;
13951
+ var localModuleEvaluationSequence = 0;
13952
+ function describeLocalError(error) {
13953
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
13954
+ const redacted = raw.replace(
13955
+ /data:text\/javascript;base64,[^\s"'`]+/g,
13956
+ "<local-patches.mjs>"
13957
+ );
13958
+ const singleLine = redacted.replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
13959
+ if (singleLine.length <= MAX_ERROR_LENGTH) return singleLine || "unknown error";
13960
+ return `${singleLine.slice(0, MAX_ERROR_LENGTH - 1)}\u2026`;
13961
+ }
13962
+ function describeLocalId(id) {
13963
+ const encoded = JSON.stringify(id);
13964
+ return encoded.length <= 96 ? encoded : `${encoded.slice(0, 95)}\u2026`;
13965
+ }
13966
+ function countOccurrences(source, needle) {
13967
+ let count = 0;
13968
+ let offset = 0;
13969
+ while ((offset = source.indexOf(needle, offset)) !== -1) {
13970
+ count += 1;
13971
+ offset += needle.length;
13972
+ }
13973
+ return count;
13974
+ }
13975
+ function reservedMarkerInventory(source) {
13976
+ const count = countOccurrences(source, RESERVED_MARKER_PREFIX);
13977
+ const markers = source.match(/\/\*ccpatch:[\s\S]*?\*\//g)?.sort() ?? [];
13978
+ return JSON.stringify([count, markers]);
13979
+ }
13980
+ function localMarkerInventory(source) {
13981
+ const count = countOccurrences(source, LOCAL_MARKER_PREFIX);
13982
+ const markers = source.match(/\/\*clodex-local:[\s\S]*?\*\//g)?.sort() ?? [];
13983
+ return JSON.stringify([count, markers]);
13984
+ }
13985
+ function inspectLocalPatchSource(enabled) {
13986
+ const path = getLocalPatchesPath();
13987
+ if (!enabled) return { enabled: false, path };
13988
+ try {
13989
+ const stats = statSync3(path);
13990
+ if (!stats.isFile()) throw Object.assign(new Error("not a regular file"), { code: "EINVAL" });
13991
+ if (stats.size > MAX_LOCAL_PATCH_BYTES) {
13992
+ throw Object.assign(new Error("module exceeds 1 MiB"), { code: "EFBIG" });
13993
+ }
13994
+ const bytes = readFileSync8(path);
13995
+ if (bytes.length > MAX_LOCAL_PATCH_BYTES) {
13996
+ throw Object.assign(new Error("module exceeds 1 MiB"), { code: "EFBIG" });
13997
+ }
13998
+ const digest = createHash7("sha256").update(bytes).digest("hex");
13999
+ const source = bytes.toString("utf8");
14000
+ if (!Buffer.from(source, "utf8").equals(bytes)) {
14001
+ return {
14002
+ enabled: true,
14003
+ path,
14004
+ configIdentity: `v${LOCAL_PATCH_API_VERSION}:invalid-utf8:${digest}`,
14005
+ error: `could not read ${path}: module must be UTF-8`
14006
+ };
14007
+ }
14008
+ return {
14009
+ enabled: true,
14010
+ path,
14011
+ configIdentity: `v${LOCAL_PATCH_API_VERSION}:${digest}`,
14012
+ source
14013
+ };
14014
+ } catch (error) {
14015
+ const code = error instanceof Error && "code" in error ? String(error.code) : "UNKNOWN";
14016
+ const reason = code === "EFBIG" ? "module exceeds 1 MiB" : code === "EINVAL" ? "not a regular file" : code;
14017
+ return {
14018
+ enabled: true,
14019
+ path,
14020
+ configIdentity: `v${LOCAL_PATCH_API_VERSION}:unavailable:${code}`,
14021
+ error: `could not read ${path}: ${reason}`
14022
+ };
14023
+ }
14024
+ }
14025
+ function failedLocalTransaction(source, patches, results, failedIndex, extra) {
14026
+ const failedName = `LOCAL ${patches[failedIndex].id}`;
14027
+ const rolledBack = results.map((result) => result.status === "OK" ? {
14028
+ status: "SKIP",
14029
+ name: result.name,
14030
+ extra: `rolled back after ${failedName} failed`
14031
+ } : result);
14032
+ rolledBack.push({ status: "FAIL", name: failedName, extra });
14033
+ for (const remaining of patches.slice(failedIndex + 1)) {
14034
+ rolledBack.push({
14035
+ status: "SKIP",
14036
+ name: `LOCAL ${remaining.id}`,
14037
+ extra: `not run after ${failedName} failed`
14038
+ });
14039
+ }
14040
+ return { content: source, results: rolledBack };
14041
+ }
14042
+ function snapshotLocalPatchDefinitions(value) {
14043
+ if (!Array.isArray(value)) return "default export must be an array";
14044
+ const rawDefinitions = [...value];
14045
+ const snapshot = [];
14046
+ const ids = /* @__PURE__ */ new Set();
14047
+ for (const [index, definition] of rawDefinitions.entries()) {
14048
+ if (definition === null || typeof definition !== "object") {
14049
+ return `entry at index ${index} must be an object`;
14050
+ }
14051
+ const candidate = definition;
14052
+ const id = candidate.id;
14053
+ const apply = candidate.apply;
14054
+ if (typeof id !== "string") return `entry at index ${index} must have a string id`;
14055
+ if (typeof apply !== "function") {
14056
+ return `entry ${describeLocalId(id)} must have an apply function`;
14057
+ }
14058
+ if (!LOCAL_PATCH_ID_PATTERN.test(id)) {
14059
+ return `invalid id at index ${index}: ${describeLocalId(id)}`;
14060
+ }
14061
+ if (ids.has(id)) return `duplicate id at index ${index}: ${describeLocalId(id)}`;
14062
+ ids.add(id);
14063
+ snapshot.push(Object.freeze({ id, apply }));
14064
+ }
14065
+ return Object.freeze(snapshot);
14066
+ }
14067
+ function applyLocalPatchSnapshot(source, patches) {
14068
+ let content = source;
14069
+ const results = [];
14070
+ for (const [index, patch] of patches.entries()) {
14071
+ const marker = `/*clodex-local:${patch.id}*/`;
14072
+ const existingMarkerCount = countOccurrences(content, marker);
14073
+ if (existingMarkerCount > 1) {
14074
+ return failedLocalTransaction(
14075
+ source,
14076
+ patches,
14077
+ results,
14078
+ index,
14079
+ "existing marker appears more than once"
14080
+ );
14081
+ }
14082
+ if (existingMarkerCount === 1) {
14083
+ results.push({
14084
+ status: "SKIP",
14085
+ name: `LOCAL ${patch.id}`,
14086
+ extra: "already patched"
14087
+ });
14088
+ continue;
14089
+ }
14090
+ let next;
14091
+ const reservedMarkers = reservedMarkerInventory(content);
14092
+ const localMarkers = localMarkerInventory(content);
14093
+ try {
14094
+ next = patch.apply(content, { marker });
14095
+ } catch (error) {
14096
+ return failedLocalTransaction(
14097
+ source,
14098
+ patches,
14099
+ results,
14100
+ index,
14101
+ describeLocalError(error)
14102
+ );
14103
+ }
14104
+ if (typeof next !== "string") {
14105
+ return failedLocalTransaction(
14106
+ source,
14107
+ patches,
14108
+ results,
14109
+ index,
14110
+ `transform returned ${typeof next} instead of a string`
14111
+ );
14112
+ }
14113
+ if (reservedMarkerInventory(next) !== reservedMarkers) {
14114
+ return failedLocalTransaction(
14115
+ source,
14116
+ patches,
14117
+ results,
14118
+ index,
14119
+ "transform changed reserved /*ccpatch: markers"
14120
+ );
14121
+ }
14122
+ const markerCount = countOccurrences(next, marker);
14123
+ if (markerCount !== 1) {
14124
+ return failedLocalTransaction(
14125
+ source,
14126
+ patches,
14127
+ results,
14128
+ index,
14129
+ markerCount === 0 ? `transform did not emit ${marker}` : `transform must emit exactly one ${marker} marker`
14130
+ );
14131
+ }
14132
+ if (localMarkerInventory(next.replace(marker, "")) !== localMarkers) {
14133
+ return failedLocalTransaction(
14134
+ source,
14135
+ patches,
14136
+ results,
14137
+ index,
14138
+ "transform changed another local patch marker"
14139
+ );
14140
+ }
14141
+ content = next;
14142
+ results.push({ status: "OK", name: `LOCAL ${patch.id}` });
14143
+ }
14144
+ return { content, results };
14145
+ }
14146
+ function localPatchSetFailure(source, extra) {
14147
+ return {
14148
+ content: source,
14149
+ results: [{ status: "FAIL", name: "LOCAL PATCH SET", extra }]
14150
+ };
14151
+ }
14152
+ async function applyLocalPatches(source, localSource) {
14153
+ if (!localSource.enabled) return { content: source, results: [] };
14154
+ if (typeof localSource.error === "string") {
14155
+ return localPatchSetFailure(source, localSource.error);
14156
+ }
14157
+ try {
14158
+ const moduleUrl = `data:text/javascript;base64,${Buffer.from(localSource.source).toString("base64")}#clodex-local-eval-${++localModuleEvaluationSequence}`;
14159
+ const loaded = await import(
14160
+ /* @vite-ignore */
14161
+ moduleUrl
14162
+ );
14163
+ const definitions = snapshotLocalPatchDefinitions(loaded.default);
14164
+ if (typeof definitions === "string") {
14165
+ return localPatchSetFailure(source, definitions);
14166
+ }
14167
+ if (definitions.length === 0) {
14168
+ return {
14169
+ content: source,
14170
+ results: [{
14171
+ status: "SKIP",
14172
+ name: "LOCAL PATCH SET",
14173
+ extra: "no patches exported"
14174
+ }]
14175
+ };
14176
+ }
14177
+ return applyLocalPatchSnapshot(source, definitions);
14178
+ } catch (error) {
14179
+ return localPatchSetFailure(
14180
+ source,
14181
+ describeLocalError(error)
14182
+ );
14183
+ }
14184
+ }
14185
+
14186
+ // src/built-in-patch-proofs.ts
14187
+ function escapeRegex(value) {
14188
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14189
+ }
14190
+ function countExactOccurrences(source, needle) {
14191
+ let count = 0;
14192
+ let offset = 0;
14193
+ while ((offset = source.indexOf(needle, offset)) !== -1) {
14194
+ count += 1;
14195
+ offset += needle.length;
14196
+ }
14197
+ return count;
14198
+ }
14199
+ function uniqueMatch(source, name, pattern) {
14200
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
14201
+ const matcher = new RegExp(pattern.source, flags);
14202
+ const match = matcher.exec(source);
14203
+ if (!match || matcher.exec(source)) {
14204
+ throw new Error(`clodex patch: could not capture built-in postcondition: ${name}`);
14205
+ }
14206
+ return match[0];
14207
+ }
14208
+ function captureNeedle(source, name, needle) {
14209
+ if (countExactOccurrences(source, needle) !== 1) {
14210
+ throw new Error(`clodex patch: could not capture built-in postcondition: ${name}`);
14211
+ }
14212
+ return {
14213
+ name,
14214
+ protectedText: needle,
14215
+ occurrences: 1
14216
+ };
14217
+ }
14218
+ function capturePattern(source, name, pattern) {
14219
+ return captureNeedle(source, name, uniqueMatch(source, name, pattern));
14220
+ }
14221
+ function normalizedPatchName(name) {
14222
+ return name.replace(/ \(refresh\)$/, "");
14223
+ }
14224
+ function protectsResult(results, name) {
14225
+ const result = results.find((entry) => normalizedPatchName(entry.name) === name);
14226
+ return result !== void 0 && result.status !== "FAIL" && result.extra !== "no aliases configured";
14227
+ }
14228
+ function configuredAliases(config) {
14229
+ const aliases = /* @__PURE__ */ new Map();
14230
+ for (const [id, entry] of Object.entries(config)) {
14231
+ if (entry.alias === void 0) continue;
14232
+ const alias = String(entry.alias).trim().toLowerCase();
14233
+ aliases.set(alias, {
14234
+ id,
14235
+ ...entry.display === void 0 ? {} : { display: String(entry.display) }
14236
+ });
14237
+ }
14238
+ return [...aliases].map(([alias, entry]) => ({ alias, ...entry }));
14239
+ }
14240
+ function effortProofPattern(marker) {
14241
+ return new RegExp(
14242
+ escapeRegex(marker) + 'var _ccv=Object\\.assign\\(Object\\.create\\(null\\),\\{[^{}]*\\}\\)\\[String\\([\\w$]+\\|\\|""\\)\\.trim\\(\\)\\.toLowerCase\\(\\)\\];if\\(_ccv!==void 0\\)return _ccv;'
14243
+ );
14244
+ }
14245
+ function captureBuiltInPatchProofs(source, config, results) {
14246
+ const proofs = [];
14247
+ const addPattern = (name, pattern) => {
14248
+ if (protectsResult(results, name)) {
14249
+ proofs.push(capturePattern(source, name, pattern));
14250
+ }
14251
+ };
14252
+ addPattern(
14253
+ "PATCH 1: Agent tool model enum",
14254
+ /\.enum\(\["sonnet","opus","haiku"(?:,"[^"]+")*\]\)\.optional\(\)\.describe\(/
14255
+ );
14256
+ addPattern(
14257
+ "PATCH 3: known-alias validator list",
14258
+ /\["sonnet","opus","haiku","fable"(?:,"[^"]+")*,"opusplan"(?:,"[^"]+")*\]/
14259
+ );
14260
+ addPattern(
14261
+ "PATCH 4: Agent tool model description",
14262
+ /describe\(`Optional model override for this agent[^`]*?`\)/
14263
+ );
14264
+ const aliases = configuredAliases(config);
14265
+ if (protectsResult(results, "PATCH 6: alias resolver switch")) {
14266
+ for (const { alias } of aliases) {
14267
+ const value = JSON.stringify(alias);
14268
+ proofs.push(captureNeedle(
14269
+ source,
14270
+ `PATCH 6: alias resolver switch (${alias})`,
14271
+ `case${value}:return ${value};`
14272
+ ));
14273
+ }
14274
+ }
14275
+ if (protectsResult(results, "PATCH 5: model picker options")) {
14276
+ for (const { alias, id, display } of aliases) {
14277
+ const entry = "{value:" + JSON.stringify(alias) + ",label:" + JSON.stringify(alias.charAt(0).toUpperCase() + alias.slice(1)) + ",description:" + JSON.stringify(display ?? `Custom model (${id})`) + "}";
14278
+ proofs.push(captureNeedle(
14279
+ source,
14280
+ `PATCH 5: model picker options (${alias})`,
14281
+ entry
14282
+ ));
14283
+ }
14284
+ }
14285
+ addPattern(
14286
+ "PATCH 7: per-model context window",
14287
+ /\/\*ccpatch:ctx\*\/var _ccw=\(\{[^{}]*\}\)\[[^\]]*\];if\(_ccw!==void 0\)return _ccw;/
14288
+ );
14289
+ addPattern(
14290
+ "PATCH 8a: effort capability",
14291
+ effortProofPattern("/*ccpatch:effort*/")
14292
+ );
14293
+ addPattern(
14294
+ "PATCH 8b: xhigh effort capability",
14295
+ effortProofPattern("/*ccpatch:xhigh-effort*/")
14296
+ );
14297
+ addPattern(
14298
+ "PATCH 8c: max effort capability",
14299
+ effortProofPattern("/*ccpatch:max-effort*/")
14300
+ );
14301
+ addPattern(
14302
+ "PATCH 9: default effort",
14303
+ /\/\*ccpatch:default-effort\*\/var _cce=Object\.assign\(Object\.create\(null\),\{[^{}]*\}\)\[String\([\w$]+\|\|""\)\.trim\(\)\.toLowerCase\(\)\];if\(_cce!==void 0\)return _cce;/
14304
+ );
14305
+ return proofs;
14306
+ }
14307
+ function builtInPatchProofsChanged(source, proofs) {
14308
+ return proofs.some(
14309
+ (proof) => countExactOccurrences(source, proof.protectedText) !== proof.occurrences
14310
+ );
14311
+ }
14312
+
14313
+ // src/patch-backup.ts
14314
+ import { createHash as createHash8 } from "crypto";
14315
+ import { existsSync as existsSync6, readFileSync as readFileSync9, readdirSync, statSync as statSync4 } from "fs";
13933
14316
  import { homedir as homedir2 } from "os";
13934
14317
  import { join as join7 } from "path";
13935
14318
  var BACKUP_SHA_PREFIX_LENGTH = 16;
@@ -13937,7 +14320,7 @@ function backupDir() {
13937
14320
  return process.env["TWEAKCC_CONFIG_DIR"]?.trim() || join7(homedir2(), ".tweakcc");
13938
14321
  }
13939
14322
  function sha256File(path) {
13940
- return createHash7("sha256").update(readFileSync8(path)).digest("hex");
14323
+ return createHash8("sha256").update(readFileSync9(path)).digest("hex");
13941
14324
  }
13942
14325
  function backupVersionTag(version) {
13943
14326
  const tag = version.trim().replace(/[^\w.-]+/g, "_");
@@ -13967,7 +14350,7 @@ function scanPristineBackups(version, dir = backupDir()) {
13967
14350
  const path = join7(dir, entry);
13968
14351
  let sha256;
13969
14352
  try {
13970
- if (!statSync3(path).isFile()) continue;
14353
+ if (!statSync4(path).isFile()) continue;
13971
14354
  sha256 = sha256File(path);
13972
14355
  } catch {
13973
14356
  corrupt.push(path);
@@ -14388,7 +14771,7 @@ function getPatchLockPath() {
14388
14771
  }
14389
14772
  function readPatchManifest(path = getPatchManifestPath()) {
14390
14773
  try {
14391
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
14774
+ const parsed = JSON.parse(readFileSync10(path, "utf8"));
14392
14775
  if (parsed && typeof parsed.binaryPath === "string" && typeof parsed.configHash === "string") {
14393
14776
  return parsed;
14394
14777
  }
@@ -14454,7 +14837,7 @@ function reportRejectedModelAliases(rejections) {
14454
14837
  );
14455
14838
  }
14456
14839
  }
14457
- function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VERSION) {
14840
+ function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VERSION, localPatchIdentity) {
14458
14841
  const canonical = Object.keys(config).sort().map((key) => {
14459
14842
  const entry = config[key];
14460
14843
  return [
@@ -14466,7 +14849,11 @@ function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VER
14466
14849
  entry.effort?.defaultLevel ?? null
14467
14850
  ];
14468
14851
  });
14469
- return createHash8("sha256").update(JSON.stringify([transformsVersion, canonical])).digest("hex");
14852
+ const payload = [transformsVersion, canonical];
14853
+ if (localPatchIdentity !== void 0) {
14854
+ payload.push(["local-patches", localPatchIdentity]);
14855
+ }
14856
+ return createHash9("sha256").update(JSON.stringify(payload)).digest("hex");
14470
14857
  }
14471
14858
  function buildDesiredPatchConfig() {
14472
14859
  const prefs = loadPreferences();
@@ -14537,7 +14924,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14537
14924
  } catch {
14538
14925
  let stale = false;
14539
14926
  try {
14540
- const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
14927
+ const existing = JSON.parse(readFileSync10(lockPath, "utf8"));
14541
14928
  stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
14542
14929
  } catch {
14543
14930
  stale = true;
@@ -14563,7 +14950,7 @@ function resolveClaudeBinaryForPatch() {
14563
14950
  return { ok: false, reason: "binary-not-found" };
14564
14951
  }
14565
14952
  try {
14566
- if (!statSync4(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
14953
+ if (!statSync5(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
14567
14954
  } catch {
14568
14955
  return { ok: false, reason: "binary-not-found" };
14569
14956
  }
@@ -14615,6 +15002,49 @@ function requiredEffortPatchFailures(results) {
14615
15002
  (result) => result.status === "FAIL" && (result.name.startsWith("PATCH 8a:") || result.name.startsWith("PATCH 8b:") || result.name.startsWith("PATCH 8c:") || result.name.startsWith("PATCH 9:"))
14616
15003
  );
14617
15004
  }
15005
+ function builtInPatchVerificationFailed(source, config, expected, proofs) {
15006
+ if (builtInPatchProofsChanged(source, proofs)) return true;
15007
+ let verification;
15008
+ try {
15009
+ verification = applyClodexPatches(source, config);
15010
+ } catch {
15011
+ return true;
15012
+ }
15013
+ if (verification.content !== source || verification.results.length !== expected.length) {
15014
+ return true;
15015
+ }
15016
+ const expectedByName = new Map(expected.map((result) => [
15017
+ result.name.replace(/ \(refresh\)$/, ""),
15018
+ result
15019
+ ]));
15020
+ for (const result of verification.results) {
15021
+ const original = expectedByName.get(result.name.replace(/ \(refresh\)$/, ""));
15022
+ if (!original) return true;
15023
+ if (original.status === "FAIL") {
15024
+ if (result.status !== "FAIL" || result.extra !== original.extra) return true;
15025
+ } else if (result.status !== "SKIP") {
15026
+ return true;
15027
+ }
15028
+ }
15029
+ return false;
15030
+ }
15031
+ function discardLocalPatchOutcome(builtInContent, results) {
15032
+ return {
15033
+ content: builtInContent,
15034
+ results: [
15035
+ ...results.map((result) => result.status === "OK" ? {
15036
+ status: "SKIP",
15037
+ name: result.name,
15038
+ extra: "rolled back after built-in verification failed"
15039
+ } : result),
15040
+ {
15041
+ status: "FAIL",
15042
+ name: "LOCAL PATCH SET",
15043
+ extra: "local output changed built-in patch sites"
15044
+ }
15045
+ ]
15046
+ };
15047
+ }
14618
15048
  async function applyPatch(binaryPath, version, desired, configHash, opts) {
14619
15049
  let candidateDir;
14620
15050
  let results;
@@ -14678,8 +15108,8 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14678
15108
  backup = canonical;
14679
15109
  }
14680
15110
  publishBackupFile(backup, tweakccMirrorBackupPath());
14681
- const patched = applyClodexPatches(loaded.source, desired.config);
14682
- results = patched.results;
15111
+ const builtIn = applyClodexPatches(loaded.source, desired.config);
15112
+ results = builtIn.results;
14683
15113
  const failedEffortPatches = requiredEffortPatchFailures(results);
14684
15114
  if (failedEffortPatches.length > 0) {
14685
15115
  throw new PatchApplyError(
@@ -14687,8 +15117,38 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14687
15117
  results
14688
15118
  );
14689
15119
  }
14690
- await writeContent(loaded.installation, patched.content);
14691
- patchedSize = statSync4(candidatePath).size;
15120
+ let builtInProofs = [];
15121
+ let local;
15122
+ if (opts.localPatches?.enabled && typeof opts.localPatches.source === "string") {
15123
+ try {
15124
+ builtInProofs = captureBuiltInPatchProofs(
15125
+ builtIn.content,
15126
+ desired.config,
15127
+ builtIn.results
15128
+ );
15129
+ } catch {
15130
+ local = {
15131
+ content: builtIn.content,
15132
+ results: [{
15133
+ status: "FAIL",
15134
+ name: "LOCAL PATCH SET",
15135
+ extra: "could not capture built-in postconditions"
15136
+ }]
15137
+ };
15138
+ }
15139
+ }
15140
+ local ??= opts.localPatches ? await applyLocalPatches(builtIn.content, opts.localPatches) : { content: builtIn.content, results: [] };
15141
+ if (local.results.some((result) => result.status === "OK") && builtInPatchVerificationFailed(
15142
+ local.content,
15143
+ desired.config,
15144
+ builtIn.results,
15145
+ builtInProofs
15146
+ )) {
15147
+ local = discardLocalPatchOutcome(builtIn.content, local.results);
15148
+ }
15149
+ results = [...results, ...local.results];
15150
+ await writeContent(loaded.installation, local.content);
15151
+ patchedSize = statSync5(candidatePath).size;
14692
15152
  patchedSha256 = sha256File(candidatePath);
14693
15153
  renameSync3(candidatePath, binaryPath);
14694
15154
  } catch (err) {
@@ -14788,6 +15248,9 @@ async function runPatchCommand(opts = {}) {
14788
15248
  return 1;
14789
15249
  }
14790
15250
  const { binaryPath, version } = target;
15251
+ if (opts.localPatches !== void 0) {
15252
+ savePreferences({ localPatchesEnabled: opts.localPatches });
15253
+ }
14791
15254
  const desired = buildDesiredPatchConfig();
14792
15255
  if (Object.keys(desired.config).length === 0) {
14793
15256
  p11.log.error("No favorite models to patch. Save favorites with `clodex models` first.");
@@ -14797,13 +15260,20 @@ async function runPatchCommand(opts = {}) {
14797
15260
  p11.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
14798
15261
  }
14799
15262
  reportRejectedModelAliases(desired.rejectedAliasRejections);
14800
- const configHash = computePatchConfigHash(desired.config);
15263
+ const localPatches = inspectLocalPatchSource(
15264
+ loadPreferences().localPatchesEnabled === true
15265
+ );
15266
+ const configHash = computePatchConfigHash(
15267
+ desired.config,
15268
+ PATCH_TRANSFORMS_VERSION,
15269
+ localPatches.enabled ? localPatches.configIdentity : void 0
15270
+ );
14801
15271
  const manifest = readPatchManifest();
14802
15272
  const state = evaluatePatchState(manifest, {
14803
15273
  binaryPath,
14804
15274
  claudeVersion: version,
14805
15275
  configHash,
14806
- binarySize: statSync4(binaryPath).size
15276
+ binarySize: statSync5(binaryPath).size
14807
15277
  });
14808
15278
  if (state === "current") {
14809
15279
  p11.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
@@ -14817,7 +15287,8 @@ async function runPatchCommand(opts = {}) {
14817
15287
  try {
14818
15288
  const outcome = await applyPatch(binaryPath, version, desired, configHash, {
14819
15289
  trace: opts.trace ?? false,
14820
- manifest
15290
+ manifest,
15291
+ localPatches
14821
15292
  });
14822
15293
  if (!outcome.ok) {
14823
15294
  p11.log.error(outcome.message);
@@ -14845,13 +15316,20 @@ async function runLaunchPatchCheck(opts = {}) {
14845
15316
  return;
14846
15317
  }
14847
15318
  const resolved = target;
14848
- const configHash = computePatchConfigHash(desired.config);
15319
+ const localPatches = inspectLocalPatchSource(
15320
+ loadPreferences().localPatchesEnabled === true
15321
+ );
15322
+ const configHash = computePatchConfigHash(
15323
+ desired.config,
15324
+ PATCH_TRANSFORMS_VERSION,
15325
+ localPatches.enabled ? localPatches.configIdentity : void 0
15326
+ );
14849
15327
  const manifest = readPatchManifest();
14850
15328
  const state = evaluatePatchState(manifest, {
14851
15329
  binaryPath: resolved.binaryPath,
14852
15330
  claudeVersion: resolved.version,
14853
15331
  configHash,
14854
- binarySize: statSync4(resolved.binaryPath).size
15332
+ binarySize: statSync5(resolved.binaryPath).size
14855
15333
  });
14856
15334
  if (state === "current") return;
14857
15335
  const interactive = !opts.dryRun && !opts.agentStdout && process.stdin.isTTY === true && process.stdout.isTTY === true;
@@ -15057,9 +15535,22 @@ function parseArgs(args) {
15057
15535
  if (arg === "--help" || arg === "-h") parsed2.showHelp = true;
15058
15536
  else if (arg === "--version" || arg === "-v") parsed2.showVersion = true;
15059
15537
  else if (arg === "--restore") parsed2.patchRestore = true;
15060
- else if (arg === "--trace") parsed2.trace = true;
15538
+ else if (arg === "--enable-local-patches") {
15539
+ if (parsed2.patchLocalPatches === false && !parsed2.error) {
15540
+ parsed2.error = "--enable-local-patches and --disable-local-patches cannot be combined";
15541
+ }
15542
+ parsed2.patchLocalPatches = true;
15543
+ } else if (arg === "--disable-local-patches") {
15544
+ if (parsed2.patchLocalPatches === true && !parsed2.error) {
15545
+ parsed2.error = "--enable-local-patches and --disable-local-patches cannot be combined";
15546
+ }
15547
+ parsed2.patchLocalPatches = false;
15548
+ } else if (arg === "--trace") parsed2.trace = true;
15061
15549
  else if (!parsed2.error) parsed2.error = `Unknown patch option: ${arg}`;
15062
15550
  }
15551
+ if (parsed2.patchRestore && parsed2.patchLocalPatches !== void 0 && !parsed2.error) {
15552
+ parsed2.error = "--restore cannot be combined with local-patch settings";
15553
+ }
15063
15554
  return parsed2;
15064
15555
  }
15065
15556
  if (first !== "claude") {
@@ -15102,7 +15593,7 @@ Bridge Claude Code to OpenAI models \u2014 OpenAI API key or ChatGPT/Codex-plan
15102
15593
  ${pc13.bold("Usage:")}
15103
15594
  clodex claude [options] [claude-flags]
15104
15595
  clodex server [options]
15105
- clodex patch [--restore]
15596
+ clodex patch [options]
15106
15597
  clodex models
15107
15598
  clodex favorites
15108
15599
  clodex providers
@@ -15314,11 +15805,15 @@ real ids, and reporting the correct context window.
15314
15805
  ${pc13.bold("Usage:")}
15315
15806
  clodex patch
15316
15807
  clodex patch --restore
15808
+ clodex patch --enable-local-patches
15809
+ clodex patch --disable-local-patches
15317
15810
  clodex patch --help
15318
15811
 
15319
15812
  ${pc13.bold("Options:")}
15320
- --restore Restore the pristine (unpatched) Claude Code binary
15321
- --trace Show per-patch-site results (OK/SKIP/FAIL)
15813
+ --restore Restore the pristine Claude Code binary
15814
+ --trace Show per-patch-site results (OK/SKIP/FAIL)
15815
+ --enable-local-patches Persistently enable ~/.clodex/local-patches.mjs
15816
+ --disable-local-patches Disable local patches and rebuild without them
15322
15817
 
15323
15818
  ${pc13.bold("Behavior:")}
15324
15819
  The patch map is built automatically from your clodex favorites and aliases
@@ -15326,6 +15821,10 @@ ${pc13.bold("Behavior:")}
15326
15821
  per-version backup is kept, and a manifest (~/.clodex/patch-state.json)
15327
15822
  makes re-runs no-ops until your config or Claude Code version changes \u2014
15328
15823
  then the binary is restored first and re-patched fresh.
15824
+
15825
+ Local patches execute after the built-ins as an all-or-none set. Enabling
15826
+ them executes trusted JavaScript from local-patches.mjs with your full user
15827
+ permissions. Local failures are reported but never block the built-ins.
15329
15828
  Run clodex patch again after every claude update.`;
15330
15829
  }
15331
15830
  function printHelp(text4) {
@@ -16132,7 +16631,11 @@ Error: ${parsed.error}
16132
16631
  printHelp(patchHelpText());
16133
16632
  return 0;
16134
16633
  }
16135
- return runPatchCommand({ restore: parsed.patchRestore, trace: parsed.trace });
16634
+ return runPatchCommand({
16635
+ restore: parsed.patchRestore,
16636
+ trace: parsed.trace,
16637
+ localPatches: parsed.patchLocalPatches
16638
+ });
16136
16639
  }
16137
16640
  if (parsed.showVersion) {
16138
16641
  console.log(VERSION);