The other half of Claude Code's voice mode.
You talk to Claude with /voice. Now Claude talks back — with real-time karaoke word highlighting in your terminal. Local by default, cloud voices (OpenAI, ElevenLabs, xAI Grok) when you want them.
- Why I Built This
- What It Does
- Install
- Providers
- The /voice slash command
- Commands
- Voices
- Config
- How It Works
- Daemon mode
- Multi-terminal routing
- Private history and playback controls
- Highlight-and-speak hotkey
- Current Pain Points
- End Goals
- Requirements
- License
Claude Code has /voice — you speak, it transcribes, Claude responds in text. But Claude never talks back. I'm staring at a terminal reading a 3-paragraph explanation when I could be hearing it while I keep working.
What I wanted: after every Claude response, automatically speak it aloud with word-by-word highlighting so I can follow along. Local. Free. Instant. No "would you like me to read this?" — just do it.
So I built a Claude Code Stop hook. After every response, the hook fires, strips markdown/code/URLs, generates audio, and renders karaoke-style highlighting to the terminal while it plays. Local Kokoro TTS (82M params, CPU) by default — and since v0.2, cloud providers when you want a nicer voice: OpenAI, ElevenLabs, xAI Grok, or any OpenAI-compatible endpoint.
- Karaoke highlighting — current word lit up, gradient around it, progress bar with %, word count and elapsed/total time
- 6 TTS providers — local Kokoro (free, private), system voice, OpenAI, ElevenLabs, xAI Grok, or any OpenAI-compatible endpoint
- True word sync on ElevenLabs — uses their timestamps API for real word-level alignment, not estimation
/voiceslash command — control everything from inside Claude Code:/voice off,/voice provider elevenlabs,/voice speed 1.2- Background daemon — the model loads once and stays in memory. Warm TTFA drops from ~6s to ~0.6s
- Multi-terminal routing — run Claude Code in five terminals at once and only the one you typed in speaks
- Per-terminal mute —
claude-voice mutesilences the current terminal only - Highlight-and-speak hotkey — select any text in any app, press a key, and the daemon reads it back (Hammerspoon snippet included)
- Opt-in private history — save up to 200 spoken responses locally, replay them, and seek or pause the latest synthesized audio
- Smart filtering: skips code-heavy responses, removes fenced code and URLs, speaks short inline code and table cells, and supports custom local pronunciations
- Playback controls: stop, pause, and resume from Claude Code; standalone karaoke can also stop on a keypress
- Themes — aurora, ember, violet, mint, mono
- One-command setup —
claude-voice setupinstalls the hooks and the slash command
git clone https://github.com/Null-Phnix/claude-voice
cd claude-voice
python3 -m venv .venv && .venv/bin/pip install -e ".[local]" # [local] = Kokoro TTS
.venv/bin/claude-voice setupCloud-only (no local model, tiny install): drop [local] and pick a provider:
.venv/bin/pip install -e .
.venv/bin/claude-voice provider openai
.venv/bin/claude-voice key openai sk-...Restart Claude Code and every response will be spoken. Try /voice status inside Claude Code.
| Provider | What it is | Key | Karaoke sync |
|---|---|---|---|
kokoro (default) |
Local Kokoro 82M — free, private, runs on CPU | none | estimated |
system |
Your OS voice (macOS say / espeak) — zero install |
none | estimated |
openai |
OpenAI TTS (gpt-4o-mini-tts) |
OPENAI_API_KEY |
estimated |
elevenlabs |
ElevenLabs | ELEVENLABS_API_KEY |
true timestamps |
grok |
xAI Grok TTS (api.x.ai/v1/tts) |
XAI_API_KEY |
estimated |
custom |
Any OpenAI-compatible /audio/speech endpoint (Groq, LocalAI, ...) |
optional | estimated |
Switch any time: claude-voice provider elevenlabs or /voice provider elevenlabs inside Claude Code. Keys come from env vars or claude-voice key <provider> <key> (stored in the config file, chmod 600). Each provider remembers its own voice.
For custom, set the endpoint in ~/.config/claude-voice/config.json:
"custom": {"base_url": "https://api.groq.com/openai/v1", "model": "playai-tts"}Cloud providers that return MP3 need ffmpeg on PATH (brew install ffmpeg).
claude-voice setup installs ~/.claude/commands/voice.md, so inside Claude Code you can type:
/voice off turn speech off globally
/voice on back on
/voice mute mute just this terminal
/voice status current state
/voice provider grok switch provider
/voice voice eve switch voice
/voice speed 1.3 faster
/voice theme violet restyle the karaoke UI
/voice announce on prefix hook speech with the project folder
/voice history on opt in to private local response history
/voice voices list voices for the current provider
The command runs instantly (it executes before the prompt is sent) and Claude just relays the result. The Stop hook does not speak that relay, so a pause command stays paused.
/voice stop stop the current response
/voice pause pause at the current position
/voice resume continue playback
/voice session off silence this Claude Code session
/voice session voice af_heart override its voice
/voice session provider kokoro override its provider
/voice session clear remove this session's overrides
/voice project off silence this project and its subdirectories
/voice project voice am_fenrir set a project voice
/voice project clear remove this project's overrides
/voice default off silence sessions without an enabling rule
/voice pronounce pytest pie test add a pronunciation for local speech
/voice pronounce pytest remove that custom pronunciation
Session settings override project settings, which override defaults. The longest matching project directory wins. Session overrides are stored locally in ~/.cache/claude-voice/sessions.json and expire one week after their last update. The global /voice off switch still disables all hook speech.
Kokoro streams one sentence at a time. Cloud providers still synthesize a full response before playback. A normal prompt pauses speech from the same terminal by default; set pause_on_prompt to any or off to change this. Stopping playback is immediate, but an in-flight synthesis must finish before the shared engine can start another response.
Resume and seek wait for the first audio chunk before opening the output stream, so system and custom voices keep their native sample rate. Synthesis failures are reported even after playback starts; partial responses are not automatically replayed. Direct text playback with --no-daemon, demos and benchmarks exit nonzero on synthesis failure. Interrupted runs do not produce a success summary or benchmark averages.
Hooks play audio without drawing into Claude Code or taking its keyboard input. Set hook_karaoke to true only if you accept those terminal interactions; standalone terminal runs still render karaoke. The ancestor-terminal lookup is Linux-specific; macOS retains the existing terminal fallback.
Custom pronunciations are applied by Kokoro and the system provider, not cloud providers. Short inline code and table cells are retained for speech; fenced code and URLs are removed.
claude-voice setup # install Stop + UserPromptSubmit hooks + /voice command
claude-voice uninstall # remove all of it
claude-voice on | off | toggle # enable / disable globally
claude-voice mute | unmute # this terminal only
claude-voice status # current state at a glance
claude-voice provider <name> # switch TTS provider (no arg: list)
claude-voice voice <name> # set voice for current provider
claude-voice voices [provider] # list voices
claude-voice key <prov> <key> # store an API key (no args: show key status)
claude-voice speed 1.2 # playback speed
claude-voice volume 80% # volume
claude-voice theme ember # UI theme
claude-voice announce on | off # identify the project before hook speech
claude-voice clip # speak the clipboard (bind to a hotkey)
claude-voice history on | off # opt in/out of private response history
claude-voice history [count] # list saved responses (newest first)
claude-voice replay [n] # replay one (1 = latest, without duplicating it)
claude-voice seek <seconds> # seek within the latest synthesized audio
claude-voice playpause # pause or resume the latest synthesized audio
claude-voice daemon-status # warm-daemon state
claude-voice daemon-stop # stop the daemon
claude-voice demo # polished demo (for screen recording)
claude-voice benchmark # latency stats
claude-voice doctor # diagnose broken installs
claude-voice "some text" # speak arbitrary text
claude-voice -p openai -v nova "text" # one-off provider/voice
claude-voice --no-daemon "text" # bypass the daemon; speak in-process- kokoro — 12 voices:
af_heart(default),af_nova,am_adam,am_fenrir,bm_george,bf_emma, ... (claude-voice voices kokoro) - openai —
marin(default),cedar,nova,onyx,shimmer,coral,fable, ... - elevenlabs —
Rachel(default),Adam,Josh,Domi, ... plus every voice on your account (fetched live), or any raw voice ID - grok —
eve(default),ara,leo,rex,sal, or any voice_id from docs.x.ai - system — whatever your OS ships (
claude-voice voices system)
~/.config/claude-voice/config.json:
{
"enabled": true,
"provider": "kokoro",
"voices": {"kokoro": "af_heart", "openai": "marin", "elevenlabs": "rachel", "grok": "eve"},
"speed": 1.0,
"volume": 1.0,
"theme": "aurora",
"chime": true,
"announce_project": false,
"history_enabled": false,
"use_daemon": true,
"min_chars": 30,
"max_chars": 8000,
"hook_karaoke": false,
"pause_on_prompt": "same",
"default_enabled": true,
"projects": {},
"pronunciations": {},
"keys": {}
}- Claude Code fires the Stop hook after every response
- Hook reads the response text from stdin JSON (
last_assistant_message, with a fallback that parsestranscript_pathon newer Claude Code versions) - Removes markdown formatting, fenced code and URLs; retains short inline code and table cells, and skips responses that are mostly code
- The hook hands the text to the daemon (spawning it if needed), which synthesizes with the active provider
- Hooks play audio without changing Claude Code's display; standalone terminal runs can show word highlighting. ElevenLabs uses character timestamps, while other providers estimate timings.
- Playback supports stop, pause and resume. When standalone karaoke is enabled, a keypress interrupts and the display cleans up afterward.
Loading Kokoro takes ~6–10s, and the Stop hook fires a fresh Python process for every response. The daemon loads the model once and accepts requests over a Unix socket (~/.cache/claude-voice/daemon.sock), so warm TTFA drops to ~0.6s. It exits after 30 minutes idle and respawns on the next speak. Cloud providers go through it too — the hook client returns in ~50ms instead of blocking.
claude-voice daemon-status, claude-voice daemon-stop, or set "use_daemon": false in config to opt out.
Run Claude Code in five terminals and only the one you're typing in speaks:
- A UserPromptSubmit hook claims the current terminal every time you send a prompt
- When a response finishes, the daemon only speaks if it finishes in the claimed terminal (claims expire after 10 minutes)
claude-voice mute/unmute(or/voice mute) silences one terminal permanently without touching the others
Response history is off by default because spoken messages can contain private project context. Opt in explicitly:
claude-voice history onEntries are stored in ~/.cache/claude-voice/history.jsonl, limited to the newest 200 messages, and forced to file mode 0600 inside a mode 0700 runtime directory. history off stops new entries without deleting existing ones. Replaying an entry does not create a duplicate.
The optional integrations/hammerspoon-panel.lua adds a floating macOS control panel with on/off, stop, speed and volume sliders, replay history, and play/pause and seek controls. Set CLAUDE_VOICE_BIN in the file, run claude-voice history on, then load it from Hammerspoon.
Seek and pause currently control audio only. Karaoke rendering intentionally stops when playback is scrubbed because it does not yet resynchronize word highlighting.
integrations/raycast/ ships three Raycast script commands:
| Command | What it does | Suggested hotkey |
|---|---|---|
| Voice On/Off | toggle speech globally | ⌘⌃T |
| Voice Stop Speaking | interrupt current playback (claude-voice stop) |
⌘⌃S |
| Voice Speak Clipboard | read the clipboard aloud (claude-voice clip) |
⌘⌃C |
Setup: Raycast → Settings → Extensions → + → Add Script Directory → pick integrations/raycast/, then record a hotkey on each command. ⌘⌃ combos are chosen to stay clear of terminal multiplexers (tmux/herdr use plain ctrl/alt) and standard macOS shortcuts.
Prefer Hammerspoon? integrations/hammerspoon-snippet.lua binds highlight-and-speak. The full integrations/hammerspoon-panel.lua adds playback controls and opt-in replay history.
- It's one file and it's getting big —
claude_voice.pynow handles six providers, a daemon, routing, rendering, and the CLI. Still "one file" by design, but it's pushing it. - Code-heavy responses get skipped — If a response is >50% inside code fences, the hook skips it. Correct, but the heuristic is crude and sometimes skips useful explanations that include code examples.
- Estimated karaoke timing on most providers — Only ElevenLabs returns real word timestamps. Kokoro/OpenAI/Grok timing is length-weighted estimation; good, not perfect.
- Grok output format is undocumented — xAI's TTS docs don't specify the audio container, so we sniff it (WAV natively, anything else via ffmpeg). Works, but it's a guess until they document it.
- Terminal compatibility — Works on Kitty, Ghostty, Alacritty, iTerm2. Windows Terminal? Probably not.
- Better code detection — distinguish "explanation with code examples" from "pure code response"
- Streaming synthesis — start speaking sentence 1 while sentence 2 is still generating (biggest remaining TTFA win)
- Integration with agent ecosystem — when Deep Video Watcher generates a comprehension report, claude-voice can read it aloud; when Blackreach finishes research, it can narrate findings
- Voice profiles per project — coding voice (fast, clear) vs reading voice (warm, expressive) vs research voice (neutral, precise)
- Persistent playback position — restore the latest narration position after the daemon or machine restarts
- Real-time conversation — full duplex: I speak, Claude thinks, Claude speaks back, I interrupt, Claude adapts. True voice mode.
- Integration with Claud-Ear — when the music analysis tool runs, claude-voice narrates the results with the right musical terminology pronunciation
- Python 3.11+ (3.12 recommended for Kokoro/torch)
sounddevice,numpy— pluskokorofor the local voiceffmpegfor cloud providers that return MP3- macOS or Linux, any terminal with ANSI true color (Kitty, Ghostty, Alacritty, iTerm2, ...)
MIT
