Personal macOS dotfiles, deployed through Home Manager out-of-store bridges.
All fourteen packages are listed in .home-manager-packages; .stow-packages
has no active entries. Legacy GNU Stow compatibility remains for recovery,
but Stow is no longer part of the normal bootstrap. See the
current migration checkpoint and recovery.
Bootstrap is intentionally explicit — no target here installs Homebrew, unlocks 1Password, or grants macOS accessibility permissions on your behalf. Do the following by hand, once per machine, as required for the full setup:
- Xcode Command Line Tools —
xcode-select --install. Suppliesgit,/usr/bin/python3(whichscripts/doctorand the unit tests use), and the linker the Brewfile depends on. - Homebrew — install per
brew.sh.
make brewthen reads the Brewfile; it never bootstraps Homebrew itself. - 1Password + SSH agent — install the desktop app, unlock the
vault, and enable the SSH agent (Settings → Developer →
"Use the SSH agent"). Commit signing requires it and stops on
failure. The secret scanner runs without it. Never pass
--no-gpg-sign. - Private font — Berkeley Mono — Ghostty, the sole supported
terminal, pins Berkeley Mono at size 10 (see
AGENTS.md). Install the.ttfpayload from your licensed download; the Brewfile cannot distribute it. - App permissions — Rectangle, Keyboard Maestro, Alfred, Keycastr, and Superwhisper each need Accessibility / Input Monitoring granted in System Settings → Privacy & Security after their first launch.
- Volta ownership of the JS toolchain — Volta is the sole
owner of
node,pnpm,yarn, and anyvolta installed binaries. Do not install Node from a.pkg, fromnvm, or frombrew install node; the shims will fight over PATH.scripts/runtime-versions.envrecords the pinned defaults (currently Node 22.14.0, pnpm 10.15.0, yarn 4.4.0). To adopt a new version, edit that file and runvolta install <tool>@<version>—setup-toolsnever rewrites an existing default silently.
git clone git@github.com:jthodge/tilde.git ~/tilde
cd ~/tilde
make brew # install the Homebrew packages the Brewfile declares
scripts/setup-tools --check # report toolchain plan, mutate nothing
make tools # explicit: bootstrap Volta node/pnpm/yarn if absent
make # preview seed actions; the Stow manifest has no active entries
make switch # seed app-owned configs; does not activate Home Manager
make plugins # explicit: init submodules + install TPM pluginsNext, explicitly install Determinate Nix if needed, then follow the
Home Manager build, preview, and activation procedure.
make home-generation builds and inspects the candidate without activation;
make switch does not deploy the Home Manager packages. After activation:
make doctor # JSON report on stdout, human summary on stderr
make check # verify the live $HOME against this checkoutA Makefile wraps the workflow. Run make help for the full target
list. make on its own is always safe: it simulates and writes
nothing. make brew, make tools, and make plugins can install
software and require an explicit invocation. No default target chains
into them. make tools preserves existing Volta defaults and only
creates ~/.venv/base if absent.
.home-manager-packages records packages transferred to Home Manager's
out-of-store bridge; home.nix defines their links. .stow-packages remains
for legacy recovery workflows but currently has no active deployment entries.
A package must not appear in both lists. The scripts/ directory is intentionally
not deployed — its contents are invoked in place.
Fresh-machine limit.
make home-generationvalidates Home Manager's generated file coverage locally, but this repo is not tested against a separate fresh macOS install end-to-end. Expect to reconcile app-owned config drift (seemake check) and permission prompts on first launch.
Three diagnostics answer different questions:
make home-generation(scripts/check-home-generation) — does the built Home Manager generation cover every Home-Manager-owned tracked file? Builds the activation package, inspects itshome-filestree, and writes nothing to$HOME. This is the closest local proxy for a fresh Home Manager bootstrap.make check(scripts/check) — does the deployed$HOMEmatch its ownership rules? Checks repository-owned links and seed-only app-owned files. See configuration ownership. It reports:MISSING— the expected target is absent.DRIFT— the target exists but resolves elsewhere. An application replaced a repository-owned link with a real file, or a seed-only file has the wrong ownership.UNDECLARED— a package-like tracked directory absent from both deployment manifests, so a fresh bootstrap would skip it. Duplicate declarations fail. For linked files, this checks resolved source identity, not which manager created the link; inspect the Home Manager generation and link chain separately.
make doctor(scripts/doctor) — does the surrounding environment satisfy the prerequisites? Emits a JSON report on stdout and a short human summary on stderr. Reports required tools, optional editor tools separately, git submodule initialisation, TPM plugin installation, and every alternative copy of each tool on PATH (to surface shadowing). The doctor never executes a discovered tool, never sources shell startup, never reads credentials, and never mutates the tree. Requirespython3from the Xcode Command Line Tools.make capabilities— do installed language tools work on synthetic inputs? Explicitly runs test runners, formatters, compilers and LSP initialization; installs nothing. Required failures return nonzero. See development capabilities, including the project-interpreter override and the known missing base-environment pytest.
make brew-diff is the package-inventory equivalent: it lists
formulae and casks that are installed but that the Brewfile does
not declare.
make test-tools # explicit: install ShellCheck and pinned Pyright
make verify # lint, typecheck, tests; no provisioning
make smoke # optional full-init Emacs check with installed packagesSee verification scope and limitations, shell startup, Emacs workflow, and terminal workflow. Adoption decisions and open checks records the earlier audit rationale. The Nix migration supersedes its decision to retain Stow as the long-term deployment mechanism, while preserving current application and runtime ownership. See reviewed upgrades and recovery before changing installed packages or migrating seed-only local settings.
GitHub treats SSH keys in two independent categories on your account:
Authentication keys (used for git push / git fetch over SSH) and
Signing keys (used to verify commit signatures). A key registered
under one does not count for the other.
Register ~/.ssh/id_ed25519.pub under both so that:
git pushover SSH works (authentication).- Commits signed locally via
op-ssh-signrender a green Verified badge on github.com (signing).
Same public key bytes; two separate entries.
At https://github.com/settings/keys, for each category:
- Click New SSH key.
- Title:
id_ed25519 (authentication)orid_ed25519 (signing). - Key type: select Authentication Key or Signing Key to match.
- Key: paste the contents of
~/.ssh/id_ed25519.pub. - Save. Repeat for the other category.
gh auth refresh -h github.com -s admin:public_key,admin:ssh_signing_key
gh ssh-key add ~/.ssh/id_ed25519.pub --type authentication --title "id_ed25519 (authentication)"
gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "id_ed25519 (signing)"The allowlists are independent so that a read-only deploy key (auth only, often on shared infrastructure) cannot be misused to forge "verified" commits in your account, and a hardware-token-bound signing key never doubles as an inbound auth vector.
Pi is the polyglot agent harness installed alongside Claude Code. Managed via Volta for parity with the rest of the JS toolchain:
volta install @earendil-works/pi-coding-agentCheck both the installed package's Node engine requirement and Volta's package-specific runtime before upgrading. The reviewed Pi 0.85.1 package requires Node ≥ 22.19.0; its Volta platform selects Node 24.18.0 independently of this repo's user default, Node 22.14.0. Do not change the user default just to make those two runtimes match. Package-manager metadata and installed payload versions can also drift; see the reassessment.
Authenticate via OAuth subscriptions rather than API keys:
pi
# inside the TUI:
/login # pick "Claude Pro/Max", complete browser OAuth
/login # pick "ChatGPT Plus/Pro (Codex Subscription)", complete browser OAuthTokens land at ~/.pi/agent/auth.json (runtime state, not tracked).
Billing note: third-party harnesses like Pi do not consume Claude Pro/Max plan limits. Usage is billed per token as "extra usage" — enable at https://claude.ai/settings/usage before invoking Anthropic models from Pi.
The git package ships a staged-secret pre-commit hook at
git/.config/git/hooks/pre-commit. After Home Manager activation, ~/.gitconfig
points core.hooksPath at ~/.config/git/hooks, making the scanner
active for every commit on this host. Bypass requires the explicit
git commit --no-verify escape after manual review.