patch-cc 0.1.4__tar.gz → 0.2.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.4 → patch_cc-0.2.0}/.github/workflows/ci.yml +1 -1
- {patch_cc-0.1.4 → patch_cc-0.2.0}/.gitignore +4 -0
- patch_cc-0.2.0/AGENTS.md +18 -0
- patch_cc-0.2.0/CLAUDE.md +1 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/PKG-INFO +77 -10
- {patch_cc-0.1.4 → patch_cc-0.2.0}/README.md +74 -8
- {patch_cc-0.1.4 → patch_cc-0.2.0}/docs/INTERNALS.md +19 -3
- {patch_cc-0.1.4 → patch_cc-0.2.0}/docs/PLAYBOOK.md +176 -13
- {patch_cc-0.1.4 → patch_cc-0.2.0}/pyproject.toml +3 -2
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/__init__.py +1 -1
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/blob.py +8 -3
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/container.py +10 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/macho.py +0 -12
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/cache.py +25 -1
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/cli.py +437 -35
- patch_cc-0.2.0/src/patch_cc/codex/__init__.py +62 -0
- patch_cc-0.2.0/src/patch_cc/codex/gateway.py +407 -0
- patch_cc-0.2.0/src/patch_cc/codex/models.py +334 -0
- patch_cc-0.2.0/src/patch_cc/codex/oauth.py +361 -0
- patch_cc-0.2.0/src/patch_cc/codex/translate.py +855 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/doctor.py +29 -8
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/locate.py +29 -17
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/menu.py +495 -68
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patcher.py +99 -45
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/__init__.py +12 -12
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/agents.py +44 -8
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/base.py +43 -17
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/chrome.py +16 -9
- patch_cc-0.2.0/src/patch_cc/patches/codex.py +523 -0
- patch_cc-0.2.0/src/patch_cc/ui.py +108 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/uv.lock +1 -1
- patch_cc-0.1.4/src/patch_cc/ui.py +0 -69
- {patch_cc-0.1.4 → patch_cc-0.2.0}/.github/workflows/release.yml +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/LICENSE +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/docs/CONDUCT.md +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/__init__.py +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/elf.py +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/bun/errors.py +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/output.py +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/streaming.py +0 -0
- {patch_cc-0.1.4 → patch_cc-0.2.0}/src/patch_cc/patches/thinking.py +0 -0
patch_cc-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# patch-cc
|
|
2
|
+
|
|
3
|
+
Interactive patcher for the Claude Code native binary. What it does and how to
|
|
4
|
+
use it: [README.md](README.md).
|
|
5
|
+
|
|
6
|
+
Before changing anything, read [docs/CONDUCT.md](docs/CONDUCT.md) — how we
|
|
7
|
+
build here: the mindset, the guardrails, and who commits (the user does). Then:
|
|
8
|
+
|
|
9
|
+
- [docs/PLAYBOOK.md](docs/PLAYBOOK.md) — matcher rules, the patch reference,
|
|
10
|
+
how to repair a patch after a Claude update, and the map of the bundle's
|
|
11
|
+
native surfaces (the model registry and its consumers).
|
|
12
|
+
- [docs/INTERNALS.md](docs/INTERNALS.md) — the Bun container format and how
|
|
13
|
+
the binary is rewritten in place.
|
|
14
|
+
|
|
15
|
+
Verify with `uv run patch-cc doctor` (every patch against a clean bundle;
|
|
16
|
+
point it at the pristine copies in `~/.local/share/patch-cc/backups/` to sweep
|
|
17
|
+
older builds). There is no test suite by design — doctor against real bundles
|
|
18
|
+
is the check.
|
patch_cc-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patch-cc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -8,7 +8,7 @@ Project-URL: Issues, https://github.com/anfreire/patch-cc/issues
|
|
|
8
8
|
Author-email: André Freire Ferreira <anfreire.dev@gmail.com>
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Keywords: bun,claude,claude-code,cli,patch,tui
|
|
11
|
+
Keywords: bun,claude,claude-code,cli,codex,gpt,openai,patch,tui
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
22
|
Classifier: Topic :: Software Development
|
|
22
23
|
Classifier: Topic :: Utilities
|
|
23
24
|
Requires-Python: >=3.11
|
|
@@ -52,12 +53,12 @@ uvx patch-cc # fullscreen menu, no install needed
|
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
The menu is a single centered panel: move with `↑ ↓`, toggle with `space`,
|
|
55
|
-
press `s` to save. Patches that carry a setting — subagent models,
|
|
56
|
-
name, the `--version` marker — open a centered modal on `enter`, and
|
|
57
|
-
then shows what you chose. Everything choosable is a picker: the agent
|
|
58
|
-
and model aliases are **discovered from your binary itself
|
|
59
|
-
|
|
60
|
-
genuinely free-text values.
|
|
56
|
+
press `s` to save. Patches that carry a setting — subagent models, Codex models,
|
|
57
|
+
the startup name, the `--version` marker — open a centered modal on `enter`, and
|
|
58
|
+
the row then shows what you chose. Everything choosable is a picker: the agent
|
|
59
|
+
names and model aliases are **discovered from your binary itself** (and the
|
|
60
|
+
Codex ones from your plan), so the menu can never offer something your build
|
|
61
|
+
would reject. Typing exists only for the genuinely free-text values.
|
|
61
62
|
|
|
62
63
|
A patched binary records what was applied inside itself, so the menu always
|
|
63
64
|
comes up showing the real current state, and `patch-cc status` answers
|
|
@@ -84,6 +85,7 @@ patch-cc # then just run it
|
|
|
84
85
|
| Chrome | Disable spinner tips | No rotating tips on the spinner |
|
|
85
86
|
| | Custom startup name | Defaults to `<your username>'s Code` |
|
|
86
87
|
| | Mark `--version` | Appends `(patched)` — or any marker you choose |
|
|
88
|
+
| Codex | Codex models | Use OpenAI/Codex-plan models in Claude Code — see [Codex models](#codex-models) |
|
|
87
89
|
|
|
88
90
|
## Usage
|
|
89
91
|
|
|
@@ -97,6 +99,7 @@ uvx patch-cc apply --brand # + branding as <username>'s Code
|
|
|
97
99
|
uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
|
|
98
100
|
uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
|
|
99
101
|
uvx patch-cc apply --suffix "(mine)" # custom --version marker
|
|
102
|
+
uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
|
|
100
103
|
uvx patch-cc apply --from-cache # replay your last remembered selection
|
|
101
104
|
uvx patch-cc status # exactly what is applied
|
|
102
105
|
uvx patch-cc doctor # do all patches match this build?
|
|
@@ -105,8 +108,67 @@ uvx patch-cc list # every patch, described
|
|
|
105
108
|
uvx patch-cc restore # put the original back
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
`--model` and `--
|
|
109
|
-
against what your installed binary actually ships
|
|
111
|
+
`--brand`, `--model` and `--codex` imply their patches; agents and models are
|
|
112
|
+
validated against what your installed binary actually ships, and Codex model ids
|
|
113
|
+
against what your plan offers. `apply --help` lists all three.
|
|
114
|
+
|
|
115
|
+
## Codex models
|
|
116
|
+
|
|
117
|
+
Bring your **ChatGPT/Codex-plan** models (GPT-5.x) into Claude Code and use them
|
|
118
|
+
alongside your Claude ones — in the `/model` picker, as a subagent override,
|
|
119
|
+
with `/effort` driving how hard they think. It is two halves: a patch that
|
|
120
|
+
teaches your binary to accept and route the models you pick, and a small
|
|
121
|
+
localhost **gateway** that translates between Claude Code and OpenAI. Only the
|
|
122
|
+
models you pick are diverted — every Anthropic request stays byte-identical,
|
|
123
|
+
so your Claude plan is untouched. Nothing extra to install: the gateway is pure
|
|
124
|
+
Python and ships with patch-cc; you just sign in to a ChatGPT or Codex plan.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uvx patch-cc codex login # sign in to your ChatGPT/Codex plan
|
|
128
|
+
uvx patch-cc # Codex models → pick models → save with `s`
|
|
129
|
+
uvx patch-cc codex serve # start the gateway; keep it running
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Which models you want is a patch setting like the startup name, so the menu's
|
|
133
|
+
**Codex models** row is where you pick them — it lists what your plan offers,
|
|
134
|
+
live. Everything the menu does the command line does too:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uvx patch-cc apply --codex gpt-5.6-sol --codex gpt-5.5
|
|
138
|
+
uvx patch-cc apply --help # lists the model ids your plan offers
|
|
139
|
+
uvx patch-cc apply --from-cache # replay your last selection
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Then pick a model like any other:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
claude --model sol # shortcut → newest gpt-5.6-sol
|
|
146
|
+
claude --model gpt-5.6-sol # the full id also works, as does /model
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- **Shortcuts** (`sol`, `terra`, `luna`, …) are the last word of the model id;
|
|
150
|
+
the newest in a family wins, the way `opus` means the latest Claude Opus. They
|
|
151
|
+
are derived from the ids you picked — there is nothing to configure.
|
|
152
|
+
- **Your binary is the record.** The models and the port live in the patched
|
|
153
|
+
binary, so `patch-cc status` names exactly what is registered, and
|
|
154
|
+
`codex serve` finds the right port with nothing to tell it.
|
|
155
|
+
- **They read as native everywhere.** A registered model is added to the
|
|
156
|
+
binary's own model table, so its real name shows in the status line and the
|
|
157
|
+
welcome banner, its plan-reported effort levels are declared, and
|
|
158
|
+
`/advisor sol` works like any other model. Ask for an effort the model
|
|
159
|
+
doesn't run and the gateway quietly runs the closest lower one — the same
|
|
160
|
+
clamping Claude Code documents for its own models.
|
|
161
|
+
- **The gateway has to be running.** Every surface that names it says whether it
|
|
162
|
+
is — the apply report as soon as you bake, `patch-cc status`, and `codex
|
|
163
|
+
status` — so you never learn it from a request that hangs instead.
|
|
164
|
+
- The gateway holds *your* OpenAI token and listens only on localhost. No
|
|
165
|
+
Anthropic-model request is ever diverted to it. The Codex ones that *are*
|
|
166
|
+
still carry Claude Code's own auth header — the gateway ignores it and never
|
|
167
|
+
forwards it, but treat the port as trusted: whatever binds it first sees it.
|
|
168
|
+
- After a Claude update, **re-bake** — the patch reverts with the binary — but
|
|
169
|
+
the gateway is separate and keeps running. Until you do, a Codex model you had
|
|
170
|
+
saved as your default reads as unavailable; the models live in the patch.
|
|
171
|
+
Change the port and the gateway needs a restart to follow it.
|
|
110
172
|
|
|
111
173
|
## After a Claude update
|
|
112
174
|
|
|
@@ -132,6 +194,8 @@ binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
|
|
|
132
194
|
|
|
133
195
|
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
134
196
|
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
197
|
+
Changing anything here starts at [docs/CONDUCT.md](docs/CONDUCT.md) — how this
|
|
198
|
+
is built, and what a patch has to prove before it ships.
|
|
135
199
|
|
|
136
200
|
## Credits
|
|
137
201
|
|
|
@@ -139,6 +203,9 @@ The patch set is a Python port of
|
|
|
139
203
|
[a-connoisseur/patch-claude-code](https://github.com/a-connoisseur/patch-claude-code),
|
|
140
204
|
with the subagent-model override idea from
|
|
141
205
|
[aleks-apostle/claude-code-patches](https://github.com/aleks-apostle/claude-code-patches).
|
|
206
|
+
Registering Codex models inside the bundle follows
|
|
207
|
+
[clodex](https://github.com/gxjansen/clodex); the routing here is done in the
|
|
208
|
+
bundle rather than with clodex's TLS interception.
|
|
142
209
|
|
|
143
210
|
## License
|
|
144
211
|
|
|
@@ -24,12 +24,12 @@ uvx patch-cc # fullscreen menu, no install needed
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
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,
|
|
28
|
-
name, the `--version` marker — open a centered modal on `enter`, and
|
|
29
|
-
then shows what you chose. Everything choosable is a picker: the agent
|
|
30
|
-
and model aliases are **discovered from your binary itself
|
|
31
|
-
|
|
32
|
-
genuinely free-text values.
|
|
27
|
+
press `s` to save. Patches that carry a setting — subagent models, Codex models,
|
|
28
|
+
the startup name, the `--version` marker — open a centered modal on `enter`, and
|
|
29
|
+
the row then shows what you chose. Everything choosable is a picker: the agent
|
|
30
|
+
names and model aliases are **discovered from your binary itself** (and the
|
|
31
|
+
Codex ones from your plan), so the menu can never offer something your build
|
|
32
|
+
would reject. Typing exists only for the genuinely free-text values.
|
|
33
33
|
|
|
34
34
|
A patched binary records what was applied inside itself, so the menu always
|
|
35
35
|
comes up showing the real current state, and `patch-cc status` answers
|
|
@@ -56,6 +56,7 @@ patch-cc # then just run it
|
|
|
56
56
|
| Chrome | Disable spinner tips | No rotating tips on the spinner |
|
|
57
57
|
| | Custom startup name | Defaults to `<your username>'s Code` |
|
|
58
58
|
| | Mark `--version` | Appends `(patched)` — or any marker you choose |
|
|
59
|
+
| Codex | Codex models | Use OpenAI/Codex-plan models in Claude Code — see [Codex models](#codex-models) |
|
|
59
60
|
|
|
60
61
|
## Usage
|
|
61
62
|
|
|
@@ -69,6 +70,7 @@ uvx patch-cc apply --brand # + branding as <username>'s Code
|
|
|
69
70
|
uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
|
|
70
71
|
uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
|
|
71
72
|
uvx patch-cc apply --suffix "(mine)" # custom --version marker
|
|
73
|
+
uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
|
|
72
74
|
uvx patch-cc apply --from-cache # replay your last remembered selection
|
|
73
75
|
uvx patch-cc status # exactly what is applied
|
|
74
76
|
uvx patch-cc doctor # do all patches match this build?
|
|
@@ -77,8 +79,67 @@ uvx patch-cc list # every patch, described
|
|
|
77
79
|
uvx patch-cc restore # put the original back
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
`--model` and `--
|
|
81
|
-
against what your installed binary actually ships
|
|
82
|
+
`--brand`, `--model` and `--codex` imply their patches; agents and models are
|
|
83
|
+
validated against what your installed binary actually ships, and Codex model ids
|
|
84
|
+
against what your plan offers. `apply --help` lists all three.
|
|
85
|
+
|
|
86
|
+
## Codex models
|
|
87
|
+
|
|
88
|
+
Bring your **ChatGPT/Codex-plan** models (GPT-5.x) into Claude Code and use them
|
|
89
|
+
alongside your Claude ones — in the `/model` picker, as a subagent override,
|
|
90
|
+
with `/effort` driving how hard they think. It is two halves: a patch that
|
|
91
|
+
teaches your binary to accept and route the models you pick, and a small
|
|
92
|
+
localhost **gateway** that translates between Claude Code and OpenAI. Only the
|
|
93
|
+
models you pick are diverted — every Anthropic request stays byte-identical,
|
|
94
|
+
so your Claude plan is untouched. Nothing extra to install: the gateway is pure
|
|
95
|
+
Python and ships with patch-cc; you just sign in to a ChatGPT or Codex plan.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uvx patch-cc codex login # sign in to your ChatGPT/Codex plan
|
|
99
|
+
uvx patch-cc # Codex models → pick models → save with `s`
|
|
100
|
+
uvx patch-cc codex serve # start the gateway; keep it running
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Which models you want is a patch setting like the startup name, so the menu's
|
|
104
|
+
**Codex models** row is where you pick them — it lists what your plan offers,
|
|
105
|
+
live. Everything the menu does the command line does too:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uvx patch-cc apply --codex gpt-5.6-sol --codex gpt-5.5
|
|
109
|
+
uvx patch-cc apply --help # lists the model ids your plan offers
|
|
110
|
+
uvx patch-cc apply --from-cache # replay your last selection
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then pick a model like any other:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
claude --model sol # shortcut → newest gpt-5.6-sol
|
|
117
|
+
claude --model gpt-5.6-sol # the full id also works, as does /model
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **Shortcuts** (`sol`, `terra`, `luna`, …) are the last word of the model id;
|
|
121
|
+
the newest in a family wins, the way `opus` means the latest Claude Opus. They
|
|
122
|
+
are derived from the ids you picked — there is nothing to configure.
|
|
123
|
+
- **Your binary is the record.** The models and the port live in the patched
|
|
124
|
+
binary, so `patch-cc status` names exactly what is registered, and
|
|
125
|
+
`codex serve` finds the right port with nothing to tell it.
|
|
126
|
+
- **They read as native everywhere.** A registered model is added to the
|
|
127
|
+
binary's own model table, so its real name shows in the status line and the
|
|
128
|
+
welcome banner, its plan-reported effort levels are declared, and
|
|
129
|
+
`/advisor sol` works like any other model. Ask for an effort the model
|
|
130
|
+
doesn't run and the gateway quietly runs the closest lower one — the same
|
|
131
|
+
clamping Claude Code documents for its own models.
|
|
132
|
+
- **The gateway has to be running.** Every surface that names it says whether it
|
|
133
|
+
is — the apply report as soon as you bake, `patch-cc status`, and `codex
|
|
134
|
+
status` — so you never learn it from a request that hangs instead.
|
|
135
|
+
- The gateway holds *your* OpenAI token and listens only on localhost. No
|
|
136
|
+
Anthropic-model request is ever diverted to it. The Codex ones that *are*
|
|
137
|
+
still carry Claude Code's own auth header — the gateway ignores it and never
|
|
138
|
+
forwards it, but treat the port as trusted: whatever binds it first sees it.
|
|
139
|
+
- After a Claude update, **re-bake** — the patch reverts with the binary — but
|
|
140
|
+
the gateway is separate and keeps running. Until you do, a Codex model you had
|
|
141
|
+
saved as your default reads as unavailable; the models live in the patch.
|
|
142
|
+
Change the port and the gateway needs a restart to follow it.
|
|
82
143
|
|
|
83
144
|
## After a Claude update
|
|
84
145
|
|
|
@@ -104,6 +165,8 @@ binary is *smaller* than the original (≈113 MB vs 267 MB), not larger.
|
|
|
104
165
|
|
|
105
166
|
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
106
167
|
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
168
|
+
Changing anything here starts at [docs/CONDUCT.md](docs/CONDUCT.md) — how this
|
|
169
|
+
is built, and what a patch has to prove before it ships.
|
|
107
170
|
|
|
108
171
|
## Credits
|
|
109
172
|
|
|
@@ -111,6 +174,9 @@ The patch set is a Python port of
|
|
|
111
174
|
[a-connoisseur/patch-claude-code](https://github.com/a-connoisseur/patch-claude-code),
|
|
112
175
|
with the subagent-model override idea from
|
|
113
176
|
[aleks-apostle/claude-code-patches](https://github.com/aleks-apostle/claude-code-patches).
|
|
177
|
+
Registering Codex models inside the bundle follows
|
|
178
|
+
[clodex](https://github.com/gxjansen/clodex); the routing here is done in the
|
|
179
|
+
bundle rather than with clodex's TLS interception.
|
|
114
180
|
|
|
115
181
|
## License
|
|
116
182
|
|
|
@@ -88,10 +88,12 @@ edit is followed by an ad-hoc `codesign` (mandatory on Apple Silicon).
|
|
|
88
88
|
|
|
89
89
|
## The manifest
|
|
90
90
|
|
|
91
|
-
Every patched bundle ends with a single comment line
|
|
91
|
+
Every patched bundle ends with a single comment line — the one description of
|
|
92
|
+
its shape; [PLAYBOOK.md](PLAYBOOK.md) covers what it means for matcher health:
|
|
92
93
|
|
|
93
94
|
```
|
|
94
|
-
//patch-cc {"v":1,"tool":"
|
|
95
|
+
//patch-cc {"v":1,"tool":"<version>","patches":[...],"brand":...,"models":{...},
|
|
96
|
+
"codex":{"port":8817,"models":["gpt-5.6-sol", ...]}}
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
That line is why `patch-cc status` can name exactly what is applied: several
|
|
@@ -100,6 +102,18 @@ can't collide with code and travels with the bundle through extract/repack.
|
|
|
100
102
|
The menu also reads it to pre-select the current patch set — the binary is the
|
|
101
103
|
state.
|
|
102
104
|
|
|
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
|
+
Each key records what was *asked for*, never what was derived from it. `codex`
|
|
110
|
+
carries model ids and a port and nothing else: a Codex model's display name and
|
|
111
|
+
context window are already baked into the bundle, and repeating them here would
|
|
112
|
+
be a second copy — one that a relabelling upstream could make disagree with the
|
|
113
|
+
binary it claims to describe. That is also what makes the manifest the single
|
|
114
|
+
home for the gateway port: `codex serve` and `codex status` read it from here
|
|
115
|
+
rather than from a store of their own.
|
|
116
|
+
|
|
103
117
|
## Safety
|
|
104
118
|
|
|
105
119
|
- Before the first patch of a version, the pristine binary is copied to
|
|
@@ -112,4 +126,6 @@ state.
|
|
|
112
126
|
- Every write is verified: patch-cc re-extracts the JS from the binary it just
|
|
113
127
|
wrote and asserts it equals what it meant to write.
|
|
114
128
|
- Patching a binary that is already marked, when no pristine backup exists, is
|
|
115
|
-
refused
|
|
129
|
+
refused outright — there is nothing clean to start from, and our edits change
|
|
130
|
+
lengths, so a second pass would corrupt rather than update. `restore` or a
|
|
131
|
+
reinstall are the only honest fixes; there is deliberately no override.
|
|
@@ -43,27 +43,37 @@ Anything the binary can enumerate for us, it does:
|
|
|
43
43
|
`"Internal` are plumbing and are not offered.
|
|
44
44
|
- **Model aliases** come from the Task tool's own input schema: the
|
|
45
45
|
`model:<zod>.enum([...])` whose describe-string starts
|
|
46
|
-
`Optional model override`
|
|
46
|
+
`Optional model override` — `agents.MODEL_ENUM`, one compiled anchor because
|
|
47
|
+
`codex-models` splices the chosen Codex ids into the very group
|
|
48
|
+
`discover_models` reads back out.
|
|
47
49
|
|
|
48
50
|
A new upstream agent or model appears in `patch-cc list`, the menu, and
|
|
49
51
|
`--model` validation without a code change. If the enum anchor ever vanishes,
|
|
50
52
|
discovery falls back to `haiku/sonnet/opus` — `doctor` prints both lists, so a
|
|
51
53
|
missing agent or alias is visible at a glance.
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
**The one hardcoded list, and why it may stay one.**
|
|
56
|
+
`models._RESERVED` names Claude's short built-ins so `apply --codex opus` is
|
|
57
|
+
refused, and so a *derived* family shortcut can never take one of those names
|
|
58
|
+
either (a pathological `gpt-5.6-opus` is the only way it could try). It is a
|
|
59
|
+
snapshot, so upstream can outgrow it — and it does not matter, because it is a
|
|
60
|
+
courtesy, not the guard. A name the bundle already knows makes `enum` and
|
|
61
|
+
`validator` find it *present*, so both rewrite nothing; they are `expect=True`,
|
|
62
|
+
so the patch reports broken, the fixpoint drops it, and nothing is written. The
|
|
63
|
+
bundle refuses the collision at bake time, derived rather than remembered.
|
|
64
|
+
Reserving the discovered set instead would buy a friendlier message for a hazard
|
|
65
|
+
that cannot land, at the price of reading a 275 MB binary before argument parsing
|
|
66
|
+
can finish.
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
//patch-cc {"v":1,"tool":"0.1.0","patches":[...],"brand":...,"models":{...}}
|
|
59
|
-
```
|
|
68
|
+
## The manifest
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
Every patched bundle ends with one comment line recording what was applied; its
|
|
71
|
+
shape lives in [INTERNALS.md](INTERNALS.md#the-manifest). What matters here is
|
|
72
|
+
that it makes `status` a parse instead of a guess — several patches are value
|
|
73
|
+
flips (`verbose:!0`) that leave no other fingerprint — and that `is_patched`
|
|
74
|
+
also still recognises the legacy fingerprints (`__cc_` identifiers, the old
|
|
75
|
+
`--version` marker), so binaries patched by pre-manifest versions are not
|
|
76
|
+
mistaken for clean.
|
|
67
77
|
|
|
68
78
|
## How resilience is detected
|
|
69
79
|
|
|
@@ -308,6 +318,13 @@ anchor, and where it lives.
|
|
|
308
318
|
definition (discovered as above): rewrite the `model:"..."` literal when the
|
|
309
319
|
definition has one, insert `model:"...",` right after `agentType:"...",`
|
|
310
320
|
when it doesn't. Both splice at offsets from a fresh discovery pass.
|
|
321
|
+
Every requested override is a **required step**: each one reaching this patch
|
|
322
|
+
has already been validated against the bundle by whichever surface asked for
|
|
323
|
+
it, so a pin that cannot be written is not a shape this build lacks — it is
|
|
324
|
+
the asked-for change failing, and the patch is dropped rather than shipping a
|
|
325
|
+
manifest that claims it. An override whose target the definition already
|
|
326
|
+
carries counts as landed: the step is judged on what it achieved, not on
|
|
327
|
+
whether bytes moved.
|
|
311
328
|
**The bypass:** one helper ignores the definition's model for a single
|
|
312
329
|
pinned agent (Explore today) — shape
|
|
313
330
|
`function f(def,main){if(def.agentType!==X.agentType||def.source!=="built-in")return def.model;…;return g(main)?PIN:"inherit"}`.
|
|
@@ -338,6 +355,152 @@ anchor, and where it lives.
|
|
|
338
355
|
build that pins two agents gets both handled, rewritten last-first so earlier
|
|
339
356
|
offsets stay valid.
|
|
340
357
|
|
|
358
|
+
### Codex — `codex.py`
|
|
359
|
+
|
|
360
|
+
- **`codex-models`** — make Claude Code accept and show the chosen Codex models,
|
|
361
|
+
and divert *only* those models' requests to the localhost gateway.
|
|
362
|
+
Eight sub-steps; with nothing chosen the patch is a no-op, like
|
|
363
|
+
`subagent-models`. The ids and the port are ordinary patch configuration
|
|
364
|
+
(`Options.codex_models` / `codex_port`) — chosen in the menu or with
|
|
365
|
+
`--codex`/`--codex-port`, never read from a store of the patch's own.
|
|
366
|
+
|
|
367
|
+
| step | what it changes | anchor |
|
|
368
|
+
|---|---|---|
|
|
369
|
+
| `enum`\* | the Task tool's `model` enum, so a subagent can be pinned to a Codex id | `agents.MODEL_ENUM` — the same anchor `discover_models` reads |
|
|
370
|
+
| `validator`\* | the known-model array — it gates *resolution*, not just acceptance | `["sonnet","opus","haiku","fable",…,"opusplan",…]` |
|
|
371
|
+
| `resolver`\* | the override resolver `J9n` | its `case"best":{…}` block |
|
|
372
|
+
| `general-resolver`\* | the resolver every ordinary request uses, `Ei` | `case"best":return X();default:}` |
|
|
373
|
+
| `redirect`\* | swaps the request origin to `127.0.0.1:<port>` | the SDK's `buildRequest`, up to `let u=this.buildURL(…)` |
|
|
374
|
+
| `picker` | the `/model` list | `?[n,r]:[r];for(let i of o)push(e,i,t);` |
|
|
375
|
+
| `context` | the real context window | the brace-free `(e,t)` body reading `CLAUDE_CODE_MAX_CONTEXT_TOKENS` |
|
|
376
|
+
| `registry` | the binary's own model table — the status-line name, effort capabilities, `/advisor` eligibility | `],aliases:{` closing the `models:[...]` registry |
|
|
377
|
+
|
|
378
|
+
\* required (`expect=True`) — without any one of them the feature is dead.
|
|
379
|
+
`picker`, `context` and `registry` are refinements: absent, you can still
|
|
380
|
+
type `/model <id>` and get the 200k default under the model's raw id.
|
|
381
|
+
`context` has no step at all when no chosen model reports a window — there
|
|
382
|
+
is no rewrite owed, and reporting that as either a missing shape or a missed
|
|
383
|
+
rewrite would blame the build for having nothing to do.
|
|
384
|
+
|
|
385
|
+
**An id is a model's whole identity.** There is no second name to carry: the id
|
|
386
|
+
is what `--codex` takes, what the manifest records, what the enum and the
|
|
387
|
+
picker and the redirect array hold, and what a diverted request already names
|
|
388
|
+
by the time the gateway sees it. Everything else is derived — the display name
|
|
389
|
+
and the window from the plan, the shortcut from the id — so nothing downstream
|
|
390
|
+
keeps a mapping that could disagree with the bundle. That is why the gateway
|
|
391
|
+
needs only the port, and it is structural rather than a rule to remember:
|
|
392
|
+
there is no map left to read.
|
|
393
|
+
|
|
394
|
+
**The picker's label rule is one rule.** Every row's label is its handle
|
|
395
|
+
*spelled as a name* — `_display_name` turns `sol` into `Sol` and
|
|
396
|
+
`gpt-5.6-sol` into `GPT 5.6 Sol` — because the binary's own rows are named,
|
|
397
|
+
not slugged. Labelling id rows with the raw id instead made them the only
|
|
398
|
+
entries in the list wearing a different sort of name than their neighbours
|
|
399
|
+
(`gpt-5.6-sol` sitting under `Opus`). Two label rules is what let that
|
|
400
|
+
through, so there is one.
|
|
401
|
+
|
|
402
|
+
**The registry is the binary's own model table, and the sharpest splice
|
|
403
|
+
here.** Everything Claude Code knows about a model it did not hardcode a
|
|
404
|
+
check for lives in one embedded object — `models:[{id, family, display_name,
|
|
405
|
+
provider_ids, context, capabilities:[...], default_effort, advisor_rank,
|
|
406
|
+
...}]` plus the `aliases` map its resolvers read — validated by a zod
|
|
407
|
+
`safeParse` whose fallback is **empty** (`models:[]`): one malformed entry
|
|
408
|
+
strips every model, Claude's included, of its metadata. So the step emits
|
|
409
|
+
only fields the schema declares — the required four (`id`, `family`,
|
|
410
|
+
`display_name`, `provider_ids.first_party`) plus values with something true
|
|
411
|
+
to record. `default_effort` is deliberately not among them: the binary
|
|
412
|
+
resolves a missing default as `high` (`?.default_effort??"high"` — the very
|
|
413
|
+
default its own flagships declare), so omission makes `/effort auto` and an
|
|
414
|
+
untouched session mean on these models exactly what they mean on Opus,
|
|
415
|
+
instead of importing Codex-the-product's own default (`low` on sol). An
|
|
416
|
+
entry is what turns "accepted" into "first-class": the status line resolves
|
|
417
|
+
`display_name` through it (raw id without one), `/advisor` eligibility is
|
|
418
|
+
exactly "has an `advisor_rank`", the effort gates read `capabilities`, and
|
|
419
|
+
surfaces nobody has enumerated inherit the same answers. `/advisor sol` on a
|
|
420
|
+
freshly baked binary is the cheap end-to-end check that the entry parsed.
|
|
421
|
+
|
|
422
|
+
**What the entry deliberately leaves out.** `pricing` and
|
|
423
|
+
`max_output_tokens` (readers guard for absence; a subscription has no
|
|
424
|
+
per-token price; the cap never reaches the wire), `context` (the window
|
|
425
|
+
resolver ends on a flat 200k without consulting the registry — the `context`
|
|
426
|
+
step stays the one home for the window), and the `aliases` /
|
|
427
|
+
`latest_per_family` maps (shortcuts already resolve through the spliced
|
|
428
|
+
arms; a second mechanism would be a second home — and `latest_per_family`
|
|
429
|
+
feeds Claude's own system-prompt text). The `/model` picker does not iterate
|
|
430
|
+
the registry either — its rows are hand-built per family upstream, which is
|
|
431
|
+
why `picker` exists — and our row push is the same `{value,label,description}`
|
|
432
|
+
+ `.some()` idiom the binary itself uses for `ANTHROPIC_CUSTOM_MODEL_OPTION`.
|
|
433
|
+
|
|
434
|
+
**Capabilities can only say yes.** The binary reads an *absent* capability
|
|
435
|
+
as "ask the provider fallback", which on the first-party API is permissive —
|
|
436
|
+
upstream's own choice for models it does not know. That is why the `/effort`
|
|
437
|
+
menu offers the whole ladder on any imported model, and why an entry cannot
|
|
438
|
+
*hide* a level; hiding would mean splicing the per-level exclusion chains,
|
|
439
|
+
a new matcher surface bought for a cosmetic win. Which levels a model
|
|
440
|
+
actually runs is the backend's per-model ruling, refused with a structured
|
|
441
|
+
400 (`param:"reasoning.effort"`, `code:"invalid_value"`) before any
|
|
442
|
+
generation — measured: gpt-5.5 runs `xhigh` and refuses `max`. Baking that
|
|
443
|
+
ruling would be a copy free to go stale between bake and runtime, so the
|
|
444
|
+
gateway clamps off the refusal itself (`translate.clamp_effort`): one rung
|
|
445
|
+
down per retry, mirroring Claude Code's documented "highest supported level
|
|
446
|
+
at or below" rule for its own models. A menu that over-offers costs one
|
|
447
|
+
extra round-trip, never a dead turn.
|
|
448
|
+
|
|
449
|
+
**It runs before `subagent-models`, and that ordering is load-bearing.**
|
|
450
|
+
`enum` registers the ids in the very schema `discover_models` reads, so a
|
|
451
|
+
subagent pinned to a Codex model is offered exactly when that model is really
|
|
452
|
+
in the bundle. Registering *after* meant `subagent-models` had to be told
|
|
453
|
+
about the ids out of band, so it landed a pin whether or not
|
|
454
|
+
`codex-models` did: drop `codex-models` for a drifted anchor and the binary
|
|
455
|
+
kept `model:"gpt-5.6-sol"` on an agent, pointing at a model nothing had
|
|
456
|
+
registered, with the manifest asserting the override. Now the dropped patch
|
|
457
|
+
takes its pins down with it.
|
|
458
|
+
|
|
459
|
+
**Two resolvers, and why both.** `Ei` is the general one — it turns `opus`
|
|
460
|
+
into `claude-opus-4-8`, its return value *replaces* the model before the
|
|
461
|
+
request is built, and it passes an unknown-but-valid name straight through
|
|
462
|
+
(`return e`). `J9n` runs only when managed `availableModels` are active and
|
|
463
|
+
defaults to `null`, not passthrough. An id needs neither arm (identity
|
|
464
|
+
*is* passthrough); a **family shortcut** (`sol` → the newest
|
|
465
|
+
`gpt-<ver>-sol`) needs one in both, and `Ei`'s is what makes it work on the
|
|
466
|
+
ordinary path.
|
|
467
|
+
|
|
468
|
+
**The shortcut gate.** Shortcuts are registered only when the `Ei` anchor is
|
|
469
|
+
present. Absent it, none are registered anywhere and the ids — which need none
|
|
470
|
+
of this — carry on. Accepted-but-unresolved is the failure worth engineering
|
|
471
|
+
against: it leaves the redirect (which matches ids only), reaches Anthropic as
|
|
472
|
+
an unknown model, and 404s.
|
|
473
|
+
|
|
474
|
+
**Why the "already added?" checks are bounded.** The resolver check matches
|
|
475
|
+
only the contiguous arms *at the insertion point* (`_RESOLVER_ARMS`), never
|
|
476
|
+
the whole bundle: a short word like `auto` occurs as `case"auto":return` in
|
|
477
|
+
stock code, so a global check would skip its arm while the id arms still
|
|
478
|
+
marked the step applied — a shortcut that resolves nowhere. The picker drops
|
|
479
|
+
its build-time check for the same reason and leans on the runtime `.some()`
|
|
480
|
+
guard it injects. `Ei`'s own idempotency is the `default:}` adjacency: it
|
|
481
|
+
matches the pristine arm only.
|
|
482
|
+
|
|
483
|
+
**Routing knows nothing about shortcuts.** The redirect tests `body.model`
|
|
484
|
+
against the baked id array, and the context table is keyed the same way — by
|
|
485
|
+
the time either runs, `Ei` has already rewritten the shortcut. Measured on the
|
|
486
|
+
wire: `claude --model sol` arrives at the gateway as `"gpt-5.6-sol"`. That is
|
|
487
|
+
why a drift in the shortcuts costs shortcuts and nothing else.
|
|
488
|
+
|
|
489
|
+
**A diverted request still carries Claude Code's auth header.** Measured, not
|
|
490
|
+
assumed: point a listener at the gateway port and a Codex turn arrives with
|
|
491
|
+
`Authorization: Bearer sk-ant-oat01-…` and the whole prompt. The gateway
|
|
492
|
+
ignores it and never forwards it, so the exposure is to whatever holds the
|
|
493
|
+
port. Stripping it from `redirect` does **not** work — don't retry it blind.
|
|
494
|
+
`options.headers` is the last source `buildHeaders` merges and its merge
|
|
495
|
+
treats `null` as delete, so setting a null (or an inert value) there ought to
|
|
496
|
+
win; neither reaches the wire. With the injected block proven to run — a
|
|
497
|
+
marker spliced into the replacement URL came through in the path — a probe
|
|
498
|
+
header set on the options object *and* on the local copy was absent from the
|
|
499
|
+
request both times, so something between `buildRequest` and `fetch` discards
|
|
500
|
+
`options.headers` on this build. A real fix needs its own anchor further
|
|
501
|
+
down, in `prepareRequest` (it receives the final `Headers` and the URL): a new
|
|
502
|
+
required step and new matcher surface, deliberately not taken for 0.2.0.
|
|
503
|
+
|
|
341
504
|
### Chrome & branding — `chrome.py`
|
|
342
505
|
|
|
343
506
|
- **`spinner-tips`** — force spinner tips off. Anchors: `spinnerTipsEnabled===!1`
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "patch-cc"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Interactive patcher for the Claude Code native binary"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -13,7 +13,7 @@ license-files = ["LICENSE"]
|
|
|
13
13
|
authors = [
|
|
14
14
|
{ name = "André Freire Ferreira", email = "anfreire.dev@gmail.com" },
|
|
15
15
|
]
|
|
16
|
-
keywords = ["claude", "claude-code", "patch", "bun", "cli", "tui"]
|
|
16
|
+
keywords = ["claude", "claude-code", "codex", "openai", "gpt", "patch", "bun", "cli", "tui"]
|
|
17
17
|
classifiers = [
|
|
18
18
|
"Development Status :: 3 - Alpha",
|
|
19
19
|
"Environment :: Console",
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Programming Language :: Python :: 3.11",
|
|
25
25
|
"Programming Language :: Python :: 3.12",
|
|
26
26
|
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
27
28
|
"Topic :: Software Development",
|
|
28
29
|
"Topic :: Utilities",
|
|
29
30
|
]
|
|
@@ -90,9 +90,14 @@ class Blob:
|
|
|
90
90
|
return self.payload(self.entry_module().ranges["contents"])
|
|
91
91
|
|
|
92
92
|
def bytecode_size(self) -> int:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
"""Bytes of precompiled bytecode on the entrypoint, for either layout.
|
|
94
|
+
|
|
95
|
+
``bytecode`` is the fourth pair, so *both* record formats carry it --
|
|
96
|
+
``FIELDS_OLD`` is the first four of ``FIELDS_NEW``. Reporting zero for the
|
|
97
|
+
36-byte layout said "already stripped" about a module that had 154 MB of
|
|
98
|
+
it, which is the one thing ``status`` reads this for.
|
|
99
|
+
"""
|
|
100
|
+
return self.entry_module().ranges["bytecode"][1]
|
|
96
101
|
|
|
97
102
|
|
|
98
103
|
def _detect_struct_size(modules_len: int) -> int:
|
|
@@ -127,3 +127,13 @@ def verify(path: str, expected: str) -> None:
|
|
|
127
127
|
"patched binary did not round-trip: extracted source differs from "
|
|
128
128
|
f"what we wrote ({len(written.source):,} vs {len(expected):,} bytes)"
|
|
129
129
|
)
|
|
130
|
+
if written.bytecode_size:
|
|
131
|
+
# Read back off the written file, not asserted in memory. Bun runs the
|
|
132
|
+
# bytecode in preference to the source, so any left behind would run the
|
|
133
|
+
# *unpatched* program while every check above agreed the source was ours
|
|
134
|
+
# -- every patch a silent no-op. docs/INTERNALS.md calls this the tripwire
|
|
135
|
+
# for a Bun that makes bytecode authoritative; this is where it trips.
|
|
136
|
+
raise ContainerError(
|
|
137
|
+
f"patched binary still carries {written.bytecode_size:,} bytes of "
|
|
138
|
+
"entrypoint bytecode, which would run instead of our edits"
|
|
139
|
+
)
|