Dr Jim Kennedy · Applied Leverage
WhisperBar
Free, private, offline Mac dictation. One hotkey. Works in any app. 170 lines of Python — built with Claude from a spec.
This page is the blueprint. Below is the full PRD that Claude used to build WhisperBar — reverse-engineered from the finished app so every requirement is real, nothing is aspirational, and there are zero TBDs.
The code is on GitHub. Take either, or both, and build your own version.
The one sentence the whole build hangs on
WhisperBar is a signal chain: sound goes in one end, and text comes out the other.
⌥Space → Mic → Buffer → Whisper → Clipboard → ⌘V → Cursor
Everything else is wrapping around that line — a menu-bar icon to show you what's happening, and a hotkey to set it off. If you understand the chain, you already understand the build plan.
Decision log
Every non-obvious choice, with the trade-off that made it. A good spec doesn't just say what — it says why. This is the part most build videos skip.
- UI style
Menu-bar app, not a floating window
Zero footprint, always-on. Cost: the icon state has to carry all the feedback.
- Trigger model
Toggle — press once to start, once to stop
Allows long dictation without holding a key. Cost: you must remember you're recording — the 🔴 mitigates it.
- Text insertion
Clipboard + simulated ⌘V
Works in literally every app via the OS paste path. Cost: clobbers the clipboard; needs Accessibility permission.
- Transcription
Local Whisper, on-device
Private, offline, no API cost or key. Cost: uses local CPU.
- Default model
base
Best everyday speed/accuracy balance — feels near-instant. small+ are more accurate but add seconds.
- Precision flag
fp16=False in model.transcribe()
Forces CPU-friendly fp32 for Apple Silicon. Avoids fp16-on-CPU warnings without needing a GPU.
- Paste timing
150ms sleep before ⌘V
Gives focus time to return to the previous app before pasting. Tunable upward on slower machines.
- Language
Hard-pinned to English
Skips auto-detect for speed and consistency. Trade-off: English only as shipped — easy to remove.
- Persistence
launchd agent + launch.sh wrapper
Always-on with no terminal. Cost: a small piece of macOS plumbing outside the Python app.
- Config
Three settings in config.py, not hardcoded
Things you want to change are in one obvious place. That's the difference between a script and a tool.
What you need
8 Python packages
Install once with pip install. They map directly to stations in the signal chain.
sounddeviceCaptures audio from the microphonenumpyHolds sound frames as numbersscipyWrites the frame buffer to a WAV fileopenai-whisperDoes the transcription (on-device)pyperclipCopies the transcript to the clipboardpyautoguiSimulates the ⌘V pasterumpsCreates the menu-bar icon and menupynputListens for the global hotkey
Plus one non-Python install: ffmpeg via Homebrew — Whisper's audio decoder.
3 macOS permissions
System Settings → Privacy & Security. Grant all three before first run.
- Microphone — Audio capture
- Input Monitoring — Global hotkey detection from any app
- Accessibility — Simulated ⌘V paste
The build: 5 stations
Built in signal-chain order — each station is independently testable and adds one link to the chain.
- 1Audio capture — sounddevice fires a callback continuously — each chunk drops into a buffer while recording is true. Start recording empties the bucket, flips the flag on, and opens the mic. One station, one job.
- 2Transcribe + paste — Stop the mic, write a temp WAV, hand it to Whisper, copy the text, sleep 150ms, simulate ⌘V, delete the temp file. The finally block runs even if something goes wrong — that's what stops the app getting stuck.
- 3Menu-bar UI — A rumps.App subclass. Three icon states (🎙 idle / 🔴 recording / ⏳ transcribing) do all the UI work. toggle() is the brain — 5 lines, two branches.
- 4Global hotkey — A pynput listener on a daemon thread maintains a set of currently-held keys. When ⌥Space matches, it calls toggle() — from any app, even when WhisperBar isn't in front.
- 5Wire up + auto-start — Load Whisper once at startup (slow — do it once, not per dictation). Start the hotkey listener on a daemon thread. Hand control to app.run(). A launchd agent starts everything at login.
Why the spec is the moat
Most build videos show you the code. The code is the commodity — Claude can write 170 lines of Python in minutes. What it can't do is know that you want toggle instead of hold-to-talk, or that you care about the clipboard gotcha, or that the launchd agent is the detail that turns a weekend script into a daily tool.
That's the 90/500 Method: your judgment writes the spec (your 90), Claude's knowledge of code executes it (the 500). You stay the architect. The PRD above is what your 90 looks like — written down, precise, and shareable.
Take a process from your own domain and try to write a spec for it. You'll understand your own workflow better than you did before you started — and you'll have something Claude can actually build from.
The code is not the important part
WhisperBar is evidence of a larger shift: when execution becomes cheaper, the valuable contribution is choosing the right problem and specifying what good looks like.
Your version does not need to be an app. It could be a service, a workflow, a guide, or simply a better way to do work that matters to you.
— Dr Jim Kennedy
Get The Signal
One useful idea each week about what AI is changing — and how to make your experience count for more.
No spam. One email a week.