
openclaw-studio-dashboard
Expert in OpenClaw Studio - web dashboard for managing OpenClaw Gateway, agents, chat, approvals, and jobs
- Installs
- 151
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/reason-machines/hermes-skills --skill openclaw-studio-dashboard- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads openclaw-studio-dashboard automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
OpenClaw Studio Dashboard
Skill by ara.so — Hermes Skills collection.
OpenClaw Studio is a clean web dashboard for OpenClaw Gateway that provides a unified interface to connect gateways, manage agents, chat, handle approvals, and configure jobs. Built with TypeScript/Next.js, it runs a server-owned control plane architecture with SSE streaming for real-time runtime events.
What OpenClaw Studio Does
- Gateway Connection: Connect to local or remote OpenClaw Gateway instances via WebSocket
- Agent Management: Create, configure, and monitor AI agents with tool policies and sandbox settings
- Chat Interface: Stream conversations with PI agents including tool calls and thinking traces
- Approval Workflows: Manage exec approvals and runtime permissions
- Job Configuration: Set up and monitor cron jobs
- Runtime Streaming: Real-time event streaming over SSE with replay/history
Installation & Startup
Quick Start (Recommended)
# Run latest version with npx
npx -y openclaw-studio@latest
# Opens on http://localhost:3000
# Default gateway URL: ws://localhost:18789
From Source
git clone https://github.com/grp06/openclaw-studio.git
cd openclaw-studio
npm install
npm run dev
Setup Helper
# Configure gateway URL/token without opening UI
npm run studio:setup
Connection Architecture
OpenClaw Studio uses a two-path architecture:
- Browser → Studio: HTTP + SSE (
/api/runtime/*,/api/intents/*) - Studio → Gateway: Server-owned WebSocket to upstream OpenClaw
Critical concept: ws://localhost:18789 means "gateway on the Studio host", not "gateway on your browser's machine".
Configuration Patterns
Deployment Scenarios
A. Both Local (Same Computer)
# Start Studio
npx -y openclaw-studio@latest
cd openclaw-studio
npm run dev
# Open http://localhost:3000
# In Studio UI:
# - Upstream URL: ws://localhost:18789
# - Upstream Token: <your-gateway-token>
Get gateway token:
openclaw config get gateway.auth.token
B. Gateway in Cloud, Studio Local
Option 1: Tailscale Serve (Recommended)
On gateway host:
tailscale serve --yes --bg --https 443 http://127.0.0.1:18789
In Studio (local laptop):
- Upstream URL:
wss://<gateway-host>.ts.net - Upstream Token:
<gateway-token>
Option 2: SSH Tunnel
From laptop:
ssh -L 18789:127.0.0.1:18789 user@<gateway-host>
In Studio:
- Upstream URL:
ws://localhost:18789 - Upstream Token:
<gateway-token>
C. Both in Cloud (Always-On)
On Studio VPS:
# Start Studio
npx -y openclaw-studio@latest
cd openclaw-studio
npm install
npm run dev
# If OpenClaw is on same VPS:
# - Upstream URL: ws://localhost:18789
# - Upstream Token: <gateway-token>
# Expose Studio over Tailscale
tailscale serve --yes --bg --https 443 http://127.0.0.1:3000
# Access from anywhere: https://<studio-host>.ts.net
Environment Variables
# Gateway connection (optional, can set in UI)
NEXT_PUBLIC_GATEWAY_URL=ws://localhost:18789
# Studio access control (required for public binds)
STUDIO_ACCESS_TOKEN=your-secure-token-here
# OpenClaw state directory
OPENCLAW_STATE_DIR=~/.openclaw
# Bind host (default: 127.0.0.1)
HOST=0.0.0.0 # Public bind - requires STUDIO_ACCESS_TOKEN
Configuration Files
Studio settings are stored in ~/.openclaw/openclaw-studio/:
~/.openclaw/
├── openclaw.json # OpenClaw Gateway config
└── openclaw-studio/
├── settings.json # Gateway URL/token
└── runtime.db # Control-plane runtime DB
API & Key Commands
Studio Setup
# Development mode with auto-restart
npm run dev
# Production build
npm run build
npm run start
# Turbo dev mode
npm run dev:turbo
# Verify/repair native dependencies
npm run verify:native-runtime:repair
npm run verify:native-runtime:check
TypeScript API Patterns
Connecting to Gateway
// In Studio UI components
import { useGatewayConnection } from '@/hooks/useGatewayConnection';
export function DashboardPage() {
const { connected, connect, disconnect, status } = useGatewayConnection();
const handleConnect = async () => {
try {
await connect({
url: 'ws://localhost:18789',
token: process.env.GATEWAY_TOKEN
});
} catch (error) {
console.error('Connection failed:', error);
}
};
return (
<div>
<button onClick={handleConnect} disabled={connected}>
{connected ? 'Connected' : 'Connect'}
</button>
<span>Status: {status}</span>
</div>
);
}
Streaming Runtime Events
// Server-side SSE streaming
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Subscribe to runtime events
const subscription = runtimeStore.subscribe((event) => {
const data = `data: ${JSON.stringify(event)}\n\n`;
controller.enqueue(encoder.encode(data));
});
// Replay history
const history = await runtimeStore.getHistory();
for (const event of history) {
const data = `data: ${JSON.stringify(event)}\n\n`;
controller.enqueue(encoder.encode(data));
}
return () => subscription.unsubscribe();
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
Creating Agents
// Agent creation API
interface AgentConfig {
name: string;
systemPrompt: string;
toolPolicy: 'all' | 'allowlist' | 'denylist';
allowedTools?: string[];
sandboxEnabled: boolean;
execApprovalRequired: boolean;
}
export async function createAgent(config: AgentConfig) {
const response = await fetch('/api/intents/agent-create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agent_name: config.name,
system_prompt: config.systemPrompt,
tool_policy: config.toolPolicy,
allowed_tools: config.allowedTools,
sandbox_config: {
enabled: config.sandboxEnabled,
mounts: config.sandboxEnabled ? [
{ host: '/tmp', container: '/workspace', readonly: false }
] : []
},
exec_approval_required: config.execApprovalRequired
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create agent');
}
return response.json();
}
Chat with Streaming
// Client-side chat with SSE
import { useEffect, useState } from 'react';
export function ChatInterface({ agentId }: { agentId: string }) {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [thinking, setThinking] = useState<string[]>([]);
useEffect(() => {
// Connect to runtime stream
const eventSource = new EventSource('/api/runtime/stream');
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'tool_call':
setMessages(prev => [...prev, {
role: 'assistant',
content: `Using tool: ${data.tool_name}`,
metadata: { type: 'tool', ...data }
}]);
break;
case 'thinking':
setThinking(prev => [...prev, data.content]);
break;
case 'transcript':
setMessages(prev => [...prev, {
role: data.role,
content: data.content
}]);
setThinking([]);
break;
}
});
return () => eventSource.close();
}, [agentId]);
const sendMessage = async (content: string) => {
await fetch('/api/intents/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agent_id: agentId,
message: content
})
});
};
return (
<div>
{messages.map((msg, i) => (
<div key={i} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
{thinking.length > 0 && (
<div className="thinking">
{thinking.map((t, i) => <p key={i}>{t}</p>)}
</div>
)}
<input onKeyPress={(e) => {
if (e.key === 'Enter') {
sendMessage(e.currentTarget.value);
e.currentTarget.value = '';
}
}} />
</div>
);
}
Real-World Examples
Example 1: Local Development Setup
#!/bin/bash
# setup-local-dev.sh
# Start OpenClaw Gateway (in separate terminal)
# openclaw gateway start
# Get gateway token
GATEWAY_TOKEN=$(openclaw config get gateway.auth.token)
# Start Studio
npx -y openclaw-studio@latest
cd openclaw-studio
# Configure via environment
export NEXT_PUBLIC_GATEWAY_URL=ws://localhost:18789
npm run dev
# Open http://localhost:3000
# Enter gateway token in UI
Example 2: Production Cloud Setup
#!/bin/bash
# deploy-studio-cloud.sh
# On your VPS running OpenClaw Gateway
cd /opt
npx -y openclaw-studio@latest
cd openclaw-studio
# Install dependencies
npm install
# Build for production
npm run build
# Set access token for public access
export STUDIO_ACCESS_TOKEN=$(openssl rand -hex 32)
# Expose over Tailscale
tailscale serve --yes --bg --https 443 http://127.0.0.1:3000
# Run production server
npm run start
echo "Studio available at: https://$(tailscale status --json | jq -r '.Self.DNSName')/"
echo "Access token: $STUDIO_ACCESS_TOKEN"
echo "Use: https://your-host.ts.net/?access_token=$STUDIO_ACCESS_TOKEN"
Example 3: Agent with Sandbox & Approvals
// create-secure-agent.ts
import { createAgent } from './lib/agent-api';
async function createSecureCodeAgent() {
const agent = await createAgent({
name: 'secure-code-assistant',
systemPrompt: `You are a secure code assistant.
Always ask before executing commands.
Work within the sandboxed environment only.`,
toolPolicy: 'allowlist',
allowedTools: [
'bash',
'read_file',
'write_file',
'search_files'
],
sandboxEnabled: true,
execApprovalRequired: true
});
console.log('Agent created:', agent.id);
return agent;
}
// Usage
createSecureCodeAgent().catch(console.error);
Example 4: Cron Job Configuration
// configure-cron-job.ts
interface CronJobConfig {
agentId: string;
schedule: string;
task: string;
enabled: boolean;
}
async function createCronJob(config: CronJobConfig) {
const response = await fetch('/api/intents/job-create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agent_id: config.agentId,
schedule: config.schedule,
task_prompt: config.task,
enabled: config.enabled
})
});
return response.json();
}
// Schedule daily backup
await createCronJob({
agentId: 'agent-123',
schedule: '0 0 * * *', // Daily at midnight
task: 'Create backup of project files and upload to storage',
enabled: true
});
Common Patterns
Pattern 1: Conditional Gateway URL
// lib/gateway-config.ts
export function getGatewayUrl(): string {
// Check environment variable first
if (process.env.NEXT_PUBLIC_GATEWAY_URL) {
return process.env.NEXT_PUBLIC_GATEWAY_URL;
}
// Check saved settings
const settings = loadStudioSettings();
if (settings?.gatewayUrl) {
return settings.gatewayUrl;
}
// Default to localhost
return 'ws://localhost:18789';
}
export function isSecureConnection(url: string): boolean {
return url.startsWith('wss://');
}
Pattern 2: Access Token Management
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const requiredToken = process.env.STUDIO_ACCESS_TOKEN;
// No token required for loopback-only binds
if (!requiredToken) {
return NextResponse.next();
}
// Check cookie first
const cookieToken = request.cookies.get('studio_access_token')?.value;
if (cookieToken === requiredToken) {
return NextResponse.next();
}
// Check query param (for initial setup)
const queryToken = request.nextUrl.searchParams.get('access_token');
if (queryToken === requiredToken) {
const response = NextResponse.next();
response.cookies.set('studio_access_token', requiredToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30 // 30 days
});
return response;
}
return new NextResponse('Unauthorized', { status: 401 });
}
export const config = {
matcher: ['/api/:path*', '/dashboard/:path*']
};
Pattern 3: Runtime Event Handling
// lib/runtime-store.ts
import Database from 'better-sqlite3';
export class RuntimeStore {
private db: Database.Database;
private subscribers: Set<(event: RuntimeEvent) => void>;
constructor(dbPath: string) {
this.db = new Database(dbPath);
this.subscribers = new Set();
this.initDb();
}
private initDb() {
this.db.exec(`
CREATE TABLE IF NOT EXISTS runtime_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
agent_id TEXT,
payload TEXT NOT NULL,
timestamp INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_timestamp
ON runtime_events(agent_id, timestamp);
`);
}
async storeEvent(event: RuntimeEvent) {
const stmt = this.db.prepare(`
INSERT INTO runtime_events (event_type, agent_id, payload, timestamp)
VALUES (?, ?, ?, ?)
`);
stmt.run(
event.type,
event.agentId,
JSON.stringify(event),
Date.now()
);
// Notify subscribers
this.subscribers.forEach(fn => fn(event));
}
async getHistory(agentId?: string, limit = 100): Promise<RuntimeEvent[]> {
const query = agentId
? `SELECT payload FROM runtime_events
WHERE agent_id = ?
ORDER BY timestamp DESC LIMIT ?`
: `SELECT payload FROM runtime_events
ORDER BY timestamp DESC LIMIT ?`;
const stmt = this.db.prepare(query);
const rows = agentId
? stmt.all(agentId, limit)
: stmt.all(limit);
return rows.map(row => JSON.parse(row.payload)).reverse();
}
subscribe(callback: (event: RuntimeEvent) => void) {
this.subscribers.add(callback);
return {
unsubscribe: () => this.subscribers.delete(callback)
};
}
}
Troubleshooting
Connection Issues
Problem: UI loads but "Connect" fails
# Check gateway is running
openclaw gateway status
# Verify gateway URL in Studio settings
cat ~/.openclaw/openclaw-studio/settings.json
# If Studio is remote, remember localhost means Studio host
# Use Tailscale Serve or SSH tunnel if needed
Problem: EPROTO / "wrong version number"
// Wrong: using wss:// to non-TLS endpoint
const url = 'wss://localhost:18789'; // ❌
// Right: use ws:// for plain HTTP
const url = 'ws://localhost:18789'; // ✅
// Right: use wss:// for Tailscale HTTPS
const url = 'wss://gateway.ts.net'; // ✅
Problem: Assets 404 under /studio
# Studio must be served at root path
# If you need /studio path:
# 1. Set basePath in next.config.js
# 2. Rebuild Studio
# next.config.js
module.exports = {
basePath: '/studio',
// ...
}
Native Module Issues
Problem: better_sqlite3.node / NODE_MODULE_VERSION mismatch
# Auto-repair (recommended)
npm run verify:native-runtime:repair
# Manual fix
npm rebuild better-sqlite3
npm install
# Check node/npm versions match
node -v && node -p "process.versions.modules"
which node && which npm
# If using nvm, ensure same node version
nvm use 20
npm install
Problem: SQLite errors on startup
# Check permissions on state directory
ls -la ~/.openclaw/openclaw-studio/
# Reset runtime DB if corrupted
rm ~/.openclaw/openclaw-studio/runtime.db
npm run dev
Access Token Issues
Problem: 401 "Studio access token required"
# Set access token for public binds
export STUDIO_ACCESS_TOKEN=your-secure-token
# Access UI with token once to set cookie
curl https://studio.ts.net/?access_token=your-secure-token
# Or set in browser
# https://studio.ts.net/?access_token=your-secure-token
Debugging Connection Path
// debug-connection.ts
export function debugConnection() {
console.log('Studio runtime environment:');
console.log('- Node version:', process.version);
console.log('- Platform:', process.platform);
console.log('- CWD:', process.cwd());
console.log('- State dir:', process.env.OPENCLAW_STATE_DIR || '~/.openclaw');
const isServer = typeof window === 'undefined';
console.log('- Running on:', isServer ? 'server' : 'browser');
if (isServer) {
const os = require('os');
console.log('- Hostname:', os.hostname());
console.log('- Network interfaces:',
Object.keys(os.networkInterfaces()));
}
console.log('\nGateway connection:');
console.log('- URL:', process.env.NEXT_PUBLIC_GATEWAY_URL || 'ws://localhost:18789');
console.log('- Token set:', !!process.env.GATEWAY_TOKEN);
console.log('- Access token required:', !!process.env.STUDIO_ACCESS_TOKEN);
}
Key Files Reference
openclaw-studio/
├── app/
│ ├── api/
│ │ ├── runtime/
│ │ │ └── stream/route.ts # SSE streaming endpoint
│ │ └── intents/
│ │ ├── agent-create/route.ts
│ │ ├── chat/route.ts
│ │ └── job-create/route.ts
│ ├── dashboard/
│ │ ├── page.tsx # Main dashboard
│ │ └── agents/page.tsx
│ └── layout.tsx
├── lib/
│ ├── runtime-store.ts # SQLite runtime DB
│ ├── gateway-client.ts # WebSocket client
│ └── settings.ts # Settings persistence
├── hooks/
│ └── useGatewayConnection.ts
├── docs/
│ ├── ui-guide.md
│ ├── pi-chat-streaming.md
│ ├── permissions-sandboxing.md
│ └── color-system.md
├── next.config.js
├── package.json
└── ARCHITECTURE.md
Additional Resources
- UI Guide:
docs/ui-guide.md- Agent creation, cron jobs, exec approvals - PI Chat Streaming:
docs/pi-chat-streaming.md- SSE event handling, replay, tool calls - Permissions:
docs/permissions-sandboxing.md- Tool policies, sandbox config, approval flows - Architecture:
ARCHITECTURE.md- Modules and data flow - Discord: https://discord.gg/EFkFHbZw
FAQ
What does the openclaw-studio-dashboard skill do?
Expert in OpenClaw Studio - web dashboard for managing OpenClaw Gateway, agents, chat, approvals, and jobs
How do I install openclaw-studio-dashboard?
Run npx skills add https://github.com/reason-machines/hermes-skills --skill openclaw-studio-dashboard in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/openclaw-studio-dashboard/ (personal) or .claude/skills/openclaw-studio-dashboard/ (project).
Which AI agents can use openclaw-studio-dashboard?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is openclaw-studio-dashboard safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains openclaw-studio-dashboard?
It is published by reason-machines in reason-machines/hermes-skills. It has 151 recorded installs.
More from reason-machines/hermes-skills
hermes-agent-self-evolution
reason-machines/hermes-skills
Evolutionary self-improvement for Hermes Agent using DSPy + GEPA to optimize skills, prompts, and code
hermes-webui-agent
reason-machines/hermes-skills
Expert in deploying, configuring, and using Hermes WebUI—a web interface for Hermes Agent with persistent memory, scheduled jobs, and multi-platform messaging.
awesome-hermes-agent-ecosystem
reason-machines/hermes-skills
Open the skill to read its SKILL.md instructions.
hermes-agent-framework
reason-machines/hermes-skills
Open the skill to read its SKILL.md instructions.
hermes-workspace-ai-agent-ui
reason-machines/hermes-skills
Open the skill to read its SKILL.md instructions.
hermes-desktop-companion
reason-machines/hermes-skills
Open the skill to read its SKILL.md instructions.
Popular data & databases skills
supabase-postgres-best-practices
supabase/agent-skills
Postgres best practices maintained by Supabase, for Postgres running anywhere. Load this skill BEFORE writing or changing anything that lives in a Postgres database: creating or altering tables and columns (including choosing column types), schema design, migrations and declarative schema files, RLS policies and the tests that verify them, indexes, triggers, database functions, queues and scheduled jobs (pgcron, pgmq), vector/semantic search (pgvector), and restoring dumps (pgrestore) or importing data. Also load it when diagnosing slow queries, high CPU, timeouts, EXPLAIN plans, connection exhaustion, locking, bloat, or rows visible to the wrong user or tenant. This is not just a performance guide — schema, migration, security, and SQL authoring tasks need these rules too, even for a one-column change or a single query.
prisma-client-api
prisma/skills
Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction".
prisma-database-setup
prisma/skills
Deprecated compatibility name for prisma-orm-setup. Use only when a user or existing integration explicitly invokes prisma-database-setup; otherwise select prisma-orm-setup.
prisma-cli
prisma/skills
Prisma ORM 7 CLI reference for existing Prisma 7 applications or explicitly selected Prisma 7 workflows. Covers prisma init, generate, migrate, db, dev, studio, and mcp. For setup, connection repair, or Prisma 8, use prisma-orm-setup; not the Prisma Platform CLI.
prisma-postgres
prisma/skills
Deprecated compatibility name for prisma-postgres-setup. Use only when a user or existing integration explicitly invokes prisma-postgres; otherwise select prisma-postgres-setup.
prisma-driver-adapter-implementation
prisma/skills
Required reference for Prisma ORM 7 SQL driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter, Transaction, savepoint, result mapping, or DriverAdapterError behavior. Covers current transaction lifecycle, optional savepoint hooks, original database-error preservation, and verification.