Metadata-Version: 2.4
Name: flow-local
Version: 0.1.0
Summary: Push-to-talk voice dictation, fully offline: faster-whisper STT + local Ollama cleanup
Author: Toni Kiuru
License: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: faster-whisper>=1.2
Requires-Dist: huggingface-hub>=0.24
Requires-Dist: numpy>=1.26
Requires-Dist: nvidia-cublas-cu12; sys_platform == 'linux'
Requires-Dist: nvidia-cudnn-cu12; sys_platform == 'linux'
Requires-Dist: pillow>=10
Requires-Dist: platformdirs>=4
Requires-Dist: pynput>=1.7
Requires-Dist: pyperclip>=1.8
Requires-Dist: pystray>=0.19
Requires-Dist: rapidfuzz>=3.5.0
Requires-Dist: requests>=2.31
Requires-Dist: sounddevice>=0.5
Description-Content-Type: text/markdown

# flow_local

[![CI](https://github.com/Tonzium/TyprFlow/actions/workflows/ci.yml/badge.svg)](https://github.com/Tonzium/TyprFlow/actions/workflows/ci.yml)

Push-to-talk voice dictation into any app — fully local, fully offline.

<!-- TODO: replace with a ~10s GIF of a dictate → paste round trip.
     Suggested path: docs/dictate-demo.gif -->
<!-- ![flow_local demo](docs/dictate-demo.gif) -->

## Download

| Platform | Get it |
|---|---|
| Windows | [Installer (.exe)](https://github.com/Tonzium/TyprFlow/releases/latest) |
| macOS | [.dmg](https://github.com/Tonzium/TyprFlow/releases/latest) |
| Linux | [AppImage](https://github.com/Tonzium/TyprFlow/releases/latest) or `pipx install flow-local` |

Grab the latest build from the [Releases page](https://github.com/Tonzium/TyprFlow/releases).
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-turbo` for English, `Finnish-NLP/whisper-large-finnish-v3-ct2` for 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](https://pynput.readthedocs.io/), 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](https://docs.astral.sh/uv/). You also need
[Ollama](https://ollama.com/) 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

1. Bump `version` in `pyproject.toml`.
2. Move the `CHANGELOG.md` "Unreleased" entries under a new `## [X.Y.Z]` heading.
3. `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.
4. Review the draft, then publish it by hand. Nothing goes live from a tag
   push alone.
5. The PyPI upload (`publish-pypi`) is gated on the `pypi` GitHub 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 `.desktop` entry on Linux).
  Hidden on unsupported platforms.
- **Check for updates** — opens the GitHub Releases page in your browser
- **Quit**
