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 are downloaded in full before running, so a truncated download never executes, but nothing checks their integrity. 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 and the Git LFS filter, 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, git-lfs, 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-dev, azure-functions-core-tools@4, azurite, bicep |
| Authoring & linting | actionlint, marp-cli, shellcheck |
| Group | Casks |
|---|---|
| Fonts | Fira Code Nerd Font, Fira Sans |
| Terminal & editors | Ghostty, JetBrains Toolbox, VS Code |
| Dev tooling | Azure CLI, .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 |
| Games | Battle.net, Steam |
| Communication & remote access | Discord, TeamViewer |
| System & hardware | Focusrite Control 2, iStat Menus, logi-options+, 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, 64px tiles magnifying to 106px, 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.
The vendored statusline.sh makes two
outbound calls on statusline renders; only the first is live.
| 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. Off — STATUSLINE_CHECK_UPDATES=false in claude/.claude/settings.json's env, which reaches the statusline however Claude Code was launched |
The usage call stays: it is what the statusline exists to display.
- A new file in an existing package needs a re-stow —
stow -d ~/dotfiles -t ~ --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; Claude Code does it on every/modeland/effort, rewritingmodelandmodelSettingsinclaude/.claude/settings.json. 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.