My opinionated macOS dev setup. Three goals: AI-assisted by default (Claude Code, hermes-agent, codex CLI side-by-side), remote access via Tailscale (private mesh, no public ports) with both OpenSSH and Tailscale SSH enabled side-by-side, and reproducible (idempotent scripts, --dry-run, CI-checked with shellcheck + bash -n + Brewfile validation + bats).
Run bootstrap.sh (one curl line on a fresh Mac) or clone + ./install.sh
manually. Either way it asks for confirmation before doing anything, then
prompts for git name/email when it gets to that step.
curl -fsSL https://raw.githubusercontent.com/voidmatcha/dotfiles/main/bootstrap.sh | bashbootstrap.sh installs Xcode Command Line Tools if git is missing, clones
this repo (with submodules) into ~/dotfiles, and hands off to ./install.sh.
The --recurse-submodules step pulls the optional company/ overlay if you
have access to the internal git host; without access the submodule clone
fails silently and install.sh proceeds normally.
Pass-through args work too:
curl -fsSL https://raw.githubusercontent.com/voidmatcha/dotfiles/main/bootstrap.sh | bash -s -- --dry-runcd ~/dotfiles ./install.sh
This setup uses code-server as the lightweight web UI for git worktrees.
Opening worktrees in browser VS Code (code-server) is handled by the
local-skills/worktree-open skill — agents build the ?folder=/?workspace=
URL directly (no installed helper binaries), preferring the Tailscale Serve
endpoint (https://<tailnet-host-or-ip>:8443) before local fallback.
scripts/code-server.sh installs the worktree-oriented code-server extensions
jackiotyu.git-worktree-manager, eamodio.gitlens, and mhutchie.git-graph.
Generated HTML reports, static build outputs, exported dashboards, and other
local browser artifacts are served by the repo-local local-preview-server
skill. It starts a durable python3 -m http.server process, prefers tmux when
available, binds to 0.0.0.0, verifies the exact reported URL, and reports
localhost plus LAN/Tailscale URLs without creating public internet exposure by
default.
Typical agent-facing commands:
plugins/local-skills/skills/local-preview-server/scripts/local-preview-server.sh start --path ./report.html --port 8377 plugins/local-skills/skills/local-preview-server/scripts/local-preview-server.sh start --path ./dist plugins/local-skills/skills/local-preview-server/scripts/local-preview-server.sh status --port 8377 plugins/local-skills/skills/local-preview-server/scripts/local-preview-server.sh stop --port 8377
Homebrew + apps — packages from Brewfile, including the usual CLI tools (ripgrep/fd/bat/eza/fzf/zoxide/atuin/direnv/jq/delta/tmux) plus bats-core for shell-script tests, uv (Python tool installer used by serena), gettext (envsubst, used by company overlay), git-filter-repo (surgical history rewrites), and docker CLI (no Docker Desktop — pair with Rancher Desktop on hosts with licensing restrictions).
macOS settings — dock autohide, Finder tweaks, keyboard repeat rates, CapsLock → Escape, three-finger drag, screenshots to ~/Screenshots.
Dev tools:
- nvm + Node.js LTS, corepack (pnpm + yarn)
- pyenv + latest Python 3
- SDKMAN + Java LTS + Maven
- Playwright CLI (for coding agents)
- whisper-cpp model (~1.5GB, large-v3-turbo)
- ccusage, agentsview, rtk, agent-browser
- defuddle — free local web extraction tool for LLM-friendly Markdown
- serena — MCP server for semantic code navigation + editing (LSP-backed). Installed via
uv tool install, registered inconfigs/mcp.jsonwith--context claude-code --project-from-cwd..zshrcwrapsclaudeto inject serena's system-prompt-override (counters Opus's bias toward built-in tools) - codegraph — read-only MCP server for exploring large codebases via a pre-indexed knowledge graph (tree-sitter + SQLite, watcher auto-syncs on save). Installed via
npm install -g @colbymchenry/codegraph. Complementary to serena: codegraph wins on "how does X reach Y" / architecture / framework-route mapping / iOS+RN cross-language bridges; serena wins on symbol-level edits and refactors. Runcodegraph init -ionce per project. Personal user-scope only — not in the NAVER MCP catalog, so excluded from company project-scope (~/work/.mcp.json). - headroom — default context compression wrapper installed via
scripts/headroom.sh(uv tool install -p 3.13 'headroom-ai[proxy,mcp,code]'). Whenheadroom+ wrapper symlinks exist,.zshrcroutesclaude,codex, andomxthrough Headroom automatically; explicitclaudeh,codexh, andomxhremain available. Bypass withHEADROOM_DEFAULT=0, per-toolHEADROOM_CLAUDE=0/HEADROOM_CODEX=0/HEADROOM_OMX=0, orcommand claude|codex|omx.codexh/omxhshare a lock/refcount around Codex's default~/.codex/config.tomlHeadroom provider so the config is only unwrapped after the last wrapped Codex/OMX session exits; the tracked Codex template intentionally leavesmodel_providerunset because Headroom injects a temporary provider at runtime. DefaultHEADROOM_MODE=cachepreserves provider prefix-cache behavior; setHEADROOM_MODE=tokenfor maximum compression. Owl is optional; context-check treats missingowl-rsas advisory, not a blocker. - Agent status labels —
scripts/statusline.shinstalls~/.local/bin/agent-session-labeland~/.local/bin/claude-statusline. Claude Code'sstatusLinecommand prefixes the existing Owl/HUD output with a cmux workspace/surface label, tmux session/window/pane label, agent session ID, or project fallback. Codex/OMX uses built-in status_line items withoutthread-titleso UUID fallback session IDs do not leak into the HUD; terminal titles stay project/branch based. - graphify — Claude/Codex skill (
/graphify) that turns any folder into a queryable knowledge graph. For pure code, codegraph is more specialized; reach for graphify on mixed content (PDFs, docs, papers). Installed viapython3 -m pip install --user graphifyy && graphify install --platform claude && graphify install --platform codex - agentsview — local-first Claude/Codex session browser and usage analytics dashboard. Installed via Homebrew cask (
brew install --cask agentsview); CLI one-liners includeagentsview serve,agentsview usage daily --all --json,agentsview stats --format json, andagentsview session usage <id> --format json. - wrangler — Cloudflare Workers/Pages/R2/D1 CLI
- Social / web read CLIs (subset of what agent-reach bundles, installed directly to keep the dependency surface small):
yt-dlp— YouTube/Bilibili/1800+ sites, metadata + subtitles, no authtwitter(public-clis/twitter-cli, via pipx) — X/Twitter read/search/timeline/profile; auto-reads browser cookies (Chrome/Firefox), no API key required — matches Agent-Reach upstreamrdt(rdt-cli, via pipx) — Reddit search/read;rdt loginonce (Reddit requires auth since 2024)feedparser(Python lib) — RSS/Atom feeds (blog/YouTube channel/GitHub releases/Hacker News etc.)- For any other URL,
curl https://r.jina.ai/<URL>returns clean Markdown (Jina Reader, no install) - See
docs/agent-reference.mdfor exact one-liners agents should call.
- MCP servers wired up by
configs/mcp.json(registered viaclaude mcp add-json --scope user):- chrome-devtools — browser control
- serena — semantic code intelligence (LSP edit/refactor)
- codegraph — pre-indexed code graph (read-only; exploration / trace / architecture)
- linkedin (
linkedin-scraper-mcpviauvx) — LinkedIn profiles/companies/jobs (browser auth on first call). Excluded on internal NAVER machines — not yet security-reviewed. - exa — semantic web search +
web_fetch_exaURL reader. Connects to Exa's provider-hosted endpoint (https://mcp.exa.ai/mcp) anonymously — no API key needed for free-plan usage. Addx-api-keyheader (key from https://dashboard.exa.ai/) only if you hit the rate limit. On company overlay machines it is not hard-pruned because it is provider-official, but it remains user-scope only and must never receive internal data. - context7 — up-to-date library/framework docs lookup. Connects to Context7's hosted endpoint (
https://mcp.context7.com/mcp) anonymously for basic usage. The company overlay sourcesCONTEXT7_API_KEYfrom~/.company.secrets.envto lift rate limits when working under~/work/. - GitHub Operations on the public dotfiles use the
ghCLI directly. The company overlay (if configured) can add agithub-enterpriseMCP for the corporate GitHub Enterprise host — seecompany/configs/AGENTS-company.md.
Shell — Oh My Zsh with zsh-autosuggestions, zsh-syntax-highlighting, zsh-completions.
Git — separate personal/work accounts via includeIf with remote-URL-based routing (see "Separate Git accounts" below). Commits and tags are SSH-signed by default — register the public key as a Signing Key on GitHub to get a verified badge. A global ~/.gitignore_global (symlink to configs/.gitignore_global) catches .DS_Store, editor leftovers, and local .env* files while leaving shared .envrc files trackable for direnv.
Claude Code:
- Install — native build via
scripts/claude.sh, which downloads the officialclaude.ai/install.sh(to~/.local) and runs it from disk (no curl-pipe-bash); thereafter it self-updates withclaude update. Deliberately not Homebrew — the cask lags upstream and gets shadowed by~/.local/binon PATH, so a brew-installedclaudeis never the one that actually runs. - Skills — agent-skills, ai-slop-cleaner, clarify, code-review, doc-coauthoring, e2e-skills, frontend-design, humanizer, im-not-ai, internal-comms, karpathy-guidelines, mcp-builder, obsidian-skills, project-session-manager (
/oh-my-claudecode:psm), security-best-practices, skill-creator, ui-clone-skills, webapp-testing, Matt Pocock's upstreamgrill-meinstalled as a pinned standalone skill byscripts/skills.sh, plus repo-local skills (dotfiles-verify,agent-usage-audit,code-intel-doctor,worktree-open,local-preview-server,work-scope-guard,context-check,source-provenance,cmux-handoff-runner,handover,agent-reap) exposed fromplugins/local-skills/skills/viascripts/skills.sh - Local agents — scout, critic, debugger, test-engineer, security-reviewer, and git-master are symlinked from
configs/agents/into~/.claude/agents/so core review/debug/test/git lanes work even before optional plugin marketplaces are available. - Plugins (enabled by default) —
local-skills@dotfiles-local(this repo as a local Claude skill plugin; Codex gets the same repo-local skills throughscripts/skills.sh codexsymlinks), claude-hud@claude-hud (context/tool/agent/todo statusline; run/claude-hud:setuponce; personal machines only), skills-janitor@skills-janitor (skill inventory, duplicates, token value, cleanup), security-guidance, superpowers, claude-md-management (/claude-md-management:revise-claude-md+claude-md-improveraudit skill), hookify, session-wrap, claude-mem@thedotmack (persistent memory + cross-session search), plus comprehensive-review and documentation-generation from the wshobson/agents marketplace. Plugin slash command names follow each plugin's manifest: many use/<plugin-name>:<command>; standalone skills (/graphify, etc.) don't take the prefix. - Plugins (parked — installed/cached but
enabledPlugins: false; largely superseded by native Claude Code features such as workflows/ultracode,/simplify,/code-review,/security-review, and worktrees; revive with one settings flip) — ralph-loop, autoresearch@autoresearch, review-loop@hamel-review (REVIEW_LOOP_CODEX_FLAGS="--sandbox workspace-write"stays set so a revival never inherits the plugin's dangerous default), rust-analyzer-lsp, fakechat, vercel, session-report, and the remaining wshobson/agents packs (javascript-typescript, python-development, frontend-mobile-development, security-scanning, unit-testing, tdd-workflows, git-pr-workflows, error-debugging, ui-design, accessibility-compliance, content-marketing, seo-*). - Hooks —
rtk hook claude(in-place PreToolUse hook registered viartk init --global; compresses Bash output 60–90%), pretool-guard (structured PreToolUse deny for risky Bash), skill-md-edit-warn (PostToolUse reminder after editingSKILL.md), work-scope-guard (SessionStart advisory when cwd is under configured work roots), context-check (UserPromptSubmit advisory for continue/compact/clear/handover pressure; never auto-clears), plus security-guidance plugin hooks (edit/stop security review; the Stop-hook code-review pass is disabled viaENABLE_CODE_SECURITY_REVIEW=0). Hooks from parked plugins (autoresearch, review-loop) stay dormant while those plugins are disabled. - MCP — chrome-devtools (browser control via Chrome DevTools Protocol), serena (semantic code intelligence, LSP edit/refactor), codegraph (read-only pre-indexed code graph for exploration), context7 (version-aware docs lookup). Defined in
configs/mcp.json;scripts/claude.shregisters each entry viaclaude mcp add-json --scope user(writes to~/.claude.json, not the older.mcp.jsonsymlink path), while Claude settings pin the approved managed servers. - Codex CLI — first-class
scripts/codex.shsetup owns@openai/codexinstall/auth, installs the official cmux Codex skill, Matt Pocock's upstreamgrill-me, plus repo-local skills into~/.codex/skills/(including$context-checkfor continue/compact/clear/handover advice), and copies the portableconfigs/codex/config.tomltemplate to~/.codex/config.tomlso Codex's machine-local trust/runtime entries don't dirty the repo. - Token saving — settings calibrated against spilist's checklist gist (verified against official docs). Claude Code:
includeGitInstructions: falsedrops the built-in git workflow instructions + git status snapshot from the system prompt.autoInstallIdeExtension: falsekeeps Claude Code as a pure terminal tool — no auto-install of VS Code/JetBrains extensions. Codex:web_search = "disabled"drops the web_search tool definition (re-enable per-invocation withcodex --search).[features].apps = falsedrops ChatGPT-connector tool definitions. Session-level compression is on by default in interactive shells when Headroom is installed; useHEADROOM_DEFAULT=0orcommand codex/command claude/command omxfor a direct session.
Codex CLI:
- CLI —
scripts/codex.shinstalls@openai/codexwith npm whencodexis missing; upstream also supportsbrew install --cask codex - Config —
configs/codex/config.tomlis copied to~/.codex/config.toml; it setsgpt-5.5, leavesmodel_providerunset so Codex uses its OpenAI default and Headroom can inject a temporary provider without duplicate TOML keys, setsapproval_policy = "on-request",sandbox_mode = "workspace-write",web_search = "disabled",[features] apps = false,[features] goals = true, and four[mcp_servers.*]entries:chrome-devtools,serena(LSP code intelligence),codegraph(read-only code graph),context7(current docs lookup) - Skills —
scripts/codex.shinstalls the cmux skill frommanaflow-ai/cmux(skills/cmux) into~/.codex/skills/cmuxand callsscripts/skills.sh codexto install pinned upstream standalone skills such asgrill-meplus repo-local symlinks into~/.codex/skills/. - Profile —
codex --profile yolois available as an explicit opt-in profile (approval_policy = "never",sandbox_mode = "danger-full-access"); don't use it outside an isolated environment - Auth —
codex.shcheckscodex login status; runcodex loginfor ChatGPT sign-in,codex login --device-authfor a headless device-code flow, orprintenv OPENAI_API_KEY | codex login --with-api-keyfor API-key auth
Hermes Agent: Nous Research's self-improving AI agent. hermes.sh runs the upstream one-shot installer (curl ... | bash) — idempotent, skips if hermes is already on PATH. Configure with hermes setup after a shell reload.
tmux — minimal ~/.tmux.conf (symlinked from configs/.tmux.conf): C-Space prefix, mouse on, vi-mode copy, |/- splits that keep CWD, 100k scrollback, true-color.
Auto-launched browser dev services (LaunchAgents):
Installed services run at every login with KeepAlive=true (throttle 60s); services.sh skips loading LaunchAgents when dependencies are missing. Both services are reached over the tailnet via tailscale serve (HTTPS via Tailscale's *.ts.net cert; configured automatically by services.sh). code-server binds to 127.0.0.1 (kernel-level isolation). purplemux binds to *:8022, so this setup relies on Tailscale Serve plus the macOS firewall rather than an app-level tailnet filter. Defense-in-depth: macos.sh already enables the macOS firewall in stealth mode, so a hostile-wifi attacker should see stealthed ports.
com.user.purplemux— purplemux, web-native terminal multiplexer for Claude Code- Installed via
npm install -g purplemux(services.sh handles this) - Listens on
*:8022(no--bindflag upstream); access is intended through Tailscale Serve, not an app-level IP filter. Logs at~/Library/Logs/purplemux.{out,err}.log - Tailnet exposure:
tailscale serve --bg --https=443 --set-path=/ http://localhost:8022 - Restart:
launchctl kickstart -k gui/$(id -u)/com.user.purplemux
- Installed via
com.user.code-server— code-server, VS Code in the browser- Installed via Brewfile (
brew "code-server") - Reads
~/.config/code-server/config.yaml(services.sh scaffolds with a random password and enforceschmod 600) - Binds to
127.0.0.1:8088. Logs at~/Library/Logs/code-server.{out,err}.log
- Installed via Brewfile (
- Tailnet exposure:
tailscale serve --bg --https=8443 --set-path=/ http://localhost:8088; worktree links preferhttps://<tailnet-host-or-ip>:8443 - Restart:
launchctl kickstart -k gui/$(id -u)/com.user.code-server
Shared agent config — canonical ~/.agent/AGENTS.md with shared rules, also symlinked to ~/.cursor/rules/AGENTS.md before Claude Code setup. ~/.claude/CLAUDE.md imports it via @~/.agent/AGENTS.md.
Dotfiles symlinks — zshrc, tmux.conf, gitconfig, gitignore_global, Claude Code settings, pretool-guard hook, skill-md-edit-warn hook, work-scope-guard hook. Codex config is copied as a mutable local file. configs/mcp.json is read by scripts/claude.sh for user-scope MCP registration.
Web extraction — use defuddle parse <url> --markdown for article-style extraction (local, LLM-friendly Markdown). For JS-heavy or auth-gated pages, use agent-browser open <url> --profile "Default" to reuse your logged-in Chrome session; if an agent-browser daemon is already running under another profile, close it first with agent-browser close --all.
Agent safety and secrets — Claude settings include permissions.deny for .env*, secrets/, WebFetch, and filesystem MCP access; MCP governance approves chrome-devtools, serena, and codegraph from the shared MCP config and denies filesystem servers by serverName. pretool-guard is a PreToolUse Bash hook that emits structured JSON permissionDecision: "deny" for destructive commands such as git reset --hard, force push, root rm -rf, curl/wget piped to shell, and direct dotenv reads. skill-md-edit-warn is a PostToolUse hook that reminds you to restart Claude after editing a skill file mid-session. Prefer ephemeral secret injection: op run --env-file .env -- <command> for 1Password or sops exec-env secrets.enc.env '<command>' for SOPS.
Tailscale + Tailscale SSH — private mesh VPN for remote access. Each device gets a stable 100.x.x.x IP and *.ts.net hostname; no port forwarding, no public exposure. tailscale.sh runs tailscale set --ssh so inbound shell access can go through Tailscale (identity from the tailnet, ACL-gated in the admin console). Persistent sessions: tailscale ssh user@<host> -- tmux attach. OpenSSH (systemsetup -setremotelogin on, set in macos.sh) is enabled in parallel — Tailscale SSH is the primary path, OpenSSH stays as a fallback for tooling that doesn't speak the Tailscale layer. Free tier covers personal use.
mosh — UDP-based shell that survives network changes / roaming / disconnects. Bootstraps over SSH for auth (so OpenSSH must stay enabled) then switches to UDP ports 60000–61000. Useful on mobile hotspots or unstable links. Connect with mosh user@host or mosh --ssh="tailscale ssh" user@host to layer mosh on top of Tailscale SSH. The macOS Application Firewall is in stealth mode, so the first mosh session may need a manual exception for mosh-server (System Settings → Network → Firewall → Options).
Tests — bats-core suites under tests/ cover scripts/lib/common.sh helpers, script syntax/conventions, config drift, GitHub Actions pinning, JSON/TOML validation, and Brewfile parsing. Run with bats tests/.
dotfiles/
├── install.sh # main entry point
├── Brewfile # Homebrew package list
├── scripts/
│ ├── brew.sh # Homebrew install
│ ├── macos.sh # macOS system settings
│ ├── dev.sh # nvm, pyenv, Java, etc.
│ ├── shell.sh # Oh My Zsh + plugins
│ ├── git.sh # Git config + SSH keys
│ ├── claude.sh # Claude Code skills, plugins, tools
│ ├── codex.sh # Codex CLI config + cmux skill + auth prompt
│ ├── headroom.sh # Headroom CLI installer + claudeh/codexh/omxh symlinks
│ ├── headroom-agent.sh # Headroom wrapper dispatcher
│ ├── statusline.sh # Claude/Codex-visible session label helpers
│ ├── skills.sh # repo-local skills/plugin installer
│ ├── verify.sh # repo smoke verifier
│ ├── lib/
│ │ └── common.sh # shared helpers (info/warn/error/run_or_dry/link_file)
│ ├── hermes.sh # Hermes Agent (Nous Research) installer wrapper
│ ├── services.sh # purplemux + code-server LaunchAgent installer
│ ├── purplemux-launch.sh # LaunchAgent wrapper for purplemux (PATH + node resolution)
├── plugins/
│ └── local-skills/ # local Claude/Codex skill bundle
│ ├── code-server-launch.sh # LaunchAgent wrapper for code-server
│ └── tailscale.sh # Tailscale VPN + `tailscale set --ssh`
├── configs/
│ ├── .zshrc
│ ├── .tmux.conf
│ ├── .gitconfig
│ ├── .gitconfig-personal
│ ├── .gitconfig-work
│ ├── .gitignore_global
│ ├── AGENTS.md # canonical shared agent rules
│ ├── CLAUDE.md # Claude Code wrapper (imports ~/.agent/AGENTS.md)
│ ├── claude-settings.json
│ ├── mcp.json # shared Claude MCP servers
│ ├── codex/
│ │ └── config.toml # Codex CLI defaults + MCP servers
│ ├── com.user.purplemux.plist # LaunchAgent template (sed-substituted at install)
│ ├── com.user.code-server.plist # LaunchAgent template (sed-substituted at install)
│ ├── rtk-config.toml
│ └── hooks/
│ ├── pretool-guard.sh # structured PreToolUse deny hook
│ ├── work-scope-guard.sh # SessionStart advisory for work roots
│ └── skill-md-edit-warn.sh # PostToolUse reminder after editing skills
├── tests/ # bats-core suites: run `bats tests/`
│ ├── common.bats
│ └── scripts.bats
└── company/ # (gitignored) optional company overlay — see company/README.md
For environments that require company-internal configuration (private plugin
marketplaces, image registries, scoped npm registries, team-issued API keys),
install.sh automatically invokes company/install.sh if that path exists.
company/ is a git submodule pointing at a separate, internally-hosted
repo (URL listed in .gitmodules). The submodule URL is visible publicly but
the repository itself is only accessible over the internal network with proper
auth — clone fails gracefully outside the company.
On a new machine:
git clone --recurse-submodules https://github.com/voidmatcha/dotfiles.git ~/dotfiles # or, if already cloned: git -C ~/dotfiles submodule update --init
See company/README.md for what lives in the overlay and how to maintain it.
./scripts/brew.sh # Homebrew only ./scripts/macos.sh # macOS settings only ./scripts/dev.sh # dev tools only ./scripts/shell.sh # shell only ./scripts/git.sh # Git only ./scripts/codex.sh # Codex CLI only
Preview without making changes:
./install.sh --dry-run
Account selection is remote-URL-based, not directory-based — a repo's
location on disk doesn't matter, only its remote.origin.url:
- Remote matches the corporate git host → work account
(
~/.gitconfig-work) - Anything else (or no remote yet) → personal account
(
~/.gitconfig-personal)
The exact host(s) that route to "work" live in configs/.gitconfig's
includeIf "hasconfig:remote.*.url:..." blocks (currently set for the
maintainer's employer; fork and edit those patterns to match your own
internal git host — https://, git@, and ssh:// URL forms are
each their own line). Requires git 2.36+.
The two account files (configs/.gitconfig-personal and .gitconfig-work)
carry only user.name and user.email. SSH signingkey paths are
machine-specific so they live in ~/.gitconfig.local (gitignored),
with an optional ~/.gitconfig.local-work for a separate work-account
signing key. configs/.gitconfig loads ~/.gitconfig.local last, so it
always wins for signing/keys.
Run git.sh — it prompts for names and emails, then writes the two
account configs plus ~/.gitconfig.local (default signing key path) and
~/.gitconfig.local-work (work signing key path).
Git identity routes by remote URL (above), but MCP loading routes by
directory. The company overlay writes its MCP config to
~/work/.mcp.json (project scope), and Claude Code picks it up only when
started inside ~/work/<repo>/. So:
- Keep company repos under
~/work/→ company MCP servers (e.g.github-enterprise) auto-load on top of the personal set, and the overlay provides company MCP secrets such asCONTEXT7_API_KEYforcontext7rate limits. - Any other location → company project MCPs are absent. On company overlay
machines, clearly unapproved MCPs such as the
linkedinscraper are pruned; provider-official MCPs such as Exa may remain user-scope only, but must not receive internal terms, URLs, source snippets, credentials, customer data, or personal information. Git author/signing still routes correctly via remote URL.
git.sh creates ~/work/ and ~/personal/ for you.
git.sh generates separate keys for personal and work.
Add the public keys to GitHub after:
cat ~/.ssh/id_ed25519_personal.pub # personal cat ~/.ssh/id_ed25519_work.pub # work