voice-channel

Ambient voice control for any Claude Code instance. Speak to your agents from
anywhere in the room. Originally built for
claude-code-hermit — long-running
agents in Docker — but the plugin is a generic channel and works in any Claude Code
session (containerized or not).
Throughout these docs, "agent" means a Claude Code session you talk to by voice;
each one is registered with the dispatcher under an agent_id and its own trigger
phrases.
Architecture
Two components, one protocol:
LAPTOP (macOS or Linux — operator's device, has AirPods / built-in mic)
voice-dispatcher (Python)
├── Silero VAD + faster-whisper-tiny — local STT, no cloud
├── Piper TTS — local TTS, no cloud
└── WebSocket server :7355 (LAN / 0.0.0.0)
↕ ws://laptop.local:7355 (home LAN)
or ws://<docker-bridge-gateway>:7355 (same host)
TARGET MACHINE (where Claude Code runs — Linux + Docker, or the same laptop)
Claude Code session
└── voice-channel plugin (TypeScript / Node)
└── MCP channel server ↔ dispatcher WebSocket
The dispatcher owns all audio; the plugin is a thin (~200 LOC) WebSocket ↔ MCP bridge.
No audio libraries or Python code enter the Claude Code environment.
Install order
1. Install the dispatcher on your laptop
cd dispatcher
./install-macos.sh # macOS (critical-path)
# or: ./install-linux.sh (Linux laptop, acceptance-gated)
The installer starts the dispatcher as a service (launchd / systemd --user)
with an empty config — you'll restart it in step 4 once an agent is registered.
2. Download a Piper voice
add-agent (next step) references a .onnx voice file that must already exist:
VOICES=~/.local/share/voice-dispatcher/voices
curl -L -o "$VOICES/en_US-lessac-medium.onnx" \
https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/lessac/medium/en_US-lessac-medium.onnx
curl -L -o "$VOICES/en_US-lessac-medium.onnx.json" \
https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json
Browse other voices: https://github.com/rhasspy/piper/blob/master/VOICES.md
(The Whisper STT model downloads automatically on first dispatcher run.)
3. Add an agent to the dispatcher
voice-dispatcher config add-agent jarvis \
--triggers "hey jarvis,jarvis,ó jarvis" \
--voice en_US-lessac-medium.onnx
The command prints the token. Copy it — you'll need it in step 6.
4. Restart the dispatcher to load the new agent
# macOS:
launchctl kickstart -k gui/$(id -u)/com.gtapps.voice-dispatcher
# Linux:
systemctl --user restart voice-dispatcher
# Or run in the foreground for testing (logs to your terminal):
# cd dispatcher && python -m voice_dispatcher run
Confirm it's listening: lsof -i :7355 should show a python process on 0.0.0.0:7355.
5. Install the plugin in the agent container
Run these commands inside the container (e.g. via docker exec -it <container> bash):
claude plugin marketplace add gtapps/voice-channel
claude plugin install voice@voice-channel --scope local
6. Configure the plugin
First find the Docker bridge gateway IP — how the container reaches the dispatcher
on the host (only needed when both run on the same machine; skip if the dispatcher
is on a separate LAN host):
# Inside the container:
python3 -c "
import struct, socket
with open('/proc/net/route') as f:
for line in f:
parts = line.split()
if parts[1] == '00000000':
print(socket.inet_ntoa(struct.pack('<I', int(parts[2], 16))))
break
"
Recommended: run /voice:configure inside a Claude Code session and answer the
prompts (dispatcher URL ws://<bridge-ip>:7355, token from step 3, agent ID jarvis).
It writes config.json to the correct location automatically — no need to know the
data-dir path.
Manual alternative (no session needed)
# Inside the container. The data dir is derived as {plugin}-{marketplace}:
DATA_DIR="$HOME/.claude/plugins/data/voice-voice-channel"
mkdir -p "$DATA_DIR"
cat > "$DATA_DIR/config.json" <<EOF
{
"dispatcher_url": "ws://<bridge-ip>:7355",
"token": "<token from step 3>",
"agent_id": "jarvis",
"enable_permission_relay": false
}
EOF