"""artifact-store command-line interface. The CLI is a thin consumer of :mod:`artifactstore.registry` (per ADR-0005). T014 ships ``version``, ``migrate``, ``replay``, and ``health`` subcommands; richer subcommands (e.g. ``push``, ``manifest``) land alongside the HTTP API in WP-0002. """ from __future__ import annotations import asyncio import json from pathlib import Path from typing import Any import typer from artifactstore import __version__ from artifactstore.config import Settings, get_settings from artifactstore.db.engine import create_engine from artifactstore.events import RegistryViewWriter from artifactstore.events import replay as events_replay from artifactstore.registry import Registry __all__ = ["app"] app = typer.Typer( help="artifact-store: artifact registry and storage gateway", no_args_is_help=True, ) @app.callback() def main() -> None: """Top-level CLI entry point.""" @app.command() def version() -> None: """Print the artifactstore version and exit.""" typer.echo(__version__) @app.command() def migrate( alembic_ini: Path = typer.Option( Path("alembic.ini"), "--alembic-ini", help="Path to alembic.ini (defaults to the repo root).", ), ) -> None: """Run ``alembic upgrade head`` against the configured database.""" from alembic import command from alembic.config import Config cfg = Config(str(alembic_ini)) command.upgrade(cfg, "head") typer.echo("alembic upgrade head: ok") @app.command() def replay() -> None: """Truncate materialised views and replay every event from sequence 1.""" settings = get_settings() last_seq = asyncio.run(_replay_async(settings)) typer.echo(f"replayed up to sequence {last_seq}") @app.command() def health() -> None: """Print a JSON liveness summary (db, backend).""" settings = get_settings() payload = asyncio.run(_health_async(settings)) typer.echo(json.dumps(payload, indent=2)) # ---- internals ------------------------------------------------------------- async def _replay_async(settings: Settings) -> int: engine = create_engine(settings) try: return await events_replay(engine, RegistryViewWriter(), reset=True) finally: await engine.dispose() async def _health_async(settings: Settings) -> dict[str, Any]: from artifactstore.app import build_registry registry: Registry = build_registry(settings) try: db_ok, db_detail = await registry.db_health() backend_status = await registry.backend_health() finally: await registry.dispose() overall = "ok" if db_ok and backend_status.healthy else "degraded" return { "service": "artifact-store", "version": __version__, "status": overall, "db": {"healthy": db_ok, "detail": db_detail}, "backend": { "backend_id": backend_status.backend_id, "healthy": backend_status.healthy, "detail": backend_status.detail, "free_bytes": backend_status.free_bytes, "total_bytes": backend_status.total_bytes, }, } if __name__ == "__main__": # pragma: no cover app()