Agent integration rules

Paste this into AGENTS.md, Cursor rules, or your agent system prompt when adding Bootstrapware Onboard. The same file ships inside the npm package.

# Integrating @bootstrapware/onboard

Use this file as agent instructions.

## What this package does

Embeddable first-run setup checklist for SaaS: required and optional steps, dependencies, eligibility, fact reconciliation, dismiss, and snooze.

**Two modes**

- **BYO ($9.99):** you own progress. Pass an `OnboardAdapter` (or callbacks wrapped with `createByoAdapter`). Bootstrapware only hosts flow config.
- **Hosted ($19.99):** omit adapter; pass `flowId` + `publishableKey`. Progress and outcomes live on Bootstrapware.

Never send host context or flags through MCP. MCP configures flows only.

## Install algorithm (do this in order)

1. Read this file.
2. If the user only needs a working UI in this app: Path A. Do not invent keys. Do not wait for MCP.
3. If they need live Hosted or BYO config: Path B or C. Call `list_capabilities` on Onboard MCP before inventing tools.
4. Never invent `user.id`. Wire the **real** session id from the host app.
5. Never mint API keys via MCP. If Path B/C and env has no publishable key, **stop and ask the human**.

### Path A — local, zero keys (true seamless)

```bash
pnpm add @bootstrapware/onboard
```

```tsx
import { Onboard, createLocalAdapter, SAAS_FIRST_RUN_ITEMS } from "@bootstrapware/onboard";
import "@bootstrapware/onboard/styles.css";

<Onboard
  user={{ id: session.user.id, name: session.user.name }}
  workspaceKey={workspace.id}
  context={{ role: membership.role, plan: workspace.plan, flags: featureFlags }}
  facts={{ hasBilling: billingConnected }}
  items={SAAS_FIRST_RUN_ITEMS}
  adapter={createLocalAdapter({ storageKey: "demo-onboard" })}
/>
```

Call `completeStep` from your app on real actions **and** pass `facts` so the widget reconciles on mount.

### Path B — Hosted

1. Confirm Cursor MCP `bootstrapware-onboard` at `https://onboard.bootstrapware.co/mcp` (OAuth URL-only). If disconnected, tell the human to click **Add to Cursor (OAuth)** on https://app.bootstrapware.co/onboard/keys
2. `list_capabilities` → `create_flow` (SaaS first-run items are already on the draft) → `update_draft` (`allowedOrigins` for localhost and production; only send `items` if you are changing them) → `publish_flow`
3. `get_install_snippet`. `flowId` is real. The key is a placeholder.
4. If env has no `bsw_test_pub_` / `bsw_live_pub_` value, **stop**. Ask the human to mint a test publishable key at https://app.bootstrapware.co/onboard/keys and paste it into `.env.local`
5. Embed with the real session user id, never `"user_1"` in production.
6. Live Hosted requires `authorToken` minted from your BFF session (never browser secret). Bind `workspaceKey`. Default permissions omit `reset`.
7. **Auto-renewal:** your BFF refreshes `authorToken` before expiry (e.g. fetch when `expiresAt` is within 5 minutes). Pass the fresh token into `<Onboard authorToken={...} />`. The widget does **not** mint tokens. Live Hosted without `authorToken` is **test keys only** — production needs BFF mint + renewal.

```tsx
<Onboard
  flowId="flw_..."
  publishableKey={process.env.NEXT_PUBLIC_BSW_ONBOARD_PUBLISHABLE_KEY}
  user={{ id: session.user.id, name: session.user.name }}
  workspaceKey={workspace.id}
  context={{ role: membership.role, plan: workspace.plan }}
  facts={integrationFacts}
  authorToken={authorTokenFromBff}
/>
```

Optional `apiBaseUrl` defaults to `https://onboard.bootstrapware.co`. Use `get_install_snippet` from Onboard MCP for a starter embed — placeholders only; never put secrets in snippets or `NEXT_PUBLIC_*`.

### Path C — BYO

Same MCP flow config as Path B. Implement `OnboardAdapter` on the customer backend. Progress stays on your backend; use `onEvent` for side effects (Bootstrapware webhooks are not used). Pass `publishableKey` so the widget can fetch published items, eligibility, and revision.

```tsx
<Onboard
  flowId="flw_..."
  publishableKey={process.env.NEXT_PUBLIC_BSW_ONBOARD_PUBLISHABLE_KEY}
  user={currentUser}
  workspaceKey={workspace.id}
  onEvent={(event, payload) => {
    /* your analytics / CRM */
  }}
  adapter={{
    getProgress: async (input) => fetch(`/api/onboard/progress?${new URLSearchParams(input as Record<string, string>)}`).then((r) => r.json()),
    completeStep: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "complete", ...input }) }).then((r) => r.json()),
    skipStep: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "skip", ...input }) }).then((r) => r.json()),
    snooze: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "snooze", ...input }) }).then((r) => r.json()),
    dismiss: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "dismiss", ...input }) }).then((r) => r.json()),
    resume: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "resume", ...input }) }).then((r) => r.json()),
    resetPersonal: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "resetPersonal", ...input }) }).then((r) => r.json()),
    reportOutcome: async (input) =>
      fetch("/api/onboard/outcomes", { method: "POST", body: JSON.stringify(input) }).then((r) => r.json()),
  }}
/>
```

## Identity

Bootstrapware does **not** authenticate end users. Your app asserts:

```tsx
user={{ id: "opaque-stable-id", name: "Ada" }}
```

`workspaceKey` scopes shared steps. Optional `authorToken`: short-lived host-signed assertion minted with your Onboard **secret** key via `POST /api/v1/author-tokens`. Required when the flow has `requireAuthorToken: true`.

```bash
curl -s -X POST https://onboard.bootstrapware.co/api/v1/author-tokens \
  -H "Authorization: Bearer $BSW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"authorId":"user_123","flowId":"flw_...","workspaceKey":"ws_acme","expiresInSec":3600}'
```

Default token permissions: `complete`, `skip`, `dismiss`, `snooze`. Pass `permissions={[..., "reset"]}` on the widget and include `"reset"` when minting from your BFF to show **Reset my progress**. Mint server-side only — never in browser snippets.

## Mixed scope and roles

Use per-item `progressScope: "user" | "workspace"` in one flow. For different roles, publish separate flows or gate items with `eligibility`.

## Hosted MCP (Cursor)

HTTP MCP endpoint: `https://onboard.bootstrapware.co/mcp`

**Preferred:** OAuth Connect, URL-only. **Add to Cursor (OAuth)** on https://app.bootstrapware.co/onboard/keys

**Fallback:** test/live secret as `Authorization: Bearer`.

Tools: `list_flows`, `get_flow`, `create_flow`, `update_draft` (does not publish), `publish_flow`, `list_revisions`, `restore_revision`, `get_published_config`, `get_install_snippet`, `list_capabilities`.

**Dashboard-only (do not invent MCP tools for these):** API key mint/revoke, webhooks, flow delete, branding, billing, Hosted summaries export/preview. Call `list_capabilities`.

Publishing stores hosted flow config. Live `bsw_live_pub_` config fetch needs BYO ($9.99) or Hosted ($19.99). Live Hosted progress writes need Hosted ($19.99). Test publishable keys work without that.

## Security

- Do not put `bsw_live_sec_` or `bsw_test_sec_` in client code, `NEXT_PUBLIC_*`, or MCP install snippets.
- Do not upload host context or flags to Bootstrapware MCP.
- `user.id` and `workspaceKey` are trust boundaries. Prefer origins + `requireAuthorToken` in production Hosted.
- BFF mints and renews `authorToken`; the widget never calls the secret-key author-tokens API.

Related: Cursor guide · Agent brief · Prompt template · Quickstart · API