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
| API | Purpose |
|---|---|
ctx.log(msg) | Prefixed logger |
ctx.pluginDir | Plugin’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/onRaw | Raw 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.