fix(tui): make "/tools list" show real colors instead of "?[32m" etc. gibberish

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.
This commit is contained in:
Dylan Socolobsky 2026-04-13 12:14:13 -03:00 committed by Teknium
parent 11369a78f9
commit 9de4a38ce0

35
cli.py
View file

@ -4210,8 +4210,37 @@ class HermesCLI:
""" """
import shlex import shlex
from argparse import Namespace from argparse import Namespace
from contextlib import redirect_stdout
from io import StringIO
from hermes_cli.tools_config import tools_disable_enable_command 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: try:
parts = shlex.split(cmd) parts = shlex.split(cmd)
except ValueError: except ValueError:
@ -4223,8 +4252,7 @@ class HermesCLI:
return return
if subcommand == "list": if subcommand == "list":
tools_disable_enable_command( _run_capture(Namespace(tools_action="list", platform="cli"))
Namespace(tools_action="list", platform="cli"))
return return
names = parts[2:] names = parts[2:]
@ -4241,8 +4269,7 @@ class HermesCLI:
label = ", ".join(names) label = ", ".join(names)
_cprint(f"{_ACCENT}{verb} {label}...{_RST}") _cprint(f"{_ACCENT}{verb} {label}...{_RST}")
tools_disable_enable_command( _run_capture(Namespace(tools_action=subcommand, names=names, platform="cli"))
Namespace(tools_action=subcommand, names=names, platform="cli"))
# Reset session so the new tool config is picked up from a clean state # Reset session so the new tool config is picked up from a clean state
from hermes_cli.tools_config import _get_platform_tools from hermes_cli.tools_config import _get_platform_tools