<!-- Markdown mirror. Canonical: https://empryo.com/docs/surfaces/voice-input -->

# Voice input

> Speak prompts in the terminal UI, desktop app, phone and CLI. Transcribe on your ChatGPT plan, live with gpt-live-transcribe, or offline with whisper.cpp.

Voice input lets you speak a prompt instead of typing it. It works in the terminal UI (TUI), the desktop app, the phone app and the headless CLI. Empryo records a short clip, turns it into text, and either puts the text in your message box for review or sends it right away.

## Option 1: your operating system's dictation

You do not need Empryo's voice feature at all if your system already has dictation. It types into any text field, including Empryo's message box.

- **macOS**: turn on **System Settings → Keyboard → Dictation**. Click into the message box, press the dictation key (🎤 or Fn), and speak.
- **Windows**: press **Win+H**.

Use Empryo's own voice input when you want better accuracy on technical words, automatic sending, headless runs, or Linux.

## Option 2: offline with whisper.cpp (free, no key)

[whisper.cpp](https://github.com/ggml-org/whisper.cpp) runs speech-to-text on your own machine. Nothing leaves your computer and you need no API key.

First install it:

```bash
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.
```

Then download a model. In the TUI, run `/voice setup`. It downloads the default model and tells you when the mic is ready. To pick a different model, name it:

| Command | Size | Notes |
|---------|------|-------|
| `/voice setup tiny.en` | ~75 MB | Fastest, English only |
| `/voice setup base.en` | ~142 MB | Fast, English only. The default |
| `/voice setup small.en` | ~466 MB | More accurate, English only |
| `/voice setup large-v3-turbo-q5_0` | ~547 MB | Best quality, all languages |

You can also download a model yourself:

```bash
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.bin
```

On Windows, use `%USERPROFILE%\.local\share\whisper-cpp` instead.

Empryo looks for models in `~/.local/share/whisper-cpp`, `~/.cache/whisper-cpp`, and Homebrew's `share/whisper-cpp/models` folder. If it finds several, it uses the most accurate one. To choose a model yourself, set `voice.localModel` to a model name (`"base.en"`) or a full file path.

The desktop app records in a different audio format. It converts the recording with `ffmpeg` before passing it to whisper.cpp, so install `ffmpeg` if you use local transcription in the desktop app.

## Option 3: cloud transcription with an OpenAI or Groq key

### Your ChatGPT plan (the default)

If you signed in to ChatGPT with `/login codex`, voice input works with no key and no extra setup. Empryo sends each clip to the transcription service ChatGPT's own apps use, so it runs on your plan instead of an API bill. It is the default whenever you are signed in, even when you also have an API key. Picking `chatgpt/transcribe` while signed out uses your OpenAI or Groq key instead; with no key, voice says to run `/login codex` rather than showing ready. If a clip fails on the plan, it goes to your key at `api.openai.com` (or Groq), never to ChatGPT.

- The text arrives when you stop talking, in about a second. Words while you speak need an OpenAI API key: [turn live dictation on](#live-dictation-words-appear-as-you-speak) to use it.
- It takes no keywords, so product names can come out misspelled ("Empryo" became "MPRIO" in our tests). Pick `openai/gpt-transcribe` and set `voice.keywords` when names matter.
- ChatGPT keeps each clip in your account for 30 days. Empryo cannot shorten that: the service ignores retention options and refuses to delete the file (tested September 30, 2026).
- It is not a public API, so OpenAI can change or block it. When a clip fails there, Empryo transcribes it with your OpenAI or Groq key, or local Whisper, instead.

### An API key

If you already have an OpenAI or Groq key, voice input works with no extra setup.

```bash
empryo --set-key groq <key>
# or
empryo --set-key openai <key>
```

| Key | Model Empryo uses | Price (September 2026 list) |
| --- | --- | --- |
| ChatGPT plan (signed in) | ChatGPT's transcription service | part of your plan |
| OpenAI | `gpt-transcribe`, OpenAI's most accurate speech-to-text model | $0.0045 per minute |
| Groq only | `whisper-large-v3-turbo` | free tier, then per hour |

A ChatGPT sign-in wins over keys, and OpenAI wins over Groq. Pick another model with `/voice model` in the TUI, the Voice page in desktop Settings, or [the config](#configure-transcription).

## Live dictation: words appear as you speak

With an OpenAI API key and no ChatGPT sign-in, live dictation is on by default. When you are signed in to ChatGPT it is off by default, so your plan does the work; turn it on to spend your key on live words. Your words show up in the draft while you talk, the way ChatGPT's dictation does, without a voice conversation. Stop talking and it stops by itself; the draft already holds the final text.

Empryo keeps a connection to OpenAI ready before you press the mic, so the first words appear about a second after you start and the last ones about half a second after you stop (measured on September 30, 2026).

- **TUI**: `/voice live off` or `/voice live on`, or `/voice settings` and pick **Live dictation**.
- **Desktop**: `/voice live off` or `/voice live on` in the composer, or **Settings → Voice → Live dictation**. `/voice settings` opens that page, and searching Settings for "dictation" jumps there.
- **Anywhere**: ask Empryo to set `voice.live` to `false` to turn it off.

## Stops when you go quiet

After you speak, about 2.5 seconds of quiet ends the recording and the text lands in the draft, or is sent if `voice.insertMode` is `"send"`. Holding the shortcut to talk ignores quiet and stops when you let go. Change the wait with `/voice auto-stop 4`, the **Stop after silence** row in desktop Settings, or `voice.autoStop`. `0` or `off` keeps listening until you stop it. The phone app reads the same wait from your computer's config.

### Hands-free: talk, pause, it sends, it listens again

Turn on hands-free and you only press the mic once. Each time you go quiet, what you said is sent, and the mic opens again for the next thing you say. Stretches with no speech are thrown away instead of sent. Press the mic again to end the session; what you said up to then is still sent. Esc ends it too, but throws away what you said since the last pause.

- **TUI**: `/voice hands-free on`, or `/voice settings` and pick **Hands-free**. The recording line shows "hands-free" while it listens.
- **Desktop**: `/voice hands-free on` in the composer, or **Settings → Voice → Hands-free**. The mic's tooltip says it is listening.
- **Phone**: touch and hold any mic and pick **Hands-free**, or open **Settings → Voice**. The phone reads and writes the same setting on your computer, so all three always agree. A small repeat badge on the mic means it is listening hands-free.
- **Anywhere**: ask Empryo to set `voice.handsFree` to `true`.

Hands-free always sends; it ignores `voice.insertMode`. If `voice.autoStop` is `0`, hands-free still stops after 2.5 seconds of quiet, because it needs a pause to know when you are done. On the ChatGPT plan it costs nothing extra; with live dictation on, the open mic streams to OpenAI and is billed per minute while it listens.

Live dictation uses `gpt-live-transcribe` ($0.017 per minute) and needs an OpenAI API key. Empryo streams your microphone at 24 kHz to OpenAI's Realtime API. A pause of about 0.7 seconds closes a sentence, so the text before it becomes final while you keep talking. Empryo still records the whole clip: if the live stream drops, it transcribes the recording with your normal model instead, so nothing you said is lost.

`voice.delay` trades speed for accuracy. The default is `low`, which gave the fastest first words in our tests without losing accuracy.

| Delay | Behaviour |
| --- | --- |
| `minimal` | Words appear fastest |
| `low` | Fast, good for captions |
| `medium` | Balanced |
| `high` | More accurate, words arrive later |
| `xhigh` | Most accurate, slowest |

Live dictation works in the TUI, the desktop app and the phone app. Headless runs record a clip and transcribe it when you press Enter.

## Use voice in the TUI

1. Press `Ctrl+Y`, click the mic button in the message box, or run `/voice`.
2. Speak. The message box shows `● REC` and a small level meter that moves with your voice, so you can see the mic is picking you up.
3. Press `Ctrl+Y` again to stop. Press `Esc` instead to cancel and throw the recording away.
4. Wait while it shows `transcribing…`. The text is added to your draft, after anything you already typed. With live dictation on, the words appear while you speak.
5. Edit the draft if you want, then press `Enter` to send.

To send the text automatically instead, set `voice.insertMode` to `"send"`.

## Use voice in the desktop app

The desktop app records through your system microphone, so it needs microphone permission. You do not need a separate recording tool.

1. Click the mic button in the message box, or press `⌘Y` on macOS or `Ctrl+Y` on Windows and Linux.
2. Speak. The mic icon pulses with your voice.
3. Click the mic button again to stop. Press `Esc` to cancel instead.
4. Review the text in your draft, then send it.

You can also **hold** the shortcut to talk. Hold the key, speak, and let go. Recording stops and transcribes when you release. A quick tap (under 400 ms) starts and stops recording as usual.

By default the text stays in your draft (`voice.insertMode` is `"insert"`). Set it to `"send"` to send it automatically.

## Use voice in headless mode

You need an interactive terminal for this. Run:

```bash
empryo --headless --voice
```

Empryo records until you press `Enter`. A status line shows the elapsed time and a level meter. Press `Esc` or `Ctrl+C` to cancel. The text becomes your prompt.

If you also type or pipe a prompt, the spoken text is added after it:

```bash
empryo --headless --voice "also update the tests"
```

## Configure transcription

Voice settings live under `voice` in `config.json`. That is `~/.empryo/config.json` for all projects, or `.empryo/config.json` for one project.

```jsonc
{
  "voice": {
    "model": "openai/gpt-transcribe",
    "language": "en",
    "insertMode": "insert",
    "live": true,
    "delay": "medium",
    "prompt": "A coding session in a TypeScript monorepo called Empryo.",
    "keywords": ["Empryo", "Genome", "useChat", "Bun"]
  }
}
```

| Key | What it does |
| --- | --- |
| `model` | Speech-to-text model as `provider/model`: `chatgpt/transcribe` (your ChatGPT plan), `openai/gpt-transcribe`, `openai/gpt-4o-transcribe`, `openai/gpt-4o-mini-transcribe` or `groq/whisper-large-v3-turbo`. That provider needs a key. |
| `language` | The language you speak, as one code (`en`) or a list (`en,fr`). Leave it out to detect the language. |
| `insertMode` | `"insert"` puts the text in your draft. `"send"` sends it right away. |
| `live` | Shows words as you speak. On by default when you have an OpenAI API key and are not signed in to ChatGPT; `true` or `false` sets it either way. |
| `autoStop` | Seconds of quiet after you speak before recording stops by itself. Default `2.5`; `0` keeps listening. |
| `handsFree` | `true` sends each time you go quiet and listens again until you stop the mic. Off by default. |
| `liveModel` | The live model. Defaults to `gpt-live-transcribe`. |
| `delay` | Live speed against accuracy: `minimal`, `low` (default), `medium`, `high` or `xhigh`. |
| `prompt` | A sentence about what you usually talk about. It helps with jargon. |
| `keywords` | Exact words to expect, such as names and identifiers. No `<`, `>` or line breaks. |

`prompt` and `keywords` go to `gpt-transcribe` and `gpt-live-transcribe`. Other models get `prompt` only. A project that is not [trusted](/docs/reference/configuration) cannot set `prompt` or `keywords`, because they can change what the transcript says.

In the TUI, `/voice settings` opens all of this in one picker, and `/voice model`, `/voice live`, `/voice hands-free`, `/voice auto-stop`, `/voice delay` and `/voice language` change one thing each. In the desktop app it is **Settings → Voice**, which also shows which model will run and why live dictation is not ready. On the phone, touch and hold any mic for hands-free, live words, what happens when you finish and the quiet wait, or open **Settings → Voice** for the same four. The phone saves them on your computer, so the TUI and desktop change with it.

Every text field on the phone has a mic: the chat box, answers to questions, steering a running step, plan notes, new schedules, handoff notes and the reason for stopping a step. Only the field you started from receives the words. Fields that send (chat, answers, steering, plan notes) follow your send and hands-free choices; the others only fill the field.

### Change the mic shortcut

`voice.key` changes the mic shortcut in both the TUI and the desktop app:

```jsonc
{
  "voice": { "key": "mod+shift+v" }
}
```

Join keys with `+`. The last part is the key. Everything before it is a modifier.

| Part | Meaning |
| --- | --- |
| `ctrl` / `control` | Control |
| `cmd` / `meta` / `command` / `super` | Command on macOS, Windows key on Windows |
| `mod` | Command on macOS, Control everywhere else |
| `alt` / `option` / `opt`, `shift` | Alt or Option, Shift |
| last part | A letter or digit (`y`), a function key (`f5`), or a key name (`space`) |

The defaults are `ctrl+y` in the TUI and `mod+y` in the desktop app.

A few rules:

- If Empryo cannot read your shortcut, it uses the default instead of showing an error.
- A single letter with no modifier is not allowed, because it would trigger on every keystroke. Function keys on their own are fine.
- `Esc` always cancels a recording. You cannot change that.
- Restart the TUI or the desktop app after changing the shortcut.

### Use your own transcription server

Set `voice.baseURL` to any server that offers an OpenAI-compatible `/audio/transcriptions` endpoint. This could be a whisper server on your machine or on your local network. Your audio then stays on your network. A custom server needs no provider key, and `model` can be a plain name:

```jsonc
{
  "voice": {
    "baseURL": "http://localhost:9000/v1",
    "model": "large-v3"
  }
}
```

### Transcribe with a chat model that understands audio

Set `voice.llm` to send the recording to a chat model that accepts audio, instead of a speech-to-text endpoint. Any provider you have set up works. Use `voice.instruction` to tell the model how to write the text, for example to remove filler words:

```jsonc
{
  "voice": {
    "llm": "openai/gpt-4o-audio-preview",
    "instruction": "Transcribe faithfully, but drop filler words and add punctuation."
  }
}
```

### Use your own recording command

`voice.command` replaces the recording command in the TUI and headless mode. Empryo replaces every `{output}` with the path of the audio file. The command must keep recording until it is stopped, and write an audio format your transcription service accepts:

```jsonc
{
  "voice": { "command": ["sox", "-d", "-c", "1", "{output}"] }
}
```

## Requirements and troubleshooting

The TUI and headless mode record with a command-line tool. Empryo uses the first one it finds:

- **macOS**: `rec` (part of SoX) or `ffmpeg`. Run `brew install sox` if you have neither.
- **Linux**: `arecord` (from `alsa-utils`), `ffmpeg`, or `rec` (SoX).
- **Windows**: `ffmpeg`.

If none is installed, the error message tells you what to install for your system.

Each recording can be up to 5 minutes long, and the audio file can be up to 10 MB.

## Privacy

Empryo sends your audio only to the transcription service you configured. With whisper.cpp, it never leaves your machine.

In the TUI and headless mode, the recording is saved to a private temporary file and deleted after transcription or when you cancel. The desktop app keeps the recording in memory until transcription finishes.
