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.
- parlando-0.4.0/.github/workflows/release.yml +44 -0
- parlando-0.4.0/.github/workflows/tests.yml +35 -0
- parlando-0.4.0/.gitignore +208 -0
- parlando-0.4.0/CHANGELOG.md +58 -0
- parlando-0.4.0/LICENSE +22 -0
- parlando-0.4.0/PKG-INFO +258 -0
- parlando-0.4.0/README.md +224 -0
- parlando-0.4.0/assets/banner.png +0 -0
- parlando-0.4.0/assets/menubar-states.png +0 -0
- parlando-0.4.0/install.sh +52 -0
- parlando-0.4.0/pyproject.toml +62 -0
- parlando-0.4.0/scripts/eval_polish.py +173 -0
- parlando-0.4.0/scripts/make_icons.py +185 -0
- parlando-0.4.0/src/parlando/__init__.py +3 -0
- parlando-0.4.0/src/parlando/assets/mic-paused.png +0 -0
- parlando-0.4.0/src/parlando/assets/mic-recording.png +0 -0
- parlando-0.4.0/src/parlando/assets/mic.png +0 -0
- parlando-0.4.0/src/parlando/engine.py +1400 -0
- parlando-0.4.0/src/parlando/menubar.py +220 -0
- parlando-0.4.0/src/parlando/stt.py +1781 -0
- parlando-0.4.0/src/parlando/webrtcvad_compat.py +53 -0
- parlando-0.4.0/tests/test_parlando.py +483 -0
- parlando-0.4.0/uv.lock +1501 -0
|
@@ -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.
|
parlando-0.4.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](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).
|