BYOK

Beta

BYOK is currently in beta and subject to improvements.

Bring your own provider and use it in Command Code CLI. Any OpenAI or Anthropic compactible API can serve models in Command Code. You can bring your key from hosted providers (DeepSeek, OpenRouter, Vercel AI Gateway), local servers (Ollama, vLLM), or other coding agent's APIs.

You can connect to any external provider in Command Code, requests go straight from your machine to that provider's endpoint with your key, never through Command Code's servers.

BYOK providers can be managed from /connect in the Command Code CLI. Below are providers and instructions on how to connect to them.

Command Code is the default and recommended provider. Every model in /model routes through your plan, and the models are optimized for the cmd harness. That optimization is real engineering: see Harness Engineering for how the harness repairs, steers, and optimizes models.

The /model menu can be used to access models from Command Code and BYOK providers. BYOK models are active alongside Command Code models, and you switch back to Command Code models anytime through the model menu.

You can use your ChatGPT Pro/Plus subscription directly in Command Code. Select OpenAI ChatGPT/Codex in /connect (or run cmd login openai, chatgpt works too) and sign in.

The provider package installs on demand at first login, and its models appear as their own section in /model. Requests run locally from the CLI over your subscription.

Same flow for an active GitHub Copilot subscription: select GitHub Copilot in /connect (or cmd login copilot), complete the device sign-in, and the copilot provider's models will be listed in /model.

The /connect menu supports 150+ providers out of the box. For those, the endpoint, wire, and current model list come prefilled. Anything else works too: any OpenAI-compatible or Anthropic-wire endpoint can also be setup.

Run /connect and pick (BYOK) Add your own provider.

From the supported providers list (the happy path)

The connect menu supports 150+ providers with endpoint, models, and config prefilled. You can search and pick a provider from this menu.

1

Pick the provider

Start typing at the id step (openrouter, deepseek, groq, …) and a browsable list of known providers shows under the input. Pick one with ↑/↓ + enter; the wire and endpoint prefill.

2

Paste your key

Keys are stored securely in ~/.commandcode/auth.json. Press enter on the empty field if the endpoint takes no key (local servers).

3

Models arrive on their own

Discovery is silent: the provider's current model list, names and context windows included, is imported for you. Finishing lands you in /model with the new models selectable and routing.

Not in the list (fully custom)

If your provider is not in the menu, or you want to declare a custom/local one, follow these steps:

1

Name it

Type any id, a short lowercase handle (work-llm). It names the entry in ~/.commandcode/providers.json and prefixes its models (work-llm/gpt-x).

2

Pick the wire

OpenAI-compatible (/chat/completions, the default: Ollama, vLLM, LM Studio, most hosted APIs) or Anthropic (/v1/messages, for endpoints speaking the Claude Messages API).

3

Point at the endpoint

The API root. OpenAI-compatible servers usually end in /v1; Anthropic roots get /v1 added on the wire when missing. A pasted URL carrying a route suffix (…/v1/chat/completions) is trimmed to the root automatically, with a note showing what was removed.

4

Add the key, or skip it

Key is securely stored in auth.json. Press enter on the empty field if the endpoint takes no key (local servers).

5

Declare the models

The menu asks the endpoint's own /models listing first. If that comes up empty, you can either type ids there or modify the providers.json file to add the models. See the config schema below for details.

Press h on any non-typing step for the condensed version of all of this; esc (or where it can't mean cursor movement) goes back one step.

Note

You can also just ask the agent: add OpenRouter as a provider and enable deepseek/deepseek-v4-flash. It reads its bundled BYOK reference, looks up current model ids and context windows when it needs them, and writes the providers.json entry for you. Review it like any other edit.

Everything related to byok providers lives in ~/.commandcode/providers.json. It is yours to edit, and changes apply live (reopen /connect or /model). For example:

// ~/.commandcode/providers.json { "provider": { "novita": { "name": "Novita", "baseURL": "https://api.novita.ai/v3/openai", "apiKey": "$NOVITA_API_KEY", "models": { "qwen/qwen3.6-27b": { "name": "Qwen 3.6 27B", "contextWindow": 131072, "maxOutput": 16384, "reasoningEfforts": ["low", "high"], "cost": { "input": 0.4, "output": 1.6 }, "options": { "temperature": 0.6 } } } }, "claude-proxy": { "api": "anthropic-messages", "baseURL": "https://api.novita.ai/anthropic", "apiKey": "$NOVITA_API_KEY", "models": { "minimax/minimax-m2.7": {} } }, "ollama": { "baseURL": "http://localhost:11434/v1", "apiKey": false, "models": { "llama3.3:70b": {} } } } }

Provider fields

FieldValuesNotes
namestringDisplay name in /connect and /model; defaults to the id
apiopenai-completions (default) | anthropic-messagesOmit for OpenAI-compatible endpoints. Any other value skips the provider
npmAI-SDK package nameCompat only: @ai-sdk/openai-compatible & @ai-sdk/openai map to openai-completions, @ai-sdk/anthropic to anthropic-messages; any other package warns and assumes openai-completions. Ignored when api is set.
baseURLURLRequired. The API root requests go to. Missing or invalid URL skips the provider. Also read from options.baseURL (top-level wins)
apiKey"$ENV_VAR" | "{env:VAR}" | "!command" | falseA key saved via /connect wins over this field. It is reference to the key, never the key itself. "$VAR" and "{env:VAR}" read that env variable; "!command" runs the command and uses its output; false = keyless endpoint, no auth header sent. A pasted raw secret is refused with a warning.
headersobjectExtra request headers, string values only. One non-string value drops the whole object with a warning
modelsobjectRequired. {} per id is enough. Zero valid models skips the provider
disabledtrueTombstone: the entry is skipped silently, no warning. "enabled": false means the same

Model fields

Every field is optional but we recommend adding contextWindow as a bare minimum. "a-model-id": {} is a complete declaration:

FieldValuesNotes
namestringDisplay name in /model
contextWindownumber > 0Tokens. Compat alias: limit.context (contextWindow wins when both are set) Default: 200K
maxOutputnumber > 0Tokens. Compat alias: limit.output
reasoningbooleantrue assumes the efforts low / medium / high; false declares none
reasoningEffortsstring[]Exact valid set: low | medium | high | xhigh | max. Unknown levels are dropped with a warning naming that set
costobject$ per 1M tokens: input, output, cacheRead, cacheWrite (snake_case cache_read / cache_write accepted); 0 is valid for free models
optionsobjectExtra request-body params merged into every call for this model (temperature, top_p, …)

A non-object model value skips that model with a warning; sibling models survive.

Below are examples of BYOK providers added in ~/.commandcode/providers.json. Command Code is the default provider, nothing to declare for it.

OpenRouter

// ~/.commandcode/providers.json { "provider": { "openrouter": { "name": "OpenRouter", "baseURL": "https://openrouter.ai/api/v1", "apiKey": "$OPENROUTER_API_KEY", "models": { "deepseek/deepseek-v4-flash": {} } } } }

Vercel AI Gateway

// ~/.commandcode/providers.json { "provider": { "vercel": { "name": "Vercel AI Gateway", "baseURL": "https://ai-gateway.vercel.sh/v1", "apiKey": "$AI_GATEWAY_API_KEY", "models": { "deepseek/deepseek-v4-flash": {} } } } }

Cloudflare AI Gateway: its OpenAI-compatible root carries your account and gateway ids, and model ids are provider/model as the gateway spells them:

// ~/.commandcode/providers.json { "provider": { "cf-gateway": { "name": "Cloudflare AI Gateway", "baseURL": "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/compat", "apiKey": "$CF_AI_GATEWAY_TOKEN", "models": { "anthropic/claude-sonnet-4-6": {} } } } }

Hugging Face router

// ~/.commandcode/providers.json { "provider": { "huggingface": { "name": "Hugging Face", "baseURL": "https://router.huggingface.co/v1", "apiKey": "$HF_TOKEN", "models": { "Qwen/Qwen3.6-27B-Instruct": {} } } } }

Ollama (local, keyless)

// ~/.commandcode/providers.json { "provider": { "ollama": { "baseURL": "http://localhost:11434/v1", "apiKey": false, "models": { "llama3.3:70b": {} } } } }

Selecting a custom provider in /connect opens its card: the endpoint, its declared model count (with a clickable config: providers.json link straight to the file), the key situation, and the actions that fit.

Key stateCard showsActions
Stored keykey sk_… ✓ stored in auth.jsonenter save · r replace key · c clear · u update models · d delete
File referencekey $VARr store key · u update models · d delete
Keylessno key — the endpoint asks for noner store key · u update models · d delete
No key yetno key yet — requests fail until one is addedenter add key · u update models · d delete

In the /connect menu itself, a healthy provider row shows a green ✓ with its model count. Only ○ needs a key appears when requests would fail without action.

Update u refetches the provider's model list (registry first, the endpoint's /models as fallback) and updates the provider's models. The key reference, headers, and wire are untouched.

Delete d removes the provider. It is deleted from providers.json, its stored key is cleared, and its models leave /model immediately; if it owned your active model, the session switches to the default model.

If there is a malformed entry in providers config, it never breaks its siblings. It's skipped with a specific warning shown under the /connect list and on --list-models:

providers.json needs attention — one entry did not load as written: provider.work-llm: no baseURL configured — provider skipped

An unreadable or unparseable file warns the same way (providers config <path>: invalid JSON (details redacted)). Your providers vanish for that run instead of crashing the CLI. The /connect writers never touch a file they can't parse: an add, model refetch, or remove against broken JSON refuses with nothing written.

For setups that must never contact Command Code's backend at all (a team running the CLI against their own endpoints, CI fleets, or other environments), they can turn on local-only mode:

cmd --local-only # or via the environment: CMD_LOCAL_ONLY=1 cmd

or persist it in ~/.commandcode/config.json:

{ "localOnly": true }

What it guarantees:

  • No telemetry, anywhere. Trace exporters, lifecycle events, telemetry identity, and machine fingerprinting are all off. (DO_NOT_TRACK=1 also stops all of these, including fingerprinting; local-only additionally blocks the non-telemetry backend traffic below.)
  • No billing reads. The plan/credits prefetches never run; model pickers treat the absent plan as unrestricted and your providers gate access themselves.
  • A refusal transport. The session's backend transport is replaced with one that throws a clear LocalOnlyError naming the route and the switch. Nothing can reach Command Code including a model id that would otherwise fall through to the Command Code provider.

What stops working, by design: Command Code catalog (gateway) models, /share, /usage, the server-proxied web_search/web_fetch tools, cmd taste push/pull, and backend agent generation. Your BYOK providers, and everything local keep working exactly as before.

Which model do sub-tasks use when I am on a BYOK model?

Your BYOK session model, for everything. Compaction and summarization, sub-agents, title generation: all the side tasks default to the session model while a BYOK model is active, so nothing silently falls back to Command Code (or bills your plan) mid-session.

Where are my API keys stored?

Keys pasted in /connect live in ~/.commandcode/auth.json (written 0600), each under its provider's id, beside your Command Code login and subscription tokens. Logging out of Command Code strips only the account fields. Provider keys survive. Update a key any time with r on the provider's card in the /connect menu.

Can I put a key directly in providers.json?

Only as a reference: "$ENV_VAR", "{env:VAR}", or "!command" (see the schema above). A raw secret pasted into the file is refused at parse with a warning; the provider survives, only the key is ignored. Per request, a stored key wins over the file's reference.

How do I customize a provider or its models?

Edit ~/.commandcode/providers.json. The full schema is documented above, and changes apply live (reopen /connect or /model). Per model you can set the display name, context window, max output, reasoning efforts, cost, and extra request-body options like temperature. Per provider: headers, the wire (api), and the endpoint.

What if a model id exists on Command Code and my provider?

Both stay selectable. /model shows each under its own heading, and picking a row routes exactly where its heading says. Two of your own providers sharing an id works the same way: each qualified model id owns its route.

Can a project or repo define its own providers?

No. BYOK providers are set only on the user-level, with config stored globally at ~/.commandcode/providers.json.

Do BYOK requests or my keys ever touch Command Code?

No. Requests go straight from your machine to your endpoint, and keys never leave it. The only thing emitted is a trimmed telemetry span: model id, provider id, usage, latency, and finish reason. Never prompts, keys, endpoint URLs, or errors after scrubbing. DO_NOT_TRACK=1 or local-only mode turns even that off.