docs: add dashboard themes and plugins documentation

- 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
This commit is contained in:
Teknium 2026-04-16 03:54:11 -07:00 committed by Teknium
parent 01214a7f73
commit 131d261a74
2 changed files with 404 additions and 0 deletions

View file

@ -0,0 +1,336 @@
---
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.

View file

@ -298,3 +298,71 @@ The frontend is built with React 19, TypeScript, Tailwind CSS v4, and shadcn/ui-
## Automatic Build on Update
When you run `hermes update`, the web frontend is automatically rebuilt if `npm` is available. This keeps the dashboard in sync with code updates. If `npm` isn't installed, the update skips the frontend build and `hermes dashboard` will build it on first launch.
## Themes
The dashboard supports visual themes that change colors, overlay effects, and overall feel. Switch themes live from the header bar — click the palette icon next to the language switcher.
### Built-in Themes
| Theme | Description |
|-------|-------------|
| **Hermes Teal** | Classic dark teal (default) |
| **Midnight** | Deep blue-violet with cool accents |
| **Ember** | Warm crimson and bronze |
| **Mono** | Clean grayscale, minimal |
| **Cyberpunk** | Neon green on black |
| **Rosé** | Soft pink and warm ivory |
Theme selection is persisted to `config.yaml` under `dashboard.theme` and restored on page load.
### Custom Themes
Create a YAML file in `~/.hermes/dashboard-themes/`:
```yaml
# ~/.hermes/dashboard-themes/ocean.yaml
name: ocean
label: Ocean
description: Deep sea blues with coral accents
colors:
background: "#0a1628"
foreground: "#e0f0ff"
card: "#0f1f35"
card-foreground: "#e0f0ff"
primary: "#ff6b6b"
primary-foreground: "#0a1628"
secondary: "#152540"
secondary-foreground: "#e0f0ff"
muted: "#1a2d4a"
muted-foreground: "#7899bb"
accent: "#1f3555"
accent-foreground: "#e0f0ff"
destructive: "#fb2c36"
destructive-foreground: "#fff"
success: "#4ade80"
warning: "#fbbf24"
border: "color-mix(in srgb, #ff6b6b 15%, transparent)"
input: "color-mix(in srgb, #ff6b6b 15%, transparent)"
ring: "#ff6b6b"
popover: "#0f1f35"
popover-foreground: "#e0f0ff"
overlay:
noiseOpacity: 0.08
noiseBlendMode: color-dodge
warmGlowOpacity: 0.15
warmGlowColor: "rgba(255,107,107,0.2)"
```
The 21 color tokens map directly to the CSS custom properties used throughout the dashboard. All fields are required for custom themes. The `overlay` section is optional — it controls the grain texture and ambient glow effects.
Refresh the dashboard after creating the file. Custom themes appear in the theme picker alongside built-ins.
### Theme API
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/dashboard/themes` | GET | List available themes + active name |
| `/api/dashboard/theme` | PUT | Set active theme. Body: `{"name": "midnight"}` |