patch-cc 0.2.3__tar.gz → 0.2.4__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 (41) hide show
  1. {patch_cc-0.2.3 → patch_cc-0.2.4}/PKG-INFO +2 -2
  2. {patch_cc-0.2.3 → patch_cc-0.2.4}/docs/PLAYBOOK.md +12 -4
  3. {patch_cc-0.2.3 → patch_cc-0.2.4}/pyproject.toml +1 -1
  4. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/__init__.py +1 -1
  5. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/cli.py +0 -5
  6. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/doctor.py +9 -8
  7. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patcher.py +11 -9
  8. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/__init__.py +0 -2
  9. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/base.py +0 -5
  10. {patch_cc-0.2.3 → patch_cc-0.2.4}/uv.lock +1 -1
  11. {patch_cc-0.2.3 → patch_cc-0.2.4}/.github/workflows/ci.yml +0 -0
  12. {patch_cc-0.2.3 → patch_cc-0.2.4}/.github/workflows/release.yml +0 -0
  13. {patch_cc-0.2.3 → patch_cc-0.2.4}/.gitignore +0 -0
  14. {patch_cc-0.2.3 → patch_cc-0.2.4}/AGENTS.md +0 -0
  15. {patch_cc-0.2.3 → patch_cc-0.2.4}/CLAUDE.md +0 -0
  16. {patch_cc-0.2.3 → patch_cc-0.2.4}/LICENSE +0 -0
  17. {patch_cc-0.2.3 → patch_cc-0.2.4}/README.md +0 -0
  18. {patch_cc-0.2.3 → patch_cc-0.2.4}/docs/CONDUCT.md +0 -0
  19. {patch_cc-0.2.3 → patch_cc-0.2.4}/docs/INTERNALS.md +0 -0
  20. {patch_cc-0.2.3 → patch_cc-0.2.4}/docs/demo.gif +0 -0
  21. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/__init__.py +0 -0
  22. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/blob.py +0 -0
  23. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/container.py +0 -0
  24. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/elf.py +0 -0
  25. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/errors.py +0 -0
  26. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/bun/macho.py +0 -0
  27. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/cache.py +0 -0
  28. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/codex/__init__.py +0 -0
  29. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/codex/gateway.py +0 -0
  30. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/codex/models.py +0 -0
  31. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/codex/oauth.py +0 -0
  32. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/codex/translate.py +0 -0
  33. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/locate.py +0 -0
  34. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/menu.py +0 -0
  35. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/agents.py +0 -0
  36. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/chrome.py +0 -0
  37. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/codex.py +0 -0
  38. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/output.py +0 -0
  39. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/streaming.py +0 -0
  40. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/patches/thinking.py +0 -0
  41. {patch_cc-0.2.3 → patch_cc-0.2.4}/src/patch_cc/ui.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: patch-cc
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -116,10 +116,18 @@ can finish.
116
116
  Every patched bundle ends with one comment line recording what was applied; its
117
117
  shape lives in [INTERNALS.md](INTERNALS.md#the-manifest). What matters here is
118
118
  that it makes `status` a parse instead of a guess — several patches are value
119
- flips (`verbose:!0`) that leave no other fingerprint — and that `is_patched`
120
- also still recognises the legacy fingerprints (`__cc_` identifiers, the old
121
- `--version` marker), so binaries patched by pre-manifest versions are not
122
- mistaken for clean.
119
+ flips (`verbose:!0`) that leave no other fingerprint — and that it is the
120
+ **only** evidence `is_patched` accepts. Authorship is declared, never inferred:
121
+ `is_patched` once also took side effects of our edits as proof — the `__cc_`
122
+ identifier prefix, the old `--version` marker — until 2.1.227 shipped
123
+ `__cc_name`/`__cc_line`/`__cc_set` shell variables of its own and every
124
+ pristine install read as patched, hard-blocking `apply`. A prefix tracks
125
+ upstream's naming fashion, never authorship (the same lesson as the `.enum(`
126
+ callee above), and once upstream ships it, it fires on every later build, so no
127
+ narrowing rescues an inferred fingerprint — `__cc_line` was already both our
128
+ arrow-param and their shell variable. Binaries patched by pre-manifest versions
129
+ therefore read as clean, which costs nothing now that Claude's daily
130
+ auto-update has long since replaced that population.
123
131
 
124
132
  ## How resilience is detected
125
133
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "patch-cc"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "Interactive patcher for the Claude Code native binary"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """patch-cc: an interactive patcher for the Claude Code native binary."""
2
2
 
3
- __version__ = "0.2.3"
3
+ __version__ = "0.2.4"
@@ -525,11 +525,6 @@ def cmd_status(args) -> int:
525
525
  if isinstance(codex.get("port"), int):
526
526
  style, note = gateway_note(codex["port"])
527
527
  console.print(f" gateway: [{style}]{note}[/{style}]")
528
- elif st.patched:
529
- console.print(
530
- " [dim]patched by an older patch-cc (no manifest); "
531
- "re-apply to record one[/dim]"
532
- )
533
528
  console.print(
534
529
  f" bytecode: "
535
530
  f"{'stripped' if st.bytecode_stripped else f'{st.bytecode_size / 1e6:.0f} MB present'}"
@@ -3,8 +3,8 @@
3
3
  Two different questions, deliberately kept apart:
4
4
 
5
5
  * **status** -- is the *installed* binary patched right now? Answered by the
6
- manifest comment every patched bundle ends with (plus legacy fingerprints),
7
- and the bytecode-stripped invariant.
6
+ manifest comment every patched bundle ends with, and the bytecode-stripped
7
+ invariant.
8
8
  * **dryrun** -- would our patches still apply to *this* bundle? Answered by
9
9
  running every patch and reporting per-step hits, so a silently drifted
10
10
  matcher shows up as a concrete "reducer.message_stop missed" instead of a
@@ -21,20 +21,23 @@ from dataclasses import dataclass, field
21
21
 
22
22
  from .bun import Bundle
23
23
  from .codex.models import CodexModel
24
- from .patcher import is_patched, read_manifest
24
+ from .patcher import read_manifest
25
25
  from .patches import ALL_PATCHES, Options, Outcome, Patch
26
26
  from .patches.agents import INHERIT, BuiltinAgent, discover_agents, discover_models
27
27
 
28
28
 
29
29
  @dataclass(slots=True)
30
30
  class Status:
31
- patched: bool
32
31
  bytecode_stripped: bool
33
32
  bytecode_size: int
34
33
  #: Parsed manifest for binaries patched by this tool; ``None`` when the
35
- #: binary is pristine or predates the manifest.
34
+ #: binary is pristine.
36
35
  manifest: dict | None
37
36
 
37
+ @property
38
+ def patched(self) -> bool:
39
+ return self.manifest is not None
40
+
38
41
  @property
39
42
  def patch_ids(self) -> list[str]:
40
43
  if not self.manifest:
@@ -48,12 +51,10 @@ class Status:
48
51
 
49
52
 
50
53
  def status(bundle: Bundle) -> Status:
51
- source = bundle.source
52
54
  return Status(
53
- patched=is_patched(source),
54
55
  bytecode_stripped=bundle.bytecode_size == 0,
55
56
  bytecode_size=bundle.bytecode_size,
56
- manifest=read_manifest(source),
57
+ manifest=read_manifest(bundle.source),
57
58
  )
58
59
 
59
60
 
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
 
12
12
  from . import __version__, locate
13
13
  from .bun import Bundle, container
14
- from .patches import ALL_PATCHES, DEFAULT_SUFFIX, SENTINEL, Options, Outcome, Patch
14
+ from .patches import ALL_PATCHES, DEFAULT_SUFFIX, Options, Outcome, Patch
15
15
 
16
16
  #: Every patched bundle ends with one comment line recording exactly what was
17
17
  #: applied. Comments cannot collide with code, survive re-extraction, and make
@@ -19,9 +19,6 @@ from .patches import ALL_PATCHES, DEFAULT_SUFFIX, SENTINEL, Options, Outcome, Pa
19
19
  #: fingerprint.
20
20
  MANIFEST_PREFIX = "//patch-cc "
21
21
 
22
- #: Fingerprints of binaries patched by versions before the manifest existed.
23
- _LEGACY_MARKER = "(Claude Code)\\n" + DEFAULT_SUFFIX
24
-
25
22
 
26
23
  class AlreadyPatchedError(RuntimeError):
27
24
  """The only source available is already patched; patching it would stack.
@@ -132,11 +129,16 @@ def read_manifest(source: str) -> dict | None:
132
129
 
133
130
 
134
131
  def is_patched(source: str) -> bool:
135
- return (
136
- ("\n" + MANIFEST_PREFIX) in source
137
- or SENTINEL in source
138
- or _LEGACY_MARKER in source
139
- )
132
+ """Whether the bundle carries our manifest -- the one mark of our work.
133
+
134
+ Authorship is declared, never inferred. This used to also sniff side
135
+ effects of our edits (the ``__cc_`` identifier prefix, the old
136
+ ``--version`` marker), until 2.1.227 shipped ``__cc_``-prefixed shell
137
+ variables of its own and every pristine install read as patched. An
138
+ inferred fingerprint is a bet that upstream's vocabulary never overlaps
139
+ ours, and once upstream ships it, it fires on every build after, forever.
140
+ """
141
+ return read_manifest(source) is not None
140
142
 
141
143
 
142
144
  def selected_patches(ids: list[str]) -> list[Patch]:
@@ -22,7 +22,6 @@ from .base import (
22
22
  GROUP_CHROME,
23
23
  GROUP_MODELS,
24
24
  GROUP_OUTPUT,
25
- SENTINEL,
26
25
  Options,
27
26
  Outcome,
28
27
  Patch,
@@ -61,7 +60,6 @@ __all__ = [
61
60
  "DEFAULT_BRAND",
62
61
  "DEFAULT_SUFFIX",
63
62
  "GROUP_ORDER",
64
- "SENTINEL",
65
63
  "Options",
66
64
  "Outcome",
67
65
  "Patch",
@@ -32,11 +32,6 @@ from ..codex import DEFAULT_PORT
32
32
  if TYPE_CHECKING:
33
33
  from ..codex.models import CodexModel
34
34
 
35
- #: Prefix for every identifier we inject. These are the only reliable
36
- #: fingerprints of our work -- value flips like ``verbose:!0`` are
37
- #: indistinguishable from the 11 that upstream already ships.
38
- SENTINEL = "__cc_"
39
-
40
35
  # Groups, in display order: what renders, what runs (and how hard), how it is
41
36
  # dressed. Generic on purpose -- a group named after a feature (Thinking,
42
37
  # Subagents, Codex) fits exactly that feature, and the next patch that is
@@ -320,7 +320,7 @@ wheels = [
320
320
 
321
321
  [[package]]
322
322
  name = "patch-cc"
323
- version = "0.2.3"
323
+ version = "0.2.4"
324
324
  source = { editable = "." }
325
325
  dependencies = [
326
326
  { name = "blessed" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes