Reusable CLI kernel for building unified command-line interfaces with consistent behavior, output style, help, and extension semantics. Built on Typer + Rich.
cli-core-yo provides the shared foundation that downstream CLI tools build on:
- Root app factory —
create_app()builds a fully configured Typer app from a declarative spec - Built-in commands —
version,info, and optionalconfig/envgroups - Plugin system — explicit callables +
cli_core_yo.pluginsentry-point discovery - XDG paths — platform-aware config/data/state/cache directory resolution (macOS + Linux)
- Output primitives —
heading,success,warning,error,action,detail,bullet,emit_json - Runtime context — immutable singleton accessible to all commands during invocation
- JSON mode —
--json/-jflag with deterministic output (indent=2, sorted keys, no ANSI) - NO_COLOR — respects the NO_COLOR convention
- Debug mode —
CLI_CORE_YO_DEBUG=1prints tracebacks to STDERR
- Python 3.10+
- pip
pip install cli-core-yoFor development:
git clone https://github.com/Daylily-Informatics/cli-core-yo.git
cd cli-core-yo
pip install -e ".[dev]"from cli_core_yo.spec import CliSpec, XdgSpec
from cli_core_yo.app import run
spec = CliSpec(
prog_name="my-tool",
app_display_name="My Tool",
dist_name="my-tool",
root_help="A CLI built with cli-core-yo.",
xdg=XdgSpec(vendor="my-tool"),
)
exit_code = run(spec)This gives you my-tool version, my-tool info, my-tool --help, --json support, and all the standard behaviors out of the box.
# my_tool/plugin.py
def register(registry, spec):
registry.add_command(None, "greet", greet_cmd, help_text="Say hello.")
def greet_cmd():
from cli_core_yo import output
output.success("Hello, world!")Register it in your spec:
from cli_core_yo.spec import PluginSpec
spec = CliSpec(
...,
plugins=PluginSpec(explicit=["my_tool.plugin.register"]),
)Or via entry points in pyproject.toml:
[project.entry-points."cli_core_yo.plugins"]
my-tool = "my_tool.plugin:register"Enable optional built-in command groups by providing specs:
from cli_core_yo.spec import ConfigSpec, EnvSpec
spec = CliSpec(
...,
config=ConfigSpec(
primary_filename="config.json",
template_bytes=b'{"key": "value"}\n',
),
env=EnvSpec(
env_dir_name=".venv",
activate_script_rel="bin/activate",
),
)This adds config path|init|show|validate|edit|reset and env status|activate|deactivate|reset.
| Symbol | Module | Description |
|---|---|---|
create_app(spec) |
cli_core_yo.app |
Build a Typer app from a CliSpec |
run(spec, argv) |
cli_core_yo.app |
Execute CLI, return exit code (never calls sys.exit()) |
CommandRegistry |
cli_core_yo.registry |
Register commands/groups with ordering and conflict detection |
get_context() |
cli_core_yo.runtime |
Access the current invocation's RuntimeContext |
CliSpec, ConfigSpec, EnvSpec, PluginSpec, XdgSpec |
cli_core_yo.spec |
Frozen dataclass specs |
output.* |
cli_core_yo.output |
UX primitives + emit_json() |
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Domain/runtime failure |
| 2 | Usage error (bad args, unknown command) |
| 130 | SIGINT |
# Run tests
python -m pytest tests/ -v --cov=cli_core_yo
# Lint + format check
ruff check cli_core_yo tests
ruff format --check cli_core_yo tests
# Type check
mypy cli_core_yo --ignore-missing-imports
# Build distribution
python -m build
twine check dist/*MIT — see LICENSE.