mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-22 05:22:09 +00:00
Themes and plugins can now pull off arbitrary dashboard reskins (cockpit
HUD, retro terminal, etc.) without touching core code.
Themes gain four new fields:
- layoutVariant: standard | cockpit | tiled — shell layout selector
- assets: {bg, hero, logo, crest, sidebar, header, custom: {...}} —
artwork URLs exposed as --theme-asset-* CSS vars
- customCSS: raw CSS injected as a scoped <style> tag on theme apply
(32 KiB cap, cleaned up on theme switch)
- componentStyles: per-component CSS-var overrides (clipPath,
borderImage, background, boxShadow, ...) for card/header/sidebar/
backdrop/tab/progress/badge/footer/page
Plugin manifests gain three new fields:
- tab.override: replaces a built-in route instead of adding a tab
- tab.hidden: register component + slots without adding a nav entry
- slots: declares shell slots the plugin populates
10 named shell slots: backdrop, header-left/right/banner, sidebar,
pre-main, post-main, footer-left/right, overlay. Plugins register via
window.__HERMES_PLUGINS__.registerSlot(name, slot, Component). A
<PluginSlot> React helper is exported on the plugin SDK.
Ships a full demo at plugins/strike-freedom-cockpit/ — theme YAML +
slot-only plugin that reproduces a Gundam cockpit dashboard: MS-STATUS
sidebar with live telemetry, COMPASS crest in header, notched card
corners via componentStyles, scanline overlay via customCSS, gold/cyan
palette, Orbitron typography.
Validation:
- 15 new tests in test_web_server.py covering every extended field
- tests/hermes_cli/: 2615 passed (3 pre-existing unrelated failures)
- tsc -b --noEmit: clean
- vite build: 418 kB bundle, ~2 kB delta for slots/theme extensions
Co-authored-by: Teknium <p@nousresearch.com>
52 lines
2 KiB
TypeScript
52 lines
2 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* Themed card primitive. Themes can restyle every card without touching
|
|
* call sites by setting CSS vars under the `card` component-style bucket:
|
|
*
|
|
* componentStyles:
|
|
* card:
|
|
* clipPath: "polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px)"
|
|
* border: "1px solid var(--color-ring)"
|
|
* background: "linear-gradient(180deg, var(--color-card) 0%, transparent 100%)"
|
|
* boxShadow: "0 0 0 1px var(--color-ring) inset, 0 0 24px -8px var(--warm-glow)"
|
|
*
|
|
* All properties are optional — vars that aren't set compute to their
|
|
* CSS initial value, so the default shadcn-y card keeps looking normal
|
|
* for themes that don't override anything.
|
|
*/
|
|
const CARD_STYLE: React.CSSProperties = {
|
|
clipPath: "var(--component-card-clip-path)",
|
|
borderImage: "var(--component-card-border-image)",
|
|
background: "var(--component-card-background)",
|
|
boxShadow: "var(--component-card-box-shadow)",
|
|
};
|
|
|
|
export function Card({ className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"border border-border bg-card/80 text-card-foreground w-full",
|
|
className,
|
|
)}
|
|
style={{ ...CARD_STYLE, ...style }}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return <div className={cn("flex flex-col gap-1.5 p-4 border-b border-border", className)} {...props} />;
|
|
}
|
|
|
|
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
return <h3 className={cn("font-expanded text-sm font-bold tracking-[0.08em] uppercase blend-lighter", className)} {...props} />;
|
|
}
|
|
|
|
export function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
return <p className={cn("font-mondwest text-xs text-muted-foreground", className)} {...props} />;
|
|
}
|
|
|
|
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return <div className={cn("p-4", className)} {...props} />;
|
|
}
|