# plugins
Plugins are opt-in external processes that talk to hrdx over bounded newline-delimited JSON on stdin and stdout. They can add context-menu actions, finder search providers, notifications, footer status, scoped workspace and pane queries, pane operations and input, private storage, and floating or docked text views. Normal startup runs no plugins. Custom harnesses and holder sessions are unchanged.
Plugins are trusted executable code, not sandboxed code. Approving a plugin lets it run with your OS permissions and inherited environment. Capability grants restrict what the plugin can ask hrdx to do, nothing more. Inspect a package before you approve it.
## try the reference plugin
# build the standard-library example from a checkout$ go build -o examples/plugins/hello/hello.exe ./examples/plugins/hello# approve it with explicit grants, then start hrdx with plugins enabled$ hrdx plugins approve --package ./examples/plugins/hello --trust \--grant ui.action.contribute --grant ui.notification --grant ui.provider.contribute \--set greeting=Hi$ hrdx --plugins# optional floating panel: reapprove with view grants and enable views$ hrdx plugins approve --package ./examples/plugins/hello --trust \--grant ui.action.contribute --grant ui.notification \--grant ui.view.contribute --grant ui.view.input$ hrdx --plugins --plugin-views
Right-click a pane or workspace and pick Hello from plugin. Open the finder with ctrl+b /and type two characters to see the example search provider's rows. The process starts lazily on first use, with activation progress in the footer.
## flags and commands
| flag | purpose |
|---|---|
| --plugins | enable the runtime for explicitly approved plugins (default off) |
| --plugin-views | allow approved plugin views, requires --plugins (default off) |
| hrdx plugins ... | effect |
|---|---|
| list, doctor | read-only inventory and diagnostics, never executes a package |
| approve --package PATH --trust | bind approval to the package path and content digest, with --grant, --workspace or --instance, and --set key=value |
| inspect, enable, disable, revoke --id ID | show or change the saved approval record |
| status, start, stop, restart, reload --id ID | lifecycle control of a running instance through the control socket |
Approvals live in plugin-approvals/ next to the state file, private plugin data in plugin-data/. Any change to a package's files requires explicit reapproval. A running instance checks approvals every 500 ms and revokes changed, removed, or disabled records. New approvals and changed grants need a restart; a reapproved package at the same path can be reloaded live. ctrl+b P opens lifecycle controls, and the settings window gains a plugins section.
## capabilities
Every operation needs its own named grant, and workspace operations also need a matching --workspace scope or --instance. Grants are checked at request time, not only at startup.
| grant | allows |
|---|---|
| ui.action.contribute | context-menu actions declared in the manifest |
| ui.provider.contribute | search providers merged into the finder |
| ui.notification | transient footer notices with info or error severity |
| ui.status.contribute | a prioritized footer status entry |
| ui.view.contribute, ui.view.input | floating or docked plain-text views, and keyboard/mouse input to them |
| workspace.read, pane.read_metadata | scoped snapshots and subscriptions without screen content |
| pane.read_screen | a pane's visible plain-text screen |
| pane.create, pane.close | durable splits and tabs, or temporary floats owned by the plugin |
| pane.send_input | bounded, ordered typing into in-scope panes |
| workspace.create, workspace.close | open or close workspaces in scope |
| storage.plugin_private | a quota-limited key-value store per plugin |
| host.events.subscribe | coalesced snapshot.changed events |
## manifest
A package is a directory with a plugin.json and an executable entrypoint. Contributions, capability requests, activation markers, and configuration options are declared, not discovered by running code.
{
"schema": 1,
"id": "example.hello",
"version": "1.1.0",
"entrypoint": "hello.exe",
"protocol": {"min": 1, "max": 1},
"requests": ["ui.action.contribute", "ui.notification", "ui.provider.contribute"],
"activation": {"markers": [".git"]},
"config": [{"key": "greeting", "type": "string", "default": "Hello"}],
"contributes": {
"actions": [{"id": "example.hello.greet", "label": "Hello from plugin", "targets": ["workspace", "pane"]}],
"providers": [{"id": "example.hello.search", "kind": "search", "label": "Hello search"}]
}
}Activation markers hide a plugin's actions and providers in workspaces that do not contain one of the named entries. Configuration values are typed, set at approval time, stored in ordinary state, and delivered to the plugin at startup. They are not a secret store.
## wire protocol
One JSON object per line. The peer sends hello, receives hello_ack with its granted capabilities and configuration, replies ready, then exchanges requests and responses in both directions with connection local ids. Frames are capped at 256 KiB, calls at 32 per direction with a ten second deadline, and a plugin that floods or stalls is disconnected rather than allowed to block the interface.
{"type":"hello","plugin":"example.hello","protocol":1}
{"type":"hello_ack","protocol":1,"instance":"...","grants":["ui.notification"],"config":{"greeting":"Hi"}}
{"type":"ready"}
{"type":"request","id":"7","method":"command.invoke","params":{"action_id":"example.hello.greet","target":"pane"}}
{"type":"response","id":"7","result":{"notification":"Hi from the example plugin"}}
{"type":"request","id":"1","method":"ui.notify","params":{"text":"Done","severity":"info"}}
{"type":"shutdown"}The complete manifest, method, grant, limit, and lifecycle reference lives in the repository at docs/plugin-platform.md. The platform is experimental and independently versioned; it does not claim compatibility with any other plugin protocol.