- JavaScript 63.2%
- CSS 25.2%
- Dockerfile 5%
- HTML 3.6%
- Just 3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .woodpecker | ||
| .gitignore | ||
| app.js | ||
| avatar.js | ||
| config.example.js | ||
| Containerfile | ||
| index.html | ||
| justfile | ||
| mock.js | ||
| mqtt.js | ||
| README.md | ||
| style.css | ||
claudia-tama
A live dashboard for claudia sessions: one avatar per active session, rendered from MQTT. Inspired by xteink-tamagotchi — but instead of one mascot for one assistant, it shows your whole fleet, one sprite per running session, each reacting as Claude works or waits.
It is three static files and no build step. The publishing side is claudia itself; this is the receiver.
How it fits together
Each claudia session reports its own status directly — no daemon:
claudia session (its supervisor process) this page
publishes while it lives, and registers ──▶ ws subscribe
a Last-Will tombstone for when it dies one avatar / session
Each session is a retained topic claudia/<host>/session/<pid> carrying:
{ "pid": 40321, "profile": "default", "title": "wire up the bridge",
"cwd": "/home/you/dev/app", "state": "working",
"host": "laptop", "started": "…", "ts": 1757308800 }
state is working (Claude is processing), idle (waiting for you), or
error (its most recent command failed — cleared by the next one). A
session that ends publishes an empty retained message (a tombstone) which
despawns its avatar — so opening the page at any time shows exactly the live
fleet. The tombstone is sent on a clean exit and, on a crash or kill, by the
broker's Last-Will, so avatars don't get stuck. The dashboard turns a
tombstone into a brief death animation on its way out.
Older setups used a per-machine
claudia mqtt bridgepoller. That still exists as a fallback for hosts without process isolation, but you don't run it when sessions self-report — seeclaudia mqtt.
Setup
1. Broker: add a WebSockets listener
Sessions publish over plain TCP (1883). Browsers can only speak MQTT over
WebSockets, so the broker needs a second listener. In mosquitto.conf:
listener 1883
listener 9001
protocol websockets
Reload mosquitto. (1883 is for the sessions; 9001 is for this page.)
2. Publisher: enable MQTT on each machine
On every machine running claudia, outside the sandbox:
claudia config set mqtt.enabled true
claudia config set mqtt.broker tcp://<broker-ip>:1883
That's it — every session from then on self-reports. No process to leave
running. Watch what a session emits with claudia mqtt tap; wipe accumulated
topics with claudia mqtt clear.
3. Dashboard: point it at the broker
Copy the template and edit it (config.js is gitignored, so your broker
address stays out of version control):
cp config.example.js config.js
window.CLAUDIA_TAMA = {
brokerUrl: "ws://<broker-ip>:9001",
topicPrefix: "claudia",
// deathFadeMs: 900, // how long a dying session lingers on screen
};
Then serve the folder (any static server — the page uses ES modules, so
file:// won't work):
python -m http.server 8080
# open http://localhost:8080
Develop without a broker
Open the page with ?mock (or leave brokerUrl empty) to render fake
sessions that flip state, spawn, and despawn:
http://localhost:8080/?mock
Files
| file | what it is |
|---|---|
index.html |
page shell |
app.js |
subscribes, reconciles tiles, renders the fleet |
mqtt.js |
minimal MQTT 3.1.1-over-WebSockets client (no deps) |
avatar.js |
inline-SVG mood sprites (swap for pixel-art here) |
style.css |
layout + sprite animations, light/dark |
config.js |
broker URL + topic prefix (the only file you edit) |
mock.js |
dev-only fake data for ?mock |
Deploy
The dashboard ships as a tiny nginx image (Containerfile, ~12MB) and runs on
the Incus cluster via ~/dev/meatbag/infra (the web module, service
claudia-tama), fronted by Caddy at https://tama.meatbag.se behind SSO.
Caddy serves the page over HTTPS and reverse-proxies /mqtt to the broker's
WebSocket listener, so the browser connects same-origin
(wss://tama.meatbag.se/mqtt) with no mixed-content block. That URL is baked
into config.js at image-build time (--build-arg BROKER_URL=… to change it).
Build and push the image from this repo:
just container-release # podman build + push to the meatbag registry
or let CI do it (.woodpecker/image.yml pushes on every push). Then, in infra:
just update-image claudia_tama # taint + pull the new image
just apply