Push-to-talk voice dictation, fully offline: faster-whisper STT + local Ollama cleanup
Project description
flow_local
Push-to-talk voice dictation into any app — fully local, fully offline.
Download
| Platform | Get it |
|---|---|
| Windows | Installer (.exe) |
| macOS | .dmg |
| Linux | AppImage or pipx install flow-local |
Grab the latest build from the Releases page.
Installers are unsigned — Windows will show a SmartScreen "unknown publisher"
warning (More info → Run anyway) and macOS needs right-click → Open the first
time (Gatekeeper). Already have Python? pipx install flow-local works
everywhere.
- STT: faster-whisper (
large-v3-turbofor English,Finnish-NLP/whisper-large-finnish-v3-ct2for Finnish) - Cleanup: local Ollama model rewrites the raw transcript — removes fillers, applies self-corrections, fixes punctuation
- Output: pasted into the focused window via clipboard + Ctrl+V / Cmd+V (old clipboard restored)
Platform support
| Platform | Status | Notes |
|---|---|---|
| Windows | ✅ Supported | Primary development platform, tested end-to-end |
| macOS | 🧪 Should work | Grant Accessibility permission (System Settings → Privacy) so global hotkeys work; paste uses Cmd+V |
| Linux | 🧪 Should work | X11 only (pynput can't capture global hotkeys under Wayland); needs PortAudio (libportaudio2) and a clipboard tool (xclip/xsel) |
Global hotkeys use pynput, and autostart, hotkeys, and paste all dispatch per-OS, so the code paths are genuinely cross-platform. Windows is tested end-to-end; macOS/Linux are expected to work but haven't been hardware-tested — reports welcome. CI runs lint + unit tests on all three.
Development
Running from source instead of the installer — for contributing, or a platform without a packaged build yet.
Managed with uv. You also need Ollama for transcript cleanup and command mode — plain dictation still works without it, falling back to the raw transcript.
# 1. Start Ollama and pull the cleanup model (default: qwen2.5:7b)
ollama serve # skip if it already runs as a background service
ollama pull qwen2.5:7b
# 2. Install and run
uv sync
uv run flow-local
No config-file setup required: on first run the app creates a config.json
(and glossary.json) for you, seeded from the committed *.example.json
files. It's written to your per-user config directory (%APPDATA%\flow-local\
on Windows, ~/Library/Application Support/flow-local/ on macOS,
$XDG_CONFIG_HOME/flow-local/ on Linux) — edit it there, or through the
tray's Settings UI. If you're working from a repo checkout and want your
personal config to live in the repo instead, drop a config.json at the repo
root (copy flow_local/config.example.json) and it takes priority. Either
way config.json/glossary.json are gitignored. The first run also
downloads the Whisper STT model (~1.6 GB).
Dev tooling:
uv sync --dev
uv run ruff check . # lint
uv run ruff format . # format
uv run pytest # unit tests (no mic/GPU/Ollama needed)
uv run python smoke_test.py # loads Whisper + hits Ollama (needs Ollama; no mic)
uv run python -m flow_local.settings_server # open the web-based settings UI
CI (GitHub Actions, .github/workflows/ci.yml) runs lint + tests on every push
and pull request, across Ubuntu, macOS, and Windows.
Why no Dockerfile: the app is a desktop tool that needs the host microphone, global keyboard hooks, the system tray, the clipboard, and the GPU — none of which a container can reach sensibly. The Ollama dependency is already an external service; if a server deployment is ever needed, the right split is a containerized transcription API + a thin native client.
Releasing
- Bump
versioninpyproject.toml. - Move the
CHANGELOG.md"Unreleased" entries under a new## [X.Y.Z]heading. git tag vX.Y.Z && git push origin vX.Y.Z— this triggers.github/workflows/release.yml, which builds the Windows/macOS/Linux installers and opens a draft GitHub Release with them attached.- Review the draft, then publish it by hand. Nothing goes live from a tag push alone.
- The PyPI upload (
publish-pypi) is gated on thepypiGitHub environment and waits for an approval, because a PyPI version is permanent — it can be yanked but never reused or truly withdrawn. Approve it once the artifacts look right. It authenticates via PyPI trusted publishing (OIDC), so there is no API token to rotate.
Hotkeys (config.json)
| Keys | Action |
|---|---|
hold Ctrl+Space |
dictate, release to transcribe & paste |
hold Ctrl+Alt+Space |
command mode: select text, hold, speak an instruction ("make this formal") — selection is replaced with the result |
hold Ctrl+Alt+C |
compose mode: speak a brief ("email to john, meeting moved to thursday") — a draft is copied to the clipboard, never auto-pasted |
hold Ctrl+Alt+O |
open mode: speak a file you want opened ("the spreadsheet I was messing with yesterday") — a clear match opens directly, an ambiguous one lists numbered candidates (hold again and say the number) |
Ctrl+Shift+Space |
toggle English / Finnish |
Ctrl+Alt+R |
toggle raw mode (skip LLM cleanup) |
Edit config.json to change hotkeys, models, or the personal dictionary
(words fed to Whisper as spelling hints). Open mode is Windows-only for now:
it matches against the Windows Recent-files list plus the directories in
open_roots, and only opens documents/media (never executables or scripts).
Tray menu
Right-click the tray icon (on Windows, bottom-right taskbar, maybe behind the
^ chevron):
- Settings — opens the web-based settings UI
- Language — toggle English / Finnish
- Wakeup Beep — toggle the sound played when recording starts
- Raw mode — toggle the LLM cleanup pass
- Start on login — checkbox; the label and mechanism are per-OS (registry
Run key on Windows, a LaunchAgent on macOS, an XDG
.desktopentry on Linux). Hidden on unsupported platforms. - Check for updates — opens the GitHub Releases page in your browser
- Quit
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flow_local-0.1.0.tar.gz.
File metadata
- Download URL: flow_local-0.1.0.tar.gz
- Upload date:
- Size: 277.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c7c351e6ab2d891db8c3d013410c94ed5ac514e0d26f64e7d33343c09b8d7f5
|
|
| MD5 |
596043ad01821ef4bdf429240c98358d
|
|
| BLAKE2b-256 |
65f80b02d1ec396994f1a18bb36117e66d40467847b13c54a45c89ffd0aa860f
|
Provenance
The following attestation bundles were made for flow_local-0.1.0.tar.gz:
Publisher:
release.yml on Tonzium/TyprFlow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flow_local-0.1.0.tar.gz -
Subject digest:
4c7c351e6ab2d891db8c3d013410c94ed5ac514e0d26f64e7d33343c09b8d7f5 - Sigstore transparency entry: 2189262123
- Sigstore integration time:
-
Permalink:
Tonzium/TyprFlow@ab1645641cc9767e238093ccadced944f8998050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Tonzium
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ab1645641cc9767e238093ccadced944f8998050 -
Trigger Event:
push
-
Statement type:
File details
Details for the file flow_local-0.1.0-py3-none-any.whl.
File metadata
- Download URL: flow_local-0.1.0-py3-none-any.whl
- Upload date:
- Size: 43.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c22958d34946ea0753c97ae75f21fecf0b42872a1654fa986dcc7d4e5c014783
|
|
| MD5 |
e4de2efa1599673cffcf7cf3612ee2fd
|
|
| BLAKE2b-256 |
da71352c295ac83571f7159d6ca5566a8785750be9dc76e8182db768fb45f75a
|
Provenance
The following attestation bundles were made for flow_local-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Tonzium/TyprFlow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flow_local-0.1.0-py3-none-any.whl -
Subject digest:
c22958d34946ea0753c97ae75f21fecf0b42872a1654fa986dcc7d4e5c014783 - Sigstore transparency entry: 2189262171
- Sigstore integration time:
-
Permalink:
Tonzium/TyprFlow@ab1645641cc9767e238093ccadced944f8998050 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Tonzium
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ab1645641cc9767e238093ccadced944f8998050 -
Trigger Event:
push
-
Statement type: