> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inklyai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Capture

> Record a click-through of a live web app from the CLI or the Chrome extension. Every click becomes a step.

Capture records a click-through of a live web app and writes it out as a demo folder. You click through the product yourself. Each click becomes one step, with a screenshot of the page you clicked on and a cursor on the thing you clicked.

There are two ways to record:

* **The CLI** opens a Chrome window for you and writes the demo into your project. A script or an agent can drive it too.
* **[The Chrome extension](#the-chrome-extension)** records in your own browser, signed in as you, with no terminal.

## Record with the CLI

Two commands bracket a session:

```sh theme={"dark"}
interactive-demo capture start https://app.example.com --name "Onboarding"
# a Chrome window opens — click through the product
interactive-demo capture stop
```

`start` returns immediately, so your terminal stays free while you record. Every `capture` subcommand prints JSON, so a script can drive the same flow a person does. So can an agent: give it the [agent skill](/open-source/agent-skill) and ask it to "record a demo of [https://your.app](https://your.app)".

### How it works

`start` launches Chrome with a debugging port and talks to it over the Chrome DevTools Protocol. It opens your URL in a new tab, injects a small recorder script, then spawns a detached listener process and hands your prompt back. The listener turns each click into a step until you run `stop` or `cancel`.

The window is visible by default, because you drive the product. `--headless` is only useful when something else drives the page: a script sending clicks over CDP, or `--connect-to-browser <url>` pointing at a browser you already control.

Chrome is found for you: Google Chrome first, then Chrome for Testing, Canary, or Chromium. Sites with bot protection tend to block the testing builds. `--browser /path/to/chrome` or the `CHROME_PATH` environment variable overrides the choice.

The tab is sized to 1440×900 by default and captured at 2× pixel density, so a default session writes 2880×1800 screenshots. `start` waits up to `--timeout` milliseconds (120000 by default) for the first page load, and refuses to record if Chrome lands on its own error page.

## What a click becomes

Clicks produce steps. Scrolling and typing are recorded as motion and only show up as [video](#video-steps). The initial page load is not a step.

Each click produces one content step:

* **The background** is the page as it looked at click time, before the click's own repaint and before any navigation it triggers. The screenshot, the pointer, and the label all describe the same page.
* **An annotation** is placed at the point you clicked: a `message` with the `cursor` variant, reading `Click on "Save changes"`. The label is the clicked element's accessible name (`aria-label`, `title`, `alt`, its own text, or a `value`, `placeholder`, or `name`), first line only, capped at 48 characters. When nothing usable is found, the text is `Continue`.
* **A zoom** of 1.35× toward the click, eased back as the point approaches an edge. `--no-zoom` leaves the screen unzoomed.
* **`advance.trigger: "click"`**, so the viewer moves on by clicking the annotation.

<Warning>
  The last page you reach is not captured, because a step describes the page you clicked on. To end the demo on the destination, click once more, anywhere harmless, before you stop.
</Warning>

Two behaviours are worth knowing while you click around:

* A click on a link is held. The recorder cancels the navigation, lets the source page be captured, then navigates for you.
* A click that opens a new tab is followed. For three seconds after a click, a page Chrome opens is treated as the one to keep recording.

## What `stop` writes

`stop` builds the demo from the recorded screens and writes a folder:

```text theme={"dark"}
demos/onboarding/
  demo.config.json
  assets/
    screen-001.png
    screen-002.webm
    screen-002-poster.png
    screen-003.png
```

The slug comes from `--name`, or the page host with `www.` trimmed, lowercased and hyphenated. If that folder exists it becomes `onboarding-2`, `onboarding-3`, and so on.

Without `--out`, `stop` must run inside a project. It writes into that project's `demos/` and appends the slug to `interactive-demo.json` when that file keeps a `demos` list. `--out <dir>` writes `<dir>/<slug>` instead and touches no project file.

The config gets a fresh demo id, `--name` as the title, and steps `s1`, `s2`, and so on. `--compress-images` re-encodes screenshots to WebP and keeps the PNG whenever the WebP is not smaller.

`stop` then tears the session down: the listener, Chrome, its temporary profile, and the working files. It prints the demo folder, the step count, and one label per step, so you can read the captured story without opening the JSON.

## Video steps

Video needs `ffmpeg` on `PATH`. `start` checks once, up front. With no ffmpeg it prints a warning to stderr, reports `videoDisabledReason` in its JSON, and records everything as stills for the whole session. `--no-video` asks for stills and silences the warning.

While a session runs, the listener keeps a rolling buffer of screencast frames. When you scroll or type and then click, that click commits the preceding motion as a video step instead of a still. Only the densest continuous run of frames is kept, so idle frames before the motion and the settle after the click are dropped.

The clip opens with a half-second hold on its first frame and is padded to at least 1.5 seconds by holding its last frame. It is encoded as VP9 WebM, with its first frame written alongside as the poster. A burst under 10 frames or 1.2 seconds, or one whose frames are all identical, falls back to a still.

A video step carries the same cursor annotation and click-to-advance as a screenshot step. The auto-zoom applies to screenshot steps only.

## Check and undo while you record

`capture status` reads the session and reports without touching the browser:

* `ready`: the recorder armed.
* `browserAlive`: the capture browser is still running. If this is `false`, further clicks record nothing. Run `stop` to keep what you have.
* `stepCount`, `stepLabels`, `steps`: the story so far, one entry per recorded click.

`capture undo` drops the most recent step. Use it for a probing click you do not want in the demo.

`capture cancel` throws the session away. It kills the listener and Chrome, removes the working files, and writes no demo.

You rarely need a session id. With one session running, every subcommand finds it. With more than one, they stop and list the ids so you can pass `--session <id>`.

## Capture an app behind a login

Two things make a login survive.

`--profile <name>` points Chrome at a persistent user-data directory instead of a throwaway one, so cookies live on between captures. A bare name maps to a folder under the capture home. A value containing a slash is treated as a directory path. `capture profiles` lists what you have, and whether each profile holds cookies.

Signing in inside the capture browser does not always work. Identity providers reject an automation-controlled browser, even when a person is doing the clicking. `capture login` opens a separate, ordinary Chrome window on the same profile, with no debugging port and no automation flags:

```sh theme={"dark"}
interactive-demo capture login https://app.example.com/sign-in --profile acme
# sign in in the window that opens; you do not have to close it
interactive-demo capture start https://app.example.com --profile acme
```

`start` evicts the login window from the profile before taking it over, so its cookies are flushed first. A persistent or attached profile is never deleted on `stop`, `cancel`, or an interrupted `start`.

## When a capture goes wrong

<AccordionGroup>
  <Accordion title="Nothing was recorded">
    `stop` with zero steps prints `ok: false` with the reason and cleans the session up, so no `cancel` is needed. The initial page load is not a step. A step needs an actual click.
  </Accordion>

  <Accordion title="The recorder never armed">
    `start` waits up to 15 seconds for the listener to report ready. If it does not, `start` fails and prints the tail of the listener log and the Chrome log. Nothing is left behind. Start again.
  </Accordion>

  <Accordion title="Clicks go missing after a navigation">
    After each click the listener captures the source page, follows the navigation, waits about two seconds, and re-arms the recorder on the new page. A click during that window can land before the recorder is back. Let the new page paint before clicking again. `capture status` shows exactly which clicks were recorded.
  </Accordion>

  <Accordion title="The page was still loading when you clicked">
    The step shows what was on screen at click time, half-loaded skeletons included. Undo it, wait, and click again.
  </Accordion>

  <Accordion title="start was interrupted">
    Ctrl+C during `start` tears down the Chrome and listener it had already spawned and removes the half-written session.
  </Accordion>

  <Accordion title="Chrome crashed mid-session">
    Each step is written to the session file as it is recorded, so the steps before the crash are still there. Run `capture stop` and it exports them.
  </Accordion>

  <Accordion title="The session file is unreadable">
    Any command that touches it says so and names the session. `capture cancel --session <id>` cleans up what it can and reports what it recovered.
  </Accordion>
</AccordionGroup>

## Where session files live

A running capture keeps its state outside your project, under `~/.interactive-demo/capture/`:

* `sessions/<id>.json` is the live session: the browser it launched, the tab it is recording, and every screen recorded so far.
* `captures/<id>/` holds the screenshots and video frames recorded so far, plus `listener.log`.
* `profiles/<name>/` holds persistent Chrome profiles.

`INTERACTIVE_DEMO_CAPTURE_HOME` moves all of it somewhere else.

Every `capture` flag and its default is in the [CLI reference](/open-source/cli#capture).

## The Chrome extension

**Interactive Demo Capture** records a demo in your own Chrome, signed in as you, with no terminal.

Install it from the [Chrome Web Store](https://chromewebstore.google.com/).

<Note>
  It is a separate extension from the hosted Inkly demo agent's, which is covered in [Chrome extension](/build/chrome-extension). Each works only with its own product.
</Note>

<Steps>
  <Step title="Open your product">
    Open your product in a tab and go to where the demo should begin.
  </Step>

  <Step title="Start recording">
    Open the extension and pick **Start Recording**. Every click becomes a step: the page as it was just before the click, with a cursor on what you clicked. Scrolling or typing before a click is kept as a short video step. The popup counts the steps as you go.
  </Step>

  <Step title="Stop and save">
    Pick **Stop & save**. The recording stays in the extension until you decide what to do with it.
  </Step>
</Steps>

A recording is one tab. A click that opens a new tab ends the useful part of it. The extension's settings turn the video steps and the click zoom off, or blur emails, numbers, and form fields while you record.

When you stop, the extension's last screen offers two ways to use the recording.

<Tabs>
  <Tab title="Download ZIP">
    The zip holds one demo folder, `demo.config.json` and its `assets/`, named after the site you recorded. Import it into a project with no unzip step:

    ```sh theme={"dark"}
    npx interactive-demo init --from ~/Downloads/acme-3f91b2.zip
    npx interactive-demo dev
    ```

    The demo lands in `demos/acme-3f91b2/`. Add `--demo <slug>` to choose the folder name.

    This path works fully offline. Nothing leaves your machine. The popup also copies the command, or a prompt that hands the import and a caption review to your agent.
  </Tab>

  <Tab title="Upload to Inkly">
    Sends the recording to your account on [interactive-demo.inklyai.dev](https://interactive-demo.inklyai.dev), where it opens ready to check and publish.

    The first upload asks you to connect the extension to your account. The recording stays in the extension while you do.
  </Tab>
</Tabs>

Either way, the recording stays in the extension until you discard it, or pick **Done** after an upload. A failed upload or a lost download costs nothing.

## After the capture

A captured demo is an ordinary demo folder, the same thing `init` writes and the editor edits. From here:

* `interactive-demo validate` checks the config and that every referenced file exists.
* `interactive-demo dev` previews it. Rewrite the generated `Click on "…"` text into something worth reading, in the [editor](/open-source/editor) or in [the config](/open-source/authoring).
* `interactive-demo publish` puts it online, or `interactive-demo build` writes the static folder to deploy. See [Publish and self-host](/open-source/publish-and-self-host).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.