跳到主要内容

hermes-vitals-light

❖ Communityv0.1.1★ 0

CPU, load, free RAM, Linux PSI CPU pressure, active sessions, running Hermes processes and round-trip time of the connected Hermes server in the Desktop status bar, plus an optional TUI card.

Open in Hermes Desktop
hermes plugins install hermes-vitals-light

Screenshots

README

From the reviewed commit 635efe9 ↗; it updates when the author re-pins.

Hermes Vitals Light

CI

Hermes Desktop and TUI plugin. The Desktop status-bar chip shows the CPU, load average, free RAM, CPU pressure (Linux PSI) and RTT of the server it is connected to: CPU 23% · Load 1.20 · 3.8G free · waited 15s/60 · RTT 12 ms · 👥 2 · ⚙ 3. On a remote gateway (SSH, URL + token) it measures the remote box, not your PC. Load is the 1-minute average (omitted when the server reports none); the tooltip adds hostname, cores, 5/15-min load, RAM used / total, free RAM, waited and RTT.

waited 15s/60 is Linux PSI CPU pressure: the seconds of the last minute at least one task was runnable but stalled waiting for a CPU, computed as round(avg60 * 0.6) from the some line of /proc/pressure/cpu. It is omitted when PSI is unreadable (macOS, Windows, containers without PSI). 3.8G free is MemAvailable from /proc/meminfo on Linux and psutil's available RAM on macOS and Windows, in GiB, one decimal. The Linux figures come from one stateless two-file read, with no psutil, subprocess or cache. If free RAM is unreadable, the chip falls back to the old percentage; waited is simply omitted where the kernel has no PSI.

RTT 12 ms is the round trip of a WebSocket ping over the Desktop's live gateway socket, measured in the Desktop half (host.request('ping')); it is left out when that socket cannot answer. It is the connection the app actually uses, not ICMP ping and not the /stats request (whose process scan can take ~50 ms). The TUI has no server to ping, so its card does not offer it. RTT does not change the chip colour.

The chip colour comes from the waited verdict, not from CPU or load: red (stuck) at waited >= 25 or less than 1 GiB free, amber (busy) at waited >= 10, otherwise normal. On macOS and Windows, where there is no PSI, only the free-RAM rule applies. When a poll fails, the chip keeps the last figures, dimmed, and its tooltip says how old they are and why; it shows CPU — only when it has never received a reading from the current server.

The first icon is a health light: pulse normally, watch when the last successful poll took 1.5 s or more, clock after a timeout, debug-disconnect when the backend is stopped or unreachable, plug when the backend half is missing, and warning for another backend error. The badge itself stays short: the icon carries the state. A stale or unavailable chip gets a compact two-line tooltip (state title + last-reading/attempt line) instead of the full host/load detail, so an offline backend does not open a large panel. Healthy polls keep the full tooltip.

👥 N is the number of active sessions on the connected server: Hermes's own active_sessions figure from its public GET /api/status (sessions not ended and active in the last 5 minutes, among the 50 most recent, current profile). It is a total, not a count per client; the tooltip reads Active sessions: N. The backend half asks the server it runs in, over loopback, on the socket address the request arrived on, and keeps the answer for 15 s, refreshed in the background (/api/status does much more than count). That needs the route to run inside the server, which is the default (plugins.isolation: in_process): in the plugin host (isolation: host) there is no address to ask and 👥 is left out.

⚙ N counts the Hermes executables running on the server right now, all profiles and users it can see: each hermes command (serve, chat, gateway, dashboard…), hermes-agent, hermes-acp. The tooltip breaks it down by command (Hermes running: chat 3 · serve 1), so several agents launched in parallel show up as chat 3. A Hermes started by another one counts once, under its top-level ancestor: the Windows launcher and its python children, the TUI's tui_gateway, a hermes an agent runs from its terminal. The Desktop app itself (Hermes, capitalised) is not counted.

Settings ▸ Plugins ▸ Hermes Vitals Light has one switch per figure (CPU, Load average, RAM, Waited on CPU, RTT, Active sessions, Hermes processes), all on by default and saved per Desktop. A hidden figure is only hidden; the colour still comes from the waited verdict. With everything off the chip is just its icon. The tooltip shows the full detail of what is measured. Active sessions and Hermes processes switched off are not measured at all: the chip polls /stats?skip=clients,processes and the server skips the /api/status ask and the process scan (the TUI does the same for its process option). RTT switched off sends no ping.

TUI

In hermes --tui the same figures sit in a card docked above the prompt, coloured like the chip (the TUI's skin colours), without 👥 or RTT: both come from the Desktop's gateway (/api/status and a WebSocket ping), and the TUI has no server to ask. The TUI measures the machine it runs on: the card runs plugin_api.py with the TUI's own Hermes python, which prints the /stats body every 3 s (/vitals every <s> changes it) for as long as the card is docked.

Command Effect
/vitals dock / undock the card
/vitals ram off hide a figure: cpu, load, ram, waited, processes (on shows it, no on/off flips it)
/vitals detail on add the Desktop tooltip lines (hostname, cores, 5/15-min load, RAM used / total, free RAM, waited, per-command counts)
/vitals all on · all off every figure (not detail)
/vitals reset back to the defaults: every figure on, detail off
/vitals help usage and the current options

Several go in one command (/vitals cpu off load off detail on); changing an option docks the card. Options are saved in <HERMES_HOME>/vitals.json, shared by every TUI of that home (the Desktop keeps its own, in Settings ▸ Plugins).

Three halves, one package (same layout as other unified Hermes plugins):

Half Where it runs What it does
desktop/plugin.js Hermes Desktop (renderer) Chip in statusBar.right; polls /stats every 3 s by default (Refresh every), times a gateway ping for RTT, skips while the window is hidden, re-polls at once when it becomes visible or on a connection/profile switch. A failed poll keeps the last figures, dimmed.
dashboard/plugin_api.py The Hermes backend you connect to Read-only GET /api/plugins/hermes-vitals-light/stats via psutil (a Hermes core dependency), a loopback GET /api/status on the server it runs in, and, on Linux, one two-file /proc read for PSI/free RAM. Returns only load figures, the hostname, the session total, per-command process counts and the waited verdict. Both counts are cached and refreshed in the background, so a request never waits for them.
tui/hermes-vitals-light.mjs Hermes TUI (<HERMES_HOME>/tui-widgets/) Ambient widget app /vitals; inlines plugin_api.py and runs it as a child (python -c) for the figures.

Without the backend half on the server, the chip shows CPU — (tooltip explains) and the poll backs off up to 30 s.

/stats is read-only and goes through the backend's usual authentication. It exposes the hostname, the load figures and the two counts; no session ids, process list, paths or environment.

What it reads, and where it goes. The backend half reads psutil's CPU / load / memory figures (including available RAM), two Linux /proc files (PSI and MemAvailable) when it runs on Linux, the process table (pid, parent pid and command line) to count Hermes executables, and the server's own loopback GET /api/status for the session total. Beyond the Desktop/TUI's normal connection to its Hermes backend, the only network call the plugin makes is that loopback ask; it sends nothing to a third party and has no telemetry or self-updater. The TUI half runs the same plugin_api.py as a python -c child of the TUI, so it reads the machine the TUI runs on.

The dashboard half is API-only: dashboard/manifest.json registers a hidden /vitals page so the web dashboard has a bundle to load beside plugin_api.py; the visible UI is the Desktop chip and the TUI card.

Requirements

  • Hermes 0.22 or newer, with Hermes Desktop plugin support (Desktop half) or hermes --tui (TUI half).
  • The backend half needs psutil and FastAPI, both already present in a hermes serve environment; no extra dependency is installed.
  • Backend and TUI halves run on Linux, macOS and Windows. waited needs Linux PSI; free uses MemAvailable on Linux and psutil's available RAM on macOS/Windows.
  • Node.js 20+ is needed only to build from source.

Install

Desktop half (the machine running the Desktop app; it ships off and must be enabled):

  • From the catalog: open Capabilities → Plugins, find Hermes Vitals Light, and install it; the app copies desktop/plugin.js into desktop-plugins/. Then enable it.
  • From a source checkout: node scripts/build.mjs && node scripts/install.mjs --desktop

Backend half (the machine running the backend your Desktop connects to, e.g. over SSH):

hermes plugins install hermes-vitals-light --enable       # from the catalog
# or:
hermes plugins install https://github.com/atostivint/hermes-vitals-light --enable
hermes plugins enable hermes-vitals-light                 # user plugins must be in plugins.enabled
# then restart that backend (hermes serve / dashboard)

TUI half (optional, the machine running hermes --tui; it measures that machine):

cp ~/.hermes/plugins/hermes-vitals-light/tui/hermes-vitals-light.mjs ~/.hermes/tui-widgets/
# Windows: %LOCALAPPDATA%\hermes\plugins\hermes-vitals-light\tui\hermes-vitals-light.mjs
#          -> %LOCALAPPDATA%\hermes\tui-widgets\

The TUI picks the widget up live, no restart. From a source checkout, use node scripts/build.mjs && node scripts/install.mjs --tui.

Uninstall

  • Desktop: disable it in Capabilities → Plugins, then delete <hermes home>/desktop-plugins/hermes-vitals-light/.
  • TUI: delete <HERMES_HOME>/tui-widgets/hermes-vitals-light.mjs; remove <HERMES_HOME>/vitals.json too to drop its saved options.
  • Backend: hermes plugins disable hermes-vitals-light, then restart that backend.

Footprint

Measured on Windows 11, 528 processes, Hermes venv python (2026-10-19). The session count was added after these figures and was not measured separately: it adds one ask of the server's own /api/status every 15 s, with everything on, refreshed in the background.

What Cost
/stats in hermes serve, everything on the load figures only (well under 1 ms warm); at most one process scan per 5 s (~55 ms of CPU on 500 processes) and one /api/status ask per 15 s run in the background
/stats?skip=processes ~0.1 ms, plus the session count refreshed in the background every 15 s
/stats?skip=clients,processes ~0.1 ms
PSI + free RAM read (Linux) two small /proc files; ~41 µs mean / ~38 µs median of CPU per call on the deployment host (p99 84 µs), no psutil, subprocess, thread or cache
First process scan after start runs in the background; ~5 s of one core on Windows (no request waits for it)
TUI card child (python -c) ~60 MB RSS (launcher + python); ~2 % of a core with processes on (scan cached 5 s), ~0.4 % off
Desktop half one ~260-byte request every 3 s, none while the window is hidden; RTT adds one gateway ping per poll while shown

Tests

node --test test/dashboard.test.mjs test/load.test.mjs test/plugin.test.mjs test/tui.test.mjs  # manifest + Desktop + TUI halves (build first)
python -m unittest discover -s test -p "test_*.py"                           # backend unit
pip install psutil fastapi httpx && python -m unittest discover -s test -p "test_*.py"
                                                                             # also mounts the real FastAPI route

CI runs both suites on Linux, macOS and Windows on every push and pull request, and fails if the committed halves do not match src/ after npm run build.

Known limits

  • The session count is Hermes's active_sessions: sessions not ended and active in the last 5 minutes, among the 50 most recent of the current profile, so it stops at 50 and is not per client. It is left out when the route runs in the plugin host, behind a unix socket, or when the server's /api/status does not answer on its own address (a TLS certificate that does not match 127.0.0.1, for one). It is cached for 15 s and refreshed in the background, so it can be up to 15 s old; the first answer after a backend restart can omit it.

  • The process count is by command line: a Hermes run some other way (python -c, renamed script) is not seen, and processes of other users whose command line is unreadable are skipped. It includes the hermes serve answering the chip. The scan is cached for 5 s, so the count can be up to 5 s old; the first answer after a backend restart can omit it.

  • A failed poll keeps the last figures (dimmed) and their age in the tooltip; polls back off to 30 s. CPU — appears only when no reading has arrived for the current server. A timeout is not treated as a missing backend half (a 404 is). A stopped backend and a dropped SSH connection both show offline; a backend that is merely too slow shows timeout.

  • The lag warning is fixed at 1.5 s; it is not a setting. A healthy /stats answers well under 100 ms, so this leaves room for a slow remote link while still flagging a struggling backend.

  • RTT is the Desktop↔gateway WebSocket path: it includes the app's own scheduling, and is left out when the gateway socket is down or the host has no host.request. It is not an ICMP ping, and it does not affect the health icon or the chip colour.

  • waited is Linux-only: it is omitted when /proc/pressure/cpu is unreadable (macOS, Windows, or a container without PSI). free is MemAvailable on Linux and psutil's available RAM elsewhere, one decimal GiB; the chip falls back to the old RAM percentage only when neither source is readable. The chip colour uses the waited verdict and free RAM: stuck at 25 waited seconds or under 1 GiB free, busy at 10 waited seconds, ok otherwise.

  • Client polls are clamped to at least 0.5 s.

  • Load average is native on Linux and macOS; on Windows psutil emulates it, so the first minutes after a backend start can read 0.00. It is display-only and does not affect the chip colour.

  • CPU % is measured between two polls (psutil.cpu_percent(interval=None)); several windows polling the same server share that window, so each sees a shorter sample. It is displayed only; the chip colour no longer uses CPU % or load average.

  • Verified in the real Hermes Desktop status bar. The backend route is tested against a plain FastAPI router and a local stand-in for /api/status, not a live hermes serve; the TUI card is tested against a stub TUI/SDK, not a live hermes --tui.

  • English-only tooltip and settings; fixed thresholds. The refresh period is a setting (Settings ▸ Plugins ▸ Refresh every: 1, 3, 5, 10, 30 or 60 s, default 3 s; /vitals every 10 in the TUI). Hosts older than Settings ▸ Plugins show every figure.

  • CPU, load and RAM are always measured (one psutil call each, under 2 ms), and on Linux waited/free are one two-file /proc read; switching any figure off only hides it.

  • TUI: a TUI attached to a remote gateway still shows its own machine. On Windows psutil's first process scan takes ~13 s of one core (750 processes; later scans ~0.1 s), so ⚙ N shows up that long after the card's first figures — the other figures are not delayed.

  • TUI: the Desktop and TUI options are separate (the Desktop's live in its local storage).

Disclosure

← Back to the catalog · catalog built Oct 29, 2026