# 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](https://nest.promptowl.ai/d/promptowl-documents/platform/plugins/plugins).

## The shape

```ts
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.
