Plugin Delivery
Every destination in Xenocept is backed by a plugin. The built-ins (File, Email, AeorDB, Claude/MCP — see Overview) are plugins that ship with Xenocept. If you want to ship sessions somewhere not covered by a built-in, write your own plugin.
This page describes the plugin model at the level of “what gets installed and what runs at dispatch time.” For the deeper plugin authoring guide — APIs, lifecycle, event subscriptions — see the Plugins section.
What “Plugin Delivery” Means
When a destination is dispatched, the frontend’s plugin loader runs the destination’s plugin and passes it the session. The plugin is a JavaScript ES module shipping with a package.json that includes a Xenocept-specific block.
A minimal sketch (the precise API surface lives in the frontend code; verify against static/plugins/ or wherever your build keeps them):
'use strict';
export async function deliver(session, attachments, config) {
// session — the parsed session.json
// attachments — fetch URLs / Uint8Arrays for screenshot.png, focus-i.png, etc.
// config — the destination's pluginConfig from destinations.json
// Use Xenocept's delivery primitives — these run server-side:
// POST /api/v1/deliver/http (HTTP proxy with SSRF guard)
// POST /api/v1/deliver/file (write to a filesystem path)
// POST /api/v1/deliver/command (run a local command)
//
// Or do entirely in-page work (transform, upload via `fetch`, etc.).
// Throw / reject on failure; return / resolve on success.
}
The exact entry-point name, the shape of attachments, and how config is delivered are frontend-side concerns. Treat the above as illustrative; the authoritative reference is the frontend plugin code in the running build.
Backend Hooks Plugins Rely On
Plugins use a handful of backend HTTP endpoints to do their work. None of these are plugin-specific — they’re general-purpose primitives:
| Endpoint | Purpose |
|---|---|
POST /api/v1/deliver/http | Outbound HTTP from a plugin (SSRF-guarded, see HTTP). |
POST /api/v1/deliver/file | Write a file to disk. |
POST /api/v1/deliver/command | Run a local command with stdin / stdout / stderr captured. |
GET /api/v1/sessions/{id}/meta | Fetch a session’s JSON. |
GET /api/v1/sessions/{id}/files/{*path} | Fetch a session’s image attachments. |
(src/api.rs:305-309, 397-399)
Installation
Plugins can be installed via the Plugins API:
POST /api/v1/plugins/install— install a published plugin (npm package) (src/api.rs:388)POST /api/v1/plugins/install-from-source— install from a local source directory (src/api.rs:389)POST /api/v1/plugins/uninstall— remove an installed plugin (src/api.rs:390)
GET /api/v1/plugins/list (src/api.rs:314) enumerates installed plugins. GET /api/v1/plugins/search (:315) searches npm.
Plugin Signing
The backend includes a signature verifier (src/security/sig.rs). The package.json field it expects is pluginId (camelCase, top-level) when verifying — not the nested xenocept.plugin_id shape that some older docs described. If you’re publishing a plugin you want to be installable in a hardened deployment, that’s the field to set.
Versus the Built-Ins
If your need fits a built-in (write to a file, send an email, push to AeorDB, deliver to Claude via MCP), use the built-in. Plugins are for the cases the built-ins don’t cover.
If you’re tempted to write a plugin that just calls POST /api/v1/deliver/http once, consider whether the user can already do that by configuring the built-in HTTP-like destination (if any exists in the build) with pluginConfig.endpoint. The plugin path is the right one when you need real logic in between.