@bman654/clodex 0.1.1 → 1.0.2
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 +4 -2
- package/dist/cli.js +214 -345
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -202,7 +202,7 @@ import { join } from "path";
|
|
|
202
202
|
// package.json
|
|
203
203
|
var package_default = {
|
|
204
204
|
name: "@bman654/clodex",
|
|
205
|
-
version: "0.
|
|
205
|
+
version: "1.0.2",
|
|
206
206
|
publishConfig: {
|
|
207
207
|
access: "public"
|
|
208
208
|
},
|
|
@@ -256,6 +256,7 @@ var package_default = {
|
|
|
256
256
|
"node-forge": "1.4.0",
|
|
257
257
|
open: "11.0.0",
|
|
258
258
|
picocolors: "1.1.1",
|
|
259
|
+
tweakcc: "4.3.0",
|
|
259
260
|
undici: "7.28.0",
|
|
260
261
|
ws: "8.21.0"
|
|
261
262
|
},
|
|
@@ -10516,14 +10517,12 @@ function planLaunchWizard(opts) {
|
|
|
10516
10517
|
}
|
|
10517
10518
|
|
|
10518
10519
|
// src/patcher.ts
|
|
10519
|
-
import { spawn } from "child_process";
|
|
10520
10520
|
import { createHash as createHash5 } from "crypto";
|
|
10521
10521
|
import {
|
|
10522
10522
|
copyFileSync as copyFileSync2,
|
|
10523
10523
|
existsSync as existsSync6,
|
|
10524
10524
|
mkdirSync as mkdirSync6,
|
|
10525
10525
|
readFileSync as readFileSync7,
|
|
10526
|
-
rmSync,
|
|
10527
10526
|
statSync as statSync2,
|
|
10528
10527
|
unlinkSync as unlinkSync2,
|
|
10529
10528
|
writeFileSync as writeFileSync5,
|
|
@@ -10531,313 +10530,189 @@ import {
|
|
|
10531
10530
|
closeSync as closeSync3,
|
|
10532
10531
|
realpathSync
|
|
10533
10532
|
} from "fs";
|
|
10534
|
-
import { homedir as homedir2
|
|
10533
|
+
import { homedir as homedir2 } from "os";
|
|
10535
10534
|
import { basename, join as join6 } from "path";
|
|
10536
10535
|
import pc12 from "picocolors";
|
|
10537
10536
|
import * as p11 from "@clack/prompts";
|
|
10538
10537
|
|
|
10539
|
-
// src/patch-
|
|
10540
|
-
var
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
// clodex:config:start
|
|
10548
|
-
const MODEL_CONFIG = {};
|
|
10549
|
-
// clodex:config:end
|
|
10550
|
-
|
|
10551
|
-
// ---- derive helpers --------------------------------------------------------
|
|
10552
|
-
// alias -> model id (only for entries that define an alias)
|
|
10553
|
-
const ALIAS_TO_ID = {};
|
|
10554
|
-
// The name Claude Code knows a model by: its alias when it has one, else its
|
|
10555
|
-
// canonical id. This single value is used for the Agent-tool enum, the
|
|
10556
|
-
// known-alias validator, the /model picker value, and the context-window table,
|
|
10557
|
-
// so the name the binary validates == the name it sends upstream == the name
|
|
10558
|
-
// the proxy echoes back == the key its context window is stored under.
|
|
10559
|
-
const IDENTITIES = [];
|
|
10560
|
-
// identity -> human label for the /model picker (falls back at use site)
|
|
10561
|
-
const DISPLAY_BY_IDENTITY = {};
|
|
10562
|
-
// lowercased alias AND id -> context-window tokens (only for models that set it)
|
|
10563
|
-
const CONTEXT_BY_KEY = {};
|
|
10564
|
-
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
10565
|
-
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
10566
|
-
if (spec.alias !== undefined) {
|
|
10567
|
-
const a = String(spec.alias).trim().toLowerCase();
|
|
10568
|
-
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
10569
|
-
throw new Error('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
10570
|
-
}
|
|
10571
|
-
ALIAS_TO_ID[a] = String(id);
|
|
10572
|
-
IDENTITIES.push(a);
|
|
10573
|
-
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
10574
|
-
} else {
|
|
10575
|
-
IDENTITIES.push(String(id));
|
|
10576
|
-
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
10538
|
+
// src/patch-transforms.ts
|
|
10539
|
+
var PatchApplyError = class extends Error {
|
|
10540
|
+
results;
|
|
10541
|
+
constructor(message, results) {
|
|
10542
|
+
super(message);
|
|
10543
|
+
this.name = "PatchApplyError";
|
|
10544
|
+
this.results = results;
|
|
10577
10545
|
}
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10546
|
+
};
|
|
10547
|
+
function formatPatchSiteLine(result) {
|
|
10548
|
+
return " " + result.status.padEnd(4) + " " + result.name + (result.extra ? " \u2014 " + result.extra : "");
|
|
10549
|
+
}
|
|
10550
|
+
function applyClodexPatches(source, config) {
|
|
10551
|
+
let js = source;
|
|
10552
|
+
const MODEL_CONFIG = config;
|
|
10553
|
+
const ALIAS_TO_ID = {};
|
|
10554
|
+
const IDENTITIES = [];
|
|
10555
|
+
const DISPLAY_BY_IDENTITY = {};
|
|
10556
|
+
const CONTEXT_BY_KEY = {};
|
|
10557
|
+
const report = [];
|
|
10558
|
+
const fail = (message) => {
|
|
10559
|
+
throw new PatchApplyError(message, report);
|
|
10560
|
+
};
|
|
10561
|
+
for (const [id, value] of Object.entries(MODEL_CONFIG)) {
|
|
10562
|
+
const spec = value && typeof value === "object" ? value : { alias: value };
|
|
10563
|
+
if (spec.alias !== void 0) {
|
|
10564
|
+
const a = String(spec.alias).trim().toLowerCase();
|
|
10565
|
+
if (!/^[a-z0-9][a-z0-9._-]*(\[1m\])?$/.test(a)) {
|
|
10566
|
+
fail('clodex patch: alias "' + spec.alias + '" is not a safe lowercase alias');
|
|
10567
|
+
}
|
|
10568
|
+
ALIAS_TO_ID[a] = String(id);
|
|
10569
|
+
IDENTITIES.push(a);
|
|
10570
|
+
if (spec.display) DISPLAY_BY_IDENTITY[a] = String(spec.display);
|
|
10571
|
+
} else {
|
|
10572
|
+
IDENTITIES.push(String(id));
|
|
10573
|
+
if (spec.display) DISPLAY_BY_IDENTITY[String(id)] = String(spec.display);
|
|
10583
10574
|
}
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10575
|
+
if (spec.context !== void 0) {
|
|
10576
|
+
const n = Number(spec.context);
|
|
10577
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
10578
|
+
fail('clodex patch: context for "' + id + '" must be a positive integer, got ' + spec.context);
|
|
10579
|
+
}
|
|
10580
|
+
if (/\[1m\]/i.test(String(spec.alias ?? "")) || /\[1m\]/i.test(id)) {
|
|
10581
|
+
fail(
|
|
10582
|
+
'clodex patch: "' + id + '" sets context but keeps the [1m] suffix \u2014 drop the suffix from both the id and the alias'
|
|
10583
|
+
);
|
|
10584
|
+
}
|
|
10585
|
+
if (spec.alias !== void 0) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
10586
|
+
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
10591
10587
|
}
|
|
10592
|
-
if (spec.alias !== undefined) CONTEXT_BY_KEY[String(spec.alias).trim().toLowerCase()] = n;
|
|
10593
|
-
CONTEXT_BY_KEY[String(id).trim().toLowerCase()] = n;
|
|
10594
|
-
}
|
|
10595
|
-
}
|
|
10596
|
-
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
10597
|
-
const MODELS = Object.keys(MODEL_CONFIG);
|
|
10598
|
-
if (MODELS.length === 0) throw new Error("clodex patch: MODEL_CONFIG is empty");
|
|
10599
|
-
|
|
10600
|
-
/** Picker/description label for an identity; falls back to the old wording. */
|
|
10601
|
-
function displayFor(identity, fallbackId) {
|
|
10602
|
-
return DISPLAY_BY_IDENTITY[identity] || "Custom model (" + fallbackId + ")";
|
|
10603
|
-
}
|
|
10604
|
-
|
|
10605
|
-
const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
|
|
10606
|
-
const q = (s) => JSON.stringify(s); // safe JS string literal
|
|
10607
|
-
|
|
10608
|
-
// ---- reporting -------------------------------------------------------------
|
|
10609
|
-
const report = [];
|
|
10610
|
-
function log(status, name, extra) {
|
|
10611
|
-
report.push({ status, name });
|
|
10612
|
-
const line = " " + status.padEnd(4) + " " + name + (extra ? " — " + extra : "");
|
|
10613
|
-
// NOTE: tweakcc reads this script's STDOUT as the JSON result, so every
|
|
10614
|
-
// diagnostic MUST go to stderr — a stray stdout write corrupts the output.
|
|
10615
|
-
console.error(line);
|
|
10616
|
-
}
|
|
10617
|
-
|
|
10618
|
-
/**
|
|
10619
|
-
* Apply exactly one regex replacement.
|
|
10620
|
-
* - marker: if present in js, treat as already-patched -> SKIP.
|
|
10621
|
-
* - expects exactly one match; 0 -> FAIL, >1 -> FAIL (ambiguous).
|
|
10622
|
-
* - fn(match, ...groups) returns the replacement text.
|
|
10623
|
-
* - required: on FAIL, throw (aborts the whole adhoc-patch).
|
|
10624
|
-
*/
|
|
10625
|
-
function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
|
|
10626
|
-
if (marker && js.includes(marker)) { log("SKIP", name, "already patched"); return; }
|
|
10627
|
-
const g = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g");
|
|
10628
|
-
const matches = js.match(g);
|
|
10629
|
-
const count = matches ? matches.length : 0;
|
|
10630
|
-
if (count === 0) {
|
|
10631
|
-
log("FAIL", name, "anchor not found");
|
|
10632
|
-
if (required) throw new Error("clodex patch: required patch failed: " + name);
|
|
10633
|
-
return;
|
|
10634
10588
|
}
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10589
|
+
const ALIASES = Object.keys(ALIAS_TO_ID);
|
|
10590
|
+
const MODELS = Object.keys(MODEL_CONFIG);
|
|
10591
|
+
if (MODELS.length === 0) fail("clodex patch: MODEL_CONFIG is empty");
|
|
10592
|
+
function displayFor(identity, fallbackId) {
|
|
10593
|
+
return DISPLAY_BY_IDENTITY[identity] || "Custom model (" + fallbackId + ")";
|
|
10639
10594
|
}
|
|
10640
|
-
const
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
// already present (anchor matched, but fn had nothing new to add) -> SKIP.
|
|
10645
|
-
if (noopIsSkip) { log("SKIP", name, "already patched"); return; }
|
|
10646
|
-
log("FAIL", name, "replacement made no change");
|
|
10647
|
-
if (required) throw new Error(name);
|
|
10648
|
-
return;
|
|
10595
|
+
const reEsc = (s) => s.replace(/[.*+?^$\{\}()|[\]\\]/g, "\\$&");
|
|
10596
|
+
const q = (s) => JSON.stringify(s);
|
|
10597
|
+
function log12(status, name, extra) {
|
|
10598
|
+
report.push(extra === void 0 ? { status, name } : { status, name, extra });
|
|
10649
10599
|
}
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
// PATCH 3 — known-alias validator list (drives "is this a known alias?").
|
|
10681
|
-
// Anchor: the master list literal, matched loosely as
|
|
10682
|
-
// ["sonnet","opus","haiku","fable", ...anything... ,"opusplan"] so it
|
|
10683
|
-
// tolerates new built-ins being added in the middle. Appending our identities
|
|
10684
|
-
// makes them recognized as first-class aliases everywhere the gate runs.
|
|
10685
|
-
// ---------------------------------------------------------------------------
|
|
10686
|
-
applyOnce(
|
|
10687
|
-
"PATCH 3: known-alias validator list",
|
|
10688
|
-
/\["sonnet","opus","haiku","fable"(?:,"[^"]+")*,"opusplan"(?:,"[^"]+")*\]/,
|
|
10689
|
-
(m) => extendAliasArray(m),
|
|
10690
|
-
{ required: true, noopIsSkip: true }
|
|
10691
|
-
);
|
|
10692
|
-
|
|
10693
|
-
// ---------------------------------------------------------------------------
|
|
10694
|
-
// PATCH 6 — alias resolver switch (IDENTITY mapping).
|
|
10695
|
-
// Anchor: case"best":{ ... } (the case"best":{ is unique). We inject
|
|
10696
|
-
// case"<alias>":return"<alias>"; right after it (before the switch's
|
|
10697
|
-
// default:return null).
|
|
10698
|
-
//
|
|
10699
|
-
// The mapping is deliberately an identity, NOT alias -> canonical id: the alias
|
|
10700
|
-
// IS the model's identity everywhere else in the patched binary (enum,
|
|
10701
|
-
// validator, picker, context table), and the MITM proxy resolves short alias
|
|
10702
|
-
// names as request model ids and echoes request bodies unrewritten. Resolving
|
|
10703
|
-
// to the canonical id here would make Claude Code send one name and look its
|
|
10704
|
-
// context window up under another — the exact mismatch that stopped auto-compact
|
|
10705
|
-
// from firing and killed agents with "Prompt is too long". The case still has to
|
|
10706
|
-
// EXIST (rather than be skipped) so the resolver returns the name instead of
|
|
10707
|
-
// falling through to default:return null.
|
|
10708
|
-
// Only aliases not already present are inserted, so a rerun (or a config
|
|
10709
|
-
// edit) tops up cleanly rather than duplicating cases.
|
|
10710
|
-
// ---------------------------------------------------------------------------
|
|
10711
|
-
{
|
|
10712
|
-
const missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
|
|
10713
|
-
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
10714
|
-
if (ALIASES.length === 0) {
|
|
10715
|
-
log("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
10716
|
-
} else {
|
|
10717
|
-
applyOnce(
|
|
10718
|
-
"PATCH 6: alias resolver switch",
|
|
10719
|
-
/(case"best":\{[^{}]*\})/,
|
|
10720
|
-
(m) => m + cases,
|
|
10721
|
-
{ required: true, noopIsSkip: true }
|
|
10722
|
-
);
|
|
10600
|
+
function applyOnce(name, regex, fn, { marker, required, noopIsSkip } = {}) {
|
|
10601
|
+
if (marker && js.includes(marker)) {
|
|
10602
|
+
log12("SKIP", name, "already patched");
|
|
10603
|
+
return;
|
|
10604
|
+
}
|
|
10605
|
+
const g = new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : regex.flags + "g");
|
|
10606
|
+
const matches = js.match(g);
|
|
10607
|
+
const count = matches ? matches.length : 0;
|
|
10608
|
+
if (count === 0) {
|
|
10609
|
+
log12("FAIL", name, "anchor not found");
|
|
10610
|
+
if (required) fail("clodex patch: required patch failed: " + name);
|
|
10611
|
+
return;
|
|
10612
|
+
}
|
|
10613
|
+
if (count > 1) {
|
|
10614
|
+
log12("FAIL", name, "anchor matched " + count + " times (expected 1)");
|
|
10615
|
+
if (required) fail("clodex patch: ambiguous anchor: " + name);
|
|
10616
|
+
return;
|
|
10617
|
+
}
|
|
10618
|
+
const before = js;
|
|
10619
|
+
js = js.replace(regex, fn);
|
|
10620
|
+
if (js === before) {
|
|
10621
|
+
if (noopIsSkip) {
|
|
10622
|
+
log12("SKIP", name, "already patched");
|
|
10623
|
+
return;
|
|
10624
|
+
}
|
|
10625
|
+
log12("FAIL", name, "replacement made no change");
|
|
10626
|
+
if (required) fail(name);
|
|
10627
|
+
return;
|
|
10628
|
+
}
|
|
10629
|
+
log12("OK", name);
|
|
10723
10630
|
}
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10631
|
+
function extendAliasArray(arrLiteral) {
|
|
10632
|
+
const toAdd = IDENTITIES.filter((a) => !new RegExp('"' + reEsc(a) + '"').test(arrLiteral));
|
|
10633
|
+
if (toAdd.length === 0) return arrLiteral;
|
|
10634
|
+
return arrLiteral.replace(/\]\s*$/, "," + toAdd.map(q).join(",") + "]");
|
|
10635
|
+
}
|
|
10636
|
+
applyOnce(
|
|
10637
|
+
"PATCH 1: Agent tool model enum",
|
|
10638
|
+
/\.enum\((\["sonnet","opus","haiku"(?:,"[^"]+")*\])\)\.optional\(\)\.describe\(/,
|
|
10639
|
+
(_m, arr) => ".enum(" + extendAliasArray(arr) + ").optional().describe(",
|
|
10640
|
+
{ required: true, noopIsSkip: true }
|
|
10641
|
+
);
|
|
10642
|
+
applyOnce(
|
|
10643
|
+
"PATCH 3: known-alias validator list",
|
|
10644
|
+
/\["sonnet","opus","haiku","fable"(?:,"[^"]+")*,"opusplan"(?:,"[^"]+")*\]/,
|
|
10645
|
+
(m) => extendAliasArray(m),
|
|
10646
|
+
{ required: true, noopIsSkip: true }
|
|
10647
|
+
);
|
|
10648
|
+
{
|
|
10649
|
+
const missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
|
|
10650
|
+
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
10651
|
+
if (ALIASES.length === 0) {
|
|
10652
|
+
log12("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
10653
|
+
} else {
|
|
10654
|
+
applyOnce(
|
|
10655
|
+
"PATCH 6: alias resolver switch",
|
|
10656
|
+
/(case"best":\{[^{}]*\})/,
|
|
10657
|
+
(m) => m + cases,
|
|
10658
|
+
{ required: true, noopIsSkip: true }
|
|
10659
|
+
);
|
|
10660
|
+
}
|
|
10661
|
+
}
|
|
10662
|
+
{
|
|
10663
|
+
const missing = ALIASES.filter((a) => !new RegExp("value:" + reEsc(q(a))).test(js));
|
|
10664
|
+
const entries = missing.map(
|
|
10740
10665
|
// value = the alias (the name the user types and the binary sends);
|
|
10741
10666
|
// description = the real model label, e.g. "GPT-5.6 Sol (OpenAI (ChatGPT))".
|
|
10667
|
+
// (tweakcc's writeContent round-trips utf8 faithfully — verified — so the
|
|
10668
|
+
// old adhoc-patch ASCII-only constraint no longer applies.)
|
|
10742
10669
|
(a) => "{value:" + q(a) + ",label:" + q(a.charAt(0).toUpperCase() + a.slice(1)) + ",description:" + q(displayFor(a, ALIAS_TO_ID[a])) + "}"
|
|
10743
|
-
)
|
|
10744
|
-
.
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
{ required: false, noopIsSkip: true }
|
|
10756
|
-
);
|
|
10670
|
+
).join(",");
|
|
10671
|
+
const inject = missing.length ? "[" + entries + "].forEach(function(_o){if(!e.some(function(_i){return _i.value===_o.value}))e.push(_o)});" : "";
|
|
10672
|
+
if (ALIASES.length === 0) {
|
|
10673
|
+
log12("SKIP", "PATCH 5: model picker options", "no aliases configured");
|
|
10674
|
+
} else {
|
|
10675
|
+
applyOnce(
|
|
10676
|
+
"PATCH 5: model picker options",
|
|
10677
|
+
/(\?\[[\w$]+,r\]:\[r\];for\(let [\w$]+ of [\w$]+\)[\w$]+\(e,[\w$]+,t\);)/,
|
|
10678
|
+
(m) => m + inject,
|
|
10679
|
+
{ required: false, noopIsSkip: true }
|
|
10680
|
+
);
|
|
10681
|
+
}
|
|
10757
10682
|
}
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
// actually are. Best-effort (cosmetic). The text is spliced into a backtick
|
|
10765
|
-
// template literal, so backticks and interpolation openers are stripped.
|
|
10766
|
-
// ---------------------------------------------------------------------------
|
|
10767
|
-
{
|
|
10768
|
-
const safe = (s) => String(s).replace(/\`/g, "'").replace(/\$\{/g, "(");
|
|
10769
|
-
const listing = IDENTITIES.map(function (i) {
|
|
10770
|
-
const d = DISPLAY_BY_IDENTITY[i];
|
|
10771
|
-
return d ? safe(i) + " = " + safe(d) : safe(i);
|
|
10772
|
-
}).join("; ");
|
|
10773
|
-
applyOnce(
|
|
10774
|
-
"PATCH 4: Agent tool model description",
|
|
10775
|
-
/(describe\(\`Optional model override for this agent[^\`]*?)(\`\))/,
|
|
10776
|
-
(_m, body, close) =>
|
|
10777
|
-
body.includes("Additional custom models")
|
|
10778
|
-
? body + close
|
|
10779
|
-
: body + " Additional custom models: " + listing + "." + close,
|
|
10780
|
-
{ required: false, noopIsSkip: true }
|
|
10781
|
-
);
|
|
10782
|
-
}
|
|
10783
|
-
|
|
10784
|
-
// ---------------------------------------------------------------------------
|
|
10785
|
-
// PATCH 7 — per-model context window.
|
|
10786
|
-
//
|
|
10787
|
-
// Claude Code funnels EVERY context-window consumer (autocompact threshold,
|
|
10788
|
-
// /context, the countdown, statusline, cost/usage records, subagent budgets)
|
|
10789
|
-
// through one resolver function. We inject a baked table lookup at the TOP of
|
|
10790
|
-
// that resolver, so it wins over the 200k clamp and the global
|
|
10791
|
-
// CLAUDE_CODE_MAX_CONTEXT_TOKENS env override. Lookup is on the raw,
|
|
10792
|
-
// lowercased model string — alias and id are both in the table, so it hits
|
|
10793
|
-
// pre- or post-alias-resolution.
|
|
10794
|
-
//
|
|
10795
|
-
// Anchor: the resolver's exact body shape. Identifiers are wildcarded (they
|
|
10796
|
-
// churn per build); the (e,t) arity + 3-statement shape matches once.
|
|
10797
|
-
// ---------------------------------------------------------------------------
|
|
10798
|
-
if (Object.keys(CONTEXT_BY_KEY).length) {
|
|
10799
|
-
const MARKER = "/*ccpatch:ctx*/";
|
|
10800
|
-
const SNIPPET =
|
|
10801
|
-
MARKER + 'var _ccw=(' + JSON.stringify(CONTEXT_BY_KEY) + ')[String(e||"").trim().toLowerCase()];if(_ccw!==void 0)return _ccw;';
|
|
10802
|
-
|
|
10803
|
-
if (js.includes(MARKER)) {
|
|
10804
|
-
// Re-patching an already-patched binary: refresh the baked table in place
|
|
10805
|
-
// so a MODEL_CONFIG edit takes effect without a restore first.
|
|
10806
|
-
applyOnce(
|
|
10807
|
-
"PATCH 7: per-model context window (refresh)",
|
|
10808
|
-
/\/\*ccpatch:ctx\*\/var _ccw=\(\{[^{}]*\}\)\[[^\]]*\];if\(_ccw!==void 0\)return _ccw;/,
|
|
10809
|
-
() => SNIPPET,
|
|
10810
|
-
{ required: true, noopIsSkip: true }
|
|
10811
|
-
);
|
|
10812
|
-
} else {
|
|
10683
|
+
{
|
|
10684
|
+
const safe = (s) => String(s).replace(/`/g, "'").replace(/\$\{/g, "(");
|
|
10685
|
+
const listing = IDENTITIES.map(function(i) {
|
|
10686
|
+
const d = DISPLAY_BY_IDENTITY[i];
|
|
10687
|
+
return d ? safe(i) + " = " + safe(d) : safe(i);
|
|
10688
|
+
}).join("; ");
|
|
10813
10689
|
applyOnce(
|
|
10814
|
-
"PATCH
|
|
10815
|
-
/(
|
|
10816
|
-
(_m,
|
|
10817
|
-
{ required: true }
|
|
10690
|
+
"PATCH 4: Agent tool model description",
|
|
10691
|
+
/(describe\(`Optional model override for this agent[^`]*?)(`\))/,
|
|
10692
|
+
(_m, body, close) => body.includes("Additional custom models") ? body + close : body + " Additional custom models: " + listing + "." + close,
|
|
10693
|
+
{ required: false, noopIsSkip: true }
|
|
10818
10694
|
);
|
|
10819
10695
|
}
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10696
|
+
if (Object.keys(CONTEXT_BY_KEY).length) {
|
|
10697
|
+
const MARKER = "/*ccpatch:ctx*/";
|
|
10698
|
+
const SNIPPET = MARKER + "var _ccw=(" + JSON.stringify(CONTEXT_BY_KEY) + ')[String(e||"").trim().toLowerCase()];if(_ccw!==void 0)return _ccw;';
|
|
10699
|
+
if (js.includes(MARKER)) {
|
|
10700
|
+
applyOnce(
|
|
10701
|
+
"PATCH 7: per-model context window (refresh)",
|
|
10702
|
+
/\/\*ccpatch:ctx\*\/var _ccw=\(\{[^{}]*\}\)\[[^\]]*\];if\(_ccw!==void 0\)return _ccw;/,
|
|
10703
|
+
() => SNIPPET,
|
|
10704
|
+
{ required: true, noopIsSkip: true }
|
|
10705
|
+
);
|
|
10706
|
+
} else {
|
|
10707
|
+
applyOnce(
|
|
10708
|
+
"PATCH 7: per-model context window",
|
|
10709
|
+
/(function [\w$]+\(e,t\)\{)(let [\w$]+=[\w$]+\(\);if\([\w$]+!==void 0\)return [\w$]+;if\([\w$]+\(e,t\)\)return [\w$]+;return [\w$]+\(e,t\)\})/,
|
|
10710
|
+
(_m, head, body) => head + SNIPPET + body,
|
|
10711
|
+
{ required: true }
|
|
10712
|
+
);
|
|
10713
|
+
}
|
|
10836
10714
|
}
|
|
10837
|
-
|
|
10838
|
-
const MODEL_CONFIG = ${JSON.stringify(config, null, 2)};
|
|
10839
|
-
${CLODEX_CONFIG_END}`;
|
|
10840
|
-
return TEMPLATE.slice(0, start) + block + TEMPLATE.slice(end + CLODEX_CONFIG_END.length);
|
|
10715
|
+
return { content: js, results: report };
|
|
10841
10716
|
}
|
|
10842
10717
|
|
|
10843
10718
|
// src/patcher.ts
|
|
@@ -10988,29 +10863,16 @@ function pristineBackupPath(version, binaryPath) {
|
|
|
10988
10863
|
const tag = version.replace(/[^\w.-]+/g, "_") || basename(binaryPath);
|
|
10989
10864
|
return join6(backupDir(), `claude-${tag}.orig`);
|
|
10990
10865
|
}
|
|
10991
|
-
function
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
let output = "";
|
|
11002
|
-
child.stdout.on("data", (chunk) => {
|
|
11003
|
-
output += String(chunk);
|
|
11004
|
-
});
|
|
11005
|
-
child.stderr.on("data", (chunk) => {
|
|
11006
|
-
output += String(chunk);
|
|
11007
|
-
});
|
|
11008
|
-
child.on("error", reject);
|
|
11009
|
-
child.on("close", (code) => resolve2({ code: code ?? 1, output }));
|
|
11010
|
-
});
|
|
11011
|
-
}
|
|
11012
|
-
function summarizePatchOutput(output) {
|
|
11013
|
-
return output.split(/\r?\n/).filter((line) => /^\s*(OK|SKIP|FAIL)\s{2,}/.test(line) || /^clodex patch:/.test(line.trim())).map((line) => line.trimEnd());
|
|
10866
|
+
function summarizePatchResults(results) {
|
|
10867
|
+
const lines = results.map(formatPatchSiteLine);
|
|
10868
|
+
const ok = results.filter((r) => r.status === "OK").length;
|
|
10869
|
+
const skip = results.filter((r) => r.status === "SKIP").length;
|
|
10870
|
+
const failed = results.filter((r) => r.status === "FAIL");
|
|
10871
|
+
lines.push(`clodex patch: ${ok} applied, ${skip} skipped, ${failed.length} failed`);
|
|
10872
|
+
if (failed.length) {
|
|
10873
|
+
lines.push(`clodex patch: FAILED patches: ${failed.map((f) => f.name).join("; ")}`);
|
|
10874
|
+
}
|
|
10875
|
+
return lines;
|
|
11014
10876
|
}
|
|
11015
10877
|
async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
11016
10878
|
const backup = pristineBackupPath(version, binaryPath);
|
|
@@ -11024,41 +10886,48 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
11024
10886
|
copyFileSync2(binaryPath, backup);
|
|
11025
10887
|
}
|
|
11026
10888
|
copyFileSync2(backup, join6(backupDir(), "native-binary.backup"));
|
|
11027
|
-
const
|
|
11028
|
-
|
|
10889
|
+
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
10890
|
+
let results;
|
|
11029
10891
|
try {
|
|
11030
|
-
const
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
10892
|
+
const installation = await tryDetectInstallation({ path: binaryPath });
|
|
10893
|
+
const source = await readContent(installation);
|
|
10894
|
+
const patched = applyClodexPatches(source, desired.config);
|
|
10895
|
+
results = patched.results;
|
|
10896
|
+
await writeContent(installation, patched.content);
|
|
10897
|
+
} catch (err) {
|
|
10898
|
+
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
10899
|
+
if (opts.trace && detailLines.length) {
|
|
10900
|
+
process.stderr.write(`${detailLines.join("\n")}
|
|
10901
|
+
`);
|
|
11040
10902
|
}
|
|
11041
|
-
const manifest = {
|
|
11042
|
-
binaryPath,
|
|
11043
|
-
claudeVersion: version,
|
|
11044
|
-
configHash,
|
|
11045
|
-
patchedSize: statSync2(binaryPath).size,
|
|
11046
|
-
patchedSha256: sha256File(binaryPath),
|
|
11047
|
-
backupPath: backup,
|
|
11048
|
-
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11049
|
-
};
|
|
11050
|
-
writePatchManifest(manifest);
|
|
11051
|
-
const modelCount = Object.keys(desired.config).length;
|
|
11052
|
-
const aliasCount = Object.values(desired.config).filter((entry) => entry.alias).length;
|
|
11053
|
-
const windowCount = Object.values(desired.config).filter((entry) => entry.context).length;
|
|
11054
10903
|
return {
|
|
11055
|
-
ok:
|
|
11056
|
-
message: `
|
|
11057
|
-
detailLines
|
|
10904
|
+
ok: false,
|
|
10905
|
+
message: `Patch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
10906
|
+
detailLines
|
|
11058
10907
|
};
|
|
11059
|
-
} finally {
|
|
11060
|
-
rmSync(scriptPath, { force: true });
|
|
11061
10908
|
}
|
|
10909
|
+
if (opts.trace) {
|
|
10910
|
+
process.stderr.write(`${summarizePatchResults(results).join("\n")}
|
|
10911
|
+
`);
|
|
10912
|
+
}
|
|
10913
|
+
const manifest = {
|
|
10914
|
+
binaryPath,
|
|
10915
|
+
claudeVersion: version,
|
|
10916
|
+
configHash,
|
|
10917
|
+
patchedSize: statSync2(binaryPath).size,
|
|
10918
|
+
patchedSha256: sha256File(binaryPath),
|
|
10919
|
+
backupPath: backup,
|
|
10920
|
+
patchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10921
|
+
};
|
|
10922
|
+
writePatchManifest(manifest);
|
|
10923
|
+
const modelCount = Object.keys(desired.config).length;
|
|
10924
|
+
const aliasCount = Object.values(desired.config).filter((entry) => entry.alias).length;
|
|
10925
|
+
const windowCount = Object.values(desired.config).filter((entry) => entry.context).length;
|
|
10926
|
+
return {
|
|
10927
|
+
ok: true,
|
|
10928
|
+
message: `Patched claude ${version}: ${modelCount} model${modelCount === 1 ? "" : "s"}, ${aliasCount} alias${aliasCount === 1 ? "" : "es"}, ${windowCount} context window${windowCount === 1 ? "" : "s"}.`,
|
|
10929
|
+
detailLines: summarizePatchResults(results)
|
|
10930
|
+
};
|
|
11062
10931
|
}
|
|
11063
10932
|
async function runPatchCommand(opts = {}) {
|
|
11064
10933
|
const resolved = resolveClaudeBinaryForPatch();
|
|
@@ -11609,7 +11478,7 @@ ${pc13.bold("Usage:")}
|
|
|
11609
11478
|
|
|
11610
11479
|
${pc13.bold("Options:")}
|
|
11611
11480
|
--restore Restore the pristine (unpatched) Claude Code binary
|
|
11612
|
-
--trace Show
|
|
11481
|
+
--trace Show per-patch-site results (OK/SKIP/FAIL)
|
|
11613
11482
|
|
|
11614
11483
|
${pc13.bold("Behavior:")}
|
|
11615
11484
|
The patch map is built automatically from your clodex favorites and aliases
|