diff --git a/cli.py b/cli.py index abd4d2391..38701bd1a 100644 --- a/cli.py +++ b/cli.py @@ -7808,10 +7808,14 @@ class HermesCLI: try: from hermes_cli.config import load_config _raw_ptt = load_config().get("voice", {}).get("record_key", "ctrl+b") - _ptt_key = _raw_ptt.lower().replace("ctrl+", "c-").replace("alt+", "a-") + _raw_ptt_lower = _raw_ptt.lower() + if "alt+" in _raw_ptt_lower: + _ptt_key = "c-b" + else: + _ptt_key = _raw_ptt_lower.replace("ctrl+", "c-").replace("shift+", "s-") except Exception: _ptt_key = "c-b" - _ptt_display = _ptt_key.replace("c-", "Ctrl+").upper() + _ptt_display = _ptt_key.replace("c-", "Ctrl+").replace("s-", "Shift+").upper() _cprint(f"\n{_ACCENT}Voice mode enabled{tts_status}{_RST}") _cprint(f" {_DIM}{_ptt_display} to start/stop recording{_RST}") _cprint(f" {_DIM}/voice tts to toggle speech output{_RST}") @@ -9611,7 +9615,13 @@ class HermesCLI: try: from hermes_cli.config import load_config _raw_key = load_config().get("voice", {}).get("record_key", "ctrl+b") - _voice_key = _raw_key.lower().replace("ctrl+", "c-").replace("alt+", "a-") + _raw_lower = _raw_key.lower() + if "alt+" in _raw_lower: + import sys as _sys + print(f"[hermes] voice.record_key '{_raw_key}': alt+ modifier not supported by prompt_toolkit, falling back to ctrl+b", file=_sys.stderr) + _voice_key = "c-b" + else: + _voice_key = _raw_lower.replace("ctrl+", "c-").replace("shift+", "s-") except Exception: _voice_key = "c-b" diff --git a/tests/hermes_cli/test_voice_record_key.py b/tests/hermes_cli/test_voice_record_key.py new file mode 100644 index 000000000..c0cd47ac4 --- /dev/null +++ b/tests/hermes_cli/test_voice_record_key.py @@ -0,0 +1,41 @@ +"""Regression tests for voice.record_key alt+ modifier handling (#11387). + +Setting voice.record_key to alt+ in config.yaml crashed startup because +prompt_toolkit only accepts c- (Ctrl) and s- (Shift) prefixes, not a- (Alt). +""" +import pytest +from unittest.mock import patch, MagicMock + + +def _translate_voice_key(raw_key: str) -> str: + """Mirrors the key-translation logic in cli.py (keybinding setup ~L8853).""" + raw_lower = raw_key.lower() + if "alt+" in raw_lower: + return "c-b" + return raw_lower.replace("ctrl+", "c-").replace("shift+", "s-") + + +class TestVoiceRecordKeyTranslation: + """Unit tests for the record_key → prompt_toolkit key translation.""" + + def test_ctrl_b_default(self): + assert _translate_voice_key("ctrl+b") == "c-b" + + def test_ctrl_t(self): + assert _translate_voice_key("ctrl+t") == "c-t" + + def test_shift_f1(self): + assert _translate_voice_key("shift+f1") == "s-f1" + + def test_alt_space_falls_back(self): + """alt+ modifier is unsupported; must fall back to c-b (#11387).""" + assert _translate_voice_key("alt+space") == "c-b" + + def test_alt_x_falls_back(self): + assert _translate_voice_key("alt+x") == "c-b" + + def test_alt_mixed_case_falls_back(self): + assert _translate_voice_key("Alt+Space") == "c-b" + + def test_ctrl_uppercase(self): + assert _translate_voice_key("CTRL+B") == "c-b"