neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
Small always-on machines for the desk, built by describing them to an agent. VibePulse is open source, runs off a Mac or a Windows PC, and is expanding to more Waveshare hardware.
Developer toolsC · PythonGitHub @niclasvestlund-YTOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
ESP32-S3 AMOLED for Claude Code & Codex usage, live agent activity, and answerable NEEDS YOU alerts. Local-first macOS/Windows host; optional encrypted relays.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A little always-on screen for your shelf that shows what your AI coding
agents are doing — taps you on the shoulder when one is stuck waiting for
you, and (if you want) lets you answer it with a tap on the glass. It packs
too: one command moves it onto whatever WiFi you are on today, and one
button-hold on the panel opens its own settings on the glass.Read the full README ↗Your coding agents, at a glance. Claude Code and Codex quotas, live agent activity and a clear signal when something needs you — on a small display beside your keyboard.
Choose a colorful AMOLED panel or the new Paper USB edition: a light, 800 × 480 e-paper view with large numbers, calm typography and a physical page switch. A local Python service supplies the readings. Local operation needs no VibePulse account; cloud relays and extra integrations are optional.
Get started · Paper USB edition · Choose your board · What's on screen · Latest release · Privacy
| Start with | Add when you need it |
|---|---|
| Claude Code and Codex quota, reset time and agent activity | Supported AMOLED panels can show NEEDS YOU and answer supported prompts on the glass |
| Local service on your computer; board-specific setup | Optional quota, encrypted interaction and agent-status relays for internet Wi-Fi |
| A display profile matched to your exact board | Optional Labs integrations: OpenRouter, Lovable and the build-time Grok preview |
Paper currently uses USB from a Mac and has its own firmware and feature limits. The AMOLED family includes 2.16 square, 2.41 V2, 1.91 Touch, round 1.75 and 1.8 V2 development profiles. Each needs its own build; support is tied to the recorded hardware evidence.
A quiet, light 800 × 480 e-paper USB edition is now available in source. It reads the existing VibePulse tokenserver: Claude/Codex weekly quota, reset time and agent activity. Unchanged values send a heartbeat instead of flashing the display; content changes use partial refresh, with periodic full cleanup.
Physical pilot photograph (paper-0.1.1), with a historical reading.
The owner has checked live Codex data, navigation and the quieter update on one Waveshare ESP32-S3-ePaper-3.97-Kit (SKU 33811). This is a dedicated USB firmware/host target, not the full AMOLED feature set. Battery, audio, Wi-Fi, OTA and remote decisions are not qualified. Build/setup guide · Real owner photographs and evidence.
The thumbnail is from a Niclas Testar Livet Short, not a simulator capture or a verification of every board profile.
When you run coding agents all day, two things are invisible:
Both answers already exist, buried in a terminal you're not looking at. VibePulse moves them onto a screen you can't miss: one glance from across the room, no window to switch to, no menu bar to squint at.
Current release: v1.5.0. Paper joins the project as a USB development edition, physically checked on one Mac-connected unit. Existing AMOLED capabilities and platform claims keep their own verification limits. Read the illustrated release notes.
VibePulse is built for vibecoders — people who build with Claude Code or Codex. You do not need to read this whole page:
secrets.h, and the
table under Independent switches. A 3 s KEY3
hold opens SETTINGS on the glass — UPDATE, WIFI, LABS and ABOUT; the
LABS choices are saved and apply after restart. Sound has no verified
backend yet.OpenPulse is included in v1.4.0, adding an optional OPENROUTER page to VibePulse Labs. The spend page shows daily, weekly and monthly spend for the selected API key, compared with a monthly display budget you choose. The details page shows remaining API-key allowance and optional account credits; BYOK usage stays separate. The display budget is a reminder, not a provider cap.
Native 466 × 466 shared LVGL captures with synthetic demo values.
Build compatible firmware from the release source with -DTORGET_OPENPULSE=ON, then
enable OPENROUTER under SETTINGS → LABS → MORE → MORE and tap
RESTART NOW. OpenRouter is off by default. Tap the bottom page indicator
to switch between spend and details, or the key name to cycle configured keys.
Missing readings show dashes; stale readings and connection errors are explicit.
You can use OpenRouter without a Claude Code or Codex account. Follow the OpenRouter-only installation guide for the Mac demo, Keychain connection, separate LAN service, round firmware build, Labs choices and troubleshooting.
The data path is OpenRouter API → OpenPulse service on your Mac → Wi-Fi → display. Browser login is not required. The Mac must be awake and the separate service running; USB supplies power, not OpenRouter data. This preview has no automatic service startup or OpenRouter relay. A Labs switch alone does not install the service. If the screen shows dashes, NO DATA or an error, check the selected page, source status and panel reachability separately.
The round 1.75 has an owner-confirmed physical checkpoint for upright live readings and the bottom page switch. Square 2.16 has native simulator checks and a CI firmware build; physical OpenRouter acceptance is pending. The other three display profiles cannot enable OpenRouter yet. This remains a macOS source-build preview, included in v1.4.0.
Owner photograph, 29 September 2026. Full touch-grid, swipe/menu and stress acceptance remain pending; see the linked checkpoint for the tested build.
The opt-in GROK page uses VibePulse's quota header, gauge and stat widgets.
The subscription plan is the default source; the hero shows percent of the plan.
EST marks estimates; copied readings say GROK.COM with their age.
There is no public usage API for Grok subscriptions or Grok Bot.
xAI and OpenRouter are optional secondary sources showing percent of budget
with dollars below, or a dollar hero without a positive limit.
Square and rectangular profiles show up to three bot rows; round 1.75 uses the
quota edge ring and one priority bot line. Sources and cached readings remain
explicit. Enable -DTORGET_GROK=ON and the host's --grok option; both are off
by default. Setup and source limits.
Shared LVGL captures with synthetic readings. Grok has no physical panel acceptance.
VibePulse, on paper. The new 3.97-inch USB edition brings a light, black-and-white desk display to the project: Codex and Claude pages, agent activity, a three-way page switch and updates that leave unchanged pixels alone. Your real hardware photos sit beside reproducible native screen fixtures.
One Mac-connected unit has owner-confirmed live Codex data, orientation, navigation and quieter partial refresh. Claude needs a working local source; missing readings show a dash. Battery, audio, Wi-Fi/OTA and remote answers remain outside the Paper support claim. The release also includes the pinned Pillow 13.0.0 security update for development tooling.
Illustrated v1.5.0 notes · Full changelog · Compare v1.4.0...v1.5.0 · Previous v1.4.0 release
This is a source release; a tag does not perform a physical flash of every final image. Earlier releases retain their dated evidence: v1.1.0 added SETTINGS, with CI-built firmware and physical-review limits recorded in its release notes.
Contributing or validating another host? Read CONTRIBUTING.md, the host support matrix, and SECURITY.md before sharing logs or test evidence.
Three quota pages form the new-install base. The examples below also show
optional LABS pages: burn rate, two Max Trackers, API-equivalent Value and a
GitHub project pulse. Value needs priced usage; its comparison says
SET YOUR PLAN COST until a plan cost is configured. Choose the pages you
want in SETTINGS → LABS and restart to apply. The screenshots are native
480×480 LVGL output; physical review status is recorded with each feature.
Usage — Claude's weekly and heaviest-model-weekly quota, plus Codex's weekly quota. Each with a reset countdown and how much you've burned today.
NEEDS YOU — when an agent blocks on your input, the whole screen turns into the alert, in that provider's colour, naming the project it's waiting on. Tap to dismiss — or, with the opt-in Needs You bridge, tap to answer it without switching windows (see below).
Live agent monitor — the header shows which agents are working right
now, with model and effort, on every page. 2 CHATS ACTIVE when several
are running.
Burn rate — a forecast per provider: on pace, running out early (and when), or how much head-room is left at reset.
Max Tracker — a GitHub-style heatmap of your daily quota peaks, with coding streaks and max counters, per provider. Red cells are days you maxed out.
Both providers get equal treatment — same pages, same alert, their own accent colour:
Codex weekly quota
Codex NEEDS YOU alert
Claude Max Tracker
The panel becomes an input device. With the opt-in Needs You bridge, when Claude Code or Codex blocks on a supported question or permission, the takeover appears and a tap answers it in the same live session — no window to switch to. The computer must be awake and the tokenserver must be running. Direct mode uses the LAN. The separate encrypted interaction relay works when the panel and computer use unrelated ordinary internet Wi-Fi: both sides make outbound HTTPS connections, so there is no router reconfiguration, inbound port, public Mac, or VPN. Cloudflare handles only fixed-size ciphertext; see the privacy and setup guide.
Attract → decision → done. A held prompt surfaces as a mascot in a depleting countdown ring; a tap reveals it; APPROVE commits the agent's explicitly recommended option (or LEAVE IT hands it back to the computer), and the flow closes on a short "ON IT" beat. The panel signs every verdict with a key shared only with your computer — it can answer a prompt that computer was already going to ask about, and nothing more. Walking away always costs nothing: an unanswered prompt just falls back to the terminal. Setup is in docs/agent-setup.md.
For Codex, only its narrow safe-command tier can show ALLOW ONCE. Unknown, mutating, secret-bearing, or text that does not fit stays on the computer; silence never means approval. Recommended questions are equally strict: Codex must mark one of two or three options itself. VibePulse never guesses.
After a firmware or Codex-bridge change, use the canonical physical smoke test in docs/agent-setup.md. A pass requires visible APPROVE, a real panel tap, and the matching answered result back in Codex. A waiting screen, timeout, LEAVE IT, or computer fallback is not a pass.
VibePulse is open source, so installing one part never silently enables another. Each row is an independent switch and every interaction/cloud choice starts off:
| Switch | What it does | Default |
|---|---|---|
| Claude interactions | Lets Claude Code questions and permissions reach the panel | Off |
| Codex interactions | Lets supported Codex questions and permissions reach the panel | Off |
| Numbers relay | Publishes only quota, reset, Max Tracker, and optional public GitHub numbers | Off |
| Interaction relay | End-to-end encrypted question/verdict mailbox for unrelated WiFi | Off |
| Live agent status relay | End-to-end encrypted Claude/Codex activity rows for unrelated WiFi | Off |
| GitHub | Shows one public repository's page and/or star notification | Off |
Installing the Codex plugin does not enable Codex interactions. Setup asks
whether to enable Claude, Codex, both, or neither, and whether bounded detail
may reach the panel. The old --interactions is a legacy alias for Claude only;
use the explicit setup command for new installations. The numbers relay and
interaction relay are different privacy choices and neither is enabled by the
plugin. Installing the Codex plugin does not enable the encrypted interaction
relay or the live agent status relay.
One public owner/repository can add a deliberately sparse optional page:
the current star count is the hero and forks are the only secondary metric.
The same raster covers every data provenance, so the glass never lies about
freshness:
Live
Cached / stale
Waiting (no data)
The page and star moments are independent switches. A new star can therefore
briefly take over the current VibePulse view even when the GitHub page is not
in rotation. It covers the previous page with a quiet black stage, shows a
large filled star, the repository, the stargazer when GitHub supplies one,
the new total, and TAP TO DISMISS; otherwise it returns to the exact
previous page after two minutes.
The computer service polls GitHub's public API and republishes a small, validated LAN payload. The ESP32 never talks to GitHub, and a GitHub timeout or rate limit cannot stall the Claude/Codex endpoints. Configure it with:
python3 tools/tokenserver/tokenserver.py --github-repo owner/repository
On Windows, persist the same source in Task Scheduler instead of relying on a foreground shell:
.\tools\tokenserver\install-windows-task.ps1 `
-GithubRepo "owner/repository"
Then open SETTINGS → LABS → MORE on the panel and enable GITHUB PAGE
and/or STAR POPUP. Restart the panel to apply the saved choices. Both start
off on a new installation; the old secrets.h macros seed defaults only until
a choice is saved. No GitHub token is required for a public repository.
TK_GITHUB_SOUND_ENABLED is a separate default-off gate for the 258 ms
A5-to-C#6 chime. The sequence and failure-isolated playback contract are in
place, but the current target intentionally registers no codec backend until
the physical speaker and display-DMA budget have passed device testing. A
missing or failed sound backend never delays the popup or any network path.
Before the first successful fetch, and whenever a source is missing, you get
dashes — never a placeholder 0% that you might believe. If the service
goes away, the last good numbers stay on screen and get marked stale rather
than silently drifting.
On current firmware a wall-powered panel also protects itself against the
specific failure where Wi-Fi still looks associated but application HTTP has
stopped. Once the quota feed has worked at least once, and only when an
independent numbers relay is configured, 60 seconds without a fresh response
recycles Wi-Fi and wakes the quota task, which waits for a new IP before
retrying. If no real success follows within another 45 seconds, the device restarts once to clear
wedged HTTP/TLS state. A reboot is disarmed until a new real success, so a real
internet outage cannot become a restart loop. LAN-only installations never
perform this automatic recovery just because their computer is asleep.
After that controlled restart, the panel sends a fixed content-free recovery
marker on local requests. GET / exposes it only as
interactions.panel.httpStallRecoveryBoot; setup doctor and the next Codex
startup can therefore distinguish a recovered wall-powered panel without a
USB serial cable. It contains no hostname, address, account, or usage value.
Run Claude only, or Codex only, and the other half simply shows dashes.
The usage pages answer how much have I spent?. The value multiple answers the question you actually have every month: it prices the tokens your agents already logged at list API rates and divides by what you pay. It's its own page on the swipeable strip, alongside GitHub — neither replaces the other.
python3 tools/tokenserver/tokenserver.py --claude-plan max5x --plan claude=100
The equivalent persistent Windows setup is explicit per provider and never guesses what you pay:
.\tools\tokenserver\install-windows-task.ps1 `
-ClaudePlan max5x -ClaudePlanCostUsd "100" `
-CodexPlan pro -CodexPlanCostUsd "20"
It counts cache tokens, which is the whole point — a real record here reads 2 input and 4 output against 23 655 cache-read, so pricing only input and output understates it by 577x.
Rates are not hand-maintained: they are generated from a public price
catalogue by tools/tokenserver/update_prices.py and committed, so the
server stays offline and refreshing is one command. An unknown model degrades
the figure to a dash rather than being silently free.
your computer your shelf
┌────────────────────────────┐ ┌──────────────┐
│ ~/.claude/projects/*.jsonl │ │ │
│ ~/.codex/sessions/*.jsonl │ ───────► │ ESP32-S3 │
│ rate-limit headers │ │ AMOLED │
└────────────────────────────┘ └──────────────┘
tokenserver.py :8737 plain JSON over your LAN,
pure Python stdlib polled every 30 s
A tiny Python service on your Mac or Windows PC reads your local Claude Code / Codex logs and rate-limit headers, and serves plain numbers over your LAN. The screen polls it every 30 seconds. Your OAuth token never leaves the computer; the screen only ever receives percentages, counts and coarse status.
The computer must be on for fresh local data. It does not have to stay in the same house when a relay is enabled, but it does have to run the tokenserver so there is something to publish. A phone hotspot is fine after it has been taught to the panel; captive portals and 5 GHz-only networks are not.
The startup/doctor health check also guards Claude's saved usage credential.
GET / exposes only claudeCredential.status and whole minutes remaining—
never an access or refresh token—and warns 30 minutes before expiry. This
matters because Claude Desktop can remain logged in after the separate
credential readable by VibePulse has aged out; stale Fable data is never
reported as current. Read that guard together with claudeProbe and the
/api/tokens stale flags: a successful probe plus a fresh model-week flag
means the current source is live even if the saved fallback is expired. That
is a future recovery risk, not a reason to restart the tokenserver.
Optionally, Claude Code itself can feed the quota: python3 tools/vibepulse_setup.py statusline install --yes-single-account points
Claude Code's statusLine at a small bridge that keeps the session and
weekly windows Claude Code already hands that command, then runs the status
line you had before. Each window is arbitrated against the OAuth probe
(the later reset wins, within one window the higher figure), and while a
fresh sample covers both windows the probe slows to every 30 minutes, so
the shared rate-limit bucket is spent less. It asks you to confirm that Claude Code and the
tokenserver use the same Claude account; see
docs/agent-setup.md.
Codex plugin 0.1.7 turns the trusted SessionStart hook into a real bounded
health check. It reads only the two loopback JSON endpoints, follows no
redirects, times out in under a second, and injects one content-free class into
the new task:
| Startup class | What it proves | First action |
|---|---|---|
HEALTHY | Provider data and recent direct panel polling are fresh | None |
HEALTHY AFTER DEVICE SELF-RECOVERY | The same, after the bounded HTTP-stall restart | Keep observing past the stale window; this is evidence, not a physical PASS |
PROVIDER DATA STALE | Claude and/or Codex source data is stale | Run setup doctor and tokenserver smoke; inspect the named provider |
DEVICE PATH STALE / PANEL LAN WAITING | Host data is fresh but direct glass polling is stale or unconfirmed | Check panel power, network, discovery, and firmware before restarting a healthy host |
SERVICE VERSION DRIFT | The loaded plugin and live tokenserver came from different source revisions | Repair all integrations from one durable checkout and start a new task |
SERVER UNAVAILABLE / LOCAL API DEGRADED | The local service or its diagnostic contract is unavailable | Run setup doctor and tokenserver smoke |
The hook diagnoses; it never approves, flashes, refreshes credentials, or silently rewrites service configuration. Firmware self-recovery remains bounded and fail-closed, while host repair remains an explicit setup action.
Four things. All four are required for the core — your usage on the glass.
No VibePulse account, no cloud service, no API key — you sign in to
Claude Code or Codex as you already do, and nothing else. The service on
your computer needs only
Git and Python; the Windows host runbook has the
winget commands and download links for both. Putting the firmware on the
board is the one step that needs more, and row 1 says what.
| You need | Because | |
|---|---|---|
| 1. A screen | One of the boards below, a USB-C cable, and its own USB power supply. Flashing it also needs the ESP-IDF 5.5 toolchain with CMake and Ninja, one time | a computer USB port usually cannot feed the running AMOLED; the firmware is built from source until the browser installer lands |
| 2. A computer | A Mac or a Windows PC with Git and Python 3.11+, awake whenever you want fresh numbers | the VibePulse service runs here and reads your agents' local usage |
| 3. An agent on that computer | Claude Code and/or Codex, installed and signed in. Either alone is fine | that is where the numbers come from |
| 4. WiFi | A 2.4 GHz network that both the screen and the computer can reach | the ESP32-S3 cannot see 5 GHz; the optional relay lifts the same-network rule later |
Flashing the firmware currently also needs ESP-IDF 5.5 on the computer — see Setup. A browser-based installer is planned so that this step disappears.
Affiliate disclosure: Product links marked affiliate may earn Niclas Vestlund a commission. Waveshare supplied hardware for development and testing. The support status below reflects our own verification of each model.
| Board | Display | Status |
|---|---|---|
| Waveshare ESP32-S3-Touch-AMOLED-2.16 (affiliate) | 480×480 AMOLED, touch. Also on the board: an IMU, and an ES8311 codec with amplified speaker output; whether a speaker is fitted is unconfirmed, and neither is verified on the unit | Display, touch, and Wi-Fi verified on a real unit (spec/hardware-capabilities.yaml is the source of every such claim). Its simulator frames are exact 480×480 renders. No soldering. Same board Clawdmeter uses. |
| Waveshare ESP32-S3-Touch-AMOLED-2.41 (affiliate), V2 / Rev2.0 only | 600×450 AMOLED in fixed landscape, capacitive touch; BOOT opens settings | Supported in current source. Display bring-up, portrait corner touch, Wi-Fi and owner-visible Codex/Claude usage verified. Install guide · exact evidence. V1, automatic rotation, OTA and physical answer replies are not validated by this port. |
| Waveshare ESP32-S3-Touch-AMOLED-1.91 (affiliate), touch variant | 536×240 AMOLED in fixed landscape; BOOT opens settings | USB-installed development port in current source. Four touch corners, phone Wi-Fi setup, saved-network reconnect and visible Codex usage checked on one real unit. Install guide · physical report. Needs You decisions stay on the computer until a safe compact button layout is verified. Menu navigation, OTA and rotation remain unverified. |
The v1.2.0 tag includes the 2.41, 1.91 and 1.75 ports; v1.4.0 also includes the
1.8 V2 development profile. Check the exact model and explicitly
select waveshare_241_v2, waveshare_191_touch, waveshare_175 or waveshare_18_v2;
firmware images are board-specific.
More boards are added after physical verification, following
Adding a display. The 2.16 registry remains under
spec/; V2 has its own
hardware registry.
Native-size simulator preview with fixture data, not a physical-panel photo or live account reading. The owner-reported physical checks are documented in the physical review.
The 1.8 product label covers incompatible revisions: V1 is not supported. Use the V2 install guide and check the rear label before building. The shared 480×480 UI is viewport-scaled to the native 368×448 panel; runtime memory high-water is not measured yet.
Real 1.91-inch panel, photographed September 23, 2026. The 9% quota was a momentary account reading; red room lighting limits color judgment. Follow the 1.91 Touch guide for USB flashing and the exact phone Wi-Fi steps. This profile is included in v1.2.0.
Real 1.75-inch panel, photographed September 24, 2026. The 18% reading was momentary. Current source keeps updates on USB and Needs You decisions on the computer. Physical evidence and open checks include touch, USB-down mounting and the new two-scan setup behavior.
On the real 2.41 V2: owner-supplied photo, added September 18, 2026. The display runs in landscape; the board is held at an angle in the photo.
These are exact 600×450 captures from the shared LVGL renderer with test data. The native fonts/icons are retained; margins, frames and footers fit the shorter display. Hold BOOT for three seconds for SETTINGS, then WIFI to provision locally. The V2 guide includes backup, recovery, sources and troubleshooting.
The 1.8 V2 development profile is now listed above. Each new port must pass the display bring-up and physical verification checklist before full support is claimed. Waveshare supplied the development hardware; product links marked affiliate may earn a commission.
Also received for VibeMatrix experiments: the controller, three LED panels and a power adapter below. This is an exploratory direction; it does not yet provide a working VibePulse installation or a validated wiring/power guide.
| Experimental hardware | Received |
|---|---|
| ESP32-S3-RGB-Matrix (affiliate) | 1 controller |
| RGB-Matrix-P2.5-64x32-B (affiliate) | 3 LED panels |
| PSU-5V4A-5.5-2.1-EU (affiliate) | 1 power adapter; SKU selects the EU plug |
For maintaining these product links, see the affiliate link notes.
| Computer | Status | Autostart |
|---|---|---|
| macOS | Supported. Daily development and physical panel reviews. macOS ships an older Python; brew install python gives you 3.11+ | launchd |
| Windows | Supported. v1 core, the physical answer loop, and the sign-in/sleep/reboot lifecycle verified on a real PC | Task Scheduler |
| Linux | Not yet. Tracked in #2 | — |
"Supported" means the computer service; it does not claim that every developer builds or flashes the firmware from that OS. The evidence behind each row is maintained in Host platform support. Windows installation and recovery use the Windows host runbook, and release candidates go through the reproducible Windows validation gate. "Supported" also does not mean every later candidate has passed the physical Windows loop: the v1 runtime's latest sanitized checkpoint is a FULL PASS, and future runtime revisions require a fresh run rather than inheriting it.
You do not set VibePulse up by hand. Your coding agent does it with you, step by step, verifying as it goes. Clone the repo and start the agent you already use, in that folder. The same three lines work in a Mac or Linux shell and in Windows PowerShell 5.1 and 7:
Claude Code
git clone https://github.com/niclasvestlund-YT/vibepulse.git
cd vibepulse
claude "Set up VibePulse for me: help me fill in secrets.h, build and flash the board over USB, and start the tokenserver on this computer."
Codex
git clone https://github.com/niclasvestlund-YT/vibepulse.git
cd vibepulse
codex "Set up VibePulse for me: help me fill in secrets.h, build and flash the board over USB, and start the tokenserver on this computer."
Any other agent (Cursor, Copilot, …): open the folder and paste the same sentence.
The repo is built for this. CLAUDE.md and AGENTS.md point the agent
straight at docs/agent-setup.md — an English
runbook written for agents, with a verification after every step, the traps
that actually cost people an evening, and a symptom→fix table. The agent
asks before it flashes the board; nothing is written to the screen without
your go-ahead. That's the whole onboarding.
Reading rather than running? That runbook is also the fastest way to understand how the pieces fit together.
Select the board first. The commands below build the original 2.16 profile. For a landscape board use its own sequence: 2.41 V2 or 1.91 Touch. Do not flash the default image to it.
The commands below show the macOS path. Windows is supported for the host service too; use the Windows ESP-IDF environment and the OS-specific Windows host runbook for the standalone Codex CLI, host address, firewall, Task Scheduler, startup health, and recovery steps.
Install ESP-IDF 5.5
and brew install cmake ninja
Clone this repo, then:
cp secrets.h.example secrets.h # fill in WiFi + your Mac's hostname (2 min)
. ~/esp/esp-idf/export.sh
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/cu.usbmodem101 flash
Don't miss this: in secrets.h, point the TK_VIBEPULSE_BASE_URL
fallback at a reachable host by replacing the DIN-MAC placeholder. Those URLs ship
active on purpose — a wrong hostname is visible in the log, whereas an
undefined URL compiles the fetch out entirely and the screen boots fine
and shows dashes forever. Use your Mac's Bonjour name
(scutil --get LocalHostName) rather than an IP, so the same firmware
works on your home network and on a phone hotspot. Current firmware also
discovers _vibepulse._tcp.local, so several Mac/Windows tokenservers can
be available without compiling their addresses into the panel.
Board not showing up under /dev/cu.usbmodem*? Hold BOOT, tap
RESET, release BOOT and it re-enumerates in download mode.
Power matters: flash with the board in download mode (screen dark). A computer USB port often cannot feed the running firmware. The AMOLED panel's draw makes the board bounce off the bus or hang, which looks like a flaky cable. After flashing, run the screen from its own USB power supply, not your computer. The firmware disables Wi-Fi modem sleep because the panel is an always-powered live display; do not re-enable it without repeating a sustained stale-window and interaction test on physical hardware.
Start the core service on your computer. Its core remains pure Python stdlib. Install the small optional discovery dependency when the panel should find this Mac/PC automatically:
python3 -m pip install -r requirements-discovery.txt
python3 tools/tokenserver/tokenserver.py
Without that package the configured URL path works exactly as before.
On macOS, validate and install autostart from this durable checkout with
python3 tools/vibepulse_macos_service.py validate followed by the explicit
install command. It atomically rewrites and fully reloads the LaunchAgent,
retries launchd's short post-bootout race within a fixed bound, and keeps
launchd from running a deleted PR worktree. Full details:
tools/tokenserver/README.md.
New installations using secrets.h.example show quotas with reset information
and local activity when available. In SETTINGS → LABS, add burn rate,
Max Tracker, API-equivalent value, a GitHub page, independent star popups, or Lovable credits.
Use MORE → MORE to hide the Claude Code or Codex quota pages and select
RESTART NOW to apply saved changes.
Tap to save a choice, then restart the panel to apply it. GitHub needs a
repository configured on the computer; Value needs prices and a plan cost for
its comparison. These choices do not start cloud services.
Existing configurations retain their initial views; saved menu choices survive firmware updates. Disabled pages are not created at boot. The new selector has shared LVGL simulator coverage; physical memory and touch review are pending before release. Setup, defaults and future experiments. Countdown clocks and coding quotes remain concepts for a later Labs addition.
An opt-in Chrome extension reads the balance shown on Lovable's billing page while a project is open. It sends only the workspace name and named credit/plan fields to the local tokenserver; the screen receives a compact credit snapshot. It does not read browser credentials, project source or chats. Missing data stays missing, and a reading becomes CACHED after three minutes without an update.
Native LVGL captures, with illustrative credit data. The browser source does not currently provide the reset countdown shown in this example. A real browser reading has also reached the round 1.75-inch panel. Automatic Chrome refresh and the Windows browser integration still need end-to-end confirmation.
Install the Lovable browser integration
for workspace selection, custom ports, startup configuration, verification,
removal and the round display's BOOT/Labs controls. No MCP login is needed for
--lovable --lovable-source browser. Lovable's documentation describes a credit
balance in get_workspace, but the authenticated response checked on
2026-09-24 omitted it. Source modes auto and mcp allow a later switch to the
official API without changing the display contract.
The default-off GROK page defaults to subscription plan percentage. EST marks estimates; copied readings say GROK.COM with their age. There is no public usage API for Grok subscriptions or Grok Bot. Optional xAI/OpenRouter secondary sources show percent of budget with dollars below (a dollar hero without a positive limit), alongside bot activity. Source and freshness stay visible; missing values show dashes, and unavailable updates retain the last real reading. Waiting bots show NEEDS YOU, without an approval action.
Enable firmware with -D TORGET_GROK=ON or TK_GROK_SCREEN_ENABLED 1 in
secrets.h, and enable the host separately with --grok. This page is not a
Labs switch. All five board profiles have simulator layouts; none has physical
Grok verification. See Grok setup and simulator commands.
2.41 V2: hold BOOT for three seconds to open SETTINGS, then choose WIFI, LABS or ABOUT. Use the V2 guide for USB updates; OTA is not validated on this model.
1.91 Touch: hold BOOT while the application runs to open SETTINGS; use the 1.91 Touch guide for setup and USB updates. Its menu navigation still needs a final physical review.
The details and 480×480 captures below describe the original 2.16. KEY3 is that panel's one user button — BOOT and reset are recovery controls, not part of normal use. Hold KEY3 for three seconds and SETTINGS opens on the glass.
The menu replaced a guess. The same hold used to open one window or the other depending on whether the panel happened to have an address. The panel decided, silently, and you found out by watching which one appeared. Now the choice is visible and yours.
A window the panel cannot use is never offered. With no address, UPDATE goes grey and refuses the press — a maintenance window with no address could never receive an upload, so offering it would be a promise the screen cannot keep. UPDATE is the only row that goes dark — WIFI, LABS and ABOUT stay lit — which leaves WIFI as the only row that can change the situation, and that is exactly where a panel in that state needs to go. ABOUT shows the address as a dash rather than inventing one. If the network drops while the menu is open, UPDATE greys out there and then; it is a live reading, not a snapshot taken when you opened it.
Any KEY3 press closes it. That is the escape hatch, and it is the same one the two windows have — including a press part-way through another hold. While an UPDATE READY notice is on the glass a hold does nothing at all, so SETTINGS can never open behind something you cannot see; answer that notice with its own LATER and UPDATE pills instead.
The consent model is unchanged by any of this. SETTINGS is reachable only from the device, so physical presence is still required before an update window can open, and the OTA token and the ten-minute lease are untouched. No script can reach the menu, and nothing but a finger on the UPDATE row opens the window behind it.
FEATURES and PAIR are in the design spec and deliberately not built yet: FEATURES needs the panel's internal-RAM budget re-measured on the unit first. Three rows that work beat five where two are promises the screen cannot keep.
Evidence, honestly: the frames above come from the shared LVGL renderer in the simulator, and the behaviour is covered by host tests.
torget-home-01has carried SETTINGS since the 2026-09-06 USB flash (v1.0.0-67-ge51b79f), but the static on-panel review — §3 of the manual test — has not been run, so nothing here is a physical verification; that review is the next gate.
This section describes 2.16. The 2.41 V2 and 1.91 Touch ports currently use board-specific USB updates; see their installation guides.
After the first USB flash, the screen updates itself over WiFi. The consent
chain is deliberate and three-factor: a physical 3-second hold on KEY3
opens SETTINGS, where UPDATE starts a ten-minute maintenance window
(the glass shows an UPDATES ON ring with the lease draining clockwise), a
64-hex token from secrets.h authenticates the upload, and the window
closes itself — a short KEY3 press closes it early. No button, no
update; a script can never open the window for you. (On a panel without
a network, UPDATE is greyed out and WIFI is the row that can fix it — an
update window with no address could never receive an upload. See
Take it with you.)
idf.py build
tools/ota-flash.sh <device-ip> # waits for KEY3 hold → UPDATE, then uploads
The hold opens SETTINGS rather than guessing which window you wanted, and on a panel with no address UPDATE is greyed out and cannot be picked. The frames are in One button, one menu.
The device verifies the image (magic, chip, project, SHA-256), writes it to
the inactive A/B slot (ota_0/ota_1, 5 MB each — see
partitions.csv), reboots into it, and a boot-health gate must approve
the new image within 15 seconds — display, UI, scheduler, NVS and memory
proofs — or the bootloader rolls back to the previous slot automatically.
USB-C remains the rescue path and is never written by an OTA. After an OTA
reboot the window re-arms itself once, so a build-test-build session needs
one hold, not one per build.
The tokenserver announces the newest build on your computer
(otaAvailableVersion on /api/tokens); when the screen runs an older
version it takes the glass with an UPDATE READY notice — answer it with
the on-glass LATER/UPDATE pills by touch. While that notice is up it owns
the glass completely: a KEY3 hold does nothing, deliberately, so SETTINGS
can never open behind it. The UPDATE pill opens the same maintenance window
the menu's UPDATE row does. A snooze returns every hour until installed. Full lifecycle reference: docs/ota.md.
If someone tells you "this project has no OTA": they are reading a tree
where partitions.csv still has a single factory partition. The OTA
foundation replaced that table (A/B slots + otadata) — check the branch
you are on before concluding anything, and never assume the flash layout
without reading partitions.csv in the checkout you are actually building.
The panel remembers up to six places. Arrive somewhere new and it needs the network once; every visit after that it joins by itself.
The normal setup path needs only the panel and a phone:
VibePulse-setup network; it does not contain your destination Wi-Fi
password. The normal screen keeps the QR dominant; tap Manual Setup
only if you need to see the temporary name, password, and local address.http://192.168.4.1/ yourself. A browser label such as Not Secure is
expected here: this is a short-lived, device-local page with no internet
route, not a public website.On a Mac there is also an optional one-command shortcut:
tools/wifi-here.sh
It reads the network your Mac is already on, takes that password out of your keychain (macOS asks you — that prompt is the consent), hands it to the panel over its temporary access point, and gives the Mac's Wi-Fi back. The phone flow remains the universal path and needs no computer or command line.
The small neutral Wi-Fi symbol is global and two-state: a slashed fan means the panel is not joined to an access point, a complete fan means it is. It shows no signal strength, and it does not mean internet access, tokenserver reachability, or relay health. During setup the complete symbol means setup mode, not a successful destination join.
The setup window opens on its own after 90 seconds without a network, or at once from a 3-second button hold (KEY3 on 2.16, BOOT on 2.41 V2 or 1.91 Touch) followed by WIFI in SETTINGS. Before that, at 60 seconds, the glass stops being coy: it names the network it is hunting and what the radio actually answered ("NOT SEEN - 2.4 GHZ ONLY", "WRONG PASSWORD") instead of showing dashes and letting you guess.
On a panel that already has a network, the same 3-second hold opens SETTINGS; tap WIFI. That is how you pre-load the phone hotspot at home before a trip — no need to wait until the panel is stranded somewhere. (The second-hold shortcut still exists, but only inside the update window: a full hold there closes it and opens WIFI SETUP. From SETTINGS a second completed hold just closes the menu.)
Two things stay true by design. The networks in secrets.h remain an
immutable floor — setup can add places, never remove your home network,
so a bad entry can never cost you a USB rescue. And the setup window
cannot write firmware: it touches the network list and nothing else,
while OTA keeps its own token and its own gate.
Honest limits: captive portals (the panel cannot click "I agree"), guest networks with client isolation, and WPA2-Enterprise are all still out of reach. The network that always works on the road is the one you bring — your phone's hotspot, with Maximize Compatibility on. Teach the panel that one once and it follows you everywhere. Full reference: docs/wifi.md.
And for the networks that do connect but wall the panel off from your machine (client isolation, IoT VLANs): the optional relay puts the numbers in a tiny mailbox on the internet — a ~150-line Cloudflare Worker on your own account — and the panel falls back to it whenever the LAN does not answer. Quota, burn rate, Max Tracker and the GitHub pulse follow you anywhere with WiFi. Agent activity stays local unless you separately opt in to one or both encrypted activity features. The Interaction relay carries only bounded Needs You views and verdicts. The independent Live agent status relay carries the minimized Claude/Codex rows the panel already renders. Both use fixed-size, end-to-end encrypted ciphertext; Cloudflare never receives question, command, project basename, activity, or verdict content in plaintext. Cloudflare can still see connection IPs, timing and a random mailbox identifier. The computer must be awake and tokenserver must be running, but it may use a different ordinary internet connection from the panel. Several machines can feed the numbers mailbox (a Mac that sleeps, an always-on PC) and the freshest source wins per number. Numbers setup: docs/relay.md. Encrypted decisions and live status: docs/interaction-relay.md.
brew install sdl2 cmake ninja
cmake -S sim -B sim/build -G Ninja && ninja -C sim/build
./sim/build/torget-sim
(On Debian/Ubuntu: apt-get install libsdl2-dev cmake ninja-build instead.)
Headless or behind a proxy (CI containers, cloud agent sessions): if the LVGL tarball download is blocked, clone the same tag and point CMake at it and run the full configure and build with the override:
git clone --depth 1 --branch v9.5.0 https://github.com/lvgl/lvgl.git /tmp/lvgl
cmake -S sim -B sim/build -G Ninja -DFETCHCONTENT_SOURCE_DIR_LVGL=/tmp/lvgl && ninja -C sim/build
Without a display, run the simulator and its tests with
SDL_VIDEODRIVER=offscreen.
Same code, same fonts, same pixels as the device — it builds the real
platform and VibePulse against the real LVGL, and feeds it the recorded
fixtures in sim-fixtures/ through the same parsers the board runs. The
flat UI captures in this README are unmodified simulator frames (the
banner places three of them side by side). The separately captioned 2.41 V2
and 1.91 Touch photographs show the owner's real panels; evidence is recorded
in the V2 physical report
and 1.91 physical report.
For the original 2.16, the
2026-08-13 physical review
covered the quota pages, agent monitor states and Max Tracker pages in that
build. It does not verify later screenshots or firmware: the v1.1.0 SETTINGS
frames are simulator evidence only (SETTINGS is on torget-home-01 since the
v1.0.0-67-ge51b79f flash of 2026-09-06, but its static on-panel review has
not been run), and the rest of this release's firmware changes landed after
that build and remain CI-built and not flashed.
Keys: [ / ] change VibePulse page, S cycles agent status, M cycles
Max Tracker fixtures, T re-feeds tokens, G simulates a new GitHub star,
N moves to the next app, L opens the launcher, and 1-4 pick a
Solelkollen fixture when that companion is checked out.
K is KEY3 itself, polled raw rather than on an edge, so the bench drives
the real time gesture: hold K for three seconds and SETTINGS opens, release
before three and an open window closes instead. U and W press the UPDATE
and WIFI rows in that menu, taking the same path a finger does. That makes
the simulator the spec for the gesture — there is no other way to exercise it
without the board.
VibePulse is an app on Torget, a deliberately small LVGL 9 app platform
for this panel. An app is one component exporting
torget_app_t { name, icon, create, enter, leave }; the platform owns WiFi,
the panel, brightness and the launcher.
This repo ships exactly one app, so that's all you get on the screen — one binary, one thing, nothing to wonder about. The platform can hold several apps at once (that's what the launcher is for), but any others live in their own repos and are only built in if you check them out.
Design rules: true black background, IBM Plex, dashes instead of invented
zeros, and provider accents locked to Claude #D97757 and Codex #6F78FF.
platform/ app contract + launcher + fonts (IBM Plex)
main/ ESP32 host layer: boot, WiFi, SNTP, app registry
components/app_* the app (VibePulse lives in app_tokens/)
tools/tokenserver/ the computer service (core is Python stdlib)
sim/ SDL simulator, the whole platform on your computer
test/ host tests, run with ./test/run.sh (no ESP-IDF needed)
spec/ hardware truth + UI design system
The deeper docs (architecture, writing an app, hardware traps) are in README.sv.md, in Swedish, because this started as a Swedish hobby project. Your agent reads Swedish just fine.
Hardware truth — capabilities, sources and which claims are verified on a
real unit — lives in the validated registries under spec/. Read
spec/hardware.md before any hardware-dependent work, and don't promote a
capability to "verified" without a physical check.
./test/run.sh is the host gate that enforces those registries, alongside
the C core tests and the Python suites. No ESP-IDF required, but it does
need a reproducible Python:
python3.12 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-dev.txt \
-r requirements-interaction-relay.txt
./test/run.sh
Python 3.11+ is required. The script uses the activated environment's Python
by default; set PYTHON_BIN to point at a different 3.11+ interpreter.
claude login writes the same
{"claudeAiOauth": {...}} record to
%USERPROFILE%\.claude\.credentials.json and the service reads it; the
Codex app-server read and the single-probe lock no longer depend on
macOS-only syscalls; state lives under %LOCALAPPDATA%\VibePulse\.
For autostart, run the shipped Task Scheduler installer from the repo root:
powershell -ExecutionPolicy Bypass -File tools\tokenserver\install-windows-task.ps1.
It runs as your signed-in user, starts immediately, restarts on failure, and
keeps interaction-provider choices in the tokenserver's saved config.
The Codex desktop app alone is not enough for the background quota read:
its Store-managed codex alias can resolve but still be denied by Windows.
Install OpenAI's standalone CLI once in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex".
VibePulse prefers its stable per-user executable under
%LOCALAPPDATA%\Programs\OpenAI\Codex\bin and ignores WindowsApps
aliases. Open a new PowerShell window, run codex --version, then rerun
python tools\vibepulse_setup.py doctor. The background task writes bounded
stdout/stderr to
%LOCALAPPDATA%\VibePulse\Logs\torget-tokenserver.log; the server's
rotation guard keeps one .old tail. Use -ValidateOnly to verify the
checkout and Python 3.11+ interpreter without changing Task Scheduler. The
complete install, hook-review, firewall, startup-health, physical-test, and
recovery procedure is the Windows host runbook.
The v1.0 core service, physical answer loop, sign-out/sign-in,
sleep/resume, and reboot all passed on a real Windows PC; see the
full sanitized evidence.
A later runtime revision still requires a fresh pass through the complete
Windows validation gate; release evidence is
never inherited across untested code.MIT © Niclas Vestlund
The "Claude" and "Codex" names and icons belong to Anthropic and OpenAI. They appear here only to identify which provider a number belongs to, they are not covered by the MIT license, and they will be removed on request. The IBM Plex fonts are used under the SIL Open Font License (platform/fonts/LICENSE-OFL.txt).
This is my first open source release. Issues and PRs are very welcome, and if VibePulse ends up on your shelf, a ⭐ helps others find it.
Built by Niclas Vestlund.
The physical 466 × 466 round panel now shows Codex weekly usage, USED TODAY and the D:H:M countdown in the owner's photographs. Serial logs confirm Wi-Fi join and token fetches. The screen uses a board-specific USB-installed source profile; physical evidence names the remaining touch, mounting and setup checks. The separate image below is native LVGL output with sample data, useful for exact layout review. Bring-up and recovery guide.
| Waveshare ESP32-S3-Touch-AMOLED-1.75 (affiliate), marking 1.75 only | 466×466 round AMOLED; PCB revision unknown | USB-installed round quota profile in current source. A real unit boots, joins Wi-Fi and displays Codex usage, USED TODAY and reset countdown in owner photographs. Bring-up guide. Needs You decisions remain on the computer and OTA is disabled in current source; use USB updates. Fixed USB-down orientation, touch and the latest portal scan still need final on-unit checks; automatic rotation is unverified. |
| Waveshare ESP32-S3-Touch-AMOLED-1.8 (affiliate), V2 only | 368×448 portrait CO5300 AMOLED, CST820 touch; BOOT opens settings | USB-only development profile. Owner reports display, touch, Wi-Fi/data path and settings working on one unit. Install guide · physical review. V1, OTA, Windows, audio, IMU, RTC, battery and microSD are not verified. Four-corner touch and transform-memory stress remain unverified. iPhone Wi-Fi onboarding still needs UX work. |
| Waveshare ESP32-S3-ePaper-3.97, SKU 33811 kit | 800×480 monochrome e-paper; three-way switch | USB development edition. Owner-confirmed Codex data, navigation and quieter partial refresh on one Mac-connected unit. Guide. Dedicated firmware/epaper_397 target; battery, audio, Wi-Fi/OTA and decisions remain unqualified. |
main