MacBook Setup for AI Engineering: macOS Tools and Workflow
This is the setup I apply to a fresh MacBook before doing AI engineering work. It covers the command-line tools macOS does not include, Homebrew, Python with uv, the shell, terminal and editor choices, Docker, and local AI tools.
The useful boundary is between required foundations and personal preferences. Xcode Command Line Tools, Homebrew, Git, and a Python workflow are foundations. Warp, iTerm2, Powerlevel10k, Cursor, and local model runners are choices. The commands below install the foundation; the later sections record the choices I make on top. This is a documented setup sequence, not a bit-for-bit reproducible machine image.
Install the macOS foundations
Start with Apple’s command-line tools:
xcode-select --install
macOS opens a dialog that walks through the installation.
Homebrew is the package manager I use for the rest of this setup:
Download the installer at a reviewed revision, verify its digest, inspect it, and then run the local copy:
(
set -e
homebrew_installer="$(mktemp)"
trap 'rm -f "$homebrew_installer"' EXIT
curl --proto '=https' --tlsv1.2 --fail --location --show-error https://raw.githubusercontent.com/Homebrew/install/150c69df1e54b0b74c9fcca5a201410a2300816a/install.sh -o "$homebrew_installer"
printf '%s %s\n' 12479a24be3f5307eecac7cde670fad7118640f031229e964f544b1367b52a41 "$homebrew_installer" | shasum -a 256 --check
less "$homebrew_installer"
/bin/bash "$homebrew_installer"
)
The Homebrew installation guide explains the
changes and default prefixes. On Apple Silicon, Homebrew uses /opt/homebrew.
Run the brew shellenv command printed at the end so brew is available in the
current shell. The installer may also configure the Homebrew prefix for future login
shells. The commit pin and SHA-256 cover the bootstrap file; update them together
after review. Homebrew itself is a rolling package manager, so later brew install
commands still resolve the then-current formula versions.
Install the command-line tools I use regularly:
brew install openssl readline sqlite3 xz zlib uv htop gitmoji pandoc ncdu tmux
The packages fall into three groups:
- openssl, readline, sqlite3, xz, and zlib provide libraries for common Python and command-line workloads.
- uv manages Python versions, environments, dependencies, commands, and lockfiles.
- htop, tmux, ncdu, gitmoji, and pandoc cover process monitoring, terminal sessions, disk inspection, commit formatting, and document conversion.
Use uv for everyday Python work
Before adopting uv, I used pyenv to manage Python environments. uv now covers my everyday workflow. Install the latest Python version managed by uv:
uv python install
That installs the latest version available to uv at setup time. For a project-specific version, use the pinning workflow in my quick guide to managing Python on macOS with uv.
Install pyenv separately only when you need its source-build path:
brew install pyenv
I use pyenv when I need a source-built interpreter or custom CPython build options
that uv’s prebuilt distributions do not provide. Remove pyenv from the shell
plugin list below if you skip this optional install.
Choose a terminal
The default macOS Terminal is fine. I used iTerm2 for years and recently moved to Warp, a Rust-based terminal with built-in AI features. The choice does not affect the later setup.
If you stay with iTerm2, these are the two settings I change:
Enable natural text editing
- Open Preferences → Profiles → Keys → Key Mappings.
- Open the Presets… dropdown.
- Select “Natural Text Editing”.
Pick a color theme
- Browse themes at iTerm2-Color-Schemes.
- Open Preferences → Profiles → Colors → Color Presets…
- Select Import and choose the downloaded theme.
Configure Zsh
macOS uses Zsh as its default login shell.
I use the bundled /bin/zsh; install
Homebrew’s Zsh only when you need a
specific newer upstream version.
Check the installed Zsh and the current login shell:
echo "$SHELL"
command -v zsh
zsh --version
If Zsh is installed but is not selected as the login shell, switch to the bundled copy:
chsh -s /bin/zsh
Open a new terminal after changing the login shell.
Oh My Zsh adds the defaults and plugin system I use. Its standard installer follows a moving branch. For a pinned setup, clone without checking out that branch, select the reviewed commit, and only then copy the template:
(
set -e
omz_dir="$HOME/.oh-my-zsh"
if [ -L "$HOME/.zshrc" ]; then
printf 'Refusing to replace symlink: %s\n' "$HOME/.zshrc" >&2
exit 1
fi
if [ -e "$HOME/.zshrc" ] && [ ! -f "$HOME/.zshrc" ]; then
printf 'Refusing to replace non-regular file: %s\n' "$HOME/.zshrc" >&2
exit 1
fi
if [ -e "$HOME/.zshrc" ]; then
omz_backup="$(mktemp "$HOME/.zshrc.pre-oh-my-zsh.XXXXXX")"
cp "$HOME/.zshrc" "$omz_backup"
printf 'Existing .zshrc backed up to %s\n' "$omz_backup"
fi
git clone --filter=blob:none --no-checkout https://github.com/ohmyzsh/ohmyzsh "$omz_dir"
git -C "$omz_dir" checkout --detach 4b657407c98bbc8830ae66c2ac7ff3d737c55a83
test "$(git -C "$omz_dir" rev-parse HEAD)" = 4b657407c98bbc8830ae66c2ac7ff3d737c55a83
new_zshrc="$(mktemp "$HOME/.zshrc.new.XXXXXX")"
trap 'rm -f "$new_zshrc"' EXIT
cp "$omz_dir/templates/zshrc.zsh-template" "$new_zshrc"
test -s "$new_zshrc"
test ! -d "$HOME/.zshrc"
mv -f "$new_zshrc" "$HOME/.zshrc"
)
The block stops before replacing .zshrc if clone or commit verification fails. It
refuses symlinks and non-regular files, backs up an existing file under a unique
.zshrc.pre-oh-my-zsh.* name, and replaces it with a verified temporary copy in one
rename. Compare the printed backup with the template and restore your local settings
before opening a new shell. The checkout is pinned to commit 4b65740; update the hash only after reviewing a newer revision.
Run the shell-configuration snippets from one setup shell, with no other process
editing .zshrc or the plugin and theme destinations. They refuse existing or
symlinked destinations, but they are not a multi-process package manager.
Add plugins
Install zsh-autosuggestions and zsh-syntax-highlighting in Oh My Zsh’s custom plugin directory:
install_pinned_zsh_repo() (
set -e
repo_url="$1"
commit="$2"
destination="$3"
if [ -L "$destination" ]; then
printf 'Refusing symlink destination: %s\n' "$destination" >&2
return 1
fi
if [ -d "$destination/.git" ] && [ "$(git -C "$destination" rev-parse HEAD)" = "$commit" ]; then
return 0
fi
destination_parent="$(dirname "$destination")"
mkdir -p "$destination_parent"
stage="$(mktemp -d "$destination_parent/.pinned-zsh.XXXXXX")"
cleanup_stage() { rm -rf "$stage"; }
trap cleanup_stage EXIT
git clone --filter=blob:none --no-checkout "$repo_url" "$stage/repo"
git -C "$stage/repo" checkout --detach "$commit"
test "$(git -C "$stage/repo" rev-parse HEAD)" = "$commit"
test ! -e "$destination"
test ! -L "$destination"
mv -h -n "$stage/repo" "$destination"
test ! -e "$stage/repo"
test "$(git -C "$destination" rev-parse HEAD)" = "$commit"
)
install_pinned_zsh_repo https://github.com/zsh-users/zsh-autosuggestions e52ee8ca55bcc56a17c828767a3f98f22a68d4eb "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
install_pinned_zsh_repo https://github.com/zsh-users/zsh-syntax-highlighting.git db085e4661f6aafd24e5acb5b2e17e4dd5dddf3e "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting"
The pinned zsh-autosuggestions commit and zsh-syntax-highlighting commit correspond to the published v0.7.1 and 0.8.0 releases. Update the hashes only after reviewing a newer release.
Edit ~/.zshrc to load them with the plugins I use:
plugins=(
aws bgnotify brew docker docker-compose
emoji forklift gcloud git history iterm2
keychain kubectl macos pre-commit
pyenv pylint python screen themes
tmux virtualenv vscode
zsh-autosuggestions zsh-syntax-highlighting
)
Keep zsh-syntax-highlighting last in the array. The Oh My Zsh plugins
wiki describes the bundled
plugins. The two external plugins suggest commands from history and highlight
commands as you type; follow the install instructions in each repository.
Add Powerlevel10k and its font
Powerlevel10k is the Zsh theme I use.
It shows the working directory, Git status, and active Python environment in the
prompt, and provides an interactive configuration wizard. Install it for Oh My
Zsh, then select the theme in ~/.zshrc:
(
set -e
p10k_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
if [ -L "$p10k_dir" ]; then
printf 'Refusing symlink destination: %s\n' "$p10k_dir" >&2
exit 1
fi
if [ -d "$p10k_dir/.git" ] && [ "$(git -C "$p10k_dir" rev-parse HEAD)" = 35833ea15f14b71dbcebc7e54c104d8d56ca5268 ]; then
exit 0
fi
test ! -e "$p10k_dir"
p10k_parent="$(dirname "$p10k_dir")"
mkdir -p "$p10k_parent"
stage="$(mktemp -d "$p10k_parent/.pinned-p10k.XXXXXX")"
cleanup_stage() { rm -rf "$stage"; }
trap cleanup_stage EXIT
git clone --filter=blob:none --no-checkout https://github.com/romkatv/powerlevel10k.git "$stage/repo"
git -C "$stage/repo" checkout --detach 35833ea15f14b71dbcebc7e54c104d8d56ca5268
test "$(git -C "$stage/repo" rev-parse HEAD)" = 35833ea15f14b71dbcebc7e54c104d8d56ca5268
test ! -L "$p10k_dir"
mv -h -n "$stage/repo" "$p10k_dir"
test ! -e "$stage/repo"
test "$(git -C "$p10k_dir" rev-parse HEAD)" = 35833ea15f14b71dbcebc7e54c104d8d56ca5268
)
The pinned Powerlevel10k commit is referenced by the v1.20.0 release.
The Git object IDs above prevent silent version drift after you obtain them; they do not authenticate the maintainer by themselves. These commands trust GitHub’s HTTPS endpoint and the repository accounts behind it. For a stronger trust model, verify a signed tag or commit against a maintainer key you obtained separately before moving the staged checkout into place.
ZSH_THEME="powerlevel10k/powerlevel10k"
Open a new shell and run p10k configure.
If you use VS Code’s integrated terminal, install the recommended font before
setting the terminal font so Powerlevel10k icons render. p10k configure can
install the font automatically in iTerm2. For other terminals, download and
install the four TTF files from the Powerlevel10k font
guide.
In VS Code, set the terminal font to MesloLGS NF:
- Open your editor’s settings.
- Search for
terminal.integrated.fontFamily. - Set it to
MesloLGS NF.
Choose editors and AI assistants
I keep one IDE open and an AI tool or two beside it.
IDEs
- Cursor is a VS Code fork with AI pair programming built in.
- VS Code has the larger extension catalog.
AI assistants
- OpenAI Codex is OpenAI’s coding agent.
- Claude is Anthropic’s assistant, which I reach for on harder tasks.
These days I use Cursor with Codex and Claude Code running in parallel.
Choose containers and local model tools
The remaining tools depend on the work I plan to do on the machine:
- GitHub Desktop, when I do not want to drop into the CLI.
- Docker Desktop, the container runtime I use.
- Podman Desktop, an alternative to Docker Desktop.
- Ollama or LM Studio, for running LLMs locally on the Mac.
Docker Desktop and local model runners are independent choices. Install them when your projects need containers or local inference; the shell and Python setup does not depend on either one.
Record the setup boundaries
This is a personal setup, not a minimal or universal macOS baseline. Remove what you do not use. It records reviewed bootstrap revisions and plugin releases, but Homebrew formulas and GUI applications still move. The parts I keep consistent are the boundaries:
- Use the bundled
/bin/zshunless a project needs a specific newer Zsh. - Use uv for everyday Python installation and project environments; keep pyenv for source-built or custom CPython interpreters.
- Keep install commands, secret-free dotfiles, editor extensions, and model locations documented so the next setup is mechanical.