sidebar_position: 17 title: "Extending the Dashboard" description: "Build themes and plugins for the Hermes web dashboard — palettes, typography, layouts, custom tabs, shell slots, page-scoped slots, and backend API routes" lang: ru
Расширение панели инструментов
Веб-панель Hermes (hermes dashboard) создана для изменения оформления и расширения без разделения кодовой базы. Обнажаются три слоя:
- Темы — файлы YAML, которые перерисовывают палитру, типографику, макет и хромирование каждого компонента панели управления. Перетащите файл в
~/.hermes/dashboard-themes/; он появляется в переключателе тем. - Плагины пользовательского интерфейса — каталог с
manifest.json+ пакет JavaScript, который регистрирует вкладку, заменяет встроенную страницу, дополняет ее через слоты на уровне страницы или внедряет компоненты в именованные слоты оболочки. - Бэкенд-плагины — файл Python внутри каталога плагинов, который предоставляет FastAPI
router; маршруты монтируются под/api/plugins/<name>/и вызываются из пользовательского интерфейса плагина.
Все три подключаются во время выполнения: нет клонирования репозитория, нет npm run build, нет исправлений исходного кода информационной панели. Эта страница является канонической ссылкой для всех трех.
Если вы просто хотите использовать панель мониторинга, см. Веб-панель мониторинга. Если вы хотите изменить оформление интерфейса командной строки терминала (а не веб-панели управления), см. раздел Скины и темы — система оформления интерфейса командной строки не связана с темами информационной панели.
📝 Note
Как складываются фигуры Темы и плагины независимы, но синергичны. Тема может быть автономной (просто файл YAML). Плагин может стоять отдельно (просто вкладка). Вместе они позволяют вам создать полную визуальную перерисовку с помощью специальных HUD — включенная в комплект демоверсия `strike-freedom-cockpit` делает именно это. См. [Демо-версия комбинированной темы + плагина](#combined-theme--plugin-demo).Содержание
- Темы
- Быстрый старт — ваша первая тема
- Палитра, типографика, макет
- Варианты макета
- Ресурсы темы (изображения в виде переменных CSS)
- Переопределения хрома компонентов
- Переопределения цвета
- Raw
customCSS - Встроенные темы
- Полная ссылка на YAML темы
- Плагины
- Быстрый старт — ваш первый плагин
- Макет каталога
- Ссылка на манифест
- The Plugin SDK
- Слоты для оболочек
- Замена встроенных страниц (
tab.override) - Дополнение встроенных страниц (слотов на уровне страницы)
- Плагины только для слотов (
tab.hidden) - Маршруты Backend API
- Пользовательский CSS для каждого плагина
- Обнаружение и перезагрузка плагина
- Объединенная тема + демо-версия плагина
- ссылка на API
- Устранение неполадок
Темы
Темы — это файлы YAML, хранящиеся в ~/.hermes/dashboard-themes/. Имя файла не имеет значения (поле name: темы — это то, что использует система), но соглашение — <name>.yaml. Каждое поле является необязательным — отсутствующие ключи относятся к встроенной теме default, поэтому тема может содержать всего один цвет.
Быстрый старт — ваша первая тема
mkdir -p ~/.hermes/dashboard-themes
# ~/.hermes/dashboard-themes/neon.yaml
name: neon
label: Neon
description: Pure magenta on black
palette:
background: "#000000"
midground: "#ff00ff"
Обновите панель мониторинга. Нажмите значок палитры в заголовке и выберите Неон. Фон становится черным, текст и акценты становятся пурпурными, и каждый производный цвет (карточка, рамка, приглушенный цвет, кольцо и т. д.) пересчитывается из этого двухцветного триплета с помощью color-mix() в CSS.
Вот и весь онбординг: один файл, два цвета. Все, что ниже, является необязательной доработкой.
Палитра, типографика, верстка
Эти три блока являются сердцем темы. Каждый из них независим: отмените один, оставьте остальные.
Палитра (3-слойная)
Палитра представляет собой тройку цветовых слоев, а также цвет виньетки теплого свечения и множитель зернистости шума. Каскад системы дизайна информационной панели извлекает каждый токен, совместимый с Shadcn (карточка, поповер, отключенный звук, граница, основной, деструктивный, кольцевой и т. д.) из этого триплета через 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}, либо пустую шестнадцатеричную строку (по умолчанию значение альфа равно 1,0).
palette:
background:
hex: "#05091a"
alpha: 1.0
midground: "#d8f0ff" # bare hex, alpha = 1.0
foreground:
hex: "#ffffff"
alpha: 0 # invisible top layer
warmGlow: "rgba(255, 199, 55, 0.24)"
noiseOpacity: 0.7
Типографика
| Ключ | Тип | Описание |
|---|---|---|
fontSans |
строка | Стек семейства шрифтов CSS для основного текста (применяется к html, body). |
fontMono |
строка | Стек семейств шрифтов CSS для блоков кода, утилит <code>, .font-mono. |
fontDisplay |
строка | Дополнительный стек заголовков/отображений. Возвращается к fontSans. |
fontUrl |
строка | Необязательный URL-адрес внешней таблицы стилей. Внедряется как <link rel="stylesheet"> в <head> при переключении темы. Один и тот же URL-адрес никогда не вводится дважды. Работает с Google Fonts, Bunny Fonts, самостоятельными листами @font-face — со всем, что можно связать. |
baseSize |
строка | Размер основного шрифта — управляет масштабом. Например. "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"
Макет
| Ключ | Ценности | Описание |
|---|---|---|
radius |
любая длина 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, поэтому отступы, пробелы и пространство между утилитами смещаются пропорционально. |
layout:
radius: "0"
density: compact
Варианты планировки
layoutVariant выбирает общую компоновку оболочки. По умолчанию используется "standard", если он отсутствует.
| Вариант | Поведение |
|---|---|
standard |
Один столбец, максимальная ширина 1600 пикселей (по умолчанию). |
cockpit |
Левая боковая панель (260 пикселей) + основной контент. Заполняется плагинами через слот 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 sidebars
crest: "/my-images/crest.svg" # for header-left plugins
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:
// In a plugin slot
const hero = getComputedStyle(document.documentElement)
.getPropertyValue("--theme-asset-hero").trim();
Переопределения хрома компонентов
componentStyles изменяет стиль отдельных компонентов оболочки без написания селекторов CSS. Записи каждого сегмента становятся переменными CSS (--component-<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.
В именах свойств используется верблюжий регистр (clipPath) и они оформляются как кебаб (clip-path). Значения представляют собой простые строки CSS — все, что CSS принимает (clip-path, border-image, background, box-shadow, animation, ...).
Переопределение цвета
Большинству тем это не понадобится — трехслойная палитра извлекает каждый токен 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, secondary, secondaryForeground, muted, mutedForeground, accent, accentForeground, destructive, destructiveForeground, success, warning, border, input, ring.
Каждая клавиша сопоставляется 1:1 с переменной CSS --color-<kebab> (например, primaryForeground → --color-primary-foreground). Любой ключ, установленный здесь, имеет приоритет над каскадом палитр только для активной темы — переключение на другую тему очищает переопределения.
Сырой customCSS
Для Chrome на уровне селектора, который componentStyles не может выразить — псевдоэлементы, анимация, медиа-запросы, переопределения на уровне темы — добавьте необработанный CSS в customCSS:
customCSS: |
/* Scanline overlay — only visible when cockpit variant is active. */
: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 КиБ на тему.
Встроенные темы
Каждая встроенная функция имеет собственную палитру, типографику и макет — переключение приводит к видимым изменениям, выходящим за рамки одного лишь цвета.
| Тема | Палитра | Типография | Макет |
|---|---|---|---|
Гермес Тил (default) |
Темно-бирюзовый + кремовый | Системный стек, 15 пикселей | Радиус 0,5 rem, удобно |
Полночь (midnight) |
Глубокий сине-фиолетовый | Интер + JetBrains Mono, 14 пикселей | Радиус 0,75 rem, удобно |
Эмбер (ember) |
Теплый малиновый + бронза | Спектральный (с засечками) + IBM Plex Mono, 15 пикселей | Радиус 0,25 rem, удобно |
Моно (mono) |
оттенки серого | IBM Plex Sans + IBM Plex Mono, 13 пикселей | радиус 0, компактный |
Киберпанк (cyberpunk) |
Неоново-зеленый на черном | Поделитесь Tech Mono везде, 14 пикселей | радиус 0, компактный |
Розовое (rose) |
Розовый + слоновая кость | Fraunces (засечки) + DM Mono, 16 пикселей | Радиус 1 метр, просторный |
Темы, которые ссылаются на шрифты Google (все, кроме Hermes Teal), загружают таблицу стилей по требованию — при первом переключении на них тег <link> добавляется в <head>.
Полная ссылка на YAML темы
Каждая ручка в одном файле — скопируйте и обрежьте то, что вам не нужно:
# ~/.hermes/dashboard-themes/ocean.yaml
name: ocean
label: Ocean Deep
description: Deep sea blues with coral accents
# 3-layer palette (accepts {hex, alpha} or bare 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" # optional
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: |
/* Any additional selector-level tweaks */
Обновите панель мониторинга после создания файла. Переключайте темы в реальном времени из панели заголовка — щелкните значок палитры. Выбор сохраняется до config.yaml в dashboard.theme и восстанавливается при перезагрузке.
Плагины
Плагин информационной панели — это каталог с manifest.json, предварительно созданным пакетом JS и, при необходимости, файлом CSS и файлом Python с маршрутами FastAPI. Плагины находятся рядом с другими плагинами Hermes в ~/.hermes/plugins/<name>/ — расширение информационной панели представляет собой подпапку dashboard/ внутри этого каталога плагинов, поэтому один плагин может расширять как CLI/шлюз, так и панель мониторинга за одну установку.
Плагины не объединяют компоненты React или пользовательского интерфейса. Они используют Plugin SDK, представленный на window.__HERMES_PLUGIN_SDK__. Это сохраняет размер пакетов плагинов небольшими (обычно несколько КБ) и позволяет избежать конфликтов версий.
Быстрый старт — ваш первый плагин
Создайте структуру каталогов:
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);
})();
Обновите панель управления — ваша вкладка появится на панели навигации после Навыки.
💡 Tip
Пропустить React.createElement Если вы предпочитаете JSX, используйте любой сборщик (esbuild, Vite, накопительный пакет) с React в качестве внешнего выхода и IIFE. Единственное жесткое требование — чтобы окончательный файл представлял собой один файл JS, загружаемый через `