Contexte

Je fais tourner une instance OpenClaw sur un Mac mini headless : un agent Codex en primary, un fallback Ollama et un canal WhatsApp. Sans écran ni exposition publique, j’avais besoin d’un point de contrôle pour voir l’état des services et dialoguer avec l’agent.

clawdeck répond à ce besoin avec un dashboard web accessible uniquement via Tailscale. Deux phases sont en place : un panneau de santé (phase 1) et un chat (phase 2).

Approche technique

Le backend est écrit en TypeScript avec Bun et Hono ; il sert aussi le front buildé (React, Vite, Tailwind, thème sombre). Les statuts remontent en temps réel par SSE sur /api/status, et le chat passe par un WebSocket relayé sur /api/chat/ws. SQLite (bun:sqlite) ne persiste que l’historique des pings.

Le panneau de santé affiche l’état de la gateway OpenClaw, d’Ollama et de son modèle de fallback, les pings vers Cloudflare et la passerelle réseau, et un graphe de latence 24h/7j. Le chat relaie vers le front le markdown, les tool calls visibles et le streaming. Côté sécurité : bind sur 127.0.0.1 ou une IP Tailscale, jamais 0.0.0.0 ; auth par AUTH_TOKEN et GATEWAY_AUTH_TOKEN ; connexion à la gateway authentifiée par identité d’appareil (Ed25519).

Ce que j’en retire

J’ai clarifié le choix des transports temps réel : SSE pour des statuts à sens unique, WebSocket pour un chat bidirectionnel relayé. Garder la persistance minimale (uniquement les pings) a simplifié le stockage sans perdre l’essentiel.

Le projet m’a aussi fait réfléchir à la posture de sécurité d’un service self-hosted : surface réseau restreinte à Tailscale, jamais de bind public, et authentification à la fois de l’accès au dashboard et de la liaison vers la gateway.