A tiny physical teammate that watches your AI coding agents and looks up at you the moment they need a decision.
Stop babysitting the terminal. Stop leaving agents blocked for 20 minutes. Glance over — and you just know.
Lore Buddy turns a $20 ESP32 touch-display into an ambient presence on your desk that mirrors the live state of Claude Code and OpenAI Codex. It has expressive animated eyes that you can read from across the room:
- It works when your agent works (calm, focused, an orange or purple comet sweeping the screen edge).
- It looks right at you, lights up, and softly chimes the instant the agent needs a decision — a permission, a question, a finished task.
- It otherwise lets you stay in flow.
Four channels of glanceable information, no reading required:
| Channel | Meaning |
|---|---|
| Color | Who — Claude is orange, Codex is purple |
| Border motion | How active — a comet sweeps while it works |
| Eyes | Emotion — focused, wide-alert, happy, worried |
| Logo + text | The specifics — which agent, and what it needs |
It's a build-status light, a Tamagotchi, and a notification system — fused into something that feels like a teammate.
Plus an idle ambient state (calm white eyes that look around and blink), and touch interactivity: the eyes follow your finger, and a tap dismisses an alert.
You ⇄ Claude Code ──hooks───────▶ ┐
├─▶ buddyd ──USB serial──▶ ESP32-S3 ──▶ expressive eyes
Codex ──notify event──────▶ ┘ (Mac daemon) (firmware)
- The firmware (
firmware/eyes/eyes.ino) renders everything itself — anti-aliased eyes via a signed-distance field, a per-pixel light-trail border, tinted logos, and text — into a full framebuffer in PSRAM, ~30 fps. It listens on USB serial forSTATE <mood> <agent> <reason>lines. buddyd(mac/buddyd.py) is a tiny always-light daemon that holds the serial port open and translates agent events into state.- Claude Code hooks feed it precise events: a prompt → working, a notification → needs you (with the real message), a finished turn → done.
- Codex feeds it through Codex's own
notifymechanism (a wrapper that also forwards to your existing notifier, so nothing breaks).
Full detail in docs/ARCHITECTURE.md.
A single off-the-shelf board, no soldering:
- Waveshare ESP32-S3-Touch-LCD-1.69 — ESP32-S3 (8 MB PSRAM, 16 MB flash), 1.69" 240×280 IPS LCD (ST7789V2), CST816T capacitive touch, buzzer, IMU, RTC, USB-C.
- A USB-C cable. That's it.
Pinout and flashing notes: docs/HARDWARE.md.
Install arduino-cli + the ESP32 core
- "GFX Library for Arduino", and an esptool 4.x venv (the core ships esptool 5, which fails the connect on this native-USB board):
python3 -m venv .esptool-venv && .esptool-venv/bin/pip install 'esptool<5'
arduino-cli core install esp32:esp32 --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
arduino-cli lib install "GFX Library for Arduino"
FQBN="esp32:esp32:esp32s3:PSRAM=opi,FlashSize=16M,CDCOnBoot=cdc"
arduino-cli compile --fqbn "$FQBN" --export-binaries firmware/eyes
# First flash only: hold BOOT, tap RST, release BOOT (enters download mode).
.esptool-venv/bin/esptool.py --chip esp32s3 -p /dev/cu.usbmodem* -b 921600 \
--before default_reset --after hard_reset write_flash \
0x0 firmware/eyes/build/esp32.esp32.esp32s3/eyes.ino.bootloader.bin \
0x8000 firmware/eyes/build/esp32.esp32.esp32s3/eyes.ino.partitions.bin \
0xe000 firmware/eyes/build/esp32.esp32.esp32s3/boot_app0.bin \
0x10000 firmware/eyes/build/esp32.esp32.esp32s3/eyes.ino.binAfter this, CDC is enabled and future flashes auto-reset — no button needed.
./install.sh # installs ~/.buddy, the `buddy` command, and the launchd agent
buddy status
buddy test # fire a test alert at the deviceAdd these to the "hooks" object in ~/.claude/settings.json (merge — don't replace existing hooks):
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "/Users/YOU/.buddy/bin/buddyctl working claude" }] }],
"Notification": [{ "hooks": [{ "type": "command", "command": "/Users/YOU/.buddy/bin/buddyctl-notify" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "/Users/YOU/.buddy/bin/buddyctl done claude" }] }]In ~/.codex/config.toml, point notify at the wrapper (it forwards to your
existing notifier, so Computer Use etc. keep working):
notify = ["/Users/YOU/.buddy/bin/codex-notify"]Now just use your agents — Lore Buddy reacts on its own.
Runs from anywhere. off fully stops the process — zero CPU/RAM, serial port released.
buddy on start now + at login buddy status on/off + device connected?
buddy off fully stop (zero resources) buddy logs watch live events
buddy toggle flip on/off buddy test flash a test alert
buddy restart buddy idle reset device to calm
buddy doctor diagnose the whole setup buddy demo cycle through every state
buddy doctor checks every link in the chain — daemon, socket, device, Claude
hooks, Codex notify — and tells you exactly what's broken and how to fix it. Run it
first whenever something isn't reacting. buddy demo walks the device through
every mood (working → needs-you → done → error) for both agents, so you can show it
off or eyeball the firmware visuals without wiring up a single agent.
Mute the chime with the BOOT button on the device — a speaker icon pops up, a corner indicator shows it's muted, and it persists across reboots.
- Colors / moods — edit the
PAL_*palettes andMOODS[]table near the top offirmware/eyes/eyes.ino. Prototype changes instantly inpreview/eyes3.html(open it in a browser — it's the design source of truth, mouse = touch). - Logos — drop new SVGs in
assets/and runpython tools/gen_logos.pyto regeneratefirmware/eyes/logos.h. - Performance —
BUDDY_HEARTBEATandBUDDY_CODEXenv vars tune the daemon (seemac/README.md).
Start with buddy doctor — it walks the whole chain and pinpoints most of the
issues below automatically. The rest:
| Symptom | Fix |
|---|---|
esptool "No serial data received" |
Firmware is holding USB — hold BOOT, tap RST, release BOOT, then flash. |
| Eyes show the wrong agent color | Process-sniffing is off by design; Codex only reacts to real notify events. Make sure step 4 is done. |
| Eyes follow your finger the wrong way | Flip the dx/dy mapping in tpRead() in the firmware. |
| Image shifted at the panel edges | Adjust the ROW_OFFSET/offsets in the Arduino_ST7789 constructor. |
| Nothing reacts to Claude Code | Restart the CLI once so it loads the new hooks. |
Created by Dakshay Mehta — x.com/dakshay.
Also worth checking out: lore.thepantheonai.com.
Logos via simple-icons (CC0). "Claude" and the Claude mark are trademarks of Anthropic; "OpenAI"/"Codex" and their marks are trademarks of OpenAI — used here nominatively to identify each agent. Text font: Adafruit GFX (FreeSansBold). Built on Arduino_GFX.
MIT © 2026 Dakshay Mehta


