diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 63f5267ddf..19e9366a20 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -15,6 +15,7 @@ import importlib.util import json import logging import os +import re import shutil import sys import copy @@ -208,12 +209,23 @@ def prompt(question: str, default: str = None, password: bool = False) -> str: else: value = input(color(display, Colors.YELLOW)) - return value.strip() or default or "" + cleaned = _sanitize_pasted_input(value) + return cleaned.strip() or default or "" except (KeyboardInterrupt, EOFError): print() sys.exit(1) +_BRACKETED_PASTE_PATTERN = re.compile(r"\x1b\[\s*200~|\x1b\[\s*201~") + + +def _sanitize_pasted_input(value: str) -> str: + """Strip terminal bracketed-paste control markers from pasted text.""" + if not isinstance(value, str) or not value: + return value + return _BRACKETED_PASTE_PATTERN.sub("", value) + + def _curses_prompt_choice(question: str, choices: list, default: int = 0, description: str | None = None) -> int: """Single-select menu using curses. Delegates to curses_radiolist.""" from hermes_cli.curses_ui import curses_radiolist diff --git a/tests/hermes_cli/test_setup_prompt_menus.py b/tests/hermes_cli/test_setup_prompt_menus.py index fd017d87df..e776ba1fc5 100644 --- a/tests/hermes_cli/test_setup_prompt_menus.py +++ b/tests/hermes_cli/test_setup_prompt_menus.py @@ -1,6 +1,28 @@ from hermes_cli import setup as setup_mod +def test_prompt_strips_bracketed_paste_markers(monkeypatch): + monkeypatch.setattr( + "builtins.input", + lambda _prompt="": "\x1b[200~sk-ant-api-key\x1b[201~", + ) + + value = setup_mod.prompt("API key") + + assert value == "sk-ant-api-key" + + +def test_password_prompt_strips_bracketed_paste_markers(monkeypatch): + monkeypatch.setattr( + "getpass.getpass", + lambda _prompt="": "\x1b[200~secret-token\x1b[201~", + ) + + value = setup_mod.prompt("API key", password=True) + + assert value == "secret-token" + + def test_prompt_choice_uses_curses_helper(monkeypatch): monkeypatch.setattr(setup_mod, "_curses_prompt_choice", lambda question, choices, default=0, description=None: 1)