"""`dashboard` CLI subcommand (spec §5, task t_b38e9af2). Wires the dashboard server into the shared `scraper/cli.py` ADDITIVELY + DEFENSIVELY, mirroring parser/correlate/analyze (the guarded `register()` pattern): `cli.py` calls `register(sub)` inside a try/except and adds its handler without touching any sibling handler. The `dashboard` name does not collide. This is the CLI/app entry point per the task contract. The server itself lives in `scraper/dashboard/server.py`; this module just parses flags and calls `server.serve(...)`. """ from __future__ import annotations import logging from .. import relational from . import server as _server log = logging.getLogger("scraper.dashboard_cli") def _already_registered(sub, name: str) -> bool: actions = getattr(sub, "_choices_actions", []) return any(getattr(a, "name", None) == name for a in actions) def register(sub) -> None: """Register the `dashboard` subcommand (guarded — skip if a sibling owns it).""" if _already_registered(sub, "dashboard"): return pd = sub.add_parser( "dashboard", help="serve the legislative dashboard (API + static UI)", description=("Serve the legislative dashboard. Reads the relational DB " "(bills/committees/members + bill_tags) and serves the " "API_CONTRACT.md endpoints + the static frontend."), ) pd.add_argument("--host", default="127.0.0.1", help="bind host (default 127.0.0.1)") pd.add_argument("--port", type=int, default=8000, help="bind port (default 8000)") pd.add_argument("--db", default=relational.DEFAULT_DB, help="SQLite path (default db/daily_file.sqlite)") pd.add_argument("--prefs", default="out/prefs.json", help="preferences JSON file (default out/prefs.json)") pd.add_argument("--auth", default="out/auth.json", help="local-auth JSON file (default out/auth.json)") pd.add_argument("--no-static", dest="static", action="store_false", help="API only, no static frontend") pd.add_argument("--log", action="store_true", help="log HTTP requests to stderr") pd.set_defaults(func=handle_dashboard) def handle_dashboard(args) -> int: """CLI handler for the `dashboard` subcommand.""" return _server.serve( host=args.host, port=args.port, db_path=args.db, prefs_path=args.prefs, auth_path=args.auth, serve_static=getattr(args, "static", True), log_on=getattr(args, "log", False), ) def main(argv=None) -> int: """Direct invocation: `python -m scraper.dashboard_cli`.""" import argparse p = argparse.ArgumentParser( prog="scraper.dashboard_cli", description="Serve the legislative dashboard.") sub = p.add_subparsers(dest="command", required=True) register(sub) args = p.parse_args(argv) return args.func(args) if __name__ == "__main__": import sys sys.exit(main())