@buffbirb/unclaude
v1.0.29
Published
An opinionated AI dev tool setup script with a terminal UI. Configure privacy, code intelligence, and tool wrappers for Claude Code and OpenCode.
Maintainers
Readme
unclaude
An AI sanitizer and setup script with a TUI and CLI.
Why?
Do you make VSCode your commit author? Do you write "sent from my Dell XPS" in your PRs?
If not, then maybe you don't want AI agents leaving their dirty footprints all over your git history. This package will clean up commits from your local environment or Claude Code Web.
Want local sandboxes with better default sanitization? Try the sbx Apple Container wrapper.
I also added opt-in support headroom and openspec, which I consider broadly useful.
Quick start
# ───────── Installing ──────────
# Launch the TUI anywhere
npx @buffbirb/unclaude@latest
# Certain features (clangd on linux, apple container) may require sudo
sudo -v && npx @buffbirb/unclaude@latest
# ───────── Extras ───────────
# After installation:
# Run opencode in sandbox with --dangerously-skip-permissions and headroom token compression
headroom-start sbx opencode
Presets
Local environments are very simple to set up, but managed/remote environments employ a lot of tricks to make their attributions stick:
- system prompt overrides
CLAUDE.md - container/environment overrides git config and
settings.json - GitHub PRs use MCP, most likely remote, which is not interceptable
- tracked files are reverted between the pre-install script and session start, potentially scrubbing
.claude/*and.gitignore - sometimes the setup script is skipped
This script offers two preset solutions:
- Local preset (default) — minimal setup for local repos; no commits.
- Hybrid preset — setup for local and managed environments; some items are committed and pulled into the remote environment to control attribution.
I could not make a purely remote setup work without relying on any committed items. There is nothing special about claude code web so feel free to explore other options.
Features
| Feature | What it does |
|---|---|
| Stop Telemetry | Exports CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 in ~/.unclaude/airc |
| Agent JSON config for git commits | Sets attribution: { commit: '', pr: '', sessionUrl: false } in Claude Code settings.json |
| Agent SessionStart hook for git commits | Registers a Claude Code SessionStart hook that installs a bash commit-msg hook in .git/hooks (and sets local git identity) at the start of every session |
| Git branch rename (immediate + pre-push) | Renames the current claude/* or claude-* branch immediately, and installs a pre-push hook in .git/hooks/ that intercepts any claude/* push, rewrites it to the stripped name on the remote, deletes the old remote branch, and aborts the original push |
| GitHub Action for PR body | Writes a workflow that strips Claude attribution from PR bodies on opened/edited |
| LSP | Installs Claude Code plugins for C++, Swift, Python, TypeScript, Go, and Rust LSP support (claude-plugins-official). Language servers themselves are not installed — see LSP Install Details. |
| Headroom | Installs headroom via uv; writes a headroom-start wrapper script to ~/.local/bin/ that starts the proxy and execs its arguments (e.g. headroom-start claude) |
| OpenSpec | Installs @fission-ai/openspec globally via npm; optionally runs openspec init in the current directory |
| Playwright CLI | Global. Installs the playwright npm package and its Chromium browser so the agent can drive a headless browser directly through its shell tool (playwright open, playwright screenshot, playwright codegen, or scripting against the playwright package) — no MCP server, no per-repo registration. |
| Apple Container Sandbox | Writes an sbx wrapper script to ~/.local/bin/ that runs Claude or OpenCode inside an Apple Container (macOS only). The sandbox images ship the same Playwright CLI and headless browser pre-installed, run entirely inside the container (no exposed port, isolated profile) so it never touches a browser outside the sandbox. |
Feature matrix
| Feature | Agents | Scope | Local default | Hybrid default | |---|---|---|---|---| | Stop Telemetry | Claude Code | Global | ✓ | ✓ | | Agent config for git commits | Claude Code | Project | ✓ | ✓ | | Agent SessionStart hook for git commits | Claude Code | Project | | ✓ | | Git branch rename (immediate + pre-push) | Claude Code, OpenCode | Project | | ✓ | | GitHub Action for PR body | Claude Code, OpenCode | Project | | ✓ | | LSP | Claude Code | Global | | | | Headroom | Claude Code, OpenCode | Global | | | | OpenSpec | Claude Code, OpenCode | Global (install), Project (init) | | | | Playwright CLI | Claude Code, OpenCode | Global | | | | Apple Container Sandbox | Claude Code, OpenCode | Global | | |
Personalization
If you select Agent SessionStart hook for git commits or Git branch rename, a second screen asks whether to personalize the setup to your identity.
| Field | Purpose |
|---|---|
| Git user | Your user.name (prefilled from git config --global user.name). Used by Agent SessionStart hook for git commits. |
| Git email | Your user.email (prefilled from git config --global user.email). Used by Agent SessionStart hook for git commits. |
| Branch prefix | Prepended to renamed claude/* branches (e.g., username/foo). Used by Git branch rename (immediate + pre-push). |
This is most useful in managed/remote environments (e.g., Claude Code Web) where the AI does not have access to your local git configuration. By embedding your identity, commits and branch metadata are authored under your name rather than an AI-generated default.
Note: Commits will still show as unverified on GitHub / GitLab because most managed providers do not offer a secure place to store a GPG or SSH signing key.
Git Settings
Currently only .gitignore is amended with a section covering generated attribution-related files.
Optional Tools
LSP Install Details
When the LSP feature is selected, unclaude runs claude plugin marketplace update claude-plugins-official followed by claude plugin install <plugin>@claude-plugins-official for each plugin below. Claude Code only — OpenCode isn't supported for this feature.
Language servers are not installed. Each plugin expects the corresponding server on PATH; install it yourself however you normally manage toolchains. If a server is missing, Claude Code surfaces it in the /plugin command's Errors tab.
| Language | Claude Plugin |
|---|---|
| C++ | clangd-lsp |
| Swift | swift-lsp |
| Python | pyright-lsp |
| TypeScript | typescript-lsp |
| Go | gopls-lsp |
| Rust | rust-analyzer-lsp |
Headroom helper
headroom-start <command> starts the headroom proxy if it isn't already running, then execs its arguments. On each invocation it:
- Starts the proxy if needed — checks port 8787 with
lsof; if nothing is there, launchesheadroom proxy --port 8787in the background withHEADROOM_CODE_AWARE_ENABLED=1andHEADROOM_HOST=0.0.0.0, then waits briefly for it to bind. - Sets LLM base URLs — exports
ANTHROPIC_BASE_URL=http://localhost:8787andOPENAI_BASE_URL=http://localhost:8787/v1so any agent started after it automatically routes through the proxy. - Execs the rest of the command — replaces itself with
"$@", soheadroom-start claudeis equivalent to running claude directly, just with the proxy in front.
If called with no arguments it just starts the proxy and sets the env vars, which is useful when sourced in a pre-session script.
Playwright CLI
Selecting Playwright CLI installs the playwright npm package globally and downloads its headless Chromium build. There's no MCP server and nothing is registered into .mcp.json or opencode.json — the agent gets browser automation the same way it gets any other CLI tool: by invoking playwright directly through its shell tool (playwright open <url>, playwright screenshot, playwright codegen, playwright pdf, or a one-off script requiring the playwright package). Because it's a plain global install rather than a per-repo registration, it works out of the box in every project without a prompt to approve a server, and it can't collide with a separately-registered MCP server the way the old project/user-scope split could.
The sbx sandbox ships the same CLI and browser baked into its image (see Sandbox) — no extra setup needed inside the container.
Sandbox (macOS only)
sbx claude / sbx opencode runs the agent inside an Apple Container. On each launch it:
- Fetches the image — pulls the latest sandbox image from GHCR if a newer version is available; falls back to the locally cached image when offline.
- Skips the directory trust warning — the working directory is bind-mounted into the container at
/workspace, so the agent starts there without prompting. - Skips the permissions warning —
--dangerously-skip-permissionsis passed automatically; you never see the interactive prompt. - Copies auth from the host — forwards your LLM credentials (see AI agent auth) and a GitHub token (see GitHub auth), so you don't log in again inside the container.
The sandbox image is based on node:22-bookworm-slim and comes with git, curl, sudo, jq, python3, gh, the agent CLI, and the Playwright CLI with a headless Chromium (contained in the sandbox — no exposed port; drive it via playwright ... through the agent's shell tool, no MCP registration needed) pre-installed. git is pre-configured to use the forwarded token for HTTPS operations and to rewrite SSH remotes to HTTPS, so the agent can clone and push without an SSH key. Language toolchains aren't baked into the image but are bootstrapped on demand for repos that opt in — see On-demand language toolchains.
AI agent auth
How your LLM credentials reach the agent inside the sandbox, and whether they expire:
Claude Code (priority order):
ANTHROPIC_API_KEYfrom the host environment, forwarded as-is — never expires (until revoked).- Claude subscription — on macOS the OAuth access token is read from the
Claude Code-credentialsKeychain entry and forwarded asCLAUDE_CODE_OAUTH_TOKEN. Only the access token is passed (not the refresh token), so it can't refresh inside the container. These tokens last ~8 hours; when one lapses, re-runsbxto grab a fresh one (your host app keeps the Keychain refreshed in the background).
OpenCode:
- Provider API keys are forwarded from the host environment (a curated allowlist —
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY,OPENROUTER_API_KEY, AWS / Azure / Bedrock / Vertex, …). Never expire. - OAuth providers (
/connectlogins) live in~/.config/opencodeand~/.local/share/opencode, which are bind-mounted read-write. Since that credential store is shared with the host, OpenCode refreshes an expired token in place — so it doesn't hard-expire like the Claude Keychain token.
In short: API-key auth never expires; the Claude subscription token is the one that lapses (~8h) and is renewed by re-running sbx.
GitHub auth
The sandbox runs the agent with --dangerously-skip-permissions, so any GitHub token forwarded into it can be used unattended. To keep the blast radius minimal, sbx can optionally mint a short-lived, single-repo token from a GitHub App instead of forwarding your full gh login.
# One-time setup after install step:
npx @buffbirb/unclaude container-gh-appFollow the instructions and set up a GitHub app. The process is mostly automatic.
GitHub Apps are private by default, and a private App can only ever be installed on the single account (your personal account, or one org) that owns it. So one App does not cover both your personal repos and an org's repos — container-gh-app supports multiple Apps side by side, one per account, stored under ~/.unclaude/github-apps/<owner>.json (Keychain entries are similarly keyed per App). Run npx @buffbirb/unclaude container-gh-app from inside a repo whose owning account isn't configured yet, and it'll ask which account to create the App on (personal or a specific org) and set one up for it. Run it again from a repo whose account is already configured and it skips the questions, reusing that account's App and just reopening its install page so you can authorize the new repo.
How it works on each sbx claude launch:
sbxreads the working directory'soriginremote to determineowner/repo, then looks for a configured App forowner.- If one exists, it signs a JWT with that App's private key read straight from the macOS Keychain (the key never enters the container) and mints an installation token scoped to only that repo, with
contents/pull_requests/workflows/issues= write. There is noadministrationpermission, so the token cannot delete repositories or touch any other repo. - That ~1-hour token is forwarded as
GITHUB_TOKEN/GH_TOKEN.ghandgit pushover HTTPS both use it (SSHgithub.comremotes are auto-rewritten to HTTPS inside the image).
The token lasts up to 1 hour and cannot be refreshed from inside the container (the key stays on the host). For a session longer than an hour, re-run
sbxto mint a fresh one.
If you choose not to configure a GitHub app, sbx falls back to forwarding your host's existing GitHub credentials as-is:
gh auth token, ifghis installed and logged in on the host.- Otherwise, the
GITHUB_TOKENenvironment variable, if set.
That token carries whatever scopes your host login already has — it isn't limited to the current repo — so the agent inside the sandbox can act on any repo (or org) your login can reach.
Headroom in the sandbox
If headroom is already running on the host when sbx launches, the container's LLM base URL is automatically pointed at it:
- Claude →
ANTHROPIC_BASE_URL=http://<host>:8787 - OpenCode → additionally
OPENAI_BASE_URL=http://<host>:8787/v1
The host address is derived from Apple Container's bridge network (192.168.64.0/24 by default; the host is its .1 address). headroom-start binds to 0.0.0.0 so it's reachable from the container.
Headroom is a transparent proxy — it forwards the client's real credentials upstream and injects none of its own, so the container still needs the genuine credentials described in AI agent auth. If headroom is not running at launch time, the container talks to the Anthropic API directly using those same credentials.
Image paste (Claude only)
Claude Code reads a pasted image by shelling out to xclip — but the container has neither xclip nor an X clipboard, and it's fully isolated from the macOS pasteboard. sbx bridges the gap so Ctrl+V pastes your macOS clipboard image straight into the sandboxed agent.
- In the container — an
xclipshim is mounted at/usr/local/bin/xclip. When Claude reads the clipboard, the shim fetches the current image from the host over a forwarded Unix socket instead of a real X clipboard. Text/copy calls are no-op'd. - On the host —
sbxstarts an in-process HTTP server bound to a Unix socket in a per-launch temp directory (mktemp -d, mode700) and reads the pasteboard directly viaosascript/JXA (NSPasteboard), normalizing whatever's on it (PNG, TIFF, JPEG, GIF, HEIC, PDF, or a copied image file) to PNG. - Forwarded via
container run --ssh— the socket is passed into the sandbox by repurposing SSH agent forwarding as a generic tunnel (SSH_AUTH_SOCK→/var/host-services/ssh-auth.sockin the guest); nothing SSH-specific actually runs on either end. No port is opened, nothing is network- or LAN-reachable, and no token is needed — the socket is a private per-launch file only thecontainerprocess can reach. - Nothing resident — the HTTP server and its temp directory live only for the duration of that one
sbx claudesession; both are torn down as soon as the container exits.
Self-installing on sbx claude — no separate setup step, no files written to ~/.cache/unclaude/ for this feature. Requires macOS + a container CLI build that supports run --ssh (both checked; the whole block is skipped otherwise — on an older container CLI or a non-Mac host, sbx just runs without image paste). Reading the clipboard via osascript needs no special permission prompt.
To copy an image on macOS: Cmd+C in most apps, or screenshot to clipboard with Cmd+Ctrl+Shift+4. Then Ctrl+V in the sandboxed agent.
On-demand language toolchains (makey)
Language toolchains aren't baked into the image (unlike python3 — see the Dockerfiles). Instead they're bootstrapped on demand via makey — a project's own Makefile defines just a venv target with its install commands; the shared plumbing in ~/.makey/common.mk supplies prepare/finalize/install around it, so the project Makefile stays boilerplate-free.
- Detection — before launching the container,
sbxchecks the repo root for aMakefilewith avenv:target. - In-container install — if found, the container's entrypoint becomes a small init script instead of the CLI directly: it runs
make install(which drivesprepare→venv→finalize) inside the container, so anything it downloads matches the container's Linux/arch, not the host's, then sources.makey/activatebeforeexec-ing intoclaude/opencode. ~/.makeyis mounted read-only — the projectMakefiledoesinclude $(HOME)/.makey/common.mk, which needs to resolve at container-runtime too, sosbxbind-mounts your host~/.makey/into the container at the same path.- Per-project, host-persisted cache —
make venv's output lands in.makey/inside the project directory itself (via$(MAKEY_DIR), which defaults to./.makey). Since the whole project directory is already bind-mounted into the container, no extra-vflag is needed for it —.makey/is just part of the repo on the host and survives across runs for that project.
Projects without a Makefile/venv target never trigger any of this. See makey/Makefile and ~/.makey/common.mk for the rustup/cargo example this pattern replaced.
Viewing a dev server from the sandbox
sbx publishes no ports by default — the image runs with none exposed, and the Playwright browser inside must never be reachable from outside the sandbox. A dev server (dx serve, vite, etc.) you start inside the container is a different thing, though, and it's often useful to watch its live-reloading output in a real browser on the host instead of through agent-driven Playwright screenshots.
Opt in per-launch with SBX_PORTS, a comma-separated list of container ports to publish:
SBX_PORTS=8080 sbx claude # dx serve --addr 0.0.0.0 --port 8080 inside the container
SBX_PORTS=8080,5173 sbx claude # multiple dev servers at onceEach port is published to 127.0.0.1 on the host only — never the LAN — so http://localhost:8080 on your Mac reaches the dev server running inside the container. The server itself still needs to bind 0.0.0.0 (not 127.0.0.1) inside the container, since the publish only forwards the port, not the loopback interface.
CLI arguments
Running with no arguments launches the interactive TUI. Pass a subcommand for non-interactive use:
unclaude install [--preset <name>] [--agents <list>] [--scopes <list>] [--features <list>] [--no-gitignore]
[--git-user <name>] [--git-email <email>] [--branch-prefix <prefix>]
unclaude uninstall [--preset <name>] [--agents <list>] [--scopes <list>] [--features <list>] [--no-gitignore]Each list option takes a comma-separated value. Omitting --scopes or --agents defaults to all valid values; omitting --features uses the preset's defaults.
| Option | Description |
|---|---|
| --preset | local, hybrid (default: local; hybrid must be selected manually) |
| --agents | claudeCode, openCode |
| --scopes | global, project |
| --features | stopTelemetry, stripCommitJSON, stripCommitAgentHook, renameBranchGit, stripPrWorkflow, lsp, headroom, openspec, playwrightCli |
| --no-gitignore | Skip adding generated project-scope files to .gitignore (default: adds them) |
| --git-user | Your user.name — used by stripCommitAgentHook to set local git identity. |
| --git-email | Your user.email — used by stripCommitAgentHook to set local git identity. |
| --branch-prefix | Prefix for renamed claude/* branches (e.g. username) |
Development
npm install
npm run dev # run (tsx, no build step)
npm run build # compile → dist/
npm publish # builds automatically via prepublishOnlyRequires Node ≥ 18 and tsx (npm install handles dev dependencies).
