🌐 Sprache / Language: English · Deutsch
Local dictation app for macOS: Global hotkey, speech-to-text with Whisper, and text cleanup with a local language model. The finished text lands right at your cursor. Recordings never leave your machine.
The complete interface — menu, settings, history, overlays, model download, and user-facing errors — is available in English and German. macOS chooses the language from the system or per-app language preferences. CLI diagnostics are localized as well.
![]() |
![]() |
| Menu bar: start/stop, history, settings, quit | History: every dictation (raw + cleaned), copy with one click |
The signed app package runs on Apple Silicon Macs with macOS 13 or later. The current local version of Ollama requires macOS 14 or later. Local speech recognition still works without Ollama; Stille Post then pastes Whisper's raw text.
If Homebrew is not installed yet, follow the short instructions at brew.sh.
brew install whisper-cppThis installs the local speech-recognition engine. Stille Post downloads its speech model separately on first launch.
Download Ollama for macOS, move it to
/Applications, and open it once. Ollama only cleans up the finished text; audio is
never sent to it.
ollama pull gemma4:e4b-it-qatThe default model uses about 6 GB of disk space and is suitable for most Macs with 16–32 GB of memory. It won a comparison of local models on fidelity and speed (see docs/cleanup-model-benchmark.md); for this narrow task a small, disciplined model cleans better than the large ones.
- Download the current DMG from GitHub Releases.
- Open the DMG and drag Stille Post to Applications.
- Start Stille Post. Accept the
large-v3-turbodownload (~1.6 GB), then grant Microphone and Accessibility access when macOS asks.
Now place the cursor in a text field, press ⌘⌥D, speak, and press ⌘⌥D again. The text is pasted at the cursor.
Stille Post checks for signed updates at most once per day and never installs one without permission. Use “Check for Updates …” in the menu to check immediately; after confirmation, Sparkle handles download, replacement, and relaunch.
Why are there two model downloads?
large-v3-turborecognizes speech and produces the raw transcript.gemma4:e4b-it-qatthen removes filler words, false starts, and repetitions. Both run locally.
- Fast completion: Whisper transcribes while recording is still in progress.
- Cleanup without rewriting: The language model removes speech artifacts but is instructed not to shorten, paraphrase, or answer the dictation.
- Safe fallback: If the cleaned result looks implausible or the service is unavailable, Stille Post uses Whisper's raw transcript.
- Local and transparent: Audio always stays on the Mac. History shows the raw text, final text, and cleanup endpoint used.
- Visible recording state: An overlay, live microphone meter, menu bar icon, and distinct sounds show what is happening.
- No audio buildup: Successful recordings are deleted immediately; failed ones remain available for retranscription.
A voice-activity detector splits the recording at natural pauses. Each completed segment is transcribed immediately while recording continues. After stopping, the language model cleans up the complete transcript exactly once, preserving context and sentence boundaries. The cleanup model is warmed when recording begins.
- The system prompt only permits removal of filler words, false starts, stutters, and repetitions — no paraphrasing, summarizing, or answering.
- A word-fidelity check aligns the raw and cleaned words and rejects any adding, replacing, translating, or reordering. Allowed are deletions (filler words), punctuation, and capitalization, plus tightly bounded repairs of Whisper artifacts (compounds split at speech pauses, a single mishearing, a short inflection ending); model and version identifiers stay untouchable, as do negations and other meaning-critical function words (German "kein" never becomes "ein"). The same applies to dropping them: if cleanup swallows a negation, the affected clause reverts to what was dictated. The length check remains active as an additional safeguard.
- A failed cleanup also falls back to the raw text.
- The overlay and history identify backup endpoints and raw-text fallbacks.
Which local models clean most faithfully, and how the check is designed, is documented with reproducible measurements in docs/cleanup-model-benchmark.md.
Two, deliberately — a long model list would only shift the decision onto you:
| Model | Size | When |
|---|---|---|
large-v3-turbo |
~1.6 GB | Default. Best mix of quality and speed. |
large-v3 |
~3.1 GB | Only if foreign words and jargon have to land better. Slower. |
Smaller models are not offered: worse recognition is not a trade anyone wants, and turbo is not that big.
All settings are available as a dialog in the menu bar menu under “Settings …”.
Underneath lives ~/Library/Application Support/StillePost/config.json
(created on first launch, menu item “Open configuration file”). The file stays
hand-editable.
The dialog is organized into four tabs, so the common cases never require editing JSON (the cleanup model shown is the default):
![]() |
![]() |
| General — recording hotkey & overlay | Cleanup — provider, model, context, fallbacks |
![]() |
![]() |
| Speech recognition — language & Whisper server | Recording — microphone, silence detection & auto-stop |
The most important switches:
| Section | Field | Meaning |
|---|---|---|
hotkey |
keyCode, modifiers |
recording hotkey (default ⌘⌥D). In the General tab, “Record hotkey” records the combination you press — no need to look up key codes |
audio |
inputDeviceUID, inputDeviceName |
selected microphone; an empty UID follows the macOS system default. The UI stores the stable CoreAudio UID; the name is only used for a readable display |
whisper |
language |
"auto" or fixed, e.g. "en". Recommendation: Pin it. With auto, Whisper guesses the language per speech segment and silently translates on misdetection |
cleanup |
enabled |
cleanup on/off |
cleanup |
provider |
"ollama" (local/own network) or "openai" (cloud, text only) |
cleanup |
model |
Ollama model name |
cleanup |
ollamaURL |
Ollama endpoint; may also be another machine on your own network |
cleanup |
keepAlive |
how long Ollama keeps the model in memory after a dictation: "2h" (default), "20m", "0" (unload at once) or "-1" (never unload). Sent with every request — nothing to configure in Ollama |
cleanup.remote |
baseURL, model |
OpenAI-compatible provider |
cleanup |
fallbacks |
backup endpoints tried when the primary does not respond (see below) |
cleanup |
dictionary |
personal glossary of preferred spellings (e.g. "RAG"). Passed to the cleanup model, and the word-fidelity check accepts sound-alike corrections to exactly these terms ("Rack" → "RAG") |
vad |
autoStopAfterSilenceSec |
absence auto-stop (0 = off) |
ui |
overlayPosition |
"mouse" or "bottomCenter" |
The Recording tab can select any input device reported by macOS directly. System Default continues to follow System Settings → Sound → Input. If an explicitly selected device disappears, Stille Post does not silently record from a different microphone; it reports the problem when recording starts.
An iPhone appears as a regular input device through Apple’s Continuity Camera: lock the iPhone and place it stably near the Mac, then refresh the device list in Stille Post. A USB connection is more reliable than wireless for long dictations.
Setting up cloud cleanup (example; works with any OpenAI-compatible provider):
The API key does not go into the file. Store it in the keychain
(stillepost-cli set-cleanup-key) or in the STILLEPOST_CLEANUP_API_KEY
environment variable.
On a RAM-constrained laptop it pays off to hand cleanup to another machine on your own
network — not for a "bigger" model (for this narrow task a small, disciplined model
actually cleans better, see the benchmark), but so the laptop keeps its ~6 GB of RAM
free. The same gemma4:e4b-it-qat runs there; the same model stays local as fallback.
On the serving machine (it does not need Stille Post):
- Pull the model:
ollama pull gemma4:e4b-it-qat - Make Ollama listen on the network interface, not just on localhost: set
OLLAMA_HOST=0.0.0.0, or use "Expose Ollama to the network" in the Ollama app. - Check from another Mac:
curl http://<ip-of-the-strong-mac>:11434/api/version
That is all — model, context size and keep-alive are sent by Stille Post with every request, so there is nothing else to set up in Ollama.
On every Mac you dictate on: install Stille Post, then open Settings → Cleanup
and enter the serving machine's endpoint (http://<ip>:11434), the model and, if you
like, a keep-alive. stillepost-cli doctor checks the whole chain and tells you
whether the endpoint answers and the model is present.
How long the model stays loaded is the “Keep loaded” (keep_alive) dropdown in
the same tab.
The default of 2 hours is a compromise: dictate again within that window and the
model answers instantly; after that Ollama frees the RAM by itself. "Permanently"
never lets go — the right choice if you have RAM to spare. The wait rarely shows
either way, because Stille Post starts warming the model the moment you press the
hotkey: it loads while you are still speaking.
fallbacks lists backup endpoints tried in order when the primary does not respond
(probe timeout 2 s; away from your home network, local Ollama takes over almost
without delay):
"cleanup": {
"ollamaURL": "http://192.168.1.50:11434", // another machine on the LAN (primary)
"model": "gemma4:e4b-it-qat",
"keepAlive": "2h", // "-1" = keep loaded forever
"fallbacks": [
{ "provider": "ollama", "ollamaURL": "http://127.0.0.1:11434", "model": "gemma4:e4b-it-qat" },
{ "provider": "openai", "remote": { "baseURL": "https://api.example.com/v1", "model": "model-name" } }
]
}Only transcribed TEXT travels over your network, never audio — speech recognition
always runs on the machine you dictate on. Which endpoint handled a cleanup is shown
by stillepost-cli cleanup and in the history window.
Stille Post can accept dictations from your own devices on the same network: the iPhone records, the Mac transcribes and cleans up, and the finished text lands on the iPhone's clipboard, ready to paste into any app — including a text field in Firefox. A shortcut on the Action button is all it takes; no app and no custom keyboard are involved.
Switch it on under Settings -> General -> "Dictation From Your Own Devices (Home Network)". The same place has the button that copies the access token to the clipboard; the token itself is never displayed. Access is off by default.
curl -s -H "Authorization: Bearer <token>" \
--data-binary @recording.m4a http://<mac-name>.local:8188/v1/dictateThree routes: GET /v1/health, POST /v1/dictate (audio -> finished text, ?raw=1
skips the cleanup) and POST /v1/cleanup (raw text -> cleaned text, for devices
that transcribe on their own).
Privacy and limits. The Mac is only a receiver here: audio is processed on this machine, and speech recognition still runs exclusively on loopback. Access is restricted to private/link-local ranges and to global IPv6 addresses inside a prefix currently used by one of this Mac's active interfaces; every request also requires the token. The connection is unencrypted, though - anyone listening on the same Wi-Fi can read your dictations. It is not meant for use away from home.
Shortcut instructions, measurements, and the decisions behind this are in docs/ios-bridge.md (German).
The entire pipeline is available through the CLI with the same logic and configuration:
stillepost-cli doctor # check dependencies (exit code 0 = ready)
stillepost-cli install-model # fetch the Whisper model (resumable)
stillepost-cli transcribe file.wav # WAV -> cleaned text on stdout
stillepost-cli transcribe file.wav --raw
stillepost-cli cleanup "raw text" # cleanup only ("-" reads stdin)
stillepost-cli history list --json
stillepost-cli history clear
stillepost-cli bridge status # network access: state, address, limits
stillepost-cli bridge token # copy the access token to the clipboard
stillepost-cli bridge serve # run the network access in the foreground
stillepost-cli set-cleanup-key # store API key from stdin in the keychainDiagnostics go to stderr, results to stdout, and failures exit non-zero. The CLI is inside the app bundle and is not added to your PATH automatically:
sudo ln -sf /Applications/StillePost.app/Contents/MacOS/stillepost-cli \
/usr/local/bin/stillepost-cliWithout the link, use
/Applications/StillePost.app/Contents/MacOS/stillepost-cli doctor. Set
STILLEPOST_CONFIG to use an alternative configuration file.
git clone https://github.com/DanielMuellerIR/stille_post.git
cd stille_post
./build.sh # build the app bundle into build/
./install.sh # build, notarize, verify, then install atomically
./release.sh # build, notarize, package and verify the release disk image
swift test # unit tests (VAD, WAV, sanity check, history …)
scripts/e2e-test.sh # end to end: say voice -> Whisper -> cleanup -> assertionsinstall.sh and release.sh need a notarytool keychain profile, taken from
NOTARY_PROFILE or from git config stillePost.notaryProfile in your clone.
release.sh additionally needs the expected Developer Team ID, from
DEVELOPER_TEAM_ID or git config stillePost.teamId. The release check
compares the signature against that value on purpose — reading it from the very
app it is about to verify would let a build signed with the wrong identity pass.
scripts/build-icon.sh turns the SVG sources in Resources/icon/ into the
finished Resources/AppIcon.icns. The result is committed, and build.sh only
copies it — so you only need this script after changing the artwork; commit the
new .icns along with it. Requires rsvg-convert (brew install librsvg).
The Sparkle feed and release automation are documented in
docs/sparkle-release.md.
Early, but usable. Planned: A comparison benchmark (quality and latency) against other local dictation tools and cloud services.





