From bff37075f61e999908d15fb3f3e4327a9e2c9a2a Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 21 Feb 2026 12:33:48 -0800 Subject: [PATCH] feat: enhance CLI input handling with password masking and placeholder text - Added input processors for password masking during sudo prompts and inline placeholder text for various states in the CLI. - Implemented a custom placeholder processor to display context-sensitive instructions based on the current state (e.g., sudo, approval, clarify). - Updated hint text logic to improve user guidance during interactive prompts, enhancing overall user experience. --- cli.py | 58 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/cli.py b/cli.py index 35903b56..7b85c571 100755 --- a/cli.py +++ b/cli.py @@ -36,6 +36,7 @@ from prompt_toolkit.styles import Style as PTStyle from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.application import Application from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl, ConditionalContainer +from prompt_toolkit.layout.processors import Processor, Transformation, PasswordProcessor, ConditionalProcessor from prompt_toolkit.filters import Condition from prompt_toolkit.layout.dimension import Dimension from prompt_toolkit.layout.menus import CompletionsMenu @@ -2077,20 +2078,54 @@ class HermesCLI: input_area.buffer.on_text_changed += _on_text_changed - # Hint line above input: context-sensitive instructions for the - # current UI state (sudo prompt, approval, clarify, interrupt). + # --- Input processors for password masking and inline placeholder --- + + # Mask input with '*' when the sudo password prompt is active + input_area.control.input_processors.append( + ConditionalProcessor( + PasswordProcessor(), + filter=Condition(lambda: bool(cli_ref._sudo_state)), + ) + ) + + class _PlaceholderProcessor(Processor): + """Render grayed-out placeholder text inside the input when empty.""" + def __init__(self, get_text): + self._get_text = get_text + + def apply_transformation(self, ti): + if not ti.document.text and ti.lineno == 0: + text = self._get_text() + if text: + return Transformation(fragments=[('class:placeholder', text)]) + return Transformation(fragments=ti.fragments) + + def _get_placeholder(): + if cli_ref._sudo_state: + return "type password (hidden), Enter to skip" + if cli_ref._approval_state: + return "" + if cli_ref._clarify_state: + return "" + if cli_ref._agent_running: + return "type a message + Enter to interrupt, Ctrl+C to cancel" + return "" + + input_area.control.input_processors.append(_PlaceholderProcessor(_get_placeholder)) + + # Hint line above input: shown only for interactive prompts that need + # extra instructions (sudo countdown, approval navigation, clarify). + # The agent-running interrupt hint is now an inline placeholder above. def get_hint_text(): import time as _time - # Sudo password prompt if cli_ref._sudo_state: remaining = max(0, int(cli_ref._sudo_deadline - _time.monotonic())) return [ - ('class:hint', ' type password (hidden) and press Enter, or Enter to skip'), + ('class:hint', ' password hidden · Enter to skip'), ('class:clarify-countdown', f' ({remaining}s)'), ] - # Dangerous command approval if cli_ref._approval_state: remaining = max(0, int(cli_ref._approval_deadline - _time.monotonic())) return [ @@ -2098,7 +2133,6 @@ class HermesCLI: ('class:clarify-countdown', f' ({remaining}s)'), ] - # Clarify question if cli_ref._clarify_state: remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic())) countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else '' @@ -2112,19 +2146,12 @@ class HermesCLI: ('class:clarify-countdown', countdown), ] - if not cli_ref._agent_running: - return [] - - # Agent is running — show interrupt hint only when buffer is empty - buf = input_area.buffer - if buf.text: - return [('class:hint', ' press Enter to send interrupt')] - return [('class:hint', ' type a message + Enter to interrupt, or Ctrl+C to cancel')] + return [] def get_hint_height(): if cli_ref._sudo_state or cli_ref._approval_state or cli_ref._clarify_state: return 1 - return 1 if cli_ref._agent_running else 0 + return 0 spacer = Window( content=FormattedTextControl(get_hint_text), @@ -2297,6 +2324,7 @@ class HermesCLI: # Style for the application style = PTStyle.from_dict({ 'input-area': '#FFF8DC', + 'placeholder': '#555555 italic', 'prompt': '#FFF8DC', 'prompt-working': '#888888 italic', 'hint': '#555555 italic',