hermes-js-plugin-builder/host exports everything a web UI needs to embed and
run plugins built by this toolkit.
import { PluginHost } from 'hermes-js-plugin-builder/host';
// or: import { PluginHost } from './src/host/index.js';| Option | Type | Description |
|---|---|---|
storage |
{ get(key), set(key, v), delete(key) } |
Storage adapter backing ctx.storage. Default: in-memory. Wrap localStorage for persistence. |
notify |
(n: {pluginId, message, level}) => void |
Sink for ctx.notifications.show. Default: console.log. |
onPanel |
(panel: {pluginId, id, title, location}) => void |
Called when a plugin registers a panel; the embedder decides where/how to mount. |
connect |
(manifest) => Promise<{endpoint, dispose}> |
Custom sandbox transport. endpoint = { postMessage(msg), onMessage(cb) -> unsubscribe }. Used for tests and non-DOM embeddings. Default: sandboxed iframe (browser) or Web Worker. |
timeoutMs |
number | RPC and boot timeout. Default 10000. |
Load a plugin. source is a bundle URL/path (fetched as text) or a code
string or { url } / { code }. manifest is the parsed plugin.json.
- Validates the manifest first; rejects with
protocol-errorlisting violations. - Creates the sandbox, runs the
hp:hello→hp:init→hp:readyhandshake, and resolves{ id, manifest, state: "active" }. - Guest→host API calls are permission-gated: a plugin calling a namespace it
didn't declare gets a
permission-deniedRPC error.
Sends hp:unload, waits for the guest's deactivate() ack (bounded by
timeoutMs), removes its commands/panels/subscriptions, and tears down the
sandbox. Returns false if the plugin wasn't loaded.
[{ id, manifest, state }] for every loaded plugin.
The internal plugin record (includes peer — treat as opaque).
Host → guest commands.invoke: runs the handler the plugin registered for
commandId and resolves its return value.
Host → guest panels.render: runs the panel's render(props) and resolves
the HTML string it returned. Mounting that HTML is the embedder's job.
The host-side API surface (also usable standalone):
host.api.onEvent(topic, cb)— subscribe to plugin-emitted events.host.api.emitEvent(topic, payload)— publish to host + subscribed plugins.host.api.listCommands(pluginId?),host.api.listPanels(pluginId?)— enumerate live contributions.
- Browser: an invisible
<iframe sandbox="allow-scripts" srcdoc=...>containing the guest bootstrap.allow-scriptswithoutallow-same-origingives the guest an opaque origin — it can run JS but cannot touch the parent DOM, cookies, or storage. The plugin bundle is delivered as source text overpostMessageand imported via a Blob URL inside the sandbox. - Worker fallback: when no DOM exists, the guest bootstrap runs in a
Workercreated from a Blob URL. - No eval on the host side for plugin code — everything crosses the sandbox boundary as data.
Permission enforcement is host-side: the guest can attempt any call, but
the host dispatcher checks the declared manifest.permissions before
executing, so a tampered guest cannot escalate.
python3 -m http.server # from the repo root
# open http://localhost:8000/examples/host-demo/
examples/host-demo/index.html loads examples/hello-panel, mounts its
panel, and wires buttons for its commands and the event bus.