Skip to content

Quickstart

Reference plugins ship in-repo under examples/plugins/. They are the artifacts these docs are written against. Start from deno-reference — the canonical sandboxed package, the shape you actually install.

1. The canonical plugin — deno-reference (a sandboxed package)

Section titled “1. The canonical plugin — deno-reference (a sandboxed package)”

A sandboxed package is a directory with a manifest.json + a main.ts entry module (plus optional ui/). The manifest declares the capabilities the jail may use; main.ts exports the lifecycle handlers Lowkey runs in the Deno sandbox.

manifest.json
{
"id": "deno-reference",
"version": "0.1.0",
"name": "Deno Reference Plugin",
"scopes": ["cards", "focus", "side-panel"],
"surface": { "slot": "focus", "url": "http://127.0.0.1:4317/focus" },
"capabilities": [
{ "id": "net.egress", "domains": ["api.github.com"] },
{ "id": "fs.project", "mode": "read" },
{ "id": "events.turn", "events": ["turn.completed"] },
{ "id": "mount.focus" }, { "id": "mount.side-panel" },
{ "id": "cards.emit" }
]
}
// main.ts — runs in the Deno jail with exactly the granted capabilities
import { emitCard, type LowkeyPluginTurnCompleteContext } from '@lowkey/plugin-sdk';
export async function onTurnComplete(ctx: LowkeyPluginTurnCompleteContext): Promise<void> {
// `fetch` works only for a granted net.egress domain; Deno.readTextFile only
// under fs.project. Undeclared access is denied by the sandbox.
await emitCard({
sessionId: ctx.event.sessionId,
card: { type: 'status', level: 'success', text: `Turn ${ctx.event.turnId} seen.` },
});
}

An admin installs the package through the web — no host access required:

  1. Plugins settings → Install uploads the package files (manifest.json, main.ts, ui/*). This calls POST /api/plugins/install, which validates the package and returns the declared capabilities + a content hash.
  2. The UI shows the capabilities for consent; approving calls POST /api/plugins/install/confirm, which records the grant and stores the package under <project>/.lowkey/plugins/<id>/.
  3. Enable it for the project (the per-project toggle). Enabling mints the installation’s scoped token used by any declarative seams.

The installer requires the main.ts entry — a manifest with no code cannot be installed this way. See Sandboxed packages for the full capability vocabulary and the sandbox enforcement.

2. Declarative-seam examples (operator sideload)

Section titled “2. Declarative-seam examples (operator sideload)”

hello-world and driven-surface-reference are manifest.json + service.mjs with no main.ts. They exercise the declarative seams in isolation, but because they ship no entry module they are not web-installable — an operator sideloads them by dropping the directory into a plugins root (~/.lowkey/plugins/<id>/ or <project>/.lowkey/plugins/<id>/; the directory name must equal the id) and enabling it. Use them to learn the seams; use a sandboxed package to ship.

hello-world — the smallest declarative plugin, one scope, one card:

{ "id": "hello-world", "version": "0.1.0", "name": "Hello World", "scopes": ["cards"] }

Its service.mjs POSTs to the cards seam with the installation’s scoped token (LOWKEY_PLUGIN_TOKEN). driven-surface-reference adds three more blocks — surface (iframe), mcp (agent-facing tools), webhook (inbound trigger) — all wired straight from manifest.json, no core code. See the per-seam reference for each.

Every manifest validates through @lowkey/shared’s validateManifest() and the published JSON Schema — parallel sources of truth. Validate before installing; an invalid manifest is rejected whole, never partially registered.