Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
whirr
A macOS system dashboard that lives in your terminal. Run whirr, leave it
running all day: processes, CPU, temperature, power draw, memory pressure,
network throughput, localhost dev servers and Claude Code sessions — all on one
glanceable screen. Named after the sound of a fan spinning up (its logo is one).
https://github.com/user-attachments/assets/0cd0abed-fcae-4c5e-893d-05d02d69a0e7
Why
Everything it shows normally requires either Activity Monitor (mouse-driven,
one view at a time) or sudo powermetrics (root, and it owns your terminal).
whirr reads the same IOKit / IOReport / sysctl data powermetrics does, but
through the read-only channels a normal user account already has access to —
no sudo, ever. The one trade-off: processes owned by other users (e.g.
WindowServer) don't expose CPU/memory counters without root, so they are not
listed.
Built for Apple Silicon. Developed and tested on an M5 running macOS 26; Intel Macs are out of scope — the IOReport power channels whirr reads don't exist there.
Install
Or, without Homebrew:
|
Or from crates.io, if you have a Rust toolchain:
Both the tap and the installer script ship a prebuilt aarch64-apple-darwin
binary — no Rust toolchain required.
&&
The screen
whirr picks one of three layouts from the terminal size. All three share the same design — the burst fan, the bitmap wordmark, the same cards — and differ only in how much room each card gets:
| Size | Layout |
|---|---|
| ≥ 120×30 | Four hero-number gauge cards in one row; processes, network, and three separate cards for localhost / Claude sessions / other listeners |
| ≥ 70×40 (narrower) | The same hero cards, stacked 2×2 across two bands — a tall, narrow window keeps the full design rather than dropping to compact |
| anything smaller | Plain readouts instead of hero numbers, and one combined Ports card |
Panels drop out in priority order as space gets tight: ports first, then network, then power, then temperature. Processes and CPU always survive.
Keys
| Key | Action |
|---|---|
Tab |
cycle focus: processes → localhost → Claude sessions → others |
↑ / ↓ |
move the selection within the focused card |
c / m |
sort processes by CPU / memory |
o |
localhost: open the dev server in your browser · sessions: jump to the terminal it's running in |
k |
kill the selected process or dev server (SIGTERM; a dialog asks first — y confirms, n or Esc cancels) |
s |
open settings: theme, accent colour, background, fan |
q / Ctrl-C |
quit |
k is deliberately inert on the Claude sessions and others cards: ending a
session mid-conversation, or killing a system agent, shouldn't be one keypress
away. o means "take me to this": on localhost it opens the dev server in
your browser, and on the sessions card it brings the terminal tab that session
is running in to the front. It's inert on processes and other listeners, which
have nowhere to go.
Sessions and your terminal
whirr finds the application hosting each Claude session by walking up its process tree, and then asks that application what it knows — keyed on the session's controlling terminal, which is the one identifier every terminal agrees on.
| Host | o jumps to |
Session label |
|---|---|---|
| cmux | the exact pane | the workspace title, e.g. ✳ Fix the port picker |
| Terminal.app, iTerm2 | the exact tab | — |
| anything else | brings the app to the front | — |
Where a host supplies its own name for a session, the card shows it beside the project — the title says what the session is doing, the project says which codebase — and drops the tty, since the two together already tell any pair of rows apart. Everywhere else nothing changes.
o only appears on a session the host can actually reach. cmux doesn't report
a surface for every tty, and an unrecognised terminal can only be brought to
the front, which looks identical to nothing happening when whirr is running
inside it.
The first jump on Terminal.app or iTerm2 will ask macOS for Automation permission. Denying it costs the precise-tab jump, nothing else.
Tab-level precision can only ever be per-terminal: macOS has no general "focus the tab attached to this tty" — each terminal owns its tab model privately. Bringing the application forward is general, so that's what every unrecognised terminal gets, and whirr says so in the status line rather than appearing to do nothing.
A process often listens on more than one port, so o opens straight away only
when there's one sensible candidate; otherwise it asks which. Ports the OS
allocated itself (49152 and above) are never offered — they're not pages
anyone means to visit — which is usually enough to leave a single candidate
and no question. Nothing in a port number says which one you meant, so
whirr asks rather than guessing.
Flags
| Flag | Effect |
|---|---|
-h, --help |
print usage and exit |
-V, --version |
print the version and exit |
--no-fan |
disable the fan animation; the UI then only redraws on new data, a keypress, or a resize — useful on battery or over a slow SSH link |
--list-sensors |
print the raw HID temperature sensors and IOReport Energy Model channels this Mac exposes, then exit (no TTY needed) |
--no-update-check |
don't ask crates.io whether a newer release exists |
The one network request
Everything on the dashboard is read locally. The single exception is a version check: once a day at most, whirr asks crates.io for the latest published version and, if it's newer than the one running, says so in the footer along with the command that upgrades your installation — Homebrew, cargo, or the installer script, inferred from where the binary lives.
It sends nothing but a User-Agent identifying whirr and its version (which
crates.io requires), it runs on its own thread so it can never delay startup
or a redraw, and the answer is cached in
~/.cache/whirr/update-check. Turn it off with --no-update-check, and
whirr makes no network requests at all.
Settings
s opens a dialog for the things that are a matter of taste: a light or dark
palette, one of five accent colours, whether the fan animates, and whether
whirr paints the frame background at all.
That last one matters if your terminal is themed or translucent — whirr paints
edge to edge by default, which overrides it. Set background to terminal
and the frame is left alone.
It's only offered under the dark palette. Light means dark text, and leaving
the frame unpainted would put that text straight onto whatever your terminal's
background is — unreadable on a dark one, and whirr has no reliable way to
find out which you have. Choosing light shows the row as painted (light);
your terminal preference is kept, not discarded, and comes back when you
switch to dark.
Changes apply as you make them and are remembered in
~/.config/whirr/config.toml (or $XDG_CONFIG_HOME/whirr/):
[]
= "dark" # dark | light
= "teal" # teal | blue | violet | amber | green
= "painted" # painted | terminal (dark palette only)
[]
= true
The file is optional and edits to it are picked up on the next launch. A value
whirr doesn't recognise is ignored and that one setting falls back to its
default — a typo in the accent won't discard your theme — and an unparseable
file is ignored entirely rather than being an error worth refusing to start
over. Flags win over the file, so --no-fan applies for that run without
rewriting your preference.
Performance budget
whirr is meant to be left running indefinitely, so it's held to a budget:
< 0.5% average CPU and < 25 MB RSS. Three sampler threads (2 s / 5 s /
10 s cadence) push immutable snapshots over a channel; the UI thread sleeps
until input, data, or the next animation frame, and only redraws when
something actually changed. The process table is scanned with raw libproc
calls (one proc_pidinfo per pid, names cached) — ~1 ms per pass instead of
the ~35 ms a full sysinfo process refresh costs.
Note: the budget is measured on an interactive terminal at normal QoS; sandboxed/background-QoS runs report inflated CPU (~5×) because the whole process is scheduled on efficiency cores at idle clocks.
Releases
Releases are automated from conventional commits:
release-plz opens a release PR with the version bump and changelog, and
merging it tags the version, publishes to crates.io, and triggers cargo-dist
to build the binary and update the tap. See CONTRIBUTING.md.
License
MIT, see LICENSE.