Skip to main content

Packages

This setup treats package installation as declarative.

That means:

  • you edit a list (Brewfile / cargo crates / go pkgs / gems / yarn / uv tools)
  • you run chezmoi apply
  • hooks install missing items and (in some systems) remove items no longer listed

The core workflow is:

  1. Edit the list file in home/
  2. Run chezmoi apply
  3. Verify the tool is installed / available

Package sources at a glance

SourceList fileHookScoped
Homebrewhome/.chezmoitemplates/brews/home/readonly_dot_Brewfile.tmplrun_onchange_after_03-install-brew-packages.fish.tmplYes
misehome/dot_config/mise/config.toml.tmplrun_onchange_after_05-install-mise-runtimes.sh.tmplYes
Cargohome/readonly_dot_default-cargo-cratesrun_onchange_after_05-update-cargo-crates.sh.tmplNo
Gohome/readonly_dot_default-golang-pkgs.tmplrun_onchange_after_05-update-golang-pkgs.sh.tmplYes
Ruby gemshome/readonly_dot_default-gemsrun_onchange_after_05-update-gems.sh.tmplNo
yarnhome/readonly_dot_default-yarn-pkgsrun_onchange_after_05-update-yarn-pkgs.sh.tmplNo
uv toolshome/readonly_dot_default-uv-tools.tmplrun_onchange_after_06-update-uv-tools.sh.tmplYes
gh extensionsrun_onchange_after_05-install-gh-extensions.fish.tmpl
Custom (GitHub/source)home/readonly_dot_default-custom-packages.tmplrun_onchange_after_05-install-custom-packages.sh.tmplYes

"Scoped" means the list is a chezmoi template that can branch on .isWork. All hooks live under home/.chezmoiscripts/.

Scope-aware package lists

Some package sources are plain lists and apply everywhere (cargo, yarn, gems). Others are templates or split source files that branch on chezmoi data like .isWork.

Example (Go, personal-only):

{{ if ne .isWork true }}
github.com/owner/personal-only-tool
{{ end -}}

Homebrew (Brewfile)

The hook runs:

  • brew bundle cleanup --global --force
  • brew bundle --global

So the assembled Brewfile acts like the source-of-truth.

If you are new to this style: it is closer to "infrastructure as code" than "I installed a thing once".

The Brewfile also carries day-to-day terminal utilities, network diagnostics such as NetWatch, browser casks such as Arc, Dia, Brave, and Zen, and personal-only casks such as Roblox.

Some Homebrew formulae are deliberately installed with link: false when their binaries collide. For example, GNU parallel and Ataraxy semantic-git sem both provide a sem binary, so both formulae stay unlinked. The commands are exposed through managed wrappers at home/exact_bin/executable_parallel and home/exact_bin/executable_sem.

mise (Tool Versions)

That hook:

  • runs mise install --yes to converge configured runtimes
  • runs mise reshim to refresh shims after runtime/package updates
  • respects project-level .tool-versions files when present
  • enables .nvmrc support for Node via idiomatic_version_file_enable_tools = ["node"]

If you only adopt one idea from this setup, make it this: pin your tool versions so projects behave consistently across machines.

Cargo crates

This hook installs missing crates and uninstalls crates not in the list.

Go tools

This hook installs missing tools and reconciles installed binaries against the list: when a module is removed, its binary is deleted from GOBIN. A state ledger (~/.cache/chezmoi/golang-pkgs-state) ensures only binaries this tooling installed are removed; hand-installed Go binaries are left untouched. See Add A Go Tool for details.

Ruby gems

Global yarn packages

The ,install-yarn-pkgs command installs missing listed packages, uninstalls packages no longer listed, then runs yarn global upgrade --latest. This keeps managed globals current even when Yarn's recorded semver range would otherwise stay on an older 0.x minor.

This list now includes some AI tooling that used to be managed elsewhere. Pi-related globals such as @earendil-works/pi-coding-agent, @earendil-works/pi-tui, pi-mcp-adapter, and pi-subagents are kept here; Pi settings reference the yarn global node_modules paths for the pi-mcp-adapter and pi-subagents extensions so Pi itself does not try to manage extension updates via npm.

If you do not want global yarn packages, keep the list empty.

uv (Python versions + global tools)

Python versions:

Global tools:

GitHub CLI extensions

This keeps a managed list of extensions installed and removes extensions that are no longer listed.

Manual packages (GitHub releases / DMGs)

Some tools and apps are installed from GitHub releases.

That installer supports:

  • DMG apps copied to /Applications
  • single-file binaries into $HOME/.local/bin
  • .tar.gz archives with a single binary

Use this for tools that are not available in Homebrew or when you need to pin a specific release asset.

It also downloads a couple extra tools directly inside the installer.

At the moment, that includes:

  • ytsurf (downloaded into ~/.local/bin)
  • amp (installed via its upstream install script if not already present)

Verification And Troubleshooting

High-signal checks:

brew bundle check --global
mise ls --current
uv tool list
yarn global list

If a package disappeared unexpectedly after apply:

  • check whether it is declared in the correct source list/template.
  • check whether the corresponding hook ran successfully.
  • for Homebrew specifically, remember brew bundle cleanup --global --force removes entries not present in the Brewfile.