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.
This commit is contained in:
58
cli.py
58
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.patch_stdout import patch_stdout
|
||||||
from prompt_toolkit.application import Application
|
from prompt_toolkit.application import Application
|
||||||
from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl, ConditionalContainer
|
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.filters import Condition
|
||||||
from prompt_toolkit.layout.dimension import Dimension
|
from prompt_toolkit.layout.dimension import Dimension
|
||||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||||
@@ -2077,20 +2078,54 @@ class HermesCLI:
|
|||||||
|
|
||||||
input_area.buffer.on_text_changed += _on_text_changed
|
input_area.buffer.on_text_changed += _on_text_changed
|
||||||
|
|
||||||
# Hint line above input: context-sensitive instructions for the
|
# --- Input processors for password masking and inline placeholder ---
|
||||||
# current UI state (sudo prompt, approval, clarify, interrupt).
|
|
||||||
|
# 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():
|
def get_hint_text():
|
||||||
import time as _time
|
import time as _time
|
||||||
|
|
||||||
# Sudo password prompt
|
|
||||||
if cli_ref._sudo_state:
|
if cli_ref._sudo_state:
|
||||||
remaining = max(0, int(cli_ref._sudo_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._sudo_deadline - _time.monotonic()))
|
||||||
return [
|
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)'),
|
('class:clarify-countdown', f' ({remaining}s)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Dangerous command approval
|
|
||||||
if cli_ref._approval_state:
|
if cli_ref._approval_state:
|
||||||
remaining = max(0, int(cli_ref._approval_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._approval_deadline - _time.monotonic()))
|
||||||
return [
|
return [
|
||||||
@@ -2098,7 +2133,6 @@ class HermesCLI:
|
|||||||
('class:clarify-countdown', f' ({remaining}s)'),
|
('class:clarify-countdown', f' ({remaining}s)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Clarify question
|
|
||||||
if cli_ref._clarify_state:
|
if cli_ref._clarify_state:
|
||||||
remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic()))
|
||||||
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
|
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
|
||||||
@@ -2112,19 +2146,12 @@ class HermesCLI:
|
|||||||
('class:clarify-countdown', countdown),
|
('class:clarify-countdown', countdown),
|
||||||
]
|
]
|
||||||
|
|
||||||
if not cli_ref._agent_running:
|
return []
|
||||||
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')]
|
|
||||||
|
|
||||||
def get_hint_height():
|
def get_hint_height():
|
||||||
if cli_ref._sudo_state or cli_ref._approval_state or cli_ref._clarify_state:
|
if cli_ref._sudo_state or cli_ref._approval_state or cli_ref._clarify_state:
|
||||||
return 1
|
return 1
|
||||||
return 1 if cli_ref._agent_running else 0
|
return 0
|
||||||
|
|
||||||
spacer = Window(
|
spacer = Window(
|
||||||
content=FormattedTextControl(get_hint_text),
|
content=FormattedTextControl(get_hint_text),
|
||||||
@@ -2297,6 +2324,7 @@ class HermesCLI:
|
|||||||
# Style for the application
|
# Style for the application
|
||||||
style = PTStyle.from_dict({
|
style = PTStyle.from_dict({
|
||||||
'input-area': '#FFF8DC',
|
'input-area': '#FFF8DC',
|
||||||
|
'placeholder': '#555555 italic',
|
||||||
'prompt': '#FFF8DC',
|
'prompt': '#FFF8DC',
|
||||||
'prompt-working': '#888888 italic',
|
'prompt-working': '#888888 italic',
|
||||||
'hint': '#555555 italic',
|
'hint': '#555555 italic',
|
||||||
|
|||||||
Reference in New Issue
Block a user