dazzle-claude-code-patcher 0.2.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.
Files changed (36) hide show
  1. dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env +9 -0
  2. dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env.cmd +11 -0
  3. dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env.ps1 +12 -0
  4. dazzle_claude_code_patcher-0.2.0.dist-info/METADATA +127 -0
  5. dazzle_claude_code_patcher-0.2.0.dist-info/RECORD +36 -0
  6. dazzle_claude_code_patcher-0.2.0.dist-info/WHEEL +5 -0
  7. dazzle_claude_code_patcher-0.2.0.dist-info/entry_points.txt +2 -0
  8. dazzle_claude_code_patcher-0.2.0.dist-info/licenses/LICENSE +21 -0
  9. dazzle_claude_code_patcher-0.2.0.dist-info/top_level.txt +1 -0
  10. dcc_patcher/__init__.py +2 -0
  11. dcc_patcher/__main__.py +7 -0
  12. dcc_patcher/_version.py +92 -0
  13. dcc_patcher/builds.py +100 -0
  14. dcc_patcher/cli.py +496 -0
  15. dcc_patcher/containers/__init__.py +85 -0
  16. dcc_patcher/containers/pe.py +128 -0
  17. dcc_patcher/diagnose.py +70 -0
  18. dcc_patcher/engine.py +200 -0
  19. dcc_patcher/install.py +535 -0
  20. dcc_patcher/launch.py +197 -0
  21. dcc_patcher/lock.py +45 -0
  22. dcc_patcher/originals.py +127 -0
  23. dcc_patcher/patches/B-banner-label/patch.toml +25 -0
  24. dcc_patcher/patches/B2-banner-label-flicker-free/patch.toml +27 -0
  25. dcc_patcher/patches/H-history-past-compaction/README.md +58 -0
  26. dcc_patcher/patches/H-history-past-compaction/patch.toml +68 -0
  27. dcc_patcher/patches/J-history-across-files/README.md +37 -0
  28. dcc_patcher/patches/J-history-across-files/patch.toml +35 -0
  29. dcc_patcher/patches/V-version-label/patch.toml +23 -0
  30. dcc_patcher/regfile.py +65 -0
  31. dcc_patcher/resolve.py +43 -0
  32. dcc_patcher/shims/claude.cmd.in +6 -0
  33. dcc_patcher/shims/claude.sh.in +4 -0
  34. dcc_patcher/spec.py +166 -0
  35. dcc_patcher/style.py +69 -0
  36. dcc_patcher/winpath.py +164 -0
@@ -0,0 +1,9 @@
1
+ # dcc-env: put the dcc-patcher launcher on PATH in THIS shell, setting it up first if needed.
2
+ # Source it: `. dcc-env` (run as a plain command it would only change a child shell).
3
+ # pip installs it beside dcc-patcher.
4
+ if DCC_ENV=1 dcc-patcher install-shim --if-needed; then
5
+ _dcc_folder="$(dcc-patcher shellenv sh --folder)"
6
+ [ -n "$_dcc_folder" ] && export PATH="$_dcc_folder:$PATH"
7
+ unset _dcc_folder
8
+ dcc-patcher status --brief
9
+ fi
@@ -0,0 +1,11 @@
1
+ @echo off
2
+ rem dcc-env: put the dcc-patcher launcher on PATH in THIS cmd window, setting it up first if needed.
3
+ rem pip installs it beside dcc-patcher.exe. No setlocal: the PATH change has to outlive this script.
4
+ set "DCC_ENV=1"
5
+ dcc-patcher install-shim --if-needed
6
+ set "DCC_ENV_CODE=%ERRORLEVEL%"
7
+ set "DCC_ENV="
8
+ if not "%DCC_ENV_CODE%"=="0" (set "DCC_ENV_CODE=" & exit /b 1)
9
+ set "DCC_ENV_CODE="
10
+ for /f "delims=" %%i in ('dcc-patcher shellenv cmd --folder') do set "PATH=%%i;%PATH%"
11
+ dcc-patcher status --brief
@@ -0,0 +1,12 @@
1
+ # dcc-env: put the dcc-patcher launcher on PATH in THIS PowerShell, setting it up first if needed.
2
+ # pip installs it beside dcc-patcher.exe. $env:Path belongs to the whole PowerShell process, so the
3
+ # change outlives this script. Needs an execution policy that runs local scripts (RemoteSigned or
4
+ # looser); otherwise use: dcc-patcher shellenv powershell | Invoke-Expression
5
+ $env:DCC_ENV = '1'
6
+ dcc-patcher install-shim --if-needed
7
+ $dccCode = $LASTEXITCODE
8
+ Remove-Item Env:\DCC_ENV -ErrorAction SilentlyContinue
9
+ if ($dccCode -ne 0) { return }
10
+ $dccFolder = dcc-patcher shellenv powershell --folder
11
+ if ($dccFolder) { $env:Path = "$dccFolder;" + $env:Path }
12
+ dcc-patcher status --brief
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: dazzle-claude-code-patcher
3
+ Version: 0.2.0
4
+ Summary: Windows-first patch tool for the native Claude Code CLI: apply, verify and diagnose community patches across releases.
5
+ Author-email: "Dustin Darcy (djdarcy)" <djdarcy@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/DazzleML/dazzle-claude-code-patcher
8
+ Project-URL: Repository, https://github.com/DazzleML/dazzle-claude-code-patcher
9
+ Project-URL: Issues, https://github.com/DazzleML/dazzle-claude-code-patcher/issues
10
+ Keywords: claude-code,patcher,bun,windows,anthropic,cli
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: OS Independent
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 :: Build Tools
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # dazzle-claude-code-patcher
32
+
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/LICENSE)
34
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
35
+ [![Platform](https://img.shields.io/badge/platform-Windows%20(first)-lightgrey.svg)](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/docs/platform-support.md)
36
+
37
+ > **Fix problematic Claude Code bugs in your local copy now, and keep the fixes working across updates.**
38
+
39
+ `dcc-patcher` diagnoses, verifies, and applies patches to the native Claude Code CLI (`claude.exe`). When Claude Code updates (roughly every two days), one command tells you which patches re-apply as-is and which need attention, and shows exactly what changed. Once set up, `claude` runs the patched build and re-patches each update by itself.
40
+
41
+ > [!NOTE]
42
+ > **Pre-alpha, Windows only.** Five patches apply unchanged on every Claude Code build from 2.1.278 to 2.1.295. The history patch is in daily use: a resumed session scrolls back past every compaction, and the model still receives exactly the same request as stock Claude Code. See the [Roadmap](https://github.com/DazzleML/dazzle-claude-code-patcher/issues/1).
43
+
44
+ ## The Problem
45
+
46
+ Some Claude Code bugs sit in code you can't configure around. For example, after a restart or `claude --resume`, everything before the last compaction disappears from the screen. It's still in the session file, but nothing brings it back. Waiting for an upstream improvement or fix can take a while, and patching by hand doesn't survive the next update (Claude Code is minified, so almost every internal name changes from one release to the next).
47
+
48
+ ## Patches
49
+
50
+ | Patch | Fixes | Status |
51
+ |---|---|---|
52
+ | [History past compaction](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/dcc_patcher/patches/H-history-past-compaction/README.md) | After a restart or `--resume`, scroll back past compactions to the start of the session file | `check` passes on 2.1.278 to 2.1.295; live-tested on 2.1.289 to 2.1.295 |
53
+ | [History across session files](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/dcc_patcher/patches/J-history-across-files/README.md) | A conversation that continued into a new session file (after running out of context, or a fork) scrolls back into the file it came from, and on to its first message. Needs the history patch. Derived from ojura/claude-patches' Patch J | `check` passes on 2.1.278 to 2.1.295; not yet live-tested |
54
+ | [Version label](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/dcc_patcher/patches/V-version-label/patch.toml) | `--version` says it is a patched build and which patcher made it: `2.1.295 + dcc 0.1.11 (Dazzle Claude Code)`. Only the displayed text changes; the version Claude Code checks against is untouched | `check` passes on 2.1.278 to 2.1.295 |
55
+ | Banner label ([classic](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/dcc_patcher/patches/B-banner-label/patch.toml), [flicker-free](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/dcc_patcher/patches/B2-banner-label-flicker-free/patch.toml)) | The header at the top of every session reads `Dazzle Claude Code v2.1.295 · dcc 0.1.11`, in both rendering modes. Only the drawn text changes | `check` passes on 2.1.278 to 2.1.295; flicker-free live-tested on 2.1.291 to 2.1.295 |
56
+
57
+ > [!CAUTION]
58
+ > **This project ships the tool and patch definitions only *NEVER* patched binaries.** You patch your own copy of Claude Code, locally. Use at your own risk. No warranty. Backup anything important first. I'd recommend: [claude-session-backup](https://github.com/DazzleML/Claude-Session-Backup).
59
+
60
+ ## Installation
61
+
62
+ Python 3.11 or newer, and nothing else.
63
+
64
+ ```bash
65
+ pip install dazzle-claude-code-patcher
66
+ dcc-patcher validate # lists the bundled patches
67
+ ```
68
+
69
+ To work on it, install from source instead (`pip install -e ".[dev]"` in a clone). Builds then go to the checkout's `private\builds\` rather than `%LOCALAPPDATA%\dazzle-claude-code-patcher\builds\`.
70
+
71
+ ## Usage
72
+
73
+ ```bash
74
+ # Does every patch still apply to this build? (read-only)
75
+ dcc-patcher check %USERPROFILE%\.local\bin\claude.exe
76
+
77
+ # Build a patched copy into the builds folder, with a lockfile recording every edit
78
+ dcc-patcher apply %USERPROFILE%\.local\bin\claude.exe
79
+ # wrote <builds>\claude-dcc-2.1.295+dcc0.1.11.exe
80
+ # and <builds>\claude-dcc-2.1.295+dcc0.1.11.exe.dcc-lock.json
81
+ # <builds>\claude-dcc.exe -> claude-dcc-2.1.295+dcc0.1.11.exe (hardlink)
82
+
83
+ # Run the patched copy instead of claude
84
+ <builds>\claude-dcc.exe --resume
85
+ ```
86
+
87
+ The official `claude.exe` is never modified. Older builds stay in the builds folder for rollback, and the original Claude Code each build was made from is kept beside it. After an update, `check` again; if an edit fails, nothing is written and `check --baseline` shows what changed. Where everything goes, and every option: [docs/launcher.md](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/docs/launcher.md).
88
+
89
+ ### Make the patched build just `claude`
90
+
91
+ ```bash
92
+ dcc-env # cmd or PowerShell; in Git Bash: . dcc-env
93
+ dcc-patcher status # which claude this shell runs, and anything left to do
94
+ ```
95
+
96
+ `dcc-env` exists because no program can change the `PATH` of the shell that started it: it's a small script that runs inside your shell, so it works in one that was already open. The first time, it writes `claude` and `claude-stock` shims and adds their folder to your user `PATH` (after asking, and after saving the old value as a `.reg` file). Every time, it puts that folder on the `PATH` of the shell you typed it in. Shells opened afterwards don't need it.
97
+
98
+ From then on, `claude` runs the current patched build. When Claude Code updates, the next start says so, rebuilds in a few seconds, and runs the new build; if a patch no longer applies, it runs stock instead and says which patch needs attention. `claude-stock` always runs the official build; `dcc-patcher use stock` makes `claude` do the same; `dcc-patcher uninstall-shim` puts everything back. The details, including how to check or undo any of it by hand: [docs/launcher.md](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/docs/launcher.md).
99
+
100
+ ## How it works
101
+
102
+ - **Patches are written by structure, not by exact text.** Each patch names a stable landmark (a string that survives builds), the shape of the code to change near it, and a replacement. Minified names are wildcards, captured once and reused by later edits. Measured on the 7 Claude Code builds from 2.1.278 to 2.1.289 (2026-09-19 to 10-03): the same edits written as exact text applied in full on only one build, while the structural patch applies unchanged on all seven.
103
+ - **It fails closed.** Every edit must match exactly the number of times it expects, or nothing is written. The output is a new file beside the official `claude.exe`, never an in-place change, and everything except the patched module is verified byte-identical.
104
+ - **It knows when to stop.** A patched module carries a signature, so a patch is never applied twice; and a patch can say how to recognise that Anthropic fixed the bug, so it reports *fixed upstream* instead of *broken*.
105
+ - **It diagnoses.** When an edit no longer matches, `check` shows a diff of that code region against the last build where the patch worked, with minified names normalised so that only real code changes show.
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ python -P -m pytest tests/ -v # tests that need a local claude.exe are skipped without one
111
+ bash scripts/repokit-common/install-hooks.sh # version sync and pre-push checks
112
+ ```
113
+
114
+ ## Related Projects
115
+
116
+ - [ojura/claude-patches](https://github.com/ojura/claude-patches): patches for the Claude Code VS Code extension, and the inspiration for this project's approach (structural matching, failing closed, byte-stable verification). This project is its Windows-first, native-CLI counterpart.
117
+ - [claude-session-logger](https://github.com/DazzleML/claude-session-logger), [Claude-Session-Backup](https://github.com/DazzleML/Claude-Session-Backup), [claude-bookmarks](https://github.com/DazzleML/claude-bookmarks): other DazzleML Claude Code tools.
118
+
119
+ ## Disclaimer
120
+
121
+ Not affiliated with or endorsed by Anthropic. Patching modifies your local copy of Claude Code and removes its code signature; use it at your own risk, and keep the official `claude.exe` in place.
122
+
123
+ ## License
124
+
125
+ dazzle-claude-code-patcher, copyright (C) 2026 Dustin Darcy.
126
+
127
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/DazzleML/dazzle-claude-code-patcher/blob/main/LICENSE) file for details.
@@ -0,0 +1,36 @@
1
+ dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env,sha256=OJDtJKF1x4EK_Aj0jiNsn-HJ7Sg7ivG8sDyJ0aXMbPA,438
2
+ dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env.cmd,sha256=6pi7RgrPjMY0DOP9dwGtTB-U8_2UYl0XxsysDQzvR6s,520
3
+ dazzle_claude_code_patcher-0.2.0.data/scripts/dcc-env.ps1,sha256=FKeL0fuyFH-_-IFqdvxx9S-ygod2gdBvaL27-np9GYs,693
4
+ dazzle_claude_code_patcher-0.2.0.dist-info/licenses/LICENSE,sha256=DlZy6_97dpafvd_ObU8YNvyk5Gzk_qX070j7pKyDJBY,1069
5
+ dcc_patcher/__init__.py,sha256=V46Q-K6E4TdmY02o4v6vY-hi_8JxyUK_N5OlLTP4WEs,147
6
+ dcc_patcher/__main__.py,sha256=0nWgP61tL90yXqIEqaFluX3oUy2mAGWnxWG8Z6q_z9Y,129
7
+ dcc_patcher/_version.py,sha256=zOHDXvnGbyJgqEHBFqGoKTIYQKk7OMEWmJ0sJhOS8Io,2805
8
+ dcc_patcher/builds.py,sha256=N9UqJT98ITTosXb1iFjhq1AMHxFSwqvDCjjKxfvn4aE,3526
9
+ dcc_patcher/cli.py,sha256=S3viS7c5sNmsYSRY9R-oQtjDO1sUcVbG8N6rl0k77uA,21169
10
+ dcc_patcher/diagnose.py,sha256=dTW2JZhxWMy7k26FB6O75dm9iPybDQ-Xs2azygNXKBo,3175
11
+ dcc_patcher/engine.py,sha256=XwBD-nSEDD4aoT3Pti8tLf0e1gf9X7wQAuIjIs8Z7yo,8372
12
+ dcc_patcher/install.py,sha256=rDdLK5qFqCVpdD2CxBevgAjbMyRqoBe8nvYq8tfGAoA,24718
13
+ dcc_patcher/launch.py,sha256=kHehry827BLmuPUfldyZ6zey9A65DGWq9wN1ASNsbVA,7559
14
+ dcc_patcher/lock.py,sha256=lX43Ulsageh4NFAj6VXsRqP2NcD4Etj_NbROwdqBYs8,1850
15
+ dcc_patcher/originals.py,sha256=Vhs2L6B5pWFGxNKqgcrzE6fgrg5pQR04zjADoJbZAlM,4409
16
+ dcc_patcher/regfile.py,sha256=QhsLh0Kr2MHqgYSroRgDKyCeHS49wFkMKrsGKhMFNGA,2902
17
+ dcc_patcher/resolve.py,sha256=qhDhBVTl4ZPdj-EE8Yne4c6nNYlaYsN3o3gJP7j24WU,1596
18
+ dcc_patcher/spec.py,sha256=bFl98aCw18Z-Jw6F35fPo8SYdsSn2m-cYM41yvE8O3A,7193
19
+ dcc_patcher/style.py,sha256=8kIFwjSCS7vKm0wB18PxmZNZDcXzhqM7Smeze31LeBg,2524
20
+ dcc_patcher/winpath.py,sha256=Xgar7ozkIUDwHd3T5OvJJ-d0d29DA4DpXzZqvSRuPow,6025
21
+ dcc_patcher/containers/__init__.py,sha256=vBHbRTWND7ra65bY5uSQCrD7OGUxZPkUEogBjlySDtQ,3114
22
+ dcc_patcher/containers/pe.py,sha256=1MmsUb7MO2ttB8_IPxE1KFhn-Spxj6fJ4Q8s-mo1lqg,6740
23
+ dcc_patcher/patches/B-banner-label/patch.toml,sha256=xlnQJJ34AGaiwvfOE5OGfqC_VnBTWzJrNzOB5Ri43Y0,1504
24
+ dcc_patcher/patches/B2-banner-label-flicker-free/patch.toml,sha256=VlWDmQ2gQUo4arFickatBbdj-DqHT4iDLcgy0srHQoU,1319
25
+ dcc_patcher/patches/H-history-past-compaction/README.md,sha256=5YWRmsqJHNHbdYVNPcSHMAMw0ggIEayn-dEp5cQnXFY,4432
26
+ dcc_patcher/patches/H-history-past-compaction/patch.toml,sha256=FmlpR8aDFk5BMB923vWps04XGnyJADv1zIur53vUdnk,4221
27
+ dcc_patcher/patches/J-history-across-files/README.md,sha256=GPj68XDk8hemVGwjzGluKK0f39pktLs2_AAQKqgaCbU,2714
28
+ dcc_patcher/patches/J-history-across-files/patch.toml,sha256=H7FFCa0Y7w-zmnqSnXLDlR8ajwxJx6INyintswmZb5U,3652
29
+ dcc_patcher/patches/V-version-label/patch.toml,sha256=kOZ2Aiw9Y4cdhc11_dKJ8bQ6VMmwGnxnqBZBRqeQxb0,934
30
+ dcc_patcher/shims/claude.cmd.in,sha256=Z-3pgpbmZDxbZlftl5Ism-J-WjCmcc8LZhRbz9657Vg,194
31
+ dcc_patcher/shims/claude.sh.in,sha256=uENxtkvIntZzaQNL3vMlEg3EHk3B05uWudC5J_s_Tog,172
32
+ dazzle_claude_code_patcher-0.2.0.dist-info/METADATA,sha256=lZgwHFbnymyGLAxOJ24_T8Bg2ELlgSGtcbiyEJv3dkQ,10115
33
+ dazzle_claude_code_patcher-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
34
+ dazzle_claude_code_patcher-0.2.0.dist-info/entry_points.txt,sha256=Wcemx_iFEhrwrmDCmDntyIYGqAbd-4oQucnX8TlmJdQ,53
35
+ dazzle_claude_code_patcher-0.2.0.dist-info/top_level.txt,sha256=8pNEQQq0GOeIQdu9-8CHDtIPHmj9084IGB9lcj3Dre0,12
36
+ dazzle_claude_code_patcher-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dcc-patcher = dcc_patcher.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dustin Darcy
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.
@@ -0,0 +1 @@
1
+ dcc_patcher
@@ -0,0 +1,2 @@
1
+ """dcc_patcher - apply, verify and diagnose patches to the native Claude Code CLI."""
2
+ from ._version import __version__, __app_name__, PIP_VERSION
@@ -0,0 +1,7 @@
1
+ """Allow running as: python -m dcc_patcher"""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
@@ -0,0 +1,92 @@
1
+ """
2
+ Version information for dazzle-claude-code-patcher.
3
+
4
+ This file is the canonical source for version numbers.
5
+ The __version__ string is automatically updated by git hooks
6
+ with build metadata (branch, build number, date, commit hash).
7
+
8
+ Format: MAJOR.MINOR.PATCH[-PHASE]_BRANCH_BUILD-YYYYMMDD-COMMITHASH
9
+ Example: 0.1.0_main_1-20260101-a1b2c3d4
10
+
11
+ Version levels:
12
+ PROJECT_PHASE: Global project maturity (prealpha -> alpha -> beta -> stable).
13
+ Changes rarely, when the overall project hits a threshold.
14
+ PHASE: Per-MINOR feature set maturity (alpha -> beta -> "" for stable).
15
+ Drops when a MINOR's feature set is complete.
16
+ """
17
+
18
+ # Version components - edit these for version bumps
19
+ MAJOR = 0
20
+ MINOR = 2
21
+ PATCH = 0
22
+ PHASE = "" # Per-MINOR feature set: "" (stable), "alpha", "beta", "rc1", etc.
23
+
24
+ # Project-level phase (independent of version phase)
25
+ PROJECT_PHASE = "" # "prealpha", "alpha", "beta", "stable", or ""
26
+
27
+ # Auto-updated by git hooks - do not edit manually
28
+ __version__ = "0.2.0_main_17-20261009-e0aa8ec3"
29
+ __app_name__ = "dazzle-claude-code-patcher"
30
+
31
+
32
+ def get_version():
33
+ """Return the full version string including branch and build info."""
34
+ return __version__
35
+
36
+
37
+ def get_base_version():
38
+ """Return the semantic version string (MAJOR.MINOR.PATCH[-PHASE])."""
39
+ if "_" in __version__:
40
+ return __version__.split("_")[0]
41
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
42
+ if PHASE:
43
+ base = f"{base}-{PHASE}"
44
+ return base
45
+
46
+
47
+ def get_display_version():
48
+ """Return a human-friendly version string with project phase.
49
+
50
+ Example: 'PREALPHA 0.1.0-alpha' or 'BETA 0.5.1' or '1.0.0'
51
+ """
52
+ base = get_base_version()
53
+ if PROJECT_PHASE and PROJECT_PHASE != "stable":
54
+ return f"{PROJECT_PHASE.upper()} {base}"
55
+ return base
56
+
57
+
58
+ def get_pip_version():
59
+ """
60
+ Return PEP 440 compliant version for pip/setuptools.
61
+
62
+ Converts our version format to PEP 440:
63
+ - Main branch: 0.1.0_main_3-20260404-hash -> 0.1.0
64
+ - Dev branch: 0.1.0_dev_3-20260404-hash -> 0.1.0.dev3
65
+ - Alpha: 0.1.0-alpha_main_3 -> 0.1.0a0
66
+ """
67
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
68
+
69
+ # Map phase to PEP 440 pre-release segment
70
+ phase_map = {"alpha": "a0", "beta": "b0"}
71
+ if PHASE:
72
+ base += phase_map.get(PHASE, PHASE)
73
+
74
+ if "_" not in __version__:
75
+ return base
76
+
77
+ parts = __version__.split("_")
78
+ branch = parts[1] if len(parts) > 1 else "unknown"
79
+
80
+ if branch == "main":
81
+ return base
82
+ else:
83
+ build_info = "_".join(parts[2:]) if len(parts) > 2 else ""
84
+ build_num = build_info.split("-")[0] if "-" in build_info else "0"
85
+ return f"{base}.dev{build_num}"
86
+
87
+
88
+ # For convenience in imports
89
+ VERSION = get_version()
90
+ BASE_VERSION = get_base_version()
91
+ PIP_VERSION = get_pip_version()
92
+ DISPLAY_VERSION = get_display_version()
dcc_patcher/builds.py ADDED
@@ -0,0 +1,100 @@
1
+ """Where builds go and what they are called (#3).
2
+
3
+ `apply` without `-o` writes `claude-dcc-<claude version>+dcc<patcher version><ext>`
4
+ and its lockfile into one fixed builds folder, whatever folder it is run from,
5
+ and points `claude-dcc<ext>` there at the newest build. The folder is
6
+ `DCC_BUILDS_DIR` when set; `private/builds/` in the repository when the package
7
+ runs from a checkout (djdarcy, 2026-10-07); otherwise a per-user data folder.
8
+
9
+ Nothing is ever written inside Claude Code's own config folder (`~/.claude`, or
10
+ `CLAUDE_CONFIG_DIR`): a relative `-o` run from there once put a 258 MB build
11
+ into a folder kept under git (#3's comment).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import re
17
+ import shutil
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from ._version import BASE_VERSION
22
+
23
+ REPO = Path(__file__).resolve().parents[1]
24
+ CURRENT = 'claude-dcc'
25
+
26
+
27
+ def builds_dir() -> Path:
28
+ env = os.environ.get('DCC_BUILDS_DIR')
29
+ if env:
30
+ return Path(env).resolve()
31
+ if (REPO / '.git').exists():
32
+ return REPO / 'private' / 'builds'
33
+ if sys.platform == 'win32':
34
+ base = os.environ.get('LOCALAPPDATA') or os.path.expanduser('~/AppData/Local')
35
+ else:
36
+ base = os.environ.get('XDG_DATA_HOME') or os.path.expanduser('~/.local/share')
37
+ return Path(base) / 'dazzle-claude-code-patcher' / 'builds'
38
+
39
+
40
+ def build_name(claude_version: str, suffix: str) -> str:
41
+ return f'{CURRENT}-{claude_version}+dcc{BASE_VERSION}{suffix}'
42
+
43
+
44
+ def version_of(data: bytes) -> str:
45
+ """Claude Code's version, from the VERSION constant its bundle carries."""
46
+ m = re.search(rb'VERSION:"(\d+\.\d+\.\d+)"', data)
47
+ return m.group(1).decode() if m else 'unknown'
48
+
49
+
50
+ def exe_suffix(path) -> str:
51
+ """'.exe' for a Windows build, '' otherwise -- also for the updater's
52
+ `claude.exe.old.<ms>` copies, whose last suffix is the timestamp."""
53
+ return '.exe' if '.exe' in Path(path).name.lower() else ''
54
+
55
+
56
+ def posix_shim_dir() -> Path:
57
+ """Where the sh shims go on Windows: ~/bin, which Git for Windows puts first on
58
+ Git Bash's PATH (/etc/profile.d/env.sh) and Windows itself never searches. An
59
+ extensionless script in a folder on the Windows PATH is picked by resolvers that
60
+ try the bare name (Python 3.12.0's shutil.which), and CreateProcess can't run it."""
61
+ return Path(os.path.expanduser('~')) / 'bin'
62
+
63
+
64
+ def config_dirs() -> list[Path]:
65
+ dirs = [Path(os.path.expanduser('~')) / '.claude']
66
+ if os.environ.get('CLAUDE_CONFIG_DIR'):
67
+ dirs.append(Path(os.environ['CLAUDE_CONFIG_DIR']))
68
+ return [d.resolve() for d in dirs]
69
+
70
+
71
+ def inside_config(path) -> Path | None:
72
+ """The Claude Code config folder `path` would land in, or None."""
73
+ target = Path(path).resolve()
74
+ for d in config_dirs():
75
+ if target == d or d in target.parents:
76
+ return d
77
+ return None
78
+
79
+
80
+ def point_current(build: Path) -> tuple[Path, str]:
81
+ """Make `<folder>/claude-dcc<ext>` the newest build: a hardlink, else a copy.
82
+
83
+ Returns (link path, 'hardlink' | 'copy'). Raises PermissionError when the
84
+ current build is running (Windows will not replace an exe in use)."""
85
+ link = build.with_name(CURRENT + build.suffix)
86
+ tmp = link.with_name(f'.dcc-{link.name}.tmp')
87
+ if tmp.exists():
88
+ tmp.unlink()
89
+ try:
90
+ os.link(build, tmp)
91
+ how = 'hardlink'
92
+ except OSError:
93
+ shutil.copyfile(build, tmp)
94
+ how = 'copy'
95
+ try:
96
+ os.replace(tmp, link)
97
+ except BaseException:
98
+ tmp.unlink()
99
+ raise
100
+ return link, how