# Configuration Nerve is configured via a `.env` file in the project root. All variables have sensible defaults — only `GATEWAY_TOKEN` is strictly required. --- ## Setup Wizard The interactive setup wizard is the recommended way to configure Nerve: ```bash npm run setup # Interactive setup (6 steps) npm run setup -- --check # Validate existing config & test gateway npm run setup -- --defaults # Non-interactive with auto-detected values npm run setup -- --help # Show help ``` ### Wizard Steps The wizard walks through **6 sections**: #### 1. Gateway Connection Connects Nerve to your OpenClaw gateway. The wizard auto-detects the gateway token from: 1. Existing `.env` (`GATEWAY_TOKEN`) 2. Environment variable `OPENCLAW_GATEWAY_TOKEN` 3. `~/.openclaw/openclaw.json` (auto-detected) Tests the connection before proceeding. If the gateway is unreachable, setup stops so you can fix the gateway or token first. On current OpenClaw builds, the wizard also: - Reads the real gateway token from the systemd service file (works around a known bug where `openclaw onboard` writes different tokens to systemd and `openclaw.json`) - Bootstraps `paired.json` and `device-auth.json` with full operator scopes if they don't exist yet - Pre-pairs Nerve's device identity in the normal setup path so it can connect without manual approval (`openclaw devices approve`) - Adds `cron`, `gateway`, and `sessions_spawn` to `gateway.tools.allow` when they are missing - Restarts the gateway to apply changes #### 2. Agent Identity Sets the `AGENT_NAME` displayed in the UI. #### 3. Access Mode Determines how you'll access Nerve. The wizard auto-configures `HOST`, `ALLOWED_ORIGINS`, `WS_ALLOWED_HOSTS`, and `CSP_CONNECT_EXTRA` based on your choice: | Mode | Bind | Description | |------|------|-------------| | **Localhost** | `127.0.0.1` | Only accessible from this machine. Safest option. | | **Tailscale IP** | `0.0.0.0` | Accessible from your Tailscale network over the machine's tailnet IP. Sets CORS + CSP for that IP. | | **Tailscale Serve** | `127.0.0.1` | Keeps Nerve loopback-only and exposes it through a Tailscale Serve HTTPS hostname when available. | | **Network (LAN)** | `0.0.0.0` | Accessible from your local network. Prompts for your LAN IP. Sets CORS + CSP for that IP. | | **Custom** | Manual | Full manual control: custom port, bind address, HTTPS certificate generation, CORS. | **HTTPS (Network and Custom modes):** The wizard can offer self-signed certificate generation via `openssl` and configure `SSL_PORT` for non-localhost access. #### 4. Authentication If you choose a network-exposed mode, the wizard prompts you to enable auth and either: - set a password, or - reuse the gateway token as the password fallback For localhost-only installs, auth can stay off. #### 5. TTS Configuration (Optional) Prompts for optional API keys: - `OPENAI_API_KEY`, enables OpenAI TTS + Whisper transcription - `REPLICATE_API_TOKEN`, enables Qwen TTS via Replicate (warns if `ffmpeg` is missing) Edge TTS always works without any keys. Xiaomi MiMo can be enabled later by setting `MIMO_API_KEY` manually or saving it from Settings, Audio. #### 6. Advanced Settings (Optional) Custom file paths for `MEMORY_PATH`, `MEMORY_DIR`, `SESSIONS_DIR`. Most users skip this. ### Modes Summary | Flag | Behavior | |------|----------| | *(none)* | Full interactive wizard. If `.env` exists, asks whether to update or start fresh. | | `--check` | Validates all config values, tests gateway connectivity, and exits. Non-destructive. | | `--defaults` | Auto-detects gateway token, applies defaults for everything else, writes `.env`. No prompts. | | `--defaults --access-mode tailscale-ip` | Non-interactive setup for direct tailnet IP access. | | `--defaults --access-mode tailscale-serve` | Non-interactive setup for loopback + Tailscale Serve HTTPS access. | The wizard backs up existing `.env` files as `.env.backup` or `.env.backup.YYYY-MM-DD` before overwriting and applies `chmod 600` to both `.env` and backup files. --- ## Environment Variables ### Server | Variable | Default | Description | |----------|---------|-------------| | `PORT` | `3080` | HTTP server port | | `SSL_PORT` | `3443` | HTTPS server port (requires certificates at `certs/cert.pem` and `certs/key.pem`) | | `HOST` | `127.0.0.1` | Bind address. Set to `0.0.0.0` for network access — see warning below | > **⚠️ Network exposure:** Setting `HOST=0.0.0.0` exposes all endpoints to the network. Enable authentication (`NERVE_AUTH=true`) and set a password via the setup wizard before binding to a non-loopback address. Without auth, anyone with network access can read/write agent memory, modify config files, and control sessions. See [Security](SECURITY.md) for the full threat model. ```bash PORT=3080 SSL_PORT=3443 HOST=127.0.0.1 ``` ### Gateway (Required) | Variable | Default | Required | Description | |----------|---------|----------|-------------| | `GATEWAY_TOKEN` | — | **Yes** | Authentication token for the OpenClaw gateway. The setup wizard auto-detects this. See note below | | `GATEWAY_URL` | `http://127.0.0.1:18789` | No | Gateway HTTP endpoint URL | | `NERVE_PUBLIC_ORIGIN` | *(empty)* | No | Explicit browser-facing Nerve origin used when server-side gateway RPC fallback must open its own WebSocket to OpenClaw. Useful for reverse-proxy, cloud, and hybrid deployments. | ```bash GATEWAY_TOKEN=your-token-here GATEWAY_URL=http://127.0.0.1:18789 # Optional for reverse-proxy / cloud / hybrid installs NERVE_PUBLIC_ORIGIN=https://nerve.example.com ``` For non-interactive installs that should talk to a remote gateway, pass the URL directly to the installer: ```bash curl -fsSL https://raw.githubusercontent.com/daggerhashimoto/openclaw-nerve/master/install.sh \ | bash -s -- --gateway-url https://gw.example.com --gateway-token --skip-setup ``` If remote workspace panels (Files, Memory, Config, Skills) fail with `origin not allowed` while chat still works, set `NERVE_PUBLIC_ORIGIN` to the exact browser origin and add that same origin to `gateway.controlUi.allowedOrigins` on the gateway. ### Token Injection Nerve performs **server-side token injection**. When a connection is established through the WebSocket proxy, Nerve automatically injects the configured `GATEWAY_TOKEN` into the connection request if the client is considered **trusted**. **Trust is granted if:** 1. The connection is from a **local loopback address** (`127.0.0.1` or `::1`). When Nerve is behind a trusted reverse proxy, proxy-aware client IP handling can preserve that loopback detection (see `TRUSTED_PROXIES`). 2. OR, the connection has a valid **authenticated session** (`NERVE_AUTH=true`). This allows the browser UI to connect without having to manually enter or store the gateway token in the browser's persistent storage. If a connection is not trusted (e.g., remote access without authentication), the token field in the UI must be filled manually. > **Note:** `OPENCLAW_GATEWAY_TOKEN` is also accepted as a fallback for `GATEWAY_TOKEN`. > > **Token detection order:** The setup wizard finds the gateway token from: (1) systemd service file (`OPENCLAW_GATEWAY_TOKEN` env var in the unit), (2) `~/.openclaw/openclaw.json`, (3) `OPENCLAW_GATEWAY_TOKEN` shell env var. The systemd source takes priority because the gateway process reads the env var over the config file — a known issue where `openclaw onboard` writes different tokens to each location. ### Agent Identity | Variable | Default | Description | |----------|---------|-------------| | `AGENT_NAME` | `Agent` | Display name shown in the UI header and server info | ```bash AGENT_NAME=Friday ``` ### API Keys (Optional) | Variable | Description | |----------|-------------| | `OPENAI_API_KEY` | Enables OpenAI TTS (multiple voices) and Whisper audio transcription | | `REPLICATE_API_TOKEN` | Enables Replicate-hosted TTS models (e.g. Qwen TTS). Requires `ffmpeg` for WAV→MP3 | | `MIMO_API_KEY` | Enables Xiaomi MiMo TTS when the Xiaomi provider is selected in Settings, Audio | ```bash OPENAI_API_KEY=sk-... REPLICATE_API_TOKEN=r8_... MIMO_API_KEY=sk-mimo-... ``` TTS provider fallback chain (when no explicit provider is requested): 1. **OpenAI** — if `OPENAI_API_KEY` is set 2. **Replicate** — if `REPLICATE_API_TOKEN` is set 3. **Edge TTS** — always available, no API key needed (default for new installs) Xiaomi MiMo is available as an explicit provider option when `MIMO_API_KEY` is set. It is not part of the automatic fallback chain. ### Speech-to-Text (STT) | Variable | Default | Description | |----------|---------|-------------| | `STT_PROVIDER` | `local` | STT provider: `local` (whisper.cpp, no API key needed) or `openai` (requires `OPENAI_API_KEY`) | | `WHISPER_MODEL` | `base` | Local whisper model: `tiny` (75 MB), `base` (142 MB), or `small` (466 MB) — multilingual variants. English-only variants (`tiny.en`, `base.en`, `small.en`) are also available. | | `WHISPER_MODEL_DIR` | `~/.nerve/models` | Directory for downloaded whisper model files | | `NERVE_LANGUAGE` | `en` | Preferred voice language (ISO 639-1). Legacy `LANGUAGE` is still accepted but deprecated | | `EDGE_VOICE_GENDER` | `female` | Edge TTS voice gender: `female` or `male` | ```bash # Use local speech-to-text (no API key needed) STT_PROVIDER=local WHISPER_MODEL=base NERVE_LANGUAGE=en ``` Nerve uses explicit language selection (`NERVE_LANGUAGE`) for voice flows; there is no user-facing auto-detect language mode. Local STT requires `ffmpeg` for audio format conversion (webm/ogg → 16kHz mono WAV). The installer handles this automatically. Models are downloaded from HuggingFace on first use. > **Migration note:** `LANGUAGE` is still read for backwards compatibility, but new writes use `NERVE_LANGUAGE`. Voice phrase overrides (stop/cancel/wake words) are stored at `~/.nerve/voice-phrases.json` and generated on first save from the UI. ### Network & Security | Variable | Default | Description | |----------|---------|-------------| | `ALLOWED_ORIGINS` | *(localhost only)* | Additional CORS origins, comma-separated. Normalised via `URL` constructor; `"null"` origins are rejected | | `CSP_CONNECT_EXTRA` | *(none)* | Additional CSP `connect-src` entries, space-separated. Only `http://`, `https://`, `ws://`, `wss://` schemes accepted. Semicolons and newlines are stripped to prevent directive injection | | `WS_ALLOWED_HOSTS` | `localhost,127.0.0.1,::1` | Additional WebSocket proxy allowed hostnames, comma-separated | | `TRUSTED_PROXIES` | `127.0.0.1,::1,::ffff:127.0.0.1` | IP addresses trusted to set `X-Forwarded-For` / `X-Real-IP` headers, comma-separated | ```bash # Tailscale example ALLOWED_ORIGINS=http://100.64.0.5:3080 CSP_CONNECT_EXTRA=http://100.64.0.5:3080 ws://100.64.0.5:3080 WS_ALLOWED_HOSTS=100.64.0.5 # Behind nginx reverse proxy TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.1 ``` If you are retrofitting Tailscale onto an existing install, see [Add Tailscale to an Existing Nerve Install](TAILSCALE.md). ### Authentication Nerve includes a built-in authentication layer that protects all API endpoints, WebSocket connections, and SSE streams with a session cookie. Auth is opt-in for localhost users and auto-prompted during setup when binding to a network interface. | Variable | Default | Description | |----------|---------|-------------| | `NERVE_AUTH` | `false` | Enable authentication. Set to `true` to require a password for access | | `NERVE_PASSWORD_HASH` | *(empty)* | scrypt hash of the password. Generated by the setup wizard | | `NERVE_SESSION_SECRET` | *(auto-generated)* | 32-byte hex string for HMAC-SHA256 cookie signing. Auto-generated during setup. If not set, an ephemeral secret is generated at startup (sessions won't survive restarts) | | `NERVE_SESSION_TTL` | `2592000000` (30 days) | Session lifetime in milliseconds | ```bash NERVE_AUTH=true NERVE_PASSWORD_HASH= NERVE_SESSION_SECRET= ``` #### `NERVE_ALLOW_INSECURE` When `HOST=0.0.0.0` and `NERVE_AUTH=false`, the server **refuses to start** to prevent accidentally exposing all endpoints without authentication. Set `NERVE_ALLOW_INSECURE=true` to override this safety check. **Not recommended for production.** ```bash NERVE_ALLOW_INSECURE=true ``` **Quick enable (with gateway token as password):** ```bash NERVE_AUTH=true NERVE_SESSION_SECRET=$(openssl rand -hex 32) # No NERVE_PASSWORD_HASH needed — your GATEWAY_TOKEN works as the password ``` **Behavior:** - When `NERVE_AUTH=false` (default): No authentication, all endpoints are open - When `NERVE_AUTH=true`: All `/api/*` routes (except auth and health) require a valid session cookie - The session cookie is `HttpOnly`, `SameSite=Strict`, and port-suffixed (`nerve_session_3080`) - WebSocket upgrade requests are also authenticated - If no password hash is set, the gateway token is accepted as a fallback password **Setup wizard:** When the access mode is set to a non-localhost option, the wizard prompts to set a password and auto-generates the session secret. ### API Base URLs Override these for proxies, self-hosted endpoints, or API-compatible alternatives. | Variable | Default | Description | |----------|---------|-------------| | `OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible API base URL | | `REPLICATE_BASE_URL` | `https://api.replicate.com/v1` | Replicate API base URL | ```bash OPENAI_BASE_URL=https://api.openai.com/v1 REPLICATE_BASE_URL=https://api.replicate.com/v1 ``` ### Codex Integration | Variable | Default | Description | |----------|---------|-------------| | `CODEX_DIR` | `.codex` | Directory for Codex integration files | ### File Paths | Variable | Default | Description | |---------|---------|-------------| | `FILE_BROWSER_ROOT` | `""` (disabled) | If set, overrides the file browser root directory for all sessions. In this mode, file-browser `agentId` scoping is bypassed, default exclusion rules are disabled, and delete operations are permanent (no `.trash` recovery). | | `MEMORY_PATH` | `~/.openclaw/workspace/MEMORY.md` | Path to the main agent's long-term memory file | | `MEMORY_DIR` | `~/.openclaw/workspace/memory/` | Directory for the main agent's daily memory files (`YYYY-MM-DD.md`) | | `SESSIONS_DIR` | `~/.openclaw/agents/main/sessions/` | Session transcript directory (scanned for token usage) | | `USAGE_FILE` | `~/.openclaw/token-usage.json` | Persistent cumulative token usage data | | `NERVE_VOICE_PHRASES_PATH` | `~/.nerve/voice-phrases.json` | Override location for per-language voice phrase overrides | | `NERVE_WATCH_WORKSPACE_RECURSIVE` | `true` | Enables recursive `fs.watch` for full workspace `file.changed` SSE events outside `MEMORY.md` and `memory/`. Set this to `false` to disable full-workspace watching if you hit Linux inotify `ENOSPC` watcher exhaustion. Memory watchers stay enabled for discovered agent workspaces even when this is `false`. | ```bash FILE_BROWSER_ROOT=/home/user MEMORY_PATH=/custom/path/MEMORY.md MEMORY_DIR=/custom/path/memory/ SESSIONS_DIR=/custom/path/sessions/ NERVE_VOICE_PHRASES_PATH=/custom/path/voice-phrases.json NERVE_WATCH_WORKSPACE_RECURSIVE=false ``` ### TTS Cache | Variable | Default | Description | |----------|---------|-------------| | `TTS_CACHE_TTL_MS` | `3600000` (1 hour) | Time-to-live for cached TTS audio in milliseconds | | `TTS_CACHE_MAX` | `200` | Maximum number of cached TTS entries (in-memory LRU) | ```bash TTS_CACHE_TTL_MS=7200000 TTS_CACHE_MAX=500 ``` ### Updater State The updater stores state in `~/.nerve/updater/`. These are not configurable via env vars — they're managed automatically by `npm run update`. | Path | Purpose | |------|---------| | `~/.nerve/updater/last-good.json` | Snapshot of the last successful state (git ref, version, env hash) | | `~/.nerve/updater/last-run.json` | Result metadata from the most recent update attempt | | `~/.nerve/updater/snapshots//.env` | Timestamped `.env` backups (mode 0600) | | `~/.nerve/updater/nerve-update.lock` | PID lock file (prevents concurrent updates) | ### Development | Variable | Description | |----------|-------------| | `NODE_ENV` | Set to `development` to enable the `POST /api/events/test` debug endpoint and verbose error logging | --- ## Kanban Kanban board configuration is stored in the runtime data file (`${NERVE_DATA_DIR:-~/.nerve}/kanban/tasks.json`), not in `.env`. Manage it via the REST API: ```bash # Read current config curl http://localhost:3080/api/kanban/config # Update config curl -X PUT http://localhost:3080/api/kanban/config \ -H 'Content-Type: application/json' \ -d '{"proposalPolicy":"auto","quickViewLimit":10}' ``` ### Board Configuration | Field | Type | Default | Description | |-------|------|---------|-------------| | `columns` | `array` | *(see below)* | Column definitions (1--10). Each: `{ key, title, wipLimit?, visible }` | | `defaults.status` | `string` | `"todo"` | Default status for new tasks | | `defaults.priority` | `string` | `"normal"` | Default priority for new tasks | | `reviewRequired` | `boolean` | `true` | Whether completed tasks must go through review before done | | `allowDoneDragBypass` | `boolean` | `false` | Allow dragging tasks directly to done (skipping review) | | `quickViewLimit` | `number` | `5` | Max tasks shown in workspace quick view (1--50) | | `proposalPolicy` | `string` | `"confirm"` | How agent proposals are handled: `"confirm"` (manual review) or `"auto"` (apply immediately) | | `defaultModel` | `string` | *(none)* | Default model for agent execution (max 100 chars). If unset, execution falls back to OpenClaw's configured default model | ### Column Schema Each column in the `columns` array: | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | `string` | Yes | Status key: `backlog`, `todo`, `in-progress`, `review`, `done`, `cancelled` | | `title` | `string` | Yes | Display label (1--100 chars) | | `wipLimit` | `number` | No | Work-in-progress limit (≥0). `0` or omitted means unlimited | | `visible` | `boolean` | Yes | Whether the column is shown on the board | ### Default Columns ```json [ { "key": "backlog", "title": "Backlog", "visible": true }, { "key": "todo", "title": "To Do", "visible": true }, { "key": "in-progress", "title": "In Progress", "visible": true }, { "key": "review", "title": "Review", "visible": true }, { "key": "done", "title": "Done", "visible": true }, { "key": "cancelled", "title": "Cancelled", "visible": false } ] ``` --- ## HTTPS Nerve automatically starts an HTTPS server on `SSL_PORT` when certificates exist at: ``` certs/cert.pem # Certificate certs/key.pem # Private key ``` Generate self-signed certificates: ```bash mkdir -p certs openssl req -x509 -newkey rsa:2048 \ -keyout certs/key.pem -out certs/cert.pem \ -days 365 -nodes -subj '/CN=localhost' ``` Or use the setup wizard's Custom access mode, which generates them automatically if `openssl` is available. > **Why HTTPS?** Browser microphone access (`getUserMedia`) requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). On `localhost` this works over HTTP, but network access requires HTTPS. --- ## Minimal `.env` Example ```bash GATEWAY_TOKEN=abc123def456 ``` Everything else uses defaults. This is sufficient for local-only usage. ## Full `.env` Example ```bash # Gateway (required) GATEWAY_TOKEN=abc123def456 GATEWAY_URL=http://127.0.0.1:18789 # Server PORT=3080 SSL_PORT=3443 HOST=0.0.0.0 AGENT_NAME=Friday # Authentication (recommended when HOST=0.0.0.0) NERVE_AUTH=true NERVE_PASSWORD_HASH= NERVE_SESSION_SECRET= NERVE_SESSION_TTL=2592000000 # API Keys OPENAI_API_KEY=sk-... REPLICATE_API_TOKEN=r8_... MIMO_API_KEY=sk-mimo-... # Speech / Language STT_PROVIDER=local WHISPER_MODEL=base NERVE_LANGUAGE=en EDGE_VOICE_GENDER=female # Network (Tailscale example) ALLOWED_ORIGINS=http://100.64.0.5:3080 CSP_CONNECT_EXTRA=http://100.64.0.5:3080 ws://100.64.0.5:3080 WS_ALLOWED_HOSTS=100.64.0.5 # TTS Cache TTS_CACHE_TTL_MS=3600000 TTS_CACHE_MAX=200 # Custom Paths (optional) MEMORY_PATH=/home/user/.openclaw/workspace/MEMORY.md MEMORY_DIR=/home/user/.openclaw/workspace/memory SESSIONS_DIR=/home/user/.openclaw/agents/main/sessions ```