Extending the Dashboard(扩展仪表盘)
Hermes Web 仪表盘(hermes dashboard)设计为无需 fork 代码库即可重新换肤和扩展。有三个可扩展的层面:
- 主题(Themes)——YAML 文件,可重绘仪表盘的调色板、排版、布局和各组件样式。将文件放入
~/.hermes/dashboard-themes/;它会出现在主题切换器中。 - UI 插件(Plugins)——包含
manifest.json+ JavaScript 包的目录,可注册标签页、替换内置页面、通过页面作用域插槽(page-scoped slots)增强页面,或将组件注入到命名外壳插槽(shell slots)中。 - 后端插件(Backend plugins)——插件目录内的 Python 文件,暴露 FastAPI
router;路由挂载在/api/plugins/<name>/下,从插件的 UI 调用。
三者均为运行时即插即用:无需克隆仓库、无需 npm run build、无需修改仪表盘源码。本页是所有三者的规范参考。
如果您只想使用仪表盘,请参见 Web Dashboard。如果您想重新换肤终端 CLI(而非 Web 仪表盘),请参见 Skins & Themes——CLI 皮肤系统与仪表盘主题完全独立。
主题
主题是一个 YAML 文件,放在 ~/.hermes/dashboard-themes/<name>.yaml 中。它无需对应插件——仅仅是一个文件。仪表盘在启动时扫描该目录,并在主题切换器中显示每个主题。
快速开始
mkdir -p ~/.hermes/dashboard-themes创建 ~/.hermes/dashboard-themes/midnight.yaml:
# ~/.hermes/dashboard-themes/midnight.yaml
name: "Midnight"
label: "Midnight"
description: "A dark, low-contrast theme"
palette:
background: "#0a1628"
foreground: "#c8d6e5"
card: "#111d35"
cardForeground: "#e0e7ff"
popover: "#111d35"
popoverForeground: "#e0e7ff"
primary: "#4f8cff"
primaryForeground: "#ffffff"
secondary: "#1e2d50"
secondaryForeground: "#94a3b8"
muted: "#1a2744"
mutedForeground: "#7f8ea3"
accent: "#2a3d6b"
accentForeground: "#e0e7ff"
border: "#233458"
input: "#1e2d50"
# ...刷新仪表盘。点击顶部栏中的调色板图标——您会看到 Midnight 与内置主题并列。选择后,它会立即重新涂抹仪表盘,并将选择持久化到 config.yaml 中的 dashboard.theme。
示例:完整主题
name: "Strike Freedom"
label: "Strike Freedom"
description: "Cockpit-style HUD for power users"
palette:
background: "#0a0f1e"
foreground: "#d4d4e8"
card: "#121827"
cardForeground: "#e2e2f0"
popover: "#121827"
popoverForeground: "#e2e2f0"
primary: "#f59e0b"
primaryForeground: "#0a0f1e"
secondary: "#1e293b"
secondaryForeground: "#94a3b8"
muted: "#1a2332"
mutedForeground: "#7e8b9e"
accent: "#f59e0b"
accentForeground: "#0a0f1e"
destructive: "#ef4444"
border: "#1e293b"
input: "#1e293b"
ring: "#f59e0b"
typography:
fontSans: "Poppins, system-ui, sans-serif"
fontMono: "Fira Code, ui-monospace, monospace"
fontDisplay: "Poppins, system-ui, sans-serif"
fontUrl: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&family=Fira+Code:wght@400;500&display=swap"
baseSize: "15px"
lineHeight: "1.6"
letterSpacing: "-0.003em"
layout:
radius: "0.75rem"
density: comfortable
layoutVariant: standard # standard | cockpit | tiled
assets:
bg: "https://example.com/ocean-bg.jpg"
hero: "/my-images/kraken.png"
crest: "/my-images/anchor.svg"
logo: "/my-images/logo.png"
custom:
pattern: "/my-images/waves.svg"
componentStyles:
card:
boxShadow: "inset 0 0 0 1px rgba(168, 208, 255, 0.18)"
header:
background: "linear-gradient(180deg, rgba(10, 22, 40, 0.95), rgba(5, 9, 26, 0.9))"
colorOverrides:
destructive: "#ff6b6b"
ring: "#ff6b6b"
customCSS: |
/* 任意额外的选择器级调整 */创建文件后刷新仪表盘。从顶部栏实时切换主题——点击调色板图标。选择持久化到 config.yaml 中的 dashboard.theme,刷新后恢复。
插件
仪表盘插件是一个目录,包含 manifest.json、预构建的 JS 包,以及可选的 CSS 文件和包含 FastAPI 路由的 Python 文件。插件位于其他 Hermes 插件旁边的 ~/.hermes/plugins/<name>/ 中——仪表盘扩展是插件目录内的 dashboard/ 子文件夹,因此一个插件可以同时扩展 CLI/网关和仪表盘。
插件不捆绑 React 或 UI 组件。它们使用在 window.__HERMES_PLUGIN_SDK__ 上暴露的 Plugin SDK。这使插件包保持小巧(通常只有几 KB)并避免版本冲突。
快速开始——您的第一个插件
创建目录结构:
mkdir -p ~/.hermes/plugins/my-plugin/dashboard/dist编写清单文件:
// ~/.hermes/plugins/my-plugin/dashboard/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"
}编写 JS 包(纯 IIFE——无需构建步骤):
// ~/.hermes/plugins/my-plugin/dashboard/dist/index.js
(function () {
"use strict";
const SDK = window.__HERMES_PLUGIN_SDK__;
const { React } = SDK;
const { Card, CardHeader, CardTitle, CardContent } = SDK.components;
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);
})();刷新仪表盘——您的标签页出现在导航栏中,位于 Skills 之后。
:::tip 跳过 React.createElement
如果您更喜欢 JSX,可以使用任何打包工具(esbuild、Vite、rollup),将 React 设为外部依赖并输出 IIFE。唯一硬性要求是最终文件是一个可通过 <script> 加载的单个 JS 文件。React 从不被捆绑;它来自 SDK.React。
:::
目录布局
~/.hermes/plugins/my-plugin/
├── plugin.yaml # 可选——现有的 CLI/网关插件清单
├── __init__.py # 可选——现有的 CLI/网关钩子
└── dashboard/ # 仪表盘扩展
├── manifest.json # 必需——标签页配置、图标、入口点
├── dist/
│ ├── index.js # 必需——预构建的 JS 包(IIFE)
│ └── style.css # 可选——自定义 CSS
└── plugin_api.py # 可选——后端 API 路由(FastAPI)
单个插件目录可以携带三个正交扩展:
plugin.yaml+__init__.py——CLI/网关插件(参见 插件页面)dashboard/manifest.json+dashboard/dist/index.js——仪表盘 UI 插件dashboard/plugin_api.py——仪表盘后端路由
它们都不是必需的;只包含您需要的层面。
清单参考
{
"name": "my-plugin",
"label": "My Plugin",
"description": "What this plugin does",
"icon": "Sparkles",
"version": "1.0.0",
"tab": {
"path": "/my-plugin",
"position": "after:skills",
"override": "/",
"hidden": false
},
"slots": ["sidebar", "header-left"],
"entry": "dist/index.js",
"css": "dist/style.css",
"api": "plugin_api.py"
}| 字段 | 必需 | 描述 |
|---|---|---|
name | 是 | 唯一的插件标识符。小写,可用连字符。用于 URL 和注册。 |
label | 是 | 导航标签页中显示的显示名称。 |
description | 否 | 简短描述(在仪表盘管理界面中显示)。 |
icon | 否 | Lucide 图标名称。默认为 Puzzle。未知名称回退为 Puzzle。 |
version | 否 | Semver 字符串。默认为 0.0.0。 |
tab.path | 是 | 标签页的 URL 路径(例如 /my-plugin)。 |
tab.position | 否 | 标签页插入位置。"end"(默认)、"after:<path>" 或 "before:<path>"——冒号后的值是目标标签页的路径段(无前导斜杠)。示例:"after:skills"、"before:config"。 |
tab.override | 否 | 设置为内置路由路径("/"、"/sessions"、"/config" 等)以替换该页面,而不是添加新标签页。参见替换内置页面。 |
tab.hidden | 否 | 为 true 时,注册组件和任何插槽但不向导航添加标签页。用于仅插槽插件。参见仅插槽插件。 |
slots | 否 | 此插件填充的命名外壳插槽。仅作为文档辅助——实际注册从 JS 包通过 registerSlot() 进行。在此列出插槽使发现界面更丰富。 |
entry | 是 | JS 包相对于 dashboard/ 的路径。默认为 dist/index.js。 |
css | 否 | CSS 文件的路径,将作为 <link> 标签注入。 |
api | 否 | 包含 FastAPI 路由的 Python 文件路径。挂载在 /api/plugins/<name>/ 下。 |
可用图标
插件使用 Lucide 图标名称。仪表盘按名称映射——未知名称静默回退为 Puzzle。
当前映射:Activity、BarChart3、Clock、Code、Database、Eye、FileText、Globe、Heart、KeyRound、MessageSquare、Package、Puzzle、Settings、Shield、Sparkles、Star、Terminal、Wrench、Zap。
需要不同的图标?向 web/src/App.tsx 的 ICON_MAP 提交 PR——纯新增更改。
Plugin SDK
插件所需的一切都在 window.__HERMES_PLUGIN_SDK__ 上。插件绝不应直接导入 React。
const SDK = window.__HERMES_PLUGIN_SDK__;
// React + hooks
SDK.React // React 实例
SDK.hooks.useState
SDK.hooks.useEffect
SDK.hooks.useCallback
SDK.hooks.useMemo
SDK.hooks.useRef
SDK.hooks.useContext
SDK.hooks.createContext
// 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.components.PluginSlot // 渲染命名插槽(对嵌套插件 UI 有用)
// Hermes API 客户端 + 原始取回器
SDK.api // 类型化客户端 —— getStatus、getSessions、getConfig 等
SDK.fetchJSON // 自定义端点的原始 fetch(插件注册的路由)
// 工具函数
SDK.utils.cn // Tailwind 类合并器(clsx + twMerge)
SDK.utils.timeAgo // 从 Unix 时间戳生成 "5m ago"
SDK.utils.isoTimeAgo // 从 ISO 字符串生成 "5m ago"
// Hooks
SDK.useI18n // 多语言插件的国际化 hook调用插件后端
SDK.fetchJSON("/api/plugins/my-plugin/data")
.then((data) => console.log(data))
.catch((err) => console.error("API call failed:", err));fetchJSON 会注入会话认证令牌,将错误作为抛出异常显示,并自动解析 JSON。
调用内置 Hermes 端点
// Agent 状态
SDK.api.getStatus().then((s) => console.log("Version:", s.version));
// 最近的会话
SDK.api.getSessions(10).then((resp) => console.log(resp.sessions.length));参见 Web Dashboard → REST API 获取完整列表。
外壳插槽(Shell slots)
插槽允许插件将组件注入到应用外壳的命名位置——驾驶舱侧边栏、页眉、页脚、覆盖层——而不占用整个标签页。多个插件可以填充同一个插槽;它们按注册顺序堆叠显示。
从插件包内注册:
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "sidebar", MySidebar);
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "header-left", MyCrest);插槽目录
外壳范围插槽(在应用 chrome 中的任意位置渲染):
| 插槽 | 位置 |
|---|---|
backdrop | 在 <Backdrop /> 层叠内部,在噪点层之上 |
header-left | 在顶部栏的 Hermes 品牌之前 |
header-right | 在顶部栏的主题/语言切换器之前 |
header-banner | 导航下方的全宽条 |
sidebar | 驾驶舱侧边栏轨道——仅在 layoutVariant === "cockpit" 时渲染 |
pre-main | 在路由出口上方(<main> 内部) |
post-main | 在路由出口下方(<main> 内部) |
footer-left | 页脚单元格内容(替换默认) |
footer-right | 页脚单元格内容(替换默认) |
overlay | 固定定位层,位于所有其他内容之上。对 customCSS 单独无法实现的效果有用(扫描线、暗角) |
页面作用域插槽(仅在命名的内置页面上渲染——使用这些插槽向现有页面注入小部件、卡片或工具栏,无需覆盖整个路由):
| 插槽 | 渲染位置 |
|---|---|
sessions:top / sessions:bottom | /sessions 页面的顶部/底部 |
analytics:top / analytics:bottom | /analytics 页面的顶部/底部 |
logs:top / logs:bottom | /logs 页面的顶部(筛选工具栏上方)/底部(日志查看器下方) |
cron:top / cron:bottom | /cron 页面的顶部/底部 |
skills:top / skills:bottom | /skills 页面的顶部/底部 |
config:top / config:bottom | /config 页面的顶部/底部 |
env:top / env:bottom | /env(Keys)页面的顶部/底部 |
docs:top / docs:bottom | /docs 页面的顶部(iframe 上方)/底部 |
chat:top / chat:bottom | /chat 页面的顶部/底部(仅在嵌入式聊天启用时生效) |
示例——在 Sessions 页面顶部添加横幅卡片:
function PinnedSessionsBanner() {
return React.createElement(Card, null,
React.createElement(CardContent, { className: "py-2 text-xs" },
"Pinned note injected by my-plugin"),
);
}
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "sessions:top", PinnedSessionsBanner);将页面作用域插槽与 tab.hidden: true 结合使用,如果您的插件仅增强现有页面且不需要自己的侧边栏标签页。
外壳仅为上述插槽渲染 <PluginSlot name="..." />。额外的名称由注册表接受,用于嵌套插件 UI——插件可以通过 SDK.components.PluginSlot 暴露自己的插槽。
重新注册与 HMR
如果同一个 (plugin, slot) 对注册两次,后一次调用替换前一次——这与 React HMR 期望的插件重新挂载行为一致。
替换内置页面(tab.override)
将 tab.override 设置为内置路由路径,会使插件的组件替换该页面,而不是添加新标签页。当主题想要自定义主页(/)但希望保持仪表盘的其余部分不变时很有用。
{
"name": "my-home",
"label": "Home",
"tab": {
"path": "/my-home",
"override": "/",
"position": "end"
},
"entry": "dist/index.js"
}设置 override 后:
- 原本在
/的页面组件从路由器中移除 - 您的插件在
/处渲染 - 不为
tab.path添加导航标签页(覆盖是重点)
只有一个插件可以覆盖给定的路径。如果两个插件声明了相同的覆盖路径,第一个获胜,第二个被忽略并显示开发模式警告。
如果您只需要向现有页面添加卡片或工具栏而不接管它,请使用页面作用域插槽代替。
增强内置页面(页面作用域插槽)
通过 tab.override 进行完全替换是重量级操作——您的插件现在拥有整个页面,包括我们未来发布的所有更新。大多数时候您只想向现有页面添加横幅、卡片或工具栏。这就是页面作用域插槽的用途。
每个内置页面都在其内容区域的顶部和底部暴露 <page>:top 和 <page>:bottom 插槽。您的插件通过调用 registerSlot() 填充一个——内置页面保持正常工作,您的组件与之一起渲染。
可用插槽:sessions:*、analytics:*、logs:*、cron:*、skills:*、config:*、env:*、docs:*、chat:*(各有 :top 和 :bottom)。参见外壳插槽 → 插槽目录中的完整目录。
最小示例——将横幅固定到 Sessions 页面顶部:
// ~/.hermes/plugins/session-notes/dashboard/manifest.json
{
"name": "session-notes",
"label": "Session Notes",
"tab": { "path": "/session-notes", "hidden": true },
"slots": ["sessions:top"],
"entry": "dist/index.js"
}// ~/.hermes/plugins/session-notes/dashboard/dist/index.js
(function () {
const SDK = window.__HERMES_PLUGIN_SDK__;
const { React } = SDK;
const { Card, CardContent } = SDK.components;
function Banner() {
return React.createElement(Card, null,
React.createElement(CardContent, { className: "py-2 text-xs" },
"Remember to label important sessions before archiving."),
);
}
// 隐藏标签页的占位符
window.__HERMES_PLUGINS__.register("session-notes", function () { return null; });
// 真正的工作
window.__HERMES_PLUGINS__.registerSlot("session-notes", "sessions:top", Banner);
})();关键点:
tab.hidden: true使插件不出现在侧边栏中——它没有独立页面slots清单字段仅作为文档辅助。实际绑定在 JS 包中通过registerSlot()进行- 多个插件可以声明同一个页面作用域插槽。它们按注册顺序堆叠渲染
- 当没有插件注册时零占用:内置页面完全像之前一样渲染
参考插件(hermes-example-plugins 中的 example-dashboard)提供了向 sessions:top 注入横幅的实时演示——安装它以端到端查看模式。
仅插槽插件(tab.hidden)
当 tab.hidden: true 时,插件注册其组件(用于直接 URL 访问)和任何插槽,但从不向导航添加标签页。用于仅存在以注入插槽的插件——页眉徽章、侧边栏 HUD、覆盖层。
{
"name": "header-crest",
"label": "Header Crest",
"tab": {
"path": "/header-crest",
"position": "end",
"hidden": true
},
"slots": ["header-left"],
"entry": "dist/index.js"
}包仍然调用 register() 传入占位组件(以防有人直接访问 URL 的好习惯),然后调用 registerSlot() 来完成真正的工作。
后端 API 路由
插件可以通过在清单中设置 api 来注册 FastAPI 路由。创建文件并导出一个 router:
# ~/.hermes/plugins/my-plugin/dashboard/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 路由绕过会话令牌认证,因为仪表盘服务器默认绑定到 localhost。如果您运行不受信任的插件,不要通过 --host 0.0.0.0 在公共接口上暴露仪表盘——它们的路由也会变得可访问。
访问 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()
@router.get("/config-snapshot")
async def config_snapshot():
cfg = load_config()
return {"model": cfg.get("model", {})}每个插件的自定义 CSS
如果您的插件需要 Tailwind 类和内联 style= 之外的样式,添加一个 CSS 文件并在清单中引用:
{
"css": "dist/style.css"
}该文件在插件加载时作为 <link> 标签注入。使用特定的类名以避免与仪表盘样式冲突,并引用仪表盘的 CSS 变量以保持主题感知:
/* dist/style.css */
.my-plugin-chart {
border: 1px solid var(--color-border);
background: var(--color-card);
color: var(--color-card-foreground);
padding: 1rem;
}
.my-plugin-chart:hover {
border-color: var(--color-ring);
}仪表盘暴露每个 shadcn 令牌作为 --color-* 以及主题额外内容(--theme-asset-*、--component-<bucket>-*、--radius、--spacing-mul)。引用这些变量,您的插件将自动与活动主题重新换肤。
插件发现与重新加载
仪表盘扫描三个目录以查找 dashboard/manifest.json:
| 优先级 | 目录 | 来源标签 |
|---|---|---|
| 1(冲突时获胜) | ~/.hermes/plugins/<name>/dashboard/ | user |
| 2 | <repo>/plugins/memory/<name>/dashboard/ | bundled |
| 2 | <repo>/plugins/<name>/dashboard/ | bundled |
| 3 | ./.hermes/plugins/<name>/dashboard/ | project——仅在设置了 HERMES_ENABLE_PROJECT_PLUGINS 时 |
发现结果在每个仪表盘进程中缓存。添加新插件后,可以:
# 强制重新扫描,无需重启
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan……或重启 hermes dashboard。
插件加载生命周期
- 仪表盘加载。
main.tsx在window.__HERMES_PLUGIN_SDK__上暴露 SDK,在window.__HERMES_PLUGINS__上暴露注册表。 App.tsx调用usePlugins()→ 获取GET /api/dashboard/plugins。- 对每个清单:注入 CSS
<link>(如果声明),然后通过<script>标签加载 JS 包。 - 插件的 IIFE 运行并调用
window.__HERMES_PLUGINS__.register(name, Component)——以及为每个插槽可选的.registerSlot(name, slot, Component)。 - 仪表盘根据清单解析注册的组件,将标签页添加到导航(除非
hidden),并将组件挂载为路由。
插件在其脚本加载后有最多 2 秒的时间调用 register()。之后仪表盘停止等待并完成初始渲染。如果插件后来注册,它仍然会出现——导航是响应式的。
如果插件的脚本加载失败(404、语法错误、IIFE 期间异常),仪表盘会在浏览器控制台记录警告并继续运行。
组合主题 + 插件演示
strike-freedom-cockpit 插件(附属仓库 hermes-example-plugins)是一个完整的重换肤演示。它将主题 YAML 与仅插槽插件配对,产生驾驶舱风格的 HUD,无需 fork 仪表盘。
它演示了:
- 使用调色板、排版、
fontUrl、layoutVariant: cockpit、assets、componentStyles(凹口卡片角、渐变背景)、colorOverrides和customCSS(扫描线覆盖)的完整主题 - 注册到三个插槽的仅插槽插件(
tab.hidden: true):sidebar——一个 MS-STATUS 面板,带有由SDK.api.getStatus()驱动的实时遥测条header-left——从活动主题读取--theme-asset-crest的派系徽章footer-right——替换默认组织行的自定义标语
- 插件通过 CSS 变量读取主题提供的艺术作品,因此切换主题会更改英雄/徽章图像,无需修改插件代码
安装:
git clone https://github.com/NousResearch/hermes-example-plugins.git
# 主题
cp hermes-example-plugins/strike-freedom-cockpit/theme/strike-freedom.yaml \
~/.hermes/dashboard-themes/
# 插件
cp -r hermes-example-plugins/strike-freedom-cockpit ~/.hermes/plugins/打开仪表盘,从主题切换器中选择 Strike Freedom。驾驶舱侧边栏出现,徽章显示在页眉中,标语替换页脚。切回 Hermes Teal,插件保持安装但不可见(sidebar 插槽仅在 cockpit 布局变体下渲染)。
阅读插件源码(附属仓库中的 strike-freedom-cockpit/dashboard/dist/index.js),了解它如何读取 CSS 变量、防范没有插槽支持的旧版仪表盘以及从一个包注册三个插槽。
API 参考
主题端点
| 端点 | 方法 | 描述 |
|---|---|---|
/api/dashboard/themes | GET | 列出可用主题 + 活动名称。内置主题返回 {name, label, description};用户主题还包含 definition 字段,包含完整的标准化主题对象。 |
/api/dashboard/theme | PUT | 设置活动主题。请求体:{"name": "midnight"}。持久化到 config.yaml 中的 dashboard.theme。 |
插件端点
| 端点 | 方法 | 描述 |
|---|---|---|
/api/dashboard/plugins | GET | 列出发现的插件(带清单,排除内部字段)。 |
/api/dashboard/plugins/rescan | GET | 强制重新扫描插件目录,无需重启。 |
/dashboard-plugins/<name>/<path> | GET | 提供插件 dashboard/ 目录中的静态资产。路径遍历被阻止。 |
/api/plugins/<name>/* | * | 插件注册的后端路由。 |
window 上的 SDK
| 全局变量 | 类型 | 提供者 |
|---|---|---|
window.__HERMES_PLUGIN_SDK__ | object | registry.ts——React、hooks、UI 组件、API 客户端、工具函数 |
window.__HERMES_PLUGINS__.register(name, Component) | function | 注册插件的主组件 |
window.__HERMES_PLUGINS__.registerSlot(name, slot, Component) | function | 注册到命名外壳插槽 |
故障排除
我的主题没有出现在选择器中。
检查文件是否在 ~/.hermes/dashboard-themes/ 中并以 .yaml 或 .yml 结尾。刷新页面。运行 curl http://127.0.0.1:9119/api/dashboard/themes——您的主题应在响应中。如果 YAML 有解析错误,仪表盘会记录到 ~/.hermes/logs/ 下的 errors.log。
我的插件标签页没有显示。
- 检查清单是否在
~/.hermes/plugins/<name>/dashboard/manifest.json(注意dashboard/子目录)。 curl http://127.0.0.1:9119/api/dashboard/plugins/rescan强制重新发现。- 打开浏览器开发者工具 → Network——确认
manifest.json、index.js和任何 CSS 没有 404 加载。 - 打开浏览器开发者工具 → Console——查找 IIFE 期间的错误或
window.__HERMES_PLUGINS__ is undefined(表示 SDK 未初始化,通常是由于更早的 React 渲染崩溃)。 - 验证您的包是否使用与
manifest.json:name相同的名称调用window.__HERMES_PLUGINS__.register(...)。
插槽注册的组件不渲染。
sidebar 插槽仅在活动主题具有 layoutVariant: cockpit 时渲染。其他插槽始终渲染。如果您注册到一个没有命中的插槽,在 registerSlot 内添加 console.log 以确认插件包确实运行了。
插件后端路由返回 404。
- 确认清单中有
"api": "plugin_api.py",指向dashboard/内的现有文件。 - 重启
hermes dashboard——插件 API 路由在启动时挂载一次,不是在重新扫描时。 - 检查
plugin_api.py是否导出了模块级router = APIRouter()。其他导出名称不会被识别。 - 追踪
~/.hermes/logs/errors.log中的Failed to load plugin <name> API routes——导入错误会在那里记录。
主题更改会丢弃我的颜色覆盖。
colorOverrides 作用域限于活动主题,并在主题切换时清除——这是设计使然。如果您想要持久化的覆盖,请将它们放在主题的 YAML 中,而不是放在实时切换器中。
主题 customCSS 被截断。
customCSS 块每个主题上限为 32 KiB。将大型样式表拆分到多个主题,或切换到通过其 css 字段注入完整样式表的插件(无大小上限)。
我想在 PyPI 上发布一个插件。
仪表盘插件按目录布局安装,而不是通过 pip 入口点。目前最干净的发布路径是一个 git 仓库,用户克隆到 ~/.hermes/plugins/。基于 pip 的仪表盘插件安装程序尚未接入。