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.