仪表盘插件
仪表盘插件允许您向 Web 仪表盘添加自定义标签页。插件可以显示自己的 UI、调用 Hermes API,并可选地注册后端端点——所有这些都无需触碰仪表盘的源代码。
快速入门
创建一个包含 manifest 和 JS 文件的插件目录:
mkdir -p ~/.hermes/plugins/my-plugin/dashboard/dist
manifest.json:
{
"name": "my-plugin",
"label": "我的插件",
"icon": "Sparkles",
"version": "1.0.0",
"tab": {
"path": "/my-plugin",
"position": "after:skills"
},
"entry": "dist/index.js"
}
dist/index.js:
(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, "我的插件")
),
React.createElement(CardContent, null,
React.createElement("p", { className: "text-sm text-muted-foreground" },
"来自我的自定义仪表盘标签页的问候!"
)
)
);
}
window.__HERMES_PLUGINS__.register("my-plugin", MyPage);
})();
刷新仪表盘——您的标签页将出现在导航栏中。
插件结构
插件位于标准的 ~/.hermes/plugins/ 目录下。仪表盘扩展是一个 dashboard/ 子文件夹:
~/.hermes/plugins/my-plugin/
plugin.yaml # 可选 — 现有 CLI/网关插件清单
__init__.py # 可选 — 现有 CLI/网关钩子
dashboard/ # 仪表盘扩展
manifest.json # 必需 — 标签页配置、图标、入口点
dist/
index.js # 必需 — 预构建 JS 包
style.css # 可选 — 自定义 CSS
plugin_api.py # 可选 — 后端 API 路由
单个插件可以从一个目录同时扩展 CLI/网关(通过 plugin.yaml + __init__.py)和仪表盘(通过 dashboard/)。
清单参考
manifest.json 文件将您的插件描述给仪表盘:
{
"name": "my-plugin",
"label": "我的插件",
"description": "此插件的功能",
"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"
}
| 字段 | 是否必需 | 描述 |
|---|---|---|
name | 是 | 唯一的插件标识符(小写,允许连字符) |
label | 是 | 在导航标签页中显示的名称 |
description | 否 | 简短描述 |
icon | 否 | Lucide 图标名称(默认值:Puzzle) |
version | 否 | Semver 版本字符串 |
tab.path | 是 | 标签页的 URL 路径(例如:/my-plugin) |
tab.position | 否 | 插入标签页的位置:end(默认)、after:<tab>、before:<tab> |
entry | 是 | 相对于 dashboard/ 的 JS 包路径 |
css | 否 | 要注入的 CSS 文件路径 |
api | 否 | 包含 FastAPI 路由的 Python 文件路径 |
标签页位置
position 字段控制您的标签页在导航栏中出现的位置:
"end"— 在所有内置标签页之后(默认)"after:skills"— 在“技能”标签页之后"before:config"— 在“配置”标签页之前"after:cron"— 在“定时任务”标签页之后
冒号后的值是目标标签页的路径段(不包含前导斜杠)。
可用图标
插件可以使用以下任何 Lucide 图标名称:
Activity, BarChart3, Clock, Code, Database, Eye, FileText, Globe, Heart, KeyRound, MessageSquare, Package, Puzzle, Settings, Shield, Sparkles, Star, Terminal, Wrench, Zap
无法识别的图标名称将回退到 Puzzle。
插件 SDK
插件不打包 React 或 UI 组件——它们使用暴露在 window.__HERMES_PLUGIN_SDK__ 上的 SDK。这避免了版本冲突,并保持了插件包的精小。
SDK 内容
var SDK = window.__HERMES_PLUGIN_SDK__;
// React
SDK.React // React 实例
SDK.hooks.useState // React 钩子
SDK.hooks.useEffect
SDK.hooks.useCallback
SDK.hooks.useMemo
SDK.hooks.useRef
SDK.hooks.useContext
SDK.hooks.createContext
// API
SDK.api // Hermes API 客户端(getStatus, getSessions 等)
SDK.fetchJSON // 用于自定义端点的原始 fetch — 自动处理认证
// UI 组件 (shadcn/ui 风格)
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
// 工具类
SDK.utils.cn // Tailwind 类合并器 (clsx + twMerge)
SDK.utils.timeAgo // 从 Unix 时间戳获取的“5m ago”
SDK.utils.isoTimeAgo // 从 ISO 字符串获取的“5m ago”
// 钩子
SDK.useI18n // i18n 翻译
SDK.useTheme // 当前主题信息
使用 SDK.fetchJSON
用于调用插件的后端 API 端点:
SDK.fetchJSON("/api/plugins/my-plugin/data")
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.error("API 调用失败:", err);
});
fetchJSON 会自动注入会话认证令牌,处理错误并解析 JSON。
使用现有 API 方法
SDK.api 对象包含所有内置 Hermes 端点的方法:
// 获取代理状态
SDK.api.getStatus().then(function (status) {
console.log("版本:", status.version);
});
// 列出会话
SDK.api.getSessions(10).then(function (resp) {
console.log("会话数:", resp.sessions.length);
});
后端 API 路由
插件可以通过在清单中设置 api 字段来注册 FastAPI 路由。创建一个导出 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}
路由挂载在 /api/plugins/<name>/,因此上述代码变为:
GET /api/plugins/my-plugin/dataPOST /api/plugins/my-plugin/action
插件 API 路由绕过了会话令牌认证,因为仪表盘服务器只绑定到本地主机。
访问 Hermes 内部功能
后端路由可以从 hermes-agent 代码库导入:
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()
自定义 CSS
如果您的插件需要自定义样式,请添加一个 CSS 文件并在清单中引用它:
{
"css": "dist/style.css"
}
CSS 文件在插件加载时作为 <link> 标签注入。请使用特定的类名以避免与仪表盘现有样式冲突。
/* dist/style.css */
.my-plugin-chart {
border: 1px solid var(--color-border);
background: var(--color-card);
padding: 1rem;
}
您可以使用仪表盘的 CSS 自定义属性(例如 --color-border、--color-foreground)来匹配当前主题。
插件加载流程
- 仪表盘加载 —
main.tsx在window.__HERMES_PLUGIN_SDK__上暴露 SDK App.tsx调用usePlugins(),该函数获取GET /api/dashboard/plugins- 对于每个插件:注入 CSS
<link>(如果声明),加载 JS<script> - 插件 JS 调用
window.__HERMES_PLUGINS__.register(name, Component) - 仪表盘将标签页添加到导航栏并作为路由挂载组件
插件在脚本加载后有最多 2 秒的时间进行注册。如果插件加载失败,仪表盘将继续运行,不受影响。
插件发现
仪表盘扫描这些目录以查找 dashboard/manifest.json:
- 用户插件:
~/.hermes/plugins/<name>/dashboard/manifest.json - 捆绑插件:
<repo>/plugins/<name>/dashboard/manifest.json - 项目插件:
./.hermes/plugins/<name>/dashboard/manifest.json(仅当设置了HERMES_ENABLE_PROJECT_PLUGINS时)
用户插件具有最高优先级——如果多个来源存在相同的插件名称,则以用户版本为准。
要在不重启服务器的情况下强制重新扫描(添加新插件后):
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
插件 API 端点
| 端点 | 方法 | 描述 |
|---|---|---|
/api/dashboard/plugins | GET | 列出已发现的插件 |
/api/dashboard/plugins/rescan | GET | 强制重新扫描以查找新插件 |
/dashboard-plugins/<name>/<path> | GET | 提供插件静态资源 |
/api/plugins/<name>/* | * | 插件注册的 API 路由 |
示例插件
仓库包含一个位于 plugins/example-dashboard/ 的示例插件,演示了:
- 使用 SDK 组件(Card, Badge, Button)
- 调用后端 API 路由
- 通过
window.__HERMES_PLUGINS__.register()进行注册
要尝试它,运行 hermes dashboard — “示例”标签页将出现在“技能”标签页之后。
提示
- 无需构建步骤 — 编写纯 JavaScript IIFE。如果您更喜欢 JSX,请使用任何目标为 IIFE 输出的打包器(esbuild, Vite, webpack),并将 React 作为外部依赖。
- 保持包小巧 — React 和所有 UI 组件都由 SDK 提供。您的包只应包含您的插件逻辑。
- 使用主题变量 — 在 CSS 中引用
var(--color-*),以自动匹配用户选择的任何主题。 - 本地测试 — 运行
hermes dashboard --no-open并使用浏览器开发工具验证您的插件是否正确加载和注册。