Skip to main content
Herm’s plugin system lets you extend the terminal UI without touching core shell code. A plugin is a single TypeScript module that exports an { id, tui } object. When Herm activates the plugin it calls tui(api), your code registers whatever it needs through the api object, and everything you register is automatically cleaned up the moment the plugin deactivates. There is no teardown boilerplate to write.

What plugins can do

Plugins have five extension points to work with:
Slots are host-managed regions of the shell where plugins can paint content. Each slot receives a typed props object so your renderer always knows the current session id, tab index, or streaming state. Available slots:
Slot nameWhere it rendersProps
app_bottomOne-row gutter below the composer{ sid, tab, streaming }
sidebar_contentStacked section inside the right sidebar{ sid }
sidebar_footerPinned row at the bottom of the sidebar{ sid }
prompt_rightInline, right of the composer input{ sid }
splash_footerBelow the splash logo on an empty session{}
The host decides the composition mode for each slot call site:
  • append (default) — your contribution stacks after lower-order plugins.
  • replace — your contribution supplants the host’s default child.
  • single_winner — only the lowest-order contribution renders; the rest are dropped.
Register a named route and Herm adds it as a navigable tab after the five built-in groups (Chat, Sessions, Profiles & Automation, Config, Eikon). You can navigate to it programmatically with api.route.navigate("YourTabName").
Register one or more commands and they appear in the Ctrl+K palette under any category label you choose. Each command gets a stable value string and an onSelect callback.
Subscribe to the live gateway event stream with api.event.on(fn). React to any GatewayEvent type — show a toast, update local state, or trigger a navigation.
Contribute a new rasterizer to the Eikon Studio tab. You supply a name, a knobs schema (cycle options, toggles, sliders), an available() check, and an async render(win, knobs) function. The Studio handles all spatial work and renders your knob controls generically.

Current limitation: bundled plugins only

External loading from npm packages or ~/.herm/plugins/ is on the roadmap but not yet available. Today every plugin must be compiled into the Herm bundle. Wiring yours in takes two steps:
1

Drop your file into the bundled directory

Create your plugin module at src/plugins/bundled/hello.tsx. It must export a default HermPlugin value with a unique id and a tui function.
import type { HermPlugin } from "../types"

const plugin: HermPlugin = {
  id: "acme.hello",
  tui(api) {
    api.slots.register({
      order: 50,
      slots: {
        app_bottom: () => (
          <text fg={api.theme.current.textMuted}>hello from acme</text>
        ),
      },
    })
  },
}

export default plugin
2

Import and register it in internal.ts

Open src/plugins/internal.ts and append your import to the INTERNAL array:
import hello from "./bundled/hello"

export const INTERNAL: ReadonlyArray<HermPlugin> = [
  clock,
  files,
  hello,   // ← your plugin
]
The order in INTERNAL is the activation order and the default precedence when two plugins contribute to the same slot at equal order.
Plugin id values must be globally unique. Use a reverse-DNS-style prefix so yours will not collide with bundled plugins or future community plugins — for example acme.hello, not just hello.

Default enablement

Plugins are enabled by default. If you want your plugin to ship turned off — useful during early development — set enabled: false on the plugin object:
const plugin: HermPlugin = {
  id: "acme.hello",
  enabled: false,   // ships disabled; user must opt in
  tui(api) { /* … */ },
}
User toggles (via the plugin manager UI) override the compiled default and persist across launches.

Where plugin state persists

All persistent data lives in a single file: ~/.hermes/herm/tui.json (or $HERM_CONFIG_DIR/tui.json when that variable is set). Herm uses this file for:
  • plugin.enabled — per-plugin on/off overrides keyed by plugin id.
  • Plugin KV data — anything you write with api.kv.set(key, value), stored under your plugin’s id namespace automatically. You never need to prefix keys yourself.

Next steps

  • API Reference — complete type signatures for every surface on the api object, all slot names and props, and the lifecycle model.
  • Examples — copy-pasteable code for common patterns: status bars, custom tabs, event listeners, persistent settings, and custom rasterizers.