Skip to content

Repository files navigation

Ziel van Sebastian

A CRT soul for a Mac mini appliance. The Wokyis M5 dock looks like a 1984 Macintosh; this app completes it: the happy-Mac face idles on a simulated phosphor tube, wakes amber when OpenClaw thinks, and speaks replies one big glowing word at a time — literally, if you give it a voice (see Speech). It mirrors every conversation OpenClaw has — direct chats stream live, and channel sessions (WhatsApp, iMessage) speak each reply as it lands.

Demo — full lifecycle under the CRT pipeline (hello theme)

Media above uses a heavier-than-default shader config so the CRT effects survive image scaling — defaults in config.example.json are subtler. Every knob is live-tunable.

What it looks like

Idle Thinking
Idle — dim off-white happy-Mac face (hello theme) Thinking — brighter off-white face with activity hint (hello theme)
Speaking Demo under the CRT pipeline
Speaking — bright off-white, one big RSVP word (hello theme) Demo — CRT scanlines, bloom, afterglow (hello theme)

Themes

The look is a named theme, selected at launch. Two built-in themes ship:

Theme Description
hello (default) Original-Macintosh "hello." — off-white on dark warm gray, hard offset drop shadow, monochrome CRT (no RGB triads). States read through brightness: dim idle, mid thinking, bright speaking.
classic The original Ziel look — green idle / amber thinking / white speaking phosphor on black, RGB aperture grille.

Classic theme — green happy-Mac face

Select in config.json:

"look": { "theme": "classic" }

Or override at launch (takes precedence over config):

"./build/…/Ziel van Sebastian" --theme classic

Any other look key in config acts as an override on top of the active theme — for example "idleTint": "#ff00ff" tints the idle state, or "shader": { "bloomStrength": 0.8 } cranks up the bloom. Unknown theme names fail at startup and print the list of valid ones. Switching themes requires a relaunch; the config watcher hot-reloads shader parameters only.

Build

brew install xcodegen
make build          # builds the app + mock-gateway
make test           # unit + integration tests
make run            # windowed demo loop, no gateway needed

Configure

mkdir -p ~/Library/Application\ Support/Ziel\ van\ Sebastian
cp config.example.json ~/Library/Application\ Support/Ziel\ van\ Sebastian/config.json
# put your OpenClaw gateway token in it

Config is watched: shader knobs and pacing reload live while the app runs (edit the file in place; delete+recreate is not detected until restart).

Speech (optional TTS)

With an ElevenLabs API key the appliance reads replies through the speaker while the display stays in sync — each word appears exactly when it is heard (the API's character timestamps drive the RSVP pacing). Add to config.json:

"speech": {
  "enabled": true,
  "apiKey": "...",
  "voiceId": "JBFqnCBsd6RMkjVDRZzb",
  "volume": 1.0
}
Key Default Notes
enabled false hot-reloads — flip to mute without restarting
apiKey / voiceId required; read at launch (restart to change). Browse the voice library; prefer voices tagged multilingual
modelId eleven_flash_v2_5 32 languages, auto-detected per sentence
languageCode unset optional ISO 639-1 pin when replies are mostly one language
speed 1.0 voice speed multiplier
volume 1.0 hot-reloads

Speech never blocks the face: any failure (missing key, network, rate limit) falls back to the normal reading-pace display for that sentence, and three consecutive failures mute speech until the next reconnect.

Voice input (Phase 2a)

An optional voice-gateway process — separate from the app, off by default — listens for "Sebastian, …" through the mic, transcribes it locally with whisper.cpp (no cloud), and emits wake/heard events over a loopback WebSocket. It runs independently: nothing in the app consumes those events yet (that wiring is Phase 3), so building or running it has no effect on the face either way.

See docs/voice-gateway.md for building it, fetching models, the microphone-permission first-run step, and installing it as a launchd agent on the appliance.

Device pairing (one-time)

The app authenticates with a token plus an Ed25519 device identity (generated on first run, stored next to the config). The first connect lands in the gateway's pairing queue with empty scopes; approve it once on the gateway host:

openclaw devices list       # find the pending request
openclaw devices approve <request-id>

The approval is durable for the device key — reconnects and reinstalls that keep the identity file need no re-approval.

Troubleshooting the gateway

./scripts/probe-gateway.sh           # handshake + pairing/scope verdict
./scripts/probe-gateway.sh ui 60     # …then listen 60s and dump live frames

The probe uses the same identity and auth path as the app; if it reports SCOPES GRANTED, the app will work.

Run against a mock gateway

./build/Build/Products/Debug/mock-gateway --scenario MockGateway/Scenarios/happy-path.json
"./build/Build/Products/Debug/Ziel van Sebastian.app/Contents/MacOS/Ziel van Sebastian" --window

Appliance install

"…/Ziel van Sebastian" --install-login-item

Flags

Flag Effect
--window 960×540 window instead of claiming a display
--demo looping scripted lifecycle, no gateway
--theme <name> select a look theme (hello, classic), overrides config
--state idle|thinking|speaking|offline jump to a state for tuning
--config <path> alternate config file
--install-login-item register for launch at login

License

MIT — see LICENSE.

About

Give Openclaw a Visual Soul

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages