mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(termux): tighten voice setup and mobile chat UX
This commit is contained in:
parent
769ec1ee1a
commit
c3141429b7
6 changed files with 136 additions and 9 deletions
34
cli.py
34
cli.py
|
|
@ -1899,7 +1899,26 @@ class HermesCLI:
|
||||||
return 0
|
return 0
|
||||||
return 0 if self._use_minimal_tui_chrome(width=width) else 1
|
return 0 if self._use_minimal_tui_chrome(width=width) else 1
|
||||||
|
|
||||||
|
def _get_voice_status_fragments(self, width: Optional[int] = None):
|
||||||
|
"""Return the voice status bar fragments for the interactive TUI."""
|
||||||
|
width = width or self._get_tui_terminal_width()
|
||||||
|
compact = self._use_minimal_tui_chrome(width=width)
|
||||||
|
if self._voice_recording:
|
||||||
|
if compact:
|
||||||
|
return [("class:voice-status-recording", " ● REC ")]
|
||||||
|
return [("class:voice-status-recording", " ● REC Ctrl+B to stop ")]
|
||||||
|
if self._voice_processing:
|
||||||
|
if compact:
|
||||||
|
return [("class:voice-status", " ◉ STT ")]
|
||||||
|
return [("class:voice-status", " ◉ Transcribing... ")]
|
||||||
|
if compact:
|
||||||
|
return [("class:voice-status", " 🎤 Ctrl+B ")]
|
||||||
|
tts = " | TTS on" if self._voice_tts else ""
|
||||||
|
cont = " | Continuous" if self._voice_continuous else ""
|
||||||
|
return [("class:voice-status", f" 🎤 Voice mode{tts}{cont} — Ctrl+B to record ")]
|
||||||
|
|
||||||
def _build_status_bar_text(self, width: Optional[int] = None) -> str:
|
def _build_status_bar_text(self, width: Optional[int] = None) -> str:
|
||||||
|
"""Return a compact one-line session status string for the TUI footer."""
|
||||||
try:
|
try:
|
||||||
snapshot = self._get_status_bar_snapshot()
|
snapshot = self._get_status_bar_snapshot()
|
||||||
if width is None:
|
if width is None:
|
||||||
|
|
@ -5979,6 +5998,13 @@ class HermesCLI:
|
||||||
reqs = check_voice_requirements()
|
reqs = check_voice_requirements()
|
||||||
if not reqs["audio_available"]:
|
if not reqs["audio_available"]:
|
||||||
if _is_termux_environment():
|
if _is_termux_environment():
|
||||||
|
details = reqs.get("details", "")
|
||||||
|
if "Termux:API Android app is not installed" in details:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Termux:API command package detected, but the Android app is missing.\n"
|
||||||
|
"Install/update the Termux:API Android app, then retry /voice on.\n"
|
||||||
|
"Fallback: pkg install python-numpy portaudio && python -m pip install sounddevice"
|
||||||
|
)
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Voice mode requires either Termux:API microphone access or Python audio libraries.\n"
|
"Voice mode requires either Termux:API microphone access or Python audio libraries.\n"
|
||||||
"Option 1: pkg install termux-api and install the Termux:API Android app\n"
|
"Option 1: pkg install termux-api and install the Termux:API Android app\n"
|
||||||
|
|
@ -8338,13 +8364,7 @@ class HermesCLI:
|
||||||
|
|
||||||
# Persistent voice mode status bar (visible only when voice mode is on)
|
# Persistent voice mode status bar (visible only when voice mode is on)
|
||||||
def _get_voice_status():
|
def _get_voice_status():
|
||||||
if cli_ref._voice_recording:
|
return cli_ref._get_voice_status_fragments()
|
||||||
return [('class:voice-status-recording', ' ● REC Ctrl+B to stop ')]
|
|
||||||
if cli_ref._voice_processing:
|
|
||||||
return [('class:voice-status', ' ◉ Transcribing... ')]
|
|
||||||
tts = " | TTS on" if cli_ref._voice_tts else ""
|
|
||||||
cont = " | Continuous" if cli_ref._voice_continuous else ""
|
|
||||||
return [('class:voice-status', f' 🎤 Voice mode{tts}{cont} — Ctrl+B to record ')]
|
|
||||||
|
|
||||||
voice_status_bar = ConditionalContainer(
|
voice_status_bar = ConditionalContainer(
|
||||||
Window(
|
Window(
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,17 @@ def _system_package_install_cmd(pkg: str) -> str:
|
||||||
return f"sudo apt install {pkg}"
|
return f"sudo apt install {pkg}"
|
||||||
|
|
||||||
|
|
||||||
|
def _termux_browser_setup_steps(node_installed: bool) -> list[str]:
|
||||||
|
steps: list[str] = []
|
||||||
|
step = 1
|
||||||
|
if not node_installed:
|
||||||
|
steps.append(f"{step}) pkg install nodejs")
|
||||||
|
step += 1
|
||||||
|
steps.append(f"{step}) npm install -g agent-browser")
|
||||||
|
steps.append(f"{step + 1}) agent-browser install")
|
||||||
|
return steps
|
||||||
|
|
||||||
|
|
||||||
def _has_provider_env_config(content: str) -> bool:
|
def _has_provider_env_config(content: str) -> bool:
|
||||||
"""Return True when ~/.hermes/.env contains provider auth/base URL settings."""
|
"""Return True when ~/.hermes/.env contains provider auth/base URL settings."""
|
||||||
return any(key in content for key in _PROVIDER_ENV_HINTS)
|
return any(key in content for key in _PROVIDER_ENV_HINTS)
|
||||||
|
|
@ -597,12 +608,18 @@ def run_doctor(args):
|
||||||
if _is_termux():
|
if _is_termux():
|
||||||
check_info("agent-browser is not installed (expected in the tested Termux path)")
|
check_info("agent-browser is not installed (expected in the tested Termux path)")
|
||||||
check_info("Install it manually later with: npm install -g agent-browser && agent-browser install")
|
check_info("Install it manually later with: npm install -g agent-browser && agent-browser install")
|
||||||
|
check_info("Termux browser setup:")
|
||||||
|
for step in _termux_browser_setup_steps(node_installed=True):
|
||||||
|
check_info(step)
|
||||||
else:
|
else:
|
||||||
check_warn("agent-browser not installed", "(run: npm install)")
|
check_warn("agent-browser not installed", "(run: npm install)")
|
||||||
else:
|
else:
|
||||||
if _is_termux():
|
if _is_termux():
|
||||||
check_info("Node.js not found (browser tools are optional in the tested Termux path)")
|
check_info("Node.js not found (browser tools are optional in the tested Termux path)")
|
||||||
check_info("Install Node.js on Termux with: pkg install nodejs")
|
check_info("Install Node.js on Termux with: pkg install nodejs")
|
||||||
|
check_info("Termux browser setup:")
|
||||||
|
for step in _termux_browser_setup_steps(node_installed=False):
|
||||||
|
check_info(step)
|
||||||
else:
|
else:
|
||||||
check_warn("Node.js not found", "(optional, needed for browser tools)")
|
check_warn("Node.js not found", "(optional, needed for browser tools)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,28 @@ class TestCLIStatusBar:
|
||||||
cli_obj._spinner_text = ""
|
cli_obj._spinner_text = ""
|
||||||
assert cli_obj._spinner_widget_height(width=90) == 0
|
assert cli_obj._spinner_widget_height(width=90) == 0
|
||||||
|
|
||||||
|
def test_voice_status_bar_compacts_on_narrow_terminals(self):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj._voice_mode = True
|
||||||
|
cli_obj._voice_recording = False
|
||||||
|
cli_obj._voice_processing = False
|
||||||
|
cli_obj._voice_tts = True
|
||||||
|
cli_obj._voice_continuous = True
|
||||||
|
|
||||||
|
fragments = cli_obj._get_voice_status_fragments(width=50)
|
||||||
|
|
||||||
|
assert fragments == [("class:voice-status", " 🎤 Ctrl+B ")]
|
||||||
|
|
||||||
|
def test_voice_recording_status_bar_compacts_on_narrow_terminals(self):
|
||||||
|
cli_obj = _make_cli()
|
||||||
|
cli_obj._voice_mode = True
|
||||||
|
cli_obj._voice_recording = True
|
||||||
|
cli_obj._voice_processing = False
|
||||||
|
|
||||||
|
fragments = cli_obj._get_voice_status_fragments(width=50)
|
||||||
|
|
||||||
|
assert fragments == [("class:voice-status-recording", " ● REC ")]
|
||||||
|
|
||||||
|
|
||||||
class TestCLIUsageReport:
|
class TestCLIUsageReport:
|
||||||
def test_show_usage_includes_estimated_cost(self, capsys):
|
def test_show_usage_includes_estimated_cost(self, capsys):
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,10 @@ def test_run_doctor_termux_treats_docker_and_browser_warnings_as_expected(monkey
|
||||||
assert "Docker backend is not available inside Termux" in out
|
assert "Docker backend is not available inside Termux" in out
|
||||||
assert "Node.js not found (browser tools are optional in the tested Termux path)" in out
|
assert "Node.js not found (browser tools are optional in the tested Termux path)" in out
|
||||||
assert "Install Node.js on Termux with: pkg install nodejs" in out
|
assert "Install Node.js on Termux with: pkg install nodejs" in out
|
||||||
|
assert "Termux browser setup:" in out
|
||||||
|
assert "1) pkg install nodejs" in out
|
||||||
|
assert "2) npm install -g agent-browser" in out
|
||||||
|
assert "3) agent-browser install" in out
|
||||||
assert "docker not found (optional)" not in out
|
assert "docker not found (optional)" not in out
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,7 @@ class TestDetectAudioEnvironment:
|
||||||
monkeypatch.delenv("SSH_TTY", raising=False)
|
monkeypatch.delenv("SSH_TTY", raising=False)
|
||||||
monkeypatch.delenv("SSH_CONNECTION", raising=False)
|
monkeypatch.delenv("SSH_CONNECTION", raising=False)
|
||||||
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (_ for _ in ()).throw(ImportError("no audio libs")))
|
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (_ for _ in ()).throw(ImportError("no audio libs")))
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: None)
|
||||||
|
|
||||||
from tools.voice_mode import detect_audio_environment
|
from tools.voice_mode import detect_audio_environment
|
||||||
result = detect_audio_environment()
|
result = detect_audio_environment()
|
||||||
|
|
@ -198,6 +199,22 @@ class TestDetectAudioEnvironment:
|
||||||
assert any("pkg install python-numpy portaudio" in w for w in result["warnings"])
|
assert any("pkg install python-numpy portaudio" in w for w in result["warnings"])
|
||||||
assert any("python -m pip install sounddevice" in w for w in result["warnings"])
|
assert any("python -m pip install sounddevice" in w for w in result["warnings"])
|
||||||
|
|
||||||
|
def test_termux_api_package_without_android_app_blocks_voice(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
|
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
||||||
|
monkeypatch.delenv("SSH_CLIENT", raising=False)
|
||||||
|
monkeypatch.delenv("SSH_TTY", raising=False)
|
||||||
|
monkeypatch.delenv("SSH_CONNECTION", raising=False)
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: False)
|
||||||
|
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (_ for _ in ()).throw(ImportError("no audio libs")))
|
||||||
|
|
||||||
|
from tools.voice_mode import detect_audio_environment
|
||||||
|
result = detect_audio_environment()
|
||||||
|
|
||||||
|
assert result["available"] is False
|
||||||
|
assert any("Termux:API Android app is not installed" in w for w in result["warnings"])
|
||||||
|
|
||||||
|
|
||||||
def test_termux_api_microphone_allows_voice_without_sounddevice(self, monkeypatch):
|
def test_termux_api_microphone_allows_voice_without_sounddevice(self, monkeypatch):
|
||||||
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
|
|
@ -206,6 +223,7 @@ class TestDetectAudioEnvironment:
|
||||||
monkeypatch.delenv("SSH_TTY", raising=False)
|
monkeypatch.delenv("SSH_TTY", raising=False)
|
||||||
monkeypatch.delenv("SSH_CONNECTION", raising=False)
|
monkeypatch.delenv("SSH_CONNECTION", raising=False)
|
||||||
monkeypatch.setattr("tools.voice_mode.shutil.which", lambda cmd: "/data/data/com.termux/files/usr/bin/termux-microphone-record" if cmd == "termux-microphone-record" else None)
|
monkeypatch.setattr("tools.voice_mode.shutil.which", lambda cmd: "/data/data/com.termux/files/usr/bin/termux-microphone-record" if cmd == "termux-microphone-record" else None)
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: True)
|
||||||
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (_ for _ in ()).throw(ImportError("no audio libs")))
|
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (_ for _ in ()).throw(ImportError("no audio libs")))
|
||||||
|
|
||||||
from tools.voice_mode import detect_audio_environment
|
from tools.voice_mode import detect_audio_environment
|
||||||
|
|
@ -224,6 +242,7 @@ class TestCheckVoiceRequirements:
|
||||||
def test_termux_api_capture_counts_as_audio_available(self, monkeypatch):
|
def test_termux_api_capture_counts_as_audio_available(self, monkeypatch):
|
||||||
monkeypatch.setattr("tools.voice_mode._audio_available", lambda: False)
|
monkeypatch.setattr("tools.voice_mode._audio_available", lambda: False)
|
||||||
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: True)
|
||||||
monkeypatch.setattr("tools.voice_mode.detect_audio_environment", lambda: {"available": True, "warnings": [], "notices": ["Termux:API microphone recording available"]})
|
monkeypatch.setattr("tools.voice_mode.detect_audio_environment", lambda: {"available": True, "warnings": [], "notices": ["Termux:API microphone recording available"]})
|
||||||
monkeypatch.setattr("tools.transcription_tools._get_provider", lambda cfg: "openai")
|
monkeypatch.setattr("tools.transcription_tools._get_provider", lambda cfg: "openai")
|
||||||
|
|
||||||
|
|
@ -286,6 +305,7 @@ class TestCreateAudioRecorder:
|
||||||
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
||||||
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: True)
|
||||||
|
|
||||||
from tools.voice_mode import create_audio_recorder, TermuxAudioRecorder
|
from tools.voice_mode import create_audio_recorder, TermuxAudioRecorder
|
||||||
recorder = create_audio_recorder()
|
recorder = create_audio_recorder()
|
||||||
|
|
@ -293,6 +313,17 @@ class TestCreateAudioRecorder:
|
||||||
assert isinstance(recorder, TermuxAudioRecorder)
|
assert isinstance(recorder, TermuxAudioRecorder)
|
||||||
assert recorder.supports_silence_autostop is False
|
assert recorder.supports_silence_autostop is False
|
||||||
|
|
||||||
|
def test_termux_without_android_app_falls_back_to_audio_recorder(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
|
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: False)
|
||||||
|
|
||||||
|
from tools.voice_mode import create_audio_recorder, AudioRecorder
|
||||||
|
recorder = create_audio_recorder()
|
||||||
|
|
||||||
|
assert isinstance(recorder, AudioRecorder)
|
||||||
|
|
||||||
|
|
||||||
class TestTermuxAudioRecorder:
|
class TestTermuxAudioRecorder:
|
||||||
def test_start_and_stop_use_termux_microphone_commands(self, monkeypatch, temp_voice_dir):
|
def test_start_and_stop_use_termux_microphone_commands(self, monkeypatch, temp_voice_dir):
|
||||||
|
|
@ -308,6 +339,7 @@ class TestTermuxAudioRecorder:
|
||||||
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
||||||
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: True)
|
||||||
monkeypatch.setattr("tools.voice_mode.time.strftime", lambda fmt: "20260409_120000")
|
monkeypatch.setattr("tools.voice_mode.time.strftime", lambda fmt: "20260409_120000")
|
||||||
monkeypatch.setattr("tools.voice_mode.subprocess.run", fake_run)
|
monkeypatch.setattr("tools.voice_mode.subprocess.run", fake_run)
|
||||||
|
|
||||||
|
|
@ -332,6 +364,7 @@ class TestTermuxAudioRecorder:
|
||||||
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
|
||||||
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
|
||||||
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
monkeypatch.setattr("tools.voice_mode._termux_microphone_command", lambda: "/data/data/com.termux/files/usr/bin/termux-microphone-record")
|
||||||
|
monkeypatch.setattr("tools.voice_mode._termux_api_app_installed", lambda: True)
|
||||||
monkeypatch.setattr("tools.voice_mode.time.strftime", lambda fmt: "20260409_120000")
|
monkeypatch.setattr("tools.voice_mode.time.strftime", lambda fmt: "20260409_120000")
|
||||||
monkeypatch.setattr("tools.voice_mode.subprocess.run", fake_run)
|
monkeypatch.setattr("tools.voice_mode.subprocess.run", fake_run)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,24 @@ def _termux_media_player_command() -> Optional[str]:
|
||||||
return shutil.which("termux-media-player")
|
return shutil.which("termux-media-player")
|
||||||
|
|
||||||
|
|
||||||
|
def _termux_api_app_installed() -> bool:
|
||||||
|
if not _is_termux_environment():
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["pm", "list", "packages", "com.termux.api"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
return "package:com.termux.api" in (result.stdout or "")
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _termux_voice_capture_available() -> bool:
|
def _termux_voice_capture_available() -> bool:
|
||||||
return _termux_microphone_command() is not None
|
return _termux_microphone_command() is not None and _termux_api_app_installed()
|
||||||
|
|
||||||
|
|
||||||
def detect_audio_environment() -> dict:
|
def detect_audio_environment() -> dict:
|
||||||
|
|
@ -84,7 +100,9 @@ def detect_audio_environment() -> dict:
|
||||||
"""
|
"""
|
||||||
warnings = [] # hard-fail: these block voice mode
|
warnings = [] # hard-fail: these block voice mode
|
||||||
notices = [] # informational: logged but don't block
|
notices = [] # informational: logged but don't block
|
||||||
termux_capture = _termux_voice_capture_available()
|
termux_mic_cmd = _termux_microphone_command()
|
||||||
|
termux_app_installed = _termux_api_app_installed()
|
||||||
|
termux_capture = bool(termux_mic_cmd and termux_app_installed)
|
||||||
|
|
||||||
# SSH detection
|
# SSH detection
|
||||||
if any(os.environ.get(v) for v in ('SSH_CLIENT', 'SSH_TTY', 'SSH_CONNECTION')):
|
if any(os.environ.get(v) for v in ('SSH_CLIENT', 'SSH_TTY', 'SSH_CONNECTION')):
|
||||||
|
|
@ -133,11 +151,19 @@ def detect_audio_environment() -> dict:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if termux_capture:
|
if termux_capture:
|
||||||
notices.append("Termux:API microphone recording available (sounddevice not required)")
|
notices.append("Termux:API microphone recording available (sounddevice not required)")
|
||||||
|
elif termux_mic_cmd and not termux_app_installed:
|
||||||
|
warnings.append(
|
||||||
|
"Termux:API Android app is not installed. Install/update the Termux:API app to use termux-microphone-record."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
warnings.append(f"Audio libraries not installed ({_voice_capture_install_hint()})")
|
warnings.append(f"Audio libraries not installed ({_voice_capture_install_hint()})")
|
||||||
except OSError:
|
except OSError:
|
||||||
if termux_capture:
|
if termux_capture:
|
||||||
notices.append("Termux:API microphone recording available (PortAudio not required)")
|
notices.append("Termux:API microphone recording available (PortAudio not required)")
|
||||||
|
elif termux_mic_cmd and not termux_app_installed:
|
||||||
|
warnings.append(
|
||||||
|
"Termux:API Android app is not installed. Install/update the Termux:API app to use termux-microphone-record."
|
||||||
|
)
|
||||||
elif _is_termux_environment():
|
elif _is_termux_environment():
|
||||||
warnings.append(
|
warnings.append(
|
||||||
"PortAudio system library not found -- install it first:\n"
|
"PortAudio system library not found -- install it first:\n"
|
||||||
|
|
@ -257,6 +283,11 @@ class TermuxAudioRecorder:
|
||||||
"Install with: pkg install termux-api\n"
|
"Install with: pkg install termux-api\n"
|
||||||
"Then install/update the Termux:API Android app."
|
"Then install/update the Termux:API Android app."
|
||||||
)
|
)
|
||||||
|
if not _termux_api_app_installed():
|
||||||
|
raise RuntimeError(
|
||||||
|
"Termux voice capture requires the Termux:API Android app.\n"
|
||||||
|
"Install/update the Termux:API app, then retry /voice on."
|
||||||
|
)
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if self._recording:
|
if self._recording:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue