softvoice
Talk to Claude Code and Codex, and hear them talk back. Everything stays on your Mac.
Your coding agent finishes a turn and reads its reply aloud, underlining each sentence on the page as it goes. You answer by talking: tap a key, say what you want, press Enter. A small glass pill floats above your windows and shows what is happening. No accounts, no API keys, no audio sent anywhere: after a one-time model download it works with the network unplugged.
- Speaking: Pocket TTS by Kyutai, a 100M-parameter voice that runs on the CPU and starts talking about 200 ms after the text arrives
- Listening: Parakeet TDT 0.6B v3 by NVIDIA, run through parakeet-mlx on Apple Silicon; transcribes faster than you speak
- Integration: the agents' own
Stop,NotificationandUserPromptSubmithooks. No MCP server, no wrapping the terminal, no changes to how you work
English first (the listener understands 24 other European languages; the voice has English, French, German, Spanish, Italian, Portuguese and Dutch). Apple Silicon Mac, macOS 14 or newer. MIT licence. Early, and in daily use by its author.
Install
Three commands, with uv doing the Python work:
brew install uv
uv tool install softvoice
softvoice setup
(Or the newest commit straight from GitHub: uv tool install git+https://github.com/SPra03/softvoice.)
setup downloads the two models once (Pocket TTS, about 230 MB; Parakeet, 2.5 GB) and asks
macOS for the permissions below. Then:
softvoice install # hooks into Claude Code (and Codex, if ~/.codex exists)
softvoice autostart on # the pill, now and at every login, with no terminal left open
install writes three hooks into ~/.claude/settings.json (a backup is kept next to it) and,
if ~/.codex exists, into ~/.codex/hooks.json. Restart your Claude Code sessions so they pick
the hooks up; Codex needs a one-time /hooks to trust them. Upgrade later with
uv tool upgrade softvoice; softvoice uninstall removes the hooks, softvoice autostart off
the login item, and everything softvoice owns lives in ~/.softvoice.
Needs about 4 GB of disk for the models and the Python environment, and about 2 GB of memory while running. The models load once at start; idle, it uses no CPU.
Permissions
macOS grants these to the app that owns the terminal you run softvoice from (Terminal, iTerm, the Claude desktop app, VS Code), or to Python itself when the login item starts it. Each is asked for once.
| permission | needed for | asked |
|---|---|---|
| Microphone | listening | the first time the mic opens |
| Accessibility | pasting your words into the prompt; reading the page for the underline | by setup, or the first paste |
| Input Monitoring | seeing the talk key and scroll events | by setup, or the first listen or app |
If you grant Accessibility after the app has started, it restarts itself (under the login item) so the underline can read the page.
Everyday use
The talk key is right Option by default: the ⌥ key to the right of the space bar, chosen
because nothing else uses it on its own. Prefer a combination? One line in the config:
hotkey = "alt+space" (or ctrl+shift+d, fn+space, ...). A combination is swallowed, so the
app in front never sees it.
| when | you | softvoice |
|---|---|---|
| any time | tap the talk key, or click the mic | opens a dictation session: talk as long as you like, your words appear in the pill |
| dictating | tap again, press Enter, or click ✓ | pastes everything into your prompt, where you can still edit it |
| dictating | press Escape, click ✕, or say "cancel" | discards it |
| dictating | end with "send it" | pastes and presses Enter for you |
| any time | hold the talk key, speak, release | push-to-talk: a quick take, pasted where your cursor is |
| while it speaks | tap the talk key, or click the speaker | stops the voice |
| while it is quiet | click the speaker | reads the text you have selected in any app, or replays the last reply |
| hands-free on | just talk after the beep | after every spoken reply the mic opens by itself, pauses send |
Enter and Escape are only taken while a dictation session is open, and only on their own: Cmd, Ctrl and Option combinations still reach your app. The second Enter, after the paste, sends your prompt as usual.
What is on device, and what is not
- Audio is recorded, transcribed and synthesised on your Mac. It is never written to disk and never sent anywhere.
- The agent's replies are read from the hook payload on your Mac and spoken locally. Anything that looks like a key or token is replaced with the word "redacted" before it is spoken; code blocks, tables and URLs are skipped.
- The network is used once, to download the models from Hugging Face. After that you can work offline.
- Nothing is collected: no analytics, no accounts, no telemetry.
Hearing replies
Nothing else to run. When an agent finishes a turn, its Stop hook calls softvoice hook, which
hands the reply to a small daemon (started on demand, kept warm) that cleans and speaks it:
- the whole turn is spoken, not just the last paragraph: every text block of at least
interim_min_chars(200) since your message, so a long answer written before a tool call is not lost, while short progress notes between tool calls are skipped - code blocks, tables, URLs and long paths are dropped or shortened: look at the screen for those
- markdown is flattened; bullets become sentences so the voice pauses
- anything that looks like a key or token is replaced with "redacted"
- the whole reply is spoken, however long; set
max_charsto cut long replies at a sentence boundary with "More on screen." - permission prompts are spoken too; sending a new prompt interrupts speech
softvoice say "hello there" # speak anything
softvoice stop # shut up
softvoice mute / unmute # stay silent across turns
softvoice voice jane # try another voice (see `softvoice voices`)
softvoice status | log | restart
Following along
While a reply is read, the pill shows the sentence being spoken on one line: the words already said and the current one are white, the rest grey, and long sentences slide left so the current word stays in view. The daemon speaks one sentence at a time and learns its own speaking rate, so the timing stays close.
On the page itself, a thin light grey underline is drawn under that sentence in the front window,
line by line, found through macOS accessibility. It works where the app exposes its text: the
Claude desktop app, Terminal, VS Code and most native apps. The underline follows the page: every
scroll event makes it re-read its position at once, and a slow safety read twice a second covers
keyboard scrolling and scrollbar drags. When there is nothing to underline, the app log says why.
Turn it off with the pill's menu or highlight_on_page = false.
Talking
The transcript is pasted wherever your cursor is: the Claude Code prompt in a terminal, the
desktop app, anything. Dictation and push-to-talk paste into whatever app is in front, since you
started them on purpose. Hands-free, where the mic opens by itself, is more careful: it pastes
only into terminals and agent apps (paste_targets) or an app you have dictated into before, and
otherwise puts the text on the clipboard and tells you.
Without the pill, from a terminal: softvoice listen (hold the key, speak, release) and
softvoice listen --hands-free. --auto-submit presses Enter for you; --no-paste only prints.
The pill and the menu bar
softvoice app --hands-free # by hand; `softvoice autostart on` does this at every login
A small dark glass pill (36 points tall) floats above every window, bottom right by default. Drag it anywhere; it remembers the spot. Clicking it never steals focus, so your cursor stays in the prompt. It uses one accent colour, and the accent always means "live": the waveform while the mic listens, the speaker while the voice talks. Everything else is grey. Whenever text shows, the capsule becomes a rounded rectangle, and the text is a single line that scrolls, newest words at the right.
| button | click | shows |
|---|---|---|
| mic | start a dictation session | coral at rest (talk is the next thing to do), a live coral waveform while it listens, grey while the voice speaks or it transcribes |
| speaker | stop the voice while it speaks; when quiet, read the text you have selected in the front app, or replay the last reply if nothing is selected | coral stop while speaking, grey play otherwise, slashed when muted |
Exactly one thing on the pill is coral at any time, and it is the thing to tap next.
Dictation session: click the mic (or tap the key) and the pill grows to the left into
waveform · live text · ✕ · ✓. Committed sentences are white; the sentence still forming, and
the hint text, are grey; the line scrolls so the newest words are always visible. Each time you
pause for dictation_pause_ms (0.7 s) the sentence is committed; the one you are still saying is
re-transcribed about once a second so you see it forming. dictation_silence_s (8 s) of silence
finishes the session by itself. The preview lives in the pill rather than in the prompt on
purpose: live text keeps revising itself, and the prompt only gets one clean paste that you can
edit before sending.
Press and hold either button, or right-click, for the options: Hands-free, Auto-send, Mute,
Underline, Voice, Hide pill, config file, Quit. panel = false in the config turns the pill off.
The menu bar gets one small waveform, the same in every state (the pill is where state shows);
menu_bar_icon = false hides it. Its first line says what softvoice is doing right now, and the
menu has Hide pill / Show pill, Talk, Stop speaking, Mute, Hands-free and
Auto-send, the config file, and Quit.
Running without a terminal
softvoice autostart on registers the app as a macOS login item: it starts when you log in, needs
no terminal, and comes back by itself if it ever crashes (not after you quit it on purpose).
softvoice autostart off removes the login item and stops the app; softvoice autostart shows
the state. The app's output goes to ~/.softvoice/app.log.
Only one copy of the app runs at a time: softvoice app while it is already running just shows
the pill of the running one. Hide pill tucks the pill away while the key, the voice and the
menu bar keep working; Show pill brings it back. Quit from the menu stops the app until the
next login or the next softvoice app.
Hands-free
A conversation loop, on with --hands-free, the menu, or hands_free = true. Each time the
daemon finishes speaking a reply:
- a short high beep: the mic is open
- you talk; Silero VAD waits until you pause for
end_silence_ms(0.9 s) - a short low beep: the mic is closed; Parakeet transcribes
- the text is pasted into the agent's prompt, where you can read and edit it, then you press Enter
To send without touching the keyboard, end what you say with "send it" (send_words), or turn
on Auto-send (auto_submit = true), which presses Enter for you. Say nothing for
listen_timeout_s (10 s) and the mic closes. Say "cancel" or "never mind" to discard a take.
Hold the talk key at any moment to interrupt the speaker and take over. If you typed a prompt
yourself, or interrupted the speaker, the mic stays closed.
Configuration
~/.softvoice/config.toml (softvoice config prints it) lists every setting with its default,
all commented out; remove the # from a line to change it. Restart the daemon after changes
(softvoice restart) and relaunch the app (softvoice autostart on again, or Quit and softvoice app).
| key | default | meaning |
|---|---|---|
voice |
alba |
a predefined voice, or a path to a short WAV to clone |
language |
english |
Pocket TTS model: english, french, german, spanish, italian, portuguese, dutch, or the tiny english_18m_2026-10 |
max_chars |
0 |
cut long replies here; 0 speaks the whole reply |
speak_whole_turn |
true |
speak every substantial text block of the turn, not only the last |
interim_min_chars |
200 |
shorter blocks before tool calls count as progress notes and are skipped |
speak_notifications |
["permission_prompt"] |
add "idle_prompt" to be nagged |
interrupt_on_prompt |
true |
stop speaking when you send a prompt |
announce_project |
false |
prefix replies with the project folder name |
hotkey |
alt_r |
the talk key: alt_r, alt_l, cmd_r, ctrl_r, shift_r, fn, f1…f20, or a combination such as alt+space, ctrl+shift+d (modifiers alt, cmd, ctrl, shift, fn plus a letter, digit, space, tab or F-key) |
stt_model |
mlx-community/parakeet-tdt-0.6b-v3 |
any Parakeet model in the mlx-community collection |
auto_submit |
false |
press Enter after pasting; off means the transcript waits in your prompt for review |
send_words |
["send it", ...] |
say one at the end of a take to send it hands-free |
tap_threshold_ms |
350 |
shorter press is tap-to-talk, longer is push-to-talk |
dictation_pause_ms |
700 |
pause that commits a sentence to the preview |
dictation_silence_s |
8 |
silence that finishes a dictation session |
panel / menu_bar_icon |
true |
show the floating pill / the menu bar icon |
highlight_on_page |
true |
underline the sentence being read in the front window |
clear_selection |
true |
after you select text and press play, ask the app to drop the selection (native apps and editors; the Claude app ignores the request) |
accent |
"#E8735A" |
the pill's live colour (waveform, speaker) |
underline |
"#AAAAAA" |
the colour of the underline on the page |
hands_free |
false |
hands-free by default |
end_silence_ms |
900 |
pause that ends your utterance |
listen_timeout_s |
10 |
close the mic when you say nothing |
earcon |
true |
beeps when the mic opens and closes |
cancel_words |
["cancel", ...] |
say one of these to discard a take |
paste_targets |
terminals, Claude, VS Code, Cursor, JetBrains, Codex | bundle ids hands-free may paste into (dictation you start yourself pastes anywhere) |
Set SOFTVOICE_DISABLED=1 in an agent's environment to silence it without uninstalling.
How it fits together
Claude Code / Codex ──Stop hook (stdin JSON)──▶ softvoice hook ──unix socket──▶ daemon
(40 ms, no ML imports) │ clean markdown
│ Pocket TTS stream
▼ speakers
you ──tap or hold the key──▶ softvoice app ──Parakeet──▶ paste into the focused prompt
▲ hands-free: the daemon's "speech_end" event opens the mic,
Silero VAD ends the take, the transcript is pasted (+ Enter)
The hook command is tiny and imports no ML code, so it never slows the agent down. The daemon holds the voice model warm between turns. The app holds the listener: the key watcher, the mic, Parakeet, the pill and the underline.
Status
Early, and in daily use by its author on an M4 MacBook Air. Verified live: the hands-free loop, the pill, dictation sessions with Enter and Escape, the Option+Space talk key, follow-along text and the underline on the page. Verified without a human: hook latency, speech from Stop and Notification payloads, interruption on new prompts, the daemon's event stream, VAD end-of-utterance timing on recorded speech, and a Pocket TTS to Parakeet round trip. Not yet: languages other than English, a signed app bundle, Windows or Linux (the listener needs Apple's MLX).
Found a rough edge? Open an issue with the lines from softvoice log and ~/.softvoice/app.log.
Credits
Pocket TTS by Kyutai. Parakeet TDT 0.6B v3 by NVIDIA (CC-BY-4.0), converted for MLX by the mlx-community and run with parakeet-mlx by senstella. Silero VAD for end-of-utterance detection. rumps for the menu bar. See LANDSCAPE.md for the survey of voice tools this started from.
Metadata
Release files for softvoice 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| softvoice-0.1.0.tar.gz | 63.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| softvoice-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 135.9 kB
Release files / softvoice-0.1.0.tar.gz
| Download URL | softvoice-0.1.0.tar.gz |
|---|---|
| Size | 63.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0adf7d98c5883279b1d8dab48d0e90643b31f2259d946831c885055cb39de08b
|
|
BLAKE2b-256 checksum How to use checksums |
d72df66d7202f2932b940ec0d1558c5bb1e970a47133003f87f553b28dbd42f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / softvoice-0.1.0-py3-none-any.whl
| Download URL | softvoice-0.1.0-py3-none-any.whl |
|---|---|
| Size | 72.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b399d6d5ca0120c1847341f563a08f4f40e80704124d1ce83bfb516eef07aa0
|
|
BLAKE2b-256 checksum How to use checksums |
a3480529b2561c7f6f9237aa67045008419da456b56c7a1ef4bef014fc07c79f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for softvoice 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.