Documentation / Plugins
Plugins
Package an executable behind Cairn’s validated manifest, bounded native contributions, and capability grants.
Package layout
<CAIRN_HOME>/plugins/
└── com.example.cairn.hello/
├── plugin.json
└── plugin.sh
When CAIRN_HOME is unset, the plugin root is ~/.cairn/plugins. Each visible child must be a real directory, not a symlink; its name must equal the manifest ID. Manifests are capped at 1 MiB.
Example manifest
{
"activationEvents" : [
"onCommand:hello",
"onPanel:status"
],
"arguments" : [
],
"contributes" : {
"commands" : [
{
"description" : "Exercise a long-running Bash plugin over JSON-RPC.",
"id" : "hello",
"systemImageName" : "hand.wave",
"title" : "Hello from Bash"
}
],
"panels" : [
{
"buttons" : [
{
"id" : "refresh",
"role" : "primary",
"title" : "Refresh"
}
],
"id" : "status",
"rows" : [
{
"label" : "Runtime",
"value" : "Bash"
},
{
"label" : "Transport",
"value" : "JSON-RPC 2.0 over NDJSON"
}
],
"status" : {
"level" : "success",
"text" : "Ready"
},
"systemImageName" : "terminal",
"title" : "Bash plugin status"
}
]
},
"executable" : "plugin.sh",
"id" : "com.example.cairn.hello",
"manifestVersion" : 1,
"name" : "Cairn Bash Example",
"permissions" : [
"api.read"
],
"version" : "1.0.0"
}
Lifecycle and wire protocol
- Discovery. Cairn validates the manifest, contribution bounds, relative executable, and executable permission. Invalid plugins are logged and ignored.
- Lazy activation.
onStartupstarts during app startup; command and panel processes start only when their declared contribution is used. - Long-lived process. Cairn sets the plugin directory as the working directory, passes manifest arguments directly, and exposes
CAIRN_PLUGIN_ID,CAIRN_API_VERSION, andCAIRN_PLUGIN_TRANSPORT=json-rpc-2.0+ndjson+stdio. - Bidirectional JSON-RPC. Cairn calls
plugin/activate,plugin/command,plugin/panelAction, andplugin/shutdown. A plugin may call granted Cairn API methods over the same stdin/stdout stream. - Isolation. Invalid records, records over 4 MiB, and unexpected exits fail only that plugin process. Standard error is captured in bounded diagnostic chunks.
| Operation | Timeout |
|---|---|
| Activation | 30 seconds |
| Command or panel action | 5 seconds |
| Graceful shutdown | 1 second |
After an unexpected exit, retries back off at 1, 2, 4, 8, 16, 32, then 60 seconds. A reload or grant change stops the old process and creates a fresh authenticated session.
Native contributions
Plugins describe content while Cairn owns rendering, focus, placement, accessibility, and action dispatch. API 1.0 does not accept arbitrary HTML, JavaScript, AppKit views, shaders, or layout code.
Command palette
A plugin may declare up to 128 commands. Each command has an ID, title, optional description, and optional SF Symbol name. Selecting one activates the plugin and sends plugin/command.
Dock panels
A plugin may declare up to 32 compact panels. Cairn renders their title, SF Symbol, rows, status, and buttons, then sends plugin/panelAction when a button is used.
Panel data model
| Element | Bounds | Behavior |
|---|---|---|
rows | 32; labels 120 bytes, values 1,000 bytes | Read-only label/value content rendered by Cairn. |
status | One; 500-byte text | Native neutral, success, warning, or error treatment. |
buttons | 8; unique IDs | Normal, primary, or destructive native buttons. |
systemImageName | 120 bytes | SF Symbol requested for the panel or command. |
Contribution IDs are at most 80 ASCII letters, digits, dots, dashes, or underscores. A command or panel is rejected unless its matching activation event is declared.
Permissions and grants
Declaring a permission in plugin.json defines the maximum capability the plugin may request; it does not grant authority.
- The manifest declares the maximum capabilities the plugin may request.
- A user or trusted local client authenticates with
plugins.manageand sets an explicit grant subset. - Cairn persists grants per plugin, intersects them with the current declaration and live API catalog, and authenticates the plugin session with only that result.
- The central router checks the required capability before every plugin-originated API call. Undeclared grants are rejected.
./scripts/cairnctl call plugins/grants/set '{"pluginId":"com.example.cairn.hello","capabilities":["api.read"]}' --capability plugins.manage
Changing grants stops a running plugin so its next activation receives a new session. Removing a permission from the manifest also removes its effective authority even if old persisted data exists.
Current capabilities
| Capability | Allows |
|---|---|
api.read | Inspect the API catalog and service health. |
events.subscribe | Subscribe to and receive developer API events. |
plugins.manage | Reload plugins and change explicit capability grants. |
plugins.read | Inspect installed plugins, contributions, and grants. |