| 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 — 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
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)
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
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)
curl -fsSL https://raw.githubusercontent.com/daggerhashimoto/openclaw-nerve/master/install.sh | bash -s -- --branch next
Or switch existing install:
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
npm run dev
PORT=3081 npm run dev:server
npm run prod
npm run setup
npm run update -- --yes
npm run build
npm run lint
npm run type-check
Configuration
Nerve uses a .env file in the project root. Key variables:
Core Settings
HOST=127.0.0.1
PORT=3080
GATEWAY_URL=http://localhost:18789
GATEWAY_TOKEN=
PASSWORD_HASH=
SESSION_SECRET=
TRUSTED_CONNECTION=false
Voice Settings
WHISPER_MODEL_PATH=/path/to/whisper/model
WAKE_WORD=nerve
LANGUAGE=en
TTS_PROVIDER=openai
OPENAI_API_KEY=
ELEVENLABS_API_KEY=
TTS_VOICE=alloy
Advanced Settings
AGENT_CONFIG_DIR=/path/to/agents
WS_PING_INTERVAL=30000
FILE_WATCH_DEBOUNCE=300
LOG_LEVEL=info
Generating PASSWORD_HASH
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:
npx tsx scripts/hash-password.ts mypassword
Deployment Modes
Local (Default)
Run Nerve and Gateway on the same machine. Best for reliability and simplicity.
HOST=127.0.0.1
PORT=3080
GATEWAY_URL=http://localhost:18789
LAN Access
Expose Nerve on your local network.
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.
HOST=0.0.0.0
PORT=3080
PASSWORD_HASH=<bcrypt_hash>
SESSION_SECRET=<random_32_char_string>
HOST=127.0.0.1
PORT=3080
Hybrid
Nerve local, Gateway in cloud.
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.
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
{{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
{{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
{{TTS_START}}
This text will be read aloud using the configured TTS provider.
{{TTS_END}}
Code Diffs
{{DIFF_START}}
- const old = "previous version";
+ const updated = "new version";
{{DIFF_END}}
Code Examples
Creating a Custom Backend Route
import { Hono } from 'hono';
import type { AppContext } from '../types';
const custom = new Hono<AppContext>();
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:
import custom from './routes/custom';
app.route('/api/custom', custom);
Adding a Frontend Component
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...;
(
);
}
WebSocket Event Handling
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.);
( [...prev, message]);
};
ws. = {
.(, error);
};
wsRef. = ws;
{
ws.();
};
}, [agentId]);
{ messages, : wsRef. };
}
Server-Side Event Streaming
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 }),
});
});
const ping = setInterval(() => {
stream.writeSSE({ data: 'ping' });
}, 30000);
stream.onAbort(() => {
clearInterval(ping);
watcher.close();
});
});
});
events;
Custom TTS Provider Integration
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, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.({
text,
: options. || ,
: options. || ,
}),
});
(!response.) {
();
}
.( response.());
}
(): [] {
[, , ];
}
}
Common Patterns
Multi-Agent Setup
Create agent-specific config files:
mkdir -p ~/.config/nerve/agents
{
"id": "researcher",
"name": "Research Agent",
"workspace": "/path/to/research-workspace",
"memory": "/path/to/research-memory.json",
"identity": "You are a research specialist focused on gathering and analyzing information.",
"skills": ["web-search", "data-analysis", "summarization"],
"model": "claude-3-5-sonnet-20241022"
}
AGENT_CONFIG_DIR=/home/user/.config/nerve/agents
Cron Task Scheduling
"You can schedule recurring tasks using cron syntax. Example:
{{CRON_START}}
{
\"schedule\": \"0 9 * * *\",
\"task\": \"Generate daily report\",
\"agent\": \"researcher\"
}
{{CRON_END}}"
Review Flow Integration
"{{PROPOSAL_START}}
{
\"type\": \"code_change\",
\"file\": \"src/app.ts\",
\"description\": \"Refactor authentication logic\",
\"diff\": \"...\"
}
{{PROPOSAL_END}}"
Context Pressure Monitoring
export function useContextMeter(sessionId: string) {
const [context, setContext] = useState({
used: 0,
limit: 200000,
percentage: 0,
});
useEffect(() => {
const checkContext = async () => {
const response = await fetch(`/api/sessions/${sessionId}/context`);
const data = await response.json();
setContext({
used: data.tokens,
limit: data.limit,
percentage: (data.tokens / data.limit) * 100,
});
};
checkContext();
const interval = setInterval(checkContext, 10000);
return () => clearInterval(interval);
}, [sessionId]);
return context;
}
Troubleshooting
Gateway Connection Issues
curl http://localhost:18789/health
WebSocket Connection Fails
Voice Transcription Not Working
ls -la $WHISPER_MODEL_PATH
File Watcher Not Updating
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
FILE_WATCH_DEBOUNCE=500
Authentication Failures (HOST=0.0.0.0)
npx tsx scripts/hash-password.ts newpassword
PASSWORD_HASH=<new_hash>
SESSION_SECRET=$(openssl rand -base64 32)
Update Fails to Roll Back
cd ~/nerve
git reflog
git reset --hard HEAD@{1}
npm install
npm run build
npm run prod
Port Already in Use
lsof -i :3080
netstat -tuln | grep 3080
kill -9 <PID>
PORT=3081 npm run prod
Resources