Files
paseo/public-docs/configuration.md
Mohamed Boudra f91806defe Use separate OpenAI endpoints for speech-to-text and text-to-speech (#1823)
* feat(voice): configure OpenAI STT and TTS endpoints separately

Replace providers.openai.voice (a single apiKey/baseUrl shared by
speech-to-text and text-to-speech) with independent providers.openai.stt
and providers.openai.tts, each carrying its own apiKey/baseUrl. STT and
TTS now resolve fully independently, so they can point at different
OpenAI-compatible endpoints. The env equivalents OPENAI_VOICE_API_KEY /
OPENAI_VOICE_BASE_URL split into OPENAI_STT_* and OPENAI_TTS_*.

No backcompat: the voice key is removed and no longer read. Each feature
still falls back to providers.openai.apiKey/baseUrl, then OPENAI_API_KEY/
OPENAI_BASE_URL. Composer dictation resolves from the STT endpoint.

* fix(voice): keep daemon bootable and respect global OpenAI key

Two issues from review of the STT/TTS endpoint split:

- A config from an older release that still sets providers.openai.voice
  crashed daemon startup, because the strict schema rejects the now-unknown
  key. Strip it before parsing (alongside the existing local.autoDownload
  strip) so the daemon boots; the value is discarded, not migrated.
- An empty endpoint env var (e.g. a copied .env.example leaving
  OPENAI_STT_API_KEY= blank) shadowed the OPENAI_API_KEY fallback, so
  speech was reported as missing credentials despite a configured global
  key. firstDefined now skips empty/whitespace strings.

* fix(voice): isolate STT and TTS option parsing per endpoint

An STT-only OpenAI setup could be broken by a stale or invalid TTS env
var (e.g. a leftover TTS_VOICE/TTS_MODEL), because the single resolution
schema validated both endpoints' option groups before the per-endpoint
gate. Split into endpoint-key, STT-option, and TTS-option schemas and
parse each option group only when that endpoint has credentials, so an
unused endpoint's bad env can no longer take down the configured one.

* fix(voice): tag voice-config shim and update direct-daemon test callers

- Mark the providers.openai.voice strip with a COMPAT(openaiVoiceConfig)
  comment + removal date so it shows up in the back-compat cleanup
  inventory, per repo convention.
- Update the tests that build the daemon directly with a resolved OpenAI
  config (bootstrap smoke + the real-API voice/daemon e2e suites) to the
  new { stt, tts } shape; the old top-level { apiKey } is no longer read,
  and these files are excluded from typecheck so the break was silent.

* fix(voice): update voice-roundtrip debug script to new OpenAI config shape

Last direct daemon caller still passing the removed top-level
openai: { apiKey }; the debug script lives outside tsconfig.scripts.json
so the stale shape wasn't caught by typecheck. Use { stt, tts }.
2026-06-30 10:00:27 +02:00

224 lines
7.3 KiB
Markdown

---
title: Configuration
description: Configure Paseo via config.json, environment variables, and CLI overrides.
nav: Configuration
order: 40
category: Configuration
---
# Configuration
Paseo loads configuration from a single JSON file in your Paseo home directory, with optional environment variable and CLI overrides.
## Where config lives
By default, Paseo uses `~/.paseo` as its home directory. The configuration file is:
```bash
~/.paseo/config.json
```
You can change the home directory by setting `PASEO_HOME` or passing `--home` to `paseo daemon start`.
## Precedence
Paseo merges configuration in this order:
1. Defaults
2. `config.json`
3. Environment variables
4. CLI flags
Lists append across sources (for example, `hostnames` and `cors.allowedOrigins`).
## Example
Minimal example that configures listening address, hostnames, and MCP:
```json
{
"$schema": "https://paseo.sh/schemas/paseo.config.v1.json",
"version": 1,
"daemon": {
"listen": "127.0.0.1:6767",
"hostnames": ["localhost", ".localhost"],
"mcp": { "enabled": true }
}
}
```
`daemon.hostnames` is the primary field. The old `daemon.allowedHosts` name still works as a deprecated alias for backward compatibility.
## Agent providers
Agent providers, both the first-class ones Paseo ships with and custom entries you add under `agents.providers`, are documented on their own page.
See [Providers](/docs/providers) for the mental model and [Supported providers](/docs/supported-providers) for the full list of agents Paseo can launch. For pointing Claude at Anthropic-compatible endpoints (Z.AI, Alibaba/Qwen), multiple profiles, custom binaries, ACP agents, and the `additionalModels` merge behavior, see [Custom providers](/docs/custom-providers). The full field reference lives on GitHub at [docs/custom-providers.md](https://github.com/getpaseo/paseo/blob/main/docs/custom-providers.md).
## Worktrees
New worktrees are created under `$PASEO_HOME/worktrees` by default. To place new worktrees somewhere else, set `worktrees.root`:
```json
{
"worktrees": {
"root": "/mnt/fast/paseo-worktrees"
}
}
```
Relative paths are resolved against `PASEO_HOME`. Existing worktrees remain where they are; changing this setting only changes where Paseo creates and discovers Paseo-managed worktrees going forward.
## Voice
Voice is configured through `features.dictation` and `features.voiceMode`, with provider credentials under `providers`.
For voice philosophy, architecture, and complete local/OpenAI setup examples, see [Voice docs](/docs/voice).
## Bundled web UI
The daemon can serve the browser web client from the same HTTP server. This is enabled in the official Docker image and disabled by default for normal CLI and desktop-managed daemons.
Enable it from the CLI:
```bash
paseo daemon start --web-ui
```
Or set the environment variable:
```bash
PASEO_WEB_UI_ENABLED=true paseo daemon start
```
Or persist it in `config.json`:
```json
{
"features": {
"webUi": {
"enabled": true
}
}
}
```
When enabled, open the daemon HTTP origin, for example `http://localhost:6767/`, to load the web app. Static UI files load without daemon auth; API and WebSocket requests still require the configured password.
## Logging
Daemon logging uses separate console and file sinks by default:
- Console: `info` and above
- File (`$PASEO_HOME/daemon.log`): `trace` and above
- File rotation: `10m` max file size, `2` retained files total (active + 1 rotated)
```json
{
"log": {
"console": {
"level": "info",
"format": "pretty"
},
"file": {
"level": "trace",
"path": "daemon.log",
"rotate": {
"maxSize": "10m",
"maxFiles": 2
}
}
}
}
```
Legacy fields `log.level` and `log.format` are still supported and map to the new destination settings.
## Password authentication
You can require a password to connect to the daemon. When set, all HTTP and WebSocket clients must authenticate. Only the `/api/health` liveness endpoint is exempt, so that process supervisors and load balancers can probe without credentials.
The easiest way to set a password is with the CLI:
```bash
paseo daemon set-password
```
This prompts for a password, writes the bcrypt hash to `config.json`, and tells you to restart the daemon.
Alternatively, set the `PASEO_PASSWORD` environment variable (plaintext, hashed automatically at startup):
```bash
PASEO_PASSWORD=my-secret paseo daemon start
```
Or write the hash directly in `config.json`:
```json
{
"daemon": {
"auth": {
"password": "$2b$12$..."
}
}
}
```
After setting a password, restart the daemon for the change to take effect.
### Connecting with a password
The CLI picks up a password from, in order:
1. The `password` query parameter on a `tcp://` host URI:
```bash
paseo --host "tcp://192.168.1.10:6767?password=my-secret" ls
```
2. The `PASEO_PASSWORD` environment variable, used as a fallback when the host carries no embedded password (works for `localhost:6767`, bare `host:port`, or `tcp://` hosts without a `password=` query):
```bash
PASEO_PASSWORD=my-secret paseo ls
PASEO_PASSWORD=my-secret paseo --host 192.168.1.10:6767 ls
```
A `password=` in the URI always wins over the env var, so you can keep `PASEO_PASSWORD` set globally and still target a different daemon by spelling its password into the URI.
In the mobile app, enter the password in the direct connection setup screen.
## Common env vars
- `PASEO_HOME`, set Paseo home directory
- `PASEO_PASSWORD`, on the daemon, the password to require (plaintext, hashed at startup); on the CLI, the password used to connect when the host URI doesn't include one
- `PASEO_LISTEN`, override `daemon.listen`
- `PASEO_HOSTNAMES`, override/extend `daemon.hostnames`
- `PASEO_ALLOWED_HOSTS`, deprecated alias for `PASEO_HOSTNAMES`
- `PASEO_WEB_UI_ENABLED`, enable or disable the daemon-served web UI
- `PASEO_WEB_UI_DIST_DIR`, override the daemon web UI build directory
- `PASEO_TRUSTED_PROXIES`, configure trusted reverse proxy ranges for `X-Forwarded-*` headers
- `PASEO_LOG_CONSOLE_LEVEL`, override `log.console.level`
- `PASEO_LOG_FILE_LEVEL`, override `log.file.level`
- `PASEO_LOG_FILE_PATH`, override `log.file.path`
- `PASEO_LOG_FILE_ROTATE_SIZE`, override `log.file.rotate.maxSize`
- `PASEO_LOG_FILE_ROTATE_COUNT`, override `log.file.rotate.maxFiles`
- `PASEO_LOG`, `PASEO_LOG_FORMAT`, legacy log overrides (still supported)
- `OPENAI_API_KEY`, override OpenAI provider key
- `OPENAI_STT_API_KEY`, `OPENAI_STT_BASE_URL`, OpenAI speech-to-text endpoint (dictation + voice mode STT)
- `OPENAI_TTS_API_KEY`, `OPENAI_TTS_BASE_URL`, OpenAI text-to-speech endpoint (voice mode TTS)
- `PASEO_VOICE_LLM_PROVIDER`, override voice LLM provider (`claude`, `codex`, `opencode`)
- `PASEO_DICTATION_STT_PROVIDER`, `PASEO_VOICE_STT_PROVIDER`, `PASEO_VOICE_TTS_PROVIDER`, override voice provider selection (`local` or `openai`)
- `PASEO_LOCAL_MODELS_DIR`, control local model directory
- `PASEO_DICTATION_LOCAL_STT_MODEL`, override local dictation STT model
- `PASEO_VOICE_LOCAL_STT_MODEL`, `PASEO_VOICE_LOCAL_TTS_MODEL`, override local voice STT/TTS models
- `PASEO_DICTATION_LANGUAGE`, `PASEO_VOICE_LANGUAGE`, override dictation and voice STT language
- `PASEO_VOICE_LOCAL_TTS_SPEAKER_ID`, `PASEO_VOICE_LOCAL_TTS_SPEED`, optional local voice TTS tuning
## Schema
For editor autocomplete/validation, set `$schema` to:
```
https://paseo.sh/schemas/paseo.config.v1.json
```