Personal macOS dotfiles: zsh + Powerlevel10k, Neovim, Vim, Ghostty, Zed, git, and cmux.
git clone git@github.com:mzywang/dotfiles.git ~/.dotfiles
~/.dotfiles/bootstrap.sh # install Homebrew + packages (see packages.yaml)
~/.dotfiles/install.sh # symlink the configs into $HOMEThen open a new terminal.
Installs Homebrew (if missing), then every tap, formula, and cask listed in
packages.yaml — currently Neovim, Vim, zsh,
zsh-autosuggestions, and cmux. Safe to re-run; brew install is idempotent.
Symlinks each tracked dotfile into $HOME at its matching path
(.zshrc, .p10k.zsh, .vimrc, .gitconfig, .config/nvim/*,
.config/ghostty/config, .config/zed/settings.json, .config/cmux/cmux.json).
Anything already at those paths is backed up to ~/.dotfiles-backup/<timestamp>/
first. Also seeds ~/.zshrc.local (see Secrets below). Safe to re-run.
Secrets are not committed. .zshrc sources ~/.zshrc.local, which is
git-ignored. install.sh creates an empty template on a new machine — add your
own exports there, e.g.:
export PAGERDUTY_USER_API_KEY="..."bootstrap.sh installs Homebrew's zsh, but your login shell stays as the
system zsh until you switch it (needs your password):
echo "$(brew --prefix)/bin/zsh" | sudo tee -a /etc/shells
chsh -s "$(brew --prefix)/bin/zsh"packages.yaml is the single source of truth for Homebrew
dependencies. Add or remove entries there and re-run bootstrap.sh; it reads
the file directly (no yq required).
| Path | Purpose |
|---|---|
.zshrc |
zsh config (Powerlevel10k, autosuggestions, PATH, aliases) |
.p10k.zsh |
Powerlevel10k prompt config |
.vimrc |
Vim config |
.gitconfig |
git config (identity, delta pager, zdiff3 conflict style) |
.config/nvim/ |
Neovim config + lazy-lock.json plugin lockfile |
.config/ghostty/config |
Ghostty terminal config |
.config/zed/settings.json |
Zed editor settings |
.config/cmux/cmux.json |
cmux config (JSONC) |
.config/kanata/nuphy.kbd |
kanata config: NuPhy Air75 V3 Cmd-Tab block |
.config/kanata/nuphy_kanata_runner.sh |
waits for the NuPhy to appear, supervises kanata with nuphy.kbd, and restarts it on disconnect/reconnect, DriverKit wedges, or wake; used by the local.kanata.nuphy LaunchDaemon |
.config/kanata/kanata_unlock_watcher.sh |
user-session LaunchAgent that kickstarts kanata daemons after screen unlock |
.config/kanata/builtin_cmd_tab.kbd |
kanata config for the built-in keyboard when the NuPhy is disconnected: Cmd-Tab block, caps→esc, and a toggleable Colemak-DH layer (keep macOS on U.S. QWERTY) |
.config/kanata/toggle_builtin_colemak.sh |
toggles the built-in keyboard between QWERTY and Colemak via kanata's TCP server (builtin-colemak-toggle alias) |
.config/kanata/builtin_block.kbd |
kanata config: blocks every key on the built-in keyboard (used when the NuPhy is connected) |
.config/kanata/nuphy_builtin_keyboard_watcher.sh |
polls for the NuPhy and switches the built-in keyboard's kanata instance between the two configs above |
launchd/*.plist |
LaunchDaemon templates for kanata + its VirtualHIDDevice daemon (installed by kanata_setup.sh, not symlinked) |
packages.yaml |
Homebrew taps / formulae / casks |
bootstrap.sh |
Installs software from packages.yaml |
install.sh |
Symlinks configs into $HOME |
kanata_setup.sh |
One-time sudo setup: VirtualHIDDevice driver + LaunchDaemons for kanata |
install_builtin_watcher.sh |
Installs/reloads just the local.kanata.builtin-watcher daemon; re-run any time after editing the watcher plist or script (aliased as builtin-watcher-install) |
install_nuphy_runner.sh |
Installs/reloads just the local.kanata.nuphy daemon; re-run any time after editing the runner plist or script (aliased as nuphy-install) |
install_unlock_watcher.sh |
Installs/reloads the screen-unlock kanata restart agent (aliased as unlock-watcher-install) |
kanata blocks Cmd-Tab on the NuPhy via
.config/kanata/nuphy.kbd, scoped to the device by name (covers cable,
Bluetooth, and 2.4GHz dongle modes). Because the dongle can take a while to enumerate after
login, nuphy_kanata_runner.sh polls for the NuPhy before starting kanata
instead of launching it directly at boot (which used to fail repeatedly until
the device appeared). The runner supervises kanata in the background and
restarts it when the NuPhy disconnects, wedges in the DriverKit virtual-HID
wait loop, or after a system wake. kanata_unlock_watcher.sh (a user
LaunchAgent) kickstarts the daemons on screen unlock.
The built-in keyboard is owned by a second, separate kanata instance, but
unlike the NuPhy it can't just run one static config, since we want it to
behave differently depending on whether the NuPhy is around: normally it
should just block Cmd-Tab, but while the NuPhy is connected it should be
fully disabled (replicating Karabiner-Elements' old "disable built-in
keyboard while external keyboard is connected" toggle, so the laptop's own
keys can't double-type alongside the NuPhy). Kanata has no built-in notion of
"device A present → block device B", and macOS device-list config
(macos-dev-names-include) isn't live-reloadable anyway, so
.config/kanata/nuphy_builtin_keyboard_watcher.sh polls kanata --list
every few seconds for the NuPhy and swaps which config owns the built-in
keyboard: builtin_block.kbd (blocks every key) while it's connected,
builtin_cmd_tab.kbd (Cmd-Tab block, caps→esc, optional Colemak layer) while it's not. Only one process
can hold the device at a time, so the watcher always stops the previous one
before starting the other.
The built-in Colemak layer is Colemak-DH with ' on the p key and ; on the
key right of l (matching the NuPhy firmware layout, not macOS's Colemak ANSI
input source). Keep macOS on U.S. QWERTY and toggle with:
builtin-colemak-toggleThe watcher starts builtin_cmd_tab.kbd with a TCP server on port 7071 so
the toggle script can switch layers without restarting kanata. The chosen layer
is saved to .config/kanata/builtin_layer and restored automatically when the
built-in kanata instance restarts (for example after plugging the NuPhy back in).
Setup on a new machine, in order:
bootstrap.shinstalls kanata via Homebrew.install.shsymlinks the.kbdconfigs and the watcher script into~/.config/kanata/.- Run
./kanata_setup.shonce (needs sudo) — it installs the Karabiner-DriverKit-VirtualHIDDevice driver kanata uses for macOS key output, and registers kanata (NuPhy) plus the built-in-keyboard watcher as LaunchDaemons so they start at boot and restart if they ever crash. - Grant two permissions manually in System Settings → Privacy & Security
(macOS doesn't allow scripting these): add
/opt/homebrew/opt/kanata/bin/kanataunder both Input Monitoring and Accessibility, then restart the daemons:sudo launchctl kickstart -k system/local.kanata.nuphy sudo launchctl kickstart -k system/local.kanata.builtin-watcher
- Run
./install_unlock_watcher.shso kanata restarts automatically after screen unlock (installs a LaunchAgent and a sudoers drop-in for passwordlesslaunchctl kickstart).
Karabiner-Elements is not used at all in this setup and should not be installed alongside it — it ships a conflicting version of the same VirtualHIDDevice driver kanata depends on.