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) создана для изменения оформления и расширения без разделения кодовой базы. Обнажаются три слоя:

  1. Темы — файлы YAML, которые перерисовывают палитру, типографику, макет и хромирование каждого компонента панели управления. Перетащите файл в ~/.hermes/dashboard-themes/; он появляется в переключателе тем.
  2. Плагины пользовательского интерфейса — каталог с manifest.json + пакет JavaScript, который регистрирует вкладку, заменяет встроенную страницу, дополняет ее через слоты на уровне страницы или внедряет компоненты в именованные слоты оболочки.
  3. Бэкенд-плагины — файл Python внутри каталога плагинов, который предоставляет FastAPI router; маршруты монтируются под /api/plugins/<name>/ и вызываются из пользовательского интерфейса плагина.

Все три подключаются во время выполнения: нет клонирования репозитория, нет npm run build, нет исправлений исходного кода информационной панели. Эта страница является канонической ссылкой для всех трех.

Если вы просто хотите использовать панель мониторинга, см. Веб-панель мониторинга. Если вы хотите изменить оформление интерфейса командной строки терминала (а не веб-панели управления), см. раздел Скины и темы — система оформления интерфейса командной строки не связана с темами информационной панели.

📝 Note

Как складываются фигуры Темы и плагины независимы, но синергичны. Тема может быть автономной (просто файл YAML). Плагин может стоять отдельно (просто вкладка). Вместе они позволяют вам создать полную визуальную перерисовку с помощью специальных HUD — включенная в комплект демоверсия `strike-freedom-cockpit` делает именно это. См. [Демо-версия комбинированной темы + плагина](#combined-theme--plugin-demo).

Содержание


Темы

Темы — это файлы 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

Значения принимаются:

Каждый ресурс также генерируется как --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, загружаемый через `