Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

EndpointPurpose
POST /api/v1/deliver/httpOutbound HTTP from a plugin (SSRF-guarded, see HTTP).
POST /api/v1/deliver/fileWrite a file to disk.
POST /api/v1/deliver/commandRun a local command with stdin / stdout / stderr captured.
GET /api/v1/sessions/{id}/metaFetch 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.