Imported from egustafson/cfgr (
AGENTS.md). Install upstream withnpx skills add egustafson/cfgr. Copyright stays with the author.
cfgr — Agent Guidelines
Project Summary
cfgr is a Click-based CLI tool for managing OS configuration files under version control.
It bridges a source directory (versioned) and a target directory (deployed location),
providing diff, push, pull, and init commands.
Build & Test
# Install / sync dependencies
uv sync
# Lint + test (must pass before every commit)
make pre-release
# Individual steps
uv run ruff check . && uv run ruff format --check .
uv run pytest tests
All source files are formatted with ruff (line length 100). Run uv run ruff format <file> to
auto-fix formatting issues.
Module Responsibilities
| File | Role |
|---|---|
cfgr.py |
CLI entry point; Click group and all subcommands |
context.py |
CfgrCtx — loads .cfgr.yml, resolves paths, ignore/include logic, hostname check |
ops.py |
Pure utility functions: files_differ, unified_diff, get_tracked_pairs, copy_file, unignore_patterns |
filetree.py |
Simple recursive file listing; used only by the hidden dbg command — intentionally has no ignore awareness |
CLI Commands
| Command | Description |
|---|---|
about |
Outputs YAML with cfgr version and python version |
diff [-s] [-I] [-u] [--nocolor] [--pager] |
Diff source vs target; side-by-side by default |
push [--force] [FILE...] |
Copy changed files source → target |
pull [--force] [FILE...] |
Copy changed files target → source |
init TARGET [-D SOURCE_DIR] |
Create a .cfgr.yml in the source directory |
dbg |
Hidden debug command; prints context and file tree |
.cfgr.yml Schema
The config file lives inside the source directory — that directory is implicitly the source
root. target must be an absolute path.
target: /etc # required; absolute path to the deployed location
hostname: myhost # optional; short or FQDN — diff warns, push/pull abort on mismatch
include: # optional allowlist (gitignore patterns); evaluated before ignore
- subdir/
ignore: # optional denylist (gitignore patterns); evaluated after include
- logs/
- "*.bak"
Child configs in source subdirectories may have include and ignore; target is forbidden
and raises an error.
Key Design Decisions
--forceonpush/pullrequires explicit file paths (UsageErrorotherwise).--forceon an ignored file triggersclick.confirmto remove the pattern from.cfgr.yml.ops.get_tracked_pairsreturns the union of source and target files so target-only files appear indiff..cfgr.ymlfiles are excluded from tracked pairs.FileTreeinfiletree.pyhas no ignore awareness — do not add it without a specific reason.- Hostname matching is liberal: only the first dot-separated component is compared, case-insensitively.
Test Conventions
- All filesystem tests use
tmp_pathand copytest_data/via_setup(tmp_path). _setuprewrites.cfgr.ymlwith an absolutetargetpath and returns the source dir path.- Pass the source dir as the working directory:
runner.invoke(cli, ["-d", wd, ...]). - Source file paths:
os.path.join(wd, "base.ini") - Target file paths:
os.path.normpath(os.path.join(wd, "..", "target", "base.ini")) test_aboutcontains a hard-coded version assertion — update it on every release.
Release Process
- Move
[Unreleased]content to[X.Y.Z] - YYYY-MM-DDinCHANGELOG.md. - Add an empty
[Unreleased]section above it. - Update comparison links at the bottom of
CHANGELOG.md. - Bump
versioninpyproject.toml. - Update the version string in
test_aboutintests/test_cfgr.py. - Run
uv syncto reinstall the package at the new version. - Run
make pre-release— must be green. - Commit:
git add -A && git commit -m "release: prepare vX.Y.Z ...". - Tag separately when ready to publish.
Commit Policy
Never commit automatically. Only run git commit when the user explicitly asks.
Commit messages should be short and to the point. If more detail is needed, reference the
CHANGELOG.md rather than embedding a long description in the commit message.