Skills clawtime
git clone https://github.com/openclaw/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/bewareofddog/clawtime-setup" ~/.claude/skills/clawdbot-skills-clawtime && rm -rf "$T"
skills/bewareofddog/clawtime-setup/SKILL.mdClawTime — Local Installation with Cloudflare Tunnel
ClawTime is a private webchat UI connecting to the OpenClaw gateway via WebSocket. Features: passkey (Face ID/Touch ID) auth, Piper TTS voice, 3D avatar.
Why Cloudflare is required: WebAuthn (passkeys) need HTTPS on a real domain.
http://localhost only works on the same machine — not from a phone on your network.
Architecture
iPhone/Browser → https://portal.yourdomain.com → Cloudflare Tunnel → localhost:3000 (ClawTime) → ws://127.0.0.1:18789 (OpenClaw Gateway)
Prerequisites
- Node.js v22+
CLI:cloudflaredbrew install cloudflared- A domain with DNS on Cloudflare (free tier works)
- OpenClaw running:
openclaw status - (Optional) Piper TTS + ffmpeg for voice
Installation Steps
1. Clone & install
cd ~/Projects git clone https://github.com/youngkent/clawtime.git cd clawtime npm install --legacy-peer-deps
2. Set up Cloudflare Tunnel
# Login to Cloudflare cloudflared tunnel login # Create named tunnel cloudflared tunnel create clawtime # Configure routing # Edit ~/.cloudflared/config.yml:
~/.cloudflared/config.yml:
tunnel: clawtime credentials-file: /Users/YOUR_USER/.cloudflared/<tunnel-id>.json ingress: - hostname: portal.yourdomain.com service: http://localhost:3000 - service: http_status:404
Then in Cloudflare DNS dashboard: add a CNAME record:
- Name:
→ Target:portal
(Proxied ✅)<tunnel-id>.cfargotunnel.com
3. Configure OpenClaw gateway
The gateway must whitelist ClawTime's origin:
openclaw config patch '{"gateway":{"controlUi":{"allowedOrigins":["https://portal.yourdomain.com"]}}}' openclaw gateway restart
⚠️
PUBLIC_URL must match this origin exactly — it's used as the WebSocket origin header for device auth.
4. Start ClawTime server
Minimum (no TTS):
cd ~/Projects/clawtime PUBLIC_URL=https://portal.yourdomain.com \ SETUP_TOKEN=<your-setup-token> \ GATEWAY_TOKEN=<gateway-token> \ node server.js
With Piper TTS:
cd ~/Projects/clawtime PUBLIC_URL=https://portal.yourdomain.com \ SETUP_TOKEN=<your-setup-token> \ GATEWAY_TOKEN=<gateway-token> \ BOT_NAME="Beware" \ BOT_EMOJI="🌀" \ TTS_COMMAND='python3 -m piper --data-dir ~/Documents/resources/piper-voices -m en_US-kusal-medium -f /tmp/clawtime-tts-tmp.wav -- {{TEXT}} && ffmpeg -y -loglevel error -i /tmp/clawtime-tts-tmp.wav {{OUTPUT}}' \ node server.js
⚠️ TTS Security Note: The
{{TEXT}} placeholder is substituted into a shell command.
ClawTime's server must sanitize text before substitution to prevent command injection.
The server should strip or escape shell metacharacters (; | & $ \ ( ) { } < >) from user input before passing it to the TTS command. If you're modifying the TTS pipeline, use child_process.execFile()with argument arrays instead ofchild_process.exec()` with string
interpolation.
5. Start Cloudflare tunnel
cloudflared tunnel run clawtime
6. Register passkey (first time only)
- Open
in Safarihttps://portal.yourdomain.com/?setup=<your-setup-token> - Follow the passkey (Face ID / Touch ID) prompt
- ❌ Do NOT use private/incognito mode — Safari blocks passkeys there
- ❌ Do NOT use Chrome on iOS — use Safari
After registration, access ClawTime at
https://portal.yourdomain.com.
Environment Variables
| Variable | Required | Description |
|---|---|---|
| ✅ | Public HTTPS URL (must match in gateway config) |
| ✅ | OpenClaw gateway auth token |
| For registration | Passphrase for passkey registration URL |
| For voice | Piper command with and placeholders |
| No | Display name (default: "Beware") |
| No | Avatar emoji (default: "🌀") |
| No | Server port (default: 3000) |
Storing Tokens Securely (recommended)
Instead of passing tokens as plaintext env vars or in plist files, store them in macOS Keychain:
# Store tokens in Keychain security add-generic-password -s "clawtime-gateway-token" -a "$(whoami)" -w "YOUR_GATEWAY_TOKEN" security add-generic-password -s "clawtime-setup-token" -a "$(whoami)" -w "YOUR_SETUP_TOKEN"
Then retrieve them at launch time:
GATEWAY_TOKEN=$(security find-generic-password -s "clawtime-gateway-token" -a "$(whoami)" -w) \ SETUP_TOKEN=$(security find-generic-password -s "clawtime-setup-token" -a "$(whoami)" -w) \ PUBLIC_URL=https://portal.yourdomain.com \ node server.js
This avoids storing secrets in plaintext on disk.
Device Authentication (Critical)
ClawTime authenticates with the OpenClaw gateway using Ed25519 keypair auth. This is where most installs break — see details in
references/device-auth.md.
Quick summary:
- Keypair auto-generated in
on first run~/.clawtime/device-key.json - Device ID = SHA-256 of raw 32-byte Ed25519 pubkey (NOT the full SPKI-encoded key)
- Signature payload format:
v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce - If device auth fails → delete
and restart~/.clawtime/device-key.json
Auto-Start on Boot (macOS launchd)
See
references/launchd.md for plist templates for both the server and tunnel.
Managing Services
# Stop server pkill -f "node server.js" # Stop tunnel pkill -f "cloudflared" # View logs (if backgrounded) tail -f /tmp/clawtime.log tail -f /tmp/cloudflared.log # Restart after code/config changes pkill -9 -f "node server.js"; sleep 2; # then re-run start command
Getting the Gateway Token
# From macOS Keychain security find-generic-password -s "openclaw-gateway-token" -a "$(whoami)" -w # From config file cat ~/.openclaw/openclaw.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('gateway',{}).get('token',''))"
Passkey Operations
# Reset passkeys (re-register from scratch) echo '[]' > ~/.clawtime/credentials.json # Restart server, then visit /?setup=<token> # Reset device key (new keypair on next restart) rm ~/.clawtime/device-key.json
Troubleshooting
See
references/troubleshooting.md for all common errors and fixes.
See references/device-auth.md for deep-dive on gateway auth issues.