A tiny desk companion that mirrors the activity of your Claude sessions — an animated pixel-art crab living on a Pimoroni Presto (RP2350B, 480×480 capacitive touch, 7-zone SK6812 ring, piezo, CYW43439 BT+WiFi), paired to Claude Desktop over BLE.
Inspired by Anthropic's Claude Desktop Buddy firmware for the M5StickC — same idea (a desk companion that reacts to your Claude activity over BLE), same wire protocol, but built for a different setup:
- Claude CLI is a first-class citizen. Claude Desktop's Hardware Buddy only reports Cowork / Desktop-imported sessions; standalone
claudeCLI sessions and plain Desktop chats never register. Since CLI is where most of our day-to-day coding happens, we're building a CPython bridge daemon inbridge/that surfaces those sessions over the same NUS protocol. - Touch-native UX instead of two physical buttons. Upstream navigates with A/B presses; we swipe between screens and tap / long-press / drag directly on the buddy sprite.
- A larger, higher-resolution canvas. The 480×480 display lets us draw a proper animated pixel-art Claude crab with PicoGraphics primitives, rather than reusing the StickC's ASCII-art sprites.
- Richer ambient output. A 7-zone SK6812 ring and piezo give us per-state colour cues and audio chirps alongside the on-screen animation.
We still borrow a lot from upstream — the 7 animation states, the heartbeat + prompt/permission protocol, the general shape of the character set — and the wire protocol stays byte-compatible so the same Claude Desktop build pairs with either device. But this isn't a straight port; the bridge and CLI focus mean the device ends up solving our problem (ambient feedback for the terminal-based workflow), not reproducing the StickC verbatim.
Concretely: the firmware runs in upstream-baseline mode unless the peer advertises extra capabilities on connect. Pairing with Claude Desktop stays seamless (it never sends a capability message, and the firmware knows not to expect one), while our bridge sends a hello with a set of flags that unlock extras like CLI-session tracking, richer stats, and custom screens. That way there's no mode toggle for the user to misconfigure and nothing Desktop needs to change.
The firmware side already:
- Advertises as
Claude-XXXXover the Nordic UART Service. Claude Desktop's Hardware Buddy window discovers it, connects, negotiates a 527-byte MTU, and starts streaming heartbeats. - Parses the upstream heartbeat schema (
total/running/waiting/msg/entries/prompt) and the one-shot{"evt":"turn"}events (text, tool_use, thinking blocks) — live transcript text and tool names appear on-screen asyou: …/claude: …/claude: -> ToolName. - Renders the animated pixel-art Claude crab in the top half of the screen — scaled-pixel renderer with per-state variants (closed eyes for sleep, blink+bob for idle, alternating leg scuttle for busy, red-tint shake + wide eyes for attention, parabolic hop for celebrate, pink pulse with heart-eyes for heart).
- Drives 7 RGB zones per state: blue breath (idle), amber chase (busy), red blink (attention), rainbow sweep (celebrate), green pulse (heart), off (sleep).
- Sounds piezo cues: two-beep chirp on attention, four-note arpeggio on celebrate.
- Handles the touch approval flow: three buttons (A / B / MENU) at the bottom flip to APPROVE / DENY when
promptarrives; approve sends{"cmd":"permission","id":…,"decision":"once"}back upstream. - Includes a MENU long-tap demo cycler for exercising the UI without a live bridge — cycles busy → attention → celebrate → idle.
- Acknowledges
owner,status,name,unpaircommands and handles time-sync payloads.
See TODO.md for the remaining porting backlog — cleanup, protocol features (folder-push, bonding, persistence), and feature-parity polish (long-press menu, extra species, SD-card character packs, ambient backlight).
Install Pimoroni's Presto MicroPython firmware (pimoroni-presto-*-micropython-with-filesystem.uf2). It already enables MICROPY_PY_BLUETOOTH + BTstack + CYW43 BT, and includes aioble in the frozen manifest.
python -m mpremote cp main.py app.py ble_nus.py ui.py buddy.py leds.py audio.py :/
python -m mpremote reset
main.py runs automatically on reset.
- Help → Troubleshooting → Enable Developer Mode in Claude Desktop.
- Developer → Open Hardware Buddy…, click Connect, pick
Claude-XXXXfrom the list. - The Presto switches from
sleep / waiting for bridgetoidle / bridge connected. Start a Cowork session to see the crab actually react — a standaloneclaudeCLI or plain Desktop chat won't register yet (see "Notes from the port" below; closing that gap is what the plannedbridge/daemon is for).
Things worth knowing, both as a user and for anyone hacking on this further:
- Hardware Buddy on Windows only tracks Cowork sessions. A standalone
claudeCLI in a terminal and Claude Desktop chats leavetotal:N / running:0 / msg:"N idle"static. This is a limitation of Claude Desktop's session discovery, not of the device — live activity flows correctly the moment a Cowork session runs. - aioble's default adv payload overflows 31 bytes with
Claude-XXXX+ the 128-bit NUS UUID. Name moved to the scan response, UUID stays in the primary advertisement. - Claude Desktop's scanner filters by name prefix + service UUID. Matching only one (e.g. name
Buddy) makes the device invisible even though Windows' own Bluetooth picker still shows it. - MicroPython's per-characteristic GATT buffer defaults to ~20 bytes and silently truncates writes regardless of negotiated MTU. Fixed by calling
gatts_set_buffer(handle, 256, False)afterregister_services. - PicoGraphics on Presto uses a 240×240 coordinate space by default (hardware does 2× scaling). Layout reads
display.get_bounds()instead of hardcoding. bitmap8is proportional, not monospace — ruled out ASCII-art sprites; we draw the crab as scaled rectangles instead.
| File | Purpose |
|---|---|
main.py |
Entry point; runs App().run(). |
app.py |
State machine, BLE line handler, render / input / LED / audio / demo-cycler async tasks. |
ble_nus.py |
Nordic UART Service peripheral (aioble) with adv/scan-response split, MTU exchange, GATT buffer tuning. |
ui.py |
Dynamic-bounds layout, touch buttons with edge detection. |
buddy.py |
Scaled-pixel Claude crab sprite + per-state animation sequences. |
leds.py |
7-zone RGB state cues. |
audio.py |
Piezo beep patterns on attention / celebrate. |
bridge/ |
(WIP) CPython daemon to surface standalone claude CLI sessions over the NUS protocol. |
test_ble.py |
Bare BLE advertising diagnostic (kept from bring-up). |
TODO.md |
Remaining porting backlog. |
