Расширение панели управления
Веб-панель Hermes («панель управления Hermes») создана для смены облика и расширения без разветвления кодовой базы. Доступны три уровня:
- Темы — YAML-файлы, которые перекрашивают палитру, типографику, макет и хромированные отдельные компоненты панели. Поместите файл в
~/.hermes/dashboard-themes/; он появляется в переключателе тем. - UI-плагины — каталог с
manifest.json+ JavaScript-бандл, который регистрирует вкладку, заменяет встроенную страницу, выполняет ее через слоты на странице или внедряет компоненты в именованные слоты обработки. - Серверные плагины — Python-файл внутри той же директории плагина, который предоставляет
routerFastAPI; Маршруты монтируются по пути/api/plugins/<name>/и вызываются из плагина пользовательского интерфейса.
Все три внедряются во время выполнения: без клонирования репозитория, без npm run build, без изменений исходников панели. Эта страница является каноническим справочником по всем трём.
Если вы просто хотите использовать панель, см. Веб-панель. Если вы хотите изменить облик терминального CLI (не веб-панели), см. Скины и темы — система скинов CLI, не связанная с темами панели.
📝 Note
Как сочетаются части Темы и плагины независимы, но синергичны. Тема может быть самостоятельной (просто YAML-файл). Плагин может быть самостоятельным (просто вкладка). Вместе с тем они позволяют сделать полный визуальный рескин с пользовательскими HUD — встроенный демо-плагинstrike-freedom-cockpit делает именно это. См. Демо обнаруженной темы и плагина.Содержание
- Темы
- Быстрый старт — ваша первая тема
- Палитра, типографика, макет
- Варианты макета
- Ресурсы темы (изображения как CSS-переменные)
- Переопределения компонентов хрома
- Переопределения цветов
- Сырой
customCSS - Встроенные темы
- Полный справочник YAML темы
- Плагины
- Быстрый старт — ваш первый плагин
- Структура дирекции
- Справочник манифеста
- SDK плагин
- Слоты обработки
- Замена встроенных страниц (
tab.override) - Дополнение встроенных страниц (слоты на странице)
- Плагины только со слотами (
tab.hidden) - Серверные API-маршруты
- Пользовательский CSS для каждого плагина
- Обнаружение и перезагрузка плагинов
- Демо обнаруженной темы и плагина
- Справочник API
- Устранение неполадок
Темы
Темы — это YAML-файлы, хранящиеся в ~/.hermes/dashboard-themes/. Имя файла не имеет значения (система использует поле name: theme), но соглашение — <name>.yaml. Каждое поле необязательно — отсутствующие ключи возвращаются к встроенной теме «по умолчанию», поэтому тема может быть такой же маленькой, как один цвет.
Быстрый старт — ваша первая тема
mkdir -p ~/.hermes/dashboard-themes
# ~/.hermes/dashboard-themes/neon.yaml
name: neon
label: Neon
description: Чистый маджента на чёрном
palette:
background: "#000000"
midground: "#ff00ff"
Обновите панель. Нажмите на значок палитры в заголовке и выберите Неон. Фон станет чёрным, текст и акценты — маджента, а все производные цвета (карточка, граница, приглушённый, кольцо и т.д.) пересчитываются из этого триплета из 2 цветов с помощью color-mix() в CSS.
Это всё введение: один файл, два цвета. Всё ниже — необязательное уточнение.
Палитра, типографика, макет
Эти три блока являются сердцем темы. Каждый независим — переопределите одного, оставьте других.
Палитра (3 слоя)
Палитра — это триплет цветовых слоёв плюс цвет тёплого свечения и множитель шума. Панель Каскад дизайн-системы выводит все совместимые с токенами Shadcn (карта, всплывающее окно, приглушенный, граница, основной, разрушительный, кольцо и т.д.) из этого триплета с помощью CSS color-mix(). Переопределение трех цветов каскадируется во всем пользовательском интерфейсе.
| Ключ | Описание |
|---|---|
палитра.фон |
Самый глубокий цвет фона — обычно почти чёрный. Определяет фон страницы и заливку карточка. |
палитра.средний план |
Основной текст и акцент. Большинство пользовательского интерфейса хрома читает это (текст переднего плана, кнопки контуров, кольца фокуса). |
палитра.передний план |
Выделение верхнего слоя. Тема по умолчанию устанавливает его на белый с альфой 0 (невидимый); Темы, которые хотят привлечь внимание сверху, могут поднять его альфу. |
палитра.теплое свечение |
Строка rgba(...), используется в качестве цветного компонента виньетки <Backdrop />. |
палитра.noiseOpacity |
Множитель 0–1,2 для наложения зернистости. Ниже = мягче, выше = грубее. |
Каждый слой принимает либо {hex: "#RRGGBB", альфа: 0.0–1.0}, либо голую шестнадцатеричную букву (альфа по умолчанию 1.0).
palette:
background:
hex: "#05091a"
alpha: 1.0
midground: "#d8f0ff" # голая hex, alpha = 1.0
foreground:
hex: "#ffffff"
alpha: 0 # невидимый верхний слой
warmGlow: "rgba(255, 199, 55, 0.24)"
noiseOpacity: 0.7
Типографика
| Ключ | Тип | Описание |
|---|---|---|
шрифтСанс |
строка | Семейство шрифтов CSS для основного текста (применяется к html, body). |
шрифтМоно |
строка | Стек CSS Font-family для блоков кода, <code>, утилита .font-mono. |
шрифтДисплей |
строка | Опциональный стек для заголовков/отображения. Возвращается к fontSans. |
fontUrl |
строка | Дополнительный URL внешней таблицы стилей. Внедряется как <link rel="stylesheet"> в <head> при переключении темы. Один и тот же URL никогда не применяется случайно. Работает с Google Fonts, Bunny Fonts, самодельными таблицами @font-face — всем, что можно связаться. |
baseSize |
строка | Корневой размер шрифта — управление шкалой rem. Например, "14px", "16px". |
lineHeight |
строка | Межстрочный интервал по умолчанию. Например, "1,5", "1,65". |
letterSpacing |
строка | Межбуквенный интервал по умолчанию. Например, "0", "0.01em", "-0.01em". |
typography:
fontSans: '"Orbitron", "Eurostile", "Impact", sans-serif'
fontMono: '"Share Tech Mono", ui-monospace, monospace'
fontDisplay: '"Orbitron", "Eurostile", sans-serif'
fontUrl: "https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Share+Tech+Mono&display=swap"
baseSize: "14px"
lineHeight: "1.5"
letterSpacing: "0.04em"
Макет
| Ключ | Значения | Описание |
|---|---|---|
радиус |
любая CSS-длина("0", "0.25rem", "0.5rem", "1rem",...) |
Токен радиуса угла. Отображается на --radius и каскадируется на --radius-sm/md/lg/xl — все скруглённые элементы изменяются вместе. |
плотность |
компактный | комфортно | просторный |
Множитель интервалов, примененный как переменная CSS --spacing-mul. компактный = 0,85×, комфортный = 1,0× (по умолчанию), просторный = 1,2×. Масштабирует базовые интервалы попутного ветра, так же как и отступы, промежутки и межблочные расстояния, преобразующие перемену. |
layout:
radius: "0"
density: compact
Варианты макета
layoutVariant выбирает общий макет изготовления. По умолчанию `"стандарт", если отсутствует.
| Вариант | Поведение |
|---|---|
стандарт |
Одна колонка, максимальная ширина 1600 пикселей (по умолчанию). |
кабина |
Левая боковая панель (260 пикселей) + основной контент. Заполняется плагин через слот sidebar — см. Слоты обработки. Без вилки панели откроется заглушка. |
плиточный |
Убирает ограничение по краю, чтобы можно было использовать весь край окна для просмотра. |
layoutVariant: cockpit
Текущий вариант доступен как document.documentElement.dataset.layoutVariant, так что сырой CSS в customCSS может нацеливаться на него через :root[data-layout-variant="cockpit"]....
Ресурсы темы (изображения как CSS-переменные)
По URL-адресу предоставлены изображения вместе с темой. Каждый именованный слот становится CSS-переменной (--theme-asset-<name>), которую можно читать встроенную вставку и любой плагин. Слот bg автоматически добавляется к фону; остальные слоты предназначены для разъемов.
assets:
bg: "https://example.com/hero-bg.jpg" # автоматически подключается к <Backdrop />
hero: "/my-images/strike-freedom.png" # для боковых панелей плагинов
crest: "/my-images/crest.svg" # для плагинов слева в заголовке
logo: "/my-images/logo.png"
sidebar: "/my-images/rail.png"
header: "/my-images/header-art.png"
custom:
scanLines: "/my-images/scanlines.png" # → --theme-asset-custom-scanLines
Значения принимают:
- Голые URL — автоматически оборачиваются в
url(...). - Предварительно обёрнутые выражения
url(...),linear-gradient(...),radial-gradient(...)— используются как есть. "none"— явный отказ.
Каждый ресурс также выводится как --theme-asset-<name>-raw (нераспакованный URL), на случай, если плагину потребуется передать его в <img src> вместо background-image.
Плагины читают их с помощью обычного CSS или JS:
// В слоте плагина
const hero = getComputedStyle(document.documentElement).getPropertyValue("--theme-asset-hero").trim();
Переопределение компонентов хрома
comComponentStyles переопределяет стили отдельных компонентов без написания CSS-селекторов. Записи каждого бакета становятся CSS-переменными (--comment-<bucket>-<kebab-property>), которые читают общие компоненты наблюдения. Таким образом, переопределение card: применяется к каждому <Card>, header: к приложениям панели и т.д.
componentStyles:
card:
clipPath: "polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px)"
background: "linear-gradient(180deg, rgba(10, 22, 52, 0.85), rgba(5, 9, 26, 0.92))"
boxShadow: "inset 0 0 0 1px rgba(64, 200, 255, 0.28)"
header:
background: "linear-gradient(180deg, rgba(16, 32, 72, 0.95), rgba(5, 9, 26, 0.9))"
tab:
clipPath: "polygon(6px 0, 100% 0, calc(100% - 6px) 100%, 0 100%)"
sidebar: {}
backdrop: {}
footer: {}
progress: {}
badge: {}
page: {}
Поддерживаемые бакеты: card, header, footer, sidebar, tab, progress, badge, backdrop, page.
Имена свойства используются CamelCase(clipPath) и создаются в kebab(clip-path). Значения — это обычные CSS-штрихи — всё, что принимает CSS («clip-path», «border-image», «background», «box-shadow», «animation»,...).
Переопределения цветов
Большинству тем это не понадобится — палитра из 3 слоёв выводит все shadcn-токены. Используйте «colorOverrides», когда вам нужен конкретный акцент, который не дает производной (более мягкий красный для разрушительного действия в пастельной зоне, конкретный зеленый для успеха в бренде).
colorOverrides:
primary: "#ffce3a"
primaryForeground: "#05091a"
accent: "#3fd3ff"
ring: "#3fd3ff"
destructive: "#ff3a5e"
border: "rgba(64, 200, 255, 0.28)"
Поддерживаемые ключи: card, cardForeground, popover, popoverForeground, primary, primaryForeground, вторичный, SecondaryForeground, muted, mutedForeground, accent, accentForeground, destructive, destructiveForeground, success, warning, border, вход, кольцо.
Каждый ключ отображается в соотношении 1:1 на CSS-переменной --color-<kebab> (например, primaryForeground → --color-primary-foreground). Любой ключ, установленный здесь, отменяет вызов палитры только для активной темы — переключение на другую тему очищает переопределение.
Сырой customCSS
Для хрома на уровне селекторов, которые comComponentStyles не может выдать — псевдоэлементы, анимации, медиа-запросы, переопределения в рамках темы — поместите сырой CSS в customCSS:
customCSS: |
/* Наложение строк развёртки — видно только при активном варианте cockpit. */:root[data-layout-variant="cockpit"] body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 100;
background: repeating-linear-gradient(to bottom,
transparent 0px, transparent 2px,
rgba(64, 200, 255, 0.035) 3px, rgba(64, 200, 255, 0.035) 4px);
mix-blend-mode: screen;
}
CSS внедряется как один изолированный тег <style data-hermes-theme-css> при применении темы и очищается при переключении темы. Ограничено 32 КБ на теме.
Встроенные темы
Каждая встроенная тема связана с собственной палитрой, типографикой и макетом — переключение дает видимые изменения, выходящие только за рамки цвета.
| Тема | Палитра | Типографика | Макет |
|---|---|---|---|
Гермес Тил (по умолчанию) |
Тёмный бирюзовый + кремовый | Системный стек, 15px | Радиус 0,5 бэр, удобный |
Гермес Тил (Большой) (default-large) |
То же самое, что по умолчанию | Системный стек, 18px, line-height 1.65 | Радиус 0,5 бэр, просторный |
| Полночь («полночь») | Глубокий сине-фиолетовый | Интер + JetBrains Mono, 14 пикселей | Радиус 0,75 бэр, удобный |
Эмбер (уголь) |
Тёплый малиновый + бронзовый | Spectral (с засечками) + IBM Plex Mono, 15px | Радиус 0,25 бэр, удобный |
| Моно («моно») | Оттенки серого | IBM Plex Sans + IBM Plex Mono, 13 пикселей | 0 радиус, компактный |
| Киберпанк («киберпанк») | Неоново-зелёный на чёрном | Поделиться Tech Mono везде, 14 пикселей | 0 радиус, компактный |
| Rosé («роза») | Розовый + слоновая кость | Fraunces (с засечками) + DM Mono, 16px | радиус 1 рем, просторный |
Темы, которые ссылаются на Google Fonts (все, кроме Hermes Teal), загружают таблицу стилей по программе — при первом переключении их в <head> включается тег <link>.
Полный справочник YAML темы
Все настройки в одном файле — скопируйте и обрежьте то, что не нужно:
# ~/.hermes/dashboard-themes/ocean.yaml
name: ocean
label: Ocean Deep
description: Глубокие морские синие с коралловыми акцентами
# 3-слойная палитра (принимает {hex, alpha} или голую hex)
palette:
background:
hex: "#0a1628"
alpha: 1.0
midground:
hex: "#a8d0ff"
alpha: 1.0
foreground:
hex: "#ffffff"
alpha: 0.0
warmGlow: "rgba(255, 107, 107, 0.35)"
noiseOpacity: 0.7
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-файлом и Python-файлом с маршрутами FastAPI. Плагины применяются рядом с другими плагинами Hermes в ~/.hermes/plugins/<name>/ — панель расширения находится в подпапке dashboard/ внутри этого плагина каталога, так что один плагин может расширять и CLI/шлюз, и панель из одной установки.
Плагины не включают React или UI-компоненты. Они используют плагин SDK, доступный в window.__HERMES_PLUGIN_SDK__. Это сохраняет бандлы плагинов микрочастицами (обычно несколько КБ) и исключает возможные варианты.
Быстрый старт — ваша первая вилка
создадим директорию структуры:
mkdir -p ~/.hermes/plugins/my-plugin/dashboard/dist
Напишите манифест:
// ~/.hermes/plugins/my-plugin/dashboard/manifest.json
{
"name": "my-plugin",
"label": "Мой плагин",
"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, "Мой плагин"),
),
React.createElement(CardContent, null,
React.createElement("p", { className: "text-sm text-muted-foreground" },
"Привет от моей пользовательской вкладки панели.",
),
),
);
}
window.__HERMES_PLUGINS__.register("my-plugin", MyPage);
})();
Обновите панель — ваша внешний вид в навигационной панели после Skills.
💡 Tip
Пропустите React.createElement Если вы предпочитаете JSX, воспользуйтесь любым сборщиком (esbuild, Vite,rollup) с React в зависимости от внешнего и IIFE-выводом. Единственное жёсткое требование — чтобы итоговый файл был одним JS-файлом, загружаемым через<script>. 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": "Мой плагин",
"description": "Что делает этот плагин",
"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"
}
| Поле | Обязательно | Описание |
|---|---|---|
имя |
Да | Уникальный идентификатор плагина. Нижний регистр, дефисы допустимы. Используется в URL и регистрации. |
этикетка |
Да | Отображаемое имя, показываемое на вкладке навигации. |
описание |
Нет | Краткое описание (показывается на выходе верхних сторон панели). |
значок |
Нет | Имя иконки Люсиде. По умолчанию Пазл. Неизвестные имена возвращаются к Puzzle. |
версия |
Нет | Строка Семвер. По умолчанию 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, регистрирует компоненты и любые слоты без добавления вкладки в навигацию. Использовались плагины только со слотами. См. Плагины только со слотами. |
слоты |
Нет | Именованные слоты обработки, которые выполняют этот плагин. Только для документации — фактическая регистрация происходит из JS-бандла через registerSlot(). Перечисление слотов здесь делает поиск поверхности более информативным. |
вход |
Да | Путь к JS-бандлу относительно dashboard/. По умолчанию dist/index.js. |
css |
Нет | Путь к CSS-файлу для развития в видеотеге <link>. |
апи |
Нет | Путь к Python-файлу с маршрутами FastAPI. Монтируется по пути /api/plugins/<имя>/. |
Доступные иконки
Плагины используют имена иконок Lucide. Панель отображает их по имени — неизвестные имена молча возвращаются к «Puzzle».
В настоящее время структуры: Activity, BarChart3, Clock, Code, Database, Eye, FileText, Globe, Heart, KeyRound, MessageSquare, Package, Puzzle, Settings, Shield, Sparkles, Star, Terminal, Гаечный ключ, Зап.
Нужна другая иконка? Откройте PR в ICON_MAP в web/src/App.tsx — чисто аддитивное изменение.
Плагин SDK
Всё, что нужно подключить, находится в window.__HERMES_PLUGIN_SDK__. Плагины не должны импортировать React никогда напрямую.
const SDK = window.__HERMES_PLUGIN_SDK__;
// React + хуки
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 плагинов)
// Клиент API Hermes + сырой загрузчик
SDK.api // типизированный клиент — getStatus, getSessions, getConfig,...
SDK.fetchJSON // сырой fetch для пользовательских конечных точек (маршруты, зарегистрированные плагином)
// Утилиты
SDK.utils.cn // объединитель классов Tailwind (clsx + twMerge)
SDK.utils.timeAgo // "5m ago" из unix-метки времени
SDK.utils.isoTimeAgo // "5m ago" из ISO-строки
// Хуки
SDK.useI18n // хук i18n для многоязычных плагинов
Вызов серверной части вашего плагина
SDK.fetchJSON("/api/plugins/my-plugin/data").then((data) => console.log(data)).catch((err) => console.error("API-вызов не удался:", err));
fetchJSON внедряет сеанс аутентификации токенов, отображает ошибки как выброшенные исключения и автоматически анализирует JSON.
Вызов внутренних конечных точек Hermes
// Статус агента
SDK.api.getStatus().then((s) => console.log("Версия:", s.version));
// Недавние сессии
SDK.api.getSessions(10).then((resp) => console.log(resp.sessions.length));
См. Веб-панель → REST API для всего списка.
Слоты обработки
Слоты позволяют вставлять компоненты в названные места приложения — боковую панель кабины, заголовок, нижний колонтитул, слой наложения — без захвата целой вкладки. Несколько разъемов могут заполнять один и тот же слот; они предоставили в порядке регистрации.
Зарегистрируйтесь в связном разъеме:
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "sidebar", MySidebar);
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "header-left", MyCrest);
Каталог слотов
Слоты обработки (отображаются в любом месте хрома приложения):
| Слот | Расположение |
|---|---|
фон |
Внутри стека слоёв <Backdrop />, над слоем шума. |
заголовок слева |
Перед брендом Hermes на верхней панели. |
заголовок справа |
Перед переключателями темы/языка в верхней панели. |
шапка-баннер |
Полноширинная полоса под навигацией. |
боковая панель |
Боковая панель кабины — отображается только при layoutVariant === "cockpit". |
предварительный |
Над выходным маршрутом (внутри <main>). |
пост-основной |
Под выходом маршрута (внутри <main>). |
нижний колонтитул слева |
Содержимое камеры нижней колонтитулы (заменяет значение по умолчанию). |
нижний колонтитул справа |
Содержимое камеры нижней колонтитулы (заменяет значение по умолчанию). |
наложение |
Слой с фиксированной позицией над всеми контактами. Полезно для хрома (строки развёртки, виньетки), который customCSS не может ограничиться одной. |
Слоты на странице (отображаются только на именованной встроенной странице — используйте их для обеспечения работоспособности виджетов, карточек или панелей на существующей странице без переопределения всего маршрута):
| Слот | Где отображается |
|---|---|
сеансы: сверху / сеансы: снизу |
Вверху / внизу страницы /sessions. |
аналитика: сверху / аналитика: снизу |
Вверху / внизу страницы /analytics. |
журналы: сверху / журналы: снизу |
Вверху (над панелью фильтров) / внизу (под просмотрщиком журналов) страницы /logs. |
cron:top / cron:bottom |
Вверху / внизу страницы /cron. |
навыки: верх / навыки: низ |
Вверху / внизу страницы /skills. |
config:top / config:bottom |
Вверху / внизу страницы /config. |
окр: верх / окр: низ |
Вверху / нижнюю страницу /env (Ключи). |
документы: сверху / документы: снизу |
Вверху (над iframe) / нижняя страница /docs. |
чат: сверху / чат: снизу |
Вверху / внизу страницы /chat (активно только при включенном встроенном чате). |
Пример — добавление карточки-баннера в верхнюю часть страницы Сессии:
function PinnedSessionsBanner() {
return React.createElement(Card, null,
React.createElement(CardContent, { className: "py-2 text-xs" },
"Закреплённая заметка, внедрённая my-plugin"),
);
}
window.__HERMES_PLUGINS__.registerSlot("my-plugin", "sessions:top", PinnedSessionsBanner);
Сочетайте слоты на странице с tab.hidden: true, если ваш плагин только дополняет добавляемую страницу и не нуждается во вкладке внутренней панели.
Оболочка отображает <PluginSlot name="..." /> только для указанных выше слотов. Дополнительные имена реестра для вложенных плагинов пользовательского интерфейса — плагин может предоставлять свои собственные слоты через SDK.comComponents.PluginSlot.
Повторная регистрация и HMR
Если одна и та же пара (plugin, slot) регистрируется случайно, более поздний вызов заменяет более ранний — это соответствует тому, как React HMR ожидает поведения перемонтирования плагинов.
Замена встроенных страниц (tab.override)
Установка tab.override на проход встроенного маршрута позволяет компоненту заменить эту страницу вместо добавления новых вкладок. Полезно, когда тема хочет пользовательскую домашнюю страницу (/), но хочет сохранить остальную часть панели нетронутой.
{
"name": "my-home",
"label": "Домой",
"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). См. полный каталог в Слоты обработки → Каталог слотов.
Минимальный пример — закрепить баннер в верхней части страницы. Сессии:
// ~/.hermes/plugins/session-notes/dashboard/manifest.json
{
"name": "session-notes",
"label": "Заметки сессий",
"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" },
"Не забудьте пометить важные сессии перед архивированием."),
);
}
// Заглушка для скрытой вкладки.
window.__HERMES_PLUGINS__.register("session-notes", function () { return null; });
// Настоящая работа.
window.__HERMES_PLUGINS__.registerSlot("session-notes", "sessions:top", Banner);
})();
Ключевые моменты:
— tab.hidden: true собирает плагин из боковой панели — у него нет отдельной страницы.
- Поле «слотов» в манифесте предназначено только для документации. Фактическое соединение происходит в JS-бандле через registerSlot().
- Несколько плагинов могут претендовать на один и тот же слот на странице. Они предоставлены в порядке регистрации.
- Нулевой след, когда ни один плагин не зарегистрирован: встроенная страница отображается точно так же, как и раньше.
Справочный плагин (example-dashboard в hermes-example-plugins) позволяет с активной демонстрацией, которая внедряет баннер в sessions:top — установите его, чтобы увидеть шаблон от начала до конца.
Плагины только со слотами (tab.hidden)
Когда tab.hidden: true, плагин регистрирует свой компонент (для прямого посещения URL) и любые слоты, но никогда не добавляйте вкладку в навигацию. Использованы плагины, которые существуют только для обеспечения питания в слотах — герб в заголовке, HUD в задней панели, наложение.
{
"name": "header-crest",
"label": "Герб заголовка",
"tab": {
"path": "/header-crest",
"position": "end",
"hidden": true
},
"slots": ["header-left"],
"entry": "dist/index.js"
}
Бандл всё равно вызывает register() с компонентом-заглушкой (хорошая практика на всякий случай, если кто-то перейдёт по URL напрямую), а затем registerSlot() для выполнения реальной работы.
Серверные API-маршруты
Плагины могут регистрировать маршруты FastAPI, установленные в манифесте API. Создайте файл и экспортируйте 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-*, --comComponent-<bucket>-*, --radius, --spacing-mul). Ссылайтесь на них, и ваш штекер автоматически перекрашивается в соответствии с активной темой.
Обнаружение и перезагрузка плагинов
Панель сканирует три каталога на наличие dashboard/manifest.json:
| Приоритет | Директория | Источник |
|---|---|---|
| 1 (побеждает при конфликте) | ~/.hermes/plugins/<имя>/dashboard/ |
пользователь |
| 2 | <repo>/plugins/memory/<имя>/dashboard/ |
в комплекте |
| 2 | <repo>/plugins/<имя>/dashboard/ |
в комплекте |
| 3 | ./.hermes/plugins/<имя>/dashboard/ |
project — только когда установлен HERMES_ENABLE_PROJECT_PLUGINS |
Результаты обнаружения кэшируются на каждой панели процесса. После добавления нового плагина либо:
# Принудительное повторное сканирование без перезапуска
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
…либо перезапустите приборную панель Гермеса.
Жизненный цикл загрузки плагина
- Панель загружается.
main.tsxпредоставляет SDK дляwindow.__HERMES_PLUGIN_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)для каждого слота. - Панель содержит зарегистрированный компонент в манифесте, включает вкладку для навигации (если она не «скрыта») и монтирует компонент по маршруту.
У плагинов есть 2 секунды после загрузки скрипта, чтобы вызвать register(). После этого панель перестаёт ждать и завершает начальный рендеринг. Если плагин подключится позже, он всё равно будет выглядеть — навигация реактивна.
Если плагин скрипта не загружается (404, синтаксическая ошибка, добавление во время IIFE), панель записывает предупреждение в консоль браузера и продолжает работу без него.
Демо обнаружения темы и плагина
Плагин strike-freedom-cockpit (сопутствующий репозиторий hermes-example-plugins) — это полная демо-версия. Он выбирает YAML-тему с плагином только для слотов, чтобы создать HUD в стиле кабины без вилки панели.
Что он приезжает:
- Полную тему с использованием палитры, типографики,
fontUrl,layoutVariant: Cockpit,assets,comComponentStyles(зубчатые преобразования карточек, градиентные фоны),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>/* |
* | Серверные маршруты, зарегистрированные плагином. |
SDK на window
| Глобальная переменная | Тип | Поставщик |
|---|---|---|
window.__HERMES_PLUGIN_SDK__ |
object | registry.ts — React, хуки, 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 есть ошибка парсинга, панель записывает её в errors.log в ~/.hermes/logs/.
Вкладка моего плагина не отображается.
1. Проверьте, что манифест находится по пути ~/.hermes/plugins/<name>/dashboard/manifest.json (обратите внимание на поддиректорию dashboard/).
2. curl http://127.0.0.1:9119/api/dashboard/plugins/rescan для принудительного повторного обнаружения.
3. Откройте инструменты разработчика браузера → Сеть — убедитесь, что manifest.json, index.js и любой CSS загрузились без 404.
4. Откройте инструменты разработчика браузера → Консоль — ищите ошибки во время IIFE или window.__HERMES_PLUGINS__ is undefined (указывает на то, что SDK не инициализировался, обычно из-за сбоя рендеринга React ранее).
5. Убедитесь, что ваш бандл вызывает window.__HERMES_PLUGINS__.register(...) с тем же именем, что и manifest.json:name.
Компоненты, зарегистрированные в слотах, не отображаются.
Слот sidebar отображается только тогда, когда активная тема имеет layoutVariant: cockpit. Остальные слоты отображаются всегда. Если вы регистрируетесь в слоте без попаданий, добавьте console.log внутри registerSlot, чтобы подтвердить, что бандл плагина вообще выполнился.
Серверные маршруты плагина возвращают 404.
1. Убедитесь, что в манифесте есть "api": "plugin_api.py", указывающий на существующий файл внутри dashboard/.
2. Перезапустите hermes dashboard — API-маршруты плагина монтируются один раз при запуске, не при повторном сканировании.
3. Проверьте, что plugin_api.py экспортирует router = APIRouter() на уровне модуля. Другие имена экспорта не подхватываются.
4. Просмотрите ~/.hermes/logs/errors.log на предмет Failed to load plugin <name> API routes — ошибки импорта записываются туда.
Смена темы сбрасывает мои переопределения цветов.
colorOverrides привязаны к активной теме и очищаются при переключении темы — это сделано намеренно. Если вам нужны переопределения, которые сохраняются, поместите их в YAML вашей темы, а не в живой переключатель.
customCSS темы обрезается.
Блок customCSS ограничен 32 КБ на тему. Разделите большие таблицы стилей на несколько тем или переключитесь на плагин, который внедряет полную таблицу стилей через своё поле css (без ограничения размера).
Я хочу опубликовать плагин на PyPI.
Плагины панели устанавливаются по структуре директории, а не через точку входа pip. Самый чистый путь распространения на сегодня — это git-репозиторий, который пользователь клонирует в ~/.hermes/plugins/. Установщик на основе pip для плагинов панели в настоящее время не подключён.