About
Claude Companion
![]()
A Noctalia v5 plugin that puts Claude Code's live status on your desktop — a pulse on the bar, a breathing orb on the desktop, and an answer panel for quick questions.
Claude Code is a brilliant agent trapped in a text box. It can't see the windows you have open, can't tap you on the shoulder when it hits a wall, and gives you nothing to glance at while it churns. So you sit there watching a terminal, or you wander off and miss the moment it needed you.
This plugin gives it a body. It wires Noctalia into Claude's lifecycle so a pulse on your bar tracks every session, an orb on your desktop breathes along with the work, and an answer panel catches one-shot replies before they scroll away. The terminal keeps doing the actual thinking — permissions, tools, MCP, all native. This is just the nervous system that lets the rest of your desktop feel it.
Don't run Claude Code? The signal bus is agent-agnostic — any agent, CI job, or shell script that can run a command on its own lifecycle can light up the same bar. See Wiring up other agents.
Plugin
| Field | Value |
|---|---|
| ID | lowcache/claude-companion |
| Entries | Service: pulse-svc; bar widget: pulse; desktop widget: orb; panels: answer, sessions; launcher: claude |
| Launcher Prefix | /claude |
Built and live-tested against Noctalia 5.0.0 (build 623210223c), with an offline widget spec suite keeping the state machine honest.
See it

One session, start to finish: the pulse on the bar and the orb on the desktop breathe through idle, thinking, a tool run, done, and needs-you.

Ask something quick with /claude ? and the whole answer waits for you in the panel, instead of scrolling off the top of the terminal.
How it works
Perceive. shim/noctalia-mcp.py is a stdio MCP shim that hands Claude a live read on your machine: niri msg -j for the windows you have open, playerctl for what's playing, noctalia msg status for the state of the shell itself. Nothing to wire up by hand. Launch through /claude and it attaches itself.
Practice. Everything on the backend funnels through claude.luau, the /claude launcher and the one door in. It normalizes the event vocabulary, throws notify-send toasts, and calls noctalia msg to move panels around. One chokepoint on purpose — so when something acts up, there's exactly one place to go look.
Pulse. pulse-svc.luau is a headless [[service]] that runs the show. Hook events land here over IPC at lowcache/claude-companion:pulse-svc, and from there it does the rest: tracks every session at once, surfaces whichever one's most urgent, and publishes a rollup to shared state under claude.pulse for subscribers to read.
And downstream is where the surfaces live. pulse.luau on the bar and orb.luau on the desktop are independent subscribers that only render. Both watch claude.pulse — pulse.luau breathes the accent color and shows per-session tooltips, while orb.luau breathes the same state frame by frame, glyph and opacity riding a sine wave, tempo picking up as things get urgent. Neither holds hooks or logic of its own. answer.luau is the answer panel that catches a /claude ? reply and holds the whole thing: wrapped, scrollable, all the parts a toast lops off the end.
Requirements
- Noctalia 5.0.0 on a supported Wayland compositor — niri, Hyprland, or Sway. The shim detects which one is running and speaks its IPC; the widgets themselves are compositor-agnostic. You only need the CLI for the compositor you actually run —
niri,hyprctl(Hyprland), orswaymsg(Sway) — not all three. - Claude Code — the
claudeagent being visualized. Optional if you're driving the widgets from another agent via PROTOCOL.md. python3for the MCP shim (stdlib only, no pip installs)- On the PATH as the shim's senses need them:
playerctl,nmcli,notify-send,ps - For the generic shell adapter (
hooks/pulse-emit, only used when driving the widgets from a non-Claude agent):tris required;timeoutis optional — the adapter falls back to a direct dispatch when it's absent.
Install
# clone and symlink into the plugins dir
ln -s "$PWD" ~/.local/share/noctalia/plugins/claude-companion
# enable the plugin
noctalia msg plugins enable lowcache/claude-companion
Then, in order:
- (Optional) Put the
pulsewidget on a bar (Settings → Bar) for the glanceable dot — capture no longer depends on it; the headlesspulse-svcservice does the listening. - Add the
orbdesktop widget if you want the ambient presence. - Merge
hooks/settings.snippet.jsoninto~/.claude/settings.jsonso Claude's lifecycle hooks actually drive the pulse. - Point Claude at
shim/noctalia-mcp.pywith--mcp-configto hand it the senses and hands. (Sessions you launch through/claudedo this for you.)
Prove it works:
noctalia msg plugin lowcache/claude-companion:pulse-svc all needs_attention # bar icon → red bell
noctalia msg plugin lowcache/claude-companion:pulse-svc all idle # back to robot
[!WARNING]
pulseno longer has to stay on a bar. The sole aggregator is now the headlesspulse-svcservice, which starts with the shell and listens whether or not any widget is placed — so pulling thepulsedot off a bar just hides the glanceable icon; the orb keeps updating and hooks/IPC still land. This retires the old D10 requirement, made possible by the[[service]]entry kind added in the Noctalia 5 beta.
Usage
/claude <task> opens a real Claude Code session in your terminal, shim already wired in. Bare /claude picks up where you left off (claude --continue). And /claude ? <question> is the quick one — a read-only ask that comes back as a toast and lands, in full, in the answer panel.
That panel opens however you like it: click the pulse, use the "Show last answer" row under /claude, or toggle it from the CLI:
noctalia msg panel-toggle lowcache/claude-companion:answer
Leave it open and it refreshes live while suppressing the toast, so you're never reading the same answer twice. A click outside or Esc puts it away.
Hover the bar and the tooltip tells you where each session stands and what it's burning — input, output, cache reads. Run a few at once and you get a line per session plus a Σ total, with the icon always showing whichever one needs you most.
Right-click the pulse for the sessions panel — the same rollup, but you can act on it. A tooltip disappears on the way to it; this doesn't. One row per live session with its state, model and burn, and a Retire button on each.
Retire is there for the one failure mode you'll actually hit: a session whose SessionEnd hook never fired — terminal killed, hook interrupted mid-distill — sits at idle forever and keeps inflating the count. Retiring it corrects the tally from the shell, without going back to a terminal. It adds no new protocol: a retire is the ordinary session_end event carrying that session's id, exactly what hooks/pulse.py sends.
noctalia msg panel-toggle lowcache/claude-companion:sessions
Settings
The plugin declares three user settings, read via noctalia.getConfig(<key>):
| Setting | Type | Range | Step | Default | Description |
|---|---|---|---|---|---|
breath_speed |
double | 0.25–3.0 | 0.05 | 1.0 | Phase-rate multiplier for the breathing animation on both the bar dot and the desktop orb. Higher = faster. |
pulse_glow_floor |
double | 0.0–0.9 | 0.05 | 0.45 | How dim the bar dot gets at the trough of its breath. 0 = dims to black, higher = stays brighter. |
orb_swell |
double | 0.0–3.0 | 0.05 | 1.0 | How far the desktop orb glyph magnifies as it breathes. 0 = static size, higher = a bigger swing. |
There are no color settings — both surfaces follow the active theme palette via accent role names (secondary, primary, error).
Wiring up other agents
None of this is Claude-specific under the hood. The pulse speaks a plain event format and doesn't care who's talking — any agent, CI job, or shell script that can run a command on its own lifecycle can light up the same bar. PROTOCOL.md has the full eight-event vocabulary, the CSV payload, session semantics, and the adapter contract. The reference emitter, hooks/pulse-emit, is plain POSIX sh and needs nothing but noctalia on your PATH:
hooks/pulse-emit turn_start mysess
hooks/pulse-emit turn_end mysess gpt-5 12000 800
hooks/pulse-emit session_end mysess
Rough edges
A few things worth knowing before they surprise you:
- Plugin panels render at
Layer::Top, so an overlay window — a notification, a quake terminal, a polkit prompt — can sit on top of the answer panel. The answer's still there; clear the overlay and you'll see it. There's an upstream ask in for panel layer control. - Eight-digit hex alpha is ignored by bar widgets — brightness is done by scaling RGB toward black. (Earlier builds didn't fire
state.watchon bars, so the pulse polled; the Noctalia 5 beta fires it, so the bar dot is now event-driven like the orb.) - Builtin and wallpaper-generated palettes have no on-disk JSON, so those fall back to fixed accent colors. Custom and community palettes are followed live, rechecked every ~8 s.
- Quick-ask rides headless
claude -p, which doesn't refresh an expired OAuth login token — only an interactive session does (upstream). The plugin checks the token's expiry before launching and, instead of burning the request on a guaranteed 401, tells you to open a terminal Claude session first; a failure it couldn't predict gets the same message in place of the raw API error. - The MCP shim is a Python prototype. A compiled port is the intended endgame.
- The shim's memory tool drops notes into
~/.memory/inboxfor the memd curator to pick up. No memd, no reader — the files get written and simply sit there. It follows memd's Inbox Protocol v1.0 (INBOX-PROTOCOL.mdin the memd repo).
License
MIT — see LICENSE.
Versions
| Version | Plugin API | Updated |
|---|---|---|
| v1.3.0 latest | 3 | Jul 30, 2026 |
Older versions stay installable on a Noctalia release whose plugin API is below the latest version's.