"""Lists the models the API actually offers today. python -m brain.cli.models Model names are settings, never logic (CLAUDE.md), and Gemini renames and retires them often. This is the command that tells you what to put in GEMINI_MODEL — the web app gets the same list through the brain's `/models` route in P3, which is what makes a renamed model a settings change rather than a redeploy. """ import asyncio import os from rich.console import Console from .. import config from ..llm.gemini import GeminiClient from ..llm.keys import KeyRing async def main_async() -> int: console = Console() settings = config.load_settings() ring = KeyRing.from_env(dict(os.environ)) if not ring.configured_slots(): console.print("[red]No GEMINI_KEY_1 in the environment.[/red] See backend/.env.example.") return 1 async with GeminiClient(ring, settings) as client: models = await client.list_models() console.print(f"[bold]{len(models)} models[/bold]\n") for name in models: marker = "" if name == settings.chat_model: marker = " [green]← GEMINI_MODEL[/green]" elif name in settings.fallback_models: marker = " [dim]← fallback[/dim]" elif name == settings.embedding_model: marker = " [cyan]← embeddings[/cyan]" console.print(f" {name}{marker}") configured = set(settings.model_chain) | {settings.embedding_model} missing = sorted(configured - set(models)) if missing: console.print( f"\n[yellow]Configured but not offered by the API:[/yellow] {', '.join(missing)}\n" "Set GEMINI_MODEL in backend/.env (or the fallbacks in brain/config.py) to ids from " "the list above." ) return 0 def main() -> int: return asyncio.run(main_async()) if __name__ == "__main__": raise SystemExit(main())