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.
- {python_voiceio-1.10.0/python_voiceio.egg-info → python_voiceio-1.11.0}/PKG-INFO +12 -3
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/README.md +11 -2
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/pyproject.toml +1 -1
- {python_voiceio-1.10.0 → python_voiceio-1.11.0/python_voiceio.egg-info}/PKG-INFO +12 -3
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/SOURCES.txt +2 -0
- python_voiceio-1.11.0/tests/test_report.py +314 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_service.py +22 -0
- python_voiceio-1.11.0/voiceio/__init__.py +1 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/__init__.py +2 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/doctor.py +24 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/uninstall.py +5 -1
- python_voiceio-1.11.0/voiceio/report.py +278 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/service.py +31 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/steps.py +7 -3
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/system.py +7 -0
- python_voiceio-1.10.0/voiceio/__init__.py +0 -1
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/LICENSE +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/setup.cfg +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_cancel.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_cli.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_commands.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_config.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_context_terms.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_corrections.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_dead_mic.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_decoders.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_engine.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_fallback.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_feedback_stream.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_focus.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_health.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_hints.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_history.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_idle_audio.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_language.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_resume.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_llm.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_mic_gain.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_numbers.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_platform.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_postprocess.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_presence.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_prompt.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_retention.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_robustness.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_server.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_setup.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_streaming.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_suggest.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_tokens.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_tts.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_vad.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/__main__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/app.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/backends.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/learn.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/commands.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/config.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/consent.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/context_terms.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/corrections.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/demo.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/display.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/feedback.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/focus.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/health.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hints.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/history.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/teacher.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/learn/train.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/llm.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/mic_gain.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/numbers.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/platform.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/postprocess.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/power.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/presence.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/privacy.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/prompt.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/recorder.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/retention.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/app.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/config.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/alert.wav +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/streaming.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/suggest.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tokens.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/ui.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vad.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.10.0 → python_voiceio-1.11.0}/voiceio/watchdog.py +0 -0
- {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.
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|