python-voiceio 1.10.0__tar.gz → 1.12.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 (228) hide show
  1. {python_voiceio-1.10.0/python_voiceio.egg-info → python_voiceio-1.12.0}/PKG-INFO +15 -6
  2. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/README.md +14 -5
  3. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/pyproject.toml +1 -1
  4. {python_voiceio-1.10.0 → python_voiceio-1.12.0/python_voiceio.egg-info}/PKG-INFO +15 -6
  5. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/python_voiceio.egg-info/SOURCES.txt +4 -0
  6. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_config.py +6 -1
  7. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_decoders.py +0 -56
  8. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_postprocess.py +13 -0
  9. python_voiceio-1.12.0/tests/test_report.py +314 -0
  10. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_service.py +22 -0
  11. python_voiceio-1.12.0/tests/test_sherpa.py +369 -0
  12. python_voiceio-1.12.0/voiceio/__init__.py +1 -0
  13. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/__init__.py +2 -0
  14. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/doctor.py +24 -0
  15. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/learn.py +20 -0
  16. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/models.py +85 -8
  17. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/uninstall.py +5 -1
  18. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/config.py +16 -6
  19. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/__init__.py +32 -11
  20. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/catalog.py +51 -4
  21. python_voiceio-1.12.0/voiceio/core/decoders/fetch.py +198 -0
  22. python_voiceio-1.12.0/voiceio/core/decoders/sherpa.py +304 -0
  23. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/health.py +26 -0
  24. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/evaluate.py +8 -3
  25. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/postprocess.py +20 -4
  26. python_voiceio-1.12.0/voiceio/report.py +278 -0
  27. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/service.py +31 -0
  28. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/noninteractive.py +5 -4
  29. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/speech.py +42 -1
  30. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/steps.py +18 -10
  31. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/system.py +7 -0
  32. python_voiceio-1.10.0/voiceio/__init__.py +0 -1
  33. python_voiceio-1.10.0/voiceio/core/decoders/sherpa.py +0 -108
  34. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/LICENSE +0 -0
  35. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
  36. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/python_voiceio.egg-info/entry_points.txt +0 -0
  37. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/python_voiceio.egg-info/requires.txt +0 -0
  38. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/python_voiceio.egg-info/top_level.txt +0 -0
  39. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/setup.cfg +0 -0
  40. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_app_wiring.py +0 -0
  41. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_audio_quality.py +0 -0
  42. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_audio_source.py +0 -0
  43. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_backend_probes.py +0 -0
  44. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_cancel.py +0 -0
  45. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_cli.py +0 -0
  46. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_clipboard_read.py +0 -0
  47. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_clipboard_typer.py +0 -0
  48. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_commands.py +0 -0
  49. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_concurrency_lockdown.py +0 -0
  50. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_context_terms.py +0 -0
  51. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_corrections.py +0 -0
  52. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_dead_mic.py +0 -0
  53. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_engine.py +0 -0
  54. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_evdev_uaccess.py +0 -0
  55. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_fallback.py +0 -0
  56. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_fcitx_bridge.py +0 -0
  57. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_feedback_stream.py +0 -0
  58. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_focus.py +0 -0
  59. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_health.py +0 -0
  60. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_hints.py +0 -0
  61. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_history.py +0 -0
  62. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_hotkey_modes.py +0 -0
  63. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_engine_limits.py +0 -0
  64. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_engine_listener.py +0 -0
  65. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_pending.py +0 -0
  66. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_pids.py +0 -0
  67. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_ping.py +0 -0
  68. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_session.py +0 -0
  69. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_textlimit.py +0 -0
  70. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_ibus_typer.py +0 -0
  71. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_idle_audio.py +0 -0
  72. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_clean.py +0 -0
  73. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_import.py +0 -0
  74. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_language.py +0 -0
  75. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_metrics.py +0 -0
  76. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_pipeline.py +0 -0
  77. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_quiet.py +0 -0
  78. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_resume.py +0 -0
  79. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_review.py +0 -0
  80. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_learn_targets.py +0 -0
  81. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_llm.py +0 -0
  82. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_llm_api.py +0 -0
  83. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_mic_gain.py +0 -0
  84. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_modifier_guard.py +0 -0
  85. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_numbers.py +0 -0
  86. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_paste_last.py +0 -0
  87. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_pipeline.py +0 -0
  88. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_platform.py +0 -0
  89. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_postcorrect.py +0 -0
  90. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_prebuffer.py +0 -0
  91. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_presence.py +0 -0
  92. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_prompt.py +0 -0
  93. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_quiet_audio.py +0 -0
  94. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_recorder_integration.py +0 -0
  95. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_retention.py +0 -0
  96. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_robustness.py +0 -0
  97. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_security_hardening.py +0 -0
  98. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_server.py +0 -0
  99. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_setup.py +0 -0
  100. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_streaming.py +0 -0
  101. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_suggest.py +0 -0
  102. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_tokens.py +0 -0
  103. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_transcriber.py +0 -0
  104. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_tts.py +0 -0
  105. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_typer_manager.py +0 -0
  106. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_vad.py +0 -0
  107. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_vocabulary.py +0 -0
  108. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_wordfreq.py +0 -0
  109. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/tests/test_worker_decode.py +0 -0
  110. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/__main__.py +0 -0
  111. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/app.py +0 -0
  112. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/audio_source.py +0 -0
  113. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/backends.py +0 -0
  114. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/configcmd.py +0 -0
  115. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/corrections.py +0 -0
  116. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/history.py +0 -0
  117. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/servicecmd.py +0 -0
  118. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/cli/vocab.py +0 -0
  119. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/clipboard_read.py +0 -0
  120. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/commands.py +0 -0
  121. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/consent.py +0 -0
  122. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/context_terms.py +0 -0
  123. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/__init__.py +0 -0
  124. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/bias.py +0 -0
  125. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/base.py +0 -0
  126. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
  127. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
  128. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/lanes.py +0 -0
  129. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/decoders/whispercpp.py +0 -0
  130. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/engine.py +0 -0
  131. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/core/pipeline.py +0 -0
  132. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/corrections.py +0 -0
  133. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
  134. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/data/__init__.py +0 -0
  135. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/demo.py +0 -0
  136. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/display.py +0 -0
  137. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/feedback.py +0 -0
  138. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/focus.py +0 -0
  139. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hints.py +0 -0
  140. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/history.py +0 -0
  141. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/__init__.py +0 -0
  142. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/base.py +0 -0
  143. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/chain.py +0 -0
  144. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/evdev.py +0 -0
  145. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/pynput_backend.py +0 -0
  146. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/hotkeys/socket_backend.py +0 -0
  147. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/__init__.py +0 -0
  148. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/engine.py +0 -0
  149. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/install.py +0 -0
  150. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/pending.py +0 -0
  151. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/session.py +0 -0
  152. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ibus/textlimit.py +0 -0
  153. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/idle_audio.py +0 -0
  154. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/__init__.py +0 -0
  155. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/augment.py +0 -0
  156. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/clean.py +0 -0
  157. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/importer.py +0 -0
  158. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/metrics.py +0 -0
  159. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/quiet.py +0 -0
  160. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/review.py +0 -0
  161. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/store.py +0 -0
  162. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/teacher.py +0 -0
  163. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/learn/train.py +0 -0
  164. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/llm.py +0 -0
  165. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/llm_api.py +0 -0
  166. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/mic_gain.py +0 -0
  167. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/models/__init__.py +0 -0
  168. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/models/silero_vad.onnx +0 -0
  169. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/numbers.py +0 -0
  170. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/pidlock.py +0 -0
  171. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/platform.py +0 -0
  172. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/postcorrect.py +0 -0
  173. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/power.py +0 -0
  174. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/presence.py +0 -0
  175. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/privacy.py +0 -0
  176. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/prompt.py +0 -0
  177. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/quiet_audio.py +0 -0
  178. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/recorder.py +0 -0
  179. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/retention.py +0 -0
  180. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/server/__init__.py +0 -0
  181. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/server/__main__.py +0 -0
  182. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/server/app.py +0 -0
  183. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/server/config.py +0 -0
  184. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/server/runtime.py +0 -0
  185. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/__init__.py +0 -0
  186. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/configfile.py +0 -0
  187. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/extras.py +0 -0
  188. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/hotkey.py +0 -0
  189. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/setup/ui.py +0 -0
  190. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/sounds/__init__.py +0 -0
  191. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/sounds/alert.wav +0 -0
  192. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/sounds/commit.wav +0 -0
  193. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/sounds/start.wav +0 -0
  194. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/sounds/stop.wav +0 -0
  195. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/streaming.py +0 -0
  196. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/suggest.py +0 -0
  197. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tokens.py +0 -0
  198. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tray/__init__.py +0 -0
  199. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tray/_icons.py +0 -0
  200. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tray/_indicator.py +0 -0
  201. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tray/_pystray.py +0 -0
  202. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/__init__.py +0 -0
  203. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/base.py +0 -0
  204. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/chain.py +0 -0
  205. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/edge_engine.py +0 -0
  206. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/espeak.py +0 -0
  207. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/piper_engine.py +0 -0
  208. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/tts/player.py +0 -0
  209. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/__init__.py +0 -0
  210. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/base.py +0 -0
  211. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/chain.py +0 -0
  212. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/clipboard.py +0 -0
  213. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/collecting.py +0 -0
  214. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/fcitx_bridge.py +0 -0
  215. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/ibus.py +0 -0
  216. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/manager.py +0 -0
  217. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/modifier_guard.py +0 -0
  218. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/paste.py +0 -0
  219. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/pynput_type.py +0 -0
  220. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/wtype.py +0 -0
  221. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/xdotool.py +0 -0
  222. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/typers/ydotool.py +0 -0
  223. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/ui.py +0 -0
  224. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/vad.py +0 -0
  225. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/vocab_stats.py +0 -0
  226. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/vocabulary.py +0 -0
  227. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/watchdog.py +0 -0
  228. {python_voiceio-1.10.0 → python_voiceio-1.12.0}/voiceio/wordfreq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 1.10.0
3
+ Version: 1.12.0
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -65,7 +65,7 @@ Dynamic: license-file
65
65
  **[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
66
66
 
67
67
  - **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
68
- - **Runs on your machine.** Whisper decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
68
+ - **Runs on your machine.** Whisper, or NVIDIA's Parakeet (about 2x faster, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
69
69
  - **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
70
70
 
71
71
  ## Install
@@ -87,7 +87,16 @@ pipx install 'python-voiceio[desktop]'
87
87
  voiceio setup # model, hotkey, autostart
88
88
  ```
89
89
 
90
- Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works and `voiceio doctor --fix` repairs what it can. NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
90
+ **Or as a Debian/Ubuntu package** (new; bundles everything, nothing to compile):
91
+
92
+ ```bash
93
+ # Debian / Ubuntu: download voiceio_*.deb from the latest release, then
94
+ sudo apt install ./voiceio_*.deb
95
+
96
+ voiceio setup
97
+ ```
98
+
99
+ Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works, `voiceio doctor --fix` repairs what it can, and `voiceio doctor --report` shares an anonymous install report on the [feedback board](https://voiceio.dev/#feedback) (it shows you the text and asks first). NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
91
100
 
92
101
  ## Make it yours
93
102
 
@@ -118,10 +127,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
118
127
 
119
128
  ## Roadmap
120
129
 
121
- - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
130
+ - **Parakeet by default for everyone**: it needs our sherpa-onnx fix ([#4084](https://github.com/k2-fsa/sherpa-onnx/pull/4084)) in an upstream release; until then `voiceio models sherpa && voiceio models use parakeet-tdt-0.6b-v2` installs a patched build ([why](CONTRIBUTING.md#model-choice-parakeet-where-it-can-be-biased-whisper-otherwise)).
122
131
  - **GPU on AMD and Intel**: let setup run a Vulkan whisper.cpp server for you.
123
132
  - **Typing without IBus on GNOME and KDE**: a libei backend.
124
- - **Distro packages**: AUR, .deb and .rpm.
133
+ - **Distro packages**: AUR (packaged, not yet published) and .rpm.
125
134
  - **One model everywhere**: sync your fine-tune across your machines.
126
135
 
127
136
  ## Contributing
@@ -130,7 +139,7 @@ Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the a
130
139
 
131
140
  ## Credits
132
141
 
133
- - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
142
+ - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
134
143
  - Ideas borrowed with thanks: modifier release, paste keys and media pausing from [Voxtype](https://github.com/peteonrails/voxtype); the landing page's look from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
135
144
 
136
145
  ## License
@@ -12,7 +12,7 @@
12
12
  **[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
13
13
 
14
14
  - **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
15
- - **Runs on your machine.** Whisper decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
15
+ - **Runs on your machine.** Whisper, or NVIDIA's Parakeet (about 2x faster, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
16
16
  - **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
17
17
 
18
18
  ## Install
@@ -34,7 +34,16 @@ pipx install 'python-voiceio[desktop]'
34
34
  voiceio setup # model, hotkey, autostart
35
35
  ```
36
36
 
37
- Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works and `voiceio doctor --fix` repairs what it can. NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
37
+ **Or as a Debian/Ubuntu package** (new; bundles everything, nothing to compile):
38
+
39
+ ```bash
40
+ # Debian / Ubuntu: download voiceio_*.deb from the latest release, then
41
+ sudo apt install ./voiceio_*.deb
42
+
43
+ voiceio setup
44
+ ```
45
+
46
+ Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works, `voiceio doctor --fix` repairs what it can, and `voiceio doctor --report` shares an anonymous install report on the [feedback board](https://voiceio.dev/#feedback) (it shows you the text and asks first). NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
38
47
 
39
48
  ## Make it yours
40
49
 
@@ -65,10 +74,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
65
74
 
66
75
  ## Roadmap
67
76
 
68
- - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
77
+ - **Parakeet by default for everyone**: it needs our sherpa-onnx fix ([#4084](https://github.com/k2-fsa/sherpa-onnx/pull/4084)) in an upstream release; until then `voiceio models sherpa && voiceio models use parakeet-tdt-0.6b-v2` installs a patched build ([why](CONTRIBUTING.md#model-choice-parakeet-where-it-can-be-biased-whisper-otherwise)).
69
78
  - **GPU on AMD and Intel**: let setup run a Vulkan whisper.cpp server for you.
70
79
  - **Typing without IBus on GNOME and KDE**: a libei backend.
71
- - **Distro packages**: AUR, .deb and .rpm.
80
+ - **Distro packages**: AUR (packaged, not yet published) and .rpm.
72
81
  - **One model everywhere**: sync your fine-tune across your machines.
73
82
 
74
83
  ## Contributing
@@ -77,7 +86,7 @@ Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the a
77
86
 
78
87
  ## Credits
79
88
 
80
- - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
89
+ - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
81
90
  - Ideas borrowed with thanks: modifier release, paste keys and media pausing from [Voxtype](https://github.com/peteonrails/voxtype); the landing page's look from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
82
91
 
83
92
  ## License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "1.10.0"
7
+ version = "1.12.0"
8
8
  description = "Voice dictation for Linux. Speak → text, locally, instantly."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 1.10.0
3
+ Version: 1.12.0
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -65,7 +65,7 @@ Dynamic: license-file
65
65
  **[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
66
66
 
67
67
  - **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
68
- - **Runs on your machine.** Whisper decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
68
+ - **Runs on your machine.** Whisper, or NVIDIA's Parakeet (about 2x faster, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
69
69
  - **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
70
70
 
71
71
  ## Install
@@ -87,7 +87,16 @@ pipx install 'python-voiceio[desktop]'
87
87
  voiceio setup # model, hotkey, autostart
88
88
  ```
89
89
 
90
- Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works and `voiceio doctor --fix` repairs what it can. NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
90
+ **Or as a Debian/Ubuntu package** (new; bundles everything, nothing to compile):
91
+
92
+ ```bash
93
+ # Debian / Ubuntu: download voiceio_*.deb from the latest release, then
94
+ sudo apt install ./voiceio_*.deb
95
+
96
+ voiceio setup
97
+ ```
98
+
99
+ Then press your hotkey, speak, and press it again (or hold it while you speak). `voiceio doctor` shows what works, `voiceio doctor --fix` repairs what it can, and `voiceio doctor --report` shares an anonymous install report on the [feedback board](https://voiceio.dev/#feedback) (it shows you the text and asks first). NixOS, other extras and installing from source: [docs/linux.md](docs/linux.md). Agent runbook: [INSTALL.md](INSTALL.md).
91
100
 
92
101
  ## Make it yours
93
102
 
@@ -118,10 +127,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
118
127
 
119
128
  ## Roadmap
120
129
 
121
- - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
130
+ - **Parakeet by default for everyone**: it needs our sherpa-onnx fix ([#4084](https://github.com/k2-fsa/sherpa-onnx/pull/4084)) in an upstream release; until then `voiceio models sherpa && voiceio models use parakeet-tdt-0.6b-v2` installs a patched build ([why](CONTRIBUTING.md#model-choice-parakeet-where-it-can-be-biased-whisper-otherwise)).
122
131
  - **GPU on AMD and Intel**: let setup run a Vulkan whisper.cpp server for you.
123
132
  - **Typing without IBus on GNOME and KDE**: a libei backend.
124
- - **Distro packages**: AUR, .deb and .rpm.
133
+ - **Distro packages**: AUR (packaged, not yet published) and .rpm.
125
134
  - **One model everywhere**: sync your fine-tune across your machines.
126
135
 
127
136
  ## Contributing
@@ -130,7 +139,7 @@ Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the a
130
139
 
131
140
  ## Credits
132
141
 
133
- - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
142
+ - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
134
143
  - Ideas borrowed with thanks: modifier release, paste keys and media pausing from [Voxtype](https://github.com/peteonrails/voxtype); the landing page's look from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
135
144
 
136
145
  ## License
@@ -65,12 +65,14 @@ tests/test_presence.py
65
65
  tests/test_prompt.py
66
66
  tests/test_quiet_audio.py
67
67
  tests/test_recorder_integration.py
68
+ tests/test_report.py
68
69
  tests/test_retention.py
69
70
  tests/test_robustness.py
70
71
  tests/test_security_hardening.py
71
72
  tests/test_server.py
72
73
  tests/test_service.py
73
74
  tests/test_setup.py
75
+ tests/test_sherpa.py
74
76
  tests/test_streaming.py
75
77
  tests/test_suggest.py
76
78
  tests/test_tokens.py
@@ -114,6 +116,7 @@ voiceio/privacy.py
114
116
  voiceio/prompt.py
115
117
  voiceio/quiet_audio.py
116
118
  voiceio/recorder.py
119
+ voiceio/report.py
117
120
  voiceio/retention.py
118
121
  voiceio/service.py
119
122
  voiceio/streaming.py
@@ -142,6 +145,7 @@ voiceio/core/pipeline.py
142
145
  voiceio/core/decoders/__init__.py
143
146
  voiceio/core/decoders/base.py
144
147
  voiceio/core/decoders/catalog.py
148
+ voiceio/core/decoders/fetch.py
145
149
  voiceio/core/decoders/lanes.py
146
150
  voiceio/core/decoders/sherpa.py
147
151
  voiceio/core/decoders/whispercpp.py
@@ -98,7 +98,7 @@ def test_example_config_matches_defaults():
98
98
  import dataclasses
99
99
  from pathlib import Path
100
100
 
101
- from voiceio.config import Config
101
+ from voiceio.config import Config, ModelConfig
102
102
  example = tomllib.loads(
103
103
  (Path(__file__).parent.parent / "config.example.toml").read_text())
104
104
  cfg = Config()
@@ -107,6 +107,11 @@ def test_example_config_matches_defaults():
107
107
  assert sec.name in example, f"[{sec.name}] missing from example"
108
108
  for f in dataclasses.fields(defaults):
109
109
  assert f.name in example[sec.name], f"{sec.name}.{f.name} missing"
110
+ if (sec.name, f.name) == ("model", "name"):
111
+ # "auto" resolves to the default decoder on construction
112
+ assert example["model"]["name"] == "auto"
113
+ assert ModelConfig(name="auto").name == defaults.name
114
+ continue
110
115
  assert example[sec.name][f.name] == getattr(defaults, f.name), \
111
116
  f"{sec.name}.{f.name}: example differs from default"
112
117
  for name, section in example.items():
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
 
4
4
  import io
5
5
  import json
6
- import sys
7
6
  import types
8
7
 
9
8
  import numpy as np
@@ -54,61 +53,6 @@ def test_whispercpp_errors_are_decode_errors(monkeypatch):
54
53
  dec.decode(np.zeros(1600, dtype=np.float32))
55
54
 
56
55
 
57
- def _fake_sherpa(monkeypatch, created):
58
- class Stream:
59
- def accept_waveform(self, sr, a):
60
- self.n = len(a)
61
-
62
- @property
63
- def result(self):
64
- return types.SimpleNamespace(text=" hello world ")
65
-
66
- class Rec:
67
- def create_stream(self):
68
- return Stream()
69
-
70
- def decode_stream(self, s):
71
- pass
72
-
73
- class OfflineRecognizer:
74
- @staticmethod
75
- def from_transducer(**kw):
76
- created.append(kw)
77
- return Rec()
78
-
79
- monkeypatch.setitem(sys.modules, "sherpa_onnx",
80
- types.SimpleNamespace(OfflineRecognizer=OfflineRecognizer))
81
-
82
-
83
- def _model_dir(tmp_path):
84
- for f in ("encoder.int8.onnx", "decoder.int8.onnx", "joiner.int8.onnx", "tokens.txt"):
85
- (tmp_path / f).write_text("x")
86
- return tmp_path
87
-
88
-
89
- def test_sherpa_greedy_declares_no_bias(monkeypatch, tmp_path):
90
- created = []
91
- _fake_sherpa(monkeypatch, created)
92
- dec = build_decoder(ModelConfig(name=f"sherpa:{_model_dir(tmp_path)}"))
93
- assert dec.capabilities.bias == BiasMode.NONE
94
- assert dec.decode(np.zeros(16000, dtype=np.float32),
95
- bias=Bias(hotwords="Kalshi")).text == "hello world"
96
- assert "hotwords_file" not in created[-1]
97
-
98
-
99
- def test_sherpa_mbs_passes_unbounded_hotwords(monkeypatch, tmp_path):
100
- created = []
101
- _fake_sherpa(monkeypatch, created)
102
- dec = build_decoder(ModelConfig(name=f"sherpa:{_model_dir(tmp_path)}",
103
- decoding="modified_beam_search"))
104
- assert dec.capabilities.bias == BiasMode.UNBOUNDED
105
- dec.decode(np.zeros(16000, dtype=np.float32), bias=Bias(hotwords="Kalshi, HyperNEAT"))
106
- kw = created[-1]
107
- assert kw["decoding_method"] == "modified_beam_search"
108
- with open(kw["hotwords_file"]) as f:
109
- assert f.read().split() == ["Kalshi", "HyperNEAT"]
110
-
111
-
112
56
  def test_lanes_share_the_thread_budget(monkeypatch):
113
57
  import voiceio.core.decoders.faster_whisper as tr
114
58
  from voiceio.core.decoders.faster_whisper.worker import auto_cpu_threads
@@ -70,6 +70,19 @@ class TestStripDisfluencies:
70
70
  "First para.\n\nSecond para."
71
71
  assert "\n\n" in strip_disfluencies("First para.\n\num\n\nSecond para.")
72
72
 
73
+ def test_filler_sentence_goes_with_its_full_stop(self):
74
+ # Parakeet writes a filler as a sentence of its own.
75
+ assert strip_disfluencies("escape them. Um. So on") == "escape them. So on"
76
+ assert strip_disfluencies("the scope of this? Um. I want") == "the scope of this? I want"
77
+ assert strip_disfluencies("Um. I want it") == "I want it"
78
+
79
+ def test_word_after_a_sentence_opening_filler_is_capitalized(self):
80
+ # Else "those. so I" reads as a false stop and cleanup makes it a comma.
81
+ assert strip_disfluencies("most of those. Um so I") == "most of those. So I"
82
+ assert strip_disfluencies("might be. Um uh yeah, and") == "might be. Yeah, and"
83
+ assert cleanup(strip_disfluencies("most of those. Um so I")) == "Most of those. So I"
84
+ assert strip_disfluencies("it was um uh good") == "it was good"
85
+
73
86
  def test_uh_huh_removed_whole(self):
74
87
  # Regression: ordering bug once stranded "-huh".
75
88
  assert strip_disfluencies("uh-huh right") == "right"
@@ -0,0 +1,314 @@
1
+ """`voiceio doctor --report` and the post-setup feedback line: what goes public."""
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ import json
6
+ import sys
7
+
8
+ import pytest
9
+
10
+ from voiceio import report
11
+ from voiceio.config import Config
12
+ from voiceio.health import BackendStatus, HealthReport
13
+ from voiceio.platform import Platform
14
+
15
+ FAKE_HOME = "/home/zelda-tester"
16
+ FAKE_USER = "zelda-tester"
17
+ FAKE_HOST = "zeldas-thinkpad"
18
+
19
+
20
+ @pytest.fixture
21
+ def identity(monkeypatch):
22
+ """A fake user, home and hostname that must never reach a report."""
23
+ monkeypatch.setenv("HOME", FAKE_HOME)
24
+ monkeypatch.setenv("USER", FAKE_USER)
25
+ monkeypatch.setattr(report.getpass, "getuser", lambda: FAKE_USER)
26
+ monkeypatch.setattr(report.socket, "gethostname", lambda: FAKE_HOST)
27
+ monkeypatch.setattr(report._py_platform, "node", lambda: FAKE_HOST)
28
+ monkeypatch.setattr(report, "_homes", lambda: [FAKE_HOME])
29
+
30
+
31
+ def _health(**kw) -> HealthReport:
32
+ defaults = dict(
33
+ platform=Platform(os="linux", display_server="wayland", desktop="gnome"),
34
+ hotkey_backends=[BackendStatus("evdev", True)],
35
+ typer_backends=[BackendStatus("ibus", True), BackendStatus("ydotool", False, "not installed")],
36
+ audio_ok=True, cli_in_path=True, tray_ok=True,
37
+ )
38
+ defaults.update(kw)
39
+ return HealthReport(**defaults)
40
+
41
+
42
+ def _assert_private(text: str) -> None:
43
+ for secret in (FAKE_HOME, FAKE_USER, FAKE_HOST, "zelda"):
44
+ assert secret not in text.lower(), (secret, text)
45
+
46
+
47
+ # ── scrubbing ──
48
+
49
+ def test_scrub_removes_home_user_host_and_addresses(identity):
50
+ raw = (f"cannot open {FAKE_HOME}/.config/voiceio/x.toml as {FAKE_USER}@{FAKE_HOST}; "
51
+ "also /home/someoneelse/.cache, 192.168.1.40, fe80:0:0:0:1:2:3:4, "
52
+ "aa:bb:cc:dd:ee:ff, /dev/input/by-id/usb-Logitech_K120_SN12345-event-kbd, "
53
+ "ZELDA-TESTER shouted")
54
+ out = report.scrub(raw)
55
+ _assert_private(out)
56
+ for leaked in ("someoneelse", "192.168", "fe80", "aa:bb", "SN12345", "Logitech"):
57
+ assert leaked not in out
58
+ assert "~/.config/voiceio/x.toml" in out
59
+
60
+
61
+ def test_scrub_keeps_versions_and_backend_names(identity):
62
+ out = report.scrub("voiceio 1.10.0.2 · ibus · python 3.12.3")
63
+ assert out == "voiceio 1.10.0.2 · ibus · python 3.12.3"
64
+
65
+
66
+ def test_report_scrubs_check_messages_and_comment(identity):
67
+ health = _health(
68
+ hotkey_backends=[BackendStatus("evdev", False, f"{FAKE_HOME}/dev denied for {FAKE_USER}")],
69
+ audio_ok=False, audio_reason=f"device on {FAKE_HOST} busy",
70
+ warnings=[f"see {FAKE_HOME}/.local/state/voiceio/voiceio.log"],
71
+ )
72
+ text = report.build_report(health, comment=f"broke in {FAKE_HOME}/src, I'm {FAKE_USER}",
73
+ cfg=Config())
74
+ _assert_private(text)
75
+ assert "failed: hotkey evdev: ~/dev denied" in text
76
+ assert "audio: device on <host> busy" in text
77
+ assert "comment: broke in ~/src" in text
78
+
79
+
80
+ def test_report_contains_only_backend_and_model_names(identity):
81
+ cfg = Config()
82
+ cfg.model.name = f"{FAKE_HOME}/models/my-tune"
83
+ cfg.model.vocabulary_file = f"{FAKE_HOME}/vocab.txt"
84
+ cfg.cloud.api_key = "sk-secret"
85
+ cfg.hotkey.key = "ctrl+alt+zelda"
86
+ text = report.build_report(_health(), cfg=cfg)
87
+ _assert_private(text)
88
+ assert "sk-secret" not in text and "vocab" not in text and "ctrl+alt" not in text
89
+ assert "model: custom (local path)" in text
90
+ assert "hotkey evdev" in text and "typer ibus" in text
91
+ assert "fallbacks unavailable: ydotool" in text
92
+ assert "failed: none" in text
93
+ assert text.splitlines()[0].startswith("voiceio ")
94
+
95
+
96
+ def test_report_lists_failures_and_warnings():
97
+ health = _health(typer_backends=[BackendStatus("ibus", False, "daemon down")],
98
+ cli_in_path=False, tray_ok=False, tray_reason="no AppIndicator")
99
+ text = report.build_report(health, missing_deps=["portaudio"], perm_issues=2, cfg=Config())
100
+ assert "typer none" in text
101
+ assert "typer ibus: daemon down" in text
102
+ assert "missing system deps" in text
103
+ assert "cli not in PATH" in text and "tray: no AppIndicator" in text
104
+ assert "insecure permissions on 2 file(s)" in text
105
+
106
+
107
+ def test_comment_is_one_bounded_line():
108
+ text = report.build_report(_health(), comment="a\nb\tc " + "x" * 1000, cfg=Config())
109
+ line = next(ln for ln in text.splitlines() if ln.startswith("comment:"))
110
+ assert line.startswith("comment: a b c x")
111
+ assert len(line) <= len("comment: ") + report.MAX_COMMENT
112
+
113
+
114
+ # ── posting ──
115
+
116
+ class _Resp(io.BytesIO):
117
+ def __enter__(self):
118
+ return self
119
+
120
+ def __exit__(self, *a):
121
+ return False
122
+
123
+
124
+ def test_post_success_reads_receipt(monkeypatch):
125
+ seen = []
126
+
127
+ def fake(url, timeout):
128
+ seen.append((url, timeout))
129
+ return _Resp(json.dumps({"ok": True, "receipt": {"id": "r123"}}).encode())
130
+
131
+ monkeypatch.setattr(report, "_urlopen", fake)
132
+ assert report.post("hello world") == (True, "r123")
133
+ url, timeout = seen[0]
134
+ assert url.startswith(report.POST_URL + "?")
135
+ assert "text=hello+world" in url and "format=json" in url and "request_id=voiceio-" in url
136
+ assert timeout <= 10
137
+
138
+
139
+ @pytest.mark.parametrize("body", [b'{"ok": false, "error": "rate limited"}', b"[]", b"not json",
140
+ b'{"ok": true}'])
141
+ def test_post_bad_response_is_failure(monkeypatch, body):
142
+ monkeypatch.setattr(report, "_urlopen", lambda url, timeout: _Resp(body))
143
+ ok, why = report.post("x")
144
+ assert not ok and why
145
+
146
+
147
+ def test_post_network_error_never_raises():
148
+ # conftest refuses every connection: this is the offline path.
149
+ ok, why = report.post("x")
150
+ assert not ok and "ConnectionRefused" in why
151
+
152
+
153
+ # ── confirm / --yes / no TTY ──
154
+
155
+ @pytest.fixture
156
+ def posted(monkeypatch):
157
+ calls: list[str] = []
158
+ monkeypatch.setattr(report, "post", lambda text: (calls.append(text), (True, "r1"))[1])
159
+ return calls
160
+
161
+
162
+ def _tty(monkeypatch, tty: bool):
163
+ monkeypatch.setattr(report, "_interactive", lambda: tty)
164
+
165
+
166
+ def test_share_asks_and_posts_on_yes(monkeypatch, capsys, posted):
167
+ _tty(monkeypatch, True)
168
+ monkeypatch.setattr("builtins.input", lambda prompt: "y")
169
+ assert report.share("the report") is True
170
+ out = capsys.readouterr().out
171
+ assert "PUBLICLY" in out and "the report" in out and report.BOARD_URL in out
172
+ assert posted == ["the report"]
173
+
174
+
175
+ @pytest.mark.parametrize("answer", ["", "n", "no", "whatever"])
176
+ def test_share_defaults_to_no(monkeypatch, capsys, posted, answer):
177
+ _tty(monkeypatch, True)
178
+ monkeypatch.setattr("builtins.input", lambda prompt: answer)
179
+ assert report.share("r") is False
180
+ assert posted == []
181
+
182
+
183
+ def test_share_eof_is_no(monkeypatch, posted):
184
+ _tty(monkeypatch, True)
185
+
186
+ def eof(prompt):
187
+ raise EOFError
188
+
189
+ monkeypatch.setattr("builtins.input", eof)
190
+ assert report.share("r") is False and posted == []
191
+
192
+
193
+ def test_share_without_tty_prints_but_never_posts(monkeypatch, capsys, posted):
194
+ _tty(monkeypatch, False)
195
+ monkeypatch.setattr("builtins.input", lambda p: pytest.fail("must not prompt"))
196
+ assert report.share("the report") is False
197
+ out = capsys.readouterr().out
198
+ assert "the report" in out and "--yes" in out
199
+ assert posted == []
200
+
201
+
202
+ def test_share_yes_skips_prompt(monkeypatch, posted):
203
+ _tty(monkeypatch, False)
204
+ monkeypatch.setattr("builtins.input", lambda p: pytest.fail("must not prompt"))
205
+ assert report.share("r", yes=True) is True and posted == ["r"]
206
+
207
+
208
+ def test_share_failure_is_reported_not_raised(monkeypatch, capsys):
209
+ assert report.share("r", yes=True) is False
210
+ assert "Could not confirm the post" in capsys.readouterr().out
211
+
212
+
213
+ # ── CLI wiring ──
214
+
215
+ @pytest.fixture
216
+ def doctor_env(monkeypatch, identity):
217
+ from voiceio import config, health
218
+ from voiceio import platform as plat
219
+ monkeypatch.setattr(health, "check_health", lambda: _health(
220
+ warnings=[f"something at {FAKE_HOME}/.config"]))
221
+ monkeypatch.setattr(health, "format_report", lambda r: "DOCTOR OUTPUT")
222
+ monkeypatch.setattr(health, "usage_findings", lambda: [])
223
+ monkeypatch.setattr(plat, "check_system_deps", lambda: [])
224
+ monkeypatch.setattr(config, "check_permissions", lambda: [])
225
+ monkeypatch.setattr("voiceio.hints.hint", lambda *a, **k: None)
226
+
227
+
228
+ def _doctor(monkeypatch, capsys, *argv):
229
+ from voiceio.cli import main
230
+ monkeypatch.setattr(sys, "argv", ["voiceio", "doctor", *argv])
231
+ with pytest.raises(SystemExit):
232
+ main()
233
+ return capsys.readouterr().out
234
+
235
+
236
+ def test_doctor_report_yes_posts_scrubbed_comment(monkeypatch, capsys, doctor_env, posted):
237
+ out = _doctor(monkeypatch, capsys, "--report", "--yes",
238
+ "--comment", f"worked, {FAKE_USER} here")
239
+ assert len(posted) == 1
240
+ _assert_private(posted[0])
241
+ _assert_private(out)
242
+ assert "comment: worked, <user> here" in posted[0]
243
+ assert report.BOARD_URL in out
244
+
245
+
246
+ def test_doctor_report_without_tty_or_yes_does_not_post(monkeypatch, capsys, doctor_env, posted):
247
+ _tty(monkeypatch, False)
248
+ out = _doctor(monkeypatch, capsys, "--report")
249
+ assert posted == []
250
+ assert "voiceio " in out and "Not posted" in out
251
+
252
+
253
+ def test_doctor_without_report_never_posts(monkeypatch, capsys, doctor_env, posted):
254
+ out = _doctor(monkeypatch, capsys)
255
+ assert posted == [] and "PUBLICLY" not in out
256
+
257
+
258
+ # ── after setup ──
259
+
260
+ def test_after_setup_enter_skips_silently(monkeypatch, capsys, posted):
261
+ _tty(monkeypatch, True)
262
+ monkeypatch.setattr("builtins.input", lambda p: "")
263
+ monkeypatch.setattr(report, "gather", lambda c: pytest.fail("no checks on skip"))
264
+ report.offer_after_setup()
265
+ assert posted == [] and "PUBLICLY" not in capsys.readouterr().out
266
+
267
+
268
+ def test_after_setup_comment_is_shown_confirmed_and_posted(monkeypatch, capsys, posted):
269
+ _tty(monkeypatch, True)
270
+ answers = iter(["went fine", "y"])
271
+ monkeypatch.setattr("builtins.input", lambda p: next(answers))
272
+ monkeypatch.setattr(report, "gather", lambda c: (None, f"voiceio x report\ncomment: {c}"))
273
+ report.offer_after_setup()
274
+ assert posted == ["voiceio x report\ncomment: went fine"]
275
+
276
+
277
+ def test_after_setup_never_raises(monkeypatch, capsys):
278
+ _tty(monkeypatch, True)
279
+ monkeypatch.setattr("builtins.input", lambda p: "it broke")
280
+
281
+ def boom(c):
282
+ raise RuntimeError("probe exploded")
283
+
284
+ monkeypatch.setattr(report, "gather", boom)
285
+ report.offer_after_setup() # must not raise
286
+ assert "Skipped" in capsys.readouterr().out
287
+
288
+
289
+ def test_after_setup_needs_a_terminal(monkeypatch, posted):
290
+ _tty(monkeypatch, False)
291
+ monkeypatch.setattr("builtins.input", lambda p: pytest.fail("must not prompt"))
292
+ report.offer_after_setup()
293
+ assert posted == []
294
+
295
+
296
+ def test_setup_wizard_offers_the_report(monkeypatch):
297
+ from voiceio.cli import main
298
+ calls = []
299
+ monkeypatch.setattr("voiceio.setup.run_wizard", lambda: calls.append("wizard"))
300
+ monkeypatch.setattr(report, "offer_after_setup", lambda: calls.append("offer"))
301
+ monkeypatch.setattr("voiceio.hints.hint", lambda *a, **k: None)
302
+ monkeypatch.setattr(sys, "argv", ["voiceio", "setup"])
303
+ main()
304
+ assert calls == ["wizard", "offer"]
305
+
306
+
307
+ def test_network_guard_blocks_real_hosts():
308
+ import socket
309
+ s = socket.socket()
310
+ try:
311
+ with pytest.raises(ConnectionRefusedError):
312
+ s.connect(("93.184.216.34", 443))
313
+ finally:
314
+ s.close()
@@ -25,6 +25,28 @@ def test_install_symlinks_relinks_and_drops_legacy(monkeypatch, tmp_path):
25
25
  assert not (local_bin / "voiceio-doctor").is_symlink()
26
26
 
27
27
 
28
+ def test_packaged_install_links_nothing_and_serves_its_own_bin(monkeypatch, tmp_path):
29
+ prefix = tmp_path / "usr-lib-voiceio"
30
+ (prefix / "bin").mkdir(parents=True)
31
+ (prefix / "bin" / "voiceio").write_text("#!/bin/sh\n")
32
+ (prefix / service.PACKAGED_MARKER).write_text("deb\n")
33
+ local_bin = tmp_path / "local-bin"
34
+ monkeypatch.setattr(service, "LOCAL_BIN", local_bin)
35
+ monkeypatch.setattr(sys, "prefix", str(prefix))
36
+
37
+ assert service.packaged_by() == "deb"
38
+ assert service.package_remove_cmd() == "sudo apt remove voiceio"
39
+ service.install_symlinks()
40
+ assert not local_bin.exists()
41
+ # The unit's ExecStart is the package's own path, stable across upgrades.
42
+ assert service._find_voiceio_bin() == str((prefix / "bin" / "voiceio").resolve())
43
+
44
+
45
+ def test_unpackaged_install_has_no_package(monkeypatch, tmp_path):
46
+ monkeypatch.setattr(sys, "prefix", str(tmp_path))
47
+ assert service.packaged_by() is None and service.package_remove_cmd() is None
48
+
49
+
28
50
  def test_learn_timer_runs_maintain_at_idle_priority(monkeypatch, tmp_path):
29
51
  calls = []
30
52
  monkeypatch.setattr(service, "has_systemd", lambda: True)