From 645a2f482de6cefcf8694a5f2f896889804d3096 Mon Sep 17 00:00:00 2001 From: Harry Riddle Date: Mon, 27 Apr 2026 01:22:51 +0700 Subject: [PATCH] fix(cli): fix shortcut config conflict in hermes_cli --- cli.py | 93 +++++++++++++++++++++++++++++++++++++++++++- hermes_cli/config.py | 1 + 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/cli.py b/cli.py index 3b9f6af531..96ecb8ecfd 100644 --- a/cli.py +++ b/cli.py @@ -10483,7 +10483,98 @@ class HermesCLI: else: self._should_exit = True event.app.exit() - + + @kb.add('c-S-c') # Ctrl+Shift+C + def handle_ctrl_shift_c(event): + """Copy text to clipboard (terminal-native). + + This is a no-op at the application level. Terminal emulators + handle the actual copy operation when Ctrl+Shift+C is pressed. + This binding prevents Hermes from intercepting the keystroke + as an interrupt signal. + + On macOS the standard copy shortcut is Cmd+C (no Hermes binding + needed). On Linux/Windows Ctrl+Shift+C is the conventional + terminal copy shortcut. + """ + return # No-op — let the terminal perform native copy + + @kb.add('c-q') # Ctrl+Q + def handle_ctrl_q(event): + """Alternative interrupt/exit shortcut (Ctrl+Q). + + Behaves like Ctrl+C: cancels active prompts, interrupts the + running agent, or clears the input buffer. Does not support + the double-press 'force exit' feature of Ctrl+C. + """ + # Cancel active voice recording. + _should_cancel_voice = False + _recorder_ref = None + with cli_ref._voice_lock: + if cli_ref._voice_recording and cli_ref._voice_recorder: + _recorder_ref = cli_ref._voice_recorder + cli_ref._voice_recording = False + cli_ref._voice_continuous = False + _should_cancel_voice = True + if _should_cancel_voice: + _cprint(f"\n{_DIM}Recording cancelled.{_RST}") + threading.Thread( + target=_recorder_ref.cancel, daemon=True + ).start() + event.app.invalidate() + return + + # Cancel sudo prompt + if self._sudo_state: + self._sudo_state["response_queue"].put("") + self._sudo_state = None + event.app.invalidate() + return + + # Cancel secret prompt + if self._secret_state: + self._cancel_secret_capture() + event.app.current_buffer.reset() + event.app.invalidate() + return + + # Cancel approval prompt (deny) + if self._approval_state: + self._approval_state["response_queue"].put("deny") + self._approval_state = None + event.app.invalidate() + return + + # Cancel /model picker + if self._model_picker_state: + self._close_model_picker() + event.app.current_buffer.reset() + event.app.invalidate() + return + + # Cancel clarify prompt + if self._clarify_state: + self._clarify_state["response_queue"].put( + "The user cancelled. Use your best judgement to proceed." + ) + self._clarify_state = None + self._clarify_freetext = False + event.app.current_buffer.reset() + event.app.invalidate() + return + + if self._agent_running and self.agent: + print("\n⚡ Interrupting agent...") + self.agent.interrupt() + else: + if event.app.current_buffer.text or self._attached_images: + event.app.current_buffer.reset() + self._attached_images.clear() + event.app.invalidate() + else: + self._should_exit = True + event.app.exit() + @kb.add('c-d') def handle_ctrl_d(event): """Ctrl+D: delete char under cursor (standard readline behaviour). diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 0f34d98528..8cf33b90fe 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -809,6 +809,7 @@ DEFAULT_CONFIG = { "enabled": False, "fields": ["model", "context_pct", "cwd"], # Order shown; drop any to hide }, + "copy_shortcut": "auto", # "auto" (platform default) | "ctrl_c" | "ctrl_shift_c" | "disabled" }, # Web dashboard settings