Voice input
Speak prompts instead of typing across Empryo's TUI, desktop app, and headless CLI. Record locally, transcribe with your provider key, then review or send.
Voice input lets you speak a prompt instead of typing it on the TUI, desktop app, or headless CLI. Each surface records a short clip, transcribes it, and inserts the transcript or sends it based on your config.
The zero-setup way: your OS's dictation
The fastest path needs nothing from Empryo: your OS ships live dictation that types into any focused field — including Empryo's composers. macOS: turn on System Settings → Keyboard → Dictation, focus the composer, tap the 🎤/Fn key and speak (words stream in on-device). Windows: Win+H. No key, no install, no config. The built-in pipeline below is for when you want higher accuracy on technical vocabulary, auto-send, headless runs, or Linux.
Free and offline: local whisper.cpp
No API key needed. Install whisper.cpp, then let Empryo fetch the model:
brew install whisper-cpp # macOS. Linux/Windows: grab the official
# prebuilt from github.com/ggml-org/whisper.cpp/releases
# (whisper-bin-ubuntu-*.tar.gz / whisper-bin-x64.zip)
# and put whisper-cli on PATH. Linux needs libgomp1.In the TUI, /voice setup downloads a model into the auto-discovered dir and tells you when the mic is ready — no config, no key. /voice setup tiny.en (~75 MB, fastest), base.en (default, ~142 MB), small.en (~466 MB, most accurate English), or large-v3-turbo-q5_0 (~547 MB, best quality, all languages). Downloads are atomic — a torn download is never picked up. Or fetch by hand:
mkdir -p ~/.local/share/whisper-cpp
curl -L -o ~/.local/share/whisper-cpp/ggml-base.en.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.binOn Windows the model dir is %USERPROFILE%\.local\share\whisper-cpp. This lane is verified on all three platforms — the same clip transcribes identically on macOS, Linux, and Windows x64.
Searched dirs: ~/.local/share/whisper-cpp, ~/.cache/whisper-cpp, and brew's share/whisper-cpp/models. With several models present the best is picked (large-v3-turbo quantized first, tiny last). Pin one with voice.localModel — a bare name ("base.en") or a full path. For speed use tiny.en; for accuracy large-v3-turbo-q5_0. The desktop app's recordings are converted via ffmpeg when present.
Use the TUI composer
If Empryo finds a recorder and an OpenAI or Groq key, press Ctrl+Y in the composer, click the mic chip, or enter /voice. The shortcut is remappable — see Remap the mic shortcut.
- Press
Ctrl+Yor run/voiceto start recording. - Speak while the composer shows
● REC— the little VU bar beside the clock
moves with your voice, so "is it hearing me" is answered at a glance.
- Press
Ctrl+Yagain to stop. PressEscto cancel and discard the recording. - Wait for
transcribing…. The transcript is inserted into the draft for review. - Edit the draft if needed, then press
Enterto send it.
The transcript is appended to any text already in the draft with a space. Set voice.insertMode to "send" to submit it automatically after transcription.
Use the desktop app
If the app has microphone permission and an OpenAI or Groq key, click the mic button in the composer or press ⌘Y (macOS) / Ctrl+Y — remappable via voice.key.
- Click the mic button and speak into the system microphone.
- Click the mic button again to stop and transcribe.
- Press
Escwhile recording to cancel and discard the audio. - Review or edit the transcript in the draft, then submit it.
Push-to-talk: hold the mic key down, speak, and release — the recording stops and transcribes the moment you let go. A quick tap (under 400 ms) behaves as the classic toggle instead, so both styles work with no setting. The mic icon pulses with your input level while recording.
The default voice.insertMode value is "insert", so the transcript stays in the draft. Set it to "send" when you want the surface to submit after transcription.
Use headless mode
If you have an interactive terminal, run:
empryo --headless --voiceThe command records until you press Enter (the status line carries the clock and a live VU bar), transcribes the recording, and uses the transcript as the prompt. If you also provide a typed or piped prompt, Empryo appends the transcript to it:
empryo --headless --voice "also update the tests"Configure transcription
Empryo uses your existing provider keys. When a Groq key is available, it uses Groq's fast whisper-large-v3-turbo; otherwise it uses OpenAI's gpt-4o-mini-transcribe. Set an explicit provider/model pair to override that choice.
{
"voice": {
"model": "openai/gpt-4o-transcribe",
"language": "en",
"insertMode": "insert"
}
}Use "insert" to put the transcript in the draft for review. Use "send" to submit it automatically. If you set model, keep the provider prefix as openai/... or groq/... and make sure that provider has a key.
Remap the mic shortcut
voice.key rebinds the mic toggle on the TUI and the desktop app — one spec, both surfaces:
{
"voice": { "key": "mod+shift+v" }
}Join tokens with +; the last token is the key, everything before it a modifier:
| Token | Meaning |
|---|---|
ctrl / control | Control |
cmd / meta / command / super | Command / Win |
mod | Command or Control — the cross-platform "primary" modifier |
alt / option / opt, shift | Alt / Option, Shift |
| final token | a letter or digit (y), an F-key (f5), or a DOM key name (space) |
Defaults: ctrl+y on the TUI, mod+y on desktop (⌘Y on macOS, Ctrl+Y elsewhere). A malformed spec falls back to the default rather than erroring; a bare letter with no modifier is refused (it would fire on every keystroke), while unmodified F-keys are fine. The composer hints and the mic tooltip pick up the new binding. Config is read at startup — restart the surface after changing it. Esc always discards a live recording and is not remappable.
The keybind lives in the same config.json as everything else — global (~/.empryo/config.json) or per project (.empryo/config.json).
Self-hosted transcription
Point voice.baseURL at any OpenAI-compatible /audio/transcriptions endpoint — a local whisper server, a LAN box — and your audio never leaves the network. A custom endpoint needs no provider key, and model may then be a bare name:
{
"voice": {
"baseURL": "http://localhost:9000/v1",
"model": "large-v3"
}
}Transcribe with an audio-capable chat model
Set voice.llm to send the audio to a chat model that can hear instead of an STT endpoint. Any registered provider works, and voice.instruction shapes the output — useful for cleaning up filler words or punctuating dictation:
{
"voice": {
"llm": "openai/gpt-4o-audio-preview",
"instruction": "Transcribe faithfully, but drop filler words and add punctuation."
}
}Custom recorder
voice.command replaces the recorder command entirely (TUI and headless). Every {output} token is substituted with the capture path; the command must record until interrupted and write a format your endpoint accepts:
{
"voice": { "command": ["sox", "-d", "-c", "1", "{output}"] }
}Requirements and troubleshooting
TUI and headless recording uses the first available recorder for your platform:
- macOS:
recfrom SoX orffmpeg. Runbrew install soxif neither is installed. - Linux:
arecordfromalsa-utils,ffmpeg, orsox. - Windows:
ffmpeg.
If no recorder binary is found, install one of the listed tools and try again. The error includes the platform-specific install hint. The desktop app records through the system mic with MediaRecorder, so it needs microphone permission instead of a recorder binary.
Cloud transcription requires an OpenAI or Groq key (Groq has a free tier). Without one, Empryo falls back to a local whisper.cpp install automatically — see the section above. To use cloud STT:
empryo --set-key groq <key>
# or
empryo --set-key openai <key>The TUI, desktop app, and headless CLI cap each recording at 5 minutes or 10 MB. Stop earlier if you do not need the full limit.
Privacy
Empryo sends audio only to the configured speech-to-text provider. TUI and headless recording uses a private temporary file while the mic is active, then deletes it after transcription or cancellation. The desktop app keeps the recording in memory until transcription finishes.