# LobeHub CLI Reverse Engineering (2026-07-02) ## Discovery LobeHub has a CLI (`@lobehub/cli`, binary `lh`) that provides a REAL API to LobeHub Cloud. This disproves the prior claim that "LobeHub = UI frontend, NOT API." ## Source - GitHub: `lobehub/lobehub` monorepo - CLI code: `apps/cli/src/` - Server routers: `apps/server/src/routers/lambda/aiChat.ts` (tRPC) - Constants: `apps/cli/src/constants/urls.ts` ## Architecture ``` lh CLI → app.lobehub.com (tRPC server) → agent-gateway.lobehub.com (WebSocket streaming) → Auth: .lobehub/credentials.json (JWT/OIDC) ``` - Server: `app.lobehub.com` (tRPC protocol) - WebSocket gateway: `agent-gateway.lobehub.com` - Auth headers: `X-API-Key` (API key) or `Oidc-Auth` (JWT) - Credentials stored: `~/.lobehub/credentials.json` (encrypted blob) - Settings: `~/.lobehub/settings.json` ## Key Commands ```bash # Auth lh login # Browser Device Code Flow lh whoami # Check current user lh login --server # Custom server # Models lh provider list # List providers (openai, anthropic, deepseek, etc.) lh model list # List models for provider lh model view # Model details # Generation lh generate text [options] # Text generation (single completion) lh generate image # Image generation lh generate video # Video generation lh generate tts # Text-to-speech lh generate asr # Speech-to-text # Other lh agent # Manage agents lh kb # Knowledge bases lh session-group # Session groups lh thread # Message threads ``` ## `lh generate text` Options ``` -m, --model Model ID (provider/model format) (default: openai/gpt-4o-mini) -p, --provider Provider name (derived from model if omitted) -s, --system System prompt --temperature Temperature (0-2) --max-tokens Maximum output tokens --stream Enable streaming (SSE, renders incrementally) --json Output full JSON response --pipe Pipe mode: read additional context from stdin ``` ## CRITICAL: Always use `--stream --json` together Without `--json`: plain text output (hard to parse). Without `--stream`: `--json` tries to parse the entire response as one JSON object — fails on models that return reasoning tokens (DeepSeek) with: `Unexpected token 'i', "id: undefi"... is not valid JSON` With `--stream --json`: JSON Lines output — each line is either: 1. A string token (text chunk): `"Hello"` → `json.loads()` gives the text 2. A JSON object (metadata): `{"delta":{"role":"assistant"},...}`, `{"totalTokens":...}`, `{"duration":...}` ## Stream Output Format (JSON Lines) ``` {"delta":{"role":"assistant"},"id":"gate-...","index":0} # initial delta "token" # text chunk (string) "another" # more text {"totalTokens":27,"cost":0,...} # usage {"duration":203,"latency":2585,"tps":29.55,...} # timing ``` ## Status markers to filter out These appear as string tokens but are NOT text content: ```python SKIP_TOKENS = {"in_progress", "message_stop", "end_turn", "completed", "done", "max_tokens"} ``` ## Available Models (verified 2026-07-02) ### OpenAI (via LobeHub) - `openai/gpt-5.5` (enabled) - `openai/gpt-5.4` (enabled) - `openai/gpt-5.4-mini` (enabled) - `openai/gpt-5.5-pro`, `gpt-5.4-pro`, `gpt-5.4-nano` (disabled by default) - `openai/o4-mini`, `o3-pro`, `o3` (disabled) ### Anthropic (via LobeHub) - `anthropic/claude-fable-5` (enabled) - `anthropic/claude-opus-4-8` (enabled) - `anthropic/claude-opus-4-7` (enabled) - `anthropic/claude-sonnet-4-6` (enabled) - `anthropic/claude-haiku-4-5` (enabled) - `anthropic/claude-opus-4-6` (disabled) ### DeepSeek (via LobeHub) - `deepseek/deepseek-v4-pro` (enabled) - `deepseek/deepseek-v4-flash` (enabled) - `deepseek/deepseek-r1` (enabled) ### Other providers available google, glmcodingplan, kimicodingplan, moonshot, bedrock, bailiancodingplan, vertexai, azure, azureai, aihubmix, openrouter, fal, ollama, ollamacloud, vllm ## Windows Subprocess Pitfall `asyncio.create_subprocess_exec("lh", ...)` fails with `FileNotFoundError: [WinError 2]`. The `lh` shim in PATH is a bash script; Windows needs `lh.cmd`. **Fix:** Use full path to `lh.cmd`: ```python LH_CMD = r"C:\Users\User\AppData\Roaming\npm\lh.cmd" proc = await asyncio.create_subprocess_exec(LH_CMD, "generate", "text", ...) ``` ## Python Async Generator Pitfall `return value` inside an `async def` generator = `SyntaxError: 'return' with value in async generator`. **Fix:** Use `yield` for final result instead of `return`: ```python async def run_lh(...): # ... process tokens ... yield {"type": "done", "text": "".join(text_buf), "usage": usage} # NO return statement ```