# Hermes Desktop Plugins

> hermes-desktop-plugins — Write desktop app plugins that add UI panes and commands.

- **Type:** Skill
- **Install:** `agentstack add skill-atlasomnia-donna-starter-hermes-desktop-plugins`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [AtlasOmnia](https://agentstack.voostack.com/s/atlasomnia)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [AtlasOmnia](https://github.com/AtlasOmnia)
- **Source:** https://github.com/AtlasOmnia/donna-starter/tree/main/skills/hermes-desktop-plugins

## Install

```sh
agentstack add skill-atlasomnia-donna-starter-hermes-desktop-plugins
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Hermes Desktop Plugins Skill

Write plugins for the Hermes desktop app: statusbar items, layout panes,
command-palette commands, keybinds, routes, and themes. A plugin is a single
plain-JavaScript ESM file the app loads at runtime — no build step, no repo
changes. A plugin can also talk to its own Python backend namespace
(`ctx.rest`/`ctx.socket` → `/api/plugins/`); the general Python plugin
system (`~/.hermes/plugins/`) is otherwise documented separately.

Full human reference (every export, area payloads, backend, security):
`website/docs/developer-guide/desktop-plugin-sdk.md`.

## When to Use

- The user asks for a new desktop UI element (a pane, a statusbar widget, a
 dashboard, a command) without modifying the app itself.
- You want to surface data you compute (via gateway RPC) inside the app.

## Prerequisites

- The Hermes desktop app (it loads plugins; the CLI/gateway alone does not).
- Write access to `$HERMES_HOME/desktop-plugins/` (usually
 `~/.hermes/desktop-plugins/`).

## How to Run

1. Create `$HERMES_HOME/desktop-plugins//plugin.js` from
 `templates/plugin.js` (relative to this skill directory) — that's
 `~/.hermes/...` by default, or `~/.hermes/profiles//...` under a
 named profile. Keep `` equal to the plugin `id`.
2. The desktop app watches that directory: the plugin loads within a few
 seconds of the file landing, and every later save hot-reloads it in
 place. No reload step. (Fallback if it doesn't appear: ⌘K →
 **Reload desktop plugins**.)
3. If loading fails the app shows a toast naming the error — fix the file
 and save again.

## Quick Reference

The ONLY import surface is `@hermes/plugin-sdk` (plus `react` /
`react/jsx-runtime`, which resolve to the app's own React — write UI with
`jsx()` calls, not JSX syntax; the file is not compiled).

- `host.state.*` — readonly reactive atoms: `activeSessionId`, `cwd`,
 `gateway`, `model`, `profile`, `viewport`. Read with `.get()` in handlers,
 `useValue(atom)` in components.
- `host.request(method, params)` — gateway JSON-RPC (sessions, config,
 skills, cron — everything the app uses).
- `host.onEvent(type, fn)` — live gateway events (`'*'` for all). Returns a
 disposer.
- `host.notify({ kind, message })`, `host.navigate(path)`, `host.logs(...)`,
 `host.status()`, `haptic('tap')`.
- `ctx.register({ id, area, order?, render?, data? })` — contribute UI.
 Key areas: `'statusBar.right'`/`'statusBar.left'` (chips),
 `'panes'` (layout zones — set `title` and
 `data: { placement, dock?, width?, height? }`; the pane auto-joins a
 matching zone), `PALETTE_AREA` (⌘K commands), `KEYBINDS_AREA` (rebindable
 actions).
- Pane placement: `placement: 'left'|'right'|'bottom'|'main'` is the
 semantic role — the pane stacks (tabs) with existing panes of that role.
 To land on a specific EDGE instead, add `dock: { pane, pos }` — the same
 gesture as dragging onto a pane's drop chip. `pane` is any pane id
 (`workspace` is the main thread; also `sessions`, `terminal`, `files`,
 `review`, `logs`), `pos` is `'top'|'bottom'|'left'|'right'|'center'`.
 E.g. "below the conversation" = `dock: { pane: 'workspace', pos: 'bottom' }`
 — declare a `height` (e.g. `'200px'`) so it doesn't take half the zone.
- Full PAGES: register `area: ROUTES_AREA` with `data: { path: '/my-page' }`
 and a `render` — the page mounts in the workspace (main) pane like any
 built-in view. Make it reachable with a sidebar nav row:
 `ctx.register({ id: 'nav', area: SIDEBAR_NAV_AREA, data: { path: '/my-page', label: 'My Page', codicon: 'project' } })`
 (renders below Artifacts, lights up at the route) — and/or a
 `PALETTE_AREA` command calling `host.navigate('/my-page')`.
- `ctx.storage.get/set/remove` — persistence namespaced to your plugin.
- `ctx.i18n.register({ en, ja, ... })` — ship your OWN locale bundles, scoped
 to your plugin (never edit core `en.ts`). Values are literal strings or
 interpolator functions; nested trees are addressed by dot-path. Read them
 reactively in components with `usePluginI18n(id)` returning `t('key', ...args)`
 (re-renders on a locale switch), or via `ctx.i18n.t` in handlers/stores.
 Resolution follows the app's active locale, then your `en`, then the raw key.
- Data: `useQuery`/`useMutation`/`useQueryClient`/`queryClient` (the app's ONE
 React Query client — cache, dedupe, `refetchInterval`, invalidate like core;
 never hand-roll a poll loop), plus `atom`/`computed` for plugin-local state.
- Backend: if the plugin ships a Python `plugin_api.py` (under
 `~/.hermes/plugins//dashboard/`, manifest `"api": "plugin_api.py"`), reach
 it with `ctx.rest('/path', { method?, body?, timeoutMs? })` and its live twin
 `ctx.socket('/events', onMessage)` — both scoped to `/api/plugins/` by
 construction (traversal rejected). `ctx.socket` is a **no-op on OAuth
 remotes**, so always keep a polling fallback. The Python backend is imported
 only when the plugin is in `plugins.enabled` in `config.yaml` (separate from
 the in-app enable toggle). For gateway-wide data use `host.request` /
 `host.onEvent` instead.
- `Contribute` (mount-scoped): render `jsx(Contribute, { area, id, children })`
 inside a component so page-owned chrome (e.g. a titlebar control in
 `TITLEBAR_AREAS.center`) leaves when the page unmounts — `ctx.register` is for
 permanent contributions.
- `defaultEnabled: false` on the default export ships an opt-in plugin: it
 inventories in Settings → Plugins, off until the user flips it on.
- Users manage plugins in Settings → Plugins (enable/disable live, reveal
 folder). A disabled plugin stays disabled across restarts — don't fight
 it; the user turned you off.
- UI: the app's design language, importable directly — `Button`, `Input`,
 `Textarea`, `Select*`, `Switch`, `Checkbox`, `SegmentedControl`, `Tabs*`,
 `Dialog*`, `ConfirmDialog`, `DropdownMenu*`, `ContextMenu*`, `Popover*`,
 `Tip`/`Tooltip*`, `Badge`, `Kbd`/`KbdGroup`, `SearchField`, `ScrollArea`,
 `Separator`, `Skeleton`, `GlyphSpinner`, `EmptyState`, `ErrorState`,
 `CopyButton`, `StatusDot`, `LogView`, `Codicon`, `DecodeText`, plus `cn`
 and `icons.*`. Prefer these over hand-rolled elements so the plugin looks
 native; style with theme vars, never hardcoded colors.

## Procedure

1. Pick a short kebab-case `id`; the folder name must match.
2. Start from `templates/plugin.js`; keep the default export shape
 (`{ id, name, register(ctx) }`).
3. For a pane, register `area: 'panes'` with a `placement` hint and a
 `render` returning your component — the app places it into a sensible
 zone automatically; the user can drag it anywhere afterwards.
4. Fetch data with `host.request` and/or subscribe with `host.onEvent`;
 never poll faster than a few seconds.
5. Write the file with your file tools, then ask the user to run
 **Reload desktop plugins** from ⌘K.

## Pitfalls

- NEVER hardcode colors or backgrounds (`#000`, `black`, `rgb(...)`). Panes
 already sit on the app's editor background — leave the background alone
 and use theme variables for everything else: `var(--ui-text-secondary)`,
 `var(--ui-text-quaternary)`, `var(--ui-stroke-secondary)`,
 `var(--ui-accent)`. For canvas drawing, resolve them once with
 `getComputedStyle(canvas).getPropertyValue('--ui-accent')`.
- Reference only what you imported — a component you forgot to import
 (e.g. `StatusDot`) is a ReferenceError at render. Double-check every
 identifier in your `jsx()` calls appears in the import line.
- Canvas panes MUST track their container with a `ResizeObserver` and
 re-size the canvas (width/height attributes, not just CSS) — panes resize
 constantly (sash drags, layout switches); a mount-time-only size leaves
 blank space or blurry scaling.
- JSX syntax will not parse — the file loads uncompiled. Use
 `jsx('div', { children: ... })` from `react/jsx-runtime`.
- Do not import anything except `@hermes/plugin-sdk`, `react`, and
 `react/jsx-runtime`; other specifiers fail to resolve.
- Handlers must read state imperatively (`$atom.get()`), never from render
 closures — rapid events will otherwise see stale values.
- Keep components small; subscribe (`useValue`) only in the leaf that
 renders the value.

## Verification

- The plugin's UI appears after **Reload desktop plugins**.
- No error toast ("Plugin  failed to load") appears; if it does, the
 message names the failure — fix and reload.
- For panes: the new zone is visible and draggable like any core pane.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [AtlasOmnia](https://github.com/AtlasOmnia)
- **Source:** [AtlasOmnia/donna-starter](https://github.com/AtlasOmnia/donna-starter)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-atlasomnia-donna-starter-hermes-desktop-plugins
- Seller: https://agentstack.voostack.com/s/atlasomnia
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
