@bman654/clodex 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,6 +205,10 @@ Enabling this feature executes that JavaScript with your full user permissions;
205
205
 
206
206
  The module must default-export an array. Each site has a unique lowercase `id` and an `apply` function that returns the complete source and emits the generated `marker` exactly once when it applies:
207
207
 
208
+ The `source` your `apply` receives is every JavaScript module in the binary joined together, with a ``/*clodex:module-boundary`;{}"]*/`` line between them. This is true of every Claude Code version — a pre-2.1.242 binary carries the bundle plus five small helper modules; a 2.1.242-or-later one carries the bundle split across roughly 1,370. Match and replace as you always have; just do not add or remove one of those lines, or the whole patch is refused. (The punctuation in the marker is there so that a wildcard bounded by a character class cannot run across a module boundary.)
209
+
210
+ If your transform works by **position** rather than by matching an anchor, mind where it lands: text prepended to `source` goes into the first module and text appended to it goes into the last, and neither is necessarily the module that runs at startup. Anchor your edit to text you match, the way the examples below do.
211
+
208
212
  ```js
209
213
  export default [
210
214
  {
package/dist/cli.js CHANGED
@@ -382,7 +382,7 @@ import { join } from "path";
382
382
  // package.json
383
383
  var package_default = {
384
384
  name: "@bman654/clodex",
385
- version: "2.8.0",
385
+ version: "2.8.1",
386
386
  publishConfig: {
387
387
  access: "public"
388
388
  },
@@ -3419,7 +3419,7 @@ function savedStopsAfter(current, assignments) {
3419
3419
  }
3420
3420
 
3421
3421
  // src/patch-transforms.ts
3422
- var PATCH_TRANSFORMS_VERSION = 8;
3422
+ var PATCH_TRANSFORMS_VERSION = 9;
3423
3423
  var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
3424
3424
  var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
3425
3425
  function projectNativeEffort(effort) {
@@ -3622,7 +3622,7 @@ function applyClodexPatches(source, config) {
3622
3622
  }).join("; ");
3623
3623
  applyOnce(
3624
3624
  "PATCH 4: Agent tool model description",
3625
- /(describe\(`Optional model override for this agent[^`]*?)(`\))/,
3625
+ /(describe\(`Optional model override for this agent(?:[^`\\]|\\.)*?)(`)/,
3626
3626
  (_m, body, close) => body.includes("Additional custom models") ? body + close : body + " Additional custom models: " + listing + "." + close,
3627
3627
  { required: false, noopIsSkip: true }
3628
3628
  );
@@ -4169,7 +4169,7 @@ function captureBuiltInPatchProofs(source, config, results) {
4169
4169
  );
4170
4170
  addPattern(
4171
4171
  "PATCH 4: Agent tool model description",
4172
- /describe\(`Optional model override for this agent[^`]*?`\)/
4172
+ /describe\(`Optional model override for this agent(?:[^`\\]|\\.)*?`/
4173
4173
  );
4174
4174
  const aliases = configuredAliases(config);
4175
4175
  if (protectsResult(results, "PATCH 6: alias resolver switch")) {
@@ -6800,6 +6800,9 @@ var BUN_OFFSETS_BYTES = 32;
6800
6800
  var TAIL_SCAN_BYTES = 16 * 1024 * 1024;
6801
6801
  var MODULE_STRUCT_BYTES_CURRENT = 52;
6802
6802
  var MODULE_STRUCT_BYTES_LEGACY = 36;
6803
+ var CONTENTS_FIELD_AT = 8;
6804
+ var LOADER_FROM_END = 3;
6805
+ var BUN_JAVASCRIPT_LOADER = 1;
6803
6806
  var MAX_MODULE_NAME_BYTES = 4096;
6804
6807
  var MIN_SHIMMABLE_NAME_BYTES = 7;
6805
6808
  function tweakccRecognizesModuleName(name) {
@@ -6854,6 +6857,8 @@ function parseBunModuleNamesAtUnchecked(fd, offsetsAt) {
6854
6857
  if (!modules) return null;
6855
6858
  const names = [];
6856
6859
  const nameOffsets = [];
6860
+ const contents = [];
6861
+ const loaders = [];
6857
6862
  for (let index = 0; index < moduleCount; index++) {
6858
6863
  const nameOffset = modules.readUInt32LE(index * structBytes);
6859
6864
  const nameLength = modules.readUInt32LE(index * structBytes + 4);
@@ -6865,8 +6870,23 @@ function parseBunModuleNamesAtUnchecked(fd, offsetsAt) {
6865
6870
  if (!/^[\x20-\x7e]+$/.test(name)) return null;
6866
6871
  names.push(name);
6867
6872
  nameOffsets.push(blobAt + nameOffset);
6873
+ contents.push({
6874
+ offset: modules.readUInt32LE(index * structBytes + CONTENTS_FIELD_AT),
6875
+ length: modules.readUInt32LE(index * structBytes + CONTENTS_FIELD_AT + 4)
6876
+ });
6877
+ loaders.push(modules.readUInt8(index * structBytes + structBytes - LOADER_FROM_END));
6868
6878
  }
6869
- return { names, entryPointId, offsets: nameOffsets };
6879
+ return {
6880
+ names,
6881
+ entryPointId,
6882
+ offsets: nameOffsets,
6883
+ contents,
6884
+ loaders,
6885
+ blobAt,
6886
+ modulesAt: blobAt + modulesOffset,
6887
+ structBytes,
6888
+ byteCount: Number(byteCount)
6889
+ };
6870
6890
  }
6871
6891
  function findStandIn(fd, fileSize, marker) {
6872
6892
  const needle = Buffer.from(marker);
@@ -6912,6 +6932,70 @@ function isMachO(fd) {
6912
6932
  const value = magic.readUInt32BE(0);
6913
6933
  return value === 4277009102 || value === 4277009103 || value === 3472551422 || value === 3489328638 || value === 3405691582 || value === 3405691583;
6914
6934
  }
6935
+ function readBunModuleTable(path) {
6936
+ const fd = openSync2(path, "r");
6937
+ try {
6938
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6939
+ if (!parsed) return null;
6940
+ const { names, loaders, contents, entryPointId, blobAt, modulesAt, structBytes, byteCount } = parsed;
6941
+ return { names, loaders, contents, entryPointId, blobAt, modulesAt, structBytes, byteCount };
6942
+ } finally {
6943
+ closeSync2(fd);
6944
+ }
6945
+ }
6946
+ function readBunJavaScriptModules(path) {
6947
+ const fd = openSync2(path, "r");
6948
+ try {
6949
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6950
+ if (!parsed) return null;
6951
+ const modules = [];
6952
+ for (let index = 0; index < parsed.names.length; index++) {
6953
+ if (parsed.loaders[index] !== BUN_JAVASCRIPT_LOADER) continue;
6954
+ const range = parsed.contents[index];
6955
+ if (range.offset < 0 || range.length < 0 || range.offset + range.length > parsed.byteCount) {
6956
+ return null;
6957
+ }
6958
+ const bytes = range.length === 0 ? Buffer.alloc(0) : readAt(fd, range.length, parsed.blobAt + range.offset);
6959
+ if (!bytes) return null;
6960
+ modules.push({
6961
+ index,
6962
+ name: parsed.names[index],
6963
+ source: bytes.toString("utf8"),
6964
+ byteLength: bytes.length
6965
+ });
6966
+ }
6967
+ return modules;
6968
+ } finally {
6969
+ closeSync2(fd);
6970
+ }
6971
+ }
6972
+ function repointBunModuleContents(path, edits) {
6973
+ if (edits.length === 0) return;
6974
+ const fd = openSync2(path, "r+");
6975
+ try {
6976
+ const parsed = readBunModuleNames(fd, statSync4(path).size);
6977
+ if (!parsed) throw new Error(`cannot read the Bun module table of ${path}`);
6978
+ for (const { index, range } of edits) {
6979
+ if (index < 0 || index >= parsed.names.length) {
6980
+ throw new Error(`module ${index} is outside the ${parsed.names.length}-module table of ${path}`);
6981
+ }
6982
+ if (range.offset < 0 || range.length < 0 || range.offset + range.length > parsed.byteCount) {
6983
+ throw new Error(
6984
+ `module ${index} would point at ${range.offset}+${range.length}, outside the ${parsed.byteCount}-byte blob of ${path}`
6985
+ );
6986
+ }
6987
+ const at = parsed.modulesAt + index * parsed.structBytes + CONTENTS_FIELD_AT;
6988
+ const pair = Buffer.alloc(8);
6989
+ pair.writeUInt32LE(range.offset, 0);
6990
+ pair.writeUInt32LE(range.length, 4);
6991
+ if (writeSync2(fd, pair, 0, pair.length, at) !== pair.length) {
6992
+ throw new Error(`short write repointing module ${index} of ${path}`);
6993
+ }
6994
+ }
6995
+ } finally {
6996
+ closeSync2(fd);
6997
+ }
6998
+ }
6915
6999
  function shimEntryModuleName(path) {
6916
7000
  const fd = openSync2(path, "r+");
6917
7001
  try {
@@ -6944,7 +7028,17 @@ function restoreEntryModuleName(path, shim, { resign }) {
6944
7028
  }
6945
7029
  writeNameBytes(fd, shim.original, offset);
6946
7030
  restoreEveryStandIn(fd, statSync4(path).size, shim);
6947
- machO = resign && isMachO(fd);
7031
+ machO = resign;
7032
+ } finally {
7033
+ closeSync2(fd);
7034
+ }
7035
+ if (machO) resignMachOBinary(path);
7036
+ }
7037
+ function resignMachOBinary(path) {
7038
+ const fd = openSync2(path, "r");
7039
+ let machO;
7040
+ try {
7041
+ machO = isMachO(fd);
6948
7042
  } finally {
6949
7043
  closeSync2(fd);
6950
7044
  }
@@ -6953,6 +7047,99 @@ function restoreEntryModuleName(path, shim, { resign }) {
6953
7047
  }
6954
7048
  }
6955
7049
 
7050
+ // src/bun-bundle.ts
7051
+ function writableModuleIndex(path) {
7052
+ const table = readBunModuleTable(path);
7053
+ if (!table) return null;
7054
+ const index = table.names.findIndex(tweakccRecognizesModuleName);
7055
+ return index < 0 ? null : index;
7056
+ }
7057
+ var BUNDLE_MODULE_SEPARATOR = '\n/*clodex:module-boundary`;{}"]*/\n';
7058
+ function readClaudeBundle(path) {
7059
+ const modules = readBunJavaScriptModules(path);
7060
+ if (!modules || modules.length === 0) return null;
7061
+ if (modules.some((module) => module.source.includes(BUNDLE_MODULE_SEPARATOR))) return null;
7062
+ if (modules.some((module) => !roundTripsAsUtf8(module.source, module.byteLength))) return null;
7063
+ return { modules, source: modules.map((module) => module.source).join(BUNDLE_MODULE_SEPARATOR) };
7064
+ }
7065
+ function roundTripsAsUtf8(source, byteLength) {
7066
+ return Buffer.byteLength(source, "utf8") === byteLength && !source.includes("\uFFFD");
7067
+ }
7068
+ function splitBundleSource(bundle, patched) {
7069
+ const parts = patched.split(BUNDLE_MODULE_SEPARATOR);
7070
+ if (parts.length !== bundle.modules.length) {
7071
+ throw new Error(
7072
+ `the patched bundle has ${parts.length} module boundaries, expected ${bundle.modules.length}`
7073
+ );
7074
+ }
7075
+ return parts;
7076
+ }
7077
+ function planBundleWrite(bundle, patchedSources, writableIndex) {
7078
+ if (patchedSources.length !== bundle.modules.length) {
7079
+ throw new Error(
7080
+ `expected ${bundle.modules.length} patched module sources, got ${patchedSources.length}`
7081
+ );
7082
+ }
7083
+ const writable = bundle.modules.findIndex((module) => module.index === writableIndex);
7084
+ if (writable < 0) {
7085
+ throw new Error(`module ${writableIndex} is not one of this binary's JavaScript modules`);
7086
+ }
7087
+ const changed = bundle.modules.map((module, at) => at).filter((at) => at !== writable && patchedSources[at] !== bundle.modules[at].source);
7088
+ if (changed.length === 0) return { content: patchedSources[writable], repoints: [] };
7089
+ const order = [writable, ...changed];
7090
+ const repoints = [];
7091
+ let content = "";
7092
+ let start = 0;
7093
+ for (const at of order) {
7094
+ const source = patchedSources[at];
7095
+ const length = Buffer.byteLength(source, "utf8");
7096
+ repoints.push({ index: bundle.modules[at].index, start, length, expected: source });
7097
+ content += source + "\0";
7098
+ start += length + 1;
7099
+ }
7100
+ return { content, repoints };
7101
+ }
7102
+ function applyBundleWritePlan(path, plan) {
7103
+ const table = readBunModuleTable(path);
7104
+ if (!table) {
7105
+ if (plan.repoints.length === 0) return;
7106
+ throw new Error(`cannot read the Bun module table of ${path} after repacking`);
7107
+ }
7108
+ const recognized = table.names.filter(tweakccRecognizesModuleName);
7109
+ if (recognized.length !== 1) {
7110
+ throw new Error(
7111
+ `${path} has ${recognized.length} modules tweakcc would write to; the patch was planned around exactly one`
7112
+ );
7113
+ }
7114
+ if (plan.repoints.length === 0) return;
7115
+ const writable = table.names.findIndex(tweakccRecognizesModuleName);
7116
+ if (writable !== plan.repoints[0].index) {
7117
+ throw new Error(
7118
+ `${path} exposes module ${writable} to tweakcc, but the patch was planned around module ${plan.repoints[0].index}`
7119
+ );
7120
+ }
7121
+ const written = table.contents[writable];
7122
+ const expectedBytes = Buffer.byteLength(plan.content, "utf8");
7123
+ if (written.length !== expectedBytes) {
7124
+ throw new Error(
7125
+ `${path} holds ${written.length} bytes where the ${expectedBytes} bytes clodex wrote should be; refusing to repoint its modules`
7126
+ );
7127
+ }
7128
+ const edits = plan.repoints.map((repoint) => ({
7129
+ index: repoint.index,
7130
+ range: { offset: written.offset + repoint.start, length: repoint.length }
7131
+ }));
7132
+ repointBunModuleContents(path, edits);
7133
+ const after = readBunJavaScriptModules(path);
7134
+ if (!after) throw new Error(`cannot re-read the modules of ${path} after repointing them`);
7135
+ const byIndex = new Map(after.map((module) => [module.index, module.source]));
7136
+ for (const repoint of plan.repoints) {
7137
+ if (byIndex.get(repoint.index) !== repoint.expected) {
7138
+ throw new Error(`module ${repoint.index} of ${path} did not read back as the patched source`);
7139
+ }
7140
+ }
7141
+ }
7142
+
6956
7143
  // src/patch-backup.ts
6957
7144
  import { createHash as createHash5 } from "crypto";
6958
7145
  import { existsSync as existsSync4, readFileSync as readFileSync7, readdirSync, statSync as statSync5 } from "fs";
@@ -12304,9 +12491,15 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
12304
12491
  copyFileSync(from, candidatePath);
12305
12492
  const shim = shimEntryModuleName(candidatePath);
12306
12493
  const installation = await tryDetectInstallation({ path: candidatePath });
12307
- const source = await readContent(installation);
12494
+ const bundle = readClaudeBundle(candidatePath);
12495
+ if (!bundle && installation.kind === "native") {
12496
+ p2.log.warn(
12497
+ `Could not read ${candidatePath} as a Bun module table, so only the module tweakcc names is being patched. On Claude Code 2.1.242 and later that module is a stub holding none of the code clodex patches, and the patch below will fail at its first required site \u2014 the cause is this read, not a changed anchor.`
12498
+ );
12499
+ }
12500
+ const source = bundle ? bundle.source : await readContent(installation);
12308
12501
  if (shim) restoreEntryModuleName(candidatePath, shim, { resign: false });
12309
- return { installation, source };
12502
+ return { installation, source, bundle };
12310
12503
  };
12311
12504
  const facts = collectPristineFacts({
12312
12505
  version,
@@ -12399,8 +12592,25 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
12399
12592
  }
12400
12593
  results = [...results, ...local.results];
12401
12594
  const writeShim = shimEntryModuleName(candidatePath);
12402
- await writeContent(loaded.installation, local.content);
12595
+ let repointed = false;
12596
+ if (loaded.bundle) {
12597
+ const writable = writableModuleIndex(candidatePath);
12598
+ if (writable === null) {
12599
+ throw new Error("no module of the patch candidate carries a name tweakcc can write to");
12600
+ }
12601
+ const plan2 = planBundleWrite(
12602
+ loaded.bundle,
12603
+ splitBundleSource(loaded.bundle, local.content),
12604
+ writable
12605
+ );
12606
+ await writeContent(loaded.installation, plan2.content);
12607
+ applyBundleWritePlan(candidatePath, plan2);
12608
+ repointed = plan2.repoints.length > 0;
12609
+ } else {
12610
+ await writeContent(loaded.installation, local.content);
12611
+ }
12403
12612
  if (writeShim) restoreEntryModuleName(candidatePath, writeShim, { resign: true });
12613
+ else if (repointed) resignMachOBinary(candidatePath);
12404
12614
  patchedSize = statSync6(candidatePath).size;
12405
12615
  patchedSha256 = sha256File(candidatePath);
12406
12616
  renameSync2(candidatePath, binaryPath);