Personal dotfiles for macOS (Apple Silicon) and Windows 11. install.sh sets up macOS and install.ps1 sets up Windows; see docs/structure.md for which files each one links.
| Directory | OS | Contents |
|---|---|---|
zsh/ |
macOS | Zsh config with zinit, fzf (with fd), zoxide, atuin history |
powershell/ |
Windows | PowerShell 7 profile with PSReadLine, fzf (PSFzf), zoxide, atuin, fnm |
git/ |
Shared | Git config with SSH signing (1Password), delta pager, trunk-based defaults |
starship/ |
Shared | Custom two-line prompt (directory, git, nodejs, docker, duration) |
ghostty/ |
macOS | Terminal config in Ghostty format, read by cmux (Monaspice Nerd Font) |
windows-terminal/ |
Windows | Windows Terminal fragment: color schemes and font for the PowerShell profile |
atuin/ |
Shared | Atuin shell history config (daemon, directory-scoped up-arrow) |
claude/ |
Shared | Claude Code global settings, rules, skills, and templates |
homebrew/ |
macOS | Brewfile with formulas, casks, and fonts |
winget/ |
Windows | winget import list with CLI tools and apps |
scoop/ |
Windows | Scoop import list for what winget lacks (Monaspace Nerd Font, Railway CLI) |
bin/ |
macOS | User scripts symlinked into ~/.local/bin |
These live outside the repo, and nothing recreates them. Copy them somewhere safe, then put them back at the same paths after running the installer:
~/.gitconfig.localand~/.config/git/allowed_signers: git identity and signature verification~/.ssh/config: SSH host aliases- Uncommitted changes in this repo (
git status)
Both installers can be rerun safely: installed packages are skipped and correct links are left alone. Rerun yours after pulling changes too, since new links (such as ~/.gitconfig.os) only appear when the installer runs.
On a fresh Mac, the first git call prompts to install the Xcode Command Line Tools. Accept, and run the command again once they're installed.
mkdir -p ~/dev
git clone https://github.com/agnlez/dotfiles.git ~/dev/dotfiles
cd ~/dev/dotfiles
./install.shClone over HTTPS: the SSH key lives in 1Password, which isn't set up yet. Switch the remote to SSH once it is (see 1Password).
The install script will:
- Install Xcode Command Line Tools (if missing)
- Install Homebrew (if missing)
- Install all packages from the Brewfile (a failure doesn't stop the linking; rerun the script once it's fixed)
- Install the Node version pinned in
.node-versionvia fnm and make it the default - Install Claude Code via the native installer (self-contained, auto-updating)
- Create
~/dev, where all projects live - Symlink config files to their expected locations
- Back up any existing files to
~/.dotfiles-backup/
Turn on Developer Mode first (Settings → System → For developers), so symlinks can be created without admin rights. Then install git and PowerShell 7, which the installer itself needs:
winget install -e --id Git.Git
winget install -e --id Microsoft.PowerShellIn a new PowerShell 7 (pwsh) window:
git clone https://github.com/agnlez/dotfiles.git $HOME/dev/dotfiles
cd $HOME/dev/dotfiles
./install.ps1Run ./install.ps1 -WhatIf first to see what it would install, set and link without changing anything. Clone over HTTPS, as on macOS.
The install script will:
- Check that symlinks can be created (Developer Mode) and that winget is present
- Install the packages in
winget/packages.json, with--no-upgradeso reruns skip installed ones (upgrade withwinget upgrade --all) - Install scoop (if missing) and the packages in
scoop/scoopfile.json - Install the PSFzf module from the PowerShell Gallery
- Add
~/.local/binand pnpm's bin directory to the userPATHand setPNPM_HOME, since Windows keeps these as persistent per-user variables rather than in the shell profile - Install the Node version pinned in
.node-versionvia fnm and make it the default - Install Claude Code via the native installer
- Create
~/dev, symlink config files to their expected locations, and back up any existing files to~/.dotfiles-backup/
As on macOS, a failed package doesn't stop the linking.
The steps for both OSes come first, then the ones specific to each.
- Sign into 1Password and enable the SSH agent
- Add your SSH key to 1Password and register the public key on GitHub as both an authentication key and a signing key
- On Windows, stop and disable the "OpenSSH Authentication Agent" service (Services → Startup type: Disabled). It serves the same named pipe as 1Password's agent
- Switch the dotfiles remote to SSH:
git -C ~/dev/dotfiles remote set-url origin git@github.com:agnlez/dotfiles.git
The signing program comes from the per-OS file linked to ~/.gitconfig.os. On macOS, git/.gitconfig.macos uses op-ssh-sign at 1Password's default path (/Applications/1Password.app/...). On Windows, git/.gitconfig.windows calls op-ssh-sign.exe from PATH, and points git at Windows' OpenSSH, the only ssh that can reach 1Password's pipe. If 1Password is installed elsewhere, override program in ~/.gitconfig.local under [gpg "ssh"].
The installer creates ~/.gitconfig.local from git/.gitconfig.local.example on first run. It's loaded via [include] after ~/.gitconfig.os and overrides both, so machine-specific values (e.g. a non-default 1Password path) belong here too. Fill in:
nameandemailsigningkey: the private key stays in 1Password, so there's no key file to point at. Use the public key itself, prefixed withkey::(key::ssh-ed25519 AAAA…). Copy it from the key's item in 1Password, or list the agent's keys:- macOS:
SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock ssh-add -L. A plainssh-add -Lasks the macOS agent, which doesn't have it. - Windows:
C:\Windows\System32\OpenSSH\ssh-add.exe -L, once the OpenSSH service is disabled. Thessh-addthat ships with Git for Windows can't reach 1Password's pipe.
- macOS:
Then create ~/.config/git/allowed_signers with a you@example.com ssh-ed25519 AAAA… line per key you trust, so git can verify signatures. Check with a commit and git log --show-signature -1.
User-scope MCP servers live in ~/.claude.json, which isn't tracked: it's Claude Code's runtime state, holding caches, account details and server credentials. The servers themselves are listed in claude/mcp-servers.json, and both installers register each one with claude mcp, replacing any existing server of the same name. To add or change a server, edit that file and rerun the installer.
${HOME}in the file is replaced with your home directory at install time.- Servers started with
npxpass--prefix ${HOME}. Claude Code starts MCP servers from the project root, and npm refuses to run when that project'sdevEngines.packageManagernames another package manager (EBADDEVENGINES).--prefixmakes npm read your home directory instead, which has nopackage.json. - The Playwright server is registered here instead of through the
playwrightplugin, which is disabled insettings.json: the plugin startsnpxwithout--prefix, and its config can't be changed. install.ps1passescommandandargstoclaude mcp add, so onlystdioservers work on Windows.- Servers that need credentials, such as SonarQube, aren't in the file. Add them by hand with
claude mcp add -s user.
Sign into the rest (Slack, Spotify, etc.)
The podman-desktop cask installs only the app. Open Podman Desktop and complete its onboarding to install the podman CLI (into /opt/podman) and start a machine. The zsh docker alias and the bin/docker shim both forward to it.
When using a Logitech mouse, manage it with Mouser — a lightweight, fully local, open-source alternative to Logitech Options+ — instead of the official Logitech app. There is no official Homebrew cask; download Mouser-macOS.zip (Apple Silicon) from the releases page, extract, and move Mouser.app to /Applications.
The dotfiles fragment styles the PowerShell profile, but profiles.defaults in Windows Terminal's settings.json overrides it. Remove colorScheme and font from the defaults if they're there; install.ps1 warns when they are. Set PowerShell (not Windows PowerShell) as the default profile, which a fragment can't do.
Pre-commit formatting and linting are managed by prek (a Rust-based, drop-in pre-commit alternative) with oxfmt, oxlint, and the standard pre-commit-hooks. pnpm manages prek itself; oxc and pre-commit-hooks are pinned in .pre-commit-config.yaml.
After cloning, in ~/dev/dotfiles:
pnpm install # installs prek; prepare hook wires .git/hooks/pre-commit
git config blame.ignoreRevsFile .git-blame-ignore-revs # skip the bulk-format commit in git blameThe same hooks run in CI on every push to main and every PR (.github/workflows/lint.yml), using the prek version pinned in package.json. Two more CI jobs lint the scripts that no test runs: PSScriptAnalyzer for the PowerShell (settings in PSScriptAnalyzerSettings.psd1) and shellcheck for install.sh, claude/statusline.sh and bin/docker. Since commits go straight to main, CI flags a problem after the push rather than blocking it; the local hook is still the first line of defence.
fnm reads .node-version (Node 24); the standalone pnpm binary (Homebrew on macOS, winget on Windows) reads devEngines.packageManager in package.json (pnpm 12) and auto-switches to the pinned version.