Settings capture — diff live config against defaults, suggest vars overrides
dotpro capture <app> handles the easy mode (file-is-overrides) apps claude, git, and vscode, with secret redaction. What remains is the generate / baseline adapters for full-default apps, whose files are not already just your overrides.
Motivation
Dotfiles Pro is push-only: it writes the settings you declare and never reads machine state back. So a setting you change through an app's own UI/CLI (Claude Code, VS Code, gh auth, …) is invisible to your repo and never reaches your next machine.
dotpro capture is the reverse of apply and the key-level inverse of "settings, not files": inspect a live config, detect what differs from the application's defaults, and propose the delta as Ansible vars overrides for review — structured and reviewed, at the key level rather than whole-file. (How it compares to a file-level capture is in Positioning, below.)
Conceptual model — four states
| State | What it is |
|---|---|
| default | the app's initial / factory config |
| managed | what your Ansible vars already declare (the overrides) |
| live | what's on disk right now (app + your edits) |
| target | what an apply would write |
The capture computes:
live − default→ everything you've customized(live − default) − managed→ customizations not yet captured → suggest adding to vars
Why it's feasible (the key realization)
Many high-value apps don't serialize their defaults — ~/.claude/settings.json, VS Code's settings.json, and .gitconfig contain only what you set. So for these, live ≈ your overrides already, and you barely need a default baseline: the capture is "parse the file → these keys are vars candidates," minus what's already managed, minus secrets.
The genuinely hard case is apps that write a full default config on first run (then live = defaults + your changes, and you need the baseline to subtract).
Where "default" comes from (the crux — per-app)
There is no universal source, which is exactly why chezmoi punts to whole-file re-add. Each capturable role declares one of:
- none — the file stores only overrides (Claude, VS Code, git). Easiest, highest value.
- command — the app emits its defaults (
tool config --defaults, a schema dump). - generate — run the app once in a throwaway
$HOME/container and capture what it writes. Robust but heavy. - baseline — a default file shipped in the role. Simple but goes stale across app versions.
Design
A dotpro capture <app> command plus a per-role capturable declaration:
| Field | Purpose |
|---|---|
path | live config location (e.g. ~/.claude/settings.json) |
format | json / jsonc / yaml / toml / ini — for structured diffing |
var | the dict var the role manages (e.g. claude_settings) |
defaults | none / command:<cmd> / generate / baseline:<file> |
secrets | key patterns to redact and route to the bitwarden/vault pattern |
Flow: structured key-level diff via yq/jq (the yq role already ships it) → subtract the already-managed keys → redact secrets → print a reviewable vars block to paste into host_vars (human-in-the-loop, never auto-applied):
$ dotpro capture claude# Add to host_vars/<hostname>.yaml:
claude_settings:
theme: dark # differs from default
# token: <redacted> # secret — route to bitwarden/vault, not varsHard parts / constraints
- Defaults provenance is per-app — no universal answer, hence the adapter model.
- Secrets are a mandatory gate, not a nice-to-have: a naive capture would serialize tokens into git. The filter (redact + route to
bitwarden/ansible-vault) is required. - Structured, not textual — diff parsed data so key order / formatting / comments don't show as changes.
jsonc(Claude/VS Code comments) needs a tolerant parser. - Works cleanly only for roles that manage config as a structured dict var (the clean pattern anyway); a role that sets keys via scattered tasks has no single var to map the delta onto.
Scope (v1)
- Easy mode only (file-is-overrides,
defaults: none) for two or three high-value apps —claude,git,vscode. - Declare-driven, human-in-the-loop, secrets-filtered.
- Defer
generate-a-baseline (full-default apps) to a later pass.
Positioning
State it plainly in docs/alternatives.md: chezmoi captures the whole file (defaults + churn + secrets = noise); Dotfiles Pro captures the reviewed, structured delta as vars — strictly better for the key-level model, and the honest answer to "but chezmoi can re-add and you can't."
Open questions
- Which target? With multiple sources (collection / personal / company repos), where does a captured override get written — always the personal repo's
host_vars? chezmoi avoids this by mandating a single source; we accept multi-source for modularity and must decide capture's destination explicitly. - Defaults versioning — a shipped baseline drifts from the installed app version; prefer
command/generatewhere available. - Parser choice for
jsoncand comment/anchor-preserving round-trips.