PromptOwl + ContextNest Docs

Building a Plugin

A Nest Plugin is a small package written against @promptowl/contextnest-plugin-sdk — an Apache-2.0 package of types and schemas with no dependency on the engine. Your plugin can be any licence you like.

For how plugins connect outside sources to a nest, see Nest Plugins.

The shape

import { definePlugin } from "@promptowl/contextnest-plugin-sdk";

export default definePlugin({
  manifest: {
    name: "acme-wiki",                    // lowercase, dashes: becomes the folder and author id
    version: "0.1.0",
    displayName: "Acme Wiki",
    description: "Pulls pages from the internal wiki.",
    capabilities: ["pull", "search"],     // pull | webhook | process | search
    itemKinds: ["document"],              // transcript | email | message | document | file
    settings: {                           // JSON Schema — the nest renders this as the settings form
      type: "object",
      required: ["baseUrl", "token"],
      properties: {
        baseUrl: { type: "string", title: "Wiki URL", format: "uri" },
        token:   { type: "string", title: "API token", "x-secret": true },   // stored write-only
      },
    },
  },

  pull(ctx, cursor) {
    // yield InboundItems since `cursor`; set result.nextCursor when done
  },
  async search(ctx, q) { /* return SearchHits */ },
  async fetchOne(ctx, id) { /* return the InboundItem for a hit */ },
});

Three optional faces:

  • pull(ctx, cursor) / webhook(ctx, req) — bring items in. Your cursor is any JSON; the host stores it and advances it only after a clean run.
  • process(ctx, item, mode) — turn an item into pages. Skip it and the host lands raw pages (or summaries via its distiller) for you.
  • search(ctx, q) + fetchOne(ctx, id) — answer a live search; hits are promotable.

What you get from the host

ctx.settings (secrets already resolved), ctx.fetch (a guarded fetch — private and loopback addresses are refused, calls time out and are budgeted), ctx.log, and ctx.distill when the server has a summariser. You never see an LLM key, the database, or the nest.

Connecting a person's account

Some sources belong to a person — their Drive, their Teams. Declare oauth: { provider: "google", scopes: [...] } in the manifest and the host does the whole flow: a Connect button in the plugin's settings, the provider's consent screen, the callback, the refresh token stored write-only, and a fresh access token before every call. Read it with oauthAccessToken(ctx.settings). validateSettings runs without a token (people save first, then connect), and the ctx CLI has no consent flow, so offer a headless alternative where one exists — google-drive takes a service-account key too. provider may be google, microsoft, or custom with your own authorization and token URLs.

What the host does with your items

Validates each item, lands it as a versioned page authored system:plugin:<name>, keys it on (plugin, externalId) so re-runs update instead of duplicate, routes it through review on a governed nest, and never overwrites a page a human edited — it reports a conflict instead.

Testing

No host needed: build a ctx with a fake fetch and call the faces directly. The reference plugins in the SDK repo (github-markdown, google-drive) show the pattern, including a fake API that honours If-None-Match.

Publishing

npm publish your package. A server admin installs it and adds its name to the plugin allowlist; nest editors take it from there.