Dotfiles agent recipes
Task-triggered project instructions moved from AGENTS.md. Load this complete page before adding packages, changing shell/helper architecture, or adding/updating deployed commands. These recipes preserve the existing installation priority, source ownership, documentation and completion requirements.
Package/App/Formula/Cask Installation Priorityโ
When user requests to "add X" (app, package, cask, formula, or CLI tool), follow this priority order:
- Brewfile (per-category partials under
home/.chezmoitemplates/brews/, assembled intohome/readonly_dot_Brewfile.tmpl) โ macOS apps/formulas/casks via Homebrew - Cargo (
home/readonly_dot_default-cargo-crates) โ Rust packages - Go (
home/readonly_dot_default-golang-pkgs.tmpl) โ Go packages - Gems (
home/readonly_dot_default-gems) โ Ruby packages - pnpm (
home/readonly_dot_default-pnpm-pkgs) โ Node.js/JavaScript packages - uv (
home/readonly_dot_default-uv-tools.tmpl) โ Python tools/packages - Custom packages (
home/readonly_dot_default-custom-packages.tmpl) โ DMGs + GitHub release CLI tools. Installed byhome/.chezmoiscripts/run_onchange_after_05-install-custom-packages.sh.tmpl.
Interpretation of the priority:
- A
# @install-priority-exception: prefer=<manager>; skip=<manager>; reason=<reason>comment immediately preceding a package entry overrides the default priority for that entry. Keep the package in theprefermanager and bypass eachskipmanager. Re-evaluate the directive only when the user explicitly requests it. - When no exception directive applies, prefer Homebrew first when a formula/cask is verified to install the requested upstream project. This applies even if the Homebrew package name differs from the repo slug.
- When no exception directive applies, lower-priority package lists (
cargo,go,gems,pnpm,uv, manual packages) are fallbacks only. Use them only when Homebrew does not provide a suitable package. - Choose the verified Homebrew package when only its name differs from the upstream/repo slug.
Workflow:
- Identify X โ app, CLI tool, library, or language-specific package.
- Check for an exception first โ search the package lists for the canonical package/project and honor any
@install-priority-exceptiondirective immediately preceding its entry. - Check GitHub โ find the official repo when one exists; read README, INSTALL, and releases to learn supported installs; verify package name, owner, version, and status.
- Check registries in priority order:
- Homebrew first: search likely names with
brew search <term>and verify candidates withbrew info <formula-or-cask>. Test repo name, normalized name,<name>-cli, collapsed owner/repo names, and official tap names. - Cargo:
cargo search <package> --limit 5โ for Rust packages - Go: verify the import path on pkg.go.dev or the official repository; for installability, use
go install <import-path>@latestor a versioned equivalent. - Gems:
gem search <package>โ Ruby packages - pnpm:
pnpm view <package>โ Node.js/JavaScript packages - uv: verify the package on PyPI or the official repository; for tool installability, use
uv tool install <package>. Do not useuv pip search; currentuvdoes not support that command. - Manual (.dmg / release asset): verified GitHub releases
- Homebrew first: search likely names with
- Stop at the first suitable match and add it to that location only.
- Use verified package names, URLs, and sources. Ask the user when verification cannot resolve them.
- Use existing patterns for the target file.
Script Architecture: Shell vs Dedicated Languagesโ
Shell scripts (.sh / .sh.tmpl) in this repo must stay thin orchestrators.
Non-trivial logic belongs in colocated scripts written in an appropriate language (Python, etc.) under scripts/.
Rules:
- Shell is for glue only: sourcing
chezmoi_lib.sh, resolving paths, evaluating chezmoi template variables, and calling external programs. It may also wire inputs/outputs between those programs. - Move logic out of shell when it involves: data transformation (JSON, YAML, TOML parsing/generation), string manipulation beyond simple variable expansion, or conditional structures more than a few lines deep. Also move anything that would benefit from real data structures, error types, or testability.
- Colocate helper scripts in
scripts/: name them after the data or task they handle (e.g.,scripts/generate_mcp_configs.py,scripts/merge_claude_mcp.py). The shell.tmplscript calls them, passing file paths or piped data. - No external dependencies in helper scripts: use only the standard library of the chosen language.
The existing
scripts/yaml_parser.pyandscripts/generate_mcp_configs.pyhand-parse YAML without PyYAML โ follow that precedent. - Existing precedent:
scripts/chezmoi_lib.sh(shared shell helpers),scripts/generate_mcp_configs.py, andscripts/merge_claude_mcp.py.
When writing or modifying a chezmoi script (home/.chezmoiscripts/):
- If the script is already pure shell glue calling a
scripts/helper, keep it that way. - If the change would add more than ~10 lines of non-trivial logic to the shell script, extract it into a new or existing
scripts/helper instead. - When creating a new
scripts/helper, follow the existing style: shebang, docstring/usage, andsys.exiton bad args. Read from file paths passed as arguments, and write to stdout or a target path.
Bin Commands & Shell Completions (Mandatory)โ
Repo-owned standalone user commands MUST use the comma namespace.
- Source files live in
home/exact_bin/executable_,<name>and deploy to~/bin/,<name>; the leading comma is part of the command name. - Do not add unprefixed standalone commands under
~/bin.
Commands are self-contained across deployed $HOME surfaces.
~/bin/ scripts cannot call repo-only scripts/ helpers because scripts/ is not deployed to $HOME.
For commands over ~200 lines or with multiple logical subsystems, keep ~/bin/,<name> as a thin launcher.
Move internals into home/exact_lib/exact_,<name>/ (deployed to ~/lib/,<name>/).
home/exact_lib/ intentionally owns the sibling ~/lib command-internals tree.
Small single-purpose commands may stay directly in home/exact_bin/.
Whenever you add or update a ~/bin/ command, you MUST add/update its shell completion in the same change:
- Fish (primary, required):
home/dot_config/fish/completions/readonly_,<name>.fishโ~/.config/fish/completions/,<name>.fish. Declare every flag (complete -c ,<name> -s o -l output -d "โฆ" -r) and positional/argument completion. Mirror the script's actual interface (--helpoutput is the source of truth). Follow existing files likehome/dot_config/fish/completions/readonly_,pdf-diff.fish(flags + positional) andhome/dot_config/fish/completions/readonly_,appid.fish(dynamic-aargument list). - Zsh (only when warranted):
home/dot_zsh/completions/readonly__comma_<name>(#compdef ,<name>). Only complex commands (e.g.,w,,wh) carry a zsh completion; do not add one unless the command needs zsh-specific completion. - Keep completions in sync on updates: when a command's flags or arguments change, update the completion file in the same change.
A
~/bin/command added or changed without its completion is incomplete.
When adding a new ~/bin/ command or home/exact_lib/exact_,<name>/ command library, also update its catalog row under docs/topics/workflow/custom-commands/.
This follows Documentation Hygiene.
Homebrew Package Managementโ
When adding formulas or casks:
- Brewfile location: per-category partials live under
home/.chezmoitemplates/brews/NN-<category>.brewfile.home/.chezmoitemplates/brews/_assemble.brewfileassembles them into the single deployedhome/readonly_dot_Brewfile.tmpl. Add thebrew/caskline to the matching category file. Create the category file if it does not exist yet and add a matchingincludeTemplateline inhome/.chezmoitemplates/brews/_assemble.brewfile. - Verify on GitHub first: read the official repo's README, INSTALL, or releases to confirm Homebrew support and identify the correct formula/tap.
- Search GitHub: look for official Homebrew taps such as
owner/homebrew-tapwhen a tap is needed. - Verify locally with
brew info <formula>orbrew info <owner/tap>/<formula>before editing the Brewfile. - Check name variations with
brew search <term>before falling back to lower-priority registries. - Use homebrew-core, official project taps, or a community tap only after verifying its repository and formula source.
- If verification fails, report findings instead of guessing.
Manual App Installation (Non-Homebrew)โ
When a macOS app is not available via Homebrew but provides a .dmg release:
- Verify DMG source: Confirm the official GitHub release asset or an HTTPS DMG linked by the publisher.
- Add registry entry: Use
home/readonly_dot_default-custom-packages.tmpl. - Use DMG format:
dmg|App Name|owner/repo|release-tag|AppName.app|asset-pattern. For direct downloads:dmg|App Name|https://.../app.dmg|latest|AppName.app|.dmg. - Installer handles:
- Latest release download from GitHub API
- DMG mounting and app copy to /Applications
- Already-installed checks
- Cleanup on failure/success
Example:
dmg|Squirrel Disk|adileo/squirreldisk|latest|SquirrelDisk.app|.dmg
Best practices:
- Use exact .app bundle name from mounted volume
- Run the relevant installer/apply check before relying on the entry.
CLI Tool Installation (Non-Homebrew, Non-DMG)โ
When a CLI tool is not available through higher-priority package managers and is distributed via GitHub releases:
- Verify CLI source: Confirm the official GitHub repo and the binary/archive asset for the target OS and architecture.
- Add registry entry: Use
home/readonly_dot_default-custom-packages.tmpl - Prefer release assets: Use
file|...(single binary),tar_gz_bin|...(archive with a binary), orzip_opt|...(zip that must keep sibling dylibs under~/.local/opt) - Template variables: Use
$os/$archblocks to pick per-platform assets
Example entry:
file|kitten|kovidgoyal/kitty|latest|kitten-darwin-arm64|kitten
tar_gz_bin|mdtt|szktkfm/mdtt|v0.3.1|mdtt_Darwin_arm64.tar.gz|mdtt|mdtt
zip_opt|sd-cli|leejet/stable-diffusion.cpp|master-820-de298c2|sd-*-bin-Darwin-macOS-*-arm64.zip|sd-cli|sd-cli
Installer: home/.chezmoiscripts/run_onchange_after_05-install-custom-packages.sh.tmpl