Contributing¶
Bug reports, fixes and documentation improvements are welcome. Open an issue or a pull request on GitHub.
Set up¶
You need uv and one of the supported Python versions (3.11 to 3.15).
$ git clone https://github.com/jaykv/cliffy.git
$ cd cliffy
$ make install # uv sync --frozen --all-extras --group docs
$ uv run cli --help
make install creates .venv with cliffy in editable mode, both extras, the test tools and the
documentation tools. To work on another Python version, run uv sync --all-extras --python 3.15.
On macOS with Python 3.14 or 3.15, some dependencies are built from source, which needs the Xcode command line tools.
Everyday commands¶
| Command | What it does |
|---|---|
make test |
Runs the test suite in parallel with coverage. |
make lint |
Checks formatting and lint rules with ruff, then types with mypy. |
make format |
Formats the code and applies ruff's automatic fixes. |
make check |
format, lint, test and clean in one go. |
make docs-serve |
Serves the documentation at http://127.0.0.1:8000 with live reload. |
make docs-generate |
Regenerates the reference pages. |
make docs-check |
Checks the reference pages and runs every documentation manifest. |
make docs-build |
docs-check, then a strict documentation build. |
make generate-schema |
Regenerates examples/cliffy_schema.json from the models. |
make generate-clis |
Regenerates examples/generated/*.py from the manifests in examples/. |
make clean |
Removes build and cache directories. |
Run a single test file or test with pytest directly:
$ uv run pytest tests/test_manifest.py -v
$ uv run pytest tests/test_manifest.py::test_validate_cli_requires -v
Run the tests through uv run so that the environment's bin directory is on the PATH; tests
that load CLIs start them by name. Do not run two test sessions in the same checkout at once,
because loaded CLIs are shared state.
CI runs make lint and make test on Python 3.11 to 3.15 for every pull request.
How the code is organized¶
A manifest goes through three stages:
| Module | Role |
|---|---|
cliffy/transformer.py |
Renders the manifest with Jinja2, resolves includes, validates it and checks requires. |
cliffy/manifest.py |
The pydantic models that define the manifest format and the JSON schema. |
cliffy/commander.py |
Builds groups, merges command templates and drives code generation. |
cliffy/commanders/typer.py, click.py |
Emit Typer or Click code. |
cliffy/parser.py |
Translates params and run scripts for the Typer backend. |
cliffy/cli.py, cliffy/ai.py |
The cli command itself. |
cliffy/loader.py, cliffy/homer.py |
Install generated CLIs and keep their metadata. |
cliffy/builder.py |
cli run and cli build. |
cliffy/reloader.py |
cli dev. |
cliffy/tester.py |
cli test. |
cliffy/doc.py |
cli docs. |
When you add a manifest key, update the model in cliffy/manifest.py, handle it in the commanders,
then run make generate-schema and make docs-generate.
Working on the documentation¶
The site is built with Zensical from the
docs/ directory. The build is strict: a broken link or a missing file is an error.
Generated pages¶
Three pages are written by scripts/docs.py and must not be edited by hand:
| Page | Generated from |
|---|---|
docs/reference/cli.md |
The Click commands in cliffy/cli.py and cliffy/ai.py |
docs/reference/manifest.md |
The models in cliffy/manifest.py |
docs/reference/examples.md |
The manifests in examples/ |
After changing a command, an option, a model or an example, run make docs-generate and commit the
result. The hand-written notes on those pages live in scripts/docs.py, next to the code that
renders them. The script stops with a message when a new command, argument, model field or example
has no description yet.
Manifests in the guides¶
Guides do not contain copies of YAML. They embed files from docs/snippets/ (or examples/) with
the snippet syntax, optionally limited to a named section:
A section is the part of the YAML file between a pair of marker comments that carry its name. Open
any file in docs/snippets/ to see them; the
snippets extension
documents the syntax.
Every manifest in docs/snippets/ should have a tests section. make docs-check loads each one,
compiles the generated code and runs its tests, so a guide cannot show a manifest that does not
work. When you add console output to a page, copy it from a real run.
Preview¶
Releasing¶
- Set the version in
pyproject.tomland runuv lock. - Move the "unreleased" entry in
docs/project/changelog.mdunder the new version and date, and add migration notes for breaking changes. - Merge to
main, then publish a GitHub release whose tag equals the version (0.7.0orv0.7.0).
The publish.yaml workflow builds the package, checks that the tag matches the version and uploads
to PyPI through trusted publishing. Pushing documentation changes to main deploys the site.