"""CLI entry point.""" from __future__ import annotations import asyncio from typing import Optional import typer from rich.console import Console from .config import DEFAULT_HOST, DEFAULT_PORT from .login import is_logged_in, load_session, login from .server import run_server app = typer.Typer(help="hm-api - DevEco Code OpenAI-compatible API CLI") console = Console() def _empty_as_none(value: str | None) -> str | None: return value if value else None @app.command("login") def login_cmd( proxy: Optional[str] = typer.Option( "", "--proxy", "-p", help="HTTP/HTTPS proxy for login requests" ), no_browser: Optional[bool] = typer.Option( False, "--no-browser", help="Print login URL instead of opening browser" ), timeout: Optional[int] = typer.Option( 600, "--timeout", "-t", help="Login callback timeout in seconds", min=60 ), ) -> None: """Login with Huawei DevEco account via browser OAuth.""" proxy = _empty_as_none(proxy) if no_browser: console.print("[bold blue]Use the URL below to login:[/bold blue]") else: console.print("[bold blue]Opening browser for DevEco login...[/bold blue]") result = asyncio.run(login(proxy=proxy, no_browser=no_browser or False, timeout=timeout or 600)) if result.success and result.user_info: console.print( f"[bold green]Login successful![/bold green] Welcome, {result.user_info.user_name}" ) elif result.cancelled: console.print("[yellow]Login cancelled by user.[/yellow]") raise typer.Exit(1) elif result.unsupported_region: console.print("[red]Only China site accounts are currently supported.[/red]") raise typer.Exit(1) else: console.print(f"[red]Login failed:[/red] {result.error}") raise typer.Exit(1) @app.command() def serve( host: Optional[str] = typer.Option( DEFAULT_HOST, "--host", "-h", help="Host to bind the server" ), port: Optional[int] = typer.Option( DEFAULT_PORT, "--port", "-p", help="Port to bind the server", min=1, max=65535 ), proxy: Optional[str] = typer.Option( "", "--proxy", help="HTTP/HTTPS proxy for upstream requests" ), key: Optional[str] = typer.Option( None, "--key", "-k", help="API key for client authentication; omit to disable auth" ), ) -> None: """Start the OpenAI-compatible API server.""" if not is_logged_in(): console.print( "[yellow]Not logged in. Run [bold]hm-api login[/bold] first.[/yellow]" ) raise typer.Exit(1) import asyncio session = asyncio.run(load_session()) if session: console.print( f"[blue]Logged in as {session.get('user_name') or session.get('user_id')}.[/blue]" ) proxy = _empty_as_none(proxy) key = _empty_as_none(key) console.print(f"[bold green]Starting server at http://{host}:{port}[/bold green]") if key: console.print("[dim]API key authentication enabled.[/dim]") else: console.print("[dim]API key authentication disabled.[/dim]") if proxy: console.print(f"[dim]Upstream proxy: {proxy}[/dim]") run_server(host=host or DEFAULT_HOST, port=port or DEFAULT_PORT, api_key=key, proxy=proxy) @app.command() def status() -> None: """Show current login status.""" import asyncio if is_logged_in(): session = asyncio.run(load_session()) if session: console.print( f"[green]Logged in[/green] as {session.get('user_name') or session.get('user_id')}" ) else: console.print("[green]Logged in[/green]") else: console.print("[red]Not logged in[/red]") @app.command("hf-secret") def hf_secret() -> None: """Print the Hugging Face Secret value for the current login.""" import asyncio session = asyncio.run(load_session()) token = session.get("access_token") if session else "" if not token: console.print("[red]Not logged in or access token is missing.[/red]") raise typer.Exit(1) console.print("Set this in Hugging Face Space Secrets:", style="bold blue") console.print(f"HM_ACCESS_TOKEN={token}", markup=False) if __name__ == "__main__": app()