hermes-agent/hermes_cli
emozilla 29b337bca7 feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard
Exposes hermes --tui over a PTY-backed WebSocket so the dashboard can
embed the real TUI rather than reimplement its surface. The browser
attaches xterm.js to the socket; keystrokes flow in, PTY output bytes
flow out.

Architecture:

    browser <Terminal> (xterm.js)
           │  onData ───► ws.send(keystrokes)
           │  onResize ► ws.send('\x1b[RESIZE:cols;rows]')
           │  write   ◄── ws.onmessage (PTY bytes)
           ▼
    FastAPI /api/pty (token-gated, loopback-only)
           ▼
    PtyBridge (ptyprocess) ── spawns node ui-tui/dist/entry.js ──► tui_gateway + AIAgent

Components
----------

hermes_cli/pty_bridge.py
  Thin wrapper around ptyprocess.PtyProcess: byte-safe read/write on the
  master fd via os.read/os.write (not PtyProcessUnicode — ANSI is
  inherently byte-oriented and UTF-8 boundaries may land mid-read),
  non-blocking select-based reads, TIOCSWINSZ resize, idempotent
  SIGHUP→SIGTERM→SIGKILL teardown, platform guard (POSIX-only; Windows
  is WSL-supported only).

hermes_cli/web_server.py
  @app.websocket("/api/pty") endpoint gated by the existing
  _SESSION_TOKEN (via ?token= query param since browsers can't set
  Authorization on WS upgrades). Loopback-only enforcement. Reader task
  uses run_in_executor to pump PTY bytes without blocking the event
  loop. Writer loop intercepts a custom \x1b[RESIZE:cols;rows] escape
  before forwarding to the PTY. The endpoint resolves the TUI argv
  through a _resolve_chat_argv hook so tests can inject fake commands
  without building the real TUI.

Tests
-----

tests/hermes_cli/test_pty_bridge.py — 12 unit tests: spawn, stdout,
stdin round-trip, EOF, resize (via TIOCSWINSZ + tput readback), close
idempotency, cwd, env forwarding, unavailable-platform error.

tests/hermes_cli/test_web_server.py — TestPtyWebSocket adds 7 tests:
missing/bad token rejection (close code 4401), stdout streaming,
stdin round-trip, resize escape forwarding, unavailable-platform ANSI
error frame + 1011 close, resume parameter forwarding to argv.

96 tests pass under scripts/run_tests.sh.
2026-04-21 02:48:16 -04:00
..
__init__.py
…
auth.py
…
auth_commands.py
…
backup.py
…
banner.py
…
callbacks.py
…
claw.py
…
cli_output.py
…
clipboard.py
…
codex_models.py
…
colors.py
…
commands.py
…
completion.py
…
config.py feat: shell hooks — wire shell scripts as Hermes hook callbacks 2026-04-20 20:53:51 -07:00
copilot_auth.py
…
cron.py
…
curses_ui.py
…
debug.py
…
default_soul.py
…
dingtalk_auth.py
…
doctor.py fix: sweep remaining provider-URL substring checks across codebase 2026-04-20 22:14:29 -07:00
dump.py
…
env_loader.py fix(env_loader): warn when non-ASCII stripped from credential env vars (#13300) 2026-04-20 22:14:03 -07:00
gateway.py
…
hooks.py feat: shell hooks — wire shell scripts as Hermes hook callbacks 2026-04-20 20:53:51 -07:00
logs.py
…
main.py fix(whatsapp): remove 120s timeout on bridge npm install (#13339) 2026-04-20 22:22:05 -07:00
mcp_config.py
…
memory_setup.py
…
model_normalize.py
…
model_switch.py
…
models.py refactor(ai-gateway): single source of truth for model catalog (#13304) 2026-04-20 22:21:21 -07:00
nous_subscription.py
…
pairing.py
…
platforms.py
…
plugins.py feat: shell hooks — wire shell scripts as Hermes hook callbacks 2026-04-20 20:53:51 -07:00
plugins_cmd.py
…
profiles.py
…
providers.py fix: sweep remaining provider-URL substring checks across codebase 2026-04-20 22:14:29 -07:00
pty_bridge.py feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard 2026-04-21 02:48:16 -04:00
runtime_provider.py fix: sweep remaining provider-URL substring checks across codebase 2026-04-20 22:14:29 -07:00
setup.py fix: extend hostname-match provider detection across remaining call sites 2026-04-20 22:14:29 -07:00
skills_config.py
…
skills_hub.py
…
skin_engine.py
…
status.py
…
timeouts.py
…
tips.py
…
tools_config.py fix: extend hostname-match provider detection across remaining call sites 2026-04-20 22:14:29 -07:00
uninstall.py
…
web_server.py feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard 2026-04-21 02:48:16 -04:00
webhook.py
…