# Environment variables

Provider API keys, the provider and base URL resolution order, embedder overrides, server authentication, plugins, and runtime knobs that configure repowise.

Most things are configured via `.repowise/config.yaml` (see
[Configuration](https://docs.repowise.dev/installation/configuration); `repowise init`
generates one for you). The handful of settings that make sense as
environment variables are below.

## LLM provider keys

**All optional.** `repowise init` never requires one: with no provider
resolvable it renders the wiki from your code's structure and exits 0. Set
a key when you want model-written prose instead. See
[Deterministic wiki](https://docs.repowise.dev/intelligence/deterministic-wiki).

**Where to put a key.** Export it in your shell, or add a `KEY=value` line
to the repo's gitignored `.repowise/.env`. That file works for every
provider and embedder key below: `init`, `update`, `generate`, `reindex`,
`serve` and `mcp` load it at startup, whichever flow wrote it, including a
line you added by hand. A variable already set in the shell wins over the
file. `repowise init` writes the key it authenticated with there unless you
pass `--no-save-key`.

Pick one. Pass `--provider <name>` on `init` / `update` / `mcp` to switch,
or set `REPOWISE_PROVIDER` / `REPOWISE_MODEL` for repos whose `config.yaml`
names no provider.

| Variable | Used by |
|---|---|
| `ANTHROPIC_API_KEY` | Default provider |
| `OPENAI_API_KEY` | `--provider openai` |
| `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `--provider gemini` |
| `OPENROUTER_API_KEY` | `--provider openrouter` |
| `DEEPSEEK_API_KEY` | `--provider deepseek` |
| `KIMI_API_KEY` | `--provider kimi` |
| `LITELLM_API_KEY` / `LITELLM_API_BASE` | `--provider litellm` |
| `EDENAI_API_KEY` / `EDENAI_BASE_URL` | `--provider edenai` |
| `OLLAMA_BASE_URL` | `--provider ollama` (default `http://localhost:11434`) |

Base URLs can also be overridden per provider: `ANTHROPIC_BASE_URL`,
`OPENAI_BASE_URL` (used for vLLM/SGLang-compatible endpoints),
`GEMINI_BASE_URL`, `DEEPSEEK_BASE_URL`, `KIMI_BASE_URL`, `LITELLM_BASE_URL`,
`EDENAI_BASE_URL`. A `base_url` in `config.yaml` takes precedence over all of
them.

### Provider, model and base URL order

The CLI, `repowise serve` chat and jobs, and the MCP server resolve the
provider, model and base URL the same way. The first match wins:

| # | Source |
|---|---|
| 1 | `--provider` / `--model`, or a per-request choice in the web UI |
| 2 | `provider`, `model` and `<provider>: {base_url: ...}` in `.repowise/config.yaml` |
| 3 | The global default saved from the web UI settings (`~/.repowise/provider_config.json`) |
| 4 | `REPOWISE_PROVIDER` / `REPOWISE_MODEL` and the provider's `*_BASE_URL` variable |
| 5 | API key variables, in order: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `OLLAMA_BASE_URL`, `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`, `KIMI_API_KEY`, `EDENAI_API_KEY` |

Picking a model or base URL for a repo in the web UI writes that repo's
`config.yaml`, so the CLI uses it too. The repo's Settings page lists the
provider's live models, accepts any typed id, and has a Test connection
button for a base URL.

### Local and OpenAI-compatible gateways

The `openai` adapter talks to anything that speaks the OpenAI API, so a
local model server needs no separate provider. An OpenAI-compatible server
on this machine (LM Studio, Ollama, vLLM) needs no API key. The simplest
setup is a base URL in the repo's `config.yaml`:

```yaml
provider: openai
model: qwen3-coder
openai:
  base_url: http://localhost:1234/v1
```

Or point `OPENAI_BASE_URL` at the gateway's `/v1` endpoint, put the gateway's key (if it wants one) in
`OPENAI_API_KEY`, and pass the model id that endpoint's `/models`
response returns:

```bash
export OPENAI_BASE_URL="http://localhost:8000/v1"
repowise init --provider openai --model qwen3 --reasoning off
```

```bash
export OPENAI_API_KEY="your-gateway-key"
export OPENAI_BASE_URL="http://localhost:20128/v1"
repowise init --provider openai --model ag/gemini-3.7-flash-medium
```

Namespaced model ids are discovered as-is, so the model does not need to
start with `gpt-`. This covers vLLM, SGLang, LM Studio and gateways such
as 9router. In an interactive `repowise init`, the menu row is
**OpenAI-compatible (Custom / local gateway)**: repowise validates the
endpoint, takes the key without echoing it, checks `/models`, and lets
you search the discovered models or type an exact id. The endpoint is
written to the repo's gitignored `.repowise/.env`, and the key is saved
there only after you confirm; `--no-save-key` keeps it process-local.
Pick the plain **openai** row instead for the official OpenAI endpoint.

### Eden AI

[Eden AI](https://www.edenai.co/) is a hosted gateway with an EU
endpoint, so inference can stay in the EU without self-hosting anything.
Every other hosted provider listed above terminates in the US; the
alternatives for a team working under a DPA are Ollama or LiteLLM on your
own infrastructure.

```bash
export EDENAI_API_KEY="..."
export EDENAI_BASE_URL="https://api.eu.edenai.run/v3"
repowise init --provider edenai --model mistral/mistral-small-latest
```

Without `EDENAI_BASE_URL` the requests go to the global endpoint. One key
reaches Mistral, GPT, Claude, Gemini, Cohere, DeepSeek and Llama through
a single OpenAI-compatible endpoint, with models addressed as
`vendor/model`. The catalogue is public and needs no authentication:
[api.edenai.run/v3/models](https://api.edenai.run/v3/models).

`EDENAI_BASE_URL` covers the embedder too, since both read the same
variable. The same key selects `edenai` as the embedder, defaulting to
`amazon/amazon.titan-embed-text-v2:0`, which is served from an EU region
and is the cheapest per token of the models this adapter declares.
Override it with `REPOWISE_EMBEDDING_MODEL`. Only the models the adapter
declares dimensions for are accepted, because a wrong dimension count
would silently mis-size stored vectors.

The default Kimi Code endpoint supports `kimi-for-coding` and
`kimi-for-coding-highspeed`. K2.x models use the separate Kimi Open
Platform: supply a key from that platform together with
`KIMI_BASE_URL=https://api.moonshot.ai/v1`. Repowise selects the
required sampling parameters for either endpoint automatically.

| Variable | Purpose |
|---|---|
| `REPOWISE_PROVIDER` | Provider when no flag, `config.yaml` or saved web UI default names one; skips auto-detection. An empty value (`REPOWISE_PROVIDER=""`, common in CI matrices) is treated as unset |
| `REPOWISE_MODEL` | Model when no flag, `config.yaml` or saved web UI default names one |
| `REPOWISE_DOC_MODEL` | Override the model used for `get_answer` synthesis specifically |
| `REPOWISE_REASONING` | Override `reasoning` (`auto`, `off`, `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) |
| `REPOWISE_ANSWER_LEAN_HIGH` | Set to `1`/`true`/`yes`/`on` to strip re-read evidence (`symbol_bodies`, `quotes`, `flow_path`, `best_guesses`, `code_rationale`) from high-confidence `get_answer` responses, shrinking the payload. Off by default; the citation and prose still carry the answer, but an agent that needs the dropped body pays a `get_symbol` round-trip. Grounded fast paths and why-questions are exempt, since their evidence is the answer. |
| `REPOWISE_CODEX_CLI_CONCURRENCY` / `REPOWISE_OPENCODE_CONCURRENCY` / `REPOWISE_CLAUDE_CLI_CONCURRENCY` / `REPOWISE_COPILOT_CLI_CONCURRENCY` | How many `codex_cli` / `opencode` / `claude_cli` / `copilot_cli` subprocesses run at once. Default `4`, minimum `1`. |
| `REPOWISE_OLLAMA_NUM_CTX` | Pin Ollama's context window (`num_ctx`). Unset sizes it per prompt. |
| `OLLAMA_NUM_PARALLEL` | How many requests repowise sends to Ollama at once. Default `1`; match the value the Ollama server was started with. |
| `REPOWISE_ANSWER_TIMEOUT_S` | Seconds `get_answer` waits for synthesis before giving up. Defaults to a per-provider budget: 60s for the remote API providers, 120s for `ollama` and `litellm`, 180s for `codex_cli` and `opencode`. Capped at 600s. Your MCP client enforces its own tool timeout underneath this one, so a value above it produces a client-side error instead of repowise's diagnosable timeout response. |

Repowise also persists provider/model choice into `.repowise/state.json`
during `init`, so the MCP server and `update` runs reuse the same
provider without you re-specifying.

## Embedder overrides

The embedder powers semantic search and the RAG behind `get_answer`. It's
separate from the LLM provider, and defaults to `mock` (no semantic
search) when no key is detected.

| Variable | Notes |
|---|---|
| `REPOWISE_EMBEDDER` | Embedder: `gemini`, `openai`, `ollama`, `openrouter` or `edenai`. `mock` is the keyless default: keyword search only |
| `REPOWISE_EMBEDDING_MODEL` | Embedding model, applies to whichever embedder is active |
| `REPOWISE_EMBEDDING_DIMS` | Embedding output dimensions (optional; inferred from the model otherwise) |
| `REPOWISE_EMBEDDING_TIMEOUT` | Embed request timeout in seconds |
| `OLLAMA_EMBEDDING_MODEL` | Ollama embedding model (also selects the `ollama` embedder) |
| `OLLAMA_EMBEDDING_DIMS` | Ollama embedding output dimensions (optional) |
| `OLLAMA_EMBEDDING_TIMEOUT` | Ollama embed request timeout in seconds (default: `30`). Raise it for long wiki pages on slow local models, otherwise those pages can silently drop out of the vector store. |

When nothing names an embedder, repowise takes the first one with a key set,
in the order gemini, openai, openrouter, ollama, edenai, and every model run
prints which embedder it uses. The full order, flags and config included, is in
[Configuration](https://docs.repowise.dev/installation/configuration#configyaml).

## Server and database

| Variable | Default | Purpose |
|---|---|---|
| `REPOWISE_DB_URL` | SQLite | Use PostgreSQL instead of SQLite (e.g. `postgresql+asyncpg://...`) |
| `REPOWISE_DATABASE_URL` | none | Legacy alias for `REPOWISE_DB_URL`, still honored |
| `REPOWISE_HOST` | `127.0.0.1` | API server host |
| `REPOWISE_PORT` | `7337` | API server port |
| `REPOWISE_MCP_PORT` | `7338` | MCP SSE server port |
| `REPOWISE_API_URL` | `http://localhost:7337` | Frontend only; backend URL for the web UI |
| `REPOWISE_API_KEY` | none | Bearer token required by clients calling the server API. Without it the server answers local callers only |
| `REPOWISE_TOKENS_FILE` | `~/.repowise/tokens.yaml` | Scoped bearer tokens file (see below) |
| `REPOWISE_PUBLIC_URL` | none | Externally visible base URL (`https://host[/prefix]`) for the MCP HTTP protected-resource document and the 401 challenge, when serving behind a proxy |
| `REPOWISE_CORS_ORIGINS` | any origin, no credentials | Comma-separated origins the API server allows. Setting it also allows credentials. |
| `REPOWISE_CONFIG_DIR` | none | Override where repowise looks for its config directory |
| `REPOWISE_GITHUB_WEBHOOK_SECRET` | none | Secret for verifying GitHub webhook signatures |
| `REPOWISE_GITLAB_WEBHOOK_TOKEN` | none | Token for verifying GitLab webhook requests |
| `REPOWISE_GITEA_WEBHOOK_SECRET` | none | HMAC-SHA256 secret for `POST /api/webhooks/gitea`, which syncs on push from Gitea and Forgejo |

### Scoped tokens

REST, MCP streamable HTTP and SSE, and webhooks all resolve a caller to a
principal with repository and tool scope. Besides the shared
`REPOWISE_API_KEY`, you can list scoped tokens, stored as SHA-256 digests:

```yaml
# ~/.repowise/tokens.yaml
tokens:
  - id: ci-reader
    sha256: "<hex SHA-256 of the token>"   # printf %s "$TOKEN" | sha256sum
    repos: [backend]                    # repository ids or names
    tools: [get_context, get_risk]      # MCP tool names
    expires_at: 2027-01-01T00:00:00Z
```

A token with `tools` is refused on every REST route except chat, which runs
only the tools in its scope. With no key and no tokens file, local callers are
served without a credential and every other caller is refused.

## Workspace MCP

| Variable | Default | Purpose |
|---|---|---|
| `REPOWISE_WORKSPACE_CONCURRENCY` | `4` | Workspace repositories a `repo="all"` call queries at once |
| `REPOWISE_WORKSPACE_REPO_TIMEOUT_S` | `60` | Seconds each repository may take before the call answers without it (named in `repos_failed`). Capped at 300 |

## Plugins and seams

| Variable | Purpose |
|---|---|
| `REPOWISE_PLUGINS` | Comma-separated plugins to load (`module` or `module:attr`), on top of installed `repowise.plugins` entry points |
| `REPOWISE_SEAM_<NAME>` | Pick the implementation of a named seam, e.g. `REPOWISE_SEAM_AUTHENTICATOR`, `REPOWISE_SEAM_CHANGE_METADATA` |

Both are read from the real process environment only, never from a
repository's `.repowise/.env`. `repowise plugins` shows what loaded and which
implementation each seam uses.

## Telemetry

Anonymous usage telemetry is **enabled by default** (opt-out).

| Variable | Purpose |
|---|---|
| `DO_NOT_TRACK` | Any truthy value disables telemetry (respects the cross-tool convention) |
| `REPOWISE_TELEMETRY_DISABLED` | Disables telemetry, repowise-specific |
| `REPOWISE_TELEMETRY_DEBUG` | Prints the telemetry payload to stderr instead of sending it |
| `REPOWISE_OFFLINE` | Disables telemetry, the PyPI release check and the `init` probe of signed-in agent CLIs. `DO_NOT_TRACK` and `REPOWISE_TELEMETRY_DISABLED` turn those off too |

Each event names the host that ran repowise as one fixed label (`claude_code`,
`codex`, `cursor`, `vscode`, `ci` or `terminal`), and a known failure carries a
fixed reason code, never its message. Nothing is queued or sent while a test
runner drives repowise.

## Misc

| Variable | Purpose |
|---|---|
| `REPOWISE_GIT_INDEX_TIMEOUT_S` | Seconds one file's git history indexing may take (default `120`). A file that runs over keeps what was read so far. Raise it for very large histories |
| `REPOWISE_GRAMMAR_DIR` | A directory of grammars staged with [`repowise grammars fetch`](https://docs.repowise.dev/cli/other#repowise-grammars-fetch), for indexing COBOL or Apex offline |
| `REPOWISE_NO_OPEN` | Stop `repowise serve` opening the web UI in a browser (same as `--no-open`) |
| `REPOWISE_GATE_SUMMARY_DIR` | A directory where each CI gate run with `--format github` leaves its result for [`repowise ci-summary`](https://docs.repowise.dev/cli/gates#repowise-ci-summary) |
| `REPOWISE_GIT_WINDOW_ANCHOR` | Git history windows (churn, prior fixes, age) are measured from the indexed commit, so re-indexing an unchanged tree later gives the same scores. Set to `now` to measure from wall-clock time instead. |
| `REPOWISE_HOOK_COVERAGE_REINGEST` | Set to `1` to turn on the coverage re-ingest hook for one session (see `hooks.coverage_reingest` in [Configuration](https://docs.repowise.dev/installation/configuration#the-hooks-block)). |
| `REPOWISE_SKIP_EDITOR_SETUP` | Stop `init` writing machine-wide editor config (same as `init --no-editor-setup`). Use it for CI, sandboxes and benchmark runs |
| `REPOWISE_NO_SAVE_KEY` | Same as `init --no-save-key`: keep the provider key process-local |
| `REPOWISE_NO_HINTS` | Turn off the occasional repowise.dev hint line (also `repowise config hints off`) |
| `REPOWISE_FORGE_HOSTS` | Self-managed git hosts as `host=forge` pairs (`git.corp.com=gitlab,tfs.corp.com=azure`). Wins over `forges.hosts` in `config.yaml` |
| `REPOWISE_MCP_DEBUG_META` | Set to `1` to restore the full `_meta` diagnostics on every MCP response |
| `REPOWISE_MCP_PRETTY_JSON` | Set to `1` for indented JSON in MCP text results |
| `REPOWISE_CHANGELOG` | Override the changelog source used by the "what's new" check |
| `REPOWISE_PARSE_WORKERS` | How many processes parse files during indexing. Defaults to your CPU count capped at 8, and never exceeds the number of files to parse. Each worker is a separate interpreter holding roughly 50 MB, so lower it on a memory-constrained machine; raising it above 8 is not measurably faster. |
| `PYTHONIOENCODING` | On Windows PowerShell, set to `utf-8` if you see codec errors during indexing |

## Hosted version

If you're using [repowise.dev](https://repowise.dev), none of the
above apply, because the platform manages keys for you. The only env var
you'd set client-side is the API key your editor uses to talk to
the hosted MCP endpoint, which the
[Settings → Editor](https://www.repowise.dev/settings?tab=editor)
page generates automatically.

> **Note**
>
> **Model keys on hosted.** Hosted plans run on the platform's model, and AI
> use is paid from credits. To use your own Anthropic, OpenAI, or Gemini key,
> run the open-source CLI or a self-hosted deployment.
