patch-cc 0.2.0__tar.gz → 0.2.1__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.0 → patch_cc-0.2.1}/PKG-INFO +34 -21
  2. {patch_cc-0.2.0 → patch_cc-0.2.1}/README.md +33 -20
  3. {patch_cc-0.2.0 → patch_cc-0.2.1}/docs/INTERNALS.md +1 -1
  4. {patch_cc-0.2.0 → patch_cc-0.2.1}/docs/PLAYBOOK.md +48 -3
  5. patch_cc-0.2.1/docs/demo.gif +0 -0
  6. {patch_cc-0.2.0 → patch_cc-0.2.1}/pyproject.toml +1 -1
  7. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/__init__.py +1 -1
  8. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/cache.py +3 -0
  9. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/cli.py +27 -2
  10. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/doctor.py +6 -1
  11. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/menu.py +37 -9
  12. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patcher.py +5 -0
  13. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/__init__.py +2 -4
  14. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/agents.py +12 -3
  15. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/base.py +11 -5
  16. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/chrome.py +56 -0
  17. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/codex.py +2 -2
  18. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/streaming.py +2 -2
  19. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/thinking.py +80 -3
  20. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/ui.py +2 -0
  21. {patch_cc-0.2.0 → patch_cc-0.2.1}/uv.lock +1 -1
  22. {patch_cc-0.2.0 → patch_cc-0.2.1}/.github/workflows/ci.yml +0 -0
  23. {patch_cc-0.2.0 → patch_cc-0.2.1}/.github/workflows/release.yml +0 -0
  24. {patch_cc-0.2.0 → patch_cc-0.2.1}/.gitignore +0 -0
  25. {patch_cc-0.2.0 → patch_cc-0.2.1}/AGENTS.md +0 -0
  26. {patch_cc-0.2.0 → patch_cc-0.2.1}/CLAUDE.md +0 -0
  27. {patch_cc-0.2.0 → patch_cc-0.2.1}/LICENSE +0 -0
  28. {patch_cc-0.2.0 → patch_cc-0.2.1}/docs/CONDUCT.md +0 -0
  29. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/__init__.py +0 -0
  30. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/blob.py +0 -0
  31. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/container.py +0 -0
  32. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/elf.py +0 -0
  33. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/errors.py +0 -0
  34. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/bun/macho.py +0 -0
  35. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/codex/__init__.py +0 -0
  36. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/codex/gateway.py +0 -0
  37. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/codex/models.py +0 -0
  38. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/codex/oauth.py +0 -0
  39. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/codex/translate.py +0 -0
  40. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/locate.py +0 -0
  41. {patch_cc-0.2.0 → patch_cc-0.2.1}/src/patch_cc/patches/output.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patch-cc
3
- Version: 0.2.0
3
+ Version: 0.2.1
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
@@ -34,8 +34,16 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  An interactive patcher for the **Claude Code native binary**. Pick the tweaks
36
36
  you want — inline and live thinking, detailed tool calls, subagent model
37
- overrides, your own startup name and apply them to your installed `claude`
38
- in one keystroke. Pure Python; no Node, no Bun.
37
+ overrides, your own startup name, your **ChatGPT/Codex-plan GPT models as
38
+ native models** and apply them to your installed `claude` in one keystroke.
39
+ Fully reversible: a pristine backup is kept, `patch-cc restore` puts it back.
40
+ Pure Python; no Node, no Bun.
41
+
42
+ ```bash
43
+ uvx patch-cc # fullscreen menu, no install needed
44
+ ```
45
+
46
+ ![patch-cc: pick tweaks, register a Codex model, pin the Plan agent to it, bake — the patched binary is 161 MB smaller](https://raw.githubusercontent.com/anfreire/patch-cc/main/docs/demo.gif)
39
47
 
40
48
  ## Requirements
41
49
 
@@ -48,17 +56,14 @@ in one keystroke. Pure Python; no Node, no Bun.
48
56
  - **macOS only:** the Xcode command line tools, for `codesign` — a patched
49
57
  binary has to be re-signed or macOS refuses to run it.
50
58
 
51
- ```bash
52
- uvx patch-cc # fullscreen menu, no install needed
53
- ```
54
-
55
59
  The menu is a single centered panel: move with `↑ ↓`, toggle with `space`,
56
60
  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
+ the startup name, the `--version` marker, the org/email label — open a centered
62
+ modal on `enter`, and the row then shows what you chose. Everything choosable
63
+ is a picker: the agent names and model aliases are **discovered from your
64
+ binary itself** (and the Codex ones from your plan), so the menu can never
65
+ offer something your build would reject. Typing exists only for the genuinely
66
+ free-text values.
62
67
 
63
68
  A patched binary records what was applied inside itself, so the menu always
64
69
  comes up showing the real current state, and `patch-cc status` answers
@@ -75,17 +80,19 @@ patch-cc # then just run it
75
80
 
76
81
  | Group | Patch | |
77
82
  |---|---|---|
78
- | Output & diffs | Detailed tool calls | Show full read/search calls, not collapsed summaries |
83
+ | Output & display | Detailed tool calls | Show full read/search calls, not collapsed summaries |
79
84
  | | Colour new files as diffs | Created files render with `+` lines and green |
80
- | Thinking | Fix blank thinking blocks | Opt out of the server-side experiment that can empty every thinking block |
85
+ | | Fix blank thinking blocks | Opt out of the server-side experiment that can empty every thinking block |
81
86
  | | Always show thinking | Thinking blocks stay inline — no `ctrl+o` |
82
87
  | | Stream thinking live | See reasoning as it is generated, inline and in order |
83
- | Subagents | Show subagent prompts | Prompt blocks visible during normal use |
88
+ | | Show subagent prompts | Prompt blocks visible during normal use |
89
+ | Models & effort | Persist max effort | `/effort max` saves as your default for new sessions, like the other levels |
90
+ | | Codex models | Use OpenAI/Codex-plan models in Claude Code — see [Codex models](#codex-models) |
84
91
  | | Override subagent models | Pick the model per built-in agent (discovered from your binary) |
85
- | Chrome | Disable spinner tips | No rotating tips on the spinner |
86
- | | Custom startup name | Defaults to `<your username>'s Code` |
92
+ | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
87
93
  | | 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) |
94
+ | | Custom startup name | Defaults to `<your username>'s Code` |
95
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it |
89
96
 
90
97
  ## Usage
91
98
 
@@ -99,6 +106,8 @@ uvx patch-cc apply --brand # + branding as <username>'s Code
99
106
  uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
100
107
  uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
101
108
  uvx patch-cc apply --suffix "(mine)" # custom --version marker
109
+ uvx patch-cc apply --org-label # hide the welcome screen's org/email
110
+ uvx patch-cc apply --org-label "Ada's Lab" # ...or show this instead
102
111
  uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
103
112
  uvx patch-cc apply --from-cache # replay your last remembered selection
104
113
  uvx patch-cc status # exactly what is applied
@@ -108,9 +117,9 @@ uvx patch-cc list # every patch, described
108
117
  uvx patch-cc restore # put the original back
109
118
  ```
110
119
 
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.
120
+ A flag that configures a patch also selects it `apply --help` lists them
121
+ all. Agents and models are validated against what your installed binary
122
+ actually ships, and Codex model ids against what your plan offers.
114
123
 
115
124
  ## Codex models
116
125
 
@@ -210,3 +219,7 @@ bundle rather than with clodex's TLS interception.
210
219
  ## License
211
220
 
212
221
  MIT
222
+
223
+ ---
224
+
225
+ **More agent tooling** — [summon-cc](https://github.com/anfreire/summon-cc): give your agent a crew of Claude Code workers · [cc-oc](https://github.com/anfreire/cc-oc): drive opencode from inside Claude Code · [omoctl](https://github.com/anfreire/omoctl): manage oh-my-openagent profiles · [wiki-spaces](https://github.com/anfreire/wiki-spaces): a wiki your AI agent keeps
@@ -5,8 +5,16 @@
5
5
 
6
6
  An interactive patcher for the **Claude Code native binary**. Pick the tweaks
7
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.
8
+ overrides, your own startup name, your **ChatGPT/Codex-plan GPT models as
9
+ native models** and apply them to your installed `claude` in one keystroke.
10
+ Fully reversible: a pristine backup is kept, `patch-cc restore` puts it back.
11
+ Pure Python; no Node, no Bun.
12
+
13
+ ```bash
14
+ uvx patch-cc # fullscreen menu, no install needed
15
+ ```
16
+
17
+ ![patch-cc: pick tweaks, register a Codex model, pin the Plan agent to it, bake — the patched binary is 161 MB smaller](https://raw.githubusercontent.com/anfreire/patch-cc/main/docs/demo.gif)
10
18
 
11
19
  ## Requirements
12
20
 
@@ -19,17 +27,14 @@ in one keystroke. Pure Python; no Node, no Bun.
19
27
  - **macOS only:** the Xcode command line tools, for `codesign` — a patched
20
28
  binary has to be re-signed or macOS refuses to run it.
21
29
 
22
- ```bash
23
- uvx patch-cc # fullscreen menu, no install needed
24
- ```
25
-
26
30
  The menu is a single centered panel: move with `↑ ↓`, toggle with `space`,
27
31
  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.
32
+ the startup name, the `--version` marker, the org/email label — open a centered
33
+ modal on `enter`, and the row then shows what you chose. Everything choosable
34
+ is a picker: the agent names and model aliases are **discovered from your
35
+ binary itself** (and the Codex ones from your plan), so the menu can never
36
+ offer something your build would reject. Typing exists only for the genuinely
37
+ free-text values.
33
38
 
34
39
  A patched binary records what was applied inside itself, so the menu always
35
40
  comes up showing the real current state, and `patch-cc status` answers
@@ -46,17 +51,19 @@ patch-cc # then just run it
46
51
 
47
52
  | Group | Patch | |
48
53
  |---|---|---|
49
- | Output & diffs | Detailed tool calls | Show full read/search calls, not collapsed summaries |
54
+ | Output & display | Detailed tool calls | Show full read/search calls, not collapsed summaries |
50
55
  | | Colour new files as diffs | Created files render with `+` lines and green |
51
- | Thinking | Fix blank thinking blocks | Opt out of the server-side experiment that can empty every thinking block |
56
+ | | Fix blank thinking blocks | Opt out of the server-side experiment that can empty every thinking block |
52
57
  | | Always show thinking | Thinking blocks stay inline — no `ctrl+o` |
53
58
  | | Stream thinking live | See reasoning as it is generated, inline and in order |
54
- | Subagents | Show subagent prompts | Prompt blocks visible during normal use |
59
+ | | Show subagent prompts | Prompt blocks visible during normal use |
60
+ | Models & effort | Persist max effort | `/effort max` saves as your default for new sessions, like the other levels |
61
+ | | Codex models | Use OpenAI/Codex-plan models in Claude Code — see [Codex models](#codex-models) |
55
62
  | | Override subagent models | Pick the model per built-in agent (discovered from your binary) |
56
- | Chrome | Disable spinner tips | No rotating tips on the spinner |
57
- | | Custom startup name | Defaults to `<your username>'s Code` |
63
+ | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
58
64
  | | 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) |
65
+ | | Custom startup name | Defaults to `<your username>'s Code` |
66
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it |
60
67
 
61
68
  ## Usage
62
69
 
@@ -70,6 +77,8 @@ uvx patch-cc apply --brand # + branding as <username>'s Code
70
77
  uvx patch-cc apply --brand "Ada's Code" # + branding, explicit name
71
78
  uvx patch-cc apply --model Explore=haiku --model general-purpose=opus
72
79
  uvx patch-cc apply --suffix "(mine)" # custom --version marker
80
+ uvx patch-cc apply --org-label # hide the welcome screen's org/email
81
+ uvx patch-cc apply --org-label "Ada's Lab" # ...or show this instead
73
82
  uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
74
83
  uvx patch-cc apply --from-cache # replay your last remembered selection
75
84
  uvx patch-cc status # exactly what is applied
@@ -79,9 +88,9 @@ uvx patch-cc list # every patch, described
79
88
  uvx patch-cc restore # put the original back
80
89
  ```
81
90
 
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.
91
+ A flag that configures a patch also selects it `apply --help` lists them
92
+ all. Agents and models are validated against what your installed binary
93
+ actually ships, and Codex model ids against what your plan offers.
85
94
 
86
95
  ## Codex models
87
96
 
@@ -181,3 +190,7 @@ bundle rather than with clodex's TLS interception.
181
190
  ## License
182
191
 
183
192
  MIT
193
+
194
+ ---
195
+
196
+ **More agent tooling** — [summon-cc](https://github.com/anfreire/summon-cc): give your agent a crew of Claude Code workers · [cc-oc](https://github.com/anfreire/cc-oc): drive opencode from inside Claude Code · [omoctl](https://github.com/anfreire/omoctl): manage oh-my-openagent profiles · [wiki-spaces](https://github.com/anfreire/wiki-spaces): a wiki your AI agent keeps
@@ -93,7 +93,7 @@ its shape; [PLAYBOOK.md](PLAYBOOK.md) covers what it means for matcher health:
93
93
 
94
94
  ```
95
95
  //patch-cc {"v":1,"tool":"<version>","patches":[...],"brand":...,"models":{...},
96
- "codex":{"port":8817,"models":["gpt-5.6-sol", ...]}}
96
+ "org":...,"codex":{"port":8817,"models":["gpt-5.6-sol", ...]}}
97
97
  ```
98
98
 
99
99
  That line is why `patch-cc status` can name exactly what is applied: several
@@ -233,9 +233,10 @@ that broke a patch along with every note.
233
233
 
234
234
  ## Patch reference
235
235
 
236
- Grouped by source module the UI folds `live-thinking` into the Thinking group,
237
- but it still lives in `streaming.py`. Each entry: what it changes, the stable
238
- anchor, and where it lives.
236
+ Grouped by source module. The menu's three groups (Output & display, Models &
237
+ effort, Chrome & branding) are display categories that each draw from several
238
+ modules module says where a matcher lives, group says what the patch changes
239
+ for you. Each entry: what it changes, the stable anchor, and where it lives.
239
240
 
240
241
  ### Output & diffs — `output.py`
241
242
 
@@ -288,6 +289,36 @@ anchor, and where it lives.
288
289
  A column of `0`s is this patch missing (or a bucket it does not yet cover);
289
290
  a spread of real lengths means the text arrived and the problem is rendering.
290
291
 
292
+ - **`max-effort`** — let `/effort max` save as the session default, as
293
+ low–xhigh already do. `max` is first-class in session (the binary's level
294
+ list is `["low","medium","high","xhigh","max"]`; the CLI `--effort` and
295
+ `CLAUDE_CODE_EFFORT_LEVEL` both accept it) — only *persistence* runs through
296
+ two whitelists that stop at xhigh, and both are required rewrites because
297
+ either alone changes nothing:
298
+
299
+ - **`gate`** — one tiny whitelist function
300
+ (`if(e==="low"||…||e==="xhigh")return e;return`), and the whole choke
301
+ point: the `/effort` save path writes `effortLevel` to userSettings only
302
+ when it returns a value, the startup resolver reads the setting back
303
+ through it, and the model-picker flow persists through it too — so write
304
+ and read widen together in one rewrite. The matcher accepts both
305
+ minifier statement forms and the whole-body match keeps it off any other
306
+ function that merely compares levels.
307
+ - **`schema`** — the settings validator's
308
+ `effortLevel:*.enum(["low","medium","high","xhigh"])`, anchored on the
309
+ prop name (the describe-string `Persisted effort level` is the listed
310
+ anchor). Its `.catch(void 0)` is the safety property: a **clean** binary
311
+ reading a settings file that still says `"max"` (baked, then reverted by
312
+ a Claude update) treats it as unset — the default effort, never a broken
313
+ settings parse. Losing the patch costs the preference, nothing else.
314
+
315
+ Both matchers tolerate the level already being present and count it as
316
+ landed — an upstream that adopts `max` persistence itself is the goal
317
+ achieved, not a miss (the same judged-on-achievement rule
318
+ `subagent-models` follows). Effort semantics downstream are untouched: the
319
+ org-limit clamp runs before the save, and a model that cannot run `max`
320
+ clamps at request time exactly as an interactive `/effort max` does today.
321
+
291
322
  - **`thinking-inline`** — make historical thinking blocks render inline.
292
323
  Anchor: `case"thinking":` containing `isTranscriptMode:`. Two rewrites:
293
324
  remove the early null-return (both `if(!a&&!b)return null;` and the 2.1.216
@@ -516,6 +547,20 @@ anchor, and where it lives.
516
547
  chosen name. Several string shapes, each its own sub-step. On by default,
517
548
  deriving `<username>'s Code`; `--brand NAME` names it explicitly, and selects
518
549
  the patch when it is not already in the set.
550
+ - **`org-label`** — replace or hide the welcome screen's third segment
551
+ (`model · plan · <organizationName>`; for a personal claude.ai account the
552
+ org name *is* the account email). Anchor: the one conditional template
553
+ joining `.organizationName` behind `!process.env.IS_DEMO`, whose false
554
+ branch is upstream's own no-org shape `` `${model} \xB7 ${plan}` ``. An
555
+ empty label (`--org-label` with no value — empty is the *value* "hide", not
556
+ "unset") emits that false branch verbatim, so the separator leaves with the
557
+ segment; a label takes the org text's place, escaped for the template
558
+ literal it lands in. Candidates count off the `\xB7 ${*.organizationName}`
559
+ composition, so a reshaped ternary reads as `candidates > 0, applied == 0`
560
+ — a matcher to repair — rather than the zero that would equally mean
561
+ upstream retired the segment. Only this line is touched: the `/status`
562
+ Organization/Email rows, the login screen, and the org's startup message
563
+ (`"Message from <org>:"`) still show the real account.
519
564
 
520
565
  ## Removed patches
521
566
 
Binary file
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "patch-cc"
7
- version = "0.2.0"
7
+ version = "0.2.1"
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.0"
3
+ __version__ = "0.2.1"
@@ -59,6 +59,7 @@ def load_strict() -> Selection | None:
59
59
  known = set(ids())
60
60
  brand = data.get("brand", DEFAULT_BRAND)
61
61
  suffix = data.get("suffix", DEFAULT_SUFFIX)
62
+ org = data.get("org_label", "")
62
63
  models = data.get("subagent_models", {})
63
64
  # Codex ids only, the same as the manifest keeps: a name and a context
64
65
  # window are the plan's to report, and remembering either would let a
@@ -72,6 +73,7 @@ def load_strict() -> Selection | None:
72
73
  version_suffix=suffix
73
74
  if isinstance(suffix, str) and suffix
74
75
  else DEFAULT_SUFFIX,
76
+ org_label=org if isinstance(org, str) else "",
75
77
  subagent_models={
76
78
  a: m
77
79
  for a, m in models.items()
@@ -98,6 +100,7 @@ def save(selection: Selection) -> None:
98
100
  "patches": selection.patches,
99
101
  "brand": selection.options.brand,
100
102
  "suffix": selection.options.version_suffix,
103
+ "org_label": selection.options.org_label,
101
104
  "subagent_models": selection.options.subagent_models,
102
105
  "codex_models": [m.id for m in selection.options.codex_models],
103
106
  "codex_port": selection.options.codex_port,
@@ -4,7 +4,8 @@ Bare ``patch-cc`` opens the interactive menu. Every action is also a
4
4
  subcommand so nothing needs the TUI:
5
5
 
6
6
  patch-cc apply [PATCH ...] [--brand [NAME]] [--model AGENT=MODEL]
7
- [--suffix TEXT] [--codex MODEL] [--codex-port N]
7
+ [--suffix TEXT] [--org-label [TEXT]]
8
+ [--codex MODEL] [--codex-port N]
8
9
  patch-cc status
9
10
  patch-cc doctor [PATH] # PATH: check any binary, e.g. an old backup
10
11
  patch-cc list
@@ -102,6 +103,12 @@ def _list_hint(patch: Patch, cached: cache.Selection) -> str:
102
103
  parts.append(f"default {DEFAULT_SUFFIX!r}")
103
104
  if opts.version_suffix != DEFAULT_SUFFIX:
104
105
  parts.append(f"cached {opts.version_suffix!r}")
106
+ elif patch.id == "org-label":
107
+ parts.append("default hides it")
108
+ if "org-label" in cached.patches:
109
+ parts.append(
110
+ f"cached {opts.org_label!r}" if opts.org_label else "cached hidden"
111
+ )
105
112
  elif patch.id == "subagent-models" and opts.subagent_models:
106
113
  picks = ", ".join(f"{a}={m}" for a, m in opts.subagent_models.items())
107
114
  parts.append(f"cached {picks}")
@@ -218,6 +225,11 @@ def _requested(args, source: str) -> tuple[list[str], Options]:
218
225
  if "version-marker" not in selected:
219
226
  selected.append("version-marker")
220
227
 
228
+ if args.org_label is not None:
229
+ options.org_label = args.org_label.strip()
230
+ if "org-label" not in selected:
231
+ selected.append("org-label")
232
+
221
233
  # Order-preserving, and deduplicated: a repeated `--codex` is one model asked
222
234
  # for twice, and every array it reaches -- the enum, the routing list, the
223
235
  # remembered selection, the manifest `status` reads back -- would otherwise
@@ -272,6 +284,7 @@ def _has_selection_args(args) -> bool:
272
284
  or args.brand is not None
273
285
  or args.model
274
286
  or args.suffix
287
+ or args.org_label is not None
275
288
  or args.codex
276
289
  or args.codex_port is not None
277
290
  )
@@ -332,7 +345,8 @@ def _from_cache(args, source: str) -> tuple[list[str], Options]:
332
345
  if _has_selection_args(args):
333
346
  err(
334
347
  "--from-cache replays your last interactive selection; do not combine "
335
- "it with patch ids or --brand / --model / --suffix / --codex."
348
+ "it with patch ids or --brand / --model / --suffix / --org-label / "
349
+ "--codex."
336
350
  )
337
351
  raise SystemExit(2)
338
352
  if not cache.cache_path().exists():
@@ -494,6 +508,8 @@ def cmd_status(args) -> int:
494
508
  console.print(f" brand: {st.manifest['brand']}")
495
509
  if "suffix" in st.manifest:
496
510
  console.print(f" suffix: {st.manifest['suffix']}")
511
+ if "org" in st.manifest:
512
+ console.print(f" org: {st.manifest['org'] or '(hidden)'}")
497
513
  for agent, model in (st.manifest.get("models") or {}).items():
498
514
  console.print(f" model: {agent} = {model}")
499
515
  codex = st.manifest.get("codex")
@@ -863,6 +879,7 @@ def _apply_epilog() -> str:
863
879
  "configuring patches:",
864
880
  f" --brand [NAME] selects branding · no value -> {derived_brand()!r}",
865
881
  f" --suffix TEXT selects version-marker · default {DEFAULT_SUFFIX!r}",
882
+ " --org-label [TEXT] selects org-label · no value -> hide the segment",
866
883
  " --model AGENT=MODEL selects subagent-models · repeatable, one per agent",
867
884
  ]
868
885
  if agents:
@@ -1004,6 +1021,14 @@ def build_parser() -> argparse.ArgumentParser:
1004
1021
  help="`claude --version` marker text; selects `version-marker` "
1005
1022
  "(default: (patched))",
1006
1023
  )
1024
+ p_apply.add_argument(
1025
+ "--org-label",
1026
+ nargs="?",
1027
+ const="",
1028
+ metavar="TEXT",
1029
+ help="welcome-screen org/email text; selects `org-label` "
1030
+ "(no value: hide the segment)",
1031
+ )
1007
1032
  p_apply.add_argument(
1008
1033
  "--codex",
1009
1034
  action="append",
@@ -110,7 +110,12 @@ def _synthetic_options(agents: list[BuiltinAgent], models: list[str]) -> Options
110
110
  if (target := next((m for m in models if m != agent.effective_model), None))
111
111
  }
112
112
  return Options(
113
- brand="patch-cc doctor", subagent_models=overrides, codex_models=codex
113
+ brand="patch-cc doctor",
114
+ subagent_models=overrides,
115
+ codex_models=codex,
116
+ # The replace branch; the hidden form differs only in the emitted tail,
117
+ # so one configuration holds the whole matcher in the net.
118
+ org_label="patch-cc doctor",
114
119
  )
115
120
 
116
121
 
@@ -4,9 +4,10 @@ A fullscreen frame: fixed title and status on top, fixed key hints at the
4
4
  bottom, and only the patch list scrolling in between. Every choice is a picker
5
5
  driven by what the binary itself offers -- agents and models are discovered,
6
6
  never typed -- and typing exists only where a value is genuinely free text
7
- (the startup name, the --version marker). All configuration happens in
8
- centered modals floating over the dimmed list; the list itself never grows
9
- sub-rows. ``s`` saves, and the same frame then shows the per-patch results.
7
+ (the startup name, the --version marker, the org/email label). All
8
+ configuration happens in centered modals floating over the dimmed list; the
9
+ list itself never grows sub-rows. ``s`` saves, and the same frame then shows
10
+ the per-patch results.
10
11
 
11
12
  The engine is deliberately small: ``blessed`` owns the terminal (fullscreen,
12
13
  cbreak, parsed keystrokes, live size) and Rich owns every pixel drawn. A frame
@@ -43,6 +44,7 @@ from .bun import Bundle, BunError, container
43
44
  from .codex import DEFAULT_PORT, is_valid_port
44
45
  from .codex.models import CodexModel, discover, reconcile
45
46
  from .patches import (
47
+ ALL_PATCHES,
46
48
  DEFAULT_BRAND,
47
49
  DEFAULT_SUFFIX,
48
50
  GROUP_ORDER,
@@ -83,8 +85,11 @@ def _hints(*pairs: tuple[str, str]) -> Text:
83
85
  return text
84
86
 
85
87
 
86
- #: Patches whose row opens a modal on enter instead of plain toggling.
87
- _CONFIGURABLE = {"subagent-models", "branding", "version-marker", "codex-models"}
88
+ #: Patches whose row opens a modal on enter instead of plain toggling --
89
+ #: exactly the ones that carry a configurable value, which is what
90
+ #: :attr:`Patch.option` declares. Derived, so a new option-carrying patch
91
+ #: cannot be left off this list with its hint reading "enter toggle".
92
+ _CONFIGURABLE = {patch.id for patch in ALL_PATCHES if patch.option}
88
93
 
89
94
 
90
95
  # ----------------------------------------------------------------- rows
@@ -182,6 +187,7 @@ class MenuModel:
182
187
  model.text_rows["suffix"] = TextRow(
183
188
  "suffix", "marker", seed.options.version_suffix
184
189
  )
190
+ model.text_rows["org"] = TextRow("org", "label", seed.options.org_label)
185
191
  return model
186
192
 
187
193
  def _seed(self) -> cache.Selection:
@@ -191,6 +197,7 @@ class MenuModel:
191
197
  codex = manifest.get("codex")
192
198
  codex = codex if isinstance(codex, dict) else {}
193
199
  port = codex.get("port")
200
+ org = manifest.get("org")
194
201
  seed = cache.Selection()
195
202
  seed.patches = [
196
203
  p for p in manifest.get("patches", []) if isinstance(p, str)
@@ -198,6 +205,9 @@ class MenuModel:
198
205
  seed.options = Options(
199
206
  brand=manifest.get("brand") or DEFAULT_BRAND,
200
207
  version_suffix=manifest.get("suffix") or DEFAULT_SUFFIX,
208
+ # `or ""` would also be right, but only by luck: "" (hidden) is
209
+ # a meaningful manifest value, unlike an absent brand/suffix.
210
+ org_label=org if isinstance(org, str) else "",
201
211
  subagent_models={
202
212
  a: m
203
213
  for a, m in (manifest.get("models") or {}).items()
@@ -269,6 +279,10 @@ class MenuModel:
269
279
  options.version_suffix = (
270
280
  self.text_rows["suffix"].value.strip() or DEFAULT_SUFFIX
271
281
  )
282
+ if "org-label" in selected:
283
+ # Empty stays empty: it is the "hide the segment" value, so unlike
284
+ # brand and suffix there is no default to fall back to.
285
+ options.org_label = self.text_rows["org"].value.strip()
272
286
  # A subagent pinned to a Codex model needs codex-models applied too -- it
273
287
  # registers the id and installs the redirect, without which the pin
274
288
  # resolves to nothing. That coupling is enforced where the user can see
@@ -709,15 +723,20 @@ class MenuApp:
709
723
  def _activate(self, row: PatchRow) -> None:
710
724
  """Enter on a patch: toggle plain ones, configure configurable ones."""
711
725
  patch_id = row.patch.id
726
+ text_keys = {
727
+ "branding": "brand",
728
+ "version-marker": "suffix",
729
+ "org-label": "org",
730
+ }
712
731
  if patch_id == "subagent-models":
713
732
  if not self.model.agent_rows:
714
733
  self.flash = "no agents discovered in this bundle"
715
734
  return
716
735
  row.on = True
717
736
  self._open_agents_modal()
718
- elif patch_id in ("branding", "version-marker"):
737
+ elif patch_id in text_keys:
719
738
  row.on = True
720
- self._open_text_modal("brand" if patch_id == "branding" else "suffix")
739
+ self._open_text_modal(text_keys[patch_id])
721
740
  elif patch_id == "codex-models":
722
741
  self._open_codex_menu(row) # submenu: pick models / set gateway port
723
742
  else:
@@ -813,10 +832,16 @@ class MenuApp:
813
832
 
814
833
  def _open_text_modal(self, key: str) -> None:
815
834
  row = self.model.text_rows[key]
816
- titles = {"brand": "Startup name", "suffix": "--version marker"}
835
+ titles = {
836
+ "brand": "Startup name",
837
+ "suffix": "--version marker",
838
+ "org": "Org/email label (empty hides it)",
839
+ }
817
840
 
818
841
  def entered(value: object) -> None:
819
- if isinstance(value, str) and value.strip():
842
+ # For the org label an emptied field is the point -- it means hide
843
+ # the segment -- where an empty brand or marker means "never mind".
844
+ if isinstance(value, str) and (value.strip() or key == "org"):
820
845
  row.value = value.strip()
821
846
 
822
847
  self._push(InputModal(titles[key], row.value), entered)
@@ -1412,6 +1437,9 @@ class MenuApp:
1412
1437
  return Text(model.text_rows["brand"].value, style=_VALUE)
1413
1438
  if row.patch.id == "version-marker":
1414
1439
  return Text(model.text_rows["suffix"].value, style=_VALUE)
1440
+ if row.patch.id == "org-label":
1441
+ value = model.text_rows["org"].value
1442
+ return Text(value, style=_VALUE) if value else Text("hidden", style="dim")
1415
1443
  if row.patch.id == "codex-models":
1416
1444
  count = len(model.codex_models)
1417
1445
  if not count:
@@ -92,6 +92,11 @@ def manifest_payload(landed: list[str], options: Options) -> dict:
92
92
  payload["suffix"] = options.version_suffix
93
93
  if options.subagent_models and "subagent-models" in applied:
94
94
  payload["models"] = options.subagent_models
95
+ if "org-label" in applied:
96
+ # Recorded even when empty: "" is the asked-for value (hide the
97
+ # segment), and omitting it would make hidden indistinguishable from
98
+ # never-configured when the menu seeds from this manifest.
99
+ payload["org"] = options.org_label
95
100
  if options.codex_models and "codex-models" in applied:
96
101
  # Ids and a port -- the whole of what was asked for. A model's name and
97
102
  # context window are baked into the bundle itself (the picker rows, the
@@ -19,11 +19,9 @@ from . import agents, chrome, codex, output, streaming, thinking
19
19
  from .base import (
20
20
  DEFAULT_BRAND,
21
21
  DEFAULT_SUFFIX,
22
- GROUP_AGENTS,
23
22
  GROUP_CHROME,
24
- GROUP_CODEX,
23
+ GROUP_MODELS,
25
24
  GROUP_OUTPUT,
26
- GROUP_THINKING,
27
25
  SENTINEL,
28
26
  Options,
29
27
  Outcome,
@@ -40,7 +38,7 @@ ALL_PATCHES: list[Patch] = [
40
38
  *chrome.PATCHES,
41
39
  ]
42
40
 
43
- GROUP_ORDER = [GROUP_OUTPUT, GROUP_THINKING, GROUP_AGENTS, GROUP_CODEX, GROUP_CHROME]
41
+ GROUP_ORDER = [GROUP_OUTPUT, GROUP_MODELS, GROUP_CHROME]
44
42
 
45
43
 
46
44
  def ids() -> list[str]:
@@ -16,7 +16,16 @@ from __future__ import annotations
16
16
  import re
17
17
  from dataclasses import dataclass
18
18
 
19
- from .base import GROUP_AGENTS, IDENT, Options, Outcome, Patch, compile_js, splice
19
+ from .base import (
20
+ GROUP_MODELS,
21
+ GROUP_OUTPUT,
22
+ IDENT,
23
+ Options,
24
+ Outcome,
25
+ Patch,
26
+ compile_js,
27
+ splice,
28
+ )
20
29
 
21
30
  # ------------------------------------------------------- prompt visibility
22
31
 
@@ -366,7 +375,7 @@ PATCHES = [
366
375
  id="subagent-prompt",
367
376
  title="Show subagent prompts",
368
377
  summary="Show a subagent's Prompt block during normal use, not only in transcript mode.",
369
- group=GROUP_AGENTS,
378
+ group=GROUP_OUTPUT,
370
379
  fn=_subagent_prompt,
371
380
  anchors=('"Backgrounded agent"', 'action:"app:toggleTranscript"'),
372
381
  ),
@@ -374,7 +383,7 @@ PATCHES = [
374
383
  id="subagent-models",
375
384
  title="Override subagent models",
376
385
  summary="Choose the default model for the built-in agents found in your binary.",
377
- group=GROUP_AGENTS,
386
+ group=GROUP_MODELS,
378
387
  fn=_subagent_models,
379
388
  default=False,
380
389
  option="--model",
@@ -37,11 +37,12 @@ if TYPE_CHECKING:
37
37
  #: indistinguishable from the 11 that upstream already ships.
38
38
  SENTINEL = "__cc_"
39
39
 
40
- # Groups, in display order.
41
- GROUP_OUTPUT = "Output & diffs"
42
- GROUP_THINKING = "Thinking"
43
- GROUP_AGENTS = "Subagents"
44
- GROUP_CODEX = "Codex models"
40
+ # Groups, in display order: what renders, what runs (and how hard), how it is
41
+ # dressed. Generic on purpose -- a group named after a feature (Thinking,
42
+ # Subagents, Codex) fits exactly that feature, and the next patch that is
43
+ # about behaviour rather than a surface has no home.
44
+ GROUP_OUTPUT = "Output & display"
45
+ GROUP_MODELS = "Models & effort"
45
46
  GROUP_CHROME = "Chrome & branding"
46
47
 
47
48
  #: Default brand: the name shown unless the user overrides it. One home so the
@@ -88,6 +89,11 @@ class Options:
88
89
  #: from the one home (:data:`patch_cc.codex.DEFAULT_PORT`) so the number
89
90
  #: never lives in two places.
90
91
  codex_port: int = DEFAULT_PORT
92
+ #: The welcome screen's org segment (for personal claude.ai accounts,
93
+ #: upstream shows the account email there). Empty is a *value* -- hide the
94
+ #: segment -- not "unset": whether the patch acts at all is the selection's
95
+ #: question, never this field's.
96
+ org_label: str = ""
91
97
 
92
98
  @property
93
99
  def rebrands(self) -> bool:
@@ -175,6 +175,52 @@ def _branding(content: str, options: Options, outcome: Outcome) -> str:
175
175
  return output
176
176
 
177
177
 
178
+ # ---------------------------------------------------------- startup org label
179
+
180
+ # The separator as the bundle may spell it: today's bundler writes the `·` as
181
+ # the escape text `\xB7`, and a charset flip would emit the raw character. Both
182
+ # spell the same string, so both are accepted -- a representation variant like
183
+ # the minifier's brace flips, not a second shape.
184
+ _SEP = r"(?:\\xB7|\xB7)"
185
+
186
+ # The welcome screen's info line: `model · plan` -- and, when the account has an
187
+ # organizationName (for personal claude.ai accounts, that is the account email),
188
+ # ` · <organizationName>` after it. One conditional template, and upstream ships
189
+ # the no-org variant as its own false branch, so hiding the segment is selecting
190
+ # a shape the bundle already carries -- separator and all -- not inventing one.
191
+ _ORG_LINE = compile_js(
192
+ rf"!process\.env\.IS_DEMO&&({IDENT})\?\.organizationName\?"
193
+ rf"`\$\{{({IDENT})\}} {_SEP} \$\{{({IDENT})\}} {_SEP} \$\{{\1\.organizationName\}}`"
194
+ rf":`\$\{{\2\}} {_SEP} \$\{{\3\}}`"
195
+ )
196
+
197
+ #: The composition itself -- a separator joining the account's organizationName
198
+ #: into a template -- is the durable fingerprint; candidates count off it so a
199
+ #: reshaped ternary reads as "found it, rewrote nothing" (a matcher to repair)
200
+ #: instead of the zero that would equally mean upstream retired the segment.
201
+ _ORG_NEEDLE = compile_js(rf"{_SEP} \$\{{{IDENT}\.organizationName\}}")
202
+
203
+
204
+ def _org_label(content: str, options: Options, outcome: Outcome) -> str:
205
+ """Replace -- or, with an empty label, drop -- the welcome line's org segment.
206
+
207
+ One rule builds the replacement either way: the line is `model · plan`,
208
+ plus ` · <label>` exactly when there is a label. Empty therefore removes
209
+ the separator with the segment, and a custom label takes the org text's
210
+ place. The `IS_DEMO`/has-org gate goes with the ternary: a baked label is
211
+ unconditional by construction, and the hidden form needs no gate at all.
212
+ """
213
+ outcome.candidates += len(_ORG_NEEDLE.findall(content))
214
+ label = options.org_label
215
+ tail = f" \\xB7 {_js_template_escape(label)}" if label else ""
216
+
217
+ def rewrite(match: re.Match[str]) -> str:
218
+ outcome.applied += 1
219
+ return f"`${{{match.group(2)}}} \\xB7 ${{{match.group(3)}}}{tail}`"
220
+
221
+ return _ORG_LINE.sub(rewrite, content)
222
+
223
+
178
224
  PATCHES = [
179
225
  Patch(
180
226
  id="spinner-tips",
@@ -203,4 +249,14 @@ PATCHES = [
203
249
  option="--brand",
204
250
  anchors=('"Welcome to Claude Code"', '{bold:!0},"Claude Code"'),
205
251
  ),
252
+ Patch(
253
+ id="org-label",
254
+ title="Startup org/email label",
255
+ summary="Replace or hide the org/email shown after the plan name on the welcome screen.",
256
+ group=GROUP_CHROME,
257
+ fn=_org_label,
258
+ default=False,
259
+ option="--org-label",
260
+ anchors=("organizationName", "IS_DEMO"),
261
+ ),
206
262
  ]
@@ -50,7 +50,7 @@ import re
50
50
  from ..codex.models import family_aliases, family_of
51
51
  from .agents import MODEL_ENUM
52
52
  from .base import (
53
- GROUP_CODEX,
53
+ GROUP_MODELS,
54
54
  IDENT,
55
55
  Options,
56
56
  Outcome,
@@ -507,7 +507,7 @@ PATCHES = [
507
507
  id="codex-models",
508
508
  title="Codex models",
509
509
  summary="Register your OpenAI/Codex models and route them to the local gateway.",
510
- group=GROUP_CODEX,
510
+ group=GROUP_MODELS,
511
511
  fn=_codex_models,
512
512
  default=False,
513
513
  option="--codex",
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
  import re
24
24
  from dataclasses import dataclass
25
25
 
26
- from .base import GROUP_THINKING, IDENT, Options, Outcome, Patch, compile_js, splice
26
+ from .base import GROUP_OUTPUT, IDENT, Options, Outcome, Patch, compile_js, splice
27
27
 
28
28
  #: The mutually-exclusive reducer variants. On any one build one of them lands;
29
29
  #: none landing means upstream shipped a fourth shape and live thinking is dead.
@@ -974,7 +974,7 @@ PATCHES = [
974
974
  title="Stream thinking live",
975
975
  summary="Show thinking as it is generated, inline and in order, instead of "
976
976
  "only after the turn finishes.",
977
- group=GROUP_THINKING,
977
+ group=GROUP_OUTPUT,
978
978
  fn=_live_thinking,
979
979
  anchors=(
980
980
  "onStreamingThinking:",
@@ -22,6 +22,9 @@ Rendering is only half of it. A fourth gate is not in the UI at all: the
22
22
  account's server-side experiment bucket, which decides whether the API puts any
23
23
  text in the thinking blocks it returns. `thinking-summaries` is that half, and
24
24
  without it the other three render an empty string perfectly.
25
+
26
+ `max-effort` lives here too: effort is thinking depth, so the module that owns
27
+ how thinking is shown also owns how hard it is asked for.
25
28
  """
26
29
 
27
30
  from __future__ import annotations
@@ -29,7 +32,8 @@ from __future__ import annotations
29
32
  import re
30
33
 
31
34
  from .base import (
32
- GROUP_THINKING,
35
+ GROUP_MODELS,
36
+ GROUP_OUTPUT,
33
37
  IDENT,
34
38
  Options,
35
39
  Outcome,
@@ -189,12 +193,73 @@ def _thinking_summaries(content: str, _options: Options, outcome: Outcome) -> st
189
193
  return _CLIENT_BUCKET.sub(rewrite, content)
190
194
 
191
195
 
196
+ # ------------------------------------------------------------- max effort
197
+ #
198
+ # `max` is a first-class level in session -- the level list the binary ships is
199
+ # ["low","medium","high","xhigh","max"], and the CLI and env var both accept it
200
+ # -- but persistence runs through two whitelists that stop at xhigh. Both are
201
+ # widened; either alone changes nothing (docs/PLAYBOOK.md), which is also why
202
+ # both steps are required.
203
+ #
204
+ # The gate is one tiny function, and it is the whole choke point: the /effort
205
+ # save path writes settings only when it returns a value, the startup resolver
206
+ # reads settings back through it, and the model-picker flow persists through it
207
+ # too. The statement variants and the optional `||x==="max"` follow the matcher
208
+ # rules: minifiers flip `return e;` and `{return e}`, and a build where upstream
209
+ # has adopted the level itself is the goal already achieved, not a miss.
210
+ _EFFORT_GATE = compile_js(
211
+ rf"function ({IDENT})\(({IDENT})\)\{{"
212
+ rf'if\(\2==="low"\|\|\2==="medium"\|\|\2==="high"\|\|\2==="xhigh"'
213
+ rf'(\|\|\2==="max")?\)'
214
+ rf"(?:\{{return \2\}}|return \2;?)return;?\}}"
215
+ )
216
+
217
+ # The persisted-settings schema entry. Its zod chain ends in `.catch(void 0)`,
218
+ # so a clean binary reading a settings file that carries "max" (baked by us,
219
+ # then reverted by a Claude update) treats it as unset rather than failing the
220
+ # whole settings parse -- the property that makes this patch safe to lose.
221
+ _EFFORT_SCHEMA = compile_js(
222
+ rf'effortLevel:({IDENT})\.enum\(\["low","medium","high","xhigh"(,"max")?\]\)'
223
+ )
224
+
225
+
226
+ def _max_effort(content: str, _options: Options, outcome: Outcome) -> str:
227
+ """Let ``/effort max`` save as the default for new sessions."""
228
+ gate = outcome.step("gate", expect=True)
229
+ schema = outcome.step("schema", expect=True)
230
+
231
+ def widen_gate(match: re.Match[str]) -> str:
232
+ gate.candidates += 1
233
+ gate.applied += 1
234
+ if match.group(3):
235
+ return match.group(0)
236
+ name, value = match.group(1), match.group(2)
237
+ return (
238
+ f"function {name}({value}){{"
239
+ f'if({value}==="low"||{value}==="medium"||{value}==="high"'
240
+ f'||{value}==="xhigh"||{value}==="max")return {value};return}}'
241
+ )
242
+
243
+ output = _EFFORT_GATE.sub(widen_gate, content)
244
+
245
+ def widen_schema(match: re.Match[str]) -> str:
246
+ schema.candidates += 1
247
+ schema.applied += 1
248
+ if match.group(2):
249
+ return match.group(0)
250
+ return (
251
+ f'effortLevel:{match.group(1)}.enum(["low","medium","high","xhigh","max"])'
252
+ )
253
+
254
+ return _EFFORT_SCHEMA.sub(widen_schema, output)
255
+
256
+
192
257
  PATCHES = [
193
258
  Patch(
194
259
  id="thinking-summaries",
195
260
  title="Fix blank thinking blocks",
196
261
  summary="Opt out of the server-side experiment bucket that can return empty thinking blocks on some accounts. Drops all experiment enrollment, not just this one.",
197
- group=GROUP_THINKING,
262
+ group=GROUP_OUTPUT,
198
263
  fn=_thinking_summaries,
199
264
  anchors=(_BUCKET_HEADER, "?.atis"),
200
265
  ),
@@ -202,8 +267,20 @@ PATCHES = [
202
267
  id="thinking-inline",
203
268
  title="Always show thinking",
204
269
  summary="Render thinking blocks inline instead of hiding them behind ctrl+o.",
205
- group=GROUP_THINKING,
270
+ group=GROUP_OUTPUT,
206
271
  fn=_thinking_inline,
207
272
  anchors=('case"thinking":', "isTranscriptMode:", "latestThinkingSummary"),
208
273
  ),
274
+ Patch(
275
+ id="max-effort",
276
+ title="Persist max effort",
277
+ summary="Let /effort max save as your default for new sessions, like the other levels.",
278
+ group=GROUP_MODELS,
279
+ fn=_max_effort,
280
+ anchors=(
281
+ '==="low"||',
282
+ '"low","medium","high","xhigh"]',
283
+ "Persisted effort level",
284
+ ),
285
+ ),
209
286
  ]
@@ -57,6 +57,8 @@ def applied_value(patch: Patch, outcome: Outcome, options: Options) -> str | Non
57
57
  return options.brand
58
58
  if patch.id == "version-marker":
59
59
  return options.version_suffix
60
+ if patch.id == "org-label":
61
+ return options.org_label or "hidden"
60
62
  if patch.id == "subagent-models" and options.subagent_models:
61
63
  return ", ".join(f"{a}={m}" for a, m in options.subagent_models.items())
62
64
  if patch.id == "codex-models" and options.codex_models:
@@ -256,7 +256,7 @@ wheels = [
256
256
 
257
257
  [[package]]
258
258
  name = "patch-cc"
259
- version = "0.2.0"
259
+ version = "0.2.1"
260
260
  source = { editable = "." }
261
261
  dependencies = [
262
262
  { name = "blessed" },
File without changes
File without changes
File without changes
File without changes
File without changes