parlando 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ name: Release to PyPI
2
+
3
+ # Publishes ONLY when a GitHub Release is published, and only after a
4
+ # Trusted Publisher is configured on PyPI for this repo (Settings on
5
+ # pypi.org -> your project -> Publishing -> add GitHub publisher with this
6
+ # repo, workflow file "release.yml" and environment "pypi"). Until both
7
+ # happen, this workflow is inert. No API tokens are stored anywhere:
8
+ # authentication uses GitHub's OIDC identity (PyPA's recommended setup).
9
+
10
+ on:
11
+ release:
12
+ types: [published]
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: astral-sh/setup-uv@v5
21
+
22
+ - name: Build wheel and sdist
23
+ run: uv build
24
+
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ environment:
34
+ name: pypi
35
+ permissions:
36
+ id-token: write # OIDC: this is what replaces API tokens
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+
43
+ - name: Publish to PyPI (trusted publishing)
44
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,35 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ unit:
10
+ runs-on: macos-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+
18
+ # Heavy dependencies (mlx, sounddevice, pyobjc...) are imported lazily
19
+ # inside parlando.py, so unit tests only need numpy + pytest.
20
+ - name: Install test deps
21
+ run: pip install numpy pytest
22
+
23
+ - name: Run unit tests
24
+ run: pytest tests -q
25
+
26
+ build:
27
+ runs-on: macos-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - uses: astral-sh/setup-uv@v5
32
+
33
+ # Validate packaging metadata and the wheel build (nothing published).
34
+ - name: Build wheel and sdist
35
+ run: uv build
@@ -0,0 +1,208 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
208
+ .idea
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0 — 2026-09-09
4
+
5
+ - **Cleanup**: unambiguous vocalized fillers (um, uh, eee, ııı) are removed
6
+ deterministically per language (`--no-cleanup` to keep them); stutter
7
+ runs of 3+ identical words collapse to one.
8
+ - **Polish (`--polish`)**: optional local-LLM cleanup at finalize — drops
9
+ contextual fillers, stutters and false starts, fixes punctuation. Guarded
10
+ so the LLM can only edit the transcript, never answer or extend it
11
+ (falls back to the raw text otherwise). Default model Qwen3-1.7B-4bit
12
+ (10/11 on the built-in benchmark, ~0.4 s); Qwen3-4B-4bit scores 11/11.
13
+ - New `scripts/eval_polish.py`: 11-case cleanup benchmark (fillers,
14
+ stutters, false starts, adversarial answer/injection guards, identity)
15
+ that runs against the real local LLM.
16
+ - Brand assets: custom template menu bar icons (three-bar speech waveform)
17
+ and a README banner, generated by `scripts/make_icons.py`.
18
+
19
+ ## 0.3.1 — 2026-09-08
20
+
21
+ - Restructured into a proper Python package (`src/parlando/`, hatchling,
22
+ `pyproject.toml`) with `parlando` and `parlando-menubar` entry points.
23
+ - `install.sh` now uses `uv tool install` (uninstall with
24
+ `uv tool uninstall parlando`); start-at-login uses the installed command.
25
+ - CI additionally validates the packaging build (`uv build`).
26
+ - PyPI publication intentionally deferred; `uvx parlando` will work once
27
+ published.
28
+
29
+ ## 0.3.0 — 2026-09-08
30
+
31
+ - Project renamed to **parlando**; full internationalization (all code,
32
+ CLI, menu bar, and docs in English).
33
+ - Language-aware voice commands: English set by default, Turkish set with
34
+ `--language Turkish`.
35
+ - `--version` flag; log moved to `~/Library/Logs/parlando.log`.
36
+ - Repository prepared for open-source release (license attribution,
37
+ upstream site assets removed, English README, CI badge).
38
+
39
+ ## 0.2.0 — 2026-09-08
40
+
41
+ - **Record mode (default)**: tap-speak-tap; the whole recording is
42
+ transcribed once and typed in one go. Long recordings split at silence.
43
+ - Global hotkey: single tap of right Option (chord-aware), configurable.
44
+ - Menu bar app (rumps) with start/stop, language switching, Enter mode,
45
+ and `--install-login` LaunchAgent.
46
+ - Quartz CGEvent unicode typing with osascript fallback.
47
+ - Audio-stream watchdog (sleep/device-change recovery); microphone and
48
+ Accessibility permission diagnostics; file logging.
49
+ - Streaming stability: normalized LocalAgreement, stall safety valve,
50
+ asymmetric noise-floor learning (deafness fix), guarded event loop.
51
+ - Optional hybrid Silero VAD (`--silero`); voice commands; 35 unit tests
52
+ and macOS CI.
53
+
54
+ ## 0.1.0 — 2026-09-08
55
+
56
+ - Initial utterance-based dictation engine on top of the dictate.sh
57
+ Qwen3-ASR MLX engine: energy VAD, append-only word commits with
58
+ LocalAgreement-2, hallucination guards, safe peak normalization.
parlando-0.4.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Furkan Can (parlando)
4
+ Copyright (c) 2026 Marc Puig (original dictate.sh ASR engine, stt.py)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.5
2
+ Name: parlando
3
+ Version: 0.4.0
4
+ Summary: Local voice dictation for macOS (Apple Silicon): tap a key, speak, and your words are typed into any app. 100% on-device.
5
+ Project-URL: Homepage, https://github.com/furkanc/parlando
6
+ Project-URL: Issues, https://github.com/furkanc/parlando/issues
7
+ Project-URL: Changelog, https://github.com/furkanc/parlando/blob/main/CHANGELOG.md
8
+ Author: Furkan Can
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: apple-silicon,asr,dictation,macos,mlx,speech-to-text,voice-typing,whisper
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: MacOS X
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: huggingface-hub
20
+ Requires-Dist: mlx-lm>=0.22.0
21
+ Requires-Dist: mlx>=0.22.0
22
+ Requires-Dist: numpy
23
+ Requires-Dist: onnxruntime
24
+ Requires-Dist: pynput
25
+ Requires-Dist: pyobjc-framework-applicationservices
26
+ Requires-Dist: pyobjc-framework-quartz
27
+ Requires-Dist: rich
28
+ Requires-Dist: rumps
29
+ Requires-Dist: setuptools
30
+ Requires-Dist: sounddevice
31
+ Requires-Dist: transformers
32
+ Requires-Dist: webrtcvad
33
+ Description-Content-Type: text/markdown
34
+
35
+ <p align="center">
36
+ <img src="assets/banner.png" alt="parlando — local voice dictation for macOS" width="720">
37
+ </p>
38
+
39
+ **Local voice dictation for macOS (Apple Silicon).** Tap a key, speak, tap
40
+ again — your words are typed into whatever window has focus. 100% on-device:
41
+ audio never leaves your machine.
42
+
43
+ [![Tests](https://github.com/furkanc/parlando/actions/workflows/tests.yml/badge.svg)](https://github.com/furkanc/parlando/actions/workflows/tests.yml)
44
+
45
+ ## Features
46
+
47
+ - **Fully local & offline** — MLX-accelerated Qwen3-ASR on the Apple Neural
48
+ stack; works on a plane, nothing is uploaded, no API keys.
49
+ - **Tap-to-dictate** — single tap of right ⌥ Option starts/stops recording
50
+ (the same pattern superwhisper and Wispr Flow use). No always-on mic.
51
+ - **Types anywhere** — text is delivered as native keyboard events (Quartz
52
+ CGEvent unicode), so it works in any app, editor, terminal, or chat box.
53
+ - **30 languages** — English by default; switch instantly (e.g. `--language
54
+ Turkish`) with no model reload.
55
+ - **Voice commands** — "period", "comma", "question mark", "new line",
56
+ "send" (presses Enter). A Turkish command set activates with
57
+ `--language Turkish`.
58
+ - **Cleanup & polish** — vocalized fillers ("um", "uh", "eee") are removed
59
+ automatically; optional `--polish` runs a small local LLM that drops
60
+ contextual fillers ("you know", "yani"), stutters and false starts, and
61
+ fixes punctuation — guarded so it can only *edit* your words, never
62
+ answer them.
63
+ - **Menu bar app** — <img src="assets/menubar-states.png" alt="menu bar icon states: idle, recording, paused" height="26" align="top"> (idle · recording · paused) with start/stop, language switcher, and start-at-login support.
64
+ - **Self-healing** — audio watchdog survives sleep/wake and device changes;
65
+ inference errors never kill the session.
66
+ - **Streaming mode (optional)** — words appear as you speak, stabilized with
67
+ LocalAgreement-2 and append-only commits (typed text is never retracted).
68
+
69
+ ## Requirements
70
+
71
+ - macOS on Apple Silicon
72
+ - [`uv`](https://docs.astral.sh/uv/) installed
73
+ - ~2 GB disk for the default ASR model (downloaded once from Hugging Face)
74
+
75
+ ## Install
76
+
77
+ One-liner (installs [`uv`](https://docs.astral.sh/uv/) if needed, puts
78
+ `parlando` on your PATH):
79
+
80
+ ```bash
81
+ curl -fsSL https://raw.githubusercontent.com/furkanc/parlando/main/install.sh | sh
82
+ ```
83
+
84
+ Then:
85
+
86
+ ```bash
87
+ parlando # terminal
88
+ parlando-menubar # menu bar app
89
+ ```
90
+
91
+ Click the window you want to type into → tap **right ⌥ Option** → speak →
92
+ tap again. Your words appear at the cursor. The speech model (~1-2 GB)
93
+ downloads once on first run; after that everything is offline.
94
+
95
+ <details>
96
+ <summary>Run from source / uninstall</summary>
97
+
98
+ ```bash
99
+ git clone https://github.com/furkanc/parlando && cd parlando
100
+ uv run parlando # run without installing
101
+ uv tool install . # or install the commands from the checkout
102
+
103
+ # uninstall
104
+ uv tool uninstall parlando
105
+ ```
106
+ </details>
107
+
108
+ ### Permissions (one-time)
109
+
110
+ macOS will ask for two permissions, granted to the app that runs parlando
111
+ (Terminal, iTerm, VS Code, ...):
112
+
113
+ | Permission | Why | Where |
114
+ |------------|-----|-------|
115
+ | Microphone | hear you | Settings → Privacy & Security → Microphone |
116
+ | Accessibility | type keystrokes + global hotkey | Settings → Privacy & Security → Accessibility |
117
+
118
+ parlando detects missing permissions and tells you explicitly instead of
119
+ failing silently.
120
+
121
+ ## Usage
122
+
123
+ ```bash
124
+ parlando --language Turkish # dictate in another language
125
+ parlando --polish # LLM cleanup (fillers, false starts, punctuation)
126
+ parlando --enter # press Enter after each utterance
127
+ parlando --mode stream # live word-by-word streaming
128
+ parlando --pipe # print to stdout (scriptable)
129
+ parlando --hotkey cmd_r # tap right Command instead
130
+ parlando --list-devices # list microphones
131
+ parlando-menubar --install-login # start at login
132
+ ```
133
+
134
+ ### Voice commands
135
+
136
+ | Say (English) | Say (Turkish) | Result |
137
+ |---|---|---|
138
+ | period / comma | nokta / virgül | `.` `,` appended to previous word |
139
+ | question mark | soru işareti | `?` appended |
140
+ | new line / new paragraph | yeni satır / yeni paragraf | line break |
141
+ | send | gönder | presses Enter |
142
+
143
+ Disable with `--no-commands`.
144
+
145
+ ### Models
146
+
147
+ **Speech recognition** (`--model`, MLX Qwen3-ASR — 30 languages):
148
+
149
+ | Model | Size | Notes |
150
+ |---|---|---|
151
+ | `mlx-community/Qwen3-ASR-1.7B-8bit` | ~2 GB | **default** — best accuracy, recommended |
152
+ | `mlx-community/Qwen3-ASR-0.6B-8bit` | ~700 MB | good balance for low-RAM machines |
153
+ | `mlx-community/Qwen3-ASR-0.6B-4bit` | ~400 MB | fastest, lowest accuracy |
154
+ | `mlx-community/Qwen3-ASR-0.6B-bf16` | ~1.3 GB | higher fidelity 0.6B, more RAM |
155
+
156
+ **Polish LLM** (`--polish-model`, used only with `--polish`; scores from
157
+ `scripts/eval_polish.py`, an 11-case cleanup benchmark):
158
+
159
+ | Model | Size | Eval | Latency | Notes |
160
+ |---|---|---|---|---|
161
+ | `mlx-community/Qwen3-1.7B-4bit` | ~1 GB | 10/11 | ~0.4 s | **default** — fast, safe |
162
+ | `mlx-community/Qwen3-4B-4bit` | ~2.3 GB | 11/11 | ~0.9 s | best quality; use on 16 GB+ Macs |
163
+ | `mlx-community/Qwen3-0.6B-4bit` | ~350 MB | — | ~0.2 s | minimal RAM, weakest cleanup |
164
+
165
+ ```bash
166
+ parlando --polish --polish-model mlx-community/Qwen3-4B-4bit
167
+ ```
168
+
169
+ All models download once from Hugging Face and are cached in
170
+ `~/.cache/huggingface`; everything runs on-device.
171
+
172
+ ### The two modes
173
+
174
+ **`record` (default)** — tap, speak freely (pauses are fine), tap again; the
175
+ whole recording is transcribed once and typed in one go. Recordings longer
176
+ than 28 s are split at natural silence points (2 min cap). This mode has no
177
+ VAD guessing, so it is the most robust.
178
+
179
+ **`stream`** — always listening; an energy VAD (hysteresis, adaptive noise
180
+ floor, optional `--silero` hybrid) segments utterances and words appear as
181
+ you speak. More "live", more sensitive to room noise.
182
+
183
+ ## Troubleshooting
184
+
185
+ | Symptom | Fix |
186
+ |---|---|
187
+ | Nothing typed, "only silence" warning | Grant microphone permission, restart |
188
+ | Nothing typed, no warning | Grant Accessibility permission (see startup warning) |
189
+ | Stalls after sleep | The watchdog reopens the stream within ~5 s automatically |
190
+ | Ghost text while silent (stream mode) | `--energy-floor 0.008` or `--silero` |
191
+ | Missing soft speech (stream mode) | `--energy-floor 0.002` |
192
+ | Wrong words | Move closer to the mic; prefer the built-in mic over AirPods (Bluetooth input drops to a low-quality codec) |
193
+ | Detailed trace | `~/Library/Logs/parlando.log` |
194
+
195
+ Note: `--device` indices shift when devices connect/disconnect, and virtual
196
+ devices ("Microsoft Teams Audio", "ZoomAudioDevice") are not microphones.
197
+ Prefer the default device.
198
+
199
+ ## Architecture
200
+
201
+ ```
202
+ microphone ── 30 ms frames ──▶ hotkey-gated recorder (record mode)
203
+ or energy VAD w/ hysteresis + adaptive floor,
204
+ optional Silero hybrid, 300 ms pre-roll (stream)
205
+ │ segment boundaries
206
+
207
+ segment audio only ──▶ Qwen3-ASR (MLX, on-device GPU)
208
+ │ readings
209
+
210
+ LocalAgreement-2 ──▶ append-only word commits ──▶ CGEvent unicode
211
+ (commit on 2-reading (typed text is never keyboard events
212
+ agreement) retracted)
213
+ ```
214
+
215
+ Key decisions (each one earned by a real failure during development):
216
+
217
+ - **Segment-based ASR, not a rolling window** — re-decoding a growing window
218
+ revises earlier words (visible flicker) and hallucinates on silence.
219
+ - **Append-only, word-count-based commits** — exact-prefix matching stalls
220
+ permanently when the ASR reshapes punctuation on committed words.
221
+ - **Normalized agreement + stall safety valve** — punctuation/case flapping
222
+ between readings must not freeze the stream.
223
+ - **Asymmetric noise-floor learning** — a symmetric EMA lets speech scraps
224
+ poison the floor and the app goes progressively deaf.
225
+ - **Every loop iteration guarded** — a single ASR exception must not silently
226
+ kill the pipeline.
227
+ - **Chord-aware hotkey** — ⌥+Q-style character chords never toggle dictation.
228
+
229
+ ## Development
230
+
231
+ ```bash
232
+ uvx --with numpy pytest tests -q # unit tests; no model/mic needed
233
+ uv run scripts/eval_polish.py # polish quality benchmark (real local LLM)
234
+ uv build # build the wheel/sdist
235
+ ```
236
+
237
+ Package layout: `src/parlando/` (engine, menubar, ASR engine), entry points
238
+ `parlando` and `parlando-menubar`. CI runs the test suite and a packaging
239
+ build on macOS via GitHub Actions.
240
+
241
+ ## Roadmap
242
+
243
+ - [x] One-line installer (`install.sh`) with `parlando` launcher command
244
+ - [x] Proper Python package (`uv tool install`, entry points) — PyPI
245
+ publication pending, after which `uvx parlando` will work
246
+ - [ ] Publish to PyPI
247
+ - [ ] Homebrew tap (`brew install parlando`)
248
+ - [ ] End-to-end regression tests with recorded WAV fixtures
249
+ - [ ] Custom vocabulary / context biasing
250
+
251
+ ## License & credits
252
+
253
+ MIT. The Qwen3-ASR MLX engine (`stt.py`) is from
254
+ [dictate.sh](https://github.com/mpuig/dictate.sh) by Marc Puig (MIT), with
255
+ local modifications (partial streaming, energy gating, error hardening).
256
+ Turn-taking research: [Whisper-Streaming /
257
+ LocalAgreement-2](https://arxiv.org/abs/2307.14743). VAD:
258
+ [Silero VAD](https://github.com/snakers4/silero-vad).