# Claude Code prompt: set up a new MacBook Pro (2026) as a power-terminal + AI-native workstation

> Source: <https://gist.github.com/lxyea/bbff8b5685cdf4238c3ff5b374169bbc>
> Published: 2026-09-18 06:23:24+00:00

Paste everything below the line into a fresh `claude` session on the new machine.
Written for **Apple Silicon macOS (Tahoe 26.x / Sequoia 15.x)**, Claude Code, and a
human who lives in the terminal.

Why this exists: most "new Mac setup" gists are a wall of `brew install`. This is a
*prompt* — it gives an agent the constraints, the decision points, the ordering, and
the verification step, so you get a working machine instead of a half-applied script.

| File | What it is | 
|---|---|
| `macbook-pro-2026-setup-prompt.md` | **this file** — the prompt you paste into Claude Code | 
| `bootstrap.sh` | idempotent executable version of PHASES 1–9. **Run `./bootstrap.sh --dry-run` first.** | 
| `zsh_plugins.txt` | antidote bundle → `~/.zsh_plugins.txt` (Oh My Zsh plugins, no Oh My Zsh) | 
| `starship.toml` | lean two-line prompt → `~/.config/starship.toml` | 
| `p10k.zsh` | *optional alternative prompt* — Powerlevel10k config →`~/.p10k.zsh` | 
| `claude-wrapper.zsh` | `claude()` shell wrapper — switch between API key / OAuth token, permissive by default | 

Two ways to use this:

- **Agent-driven** (recommended) — paste this file into`claude` . It asks the DECISIONS, adapts, and explains.
- **Script-driven** —`./bootstrap.sh --dry-run` to preview, then run it. No sudo, resumable via`--from <phase>` .

They compose: run `bootstrap.sh` for the mechanical 80%, then hand the agent this file for the judgement calls.

You are setting up a brand-new MacBook Pro (Apple Silicon) for a **senior engineer who
is a terminal power user and works with AI agents daily**. You have shell access via
your Bash tool. Treat this machine as a long-lived primary workstation.

Your job is not to run a script. Your job is to **build a reproducible, agent-friendly
development environment and leave behind the artifacts that let it be rebuilt in 20
minutes**.

1. **Idempotent.** Every step must be safe to re-run. Guard with`command -v x >/dev/null ||` ,`brew list x || brew install x` ,`grep -q ... file || echo ... >> file` . Never blindly append.
2. **Never clobber.** Before overwriting any dotfile,`cp` it to`~/.setup-backup/<timestamp>/` .
3. **Checkpoint.** Maintain`~/.setup-state.json` —`{phase, completed: [], skipped: [], notes: []}` .
Update it after every phase. If the session dies, a fresh agent must be able to read it and resume.
4. **Ask before** : anything needing`sudo` , changing the login shell, installing a kernel
extension or VPN, touching System Settings that affects security, or spending money
(paid apps, cloud resources).
5. **Batch the questions.** Ask all open decisions in ONE round up front (see DECISIONS),
not one at a time across an hour.
6. **Prefer `brew` formulae/casks** over curl-pipe-bash. When upstream only ships an
installer script, read it first and say what it does.
7. **Show, don't claim.** End each phase with the actual command output proving it worked.
"Installed successfully" without output is not acceptable.
8. **No secrets in files.** No API keys in`.zshrc` , ever. See PHASE 8.

| # | Question | Default if user says "you pick" | 
|---|---|---|
| 1 | Shell: **zsh** (macOS default, universal) or**fish** (best interactive UX, non-POSIX)? | zsh — agents and scripts assume POSIX | 
| 1b | Plugin manager: **antidote** (static file + deferred loading; loads Oh My Zsh plugins without Oh My Zsh),**Oh My Zsh** (simpler, slower), or plain? | antidote | 
| 1c | Prompt: **starship** (cross-shell, TOML) or**Powerlevel10k** (zsh-only, instant prompt)? | starship | 
| 2 | Terminal: **Ghostty** (fast, native, 2026 default),**WezTerm** (Lua-scriptable),**iTerm2** (mature),**Warp** (AI-native, telemetry)? | Ghostty | 
| 3 | Multiplexer: **tmux** ,**Zellij** , or terminal-native splits only? | tmux (agents/ssh assume it) | 
| 4 | Dotfile strategy: **chezmoi** ,**GNU stow** , or**bare git repo** ? | chezmoi (templating + secrets integration) | 
| 5 | Containers: **OrbStack** (fast, low battery),**Docker Desktop** ,**colima** , or none? | OrbStack | 
| 6 | Password/secret manager already in use? (1Password, Bitwarden, keychain-only) | ask — do not guess | 
| 6b | Editor/IDE: **VS Code** (or a fork: Cursor/Windsurf/Kiro),**JetBrains** ,**Zed** ,**Neovim** , or terminal-only? | ask — this is the most personal choice here | 
| 7 | Which languages actually matter? (node/py/go/rust/ruby/java/…) | node + python + go, add on demand | 
| 8 | Personal or work machine? (changes SSH/GPG signing, MDM constraints, telemetry tolerance) | ask | 

Also run recon before asking, so your questions are informed:

```
sw_vers; uname -m; echo "---"
command -v brew git zsh fish ghostty antidote starship fnm uv go claude 2>/dev/null
echo "---"; ls -a ~ | head -50
echo "---"; csrutil status 2>/dev/null; fdesetup status 2>/dev/null
echo "---"; profiles status -type enrollment 2>/dev/null   # is this MDM-managed?
```

- Xcode Command Line Tools: `xcode-select -p || xcode-select --install` (this is
interactive/GUI — tell the user to click through, then poll until`xcode-select -p` succeeds).
- **Homebrew** at`/opt/homebrew` (arm64). Add the shellenv line to`~/.zprofile` , not`~/.zshrc` .
- Create a `Brewfile`**from the start** in the dotfiles repo. Every install goes through it:`brew bundle --file=~/dotfiles/Brewfile` . This is the reproducibility artifact.
- `mkdir -p ~/.local/bin ~/Developer ~/.config` and ensure`~/.local/bin` is first on`PATH` .
- Rosetta only if the user names an actual x86-only dependency. Do not install it "just in case".

- Terminal emulator per DECISION 2. Config in `~/.config/ghostty/config` (XDG, git-trackable).
- **Nerd Font** — required or every glyph in the prompt renders as tofu:`brew install --cask font-jetbrains-mono-nerd-font` (or Maple Mono NF / Berkeley Mono if owned).
Set it in the terminal config, and verify by echoing a glyph.
- Prompt: **starship** —`brew install starship` , config at`~/.config/starship.toml` . See PHASE 2b.
Keep it fast — set`command_timeout` and disable modules that stat the filesystem on
every prompt in big repos. A slow prompt is a tax you pay thousands of times a day.
- Multiplexer per DECISION 3, config in `~/.config/` .
- Plugin manager + prompt: **antidote + starship** — see PHASE 2b, big enough for its own phase.
- **Benchmark startup and report the number** :`hyperfine --warmup 3 'zsh -i -c exit'` .
Target < 150ms. If it's over, bisect with`zsh -xv` and fix it now, not later.

**antidote** is the plugin manager; **starship** is the prompt. Neither is Oh My Zsh — but
you keep OMZ's plugin library, which is the part actually worth having.

Oh My Zsh is two things bolted together: a **plugin library** (excellent, ~300 plugins,
~150 git aliases) and a **framework loader** (sources everything serially on every shell
start, 150–400ms). antidote lets you keep the first and throw away the second:

```
ohmyzsh/ohmyzsh path:plugins/git      # ← the OMZ git plugin, no OMZ install
```

It works by generating a **static, pre-compiled load script**. Startup sources one flat
file instead of walking 20 git repos. It also supports `kind:defer` (via `zsh-defer`) to
load plugins *after* the prompt paints, and `kind:fpath` for completions that should go on
`fpath` without being sourced at all.

```
brew install antidote
```

Bundle file → `~/.zsh_plugins.txt` (**`zsh_plugins.txt` in this gist** is a working one).

```
# --- antidote: regenerate the static file only when the bundle changed -------
zstyle ':antidote:bundle' use-friendly-names 'yes'
antidote_dir="$(brew --prefix)/opt/antidote/share/antidote"
[[ -e $antidote_dir/antidote.zsh ]] && source $antidote_dir/antidote.zsh

zsh_plugins=${ZDOTDIR:-$HOME}/.zsh_plugins
if [[ ! ${zsh_plugins}.zsh -nt ${zsh_plugins}.txt ]]; then
  antidote bundle <${zsh_plugins}.txt >|${zsh_plugins}.zsh
fi
source ${zsh_plugins}.zsh

autoload -Uz compinit && compinit -C     # -C skips the security check: much faster

# --- tool inits: MUST come after antidote load -------------------------------
# These rebind keys and hook precmd. Load them before the plugins and the
# plugins will overwrite the bindings — silently, with no error anywhere.
eval "$(starship init zsh)"
eval "$(fnm env --use-on-cd --shell zsh --version-file-strategy=recursive --resolve-engines)"
eval "$(zoxide init zsh)"
eval "$(direnv hook zsh)"
eval "$(atuin init zsh --disable-up-arrow)"   # keeps ↑ for history-substring-search
source <(fzf --zsh)

ZSH_AUTOSUGGEST_STRATEGY=(history completion)
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20
```

1. **`zsh-syntax-highlighting` must be last** in the bundle. It wraps every widget defined
before it; anything loaded afterwards simply isn't highlighted.
2. `zsh-history-substring-search` comes**after** syntax-highlighting.
3. `ohmyzsh/ohmyzsh path:lib/*` comes**before** any`path:plugins/*` that depends on it.
4. Everything that hooks `precmd` or rebinds keys — starship, fnm, zoxide, atuin, fzf,
direnv — goes in`.zshrc`**after**` source ${zsh_plugins}.zsh` .

```
brew install starship          # config → ~/.config/starship.toml
```

**`starship.toml` in this gist** is a lean two-line prompt. The setting that matters most:

```
command_timeout = 200    # default is 500ms — long enough to feel broken
```

Any module slower than that is dropped for that render rather than stalling your shell.
The config also turns **prod Kubernetes contexts red**, on the same reasoning that root's
prompt is red: make the state you can regret visually unmistakable.

Prefer Powerlevel10k? `p10k.zsh` in this gist is a full hand-written config with instant
prompt and transient prompt. **Pick exactly one** — running both wastes startup and only
one can own the prompt.

antidote's whole point is that you stop paying for plugins you aren't using:

- **Measure** :`hyperfine --warmup 3 'zsh -i -c exit'` .**Target < 100ms** — antidote should
beat an OMZ setup comfortably. If it doesn't, something in`.zshrc` is doing real work.
- **Profile** :`zmodload zsh/zprof` as line 1 of`.zshrc` ,`zprof` as the last line.
- **Defer** anything non-essential: append`kind:defer` in the bundle file.
- `compinit -C` skips the daily security audit of`fpath` . Worth 20–70ms. Drop the`-C` if
you install completions from untrusted sources.

| Conflict | Resolution | 
|---|---|
| OMZ `nvm` /`pyenv` plugins vs**fnm/uv** | don't bundle them — see PHASE 5 | 
| OMZ theme vs **starship** | no OMZ theme exists here; starship owns the prompt | 
| **atuin** vs`history-substring-search` | `atuin init zsh --disable-up-arrow` | 
| OMZ `fzf` plugin vs`fzf --zsh` | pick one, or you double-bind `Ctrl-R` | 
| OMZ `ssh-agent` vs**1Password agent** | don't bundle it, use `IdentityAgent` (PHASE 8) | 
| `aws` /`kubectl` /`terraform` OMZ plugins | shell out at load; add `kind:defer` or skip | 

You still get OMZ's ~150 git aliases (`gst`, `gco`, `gp`) — and they are still
**interactive-only**. Your Bash tool, CI, and any `bash -c` will not see them. PHASE 4's
rule is unchanged: anything an agent must call has to be a real executable in
`~/.local/bin`.

Put this in `~/.claude/CLAUDE.md` so the model doesn't guess wrong:

Shell is zsh + antidote loading Oh My Zsh plugins. Aliases (`gst`, `gco`, `k`, …) exist
only in interactive shells — always use full commands (`git status`) in tool calls.

Install via Brewfile. For each, wire up the shell integration *and* verify it:

| Replaces | Tool | Why it earns its place | 
|---|---|---|
| `cat` | **bat** | syntax highlight + git gutter; `--style=plain` for piping | 
| `ls` | **eza** | git status column, tree mode, sane colors | 
| `grep` | **ripgrep** | 10–100× faster, respects `.gitignore` by default | 
| `find` | **fd** | sane syntax, parallel, gitignore-aware | 
| `cd` | **zoxide** | frecency jumps: `z proj` | 
| `du` | **dust** /`df` →**duf** | actually readable | 
| `ps` /`top` | **procs** ,**btop** | modern output | 
| `sed` /`awk` | **sd** ,**choose** | for the 90% case; keep the originals | 
| `diff` | **delta** | wire into `git.pager` , not just standalone | 
| history | **atuin** | SQLite history, sync, `Ctrl-R` that actually works | 
| — | **fzf** | the connective tissue; install `fzf --zsh` keybindings | 
| — | **jq** +**yq** +**gron** | JSON/YAML surgery; `gron` makes JSON greppable | 
| — | **hyperfine** ,**tokei** ,**watchexec** | benchmark, count, re-run on change | 
| — | **gh** ,**lazygit** ,**git-absorb** | GitHub CLI, TUI git, auto-fixup commits | 
| — | **httpie** or**xh** ,**curlie** | HTTP without flag archaeology | 
| — | **tealdeer** (`tldr` ) | examples instead of man pages | 
| — | **direnv** (or mise's env) | per-project env vars | 

If installing only two: **fzf + zoxide**. They remove the most daily friction.

This is what turns a pretty terminal into one an AI agent can actually drive.

1. 
**Aliases don't exist for tools.** A non-interactive shell (`bash -c "..."` , CI, an
agent's Bash tool) does not source your interactive aliases. So:Anything you want *both* you and an agent to call must be a**real executable in
`~/.local/bin`** , not an alias or an interactive-only function.Write them as small `#!/usr/bin/env bash` scripts with`set -euo pipefail` , and keep
aliases only for pure typing shortcuts.
2. 
**Guard the interactive-only parts** so non-interactive shells stay fast and quiet:

```
[[ -o interactive ]] || return    # top of the interactive-only section
```

 Split config: `~/.zshenv` (env, always),`~/.zprofile` (login/PATH),`~/.zshrc` (interactive).
3. 
**Kill every pager.** A pager in a non-TTY is a hung tool call:

```
export PAGER=cat GH_PAGER=cat
export LESS='-FRX'
git config --global core.pager 'delta --paging=never'
```

 Also `export HOMEBREW_NO_ENV_HINTS=1` and set`GIT_TERMINAL_PROMPT=0` so git fails
fast instead of blocking on a credential prompt.
4. 
**Non-interactive by default.**`alias` -free wrappers should never prompt. Prefer`brew install -q` ,`npm ci --no-fund --no-audit` ,`gh --json` . Anything that might
open`$EDITOR` needs`--no-edit` or`EDITOR=true` .
5. 
**Structured output where it exists.** Teach the setup (and CLAUDE.md) to reach for`gh ... --json` ,`docker ... --format json` ,`jq` ,`sd` ,`rg --json` . Agents parse
JSON reliably and scrape human tables badly.
6. 
**Safety rails that survive an agent typo:**
  - `brew install trash` ; make`rm` in*interactive* zsh a wrapper around`trash` , and
leave real`rm` for scripts.
  - `git config --global rerere.enabled true` ,`push.default simple` ,`pull.rebase true` ,`init.defaultBranch main` ,`fetch.prune true` .
  - Set up `git commit --no-gpg-sign` fallback if signing would prompt for a passphrase
in a non-TTY (or use an SSH-agent-backed signing key that doesn't prompt).
7. 
**`.inputrc` / zsh keybindings** for real line editing:`bindkey -e` , word-wise`Alt-←/→` ,`Ctrl-x Ctrl-e` to open the current command in`$EDITOR` .

The 2026 shift is away from a single polyglot manager toward **fast, native, per-language
tools** — plus the language's own built-in mechanism where one now exists. Use `mise` only
for the long tail.

| Language | Tool | Why this one | 
|---|---|---|
| **Node** | `fnm` | Rust; ~40× faster than nvm; auto-switches on `cd` | 
| **Python** | `uv` | replaces pip + virtualenv + poetry + **pyenv** , all in one | 
| **Rust** | `rustup` | canonical, no alternative worth considering | 
| **Go** | *(built in)* | `GOTOOLCHAIN` +`go.mod` — no third-party manager needed | 
| everything else | `mise` | Java, Ruby, Erlang, Deno, Bun, CLI tools | 

```
brew install fnm
fnm install --lts && fnm default lts-latest
eval "$(fnm env --use-on-cd --shell zsh --version-file-strategy=recursive --resolve-engines)"
```

- `--use-on-cd` switches version automatically when you enter a project.
- `--version-file-strategy=recursive` looks in parent dirs, so it works from a subdirectory
of a monorepo, not just the root.
- `--resolve-engines` honours`"engines": {"node": "..."}` in`package.json` .
- Reads both `.nvmrc` and`.node-version` , so it's a drop-in for an nvm team.

**Corepack gotcha (new in 2026):** Corepack was **removed from Node 25+** in January 2026.
If you rely on `packageManager` in `package.json` to pin pnpm/yarn, install it yourself:
`npm install -g corepack`. `fnm --corepack-enabled` only helps on versions that still
bundle it. Silent breakage on a fresh machine otherwise.

`uv` manages **interpreters as well as packages** — pyenv is redundant.

```
brew install uv
uv python install 3.13 3.12     # interpreters → ~/.local/share/uv/python/
uv python pin 3.13              # writes .python-version for this project
```

Daily use: `uv venv`, `uv sync`, `uv run <script>`, `uv add <pkg>`, `uv tool install <cli>`,
`uvx <cli>` for one-off runs. **Never `pip install --user`**, and never install into system
Python — uv's interpreters are isolated from it by design.

```
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
rustup component add clippy rustfmt rust-analyzer
```

Use `rustup`, **not** `brew install rust` — brew's is a fixed version with no toolchain
switching, and it collides with rustup if both are on `PATH`. Per-project pinning is
`rust-toolchain.toml`, which rustup honours automatically.

Since Go 1.21 the toolchain manages itself. There is no reason to run `goenv` or `g`:

```
brew install go
export GOTOOLCHAIN=auto        # in ~/.zshenv
```

A `go.mod` with a `toolchain` directive causes the `go` command to **download and use that
exact toolchain automatically**. Everyone building the module gets the same compiler with
no version-manager coordination — the "works on my machine" class of Go bug is gone.

```
go 1.24            // minimum version required to build
toolchain go1.25.1 // preferred toolchain, fetched on demand
```

`GOTOOLCHAIN=auto` opts into that. `GOTOOLCHAIN=local` disables it and pins you to whatever
`brew` installed — which is what you want in a hermetic CI image, not on a laptop.

```
brew install mise
mise use -g java@temurin-21 ruby@3.4 deno@latest
eval "$(mise activate zsh)"
```

Keep mise **out of** Node/Python/Rust/Go. Two managers racing for the same `PATH` entry is a
genuinely miserable class of bug — you get a version that depends on the order your `.zshrc`
happened to `eval`. One language, one owner.

```
git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global push.default simple
git config --global fetch.prune true
git config --global rerere.enabled true          # remember conflict resolutions
git config --global core.pager 'delta --paging=never'
```

Whatever the user names. If Neovim, start from LazyVim or kickstart rather than hand-rolling
a config, and set `EDITOR`/` VISUAL` consistently.

The one decision in this document you should **not** default. Ask, then set it up properly.

For an AI-native workstation the axis that matters isn't syntax highlighting or plugin
count — it's **how Claude Code connects to the editor**. Claude Code ships an IDE protocol
(a local WebSocket speaking MCP) that gives the agent your selection, open files, and
diagnostics, and renders diffs inline instead of in the terminal. Support tiers differ:

| Editor | Claude Code integration | Trade | 
|---|---|---|
| **VS Code** + forks (Cursor, Windsurf, Kiro) | **official extension** | biggest ecosystem; Electron, so heaviest | 
| **JetBrains** (IntelliJ, PyCharm, WebStorm, GoLand, …) | **official plugin** | best refactoring/debugger in class; slow start, RAM-hungry | 
| **Zed** | evolving — native support in progress; CLI works today | fastest editor here, Rust, great collab | 
| **Neovim** | community plugin implementing the same WebSocket protocol | lowest latency, fully terminal-native; you own the config | 
| **anything + a terminal** | the CLI itself, always | zero integration work, no inline diffs | 

**Nobody is locked out.** The CLI works in any terminal, including the built-in terminal of
an editor with no extension at all. The extension buys inline diffs and automatic context —
not access.

1. **`claude --ide` auto-connects** on startup when exactly one valid IDE is available. Live
connections appear as lock files in`~/.claude/ide/` . If the agent doesn't seem to see
your open file, check that directory before debugging anything else.
2. **The extension and the CLI share conversation history.** Start in the editor, continue in
the terminal with`claude --resume` , or the reverse. You are not picking a silo — so
optimise the choice for how you like to*edit* , not for where the agent lives.

- Already in JetBrains → **stay** . The plugin is first-class; nothing below beats its
debugger or refactoring, and switching editors to chase an agent is a bad trade.
- Wants maximum AI integration → **Cursor** (VS Code fork, so the Claude Code extension
works, plus its own agent). Accept the Electron footprint.
- Wants speed and a native feel → **Zed** . Genuinely faster than VS Code; run Claude Code in
its terminal until native support lands.
- Terminal purist → **Neovim** with LazyVim or kickstart. Do**not** hand-roll from an empty`init.lua` on a machine you need working today.
- Undecided → **VS Code** . Largest ecosystem, official extension, easiest to leave.

This is where PHASE 4 and this phase intersect, and it's easy to get subtly wrong.

```
# ~/.zshenv — applies to every shell
export EDITOR="code --wait"     # or: "cursor --wait", "zed --wait", "nvim"
export VISUAL="$EDITOR"
```

`--wait` is **required** for GUI editors: without it the command returns instantly, git sees
an empty buffer and aborts your commit. But the same flag is a **hang** in a non-interactive
shell — the agent's tool call blocks forever on a window nobody will close. So:

```
# non-interactive shells must never open an editor at all
[[ -o interactive ]] || export GIT_EDITOR=true
```

Pair that with `GIT_TERMINAL_PROMPT=0` from PHASE 4 and git fails fast in tool calls instead
of blocking. A terminal editor (`nvim`, `vim`) doesn't need `--wait` and doesn't have this
problem — one more quiet point in favour of the terminal-native option.

- The CLI launcher (`code` ,`cursor` ,`zed` ) so`$EDITOR` resolves — VS Code needs*Shell Command: Install 'code' command in PATH* from the command palette.
- Your language servers via the editor, **not** duplicated with the ones in PHASE 6.
- `ApplePressAndHoldEnabled -bool false` from PHASE 7 — without it, holding a key in Vim
mode shows the accent-picker instead of repeating. Already in this document; it matters
most here.

- 
`claude` CLI installed and authenticated (`claude doctor` must pass — show its output).
- 
**`~/.claude/CLAUDE.md`** — global operating instructions. Keep it short and*specific* ;
it's loaded into every session, so every line costs context. Good content:
  - which package manager/toolchain this machine uses (`uv` , not`pip` ;`pnpm` , not`npm` )
  - "prefer `rg` /`fd` over`grep` /`find` ", "use`gh --json` for GitHub"
  - project layout conventions (`~/Developer/<org>/<repo>` )
  - the user's commit-message and PR conventions
  - what NOT to do on this machine (e.g. never push to `main` , never`brew upgrade` unprompted)
- which package manager/toolchain this machine uses (
- 
**`~/.claude/settings.json`** — the permission allowlist is the single highest-leverage
thing for a terminal power user. Allowlist the read-only commands you run constantly so
you stop approving them:`rg` ,`fd` ,`eza` ,`bat` ,`git status/diff/log` ,`gh pr view` ,`jq` ,`ls` ,`cat` ,`tokei` . Deny-list the destructive ones outright.
- 
**Hooks** for the policy layer that shouldn't depend on the model remembering:
format-on-edit (`PostToolUse` ), block`rm -rf /` and`git push --force` on protected
branches (`PreToolUse` ), desktop notification on`Stop` .
- 
**MCP servers** — add narrowly, they cost context. The 80% stack:**Context7** (live
library docs),**GitHub** ,**Playwright** (browser). Add Postgres/Sentry/Linear only if
the user actually uses them daily. Prefer project-scoped`.mcp.json` over global.
- 
**Skills and subagents** in`~/.claude/skills/` for repeatable multi-step workflows.
- 
Optional second opinion: keep one alternate agent CLI installed for cross-checking, and `ollama` if the user wants local models for offline/private work.
- 
**Status line** : show model, cwd, git branch, and context remaining.
- 
**`claude()` shell wrapper** — see`claude-wrapper.zsh` in this gist. Paste into`~/.zshrc` .
It solves three things the bare CLI doesn't:
  1. **Two credentials, one switch.** Holds both an`ANTHROPIC_API_KEY` and a`CLAUDE_CODE_OAUTH_TOKEN` ;`claude-api` /`claude-oauth` /`claude-use <mode>` pick between them. API key = pay-per-token console billing; OAuth token = your
Claude subscription. Which one you're on is a billing decision, so make it explicit.
  2. **Mutual exclusion.** If both variables are visible to the CLI its choice is
ambiguous — you get confusing 401s or bill the wrong account. The wrapper runs in a
subshell that`unset` s the one it isn't using, so exactly one ever reaches`claude` .Do **not** implement this as`env -u OTHER_VAR VAR=x command claude` .`env` execs an
external program, so it cannot invoke zsh's`command` builtin — it either fails with
"command not found" or silently runs some unrelated`/usr/local/bin/command` on your
PATH. A subshell keeps the builtin and confines the`unset` .
  3. **Default flags, safely.** Injects`--dangerously-skip-permissions` , but suppresses it
for subcommands that reject it (`mcp` ,`doctor` ,`setup-token` ,`config` , …), as root,
when you already passed a permission flag, and whenever`CLAUDE_SAFE=1` is set.
 **This means every `claude` run can edit files and execute commands without asking.** That is the point of the flag, but it is a real trade: use`claude-safe` in any directory
you don't fully trust, and keep the`deny` list in`settings.json` populated regardless.

Apply with `defaults write`, then `killall Finder Dock SystemUIServer`. Confirm the list
with the user first — these change muscle memory:

```
defaults write NSGlobalDomain KeyRepeat -int 2
defaults write NSGlobalDomain InitialKeyRepeat -int 15
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false   # key repeat in editors
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
defaults write com.apple.finder AppleShowAllFiles -bool true
defaults write com.apple.finder ShowPathbar -bool true
defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv"
defaults write com.apple.dock autohide-time-modifier -float 0.15
defaults write com.apple.dock show-recents -bool false
defaults write com.apple.screencapture location -string "$HOME/Screenshots"
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
```

Apps worth the slot: **Raycast** (replaces Spotlight; scriptable, has a Claude/AI layer),
**AeroSpace** or **Rectangle** (tiling/window management), **Karabiner-Elements**
(Caps Lock → Ctrl/Esc — the single best keyboard change for a terminal user),
**Hammerspoon** if they like Lua automation.

- **SSH key** :`ssh-keygen -t ed25519 -C "<email>"` , add to keychain via`~/.ssh/config` (`UseKeychain yes` ,`AddKeysToAgent yes` ,`IdentityFile ~/.ssh/id_ed25519` ).
- Prefer a **1Password (or Secretive) SSH agent** so keys live in the Secure Enclave and
never touch disk unencrypted. Set`IdentityAgent` accordingly.
- **Sign commits with the SSH key** , not GPG — far less friction:`git config --global gpg.format ssh` +`user.signingkey` +`commit.gpgsign true` .
- Secrets in the shell: never literal values in dotfiles. Use `op read op://vault/item/field` at call time, or macOS keychain (`security find-generic-password -w -s <svc>` ).
A dotfiles repo containing a real key is a breach waiting for a`git push --public` .
- Confirm **FileVault is on** , firewall enabled, and Touch ID works for`sudo` (`/etc/pam.d/sudo_local` — this needs sudo, so ask first).
- Add a pre-commit secret scanner (`gitleaks` or`trufflehog` ) as a global git hook.

1. Write `~/.local/bin/doctor` — one script that checks every tool resolves, the shell
starts under budget,`git` /`gh` /`ssh -T git@github.com` auth works,`claude doctor` passes, and`fnm` /`uv` /`go` /`rustup` each resolve a version.**Run it and paste the full
output.**
2. Commit the dotfiles repo (chezmoi/stow/bare) and push it. Include `Brewfile` ,`~/.config/*` ,`~/.claude/` , and a`bootstrap.sh` that replays PHASES 1–8 on a fresh Mac.**`bootstrap.sh` in this gist is a working starting point** — bash 3.2 compatible (macOS
ships bash 3.2, so no associative arrays /`mapfile` /`${var,,}` ),`--dry-run` capable,
never calls sudo, and prints a "Needs a human" list for the GUI-only steps.
3. Write a short `SETUP.md` : what was installed,**what was deliberately skipped and why** ,
what still needs the user's hands (GUI logins, App Store, MDM-blocked items).
4. Final report format:
  - ✅ Done (with proof)
  - ⏭️ Skipped + reason
  - ⚠️ Needs a human
  - 📊 `zsh` startup ms,`brew list | wc -l` , disk used

- Installing 80 brew packages "because the gist said so". Every tool is context the user must hold. If they won't use it this month, skip it and note it in SETUP.md.
- Pasting a 30-line bundle file in by reflex. Every non-deferred entry is sourced on every
shell start. Bundle what you use, profile with `zprof` ,`kind:defer` or drop the rest.
- Running two version managers for one language (mise *and* fnm for Node). Whichever`eval` s last wins the`PATH` , so your version silently depends on`.zshrc` line order.
- Curl-pipe-bash from a URL you haven't read.
- Symlinking a dotfiles repo over existing config without backing up.
- Claiming a phase is done without output. Run the command. Show the result.
- Editing `/etc/*` or running`sudo` without asking.
- Putting an API key in `.zshrc` and then committing`.zshrc` to a public dotfiles repo.
If you keep credentials in shell config (see`claude-wrapper.zsh` ),`chmod 600` it and
split the secrets into a gitignored`~/.claude-auth.zsh` that`.zshrc` sources.

If you're a fresh agent picking this up: `cat ~/.setup-state.json`, then continue from
`phase`. Re-running completed phases must be harmless — that's what RULE 1 is for.

- Ghostty + Starship modern terminal setup — [https://alexanderkey.com/blog/how-to-set-up-a-modern-terminal-with-ghostty-zsh-and-starship/](https://alexanderkey.com/blog/how-to-set-up-a-modern-terminal-with-ghostty-zsh-and-starship/)
- The Ultimate Terminal Stack in 2026 — [https://medium.com/vmacwrites/the-ultimate-terminal-stack-in-2026-a-cross-platform-guide-for-macos-linux-and-windows-c0d1f93cd9cc](https://medium.com/vmacwrites/the-ultimate-terminal-stack-in-2026-a-cross-platform-guide-for-macos-linux-and-windows-c0d1f93cd9cc)
- Modern Rust CLI tools (eza, bat, fd, zoxide…) — [https://32blog.com/en/cli/cli-modern-rust-tools](https://32blog.com/en/cli/cli-modern-rust-tools)
- antidote — [https://antidote.sh/](https://antidote.sh/) ·  bundle annotations:[https://github.com/mattmc3/antidote](https://github.com/mattmc3/antidote)
- Oh My Zsh plugin directory (loadable via antidote) — [https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins](https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins)
- starship config reference — [https://starship.rs/config/](https://starship.rs/config/)
- fnm — [https://github.com/Schniz/fnm](https://github.com/Schniz/fnm)
- uv: managing Python versions — [https://docs.astral.sh/uv/concepts/python-versions/](https://docs.astral.sh/uv/concepts/python-versions/)
- Go toolchains (GOTOOLCHAIN) — [https://go.dev/doc/toolchain](https://go.dev/doc/toolchain)
- Claude Code IDE integrations — [https://developertoolkit.ai/en/claude-code/quick-start/ide-integration/](https://developertoolkit.ai/en/claude-code/quick-start/ide-integration/)
- Zed vs every editor — [https://zed.dev/compare](https://zed.dev/compare)
- Powerlevel10k (alternative prompt) — [https://github.com/romkatv/powerlevel10k](https://github.com/romkatv/powerlevel10k)
- mise (for the long tail) — [https://mise.jdx.dev/](https://mise.jdx.dev/)
- Claude Code + MCP guide 2026 — [https://www.jitendrazaa.com/blog/ai/claude-code-complete-guide-2026-from-basics-to-advanced-mcp-2/](https://www.jitendrazaa.com/blog/ai/claude-code-complete-guide-2026-from-basics-to-advanced-mcp-2/)
- Best MCP servers for Claude Code 2026 — [https://evomap.ai/blog/best-mcp-servers-for-claude-code-2026](https://evomap.ai/blog/best-mcp-servers-for-claude-code-2026)
