From 9de4a38ce06eff052d09772b2a975ef029bc042d Mon Sep 17 00:00:00 2001 From: Dylan Socolobsky Date: Mon, 13 Apr 2026 12:14:13 -0300 Subject: [PATCH] fix(tui): make "/tools list" show real colors instead of "?[32m" etc. gibberish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The colored ✓/✗ marks in /tools list, /tools enable, and /tools disable were showing up as "?[32m✓ enabled?[0m" instead of green and red. The colors come out as ANSI escape codes, but the tui eats the ESC byte and replaces it with "?" when those codes are printed straight to stdout. They need to go through prompt_toolkit's renderer. Fix: capture the command's output and re-print each line through _cprint(), the same workaround used elsewhere for #2262. The capture buffer fakes isatty()=True so the color helper still emits escapes (StringIO.isatty() is False, which would otherwise strip colors). The capture path only runs inside the TUI; standalone CLI and tests go straight through to real stdout where colors already work. --- cli.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/cli.py b/cli.py index 7f93f0736..15f60aa30 100644 --- a/cli.py +++ b/cli.py @@ -4210,8 +4210,37 @@ class HermesCLI: """ import shlex from argparse import Namespace + from contextlib import redirect_stdout + from io import StringIO from hermes_cli.tools_config import tools_disable_enable_command + def _run_capture(ns: Namespace) -> None: + """Run tools_disable_enable_command, routing its ANSI-colored + print() output through _cprint when inside the interactive TUI + so escapes aren't mangled by patch_stdout's StdoutProxy into + garbled '?[32m...?[0m' text. + + Outside the TUI (standalone mode, tests), call straight through + so real stdout / pytest capture works as expected. + """ + # Standalone/tests, run as usual + if getattr(self, "_app", None) is None: + tools_disable_enable_command(ns) + return + + # Buffer reports isatty()=True so color() in hermes_cli/colors.py + # still emits ANSI escapes. StringIO.isatty() is False, which + # would otherwise strip all colors before we re-render them. + class _TTYBuf(StringIO): + def isatty(self) -> bool: + return True + + buf = _TTYBuf() + with redirect_stdout(buf): + tools_disable_enable_command(ns) + for line in buf.getvalue().splitlines(): + _cprint(line) + try: parts = shlex.split(cmd) except ValueError: @@ -4223,8 +4252,7 @@ class HermesCLI: return if subcommand == "list": - tools_disable_enable_command( - Namespace(tools_action="list", platform="cli")) + _run_capture(Namespace(tools_action="list", platform="cli")) return names = parts[2:] @@ -4241,8 +4269,7 @@ class HermesCLI: label = ", ".join(names) _cprint(f"{_ACCENT}{verb} {label}...{_RST}") - tools_disable_enable_command( - Namespace(tools_action=subcommand, names=names, platform="cli")) + _run_capture(Namespace(tools_action=subcommand, names=names, platform="cli")) # Reset session so the new tool config is picked up from a clean state from hermes_cli.tools_config import _get_platform_tools