Changelog¶
Notable changes per release. Entries before 0.7.0 were reconstructed from the git history and tags, because no changelog was kept at the time; they list the main changes, not every commit. Release artifacts are on PyPI.
0.7.1 (2026-10-12)¶
Added¶
cli ai llms-txtprints the documentation bundled with cliffy as one llms.txt file, for coding agents. It needs no model and noaiextra. The documentation site publishes the same file at/llms.txt. See AI commands.cli validatewarns when thedefault,helporshortof a param is ignored because its type intypesis a complete Typer definition.
Fixed¶
cli initaccepts names with a hyphen, ascli init --rawalways did.
0.7.0 (2026-10-12)¶
The first release after a long pause. It brings the project up to date with current Python and dependency versions. Read the migration notes before upgrading.
Changed¶
- Supported Python versions are now 3.11 to 3.15. Python 3.9 and 3.10 are no longer supported.
- Depends on
typerinstead oftyper-slim, sorichandshellinghamare always installed. - Minimum dependency versions were raised to current releases, among them
click>=8.2.1,typer>=0.24.0,pydantic>=2.12.0,rich-click>=1.9.0andpydantic-ai>=2.0.0. - The
aiextra requires pydantic-ai 2. Model names need a provider prefix. Without--model, theCLIFFY_AI_MODELenvironment variable or a default for the provider whose API key is set is used, and generated manifests are validated before they are written. See AI commands. cli buildbundles the framework the generated code imports (Typer or rich-click), pinned to the installed version, writes a reproducible file and reports the Python version it needs.- Commands exit with 1 on failure, among them
cli testwhen any test fails,cli buildwhen a build fails andcli validatefor an invalid manifest. - Include paths are looked up next to the including manifest first.
- With the Click backend a
booloption is a--flag/--no-flagswitch, and short option names,global_paramsandcli testwork as they do with Typer. cli dev --run-cliis a real flag. It no longer re-runs the CLI on every reload when the flag is not given.cli buildprints the build output when a build fails.- Requires pybash 0.4. Shell lines are parsed like a shell parses them: quotes and backslashes reach
the command unchanged, a value inserted into a
>line is quoted, and>lines keep their quotes. See the migration notes. - A CLI imports
pybash.runtimewhen it uses pipes, redirects or chains in$lines, and nothing butsubprocessotherwise. Built CLIs carry that helper themselves and do not need pybash. - A shell line that cannot be translated, or a Python syntax error in a script, is reported when the manifest is loaded, with the command, the line and the column.
- After a build,
cli buildprints the size of the file and how to run it.--pythonhas no fixed default any more: each target has its own. - The source distribution only contains the package, tests and examples.
- Documentation: new site structure with a tutorial, task guides and reference pages generated from
the code. Every manifest shown in the guides is run by
make docs-check. cli validatecompiles and imports the generated code and reports a problem against the command it comes from. It used to check only the YAML, the schema andrequires.manifestVersionis checked.v1,v2and unknown values stop the load with an error that says what to change, also in include files and blueprint specs. The key used to be ignored.- Click backend, explicit params: a
namewithout--is a positional argument, as it is with Typer. It used to become an option. Writename: --widthfor an option. - Click backend, shorthand arguments:
label: stris optional andNonewhen omitted, as it is with Typer. Writelabel: str!for a required argument. - Plain types in
typesare aliases for both backends. WithCount: int, the param--n: Countis the option--nand an explicit param keeps itsdefaultandhelp. The Typer backend used to replace the whole definition, which made--n: Counta positional argument. - The Click backend supports
types, commandconfig(rich_help_panelbecomes rich-click'spanel), argument help, andOptional[...]andlist[...]types. A Typer definition in a Click manifest is rejected with an error that names the command. - A manifest no code can be generated for (a command or param name that cannot be a Python name, an
unknown
template) stops every command with~ error in <file>: command <name>: ...instead of a traceback. - Generated Click code imports rich-click when it is installed and plain Click otherwise, so
use_click: trueno longer needs therichextra.cli buildships rich-click with such a CLI. - Generated code no longer starts with the time of generation. The first line is
## Generated <name> with cliffy <version>, so the same manifest renders the same code every time. varskeys must be valid Python identifiers that the generated code does not use itself.my-varused to produce code that did not compile.- A var that refers to itself, or vars that refer to each other in a circle, are an error.
cli init --rawwrites a manifest with one command and one test instead of an empty skeleton.- A corrupt metadata file is listed under the name of its CLI (
hello, nothello.json), so it can be removed withcli rm. make generate-clisrendersexamples/generated/withscripts/examples.pyand no longer installs packages. Thegenerate-cleanuptarget is gone.cli renderprints exactly the generated module. Long lines are no longer broken, socli render x.yaml > x.pyis the code that runs. The# Rendered ...line goes to stderr. The same holds forcli init --render.cli testreports errors against the manifest instead of printing tracebacks. A command that raises is shown ascommand <name> raised <Error>: <message>with the line of its run block; an assertion that raises or is not valid Python is shown with its line; a manifest that does not import and a shell step that cannot be started end the run with an error message.cli removeremoves a CLI completely or not at all. When the launcher is not the file cliffy wrote, nothing is removed, the CLI stays loaded and the command exits with 1.cli remove-allexits with 1 when a CLI could not be removed.cli loadonly replaces a launcher it wrote itself: a regular file with exactly the launcher text for that CLI. A file that merely containedfrom cliffy.clis.used to count as cliffy's own.- A
requiresentry that starts with-is rejected.requireslists packages. cli devruns one reload at a time. A second save within a second of the first is no longer dropped.
Added¶
cli build --targetchooses what is built from the same manifest:zipapp(the default, as before),scriptfor a single Python file with its dependencies declared inline, to run with uv, andnativefor a compiled executable that brings its own Python. See Building executables.- Shell lines support
&&,||,;,|&,2>,&>,2>&1,$(command), continuation lines and comments. See Shell commands. - Blueprints: custom YAML specs that map onto a CLI, selected with
--blueprintor ablueprint:key, and thecli blueprint listandcli blueprint schemacommands. callbacksin manifests: code that runs before the commands of the CLI or of a group, for both backends.varsvalues can be numbers and booleans, and a var can be built from other vars in any order.cli devalso watches included manifests and the Python file of a file blueprint.- The manifest's
examplesare shown at the end of the--helpof the generated CLI, for both backends. See Generating docs. cli test --tracebackprints the full Python traceback of a command that raises.
Security¶
- Text from a manifest (help, names, defaults, versions) is escaped in the generated code and names are validated, so manifest data cannot change the code that is generated.
- The name given to
cli remove,update,info,buildanddocsis validated. A name such as../xcould select and delete files outside of cliffy's own directories. - Launchers and generated modules are written to a temporary file and renamed into place, a new launcher is only created when nothing has its name, and symbolic links are never followed.
cli buildnever passes arequiresentry to pip as an option. An entry such as--index-url=...used to change where pip installs from.
Removed¶
- The
cliffy[dev]extra. Development dependencies are a dependency group (uv sync).
Fixed¶
- Manifest file handles are closed after
cli updateand after loading a manifest. cli --versionprints cliffy's version. With therichextra it printed the version of rich-click.- Line breaks in command and group help become spaces (
Greet\nuserwas shown asGreetuser) and blank lines stay paragraph breaks. - Two manifest names that map to the same Python name (command
db-appand groupdb, a command and a name fromimports,functionsorvars) no longer break the generated module. - The
cli testcounter counts test cases only; it could print10 of 9. cli testremoves colors and styles from the output before the assertions run. On a CI system rich styles help output, which made tests that passed locally fail there.- A Jinja error in a manifest is reported with file, line and the text of the line instead of a traceback.
- The field descriptions in the JSON schema and the
cli inittemplate were wrong in places. cli dev: a reload that started earlier can no longer finish last and load an older version of the manifest over a newer one.cli dev --run-cliandcli teston several threads no longer delete each other's temporary files. Each run has a temporary directory of its own.- The MCP example blueprint answers a JSON-RPC message that is not an object, or a batch, with an error instead of exiting.
- Releases: the upload to PyPI can be re-run after it stopped half way.
- cliffy type-checks and its test suite passes without the optional extras. The test suite no longer
extracts zipapps into
~/.shiv. - Compatibility with Click 8.2 and later, rich-click 1.9 and Python 3.12 and later (deprecated APIs replaced).
0.6.1 (2025-02-05)¶
- Changed the default model of the AI commands.
- Documentation updates.
0.6.0 (2025-02-02)¶
- Added the
cli ai generateandcli ai askcommands behind the newaiextra. - Added
cli docsto generate Markdown, reStructuredText or HTML documentation for a CLI, and theexamplesmanifest key that feeds it. - Expanded the documentation site.
0.5.3 (2025-01-24)¶
- Added the Click backend (
use_click: true) and refactored code generation into per-framework commanders. - Moved the project to uv.
- Added Python 3.13 to the test matrix and extended the test suite.
0.5.2 (2025-01-16)¶
- Reworked command parameter parsing.
0.5.1 (2025-01-16)¶
- Added the
todoexample and documentation updates.
0.5.0 (2025-01-14)¶
- Manifest schema v3.
argswas renamed toparams; v2 manifests are rejected. - Added
global_params. - Added the JSON schema for manifests and
cli init --json-schema.
0.4.0 (2025-01-11)¶
- Manifest schema v2; v1 manifests are rejected.
- Added command templates and the
pre_runandpost_runhooks. - First version of the documentation site.
0.3.8 (2024-08-26)¶
- Added
cli testand thetestsmanifest key. - Added Python 3.12 to the test matrix.
- Fixed path handling on Windows.
0.3.7 (2024-08-07)¶
- Added command aliases.
0.3.6 (2024-08-02)¶
- Added
cli dev(hot reload) andcli rm-all.
0.3.5 (2023-09-19)¶
- Declared Click as an explicit dependency.
0.3.4 (2023-08-06)¶
- CLI metadata is stored inside the installed package.
0.3.3 (2023-07-17)¶
- Merged the
bundlecommand intocli build. - Type-checking fixes.
0.3.0 to 0.3.2 (2023-06)¶
- Internal refactoring and a newer pybash.
0.2.9 (2023-05-22)¶
- Environment variables can be used in
vars.
0.2.8 (2023-05-21)¶
- Added
cli run. - Executables are built with shiv.
0.2.7 (2023-05-02)¶
- Rich became optional.
- Windows fixes.
0.2.6 (2023-04-27)¶
- Added
requires.
0.2.5 (2023-04-16)¶
- Added
varswith Jinja2 templating. - Added
includeswith deep merging.
0.2.3 (2023-04-03)¶
- Added loading, unloading and listing of CLIs.
0.2.2 (2023-03-15)¶
- Added
cli init. - Several manifests can be loaded in one call.
- Added manifest versioning.
0.2.0 and 0.2.1 (2023-03-09)¶
- Renamed the project to cliffy.
- Added
help,cli_options, option aliases, custom imports and a shorter param syntax.
0.1.0 (2023-01-29)¶
- First release.