---
name: capturebeam
description: Author CaptureBeam demo.yaml files and render polished product video via the CaptureBeam REST API. Use when the user wants to create or update a product demo / onboarding video / release-note clip / sales walkthrough that should regenerate on every UI change.
---

# CaptureBeam — agent skill

CaptureBeam is a **demo compiler**. You write a YAML script describing a flow; CaptureBeam captures the flow with Playwright and renders it with Remotion into a polished MP4 (synthetic cursor, auto-zoom, click ripples, captions). The same script re-rendered against a new build emits the new video — the wedge is reproducibility.

This skill teaches you how to:

1. Author a valid `demo.yaml`.
2. Render it via the REST API.
3. Recover when targets fail to resolve.

## The contract

```
GET  /api/v1/schema              # JSON Schema (no auth) — what's valid YAML
POST /api/v1/probe               # Bearer-authed — what's actually on the page now
POST /api/v1/renders             # Bearer-authed — turn this YAML into an MP4
GET  /api/v1/renders/:id         # Bearer-authed — poll job status
```

API base: `https://capturebeam.com` (or self-hosted equivalent).
Auth: `Authorization: Bearer cb_live_…` for everything except `/schema`.

Per-account concurrency is 3 simultaneous renders.

## YAML shape

A demo is a single YAML document with `title`, an optional `render` block, and an array of `steps`. **Every step has a `type:` field** that discriminates which other fields it accepts.

```yaml
title: "Onboarding tour"
subtitle: "v0.5"               # optional

viewport:                      # optional; defaults to 1920×1080@2x
  width: 1920
  height: 1080
  deviceScaleFactor: 2

render:                        # all fields optional with sensible defaults
  preset: midnight             # midnight | iris | mint | sunset | paper | custom
  aspect: "16:9"               # 16:9 (default) | 9:16 | 1:1
  quality: "1080p"             # 1080p (default) | 1440p | 4k
  fidelity: standard           # standard (default) | max
  titleCard: true              # show the title card at the start
  cursor:                      # optional override of the preset's cursor
    color: white
    size: 36

steps:
  - type: goto
    url: https://app.example.com/welcome

  - type: click
    target: { role: button, name: "Get started" }

  - type: type
    target: { label: "Workspace name" }
    text: "Acme HQ"
```

### Step types

Every step row starts with `type: <name>`. Only the listed fields are valid for each type.

| `type` | Required fields | Optional fields |
|--------|-----------------|-----------------|
| `goto` | `url` | — |
| `click` | `target` | `superZoom` |
| `type` | `target`, `text` | `perCharDelayMs`, `pressEnterAfter` |
| `scroll` | — | `target`, `to` (`top` \| `bottom` \| number; default `bottom`) |
| `wait` | — (one of `ms` or `networkIdle: true`) | `ms`, `networkIdle` |
| `hover` | `target` | — |
| `dragTo` | `from`, `to` | — |
| `keyPress` | `key` (e.g. `"Cmd+K"`) | `repeat` |
| `pause` | `ms` | — |
| `highlight` | `target` | `durationMs`, `color`, `superZoom` |
| `cameraPan` | — | `target`, `scale`, `durationMs`, `superZoom` |
| `underline` | `target` | `durationMs`, `color` |

(`assertVisible` and `assertHidden` still validate but are deprecated for the editor — prefer a `wait` if you just need the runner to pause for an element.)

### Common step fields

Every step also accepts these optional fields:

```yaml
- type: click
  target: { role: button, name: "Save" }
  description: "Save the project"   # author note; not visible in the video
  waitAfterMs: 600                  # extra delay after the action
  cameraZoom: 1.5                   # 1.0 = no zoom; 1–10× supported
  caption:                          # narration shown over the step
    text: "Click Save when you're done"
    position: bottom                # bottom (default) | top
    durationMs: 2000                # optional; otherwise auto-fits
```

### Targets

Targets are NL-first. **Never** use CSS selectors. A target is either a free-text string (matches visible text) or a structured object.

```yaml
# Free-text — matches any element whose visible text contains "Create project"
target: "Create project"

# Structured — preferred when text is ambiguous or repeats
target:
  role: button
  name: "Create project"
  exact: false        # set true to require exact text match (avoids substring mismatches)
```

Allowed structured fields:

| Field | What it matches |
|-------|-----------------|
| `role` | ARIA role: `button`, `link`, `textbox`, `checkbox`, `tab`, `menuitem`, `heading`, etc. |
| `name` | Accessible name (the visible label / `aria-label`) |
| `label` | Text of the `<label for="…">` linked to the input |
| `text` | Visible substring (set `exact: true` for whole-string match) |
| `placeholder` | `<input placeholder="…">` |
| `testId` | `data-testid` attribute (most stable; prefer when present) |

The runner resolves targets via Playwright's `getByRole` / `getByLabel` / `getByText` / `getByTestId` with multi-attempt recovery and a persistent historical-success cache.

## Authoring loop

### 1. Probe the URL

`POST /api/v1/probe` returns every interactive element on the page (role, name, placeholder, testId). Use these to write valid targets.

```bash
curl -X POST https://capturebeam.com/api/v1/probe \
  -H "Authorization: Bearer $CB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://app.example.com"}'
```

### 2. Draft the YAML

- Always start with `type: goto`.
- Keep demos short — 5–12 steps, ~30 seconds rendered.
- One narrative beat per step.
- Add a `caption` only on steps that benefit from labeling.
- Prefer `role` + `name` targets; they survive redesigns.

### 3. Render

Submit raw YAML:

```bash
curl -X POST https://capturebeam.com/api/v1/renders \
  -H "Authorization: Bearer $CB_TOKEN" \
  -H "Content-Type: application/yaml" \
  --data-binary @demo.yaml
```

…or by stored project id:

```bash
curl -X POST https://capturebeam.com/api/v1/renders \
  -H "Authorization: Bearer $CB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"prj_…"}'
```

Response: `202 Accepted` with `{ "id": "rj_…", "status": "pending" }`.

### 4. Poll

```bash
curl https://capturebeam.com/api/v1/renders/$RENDER_ID \
  -H "Authorization: Bearer $CB_TOKEN"
```

States: `pending` → `running` → `succeeded` (with `videoUrl`) or `failed` (with `error`). Every 2 seconds is fine; most renders finish in 5–30s.

### 5. Self-correct on error

If a step fails with a target-resolution error:

1. Re-probe the URL — the page may have changed.
2. Inspect the failed step's `target` against the latest probe results.
3. Patch the YAML and re-submit.

If the same step fails twice with the same target, surface it to the user — the element may not exist at all.

## Worked example

```yaml
title: "Acme onboarding"
render:
  preset: iris
  aspect: "16:9"
  quality: "1080p"
steps:
  - type: goto
    url: https://app.acme.com/welcome

  - type: click
    target: { role: button, name: "Get started" }
    caption:
      text: "Welcome — let's set up your workspace"

  - type: type
    target: { label: "Workspace name" }
    text: "Acme HQ"

  - type: click
    target: { role: button, name: "Create" }

  - type: wait
    networkIdle: true
    ms: 1500

  - type: cameraPan
    target: { role: navigation }
    durationMs: 1200
    caption:
      text: "Your projects live in the sidebar"

  - type: highlight
    target: { role: button, name: "+ New project" }
    caption:
      text: "Click here to start your first project"
```

## Common errors

| Error | What it means | Fix |
|-------|---------------|-----|
| `target_not_resolved` | None of the layered selectors matched | Re-probe; tighten/broaden the target |
| `goto_timeout` | Initial navigate took >30s | Check the URL, add a `wait` step after |
| `quota_exceeded` | Account hit the yearly safety net | Surface to the user |
| `subscription_required` | API key valid, no active subscription | Surface to the user |

## Reference

- Schema (live): `GET https://capturebeam.com/api/v1/schema`
- Probe API (Bearer): `POST https://capturebeam.com/api/v1/probe`
- Render API (Bearer): `POST https://capturebeam.com/api/v1/renders`
- Job status (Bearer): `GET https://capturebeam.com/api/v1/renders/:id`
- Examples: see the `examples/` directory in this skill bundle.
- Docs: <https://capturebeam.com/docs/agents>
