patch-cc 0.1.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.
- patch_cc-0.1.0/.github/workflows/ci.yml +32 -0
- patch_cc-0.1.0/.github/workflows/release.yml +51 -0
- patch_cc-0.1.0/.gitignore +22 -0
- patch_cc-0.1.0/LICENSE +21 -0
- patch_cc-0.1.0/PKG-INFO +141 -0
- patch_cc-0.1.0/README.md +113 -0
- patch_cc-0.1.0/docs/CONDUCT.md +54 -0
- patch_cc-0.1.0/docs/INTERNALS.md +115 -0
- patch_cc-0.1.0/docs/PLAYBOOK.md +208 -0
- patch_cc-0.1.0/pyproject.toml +59 -0
- patch_cc-0.1.0/src/patch_cc/__init__.py +3 -0
- patch_cc-0.1.0/src/patch_cc/bun/__init__.py +6 -0
- patch_cc-0.1.0/src/patch_cc/bun/blob.py +256 -0
- patch_cc-0.1.0/src/patch_cc/bun/container.py +129 -0
- patch_cc-0.1.0/src/patch_cc/bun/elf.py +281 -0
- patch_cc-0.1.0/src/patch_cc/bun/errors.py +12 -0
- patch_cc-0.1.0/src/patch_cc/bun/macho.py +124 -0
- patch_cc-0.1.0/src/patch_cc/cache.py +86 -0
- patch_cc-0.1.0/src/patch_cc/cli.py +399 -0
- patch_cc-0.1.0/src/patch_cc/doctor.py +116 -0
- patch_cc-0.1.0/src/patch_cc/locate.py +117 -0
- patch_cc-0.1.0/src/patch_cc/menu.py +1135 -0
- patch_cc-0.1.0/src/patch_cc/patcher.py +244 -0
- patch_cc-0.1.0/src/patch_cc/patches/__init__.py +75 -0
- patch_cc-0.1.0/src/patch_cc/patches/agents.py +289 -0
- patch_cc-0.1.0/src/patch_cc/patches/base.py +200 -0
- patch_cc-0.1.0/src/patch_cc/patches/chrome.py +187 -0
- patch_cc-0.1.0/src/patch_cc/patches/output.py +173 -0
- patch_cc-0.1.0/src/patch_cc/patches/streaming.py +922 -0
- patch_cc-0.1.0/src/patch_cc/patches/thinking.py +88 -0
- patch_cc-0.1.0/src/patch_cc/ui.py +23 -0
- patch_cc-0.1.0/uv.lock +358 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --group dev
|
|
24
|
+
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: uv run ruff check src/
|
|
27
|
+
|
|
28
|
+
- name: Format check
|
|
29
|
+
run: uv run ruff format --check src/
|
|
30
|
+
|
|
31
|
+
- name: Type check
|
|
32
|
+
run: uv run mypy src/patch_cc
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build distributions
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Build sdist + wheel
|
|
22
|
+
run: uv build
|
|
23
|
+
|
|
24
|
+
- name: Verify metadata
|
|
25
|
+
run: |
|
|
26
|
+
uv tool run --from twine twine check dist/*
|
|
27
|
+
|
|
28
|
+
- name: Upload distributions
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
name: Publish to PyPI
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/patch-cc
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
steps:
|
|
44
|
+
- name: Download distributions
|
|
45
|
+
uses: actions/download-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
- name: Publish to PyPI
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
patch_cc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 André Freire Ferreira
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
patch_cc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: patch-cc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive patcher for the Claude Code native binary
|
|
5
|
+
Project-URL: Homepage, https://github.com/anfreire/patch-cc
|
|
6
|
+
Project-URL: Repository, https://github.com/anfreire/patch-cc
|
|
7
|
+
Project-URL: Issues, https://github.com/anfreire/patch-cc/issues
|
|
8
|
+
Author-email: André Freire Ferreira <anfreire.dev@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bun,claude,claude-code,cli,patch,tui
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: blessed>=1.47.0
|
|
25
|
+
Requires-Dist: lief>=0.15; sys_platform == 'darwin'
|
|
26
|
+
Requires-Dist: rich>=13.7
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# patch-cc
|
|
30
|
+
|
|
31
|
+
[](https://github.com/anfreire/patch-cc/actions/workflows/ci.yml)
|
|
32
|
+
[](https://pypi.org/project/patch-cc/)
|
|
33
|
+
|
|
34
|
+
An interactive patcher for the **Claude Code native binary**. Pick the tweaks
|
|
35
|
+
you want — inline and live thinking, detailed tool calls, subagent model
|
|
36
|
+
overrides, your own startup name — and apply them to your installed `claude`
|
|
37
|
+
in one keystroke. Pure Python; no Node, no Bun.
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- **Linux or macOS**
|
|
42
|
+
- **Python 3.11+**
|
|
43
|
+
- **[uv](https://docs.astral.sh/uv/)** — how patch-cc is run and installed
|
|
44
|
+
below. Install it with `curl -LsSf https://astral.sh/uv/install.sh | sh`.
|
|
45
|
+
Not using uv? `pipx install patch-cc` (or `pip install patch-cc`) works too;
|
|
46
|
+
it is an ordinary PyPI package.
|
|
47
|
+
- **macOS only:** the Xcode command line tools, for `codesign` — a patched
|
|
48
|
+
binary has to be re-signed or macOS refuses to run it.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx patch-cc # fullscreen menu, no install needed
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The menu is a single centered panel: move with `↑ ↓`, toggle with `space`,
|
|
55
|
+
press `s` to save. Patches that carry a setting — subagent models, the startup
|
|
56
|
+
name, the `--version` marker — open a centered modal on `enter`, and the row
|
|
57
|
+
then shows what you chose. Everything choosable is a picker: the agent names
|
|
58
|
+
and model aliases are **discovered from your binary itself**, so the menu can
|
|
59
|
+
never offer something your build would reject. Typing exists only for the two
|
|
60
|
+
genuinely free-text values.
|
|
61
|
+
|
|
62
|
+
A patched binary records what was applied inside itself, so the menu always
|
|
63
|
+
comes up showing the real current state, and `patch-cc status` answers
|
|
64
|
+
exactly.
|
|
65
|
+
|
|
66
|
+
Prefer it always available on your PATH? Install it:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv tool install patch-cc
|
|
70
|
+
patch-cc # then just run it
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What it can do
|
|
74
|
+
|
|
75
|
+
| Group | Patch | |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Output & diffs | Detailed tool calls | Show full read/search calls, not collapsed summaries |
|
|
78
|
+
| | Colour new files as diffs | Created files render with `+` lines and green |
|
|
79
|
+
| Thinking | Always show thinking | Thinking blocks stay inline — no `ctrl+o` |
|
|
80
|
+
| Live thinking | Stream thinking live | See reasoning as it is generated, inline and in order |
|
|
81
|
+
| Subagents | Show subagent prompts | Prompt blocks visible during normal use |
|
|
82
|
+
| | Override subagent models | Pick the model per built-in agent (discovered from your binary) |
|
|
83
|
+
| Chrome | Disable spinner tips | No rotating tips on the spinner |
|
|
84
|
+
| | Custom startup name | Defaults to `<your username>'s Code` |
|
|
85
|
+
| | Mark `--version` | Appends `(patched)` — or any marker you choose |
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
Everything the menu does is also a non-interactive subcommand (shown with
|
|
90
|
+
`uvx`; drop it if you installed the tool):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uvx patch-cc apply # the default patch set
|
|
94
|
+
uvx patch-cc apply tool-calls live-thinking # just these
|
|
95
|
+
uvx patch-cc apply --brand # + branding as <username>'s Code
|
|
96
|
+
uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
|
|
97
|
+
uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
|
|
98
|
+
uvx patch-cc apply --suffix "(mine)" # custom --version marker
|
|
99
|
+
uvx patch-cc status # exactly what is applied
|
|
100
|
+
uvx patch-cc doctor # do all patches match this build?
|
|
101
|
+
uvx patch-cc list # patches + your binary's agents/models
|
|
102
|
+
uvx patch-cc restore # put the original back
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`--model` and `--brand` imply their patches; agents and models are validated
|
|
106
|
+
against what your installed binary actually ships.
|
|
107
|
+
|
|
108
|
+
## After a Claude update
|
|
109
|
+
|
|
110
|
+
Claude auto-updates roughly daily and replaces the binary, which reverts the
|
|
111
|
+
patch. Re-run `patch-cc` — the menu remembers your last selection — or re-apply
|
|
112
|
+
your set explicitly:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
uvx patch-cc apply --brand --model Explore=haiku
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`uvx patch-cc status` tells you whether the current binary is patched, and the
|
|
119
|
+
startup name / `--version` marker are visible tells too.
|
|
120
|
+
|
|
121
|
+
## Why native-only, and why it stays small
|
|
122
|
+
|
|
123
|
+
Claude Code now ships only as a Bun single-file executable; the npm package is a
|
|
124
|
+
wrapper that downloads it. patch-cc edits the JavaScript bundle embedded in the
|
|
125
|
+
binary's `.bun` section in place. It also drops the module's 154 MB of stale
|
|
126
|
+
precompiled bytecode — editing the source invalidates it anyway — so a patched
|
|
127
|
+
binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
|
|
128
|
+
|
|
129
|
+
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
130
|
+
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
131
|
+
|
|
132
|
+
## Credits
|
|
133
|
+
|
|
134
|
+
The patch set is a Python port of
|
|
135
|
+
[a-connoisseur/patch-claude-code](https://github.com/a-connoisseur/patch-claude-code),
|
|
136
|
+
with the subagent-model override idea from
|
|
137
|
+
[aleks-apostle/claude-code-patches](https://github.com/aleks-apostle/claude-code-patches).
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
patch_cc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# patch-cc
|
|
2
|
+
|
|
3
|
+
[](https://github.com/anfreire/patch-cc/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/patch-cc/)
|
|
5
|
+
|
|
6
|
+
An interactive patcher for the **Claude Code native binary**. Pick the tweaks
|
|
7
|
+
you want — inline and live thinking, detailed tool calls, subagent model
|
|
8
|
+
overrides, your own startup name — and apply them to your installed `claude`
|
|
9
|
+
in one keystroke. Pure Python; no Node, no Bun.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- **Linux or macOS**
|
|
14
|
+
- **Python 3.11+**
|
|
15
|
+
- **[uv](https://docs.astral.sh/uv/)** — how patch-cc is run and installed
|
|
16
|
+
below. Install it with `curl -LsSf https://astral.sh/uv/install.sh | sh`.
|
|
17
|
+
Not using uv? `pipx install patch-cc` (or `pip install patch-cc`) works too;
|
|
18
|
+
it is an ordinary PyPI package.
|
|
19
|
+
- **macOS only:** the Xcode command line tools, for `codesign` — a patched
|
|
20
|
+
binary has to be re-signed or macOS refuses to run it.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvx patch-cc # fullscreen menu, no install needed
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The menu is a single centered panel: move with `↑ ↓`, toggle with `space`,
|
|
27
|
+
press `s` to save. Patches that carry a setting — subagent models, the startup
|
|
28
|
+
name, the `--version` marker — open a centered modal on `enter`, and the row
|
|
29
|
+
then shows what you chose. Everything choosable is a picker: the agent names
|
|
30
|
+
and model aliases are **discovered from your binary itself**, so the menu can
|
|
31
|
+
never offer something your build would reject. Typing exists only for the two
|
|
32
|
+
genuinely free-text values.
|
|
33
|
+
|
|
34
|
+
A patched binary records what was applied inside itself, so the menu always
|
|
35
|
+
comes up showing the real current state, and `patch-cc status` answers
|
|
36
|
+
exactly.
|
|
37
|
+
|
|
38
|
+
Prefer it always available on your PATH? Install it:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv tool install patch-cc
|
|
42
|
+
patch-cc # then just run it
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What it can do
|
|
46
|
+
|
|
47
|
+
| Group | Patch | |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| Output & diffs | Detailed tool calls | Show full read/search calls, not collapsed summaries |
|
|
50
|
+
| | Colour new files as diffs | Created files render with `+` lines and green |
|
|
51
|
+
| Thinking | Always show thinking | Thinking blocks stay inline — no `ctrl+o` |
|
|
52
|
+
| Live thinking | Stream thinking live | See reasoning as it is generated, inline and in order |
|
|
53
|
+
| Subagents | Show subagent prompts | Prompt blocks visible during normal use |
|
|
54
|
+
| | Override subagent models | Pick the model per built-in agent (discovered from your binary) |
|
|
55
|
+
| Chrome | Disable spinner tips | No rotating tips on the spinner |
|
|
56
|
+
| | Custom startup name | Defaults to `<your username>'s Code` |
|
|
57
|
+
| | Mark `--version` | Appends `(patched)` — or any marker you choose |
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
Everything the menu does is also a non-interactive subcommand (shown with
|
|
62
|
+
`uvx`; drop it if you installed the tool):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uvx patch-cc apply # the default patch set
|
|
66
|
+
uvx patch-cc apply tool-calls live-thinking # just these
|
|
67
|
+
uvx patch-cc apply --brand # + branding as <username>'s Code
|
|
68
|
+
uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
|
|
69
|
+
uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
|
|
70
|
+
uvx patch-cc apply --suffix "(mine)" # custom --version marker
|
|
71
|
+
uvx patch-cc status # exactly what is applied
|
|
72
|
+
uvx patch-cc doctor # do all patches match this build?
|
|
73
|
+
uvx patch-cc list # patches + your binary's agents/models
|
|
74
|
+
uvx patch-cc restore # put the original back
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`--model` and `--brand` imply their patches; agents and models are validated
|
|
78
|
+
against what your installed binary actually ships.
|
|
79
|
+
|
|
80
|
+
## After a Claude update
|
|
81
|
+
|
|
82
|
+
Claude auto-updates roughly daily and replaces the binary, which reverts the
|
|
83
|
+
patch. Re-run `patch-cc` — the menu remembers your last selection — or re-apply
|
|
84
|
+
your set explicitly:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uvx patch-cc apply --brand --model Explore=haiku
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`uvx patch-cc status` tells you whether the current binary is patched, and the
|
|
91
|
+
startup name / `--version` marker are visible tells too.
|
|
92
|
+
|
|
93
|
+
## Why native-only, and why it stays small
|
|
94
|
+
|
|
95
|
+
Claude Code now ships only as a Bun single-file executable; the npm package is a
|
|
96
|
+
wrapper that downloads it. patch-cc edits the JavaScript bundle embedded in the
|
|
97
|
+
binary's `.bun` section in place. It also drops the module's 154 MB of stale
|
|
98
|
+
precompiled bytecode — editing the source invalidates it anyway — so a patched
|
|
99
|
+
binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
|
|
100
|
+
|
|
101
|
+
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
102
|
+
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
103
|
+
|
|
104
|
+
## Credits
|
|
105
|
+
|
|
106
|
+
The patch set is a Python port of
|
|
107
|
+
[a-connoisseur/patch-claude-code](https://github.com/a-connoisseur/patch-claude-code),
|
|
108
|
+
with the subagent-model override idea from
|
|
109
|
+
[aleks-apostle/claude-code-patches](https://github.com/aleks-apostle/claude-code-patches).
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CONDUCT.md — patch-cc
|
|
2
|
+
|
|
3
|
+
How we build here. What the project *is* and how to use it live in the
|
|
4
|
+
[README](../README.md); this file is the *how*, not the *what*. Read it before
|
|
5
|
+
touching a matcher or the container layer.
|
|
6
|
+
|
|
7
|
+
## Mindset
|
|
8
|
+
|
|
9
|
+
Every change reaches for the **minimal, elegant, graceful** form — the simplest
|
|
10
|
+
shape that already absorbs every case, found rather than bolted on.
|
|
11
|
+
|
|
12
|
+
- **Grace, not branches.** Dissolve edge cases into the common path instead of
|
|
13
|
+
guarding them with an `if`. Empty, missing, already-applied, absent-on-this-
|
|
14
|
+
build should flow through the *same* code as the normal case. A special-case
|
|
15
|
+
branch you could dissolve is a miss, not a smaller win.
|
|
16
|
+
- **DRY — one source of truth.** Every value, rule, and fact has one home;
|
|
17
|
+
everything else links to it. This holds for the docs too: if it's in the
|
|
18
|
+
README, don't restate it here. Two copies drift, and the reader can't tell
|
|
19
|
+
which one is true.
|
|
20
|
+
- **Cut, don't accrete.** Keep the smallest surface that does the job. Delete
|
|
21
|
+
superseded code, flags, and comments in the same change; add no abstraction
|
|
22
|
+
for a caller that doesn't exist yet.
|
|
23
|
+
|
|
24
|
+
## Guidelines
|
|
25
|
+
|
|
26
|
+
- **Never corrupt the user's binary.** Writes are staged, re-extracted, and
|
|
27
|
+
verified byte-exact before replacing the original, and a pristine backup
|
|
28
|
+
always exists for `restore`. A bug should leave a working `claude`, never a
|
|
29
|
+
brick.
|
|
30
|
+
|
|
31
|
+
- **Explicit invocations are hermetic.** A non-interactive command's arguments
|
|
32
|
+
are its whole input; no saved state may silently change what it does, so the
|
|
33
|
+
same command always yields the same result. Persisted choices belong to the
|
|
34
|
+
interactive UI alone — they pre-fill a prompt, never trigger an action.
|
|
35
|
+
|
|
36
|
+
- **Anchor matchers on meaning.** String literals, `case` labels, prop names,
|
|
37
|
+
control-flow shape — never a minified local that changes every build. A new
|
|
38
|
+
upstream shape earns a narrow new branch, not a looser regex. Full rules and
|
|
39
|
+
the repair loop: [PLAYBOOK.md](PLAYBOOK.md).
|
|
40
|
+
|
|
41
|
+
- **Report absent apart from broken.** A matcher that finds nothing may be a
|
|
42
|
+
shape this build simply lacks — most patches carry several — not a regression.
|
|
43
|
+
Keep "gone", "already applied", and "not on this build" as distinct signals;
|
|
44
|
+
never collapse them into one number.
|
|
45
|
+
|
|
46
|
+
- **Port faithfully.** When you change a patch, verify its output against the
|
|
47
|
+
upstream reference on a real bundle — byte-identical where behaviour must not
|
|
48
|
+
change. The JS→Python porting traps are in [PLAYBOOK.md](PLAYBOOK.md).
|
|
49
|
+
|
|
50
|
+
- **The user controls commits and releases.** Don't commit, push, or publish
|
|
51
|
+
unless asked.
|
|
52
|
+
|
|
53
|
+
The binary format, and why the ELF write is in-place and the bytecode is
|
|
54
|
+
dropped: [INTERNALS.md](INTERNALS.md).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Internals
|
|
2
|
+
|
|
3
|
+
How patch-cc gets from a 267 MB binary to a patched, smaller one.
|
|
4
|
+
|
|
5
|
+
## The shape of a native Claude binary
|
|
6
|
+
|
|
7
|
+
Claude Code ships as a [Bun](https://bun.sh) single-file executable. The whole
|
|
8
|
+
app — a ~20 MB minified JS bundle plus a few asset modules — is embedded in the
|
|
9
|
+
binary:
|
|
10
|
+
|
|
11
|
+
- **Linux**: an ELF section named `.bun`
|
|
12
|
+
- **macOS**: a Mach-O section `__BUN,__bun`
|
|
13
|
+
- **Windows**: a PE `.bun` section (not supported here)
|
|
14
|
+
|
|
15
|
+
Inside that section is a *Bun module graph*: a flat arena of payloads, a module
|
|
16
|
+
table describing them, and a trailer.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
.bun section
|
|
20
|
+
└── [u64 size prefix] (u32 on Bun < 1.3.4)
|
|
21
|
+
└── Bun blob
|
|
22
|
+
├── payload arena name / contents / sourcemap / bytecode / ... bytes
|
|
23
|
+
├── module table N records × 52 bytes (36 on old Bun)
|
|
24
|
+
├── compileExecArgv
|
|
25
|
+
├── offsets struct 32 bytes: byteCount, modulesPtr, entryId, argvPtr, flags
|
|
26
|
+
└── "\n---- Bun! ----\n" 15-byte trailer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Every pointer is a `(u32 offset, u32 length)` pair relative to the blob start,
|
|
30
|
+
and pointers live in only two places: the module table and the offsets struct.
|
|
31
|
+
That is what makes rewriting tractable — move a payload, fix the handful of
|
|
32
|
+
pointers that describe it.
|
|
33
|
+
|
|
34
|
+
A module record (new 52-byte format) is six such pairs — `name`, `contents`,
|
|
35
|
+
`sourcemap`, `bytecode`, `moduleInfo`, `bytecodeOriginPath` — followed by four
|
|
36
|
+
`u8` flags (`encoding`, `loader`, `moduleFormat`, `side`).
|
|
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.
|
|
40
|
+
|
|
41
|
+
Code: `src/patch_cc/bun/blob.py`.
|
|
42
|
+
|
|
43
|
+
## The 154 MB bytecode, and why we drop it
|
|
44
|
+
|
|
45
|
+
The entry module also carries ~154 MB of precompiled Bun **bytecode** — more
|
|
46
|
+
than half the binary. Every other module has none.
|
|
47
|
+
|
|
48
|
+
Any edit to `contents` invalidates that bytecode; Bun detects the mismatch and
|
|
49
|
+
recompiles from source at launch. So keeping it buys nothing:
|
|
50
|
+
|
|
51
|
+
| binary | size | startup |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| original (valid bytecode) | 267 MB | ~100 ms |
|
|
54
|
+
| patched, bytecode kept | 267 MB | ~650 ms |
|
|
55
|
+
| patched, bytecode dropped | **113 MB** | ~650 ms |
|
|
56
|
+
|
|
57
|
+
Patching pays the recompile cost either way, so patch-cc drops the entry
|
|
58
|
+
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.
|
|
64
|
+
|
|
65
|
+
## Writing it back without ballooning
|
|
66
|
+
|
|
67
|
+
`.bun` is the last *allocated* ELF section; only non-allocated metadata
|
|
68
|
+
(`.comment`, `.symtab`, `.strtab`, `.shstrtab`) follows it. patch-cc rewrites
|
|
69
|
+
the ELF bytes in place:
|
|
70
|
+
|
|
71
|
+
1. Splice the new (smaller) blob over the old `.bun` bytes.
|
|
72
|
+
2. Shift `e_shoff`, `e_phoff`, and the trailing non-alloc sections/segments by
|
|
73
|
+
the size delta.
|
|
74
|
+
3. Grow or shrink the containing `PT_LOAD` segment's `filesz`/`memsz` to match.
|
|
75
|
+
|
|
76
|
+
`.bun` keeps its original file offset. This is deliberately *not* done with a
|
|
77
|
+
general ELF library: LIEF rebuilds the binary and relocates `.bun` so its file
|
|
78
|
+
offset equals its virtual address (`0x20000000`), which inflates the file to
|
|
79
|
+
~715 MB. Raw in-place surgery avoids that entirely.
|
|
80
|
+
|
|
81
|
+
Guards refuse anything that could corrupt the mapping: allocated sections after
|
|
82
|
+
`.bun`, growth into a header table, an unrelated spanning segment, or a
|
|
83
|
+
misaligned `PT_LOAD` shift. If any fires, the write aborts rather than guesses.
|
|
84
|
+
|
|
85
|
+
Code: `src/patch_cc/bun/elf.py`. macOS uses LIEF (`macho.py`) — Mach-O segment
|
|
86
|
+
growth is page-aligned and bounded, with no relocation pathology, and every
|
|
87
|
+
edit is followed by an ad-hoc `codesign` (mandatory on Apple Silicon).
|
|
88
|
+
|
|
89
|
+
## The manifest
|
|
90
|
+
|
|
91
|
+
Every patched bundle ends with a single comment line:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
//patch-cc {"v":1,"tool":"0.1.0","patches":[...],"brand":...,"models":{...}}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
That line is why `patch-cc status` can name exactly what is applied: several
|
|
98
|
+
patches are value flips (`verbose:!0`) that leave no other trace. A comment
|
|
99
|
+
can't collide with code and travels with the bundle through extract/repack.
|
|
100
|
+
The menu also reads it to pre-select the current patch set — the binary is the
|
|
101
|
+
state.
|
|
102
|
+
|
|
103
|
+
## Safety
|
|
104
|
+
|
|
105
|
+
- Before the first patch of a version, the pristine binary is copied to
|
|
106
|
+
`~/.local/share/patch-cc/backups/`. `restore` copies it back — never an
|
|
107
|
+
inverse patch (insertions cascade, so a reverse diff is meaningless).
|
|
108
|
+
- Patching always starts from that pristine copy, so re-applying never stacks
|
|
109
|
+
edits on edits, and an apply where **no** patch changes anything leaves the
|
|
110
|
+
binary untouched entirely (stripping bytecode for nothing would only slow
|
|
111
|
+
startup).
|
|
112
|
+
- Every write is verified: patch-cc re-extracts the JS from the binary it just
|
|
113
|
+
wrote and asserts it equals what it meant to write.
|
|
114
|
+
- Patching a binary that is already marked, when no pristine backup exists, is
|
|
115
|
+
refused unless `--force` — there is nothing clean to start from.
|