cli-core-yo v2.0.0 is an opinionated enforcement layer for downstream Python CLIs. It keeps Typer, Click, and Rich as the implementation substrate, but the framework owns root parsing, output mode, dry-run mode, runtime validation, and command policy enforcement.
It is not a standalone service CLI. It is the shared kernel downstream repos embed into their own entrypoints.
Use create_app(spec) when you need the configured CLI object directly, or run(spec, argv=None) when you want process-style execution with an exit code.
The top-level configuration is an immutable CliSpec.
from cli_core_yo.app import run
from cli_core_yo.spec import CliSpec, PolicySpec, XdgSpec
SPEC = CliSpec(
prog_name="my-tool",
app_display_name="My Tool",
dist_name="my-tool",
root_help="Unified CLI for My Tool.",
xdg=XdgSpec(app_dir_name="my-tool"),
policy=PolicySpec(),
)
raise SystemExit(run(SPEC))CliSpec in v2 requires:
| Field | Purpose |
|---|---|
prog_name |
CLI program name |
app_display_name |
Human-facing app name |
dist_name |
Installed distribution name |
root_help |
Root help text |
xdg |
App-scoped XDG directory policy |
policy |
Framework policy profile |
Optional sections include config, env, runtime, context, output, plugins, info_hooks, and deploy.
The framework owns these root options:
--json--dry-run--no-color--debug--config PATH--runtime-backend BACKEND--skip-runtime-check- any
InvocationContextSpecselectors
JSON is root-global. Downstream commands do not define their own --json flags. --dry-run is root-global too, and commands that do not support it fail before execution.
Register downstream behavior through the policy-aware registry.
from cli_core_yo import output
from cli_core_yo.registry import CommandRegistry
from cli_core_yo.spec import CliSpec, CommandPolicy
def greet() -> None:
output.success("hello")
def register(registry: CommandRegistry, spec: CliSpec) -> None:
registry.add_command(
None,
"greet",
greet,
help_text="Say hello.",
policy=CommandPolicy(),
)The supported registry surface is:
add_group(name, help_text="", order=None)add_command(group_path, name, callback, *, help_text="", policy, order=None)
add_typer_app() is not part of the v2 contract.
The framework provides built-ins through the same registry and policy machinery:
versioninfo- optional
config - optional
env - optional
runtime
runtime is enabled only when RuntimeSpec is configured. When present, it includes runtime status, runtime check, and runtime explain.
Use get_context() inside commands when you need invocation-scoped state.
from cli_core_yo.runtime import get_context
def show_runtime() -> None:
ctx = get_context()
print(ctx.spec.prog_name)
print(ctx.json_mode)
print(ctx.dry_run)
print(ctx.backend_name)Output helpers live in cli_core_yo.output. Human output goes to stdout, diagnostics go to stderr, and JSON output is deterministic UTF-8 with sorted keys, indent=2, and a trailing newline.
Environment hooks that matter at the framework level:
NO_COLORdisables ANSI stylingCLI_CORE_YO_DEBUG=1enables traceback diagnosticsXDG_CONFIG_HOME,XDG_DATA_HOME,XDG_STATE_HOME, andXDG_CACHE_HOMEoverride XDG resolution
Downstream repos can reuse cli_core_yo.conformance in pytest suites.
from cli_core_yo.app import create_app
from cli_core_yo.conformance import assert_exit_code, assert_json_output, invoke
from cli_core_yo.spec import CliSpec, PolicySpec, XdgSpec
SPEC = CliSpec(
prog_name="my-tool",
app_display_name="My Tool",
dist_name="my-tool",
root_help="Unified CLI for My Tool.",
xdg=XdgSpec(app_dir_name="my-tool"),
policy=PolicySpec(),
)
app = create_app(SPEC)
result = invoke(app, ["--json", "version"])
assert_exit_code(result, 0)
data = assert_json_output(result)
assert "version" in dataThe helper module is intentionally small and generic:
invoke(app, argv, ...)stdout_text(result)stderr_text(result)json_output(result)assert_exit_code(result, expected)assert_json_output(result, expected=None)assert_no_ansi(text)assert_stdout_only(result)
Bootstrap a local environment from the repo root:
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Validation commands used by this repo:
python -m pytest tests/ -v --cov=cli_core_yo
ruff check cli_core_yo tests
ruff format --check cli_core_yo tests
mypy cli_core_yo --ignore-missing-imports
python -m build
twine check dist/*- Treat this repository as the shared CLI framework layer, not as a downstream app.
- Define exactly one immutable
CliSpecin downstream CLIs. - Register commands through
CommandRegistrywith explicitCommandPolicymetadata. - Keep JSON and dry-run ownership in the framework, not in downstream callbacks.
- Do not rely on
add_typer_app()or legacy JSON flag behavior. - Use
cli_core_yo.outputfor user-facing output andcli_core_yo.conformancefor pytest contract checks.
For the repo-specific agent policy, see AI_DIRECTIVE.md.
MIT. See LICENSE.