python-voiceio 1.10.0__tar.gz → 1.11.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 (225) hide show
  1. {python_voiceio-1.10.0/python_voiceio.egg-info → python_voiceio-1.11.0}/PKG-INFO +12 -3
  2. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/README.md +11 -2
  3. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/pyproject.toml +1 -1
  4. {python_voiceio-1.10.0 → python_voiceio-1.11.0/python_voiceio.egg-info}/PKG-INFO +12 -3
  5. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/SOURCES.txt +2 -0
  6. python_voiceio-1.11.0/tests/test_report.py +314 -0
  7. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_service.py +22 -0
  8. python_voiceio-1.11.0/voiceio/__init__.py +1 -0
  9. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/__init__.py +2 -0
  10. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/doctor.py +24 -0
  11. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/uninstall.py +5 -1
  12. python_voiceio-1.11.0/voiceio/report.py +278 -0
  13. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/service.py +31 -0
  14. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/steps.py +7 -3
  15. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/system.py +7 -0
  16. python_voiceio-1.10.0/voiceio/__init__.py +0 -1
  17. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/LICENSE +0 -0
  18. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
  19. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/entry_points.txt +0 -0
  20. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/requires.txt +0 -0
  21. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/top_level.txt +0 -0
  22. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/setup.cfg +0 -0
  23. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_app_wiring.py +0 -0
  24. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_audio_quality.py +0 -0
  25. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_audio_source.py +0 -0
  26. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_backend_probes.py +0 -0
  27. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_cancel.py +0 -0
  28. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_cli.py +0 -0
  29. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_clipboard_read.py +0 -0
  30. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_clipboard_typer.py +0 -0
  31. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_commands.py +0 -0
  32. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_concurrency_lockdown.py +0 -0
  33. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_config.py +0 -0
  34. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_context_terms.py +0 -0
  35. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_corrections.py +0 -0
  36. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_dead_mic.py +0 -0
  37. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_decoders.py +0 -0
  38. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_engine.py +0 -0
  39. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_evdev_uaccess.py +0 -0
  40. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_fallback.py +0 -0
  41. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_fcitx_bridge.py +0 -0
  42. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_feedback_stream.py +0 -0
  43. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_focus.py +0 -0
  44. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_health.py +0 -0
  45. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_hints.py +0 -0
  46. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_history.py +0 -0
  47. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_hotkey_modes.py +0 -0
  48. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_engine_limits.py +0 -0
  49. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_engine_listener.py +0 -0
  50. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_pending.py +0 -0
  51. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_pids.py +0 -0
  52. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_ping.py +0 -0
  53. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_session.py +0 -0
  54. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_textlimit.py +0 -0
  55. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_typer.py +0 -0
  56. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_idle_audio.py +0 -0
  57. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_clean.py +0 -0
  58. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_import.py +0 -0
  59. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_language.py +0 -0
  60. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_metrics.py +0 -0
  61. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_pipeline.py +0 -0
  62. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_quiet.py +0 -0
  63. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_resume.py +0 -0
  64. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_review.py +0 -0
  65. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_targets.py +0 -0
  66. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_llm.py +0 -0
  67. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_llm_api.py +0 -0
  68. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_mic_gain.py +0 -0
  69. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_modifier_guard.py +0 -0
  70. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_numbers.py +0 -0
  71. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_paste_last.py +0 -0
  72. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_pipeline.py +0 -0
  73. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_platform.py +0 -0
  74. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_postcorrect.py +0 -0
  75. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_postprocess.py +0 -0
  76. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_prebuffer.py +0 -0
  77. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_presence.py +0 -0
  78. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_prompt.py +0 -0
  79. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_quiet_audio.py +0 -0
  80. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_recorder_integration.py +0 -0
  81. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_retention.py +0 -0
  82. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_robustness.py +0 -0
  83. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_security_hardening.py +0 -0
  84. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_server.py +0 -0
  85. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_setup.py +0 -0
  86. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_streaming.py +0 -0
  87. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_suggest.py +0 -0
  88. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_tokens.py +0 -0
  89. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_transcriber.py +0 -0
  90. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_tts.py +0 -0
  91. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_typer_manager.py +0 -0
  92. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_vad.py +0 -0
  93. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_vocabulary.py +0 -0
  94. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_wordfreq.py +0 -0
  95. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_worker_decode.py +0 -0
  96. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/__main__.py +0 -0
  97. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/app.py +0 -0
  98. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/audio_source.py +0 -0
  99. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/backends.py +0 -0
  100. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/configcmd.py +0 -0
  101. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/corrections.py +0 -0
  102. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/history.py +0 -0
  103. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/learn.py +0 -0
  104. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/models.py +0 -0
  105. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/servicecmd.py +0 -0
  106. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/vocab.py +0 -0
  107. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/clipboard_read.py +0 -0
  108. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/commands.py +0 -0
  109. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/config.py +0 -0
  110. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/consent.py +0 -0
  111. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/context_terms.py +0 -0
  112. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/__init__.py +0 -0
  113. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/bias.py +0 -0
  114. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/__init__.py +0 -0
  115. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/base.py +0 -0
  116. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/catalog.py +0 -0
  117. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
  118. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
  119. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/lanes.py +0 -0
  120. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/sherpa.py +0 -0
  121. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/whispercpp.py +0 -0
  122. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/engine.py +0 -0
  123. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/pipeline.py +0 -0
  124. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/corrections.py +0 -0
  125. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
  126. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/data/__init__.py +0 -0
  127. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/demo.py +0 -0
  128. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/display.py +0 -0
  129. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/feedback.py +0 -0
  130. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/focus.py +0 -0
  131. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/health.py +0 -0
  132. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hints.py +0 -0
  133. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/history.py +0 -0
  134. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/__init__.py +0 -0
  135. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/base.py +0 -0
  136. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/chain.py +0 -0
  137. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/evdev.py +0 -0
  138. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/pynput_backend.py +0 -0
  139. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/socket_backend.py +0 -0
  140. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/__init__.py +0 -0
  141. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/engine.py +0 -0
  142. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/install.py +0 -0
  143. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/pending.py +0 -0
  144. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/session.py +0 -0
  145. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/textlimit.py +0 -0
  146. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/idle_audio.py +0 -0
  147. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/__init__.py +0 -0
  148. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/augment.py +0 -0
  149. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/clean.py +0 -0
  150. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/evaluate.py +0 -0
  151. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/importer.py +0 -0
  152. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/metrics.py +0 -0
  153. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/quiet.py +0 -0
  154. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/review.py +0 -0
  155. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/store.py +0 -0
  156. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/teacher.py +0 -0
  157. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/train.py +0 -0
  158. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/llm.py +0 -0
  159. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/llm_api.py +0 -0
  160. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/mic_gain.py +0 -0
  161. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/models/__init__.py +0 -0
  162. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/models/silero_vad.onnx +0 -0
  163. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/numbers.py +0 -0
  164. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/pidlock.py +0 -0
  165. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/platform.py +0 -0
  166. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/postcorrect.py +0 -0
  167. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/postprocess.py +0 -0
  168. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/power.py +0 -0
  169. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/presence.py +0 -0
  170. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/privacy.py +0 -0
  171. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/prompt.py +0 -0
  172. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/quiet_audio.py +0 -0
  173. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/recorder.py +0 -0
  174. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/retention.py +0 -0
  175. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/__init__.py +0 -0
  176. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/__main__.py +0 -0
  177. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/app.py +0 -0
  178. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/config.py +0 -0
  179. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/runtime.py +0 -0
  180. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/__init__.py +0 -0
  181. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/configfile.py +0 -0
  182. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/extras.py +0 -0
  183. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/hotkey.py +0 -0
  184. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/noninteractive.py +0 -0
  185. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/speech.py +0 -0
  186. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/ui.py +0 -0
  187. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/__init__.py +0 -0
  188. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/alert.wav +0 -0
  189. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/commit.wav +0 -0
  190. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/start.wav +0 -0
  191. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/stop.wav +0 -0
  192. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/streaming.py +0 -0
  193. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/suggest.py +0 -0
  194. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tokens.py +0 -0
  195. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/__init__.py +0 -0
  196. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_icons.py +0 -0
  197. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_indicator.py +0 -0
  198. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_pystray.py +0 -0
  199. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/__init__.py +0 -0
  200. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/base.py +0 -0
  201. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/chain.py +0 -0
  202. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/edge_engine.py +0 -0
  203. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/espeak.py +0 -0
  204. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/piper_engine.py +0 -0
  205. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/player.py +0 -0
  206. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/__init__.py +0 -0
  207. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/base.py +0 -0
  208. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/chain.py +0 -0
  209. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/clipboard.py +0 -0
  210. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/collecting.py +0 -0
  211. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/fcitx_bridge.py +0 -0
  212. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/ibus.py +0 -0
  213. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/manager.py +0 -0
  214. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/modifier_guard.py +0 -0
  215. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/paste.py +0 -0
  216. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/pynput_type.py +0 -0
  217. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/wtype.py +0 -0
  218. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/xdotool.py +0 -0
  219. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/ydotool.py +0 -0
  220. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ui.py +0 -0
  221. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vad.py +0 -0
  222. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vocab_stats.py +0 -0
  223. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vocabulary.py +0 -0
  224. {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/watchdog.py +0 -0
  225. {python_voiceio-1.10.0 → python_voiceio-1.11.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.11.0
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -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
 
@@ -121,7 +130,7 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
121
130
  - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
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
@@ -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
 
@@ -68,7 +77,7 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
68
77
  - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
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
@@ -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.11.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.11.0
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -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
 
@@ -121,7 +130,7 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
121
130
  - **Unbounded vocabulary**: a decoder whose biasing has no token cap (see [model choice](CONTRIBUTING.md#model-choice-why-whisper-and-what-would-change-it)).
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
@@ -65,6 +65,7 @@ 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
@@ -114,6 +115,7 @@ voiceio/privacy.py
114
115
  voiceio/prompt.py
115
116
  voiceio/quiet_audio.py
116
117
  voiceio/recorder.py
118
+ voiceio/report.py
117
119
  voiceio/retention.py
118
120
  voiceio/service.py
119
121
  voiceio/streaming.py
@@ -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)
@@ -0,0 +1 @@
1
+ __version__ = "1.11.0"
@@ -158,6 +158,8 @@ def _cmd_setup(args: argparse.Namespace) -> None:
158
158
 
159
159
  from voiceio.setup import run_wizard
160
160
  run_wizard()
161
+ from voiceio.report import offer_after_setup
162
+ offer_after_setup()
161
163
  from voiceio.hints import hint
162
164
  hint("test", "Run 'voiceio doctor --live' to try a quick recording")
163
165
 
@@ -10,6 +10,13 @@ def register(sub: argparse._SubParsersAction) -> None:
10
10
  p.add_argument("--fix", action="store_true", help="Try to fix what is broken")
11
11
  p.add_argument("--live", action="store_true",
12
12
  help="Also record from the mic and transcribe it")
13
+ p.add_argument("--report", action="store_true",
14
+ help="Post a short anonymous report to the public feedback board "
15
+ "(shows it and asks first)")
16
+ p.add_argument("--comment", type=str, default="",
17
+ help="With --report: one line about how it went")
18
+ p.add_argument("--yes", "-y", action="store_true",
19
+ help="With --report: post without asking (for agents, after asking the user)")
13
20
  p.set_defaults(func=_cmd_doctor)
14
21
 
15
22
 
@@ -79,6 +86,8 @@ def _cmd_doctor(args: argparse.Namespace) -> None:
79
86
  print("Run 'voiceio doctor --fix' to tighten file permissions.")
80
87
  from voiceio.hints import hint
81
88
  hint("vocab_suggest", "Run 'voiceio vocab suggest' to find names it keeps mishearing")
89
+ if args.report:
90
+ _share_report(args, report, missing_deps, len(perm_issues))
82
91
  sys.exit(0 if (report.all_ok and not perm_issues and not missing_deps) else 1)
83
92
 
84
93
  # Auto-fix mode
@@ -188,4 +197,19 @@ def _cmd_doctor(args: argparse.Namespace) -> None:
188
197
  report = check_health()
189
198
  print(format_report(report))
190
199
 
200
+ if args.report:
201
+ _share_report(args, report, plat.check_system_deps(),
202
+ len(_config.check_permissions()))
191
203
  sys.exit(0 if report.all_ok else 1)
204
+
205
+
206
+ def _share_report(args: argparse.Namespace, report, missing_deps: list[str],
207
+ perm_issues: int) -> None:
208
+ from voiceio.report import build_report, share
209
+ try:
210
+ text = build_report(report, missing_deps=missing_deps, perm_issues=perm_issues,
211
+ comment=args.comment or "")
212
+ except Exception as e: # noqa: BLE001 — the report must not take doctor down
213
+ print(f"\nCould not build the report ({type(e).__name__}); nothing was posted.")
214
+ return
215
+ share(text, yes=args.yes)
@@ -195,8 +195,12 @@ def _cmd_uninstall() -> None:
195
195
 
196
196
  # Offer to uninstall the Python package itself
197
197
  from voiceio.config import PYPI_NAME
198
+ from voiceio.service import package_remove_cmd
198
199
  is_pipx = "pipx" in sys.prefix
199
- if is_pipx:
200
+ remove_cmd = package_remove_cmd()
201
+ if remove_cmd:
202
+ print(f"\nvoiceio itself came from a system package. To remove it:\n {remove_cmd}")
203
+ elif is_pipx:
200
204
  answer = input("\nAlso uninstall the voiceio Python package (pipx uninstall)? [Y/n] ").strip().lower()
201
205
  if answer in ("y", "yes", ""):
202
206
  try: