{"_id":"@codehornets/ccpatch","name":"@codehornets/ccpatch","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@codehornets/ccpatch","version":"0.2.0","type":"module","publishConfig":{"access":"public"},"description":"A patch framework for the official Anthropic Claude Code CLI — extract, unminify, and apply JavaScript-level patches to add features, fix bugs, and expose internals.","keywords":["claude","claude-code","anthropic","cli","patch","bundle","unminify"],"license":"MIT","author":{"name":"Anga Ntakpe","email":"codehornets@gmail.com"},"bin":{"ccpatch":"bin/patch-cli.mjs"},"repository":{"type":"git","url":"git+https://github.com/angantakpe/ccpatch.git"},"bugs":{"url":"https://github.com/angantakpe/ccpatch/issues"},"homepage":"https://github.com/angantakpe/ccpatch#readme","engines":{"node":">=20"},"scripts":{"test:patches":"node --test tests/patch-verification.test.mjs","test:boot":"node --test tests/boot-smoke.test.mjs","test:integration":"node tests/integration_roundtrip.mjs","test:all":"node --test tests/*.test.mjs","lint:dead":"node scripts/lint-dead.mjs","lint:unused":"knip","lint:unused:fix":"knip --fix","lint:unused:prod":"knip --production --strict","lint:types":"node scripts/gen-types.mjs && tsc --noEmit --strict types/patch.d.ts","lint:declarative":"node scripts/check-declarative.mjs","lint:anchors":"node scripts/lint-anchors.mjs","lint":"npm run lint:dead && npm run lint:unused && npm run lint:types && npm run lint:declarative && npm run lint:anchors","gen:types":"node scripts/gen-types.mjs","tui":"node bin/patch-tui.mjs","test-patches":"npm run test:patches","test-boot":"npm run test:boot","test-integration":"npm run test:integration","test-all":"npm run test:all","lint-dead":"npm run lint:dead","lint-unused":"npm run lint:unused","lint-types":"npm run lint:types","lint-declarative":"npm run lint:declarative","lint-anchors":"npm run lint:anchors","gen-types":"npm run gen:types"},"dependencies":{"acorn":"^8.14.0","acorn-walk":"^8.3.4","diff":"^9.0.0","ink":"^6.8.0","js-yaml":"^4.1.1","react":"^19.2.5","source-map":"^0.7.4"},"devDependencies":{"@types/node":"^22.10.0","knip":"^5.39.0","typescript":"^5.7.0"},"gitHead":"a32060d8db2fbddaf3ef11ed3938bbc4f1ba6198","_id":"@codehornets/ccpatch@0.2.0","_nodeVersion":"26.0.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-+36TuAi/t/X/y0oISKvtwvb0UP+0HUecbyXu3T2EbhsBQ56159+pNry0rzblvwQdKm4q6pJNWnXJnH1Zs5y+LA==","shasum":"a5b88cfff46414eaab37abfe42826d77bf80fb32","tarball":"https://registry.npmjs.org/@codehornets/ccpatch/-/ccpatch-0.2.0.tgz","fileCount":153,"unpackedSize":1182576,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD15e6+y6SxKVMke4trJS4R7K7gT3T2o0fsgryvcSssWgIgAqtRSzsz/DKAUYSyRI1UaE7nVo3zlLwMZz+UlW01V/4="}]},"_npmUser":{"name":"codehornets","email":"codehornets@gmail.com"},"directories":{},"maintainers":[{"name":"codehornets","email":"codehornets@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ccpatch_0.2.0_1780412260210_0.402840984449516"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-02T14:57:40.021Z","0.2.0":"2026-06-02T14:57:40.497Z","modified":"2026-06-02T14:57:40.785Z"},"maintainers":[{"name":"codehornets","email":"codehornets@gmail.com"}],"description":"A patch framework for the official Anthropic Claude Code CLI — extract, unminify, and apply JavaScript-level patches to add features, fix bugs, and expose internals.","homepage":"https://github.com/angantakpe/ccpatch#readme","keywords":["claude","claude-code","anthropic","cli","patch","bundle","unminify"],"repository":{"type":"git","url":"git+https://github.com/angantakpe/ccpatch.git"},"author":{"name":"Anga Ntakpe","email":"codehornets@gmail.com"},"bugs":{"url":"https://github.com/angantakpe/ccpatch/issues"},"license":"MIT","readme":"[![drift-check](https://github.com/angantakpe/ccpatch/actions/workflows/drift-check.yml/badge.svg)](https://github.com/angantakpe/ccpatch/actions/workflows/drift-check.yml)\n\n# ccpatch\n\nccpatch injects scripts into Claude Code's `cli.js` to extend and alter its behavior from within the process:\n\n- **Modify the tool list** before it is sent to the API — add, remove, or reshape tools\n- **Flip internal feature flags** (`loop_dynamic`, `durable_cron`, `extended_thinking`) that are boolean checks hardcoded in the bundle\n- **Intercept user input** at the submit level, before the harness processes it — add native slash commands\n- **Access internal conversation state** — the agent loop, turn history, and module-scope variables\n- **Expose internal APIs** — `expose_tool_dispatch`, `expose_api_client`, `expose_submit_input` let external scripts call into the running CLI process\n- **Patch the UI** — React/Ink component tree, input bar rendering, terminal output\n\nccpatch is not a fork. It ships **no Anthropic code**. It transforms a copy of the Claude Code CLI that is already installed on your machine.\n\n---\n\n## How it works\n\n- **Anchor → transform → verify.** Each patch declares a stable string anchor (or AST anchor via windowed Acorn parse), a transform function over the bundle text, and a `verify.present` / `verify.absent` assertion that runs immediately after apply. Anchor misses are logged with fuzzy candidates so drift is diagnosable, not silent.\n- **Shims-as-patches.** Substantial logic lives in real `.mjs` files under `core/` and `extensions/`. Patches inject a small wrapper at the anchor that calls into the shim, so contributors edit normal JavaScript instead of escaped patch strings.\n- **Phase-based runner.** Patches declare `phase: pre | main | post` and optional `dependsOn`. The runner topo-sorts within each phase and enforces that dependencies live in the same or an earlier phase.\n- **Native binary repack.** For versions shipped as a Bun-compiled binary, ccpatch extracts the embedded JS, patches it, and splices the patched JS back into the binary at the original byte offset (direct buffer rewrite — no `node-lief`). When the patched JS is *smaller* than the original region it is padded with insignificant whitespace; when it is the *same* size it is spliced as-is. When it is *larger* — which can happen because patches add code — ccpatch decodes Bun's `StandaloneModuleGraph` and rewrites every blob-relative offset (the `payload_len` header, `byte_count`, module-record `StringPointer`s, and the ELF section/segment offsets) by the growth delta, then gates the result on a smoke check that the repacked binary boots the embedded entrypoint (`bin/bun-sea-graph.mjs`). This grow path is implemented for ELF (linux-x64) and thin Mach-O (darwin-arm64/x64) Bun binaries; fat/universal Mach-O (thin it to the target slice first) and PE (Windows) growth still **fail loudly** rather than risk corruption, and those targets fall back to the plain-JS path or a reduced patch set. Growing a Mach-O binary invalidates and **strips** its code signature, so the emitted darwin binary is **unsigned** — re-sign before distribution on a darwin host (`codesign -s - --force <output>` for ad-hoc, or with a Developer ID). ccpatch does not forge signatures. Native repack now **fails closed**: the post-repack smoke check is required and Bun-version drift aborts the build; pass `--allow-unverified` (local dev) to downgrade these to warnings.\n\n---\n\n## Install\n\nRequires Node.js 20+ and either Bun or npm.\n\n**Quick path (no clone) — run it straight from npm:**\n\n```\nnpx @codehornets/ccpatch doctor <path-to-cli.js>   # read-only anchor-health check\nnpx @codehornets/ccpatch <input.js> <output.js>    # apply patches to a bundle\n```\n\n`npx @codehornets/ccpatch --help` lists every subcommand. The published package ships\nthe `ccpatch` runner, `ccpatch.yml`, and the patch sources, so the one-shot\n`npx` invocation works without a checkout — handy for CI or a quick `doctor`\nrun. Clone the repo (below) when you want the `make` wrappers,\nauto-version-detection, the TUI, or to author new patches.\n\n**Full checkout (recommended for day-to-day work):**\n\n```\ngit clone https://github.com/angantakpe/ccpatch.git ccpatch\ncd ccpatch\nbun install        # or: npm install\n```\n\n> **Native repack** (`patch-claude-code-native`) runs entirely on Node's built-in `fs`/`Buffer` plus the in-repo `tools/bun-decompiler` — there are no native build dependencies to trust, so a plain `bun install` / `npm install` is sufficient.\n\nThe Makefile auto-detects the locally installed `claude` binary's version. Override with `VERSION=x.y.z` on any target.\n\n---\n\n## Which entry point should I use?\n\nThere are three layers, and they wrap each other: **`make` targets → `bin/patch-cli.mjs` → the runner (`runner/`)**. `bin/patch-cli.mjs` *is* the `ccpatch` CLI (it just calls `runPatchCli` in `runner/cli.mjs`); the `make` targets are thin wrappers that invoke it with the version auto-detected and the right arguments filled in. Reach for the highest layer that does what you need:\n\n| Task | Canonical path | Notes |\n| --- | --- | --- |\n| Apply patches | `make patch-claude-code` | Auto-detects the installed `claude` version + standard profile. Drop to `node bin/patch-cli.mjs <in> <out>` only when you need an explicit input/output path or a flag the target doesn't expose. |\n| Drift / health check | `make doctor` | Read-only anchor-health check against the installed bundle (wraps `bin/patch-cli.mjs doctor`). |\n| Coverage | `make patch-coverage` | Apply + smoke-run + cross-reference apply-time results with runtime hits (wraps `bin/patch-cli.mjs coverage`). |\n| Revert a patched bundle | `node bin/patch-cli.mjs revert <bundle>` | No `make` wrapper — restores each patched region from the recorded pre-patch sha. |\n| Author a new patch | `make new-patch NAME=my_feature` | Scaffolds a manifest-valid patch stub (wraps `bin/scaffold-patch.mjs`). Add `KIND=prefix\\|free\\|postfix\\|transpiler\\|splice\\|flag` and `CATEGORY=extension\\|core`. The scaffolder prints the exact single-patch dry-run command to iterate with. |\n| Interactive TUI | `node bin/patch-tui.mjs` | Menu-driven front-end over the same subcommands (apply, doctor, diff, …) — handy for browsing/toggling patches without memorizing flags. Run with `--help` for details. |\n| Everything else | `node bin/patch-cli.mjs <subcommand>` | `explain`, `capabilities`, `diff`, `heal`, `fallback-capture`, … — subcommands without a dedicated `make` target. |\n\nRule of thumb: prefer `make` for day-to-day work; drop to `bin/patch-cli.mjs` when you need a subcommand or argument the target doesn't pass through; touch the runner modules only when authoring or debugging patches.\n\n> **npm scripts mirror the `make` targets.** `package.json` exposes `npm run test:patches`, etc., which run the same checks as the corresponding `make` target. Both spellings work in both build systems — `npm run test:patches` and `npm run test-patches` are aliases, as are `make test-patches` and `make test:patches` (and likewise for the `lint:dead` / `lint-dead` and `lint:unused` / `lint-unused` pairs). Use whichever fits your workflow.\n\n---\n\n## Quick start\n\nApply every patch enabled in `ccpatch.yml` to your locally installed Claude Code version:\n\n```\nmake patch-claude-code\n```\n\nPatch a specific version and write to a custom path:\n\n```\nmake patch-claude-code VERSION=2.1.148 OUTPUT=./my-cli.js\n```\n\nOverride the YAML and apply an explicit list of patches:\n\n```\nmake patch-claude-code PATCH=fetch_interceptor,bun_shim,cost_tracker\n```\n\nPreview the diff without writing the output:\n\n```\nnode bin/patch-cli.mjs <input.js> <output.js> --dry-run\n```\n\nList every patch that's loadable:\n\n```\nmake patch-list\n```\n\nPatch a native (Bun-compiled) Claude Code binary end-to-end:\n\n```\nmake patch-claude-code-native VERSION=2.1.148\n```\n\n> **Prerequisite:** `esm_compat` and `bun_shim` must be disabled in `ccpatch.yml` (or excluded from `PATCH=`) before running the native pipeline. Those patches rewrite the CJS wrapper for Node.js and produce output that cannot be embedded back into a Bun SEA binary.\n\nRun the patch verification test suite:\n\n```\nmake test-patches\n```\n\n### Profiles\n\n`--profile <name>` (or `-p`) bundles a curated patch set defined under `profiles:` in `ccpatch.yml`. When set, only the patches listed in that profile are applied — the per-patch `enabled:` flags in `ccpatch.yml` are ignored for that run. When omitted, behaviour falls back to the `ccpatch.yml` enabled flags.\n\n`make patch-claude-code` defaults to the **standard** profile. Override with `PROFILE=` (or disable with `PROFILE=`, empty):\n\n```\nmake patch-claude-code                  # standard profile (default)\nmake patch-claude-code PROFILE=minimal  # bug fixes + minimum infra\nmake patch-claude-code PROFILE=power    # every patch listed in ccpatch.yml\nmake patch-claude-code PROFILE=         # no profile — use ccpatch.yml enabled flags\n```\n\nDirectly via the CLI:\n\n```\nnode bin/patch-cli.mjs <input.js> <output.js> --profile standard\n```\n\nThe three curated profiles:\n\n| Profile | Intent | Contents |\n| --- | --- | --- |\n| **minimal** | Bug fixes plus the minimum infrastructure those fixes need to run. | `react_singleton`, `esm_compat`, `contracts`, `overlay_loader`, `fetch_interceptor`, `bun_shim`, `stdin_da1_leak`, `message_normalizer`, `project_root`, `tool_result_error_content`, `subagent_hooks_stub` |\n| **standard** _(default)_ | `minimal` plus quality-of-life features (model/command system, thinking unlocks, context guards, MCP lazy-load). | everything in `minimal`, plus `input_bar_color`, `model`, `custom_commands`, `slash_dispatch`, `context_budget_warn`, `recap_strip_hint`, `unhide_features`, `extended_thinking`, `force_thinking`, `tool_result_trim`, `large_content_guard`, `hook_noise_mute`, `mcp_lazy`, `dotenv_loader`, `block_tools` |\n| **power** | Every patch listed under `patches:` in `ccpatch.yml` — all features, observability, exposed internals, and optional integrations. | the full patch set (infrastructure, bug fixes, QoL, feature unlocks, command system, observability, exposed internals, and integrations such as `cost_tracker`, `webhook`, `save_conversations`, `cache_responses`) |\n\nTwo additional vertical profiles ship for automation use cases: **daemon** (drive the running CLI headlessly via the event bus) and **orchestrator**. Profile membership is the source of truth in `ccpatch.yml`; an unknown patch name in a profile is skipped with a warning.\n\n`doctor` and `capabilities` accept the same `--profile` flag (`make doctor PROFILE=standard`). An explicit `PATCH=name1,name2` list bypasses the profile entirely.\n\n### Drift check\n\n```\nnode bin/patch-cli.mjs doctor <bundle> --version <x.y.z>\n```\n\nReports patches whose anchors have drifted in a new Claude Code release. Fuzzy candidates are logged to `storage/outputs/anchor-drift.jsonl` with scores and offsets so re-anchoring is a targeted lookup, not a hunt.\n\n### Build failure modes\n\nA patch that declares a real `verify.present` (it has a positive thing to inject) but produces **no change** has silently failed — its anchor almost certainly drifted. By default this is now a **build failure**: the build prints the no-op, exits non-zero, and the end-of-run summary reports a `No-op:` count. Patches that declare only `verify.absent` (a desired end-state, not a transform) keep the lenient \"no-change is fine\" semantics.\n\nPass `--best-effort` (or set `CCPATCH_BEST_EFFORT=1`) to restore the older lenient behaviour — a `verify.present` no-op is downgraded to a warning and the build continues:\n\n```\nnode bin/patch-cli.mjs <input.js> <output.js> --best-effort\n```\n\nWhen `apply()` no-ops but a stored unified diff (`fallbackDiff`) still applies, the patch is recorded as **applied via stale fallback diff** — a loud, separately-counted outcome (the summary shows a `Stale fallback:` count). The anchors have drifted even though the textual diff still landed, so fix the anchors. Under `--strict` a stale-fallback apply is **fatal**; in the default mode it applies but warns prominently.\n\n---\n\n## Patch categories\n\nThe current patch set, grouped by intent. Full list and toggles live in `ccpatch.yml`.\n\n| Category | Patches |\n| --- | --- |\n| **Infrastructure** (`core/`) | `react_singleton`, `esm_compat`, `contracts`, `fetch_interceptor` |\n| **Bug fixes** (`core/`) | `bun_shim`, `stdin_da1_leak`, `message_normalizer`, `project_root`, `tool_result_error_content` |\n| **Fixes / QoL** (`extensions/`) | `dotenv_loader`, `hook_noise_mute`, `cache_ttl`, `grep_shadow`, `rate_limit`, `large_content_guard`, `recap_strip_hint`, `input_bar_color` |\n| **Feature unlocks** | `durable_cron`, `loop_dynamic`, `unhide_features`, `extended_thinking`, `force_thinking`, `mcp_lazy` |\n| **Command system** | `custom_commands`, `slash_dispatch`, `subagent_hooks_stub` |\n| **Observability** | `cost_tracker`, `context_budget_warn`, `tool_result_trim`, `tools_log`, `boost_project_commands`, `session_timer`, `debug` |\n| **Expose internals** | `expose_tool_dispatch`, `expose_api_client`, `expose_submit_input`, `expose_agent_tool`, `prime_agent_tool_on_boot`, `capture_interactive_request` |\n| **Optional integrations** | `model`, `block_tools`, `save_conversations`, `webhook`, `cache_responses` |\n\nBy default only `core/` infrastructure and bug fixes are enabled. Extensions are opt-in via `ccpatch.yml`.\n\n---\n\n## Compatibility & safety\n\n- Patches run inside your local Claude Code process. They share the same trust boundary as the CLI itself — anything the CLI can do, an enabled patch can also do.\n- ccpatch makes **no network calls** from the patcher and ships **no telemetry**.\n- ccpatch ships **no Anthropic source code** and is not affiliated with, sponsored by, or endorsed by Anthropic.\n- See [THREAT_MODEL.md](./THREAT_MODEL.md) for a per-patch breakdown of what each touches, reads, and sends.\n- Patches that declare `network`, `exec`, or `env` capabilities are gated by an `ack:` block in `ccpatch.yml`. Builds fail until you acknowledge each capability per patch (e.g. `fetch_interceptor: [network]`).\n- Acking is a one-line attestation that you've read THREAT_MODEL.md for that patch. Pass `--allow-unacked` to bypass the gate (legacy warn-only mode).\n- See `ccpatch.yml`'s `ack:` block for the shipped defaults that cover the always-on core patches.\n- See [SUPPORTED_VERSIONS.md](./SUPPORTED_VERSIONS.md) for the upstream versions exercised in CI and known bundle hashes.\n- See [NOTICE](./NOTICE) for trademark and terms-of-service notes.\n\nWhen Anthropic ships a new Claude Code version, anchors may drift. The runner logs near-miss candidates to `storage/outputs/anchor-drift.jsonl` so the relevant patch can be re-anchored quickly. Most patches use stable string literals (e.g. feature-flag keys) rather than minified identifiers, which keeps drift surface small.\n\n---\n\n## TUI — read-only patch browser\n\n```\nnode bin/patch-tui.mjs\n```\n\nThe TUI is a **read-only patch browser**: it lists every loadable patch with its\nenabled/ack state and the latest anchor drift, and lets you expand a patch to\ninspect its manifest. It never mutates `ccpatch.yml` or any patch file. It is\nintentionally narrow — for the rich operations (apply, `doctor`, `heal`,\n`capabilities`, `coverage`, `module`, `repl`, `revert`/`diff`, …) use the CLI\n(`node bin/patch-cli.mjs <command>`). Run `node bin/patch-tui.mjs --help` for\nkeybindings.\n\n---\n\n## Security\n\nReport vulnerabilities privately via GitHub Security Advisories or the email\nin [SECURITY.md](./SECURITY.md). Please do not open public issues for\nsecurity reports.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for the patch contract, the step-by-step \"add a new patch\" walkthrough, and the PR checklist.\n\n---\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n","readmeFilename":"README.md","_rev":"1-856302f5e7ab7b3c2eaeaeea9262364a"}