AI TaskHub

Multi-purpose View the repo

TaskHub for StickS3 is firmware that turns an M5StickS3 into a compact, Wi‑Fi‑connected status display for AI agent tasks running on a macOS host, showing task states, supporting button navigation, voice input, and low‑power operation.

auto-summary

28 stars · 3 forks

Maintainer
sheepxux
Chip families
esp32-s3

Runs on these boards

Unverified

  • M5StickS3UnverifiedM5Stack
    Flash M5StickS3guided

    Unverified esp-atlas asserts this board↔firmware link at the shown trust tier — it does not guarantee your specific unit or the current firmware version. Flashing can erase keys/config and can brick the device. At your own risk.

README

TaskHub for StickS3

A pocket hardware dashboard for AI agent work across your Macs.

简体中文 | Installation

TaskHub for StickS3 turns an M5StickS3 into a tiny always-nearby status screen for AI coding agents, desktop AI apps, and browser-based agent tools. A local Mac Host reads task metadata from sources such as Codex, Claude Code, Cursor, Cline / Roo Code / Kilo Code, Gemini CLI, Qwen Code, GitHub Copilot CLI, OpenClaw, Manus, Perplexity, Gemini, Lovable, Kimi, WorkBuddy, and Grok, discovers other authorized TaskHub Hosts on the same LAN, then sends a compact task list to the StickS3 over Wi-Fi.

The device shows which task is running, which one needs your input, what recently finished, and token or turn usage when the source exposes it. BtnB can open the source app on the Mac, BtnA switches tasks, and the firmware sleeps between refreshes so the small StickS3 battery remains usable.

Why It Exists

AI agents are easy to start and easy to forget. TaskHub gives them a physical status surface:

  • See active AI work without switching windows.
  • Catch Codex or Claude Code prompts when they need a reply.
  • Track tasks across more than one Mac on the same Wi-Fi.
  • Keep task state local instead of sending agent metadata to a cloud service.
  • Use a small, inexpensive device instead of dedicating another monitor.

Screens

Pixel-accurate renders of what the StickS3 draws at its native 240x135 resolution. Regenerate them with python3 docs/render_screens.py.


BOOT - Wi-Fi sync splash

WAKE - reconnect after sleep

RUN - active agent turn

WAIT - needs your input

FAIL - error or attention

DONE - finished task

No tasks - idle hub

Hub unreachable - Wi-Fi lost

Current Release

v2.2.0 is the current public build for developers and hardware makers. The core pipeline is working: Mac Host, StickS3 firmware, Wi-Fi discovery, compact task display, button actions, deep sleep, voice input, and LAN multi-device aggregation.

Some AI apps expose rich local task logs; others expose only app activity or visible browser UI. TaskHub is explicit about that distinction so users know when a row is exact task tracking versus best-effort local signal detection.

Feature Matrix

| Area | Status | Notes | | --- | --- | --- | | StickS3 firmware | Ready | Native 240x135 UI, buttons, Wi-Fi discovery, deep sleep | | M5Burner public firmware | Ready | No secrets in the binary; first boot opens a Wi-Fi captive portal, then pairs with the Host by a 4-digit code | | macOS Host | Ready | LaunchAgent installer, local HTTP API, UDP discovery | | macOS Host package | Preview | Builds a secret-free .pkg; public distribution still requires Developer ID signing/notarization | | Host diagnostics | Ready | /diagnostics checks adapters, voice mode, local permissions, caches, and peers without exposing tokens or task titles | | Multi-Mac aggregation | Ready | Authorized Hosts discover peers and merge task lists | | BtnB open source | Ready | Opens local source app; remote tasks forward to the origin Mac | | WAIT attention mode | Ready | Wakes and holds the display briefly when input is needed, then sleeps and keeps checking | | WAIT alert | Ready | Per-task edge: screen wake + short double beep each time a task enters WAIT, even while another is already waiting (ALERT_* tunable) | | DONE alert | Ready | Per-task edge: softer rising chime when a running task finishes | | FAIL alert | Ready | Per-task edge: falling two-note buzz when a task errors out | | Battery-aware operation | Ready | Sleeps by default, short timer-wake screen time, low brightness, charging bolt on the battery icon | | Auto-rotation | Ready | IMU gravity rotates the screen to match how it's held; portrait shows a multi-task list (ROTATE_* tunable) | | Voice input | Ready | Hold BtnB to dictate (Mandarin/English) → local whisper.cpp → text pasted and sent in the selected task's app (POST /voice?enter=1) | | Codex adapter | Detailed | Tracks title, folder, turns, token usage, running/wait state | | Claude Code adapter | Detailed | Tracks transcript turn state, prompts, plan approvals, usage, resume process | | Cursor adapter | Detailed | Composer title/folder, RUN via live counters, pending-approval WAIT, context token usage | | OpenClaw adapter | Detailed | Reads local task/session stores | | Manus adapter | Best effort | Reads local app storage and usage counters when available | | Perplexity adapter | Activity | Local app/browser activity; exact Perplexity Computer tasks are not guaranteed | | Gemini adapter | Activity | App/web activity and visible browser title when exposed | | Lovable adapter | Activity | App/browser activity, renderer CPU, visible generation controls | | Kimi adapter | Activity | Local conversation status plus daemon active-turn state and web title; open app alone is not RUN | | WorkBuddy adapter | Detailed | Local session title, tool calls, RUN/WAIT/DONE, folder, token data when present | | Cline / Roo Code / Kilo Code adapter | Detailed | Extension task transcripts inside VS Code, Cursor, Windsurf, VSCodium, Antigravity, Trae; unanswered approval/question is an exact WAIT, request in flight is RUN, completion is DONE, token/cost usage | | Gemini CLI / Qwen Code adapter | Detailed | Chat recordings under ~/.gemini / ~/.qwen: RUN while the model answers or tools execute, WAIT on a tool awaiting approval, DONE on a finished reply, folder from projects.json (fixture-verified; real-world reports welcome) | | GitHub Copilot CLI adapter | Best effort | ~/.copilot/session-state/*/events.jsonl turn events: RUN/DONE/FAIL, WAIT on a pending permission event, workspace summary as title (fixture-verified; real-world reports welcome) | | Grok adapter | Activity | Web/Safari app title and visible Stop control; open app alone is not RUN | | Browser web bridge | Optional | extension/ (Chrome/Edge) reads Gemini/Grok/Kimi/Lovable/Perplexity tab titles and pushes them via POST /ingest | | External push API | Ready | POST /ingest accepts tasks from any local script; entries expire on a TTL |

Browser Web Bridge (optional)

Browser-based AI tools keep task data on their servers, so the Host can only see them as "active". The optional Chrome/Edge extension in extension/ reads the open Gemini / Grok / Kimi / Lovable / Perplexity tab's title and pushes it to the Host via POST /ingest, so those tasks show real titles on the StickS3. It is local-only (talks to 127.0.0.1) and best-effort (page selectors fall back to the tab title). See extension/README.md to load it.

Any script can push too:

curl -X POST -H 'X-Device-Token: <token>' http://127.0.0.1:5577/ingest \
  -d '{"source":"Manus","title":"Draft weekly report","status":"running","ttl_sec":120}'

Voice Mode

Hold BtnB on the StickS3 to dictate into the AI app you're working with — Mandarin or English, transcribed locally, pasted into the chat box, and sent by default.

  1. Short-press BtnB to open a task's app (brings e.g. Claude to the front).
  2. Hold BtnB and speak; release to transcribe and send.
  3. The clip is POSTed to POST /voice, transcribed by a resident whisper.cpp server (large-v3-turbo-q5_0, Simplified Chinese + English), and pasted into the task's app. ?task=<id> targets that app deterministically, and ?enter=1 submits the text with Return.

Everything stays local — the audio never leaves your machine/LAN.

One-time setup:

brew install whisper-cpp
mkdir -p host/models && curl -L -o host/models/ggml-large-v3-turbo-q5_0.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin
./host/install_whisper_server.sh   # resident whisper-server LaunchAgent on :8080

Grant the Host Accessibility permission (System Settings → Privacy & Security → Accessibility) so it can paste into other apps. Tunables: TASK_HUB_WHISPER_MODEL, TASK_HUB_WHISPER_LANGUAGE (auto/zh/en). Device-side auto-send is on by default; set VOICE_AUTO_SEND 0, TASKHUB_VOICE_SEND=0, or provision with --voice-send off if you want paste-only review before sending. Targeting works for Claude, Codex, Cursor, Manus, and Perplexity desktop apps.

Status Model

TaskHub keeps the full task list on the Mac. The StickS3 applies a display-only filter so old rows disappear from the tiny screen without deleting anything from the computer.

| Label | Color intent | Meaning | StickS3 visibility | | --- | --- | --- | --- | | RUN | Blue | Active task or active agent turn | Always visible | | WAIT | Yellow | Waiting for user input or queued attention | Always visible while awake; fires a one-shot wake + double-beep alert, then sleeps again after the attention hold | | FAIL | Red | Failed or needs attention | Always visible | | DONE | Green | Completed | Hidden after 10 minutes by default; a running-to-finished edge plays a softer chime | | REC | White/gray | Recently active | Hidden after 1 hour by default | | IDLE | Dark gray | Source is idle | Hidden after 10 minutes by default | | HIDDEN | Gray/black | Display-only stale row | Not shown on device |

stateDiagram-v2
  [*] --> RUN: active turn
  [*] --> WAIT: prompt needs input
  RUN --> REC: turn finished recently
  WAIT --> RUN: work resumes
  RUN --> FAIL: error or aborted
  REC --> DONE: terminal state reported
  DONE --> Hidden: older than 10 minutes
  IDLE --> Hidden: older than 10 minutes
  REC --> Hidden: older than 1 hour
  FAIL --> [*]: stays visible
  WAIT --> [*]: sleeps after attention hold
  RUN --> [*]: stays visible

Architecture

flowchart LR
  subgraph "LAN Macs"
    Host["TaskHub Host\nAggregator + HTTP API"]
    Peer["Peer TaskHub Host\nscope=local"]
    Codex["Codex\nsessions + usage"]
    Claude["Claude Code\ntranscripts + resume process"]
    Cursor["Cursor\ncomposer index + transcripts"]
    OpenClaw["OpenClaw\nlocal tasks + sessions"]
    Manus["Manus\nlocal app storage"]
    Perplexity["Perplexity\nlocal app/browser activity"]
    Gemini["Gemini\nlocal app/browser activity"]
    Lovable["Lovable\napp + browser tabs"]
    Kimi["Kimi\ndesktop + browser UI"]
    WorkBuddy["WorkBuddy\nlocal session events"]
    Grok["Grok\nSafari app + browser UI"]
  end

  subgraph "M5StickS3"
    Firmware["Task monitor firmware"]
    Screen["Compact status screen"]
    Buttons["BtnA / BtnB"]
    Sleep["Deep sleep timer"]
  end

  Peer <-->|"UDP discovery + token"| Host
  Codex --> Host
  Claude --> Host
  Cursor --> Host
  OpenClaw --> Host
  Manus --> Host
  Perplexity --> Host
  Gemini --> Host
  Lovable --> Host
  Kimi --> Host
  WorkBuddy --> Host
  Grok --> Host
  Host -- "UDP discovery response" --> Firmware
  Firmware -- "GET /tasks?format=stick" --> Host
  Firmware -- "POST /tasks/:id/open" --> Host
  Firmware --> Screen
  Buttons --> Firmware
  Firmware --> Sleep

Quick Start

Two things, no terminal required for the Stick: firmware from M5Burner (M5Stack's official burning tool) and the Mac Host from GitHub.

1. Burn the firmware

Open M5Burner, search TaskHub for StickS3, plug the StickS3 in over USB-C and click Burn.

2. Put the Stick on your Wi-Fi

Power it on. The screen shows a QR code and a Wi-Fi name like TaskHub-3F2A. Join that network from your phone (scan the QR or pick it in Wi-Fi settings), choose your home Wi-Fi in the page that pops up, and enter its password. The Stick joins and shows a 4-digit pairing code.

3. Install the Mac Host and pair

Paste this in Terminal (Spotlight → "Terminal"):

curl -fsSL https://raw.githubusercontent.com/sheepxux/Taskhub-for-StickS3/main/scripts/install.sh | bash

It installs the Host as a login item, then asks for the code on the Stick. Type it, and the Stick restarts into your task list. From now on it finds the Host on its own (UDP discovery on port 5578), so your Mac's IP can change.

Pair another Stick later with python3 ~/Library/Application\ Support/StickS3TaskHub/taskhub_pair.py, or open TaskHub Host.app if you installed the .pkg. Holding both buttons while powering on wipes the Stick's Wi-Fi and token; holding A on the pairing screen redoes only the Wi-Fi.

Other install paths

  • macOS .pkg: a double-click installer that ends in the pairing dialog. Until the package is signed with a Developer ID, macOS blocks it on first open (System Settings → Privacy & Security → Open Anyway).
  • Developers: build and flash from source with arduino-cli, bake Wi-Fi and token into secrets.h, or provision over USB serial. See INSTALL.md.

Controls

| Control | Action | | --- | --- | | BtnB | Open the selected task's source app on the Mac | | BtnB hold | Voice input — hold to talk, release to dictate and send in the task's app | | BtnA | Select the next task | | BtnA hold | Refresh immediately |

Multi-Device Mode

Install the Mac Host on every Mac you want to include and use the same token on each Host. Any TaskHub Host can act as the aggregator:

  • Hosts announce themselves over UDP port 5578.
  • The aggregator fetches each peer's /tasks?scope=local list.
  • Compact StickS3 rows include a short device label, such as Codex@MBP.
  • BtnB on a remote task forwards the open request back to the Mac that owns it.

Useful environment variables:

| Variable | Default | Purpose | | --- | --- | --- | | TASK_HUB_DEVICE_NAME | macOS hostname | Human-readable device name | | TASK_HUB_DEVICE_ID | stable host hash | Stable LAN peer identity | | TASK_HUB_ENABLE_PEERS | 1 | Set to 0 to disable peer aggregation | | TASK_HUB_PEER_DISCOVERY_MS | 15000 | UDP peer discovery interval | | TASK_HUB_PEER_CACHE_MS | 5000 | Remote task cache duration | | TASK_HUB_ADAPTER_MAX_WORKERS | 8 | Concurrent local adapter scans |

Diagnostics:

open http://127.0.0.1:5577/diagnostics
curl http://127.0.0.1:5577/diagnostics.json
open http://127.0.0.1:5577/peers
curl http://127.0.0.1:5577/peers.json?refresh=1

Local API

| Endpoint | Purpose | | --- | --- | | /health | Host status, version, LAN identity | | /tasks | Full task list for the web/debug page | | /tasks?format=stick | Compact payload used by the StickS3 | | /tasks?scope=local | Local Mac tasks only, used by peer aggregation | | /tasks/:id | Local detail/debug page | | /tasks/:id/open | Open selected source from the StickS3 | | /tasks/:id/open-native | Host-to-host remote open forwarding | | /voice | Transcribe a posted audio clip and paste it into a target app (voice mode) | | POST /pair | Unauthenticated pairing poll from an unprovisioned StickS3; answers pending until the code is approved | | /pair/pending, POST /pair/approve | Loopback-only: list devices asking to pair, approve a code (used by taskhub_pair.py) | | /diagnostics | Human-readable Host health, adapter, voice, cache, and peer diagnostics | | /diagnostics.json | Machine-readable diagnostics without token values or task titles | | /peers | Human-readable multi-device diagnostics | | /peers.json | Machine-readable peer status | | /debug/lovable | Lovable app/browser signal diagnostics |

Power Profile

The firmware is battery-first by default. It wakes, fetches once, shows the last task snapshot immediately when possible, stays visible briefly, then sleeps again. Active and WAIT tasks still refresh more often, but Eco Mode avoids keeping the tiny display lit forever.

| Setting | Default | | --- | --- | | Eco mode | ECO_MODE=1 | | Normal timer wake | AUTO_WAKE_SECONDS=600 | | Active/attention timer wake | ACTIVE_WAKE_SECONDS=120 | | Low-battery timer wake | LOW_BATTERY_WAKE_SECONDS=1200 | | Timer-wake screen time | QUIET_TIMER_TIMEOUT_MS=1500 | | Button-wake screen time | INTERACTIVE_TIMEOUT_MS=6000 | | WAIT attention hold | WAIT_ATTENTION_TIMEOUT_MS=180000 | | Low-battery WAIT hold | LOW_BATTERY_WAIT_ATTENTION_TIMEOUT_MS=45000 | | Normal brightness | DISPLAY_BRIGHTNESS=22 | | Low-battery brightness | LOW_BATTERY_BRIGHTNESS=8 | | Awake active refresh | AWAKE_REFRESH_ACTIVE_MS=15000 | | Awake WAIT refresh | AWAKE_REFRESH_WAIT_MS=15000 | | CPU clock | POWER_SAVE_CPU_MHZ=80 | | Charge current | CHARGE_CURRENT_MA=200 |

A WAIT almost always follows a running task, so the device is usually deep-sleeping with an active task when one appears. ACTIVE_WAKE_SECONDS=120 caps how long a new WAIT can go unnoticed to about two minutes while cutting radio wakeups in half versus the old 60-second default. Set WAIT_ATTENTION_TIMEOUT_MS=0 if you want the old always-on WAIT behavior.

The on-device WAIT/DONE/FAIL alerts are tunable in firmware/task_monitor/secrets.h. Transitions are detected per task (by id) against the previous refresh, so a second session entering WAIT still rings; when several edges land in one refresh only the most urgent plays (WAIT > FAIL > DONE) and the screen jumps to that task.

| Setting | Default | Purpose | | --- | --- | --- | | ALERT_ON_WAIT | 1 | Master switch for the WAIT alert | | ALERT_ON_DONE | 1 | Master switch for the DONE chime | | ALERT_ON_FAIL | 1 | Master switch for the FAIL buzz | | ALERT_BEEP | 1 | Speaker beep/chime; set 0 for silent screen-only alerts | | ALERT_WAIT_HZ / ALERT_DONE_HZ / ALERT_FAIL_HZ | 2400 / 1500 / 1800 | WAIT double-beep pitch, DONE base pitch, FAIL starting pitch | | ALERT_BEEP_VOLUME | 150 | Shared speaker loudness |

Vibration: the M5StickS3 is not driven as a motor by the pinned M5Unified, so ALERT_VIBRATION is a no-op on this board and stays off — the alert uses the screen and speaker instead.

For UI or network debugging, set ENABLE_DEEP_SLEEP to 0 in firmware/task_monitor/secrets.h. Re-enable it before normal use.

Source Accuracy

TaskHub uses local data only. Accuracy depends on what each source exposes on disk, through local process state, or through visible browser UI.

| Source | What is usually available | | --- | --- | | Codex | Task title, folder, turn state, token usage, running/wait status | | Claude Code | Transcript state, prompt/wait detection, plan approvals, usage, resume process | | Cursor | Composer title, workspace folder, turn state, pending approvals, context token usage | | OpenClaw | Local task registry, session title, task state | | Manus | Local session metadata, timestamps, status codes, usage counters | | Perplexity | App/browser activity; exact Perplexity Computer task names may be unavailable | | Gemini | App/browser activity; visible tab title when exposed by the browser | | Lovable | App/browser activity, project tabs, renderer CPU, visible generation controls | | Kimi | Local running/completed state cross-checked with daemon active turns, plus browser title | | WorkBuddy | Local session title, folder, tool calls, completion state, token data when exposed | | Cline / Roo Code / Kilo Code | Task prompt, host editor, workspace folder, pending approval/question (WAIT), request in flight (RUN), completion, tokens and cost | | Gemini CLI / Qwen Code | First prompt, project folder, model/tool turn state, tool awaiting approval (WAIT), token usage | | GitHub Copilot CLI | Workspace summary, folder, turn start/end and tool events, pending permission (WAIT) | | Grok | Safari Web App/browser title and visible Stop control; exact title is best with the web bridge |

If an app is open but not actively generating or executing, TaskHub should show REC or IDLE, not RUN.

Privacy And Security

TaskHub is local-first.

  • The StickS3 talks to your Mac Host on your LAN.
  • The Host does not upload task data to a cloud service.
  • Firmware Wi-Fi secrets live in secrets.h, which is gitignored.
  • The StickS3 API does not return auth tokens or message bodies.
  • LAN peers must use the same token to participate.
  • Host-to-device traffic uses plain HTTP on your LAN; run TaskHub only on a trusted local network.
  • Do not expose port 5577 or 5578 directly to the public internet.

Troubleshooting

| Problem | Check | | --- | --- | | StickS3 cannot find the Host | Confirm Mac and StickS3 are on the same Wi-Fi, then check /health. A VPN/proxy (Clash, Surge, Tailscale) used to make the Host advertise its tunnel IP; the Host now picks the LAN interface facing the Stick | | Pairing screen says "looking for Mac Host" forever | Same Wi-Fi as the Mac? Guest networks and "AP isolation" block the UDP discovery on port 5578. Is the Host running (/health)? | | 401 from the Host | Confirm DEVICE_TOKEN matches the Host token file, or re-pair: hold both buttons while powering on, then run taskhub_pair.py | | Task status looks stale or wrong | Open /diagnostics and check the source adapter row | | Cursor (or another source) shows 0 tasks with a "Full Disk Access" adapter error | macOS TCC blocks the launchd Host from other apps' data; grant Full Disk Access to the Python that runs the Host (see INSTALL.md) | | No peer Macs show up | Open /peers.json?refresh=1, check token match and UDP port 5578 | | An app only shows REC | The app may expose activity but no active task signal | | Lovable running state looks wrong | Open /debug/lovable and inspect renderer CPU/browser basis | | Battery drains too quickly | Lower brightness, shorten timeouts, keep deep sleep enabled | | Browser task title is missing | Give the browser accessibility permission or keep the tab visible |

Development

Useful checks before publishing a build:

python3 -m unittest discover -s host/tests   # host adapter regression suite
python3 -m py_compile host/task_hub.py docs/render_screens.py
python3 docs/render_screens.py
./firmware/flash_task_monitor.sh compile

Testing & CI

The host logic ships with a dependency-free unittest suite in host/tests/ covering status derivation, WAIT detection, case-insensitive process matching, token accounting, LRU scan memoisation, and /ingest validation/expiry. GitHub Actions runs the suite, byte-compiles the Host, and compiles the firmware against the ESP32 core on every push and pull request.

Repository layout:

firmware/task_monitor/   StickS3 firmware
firmware/flash_task_monitor.sh
host/task_hub.py         Local macOS Host
host/taskhub_config.py   Host runtime configuration
host/install_task_hub.sh LaunchAgent installer/repair script
host/README.md           Host diagnostics and adapter notes
scripts/setup.sh         First-run setup helper
INSTALL.md               Full installation and troubleshooting guide
docs/                    Device screen renders
CHANGELOG.md             Release notes

Roadmap

  • Signed and notarized distribution of the current Mac Host package.
  • Recorded local-metadata fixtures to broaden the adapter regression suite.
  • More detailed browser-task extraction and recorded fixtures as supported sites evolve.
  • Optional external buzzer / vibration motor for alerts.
  • Better first-run setup flow for non-developer users.

Release

Current release: v2.2.0.

See CHANGELOG.md for release notes.

Read the full README on GitHub

source github.com/sheepxux/Taskhub-for-StickS3