A native Windows desktop client for a locally installed Hermes agent, with layout by Clay and an 80s-cyberpunk terminal aesthetic.
No Electron and no browser shell. One native executable talks to the locally installed Hermes Agent through its supported JSON-RPC gateway.
Requires TDM-GCC (or any MinGW GCC) on PATH. There is nothing to fetch — Clay is
vendored in vendor/.
build.batThat produces HermesConsole.exe in the project root. It is a unity build: src/main.c
includes everything, which keeps the GDI renderer's file-scope inline definitions
linkable under GCC's C99 rules.
HermesConsole.exeThe console finds your Hermes install automatically at
%LOCALAPPDATA%\hermes\hermes-agent and launches its JSON-RPC gateway as a child
process. Nothing needs to be running beforehand; quitting the console shuts the
gateway down with it.
| Variable | Effect |
|---|---|
HERMES_CWD |
Working directory for new sessions. Unset, terminal.cwd from Hermes's own config.yaml governs |
HERMES_AGENT_DIR |
Point at a different hermes-agent folder |
HERMES_NO_CRT=1 |
Disable the scanline overlay |
HERMES_TRACE=1 |
Append every inbound gateway frame to %TEMP%\hermes-console-frames.log |
Point the session cwd at real work, not at Hermes. Hermes injects the AGENTS.md
chain from git root down to the session cwd into every single turn. Hermes's stock
terminal.cwd: . resolves to its own source repo, whose AGENTS.md is 79 KB of
contributor documentation. Measured on the same prompt:
| Session cwd | Input tokens/turn | Time |
|---|---|---|
| Hermes's stock default (its own repo) | 39,556 | 21.6s |
A directory with no AGENTS.md |
19,606 | 7.8s |
Fix it once in %LOCALAPPDATA%\hermes\config.yaml under terminal.cwd and every
Hermes client benefits. This console deliberately does not override that — it only
sends a cwd when HERMES_CWD is set explicitly.
The gateway's own stderr is always captured to
%TEMP%\hermes-console-gateway.log — check there first when something misbehaves.
| Key | Action |
|---|---|
Enter |
Send |
Shift+Enter |
New line |
Esc |
Interrupt the running turn |
Ctrl+N |
New session |
Ctrl+L |
Clear the transcript view |
Ctrl+V |
Paste |
↑ ↓ PgUp PgDn |
Scroll the transcript |
1–4 |
Answer a permission prompt |
F1 |
Keyboard help |
F12 |
Clay's layout debugger |
Clarification requests open a blocking card with up to four choices and a free-form answer field. Sudo and secret requests use a masked volatile field: the value is sent only in the matching gateway response, never added to the transcript/activity log, and scrubbed on submit, cancel, replacement, or expiry.
Click a session in the sidebar to resume it, and click a REASONING block to unfold it.
The transcript follows new output automatically and stops following the moment you
scroll up — scroll back to the bottom to re-arm it.
The model picker is populated from the gateway's live model.options response and
shows authenticated provider/model choices only. Opening the picker is read-only.
Changing the configured model requires an explicit click and is committed only
after the tracked config.set response succeeds; errors leave the prior selection
active.
Hermes Console does not install providers, invent fallback models, or silently change routing policy. Provider credentials, retry policy, timeout policy, and fallback chains remain owned by Hermes itself. Adding or switching a model/provider should be an explicit user decision made after reviewing cost, privacy, speed, and reliability trade-offs.
Hermes exposes a JSON-RPC gateway over stdio — the same dispatcher its own TUI and desktop clients use, so anything they can do is reachable from here.
HermesConsole.exe
└── python -u -m tui_gateway.entry (child process)
stdin ← newline-delimited JSON-RPC requests
stdout → newline-delimited responses + events
stderr → %TEMP%\hermes-console-gateway.log
A dedicated reader thread drains stdout and hands complete lines to the UI thread through a locked ring buffer, so the UI never blocks on the pipe.
Requests used: session.create, session.list, session.resume,
session.interrupt, prompt.submit, approval.respond, model.options,
config.get, config.set, clarify.respond, sudo.respond, secret.respond,
agents.list, skills.manage, skills.reload,
plugins.list, plugins.manage, cron.manage, and setup.status.
Events handled: gateway.ready, session.info, session.title,
sessions.changed, message.start/delta/complete, reasoning.delta,
reasoning.available, thinking.delta, tool.start/progress/generating/complete,
approval.request, clarify.request/expire, sudo.request/expire,
secret.request/expire, turn.error, turn.end, plus subagent lifecycle events.
Run the non-destructive release gates from the project root:
python verify_release.pyThis runs static/runtime contract tests, the read-only gateway smoke test, secure
interactive-prompt tests, and the canonical native build. It stops only a running
HermesConsole.exe before linking; it never broadly terminates Python/Hermes
processes and never changes the selected model/provider. --full additionally
runs RED-first contracts for parity work that is not complete yet.
The authoritative implementation/verification status for every gateway surface is
maintained in docs/FEATURE_PARITY.md. A rendered control
is not counted as complete until its real gateway contract has been exercised.
src/
main.c WinMain, window, fonts, input, CRT overlay
ui.h the whole interface, declared with Clay
app.h application state + Hermes protocol handling
gateway.h child process, pipes, reader thread
json.h dependency-free JSON parser and writer
theme.h palette, metrics, font slots
vendor/
clay.h Clay 0.14, unmodified
clay_renderer_gdi.c Clay's Win32 GDI renderer, patched (see below)
Three deliberate edits, each marked [hermes] in the source:
- UTF-8 text. Upstream draws with
DrawTextAand measures withGetTextExtentPoint32, which reinterpret Clay's UTF-8 bytes in the system codepage and mangle anything non-ASCII the agent emits. Both now convert to UTF-16 and use the wide APIs. - DC leak. The measurement path returned early without releasing the screen DC, leaking one per call. Now released on every path.
- Post-render hook. A nullable callback fires just before the final
BitBlt, which is where the scanline pass composites.
The CRT overlay composites a full-size premultiplied DIB at 1:1. An earlier version
stored a single-pixel-wide column and let AlphaBlend stretch it — far cheaper, but
GDI does not blend a stretched per-pixel-alpha source faithfully and it crushed every
colour to roughly a sixth of its value. Blitting at 1:1 is the only reliable form.
Hermes Console is released under the MIT License.
The vendored Clay 0.14 layout library uses the zlib/libpng license, not MIT.
Its complete license is preserved in vendor/CLAY-LICENSE.md,
and bundled components and local modifications are summarized in
THIRD_PARTY_NOTICES.md.