# Using crowbot with opencode [opencode](https://opencode.ai) reaches crowbot through the AI SDK's OpenAI-compatible provider, which is a config block rather than an integration to build. The installer also drops in a small plugin. A provider declared in `opencode.json` has a **frozen** model list, because opencode never asks us what we serve; the plugin refreshes it at startup. ## Install ```sh curl -fsSL https://crowbot.sh/install.sh | bash ``` On Windows, in PowerShell (5.1 or 7): ```powershell irm https://crowbot.sh/install.ps1 | iex ``` It asks whether to configure opencode globally or for one project, then takes your account number or pairs through the browser. Pairing creates a **separate key for that machine**, so a laptop is revocable without changing your account number. Non-interactive: ```sh curl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --key 4192083755612094 curl -fsSL https://crowbot.sh/install.sh | bash -s -- --local --pair curl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --pair --dry-run # print, write nothing ``` The PowerShell installer takes the same flags as `-Global`, `-Local`, `-Key`, `-Pair` and `-DryRun`; `iex` cannot pass them, so use the script-block form: ```powershell & ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Global -Key 4192083755612094 & ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Local -Pair ``` The installer **merges** into an existing config and keeps a timestamped backup. It only ever writes the `provider.crowbot` key, and it refuses to touch a config it cannot parse. Then run `/models` inside opencode and pick a crowbot model. ## Keeping the model list current Two files get written: | | | |---|---| | `opencode.json` | the provider block, including a snapshot of the models | | `plugin/crowbot.js` | refreshes that model list at startup | The plugin loads by sitting in that directory, so there is nothing to add to your config and re-running the installer cannot duplicate it. It fetches `https://api.crowbot.sh/docs/opencode.json`, replaces the model list and touches nothing else — never your key. If we cannot be reached it does nothing, capped at a couple of seconds so an outage here cannot stop opencode starting. Delete `plugin/crowbot.js` if you would rather pin the model list. Re-run the installer when it needs updating. A 404 naming a model you thought you had means your config predates a rename; the error says what replaced it, and re-running the installer repoints you. ## Config, by hand The generated block with current models, limits and prices is always at **[https://api.crowbot.sh/docs/opencode.json](https://api.crowbot.sh/docs/opencode.json)**. > **Do not `curl -o` over your global config.** `~/.config/opencode/opencode.json` holds > your other providers, agents, keybinds and MCP servers, and opencode merges settings > across levels rather than replacing them. Redirecting the generated file over it > deletes all of that. Paste the `provider` block into your existing file instead, or let > the installer merge it. ```json { "$schema": "https://opencode.ai/config.json", "provider": { "crowbot": { "npm": "@ai-sdk/openai-compatible", "name": "crowbot", "options": { "baseURL": "https://api.crowbot.sh/v1", "apiKey": "{env:CROWBOT_API_KEY}" }, "models": { "crow-2": { "name": "CROW 2", "limit": { "context": 1000000, "output": 32768 }, "cost": { "input": 6.0, "output": 10.0, "cache_read": 0.6 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "crow-1": { "name": "CROW 1", "limit": { "context": 262144, "output": 32768 }, "cost": { "input": 3.0, "output": 5.0, "cache_read": 0.3 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-6-astra": { "name": "GPT 6 Astra", "limit": { "context": 1048576, "output": 272000 }, "cost": { "input": 0.3, "output": 1.5, "cache_read": 0.03 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-6-sol": { "name": "GPT 6 Sol", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.18, "output": 0.9, "cache_read": 0.018 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "kimi-k3": { "name": "Kimi K3", "limit": { "context": 1048576, "output": 1048576 }, "cost": { "input": 0.27, "output": 1.35, "cache_read": 0.027 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-5.6-sol": { "name": "GPT 5.6 Sol", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.08, "output": 0.4, "cache_read": 0.008 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "deepseek-v4.1-flash": { "name": "DeepSeek V4.1 Flash", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.045, "output": 0.18, "cache_read": 0.0009 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gemini-3.8-flash": { "name": "Gemini 3.8 Flash", "limit": { "context": 1048576, "output": 65536 }, "cost": { "input": 0.0675, "output": 0.3375, "cache_read": 0.0067 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false } } } } } ``` Set the key the config expects: ```sh export CROWBOT_API_KEY=4192083755612094 # your account number, no spaces ``` ## What each field is doing | field | why it matters | |---|---| | `npm` | `@ai-sdk/openai-compatible` is the right driver: we serve `/v1/chat/completions`, not `/v1/responses` | | `apiKey` | `{env:CROWBOT_API_KEY}` keeps your account number out of the file | | `tool_call` | true where the model calls tools; we translate them in both directions, so agent mode works | | `reasoning` + `interleaved` | `"reasoning_content"` is the field our SSE actually emits, so opencode renders the thinking trace instead of discarding it | | `temperature` | true where the model takes a sampling temperature; sampling parameters are forwarded as you send them | | `cost` | **our** rates, not the upstream vendor's — so opencode's spend display matches what you are actually charged | | `limit` | real context and output ceilings, so opencode truncates before we have to | ## Notes - **Fund the account first.** A new account is empty, so opencode gets an `insufficient_balance` 402 until you top up. See [Quickstart](https://crowbot.sh/docs/quickstart.md). - **Your system prompt is honoured.** opencode's agent definition reaches the model unchanged and stays first. See [API](https://crowbot.sh/docs/api.md). - **No proof-of-work on the API.** The chat requires one per message; `/v1` never does. - If a model shows up but every call fails, check `/api/me` for your balance first.