patch-cc 0.1.0__py3-none-any.whl
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/__init__.py +3 -0
- patch_cc/bun/__init__.py +6 -0
- patch_cc/bun/blob.py +256 -0
- patch_cc/bun/container.py +129 -0
- patch_cc/bun/elf.py +281 -0
- patch_cc/bun/errors.py +12 -0
- patch_cc/bun/macho.py +124 -0
- patch_cc/cache.py +86 -0
- patch_cc/cli.py +399 -0
- patch_cc/doctor.py +116 -0
- patch_cc/locate.py +117 -0
- patch_cc/menu.py +1135 -0
- patch_cc/patcher.py +244 -0
- patch_cc/patches/__init__.py +75 -0
- patch_cc/patches/agents.py +289 -0
- patch_cc/patches/base.py +200 -0
- patch_cc/patches/chrome.py +187 -0
- patch_cc/patches/output.py +173 -0
- patch_cc/patches/streaming.py +922 -0
- patch_cc/patches/thinking.py +88 -0
- patch_cc/ui.py +23 -0
- patch_cc-0.1.0.dist-info/METADATA +141 -0
- patch_cc-0.1.0.dist-info/RECORD +26 -0
- patch_cc-0.1.0.dist-info/WHEEL +4 -0
- patch_cc-0.1.0.dist-info/entry_points.txt +2 -0
- patch_cc-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Make thinking blocks visible in the normal UI instead of transcript-only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from .base import (
|
|
8
|
+
GROUP_THINKING,
|
|
9
|
+
IDENT,
|
|
10
|
+
Options,
|
|
11
|
+
Outcome,
|
|
12
|
+
Patch,
|
|
13
|
+
compile_js,
|
|
14
|
+
splice,
|
|
15
|
+
switch_case_end,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# The early return that hides thinking outside transcript/verbose mode.
|
|
19
|
+
# 2.1.216 wrapped the body in braces (`{return null}`); older builds used the
|
|
20
|
+
# bare statement. Accept both -- missing the block form is exactly how this
|
|
21
|
+
# patch silently broke once.
|
|
22
|
+
_NULL_GUARD = compile_js(
|
|
23
|
+
rf"if\(!{IDENT}(?:&&!{IDENT}){{1,2}}\)(?:\{{return null\}}|return null;?)"
|
|
24
|
+
)
|
|
25
|
+
_RENDERER_PROPS = compile_js(rf"((?:createElement|jsx|jsxs)\({IDENT},\{{)([^}}]*)\}}")
|
|
26
|
+
_IS_TRANSCRIPT = compile_js(r"isTranscriptMode:[^,}]+")
|
|
27
|
+
_HIDE_IN_TRANSCRIPT = compile_js(r"hideInTranscript:[^,}]+")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _thinking_inline(content: str, _options: Options, outcome: Outcome) -> str:
|
|
31
|
+
"""Drop the transcript-only gate around thinking blocks.
|
|
32
|
+
|
|
33
|
+
Two rewrites inside every ``case"thinking":`` arm that renders with an
|
|
34
|
+
``isTranscriptMode:`` prop: remove the early null-return, and force the
|
|
35
|
+
renderer into transcript presentation (full markdown instead of the
|
|
36
|
+
one-line collapsed form).
|
|
37
|
+
"""
|
|
38
|
+
guard = outcome.step("null-guard")
|
|
39
|
+
props = outcome.step("renderer-props")
|
|
40
|
+
output = content
|
|
41
|
+
needle = 'case"thinking":'
|
|
42
|
+
index = 0
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
start = output.find(needle, index)
|
|
46
|
+
if start == -1:
|
|
47
|
+
break
|
|
48
|
+
end = switch_case_end(output, start + len(needle))
|
|
49
|
+
segment = output[start:end]
|
|
50
|
+
index = start + len(needle)
|
|
51
|
+
|
|
52
|
+
if "isTranscriptMode:" not in segment:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
next_segment, dropped = _NULL_GUARD.subn("", segment, count=1)
|
|
56
|
+
if dropped:
|
|
57
|
+
guard.candidates += 1
|
|
58
|
+
guard.applied += 1
|
|
59
|
+
|
|
60
|
+
def rewrite_props(match: re.Match[str]) -> str:
|
|
61
|
+
prefix, body = match.group(1), match.group(2)
|
|
62
|
+
updated = _IS_TRANSCRIPT.sub("isTranscriptMode:!0", body)
|
|
63
|
+
updated = _HIDE_IN_TRANSCRIPT.sub("hideInTranscript:!1", updated)
|
|
64
|
+
if updated == body:
|
|
65
|
+
return match.group(0)
|
|
66
|
+
props.candidates += 1
|
|
67
|
+
props.applied += 1
|
|
68
|
+
return f"{prefix}{updated}}}"
|
|
69
|
+
|
|
70
|
+
next_segment = _RENDERER_PROPS.sub(rewrite_props, next_segment)
|
|
71
|
+
|
|
72
|
+
if next_segment != segment:
|
|
73
|
+
output = splice(output, start, end, next_segment)
|
|
74
|
+
index = start + len(next_segment)
|
|
75
|
+
|
|
76
|
+
return output
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
PATCHES = [
|
|
80
|
+
Patch(
|
|
81
|
+
id="thinking-inline",
|
|
82
|
+
title="Always show thinking",
|
|
83
|
+
summary="Render thinking blocks inline instead of hiding them behind ctrl+o.",
|
|
84
|
+
group=GROUP_THINKING,
|
|
85
|
+
fn=_thinking_inline,
|
|
86
|
+
anchors=('case"thinking":', "isTranscriptMode:"),
|
|
87
|
+
),
|
|
88
|
+
]
|
patch_cc/ui.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Shared console helpers, so output styling lives in one place."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def heading(text: str) -> None:
|
|
11
|
+
console.print(f"\n[bold]{text}[/bold]")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ok(text: str) -> None:
|
|
15
|
+
console.print(f"[green]✓[/green] {text}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def warn(text: str) -> None:
|
|
19
|
+
console.print(f"[yellow]![/yellow] {text}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def err(text: str) -> None:
|
|
23
|
+
console.print(f"[red]✗[/red] {text}")
|
|
@@ -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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
patch_cc/__init__.py,sha256=0A68UdHt0gdfU4e2YJ6Rlz7zVz58STcp4rr3hdq23YQ,97
|
|
2
|
+
patch_cc/cache.py,sha256=90dgFQMfnLCZlh5OluNGAlfxgyPOI2MhsDINhwARj00,3022
|
|
3
|
+
patch_cc/cli.py,sha256=7QA5eFOaWf7Xya7bw5iXJcGhYXi70riimxpxyslJW4M,13853
|
|
4
|
+
patch_cc/doctor.py,sha256=zgOpIzJSz6ujTXsSpO9pKFa5xa2rE_lBmTR9Jkck_us,3859
|
|
5
|
+
patch_cc/locate.py,sha256=vp7zDD4a6-3rG_MTf9KbPhpPf7dnpC2OVym6w-kAbAg,3417
|
|
6
|
+
patch_cc/menu.py,sha256=FuSfYXpijzqk3CXnd990DXYza4yMf14dqcPCUZg1bcY,40617
|
|
7
|
+
patch_cc/patcher.py,sha256=yLR_b1FalwrWdKPr3VZJ5b9dzmsPxTi6xk-w-IcTH1M,8629
|
|
8
|
+
patch_cc/ui.py,sha256=CM1Jxz_WmxR1gIww8x_1D5PmR0rTP3YZg_kv7egndh0,467
|
|
9
|
+
patch_cc/bun/__init__.py,sha256=0keXjmpe6-9DwTcvDL8WHC5Pq-8egupsPCksJ7IHweI,210
|
|
10
|
+
patch_cc/bun/blob.py,sha256=q3HzvFW-TCMSHGZ878gtxMg7dDFkCyMeZEADIv1d6Gc,8665
|
|
11
|
+
patch_cc/bun/container.py,sha256=Ss8SbhzL_3RzQTD4ntzqTnJQO8w76_hITToMcTRMujc,3791
|
|
12
|
+
patch_cc/bun/elf.py,sha256=54bjOwvrzkGIHYNQg15mh-XInPH5Hpkbpo2aZACeduE,9312
|
|
13
|
+
patch_cc/bun/errors.py,sha256=67SW2E0GnYFZna_HU49CNI0YRpDepdm9R8Txi025PtI,402
|
|
14
|
+
patch_cc/bun/macho.py,sha256=uPYu43EHGZGp-SdYoiOGY3iIRwHTIu9tlcWQKqtN-sc,3984
|
|
15
|
+
patch_cc/patches/__init__.py,sha256=fMvb9OIKTrSVvhe9dL7P4kABgfbkPVbO4RRBjRRachw,1624
|
|
16
|
+
patch_cc/patches/agents.py,sha256=KSDFtnbR7ADzV-GWQDnfjEJMAvmlJaRd_DZ57JwTJV4,10479
|
|
17
|
+
patch_cc/patches/base.py,sha256=qY317zj0Fugj11LBvSM6LR6dkpv1G0v6ICmZnE3HOH0,6855
|
|
18
|
+
patch_cc/patches/chrome.py,sha256=6OuOTsueSfBX4UQtwerhWRQuPZ8NVdaMMoBUdny8GSc,5796
|
|
19
|
+
patch_cc/patches/output.py,sha256=QRhfeAE3-YeOkqZAxXCvHYfsW4yDn9dH30Tv6lHEx3E,6016
|
|
20
|
+
patch_cc/patches/streaming.py,sha256=jn3erCoKyKvE4BJThwUzrhBTxeFGg_Z-2d3z51fOxuE,35713
|
|
21
|
+
patch_cc/patches/thinking.py,sha256=HqRubTd4BGWyc2xShl-j3qDou-4rdtSswZL7UXlC9N0,2829
|
|
22
|
+
patch_cc-0.1.0.dist-info/METADATA,sha256=vR4AqCPZwSSYuWJqf-bDUQCJkbT-ELLDobR4pUmqUOc,6053
|
|
23
|
+
patch_cc-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
24
|
+
patch_cc-0.1.0.dist-info/entry_points.txt,sha256=ZvMxVPspj3fFAfiIvpMoT6tjx09R8qwjhTwsKZRDVXc,47
|
|
25
|
+
patch_cc-0.1.0.dist-info/licenses/LICENSE,sha256=9QUfkF5H2SyTXCNIlJHFVtyF0J3iYtDrHTtCYOeeXF0,1079
|
|
26
|
+
patch_cc-0.1.0.dist-info/RECORD,,
|
|
@@ -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.
|