sessionvoice 0.3.0__tar.gz → 0.3.2__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.
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/PKG-INFO +72 -159
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/README.md +71 -158
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/pyproject.toml +14 -2
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/call-ears +8 -5
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/ears-keeper.sh +57 -18
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/ears-mail.py +117 -52
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/ears-sounds.py +25 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/mouth-sounds.py +24 -3
- sessionvoice-0.3.2/src/ears/_keeper/phone_roots.py +73 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_settings/ears.schema.json +1 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_settings/mouth.schema.json +6 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speakers/run.sh +2 -3
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speakers/speaker-sidecar2.py +197 -40
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speech/build-constraints.txt +7 -6
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speech/requirements.lock +70 -4
- sessionvoice-0.3.2/src/ears/callrec.py +26 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/eval.py +2 -3
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/heard.py +1 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/home.py +82 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/__main__.py +10 -4
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/loop.py +39 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/sources.py +47 -12
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/watchdog.py +46 -9
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/services.py +927 -117
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/__init__.py +83 -8
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/__main__.py +83 -6
- sessionvoice-0.3.2/src/mouth/_separator/requirements.lock +703 -0
- sessionvoice-0.3.2/src/mouth/_separator/run.py +302 -0
- sessionvoice-0.3.2/src/mouth/autosplit.py +260 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/backends.py +31 -9
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/bed.py +12 -3
- sessionvoice-0.3.2/src/mouth/canplay.py +189 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/devices.py +14 -4
- sessionvoice-0.3.2/src/mouth/instrumental.py +121 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/notice.py +2 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/output.py +13 -19
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/player.py +67 -8
- sessionvoice-0.3.2/src/mouth/separator.py +429 -0
- sessionvoice-0.3.2/src/mouth/sidecar.py +318 -0
- sessionvoice-0.3.2/src/mouth/split.py +619 -0
- sessionvoice-0.3.2/src/mouth/splitwait.py +162 -0
- sessionvoice-0.3.2/src/mouth/stems.py +155 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/voices.py +65 -4
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/PKG-INFO +72 -159
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/SOURCES.txt +30 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/entry_points.txt +4 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/cli.py +29 -4
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/index.html +3 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/server.py +119 -9
- sessionvoice-0.3.2/src/talk/web.py +76 -0
- sessionvoice-0.3.2/tests/test_bin_tool_fallback.py +136 -0
- sessionvoice-0.3.2/tests/test_call_sock_search.py +292 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_call_tap_silero.py +1 -1
- sessionvoice-0.3.2/tests/test_callrec.py +55 -0
- sessionvoice-0.3.2/tests/test_capture_mislabel.py +321 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_clock_check.py +9 -7
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_configuration_docs.py +5 -2
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_conftest_ffmpeg.py +9 -8
- sessionvoice-0.3.2/tests/test_dependencies_docs.py +135 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_mail.py +124 -33
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_sounds_stream.py +30 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_wake.py +9 -9
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_echo_cut.py +6 -3
- sessionvoice-0.3.2/tests/test_echo_speaker.py +209 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_eval.py +13 -9
- sessionvoice-0.3.2/tests/test_instrumental_docs.py +251 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_deaf.py +1 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_defaults.py +16 -9
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_in_wheel.py +14 -4
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_speakers_deaf.py +84 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_launchd_templates.py +73 -10
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_redecode.py +70 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_watchdog.py +77 -0
- sessionvoice-0.3.2/tests/test_log_dir.py +443 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_plugin_install_doc.py +4 -3
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_say_again_e2e.py +3 -1
- sessionvoice-0.3.2/tests/test_separator_runner.py +690 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_service_build_constraints.py +7 -6
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_service_hash_pins.py +5 -1
- sessionvoice-0.3.2/tests/test_services_adopt_cold.py +271 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_install_checks.py +113 -20
- sessionvoice-0.3.2/tests/test_services_install_golden.py +168 -0
- sessionvoice-0.3.2/tests/test_services_install_load_fail.py +316 -0
- sessionvoice-0.3.2/tests/test_services_install_preflight.py +371 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_label_move.py +1 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_one_keeper.py +1 -0
- sessionvoice-0.3.2/tests/test_services_phone_only.py +307 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_sounds.py +1 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_speakers.py +3 -2
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_speech.py +104 -2
- sessionvoice-0.3.2/tests/test_services_speech_cache.py +217 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_stt_model.py +3 -1
- sessionvoice-0.3.2/tests/test_services_stt_row.py +98 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_talk.py +1 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_settings_dir.py +13 -1
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_settings_in_wheel.py +18 -1
- sessionvoice-0.3.2/tests/test_settings_schema_refresh.py +241 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_call.py +114 -15
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_follow.py +331 -15
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_parity.py +3 -2
- sessionvoice-0.3.2/tests/test_talk_kit.py +410 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_launchd.py +6 -3
- sessionvoice-0.3.2/tests/test_talk_prefix.py +250 -0
- sessionvoice-0.3.2/tests/test_talk_web_room.py +333 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_wheel_build.py +16 -5
- sessionvoice-0.3.0/src/ears/callrec.py +0 -34
- sessionvoice-0.3.0/tests/test_call_sock_search.py +0 -90
- sessionvoice-0.3.0/tests/test_callrec.py +0 -39
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/LICENSE +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/setup.cfg +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/__init__.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/held_stream.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_keeper/input-watch.swift +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_settings/ears-sounds.schema.json +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speakers/cluster-room.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speakers/make-refs.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/_speakers/requirements.lock +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/backends/__init__.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/backends/apple.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/backends/nemotron.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/backends/proc.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/backends/whisper.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/cli.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/envnames.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/eval_mlx.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/events.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/extras.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/heard_wait.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/keys.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/lag.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/__init__.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/aged.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/chunker.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/detector.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/missed.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/silero.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/sink.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/speakers.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/streaming.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/stt.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/listen/turnaudio.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/maildir.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/message_bin.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/operator_config.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/previews.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/recording.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/redecode.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/serve.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/smartturn.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/speakers.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/speech.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/turns.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/vad.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/ears/wake.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/audio.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/bargeswitch.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/box.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/coreaudio.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/duckswitch.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/hold.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/inbox.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/level.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/maillog.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/mpv.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/onset.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/paths.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/reply.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/resolver.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/mouth/timeline.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/dependency_links.txt +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/requires.txt +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/sessionvoice.egg-info/top_level.txt +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/__init__.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/__main__.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/manifest.json +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/src/talk/token_store.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_anchor.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_apple_assets_why_not.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_arm_finds_heard_first.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_arm_help.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_audio_devices_schema.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_audio_fence.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_call_tap_help.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_call_tap_listen_off.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_changelog_union.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_child_pytest.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_conftest_order.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_docs_layout.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_keys.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_no_backend.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_serve_cli.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_settings_schema.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_sounds_call.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_ears_stt_key.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_emitted_neutral.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_envnames.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_export_ignore.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_extras.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_github_links.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_heard_contract.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_heard_cursor_by.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_heard_partials_only.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_heard_wait.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_heard_writer.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_home.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_install_apple_assets.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_install_apple_optin.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_install_silero_pin.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_adopt.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_again.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_beat.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_calls.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_fallback.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_lag.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_lid.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_listen_off.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_missed_reply.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_model.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_room.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_stop.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_stt_deaf.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_swiftc_guard.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_keeper_zeros.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_launchd_no_machine_paths.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_aged.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_audio_stamp.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_call_source.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_clean_exit.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_hygiene.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_loop.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_missed_reply.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_parts.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_silero.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_speaker_agent.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_speaker_split.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_stack_dump.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_stream_feed.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_streaming.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_stt_follow.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_stt_waits.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_turn_speaker.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_vad_edges.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_listen_wedge_scenarios.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_live_home_guard.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_lock_wheels.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_maildir_private.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_marketplace_description.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_message_bin.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_mouth_notice_fence.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_mouth_schema_plain.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_mouth_sounds_scoop_by.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_mouth_sounds_wake.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_name_hint_bench.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_no_real_times.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_no_time_of_day_stamps.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_no_tracker_ids.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_no_work_hour_stamps.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_operator_config.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_persona_defaults.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_readme_links.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_recording.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_recording_retention.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_release_script.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_scanlist.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_sendmail_tripwire.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_bootstrap.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_dry_run.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_kit_labels.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_legacy_plists.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_prefetch.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_speakers_checks.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_services_weights.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_settings_stranded.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_enrol.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_in_package.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speakers_name.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_speech.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_stt_name_hint.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_system_python_scripts.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_cli.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_generalise.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_host_check.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_page_pair.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_pair.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_server.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_token.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_token_gate.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_talk_unix.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_turn_audio.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_turns.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_version_sync.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_waiter_shims.py +0 -0
- {sessionvoice-0.3.0 → sessionvoice-0.3.2}/tests/test_waiters.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sessionvoice
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: One listening engine for coding agents and the Delta bot: Apple / Nemotron / whisper -> one event stream -> VAD -> turns
|
|
5
5
|
Author-email: Mike Bailey <mike@sessionkit.ai>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -76,143 +76,13 @@ streams transcription, and decides when the speaker has finished.
|
|
|
76
76
|
|
|
77
77
|
## Requirements
|
|
78
78
|
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
(`uv run ears install --apple`: the wheel does not ship `swift/`, and
|
|
87
|
-
`--apple` without it refuses). The engine alone (`ears file`, `ears
|
|
88
|
-
listen` with `whisper`) needs no Mac.
|
|
89
|
-
- **Python 3.10-3.14** (`requires-python` in `pyproject.toml`) and
|
|
90
|
-
**[uv](https://docs.astral.sh/uv/)**: `make test` runs `uv run --locked`.
|
|
91
|
-
- **A speech server on `127.0.0.1:8890`**: mlx-audio's server, which serves
|
|
92
|
-
both STT and TTS. It is the ears' default STT (Parakeet;
|
|
93
|
-
`src/ears/listen/stt.py:86`, `src/ears/services.py:530`) and the
|
|
94
|
-
mouth's Kokoro voices (`src/mouth/backends.py:289`). `ears services
|
|
95
|
-
install` finds one or installs its own (`src/ears/speech.py`), and asks
|
|
96
|
-
before it acts, in this order:
|
|
97
|
-
- **Opt-out.** `final.url` in `~/.sessionkit/voice/settings/ears.json` (or
|
|
98
|
-
`EARS_STT_URL` at install) names another engine, and the mouth is not
|
|
99
|
-
being installed: `:8890` is not sessionvoice's to manage.
|
|
100
|
-
- **Use.** Something listens on `:8890` and says it is mlx-audio: `GET /`
|
|
101
|
-
returns mlx-audio's welcome line, or `/openapi.json` lists its audio
|
|
102
|
-
routes. sessionvoice uses that server, whoever installed it (VoiceMode,
|
|
103
|
-
for example), and starts nothing.
|
|
104
|
-
- **Refuse.** Something else holds `:8890`, or holds it and does not
|
|
105
|
-
answer. Install starts nothing, replaces nothing, and names the holder.
|
|
106
|
-
When the ears hear through `:8890` it exits 2 and says what to run;
|
|
107
|
-
when only the mouth does, it warns and the mouth uses `say`. Free the
|
|
108
|
-
port, or point `final.url` at another speech server.
|
|
109
|
-
- **Never a second.** Nothing answers on `:8890`, but a loaded launch
|
|
110
|
-
agent runs `mlx_audio.server` (under any label, so another installer's
|
|
111
|
-
job between restarts counts). Install starts nothing. When that job is
|
|
112
|
-
sessionvoice's own, run `ears services uninstall --only speech`, then
|
|
113
|
-
install again.
|
|
114
|
-
- **Install.** Nothing on `:8890` and no such job. On an Apple-silicon
|
|
115
|
-
Mac (macOS 14 or later), install builds sessionvoice's own venv,
|
|
116
|
-
`~/.local/share/ears/speech/venv`, from the shipped
|
|
117
|
-
`speech/requirements.lock` (`mlx-audio==0.5.8`), and loads it as the
|
|
118
|
-
launch agent `dev.sessionkit.voice.mlx-audio`. `EARS_SPEECH_PY` names a
|
|
119
|
-
python that already has it, and no venv is built. On any other host it
|
|
120
|
-
installs nothing: it refuses and says what to run when the ears need
|
|
121
|
-
`:8890`, and warns when only the mouth does.
|
|
122
|
-
|
|
123
|
-
`--dry-run` says which of these install would take, and builds and writes
|
|
124
|
-
nothing. `ears services status` has a `speech` row that names who serves
|
|
125
|
-
`:8890`. `ears services uninstall --only speech` removes only
|
|
126
|
-
`dev.sessionkit.voice.mlx-audio`, never a server someone else installed, and
|
|
127
|
-
keeps its venv. While sessionvoice's own server runs, a re-install just
|
|
128
|
-
uses it, so to upgrade it, uninstall it and install again: install
|
|
129
|
-
re-applies the lock's pins to the venv it finds. The lock carries a hash
|
|
130
|
-
for every pin, and install refuses an artifact that does not match it.
|
|
131
|
-
Two pins, `docopt` and `webrtcvad`, ship only as source, so uv builds them,
|
|
132
|
-
and the setuptools it builds them with is hash-checked too, from the
|
|
133
|
-
shipped `speech/build-constraints.txt`. A new venv is checked in full. A
|
|
134
|
-
re-apply checks only what it installs, not what is already in the venv, so
|
|
135
|
-
to check an existing venv, remove it and install again. Install then
|
|
136
|
-
fetches the server's weights, about 2.9 GB, so the first call does not wait
|
|
137
|
-
on them. It prints the size first, skips any already cached and prints
|
|
138
|
-
progress while it fetches. `--no-prefetch` (or `EARS_NO_PREFETCH=1`) skips
|
|
139
|
-
this on a host that must not download, and `--dry-run` prints the size and
|
|
140
|
-
fetches nothing. A fetch that fails does not fail the install. It says why
|
|
141
|
-
and how much is left. Whatever is not fetched at install is downloaded by
|
|
142
|
-
the server on first use: the first transcription waits on Parakeet, and the
|
|
143
|
-
first line spoken waits on Kokoro and its voices.
|
|
144
|
-
|
|
145
|
-
**These weights are not pinned.** mlx-audio's server takes whatever is on
|
|
146
|
-
Hugging Face `main` at first use, and it has no way to ask for a revision,
|
|
147
|
-
so a later first use (a new Mac, a cleared cache) can load different
|
|
148
|
-
weights. `ears services weights` (and the `weight` rows of `ears services
|
|
149
|
-
status`) reads the three, `mlx-community/parakeet-tdt-0.6b-v3`,
|
|
150
|
-
`mlx-community/Kokoro-82M-bf16` and `prince-canuma/Kokoro-82M` (the
|
|
151
|
-
voices), from the Hugging Face cache: `cached`, `downloading 412 MB of
|
|
152
|
-
2.5 GB`, or `not cached`, and `DIFFERS` when the cache's `main` is not the
|
|
153
|
-
commit sessionvoice expects (`SPEECH_WEIGHTS` in `src/ears/services.py`).
|
|
154
|
-
`--json` prints the same for a program, schema `ears.weights/1`
|
|
155
|
-
([docs/models.md](docs/models.md)). It reads the cache's files and never
|
|
156
|
-
fetches. It only reports; it does not stop or change what the server loads.
|
|
157
|
-
A real pin, fixed at install, is not built yet.
|
|
158
|
-
|
|
159
|
-
sessionvoice can promise "never two servers" only from its own side:
|
|
160
|
-
VoiceMode's installer does not ask, so `voicemode service install
|
|
161
|
-
mlx-audio` run after sessionvoice's install loads a second mlx-audio job,
|
|
162
|
-
and one of the two crash-loops.
|
|
163
|
-
|
|
164
|
-
Without a speech server, point the ears at another OpenAI-compatible STT
|
|
165
|
-
(`EARS_LISTEN_STT_URL`, or `url` in `~/.sessionkit/voice/settings/ears.json`;
|
|
166
|
-
`src/ears/listen/stt.py:105`) and the mouth at another TTS
|
|
167
|
-
(`MOUTH_KOKORO_URL`, or `tts` in `~/.sessionkit/voice/settings/mouth.json`),
|
|
168
|
-
a hosted one included, with `MOUTH_TTS_KEY` or the key file
|
|
169
|
-
`~/.sessionkit/voice/settings/tts.key` for a keyed one: see
|
|
170
|
-
[A hosted speech endpoint](docs/models.md#a-hosted-speech-endpoint-instead).
|
|
171
|
-
On a Mac the mouth's `auto` falls back to `say`
|
|
172
|
-
(`src/mouth/backends.py:200`).
|
|
173
|
-
- **ffmpeg** on `PATH`: `mouth play` (`src/mouth/backends.py:261`), the
|
|
174
|
-
mouth's `.mp3`/`.m4a` output (`src/mouth/audio.py:137`), `ears file`
|
|
175
|
-
(`src/ears/cli.py:92`) and `ears eval` (`src/ears/eval.py:169`).
|
|
176
|
-
- **SwitchAudioSource** on `PATH` (Homebrew `switchaudio-osx`): the keeper
|
|
177
|
-
sets the default output with it, and reads with it only as a fallback
|
|
178
|
-
(`keeper/ears-keeper.sh`): `check_output` reads the default output and the
|
|
179
|
-
output list from input-watch's JSON file (`docs/audio-devices.md`) and asks
|
|
180
|
-
SwitchAudioSource only when that file is missing or stale; `default_input`
|
|
181
|
-
reads the legacy `input-watch.state` while the watcher is running and asks
|
|
182
|
-
SwitchAudioSource only when it is not. `keeper/speak:13-17` still reads with it.
|
|
183
|
-
- **sessionmail's `message`** only if you use mail: the mouth's notices and
|
|
184
|
-
the keeper's turn mail (`keeper/ears-mail.py`) go out by `message send`,
|
|
185
|
-
never straight to an MTA. With no `sendmail` key in
|
|
186
|
-
sessionmail's settings file (`~/.sessionkit/mail/settings/mail.json`)
|
|
187
|
-
and no `SESSIONMAIL_SENDMAIL`, sessionmail (0.2.0 and later) delivers
|
|
188
|
-
by its mail drop: it writes each mail straight into your own mailbox,
|
|
189
|
-
as you, with no mail server. That is how the launch agents deliver on
|
|
190
|
-
a Mac with no postfix. The drop never makes a mailbox, so the `heard`
|
|
191
|
-
box must exist first: `message setup --box heard` makes it. A turn
|
|
192
|
-
mailed to a box that is not there is refused, nothing is delivered,
|
|
193
|
-
and the turn is lost. To send by postfix instead, set
|
|
194
|
-
`"sendmail": "/usr/sbin/sendmail"` in that file: the launch agents do
|
|
195
|
-
not see a `SESSIONMAIL_SENDMAIL` set in your shell.
|
|
196
|
-
On a drop host, `ears services install` runs sessionmail's first-run setup, `message setup --box heard`.
|
|
197
|
-
Mail to `mouth@<host>` reaches the speech queue only if sessionmail
|
|
198
|
-
delivers it there (`src/mouth/box.py`). The mouth's notices go through
|
|
199
|
-
`MOUTH_SENDMAIL` (`src/mouth/notice.py`): only the mouth service (`mouth serve`, `mouth mail`,
|
|
200
|
-
`mouth retract`, `mouth again`) uses the default `message`; any other process
|
|
201
|
-
must name its own. ears-mail's command is `EARS_MAIL_MESSAGE`, else
|
|
202
|
-
`message` on PATH, else where uv installs it (`~/.local/bin`, then uv's
|
|
203
|
-
tool dir; `src/ears/message_bin.py`).
|
|
204
|
-
- **Models.** `ears install` fetches only Silero VAD, to
|
|
205
|
-
`~/.cache/ears/silero_vad.onnx`, pinned to one upstream commit and checked
|
|
206
|
-
against its SHA-256 (`install()` in `src/ears/cli.py`; the pin is in
|
|
207
|
-
`src/ears/vad.py`, and `docs/models.md` names it). **It does not
|
|
208
|
-
fetch the Smart Turn model** that `--turns smart` needs
|
|
209
|
-
(`src/ears/smartturn.py:48-72`). With mlx-audio installed, the `mlx`
|
|
210
|
-
backend loads `~/.cache/ears/smart-turn-v3-mlx` if it is there, and
|
|
211
|
-
otherwise `mlx-community/smart-turn-v3` from Hugging Face on first use.
|
|
212
|
-
Without mlx-audio, the `onnx` backend needs `smart-turn-v3.2-gpu.onnx` at
|
|
213
|
-
`~/.cache/ears/` (or the path in `EARS_SMART_TURN_ONNX`). Download it
|
|
214
|
-
yourself from
|
|
215
|
-
[pipecat-ai/smart-turn-v3](https://huggingface.co/pipecat-ai/smart-turn-v3).
|
|
79
|
+
- [x] macOS (tested on Apple silicon)
|
|
80
|
+
- [ ] Linux
|
|
81
|
+
- [ ] WSL
|
|
82
|
+
- [ ] Windows
|
|
83
|
+
|
|
84
|
+
What sessionvoice depends on, what needs each one, and how to upgrade it:
|
|
85
|
+
[dependencies](docs/dependencies.md).
|
|
216
86
|
|
|
217
87
|
## Components
|
|
218
88
|
|
|
@@ -263,10 +133,15 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
|
|
|
263
133
|
package or a checkout; a bare install never does.
|
|
264
134
|
- **Turn mail** (`keeper/ears-mail.py`, launchd `dev.sessionkit.voice.ears-mail`):
|
|
265
135
|
mails each of the user's finished turns To and From
|
|
266
|
-
`heard
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
136
|
+
`heard+room@<host>`, whatever input device the capture is following, with
|
|
137
|
+
the `Ears-*` headers of the pager genie's contract (sessionmail
|
|
138
|
+
`openspec/changes/pager-genie`). The device is named by the `Ears-Device`
|
|
139
|
+
header. A session hears the room, on every device, by subscribing to that
|
|
140
|
+
one address. While `--device-copy` is on (the default), the same send (one
|
|
141
|
+
message, one Message-ID) also goes Cc `heard+<device>@<host>`, so a
|
|
142
|
+
session subscribed to a device's address keeps hearing that device. The
|
|
143
|
+
room's tag is `--room` (`EARS_MAIL_ROOM`, default `room`). The box is
|
|
144
|
+
`heard` (`--sender`), because `ears` is this project's own box.
|
|
270
145
|
sessionmail trusts only its keeper's From (`_voice.KEEPER`), so the
|
|
271
146
|
plist loads only once that says `heard` too. `--to seats` is the older mail, To
|
|
272
147
|
each seat a turn is for. **Never run `--to seats` and `--to device` for
|
|
@@ -278,6 +153,12 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
|
|
|
278
153
|
one whose label is empty or names nobody, keeps `--speaker`
|
|
279
154
|
(`$EARS_SPEAKER`, else `unknown@<host>`: a voice the matcher could not
|
|
280
155
|
place is never mailed under a person's name).
|
|
156
|
+
When the user speaks over a seat's line, the capture may label the whole
|
|
157
|
+
turn with that seat, since the line's voice fills most of its audio. Once
|
|
158
|
+
the echo cut has taken that seat's line out and left the user's words, the
|
|
159
|
+
label is dropped: the remaining words get `--speaker` (else
|
|
160
|
+
`unknown@<host>`), never the seat whose echo was cut, and they can be a
|
|
161
|
+
reply to that seat. A label naming anyone else is kept.
|
|
281
162
|
`Ears-Echo` says a mouth line heard back through the mic was cut out of
|
|
282
163
|
the turn. On the speakers the mic hears the mouth, so a line's
|
|
283
164
|
words are cut wherever they sit in the user's turn: at its start (up to `3 + 5
|
|
@@ -363,21 +244,23 @@ Every key, its type and its default:
|
|
|
363
244
|
- **The heard log:** `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
|
|
364
245
|
one JSON line for each thing the ears heard and the mouth said, where
|
|
365
246
|
`<home>` is the home above. A new file starts each day.
|
|
366
|
-
- **The services' logs:**
|
|
247
|
+
- **The services' logs:** `~/.sessionkit/voice/log/` (`EARS_LOG_DIR` moves it).
|
|
367
248
|
`ears-keeper.log` is what the keeper did (devices, restarts, calls, a
|
|
368
249
|
deaf mic); `ears-keeper-capture.log` is the room capture's own output;
|
|
369
250
|
`ears-mail.log` is each turn mail sent or skipped; `ears-mouth.log` is
|
|
370
|
-
the mouth's player.
|
|
251
|
+
the mouth's player. Logs written before the move stay where they
|
|
252
|
+
were, and nothing deletes them. `ears services status` prints the folder.
|
|
371
253
|
|
|
372
254
|
Follow one with `tail -F`, for example
|
|
373
|
-
`tail -F
|
|
255
|
+
`tail -F ~/.sessionkit/voice/log/ears-keeper.log`, or today's heard log with
|
|
374
256
|
`tail -F <home>/logs/conversations/heard_$(date +%F).jsonl`.
|
|
375
257
|
Every log file and what each line means:
|
|
376
258
|
[docs/logs.md](docs/logs.md).
|
|
377
259
|
|
|
378
260
|
- **Health:** `ears services status` prints one row per launch agent
|
|
379
|
-
(`running, pid N`, `loaded, not running` or `not installed
|
|
380
|
-
|
|
261
|
+
(`running, pid N`, `loaded, not running` or `not installed`; the
|
|
262
|
+
`stt` row, the optional `--apple` server, also names what the ears hear
|
|
263
|
+
through), the keeper's `mode` (`input-watch`, or `polling system_profiler` and why),
|
|
381
264
|
the one that serves speech on `127.0.0.1:8890`, and the home in use.
|
|
382
265
|
|
|
383
266
|
## Use
|
|
@@ -421,11 +304,17 @@ at `http://127.0.0.1:<port>/`, never at `localhost`. The code is `src/talk/`.
|
|
|
421
304
|
`ears services install --only talk` keeps it running as a launch agent
|
|
422
305
|
(opt-in; a bare install never does).
|
|
423
306
|
|
|
307
|
+
In the kit's web page, Talk is a room: the kit's front door runs
|
|
308
|
+
`talk serve --kit` on a unix socket and logs you in, so there is nothing to
|
|
309
|
+
pair and you never run `--kit` by hand
|
|
310
|
+
([docs/talk.md](docs/talk.md#in-the-kits-web-page)).
|
|
311
|
+
|
|
424
312
|
## Install
|
|
425
313
|
|
|
426
314
|
The distribution is `sessionvoice` (`sessionvoice[nemotron]`, `sessionvoice[all]`);
|
|
427
|
-
it installs the `ears`, `ears-serve`, `mouth` and `
|
|
428
|
-
`pip install sessionvoice` carries what `mouth say` needs.
|
|
315
|
+
it installs the `ears`, `ears-serve`, `mouth`, `talk` and `sessionvoice-web`
|
|
316
|
+
commands. A bare `pip install sessionvoice` carries what `mouth say` needs.
|
|
317
|
+
The extras are
|
|
429
318
|
`capture` (the mic and call ears), `silero` (the Silero VAD), `smart-turn`
|
|
430
319
|
(the Smart Turn gate's ONNX backend), `nemotron` (mlx-audio) and `all`. An
|
|
431
320
|
import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
|
|
@@ -436,6 +325,15 @@ import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
|
|
|
436
325
|
ears services install --only sounds # opt-in: the ears and mouth sounds
|
|
437
326
|
ears services install --only talk # opt-in: talk serve, the live view
|
|
438
327
|
|
|
328
|
+
A phone-only Mac (for example one that runs a Delta bot, with no agent
|
|
329
|
+
seat to wake) installs with `--no-agent`: nobody is woken when you speak,
|
|
330
|
+
and the keeper's notices go to the log. `--agent NAME` is for a Mac where
|
|
331
|
+
an agent seat should be woken when you speak. Before it writes anything,
|
|
332
|
+
install says how big the speech models are (about 2.9 GB, fetched once,
|
|
333
|
+
after the speech server loads) and that the speech server runs as a login
|
|
334
|
+
service; `--dry-run` prints the same line, and `--no-prefetch` leaves the
|
|
335
|
+
download to the first call.
|
|
336
|
+
|
|
439
337
|
### The proof of a working install: you speak, and the agent WAKES and answers
|
|
440
338
|
|
|
441
339
|
**Not "a heard line appears".** The keeper alone writes the heard line, and
|
|
@@ -447,11 +345,19 @@ agent deaf to its person, and one that retires room mode on that proof goes
|
|
|
447
345
|
deaf for good. So the proof is: say "hey NAME", and NAME wakes and
|
|
448
346
|
answers.
|
|
449
347
|
|
|
450
|
-
`ears services install` (`--dry-run` lists the same checks and writes nothing)
|
|
348
|
+
`ears services install` (`--dry-run` lists the same checks and writes nothing).
|
|
349
|
+
Before it writes anything, it checks every prerequisite it can check without
|
|
350
|
+
building or fetching. When any are missing, it lists them all in one run, each
|
|
351
|
+
with its fix, writes nothing and exits 2. A launch agent that fails to load
|
|
352
|
+
also makes it exit 2, once every other service has been tried: one block on
|
|
353
|
+
stderr names each service that did not load, the install log and `ears
|
|
354
|
+
services status`. Every real run appends its output and launchctl's answers,
|
|
355
|
+
timestamped, to `install.log` in the log folder, `~/.sessionkit/voice/log/`, and prints that path
|
|
356
|
+
as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
|
|
451
357
|
|
|
452
358
|
- first finds the mlx-audio speech server on `:8890`, or installs its own
|
|
453
|
-
(`dev.sessionkit.voice.mlx-audio`), as
|
|
454
|
-
says, and prints which step it took. When it loads its own, the new
|
|
359
|
+
(`dev.sessionkit.voice.mlx-audio`), as [the speech server](docs/speech-server.md)
|
|
360
|
+
page says, and prints which step it took. When it loads its own, the new
|
|
455
361
|
`final` below is written without the probe: that server has not started
|
|
456
362
|
yet;
|
|
457
363
|
- installs `dev.sessionkit.voice.ears-mail` beside the mouth and the keeper, unless
|
|
@@ -497,14 +403,17 @@ answers.
|
|
|
497
403
|
read (no `message`, or one older than 0.2.0), it warns, makes no box,
|
|
498
404
|
checks neither, and goes on.
|
|
499
405
|
On a postfix host it also prints the box to subscribe to:
|
|
500
|
-
`message subscription add heard+
|
|
406
|
+
`message subscription add heard+room`;
|
|
501
407
|
- `--agent NAME` adds NAME's wake seat (`ears wake add NAME`) and sends the
|
|
502
408
|
keeper's notices to NAME (`EARS_NOTIFY`; `--notify ADDR` sends them
|
|
503
409
|
elsewhere). Without it, and when the wake file names no seat, nobody would
|
|
504
410
|
be woken, so an install that includes `mail` REFUSES (exit 2, before
|
|
505
411
|
anything is written; `--dry-run` says `REFUSED`, still lists, and exits 2
|
|
506
|
-
too). The refusal prints both forms
|
|
507
|
-
|
|
412
|
+
too). The refusal prints both forms and what each means:
|
|
413
|
+
`ears services install --agent NAME` for a Mac where an agent seat should
|
|
414
|
+
be woken when you speak, and `ears services install --no-agent` for a
|
|
415
|
+
phone-only Mac (for example a Delta bot), where nobody is woken and the
|
|
416
|
+
notices go to the log; then `ears wake add NAME`, to add a seat later. Inside
|
|
508
417
|
an agent seat (`TM_AGENT_ADDRESS` set) it suggests that address's local
|
|
509
418
|
part as NAME; it never installs on the suggestion. `--no-agent` installs
|
|
510
419
|
anyway, and install warns that NOBODY is woken. A wake file that already
|
|
@@ -519,9 +428,13 @@ answers.
|
|
|
519
428
|
missing: a checkout's `settings/`, else the wheel's `ears/_settings/`. It
|
|
520
429
|
then copies each beside its settings file as a dot file
|
|
521
430
|
(`.ears.schema.json`, `.ears-sounds.schema.json`, `.mouth.schema.json`);
|
|
522
|
-
-
|
|
523
|
-
|
|
524
|
-
|
|
431
|
+
- fetches the Silero VAD model, before any plist is written, when it is
|
|
432
|
+
missing and VoiceMode's copy (`~/.voicemode/models/silero/`) is not there to
|
|
433
|
+
link. It is the file `ears install` fetches, from the same pinned source,
|
|
434
|
+
checked against the same SHA-256, and install names the version, the source
|
|
435
|
+
and the destination. A failed fetch or a checksum mismatch is refused, and no
|
|
436
|
+
file is left behind. `--dry-run` says what it would fetch (the version and
|
|
437
|
+
the size) and fetches nothing. A keeper that finds no model says so once and
|
|
525
438
|
holds, and does not restart its capture every few seconds;
|
|
526
439
|
- probes a new `final` engine with one short clip before writing it to
|
|
527
440
|
`ears.json`, and falls back to whisper `:2022` (and says so) when it does
|
|
@@ -555,7 +468,7 @@ the agent's next tool call. `EARS_HEARD_HOOK=off` silences it without
|
|
|
555
468
|
editing the file.
|
|
556
469
|
|
|
557
470
|
`ears heard hook` is the canonical heard hook. A seat that also gets heard
|
|
558
|
-
mail (it subscribes to `heard
|
|
471
|
+
mail (it subscribes to `heard+room`) gets each finished turn by mail, so
|
|
559
472
|
give it `ears heard hook --partials-only || exit 1` instead: the hook then
|
|
560
473
|
shows only the partial preview of a turn still being spoken and the shown
|
|
561
474
|
events (wake word, barge-in, device change), never the finished turn a
|
|
@@ -32,143 +32,13 @@ streams transcription, and decides when the speaker has finished.
|
|
|
32
32
|
|
|
33
33
|
## Requirements
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
(`uv run ears install --apple`: the wheel does not ship `swift/`, and
|
|
43
|
-
`--apple` without it refuses). The engine alone (`ears file`, `ears
|
|
44
|
-
listen` with `whisper`) needs no Mac.
|
|
45
|
-
- **Python 3.10-3.14** (`requires-python` in `pyproject.toml`) and
|
|
46
|
-
**[uv](https://docs.astral.sh/uv/)**: `make test` runs `uv run --locked`.
|
|
47
|
-
- **A speech server on `127.0.0.1:8890`**: mlx-audio's server, which serves
|
|
48
|
-
both STT and TTS. It is the ears' default STT (Parakeet;
|
|
49
|
-
`src/ears/listen/stt.py:86`, `src/ears/services.py:530`) and the
|
|
50
|
-
mouth's Kokoro voices (`src/mouth/backends.py:289`). `ears services
|
|
51
|
-
install` finds one or installs its own (`src/ears/speech.py`), and asks
|
|
52
|
-
before it acts, in this order:
|
|
53
|
-
- **Opt-out.** `final.url` in `~/.sessionkit/voice/settings/ears.json` (or
|
|
54
|
-
`EARS_STT_URL` at install) names another engine, and the mouth is not
|
|
55
|
-
being installed: `:8890` is not sessionvoice's to manage.
|
|
56
|
-
- **Use.** Something listens on `:8890` and says it is mlx-audio: `GET /`
|
|
57
|
-
returns mlx-audio's welcome line, or `/openapi.json` lists its audio
|
|
58
|
-
routes. sessionvoice uses that server, whoever installed it (VoiceMode,
|
|
59
|
-
for example), and starts nothing.
|
|
60
|
-
- **Refuse.** Something else holds `:8890`, or holds it and does not
|
|
61
|
-
answer. Install starts nothing, replaces nothing, and names the holder.
|
|
62
|
-
When the ears hear through `:8890` it exits 2 and says what to run;
|
|
63
|
-
when only the mouth does, it warns and the mouth uses `say`. Free the
|
|
64
|
-
port, or point `final.url` at another speech server.
|
|
65
|
-
- **Never a second.** Nothing answers on `:8890`, but a loaded launch
|
|
66
|
-
agent runs `mlx_audio.server` (under any label, so another installer's
|
|
67
|
-
job between restarts counts). Install starts nothing. When that job is
|
|
68
|
-
sessionvoice's own, run `ears services uninstall --only speech`, then
|
|
69
|
-
install again.
|
|
70
|
-
- **Install.** Nothing on `:8890` and no such job. On an Apple-silicon
|
|
71
|
-
Mac (macOS 14 or later), install builds sessionvoice's own venv,
|
|
72
|
-
`~/.local/share/ears/speech/venv`, from the shipped
|
|
73
|
-
`speech/requirements.lock` (`mlx-audio==0.5.8`), and loads it as the
|
|
74
|
-
launch agent `dev.sessionkit.voice.mlx-audio`. `EARS_SPEECH_PY` names a
|
|
75
|
-
python that already has it, and no venv is built. On any other host it
|
|
76
|
-
installs nothing: it refuses and says what to run when the ears need
|
|
77
|
-
`:8890`, and warns when only the mouth does.
|
|
78
|
-
|
|
79
|
-
`--dry-run` says which of these install would take, and builds and writes
|
|
80
|
-
nothing. `ears services status` has a `speech` row that names who serves
|
|
81
|
-
`:8890`. `ears services uninstall --only speech` removes only
|
|
82
|
-
`dev.sessionkit.voice.mlx-audio`, never a server someone else installed, and
|
|
83
|
-
keeps its venv. While sessionvoice's own server runs, a re-install just
|
|
84
|
-
uses it, so to upgrade it, uninstall it and install again: install
|
|
85
|
-
re-applies the lock's pins to the venv it finds. The lock carries a hash
|
|
86
|
-
for every pin, and install refuses an artifact that does not match it.
|
|
87
|
-
Two pins, `docopt` and `webrtcvad`, ship only as source, so uv builds them,
|
|
88
|
-
and the setuptools it builds them with is hash-checked too, from the
|
|
89
|
-
shipped `speech/build-constraints.txt`. A new venv is checked in full. A
|
|
90
|
-
re-apply checks only what it installs, not what is already in the venv, so
|
|
91
|
-
to check an existing venv, remove it and install again. Install then
|
|
92
|
-
fetches the server's weights, about 2.9 GB, so the first call does not wait
|
|
93
|
-
on them. It prints the size first, skips any already cached and prints
|
|
94
|
-
progress while it fetches. `--no-prefetch` (or `EARS_NO_PREFETCH=1`) skips
|
|
95
|
-
this on a host that must not download, and `--dry-run` prints the size and
|
|
96
|
-
fetches nothing. A fetch that fails does not fail the install. It says why
|
|
97
|
-
and how much is left. Whatever is not fetched at install is downloaded by
|
|
98
|
-
the server on first use: the first transcription waits on Parakeet, and the
|
|
99
|
-
first line spoken waits on Kokoro and its voices.
|
|
100
|
-
|
|
101
|
-
**These weights are not pinned.** mlx-audio's server takes whatever is on
|
|
102
|
-
Hugging Face `main` at first use, and it has no way to ask for a revision,
|
|
103
|
-
so a later first use (a new Mac, a cleared cache) can load different
|
|
104
|
-
weights. `ears services weights` (and the `weight` rows of `ears services
|
|
105
|
-
status`) reads the three, `mlx-community/parakeet-tdt-0.6b-v3`,
|
|
106
|
-
`mlx-community/Kokoro-82M-bf16` and `prince-canuma/Kokoro-82M` (the
|
|
107
|
-
voices), from the Hugging Face cache: `cached`, `downloading 412 MB of
|
|
108
|
-
2.5 GB`, or `not cached`, and `DIFFERS` when the cache's `main` is not the
|
|
109
|
-
commit sessionvoice expects (`SPEECH_WEIGHTS` in `src/ears/services.py`).
|
|
110
|
-
`--json` prints the same for a program, schema `ears.weights/1`
|
|
111
|
-
([docs/models.md](docs/models.md)). It reads the cache's files and never
|
|
112
|
-
fetches. It only reports; it does not stop or change what the server loads.
|
|
113
|
-
A real pin, fixed at install, is not built yet.
|
|
114
|
-
|
|
115
|
-
sessionvoice can promise "never two servers" only from its own side:
|
|
116
|
-
VoiceMode's installer does not ask, so `voicemode service install
|
|
117
|
-
mlx-audio` run after sessionvoice's install loads a second mlx-audio job,
|
|
118
|
-
and one of the two crash-loops.
|
|
119
|
-
|
|
120
|
-
Without a speech server, point the ears at another OpenAI-compatible STT
|
|
121
|
-
(`EARS_LISTEN_STT_URL`, or `url` in `~/.sessionkit/voice/settings/ears.json`;
|
|
122
|
-
`src/ears/listen/stt.py:105`) and the mouth at another TTS
|
|
123
|
-
(`MOUTH_KOKORO_URL`, or `tts` in `~/.sessionkit/voice/settings/mouth.json`),
|
|
124
|
-
a hosted one included, with `MOUTH_TTS_KEY` or the key file
|
|
125
|
-
`~/.sessionkit/voice/settings/tts.key` for a keyed one: see
|
|
126
|
-
[A hosted speech endpoint](docs/models.md#a-hosted-speech-endpoint-instead).
|
|
127
|
-
On a Mac the mouth's `auto` falls back to `say`
|
|
128
|
-
(`src/mouth/backends.py:200`).
|
|
129
|
-
- **ffmpeg** on `PATH`: `mouth play` (`src/mouth/backends.py:261`), the
|
|
130
|
-
mouth's `.mp3`/`.m4a` output (`src/mouth/audio.py:137`), `ears file`
|
|
131
|
-
(`src/ears/cli.py:92`) and `ears eval` (`src/ears/eval.py:169`).
|
|
132
|
-
- **SwitchAudioSource** on `PATH` (Homebrew `switchaudio-osx`): the keeper
|
|
133
|
-
sets the default output with it, and reads with it only as a fallback
|
|
134
|
-
(`keeper/ears-keeper.sh`): `check_output` reads the default output and the
|
|
135
|
-
output list from input-watch's JSON file (`docs/audio-devices.md`) and asks
|
|
136
|
-
SwitchAudioSource only when that file is missing or stale; `default_input`
|
|
137
|
-
reads the legacy `input-watch.state` while the watcher is running and asks
|
|
138
|
-
SwitchAudioSource only when it is not. `keeper/speak:13-17` still reads with it.
|
|
139
|
-
- **sessionmail's `message`** only if you use mail: the mouth's notices and
|
|
140
|
-
the keeper's turn mail (`keeper/ears-mail.py`) go out by `message send`,
|
|
141
|
-
never straight to an MTA. With no `sendmail` key in
|
|
142
|
-
sessionmail's settings file (`~/.sessionkit/mail/settings/mail.json`)
|
|
143
|
-
and no `SESSIONMAIL_SENDMAIL`, sessionmail (0.2.0 and later) delivers
|
|
144
|
-
by its mail drop: it writes each mail straight into your own mailbox,
|
|
145
|
-
as you, with no mail server. That is how the launch agents deliver on
|
|
146
|
-
a Mac with no postfix. The drop never makes a mailbox, so the `heard`
|
|
147
|
-
box must exist first: `message setup --box heard` makes it. A turn
|
|
148
|
-
mailed to a box that is not there is refused, nothing is delivered,
|
|
149
|
-
and the turn is lost. To send by postfix instead, set
|
|
150
|
-
`"sendmail": "/usr/sbin/sendmail"` in that file: the launch agents do
|
|
151
|
-
not see a `SESSIONMAIL_SENDMAIL` set in your shell.
|
|
152
|
-
On a drop host, `ears services install` runs sessionmail's first-run setup, `message setup --box heard`.
|
|
153
|
-
Mail to `mouth@<host>` reaches the speech queue only if sessionmail
|
|
154
|
-
delivers it there (`src/mouth/box.py`). The mouth's notices go through
|
|
155
|
-
`MOUTH_SENDMAIL` (`src/mouth/notice.py`): only the mouth service (`mouth serve`, `mouth mail`,
|
|
156
|
-
`mouth retract`, `mouth again`) uses the default `message`; any other process
|
|
157
|
-
must name its own. ears-mail's command is `EARS_MAIL_MESSAGE`, else
|
|
158
|
-
`message` on PATH, else where uv installs it (`~/.local/bin`, then uv's
|
|
159
|
-
tool dir; `src/ears/message_bin.py`).
|
|
160
|
-
- **Models.** `ears install` fetches only Silero VAD, to
|
|
161
|
-
`~/.cache/ears/silero_vad.onnx`, pinned to one upstream commit and checked
|
|
162
|
-
against its SHA-256 (`install()` in `src/ears/cli.py`; the pin is in
|
|
163
|
-
`src/ears/vad.py`, and `docs/models.md` names it). **It does not
|
|
164
|
-
fetch the Smart Turn model** that `--turns smart` needs
|
|
165
|
-
(`src/ears/smartturn.py:48-72`). With mlx-audio installed, the `mlx`
|
|
166
|
-
backend loads `~/.cache/ears/smart-turn-v3-mlx` if it is there, and
|
|
167
|
-
otherwise `mlx-community/smart-turn-v3` from Hugging Face on first use.
|
|
168
|
-
Without mlx-audio, the `onnx` backend needs `smart-turn-v3.2-gpu.onnx` at
|
|
169
|
-
`~/.cache/ears/` (or the path in `EARS_SMART_TURN_ONNX`). Download it
|
|
170
|
-
yourself from
|
|
171
|
-
[pipecat-ai/smart-turn-v3](https://huggingface.co/pipecat-ai/smart-turn-v3).
|
|
35
|
+
- [x] macOS (tested on Apple silicon)
|
|
36
|
+
- [ ] Linux
|
|
37
|
+
- [ ] WSL
|
|
38
|
+
- [ ] Windows
|
|
39
|
+
|
|
40
|
+
What sessionvoice depends on, what needs each one, and how to upgrade it:
|
|
41
|
+
[dependencies](docs/dependencies.md).
|
|
172
42
|
|
|
173
43
|
## Components
|
|
174
44
|
|
|
@@ -219,10 +89,15 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
|
|
|
219
89
|
package or a checkout; a bare install never does.
|
|
220
90
|
- **Turn mail** (`keeper/ears-mail.py`, launchd `dev.sessionkit.voice.ears-mail`):
|
|
221
91
|
mails each of the user's finished turns To and From
|
|
222
|
-
`heard
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
92
|
+
`heard+room@<host>`, whatever input device the capture is following, with
|
|
93
|
+
the `Ears-*` headers of the pager genie's contract (sessionmail
|
|
94
|
+
`openspec/changes/pager-genie`). The device is named by the `Ears-Device`
|
|
95
|
+
header. A session hears the room, on every device, by subscribing to that
|
|
96
|
+
one address. While `--device-copy` is on (the default), the same send (one
|
|
97
|
+
message, one Message-ID) also goes Cc `heard+<device>@<host>`, so a
|
|
98
|
+
session subscribed to a device's address keeps hearing that device. The
|
|
99
|
+
room's tag is `--room` (`EARS_MAIL_ROOM`, default `room`). The box is
|
|
100
|
+
`heard` (`--sender`), because `ears` is this project's own box.
|
|
226
101
|
sessionmail trusts only its keeper's From (`_voice.KEEPER`), so the
|
|
227
102
|
plist loads only once that says `heard` too. `--to seats` is the older mail, To
|
|
228
103
|
each seat a turn is for. **Never run `--to seats` and `--to device` for
|
|
@@ -234,6 +109,12 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
|
|
|
234
109
|
one whose label is empty or names nobody, keeps `--speaker`
|
|
235
110
|
(`$EARS_SPEAKER`, else `unknown@<host>`: a voice the matcher could not
|
|
236
111
|
place is never mailed under a person's name).
|
|
112
|
+
When the user speaks over a seat's line, the capture may label the whole
|
|
113
|
+
turn with that seat, since the line's voice fills most of its audio. Once
|
|
114
|
+
the echo cut has taken that seat's line out and left the user's words, the
|
|
115
|
+
label is dropped: the remaining words get `--speaker` (else
|
|
116
|
+
`unknown@<host>`), never the seat whose echo was cut, and they can be a
|
|
117
|
+
reply to that seat. A label naming anyone else is kept.
|
|
237
118
|
`Ears-Echo` says a mouth line heard back through the mic was cut out of
|
|
238
119
|
the turn. On the speakers the mic hears the mouth, so a line's
|
|
239
120
|
words are cut wherever they sit in the user's turn: at its start (up to `3 + 5
|
|
@@ -319,21 +200,23 @@ Every key, its type and its default:
|
|
|
319
200
|
- **The heard log:** `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
|
|
320
201
|
one JSON line for each thing the ears heard and the mouth said, where
|
|
321
202
|
`<home>` is the home above. A new file starts each day.
|
|
322
|
-
- **The services' logs:**
|
|
203
|
+
- **The services' logs:** `~/.sessionkit/voice/log/` (`EARS_LOG_DIR` moves it).
|
|
323
204
|
`ears-keeper.log` is what the keeper did (devices, restarts, calls, a
|
|
324
205
|
deaf mic); `ears-keeper-capture.log` is the room capture's own output;
|
|
325
206
|
`ears-mail.log` is each turn mail sent or skipped; `ears-mouth.log` is
|
|
326
|
-
the mouth's player.
|
|
207
|
+
the mouth's player. Logs written before the move stay where they
|
|
208
|
+
were, and nothing deletes them. `ears services status` prints the folder.
|
|
327
209
|
|
|
328
210
|
Follow one with `tail -F`, for example
|
|
329
|
-
`tail -F
|
|
211
|
+
`tail -F ~/.sessionkit/voice/log/ears-keeper.log`, or today's heard log with
|
|
330
212
|
`tail -F <home>/logs/conversations/heard_$(date +%F).jsonl`.
|
|
331
213
|
Every log file and what each line means:
|
|
332
214
|
[docs/logs.md](docs/logs.md).
|
|
333
215
|
|
|
334
216
|
- **Health:** `ears services status` prints one row per launch agent
|
|
335
|
-
(`running, pid N`, `loaded, not running` or `not installed
|
|
336
|
-
|
|
217
|
+
(`running, pid N`, `loaded, not running` or `not installed`; the
|
|
218
|
+
`stt` row, the optional `--apple` server, also names what the ears hear
|
|
219
|
+
through), the keeper's `mode` (`input-watch`, or `polling system_profiler` and why),
|
|
337
220
|
the one that serves speech on `127.0.0.1:8890`, and the home in use.
|
|
338
221
|
|
|
339
222
|
## Use
|
|
@@ -377,11 +260,17 @@ at `http://127.0.0.1:<port>/`, never at `localhost`. The code is `src/talk/`.
|
|
|
377
260
|
`ears services install --only talk` keeps it running as a launch agent
|
|
378
261
|
(opt-in; a bare install never does).
|
|
379
262
|
|
|
263
|
+
In the kit's web page, Talk is a room: the kit's front door runs
|
|
264
|
+
`talk serve --kit` on a unix socket and logs you in, so there is nothing to
|
|
265
|
+
pair and you never run `--kit` by hand
|
|
266
|
+
([docs/talk.md](docs/talk.md#in-the-kits-web-page)).
|
|
267
|
+
|
|
380
268
|
## Install
|
|
381
269
|
|
|
382
270
|
The distribution is `sessionvoice` (`sessionvoice[nemotron]`, `sessionvoice[all]`);
|
|
383
|
-
it installs the `ears`, `ears-serve`, `mouth` and `
|
|
384
|
-
`pip install sessionvoice` carries what `mouth say` needs.
|
|
271
|
+
it installs the `ears`, `ears-serve`, `mouth`, `talk` and `sessionvoice-web`
|
|
272
|
+
commands. A bare `pip install sessionvoice` carries what `mouth say` needs.
|
|
273
|
+
The extras are
|
|
385
274
|
`capture` (the mic and call ears), `silero` (the Silero VAD), `smart-turn`
|
|
386
275
|
(the Smart Turn gate's ONNX backend), `nemotron` (mlx-audio) and `all`. An
|
|
387
276
|
import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
|
|
@@ -392,6 +281,15 @@ import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
|
|
|
392
281
|
ears services install --only sounds # opt-in: the ears and mouth sounds
|
|
393
282
|
ears services install --only talk # opt-in: talk serve, the live view
|
|
394
283
|
|
|
284
|
+
A phone-only Mac (for example one that runs a Delta bot, with no agent
|
|
285
|
+
seat to wake) installs with `--no-agent`: nobody is woken when you speak,
|
|
286
|
+
and the keeper's notices go to the log. `--agent NAME` is for a Mac where
|
|
287
|
+
an agent seat should be woken when you speak. Before it writes anything,
|
|
288
|
+
install says how big the speech models are (about 2.9 GB, fetched once,
|
|
289
|
+
after the speech server loads) and that the speech server runs as a login
|
|
290
|
+
service; `--dry-run` prints the same line, and `--no-prefetch` leaves the
|
|
291
|
+
download to the first call.
|
|
292
|
+
|
|
395
293
|
### The proof of a working install: you speak, and the agent WAKES and answers
|
|
396
294
|
|
|
397
295
|
**Not "a heard line appears".** The keeper alone writes the heard line, and
|
|
@@ -403,11 +301,19 @@ agent deaf to its person, and one that retires room mode on that proof goes
|
|
|
403
301
|
deaf for good. So the proof is: say "hey NAME", and NAME wakes and
|
|
404
302
|
answers.
|
|
405
303
|
|
|
406
|
-
`ears services install` (`--dry-run` lists the same checks and writes nothing)
|
|
304
|
+
`ears services install` (`--dry-run` lists the same checks and writes nothing).
|
|
305
|
+
Before it writes anything, it checks every prerequisite it can check without
|
|
306
|
+
building or fetching. When any are missing, it lists them all in one run, each
|
|
307
|
+
with its fix, writes nothing and exits 2. A launch agent that fails to load
|
|
308
|
+
also makes it exit 2, once every other service has been tried: one block on
|
|
309
|
+
stderr names each service that did not load, the install log and `ears
|
|
310
|
+
services status`. Every real run appends its output and launchctl's answers,
|
|
311
|
+
timestamped, to `install.log` in the log folder, `~/.sessionkit/voice/log/`, and prints that path
|
|
312
|
+
as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
|
|
407
313
|
|
|
408
314
|
- first finds the mlx-audio speech server on `:8890`, or installs its own
|
|
409
|
-
(`dev.sessionkit.voice.mlx-audio`), as
|
|
410
|
-
says, and prints which step it took. When it loads its own, the new
|
|
315
|
+
(`dev.sessionkit.voice.mlx-audio`), as [the speech server](docs/speech-server.md)
|
|
316
|
+
page says, and prints which step it took. When it loads its own, the new
|
|
411
317
|
`final` below is written without the probe: that server has not started
|
|
412
318
|
yet;
|
|
413
319
|
- installs `dev.sessionkit.voice.ears-mail` beside the mouth and the keeper, unless
|
|
@@ -453,14 +359,17 @@ answers.
|
|
|
453
359
|
read (no `message`, or one older than 0.2.0), it warns, makes no box,
|
|
454
360
|
checks neither, and goes on.
|
|
455
361
|
On a postfix host it also prints the box to subscribe to:
|
|
456
|
-
`message subscription add heard+
|
|
362
|
+
`message subscription add heard+room`;
|
|
457
363
|
- `--agent NAME` adds NAME's wake seat (`ears wake add NAME`) and sends the
|
|
458
364
|
keeper's notices to NAME (`EARS_NOTIFY`; `--notify ADDR` sends them
|
|
459
365
|
elsewhere). Without it, and when the wake file names no seat, nobody would
|
|
460
366
|
be woken, so an install that includes `mail` REFUSES (exit 2, before
|
|
461
367
|
anything is written; `--dry-run` says `REFUSED`, still lists, and exits 2
|
|
462
|
-
too). The refusal prints both forms
|
|
463
|
-
|
|
368
|
+
too). The refusal prints both forms and what each means:
|
|
369
|
+
`ears services install --agent NAME` for a Mac where an agent seat should
|
|
370
|
+
be woken when you speak, and `ears services install --no-agent` for a
|
|
371
|
+
phone-only Mac (for example a Delta bot), where nobody is woken and the
|
|
372
|
+
notices go to the log; then `ears wake add NAME`, to add a seat later. Inside
|
|
464
373
|
an agent seat (`TM_AGENT_ADDRESS` set) it suggests that address's local
|
|
465
374
|
part as NAME; it never installs on the suggestion. `--no-agent` installs
|
|
466
375
|
anyway, and install warns that NOBODY is woken. A wake file that already
|
|
@@ -475,9 +384,13 @@ answers.
|
|
|
475
384
|
missing: a checkout's `settings/`, else the wheel's `ears/_settings/`. It
|
|
476
385
|
then copies each beside its settings file as a dot file
|
|
477
386
|
(`.ears.schema.json`, `.ears-sounds.schema.json`, `.mouth.schema.json`);
|
|
478
|
-
-
|
|
479
|
-
|
|
480
|
-
|
|
387
|
+
- fetches the Silero VAD model, before any plist is written, when it is
|
|
388
|
+
missing and VoiceMode's copy (`~/.voicemode/models/silero/`) is not there to
|
|
389
|
+
link. It is the file `ears install` fetches, from the same pinned source,
|
|
390
|
+
checked against the same SHA-256, and install names the version, the source
|
|
391
|
+
and the destination. A failed fetch or a checksum mismatch is refused, and no
|
|
392
|
+
file is left behind. `--dry-run` says what it would fetch (the version and
|
|
393
|
+
the size) and fetches nothing. A keeper that finds no model says so once and
|
|
481
394
|
holds, and does not restart its capture every few seconds;
|
|
482
395
|
- probes a new `final` engine with one short clip before writing it to
|
|
483
396
|
`ears.json`, and falls back to whisper `:2022` (and says so) when it does
|
|
@@ -511,7 +424,7 @@ the agent's next tool call. `EARS_HEARD_HOOK=off` silences it without
|
|
|
511
424
|
editing the file.
|
|
512
425
|
|
|
513
426
|
`ears heard hook` is the canonical heard hook. A seat that also gets heard
|
|
514
|
-
mail (it subscribes to `heard
|
|
427
|
+
mail (it subscribes to `heard+room`) gets each finished turn by mail, so
|
|
515
428
|
give it `ears heard hook --partials-only || exit 1` instead: the hook then
|
|
516
429
|
shows only the partial preview of a turn still being spoken and the shown
|
|
517
430
|
events (wake word, barge-in, device change), never the finished turn a
|