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.
Tooling
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.
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:
[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.
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:
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.
$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.
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.
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.The README’s install command is the only command to remember:
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.