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