feat: dashboard plugin system — extend the web UI with custom tabs#10951
Merged
Conversation
Add a plugin system that lets plugins add new tabs to the dashboard.
Plugins live in ~/.hermes/plugins/<name>/dashboard/ alongside any
existing CLI/gateway plugin code.
Plugin structure:
plugins/<name>/dashboard/
manifest.json # name, label, icon, tab config, entry point
dist/index.js # pre-built JS bundle (IIFE, uses SDK globals)
plugin_api.py # optional FastAPI router mounted at /api/plugins/<name>/
Backend (hermes_cli/web_server.py):
- Plugin discovery: scans plugins/*/dashboard/manifest.json from user,
bundled, and project plugin directories
- GET /api/dashboard/plugins — returns discovered plugin manifests
- GET /api/dashboard/plugins/rescan — force re-discovery
- GET /dashboard-plugins/<name>/<path> — serves plugin static assets
with path traversal protection
- Optional API route mounting: imports plugin_api.py and mounts its
router under /api/plugins/<name>/
- Plugin API routes bypass session token auth (localhost-only)
Frontend (web/src/plugins/):
- Plugin SDK exposed on window.__HERMES_PLUGIN_SDK__ — provides React,
hooks, UI components (Card, Badge, Button, etc.), API client,
fetchJSON, theme/i18n hooks, and utilities
- Plugin registry on window.__HERMES_PLUGINS__.register(name, Component)
- usePlugins() hook: fetches manifests, loads JS/CSS, resolves components
- App.tsx dynamically adds nav items and routes for discovered plugins
- Icon resolution via static map of 20 common Lucide icons (no tree-
shaking penalty — bundle only +5KB over baseline)
Example plugin (plugins/example-dashboard/):
- Demonstrates SDK usage: Card components, backend API call, SDK reference
- Backend route: GET /api/plugins/example/hello
Tested: plugin discovery, static serving, API routes, path traversal
blocking, unknown plugin 404, bundle size (400KB vs 394KB baseline).
- 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a plugin system to the web dashboard. Plugins can add new tabs with custom content, serve their own static assets, and optionally register backend API routes. Zero build step required for the dashboard itself — plugins are discovered at runtime.
Plugin structure
Plugins live inside the existing
~/.hermes/plugins/<name>/directories, in adashboard/subfolder:manifest.json
{ "name": "my-plugin", "label": "My Plugin", "icon": "Sparkles", "version": "1.0.0", "tab": { "path": "/my-plugin", "position": "after:skills" }, "entry": "dist/index.js", "api": "plugin_api.py" }Plugin SDK
Plugins don't bundle React or UI components — they use the SDK exposed on
window.__HERMES_PLUGIN_SDK__:ReacthookscomponentsapifetchJSONutilsuseI18nuseThemeMinimal plugin example
Backend
GET /api/dashboard/plugins— discovered plugin manifestsGET /api/dashboard/plugins/rescan— force re-discoveryGET /dashboard-plugins/<name>/<path>— static assets (path traversal protected)/api/plugins/<name>/(fromplugin_api.pyFastAPI routers)Frontend loading flow
main.tsxexposes SDK on window before renderusePlugins()hook fetches manifests from backend<link>, loads JS<script>window.__HERMES_PLUGINS__.register(name, Component)App.tsxdynamically adds nav items + routes for registered pluginsBundle impact
+5KB over baseline (400KB vs 394KB). No tree-shaking penalty — icon map is a static whitelist of 20 common Lucide icons, not a wildcard import.
Test results
npx tsc -b— cleannpm run build— clean (400KB JS)pytest tests/hermes_cli/test_web_server.py— 76/76 passedpytest tests/hermes_cli/test_config.py— 49/49 passedLive integration tests verified: