Skip to content
Mac Long

Tooling

Bootstrapping a new Mac with mise

How one setup.sh script and a mise-managed dotfiles repo take a blank Mac to a fully configured shell, editor, and AI agent environment.

· 7 min read
MiseZedZsh
Bootstrapping a new Mac index card listing mise, Zed, and zsh as the core tools.

One script, not a checklist

Setting up a new Mac used to mean a personal checklist: install Xcode tools, generate an SSH key, clone a dotfiles repo, configure Touch ID for sudo, and remember every preference. Checklists drift. Steps get skipped, forgotten, or done in the wrong order.

~/Developer/configuration replaces that checklist with one reproducible entry point. setup.sh is deliberately linear and idempotent: it installs Xcode Command Line Tools if missing, generates an ed25519 SSH key, clones the dotfiles repo, installs mise, then hands tools, packages, dotfiles, and macOS defaults over to mise itself. The SSH identity is then ready to be registered with Tangled.

Most dotfiles setups reinvent a small symlinking script. mise already has one built in. The [dotfiles] table in .config/mise/config.toml maps $HOME paths to files in the repo, and dotfiles.default_mode = "symlink" means editing ~/.config/zsh/.zshrc and editing the file in the repo are the same action:

toml
[settings]
dotfiles.default_mode = "symlink"
dotfiles.root = "~/Developer/configuration/"

[dotfiles] ”~/.config/git/config” = {} ”~/.config/mise/config.toml” = {} ”~/.config/zsh/.zshrc” = {} ”~/.zshenv” = { source = ”~/Developer/configuration/.config/zsh/.zshenv” }

Most entries just mirror the repo path under $HOME; a source override handles the handful of files, like .zshenv, that live at a different path than their target. mise bootstrap --yes --force-dotfiles applies the whole table in one pass.

The same config file also feeds mise bootstrap two other tables: [bootstrap.packages] (Homebrew casks and Mac App Store IDs) and [bootstrap.macos.*] (Dock, Finder, trackpad, and window-manager defaults). One mise.toml, three different kinds of machine state.

Skills are a bootstrap step, not a one-off install

Agent instructions and skills get the same treatment as shell config. AGENTS.md is one file, symlinked to ~/.agents/AGENTS.md, so its instructions have one checked-in source of truth rather than a hand-managed local copy.

Skills go further: a mise run skills task pulls specific named skills out of other people’s repos — Swift concurrency, SwiftUI, Core Data, iOS accessibility, FastAPI, Vue, and more — into Zed’s global skill directory:

sh
npx --yes skills add twostraws/Swift-Concurrency-Agent-Skill \
  --global \
  --agent zed \
  --skill swift-concurrency-pro \
  --copy \
  --yes

Every source repo names its skill directory differently, so a second pass normalizes them: lowercase, hyphenated folder names, and the name: field inside each SKILL.md rewritten to match. The goal is compatibility: some agents only accept skills whose names are lowercase and hyphenated, so the normalization gives every installed skill a name they can load.

zsh keeps shell state out of $HOME

.zshenv does only the work that must happen for every shell: it redirects ZDOTDIR to ~/.config/zsh, disables Terminal’s session-restoration noise, and optionally loads machine-local settings. The heavier setup stays in .zshrc, where zsh4humans bootstraps itself into a cache, checks for updates every 28 days, and loads the Powerlevel10k prompt configuration.

The shell configuration also enables terminal shell integration, uses the Mac key bindings, accepts the full autosuggestion with the right-arrow key, and keeps fzf completion from recursively walking directories. It deduplicates PATH, ignores duplicate history entries, and finishes by activating mise for zsh. The result is a useful interactive shell without letting its framework files spill across $HOME.

Zed is configured as an editor first

The checked-in Zed settings install the extensions needed for the work here — Swift, Pkl, TOML, Vue, SQL, Markdown, and project-file support — and keep editor behaviour consistent with 2-space tabs, format-on-save, a 120-column preferred line length, autosave after one second, unified diffs, and relative line numbers. Vim mode uses Zed’s own keymap and keeps the system clipboard opt-in.

The agent panel is part of that setup, not the whole of it: its default model and write profile are configured alongside agent-server entries, so those choices travel with the rest of the editor configuration.

Git defaults worth stealing

  • gpg.format = ssh plus a signingkey pointed at the same ed25519 key used for Tangled auth — one key, no separate GPG key to generate or back up.
  • merge.conflictstyle = zdiff3 — three-way conflict markers that show the common ancestor, not just both sides.
  • rerere.enabled = true — Git remembers how a conflict was resolved once and replays that resolution automatically if it recurs.

What one command gets you

The README’s install command is the only command to remember:

sh
curl -fsSL https://raw.githubusercontent.com/mac95sb/configuration/main/setup.sh | sh

By the time setup.sh finishes, a blank Mac has Xcode’s tools, an SSH identity to be registered with Tangled, every dotfile symlinked into place, a curated set of agent skills installed and normalized, Touch ID enabled for sudo, and a fixed set of apps pinned to the Dock. On macOS, Touch ID comes from /etc/pam.d/sudo_local: the script copies the system template there, then uncomments its auth line. None of it required remembering a checklist — the checklist is the repo.

Discuss on Bluesky