easyspeak-linux 0.2.0__tar.gz → 0.3.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.
Files changed (46) hide show
  1. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.github/workflows/pipeline.yml +18 -1
  2. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.gitignore +4 -0
  3. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/PKG-INFO +37 -17
  4. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/README.md +27 -16
  5. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/PKG-INFO +37 -17
  6. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/SOURCES.txt +8 -3
  7. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/requires.txt +12 -0
  8. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/pyproject.toml +26 -3
  9. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/core/config.py +3 -0
  10. easyspeak_linux-0.3.0/src/core/gnome_extension.py +335 -0
  11. easyspeak_linux-0.3.0/src/core/main.py +472 -0
  12. easyspeak_linux-0.3.0/src/core/mediakeys.py +52 -0
  13. easyspeak_linux-0.3.0/src/core/speech.py +240 -0
  14. easyspeak_linux-0.3.0/src/core/tray.py +194 -0
  15. easyspeak_linux-0.3.0/src/extension-helpers.js +60 -0
  16. easyspeak_linux-0.3.0/src/extension.js +680 -0
  17. easyspeak_linux-0.3.0/src/metadata.json +7 -0
  18. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/00_mousegrid.py +2 -136
  19. easyspeak_linux-0.3.0/src/plugins/apps.py +203 -0
  20. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/browser.py +26 -4
  21. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/files.py +2 -1
  22. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/media.py +16 -8
  23. easyspeak_linux-0.3.0/src/plugins/sleep.py +34 -0
  24. easyspeak_linux-0.3.0/src/plugins/system.py +161 -0
  25. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/zz_base.py +5 -5
  26. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/uv.lock +283 -50
  27. easyspeak_linux-0.2.0/CONTRIBUTING.md +0 -89
  28. easyspeak_linux-0.2.0/flake.lock +0 -61
  29. easyspeak_linux-0.2.0/flake.nix +0 -236
  30. easyspeak_linux-0.2.0/src/core/main.py +0 -311
  31. easyspeak_linux-0.2.0/src/plugins/apps.py +0 -93
  32. easyspeak_linux-0.2.0/src/plugins/system.py +0 -122
  33. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.github/actions/benchmark/action.yml +0 -0
  34. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.github/workflows/benchmark.yml +0 -0
  35. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.github/workflows/publish.yml +0 -0
  36. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/.github/workflows/safety.yml +0 -0
  37. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/LICENSE +0 -0
  38. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/dependency_links.txt +0 -0
  39. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/entry_points.txt +0 -0
  40. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/easyspeak_linux.egg-info/top_level.txt +0 -0
  41. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/setup.cfg +0 -0
  42. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/core/__init__.py +0 -0
  43. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/core/__main__.py +0 -0
  44. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/00_eyetrack.py +0 -0
  45. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/__init__.py +0 -0
  46. {easyspeak_linux-0.2.0 → easyspeak_linux-0.3.0}/src/plugins/dictation.py +0 -0
@@ -20,7 +20,7 @@ jobs:
20
20
  - lint --
21
21
  - types
22
22
  - package
23
- - functional
23
+ - gate
24
24
  fail-fast: false
25
25
  steps:
26
26
  - uses: actions/checkout@v6
@@ -33,6 +33,23 @@ jobs:
33
33
  sudo apt install -y portaudio19-dev
34
34
  - run: just ${{ matrix.task }}
35
35
 
36
+ js:
37
+ runs-on: ubuntu-latest
38
+ strategy:
39
+ matrix:
40
+ task:
41
+ - lint-js
42
+ - test-js
43
+ fail-fast: false
44
+ steps:
45
+ - uses: actions/checkout@v6
46
+ - uses: actions/setup-node@v5
47
+ with:
48
+ node-version: '22'
49
+ - uses: taiki-e/install-action@just
50
+ - run: npm install --global eslint@10
51
+ - run: just ${{ matrix.task }}
52
+
36
53
  test:
37
54
  runs-on: ubuntu-latest
38
55
  strategy:
@@ -52,3 +52,7 @@ Thumbs.db
52
52
  # Project specific
53
53
  *.wav
54
54
  *.log
55
+
56
+ # locally installed agent skills
57
+ .agents/
58
+ skills-lock.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyspeak-linux
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Voice control for Linux desktops. Fully local, no cloud, Wayland-native.
5
5
  Author-email: Matt Hartley <matt@matthartley.com>
6
6
  Maintainer-email: Matt Hartley <matt@matthartley.com>
@@ -24,19 +24,28 @@ Requires-Python: <3.14,>=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: faster-whisper>=1.2.1
27
+ Requires-Dist: jeepney>=0.9
27
28
  Requires-Dist: numpy>=2.2.6
28
29
  Requires-Dist: onnxruntime>=1.23.2
29
30
  Requires-Dist: onnxruntime<1.24; python_full_version < "3.11"
30
31
  Requires-Dist: pyaudio>=0.2.14
32
+ Provides-Extra: acceptance
33
+ Requires-Dist: pytest>=8; extra == "acceptance"
34
+ Requires-Dist: pytest-bdd>=7; extra == "acceptance"
31
35
  Provides-Extra: benchmark
32
36
  Requires-Dist: pytest>=8; extra == "benchmark"
33
37
  Requires-Dist: pytest-benchmark>=5; extra == "benchmark"
34
38
  Provides-Extra: head-tracking
35
39
  Requires-Dist: opencv-python>=4.13.0.90; extra == "head-tracking"
36
40
  Requires-Dist: sixdrepnet>=0.1.6; extra == "head-tracking"
41
+ Provides-Extra: integration
42
+ Requires-Dist: pytest>=8; extra == "integration"
37
43
  Provides-Extra: mypy
38
44
  Requires-Dist: mypy>=1.19.1; extra == "mypy"
39
45
  Requires-Dist: types-pyaudio>=0.2.16.20250801; extra == "mypy"
46
+ Provides-Extra: unittest
47
+ Requires-Dist: coverage[toml]>=7.14; extra == "unittest"
48
+ Requires-Dist: pytest>=8; extra == "unittest"
40
49
  Dynamic: license-file
41
50
 
42
51
  # EasySpeak [![PyPI](https://img.shields.io/pypi/v/easyspeak-linux)](https://pypi.org/project/easyspeak-linux/)
@@ -196,7 +205,10 @@ easyspeak # the project execution script
196
205
 
197
206
  Activate the venv each time you open a new terminal.
198
207
 
199
- Say "Hey Jarvis" followed by a command.
208
+ Say "Hey Jarvis" followed by a command. After a command that gives no spoken
209
+ reply (such as volume changes), EasySpeak keeps listening for a few seconds so
210
+ you can chain commands — say "louder", "louder", "louder" without repeating the
211
+ wake word each time.
200
212
 
201
213
  ## Commands
202
214
 
@@ -315,9 +327,10 @@ Built-in bookmarks: youtube, google, gmail, github, reddit, twitter, facebook, a
315
327
  | close [app] | Close application |
316
328
 
317
329
  Default apps in `plugins/apps.py` (edit to match your system):
318
- - firefox, steam, spotify, calculator, settings, files, terminal, browser
330
+ - firefox, steam, spotify, calculator, settings, files, terminal, browser, music player
319
331
 
320
- These are just examples. Edit `apps.py` to add your own apps.
332
+ These are just examples. Edit `apps.py` to add your own apps. Some accept
333
+ spoken aliases — e.g. "open music app" works the same as "open music player".
321
334
 
322
335
  ### Files
323
336
 
@@ -328,6 +341,7 @@ These are just examples. Edit `apps.py` to add your own apps.
328
341
  | open pictures | Open Pictures folder |
329
342
  | open music | Open Music folder |
330
343
  | open videos | Open Videos folder |
344
+ | open projects | Open Projects folder |
331
345
  | open home | Open home folder |
332
346
  | open desktop | Open Desktop folder |
333
347
 
@@ -336,7 +350,7 @@ These are just examples. Edit `apps.py` to add your own apps.
336
350
  | Command | Action |
337
351
  |---------|--------|
338
352
  | play | Resume playback |
339
- | pause | Pause playback |
353
+ | pause / stop the music | Pause playback |
340
354
  | next / skip | Next track |
341
355
  | previous / back | Previous track |
342
356
 
@@ -344,30 +358,35 @@ These are just examples. Edit `apps.py` to add your own apps.
344
358
 
345
359
  | Command | Action |
346
360
  |---------|--------|
347
- | volume up/down | Adjust volume |
361
+ | volume up/down (or louder / quieter) | Adjust volume one step (repeat to keep going) |
362
+ | very loud / very silent | Jump straight to near-max (85%) / low (15%, not muted) |
348
363
  | mute | Toggle mute |
349
364
  | brightness up/down | Adjust brightness |
350
365
  | do not disturb on/off | Toggle notifications |
351
366
 
367
+ Volume changes are silent — GNOME's own on-screen display and chime acknowledge them.
368
+
352
369
  ### General
353
370
 
354
371
  | Command | Action |
355
372
  |---------|--------|
356
373
  | help | List all commands |
357
- | stop / exit / quit | Exit EasySpeak |
374
+ | go to sleep / stop listening | Release the mic (reactivate from the tray icon) |
375
+ | quit / exit / goodbye | Exit EasySpeak |
358
376
 
359
377
  ## File Structure
360
378
 
361
379
  ```
362
380
  easyspeak/
363
- ├── extension.js # GNOME Shell extension
364
- ├── metadata.json # Extension metadata
365
381
  ├── pyproject.toml
366
382
  ├── src
383
+ │ ├── extension.js # GNOME Shell extension (bundled as package data)
384
+ │ ├── metadata.json # Extension metadata
367
385
  │ ├── core
368
386
  │ │ ├── __init__.py
369
387
  │ │ ├── __main__.py
370
388
  │ │ ├── config.py # Tuning constants + Whisper model factory
389
+ │ │ ├── gnome_extension.py # Installs/refreshes/enables the extension
371
390
  │ │ └── main.py # EasySpeak class + main loop
372
391
  │ └── plugins
373
392
  │ ├── __init__.py
@@ -386,9 +405,9 @@ easyspeak/
386
405
  └── plugins # tests for src/plugins/
387
406
  ```
388
407
 
389
- On first start, the `mousegrid` plugin auto-installs the GNOME Shell
390
- extension to the user-local extensions directory (unless GNOME already
391
- sees it via a system-wide install). Files copied:
408
+ On first start, core auto-installs the GNOME Shell extension to the
409
+ user-local extensions directory (unless GNOME already sees it via a
410
+ system-wide install). Files copied:
392
411
 
393
412
  ```
394
413
  ~/.local/share/gnome-shell/extensions/easyspeak-grid@local/
@@ -444,11 +463,12 @@ def handle(cmd, core):
444
463
 
445
464
  **Mouse grid: "Failed to show grid — is extension enabled?"**
446
465
 
447
- EasySpeak auto-installs the GNOME Shell extension to
448
- `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` on first
449
- run (look for a `mousegrid: installed ...` message at startup). On
450
- Wayland, GNOME Shell only scans for new extensions at login, so you
451
- typically have to **log out and back in** before it becomes loadable.
466
+ You don't install the extension yourself EasySpeak does it automatically
467
+ on startup, copying it to
468
+ `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` and keeping it
469
+ up to date (look for an `easyspeak: installed ...` or `easyspeak: updated ...`
470
+ message). On Wayland, GNOME Shell only scans for new extensions at login, so
471
+ you typically have to **log out and back in** before it becomes loadable.
452
472
 
453
473
  After re-login, enable it from the command line:
454
474
 
@@ -155,7 +155,10 @@ easyspeak # the project execution script
155
155
 
156
156
  Activate the venv each time you open a new terminal.
157
157
 
158
- Say "Hey Jarvis" followed by a command.
158
+ Say "Hey Jarvis" followed by a command. After a command that gives no spoken
159
+ reply (such as volume changes), EasySpeak keeps listening for a few seconds so
160
+ you can chain commands — say "louder", "louder", "louder" without repeating the
161
+ wake word each time.
159
162
 
160
163
  ## Commands
161
164
 
@@ -274,9 +277,10 @@ Built-in bookmarks: youtube, google, gmail, github, reddit, twitter, facebook, a
274
277
  | close [app] | Close application |
275
278
 
276
279
  Default apps in `plugins/apps.py` (edit to match your system):
277
- - firefox, steam, spotify, calculator, settings, files, terminal, browser
280
+ - firefox, steam, spotify, calculator, settings, files, terminal, browser, music player
278
281
 
279
- These are just examples. Edit `apps.py` to add your own apps.
282
+ These are just examples. Edit `apps.py` to add your own apps. Some accept
283
+ spoken aliases — e.g. "open music app" works the same as "open music player".
280
284
 
281
285
  ### Files
282
286
 
@@ -287,6 +291,7 @@ These are just examples. Edit `apps.py` to add your own apps.
287
291
  | open pictures | Open Pictures folder |
288
292
  | open music | Open Music folder |
289
293
  | open videos | Open Videos folder |
294
+ | open projects | Open Projects folder |
290
295
  | open home | Open home folder |
291
296
  | open desktop | Open Desktop folder |
292
297
 
@@ -295,7 +300,7 @@ These are just examples. Edit `apps.py` to add your own apps.
295
300
  | Command | Action |
296
301
  |---------|--------|
297
302
  | play | Resume playback |
298
- | pause | Pause playback |
303
+ | pause / stop the music | Pause playback |
299
304
  | next / skip | Next track |
300
305
  | previous / back | Previous track |
301
306
 
@@ -303,30 +308,35 @@ These are just examples. Edit `apps.py` to add your own apps.
303
308
 
304
309
  | Command | Action |
305
310
  |---------|--------|
306
- | volume up/down | Adjust volume |
311
+ | volume up/down (or louder / quieter) | Adjust volume one step (repeat to keep going) |
312
+ | very loud / very silent | Jump straight to near-max (85%) / low (15%, not muted) |
307
313
  | mute | Toggle mute |
308
314
  | brightness up/down | Adjust brightness |
309
315
  | do not disturb on/off | Toggle notifications |
310
316
 
317
+ Volume changes are silent — GNOME's own on-screen display and chime acknowledge them.
318
+
311
319
  ### General
312
320
 
313
321
  | Command | Action |
314
322
  |---------|--------|
315
323
  | help | List all commands |
316
- | stop / exit / quit | Exit EasySpeak |
324
+ | go to sleep / stop listening | Release the mic (reactivate from the tray icon) |
325
+ | quit / exit / goodbye | Exit EasySpeak |
317
326
 
318
327
  ## File Structure
319
328
 
320
329
  ```
321
330
  easyspeak/
322
- ├── extension.js # GNOME Shell extension
323
- ├── metadata.json # Extension metadata
324
331
  ├── pyproject.toml
325
332
  ├── src
333
+ │ ├── extension.js # GNOME Shell extension (bundled as package data)
334
+ │ ├── metadata.json # Extension metadata
326
335
  │ ├── core
327
336
  │ │ ├── __init__.py
328
337
  │ │ ├── __main__.py
329
338
  │ │ ├── config.py # Tuning constants + Whisper model factory
339
+ │ │ ├── gnome_extension.py # Installs/refreshes/enables the extension
330
340
  │ │ └── main.py # EasySpeak class + main loop
331
341
  │ └── plugins
332
342
  │ ├── __init__.py
@@ -345,9 +355,9 @@ easyspeak/
345
355
  └── plugins # tests for src/plugins/
346
356
  ```
347
357
 
348
- On first start, the `mousegrid` plugin auto-installs the GNOME Shell
349
- extension to the user-local extensions directory (unless GNOME already
350
- sees it via a system-wide install). Files copied:
358
+ On first start, core auto-installs the GNOME Shell extension to the
359
+ user-local extensions directory (unless GNOME already sees it via a
360
+ system-wide install). Files copied:
351
361
 
352
362
  ```
353
363
  ~/.local/share/gnome-shell/extensions/easyspeak-grid@local/
@@ -403,11 +413,12 @@ def handle(cmd, core):
403
413
 
404
414
  **Mouse grid: "Failed to show grid — is extension enabled?"**
405
415
 
406
- EasySpeak auto-installs the GNOME Shell extension to
407
- `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` on first
408
- run (look for a `mousegrid: installed ...` message at startup). On
409
- Wayland, GNOME Shell only scans for new extensions at login, so you
410
- typically have to **log out and back in** before it becomes loadable.
416
+ You don't install the extension yourself EasySpeak does it automatically
417
+ on startup, copying it to
418
+ `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` and keeping it
419
+ up to date (look for an `easyspeak: installed ...` or `easyspeak: updated ...`
420
+ message). On Wayland, GNOME Shell only scans for new extensions at login, so
421
+ you typically have to **log out and back in** before it becomes loadable.
411
422
 
412
423
  After re-login, enable it from the command line:
413
424
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyspeak-linux
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Voice control for Linux desktops. Fully local, no cloud, Wayland-native.
5
5
  Author-email: Matt Hartley <matt@matthartley.com>
6
6
  Maintainer-email: Matt Hartley <matt@matthartley.com>
@@ -24,19 +24,28 @@ Requires-Python: <3.14,>=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: faster-whisper>=1.2.1
27
+ Requires-Dist: jeepney>=0.9
27
28
  Requires-Dist: numpy>=2.2.6
28
29
  Requires-Dist: onnxruntime>=1.23.2
29
30
  Requires-Dist: onnxruntime<1.24; python_full_version < "3.11"
30
31
  Requires-Dist: pyaudio>=0.2.14
32
+ Provides-Extra: acceptance
33
+ Requires-Dist: pytest>=8; extra == "acceptance"
34
+ Requires-Dist: pytest-bdd>=7; extra == "acceptance"
31
35
  Provides-Extra: benchmark
32
36
  Requires-Dist: pytest>=8; extra == "benchmark"
33
37
  Requires-Dist: pytest-benchmark>=5; extra == "benchmark"
34
38
  Provides-Extra: head-tracking
35
39
  Requires-Dist: opencv-python>=4.13.0.90; extra == "head-tracking"
36
40
  Requires-Dist: sixdrepnet>=0.1.6; extra == "head-tracking"
41
+ Provides-Extra: integration
42
+ Requires-Dist: pytest>=8; extra == "integration"
37
43
  Provides-Extra: mypy
38
44
  Requires-Dist: mypy>=1.19.1; extra == "mypy"
39
45
  Requires-Dist: types-pyaudio>=0.2.16.20250801; extra == "mypy"
46
+ Provides-Extra: unittest
47
+ Requires-Dist: coverage[toml]>=7.14; extra == "unittest"
48
+ Requires-Dist: pytest>=8; extra == "unittest"
40
49
  Dynamic: license-file
41
50
 
42
51
  # EasySpeak [![PyPI](https://img.shields.io/pypi/v/easyspeak-linux)](https://pypi.org/project/easyspeak-linux/)
@@ -196,7 +205,10 @@ easyspeak # the project execution script
196
205
 
197
206
  Activate the venv each time you open a new terminal.
198
207
 
199
- Say "Hey Jarvis" followed by a command.
208
+ Say "Hey Jarvis" followed by a command. After a command that gives no spoken
209
+ reply (such as volume changes), EasySpeak keeps listening for a few seconds so
210
+ you can chain commands — say "louder", "louder", "louder" without repeating the
211
+ wake word each time.
200
212
 
201
213
  ## Commands
202
214
 
@@ -315,9 +327,10 @@ Built-in bookmarks: youtube, google, gmail, github, reddit, twitter, facebook, a
315
327
  | close [app] | Close application |
316
328
 
317
329
  Default apps in `plugins/apps.py` (edit to match your system):
318
- - firefox, steam, spotify, calculator, settings, files, terminal, browser
330
+ - firefox, steam, spotify, calculator, settings, files, terminal, browser, music player
319
331
 
320
- These are just examples. Edit `apps.py` to add your own apps.
332
+ These are just examples. Edit `apps.py` to add your own apps. Some accept
333
+ spoken aliases — e.g. "open music app" works the same as "open music player".
321
334
 
322
335
  ### Files
323
336
 
@@ -328,6 +341,7 @@ These are just examples. Edit `apps.py` to add your own apps.
328
341
  | open pictures | Open Pictures folder |
329
342
  | open music | Open Music folder |
330
343
  | open videos | Open Videos folder |
344
+ | open projects | Open Projects folder |
331
345
  | open home | Open home folder |
332
346
  | open desktop | Open Desktop folder |
333
347
 
@@ -336,7 +350,7 @@ These are just examples. Edit `apps.py` to add your own apps.
336
350
  | Command | Action |
337
351
  |---------|--------|
338
352
  | play | Resume playback |
339
- | pause | Pause playback |
353
+ | pause / stop the music | Pause playback |
340
354
  | next / skip | Next track |
341
355
  | previous / back | Previous track |
342
356
 
@@ -344,30 +358,35 @@ These are just examples. Edit `apps.py` to add your own apps.
344
358
 
345
359
  | Command | Action |
346
360
  |---------|--------|
347
- | volume up/down | Adjust volume |
361
+ | volume up/down (or louder / quieter) | Adjust volume one step (repeat to keep going) |
362
+ | very loud / very silent | Jump straight to near-max (85%) / low (15%, not muted) |
348
363
  | mute | Toggle mute |
349
364
  | brightness up/down | Adjust brightness |
350
365
  | do not disturb on/off | Toggle notifications |
351
366
 
367
+ Volume changes are silent — GNOME's own on-screen display and chime acknowledge them.
368
+
352
369
  ### General
353
370
 
354
371
  | Command | Action |
355
372
  |---------|--------|
356
373
  | help | List all commands |
357
- | stop / exit / quit | Exit EasySpeak |
374
+ | go to sleep / stop listening | Release the mic (reactivate from the tray icon) |
375
+ | quit / exit / goodbye | Exit EasySpeak |
358
376
 
359
377
  ## File Structure
360
378
 
361
379
  ```
362
380
  easyspeak/
363
- ├── extension.js # GNOME Shell extension
364
- ├── metadata.json # Extension metadata
365
381
  ├── pyproject.toml
366
382
  ├── src
383
+ │ ├── extension.js # GNOME Shell extension (bundled as package data)
384
+ │ ├── metadata.json # Extension metadata
367
385
  │ ├── core
368
386
  │ │ ├── __init__.py
369
387
  │ │ ├── __main__.py
370
388
  │ │ ├── config.py # Tuning constants + Whisper model factory
389
+ │ │ ├── gnome_extension.py # Installs/refreshes/enables the extension
371
390
  │ │ └── main.py # EasySpeak class + main loop
372
391
  │ └── plugins
373
392
  │ ├── __init__.py
@@ -386,9 +405,9 @@ easyspeak/
386
405
  └── plugins # tests for src/plugins/
387
406
  ```
388
407
 
389
- On first start, the `mousegrid` plugin auto-installs the GNOME Shell
390
- extension to the user-local extensions directory (unless GNOME already
391
- sees it via a system-wide install). Files copied:
408
+ On first start, core auto-installs the GNOME Shell extension to the
409
+ user-local extensions directory (unless GNOME already sees it via a
410
+ system-wide install). Files copied:
392
411
 
393
412
  ```
394
413
  ~/.local/share/gnome-shell/extensions/easyspeak-grid@local/
@@ -444,11 +463,12 @@ def handle(cmd, core):
444
463
 
445
464
  **Mouse grid: "Failed to show grid — is extension enabled?"**
446
465
 
447
- EasySpeak auto-installs the GNOME Shell extension to
448
- `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` on first
449
- run (look for a `mousegrid: installed ...` message at startup). On
450
- Wayland, GNOME Shell only scans for new extensions at login, so you
451
- typically have to **log out and back in** before it becomes loadable.
466
+ You don't install the extension yourself EasySpeak does it automatically
467
+ on startup, copying it to
468
+ `~/.local/share/gnome-shell/extensions/easyspeak-grid@local/` and keeping it
469
+ up to date (look for an `easyspeak: installed ...` or `easyspeak: updated ...`
470
+ message). On Wayland, GNOME Shell only scans for new extensions at login, so
471
+ you typically have to **log out and back in** before it becomes loadable.
452
472
 
453
473
  After re-login, enable it from the command line:
454
474
 
@@ -1,9 +1,6 @@
1
1
  .gitignore
2
- CONTRIBUTING.md
3
2
  LICENSE
4
3
  README.md
5
- flake.lock
6
- flake.nix
7
4
  pyproject.toml
8
5
  uv.lock
9
6
  .github/actions/benchmark/action.yml
@@ -17,10 +14,17 @@ easyspeak_linux.egg-info/dependency_links.txt
17
14
  easyspeak_linux.egg-info/entry_points.txt
18
15
  easyspeak_linux.egg-info/requires.txt
19
16
  easyspeak_linux.egg-info/top_level.txt
17
+ src/extension-helpers.js
18
+ src/extension.js
19
+ src/metadata.json
20
20
  src/core/__init__.py
21
21
  src/core/__main__.py
22
22
  src/core/config.py
23
+ src/core/gnome_extension.py
23
24
  src/core/main.py
25
+ src/core/mediakeys.py
26
+ src/core/speech.py
27
+ src/core/tray.py
24
28
  src/plugins/00_eyetrack.py
25
29
  src/plugins/00_mousegrid.py
26
30
  src/plugins/__init__.py
@@ -29,5 +33,6 @@ src/plugins/browser.py
29
33
  src/plugins/dictation.py
30
34
  src/plugins/files.py
31
35
  src/plugins/media.py
36
+ src/plugins/sleep.py
32
37
  src/plugins/system.py
33
38
  src/plugins/zz_base.py
@@ -1,4 +1,5 @@
1
1
  faster-whisper>=1.2.1
2
+ jeepney>=0.9
2
3
  numpy>=2.2.6
3
4
  onnxruntime>=1.23.2
4
5
  pyaudio>=0.2.14
@@ -6,6 +7,10 @@ pyaudio>=0.2.14
6
7
  [:python_full_version < "3.11"]
7
8
  onnxruntime<1.24
8
9
 
10
+ [acceptance]
11
+ pytest>=8
12
+ pytest-bdd>=7
13
+
9
14
  [benchmark]
10
15
  pytest>=8
11
16
  pytest-benchmark>=5
@@ -14,6 +19,13 @@ pytest-benchmark>=5
14
19
  opencv-python>=4.13.0.90
15
20
  sixdrepnet>=0.1.6
16
21
 
22
+ [integration]
23
+ pytest>=8
24
+
17
25
  [mypy]
18
26
  mypy>=1.19.1
19
27
  types-pyaudio>=0.2.16.20250801
28
+
29
+ [unittest]
30
+ coverage[toml]>=7.14
31
+ pytest>=8
@@ -43,6 +43,7 @@ readme = "README.md"
43
43
  requires-python = ">=3.10, <3.14"
44
44
  dependencies = [
45
45
  "faster-whisper>=1.2.1",
46
+ "jeepney>=0.9",
46
47
  "numpy>=2.2.6",
47
48
  # "openwakeword", # requires Python <3.13 for speexdsp-ns (missing wheels)
48
49
  "onnxruntime>=1.23.2",
@@ -51,6 +52,10 @@ dependencies = [
51
52
  ]
52
53
 
53
54
  [project.optional-dependencies]
55
+ acceptance = [
56
+ "pytest>=8",
57
+ "pytest-bdd>=7",
58
+ ]
54
59
  benchmark = [
55
60
  "pytest>=8",
56
61
  "pytest-benchmark>=5",
@@ -59,10 +64,17 @@ head-tracking = [
59
64
  "opencv-python>=4.13.0.90",
60
65
  "sixdrepnet>=0.1.6",
61
66
  ]
67
+ integration = [
68
+ "pytest>=8",
69
+ ]
62
70
  mypy = [
63
71
  "mypy>=1.19.1",
64
72
  "types-pyaudio>=0.2.16.20250801",
65
73
  ]
74
+ unittest = [
75
+ "coverage[toml]>=7.14",
76
+ "pytest>=8",
77
+ ]
66
78
 
67
79
  [project.scripts]
68
80
  easyspeak = "easyspeak.core.main:run"
@@ -72,6 +84,7 @@ easyspeak = "easyspeak.core.main:run"
72
84
 
73
85
  [tool.coverage.report]
74
86
  exclude_also = ["if __name__ == .__main__.:"]
87
+ fail_under = 99
75
88
  show_missing = true
76
89
 
77
90
  [tool.coverage.run]
@@ -84,9 +97,13 @@ warn_unreachable = true
84
97
  warn_unused_ignores = true
85
98
 
86
99
  [tool.pytest.ini_options]
87
- # Whisper benchmarks download large models and take minutes; opt-in only.
88
- # Run them with `just benchmark` or `pytest tests/benchmarks/`.
89
- addopts = "--ignore=tests/benchmarks"
100
+ # Folders excluded from default discovery are opt-in: they need preparation
101
+ # the plain unit run doesn't (large model downloads, real external binaries,
102
+ # an audio server). Each has its own `just` target.
103
+ # tests/acceptance -> `just acceptance` (Gherkin scenarios; needs pytest-bdd)
104
+ # tests/benchmarks -> `just benchmark` (downloads large Whisper models)
105
+ # tests/integration -> `just integration` (runs real piper/audio-player binaries)
106
+ addopts = "--ignore=tests/acceptance --ignore=tests/benchmarks --ignore=tests/integration"
90
107
 
91
108
  [tool.ruff.lint]
92
109
  extend-select = ["I"] # "ALL" one day
@@ -95,6 +112,12 @@ extend-ignore = ["D", "E722"]
95
112
  [tool.setuptools.package-dir]
96
113
  easyspeak = "src"
97
114
 
115
+ # The bundled GNOME Shell extension is package data: core.gnome_extension
116
+ # copies it into the user's extensions directory at startup, so it must ship
117
+ # inside the wheel (alongside the easyspeak package), not just the sdist.
118
+ [tool.setuptools.package-data]
119
+ easyspeak = ["extension.js", "extension-helpers.js", "metadata.json"]
120
+
98
121
  [tool.setuptools_scm]
99
122
  local_scheme = "no-local-version"
100
123
 
@@ -18,6 +18,9 @@ WAKE_COOLDOWN = 3.0 # Seconds to ignore wake word after trigger
18
18
  SILENCE_THRESHOLD = 300
19
19
  SILENCE_DURATION = 0.3
20
20
 
21
+ MISUNDERSTAND_GRACE = 4.0 # Seconds to ignore repeat misses after feedback
22
+ FOLLOWUP_IDLE_ROUNDS = 2 # Quiet listens tolerated before a command session ends
23
+
21
24
  # --- Models ---
22
25
  PIPER_MODEL = os.environ.get(
23
26
  "EASYSPEAK_PIPER_MODEL",