Local voice dictation for macOS. Whisper + Llama 3.2 3B run on-device via Apple's MLX framework. No accounts, no API keys, no network.
Project description
Susurro
Local voice dictation for macOS. Fully offline. MIT licensed.
Hold a hotkey, talk, release. The transcript is polished into structured text — ordinals become numbered lists, fillers get stripped, self-corrections get applied — and pasted at the cursor in any app. Everything runs on your Mac through Apple's MLX framework. No accounts, no API keys, no network.
Install (one line)
curl -fsSL https://raw.githubusercontent.com/danilobrando/susurro/main/install.sh | bash
Or with pipx:
pipx install susurro
susurro
After install, hold the right Option key (⌥) to dictate. The first launch downloads Whisper + Llama weights (~5 GB total, one-time) and triggers three macOS permission prompts (Microphone, Accessibility, Input Monitoring).
Why Susurro
- Fully offline. Audio never leaves your machine. No telemetry, no analytics, no cloud calls during normal use.
- WisprFlow-grade smart formatting. An LLM polishes the raw transcript: ordinals become numbered lists, fillers like "um/eh/o sea" get removed, self-corrections ("Pedro, eh, Pablo digo") collapse to the final intent.
- Auditable. Every (raw → polished) edit is logged locally to
~/.susurro/polish.jsonl. Nothing is hidden. - MIT licensed. Read the code, fork it, redistribute it.
If you want zero local RAM + lower latency at the cost of cloud transcription, Susurro Pro is a paid hosted variant that extends this package.
Requirements
- Apple Silicon Mac (M1 or later). MLX doesn't support Intel.
- macOS 13+ recommended (tested on 26).
- Python 3.10+.
- ~5 GB free disk (one-time model download).
- ~3 GB free RAM while running.
Usage
- Click into any text field.
- Hold the right Option key (⌥) and speak.
- Release. After ~1.5 s, the polished transcript pastes at the cursor via Cmd+V.
While recording, a small dark waveform pill appears near the bottom of the active screen, with 16 white bars rippling to your voice. Toggle off via the Show waveform indicator menu item.
Menu bar icon reflects state:
| Icon | Meaning |
|---|---|
| 🎙 idle | Ready. Hold the hotkey to record. |
| 🔴 recording | Listening. Release to transcribe. |
| ⏳ processing | Transcribing + polishing on-device. |
Smart formatting
The polish step turns raw dictation into structured text. Three modes (switchable from the menu):
- Off — paste raw STT output unchanged.
- Rules only — regex cleanup: filler removal (
eh,mmm,o sea sí,um,uh), whitespace normalization. <5 ms. - Smart (LLM) — rules + local Llama 3.2 3B polish, but only when triggers fire (ordinal markers, backtrack phrases, long-form input). Otherwise stays rules-only to keep latency low.
Example (smart mode):
Raw: "Vamos a seguir tres pasos. Primero, reinicia. Segundo, vuelve a registrarte. Tercero, envía un correo."
Polished:
Vamos a seguir tres pasos.
1. Reinicia
2. Vuelve a registrarte
3. Envía un correo
Configuration
Edit susurro/config.py:
STT_BACKEND—local(default). Extension packages can register more.POLISH_MODE—smart(default),rules, oroff.LOCAL_STT_MODEL—whisper-large-v3-turbo(default), orwhisper-large-v3-mlxfor max accuracy.LOCAL_POLISH_MODEL—Llama-3.2-3B-Instruct-4bit(default), or any mlx-community 3B-class model.HOTKEY—alt_r(default). Any pynputKeyname:alt_l,ctrl_r,f19, etc.LANGUAGE—Nonefor auto-detect, or pin to"es"/"en"to save ~100 ms per request.INPUT_DEVICE— pick a specific mic. Runpython -m sounddeviceto list devices.
Permissions
macOS will prompt for three permissions the first time you run Susurro:
- Microphone — to capture your voice.
- Accessibility — to paste the transcript into the focused app.
- Input Monitoring — to listen for the global hotkey.
After granting any of these, fully quit and relaunch your terminal for the new permission to take effect. The menu bar has direct links to each pane.
Architecture
audio (sounddevice → 16kHz mono float32)
→ MLX Whisper (whisper-large-v3-turbo, on-device)
→ raw text
→ Polisher
├ Tier 1: regex rules (filler removal, whitespace)
├ Tier 2: trigger check (ordinals / backtrack / long-form)
└ Tier 3: MLX-LM polish (Llama 3.2 3B Instruct, on-device)
→ polished text
→ clipboard write + Cmd+V into focused app
Source layout — under ~1500 lines of Python total:
susurro/
config.py # all tunables
audio.py # mic capture + peak_level for indicator
hotkey.py # pynput global hotkey
typer.py # clipboard / keystroke insertion
indicator.py # floating waveform pill (PyObjC)
permissions.py # System Settings deep links
app.py # rumps menu bar + main loop (subclassable)
backends/
base.py # protocols (Transcriber, PolishLLM)
local_mlx.py # local Whisper via MLX
local_mlx_lm.py # local polish LLM via mlx-lm
__init__.py # factories + extension registration
polish/
__init__.py # Polisher orchestrator
rules.py # regex cleanup
triggers.py # decides if LLM should fire
prompt.py # system prompt + few-shot examples
icons/ # template PNGs for menu bar
Extending Susurro
External packages can register additional backends without modifying this code:
# in your_extension/__init__.py
from susurro.backends import register_transcriber, register_polish_llm
class MyCloudSTT:
name = "mycloud"
def warmup(self): ...
def transcribe(self, audio): ...
register_transcriber("mycloud", lambda: MyCloudSTT())
Then set STT_BACKEND = "mycloud" in susurro/config.py or via environment.
Troubleshooting
- Menu bar icon invisible — emoji-only menu bar items can be hidden on MacBooks with a notch. This release ships a real template PNG, which fixes it for most users.
- "This process is not trusted" — Accessibility permission isn't granted. Use the Open Accessibility Settings… menu item, then fully restart the terminal.
- Hotkey doesn't trigger — Input Monitoring permission is missing.
- Silent recordings / empty transcript — Microphone permission is missing, or
INPUT_DEVICEis pointing at the wrong device. - First transcription is slow — the model is still warming up. Wait until the menu shows Status: idle before the first real dictation.
Logs land in ~/.susurro/susurro.log; polish events in ~/.susurro/polish.jsonl.
Contributing
See CONTRIBUTING.md. PRs welcome; please keep the package under ~1500 lines.
Security
See SECURITY.md. Report vulnerabilities privately to the maintainer.
Maintainer
Built and maintained by Danny Bravo (dannybravo@gmail.com). Product strategist, AI ecosystem builder, educator — based in Bogotá.
License
MIT © 2026 Danny Bravo.
Credits
- ml-explore/mlx and mlx-examples/whisper — Apple's MLX framework and the MLX Whisper port.
- OpenAI Whisper — the model.
- Meta Llama 3.2 — the polish LLM.
- rumps, pynput, sounddevice — Python ↔ macOS glue.
- WisprFlow and SuperWhisper — the product UX this clones.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file susurro-0.4.1.tar.gz.
File metadata
- Download URL: susurro-0.4.1.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7d8cbd72728efbfd1ddd6d02b6fbecc2c389c4d915824217cff9ca54ba0af93
|
|
| MD5 |
4af368f0aace0364b6160cdf1e64e442
|
|
| BLAKE2b-256 |
a4d8ee9fc4237fec8ada64c00ebfa990f517dcb1cf8eb8dc717051976bad5379
|
File details
Details for the file susurro-0.4.1-py3-none-any.whl.
File metadata
- Download URL: susurro-0.4.1-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e090dab90f97d35031501084b2d223e0b54e8ad09ba94d70b2ba76814facf98
|
|
| MD5 |
ee1889e0a4a29c4930399ec059140fb1
|
|
| BLAKE2b-256 |
0d88a7687574c4ae6d0ac871416af04f45140164fd88ae3397ca707dfc4fe92a
|