Every non-hidden top-level directory is a stow package whose
contents mirror $HOME — zsh/.zshrc becomes ~/.zshrc — and one interactive ./install links the
lot. macOS on Apple Silicon only: every Homebrew path hardcodes /opt/homebrew.
- Leaf-only symlinks —
--no-foldingkeeps~/.sshand~/.claudereal directories, so runtime state and secrets never land in the repo - One interactive installer —
./installprompts per step and per package, from a commentedBrewfile - Catppuccin Frappé everywhere — Ghostty, Neovim, bat, btop, lazygit, delta, fzf
- Private by default — telemetry opt-outs in
.zprofile, allowlist.gitignores for credentials - No CI, no tests —
zsh -n installplus astow -ndry run, wrapped in the/dotfiles-checkskill
git clone https://github.com/itsdanieldk/dotfiles.git ~/dotfiles
cd ~/dotfiles
./installSeven prompted steps, in order: Xcode Command Line Tools, Homebrew, Brewfile packages (taps first,
then formulae, then casks, skipping what's installed), stow, Oh My Zsh, Powerlevel10k and three
plugins, macOS defaults. A btop theme seed runs unprompted after stow, only if there's no
~/.config/btop/btop.conf.
| Flag | Effect |
|---|---|
--yes / -y |
Auto-yes every prompt except the security-weakening ones, which are skipped with a warning. Also sets NONINTERACTIVE=1 for the Homebrew bootstrap, which then uses sudo -n — prime it with sudo -v |
--adopt |
Resolve stow conflicts by adopting $HOME files into the repo. Destructive — it overwrites tracked configs, so review git diff afterwards |
--allow-insecure |
Also auto-yes the security-weakening prompts. Only meaningful with --yes |
- arm64 is a hard requirement —
installchecksuname -mand exits 3 on an Intel Mac rather than half-installing in silence - It's zsh, not bash —
read -q,print,*(/); syntax-check withzsh -n install, never shellcheck - Stow runs before Oh My Zsh — OMZ's
--keep-zshrconly protects a~/.zshrcthat already exists, so on a clean machine it would write its own template and the stow step would then die on the conflict - One prompt weakens a security control — it disables the Gatekeeper "downloaded from the internet" warning (
LSQuarantine). AnswerNto keep it;--yesdeclines it automatically - Upstreams are unpinned — both bootstraps, Powerlevel10k and the plugins track a default branch, so a compromised upstream runs as your user. Both bootstraps verify the download before running it. Install Homebrew and Oh My Zsh yourself first if you'd rather not take the trade;
installskips both when present - Failures warn rather than abort — only an unknown flag (exit 2), a stow conflict (exit 1) and a non-arm64 machine (exit 3) stop the run
| Package | Contents |
|---|---|
bat |
bat config |
btop |
btop theme — install seeds color_theme separately, because btop rewrites its own config on exit and would write through the symlink |
claude |
Claude Code settings and statusline |
ghostty |
Ghostty terminal (Fira Code Nerd Font Mono) |
git |
.gitconfig with the delta pager, plus the global gitignore. Repos under ~/work/ use a separate identity from an untracked config-work; git ignores the includeIf when it's absent |
hushlogin |
Suppresses the "Last login" message |
lazygit |
lazygit config (delta diff renderer) |
nvim |
Neovim — zero plugins, no lockfile; init.lua and a vendored colorscheme |
ssh |
SSH config with macOS Keychain and hardened algorithms. Machine-local hosts go in the untracked ~/.ssh/config.local, included first |
zsh |
.zshrc (Oh My Zsh + Powerlevel10k, fzf, zoxide, direnv, aliases), .p10k.zsh, .zprofile |
.claude/ is this repo's own tooling — the dotfiles-check and new-package skills — not a $HOME
config. It needs no exclusion: the installer globs *(/), which doesn't match dotted directories.
zsh expects ten Oh My Zsh built-ins and three external plugins that install clones. Their order
in plugins=() is load-bearing.
| Plugin | What it does |
|---|---|
| fzf-tab | Replaces tab completion with an fzf menu, with bat and eza previews |
| zsh-autosuggestions | Fish-like suggestions from history; → accepts |
| zsh-syntax-highlighting | Valid commands green, invalid red |
The Brewfile is a catalogue, not a manifest — install prompts per entry and doesn't
remember a decline. A hand-rolled regex parses it instead of brew bundle, so the verb "name"
shape is load-bearing: any other line is skipped silently. Two taps are declared, azure/functions
and azure/bicep, each granted trust as it's added and each kept above the packages drawn from it.
| Group | Formulae |
|---|---|
| Core | stow, git, neovim |
| Modern CLI replacements | bat, dust, eza, fd, ripgrep, zoxide |
| Shell utilities | direnv, fzf, tealdeer, tree |
| Data processing | jq, yq |
| System monitoring | btop, fastfetch |
| Git | gh, git-delta, lazygit |
| Containers | docker, docker-compose, lazydocker |
| Languages & runtimes | mise, elixir, elm, node, pnpm, powershell — mise owns the project node; node is declared only because azurite and marp-cli depend on it |
| Azure | azure-cli, azure-dev, azure-functions-core-tools@4, azurite, bicep |
| Authoring & linting | marp-cli, shellcheck |
| Group | Casks |
|---|---|
| Fonts | Fira Code Nerd Font, Fira Sans |
| Terminal & editors | Ghostty, JetBrains Toolbox, VS Code |
| Dev tooling | .NET SDK, OrbStack, Yaak |
| Windows compatibility | CrossOver — the one entry that still needs Rosetta 2 |
| AI | Claude, Claude Code, Copilot CLI |
| Notes & productivity | Obsidian |
| Browsers & media | Chrome, IINA |
| Communication & remote access | Discord, TeamViewer |
| System & hardware | Focusrite Control 2, logi-options+, macs-fan-control, MonitorControl, OnyX, Philips Hue Sync, Thaw |
Five groups, each prompted separately — plus the Gatekeeper prompt above, which runs between Finder and Dock:
- Finder — extensions, path and status bars, list view, folders on top, search scoped to the current folder, visible
~/Library, no.DS_Storeon network or USB volumes - Dock — minimize into the app icon, no recents, fixed tile size with magnification off, Spaces stay put, faster Mission Control
- Keyboard — key repeat faster than the sliders can express (
KeyRepeat=1.5,InitialKeyRepeat=12, in 15 ms ticks), every text substitution off because smart quotes corrupt pasted code, full keyboard access. Takes effect after a logout - Screenshots — PNGs to
~/Pictures/Screenshots, no drop shadow - Miscellaneous — save and print panels expanded by default
.zprofile opts out of telemetry for every tool here that collects it, in one auditable block.
| Variable | Tool |
|---|---|
DOTNET_CLI_TELEMETRY_OPTOUT=1 |
dotnet CLI |
FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT=1 |
Azure Functions Core Tools |
AZURE_CORE_COLLECT_TELEMETRY=0 |
azure-cli |
AZURE_DEV_COLLECT_TELEMETRY=no |
azd |
HOMEBREW_NO_ANALYTICS=1 |
Homebrew |
These reach anything launched from a shell that sourced .zprofile — including VS Code's integrated
terminal, but not apps launched from Finder. brew analytics off is the persistent equivalent.
Two outbound calls aren't covered, both from the vendored
statusline.sh, on every statusline render.
| Call | Frequency | Notes |
|---|---|---|
api.anthropic.com/api/oauth/usage |
≤ every 60s, cached | Your usage quota. Sends an OAuth token from the Keychain, passed to curl via --config on stdin so it never shows in ps |
api.github.com/.../releases/latest |
≤ every 24h, cached | Upstream update check. Disable with STATUSLINE_CHECK_UPDATES=false |
Only the update check is switchable — the usage call is what the statusline exists to display. It
inherits Claude Code's environment, so .zprofile covers a shell-launched Claude Code but not one
started from Finder; use launchctl setenv for that.
- A new file in an existing package needs a re-stow —
stow -d ~/dotfiles --no-folding -R <package>. Editing an existing file takes effect immediately; adding one does nothing until you re-stow - Apps rewrite their own stowed configs — the write goes through the symlink and lands as an unexplained modification in
git status. lazygit did it renaminggit.pagers; OrbStack did it twice. Checkgit statusafter installing anything that manages its own config .zshrcload order is load-bearing — p10k's instant prompt first with nothing writing to stdout before it, andfzf-tabbeforezsh-autosuggestionsandzsh-syntax-highlighting, or completion and highlighting break without saying so- The Brewfile is read from FD 3 —
ask()'sread -qconsumes stdin, so a plain< Brewfilemakes every prompt eat the next line and silently skip packages - Two ignore files, different scopes —
/.gitignoreis allowlist-based, so a tracked file added tossh/orclaude/needs a matching!line or git never sees it;git/.config/git/ignoreapplies to every repo on the machine. Neither has trailing-comment syntax
Public domain under The Unlicense, with one exception:
claude/.claude/statusline.sh is vendored from
ClaudeCodeStatusLine and stays MIT,
Copyright (c) 2025 Daniel Oliveira; its notice is retained in the file header.