Веб-панель Hermes (hermes dashboard) создана для переоформления и расширения без форка репозитория. Доступны три слоя:
Themes — YAML-файлы, которые перекрашивают палитру, типографику, макет и хром компонентов панели. Поместите файл в ~/.hermes/dashboard-themes/ — он появится в переключателе тем.
UI плагины — директория с manifest.json + JavaScript-бандлом, который регистрирует вкладку, заменяет встроенную страницу, дополняет её через слоты области страницы или внедряет компоненты в именованные слоты оболочки.
Backend плагины — Python-файл внутри директории плагина, который предоставляет FastAPI router; маршруты монтируются по адресу /api/plugins/<name>/ и вызываются из UI плагина.
Все три устанавливаются на лету: не требуется клонирование репозитория, npm run build или патчинг исходного кода панели. Эта страница является каноническим справочником для всех трёх.
Если вы просто хотите использовать панель, см. Web Dashboard. Если вы хотите переоформить терминальный CLI (не веб-панель), см. Skins & Themes — система скинов CLI не связана с темами панели.
note Как части сочетаются
Темы и плагины независимы, но синергичны. Тема может существовать сама по себе (просто YAML-файл). Плагин может существовать сам по себе (просто вкладка). Вместе они позволяют создать полный визуальный рескин с собственными HUD — демонстрационный бандл strike-freedom-cockpit делает именно это. См. Комбинированная тема + демо плагина.
Темы — это YAML-файлы, хранящиеся в ~/.hermes/dashboard-themes/. Имя файла не имеет значения (система использует поле name: темы), но по соглашению используется <name>.yaml. Каждое поле необязательно — отсутствующие ключи возвращаются к встроенной теме default, поэтому тема может состоять всего из одного цвета.
Быстрый старт — ваша первая тема
mkdir-p~/.hermes/dashboard-themes
# ~/.hermes/dashboard-themes/neon.yamlname:neonlabel:Neondescription:Pure magenta on blackpalette:background:"#000000"midground:"#ff00ff"
Обновите панель. Нажмите на иконку палитры в заголовке и выберите Neon. Фон станет чёрным, текст и акценты — малиновыми, а все производные цвета (карточка, граница, приглушённый, кольцо и т.д.) будут пересчитаны из этого триплета из 2 цветов через color-mix() в CSS.
Это весь процесс: один файл, два цвета. Всё ниже — необязательные улучшения.
Палитра, типографика, макет
Эти три блока являются основой темы. Каждый независим — переопределите один, оставьте другие.
Палитра (3 слоя)
Палитра — это триплет цветовых слоёв плюс цвет тёплого свечения и множитель шумовой текстуры. Каскад дизайн-системы панели выводит каждый shadcn-совместимый токен (card, popover, muted, border, primary, destructive, ring и т.д.) из этого триплета через CSS color-mix(). Переопределение трёх цветов каскадно меняет весь интерфейс.
Ключ
Описание
palette.background
Самый глубокий цвет холста — обычно почти чёрный. Определяет фон страницы и заливку карточек.
palette.midground
Основной текст и акцент. Большинство элементов интерфейса используют его (основной текст, контуры кнопок, кольца фокуса).
palette.foreground
Верхний слой подсветки. Стандартная тема устанавливает его белым с альфой 0 (невидимый); темы, желающие яркий акцент сверху, могут увеличить его альфу.
palette.warmGlow
Строка rgba(...), используемая как цвет виньетки компонентом <Backdrop />.
palette.noiseOpacity
Множитель 0–1.2 для шумовой текстуры. Меньше = мягче, больше = зернистее.
Каждый слой принимает либо {hex: "#RRGGBB", alpha: 0.0–1.0}, либо простую hex-строку (альфа по умолчанию 1.0).
palette:background:hex:"#05091a"alpha:1.0midground:"#d8f0ff"# bare hex, alpha = 1.0foreground:hex:"#ffffff"alpha:0# invisible top layerwarmGlow:"rgba(255,199,55,0.24)"noiseOpacity:0.7
Типографика
Ключ
Тип
Описание
fontSans
string
CSS-список семейств шрифтов для основного текста (применяется к html, body).
fontMono
string
CSS-список семейств шрифтов для блоков кода, <code>, утилит .font-mono.
fontDisplay
string
Необязательный стек для заголовков/дисплея. Возвращается к fontSans.
fontUrl
string
Необязательный URL внешней таблицы стилей. Внедряется как <link rel="stylesheet"> в <head> при переключении темы. Один и тот же URL никогда не внедряется дважды. Работает с Google Fonts, Bunny Fonts, самостоятельными @font-face таблицами — всем, на что можно дать ссылку.
baseSize
string
Корневой размер шрифта — управляет шкалой rem. Например "14px", "16px".
lineHeight
string
Межстрочный интервал по умолчанию. Например "1.5", "1.65".
letterSpacing
string
Межбуквенный интервал по умолчанию. Например "0", "0.01em", "-0.01em".
любая CSS длина ("0", "0.25rem", "0.5rem", "1rem", ...)
Токен радиуса углов. Отображается в --radius и каскадно переходит в --radius-sm/md/lg/xl — все скруглённые элементы меняются вместе.
density
compact | comfortable | spacious
Множитель отступов, применяемый как CSS-переменная --spacing-mul. compact = 0.85×, comfortable = 1.0× (по умолчанию), spacious = 1.2×. Масштабирует базовые отступы Tailwind, поэтому padding, gap и space-between пропорционально сдвигаются.
layout:radius:"0"density:compact
Варианты макета
layoutVariant выбирает общий макет оболочки. По умолчанию "standard" при отсутствии.
Вариант
Поведение
standard
Одна колонка, максимальная ширина 1600px (по умолчанию).
cockpit
Левая боковая панель (260px) + основной контент. Заполняется плагинами через слот sidebar — см. Слоты оболочки. Без плагина панель показывает заглушку.
tiled
Убирает ограничение максимальной ширины, позволяя страницам использовать всю ширину окна.
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"# auto-wired into <Backdrop />hero:"/my-images/strike-freedom.png"# for plugin sidebarscrest:"/my-images/crest.svg"# for header-left pluginslogo:"/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:
// In a plugin slotconsthero=getComputedStyle(document.documentElement).getPropertyValue("--theme-asset-hero").trim();
Переопределение хрома компонентов
componentStyles переопределяет стили отдельных компонентов оболочки без написания CSS-селекторов. Записи каждой группы становятся CSS-переменными (--component-<bucket>-<kebab-property>), которые читают общие компоненты оболочки. Таким образом, переопределения card: применяются к каждому <Card>, header: — к панели приложения и т.д.
Имена свойств пишутся в camelCase (clipPath) и выводятся в kebab-case (clip-path). Значения — обычные CSS-строки — всё, что принимает CSS (clip-path, border-image, background, box-shadow, animation, ...).
Переопределение цветов
Большинству тем это не понадобится — 3-слойная палитра выводит каждый shadcn-токен. Используйте colorOverrides, когда нужен определённый акцент, который вывод не даёт (более мягкий красный для разрушительного действия в пастельной теме, определённый зелёный успеха для бренда).
Каждый ключ отображается 1:1 в CSS-переменную --color-<kebab> (например, primaryForeground → --color-primary-foreground). Любой ключ, установленный здесь, переопределяет каскад палитры только для активной темы — переключение на другую тему очищает переопределения.
Пользовательский CSS (customCSS)
Для стилизации на уровне селекторов, которую componentStyles не может выразить — псевдоэлементы, анимации, медиа-запросы, переопределения в рамках темы — поместите обычный CSS в customCSS:
CSS внедряется как один изолированный тег <style data-hermes-theme-css> при применении темы и удаляется при переключении. Ограничение: 32 КБ на тему.
Встроенные темы
Каждая встроенная тема поставляется с собственной палитрой, типографикой и макетом — переключение даёт видимые изменения, выходящие за рамки только цвета.
Тема
Палитра
Типографика
Макет
Hermes Teal (default)
Тёмный бирюзовый + кремовый
Системный стек, 15px
Радиус 0.5rem, comfortable
Hermes Teal (Large) (default-large)
Такой же, как default
Системный стек, 18px, межстрочный 1.65
Радиус 0.5rem, spacious
Midnight (midnight)
Глубокий сине-фиолетовый
Inter + JetBrains Mono, 14px
Радиус 0.75rem, comfortable
Ember (ember)
Тёплый малиновый + бронзовый
Spectral (serif) + IBM Plex Mono, 15px
Радиус 0.25rem, comfortable
Mono (mono)
Оттенки серого
IBM Plex Sans + IBM Plex Mono, 13px
Радиус 0, compact
Cyberpunk (cyberpunk)
Неоново-зелёный на чёрном
Share Tech Mono везде, 14px
Радиус 0, compact
Rosé (rose)
Розовый + слоновая кость
Fraunces (serif) + DM Mono, 16px
Радиус 1rem, spacious
Темы, ссылающиеся на Google Fonts (все кроме Hermes Teal), загружают таблицу стилей по требованию — при первом переключении на них тег <link> внедряется в <head>.
Полный справочник YAML темы
Все настройки в одном файле — скопируйте и удалите то, что не нужно:
# ~/.hermes/dashboard-themes/ocean.yamlname:oceanlabel:Ocean Deepdescription:Deep sea blues with coral accents# 3-layer palette (accepts {hex, alpha} or bare hex)palette:background:hex:"#0a1628"alpha:1.0midground:hex:"#a8d0ff"alpha:1.0foreground:hex:"#ffffff"alpha:0.0warmGlow:"rgba(255,107,107,0.35)"noiseOpacity:0.7typography:fontSans:"Poppins,system-ui,sans-serif"fontMono:"FiraCode,ui-monospace,monospace"fontDisplay:"Poppins,system-ui,sans-serif"# optionalfontUrl:"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:comfortablelayoutVariant:standard# standard | cockpit | tiledassets: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:"inset0001pxrgba(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:|/* Any additional selector-level tweaks */
Обновите панель после создания файла. Переключайте темы в реальном времени из панели заголовка — нажмите на иконку палитры. Выбор сохраняется в config.yaml в разделе dashboard.theme и восстанавливается при перезагрузке.
Плагины
Плагин панели — это директория с manifest.json, предварительно собранным JS-бандлом, опционально CSS-файлом и Python-файлом с FastAPI-маршрутами. Плагины располагаются рядом с другими плагинами Hermes в ~/.hermes/plugins/<name>/ — расширение панели находится в подпапке dashboard/ внутри директории плагина, так что один плагин может расширять и CLI/шлюз, и панель из одной установки.
Плагины не включают React или UI-компоненты. Они используют SDK плагина, доступный через window.__HERMES_PLUGIN_SDK__. Это сохраняет бандлы плагинов крошечными (обычно несколько КБ) и предотвращает конфликты версий.
Напишите JS-бандл (обычный IIFE — этап сборки не требуется):
// ~/.hermes/plugins/my-plugin/dashboard/dist/index.js(function(){"use strict";constSDK=window.__HERMES_PLUGIN_SDK__;const{React}=SDK;const{Card,CardHeader,CardTitle,CardContent}=SDK.components;functionMyPage(){returnReact.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-выводом. Единственное требование — конечный файл должен быть одним JS-файлом, загружаемым через <script>. React никогда не встраивается; он берётся из SDK.React.
Ни одно из них не обязательно; включайте только те слои, которые вам нужны.
Справочник манифеста
{"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
Нет
Строка семантического версионирования. По умолчанию 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
Нет
Путь к Python-файлу с FastAPI-маршрутами. Монтируется по адресу /api/plugins/<name>/.
Доступные иконки
Плагины используют имена иконок Lucide. Панель отображает их по имени — неизвестные имена незаметно возвращаются к Puzzle.
Нужна другая иконка? Откройте PR в ICON_MAP в web/src/App.tsx — чистое аддитивное изменение.
SDK плагина
Всё, что нужно плагину, находится в window.__HERMES_PLUGIN_SDK__. Плагины не должны импортировать React напрямую.
constSDK=window.__HERMES_PLUGIN_SDK__;// React + hooksSDK.React// the React instanceSDK.hooks.useStateSDK.hooks.useEffectSDK.hooks.useCallbackSDK.hooks.useMemoSDK.hooks.useRefSDK.hooks.useContextSDK.hooks.createContext// UI components (shadcn/ui primitives)SDK.components.CardSDK.components.CardHeaderSDK.components.CardTitleSDK.components.CardContentSDK.components.BadgeSDK.components.ButtonSDK.components.InputSDK.components.LabelSDK.components.SelectSDK.components.SelectOptionSDK.components.SeparatorSDK.components.TabsSDK.components.TabsListSDK.components.TabsTriggerSDK.components.PluginSlot// render a named slot (useful for nested plugin UIs)// Hermes API client + raw fetcherSDK.api// typed client — getStatus, getSessions, getConfig, ...SDK.fetchJSON// raw fetch for custom endpoints (plugin-registered routes)// UtilitiesSDK.utils.cn// Tailwind class merger (clsx + twMerge)SDK.utils.timeAgo// "5m ago" from unix timestampSDK.utils.isoTimeAgo// "5m ago" from ISO string// HooksSDK.useI18n// i18n hook for multi-language plugins
Слоты позволяют плагину вставлять компоненты в именованные места оболочки приложения — боковую панель, заголовок, подвал, слой наложения — не занимая целую вкладку. Несколько плагинов могут заполнять один и тот же слот; они отображаются стопкой в порядке регистрации.
Слоты уровня оболочки (отображаются в любом месте хрома приложения):
Слот
Расположение
backdrop
Внутри стека слоёв <Backdrop />, над слоем шума.
header-left
Перед брендом Hermes в верхней панели.
header-right
Перед переключателями темы/языка в верхней панели.
header-banner
Полноширинная полоса под навигацией.
sidebar
Панель кабины — отображается только когда layoutVariant === "cockpit".
pre-main
Выше выхода маршрута (внутри <main>).
post-main
Ниже выхода маршрута (внутри <main>).
footer-left
Содержимое ячейки подвала (заменяет умолчание).
footer-right
Содержимое ячейки подвала (заменяет умолчание).
overlay
Фиксированный слой поверх всего остального. Полезно для хрома (сканлайны, виньетки), который customCSS не может достичь сам по себе.
Слоты области страницы (отображаются только на указанной встроенной странице — используйте их для вставки виджетов, карточек или панелей инструментов на существующую страницу без переопределения всего маршрута):
Вверху / внизу страницы /chat (активно только когда встроенный чат включён).
Пример — добавьте баннер-карточку в верхнюю часть страницы Sessions:
functionPinnedSessionsBanner(){returnReact.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 в путь встроенного маршрута заставляет компонент плагина заменять эту страницу вместо добавления новой вкладки. Полезно, когда тема хочет иметь собственную домашнюю страницу (/), но сохранить остальную часть панели нетронутой.
Исходный компонент страницы по адресу / удаляется из маршрутизатора.
Ваш плагин отображается по адресу / вместо него.
Навигационная вкладка для tab.path не добавляется (смысл переопределения именно в этом).
Только один плагин может переопределить данный путь. Если два плагина претендуют на одно и то же переопределение, побеждает первый, а второй игнорируется с предупреждением в режиме разработки.
Если вам нужно только добавить карточку или панель инструментов на существующую страницу без её захвата, используйте слоты области страницы.
Дополнение встроенных страниц (слоты области страницы)
Полная замена через tab.override слишком тяжеловесна — ваш плагин теперь владеет всей страницей, включая любые будущие обновления, которые мы в неё добавим. В большинстве случаев вы просто хотите добавить баннер, карточку или панель инструментов на существующую страницу. Для этого предназначены слоты области страницы.
Каждая встроенная страница предоставляет слоты <page>:top и <page>:bottom, отображаемые вверху и внизу области контента. Ваш плагин заполняет один из них вызовом registerSlot() — встроенная страница продолжает работать normalmente, а ваш компонент отображается рядом с ней.
Доступные слоты: sessions:*, analytics:*, logs:*, cron:*, skills:*, config:*, env:*, docs:*, chat:* (каждый с :top и :bottom). Полный каталог см. в Слоты оболочки → Каталог слотов.
Минимальный пример — закрепить баннер в верхней части страницы Sessions:
// ~/.hermes/plugins/session-notes/dashboard/dist/index.js(function(){constSDK=window.__HERMES_PLUGIN_SDK__;const{React}=SDK;const{Card,CardContent}=SDK.components;functionBanner(){returnReact.createElement(Card,null,React.createElement(CardContent,{className:"py-2 text-xs"},"Remember to label important sessions before archiving."),);}// Placeholder for the hidden tab.window.__HERMES_PLUGINS__.register("session-notes",function(){returnnull;});// The real work.window.__HERMES_PLUGINS__.registerSlot("session-notes","sessions:top",Banner);})();
Ключевые моменты:
tab.hidden: true убирает плагин из боковой панели — у него нет отдельной страницы.
Поле манифеста slots предназначено только для документации. Фактическое связывание происходит в JS-бандле через registerSlot().
Несколько плагинов могут претендовать на один и тот же слот области страницы. Они отображаются стопкой в порядке регистрации.
Нулевой след, когда ни один плагин не зарегистрирован: встроенная страница отображается точно так же, как и раньше.
Эталонный плагин (example-dashboard в hermes-example-plugins) содержит живое демо, которое вставляет баннер в sessions:top — установите его, чтобы увидеть шаблон от начала до конца.
Плагины только со слотами (tab.hidden)
Когда tab.hidden: true, плагин регистрирует свой компонент (для прямых посещений URL) и любые слоты, но никогда не добавляет вкладку в навигацию. Используется плагинами, которые существуют только для внедрения в слоты — герб заголовка, HUD боковой панели, наложение.
Бандл всё равно вызывает register() с компонентом-заглушкой (хорошая практика на случай, если кто-то перейдёт по URL напрямую), а затем registerSlot() для реальной работы.
Backend API маршруты
Плагины могут регистрировать FastAPI-маршруты, установив api в манифесте. Создайте файл и экспортируйте router:
Маршруты монтируются по адресу /api/plugins/<name>/, поэтому вышеуказанные становятся:
GET /api/plugins/my-plugin/data
POST /api/plugins/my-plugin/action
API-маршруты плагинов обходят аутентификацию по токену сессии, так как сервер панели по умолчанию привязывается к localhost. Не открывайте панель на публичном интерфейсе с --host 0.0.0.0, если запускаете ненадёжные плагины — их маршруты также станут доступны.
Доступ к внутренностям Hermes
Backend-маршруты выполняются внутри процесса панели, поэтому они могут импортировать из кодовой базы hermes-agent напрямую:
Если вашему плагину нужны стили, выходящие за рамки Tailwind-классов и встроенного style=, добавьте CSS-файл и укажите его в манифесте:
{"css":"dist/style.css"}
Файл внедряется как тег <link> при загрузке плагина. Используйте конкретные имена классов, чтобы избежать конфликтов со стилями панели, и ссылайтесь на CSS-переменные панели, чтобы оставаться в курсе темы:
Панель предоставляет каждый 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
Результаты обнаружения кэшируются на время процесса панели. После добавления нового плагина либо:
# Force a rescan without restart
curlhttp://127.0.0.1:9119/api/dashboard/plugins/rescan
…либо перезапустите hermes dashboard.
Жизненный цикл загрузки плагина
Панель загружается. 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) для каждого слота.
Панель сопоставляет зарегистрированный компонент с манифестом, добавляет вкладку в навигацию (если не hidden) и монтирует компонент как маршрут.
У плагинов есть до 2 секунд после загрузки скрипта, чтобы вызвать register(). После этого панель перестаёт ждать и завершает начальный рендеринг. Если плагин регистрируется позже, он всё равно появится — навигация реактивна.
Если скрипт плагина не загружается (404, синтаксическая ошибка, исключение во время IIFE), панель записывает предупреждение в консоль браузера и продолжает работу без него.
Комбинированная тема + демо плагина
Плагин strike-freedom-cockpit (репозиторий-компаньон hermes-example-plugins) — это полная демонстрация переоформления. Он объединяет YAML-тему с плагином только со слотами для создания HUD в стиле кабины без форка панели.
Откройте панель, выберите Strike Freedom в переключателе тем. Появится боковая панель кабины, герб отобразится в заголовке, строка заменит подвал. Переключитесь обратно на Hermes Teal — плагин останется установленным, но станет невидимым (слот sidebar отображается только при варианте макета cockpit).
Прочитайте исходный код плагина (strike-freedom-cockpit/dashboard/dist/index.js в репозитории-компаньоне), чтобы увидеть, как он читает CSS-переменные, защищается от старых панелей без поддержки слотов и регистрирует три слота из одного бандла.
Справочник API
Endpoints тем
Endpoint
Метод
Описание
/api/dashboard/themes
GET
Список доступных тем + имя активной. Встроенные возвращают {name, label, description}; пользовательские темы также включают поле definition с полным нормализованным объектом темы.
/api/dashboard/theme
PUT
Установить активную тему. Тело: {"name": "midnight"}. Сохраняется в config.yaml в разделе dashboard.theme.
Endpoints плагинов
Endpoint
Метод
Описание
/api/dashboard/plugins
GET
Список обнаруженных плагинов (с манифестами, без внутренних полей).
/api/dashboard/plugins/rescan
GET
Принудительное повторное сканирование директорий плагинов без перезапуска.
/dashboard-plugins/<name>/<path>
GET
Обслуживание статических ресурсов из директории dashboard/ плагина. Path traversal заблокирован.
Моя тема не отображается в переключателе.
Проверьте, что файл находится в ~/.hermes/dashboard-themes/ и заканчивается на .yaml или .yml. Обновите страницу. Выполните curl http://127.0.0.1:9119/api/dashboard/themes — ваша тема должна быть в ответе. Если в YAML есть ошибка парсинга, панель записывает её в errors.log в ~/.hermes/logs/.
Вкладка моего плагина не отображается.
Проверьте, что манифест находится по адресу ~/.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).
Убедитесь, что ваш бандл вызывает window.__HERMES_PLUGINS__.register(...) с тем же именем, что и manifest.json:name.
Компоненты, зарегистрированные в слотах, не отображаются.
Слот sidebar отображается только когда активная тема имеет layoutVariant: cockpit. Другие слоты отображаются всегда. Если вы регистрируетесь в слот без попаданий, добавьте console.log внутри registerSlot, чтобы подтвердить, что бандл плагина вообще запустился.
Backend-маршруты плагина возвращают 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 вашей темы, а не в живой переключатель.
Пользовательский CSS темы обрезается.
Блок customCSS ограничен 32 КБ на тему. Разделите большие таблицы стилей на несколько тем или переключитесь на плагин, который внедряет полную таблицу стилей через поле css (без ограничения размера).
Я хочу опубликовать плагин на PyPI.
Плагины панели устанавливаются по структуре директорий, а не через точку входа pip. Самый чистый путь распространения на сегодня — git-репозиторий, который пользователь клонирует в ~/.hermes/plugins/. Установщик на основе pip для плагинов панели в настоящее время не настроен.