One repo that defines both Macs: ghost (Mac Studio, the always-on compute machine) and silence (MacBook Pro, the portable client). Config is shared through Stow. Programs are installed directly — no Homebrew. Each machine keeps its own secrets and app state.
- ghost — always on. Owns all projects, the OpenCode service, Postgres, the SSH host, and the Zed/Cursor remote servers. Work happens here.
- silence — portable client. UIs run here; files, tools, and agents act on ghost over Tailscale + SSH. silence is set up; its ongoing job is to stay in sync with ghost.
home/— the Stow package (dotfiles), target$HOMEscripts/doctor.sh— read-only health check for the current machinedocs/tools.md— how programs are installed and updated (read this before touching any tool)docs/workflows.md— the daily operating manual
Every CLI tool lives at ~/.local/opt/<tool>/ with symlinks to its
executables in ~/.local/bin, which .zshenv puts first on PATH. Runtime
state (Postgres data, logs) lives in ~/.local/var/. One exception: OpenCode
installs itself to ~/.opencode/bin.
To install or update any program — "install bun", "update nvim", anything — follow the playbook in docs/tools.md. It is the same procedure for every tool. This README and that file together are the source of truth: when you change a tool, update its entry in the inventory in the same commit.
cd ~/Developer/environment
stow --no-folding -n -t "$HOME" home # dry run — read it
stow --no-folding -t "$HOME" home # apply--no-folding links files but leaves parent directories real (~/.cursor
and the Cursor User directory must stay real; the apps write state into
them).
- Edit files under
home/in this repo. A GUI may rewrite a linked file (Zed settings, Cursor settings, OpenCode config): commit real setting changes,git restorethe noise. - Stowed files:
.zshenv(PATH),.zshrc,.ssh/config, Neovim config, Ghostty config, Zed settings, OpenCodeopencode.jsonc, Cursor rules andUser/settings.json. - Machine-specific values go in the gitignored
.localfiles, which the stowed configs source automatically:~/.zshrc.local(aliases),~/.zshenv.local(env vars),~/.ssh/config.local(SSH overrides). - On the other machine:
git pull— existing symlinks update in place, no re-stow needed. On a machine without the links yet, run Stow after pulling. - If the dry run reports a conflict, move that file out of
$HOMEand retry.
Pin a version, put the program in ~/.local/opt/<tool>, symlink its entry
points into ~/.local/bin, record the version in the tools inventory,
verify with doctor.sh, repeat on the other machine. Full procedure:
docs/tools.md.
The five tarball tools (nvim, pnpm, bun, node, gh) are scripted:
scripts/sync-tools.sh installs or updates them to the pinned versions on
whichever machine it runs (--check is read-only; doctor uses it). stow
and postgres (source builds) and opencode (self-updating) stay manual by
design.
cd ~/Developer/environment && scripts/doctor.shA clean run means the Stow tree is settled and this machine's checks passed. Run it after every change, and on both machines.
.gitignore blocks the copies that would land inside home/:
- SSH private keys,
known_hosts,authorized_keys - the
.localoverride files - OpenCode
service.json,auth.json, and~/.local/share/opencodedata - Cursor
argv.json(per-machine crash-reporter id),cli-config.json, plugins, extensions, project caches, and other app state the apps rewrite
- No Homebrew. Every tool follows the direct-install pattern, or documents
its exception in
docs/tools.md. - Docs are part of the system. Any tool change updates the inventory in
docs/tools.mdin the same commit. - Secrets and app state never enter the repo. Machine-specific config goes
in the
.localfiles. - ghost and silence run the same tool versions. Doctor runs clean on both.