Decisions
ADR-004: Chezmoi + Custom CLI Wrapper Architecture
Status: Accepted; command layout superseded by ADR-020 Date: 2026-02-09 Authors: @sebastienrousseau
Status note (0.2.539): Chezmoi as the engine with
dotas the wrapper still holds. The command modules, thescripts/dot/commands/layout, the delegation pattern and "Adding a New Command" below are the original design. Today each domain is a plugin: it is declared inplugins/<domain>/dot-plugin.toml, runs fromplugins/<domain>/dot-<domain>(or itslibexec/), andbin/dotdispatches through the command table built from the plugins' route fragments. Commands are spelleddot <domain> [object] <verb>(ADR-020). Onlydiagnostics.sh,manual.shandmeta.shremain inscripts/dot/commands/.
Context
Managing dotfiles requires:
- Tracking file changes and applying them consistently
- Handling platform-specific configurations
- Supporting encrypted secrets
- Providing a good developer experience
Options considered:
- Bare git repository: Simple but poor UX, no templating
- GNU Stow: Symlink-based, limited features
- Chezmoi only: Powerful but complex CLI
- Custom from scratch: High maintenance burden
- Chezmoi + wrapper: Best of both worlds
Decision
Use Chezmoi as the core engine with a custom dot CLI wrapper that:
Architecture
┌─────────────────────────────────────────────┐
│ dot CLI │
│ (User-friendly interface, custom commands) │
├─────────────────────────────────────────────┤
│ Command Modules │
│ core │ diagnostics │ tools │ appearance │
│ secrets │ security │ meta │
├─────────────────────────────────────────────┤
│ Shared Library │
│ utils.sh (resolve_source_dir, run_script) │
├─────────────────────────────────────────────┤
│ Chezmoi │
│ (Template engine, state management, apply) │
└─────────────────────────────────────────────┘
Core Principles
1. Chezmoi handles complexity:
- Template rendering with Go text/template
- Encrypted secrets with age
- State tracking (what's applied vs source)
- Cross-platform path handling
2. dot CLI handles UX:
- Memorable command names (
dot syncvschezmoi apply) - Domain-specific commands (
dot doctor,dot theme) - Integration with external tools (Nix, Docker, Neovim)
- Consistent help and error messages
3. Modular command structure:
scripts/dot/
├── lib/
│ └── utils.sh # Shared functions
└── commands/
├── core.sh # apply, sync, update, add, diff
├── diagnostics.sh # doctor, heal, health, benchmark
├── tools.sh # tools, new, packages
├── appearance.sh # theme, wallpaper, fonts
├── secrets.sh # secrets-init, secrets
├── security.sh # firewall, backup, encrypt-check
└── meta.sh # upgrade, docs, learn
4. Delegation pattern:
# Main dispatcher in dot CLI
Chezmoi Integration Points
| Feature | Chezmoi | dot CLI |
|---|---|---|
| Apply changes | chezmoi apply | dot apply (dot config apply); dot sync pulls first |
| View diff | chezmoi diff | dot diff (dot config diff) |
| Edit secrets | chezmoi edit --encrypted | dot secret edit |
| Source directory | chezmoi source-path | dot cd (dot config path) |
| Health check | chezmoi doctor | dot doctor (extended) |
Extension Points
Custom commands can:
- Wrap chezmoi commands with better defaults
- Add entirely new functionality (benchmarks, themes)
- Integrate with system tools (nix, docker, brew)
- Provide interactive experiences (tour, learn)
Consequences
Positive
- Leverage Chezmoi's battle-tested engine
- User-friendly interface for common tasks
- Easy to add domain-specific commands
- Modular structure enables testing and maintenance
- Single entry point (
dot) for all operations
Negative
- Two layers to understand (chezmoi + dot)
- Version coupling between chezmoi and scripts
- Some chezmoi features not exposed via dot
Neutral
- Advanced users can still use chezmoi directly
- Documentation needed for both layers
- Upgrade path when chezmoi adds new features
Implementation Notes
Adding a New Command
The steps below are the original ones. For a command today, declare it in the domain's plugins/<domain>/dot-plugin.toml and follow ADR-020.
- Identify the appropriate module (or create new one)
- Add function
cmd_<name>()to module - Add case to module's dispatch
- Add case to main dot CLI dispatcher
- Update help text
- Add tests if complex
Module Template
#!/usr/bin/env bash
# Dotfiles CLI - <Category> Commands
SCRIPT_DIR=""
case "" in
example) ; ;;
*) ; ;;
esac