diff --git a/hermes_cli/cli_commands_mixin.py b/hermes_cli/cli_commands_mixin.py index ebf499e8a5..19903745fd 100644 --- a/hermes_cli/cli_commands_mixin.py +++ b/hermes_cli/cli_commands_mixin.py @@ -1,19 +1,26 @@ -"""Slash-command handlers for the interactive CLI, lifted out of ``cli.py``'s ``HermesCLI``. +"""Slash-command handlers for the interactive CLI (``HermesCLI`` inherits ``CLICommandsMixin``). -``HermesCLI`` inherits ``CLICommandsMixin`` so every ``self.`` resolves via the MRO. -cli.py-internal symbols (``_cprint``/``_ACCENT``/``save_config_value``…) are imported LAZILY -inside each handler via ``from cli import ...`` — the mixin never imports ``cli`` at top level -(import cycle). +cli.py-internal symbols (``_cprint``/``_ACCENT``/``save_config_value``…) are imported LAZILY inside +the helpers/handlers via ``from cli import ...`` — cli.py imports this module (cycle otherwise). """ from __future__ import annotations +import argparse +import atexit +import importlib +import io import json import os +import shlex +import subprocess import sys +import tempfile import threading import time import uuid +from contextlib import redirect_stdout, suppress +from io import StringIO from datetime import datetime from urllib.parse import urlparse @@ -24,72 +31,199 @@ from rich.panel import Panel from hermes_constants import display_hermes_home, is_termux as _is_termux_environment from agent.turn_context import extract_api_content_sidecar from hermes_cli.browser_connect import ( - DEFAULT_BROWSER_CDP_URL, - discover_local_cdp_url, - find_free_debug_port, - is_browser_debug_ready, - launch_chrome_debug, - local_port_in_use, - manual_chrome_debug_command, -) + DEFAULT_BROWSER_CDP_URL, discover_local_cdp_url, find_free_debug_port, is_browser_debug_ready, + launch_chrome_debug, local_port_in_use, manual_chrome_debug_command) -def _print_lightpanda_engine_status() -> None: - """``/browser status`` line(s) about ``browser.engine: lightpanda`` — silent unless set; - says whether it is in use or which higher-precedence setting shadows it.""" +# Output helpers. Slash-command text is user-visible: every literal below is load-bearing. +def _cp(*lines: str) -> None: + """``_cprint`` each line (lazy import: cli.py imports this module).""" + from cli import _cprint + for line in lines: + _cprint(line) + + +def _pr(*lines: str) -> None: + """print() each line.""" + for line in lines: + print(line) + + +def _save(key: str, value) -> bool: + """cli.save_config_value, resolved lazily (cli.py imports this module).""" + from cli import save_config_value + return save_config_value(key, value) + + +def _dim(text: str) -> str: + """Wrap ``text`` in the dim ANSI escape.""" + from cli import _DIM, _RST + return f"{_DIM}{text}{_RST}" + + +def _dim_line(text: str) -> str: + """Two-space indented dim line (the standard slash-command hint shape).""" + return " " + _dim(text) + + +def _accent(text: str) -> str: + """Wrap ``text`` in the skin accent ANSI escape.""" + from cli import _ACCENT, _RST + return f"{_ACCENT}{text}{_RST}" + + +def _accent_line(text: str) -> str: + """Two-space indented accent line (the standard slash-command headline shape).""" + return f" {_accent(text)}" + + +def _probe(module: str, name: str, default, *args): + """``.(*args)`` or ``default`` when the import or the call fails (optional + subsystems: browser backends, async delegations, wake word, ...).""" try: - from tools.browser_tool import lightpanda_engine_status, _using_lightpanda_engine - - if not _using_lightpanda_engine(): - return - used, reason = lightpanda_engine_status() + return getattr(importlib.import_module(module), name)(*args) except Exception: - return - if not used: - print(f" ⚠ browser.engine is 'lightpanda' but it is NOT in use: {reason}") - return - print(f" Engine: Lightpanda — {reason} (no screenshots)") + return default + + +class _TTYBuf(StringIO): + """StringIO that claims to be a TTY so ``hermes_cli.colors.color()`` still emits ANSI escapes.""" + def isatty(self) -> bool: + return True + + +_FAILED = object() + + +def _attempt(label: str, errors, fn, *args, **kwargs): + """``fn(*args, **kwargs)``, or ``_FAILED`` after printing ``