Skip to main content

CLI Architecture

The CLI is organised as a package (src/zenzic/cli/) rather than a single module. Each file owns one domain of responsibility.


Module Map

ModuleResponsibility
_shared.pyconsole singleton, _ui singleton, configure_console(), and all cross-command utilities (_build_exclusion_manager, _output_json_findings, _render_link_error, etc.)
_check.pycheck_app Typer sub-app + seven check * commands; private helpers re-exported from _governance.py, _target_resolver.py, and _command_setup.py
_command_setup.pysetup_command() factory — consolidates repo-root discovery, config loading, target resolution, and exclusion-manager construction used by all check commands
_clean.pyclean_app Typer sub-app + clean assets command
_config_explain.pyexplain command + config genealogy / rule introspection surface
_governance.pyconfig_app Typer sub-app + governance profile commands + per-file-ignore and directory-policy filter helpers (_apply_per_file_ignores, _apply_directory_policies)
_guard.pyguard_app Typer sub-app + scan / init commands for the fast secret guard
_inspect.pyinspect_app Typer sub-app + capabilities, codes, and routes commands
_lab.pylab command + interactive scenario showcase
_metadata.pySingle source of truth for root help panels, command grouping, and short help text
_standalone.pyscore, diff, and init commands + their private helpers
_target_resolver.py_resolve_target() and _apply_target() — path lookup and config-patching helpers shared by check commands and the lab command
__init__.pyPublic re-export surface consumed by main.pydo not add logic here

main.py is the unified Typer registration factory. New top-level commands and sub-apps must be registered there, and root help metadata must stay aligned with _metadata.py.


The Visual State Manager

_shared.py is the sole owner of all console and UI state. This is the most critical architectural rule in the CLI layer:

PROHIBITION: No command module may instantiate Console() or a custom UI class directly. All output must go through get_ui() and get_console() from _shared.py.

# ✅ Correct — in any _check.py / _clean.py / _standalone.py command
from . import _shared
_shared.get_ui().print_header(__version__)
_shared.get_console().print("output")

# ❌ FORBIDDEN — never do this in a command module
from rich.console import Console
from mypackage.ui import LegacyInterfaceV1
console = Console(...) # breaks shared state
ui = LegacyInterfaceV1(console) # creates an orphaned instance

For the design rationale behind UI state sharing, see ADR 004 — Unified Console State.

UI output conventions:

  • Always use ZenzicPalette.DIM for dim/secondary text — never the raw Rich tag [dim].
  • Vertical spacing: compact (Ruff-style). No blank lines between individual footer lines. Use Rule() separators only to divide major report sections.
  • New symbols must be added to _EMOJI in zenzic/core/ui.py before use — never inline Unicode literals.

Adding CLI Commands

For step-by-step instructions on how to add commands or new sub-apps to the CLI, see Writing a New Check — CLI Wiring.


Exit Codes

The CLI exits with one of four codes. These are frozen — do not add new exit codes without an explicit architecture decision:

CodeMeaning
0All checks passed
1Quality issues found
2SECURITY — leaked credential detected
3SECURITY — system-path traversal detected

PLUGIN_FORBIDDEN_EXITS enforces that third-party adapters cannot emit exit codes outside this set. See the Adapter API reference for the full contract.