- name
- openclaw-bot-review-dashboard
- description
- A lightweight web dashboard for monitoring OpenClaw agents, models, sessions, and health status in real-time without a database
- triggers
- ["how do I monitor my OpenClaw bots","set up OpenClaw dashboard","view OpenClaw agent status","monitor OpenClaw model usage and tokens","check OpenClaw session health","deploy OpenClaw monitoring dashboard","configure OpenClaw bot review interface","track OpenClaw agent performance"]
# OpenClaw Bot Review Dashboard
> Skill by [ara.so](https://ara.so) — Hermes Skills collection.
## What It Does
OpenClaw Bot Review Dashboard is a Next.js-based web interface that provides real-time monitoring for OpenClaw agents across multiple platforms (Feishu, Discord, etc.). It reads directly from `~/.openclaw/openclaw.json` and local session files to display:
- Bot/agent status with model bindings and platform health
- Model configurations with context windows and capabilities
- Session management with token usage tracking
- Statistics and trends (token consumption, response times)
- Skill inventory (built-in, extension, custom)
- Alert rules with notification support
- Gateway health monitoring with auto-polling
- Pixel-art office visualization of agents
No database required — all data is derived from OpenClaw's local configuration.
## Installation
### Standard Setup
```bash
# Clone the repository
git clone https://github.com/xmanrui/OpenClaw-bot-review.git
cd OpenClaw-bot-review
# Install dependencies
npm install
# Start development server
npm run start
```
The dashboard will be available at `http://localhost:3000`.
### Production Build
```bash
# Build for production
npm run build
# Start production server
npm run start
```
### Docker Deployment
```bash
# Build Docker image
docker build -t openclaw-dashboard .
# Run container with default OpenClaw path (~/.openclaw)
docker run -d --name openclaw-dashboard \
-p 3000:3000 \
-v $HOME/.openclaw:/root/.openclaw:ro \
openclaw-dashboard
# Run with custom OpenClaw config path
docker run -d --name openclaw-dashboard \
-p 3000:3000 \
-e OPENCLAW_HOME=/opt/openclaw \
-v /path/to/openclaw:/opt/openclaw:ro \
openclaw-dashboard
```
## Requirements
- **Node.js**: 18+ required
- **OpenClaw**: Must be installed with config at `~/.openclaw/openclaw.json`
- **Platforms**: Works with Feishu, Discord, and other OpenClaw-supported platforms
## Configuration
### Environment Variables
```bash
# Custom OpenClaw config path
export OPENCLAW_HOME=/opt/openclaw
# Port configuration (default: 3000)
export PORT=3000
# Start with custom config
OPENCLAW_HOME=/opt/openclaw npm run start
```
### Directory Structure
The dashboard expects the following OpenClaw directory structure:
```
~/.openclaw/
├── openclaw.json # Main configuration file
├── sessions/ # Session data per agent
│ ├── agent1/
│ │ ├── session1.json
│ │ └── session2.json
│ └── agent2/
└── skills/ # Installed skills
```
### OpenClaw Configuration
The dashboard reads from `openclaw.json`:
```json
{
"agents": [
{
"name": "my-agent",
"emoji": "🤖",
"model": "gpt-4",
"platforms": {
"feishu": {
"app_id": "${FEISHU_APP_ID}",
"app_secret": "${FEISHU_APP_SECRET}"
}
}
}
],
"models": {
"gpt-4": {
"provider": "openai",
"api_key": "${OPENAI_API_KEY}",
"context_window": 8192,
"max_output": 4096
}
},
"gateway": {
"enabled": true,
"port": 8080
}
}
```
## Key Features and Usage
### Bot Dashboard
View all agents at a glance:
```typescript
// The dashboard automatically reads from ~/.openclaw/openclaw.json
// Each bot card shows:
// - Name and emoji
// - Bound model
// - Platform status (Feishu, Discord, etc.)
// - Session count
// - Gateway health indicator
```
### Model Management
Monitor all configured models:
```typescript
// Models page displays:
// - Provider (OpenAI, Anthropic, etc.)
// - Model name and version
// - Context window size
// - Max output tokens
// - Reasoning support
// - Per-model connectivity test
```
### Session Monitoring
Track active sessions per agent:
```typescript
// Sessions are automatically detected from:
// ~/.openclaw/sessions/{agent-name}/*.json
// Each session shows:
// - Type (DM, group, cron)
// - Platform
// - Token usage
// - Last activity
// - Connectivity test button
```
### Auto-Refresh Configuration
```typescript
// Available refresh intervals:
// - Manual (no auto-refresh)
// - 10 seconds
// - 30 seconds
// - 1 minute
// - 5 minutes
// - 10 minutes
// Select from dropdown in top-right corner
```
### Theme and Language
```typescript
// Theme toggle: Light / Dark mode
// Available in sidebar
// Language toggle: English / 中文
// Available in top navigation
```
## API Routes
The dashboard includes Next.js API routes for reading OpenClaw data:
### Get All Bots
```typescript
// pages/api/bots.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { readOpenClawConfig } from '@/lib/openclaw';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const config = await readOpenClawConfig();
const bots = config.agents.map(agent => ({
name: agent.name,
emoji: agent.emoji,
model: agent.model,
platforms: Object.keys(agent.platforms || {}),
sessionCount: getSessionCount(agent.name),
}));
res.status(200).json({ bots });
} catch (error) {
res.status(500).json({ error: 'Failed to read config' });
}
}
```
### Get Models
```typescript
// pages/api/models.ts
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const config = await readOpenClawConfig();
const models = Object.entries(config.models).map(([name, model]) => ({
name,
provider: model.provider,
contextWindow: model.context_window,
maxOutput: model.max_output,
reasoning: model.reasoning_support || false,
}));
res.status(200).json({ models });
}
```
### Test Platform Connection
```typescript
// pages/api/test/platform.ts
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { platform, agentName } = req.query;
try {
const result = await testPlatformConnection(
platform as string,
agentName as string
);
res.status(200).json({ success: result.success, message: result.message });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
}
```
### Gateway Health Check
```typescript
// pages/api/gateway/health.ts
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const config = await readOpenClawConfig();
if (!config.gateway?.enabled) {
return res.status(200).json({ healthy: false, reason: 'disabled' });
}
try {
const port = config.gateway.port || 8080;
const response = await fetch(`http://localhost:${port}/health`);
const healthy = response.ok;
res.status(200).json({ healthy, port });
} catch (error) {
res.status(200).json({ healthy: false, reason: 'unreachable' });
}
}
```
## Common Patterns
### Reading OpenClaw Config
```typescript
// lib/openclaw.ts
import fs from 'fs/promises';
import path from 'path';
import os from 'os';
export interface OpenClawConfig {
agents: Agent[];
models: Record<string, Model>;
gateway?: Gateway;
}
export async function readOpenClawConfig(): Promise<OpenClawConfig> {
const openclawHome = process.env.OPENCLAW_HOME ||
path.join(os.homedir(), '.openclaw');
const configPath = path.join(openclawHome, 'openclaw.json');
try {
const content = await fs.readFile(configPath, 'utf-8');
return JSON.parse(content);
} catch (error) {
throw new Error(`Failed to read OpenClaw config: ${error.message}`);
}
}
```
### Reading Session Data
```typescript
// lib/sessions.ts
export async function getAgentSessions(agentName: string) {
const openclawHome = process.env.OPENCLAW_HOME ||
path.join(os.homedir(), '.openclaw');
const sessionsDir = path.join(openclawHome, 'sessions', agentName);
try {
const files = await fs.readdir(sessionsDir);
const sessions = await Promise.all(
files
.filter(f => f.endsWith('.json'))
.map(async file => {
const content = await fs.readFile(
path.join(sessionsDir, file),
'utf-8'
);
return JSON.parse(content);
})
);
return sessions;
} catch (error) {
return [];
}
}
```
### Testing Platform Connection
```typescript
// lib/test.ts
export async function testPlatformConnection(
platform: string,
agentName: string
): Promise<{ success: boolean; message: string }> {
const config = await readOpenClawConfig();
const agent = config.agents.find(a => a.name === agentName);
if (!agent) {
return { success: false, message: 'Agent not found' };
}
const platformConfig = agent.platforms?.[platform];
if (!platformConfig) {
return { success: false, message: `${platform} not configured` };
}
if (platform === 'feishu') {
// Test Feishu API
try {
const response = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_id: process.env[platformConfig.app_id] || platformConfig.app_id,
app_secret: process.env[platformConfig.app_secret] || platformConfig.app_secret,
}),
});
const data = await response.json();
return {
success: data.code === 0,
message: data.code === 0 ? 'Connected' : data.msg,
};
} catch (error) {
return { success: false, message: error.message };
}
}
return { success: false, message: 'Platform test not implemented' };
}
```
### Component Example: Bot Card
```typescript
// components/BotCard.tsx
import { useState } from 'react';
interface BotCardProps {
bot: {
name: string;
emoji: string;
model: string;
platforms: string[];
sessionCount: number;
};
}
export default function BotCard({ bot }: BotCardProps) {
const [isTestingPlatform, setIsTestingPlatform] = useState(false);
const testPlatform = async (platform: string) => {
setIsTestingPlatform(true);
try {
const res = await fetch(
`/api/test/platform?platform=${platform}&agentName=${bot.name}`
);
const data = await res.json();
alert(data.message);
} finally {
setIsTestingPlatform(false);
}
};
return (
<div className="border rounded-lg p-4 shadow-sm hover:shadow-md transition">
<div className="flex items-center gap-2 mb-2">
<span className="text-3xl">{bot.emoji}</span>
<h3 className="text-lg font-semibold">{bot.name}</h3>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
<p>Model: {bot.model}</p>
<p>Sessions: {bot.sessionCount}</p>
</div>
<div className="mt-3 flex gap-2">
{bot.platforms.map(platform => (
<button
key={platform}
onClick={() => testPlatform(platform)}
disabled={isTestingPlatform}
Auf GitHub ansehen