A native macOS desktop companion for Claude Code — a pixel crab that lives on your desktop, reacts to what your agent sessions are doing, and keeps your usage limits one glance away in the menu bar.
Written in Swift against AppKit and Core Animation. No Electron, no WebView.
Measured on an M-series Mac, idle and while playing a 60-frame sprite loop:
| CPU | Memory | |
|---|---|---|
| Resting (vector idle) | 0.0% | 42.7 MB |
| One sprite animation playing | 0.0% | 43.2 MB |
| After cycling five states | 0.0% | 86.9 MB |
CPU stays at zero regardless of what is on screen. Memory is the real cost: each
302×300 sprite is ~360 KB per frame over 45–90 frames, so a decoded animation is
~15 MB. AnimationCache keeps at most three and evicts the rest, which is what
bounds that last row — and the cache is purged entirely whenever the renderer
parks.
The zero-CPU figure is not an accident of measurement — it falls out of three decisions:
- Every animation is a Core Animation animation. Sprite playback is a
CAKeyframeAnimationovercontentswith discrete timing. The render server owns the timeline, so this process gets no per-frame wakeups: no timer, noCADisplayLink, no draw loop. - The resting crab is vector, not bitmap. The source sprites are pure
axis-aligned rects on a pixel grid, so
VectorSpriteparses them straight intoCALayers. Breathing, blinking and cursor-following are layer transforms. No GIF decode, no texture memory, crisp at any size. - Invisible means stopped. When the panel is occluded, the display sleeps, the screen locks, or the crab falls asleep, animations are removed and the decode cache is purged — not merely paused.
Everything upstream of the renderer is event-driven too. State arrives over a loopback HTTP endpoint from Claude Code's hooks; nothing polls.
Requires macOS 14+ and a Swift 6 toolchain (Xcode 16+).
./Scripts/fetch-assets.sh && ./Scripts/build-app.sh && open dist/CCrab.appOn first launch CCrab offers to connect itself to Claude Code. That writes hook
entries into ~/.claude/settings.json; see Connecting for exactly
what it touches.
On your desktop. A borderless, non-activating floating panel — clicking the crab never steals focus from your terminal. Drag it anywhere; it remembers. Right-click for settings. Poke it a few times and it will get progressively less amused.
In your menu bar. A live glyph for the current state, your 5-hour usage percentage inline, and a menu with:
- 5-hour and weekly usage bars with reset countdowns, colour-coded past 60% / 85%
- every live session — project, model, current state — click to open its folder
- recent projects, read from the
~/.claude/projectsdirectory Claude Code already maintains - connect / disconnect the Claude Code hooks
- show/hide the crab
Reactions. 49 clips wired to the state machine. Beyond the plain state map, selection is context-aware:
| Signal | Result |
|---|---|
Bash / BashOutput |
debugger pose |
Write / Edit |
hard hat |
WebSearch / WebFetch |
thinking |
Workflow |
wizard |
| 2 live sessions | headphones |
| 3+ live sessions | building |
| 1 subagent | headphones groove |
| 2+ subagents | three-ball juggling |
| Permission prompt | alert pose + sound |
PreCompact |
sweeping |
| 90s quiet | sleeping, and the renderer parks |
The resting pose also rotates every couple of minutes through reading, dozing, coffee and others, so the crab never looks frozen.
Connect edits ~/.claude/settings.json. It is written to be safe on a config
you already care about:
- a timestamped backup is copied to
~/.ccrab/backups/before any change - existing hook entries for other tools are preserved; ours are appended
- an existing
statusLineis chained, never replaced — CCrab's shim runs first, forwards the payload, then executes your original command and passes its stdout through untouched Disconnectremoves only entries tagged as ours and restores the statusline it wrapped
The shim (~/.ccrab/bin/ccrab-hook.py) is deliberately defensive: every failure
path exits 0 with empty output, and if CCrab is not running it is a ~15 ms no-op.
It refuses to post to a port whose recorded owner pid is dead, so a stale
~/.ccrab/runtime.json can never redirect your hook traffic somewhere else.
Usage numbers come from Claude Code's statusline payload (rate_limits), which
carries the Pro/Max window percentages directly. No extra API calls are made
and no token is ever read.
Claude Code hook ──POST /state──▶ HookServer (127.0.0.1:45820)
│
▼
SessionRegistry ── derives one state from N sessions
│
┌─────────────┴─────────────┐
▼ ▼
CrabRenderer StatusItemController
vector idle · GIF states glyph · quota · sessions
▲
PowerGovernor ── occlusion · display sleep · lock · battery
| Path | Role |
|---|---|
State/ |
state model, event mapping, multi-session derivation |
Bridge/ |
loopback HTTP endpoint, ~/.ccrab/runtime.json |
Render/ |
SVG rect parser, GIF decoder, clip catalog, renderer |
Window/ |
non-activating panel, drag/poke handling |
Integration/ |
settings.json installer, Claude app watcher, recent projects |
Energy/ |
when the crab is allowed to animate |
Debug/ |
--audit and --dump offline render checks |
./.build/debug/CCrab --auditLists every clip and whether its assets resolve.
./.build/debug/CCrab --dump clawd-typing /tmp/out.pngRenders a clip through the live code path and writes a PNG — no screen-capture permission needed.
The sprites are not part of this repository and are not redistributable.
Clawd is Anthropic's character. The pixel art is fan-made by the
clawd-on-desk project and is
published All Rights Reserved, explicitly non-commercial. Scripts/fetch-assets.sh
downloads it from upstream into your working copy for personal use; .gitignore
keeps it out of the repo, and no build containing it should be distributed.
Anthropic enforces the Clawd trademark — the Clawdbot project was required to rename to Moltbot in January 2026. If CCrab is ever to be released rather than run locally, it needs its own original character. The renderer is asset-agnostic by design: an alternative sprite set is a directory swap, not a refactor.
Source code: MIT. Artwork: not covered — see above.