AI agent instructions for
template-python-cli. Human documentation → README.md.
uv sync # Install deps + create .venv
uv run ruff check src/ tests/ # Lint
uv run ruff format src/ tests/ # Format
uv run pytest # Run tests (with coverage)
uv build # Build wheel + sdist
uv run template-python-cli --help # Smoke testsrc/template_python_cli/
├── __init__.py # Empty — version via importlib.metadata
├── py.typed # PEP 561 marker
├── cli.py # Typer app, signal handlers, commands
├── config.py # 3-layer config merge
├── defaults.toml # Bundled defaults (layer 1)
└── utils.py # InputError, error_message
tests/
├── fixtures.py # make_config() factory
├── test_cli.py # CLI tests via CliRunner
├── test_config.py # Config loading tests
└── test_utils.py # Unit tests for utils
Config.load() in config.py:
- Bundled
defaults.tomlviaimportlib.resources.files("template_python_cli").joinpath("defaults.toml") - User TOML at
~/.config/template-python-cli/config.toml(XDG-aware) TEMPLATE_PYTHON_CLI_SECTION_KEY=valueenv vars (e.g.TEMPLATE_PYTHON_CLI_GENERAL_GREETING=Hi)
Deep merge at section level. Unknown env vars are silently ignored.
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)Registered at module import before app = typer.Typer(...). Exit code 130 = 128 + SIGINT.
Bundled assets loaded without filesystem assumptions:
importlib.resources.files("template_python_cli").joinpath("defaults.toml").read_text(encoding="utf-8")- stdout: primary result only. For
--json, a single-line JSON document parseable byjson.loads. No progress, warnings, or diagnostics. - stderr: all other output — progress, warnings, errors, tracebacks.
typer.echo(..., err=True)for error messages.loggingconfigured tostream=sys.stderr.
Violation breaks downstream agent pipelines that capture stdout for parsing.
except InputError as e:
typer.echo(f"Error: {error_message(e)}", err=True)
raise typer.Exit(code=2)
except typer.Exit:
raise # Let Typer manage its own exits
except Exception as e:
typer.echo(f"Error: {error_message(e)}", err=True)
raise typer.Exit(code=1)InputError (invalid user input) → exit 2. Bare Exception (runtime failure) → exit 1.
--json must emit exactly one line on stdout that json.loads() accepts. No trailing text, no progress, no warnings on stdout.
Use importlib.metadata.version("template-python-cli") — never hardcode the version string.
- Python 3.11+ required (
tomllibstdlib,matchstatements, type-union syntax) uv.lockis committed and checked in CI (uv lock --check)cli.pyis excluded from coverage measurement (side-effecting module-level code)- No
sudoor interactive auth in CI
- Push conventional commits to
main - release-please opens a release PR (bumps version in
pyproject.toml+CHANGELOG.md) uv.lockis auto-updated in the release PR branch by the App bot- Merge the PR → GitHub release created → PyPI published (OIDC) → Homebrew tap updated
make_config(**overrides)intests/fixtures.py— inject test configs viamonkeypatch.setattr(Config, "load", ...)error_message(err)inutils.py— safe string representation of any exception