This runbook covers setting up a local VocalizeAI development environment on macOS or Linux. It is the detailed companion to the README Quick Start.
Required:
- Python 3.11+
- macOS:
brew install python@3.11 - Debian/Ubuntu:
sudo apt install python3.11 python3.11-venv
- macOS:
- Node 20+
- Via nvm (recommended):
nvm install 20 && nvm use 20 - macOS:
brew install node@20 - Debian/Ubuntu:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install -y nodejs
- Via nvm (recommended):
- git and curl (standard on both platforms)
Optional but recommended:
uv— automatically installed bydev-install.sh; enablesuv pip sync uv.lockfor deterministic Python dep installationwebsocat— used byscripts/smoke.shfor the WS step (falls back to Pythonwebsockets)- macOS:
brew install websocat - Debian/Ubuntu: download from https://github.com/vi/websocat/releases
- macOS:
jq— required byscripts/smoke.shfor JSON parsing- macOS:
brew install jq - Debian/Ubuntu:
sudo apt install jq
- macOS:
shellcheck— for verifying shell scripts during development
# 1. Clone the repository:
git clone https://github.com/DGPisces/VocalizeAI.git vocalize
cd vocalize
# 2. Run the dev installer:
bash install/dev-install.shWhat the installer does:
- Detects Python 3.11+ (fails with a hint if absent or older)
- Detects Node 20+ (fails with a hint if absent or older)
- Creates
.venvviapython -m venv .venv(skips if already present) - Activates the venv and runs
pip install --upgrade pip uv - If
uv.lockexists: runsuv pip sync uv.lockfor deterministic install, thenpip install -e .to register the local package in editable mode - If
uv.lockabsent (fresh checkout without lock): falls back topip install -e . - Runs
cd frontend && npm cito install frontend dependencies - Copies
.env.example→.envonly if.envdoes not already exist (preserves any local config you have set)
The installer is idempotent — re-running it on an existing clone is safe.
After the installer runs, edit .env:
$EDITOR .envAll 17 env vars explained:
| Key | Required? | Purpose |
|---|---|---|
OPENAI_API_KEY |
yes | LLM authentication — any OpenAI-compatible provider (OpenAI, DeepSeek, Qwen, etc.) |
OPENAI_BASE_URL |
default ok | LLM endpoint; default https://api.deepseek.com/v1 |
OPENAI_MODEL |
default ok | Model name; default deepseek-chat |
GPU_HOST |
only if using GPU | STT/TTS host; use localhost for single-machine dev, Tailscale IP for remote-GPU deployment (e.g. Raspberry Pi orchestrator → GPU node) |
SENSEVOICE_WS_PORT |
default ok | SenseVoice STT WebSocket port; default 8000 |
COSYVOICE_WS_PORT |
default ok | CosyVoice TTS WebSocket port; default 8001 |
VOCALIZE_HOST |
default ok | uvicorn bind host; 127.0.0.1 for local dev, 0.0.0.0 for production |
VOCALIZE_PORT |
default ok | uvicorn bind port; default 8080 (note: dev main.py defaults to 8000) |
ORCHESTRATOR_LISTEN_PORT |
default ok | Orchestrator service port; default 8080 (legacy; mirrors VOCALIZE_PORT) |
VOCALIZE_WS_BASE_URL |
required when non-localhost | Public WS base URL (e.g. wss://api.example.com); startup raises if missing in non-localhost mode (D-11) |
VOCALIZE_CORS_ORIGINS |
default ok | Comma-separated allowed CORS origins; auto-picked from VOCALIZE_HOST in dev mode |
DEFAULT_LANGUAGE |
default ok | Session default language; zh or en; default zh |
LOG_DIR |
default ok | Log directory; default logs |
NEXT_PUBLIC_VOCALIZE_API_BASE_URL |
yes for frontend | Frontend API base URL baked into the Next.js JS bundle at build time |
NEXT_PUBLIC_VOCALIZE_WS_BASE_URL |
optional | Frontend WS base; derived from NEXT_PUBLIC_VOCALIZE_API_BASE_URL if absent |
Backend auth posture: v1 ships no request-level auth on
POST /api/sessions or the WebSocket. For non-localhost deployments,
restrict reachability at the network or proxy layer (Cloudflare Access,
VPN, reverse-proxy auth, etc.). Per-user auth is v1.x scope
(requirement AUTH-01).
Minimum for local dev (no GPU):
OPENAI_API_KEY=<your-key>
NEXT_PUBLIC_VOCALIZE_API_BASE_URL=http://127.0.0.1:8000With these two set, the backend and frontend work end-to-end. GPU services
(GPU_HOST) are optional — GET /health will report gpu_reachable=false but
the LLM path (task planning, preflight) still works.
# Activate the venv:
source .venv/bin/activate
# Start the backend with hot-reload:
uvicorn vocalize.main:app --host 127.0.0.1 --port 8000 --reloadAlternative launcher (same result, uses uvicorn.run under the hood):
python -m vocalize.mainThe backend listens on http://127.0.0.1:8000 by default. You can confirm it is
running:
curl -s http://127.0.0.1:8000/health
# → {"ok": true, "gpu_reachable": false}Open a second terminal:
cd frontend
npm run dev -- --hostname 127.0.0.1 --port 3000Open http://127.0.0.1:3000 in your browser.
The frontend calls the backend directly through NEXT_PUBLIC_VOCALIZE_API_BASE_URL.
If the frontend was built without setting this variable, it will default to
http://127.0.0.1:8000. Set it explicitly in .env for consistent behaviour.
bash scripts/smoke.shExit code 0 = the development environment is working. The smoke script exercises 6 round-trips: health check, create session, set task, WS upgrade + send/recv, delete session. Total runtime is ~20 seconds.
The smoke script uses VOCALIZE_API_BASE (default http://127.0.0.1:8000).
Backend (pytest):
source .venv/bin/activate
pytestFrontend unit tests (vitest):
cd frontend && npm testFrontend integration tests (Playwright):
cd frontend && npm run test:integrationNote: tests/integration/ release-audio cases require a physical audio setup
(microphone + speaker) and a live Linux-host orchestrator. These are gated behind
--release-audio and do not run in PR CI. The standard integration test suite
(npm run test:integration) runs the 8 text-bypass AI-merchant scenarios and
does not require physical hardware.
Backend won't start:
- Check that the venv is activated:
source .venv/bin/activate - Check for a missing
OPENAI_API_KEY: the app will start but Layer 1 will fail on the first task set - Check for missing
VOCALIZE_WS_BASE_URLin non-localhost mode: startup raisesRuntimeError(D-11 guard) — set it or switch toVOCALIZE_HOST=127.0.0.1
Frontend can't reach backend:
- Check that
NEXT_PUBLIC_VOCALIZE_API_BASE_URLin.envmatches the backend port (defaulthttp://127.0.0.1:8000) - Restart the frontend dev server after editing
.env(Next.js bakes env vars at build time; hot-reload does NOT pick up.envchanges)
scripts/smoke.sh fails on the WS step:
- Install
websocat:brew install websocat(macOS) or download the binary from https://github.com/vi/websocat/releases - Alternatively, ensure the Python
websocketspackage is installed in the venv: it is already a declared dependency inpyproject.toml, sopip install -e .oruv pip sync uv.lockshould cover it - Check that the backend is actually running and the WS route is up:
curl -s http://127.0.0.1:8000/healthshould return{"ok": true, ...}
/health returns gpu_reachable=false:
- This is expected when GPU services are not running. The LLM path works without GPU; only STT/TTS (audio pipeline) require the GPU host.
- To enable GPU: set
GPU_HOSTto your GPU node's Tailscale IP and ensure SenseVoice + CosyVoice are running on that host.