import webbrowser import socket import typer import uvicorn from led_platform.core.config import get_settings app = typer.Typer(help="LED wall projection platform CLI.") def _lan_base_url(port: int = 8000) -> str: try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: sock.connect(("8.8.8.8", 80)) host = sock.getsockname()[0] except OSError: host = "127.0.0.1" return f"http://{host}:{port}" @app.command() def serve( host: str = typer.Option(None, help="Bind host."), port: int = typer.Option(None, help="Bind port."), reload: bool = typer.Option(False, help="Enable uvicorn reload."), ) -> None: settings = get_settings() uvicorn.run( "led_platform.main:app", host=host or settings.app_host, port=port or settings.app_port, reload=reload, ) @app.command() def urls( base: str = typer.Option(None, help="Controller base URL."), ) -> None: base = base or _lan_base_url() typer.echo(f"Admin: {base}/") typer.echo(f"Left output: {base}/output/left") typer.echo(f"Right output:{base}/output/right") typer.echo(f"Health: {base}/healthz") typer.echo(f"Sync: {base}/api/sync/status") @app.command() def open( base: str = typer.Option("http://127.0.0.1:8000", help="Controller base URL."), target: str = typer.Option("admin", help="admin, left, right, sync"), ) -> None: targets = { "admin": f"{base}/", "left": f"{base}/output/left", "right": f"{base}/output/right", "sync": f"{base}/api/sync/status", } url = targets.get(target) if not url: raise typer.BadParameter("target must be one of: admin, left, right, sync") webbrowser.open(url) typer.echo(url) if __name__ == "__main__": app()