Giving Claude Code a Body: Desktop Presence for Your AI Agent
Claude Code is careful engineering trapped in an invisible container. It runs in your terminal, thinks for minutes at a time, and gives you exactly one signal that it’s working: a blinking cursor. You either stare at the terminal waiting, or you walk away and miss the moment it hit a wall and needed a decision.
Neither is good. noctalia-claude-plugin is my answer to that problem on a Noctalia v5 + niri Wayland desktop.
Tip
Claude-Companion was tested under and is available for Noctalia v5 for Hyprland, Sway, and Niri! So the only dependency is Noctalia Shell v5! Get Noctalia Shell v5 for Hyprland, Sway, or Niri and install claude-companion under settings > plugins!
The three-component model
The plugin exposes three surfaces, each with a single responsibility:
- pulse — a bar widget that tracks every active Claude session simultaneously, shows which one needs attention, and displays token burn on hover. This is the information-dense view.
- orb — a desktop widget that breathes. Glyph and opacity ride a sine wave; tempo increases as urgency increases. No logic, no state of its own, just a reflection of the pulse state.
- answer — a panel that catches
/claude ? <question>replies and holds them scrollable and complete. One-shot questions don’t scroll off the top of the terminal anymore.
The hard design constraint: each component has exactly one job and reads from one source. The orb doesn’t have hooks. The answer panel doesn’t have session state. If something breaks, there’s one place to look.
The event pipeline
Claude Code’s lifecycle hooks fire at session start, tool run, turn start/end, needs-input, session end, and pre-compact. The plugin captures these through ~/.claude/settings.json hooks and normalizes them into eight events with a plain CSV payload:
hooks/pulse-emit session_start mysess
hooks/pulse-emit turn_start mysess
hooks/pulse-emit turn_end mysess claude-opus-4 42000 8000
hooks/pulse-emit needs_attention mysess
hooks/pulse-emit session_end mysess
The emitter is POSIX sh. It doesn’t care whether the agent is Claude. hooks/pulse-emit is a generic adapter. Any agent, CI job, or script that fires at lifecycle boundaries can drive the same bar. The PROTOCOL.md documents the full vocabulary.
Perceive: the MCP shim
The plugin ships shim/noctalia-mcp.py, a stdio MCP server that hands Claude a live read on the desktop: niri msg -j for open windows, playerctl for what’s playing, noctalia msg status for shell state. Launch through /claude and it attaches automatically.
This feeds memd’s inbox protocol on the side. When the shim observes something worth remembering, it drops a note into ~/.memory/inbox/ using the atomic publish protocol. memd picks it up on the next sweep. The agent’s context about the desktop state persists across sessions without any additional wiring.
The headless pulse-svc design
Earlier versions required the pulse widget to be on a bar to function. That created a fragile dependency: remove the widget from your bar layout and hooks stopped landing. The current architecture separates aggregation from display. A headless [[service]] entry called pulse-svc starts with the shell and listens whether or not any widget is placed. The orb, the bar dot, the hover tooltip: all views. The service is the single aggregator. Pull the bar dot, the orb keeps breathing.
What it actually feels like
The orb is the part that changes behavior. Having an ambient presence on the desktop creates a kind of peripheral awareness. You’re in another window writing, the orb pulses faster, you glance over, you switch contexts. It turns a terminal-bound background process into something that lives in the room with you.
The answer panel matters for a different reason: /claude ? what does this function return fires a headless claude -p query and drops the full reply into the panel. Quick factual questions that don’t need a full session stop requiring you to open a terminal at all.
Tip
Install: Through Noctalia’s Plugin Browser in Settings. Just enable community plugins and view “Browse Plugins” If you want to take a closer look the Claude-Companion plugin is in Noctalia-dev’s Community Plugins Repo on Github
Written from the machine it describes. More field notes: the archive · RSS.
Working on something in this territory? I take on scoped engagements.