patch-cc 0.2.4__tar.gz → 0.3.0__tar.gz

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.
Files changed (50) hide show
  1. {patch_cc-0.2.4 → patch_cc-0.3.0}/AGENTS.md +7 -0
  2. {patch_cc-0.2.4 → patch_cc-0.3.0}/PKG-INFO +12 -6
  3. {patch_cc-0.2.4 → patch_cc-0.3.0}/README.md +9 -5
  4. {patch_cc-0.2.4 → patch_cc-0.3.0}/docs/CONDUCT.md +31 -13
  5. {patch_cc-0.2.4 → patch_cc-0.3.0}/docs/INTERNALS.md +56 -23
  6. patch_cc-0.3.0/docs/PLAYBOOK.md +1227 -0
  7. patch_cc-0.3.0/docs/corpus.md +81 -0
  8. {patch_cc-0.2.4 → patch_cc-0.3.0}/pyproject.toml +17 -1
  9. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/__init__.py +1 -1
  10. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/blob.py +70 -26
  11. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/container.py +56 -12
  12. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/elf.py +17 -0
  13. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/macho.py +9 -0
  14. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/cache.py +34 -47
  15. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/cli.py +79 -25
  16. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/codex/__init__.py +11 -0
  17. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/codex/gateway.py +33 -10
  18. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/codex/models.py +8 -6
  19. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/codex/oauth.py +44 -4
  20. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/codex/translate.py +76 -21
  21. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/doctor.py +71 -14
  22. patch_cc-0.3.0/src/patch_cc/js.py +1007 -0
  23. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/locate.py +12 -1
  24. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/menu.py +32 -27
  25. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/patcher.py +79 -50
  26. patch_cc-0.3.0/src/patch_cc/patches/agents.py +577 -0
  27. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/patches/base.py +150 -102
  28. patch_cc-0.3.0/src/patch_cc/patches/chrome.py +450 -0
  29. patch_cc-0.3.0/src/patch_cc/patches/codex.py +879 -0
  30. patch_cc-0.3.0/src/patch_cc/patches/output.py +258 -0
  31. patch_cc-0.3.0/src/patch_cc/patches/streaming.py +1024 -0
  32. patch_cc-0.3.0/src/patch_cc/patches/thinking.py +527 -0
  33. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/ui.py +44 -0
  34. {patch_cc-0.2.4 → patch_cc-0.3.0}/uv.lock +60 -1
  35. patch_cc-0.2.4/docs/PLAYBOOK.md +0 -638
  36. patch_cc-0.2.4/src/patch_cc/patches/agents.py +0 -393
  37. patch_cc-0.2.4/src/patch_cc/patches/chrome.py +0 -262
  38. patch_cc-0.2.4/src/patch_cc/patches/codex.py +0 -519
  39. patch_cc-0.2.4/src/patch_cc/patches/output.py +0 -173
  40. patch_cc-0.2.4/src/patch_cc/patches/streaming.py +0 -837
  41. patch_cc-0.2.4/src/patch_cc/patches/thinking.py +0 -287
  42. {patch_cc-0.2.4 → patch_cc-0.3.0}/.github/workflows/ci.yml +0 -0
  43. {patch_cc-0.2.4 → patch_cc-0.3.0}/.github/workflows/release.yml +0 -0
  44. {patch_cc-0.2.4 → patch_cc-0.3.0}/.gitignore +0 -0
  45. {patch_cc-0.2.4 → patch_cc-0.3.0}/CLAUDE.md +0 -0
  46. {patch_cc-0.2.4 → patch_cc-0.3.0}/LICENSE +0 -0
  47. {patch_cc-0.2.4 → patch_cc-0.3.0}/docs/demo.gif +0 -0
  48. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/__init__.py +0 -0
  49. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/bun/errors.py +0 -0
  50. {patch_cc-0.2.4 → patch_cc-0.3.0}/src/patch_cc/patches/__init__.py +0 -0
@@ -16,3 +16,10 @@ Verify with `uv run patch-cc doctor` (every patch against a clean bundle;
16
16
  point it at the pristine copies in `~/.local/share/patch-cc/backups/` to sweep
17
17
  older builds). There is no test suite by design — doctor against real bundles
18
18
  is the check.
19
+
20
+ Be exact about what that covers: doctor runs the **matchers** and parses the
21
+ bundle they produce. It never runs the gateway, the translator, the OAuth flow
22
+ or the menu, so a change under `src/patch_cc/codex/` (the bridge — all of it
23
+ runtime, none of it a patch; the Codex *patch* is `patches/codex.py`, which the
24
+ sweep does cover) or in `menu.py` is checked by exercising it — a real `codex
25
+ serve` against a real turn — and a green sweep says nothing about it.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: patch-cc
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Interactive patcher for the Claude Code native binary
5
5
  Project-URL: Homepage, https://github.com/anfreire/patch-cc
6
6
  Project-URL: Repository, https://github.com/anfreire/patch-cc
@@ -25,6 +25,8 @@ Requires-Python: >=3.11
25
25
  Requires-Dist: blessed>=1.47.0
26
26
  Requires-Dist: lief>=0.15; sys_platform == 'darwin'
27
27
  Requires-Dist: rich>=13.7
28
+ Requires-Dist: tree-sitter-javascript>=0.23
29
+ Requires-Dist: tree-sitter>=0.25
28
30
  Description-Content-Type: text/markdown
29
31
 
30
32
  # patch-cc
@@ -37,7 +39,7 @@ you want — inline and live thinking, detailed tool calls, subagent model
37
39
  overrides, your own startup name, your **ChatGPT/Codex-plan GPT models as
38
40
  native models** — and apply them to your installed `claude` in one keystroke.
39
41
  Fully reversible: a pristine backup is kept, `patch-cc restore` puts it back.
40
- Pure Python; no Node, no Bun.
42
+ No Node, no Bun.
41
43
 
42
44
  ```bash
43
45
  uvx patch-cc # fullscreen menu, no install needed
@@ -92,7 +94,7 @@ patch-cc # then just run it
92
94
  | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
93
95
  | | Mark `--version` | Appends `(patched)` — or any marker you choose |
94
96
  | | Custom startup name | Defaults to `<your username>'s Code` |
95
- | | Startup org/email label | Replace the org/email on the welcome screen — or hide it |
97
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line) |
96
98
 
97
99
  ## Usage
98
100
 
@@ -197,9 +199,13 @@ startup name / `--version` marker are visible tells too.
197
199
 
198
200
  Claude Code now ships only as a Bun single-file executable; the npm package is a
199
201
  wrapper that downloads it. patch-cc edits the JavaScript bundle embedded in the
200
- binary's `.bun` section in place. It also drops the module's 154 MB of stale
201
- precompiled bytecode — editing the source invalidates it anyway so a patched
202
- binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
202
+ binary's `.bun` section in place. It also drops the entry module's stale
203
+ precompiled bytecode — editing the source invalidates it anyway, and it is more
204
+ than half the download so on Linux, where the ELF section is rewritten in
205
+ place, a patched binary is *smaller* than the original, not larger. (On macOS
206
+ the freed bytes are not yet reclaimed, so the file keeps its size; it still runs
207
+ correctly.) [docs/INTERNALS.md](docs/INTERNALS.md#the-bytecode-and-why-we-drop-it)
208
+ has the measurements; `patch-cc status` has yours.
203
209
 
204
210
  See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
205
211
  [docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
@@ -8,7 +8,7 @@ you want — inline and live thinking, detailed tool calls, subagent model
8
8
  overrides, your own startup name, your **ChatGPT/Codex-plan GPT models as
9
9
  native models** — and apply them to your installed `claude` in one keystroke.
10
10
  Fully reversible: a pristine backup is kept, `patch-cc restore` puts it back.
11
- Pure Python; no Node, no Bun.
11
+ No Node, no Bun.
12
12
 
13
13
  ```bash
14
14
  uvx patch-cc # fullscreen menu, no install needed
@@ -63,7 +63,7 @@ patch-cc # then just run it
63
63
  | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
64
64
  | | Mark `--version` | Appends `(patched)` — or any marker you choose |
65
65
  | | Custom startup name | Defaults to `<your username>'s Code` |
66
- | | Startup org/email label | Replace the org/email on the welcome screen — or hide it |
66
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line) |
67
67
 
68
68
  ## Usage
69
69
 
@@ -168,9 +168,13 @@ startup name / `--version` marker are visible tells too.
168
168
 
169
169
  Claude Code now ships only as a Bun single-file executable; the npm package is a
170
170
  wrapper that downloads it. patch-cc edits the JavaScript bundle embedded in the
171
- binary's `.bun` section in place. It also drops the module's 154 MB of stale
172
- precompiled bytecode — editing the source invalidates it anyway so a patched
173
- binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
171
+ binary's `.bun` section in place. It also drops the entry module's stale
172
+ precompiled bytecode — editing the source invalidates it anyway, and it is more
173
+ than half the download so on Linux, where the ELF section is rewritten in
174
+ place, a patched binary is *smaller* than the original, not larger. (On macOS
175
+ the freed bytes are not yet reclaimed, so the file keeps its size; it still runs
176
+ correctly.) [docs/INTERNALS.md](docs/INTERNALS.md#the-bytecode-and-why-we-drop-it)
177
+ has the measurements; `patch-cc status` has yours.
174
178
 
175
179
  See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
176
180
  [docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
@@ -30,26 +30,44 @@ shape that already absorbs every case, found rather than bolted on.
30
30
 
31
31
  - **Explicit invocations are hermetic.** A non-interactive command's arguments
32
32
  are its whole input; no saved state may *silently* change what it does.
33
- Persisted choices pre-fill the interactive UI and never act on their own — the
34
- lone exception is `apply --from-cache`, which takes the cache as its *named*
35
- input, so the state it reads is declared in the arguments, not hidden behind a
36
- default. Absent that flag, the same command still always yields the same result.
33
+ Persisted choices pre-fill the interactive UI and never act on their own. Two
34
+ flags do read state, and each *names* the state it reads: `apply --from-cache`
35
+ takes the remembered selection, and `apply --codex` takes your plan's
36
+ catalogue asking for a Codex model is asking for the plan that describes it,
37
+ so the display name and context window are read from there rather than
38
+ invented. Neither is hidden behind a default, and absent both, the same command
39
+ always yields the same result. Asking for a Codex model is also the one thing
40
+ whose *output* can differ between two identical invocations — under either flag
41
+ that does it, `--codex` naming ids directly or `--from-cache` replaying a
42
+ remembered set that holds them (both re-read the plan at bake time): a plan
43
+ that cannot be reached is not an error, so the id still bakes, on its
44
+ fallbacks. That is the price of not guessing a context window, and it is paid
45
+ under flags that say so.
37
46
 
38
- - **Anchor matchers on meaning.** String literals, `case` labels, prop names,
39
- control-flow shape never a minified local that changes every build. A new
40
- upstream shape earns a narrow new branch, not a looser regex. Full rules and
41
- the repair loop: [PLAYBOOK.md](PLAYBOOK.md).
47
+ - **Find by the name upstream wrote; edit the grammar node.** The authored
48
+ namesstring literals, `case` labels, property names and the shape of the
49
+ tree are what a build keeps. Never describe the syntax between them, and never
50
+ anchor on a minified local: that is the half a minifier regenerates. A new
51
+ upstream *shape* earns a narrow new branch; a new *spelling* of one shape
52
+ should already cost nothing. Full rules and the repair loop:
53
+ [PLAYBOOK.md](PLAYBOOK.md).
42
54
 
43
55
  - **Report absent apart from broken.** A matcher that finds nothing may be a
44
56
  shape this build simply lacks — most patches carry several — not a regression.
45
57
  Keep "gone", "already applied", and "not on this build" as distinct signals;
46
58
  never collapse them into one number. Which one a sub-step's silence means is
47
- not guesswork: declare it (`expect=True`, or a variant group) so a green tick
48
- cannot cover a dead feature. See [PLAYBOOK.md](PLAYBOOK.md).
59
+ not guesswork: declare it (`expect=True`) so a green tick cannot cover a dead
60
+ feature. A step nobody declared is a step that cannot report its own death:
61
+ `branding` carried none, so a name badge that stopped being a bold render was
62
+ a green run, an unchanged banner, and a manifest asserting the new name. See
63
+ [PLAYBOOK.md](PLAYBOOK.md).
49
64
 
50
- - **Port faithfully.** When you change a patch, verify its output against the
51
- upstream reference on a real bundle — byte-identical where behaviour must not
52
- change. The JS→Python porting traps are in [PLAYBOOK.md](PLAYBOOK.md).
65
+ - **Port faithfully.** When you change a patch, verify its output against a real
66
+ bundle — byte-identical where behaviour must not change. `doctor` over the
67
+ archived corpus is that check, and the *diff* between two sweeps is the half
68
+ that matters: a red build is loud on its own, but a widened locator shows up
69
+ only as an old build's counts quietly moving. Be able to say what every moved
70
+ number means. The sweep is in [PLAYBOOK.md](PLAYBOOK.md).
53
71
 
54
72
  - **The user controls commits and releases.** Don't commit, push, or publish
55
73
  unless asked.
@@ -1,6 +1,6 @@
1
1
  # Internals
2
2
 
3
- How patch-cc gets from a 267 MB binary to a patched, smaller one.
3
+ How patch-cc gets from a Claude binary to a patched, smaller one.
4
4
 
5
5
  ## The shape of a native Claude binary
6
6
 
@@ -35,32 +35,52 @@ A module record (new 52-byte format) is six such pairs — `name`, `contents`,
35
35
  `sourcemap`, `bytecode`, `moduleInfo`, `bytecodeOriginPath` — followed by four
36
36
  `u8` flags (`encoding`, `loader`, `moduleFormat`, `side`).
37
37
 
38
- The module we patch is the entrypoint, named `/$bunfs/root/src/entrypoints/cli.js`
39
- (or `claude` / `claude.exe` on other builds). Its `contents` is the JS we edit.
38
+ The module we patch is the entrypoint, which the offsets struct names by index
39
+ (`entry_point_id`) the same index Bun itself resolves it by. Its `contents` is
40
+ the JS we edit. Its *name* is upstream's to change and we never read it:
41
+ 2.1.229 renamed it `/$bunfs/root/src/entrypoints/cli.js` → `/$bunfs/root/cli`.
40
42
 
41
43
  Code: `src/patch_cc/bun/blob.py`.
42
44
 
43
- ## The 154 MB bytecode, and why we drop it
45
+ ## The bytecode, and why we drop it
44
46
 
45
- The entry module also carries ~154 MB of precompiled Bun **bytecode** — more
46
- than half the binary. Every other module has none.
47
+ The entry module also carries precompiled Bun **bytecode** — more than half the
48
+ binary. Every other module has none.
47
49
 
48
50
  Any edit to `contents` invalidates that bytecode; Bun detects the mismatch and
49
51
  recompiles from source at launch. So keeping it buys nothing:
50
52
 
51
53
  | binary | size | startup |
52
54
  |---|---|---|
53
- | original (valid bytecode) | 267 MB | ~100 ms |
54
- | patched, bytecode kept | 267 MB | ~650 ms |
55
- | patched, bytecode dropped | **113 MB** | ~650 ms |
55
+ | original (valid bytecode) | 323 MB | ~100 ms |
56
+ | patched, bytecode kept | 323 MB | ~650 ms |
57
+ | patched, bytecode dropped | **125 MB** | ~650 ms |
56
58
 
57
59
  Patching pays the recompile cost either way, so patch-cc drops the entry
58
60
  module's bytecode (`rebuild(..., drop_bytecode=True)`). The result runs source,
59
- guaranteeing our edits are authoritative, and is ~154 MB smaller.
60
-
61
- `doctor` asserts the patched binary has `bytecode == 0`. If a future Bun build
62
- makes bytecode authoritative over source, that assert is the tripwire — every
63
- patch would silently no-op otherwise.
61
+ guaranteeing our edits are authoritative, and on Linux — is smaller by
62
+ exactly the bytecode.
63
+
64
+ The size figures are the **ELF** path: the `.bun` section is rewritten in place,
65
+ so the dropped bytecode is genuinely reclaimed (`container.verify` refuses a
66
+ Linux write that did not shrink). On **macOS** the file keeps its original size:
67
+ `macho.py` grows a segment but never shrinks one, so the freed bytes stay as
68
+ dead space. The binary still runs correctly (the bytecode is gone), it is just
69
+ not smaller — reclaiming it means shrinking the Mach-O segment and re-laying
70
+ `__LINKEDIT`, which is not done yet.
71
+
72
+ Those are **2.1.232's** Linux numbers, and they are a measurement rather than a
73
+ promise: the same table read 267 / 113 MB against 2.1.216, because the bytecode
74
+ grew from 154 MB to 198 MB in the fifteen builds between them (2.1.230 was
75
+ never published). Read the current pair off any
76
+ binary with `patch-cc status` rather than off this table.
77
+
78
+ Every write asserts the binary it produced carries `bytecode == 0`
79
+ (`container.verify`, beside the round-trip check), and `patch-cc status` reports
80
+ the field for an installed one. `doctor` cannot: a dry run is handed a *clean*
81
+ bundle, which still has its bytecode by definition. If a future Bun build makes
82
+ bytecode authoritative over source, that assert is the tripwire — every patch
83
+ would silently no-op otherwise.
64
84
 
65
85
  ## Writing it back without ballooning
66
86
 
@@ -92,20 +112,23 @@ Every patched bundle ends with a single comment line — the one description of
92
112
  its shape; [PLAYBOOK.md](PLAYBOOK.md) covers what it means for matcher health:
93
113
 
94
114
  ```
95
- //patch-cc {"v":1,"tool":"<version>","patches":[...],"brand":...,"models":{...},
96
- "org":...,"codex":{"port":8817,"models":["gpt-5.6-sol", ...]}}
115
+ //patch-cc {"v":1,"tool":"<version>","patches":[...],"brand":...,"suffix":...,
116
+ "models":{...},"org":...,"codex":{"port":8817,"models":["gpt-5.6-sol"]}}
97
117
  ```
98
118
 
119
+ Every key after `patches` is a configurable patch's own, declared in one place
120
+ (`Patch.setting`) so the manifest here, the cache, and the menu cannot spell it
121
+ three ways. Each is written only when *that* patch landed **and** has a value
122
+ worth recording, so `status` can never assert a name, marker, or model the
123
+ bundle does not contain — and so this is the *widest* the line gets, not its
124
+ fixed shape.
125
+
99
126
  That line is why `patch-cc status` can name exactly what is applied: several
100
127
  patches are value flips (`verbose:!0`) that leave no other trace. A comment
101
128
  can't collide with code and travels with the bundle through extract/repack.
102
129
  The menu also reads it to pre-select the current patch set — the binary is the
103
130
  state.
104
131
 
105
- Every key after `patches` belongs to a patch and is written only when *that*
106
- patch landed, so `status` can never assert a name, marker, or model the bundle
107
- does not contain.
108
-
109
132
  Each key records what was *asked for*, never what was derived from it. `codex`
110
133
  carries model ids and a port and nothing else: a Codex model's display name and
111
134
  context window are already baked into the bundle, and repeating them here would
@@ -120,9 +143,19 @@ rather than from a store of their own.
120
143
  `~/.local/share/patch-cc/backups/`. `restore` copies it back — never an
121
144
  inverse patch (insertions cascade, so a reverse diff is meaningless).
122
145
  - Patching always starts from that pristine copy, so re-applying never stacks
123
- edits on edits, and an apply where **no** patch changes anything leaves the
124
- binary untouched entirely (stripping bytecode for nothing would only slow
125
- startup).
146
+ edits on edits, and an apply where **nothing lands** every selected patch
147
+ broken, so the manifest would claim nothing leaves the binary untouched
148
+ entirely (stripping bytecode for nothing would only slow startup). A patch
149
+ that *lands* still writes even where it changed no bytes, because landing
150
+ includes an override the build already satisfies: the manifest records what
151
+ was asked and verified present, so `status` can report it.
152
+ - The bundle is parsed, and any syntax error aborts before the binary or the
153
+ backup is touched — the two checks below answer "did we write what we meant
154
+ to", which a corrupt splice satisfies perfectly. The parse is the same one
155
+ the patches locate with, so it costs nothing extra and sits at *every* batch
156
+ of edits as well as on the final bytes: a patch that produces rubble is named
157
+ and dropped rather than aborting the run. See
158
+ [PLAYBOOK.md](PLAYBOOK.md#the-syntax-gate).
126
159
  - Every write is verified: patch-cc re-extracts the JS from the binary it just
127
160
  wrote and asserts it equals what it meant to write.
128
161
  - Patching a binary that is already marked, when no pristine backup exists, is