Skip to content

Latest commit

 

History

History
80 lines (56 loc) · 7.11 KB

File metadata and controls

80 lines (56 loc) · 7.11 KB

Claude Presto Buddy

Claude Presto Buddy

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.

Inspiration and direction

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 claude CLI 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 in bridge/ 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.

What works today

The firmware side already:

  • Advertises as Claude-XXXX over 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 as you: … / 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 prompt arrives; 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, unpair commands 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).

Getting started

1. Flash the firmware

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.

2. Copy the Python files and reset

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.

3. Pair with Claude Desktop

  1. Help → Troubleshooting → Enable Developer Mode in Claude Desktop.
  2. Developer → Open Hardware Buddy…, click Connect, pick Claude-XXXX from the list.
  3. The Presto switches from sleep / waiting for bridge to idle / bridge connected. Start a Cowork session to see the crab actually react — a standalone claude CLI or plain Desktop chat won't register yet (see "Notes from the port" below; closing that gap is what the planned bridge/ daemon is for).

Notes from the port

Things worth knowing, both as a user and for anyone hacking on this further:

  • Hardware Buddy on Windows only tracks Cowork sessions. A standalone claude CLI in a terminal and Claude Desktop chats leave total: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) after register_services.
  • PicoGraphics on Presto uses a 240×240 coordinate space by default (hardware does 2× scaling). Layout reads display.get_bounds() instead of hardcoding.
  • bitmap8 is proportional, not monospace — ruled out ASCII-art sprites; we draw the crab as scaled rectangles instead.

Project layout

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.