Building Claudeq: a touchscreen control surface for Claude Code
The problem: agents that stop and wait Claude Code is Anthropic's terminal-based coding agent. It's autonomous for stretches, but by design it periodically stops: a permission prompt before it touches something risky,
The problem: agents that stop and wait
Claude Code is Anthropic's terminal-based coding agent. It's autonomous for stretches, but by design it periodically stops: a permission prompt before it touches something risky, a clarifying question when it genuinely isn't sure what you want. That's a feature, not a bug β except when you're running several sessions across different projects, because then the failure mode isn't "the AI got something wrong," it's "I didn't notice it was waiting on me for twenty minutes."
I wanted a physical, glanceable answer to "which session needs me right now." That's Claudeq.
It's way faster than fiddling around with the Claude Code app in your phone, and still works if you forgot to start the /remote-control before leaving your desk.
What it is
Claudeq is a free, open-source (MIT), personal project β not a product, not a company, no monetization. It turns a Waveshare ESP32-S3-Touch-LCD-3.49 (a ~$30, 172Γ640 portrait touchscreen) into a dedicated control surface for Claude Code:
- Claude's
AskUserQuestionprompts render on-screen β tap an option to answer. Multi-question prompts step through one at a time. - Every running session is a chip across the top; the one that needs you glows, and a new question auto-focuses whoever asked.
- It discovers every bridge on your LAN via mDNS and, optionally, over a Tailscale tailnet β so sessions running on other machines merge into the same screen.
- A macro deck fires a saved prompt or slash-command with one tap.
- Tap-to-talk voice input, transcribed locally via whisper.cpp (no API key, nothing leaves your machine until you review the transcript and hit Send).
- OTA firmware updates, pulled from GitHub over HTTPS, with automatic rollback if an update fails to boot.
- On-device WiFi/Tailscale setup through a captive portal β no rebuilding firmware to change networks.
Architecture
Claude Code A Claude Code B The claudeq command
β hooks β hooks launches a tmux session
βΌ βΌ
bridge.mjs ββ bridge.mjs ββ Each advertises itself
:8787 β :8787 β via mDNS, device discovers
β β & websocket to ALL bridges
β β (taps/focus/voice are
ββββββΆ claudeq βββββ routed back to source session)
The firmware is C on ESP-IDF v5.4.1 with LVGL for the UI. Each dev machine runs a small Node "bridge" that Claude Code's own hooks feed session events into (question asked, tool running, reply received); the bridge relays those over a JSON-over-WebSocket protocol to any connected device. A claudeq launcher script is the actual entry point end users touch: it wraps claude in a tmux session named after the project directory (so it shows up correctly as a chip), starts the bridge if it isn't already running, and otherwise gets out of the way β plain claude still works exactly as before.
Some implementation details worth sharing
OTA and the partition problem. Adding OTA support after the fact meant changing the flash partition table β which itself can't be done over the air. Anything flashed before OTA existed needs exactly one more USB flash to adopt the new layout; every update after that is wireless, with automatic rollback if a new image doesn't boot cleanly.
Zero-toolchain distribution. Release builds are served through a static page using ESP Web Tools (WebSerial), so installing doesn't require ESP-IDF, Python, or any CLI β plug in USB-C, open Chrome or Edge, click Install. A claudeq flash CLI command exists too, for anyone who does have the toolchain and just wants a shortcut.
Standby vs. lock. The backlight is the dominant power draw, so standby only turns off the screen β WiFi and the WebSocket stay connected, so the deck lights back up instantly on an incoming question or alert rather than needing to reconnect. Locking (long-press the BOOT button) is a separate, stronger state for "this is going in my pocket": screen dark, touch and incoming wake events both ignored, until another long-press unlocks it.
No auth yet. Worth stating plainly: the deviceβbridge WebSocket has no authentication. It's fine on a home LAN or personal tailnet you trust; it is not meant to be exposed to anything untrusted. That's explicitly called out in the README and is next on the roadmap.
Installing it
- Flash from a browser (WebSerial, Chrome/Edge, no toolchain) β or
claudeq flashif you have Node already. -
brew install Positronico/tap/claudeqβ pulls in Node, tmux, ffmpeg, and putsclaudeqon your PATH. - Connect the device to WiFi via its own captive portal (
Claudeq-setuphotspot on first boot). - Run
claudeqinstead ofclaudein any project.
Building from source (only needed if you want to change the firmware or bridge) is documented in the README, including the ESP-IDF setup and the build-dist.sh packaging script.
Why share it
This started as a for-myself project β I use it daily now β but the underlying problem (many agent sessions, only one pair of eyes) doesn't seem specific to me. It's MIT-licensed and free; the project page (https://invisible.cat/claudeq) has the overview and links through to the GitHub repo, which has everything including a protocol doc and pinout reference if you want to poke at the firmware/bridge.
Links
- Project page: https://invisible.cat/claudeq
- Repo (source): https://github.com/Positronico/claudeq
- Browser flasher: https://positronico.github.io/claudeq/
- Protocol doc: https://github.com/Positronico/claudeq/blob/main/docs/PROTOCOL.md
- Pinout doc: https://github.com/Positronico/claudeq/blob/main/docs/PINOUT.md
- License: https://github.com/Positronico/claudeq/blob/main/LICENSE
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.



