"""User-facing output utilities for GRM. Console messages are colorised via ``click.style`` for visual feedback. The persistent log file always receives plain text (no ANSI codes). """ from __future__ import annotations import logging import os import click def _console_level() -> int: """Return the minimum level for console output from ``GRM_LOG_LEVEL``.""" value = os.getenv("GRM_LOG_LEVEL", "INFO") try: return getattr(logging, value.upper()) except AttributeError: return logging.INFO def say(msg: str, level: int = logging.INFO, err: bool = False, color: str | None = None) -> None: """Output a message to the user and also log it for auditing. Console output goes via ``click.echo`` (handles encoding, CliRunner, Windows colorama) only when *level* is at least ``GRM_LOG_LEVEL``. The same message is always sent to the ``grm`` logger so it appears in the persistent log file regardless of console verbosity. The optional *color* parameter is passed to ``click.style`` so the console line is tinted (e.g. ``color="green"``). The log file always stores the raw plain text. """ if level >= _console_level(): styled = click.style(msg, fg=color) if color else msg click.echo(styled, err=err) logging.getLogger("grm").log(level, msg)