← All projects
In developmentSelected project

yeetr

A tiny, typed, signature-driven CLI runner.

  • Python
  • Type hints
  • Async
  • CLI

Just yeet the function

Small command-line tools tend to begin as functions and accumulate framework structure around themselves. yeetr keeps the function at the centre. Its signature describes the interface; the runner handles the terminal boundary. The yeet command can find and run main directly, so the file does not need a runner block.

uv add yeetr

Install it from PyPI. yeetr supports Python 3.13 and 3.14, with its build and test coverage published alongside the project.

from pathlib import Path


async def main(
    files: list[Path],
    *,
    workers: int = 4,
    verbose: bool = False,
) -> None:
    ...


# Run from the terminal:
# yeet app.py report.csv --workers 4

Positional function parameters become positional CLI arguments. Keyword-only parameters become options. Python’s snake_case becomes terminal-friendly kebab-case.

The signature is the command model

yeetr reads ordinary annotations including int, float, str, bool, Path, date and time types, UUID, Decimal, Literal, Enum, optional values, collections, dataclasses, and NamedTuple structures. It supports synchronous and asynchronous callables without asking the function to inherit from a command class or carry framework decorators.

Boolean defaults map to the way people expect flags to behave: False becomes --flag, while True becomes --no-flag. Parser failures stay clean and terminal-oriented.

Built to test as normal Python

The runner parses sys.argv by default, but accepts an explicit argv override. Tests can exercise command behaviour without patching process-global state or launching a subprocess:

yeetr.run(main, argv=["report.csv", "--workers", "8", "--verbose"])

The same small model extends to lightweight #!yeet scripts—useful when a file should feel like a script while retaining typed argument parsing.

Intentionally narrow

yeetr is not trying to become a nested-command application platform. Its advantage is the short distance between a useful Python function and a good terminal interface. The constraint is the product: once a CLI needs complex command trees or deeply customised parsing, a larger framework may be the better fit.