Skip to main content

openclaw-nerve-cockpit

Real-time web cockpit for OpenClaw providing voice conversations, agent fleet control, kanban boards, workspace management, and usage visibility

Jump to install

Source facts

Repository
reason-machines/hermes-skills
Last source activity
July 30, 2026 at 03:03
Detected SKILL.md language
English
Stars
5
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
openclaw-nerve-cockpit
description
Real-time web cockpit for OpenClaw providing voice conversations, agent fleet control, kanban boards, workspace management, and usage visibility
triggers
["set up nerve for openclaw","add voice control to my openclaw agent","create an openclaw dashboard","manage multiple openclaw agents with nerve","configure nerve kanban board","integrate nerve with openclaw gateway","troubleshoot nerve installation","add charts to openclaw responses"]
# OpenClaw Nerve Cockpit > Skill by [ara.so](https://ara.so) — Hermes Skills collection. Nerve is a real-time web cockpit for OpenClaw that transforms agent interaction from chat-only to full operational control. It provides voice conversations, multi-agent fleet management, automated kanban boards, workspace/file control, sub-agent sessions, inline charts, and comprehensive usage visibility. ## What Nerve Does Nerve sits between your browser and the OpenClaw Gateway, providing: - **Fleet Control**: Manage multiple agents from one interface, each with its own workspace, memory, identity, and skills - **Voice Interface**: Push-to-talk, wake word, local Whisper transcription, multilingual commands, multiple TTS providers - **Full Context**: Live workspace browser, tabbed editor, memory editing, config editing, skills inspection - **Operations Layer**: Session trees, cron scheduling, kanban task boards, review flows, proposal inbox, model overrides - **Rich Output**: Charts, diffs, previews, syntax-highlighted code, structured tool rendering, streaming UI - **Observability**: Token usage, cost tracking, context meter, agent logs, event logs ## Architecture ```text Browser ─── Nerve (:3080) ─── OpenClaw Gateway (:18789) │ │ ├─ WS ──────┤ proxied to gateway ├─ SSE ─────┤ file watchers, real-time sync └─ REST ────┘ files, memories, TTS, models ``` Frontend: React 19, Tailwind CSS 4, shadcn/ui, Vite 7 Backend: Hono 4 on Node.js 22+ ## Installation ### Quick Install (Recommended) ```bash curl -fsSL https://raw.githubusercontent.com/daggerhashimoto/openclaw-nerve/master/install.sh | bash ``` The installer handles dependencies, clone, build, and launches the setup wizard with guided access modes (localhost, LAN, Tailscale tailnet IP, Tailscale Serve). ### Manual Install ```bash git clone https://github.com/daggerhashimoto/openclaw-nerve.git cd openclaw-nerve npm install npm run setup npm run prod ``` ### Install from Next Branch (Latest Features) ```bash curl -fsSL https://raw.githubusercontent.com/daggerhashimoto/openclaw-nerve/master/install.sh | bash -s -- --branch next ``` Or switch existing install: ```bash cd ~/nerve git fetch origin git switch next || git switch -c next --track origin/next git pull --ff-only npm install npm run build npm run prod ``` ## Key Commands ```bash # Development npm run dev # Start frontend (Vite on :3080) PORT=3081 npm run dev:server # Start backend (explicit split-port dev) # Production npm run prod # Build and run production server # Management npm run setup # Interactive setup wizard npm run update -- --yes # Auto-update with rollback on failure npm run build # Build frontend and backend # Utilities npm run lint # Lint TypeScript files npm run type-check # TypeScript type checking ``` ## Configuration Nerve uses a `.env` file in the project root. Key variables: ### Core Settings ```bash # Server binding HOST=127.0.0.1 # Bind to localhost (default) # HOST=0.0.0.0 # Bind to all interfaces (enables network access) PORT=3080 # Server port # OpenClaw Gateway GATEWAY_URL=http://localhost:18789 GATEWAY_TOKEN= # Optional: if gateway requires auth # Security (required when HOST=0.0.0.0) PASSWORD_HASH= # bcrypt hash of UI password SESSION_SECRET= # Random string for session signing (32+ chars) TRUSTED_CONNECTION=false # Set true for server-side gateway token injection ``` ### Voice Settings ```bash # Voice input WHISPER_MODEL_PATH=/path/to/whisper/model # Local Whisper model WAKE_WORD=nerve # Wake word for voice activation LANGUAGE=en # Transcription language # TTS Provider (choose one) TTS_PROVIDER=openai # Options: openai, elevenlabs, edge OPENAI_API_KEY= # Required if TTS_PROVIDER=openai ELEVENLABS_API_KEY= # Required if TTS_PROVIDER=elevenlabs TTS_VOICE=alloy # Voice ID for chosen provider ``` ### Advanced Settings ```bash # Multi-agent setup AGENT_CONFIG_DIR=/path/to/agents # Directory with per-agent configs # Performance WS_PING_INTERVAL=30000 # WebSocket ping interval (ms) FILE_WATCH_DEBOUNCE=300 # File watcher debounce (ms) # Logging LOG_LEVEL=info # Options: debug, info, warn, error ``` ### Generating PASSWORD_HASH ```typescript // scripts/hash-password.ts import bcrypt from 'bcrypt'; const password = process.argv[2]; if (!password) { console.error('Usage: tsx scripts/hash-password.ts <password>'); process.exit(1); } const hash = await bcrypt.hash(password, 10); console.log(hash); ``` Run: ```bash npx tsx scripts/hash-password.ts mypassword ``` ## Deployment Modes ### Local (Default) Run Nerve and Gateway on the same machine. Best for reliability and simplicity. ```bash HOST=127.0.0.1 PORT=3080 GATEWAY_URL=http://localhost:18789 ``` ### LAN Access Expose Nerve on your local network. ```bash HOST=0.0.0.0 PORT=3080 PASSWORD_HASH=<bcrypt_hash> SESSION_SECRET=<random_32_char_string> GATEWAY_URL=http://localhost:18789 ``` ### Tailscale Access Expose via Tailscale tailnet IP or Tailscale Serve. ```bash # Via tailnet IP HOST=0.0.0.0 PORT=3080 PASSWORD_HASH=<bcrypt_hash> SESSION_SECRET=<random_32_char_string> # Via Tailscale Serve (alternative) HOST=127.0.0.1 PORT=3080 # Run: tailscale serve https / http://127.0.0.1:3080 ``` ### Hybrid Nerve local, Gateway in cloud. ```bash HOST=127.0.0.1 PORT=3080 GATEWAY_URL=https://your-gateway.example.com GATEWAY_TOKEN=<gateway_token> ``` ### Full Cloud Both Nerve and Gateway in cloud. ```bash HOST=0.0.0.0 PORT=3080 PASSWORD_HASH=<bcrypt_hash> SESSION_SECRET=<random_32_char_string> GATEWAY_URL=https://your-gateway.example.com GATEWAY_TOKEN=<gateway_token> ``` ## Agent Markers for Rich Output Nerve recognizes special markers in agent responses to render rich UI components. ### Charts ```markdown {{CHART_START}} { "type": "line", "data": { "labels": ["Jan", "Feb", "Mar", "Apr"], "datasets": [{ "label": "Revenue", "data": [12000, 19000, 15000, 25000] }] } } {{CHART_END}} ``` Supported chart types: `line`, `bar`, `pie`, `doughnut`, `radar`, `scatter` ### Kanban Tasks ```markdown {{KANBAN_START}} { "title": "Implement user authentication", "description": "Add JWT-based auth to API endpoints", "priority": "high", "tags": ["security", "backend"], "assignee": "agent-1" } {{KANBAN_END}} ``` ### TTS Control ```markdown {{TTS_START}} This text will be read aloud using the configured TTS provider. {{TTS_END}} ``` ### Code Diffs ```diff {{DIFF_START}} - const old = "previous version"; + const updated = "new version"; {{DIFF_END}} ``` ## Code Examples ### Creating a Custom Backend Route ```typescript // server/routes/custom.ts import { Hono } from 'hono'; import type { AppContext } from '../types'; const custom = new Hono<AppContext>(); // Protected route (requires auth when HOST=0.0.0.0) custom.get('/agent-status', async (c) => { const gatewayUrl = c.env.GATEWAY_URL; const token = c.env.GATEWAY_TOKEN; const response = await fetch(`${gatewayUrl}/api/agents`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); const agents = await response.json(); return c.json({ total: agents.length, active: agents.filter(a => a.status === 'active').length, agents, }); }); export default custom; ``` Register in `server/index.ts`: ```typescript import custom from './routes/custom'; app.route('/api/custom', custom); ``` ### Adding a Frontend Component ```typescript // src/components/AgentStatusCard.tsx import { useEffect, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; interface AgentStatus { total: number; active: number; } export function AgentStatusCard() { const [status, setStatus] = useState<AgentStatus | null>(null); useEffect(() => { const fetchStatus = async () => { const response = await fetch('/api/custom/agent-status'); const data = await response.json(); setStatus(data); }; fetchStatus(); const interval = setInterval(fetchStatus, 5000); return () => clearInterval(interval); }, []); if (!status) return <div>Loading...</div>; return ( <Card> <CardHeader> <CardTitle>Agent Fleet Status</CardTitle> </CardHeader> <CardContent> <div className="text-2xl font-bold">{status.active}/{status.total}</div> <p className="text-sm text-muted-foreground">Active agents</p> </CardContent> </Card> ); } ``` ### WebSocket Event Handling ```typescript // src/hooks/useGatewayWebSocket.ts import { useEffect, useRef, useState } from 'react'; interface GatewayMessage { type: string; payload: unknown; } export function useGatewayWebSocket(agentId: string) { const [messages, setMessages] = useState<GatewayMessage[]>([]); const wsRef = useRef<WebSocket | null>(null); useEffect(() => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${protocol}//${window.location.host}/ws/gateway`); ws.onopen = () => { ws.send(JSON.stringify({ type: 'subscribe', agentId })); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); setMessages((prev) => [...prev, message]); }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; wsRef.current = ws; return () => { ws.close(); }; }, [agentId]); return { messages, ws: wsRef.current }; } ``` ### Server-Side Event Streaming ```typescript // server/routes/events.ts import { Hono } from 'hono'; import { streamSSE } from 'hono/streaming'; import type { AppContext } from '../types'; const events = new Hono<AppContext>(); events.get('/file-changes', async (c) => { return streamSSE(c, async (stream) => { const watcher = watchWorkspace(c.env.WORKSPACE_PATH); watcher.on('change', (file) => { stream.writeSSE({ data: JSON.stringify({ type: 'file_changed', file }), }); }); // Keep connection alive const ping = setInterval(() => { stream.writeSSE({ data: 'ping' }); }, 30000); // Cleanup on disconnect stream.onAbort(() => { clearInterval(ping); watcher.close(); }); }); }); export default events; ``` ### Custom TTS Provider Integration ```typescript // server/services/tts/custom-provider.ts import type { TTSProvider, TTSOptions } from './types'; export class CustomTTSProvider implements TTSProvider { private apiKey: string; private endpoint: string; constructor(apiKey: string, endpoint: string) { this.apiKey = apiKey; this.endpoint = endpoint; } async synthesize(text: string, options: TTSOptions): Promise<Buffer> { const response = await fetch(this.endpoint, {
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub