Arc Reactor style visual status display for OpenClaw AI agents.
Inspired by Iron Man's J.A.R.V.I.S. interface β see your AI assistant think, respond, and idle in real-time.
- π€ OpenClaw Integration β Real-time visualization of your AI agent's state
- π΅ Smooth animated rings with overlapping layers
- π¨ 5 status states:
idle,active,thinking,listening,error - β‘ WebSocket sync with OpenClaw Gateway
- π REST API for custom integrations
- π₯οΈ Kiosk mode for Raspberry Pi displays
- π Color highlights and glossy effects
- π± Multi-agent support β filter by agent ID
# Clone
git clone https://github.com/f2daz/jarvis-reactor.git
cd jarvis-reactor
# Install
npm install
# Run
npm start
# Open http://localhost:3030| State | Color | Description |
|---|---|---|
idle |
Cyan | Default, slow rotation |
active |
Bright Cyan | Fast rotation, responding |
thinking |
Purple | Processing, inner rings fast |
listening |
Green | Waiting for input |
error |
Red | Alert state |
# Get current state
curl http://localhost:3030/status
# Set state
curl -X POST http://localhost:3030/status \
-H "Content-Type: application/json" \
-d '{"state": "thinking"}'Connect to ws://localhost:3030:
const ws = new WebSocket('ws://localhost:3030');
ws.onmessage = (e) => {
const { state } = JSON.parse(e.data);
console.log('State:', state);
};
ws.send(JSON.stringify({ state: 'active' }));reactor.set('thinking');
reactor.set('active', 5000); // Auto-reset to idle after 5sHide all controls for clean display:
http://localhost:3030?kiosk
Or toggle controls with:
- Press
C- toggle controls - Double-click - toggle controls
Connect directly to your OpenClaw Gateway:
http://localhost:3030?gw=ws://YOUR_GATEWAY:PORT&token=YOUR_TOKEN&kiosk
| Parameter | Description |
|---|---|
gw |
Gateway WebSocket URL (required) |
token |
Authentication token (required) |
agent |
Filter by agent ID (any string matching your agent config) |
kiosk |
Hide controls |
If you run multiple OpenClaw agents, use the agent parameter to filter which one animates the reactor:
# Only react to a specific agent (use your agent ID)
http://localhost:3030?gw=ws://...&token=...&agent=main
http://localhost:3030?gw=ws://...&token=...&agent=work
http://localhost:3030?gw=ws://...&token=...&agent=dev
# React to all agents (default)
http://localhost:3030?gw=ws://...&token=...
The agent value must match the agent ID configured in your OpenClaw setup.
The reactor automatically shows:
thinkingwhen message receivedactivewhen respondingidlewhen done
- Raspberry Pi 3/4/5 (or Zero 2 W)
- Raspberry Pi OS (Lite or Desktop)
- Display (HDMI or DSI)
- Network connection
- Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit)
- Enable SSH and configure WiFi in Imager settings
- Boot and SSH into your Pi
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js 20.x
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version # Should show v20.x.x# Clone repository
cd ~
git clone https://github.com/f2daz/jarvis-reactor.git
cd jarvis-reactor
# Install dependencies
npm install# Install minimal X server and Chromium
sudo apt install -y --no-install-recommends \
xserver-xorg x11-xserver-utils xinit \
chromium-browser unclutter
# Create kiosk startup script
cat > ~/kiosk.sh << 'EOF'
#!/bin/bash
# Disable screen blanking
xset s off
xset s noblank
xset -dpms
# Hide cursor after 0.5s of inactivity
unclutter -idle 0.5 -root &
# Start Chromium in kiosk mode
chromium-browser \
--kiosk \
--noerrdialogs \
--disable-infobars \
--disable-session-crashed-bubble \
--disable-restore-session-state \
--no-first-run \
--start-fullscreen \
--autoplay-policy=no-user-gesture-required \
"http://localhost:3030?kiosk&gw=ws://YOUR_GATEWAY:PORT&token=YOUR_TOKEN"
EOF
chmod +x ~/kiosk.shCreate systemd service for the Node.js server:
sudo tee /etc/systemd/system/jarvis-reactor.service << EOF
[Unit]
Description=Jarvis Reactor Server
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=/home/$USER/jarvis-reactor
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable jarvis-reactor
sudo systemctl start jarvis-reactorCreate systemd service for kiosk browser:
sudo tee /etc/systemd/system/jarvis-kiosk.service << EOF
[Unit]
Description=Jarvis Reactor Kiosk
After=jarvis-reactor.service
Wants=jarvis-reactor.service
[Service]
Type=simple
User=$USER
Environment=DISPLAY=:0
ExecStartPre=/bin/sleep 5
ExecStart=/usr/bin/startx /home/$USER/kiosk.sh -- -nocursor
Restart=always
RestartSec=10
[Install]
WantedBy=graphical.target
EOF
sudo systemctl enable jarvis-kiosksudo raspi-config
# β System Options β Boot / Auto Login β Console Autologinsudo rebootThe Raspberry Pi should now boot directly into the Jarvis Reactor display!
Black screen?
# Check if server is running
sudo systemctl status jarvis-reactor
# Check logs
journalctl -u jarvis-reactor -fBrowser not starting?
# Check kiosk service
sudo systemctl status jarvis-kiosk
journalctl -u jarvis-kiosk -fTest manually:
# Start server
cd ~/jarvis-reactor && node server.js &
# Start X and browser
startx ~/kiosk.shFor rotated displays, edit /boot/config.txt:
# 90Β° clockwise
display_rotate=1
# 180Β°
display_rotate=2
# 270Β° clockwise
display_rotate=3- Use Raspberry Pi 4 or 5 for smooth 60fps
- Disable compositor:
sudo raspi-configβ Advanced β Compositor β No - Use wired ethernet for reliable WebSocket connection
- Reduce GPU memory if not using video:
gpu_mem=64in/boot/config.txt
Control the reactor via MQTT:
# Environment variables
export MQTT_URL=mqtt://192.168.90.60:1883
export MQTT_USER=mqtt
export MQTT_PASSWORD=your_password
export MQTT_TOPIC=jarvis/reactor/state # optional, this is default
# Start with MQTT
node server.jsSubscribe to jarvis/reactor/state to receive state changes.
Publish to jarvis/reactor/state to change state:
mosquitto_pub -h mqtt.local -t jarvis/reactor/state -m "thinking"Valid states: idle, active, thinking, listening, error
rest_command:
jarvis_state:
url: "http://raspberry-pi:3030/status"
method: POST
content_type: "application/json"
payload: '{"state": "{{ state }}"}'
automation:
- alias: "Jarvis Thinking"
trigger:
platform: state
entity_id: binary_sensor.voice_assistant
to: "on"
action:
service: rest_command.jarvis_state
data:
state: listeningEdit reactor.js CONFIG section:
states: {
idle: {
color: { primary: '#00d4ff', secondary: '#0099bb', core: '#00aacc' },
// ...
},
// ...
}Adjust speeds (degrees per second) in CONFIG:
speeds: [
[3, 5], // Ring 1: [main, overlay]
[-6, -9], // Ring 2 (negative = counter-clockwise)
[10, 15], // Ring 3
[-5, -8], // Ring 4
],Add to CONFIG.states in reactor.js:
myState: {
color: { primary: '#ff8800', secondary: '#cc6600', core: '#ffaa00' },
speeds: [[5, 8], [-8, -12], [15, 22], [-6, -10]],
coreSpeed: 0.5,
coreBrightness: 1.2,
},| File | Description |
|---|---|
server.js |
Node.js bridge server (REST + WebSocket) |
reactor.js |
Animation engine (JS, no dependencies) |
openclaw.js |
OpenClaw Gateway integration |
index.html |
Main UI |
favicon.svg |
Browser icon |
MIT
Inspired by Iron Man's J.A.R.V.I.S. interface and the clawd-face project.
