Plugin development Reference

Main-process context

The PluginMainContext API available to a plugin's main.cjs entry point.

A plugin’s main-process entry (main.cjs) receives a PluginMainContext as the ctx argument to activate(ctx). It runs with full Node access in the Electron main process and is where you observe lifecycle hooks, handle IPC from your renderer half, read config, persist storage, and observe raw terminal byte streams.

PluginMainContext

APIPurpose
ctx.log(msg)Prefixed logger
ctx.pluginDirPlugin’s directory
ctx.hooks.on(name, fn)Listen to a main hook (see the Hook catalog)
ctx.ipc.handle(name, fn)Handle invokes on plugin:<id>:<name> (the renderer’s ctx.invoke)
ctx.ipc.handleRaw/onRawRaw channels — for CORE plugins owning legacy surfaces
ctx.config.get()Read-only app config snapshot
ctx.terminal.onSessionOutput(cb)Observe raw PTY output bytes (sessionId, data) — disposable
ctx.terminal.onSessionInput(cb)Observe raw renderer→PTY input bytes (sessionId, data) — disposable
ctx.storage.get()/set(v)Persisted JSON blob for this plugin
ctx.track(d)Tie an arbitrary disposable to the plugin lifetime

IPC with your renderer half

ctx.ipc.handle(name, fn) registers a handler on the namespaced channel plugin:<id>:<name>. The renderer half calls it with ctx.invoke(name, payload?):

// main.cjs
let pings = 0;
ctx.ipc.handle("ping", () => ({ pings: ++pings }));
// renderer.js
const r = await ctx.invoke("ping"); // { pings: 1 }

Terminal byte streams

ctx.terminal.onSessionOutput and ctx.terminal.onSessionInput stream high-frequency PTY bytes, each tagged with the originating sessionId. These are not lifecycle events — they’re continuous byte streams — which is why they’re context APIs rather than hooks. Both return a Disposable and are auto-removed on deactivate.

const sub = ctx.terminal.onSessionOutput((sessionId, data) => {
  if (data.includes("password:")) {
    // …
  }
});
// sub is tracked; it's disposed when the plugin deactivates

The SSH core plugin uses exactly this pattern: it watches output for a live password: prompt and captures the typed reply on the input stream.

Storage

ctx.storage.get() / ctx.storage.set(v) read and write a persisted JSON blob for your plugin, stored at <userData>/plugin-data/<id>.json.

trackedIpc for core plugins

Core plugins that own a legacy TerminalChannel surface (Sprites, SSH) pass their ctx to trackedIpc(ctx) (exported from src/main/plugins/plugin-context.ts). It returns an IpcMainLike adapter whose handle / on calls are wired to ctx.ipc.handleRaw / onRaw, so all registrations are auto-tracked and removed on deactivate. Third-party plugins normally use ctx.ipc.handle and don’t need the raw channels.

For the events you can subscribe to with ctx.hooks.on, see the Hook catalog.