Skip to content

Developer Productivity

Mac Developer Dotfiles in 2026 — The Aidxn Setup

Your Mac is Identical for the First 5 Minutes. zsh + Starship Prompt, fnm for Node Switching, Homebrew Bundle, Git Aliases, asdf Backup. Real Config Shared. Skip 2 Hours of Manual Setup.

⚙️ 🚀

Every new MacBook starts the same way: vanilla OS, no Git config, Python 2.7 from 2015, no package manager, no shell customization. You spend 2 hours installing Homebrew, Node via some manager you'll forget, a terminal theme, git aliases, npm globals, and shell functions. Then you switch machines and do it all again. Or you stare at a coworker's terminal wondering how their prompt has weather and Git status but yours is just $ . Dotfiles solve this. A dotfiles repo is your OS config + shell config + aliases + Git config + editor config living in version control. Clone the repo, run a setup script, 5 minutes later your new Mac is identical to your old one: same prompt, same tools, same shortcuts, same Git workflow. Aidxn's setup lives at the intersection of minimal (no bloat, no 500-line shell scripts) and complete (every tool you actually use, annotated). This is it.

What Are Dotfiles? (And Why They're a Productivity Multiplier)

Dotfiles are config files. They start with a dot (hence the name). .zshrc (shell config), .gitconfig (Git config), .ssh/config (SSH config), .vimrc (Vim config). Every time you customize something on your Mac — a shell function, a Git alias, a Homebrew package list — you're editing a dotfile. If you lose your Mac tomorrow, all that knowledge is gone. If you store those files in a Git repo, you can clone them on a new machine and be back to 100% in minutes.

Why they're a productivity multiplier: Shell functions save 50+ keystroke-hours per year. A 3-character alias (gs for git status) doesn't sound like much until you use it 20 times a day. A smart prompt that shows Git branch + dirty state means you never commit to the wrong branch. A Homebrew bundle means you install 40 dev tools in one command instead of 40 separate brew install lines. Individually, each tweak is a few seconds. Collectively, they're the difference between a Mac that feels natural and one that feels borrowed.

The Aidxn approach: Zsh (macOS default since Catalina) + Starship prompt (beautiful, fast, cross-shell) + fnm (lightweight Node manager) + Homebrew bundle (one-line dev-tool install) + asdf (Erlang, Ruby, Python multi-version) + sensible Git aliases (push, pull, rebase, cherry-pick shortcuts). No Oh My Zsh bloat, no 100-plugin framework, no custom theme management. Just the tools and config you actually use, annotated and shareable.

The Annotated .zshrc — Shell Functions and Aliases

Here's the real .zshrc Aidxn uses (trimmed for clarity; full version in dotfiles repo):

# ~/.zshrc — Aidxn Mac Dev Setup 2026 # --- GENERAL SETTINGS --- # Use Vim keybindings bindkey -v # Case-insensitive tab completion autoload -Uz compinit && compinit zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' # Expand variables in completion zstyle ':completion:*' expand suffix # --- PATH SETUP --- # fnm (Node Version Manager) — lightweight, Rust-based, ~5MB vs nvm's ~500MB export PATH="$HOME/.local/share/fnm:$PATH" eval "$(fnm env)" # asdf for multi-runtime fallback (Erlang, Python, Ruby if needed) export ASDF_DIR="$HOME/.asdf" [ -s "$ASDF_DIR/asdf.sh" ] && . "$ASDF_DIR/asdf.sh" # Homebrew (on Apple Silicon, Homebrew installs to /opt/homebrew) export PATH="/opt/homebrew/bin:$PATH" # npm globals (pnpm, turbo, astro, etc.) export PATH="$HOME/.npm-global/bin:$PATH" # --- SHELL FUNCTIONS (MONEY MOVES) --- # 1. dev — jump to project directory and open VSCode dev() { local dir="$HOME/Desktop/00 - Aidxn/$1" if [ ! -d "$dir" ]; then echo "Project '$1' not found at $dir" return 1 fi cd "$dir" && code . } # 2. gswitch — interactive Git branch switcher gswitch() { git checkout "$(git branch --list | fzf --preview 'git log --oneline -10 {}')" } # 3. bump — semantic version bump (patch, minor, major) bump() { local version=$(grep '"version"' package.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) local major=$(echo $version | cut -d. -f1) local minor=$(echo $version | cut -d. -f2) local patch=$(echo $version | cut -d. -f3) case "$1" in patch) patch=$((patch + 1)) ;; minor) minor=$((minor + 1)); patch=0 ;; major) major=$((major + 1)); minor=0; patch=0 ;; *) echo "Usage: bump [patch|minor|major]"; return 1 ;; esac local new_version="$major.$minor.$patch" npm version "$new_version" --no-git-tag-version echo "Bumped to $new_version" } # 4. serve — local HTTP server (port 8000) serve() { local port="${1:-8000}" python3 -m http.server "$port" } # 5. cleanup — kill node/npm processes if your build is hung cleanup() { pkill -f node pkill -f npm echo "Cleaned up node/npm processes" } # --- STARSHIP PROMPT CONFIG --- # Starship config is in ~/.config/starship.toml (see next section) eval "$(starship init zsh)" # --- GIT CONFIGURATION (ALIASES MOVED TO .gitconfig) --- # Git aliases are in ~/.gitconfig for portability across shells # --- FZF (FUZZY FINDER) INTEGRATION --- [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git' export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" # --- HOMEBREW --- # Silence Homebrew analytics export HOMEBREW_NO_ANALYTICS=1 # --- NODE / NPM --- # Use pnpm as primary (if installed) if command -v pnpm &> /dev/null; then alias npm="pnpm" fi # npm globals folder (avoid sudo) npm config set prefix ~/.npm-global # --- CONVENIENCE ALIASES --- alias ls="ls -lah" alias la="ls -la" alias ll="ls -l" alias c="clear" alias h="history | tail -20" alias now="date +%Y-%m-%d\ %H:%M:%S" alias myip="curl -s https://ifconfig.me" # --- EDITOR --- export EDITOR="code" export VISUAL="code" # --- STARPORT (custom function for quick terminal reload) --- starport() { exec zsh }

Key Decisions Explained

fnm over nvm: nvm is the heavyweight champ (universal, well-documented), but it's 500MB and adds 200ms to every shell startup. fnm is Rust-based, 5MB, and instant. Both do the same job (Node version switching). fnm wins on speed.

asdf as a fallback: If you ever need to switch between Erlang versions (Elixir backend) or manage multiple Python versions, asdf handles it. But fnm + Homebrew cover 95% of cases. asdf is insurance.

Git aliases in .gitconfig, not shell: Git aliases are portable across shells (works in Bash, Zsh, Fish). Shell aliases are not. This separation matters if you ever jump shells.

fzf for fuzzy branch switching: gswitch uses fzf (fuzzy finder) to search branches. git checkout alone shows a list; fzf lets you search in real time. Try it once and you'll never go back.

Starship Prompt: Beautiful, Zero Latency, Language-Agnostic

Starship is a cross-shell prompt that shows Git status, current language (Node version, Python version, Rust toolchain), exit codes, and execution time. It's 15MB, one binary, zero plugins. Here's the Aidxn ~/.config/starship.toml:

# ~/.config/starship.toml # Global settings add_newline = true command_timeout = 500 scan_timeout = 10 # Format (left side of prompt) format = """ [┌─────────────────>](bold green) [│](bold green) $username $hostname in $directory$git_branch$git_status$nodejs$python$rust $status [└─> ](bold green)$character """ # Right side (trailing info) right_format = """$cmd_duration""" # Git branch + status [git_branch] symbol = " " format = "[$symbol$branch]($style) " style = "bold purple" [git_status] conflicted = "🏳" ahead = "⇡$count" behind = "⇣$count" diverged = "⇕⇡$ahead_count⇣$behind_count" untracked = "🤷" stashed = "📦" modified = "!!" staged = "✓✓" renamed = "»" deleted = "✘" format = "([$all_status$ahead_behind]($style)) " style = "bold red" # Node version (only show if in a Node project) [nodejs] symbol = " " format = "[$symbol$version]($style) " style = "bold green" only_with_files = ["package.json", "node_modules"] # Python [python] symbol = "🐍 " format = "[${symbol}$version]($style) " style = "bold blue" # Rust [rust] symbol = "🦀 " format = "[${symbol}$version]($style) " style = "bold red" # Directory [directory] truncation_length = 3 truncate_to_repo = true format = "[$path]($style) " style = "bold cyan" # Status (exit code) [status] symbol = "✖" format = "[$symbol$status]($style) " style = "bold red" disabled = false # Command duration (only show if >2 seconds) [cmd_duration] min_time = 2000 format = "(took [$duration]($style)) " style = "bold yellow" # Username (only show if SSH) [username] show_always = false format = "[$user]($style)@" style_user = "bold yellow" # Hostname [hostname] ssh_only = true format = "[$hostname]($style) " style = "bold blue" # Character ($ or # based on exit status) [character] success_symbol = "[➜](bold green)" error_symbol = "[➜](bold red)"

What You Get

Left side: Git branch in purple, file status (modified/staged/untracked), Node/Python/Rust version if detected, exit code (red ✖ if last command failed). Right side: how long the command took (only shows if >2 seconds). No clutter, everything context-aware.

Homebrew Bundle: One Command, 40 Dev Tools

Homebrew is macOS's package manager. brew install node fetches and installs Node. But listing 40+ packages is tedious. Homebrew bundle solves this: a Brewfile declares all tools + apps + fonts, and one command installs them all.

Here's the Aidxn ~/Brewfile (keep at home root, not in dotfiles repo — it's machine-specific):

# ~/Brewfile — Aidxn Dev Tools Bundle 2026 # Taps (third-party formula repos) tap "homebrew/services" tap "caarlos0/tap" # CLI Tools — must-have brew "git" # version control brew "gh" # GitHub CLI (auth, PR, issue management) brew "fzf" # fuzzy finder (shell integration via ~/.fzf.zsh) brew "ripgrep" # rg — fast file search (grep alternative) brew "fd" # fast directory search (find alternative) brew "bat" # cat with syntax highlighting brew "starship" # prompt (the real star) brew "exa" # ls replacement (colorful, fast) brew "neovim" # editor brew "jq" # JSON query language (data processing) brew "httpie" # HTTP client (curl alternative) brew "speedtest-cli" # internet speed (cli) # Node version management brew "fnm" # fast node manager (5MB, instant) # Language runtimes brew "python@3.12" # Python 3.12 (specific version) brew "ruby" # Ruby (if needed for Jekyll, etc.) brew "postgresql" # Postgres (local dev database) # Build tools brew "cmake" # C++ build system (some npm packages need it) brew "pkg-config" # dependency resolver # Image processing (for image CDN work, After Effects pipeline) brew "imagemagick" # image manipulation CLI brew "ffmpeg" # video/audio encoding (FFmpeg for pipeline work) # Git extras brew "git-flow" # Git branching model (optional, Aidxn doesn't use) brew "lazygit" # Git CLI UI (optional, but nice for commit browsing) # Applications (GUI) cask "visual-studio-code" # editor (could also use code from Microsoft tap) cask "figma" # design tool cask "slack" # team chat cask "arc" # browser (Arc is polished, worth it) cask "raycast" # launcher + clipboard (replaces Spotlight) cask "1password" # password manager cask "whisk" # GIF creator (if doing motion work) # Fonts (for terminal + design work) tap "homebrew/cask-fonts" cask "font-fira-code" # monospace (terminal) cask "font-inter" # sans-serif (UI, design) cask "font-jetbrains-mono" # monospace (editor alternative) # Mac App Store apps (requires mas CLI) brew "mas" mas "Xcode", id: 497799835 # Apple's IDE (needed for some Node modules, Python builds) mas "Tot", id: 1491071483 # quick notes # Homebrew Cask font paths # If you want to use these fonts in design tools, symlink or add to FontBook manually after install

Install everything with one command:

brew bundle --file ~/Brewfile

That's it. 5 minutes later, you have 40+ tools installed, globally configured, and ready to go.

Git Config: Aliases That Stick

Git aliases save keystrokes on repeat commands. Aidxn's ~/.gitconfig (the official Git config file, not shell aliases):

# ~/.gitconfig [user] name = Aiden Wood email = aiden@rebuildrelief.com.au [core] editor = code --wait autocrlf = input # normalize line endings (LF on Unix, CRLF on Windows files) safecrlf = warn [init] defaultBranch = main # --- ALIASES (the money moves) --- [alias] # Basic status + history st = status s = status -s # short status (one line per file) ss = status -sb # short status with branch l = log --oneline -10 # last 10 commits, one line each ll = log --oneline # all commits, one line each lg = log --graph --oneline --all --decorate # visual graph h = log -1 # last commit (HEAD) # Staging and commits a = add aa = add -A # stage everything (changes + deletes) c = commit -m ca = commit --amend --no-edit # amend last commit without prompt cam = commit --amend -m # amend with new message # Branching b = branch ba = branch -a # all branches (local + remote) bd = branch -d # delete local branch (safe, prevents accidental deletes) bD = branch -D # force delete bn = checkout -b # create new branch and switch br = branch -m # rename current branch # Checkout / switching co = checkout main = checkout main master = checkout master dev = checkout develop # Pushing / pulling / rebasing p = push pf = push --force-with-lease # safer force push (won't overwrite others' work) pu = push -u origin # push and set upstream pl = pull plr = pull --rebase # pull with rebase (cleaner history than merge) rb = rebase rbi = rebase -i # interactive rebase (squash, reorder, edit commits) # Cleaning up prune = fetch --prune # remove deleted remote branches from local tracking clean-merged = branch --merged | grep -v '\*' | xargs -n1 git branch -d # delete merged branches # Cherry-pick (pull specific commits) cp = cherry-pick cpc = cherry-pick --continue cpa = cherry-pick --abort # Stashing st = stash stl = stash list stp = stash pop std = stash drop # Diffing d = diff ds = diff --staged # diff of staged changes dw = diff --word-diff # word-level diff (good for prose) # Reset and discard reset-hard = reset --hard HEAD # NUCLEAR: discard all changes, back to HEAD reset-soft = reset --soft HEAD~ # undo last commit, keep changes staged reset-mixed = reset --mixed HEAD~ # undo last commit, keep changes unstaged (default) # Remotes rem = remote -v # list all remotes with URLs remotename = rev-parse --abbrev-ref --symbolic-full-name @{u} # show upstream branch name # Utility url = remote get-url origin # show git repo URL squash = rebase -i HEAD~ # squash last 2 commits (interactive rebase) root = rev-parse --show-toplevel # print root of git repo (useful in scripts) lines = diff --stat # show file changes (not line-by-line diff) [push] default = current # Only push to the current branch (not all branches with the same name) [pull] rebase = true # Default to rebase when pulling (cleaner history, no merge commits) [fetch] prune = true # Automatically prune deleted remote branches on fetch [rebase] autostash = true # Auto-stash changes before rebase, auto-pop after (safer rebase) [merge] conflictstyle = diff3 # Show 3-way merge conflicts (original, yours, theirs) for clarity [diff] colorMoved = zebra # Highlight moved lines in diffs (helps spot refactors) [color] ui = true branch = true diff = true status = true [filter "lfs"] clean = git-lfs clean -- %f smudge = git-lfs smudge -- %f required = true # Git LFS for large files (if you use it) [includeIf "gitdir:~/Builds/"] path = ~/.gitconfig-work # Conditional config: use work email for repos in ~/Builds folder

Now git st shows status, git ca amends the last commit, git plr pulls with rebase, git pf force-pushes safely. Every alias saves 5–10 characters; multiply by 50+ commits a week and you're saving hours every month.

Setup Script: One-Click Bootstrap

Put this in your dotfiles repo as install.sh. Run it on a new Mac and everything copies into place:

#!/bin/bash set -e echo "🚀 Aidxn Dotfiles Setup" # 1. Copy .zshrc echo "Installing .zshrc..." cp .zshrc ~/.zshrc source ~/.zshrc # 2. Copy Starship config echo "Installing Starship config..." mkdir -p ~/.config cp starship.toml ~/.config/starship.toml # 3. Copy .gitconfig echo "Installing .gitconfig..." cp .gitconfig ~/.gitconfig # 4. Install Homebrew (if not already installed) if ! command -v brew &> /dev/null; then echo "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi # 5. Install Homebrew bundle echo "Installing dev tools (Homebrew)..." echo "cp ~/Brewfile . (you need to provide this manually; it's machine-specific)" # brew bundle --file ~/Brewfile # 6. Install fnm and Node if ! command -v fnm &> /dev/null; then echo "Installing fnm..." curl -fsSL https://fnm.io/install | bash fi # 7. Reload shell echo "✨ Setup complete. Run 'exec zsh' to reload your shell."

Six FAQs

Should I use Oh My Zsh instead of plain Zsh?

Oh My Zsh is bloated (200+ plugins, 20MB download, 500ms shell startup tax). Plain Zsh + Starship + selective functions is faster and more maintainable. Oh My Zsh shines if you're customizing every week; for production setup, stay minimal.

fnm vs nvm vs asdf — which should I really use?

fnm for Node only (5MB, instant, Aidxn default). nvm for Node if you're on older Macs or want the most docs. asdf if you manage 3+ languages (Erlang, Python, Ruby, all one tool). Aidxn uses fnm (main) + asdf (fallback).

Do I have to use Git aliases in .gitconfig?

No, you can use shell aliases (alias gs='git status'). But .gitconfig aliases work in every shell (Bash, Zsh, Fish). Shell aliases are shell-specific. If you ever switch shells, .gitconfig aliases follow you.

Can I share my dotfiles repo publicly?

Yes, as long as you don't commit secrets (.env files, SSH keys, credentials). Keep secrets in a separate .env or use git update-index --skip-worktree to ignore changes to sensitive files. Aidxn's dotfiles are public — nothing sensitive lives there.

How do I keep dotfiles in sync across machines?

Clone the repo on every machine, then keep it updated with git pull. If you make changes on Machine A, commit + push, then pull on Machine B. Alternatively, use a symlink setup (symlink config files from ~/.dotfiles to ~/, so edits in either location sync automatically). For simplicity, Aidxn just pulls when jumping between Macs.

What if I want different Git config for work vs personal projects?

Use Git's includeIf directive (shown above in .gitconfig). Set a work email for repos in a specific folder, personal email elsewhere. When you commit in a work repo, Git auto-switches the config.

The Bottom Line

Dotfiles are the least glamorous dev productivity boost and the highest ROI. You spend 30 minutes setting them up once, and they save you 50+ hours over the next 2 years (shell startup, Git workflow, alias muscle memory). Aidxn's stack (fnm + Starship + sensible Git aliases + Homebrew bundle) is battle-tested across 5+ Macs and does exactly what a developer needs: fast, beautiful, portable. Your terminal should feel like home. These dotfiles are the config that makes that happen.

Building a team and everyone's on a different setup? Check out developer experience consulting to standardize across your org, or dive deeper into monorepo setup to pair fast shells with fast builds.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.