Push-to-talk dictation for macOS. Hold Fn, speak, release. Text is typed at the cursor. Runs entirely locally on Apple Silicon using Kyutai STT (MLX). Lives in the menu bar, updates itself from your Forgejo repo on demand.
The .app is unsigned, so macOS blocks it the first time. To allow it:
- Double-click
Whisp.appand dismiss the warning. - Open
System Settings->Privacy & Security. - Scroll to the bottom. Next to "Whisp was blocked to protect your Mac" click Open Anyway.
- Authenticate, then click Open in the confirmation dialog.
One-liner alternative from Terminal:
xattr -dr com.apple.quarantine /Applications/Whisp.appAfter this, double-click works normally. In-app updates (Check for updates) don't re-trigger this prompt.
- Apple Silicon Mac (M-series), macOS 13+
uv(brew install uv)- ~3 GB free for model weights on first run
uv run --script whisp.pyFirst run pulls deps and downloads ~2 GB of weights. Subsequent launches are ~5 seconds. You'll see a menu bar item with a dot. Hold Fn to dictate.
After CI builds and publishes a release (see below):
- Grab
Whisp.app.zipfromhttps://git.retardhub.com/ahtavarasmus/whisp/releases/tag/latest - Unzip, drag
Whisp.appto/Applications(or~/Applications) - Open it past Gatekeeper — see First open above
- Grant Microphone and Accessibility in System Settings -> Privacy & Security
- Click the menu bar dot -> Check for updates any time to pull the
latest
whisp.pyfrommainand restart in place
whisp.py # daemon (single file, uv PEP 723 deps)
app/
Info.plist # bundle metadata template
launcher.sh # bash entry point inside the .app
scripts/
build_app.sh # assembles Whisp.app from sources
publish_release.sh # uploads the .zip to a Forgejo release
.forgejo/workflows/build.yaml # CI: build on push to main
com.ahtava.whisp.plist # optional LaunchAgent (run-on-login)
whisp.pycarries a__VERSION__stamp thatbuild_app.shrewrites with the git commit SHA.- The .app launcher copies the bundled script to
~/Library/Application Support/Whisp/whisp.pyon first launch, then always runs that copy. Self-updates overwrite the copy. - Check for updates in the menu bar hits Forgejo's
/api/v1/repos/.../commitsendpoint, compares to__VERSION__, and if newer, downloadswhisp.pyfromraw/branch/main/whisp.py, writes it, andos.execvs the same Python on the new file. - The .app bundle itself rarely needs replacing - only when you change
Info.plist,launcher.sh, or want a clean install.
cd /Users/ahtavarasmus/Developer/whips
git init
git add .
git commit -m "initial commit"
git remote add origin https://git.retardhub.com/ahtavarasmus/whisp.git
git push -u origin mainThen in Forgejo:
- Repo Settings -> Actions -> enable for this repo
- Repo Settings -> Secrets -> add
FORGEJO_TOKEN(a personal access token withrepowrite scope - needed to create releases) - Make sure a runner is online (Site Admin -> Actions -> Runners)
The workflow runs ubuntu-latest. The .app is just a directory tree plus
a bash launcher, so no macOS runner is needed for the build.
bash scripts/build_app.sh
open dist/Whisp.app| Flag | What |
|---|---|
--hf-repo REPO |
Different STT model. Default kyutai/stt-1b-en_fr-mlx (1B, EN+FR, ~0.5s delay). Try kyutai/stt-2.6b-en-mlx for higher accuracy at ~2.5s delay. |
--device IDX |
Input device index. python -m sounddevice lists them. |
--version |
Print the build's __VERSION__ stamp and exit. |
-v |
Echo recognised text to stderr as it streams. |
- macOS built-in Press Fn to Dictate fights whisp for the key. Disable
in
System Settings -> Keyboard -> Dictation. - Memory at idle: ~2 GB (bf16 weights resident).
- The .app is unsigned. See First open for the Gatekeeper unblock step.
- Permissions follow the launching app. The .app gets them tied to the
uv-vendored Python binary, which moves between builds, so granting
permission once may not survive a
uvcache wipe. If that happens, re-grant after the prompt.
- Kyutai STT - the model.
- delayed-streams-modeling - reference MLX implementation.