mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
- web-dashboard.md: add Themes section covering built-in themes, custom theme YAML format (21 color tokens + overlay), and theme API endpoints - dashboard-plugins.md: full plugin authoring guide covering manifest format, plugin SDK reference, backend API routes, custom CSS, loading flow, discovery, and tips
336 lines
9.9 KiB
Markdown
336 lines
9.9 KiB
Markdown
---
|
|
sidebar_position: 16
|
|
title: "Dashboard Plugins"
|
|
description: "Build custom tabs and extensions for the Hermes web dashboard"
|
|
---
|
|
|
|
# Dashboard Plugins
|
|
|
|
Dashboard plugins let you add custom tabs to the web dashboard. A plugin can display its own UI, call the Hermes API, and optionally register backend endpoints — all without touching the dashboard source code.
|
|
|
|
## Quick Start
|
|
|
|
Create a plugin directory with a manifest and a JS file:
|
|
|
|
```bash
|
|
mkdir -p ~/.hermes/plugins/my-plugin/dashboard/dist
|
|
```
|
|
|
|
**manifest.json:**
|
|
|
|
```json
|
|
{
|
|
"name": "my-plugin",
|
|
"label": "My Plugin",
|
|
"icon": "Sparkles",
|
|
"version": "1.0.0",
|
|
"tab": {
|
|
"path": "/my-plugin",
|
|
"position": "after:skills"
|
|
},
|
|
"entry": "dist/index.js"
|
|
}
|
|
```
|
|
|
|
**dist/index.js:**
|
|
|
|
```javascript
|
|
(function () {
|
|
var SDK = window.__HERMES_PLUGIN_SDK__;
|
|
var React = SDK.React;
|
|
var Card = SDK.components.Card;
|
|
var CardHeader = SDK.components.CardHeader;
|
|
var CardTitle = SDK.components.CardTitle;
|
|
var CardContent = SDK.components.CardContent;
|
|
|
|
function MyPage() {
|
|
return React.createElement(Card, null,
|
|
React.createElement(CardHeader, null,
|
|
React.createElement(CardTitle, null, "My Plugin")
|
|
),
|
|
React.createElement(CardContent, null,
|
|
React.createElement("p", { className: "text-sm text-muted-foreground" },
|
|
"Hello from my custom dashboard tab!"
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
window.__HERMES_PLUGINS__.register("my-plugin", MyPage);
|
|
})();
|
|
```
|
|
|
|
Refresh the dashboard — your tab appears in the navigation bar.
|
|
|
|
## Plugin Structure
|
|
|
|
Plugins live inside the standard `~/.hermes/plugins/` directory. The dashboard extension is a `dashboard/` subfolder:
|
|
|
|
```
|
|
~/.hermes/plugins/my-plugin/
|
|
plugin.yaml # optional — existing CLI/gateway plugin manifest
|
|
__init__.py # optional — existing CLI/gateway hooks
|
|
dashboard/ # dashboard extension
|
|
manifest.json # required — tab config, icon, entry point
|
|
dist/
|
|
index.js # required — pre-built JS bundle
|
|
style.css # optional — custom CSS
|
|
plugin_api.py # optional — backend API routes
|
|
```
|
|
|
|
A single plugin can extend both the CLI/gateway (via `plugin.yaml` + `__init__.py`) and the dashboard (via `dashboard/`) from one directory.
|
|
|
|
## Manifest Reference
|
|
|
|
The `manifest.json` file describes your plugin to the dashboard:
|
|
|
|
```json
|
|
{
|
|
"name": "my-plugin",
|
|
"label": "My Plugin",
|
|
"description": "What this plugin does",
|
|
"icon": "Sparkles",
|
|
"version": "1.0.0",
|
|
"tab": {
|
|
"path": "/my-plugin",
|
|
"position": "after:skills"
|
|
},
|
|
"entry": "dist/index.js",
|
|
"css": "dist/style.css",
|
|
"api": "plugin_api.py"
|
|
}
|
|
```
|
|
|
|
| Field | Required | Description |
|
|
|-------|----------|-------------|
|
|
| `name` | Yes | Unique plugin identifier (lowercase, hyphens ok) |
|
|
| `label` | Yes | Display name shown in the nav tab |
|
|
| `description` | No | Short description |
|
|
| `icon` | No | Lucide icon name (default: `Puzzle`) |
|
|
| `version` | No | Semver version string |
|
|
| `tab.path` | Yes | URL path for the tab (e.g. `/my-plugin`) |
|
|
| `tab.position` | No | Where to insert the tab: `end` (default), `after:<tab>`, `before:<tab>` |
|
|
| `entry` | Yes | Path to the JS bundle relative to `dashboard/` |
|
|
| `css` | No | Path to a CSS file to inject |
|
|
| `api` | No | Path to a Python file with FastAPI routes |
|
|
|
|
### Tab Position
|
|
|
|
The `position` field controls where your tab appears in the navigation:
|
|
|
|
- `"end"` — after all built-in tabs (default)
|
|
- `"after:skills"` — after the Skills tab
|
|
- `"before:config"` — before the Config tab
|
|
- `"after:cron"` — after the Cron tab
|
|
|
|
The value after the colon is the path segment of the target tab (without the leading slash).
|
|
|
|
### Available Icons
|
|
|
|
Plugins can use any of these Lucide icon names:
|
|
|
|
`Activity`, `BarChart3`, `Clock`, `Code`, `Database`, `Eye`, `FileText`, `Globe`, `Heart`, `KeyRound`, `MessageSquare`, `Package`, `Puzzle`, `Settings`, `Shield`, `Sparkles`, `Star`, `Terminal`, `Wrench`, `Zap`
|
|
|
|
Unrecognized icon names fall back to `Puzzle`.
|
|
|
|
## Plugin SDK
|
|
|
|
Plugins don't bundle React or UI components — they use the SDK exposed on `window.__HERMES_PLUGIN_SDK__`. This avoids version conflicts and keeps plugin bundles tiny.
|
|
|
|
### SDK Contents
|
|
|
|
```javascript
|
|
var SDK = window.__HERMES_PLUGIN_SDK__;
|
|
|
|
// React
|
|
SDK.React // React instance
|
|
SDK.hooks.useState // React hooks
|
|
SDK.hooks.useEffect
|
|
SDK.hooks.useCallback
|
|
SDK.hooks.useMemo
|
|
SDK.hooks.useRef
|
|
SDK.hooks.useContext
|
|
SDK.hooks.createContext
|
|
|
|
// API
|
|
SDK.api // Hermes API client (getStatus, getSessions, etc.)
|
|
SDK.fetchJSON // Raw fetch for custom endpoints — handles auth automatically
|
|
|
|
// UI Components (shadcn/ui style)
|
|
SDK.components.Card
|
|
SDK.components.CardHeader
|
|
SDK.components.CardTitle
|
|
SDK.components.CardContent
|
|
SDK.components.Badge
|
|
SDK.components.Button
|
|
SDK.components.Input
|
|
SDK.components.Label
|
|
SDK.components.Select
|
|
SDK.components.SelectOption
|
|
SDK.components.Separator
|
|
SDK.components.Tabs
|
|
SDK.components.TabsList
|
|
SDK.components.TabsTrigger
|
|
|
|
// Utilities
|
|
SDK.utils.cn // Tailwind class merger (clsx + twMerge)
|
|
SDK.utils.timeAgo // "5m ago" from unix timestamp
|
|
SDK.utils.isoTimeAgo // "5m ago" from ISO string
|
|
|
|
// Hooks
|
|
SDK.useI18n // i18n translations
|
|
SDK.useTheme // Current theme info
|
|
```
|
|
|
|
### Using SDK.fetchJSON
|
|
|
|
For calling your plugin's backend API endpoints:
|
|
|
|
```javascript
|
|
SDK.fetchJSON("/api/plugins/my-plugin/data")
|
|
.then(function (result) {
|
|
console.log(result);
|
|
})
|
|
.catch(function (err) {
|
|
console.error("API call failed:", err);
|
|
});
|
|
```
|
|
|
|
`fetchJSON` automatically injects the session auth token, handles errors, and parses JSON.
|
|
|
|
### Using Existing API Methods
|
|
|
|
The `SDK.api` object has methods for all built-in Hermes endpoints:
|
|
|
|
```javascript
|
|
// Fetch agent status
|
|
SDK.api.getStatus().then(function (status) {
|
|
console.log("Version:", status.version);
|
|
});
|
|
|
|
// List sessions
|
|
SDK.api.getSessions(10).then(function (resp) {
|
|
console.log("Sessions:", resp.sessions.length);
|
|
});
|
|
```
|
|
|
|
## Backend API Routes
|
|
|
|
Plugins can register FastAPI routes by setting the `api` field in the manifest. Create a Python file that exports a `router`:
|
|
|
|
```python
|
|
# plugin_api.py
|
|
from fastapi import APIRouter
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/data")
|
|
async def get_data():
|
|
return {"items": ["one", "two", "three"]}
|
|
|
|
@router.post("/action")
|
|
async def do_action(body: dict):
|
|
return {"ok": True, "received": body}
|
|
```
|
|
|
|
Routes are mounted at `/api/plugins/<name>/`, so the above becomes:
|
|
- `GET /api/plugins/my-plugin/data`
|
|
- `POST /api/plugins/my-plugin/action`
|
|
|
|
Plugin API routes bypass session token authentication since the dashboard server only binds to localhost.
|
|
|
|
### Accessing Hermes Internals
|
|
|
|
Backend routes can import from the hermes-agent codebase:
|
|
|
|
```python
|
|
from fastapi import APIRouter
|
|
from hermes_state import SessionDB
|
|
from hermes_cli.config import load_config
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/session-count")
|
|
async def session_count():
|
|
db = SessionDB()
|
|
try:
|
|
count = len(db.list_sessions(limit=9999))
|
|
return {"count": count}
|
|
finally:
|
|
db.close()
|
|
```
|
|
|
|
## Custom CSS
|
|
|
|
If your plugin needs custom styles, add a CSS file and reference it in the manifest:
|
|
|
|
```json
|
|
{
|
|
"css": "dist/style.css"
|
|
}
|
|
```
|
|
|
|
The CSS file is injected as a `<link>` tag when the plugin loads. Use specific class names to avoid conflicts with the dashboard's existing styles.
|
|
|
|
```css
|
|
/* dist/style.css */
|
|
.my-plugin-chart {
|
|
border: 1px solid var(--color-border);
|
|
background: var(--color-card);
|
|
padding: 1rem;
|
|
}
|
|
```
|
|
|
|
You can use the dashboard's CSS custom properties (e.g. `--color-border`, `--color-foreground`) to match the active theme.
|
|
|
|
## Plugin Loading Flow
|
|
|
|
1. Dashboard loads — `main.tsx` exposes the SDK on `window.__HERMES_PLUGIN_SDK__`
|
|
2. `App.tsx` calls `usePlugins()` which fetches `GET /api/dashboard/plugins`
|
|
3. For each plugin: CSS `<link>` injected (if declared), JS `<script>` loaded
|
|
4. Plugin JS calls `window.__HERMES_PLUGINS__.register(name, Component)`
|
|
5. Dashboard adds the tab to navigation and mounts the component as a route
|
|
|
|
Plugins have up to 2 seconds to register after their script loads. If a plugin fails to load, the dashboard continues without it.
|
|
|
|
## Plugin Discovery
|
|
|
|
The dashboard scans these directories for `dashboard/manifest.json`:
|
|
|
|
1. **User plugins:** `~/.hermes/plugins/<name>/dashboard/manifest.json`
|
|
2. **Bundled plugins:** `<repo>/plugins/<name>/dashboard/manifest.json`
|
|
3. **Project plugins:** `./.hermes/plugins/<name>/dashboard/manifest.json` (only when `HERMES_ENABLE_PROJECT_PLUGINS` is set)
|
|
|
|
User plugins take precedence — if the same plugin name exists in multiple sources, the user version wins.
|
|
|
|
To force re-scanning after adding a new plugin without restarting the server:
|
|
|
|
```bash
|
|
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
|
|
```
|
|
|
|
## Plugin API Endpoints
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/api/dashboard/plugins` | GET | List discovered plugins |
|
|
| `/api/dashboard/plugins/rescan` | GET | Force re-scan for new plugins |
|
|
| `/dashboard-plugins/<name>/<path>` | GET | Serve plugin static assets |
|
|
| `/api/plugins/<name>/*` | * | Plugin-registered API routes |
|
|
|
|
## Example Plugin
|
|
|
|
The repository includes an example plugin at `plugins/example-dashboard/` that demonstrates:
|
|
|
|
- Using SDK components (Card, Badge, Button)
|
|
- Calling a backend API route
|
|
- Registering via `window.__HERMES_PLUGINS__.register()`
|
|
|
|
To try it, run `hermes dashboard` — the "Example" tab appears after Skills.
|
|
|
|
## Tips
|
|
|
|
- **No build step required** — write plain JavaScript IIFEs. If you prefer JSX, use any bundler (esbuild, Vite, webpack) targeting IIFE output with React as an external.
|
|
- **Keep bundles small** — React and all UI components are provided by the SDK. Your bundle should only contain your plugin logic.
|
|
- **Use theme variables** — reference `var(--color-*)` in CSS to automatically match whatever theme the user has selected.
|
|
- **Test locally** — run `hermes dashboard --no-open` and use browser dev tools to verify your plugin loads and registers correctly.
|