OpenCode Voice — © 2026 Di1r1 · MIT · https://github.com/Di1r1/OpenCode-Voice
Voice input for OpenCode: speak, and your words land in the prompt. Works locally (faster-whisper on CPU, whisper.cpp on GPU) or in the cloud (OpenAI Whisper API), with push-to-talk in the TUI, a 🎤 button in the web UI, and a built-in doctor for diagnostics.
English | Русский
Release history: CHANGELOG.md.
| Part | What it does |
|---|---|
voice-opencode-plugin/src/ |
OpenCode plugin (TypeScript): the /voice command, push-to-talk recording, STT backends, server autostart |
voice-opencode-plugin/stt-server/ |
Flask + faster-whisper / whisper.cpp: HTTP API, server-side recording via PulseAudio, transcription |
voice-opencode-plugin/extension/ |
Chrome extension (MV3): the 🎤 button in the web UI (browser recording → server) |
voice-opencode-plugin/doctor.sh |
Diagnostics and repair for the button/extension path (server, CORS, stuck recording, microphone) |
voice-opencode-plugin/fix-mic.sh |
Recreates the WSLg audio channel when the microphone dies (WSL2) |
voice-opencode-plugin/sync-plugin.sh |
Generates the local plugin/TUI entry points that OpenCode loads |
voice-opencode-plugin/setup.sh |
Установка: проверяет зависимости, ставит системные пакеты (apt), Python-пакеты, опционально GPU build, генерирует entry-файлы плагина и команды, пишет конфиг OpenCode |
- Linux desktop, or Windows 10/11 + WSL2 with WSLg (audio is delivered through WSLg PulseAudio in WSL2).
- Node.js 22+ and npm — for the plugin (and for
npm ci/ typecheck). - Python 3.9+ — for the STT server.
- System packages:
alsa-utils(arecord),libasound2-plugins,ffmpeg,libnotify(optional). - Автоматическая установка:
bash setup.sh --yesдоустанавливаетpython3-pip,python3-venv,alsa-utils,libasound2-plugins(WSL2) черезapt-getперед остальной установкой. - Chrome/Chromium — for the 🎤 button in the web UI (the TUI works without it).
- Optional: an NVIDIA GPU with a WSL-capable driver, for
whisper.cpp+ CUDA.
Generated vs versioned in
.opencode/.sync-plugin.shgeneratesvoice-opencode-plugin/.opencode/plugins/index.ts(a copy ofsrc/index.tswith rewritten imports) — that file is git-ignored, so run the sync step after every clone and after editingsrc/index.ts. Versioned runtime assets:.opencode/tui/,.opencode/web/,.opencode/commands/..opencode/agents/and.opencode/skills/are optional local dev material — neither committed nor published.
git clone https://github.com/Di1r1/OpenCode-Voice.git
cd OpenCode-Voice/voice-opencode-plugin
# one command: checks, system deps, server packages, plugin entry points, config, doctor
./setup.sh # CPU: faster-whisper (model downloads on first run)
./setup.sh --yes # same, but answers "yes" to all prompts
./setup.sh --all # everything at once: deps, TTS, config write, env file, doctor
./setup.sh --gpu # optional: build whisper.cpp with CUDA + fetch a ggml modelsetup.sh проверяет окружение, доустанавливает системные пакеты (python3-pip, python3-venv, alsa-utils, libasound2-plugins для WSL2), ставит stt-server/requirements.txt, запускает sync-plugin.sh, копирует .opencode/commands/voice.md в проект и пишет конфиг в ~/.config/opencode/opencode.jsonc.
--all включает всё сразу (системные пакеты + TTS + запись конфига + env-файл + doctor). --yes — без вопросов. --write-config — применяет конфиг автоматически с бэкапом.
При установке через npm install @di1r1/opencode-voice пакет включает .opencode/commands/ и setup.sh; после npm install запустите bash voice-opencode-plugin/setup.sh --yes.
Manual equivalent:
# 1. server deps (CPU backend)
pip install --no-input -r stt-server/requirements.txt
# 2. plugin deps + local entry points
npm install
bash sync-plugin.shThen register the plugin in your OpenCode config (step 3 below), and start it:
opencode web --hostname 0.0.0.0The plugin auto-starts the STT server on load and keeps it alive with a watchdog — a separate python3 stt_server.py is only needed for manual runs. Verify with /voice doctor (see step 5).
Linux desktop (no extra setup — PulseAudio/ALSA is used directly). Find your microphone source name:
pactl list short sources # e.g. alsa_input.pci-0000_00_1f.3.analog-stereo
export OPENCODE_VOICE_SOURCE=$(pactl get-default-source) # pin it (see Configuration)
arecord -D pulse -f S16_LE -r 16000 -c 1 -t wav -d 3 /tmp/t.wav && ls -la /tmp/t.wavWSL2:
sudo apt-get update
sudo apt-get install -y alsa-utils libasound2-plugins ffmpeg
export PULSE_SERVER=unix:/mnt/wslg/PulseServer
# check (must write ~500 KB, not a 44-byte stub)
pactl info
arecord -D pulse -f S16_LE -r 16000 -c 1 -t wav -d 3 /tmp/t.wav && ls -la /tmp/t.wavThere is no /dev/snd in WSL2 — that is expected; the microphone is reachable only through PULSE_SERVER=unix:/mnt/wslg/PulseServer. Recordings are written to RAM (/dev/shm/opencode-voice, tmpfs).
cd voice-opencode-plugin/stt-server
pip install --no-input -r requirements.txt # flask + faster-whisper (CPU backend)
export PULSE_SERVER=unix:/mnt/wslg/PulseServer # WSL2 only
python3 stt_server.py --model medium --port 8765- Binds
127.0.0.1by default; check withcurl -s 127.0.0.1:8765/health. - Starts without
faster-whisperwhen the GPU (whisper.cpp) backend is used — the CPU package is imported lazily. - Models:
tiny/base/small/medium(default) /large. - On startup it prints runtime checks (Python, CUDA driver, recorder, whisper.cpp CLI/model,
PULSE_SERVER). - Microphone-free smoke test:
OPENCODE_VOICE_FAKE_AUDIO=/tmp/test-voice.wav python3 stt_server.py --port 8765.
cd voice-opencode-plugin
npm install
bash sync-plugin.sh # generates .opencode/plugins/index.ts
npm run typecheckAlternatively, install the package from npm — it bundles the plugin, the STT server, the extension and the installer scripts:
npm install @di1r1/opencode-voiceand reference it by name (no sync-plugin.sh needed; the plugin locates stt-server/stt_server.py inside the package automatically):
A tarball built with npm pack has the same layout.
Then point OpenCode at the generated entry point. The plugin and the TUI hotkey are registered as file URLs in the OpenCode config (this is exactly how it is wired in a working setup):
// ~/.config/opencode/opencode.jsonc (global) — или ./opencode.jsonc (project)
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
// ... другие плагины ...
"file:///ABS/PATH/voice-opencode-plugin/.opencode/plugins/index.ts"
],
"commands": {
"voice": {
"template": "$ARGUMENTS",
"description": "Voice: /voice — запись; /voice help; backend [local|api]; lang [ru|en/auto]; device [auto|gpu|cpu]; doctor [--fix]; <file.wav>",
"agent": "build"
}
}
}Команда voice также определена как markdown-файл в .opencode/commands/voice.md — при установке через npm install @di1r1/opencode-voice или bash setup.sh --yes она копируется в проект автоматически.
// ~/.config/opencode/tui.json (global) — optional: enables the <leader>v hotkey
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["file:///ABS/PATH/voice-opencode-plugin/.opencode/tui/voice.ts"]
}Replace /ABS/PATH with the absolute path to the cloned repo (e.g. /home/you/OpenCode-Voice). Then restart OpenCode — config and plugins are loaded once at startup.
- Open
chrome://extensionsand enable Developer mode. - Load unpacked → select the
voice-opencode-plugin/extensionfolder. - Open the OpenCode web UI — the 🎤 button appears next to the input field.
- Optional: hold
Alt+Zon the OpenCode page for push-to-talk — recording runs while you hold the keys, release to stop and transcribe (works only on the OpenCode page). The combo is configurable in the extension popup (Alt+Z,Ctrl+Shift+Z,Alt+Q,F9); press the modifier first, then the key, and release in any order (Escape cancels). HoldAlt+Xdoes the same but sends the request immediately (bypassing the input box; append-and-send if the field already has text) — can be turned off in the popup.
The extension talks to the STT server at http(s)://<host>:8765 (STT_PORT in extension/content.js). extension/manifest.json lists localhost/127.0.0.1; add your host to host_permissions if it differs. Optional settings in the popup: access token, sound beeps, hotkey, auto-send, and a 🔧 Restore server button (POST /heal?restart=1 — resets a stuck recording, purges stale audio, restarts the process; the server respawns itself in ~3–5 s and the popup waits for /health showing a neutral waiting state).
# full check of the button/extension path (server, CORS, stuck recording, mic)
bash voice-opencode-plugin/doctor.sh # add --fix to auto-repair
# or from the TUI:
/voice doctor --fix
# server health (includes "version", "backend", "device", "auth")
curl -s 127.0.0.1:8765/health
# tests (no microphone/model needed)
cd voice-opencode-plugin
pip install --no-input -r stt-server/requirements-dev.txt
python3 -m pytest # 62 server tests (incl. /speak)
npm test # 74 tests (stripNonSpeech/cleanForSpeech, whisper paths, state, E2E: plugin pipeline + server over HTTP)
npm run typecheck
bash sync-plugin.sh --check
bash check-workflows.sh # validate .github/workflows/*.yml (needs PyYAML)By default the server runs faster-whisper on CPU. With an NVIDIA GPU exposed to WSL2 (or a Linux host) you can run whisper.cpp with CUDA instead (verified on a GTX 950M / Maxwell, CC 5.0).
- Update the NVIDIA driver to a WSL-capable branch (R470+). After a reboot
/usr/lib/wsl/lib/libcuda.so.1should exist. - Install the CUDA toolkit into your home directory (no root). Use a toolkit that still supports your GPU — CUDA 13 dropped Maxwell/Pascal, so use 12.6 for those:
sh cuda_12.6.0_560.28.03_linux.run --silent --toolkit --toolkitpath=$HOME/cuda-12.6 \
--no-opengl-libs --no-man-page --override- Build whisper.cpp with CUDA (
<cc>= compute capability:50Maxwell,61Pascal,75Turing,86Ampere):
git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=<cc> -DCMAKE_BUILD_TYPE=Release \
-DWHISPER_BUILD_TESTS=OFF -DCUDAToolkit_ROOT=$HOME/cuda-12.6 \
-DCMAKE_CUDA_FLAGS=-allow-unsupported-compiler \
-DCMAKE_EXE_LINKER_FLAGS="-L$HOME/cuda-12.6/lib64 -Wl,--copy-dt-needed-entries"
cmake --build build -j4 --target whisper-cli- Install the CLI + model where the plugin/server look for them:
DEST=$HOME/.local/share/opencode-voice/whisper
mkdir -p $DEST/bin
cp build/bin/whisper-cli build/bin/*.so* $DEST/bin/
cp models/ggml-medium.bin $DEST/The server and the /voice command auto-detect the CLI (/health then shows "backend":"whispercpp", "device":"cuda"). If there is no GPU (no libcuda), both fall back to faster-whisper on CPU. Force a device with /voice device cpu|gpu|auto or OPENCODE_VOICE_DEVICE; the legacy alias OPENCODE_VOICE_STT_BACKEND=faster-whisper also works.
OpenCode Voice can read assistant answers aloud. It is strictly additive: with the toggle off (the default) nothing changes.
Two engines:
- Browser (default) — the Web Speech API; no server or model needed. With "local only" on, only
voices where
localService === trueare used; remote voices may send the text to the browser vendor. - Server —
POST /speakon the local STT server returns a WAV that the browser plays, using offline Piper voices. Enable withOPENCODE_VOICE_TTS=1.
Install the server engine (Piper + Russian and English voices) into $OPENCODE_VOICE_HOME/tts:
./setup.sh --tts # downloads piper + all 4 RU voices (~260 MB) + EN voice lessac (~60 MB)
export OPENCODE_VOICE_TTS=1
# OPENCODE_VOICE_TTS_BIN=$HOME/.local/share/opencode-voice/tts/piper/piper
# OPENCODE_VOICE_TTS_VOICES_DIR=$HOME/.local/share/opencode-voice/tts/voices
# ...or source the persisted file written by setup.sh: source $HOME/.local/share/opencode-voice/env.shPersistence: the server reads
OPENCODE_VOICE_TTSonce at startup. Export it in the environment OpenCode itself starts from (e.g.~/.bashrc) and restart the server — neither the popup "heal" button nor the watchdog can enable it on a running process (they respawn with the old environment).
Then open the extension popup, turn on 🔊 Voice assistant answers and pick the engine
(Browser/Server), mode (brief — first sentences plus any error lines / full),
language, browser voice, server voice (Piper catalog from GET /voices) and speed. The popup UI itself
switches RU/EN (top selector, key uiLang). Settings live in chrome.storage.local (keys tts, ttsEngine,
ttsMode, ttsLang, ttsVoice, ttsServerVoice, ttsRate, ttsLocalOnly) and apply without a reload. Ctrl+C
stops speech only while it is speaking; starting a recording pauses it.
The text comes from the same-origin web-UI API (/api/session, /session/{id}/message), not from
DOM scraping. Long answers are chunked (≤180 chars) so the server maxChars limit never triggers a
fallback; a stuck browser utterance is retried once, then a toast tells you to fully close and reopen
the tab (a stale renderer survives F5). The server route reuses the usual token/limits/CORS, keeps an LRU WAV cache and
returns 501 when disabled — the extension then falls back to the browser voice. Synthesis log:
/tmp/opencode/voice-tts.log.
| Command | Action |
|---|---|
/voice |
Push-to-talk: records until ~1.5 s of silence (hard cap OPENCODE_VOICE_MAX_RECORD_SECONDS, default 300 s), transcribes, inserts the text into the prompt |
/voice <file.wav> |
Transcribe a local audio file (wav/mp3/m4a/ogg/flac) |
/voice backend [local|api] |
Show/switch the STT backend |
/voice lang [ru|en|auto] |
Show/switch the language |
/voice device [auto|gpu|cpu] |
Show/switch local device: GPU (whisper.cpp) or CPU (faster-whisper) |
/voice doctor [--fix] |
Diagnose the button/extension path and optionally auto-repair |
/voice heal |
Recover: reset a stuck recording, restart the server, recreate the audio channel (doctor.sh --fix) |
/voice help |
List all subcommands |
The backend/lang/device choices are persisted to ~/.config/opencode-voice/state.json, so they survive a restart (env vars still take precedence).
TUI: the <leader>v hotkey (leader is ctrl+x by default) triggers push-to-talk. Web UI: the 🎤 button from the extension, or hold Alt+Z on the OpenCode page (push-to-talk; the combo is selectable in the popup).
Known limitations of the TUI command (by OpenCode design): the hook blocks while recording (so there is no live on-screen timer), and a failed attempt is logged as an ERROR in the OpenCode log (the plugin throws to avoid sending an empty prompt).
| Variable | Purpose | Default |
|---|---|---|
OPENCODE_VOICE_BACKEND |
local | api |
local |
OPENCODE_VOICE_LANGUAGE |
ru | en | auto |
ru |
OPENCODE_VOICE_DEVICE |
auto (GPU, else CPU) | gpu | cpu |
auto |
OPENCODE_VOICE_STATE_FILE |
Where /voice backend|lang|device choices are persisted |
~/.config/opencode-voice/state.json |
OPENAI_API_KEY |
key for the api backend |
— |
OPENCODE_VOICE_MODEL |
model name for the api backend |
whisper-1 |
WHISPER_MODEL |
model size for the local backend; default depends on the device (medium on GPU, small on CPU) | auto (device) |
OPENCODE_VOICE_HOME |
base directory for the model/CLI install | ~/.local/share/opencode-voice |
OPENCODE_VOICE_WHISPER_DIR |
whisper.cpp directory (CLI + ggml-*.bin) |
<home>/whisper |
OPENCODE_VOICE_SOURCE |
PulseAudio source (microphone) for recording; pinned so the default cannot drift to a playback monitor | RDPSource (WSLg) |
OPENCODE_VOICE_TMP_DIR |
directory for recordings (RAM by default) | /dev/shm/opencode-voice |
OPENCODE_VOICE_RETAIN_SECONDS |
keep a recording before auto-delete, s (0 = delete right after transcription) |
300 |
OPENCODE_VOICE_MAX_UPLOAD_MB |
max size of an uploaded audio file (413 above it) |
25 |
OPENCODE_VOICE_MAX_AUDIO_SECONDS |
max audio duration accepted (400 above it) |
300 |
OPENCODE_VOICE_MAX_CONCURRENT |
transcriptions allowed at once (others get 429) |
1 |
OPENCODE_VOICE_TRANSCRIBE_TIMEOUT |
transcription timeout in seconds (504 after it) |
300 |
OPENCODE_VOICE_RATE_LIMIT |
requests per minute per IP/endpoint (0 = off) |
60 |
OPENCODE_VOICE_PURGE_INTERVAL |
how often the RAM dir is purged, s | 600 |
OPENCODE_VOICE_STALE_CLEANUP |
on startup kill stray recorders (voice-ptt-, arecord, ffmpeg); 0 = leave other processes alone (tests/parallel runs) |
1 |
OPENCODE_VOICE_KEEP_AUDIO |
if set, do not delete recordings (debug) | — |
OPENCODE_VOICE_AUTO_RECOVER |
recreate the WSLg audio channel on a silent source | 1 |
OPENCODE_VOICE_AUTO_RECOVER_COOLDOWN, OPENCODE_VOICE_RECOVER_WAIT_WESTON, OPENCODE_VOICE_RECOVER_WAIT_PULSE |
recovery timing | 90, 8000, 5000 ms |
OPENCODE_VOICE_AUTO_HEAL, OPENCODE_VOICE_AUTO_HEAL_COOLDOWN |
on a failed /voice, run doctor.sh --fix and retry the recording once (0 disables; cooldown in seconds) |
1, 90 |
OPENCODE_VOICE_MAX_RECORD_SECONDS |
Hard cap on /voice recording length (seconds); it normally stops earlier — ~1.5 s after you stop speaking |
300 |
OPENCODE_VOICE_RECORDER_BIN |
replace the recorder binary (tests/E2E): gets the same args as arecord and writes the WAV to the last argument |
— |
| Variable | Purpose | Default |
|---|---|---|
OPENCODE_VOICE_PORT |
server port | 8765 |
OPENCODE_VOICE_HOST |
bind host; localhost-only by default | 127.0.0.1 |
OPENCODE_VOICE_SERVER |
auto-start the server on plugin load | 1 |
OPENCODE_VOICE_SERVER_SCRIPT |
path to stt_server.py (non-standard layouts) |
auto |
OPENCODE_VOICE_SERVER_LOG |
server log file | /tmp/opencode/stt_server.log |
OPENCODE_VOICE_SERVER_WATCHDOG_MS |
health-check/restart interval, ms (0 = off) |
120000 |
OPENCODE_VOICE_MAX_SECONDS |
max server-side recording length, s | 300 |
OPENCODE_VOICE_FAKE_AUDIO |
WAV used instead of the microphone (testing) | — |
OPENCODE_VOICE_TOKEN |
shared secret; when set, every endpoint except /health requires X-Voice-Token (or Authorization: Bearer) |
empty (off) |
PULSE_SERVER |
PulseAudio socket | /mnt/wslg/PulseServer on WSL2 |
| Variable | Purpose | Default |
|---|---|---|
OPENCODE_VOICE_TTS |
enable POST /speak (when 0, it answers 501) |
0 |
OPENCODE_VOICE_TTS_ENGINE |
synthesis engine | piper |
OPENCODE_VOICE_TTS_VOICE |
Piper voice (default; catalog in popup via GET /voices) |
ru_RU-irina-medium |
OPENCODE_VOICE_TTS_VOICES |
space-separated voice names for ./setup.sh --tts |
irina dmitri denis ruslan |
OPENCODE_VOICE_TTS_RATE |
speech speed | 1.0 |
OPENCODE_VOICE_TTS_MAX_CHARS |
trim long text (413 above it) |
300 |
OPENCODE_VOICE_TTS_BRIEF_SENTENCES |
brief mode: first sentences (errors always included) |
2 |
OPENCODE_VOICE_TTS_MODE |
default mode: brief | full (switchable in the popup) |
brief |
OPENCODE_VOICE_TTS_LANG |
voice language (auto = by reply language) |
auto |
OPENCODE_VOICE_TTS_BIN |
Piper binary (./setup.sh --tts installs it) |
auto (<home>/tts/piper/piper) |
OPENCODE_VOICE_TTS_VOICES_DIR |
Piper voices directory | auto (<home>/tts/voices) |
OPENCODE_VOICE_TTS_CACHE_MB |
LRU WAV cache size, MB | 64 |
OPENCODE_VOICE_TTS_TIMEOUT |
synthesis timeout, s | 60 |
OPENCODE_VOICE_TTS_LOG |
synthesis log | /tmp/opencode/voice-tts.log |
| Variable | Purpose | Default |
|---|---|---|
WHISPER_CPP_BIN |
whisper.cpp CLI path (auto-detected in the whisper dir) | <home>/whisper/bin/whisper-cli |
WHISPER_CPP_MODEL |
whisper.cpp ggml model (explicit path) | <home>/whisper/ggml-<size>.bin |
WHISPER_CPP_MODEL_SIZE |
model size for whisper.cpp (tiny…large) |
auto (medium GPU / small CPU) |
WHISPER_CPP_LIB_DIR, WHISPER_CPP_EXTRA_LIBS |
extra library paths for the CLI | auto (CUDA dirs discovered) |
CUDA_HOME, CUDA_PATH |
CUDA toolkit root; its lib64 is added to LD_LIBRARY_PATH automatically |
— |
WHISPER_BEAM_SIZE |
decoder beam (1 = greedy/fastest) |
1 |
WHISPER_VAD |
voice-activity filter (1/0) |
1 |
WHISPER_INITIAL_PROMPT |
context hint for Whisper | empty (off) |
WHISPER_LANG_DETECT_SEGMENTS / WHISPER_LANG_DETECT_THRESHOLD |
auto language detection tuning | 3 / 0.6 |
OPENCODE_VOICE_SILENCE_PEAK / _RMS |
silence gate: below both thresholds the clip is treated as "no speech" (Whisper hallucinates on silence) | 700 / 80 |
OPENCODE_VOICE_STT_BACKEND |
whispercpp (GPU) | faster-whisper (CPU); empty = auto |
auto |
OPENCODE_VOICE_LANGUAGE |
server-side language (ru/en); empty = auto-detect |
auto |
On short phrases auto language detection is unreliable — if you usually speak one language, pin it (OPENCODE_VOICE_LANGUAGE=ru).
Security: the server listens on 127.0.0.1 only and answers CORS only for local origins (OpenCode UI, the extension). Set OPENCODE_VOICE_TOKEN to require a shared secret on every request except /health. Without a token, if you expose the server (--host 0.0.0.0 / OPENCODE_VOICE_HOST=0.0.0.0), anyone on the network can record from your microphone and read transcripts.
| File | Contents |
|---|---|
/tmp/opencode/stt_server.log |
STT server log: startup checks and every transcription (button and server-side records) |
/tmp/opencode/voice-recognized.log |
every recognized text with source= (command = /voice, button = extension), backend, model, language, duration |
/tmp/opencode/voice-stt.log |
which backend/model the plugin used, plus audio levels |
/tmp/opencode/voice-requests.log |
incoming HTTP requests (button path): /beep, /transcribe, /record/* |
/tmp/opencode/voice-tts.log |
TTS /speak synthesis: cache hit/miss, errors |
/dev/shm/opencode-voice/ |
recordings (RAM); deleted after OPENCODE_VOICE_RETAIN_SECONDS |
/mnt/wslg/wlog.log |
WSLg log; audin … error 1359 lines are a known channel teardown artifact, not a criterion |
/voice doctor (or bash doctor.sh) prints a summary of all of the above and can repair common problems with --fix.
The server returns one of two errors:
Нет доступа к микрофону: PulseAudio не отвечает…— the recorder could not connect.Аудиоисточник молчит: рекордер подключился, но данных нет (получено N байт)…— it connected, but no audio arrives over the WSLgaudinchannel.
PULSE_SERVER=unix:/mnt/wslg/PulseServer pactl info
PULSE_SERVER=unix:/mnt/wslg/PulseServer arecord -D pulse -f S16_LE -r 16000 -c 1 -t wav -d 3 /tmp/t.wavQuick fix (WSL2): bash voice-opencode-plugin/fix-mic.sh recreates WSLg's internal RDP channel (restarts weston and pulseaudio). The server also self-heals once on a silent source (OPENCODE_VOICE_AUTO_RECOVER=0 disables that).
Also make sure the app has microphone access in Windows (Settings → Privacy → Microphone); in an RDP session enable "Record from this computer". As a last resort from Windows: wsl --shutdown.
Usually the server is down or its CORS is stale. Run bash voice-opencode-plugin/doctor.sh --fix: it restarts the server via the watchdog and re-checks the preflight for X-Voice-Source/X-Voice-Token. If a token is set, enter the same token in the extension popup.
A stuck server-side recording returns 409. The extension recovers automatically; you can also clear it with curl -X POST http://127.0.0.1:8765/record/stop. doctor.sh --fix does this too.
/voice records through the RDP audin channel, which can deliver audio slower than real time. Recognition quality then depends on the RDP client's microphone capture. What helps: use a client with proper audio-input redirection, select a real microphone (not "Stereo Mix"), disable AGC/noise suppression, and prefer the browser 🎤 button (it captures on the Windows side and uploads a file, bypassing audin entirely).
voice-opencode-plugin/
├── src/ # plugin code (index.ts, lib/{config,stt,recorder,beep,server-launcher,text,state,heal}.ts)
├── stt-server/ # Flask server: stt_server.py, requirements*.txt, tests/
├── extension/ # Chrome extension (MV3): content.js, tts.js, popup, manifest
├── doctor.sh # diagnostics/repair (/voice doctor)
├── fix-mic.sh # recreate the WSLg audio channel
├── setup.sh # one-command install (deps, optional GPU build, config hints)
├── sync-plugin.sh # generate the local plugin/TUI entry points (--check for CI)
├── check-workflows.sh # validate .github/workflows YAML before pushing (catches startup-breaking YAML)
├── pytest.ini # hermetic server tests
├── shared/ # single source of truth for TS+Python (stt-spec.json, strip-cases.json, tts-cases.json)
└── .opencode/ # plugins/index.ts is generated; tui/web/commands are versioned
Repo root also contains README.md, README.ru.md, CHANGELOG.md (release history), LICENSE, and CI in .github/workflows/ (ci.yml plus workflow-lint.yml, which validates the workflow YAML itself).
cd voice-opencode-plugin
npm install
bash sync-plugin.sh # after every change to src/index.ts
npm run typecheck
python3 -m pytest # server tests (hermetic: no mic, no model)
npm test # TS tests + hermetic E2E (node:test)
bash sync-plugin.sh --check # CI guard: entry points are up to date
bash check-workflows.sh # CI guard: workflow YAML is valid (needs PyYAML)CI runs the same checks on every push. For interactive development: npm run dev (opencode --plugin .).
MIT