Show host CPU / RAM vitals in the sidebar #19

Merged
pedromcaraujo merged 1 commit from sidebar-host-vitals into main 2026-07-31 09:36:40 +01:00

Closes #18.

What

Whole-box CPU and RAM gauges at the foot of the sidebar, polled from a new
/vitals endpoint on the tmux sidecar. Global only — per-channel usage is
going in Claude Code's own status line instead.

Backend — web/tmux_ctl.py

GET /vitals, read straight from /proc (no psutil, so the sidecar keeps the
same dependency set as the rest of the launcher):

  • cpu — busy %, measured as a delta between successive polls, so each answer
    covers exactly the window the user just watched. A fresh 150ms sample is
    taken when there's no usable previous one (first call after a restart, or
    after a >60s gap because the tab was hidden).
  • mem — uses MemAvailable, so page cache doesn't read as "full".
  • swap, disk (the session user's home), load, cores, uptime.

Every field is independently nullable; anything unreadable comes back null
and the frontend hides that gauge rather than inventing a zero.

modules/launcher.nix proxies /vitals alongside /status.

Frontend — web/index.html

Two gauges + a secondary line (6.2G/7.8G · disk 72%), with load/swap/uptime
in the tooltip. Amber past 75%, red past 90% — darkened in the light theme,
where the dark palette's amber washes out on a Boox. On mobile it collapses to
a CPU 34% RAM 80% pill beside the channel chips.

Polling is 5s and skipped while the tab is hidden: the cadence is the CPU
averaging window, and a phone in a pocket shouldn't keep the box awake. The
existing visibility handler catches back up on focus.

Test plan

  • nix flake check passes
  • launcher inline scripts parse (check-launcher.mjs)
  • /vitals returns sane values across successive calls (TestClient)
  • rendered headlessly against a stub server — desktop dark, mobile pill,
    and light/e-ink all lay out correctly, with RAM at 80% colouring amber
  • deployed and eyeballed on the real box
Closes #18. ## What Whole-box CPU and RAM gauges at the foot of the sidebar, polled from a new `/vitals` endpoint on the tmux sidecar. Global only — per-channel usage is going in Claude Code's own status line instead. ## Backend — `web/tmux_ctl.py` `GET /vitals`, read straight from `/proc` (no psutil, so the sidecar keeps the same dependency set as the rest of the launcher): - `cpu` — busy %, measured as a delta between successive polls, so each answer covers exactly the window the user just watched. A fresh 150ms sample is taken when there's no usable previous one (first call after a restart, or after a >60s gap because the tab was hidden). - `mem` — uses `MemAvailable`, so page cache doesn't read as "full". - `swap`, `disk` (the session user's home), `load`, `cores`, `uptime`. Every field is independently nullable; anything unreadable comes back `null` and the frontend hides that gauge rather than inventing a zero. `modules/launcher.nix` proxies `/vitals` alongside `/status`. ## Frontend — `web/index.html` Two gauges + a secondary line (`6.2G/7.8G · disk 72%`), with load/swap/uptime in the tooltip. Amber past 75%, red past 90% — darkened in the light theme, where the dark palette's amber washes out on a Boox. On mobile it collapses to a `CPU 34% RAM 80%` pill beside the channel chips. Polling is 5s and skipped while the tab is hidden: the cadence *is* the CPU averaging window, and a phone in a pocket shouldn't keep the box awake. The existing visibility handler catches back up on focus. ## Test plan - [x] `nix flake check` passes - [x] launcher inline scripts parse (`check-launcher.mjs`) - [x] `/vitals` returns sane values across successive calls (TestClient) - [x] rendered headlessly against a stub server — desktop dark, mobile pill, and light/e-ink all lay out correctly, with RAM at 80% colouring amber - [ ] deployed and eyeballed on the real box
forge: show host CPU / RAM vitals in the sidebar (#18)
All checks were successful
CI / check (pull_request) Successful in 13s
dd4fb69dd9
The launcher reported per-channel session state but nothing about the box
running them — so "is this thing swapping?" meant dropping into a shell.

Adds /vitals to the tmux sidecar, read straight from /proc so the sidecar
keeps its current dependency set. CPU is a rate, so it's measured as a delta
between successive polls: the launcher's 5s cadence *is* the averaging
window, with a fresh short sample taken when there's no usable previous one
(first call after a restart, or after the tab was hidden). Every field is
independently optional — anything unreadable comes back null and the
frontend hides that gauge instead of showing a fabricated zero.

The gauges sit at the foot of the sidebar and collapse to a pill beside the
channel chips on mobile. Amber past 75%, red past 90% — darkened in the
light theme, where the dark palette's amber washes out on a Boox.

Deliberately whole-box only; per-channel usage belongs in Claude Code's own
status line.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pedromcaraujo/forge!19
No description provided.