Agent integration rules

Cursor can install Onboard. Open Onboard Keys at https://app.bootstrapware.co/onboard/keys and click Add to Cursor (OAuth). The MCP endpoint is https://onboard.bootstrapware.co/mcp.

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.

The first incomplete unblocked item is highlighted as the next step. Incomplete unblocked items with `href` use a primary button-styled link (`ctaLabel`, otherwise Continue) and **Mark complete** as secondary. Items without `href` keep **Mark complete** primary. Blocked items have no primary CTA. Clicking the href CTA does **not** auto-complete — call `completeStep` from the host and pass `facts`. The widget only follows `http(s)`, hash (`#…`), and relative path hrefs. It ignores `javascript:`, `data:`, other schemes, and protocol-relative URLs, including when a tab, newline, or leading control character hides the scheme, or a backslash would be read as a slash. Optional `ctaLabel` round-trips dashboard, MCP `update_draft`, published config, and the widget.

When `requirementsSatisfied` is true, the widget shows a celebrate/done card (**You're all set**) above the checklist. The required-progress bar, skip, snooze, dismiss, and resume stay. If optional steps remain, **Required setup complete. Optional steps remain.** still appears alongside the card. Completion still uses existing `onEvent` (`onboard.flow_completed`); there is no extra analytics SDK.

**Hide** collapses the checklist in this browser without dismissing or snoozing it. **Show** brings it back. That choice is remembered here only. `hideWhenComplete` collapses again once required setup is complete; Show still opens the celebrate card. Dismiss and snooze stay the persisted lifecycle and still need Resume.

`presentation` is `section` (default, inline on the page), `modal` (dialog and backdrop), `window` (floating panel, page stays usable), or `launcher` (corner button). Modal, window, and launcher start closed unless `defaultOpen`. Escape, the backdrop, or Hide closes modal and launcher. Escape or Hide closes a window. Nudge a launcher or window with `--bsw-ob-launcher-right` and `--bsw-ob-launcher-bottom`.

Skipped optional steps show **Undo** and **Mark complete**. Undo calls optional `unskipStep` (same `skip` permission) and deletes that skipped progress row so the step is incomplete again. The button renders only when the adapter implements `unskipStep`. Undo does not uncomplete a complete row. A `facts` value of `true` still evaluates as complete. Hosted emits additive `onboard.item_unskipped` (subscribers opt in). A stored skip applies only while the step stays optional. If that step is later required, the leftover skip is ignored: the step is incomplete or blocked, counts toward required progress, and can be marked complete. Undo stays on optional skips only.

**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 live or secret API keys via MCP. For a test publishable key, call `ensure_test_publishable`.

### 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.

The checklist paints after the first `getProgress`. Fact completions keep running in the background and do not replace the list with loading skeletons. The widget already re-reconciles on window focus — do not also refetch host facts on every focus unless those values actually changed.

### 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; items may include optional `href` and `ctaLabel`; `name` is optional) → `publish_flow`
3. `ensure_test_publishable`. Put `envLine` in `.env.local`. The full test publishable is returned every time.
4. `get_install_snippet`. `flowId` is real. Use the env publishable key, never invent one.
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. Never put secret keys 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()),
    unskipStep: async (input) =>
      fetch("/api/onboard/progress", { method: "POST", body: JSON.stringify({ action: "unskip", ...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`. **Live Hosted** always requires it (live keys without a token are test keys only). Token is also required when the flow sets `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; name optional; items may include optional `href` and `ctaLabel`), `publish_flow`, `list_revisions`, `restore_revision`, `get_published_config`, `get_install_snippet`, `ensure_test_publishable`, `list_capabilities`.

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

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.

## Appearance

Optional `theme` (`"light"` | `"dark"`). Omit it and the checklist stays the light cream default: no `data-theme`, and no `prefers-color-scheme` or system theme. Hosts may set `--bsw-ob-bg`, `--bsw-ob-panel`, `--bsw-ob-fg`, `--bsw-ob-muted`, `--bsw-ob-line`, `--bsw-ob-ok`, `--bsw-ob-error`, `--bsw-ob-accent`, `--bsw-ob-accent-fg`, and `--bsw-ob-radius` on the embed root (`.bsw-ob`; with `theme="dark"`, `.bsw-ob[data-theme="dark"]`). Those overrides are the supported API. Do not fight `.bsw-*` classes. `primaryColor` still sets `--bsw-ob-accent` inline when it is set. `className` is layout only.

## 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