from __future__ import annotations import argparse import json import sys from collections.abc import Callable from pathlib import Path from typing import Any from .api import serve from .service import ( CanonServiceError, artifact_graph, inspect_canon, list_artifacts, list_models, list_standards, profile_inspect, validate_canon, ) Command = Callable[[argparse.Namespace], dict[str, Any]] def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="info-tech-canon") parser.add_argument( "--root", default="", help="Infospace root. Defaults to ./infospace from the repository root.", ) sub = parser.add_subparsers(dest="command", required=True) inspect = sub.add_parser("inspect", help="Inspect the canon infospace") inspect.set_defaults(handler=_inspect) artifacts = sub.add_parser("artifacts", help="List canon artifacts") artifacts.add_argument("--kind", default="") artifacts.set_defaults(handler=_artifacts) models = sub.add_parser("models", help="List canon model artifacts") models.set_defaults(handler=_models) standards = sub.add_parser("standards", help="List canon standard artifacts") standards.set_defaults(handler=_standards) validate = sub.add_parser("validate", help="Validate the canon infospace") validate.set_defaults(handler=_validate) graph = sub.add_parser("graph", help="Export the canon artifact graph") graph.add_argument("--format", choices=["json", "mermaid"], default="json") graph.set_defaults(handler=_graph) profile = sub.add_parser("profile", help="Inspect canon profiles") profile_sub = profile.add_subparsers(dest="profile_command", required=True) profile_inspect_cmd = profile_sub.add_parser("inspect", help="Inspect a profile") profile_inspect_cmd.add_argument("profile") profile_inspect_cmd.set_defaults(handler=_profile_inspect) api = sub.add_parser("api", help="Run the read-only local API") api.add_argument("--host", default="127.0.0.1") api.add_argument("--port", type=int, default=8765) api.set_defaults(handler=_api) return parser def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) handler: Command = args.handler try: result = handler(args) except CanonServiceError as exc: _print_json(exc.to_dict()) return 2 except Exception as exc: _print_json( { "ok": False, "error": { "code": "unhandled_error", "message": str(exc), "details": {}, }, } ) return 1 if result: _print_json(result) return 0 if result.get("ok", False) else 1 def _root(args: argparse.Namespace) -> Path | None: return Path(args.root) if args.root else None def _inspect(args: argparse.Namespace) -> dict[str, Any]: return inspect_canon(_root(args)) def _artifacts(args: argparse.Namespace) -> dict[str, Any]: return list_artifacts(_root(args), kind=args.kind or None) def _models(args: argparse.Namespace) -> dict[str, Any]: return list_models(_root(args)) def _standards(args: argparse.Namespace) -> dict[str, Any]: return list_standards(_root(args)) def _validate(args: argparse.Namespace) -> dict[str, Any]: return validate_canon(_root(args)) def _graph(args: argparse.Namespace) -> dict[str, Any]: return artifact_graph(_root(args), output_format=args.format) def _profile_inspect(args: argparse.Namespace) -> dict[str, Any]: return profile_inspect(args.profile, _root(args)) def _api(args: argparse.Namespace) -> dict[str, Any]: serve(host=args.host, port=args.port, root=_root(args)) return {} def _print_json(data: dict[str, Any]) -> None: json.dump(data, sys.stdout, indent=2, sort_keys=True) sys.stdout.write("\n")