Local-first dictation for macOS, triggered by the right ⌘ key. Speak in any app — the text appears at your cursor ~2 seconds later. Run Whisper entirely on your Apple Silicon GPU via MLX, or use your own OpenAI-compatible speech-to-text API key.
- One key, two modes — hold right ⌘ to push-to-talk, tap it to toggle a longer recording. Regular right-⌘ shortcuts (⌘C, ⌘V…) keep working.
- Notch glow — an Apple-Intelligence-style light animation around the MacBook notch shows state: warm colors while recording, cool while transcribing, a green flash when the text lands. On external displays a virtual notch appears top-center.
- Local or cloud STT — run whisper-large-v3-turbo on the GPU, use the built-in Groq preset, or configure another OpenAI-compatible transcription endpoint.
- Custom glossary — domain terms you dictate ("spid dot center" →
spid.center) are fixed two ways: the term list biases Whisper's decoder, and regex rules clean up what still slips through. - Self-learning — optionally, an LLM reviews your recent dictations and teaches the glossary new terms automatically. Works with a fully local LLM (Ollama) or CLI agents (Claude Code, Codex, Pi), or hand the task to any coding agent manually.
- Setup wizard — choose Local MLX or Cloud API, then pick your speech model and learning backend in a native settings window.
- Private by design — Local MLX keeps audio on your Mac. Cloud mode stores the API key in macOS Keychain and sends audio only to the endpoint you configure.
- Apple Silicon Mac (M1 or newer), macOS 14+
- Xcode Command Line Tools (
xcode-select --install) - Python 3.10+
One line:
curl -fsSL https://raw.githubusercontent.com/dojdikkkkkk/WhisperKey/main/install.sh | bashOr manually:
git clone https://github.com/dojdikkkkkk/WhisperKey ~/WhisperKey
cd ~/WhisperKey
server/setup.sh # Python venv + mlx-whisper
./build.sh # builds and installs /Applications/WhisperKey.app
open /Applications/WhisperKey.appOn first launch the setup wizard opens. Choose Local MLX to download a speech model on first use, or Cloud API to avoid downloading model weights.
WhisperKey needs two grants in System Settings → Privacy & Security:
- Microphone — to record you;
- Accessibility — to deliver text into other apps.
Gotchas we learned the hard way:
- After granting Accessibility, relaunch the app — the grant doesn't apply to a running process.
- The right-⌘ hotkey works even without Accessibility (modifier-key monitoring is exempt), so "recording works but no text appears" almost always means the Accessibility grant is missing or stale.
- If you build without a code-signing identity (ad-hoc), macOS revokes the grant on every rebuild.
build.shpicks up any Apple Development certificate automatically — creating a free one in Xcode saves you a lot of pain.
Everything lives in ~/.whisperkey/config.json and in Settings… (menu bar icon):
| Key | Default | Meaning |
|---|---|---|
model |
mlx-community/whisper-large-v3-turbo |
Local MLX Whisper model (HF repo) |
language |
auto |
Whisper language code (ru, en, …), or automatic detection |
warmOnWake |
false |
warm the local model after the Mac wakes; uses extra battery |
transcriptionBackend |
local |
local | openai |
cloudProvider |
groq |
groq | custom |
cloudEndpoint |
Groq transcription URL | Full OpenAI-compatible /audio/transcriptions endpoint |
cloudModel |
whisper-large-v3-turbo |
provider-specific transcription model |
learnBackend |
off |
ollama | claude | codex | pi | agent-manual | off |
ollamaModel |
qwen3:4b |
model for the Ollama backend |
holdThreshold |
0.35 |
seconds separating a tap from a hold |
learnEvery |
20 |
run glossary learning every N dictations |
logTranscripts |
true |
keep a local transcription log (needed for learning) |
The cloud API key is stored in macOS Keychain and is never written to this file.
Change the local model from the terminal:
# pick any repo from https://huggingface.co/mlx-community?search_models=whisper
python3 -c "import json,pathlib; p=pathlib.Path.home()/'.whisperkey/config.json'; \
c=json.loads(p.read_text()); c['model']='mlx-community/whisper-small-mlx'; \
p.write_text(json.dumps(c,indent=2))"
curl -X POST localhost:8737/restart # server reloads with the new modelIn Settings… → Speech-to-text, choose Cloud API:
- Groq pre-fills
https://api.groq.com/openai/v1/audio/transcriptionsandwhisper-large-v3-turbo. Groq's direct API uses that model name without agroq/prefix. - Custom accepts a full OpenAI-compatible transcription endpoint and provider-specific model name, including gateway-style names when the gateway requires them.
Enter the API key and click Save & Use Cloud STT. WhisperKey sends the WAV as standard multipart fields (file, model, and the glossary prompt) with Bearer authentication. Remote endpoints must use HTTPS; HTTP is allowed only for loopback development endpoints. Cloud failures produce a macOS notification and never trigger an automatic local-model fallback.
Cloud mode sends recorded audio and glossary prompt terms to the configured provider. Local transcript logging remains controlled by logTranscripts.
server/glossary.json has two halves:
terms— sent as a decoding prompt to the active transcription backend (first ~150 words win, keep it curated);rules— case-insensitive regex replacements applied to the output.
The file hot-reloads — edit it any time. To seed it from texts you already write, see server/seed_glossary.py --help.
With a learnBackend configured, every 20 dictations (and on Learn from recent dictation in the menu) an LLM compares recent transcriptions against the glossary and appends new terms/rules. The pi backend runs pi -p --no-session --no-tools with your configured Pi provider and model; authenticate Pi before selecting it. The agent-manual backend writes the task to server/learn_request.md instead — hand that file to whatever coding agent you use (see AGENTS.md).
right ⌘ ──▶ WhisperKey.app (Swift, menu bar)
├─ AVAudioRecorder → 16 kHz WAV
├─ POST /transcribe ──▶ transcribe_server.py (Python, localhost:8737)
│ ├─ Local: mlx-whisper on the GPU
│ ├─ Cloud: OpenAI-compatible STT API
│ └─ glossary + history + self-learning
└─ text → focused app (AX API → unicode typing → clipboard fallback)
The app launches and supervises the local gateway. Local mode keeps the model loaded between dictations; cloud mode skips MLX warm-up and does not download model weights.
| Symptom | Cause / fix |
|---|---|
| Recording works, no text appears | Accessibility grant missing/stale — re-grant, relaunch the app |
| Nothing happens on right ⌘ | Another app intercepts it, or the app isn't running (check menu bar) |
| First local dictation very slow | The model is downloading/warming — watch server/server.log |
| Cloud transcription fails | Check the macOS notification and server/server.log; verify the endpoint, model, Keychain API key, account quota, and provider status |
| Empty text from a long recording | Mic recorded silence — check the input device, enable debugLog and look for SILENT RECORDING |
| Every rebuild asks for permissions again | Ad-hoc signing — create a free Apple Development certificate |
- Windows version — tray app + CUDA/CPU Whisper backend (windows/README.md)
- Streaming (real-time) transcription
- Per-app glossaries