Skip to content

feat: dashboard plugin system — extend the web UI with custom tabs#10951

Merged
teknium1 merged 2 commits into
mainfrom
feat/dashboard-plugins
Apr 16, 2026
Merged

feat: dashboard plugin system — extend the web UI with custom tabs#10951
teknium1 merged 2 commits into
mainfrom
feat/dashboard-plugins

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

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 a dashboard/ subfolder:

~/.hermes/plugins/my-plugin/
  plugin.yaml              # existing CLI/gateway plugin manifest
  dashboard/               # new: dashboard extension
    manifest.json          # tab config, icon, entry point
    dist/index.js          # pre-built JS (IIFE using SDK globals)
    dist/style.css         # optional CSS
    plugin_api.py          # optional FastAPI router

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__:

Key Contents
React React instance
hooks useState, useEffect, useCallback, useMemo, useRef, useContext, createContext
components Card, Badge, Button, Input, Label, Select, Separator, Tabs, etc.
api Hermes API client (getStatus, getSessions, etc.)
fetchJSON Raw fetch for plugin-specific endpoints
utils cn(), timeAgo(), isoTimeAgo()
useI18n i18n hook
useTheme Theme hook

Minimal plugin example

(function() {
  const { React, components: { Card, CardHeader, CardTitle, CardContent } } = window.__HERMES_PLUGIN_SDK__;

  function MyPage() {
    return React.createElement(Card, null,
      React.createElement(CardHeader, null,
        React.createElement(CardTitle, null, "Hello from plugin")
      ),
      React.createElement(CardContent, null, "Custom tab content")
    );
  }

  window.__HERMES_PLUGINS__.register("my-plugin", MyPage);
})();

Backend

  • GET /api/dashboard/plugins — discovered plugin manifests
  • GET /api/dashboard/plugins/rescan — force re-discovery
  • GET /dashboard-plugins/<name>/<path> — static assets (path traversal protected)
  • Plugin API routes mounted at /api/plugins/<name>/ (from plugin_api.py FastAPI routers)
  • Plugin API routes bypass session auth (localhost-only server)

Frontend loading flow

  1. main.tsx exposes SDK on window before render
  2. usePlugins() hook fetches manifests from backend
  3. For each plugin: injects CSS <link>, loads JS <script>
  4. Plugin JS calls window.__HERMES_PLUGINS__.register(name, Component)
  5. App.tsx dynamically adds nav items + routes for registered plugins

Bundle 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 — clean
  • npm run build — clean (400KB JS)
  • pytest tests/hermes_cli/test_web_server.py — 76/76 passed
  • pytest tests/hermes_cli/test_config.py — 49/49 passed

Live integration tests verified:

  • ✓ Plugin discovery (1 example plugin found)
  • ✓ Static asset serving (JS bundle, 4.8KB)
  • ✓ Plugin backend API (GET /api/plugins/example/hello)
  • ✓ Path traversal blocked (403)
  • ✓ Unknown plugin returns 404
  • ✓ Plugin rescan endpoint
  • ✓ Bundle contains SDK + registry + loader code

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
@teknium1 teknium1 merged commit 131d261 into main Apr 16, 2026
6 of 8 checks passed
@teknium1 teknium1 deleted the feat/dashboard-plugins branch April 16, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant