patch-cc 0.1.0__tar.gz → 0.1.2__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 (33) hide show
  1. {patch_cc-0.1.0 → patch_cc-0.1.2}/PKG-INFO +1 -1
  2. {patch_cc-0.1.0 → patch_cc-0.1.2}/pyproject.toml +1 -1
  3. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/__init__.py +1 -1
  4. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/menu.py +1 -1
  5. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/agents.py +1 -0
  6. patch_cc-0.1.2/src/patch_cc/patches/thinking.py +142 -0
  7. {patch_cc-0.1.0 → patch_cc-0.1.2}/uv.lock +1 -1
  8. patch_cc-0.1.0/src/patch_cc/patches/thinking.py +0 -88
  9. {patch_cc-0.1.0 → patch_cc-0.1.2}/.github/workflows/ci.yml +0 -0
  10. {patch_cc-0.1.0 → patch_cc-0.1.2}/.github/workflows/release.yml +0 -0
  11. {patch_cc-0.1.0 → patch_cc-0.1.2}/.gitignore +0 -0
  12. {patch_cc-0.1.0 → patch_cc-0.1.2}/LICENSE +0 -0
  13. {patch_cc-0.1.0 → patch_cc-0.1.2}/README.md +0 -0
  14. {patch_cc-0.1.0 → patch_cc-0.1.2}/docs/CONDUCT.md +0 -0
  15. {patch_cc-0.1.0 → patch_cc-0.1.2}/docs/INTERNALS.md +0 -0
  16. {patch_cc-0.1.0 → patch_cc-0.1.2}/docs/PLAYBOOK.md +0 -0
  17. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/__init__.py +0 -0
  18. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/blob.py +0 -0
  19. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/container.py +0 -0
  20. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/elf.py +0 -0
  21. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/errors.py +0 -0
  22. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/bun/macho.py +0 -0
  23. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/cache.py +0 -0
  24. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/cli.py +0 -0
  25. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/doctor.py +0 -0
  26. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/locate.py +0 -0
  27. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patcher.py +0 -0
  28. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/__init__.py +0 -0
  29. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/base.py +0 -0
  30. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/chrome.py +0 -0
  31. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/output.py +0 -0
  32. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/patches/streaming.py +0 -0
  33. {patch_cc-0.1.0 → patch_cc-0.1.2}/src/patch_cc/ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patch-cc
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "patch-cc"
7
- version = "0.1.0"
7
+ version = "0.1.2"
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.1.0"
3
+ __version__ = "0.1.2"
@@ -906,7 +906,7 @@ class MenuApp:
906
906
  return seg_lines
907
907
 
908
908
  def _head(self, panel_width: int) -> list[Text]:
909
- title = Text("✳ patch-cc ✳", style=f"bold {_ACCENT}")
909
+ title = Text("patch-cc", style=f"bold {_ACCENT}")
910
910
  return [
911
911
  Text(""),
912
912
  _center(title, panel_width),
@@ -275,6 +275,7 @@ PATCHES = [
275
275
  summary="Show a subagent's Prompt block during normal use, not only in transcript mode.",
276
276
  group=GROUP_AGENTS,
277
277
  fn=_subagent_prompt,
278
+ default=False,
278
279
  anchors=('"Backgrounded agent"', 'action:"app:toggleTranscript"'),
279
280
  ),
280
281
  Patch(
@@ -0,0 +1,142 @@
1
+ """Make thinking blocks visible in the normal UI instead of transcript-only.
2
+
3
+ Three gates hide a finished thinking block in the normal view, and all three
4
+ must go or the patch silently does nothing (which is exactly how it shipped
5
+ broken in 0.1.0):
6
+
7
+ 1. **Grouping.** The activity-group builder swallows every thinking-first
8
+ assistant message into the collapsed "✻ ... (thought for Ns)" group, so the
9
+ message never reaches the renderer at all. This was the missed gate: the
10
+ two rewrites below applied cleanly to a switch arm that thinking text never
11
+ flows through.
12
+ 2. **The null guard.** The ``case"thinking":`` arm returns null outside
13
+ transcript/verbose mode.
14
+ 3. **Presentation.** The renderer collapses the text to one italic line unless
15
+ ``isTranscriptMode`` is set.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import re
21
+
22
+ from .base import (
23
+ GROUP_THINKING,
24
+ IDENT,
25
+ Options,
26
+ Outcome,
27
+ Patch,
28
+ compile_js,
29
+ splice,
30
+ switch_case_end,
31
+ )
32
+
33
+ # The group-builder branch pair that decides where a finished thinking message
34
+ # goes. Branch one (queued prompts) is matched only to *discover* the flush
35
+ # function, visible list, and loop variable; it is reconstructed verbatim.
36
+ # Branch two is the swallow: stash the text as the group's summary line and
37
+ # hide the message inside the group. Rewritten to flush the open group and
38
+ # push the message into the visible flow instead, keeping the think-time
39
+ # accounting.
40
+ _GROUP_ROUTING = compile_js(
41
+ rf"else if\(({IDENT})\(({IDENT})\)\)({IDENT})\(\),({IDENT})\.push\(\2\);"
42
+ rf"else if\(({IDENT})!==void 0\)\{{"
43
+ rf'if\(({IDENT})\.latestThinkingSummary=\5\.text\.trim\(\)\.replace\(/\\s\+/g," "\),'
44
+ rf"({IDENT})!==void 0\)\{{"
45
+ rf"let ({IDENT})=Date\.parse\(\2\.timestamp\)-Date\.parse\(\7\);"
46
+ rf"if\(Number\.isFinite\(\8\)&&\8>0\)\6\.thoughtForMs\+=Math\.min\(\8,({IDENT}|\d+)\)\}}"
47
+ rf"\6\.messages\.push\(\5\.message\)\}}"
48
+ )
49
+
50
+ # The early return that hides thinking outside transcript/verbose mode.
51
+ # 2.1.216 wrapped the body in braces (`{return null}`); older builds used the
52
+ # bare statement. Accept both -- missing the block form is exactly how this
53
+ # patch silently broke once.
54
+ _NULL_GUARD = compile_js(
55
+ rf"if\(!{IDENT}(?:&&!{IDENT}){{1,2}}\)(?:\{{return null\}}|return null;?)"
56
+ )
57
+ _RENDERER_PROPS = compile_js(rf"((?:createElement|jsx|jsxs)\({IDENT},\{{)([^}}]*)\}}")
58
+ _IS_TRANSCRIPT = compile_js(r"isTranscriptMode:[^,}]+")
59
+ _HIDE_IN_TRANSCRIPT = compile_js(r"hideInTranscript:[^,}]+")
60
+
61
+
62
+ def _reroute_grouping(content: str, outcome: Outcome) -> str:
63
+ """Send finished thinking messages to the visible list, not the group."""
64
+ step = outcome.step("group-routing")
65
+
66
+ def rewrite(match: re.Match[str]) -> str:
67
+ gate, loop, flush, visible, summary, group, stamp, delta, cap = match.groups()
68
+ step.candidates += 1
69
+ step.applied += 1
70
+ return (
71
+ f"else if({gate}({loop})){flush}(),{visible}.push({loop});"
72
+ f"else if({summary}!==void 0){{"
73
+ f"if({stamp}!==void 0){{"
74
+ f"let {delta}=Date.parse({loop}.timestamp)-Date.parse({stamp});"
75
+ f"if(Number.isFinite({delta})&&{delta}>0)"
76
+ f"{group}.thoughtForMs+=Math.min({delta},{cap})}}"
77
+ f"{flush}(),{visible}.push({loop})}}"
78
+ )
79
+
80
+ return _GROUP_ROUTING.sub(rewrite, content)
81
+
82
+
83
+ def _thinking_inline(content: str, _options: Options, outcome: Outcome) -> str:
84
+ """Route thinking past the collapse machinery and render it expanded.
85
+
86
+ One rewrite in the activity-group builder (see :data:`_GROUP_ROUTING`),
87
+ then two inside every ``case"thinking":`` arm that renders with an
88
+ ``isTranscriptMode:`` prop: remove the early null-return, and force the
89
+ renderer into transcript presentation (full markdown instead of the
90
+ one-line collapsed form).
91
+ """
92
+ guard = outcome.step("null-guard")
93
+ props = outcome.step("renderer-props")
94
+ output = _reroute_grouping(content, outcome)
95
+ needle = 'case"thinking":'
96
+ index = 0
97
+
98
+ while True:
99
+ start = output.find(needle, index)
100
+ if start == -1:
101
+ break
102
+ end = switch_case_end(output, start + len(needle))
103
+ segment = output[start:end]
104
+ index = start + len(needle)
105
+
106
+ if "isTranscriptMode:" not in segment:
107
+ continue
108
+
109
+ next_segment, dropped = _NULL_GUARD.subn("", segment, count=1)
110
+ if dropped:
111
+ guard.candidates += 1
112
+ guard.applied += 1
113
+
114
+ def rewrite_props(match: re.Match[str]) -> str:
115
+ prefix, body = match.group(1), match.group(2)
116
+ updated = _IS_TRANSCRIPT.sub("isTranscriptMode:!0", body)
117
+ updated = _HIDE_IN_TRANSCRIPT.sub("hideInTranscript:!1", updated)
118
+ if updated == body:
119
+ return match.group(0)
120
+ props.candidates += 1
121
+ props.applied += 1
122
+ return f"{prefix}{updated}}}"
123
+
124
+ next_segment = _RENDERER_PROPS.sub(rewrite_props, next_segment)
125
+
126
+ if next_segment != segment:
127
+ output = splice(output, start, end, next_segment)
128
+ index = start + len(next_segment)
129
+
130
+ return output
131
+
132
+
133
+ PATCHES = [
134
+ Patch(
135
+ id="thinking-inline",
136
+ title="Always show thinking",
137
+ summary="Render thinking blocks inline instead of hiding them behind ctrl+o.",
138
+ group=GROUP_THINKING,
139
+ fn=_thinking_inline,
140
+ anchors=('case"thinking":', "isTranscriptMode:", "latestThinkingSummary"),
141
+ ),
142
+ ]
@@ -256,7 +256,7 @@ wheels = [
256
256
 
257
257
  [[package]]
258
258
  name = "patch-cc"
259
- version = "0.1.0"
259
+ version = "0.1.2"
260
260
  source = { editable = "." }
261
261
  dependencies = [
262
262
  { name = "blessed" },
@@ -1,88 +0,0 @@
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
- ]
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