Local macOS dictation app:
- Hold
Fnto record - Release
Fnto transcribe - Press
Fn + Spacewhile recording to lock recording mode; pressFnagain to stop and transcribe - Configure provider/cleanup/dictionary in app menu ->
Settings... - Text is pasted into the focused text field
- macOS (Apple Silicon recommended)
uv- Xcode Command Line Tools
- For
whisperkitprovider: WhisperKit local server running (see section below)
cp .env.example .env
./scripts/dictation.sh setupBy default the app is ad-hoc signed, which means macOS treats every rebuild as a new app and re-asks for permissions. To avoid this:
./scripts/codesign-create.shThen add to .env:
DICTATION_CODESIGN_IDENTITY=DictationDevIf you have an Apple Developer account you can use Apple Development
instead of a self-signed certificate.
To remove the certificate later:
./scripts/codesign-remove.sh./scripts/dictation.sh bundle --install
open "$HOME/Applications/Dictation.app"The app automatically starts and manages the Python backend process.
A local auth token file is created at ~/.dictation/asr-token (override with DICTATION_ASR_TOKEN_FILE).
Grant Dictation.app permissions:
- Microphone
- Accessibility
- Input Monitoring (Optional) Choose a specific microphone from the menu bar icon:
Microphone -> <device name>
open "$HOME/Applications/Dictation.app"The backend starts automatically with the app and restarts when you change settings.
git pull
./scripts/dictation.sh setup # if Python deps or model defaults changed
./scripts/dictation.sh bundle --install # if Swift code changed
open "$HOME/Applications/Dictation.app"If you need to reset permissions for any reason:
- Quit app:
pkill -f Dictation || true- Reset TCC entries for your bundle id:
BID="$(rg '^DICTATION_BUNDLE_ID=' .env | cut -d= -f2)"
tccutil reset Accessibility "$BID"
tccutil reset ListenEvent "$BID"
tccutil reset Microphone "$BID"- Rebuild/install and reopen:
./scripts/dictation.sh bundle --install
open "$HOME/Applications/Dictation.app"- Re-enable
Dictation.appin:
- Privacy & Security -> Accessibility
- Privacy & Security -> Input Monitoring
- Privacy & Security -> Microphone
# 1. Quit the app
pkill -f Dictation || true
# 2. Remove the app
rm -rf "$HOME/Applications/Dictation.app"
# 3. Remove permissions macOS stored for the app
BID="$(grep '^DICTATION_BUNDLE_ID=' .env | cut -d= -f2)"
tccutil reset Accessibility "$BID"
tccutil reset ListenEvent "$BID"
tccutil reset Microphone "$BID"
# 4. Remove the signing certificate (if you created one)
./scripts/codesign-remove.sh
# 5. Remove downloaded models and build artifacts
rm -rf models/ dist/ .build/ .venv/ .uv-cache/Most users only need:
DICTATION_BUNDLE_IDDICTATION_CODESIGN_IDENTITYDICTATION_SETTINGS_FILE
Advanced overrides (optional):
DICTATION_DEBUG_MODEDICTATION_PROJECT_ROOTDICTATION_WHISPERKIT_ENDPOINTDICTATION_ASR_TOKEN_FILEDICTATION_CLEANUP_PROVIDER(appleorlmstudio)DICTATION_LMSTUDIO_ENDPOINT(default:http://localhost:1234/v1/responses)DICTATION_LMSTUDIO_MODEL(default:essentialai/rnj-1)DICTATION_LMSTUDIO_TEMPERATURE(default:0.1)DICTATION_LMSTUDIO_MAX_OUTPUT_TOKENS(default:96)
All provider/cleanup/dictionary behavior should be managed in the app Settings... window, including:
- Cleanup provider (
appleorlmstudio) - LM Studio cleanup model
- Cleanup system prompt
- Debug Mode (stops the backend so you can run it manually and see logs in your terminal)
Settings precedence:
- Settings JSON written by the app (
Settings...window) - Process environment variables
.env/.env.local- Built-in defaults
Enable Debug Mode in Settings... to stop the managed backend process.
Then run the backend manually in a terminal to see all logs:
uv run src/main.pyDisable Debug Mode and save to let the app manage the backend again.
To run Qwen transcription through Apple Foundation Models for cleanup:
- Install Apple SDK locally:
uv pip install -e ./.external- In
.env:
DICTATION_CLEANUP_ENABLED=trueWhen enabled, raw transcript text is rewritten to remove filler words, pauses, stutters, and false starts while preserving intended meaning.
If you choose lmstudio as cleanup provider in Settings..., the backend sends cleanup requests to:
http://localhost:1234/v1/responses(default)- model default:
essentialai/rnj-1
You can override endpoint/model with env vars if needed.
Set in .env:
DICTATION_ASR_PROVIDER=qwenDownload model:
./scripts/dictation.sh setupSet in .env:
DICTATION_ASR_PROVIDER=whisperkit
DICTATION_WHISPERKIT_ENDPOINT=http://127.0.0.1:50060/v1/audio/transcriptions
DICTATION_WHISPERKIT_MODEL=large-v3Start WhisperKit local server separately:
whisperkit-cli serve --host 127.0.0.1 --port 50060