browser-clipdocs
Reference

Script reference

Every section, key, step, target and modifier of a script. This page is the skill's own reference, references/script.md.

A script is YAML (JSON works too; in a Markdown file, the first fenced block tagged browser-clip is the script, and it ends where CommonMark ends it: a fence indented 4 or more spaces past the opening one, as in a card: | text, is text). Every key is optional except script. Omitted keys take the default below. Unknown keys and out-of-range values are reported by validate, then ignored or clamped.

A script has two halves. The steps and the browser, capture and typing settings say what happens in the browser: they make the take (the captured frames and event log). Everything else says how the take is filmed, and changing it never captures again.

Contents: Shape · Top level · browser · capture · video · typing · cursor · camera · speed · Steps · Targets · Keys · Modifiers · Markers and time · Takes · Diagnostics · CLI

Shape#

# yaml-language-server: $schema=<path>/references/schema.json
version: 1
title: Create a project
platform: windows                 # terminal-clip's look: frame, backdrop, cursor
browser:
  url: http://localhost:5173      # base for relative goto paths
  viewport: 1280x800              # CSS pixels
camera: auto
speed: auto                       # compress page loads and waits
script:
  - goto: /
    caption: The projects page
  - click: New project
    id: new
  - type: Launch plan
    into: { label: Name }
  - press: Enter
  - wait: { text: "Project created: Launch plan" }
    pause: 1                      # a freeze in compose, not a wait in capture

Top level#

KeyDefaultMeaning
version1Format version
title""Name of the clip (outputs are named after the script file, or video.name)
notes—Free text or a list (sources, intent). Not rendered
platformnonegeneric, windows, macos or linux: terminal-clip's look for that platform
seed7Pointer paths, typing rhythm and typos. Each step has its own stream, keyed by its order and id
browser, capture, video, typing, cursor, camera, speed, scriptSections below

platform sets the defaults that browser.frame.style, cursor.style and video.background would otherwise take, so that a browser clip cuts cleanly against a terminal-clip clip:

platformFrameCursorBackdrop (135°)
nonegenericmacosby browser.colorScheme: light #dde4ee → #9fadc2, dark #252b3b → #0d0f15
genericgenericgeneric#252b3b → #0d0f15
windowswindowswindows#1d4a7a → #0a1426
macosmacosmacos#4a3470 → #121225
linuxgenericgeneric#4a1d3c → #140a14

browser#

What is captured. Everything here except frame is part of the take.

KeyDefaultMeaning
url—Base URL for relative goto paths: http, https or file, e.g. http://localhost:5173. A path resolves against it as a link does: /settings from its origin, settings or ./settings under its own path. So with https://user.github.io/repo/, goto: / loads https://user.github.io/ and goto: ./ the repository's page (validate hints, H-URL)
viewport1280x800WxH in CSS pixels (1280 × 800 works too): width 320–3840, height 240–2160
scale2Device pixel ratio at capture (1–4): zoom headroom for the camera
colorSchemelightlight or dark (prefers-color-scheme)
frameThe drawn browser window (not captured): see below. frame: macos sets the style

frame:

KeyDefaultMeaning
styleautogeneric, macos, windows or none; auto follows platform
addresstrueDraw the toolbar with the address bar (it shows the page's URL)
tabsfalseDraw a tab strip with the page title

capture#

KeyDefaultMeaning
timeout10Seconds an action waits for its target (0.5–120); the timeout modifier overrides it per step
clock—The page's clock when the capture starts, ISO 8601: 2026-10-12T09:30:00Z, or 2026-10-12T09:30:00+02:00. Without Z or an offset, the time is read in timezone (UTC without one). It then runs on, one clock for every page and frame of the capture: Date (and classes that extend it), Intl dates given no date, and Temporal.Now read it; workers keep the real clock
timezone—IANA time zone for the page: Europe/Madrid, UTC
mocks[]Requests answered from the script, so the page shows made-up data: see below
block[]URL globs whose requests fail, e.g. "**/analytics/**": trackers, chat widgets, ads
login—A login state file next to the script (<name>.storage.json): its cookies and local storage apply before the first page. See below

The browser's language and locale are the machine's: number and date formats, and the Accept-Language the page gets, follow it, so the same script captured on machines set to other languages can differ. meta.json records it (locale); there is no setting for it yet.

A mock answers every request whose URL matches its glob (and its method, when it has one):

KeyDefaultMeaning
urlrequiredURL glob: * stays within a path segment, ** crosses segments. /api/projects is that path on any origin, and //cdn.example.com/** that host on any scheme. Without a ? in the glob, the query string is ignored
methodanyGET, POST, PUT, PATCH, DELETE, HEAD or OPTIONS
status200HTTP status (100–599)
file—A file next to the script, served as the body; the type comes from its extension
json—An inline value, served as JSON
body—Text, served as the body
headers—Extra response headers: { X-Total-Count: "42" }
delay0Seconds before the answer (0–30), to show a loading state
  • A mock has one of file, json and body, or none for an empty body.
  • Globs (of mocks and of block) match the whole URL, so they start with / (a path), * or a scheme; validate warns about any other (W-GLOB): api/users or example.com match nothing. The scheme and host compare without case, the path with it.
  • Changed 2026-10-14: a glob that starts with / used to match the end of any URL's path, as **/api/projects still does. Now /api/projects matches http://localhost:5173/api/projects but not https://widget.example/v1/api/projects. Write **/… where the old meaning was meant.
  • Mocks answer CORS for the page, so an API on another origin needs no setup: they allow the request's origin with credentials (* for a request without an Origin), let the page read the mock's own headers, and answer the browser's preflight (not counted as a mocked request). A mock's headers replace these and the defaults, whatever the case of their names.
  • The first matching mock wins. A blocked request fails as ERR_BLOCKED_BY_CLIENT.
  • capture prints how many requests the mocks answered and how many were blocked, and warns about a mock that answered nothing (a wrong glob or method) and a block that blocked nothing.
  • Mocks, blocks and the bytes of mock files are part of the take: editing a mock file captures again.

A login state lets a capture start signed in, with no password in the script:

  1. browser-clip login https://app.example.com/sign-in -o demo.storage.json opens a browser window with a fresh profile. Sign in there, then press Enter in the terminal. The command saves every cookie, and the local storage of each origin you visited, then closes the window.
  2. capture: { login: demo.storage.json } applies them before the first goto. The local storage is filled before the page's own scripts run, once per origin.

The file holds a live session, like a password:

  • never commit it. *.storage.json is git-ignored here, and login warns when git would not ignore the file;
  • prefer a demo account;
  • sessions expire: run login again (--force replaces the file).

A credential written in a URL would sit in the script, in the take and maybe in the drawn address bar. validate warns (W-SECRET) when browser.url or a goto carries a user name and password (user:pass@), or a query or fragment parameter named like a secret: token, access_token, apiKey, key, password, secret, auth, sig… Sign in with a login state, or answer the request with a mock.

Its bytes are part of the take, so a new session captures again. For scripts and tests, --save-after <seconds> saves without waiting for Enter, and --headless hides the window.

video#

KeyDefaultMeaning
formatwidewide 1920×1080, short 1080×1920, square 1080×1080, portrait 1080×1350, or WxH (64–7680 px a side)
res1Scale of the output (0.25–4): 2 is 4K for wide. The video stays within 8192 px on its long side
fps6024, 25, 30, 50 or 60
durationautoSeconds (0.5–3600). Shorter than the clip: fit: cut ends early, fit: speed speeds everything up (the global rate, from 0.1× to 8×; pauses, cards, the lead and the hold keep their length)
fitcutcut or speed
lead, hold0.6, 2Seconds on the first frame before the first step, and on the last frame after the last
fadeIn, fadeOut0Seconds of fade from and to black (or transparency)
backgroundautoauto (see platform), a hex color ("#rgb", "#rrggbb", or either with alpha), "gradient #a #b 135" (hex colors, then an optional angle) or transparent
paddingautoSpace around the window, px at 1080p: auto is 56, or 36 in a frame taller than wide
shadowtrueThe window shadow, as terminal-clip draws it (or 0–1)
safeautoArea kept clear of platform UI: auto (from the format), none, or { top, right, bottom, left } fractions (0–0.45)
captionsCaption style: see below
namefile nameBase name of the output files, next to the script (clips/demo makes a folder). No < > : " | ? *, which Windows file names cannot hold; an absolute path or .. gets a warning

safe: auto keeps 9 % at the top, 6 % at the sides and 20 % at the bottom of a short frame, and 4 %, 4 % and 10 % of a portrait one; wide, square and WxH keep none. The window fits the padded safe area, the camera centres on it, and captions and badges stay inside it. A viewport fills a vertical frame when its shape matches: 720x1014 for short, 900x1019 for portrait, 960x916 for square (with the address bar and no tabs).

captions, with terminal-clip's keys and defaults so both tools' captions match:

KeyDefaultMeaning
positionbottombottom or top
size44Text size, px at 1080p (12–160)
color#ffffffText color: hex (#fff, #ffffffcc), rgb(), rgba(), hsl(), hsla() or a CSS name
backgroundrgba(0,0,0,0.62)Box color, in the same forms
fontsanssans (Inter, bundled) or mono (a system monospace font)
maxWidth0.86Widest line, as a fraction of the safe area (0.3–1)

typing#

KeyDefaultMeaning
wpm75Typing speed in words per minute (5 characters a word), 10–900; part of the take
typostrueOccasional wrong keys fixed with Backspace, or a frequency 0–1; part of the take
soundwavThe keyboard sound: wav (a recorded key), mechanical, soft or none; true is wav, false is none
volume0.8Keyboard sound volume, 0–1
showKeysfalseBadges for pressed keys and for held keys while they are down. Typed text gets none

The keyboard sound is terminal-clip's, at the times the take recorded: each typed or pressed key, and a held key as it goes down and, softer, as it comes up. Keys 35 ms apart or closer make one sound (never Enter). While the clip runs at 3× or faster only Enter and shortcuts sound, and sped-up keys are quieter. The MP4 carries the track (AAC) unless the sound is none or the volume is 0, and frames writes it as audio.wav.

cursor#

The synthetic pointer, drawn over the page along the paths recorded at capture. cursor: windows sets the style.

KeyDefaultMeaning
styleautomacos, windows, generic or none; auto follows platform (macos without one)
size1.5Scale of the arrow (0.5–3): 1 is the size of a real pointer on the page
clickRippletrueA ripple on clicks and on the press that starts a drag
presstrueThe pointer shrinks while the button is down (a click, the click into a field, a drag) and springs back: true (to 0.8 of its size), false, or a scale 0.5–1. Quick clicks never take it below that scale
clickSoundfalseA mouse click sound: the press and the release of each click, of the click into a field before typing, and of a drag. true (volume 0.6) or a volume 0–1
hide3Seconds at rest (video time) before the pointer fades out; it fades in again just before it moves. false keeps it on screen. Before its first move it counts as resting, so a clip played only with keys shows none

camera#

camera: static sets the mode.

KeyDefaultMeaning
modeautoauto, static, follow, push-in, pull-out, drift, handheld or manual
shotper modewide, window, medium, close or detail
focusactionaction, cursor, page, window, or { step: <id or stepN> }
zoom—Fixed zoom (0.3–8) instead of the shot's, for every mode but auto (the director picks one per shot; validate warns). push-in ends at it, pull-out starts from it
lag0.35Seconds the camera trails what it follows (0.05–3)
deadzone0.12Fraction of the view the target may move in without the camera moving (0–0.45)
shake0Hand-held shake, 0–1 (handheld uses 0.35)
clamptrueA zoomed-in shot stays inside the window. The move between two shots is not held to it, so it never swings back
movesmoothTransition between shots: cut, smooth (1 s), fast (0.25 s) or whip (0.2 s, blurred while it moves)
shots[]Explicit shots, see below

The modes:

  • auto: the director. It opens on the window, frames each action box (a run of nearby actions shares one shot), shows the whole page for scrolls and held keys, and ends on the window. It holds through a pause or a card (they hold the step before) and sets off for the next step just before it.
  • static: the whole window, all the time. Use it for README GIFs, overlays in an editor, and to keep a whole page in view in a vertical clip.
  • follow: follows the action or the pointer.
  • push-in, pull-out, drift: slow moves on the window.
  • handheld: static with shake.
  • manual: only the shots you write.

A shot in shots, or the camera modifier on a step. A step's shot starts just before the step and lasts while it runs, its pause included; on a pause or card step, the whole freeze:

KeyMeaning
atMarker where the shot starts: required in shots; a step's shot starts with the step
shot, focus, zoom, move, shakeAs above
durVideo seconds the shot lasts; then the mode resumes
transitionSeconds the move into the shot takes
rigstatic, follow, push-in, pull-out or drift during the shot
tiltDegrees (−20 to 20)

A shot can be written as its name (camera: close) or its move (camera: whip).

How shots meet:

  • A shot that starts while the camera is still moving sets off from where the camera is, at its speed: the camera never jumps.
  • A shot too short for its move is skipped, unless the next shot cuts, since the camera would only set off and turn back: the director's (or the mode's) own when the next shot comes before its move ends; yours (shots, a step's camera) when it comes before its move is half done, with W-SHOT. Start it earlier, or make its move quicker (move: fast, transition).
  • focus: action frames the action box of the step under way when the shot arrives (after its move), else of the last step before it, whose result is on screen. A box out of view (a wait that found its text below the fold) is not framed: the director leaves it out, and a shot that names its step frames the pointer.

speed#

speed: 2 sets the rate, speed: auto turns auto on, and a list sets the ramps.

KeyDefaultMeaning
rate1Global playback rate (0.1–8). Ramps, step speeds and the automatic speed-up multiply it, as does the rate fit: speed finds
autofalseCompress the spans a viewer only waits through (a page load, a wait, an action waiting for its target) when they last over over seconds (3), down to to seconds (1.5); never a hover's hold. true or { over, to }
badgeauto: trueShow "⏩ 6×" while sped up, with the rate on screen; text may use {rate}. On by default for automatic speed-ups (up to 32×), off for ramps and step speeds
ramps[]{ during: <marker range> } or { from, to } markers, with rate (required, 0.05–32), ease (seconds, 0.4) and badge

A ramp or step speed ends by easing back to the global rate. When the next one starts where it ends (the next from is its to, or the next step follows), it hands over at its own rate instead: 2, 4, 8 play as a staircase that never drops back to 1× in between, and their badges change number in place.

Pauses, cards and the lead inside a ramp or step speed play: the picture holds, then goes on at the ramp's rate, and the badge steps aside meanwhile.

One inside another plays at its own rate, and the outer one resumes after it: a ramp from start to end at 2 with speed: 4 on a step plays that step at 4×, the rest at 2×. Two that overlap only partly are trimmed: the later one starts where the earlier one ends (W-RAMP). A range past the clip's ends is clamped to them (W-RAMP). during spans a step that takes scene time: not start or end, not a pause, caption or card step.

Steps#

Each step is a map with one action. The first nine run in the browser; pause, caption and card exist only in compose and never change the take.

ActionValueFields
gotoURL (http, https or file; about: and data: too), or a path resolved against browser.url (see browser)typed: first show the URL typed into the drawn address bar (video only): true, or the text to type
clicktargetat: [x, y]: a point in the target's box, as fractions (0–1)
hovertargethold: seconds the pointer rests (1); at
typetext, or { text, into, wpm, typos }into: a target clicked first; wpm, typos for this step. A line break in the text is inserted as text, not pressed as Enter (use press: Enter for that). A visible text field that does not end with the typed text afterwards (a mask, a length limit, a number field) gets a warning from capture
pressa key or a list of keys, pressed in turntimes: repeat (1–100)
holda key or a list of keys held togetherfor: seconds down (required); repeat: the last ordinary key auto-repeats after 0.5 s, 30 a second (true)
dragtargetfrom: [x, y] ([0.5, 0.5]), to: [x, y] (required), for: seconds the move takes (0.8)
scrollpixels (negative scrolls up), { to: <target> } or { by: <pixels> }Pixels and by turn the wheel over whatever is under the pointer. to turns the target's own scrolling area (a panel of an app shell, or the page), the pointer first moving over it when it rests elsewhere, until the target sits near the area's top; a target still out of view afterwards gets a warning
waitseconds, or { text }, { css } or { js: <expression> }, with gone: true to wait until it goes
pausesecondsA freeze on the frame that shows the previous step's result
captiontext, or { text, dur, position }dur: video seconds on screen; position: bottom or top
cardtitle, or { title, subtitle }title, subtitle, background, color (colors as for captions), align (center or left), dur (2.5)

Details:

  • Every action takes a value. - wait: or - caption: alone is an error, and so is an empty press or hold list; caption: "" ends a caption. A card's title and subtitle may sit next to card: instead. A script needs at least one step, and a goto: without a page, capture would film a blank one.
  • typed on a goto: before the page, the video shows its URL typed into the drawn address bar. The camera moves to the bar, the keys sound, Enter brings the page (the bar keeps the URL while it loads), and the camera pulls back to the window. On the first goto it starts from an empty browser; on a later one, from the page before. It is compose only: the take does not change. true types what the bar shows for the URL (example.com/pricing); a string types that text. It needs the address bar (frame.address). A URL longer than the field shows its end while it is typed, as a browser scrolls the field.
  • The address bar shows a URL as a browser does: without https://, and with an international host and the escaped UTF-8 of a path, query or fragment as their characters (müller.de/über, not xn--mller-kva.de/%C3%BCber). Escaped ASCII (%20, %2F) and invisible or reordering characters stay escaped.
  • wait: { js } takes one JavaScript expression, which may use await; the step waits until it is true (with gone, until it is false). Expose the state a script needs in the page you film, e.g. window.game.ready. An expression still unsettled at the step's timeout (an awaited request the server holds) fails the step then.
  • hold presses the keys in order, keeps them down for for seconds, then releases them in reverse order. A modifier may be held alone: hold: [Shift, w].
  • drag moves to from, presses, moves to to with the button down along an eased path, and releases.
  • caption, as a step, shows from where the previous step ended until the next caption, its dur, or the end of the video. Two caption steps in a row switch without a fade; caption: "" ends the one on screen. Next to an action it is the modifier.
  • card holds the page for dur seconds under a full-frame title card. A card first in the script opens the video, from its first frame. With align: left its lines start at the left margin.
  • Caption and card text breaks at spaces and at line breaks in the text ("Line one\nLine two", or a YAML block). A word too long for a line (a URL, a CJK sentence) breaks where a browser would: between its words, after / or &, or between characters.
  • Caption and card text takes terminal-clip's markup: [bold]Done[/], [#8ab4ff]Projects[/], [green]ok[/], [color(33)] (the 256 colours), [default] (the text's own colour). A background ([white on #202020]) is accepted and not drawn: the caption has its box.
  • Pointer lock (mouse-look) does not work in headless capture.

Targets#

How an action finds its element, in the order to prefer:

FormFinds
Save (a string)An element whose visible text is Save (exactly, else containing it), lifted to its button, link or label; or a button <input> whose value is Save. For an accessible name (an icon button's aria-label), use { role, name } or { label }
{ role: button, name: Save }An element by ARIA role and accessible name
{ label: Email }A form field by its label
{ text: Saved }An element by its visible text
{ testid: save-button }data-testid="save-button"
{ css: "#dialog-close" }A CSS selector: the escape hatch

Before acting, the step waits up to its timeout until the target exists, has been scrolled into view, has stopped moving, and is not covered at the point the action uses. A map uses exactly one of these keys (name goes with role), and none of them may be blank. A target that never gets there fails its step with the reason: not found, not displayed, zero size, hidden, moving or covered. A control found through its <label> that cannot take the click itself (a checkbox drawn by its label, the input visually hidden) is clicked through the label, as a person would.

A selector the browser cannot parse fails its step at once ("invalid CSS selector"): other tools' syntax, such as Playwright's :has-text() and text=, jQuery's :contains() or XPath, and an id that starts with a digit (#123; write [id="123"]). validate hints about them (H-CSS).

Keys#

For press, hold and the badges:

  • a character: a, A, 7, /;
  • a named key: enter, tab, escape, backspace, delete, up, down, left, right, home, end, pageup, pagedown, insert, space, f1–f12. Case does not matter; ArrowLeft, Return, Esc, Del and Bs are aliases;
  • a combination: ctrl+a, shift+tab, alt+f4, meta+k (cmd is meta). With shift, a letter is its capital, as on a keyboard: shift+g sends G;
  • in hold, a modifier alone: shift, ctrl, alt, meta.

Badges name every modifier of a combination: Ctrl+Shift+A, or ⌃⇧A on platform: macos; meta and cmd are ⌘.

Modifiers#

On any step:

KeyMeaning
idA name for markers: lowercase letters, digits and _, starting with a letter. Not start, end or step1, step2…, which markers already mean
pauseSeconds of freeze after the step
timeoutSeconds this step waits for its target (0.5–120)
speedPlayback rate while the step runs (0.05–32)
cameraA shot while the step runs (see camera)
captionA caption while the step runs (its pause included), for at least 1.2 s; or { text, dur, position }
commentAnything; ignored

pause and caption next to another action are modifiers; hold next to hover is the rest time.

Markers and time#

A marker names a moment: start, end, a step (step3, or its id) and one of its events, plus an offset in seconds: save, save.act, save.end+0.4, step2.settled-0.2. A step without an event means its start, except in a speed ramp's during, which spans the step from start to end, and to, where it means the step's end: { from: load, to: save } speeds up both steps.

EventWhen
startThe step begins (the pointer starts moving, the wait starts)
actThe click, the first key or the wheel event is dispatched
settledThe page has settled after the action
endThe step is over

Markers anchor camera shots (at), speed ramps (during, from, to), and the --at and --range options. Scene time is the take's own time; video time is what the viewer sees after speed changes, pauses and cards. Offsets and numbers under camera: and on the command line are video seconds; offsets in speed ramps are scene seconds.

Next to pauses and cards, a marker means the same moment however the capture's frames fell:

  • A step starts after the pauses, cards and lead before it, and its settled and end come before its own pause: save.end is the moment the pause after save begins.
  • A pause or card step spans its freeze: start and act when it begins, settled and end when it ends. A caption step is where its caption appears.
  • A goto with typed starts as the typing starts; its act, settled and end come after Enter, before the lead.
  • A speed ramp leaves out the pauses and cards at its ends: one from: save.end starts after the pause after save.

info lists each step at the times of its markers.

Takes#

capture, or the first command that needs a take (info, render, gif, sheet, still, frames), runs the script in headless Chrome, Edge or Chromium. It stores the take in .browser-clip/takes/<key>/ next to the script: the frames (JPEG), events.json (each action's times, boxes, pointer paths and keys), meta.json and snapshots.json.

  • After each captured step, capture records the elements on screen: controls, links, fields, canvases, elements with a test id, and text. Each has its box and the targets whose first match it is, checked in the page with capture's own lookups: its text, a label, a role and name (for a control with no text), a test id, a CSS selector. targets lists them, and the studio's picker uses them. The video leaves out the time this takes, and the time the browser stalled on an input (capture reports such stalls).
  • A snapshot keeps at most 400 elements: the controls first, then text. targets says when a snapshot was cut (a page of long text).
  • A take captured before snapshots existed has none: capture --force records them.
  • The key hashes what the take depends on:
    • the captured steps, with their capture fields, id (it keys the step's random stream) and timeout. A value written out as capture would use it anyway (times: 1, a step's wpm, typos or timeout equal to the script's) is left out, so it keeps the take;
    • browser except frame, capture (with the bytes of mock files and of the login state), typing.wpm, typing.typos and seed.
  • Presentation edits reuse the take: video, camera, speed, cursor, browser.frame, typing.sound, typing.volume, typing.showKeys, the pause, caption and card steps, and the pause, speed, camera and caption modifiers.
  • --recapture captures again. Never commit .browser-clip/.
  • Old takes are pruned. A take names its script in meta.json (script), and each new take removes from .browser-clip/takes/ the takes nothing needs any more. Each script there keeps its 3 newest takes (a take kept after a failing step counts, until its key is captured) and the take it needs now; a take from before scripts were named stays only while a script there needs it. A capture in progress in another process is never touched, nor anything else in the folder. prune does the same on request (--keep <n>). A take that went is captured again when it is needed.
  • The take stops at the first step that fails, and compose refuses a take that stopped early. A page that crashes or a browser that exits fails the step it was on, and so does a page that has not answered for the step's timeout plus 2 s (a script that never ends).
  • Some steps succeed with a warning, which capture prints and meta.json keeps: a page the server answered with an error status (404, 500…), which the video shows, and a typed text the field does not end with.
  • The address bar follows the page: each document it loads, and each route a single-page app sets with the history API (pushState) or a hash. A goto to a hash route of the open page loads no document, so it waits for none. The tab shows the page's own title, never a frame's or another tab's.
  • After each step, capture waits for the network to go quiet. A stream (EventSource) never counts, nor a request still open after 5 s (a long poll, a video); capture lists those. A check that lands on a navigation (a sign-in that moves on) runs again in the next page.
  • A wait for text, css or js ends once the screencast has sent a frame that shows the page in that state (at most 0.4 s later; when the page is at rest, the video leaves that time out), so a pause or card right after it holds the result.
  • Capture accepts every JavaScript dialog (alert, confirm, prompt with its default text, and the question a page asks before you leave it), records it in the take and lists it in the capture output. The video does not show native dialogs: film the result of the choice.
  • Capture films one tab. A click that opens another (target=_blank, window.open) gets a warning, the filmed tab is brought back to the front, and a step that fails later names the new tab: to film that page, goto its URL. Downloads are denied (a take keeps no files, and nothing is saved on the machine); capture lists them.
  • Frames and events are timed from one moment, read on both of the machine's clocks when the capture starts. A take whose frames do not fit that time (the system clock changed during the capture) gets a warning: capture it again.
  • The studio never captures on its own: it shows the last take until you ask (its button, or ctl capture).

Diagnostics#

validate prints file:line:col severity CODE path: message (fix), for example demo.yaml:6:5 warning W-KEY script[2].gone: "gone" beside wait: is ignored (put it inside wait:, e.g. wait: { …, gone: true }). The path is left out for the YAML text itself, and the fix when there is none. --json gives them as data.

CodeMeaning
E-YAMLThe text is not valid YAML (or JSON)
E-TYPEA value has the wrong type or form: a viewport that is not WxH, a bad URL, clock or time zone, a missing required field, a js wait that is not one expression, an action with no value, a script with no steps, a color or backdrop the renderer cannot paint, a video.name Windows cannot hold, an override path that is not a setting
E-ACTIONA step has no action, or two
E-TARGETA target does not use exactly one way to find the element, or a value of it is blank
E-KEYAn unknown key name
E-REFA marker, focus or id that names no step; an id that markers already mean (start, end, stepN); a relative goto without browser.url; a mock or login file that is not next to the script
E-ORDERAn action before any goto, or a script that never loads a page
E-DUPIDTwo steps with the same id
E-TAKEThe take does not match the script, or it stopped at a failing step
E-LOADIn the studio: the loader failed on the text, a bug in browser-clip; the message says what failed
W-KEYAn unknown or misplaced key, ignored (with "did you mean"), a card with no text, or a video.name outside the script's folder
W-RANGEA value outside its range, clamped
W-RAMPA speed ramp too short for its ease, overlapping another, past the clip's ends, or with nothing to speed up (during: start, during over a pause, from or to next to during)
W-TIMEvideo.duration with fit: speed needs an implausible rate, or cannot be reached at any
W-SHOTA camera shot (in shots, or a step's camera) that the next one replaces before its move is half done; skipped
W-GLOBA mock's or block's URL glob that matches no URL: it starts with neither /, * nor a scheme (api/users, example.com); the fix shows the glob as a path or a host
W-SECRETbrowser.url or a goto carries a credential: user:pass@, or a parameter such as token, key or password. Use capture.login or a mock
W-YAMLA YAML parser warning
H-YAMLText YAML would misread, quoted for you: the value of a text key (click, type, text, caption, title…) with ": " or a final ":", or that starts with "- " or "? "; the fix shows it quoted. Or yes/no-like text, which YAML 1.2 reads as text. Where a field takes true or false and also text or a number (typed, badge, typing.sound, cursor.hide…), a plain yes, no, on or off is read as true or false; quote it for the text
H-LOGINA login file not named <name>.storage.json, the name .gitignore excludes
H-CSSA css target or wait the browser cannot parse: Playwright's :has-text() or text=, jQuery's :contains(), XPath, an id that starts with a digit. Capture would fail the step
H-URLA goto path from / under a browser.url with a path of its own: it loads from the origin, outside that path; the fix gives ./path

Exit codes: 0 ok, 1 errors or failure, 2 usage, 3 warnings with --strict, 4 environment (no browser found, or it would not start: the error shows the last lines it wrote), 130 Ctrl+C, 143 SIGTERM. A command stopped by Ctrl+C or a signal closes its browser and removes its temporary folders.

CLI#

node <skill>/scripts/browser-clip.mjs <command> [script] [options]
CommandDoes
examplesLists the bundled example scripts
init [file]Writes a commented starter script (--url, --platform, --format; the viewport fills the format), or copies an example (--from <name>). Either starts with the schema modeline. --force overwrites
validate <script>Checks the script (alias check); --json, --strict, --quiet (no hints)
capture <script>Captures the take, or reuses a cached one; --force captures again. After a new take it prunes .browser-clip/takes/ and prints what stays and what went
prune <script>Removes the takes next to the script that nothing needs any more: each script there keeps its 3 newest (--keep <n>; 0 keeps only the takes the scripts need now) and the one it needs now, the given script with the command's overrides (Takes). A folder works in place of a script
info <script>Duration, steps, camera shots, speed segments, overlays and the sound, in video seconds; --json adds every marker
targets <script>The elements on screen after each captured step and the targets that find them, best first: --after <step> (a number or an id), --json
render <script>MP4 (H.264 by default) with the sound track (AAC, or Opus where AAC is missing): --preview (half size, 30 fps), --fps, --range a..b, --codec h264|vp9|av1, --bitrate 2M, --deterministic (software VP9, identical files; AV1 where VP9 is missing). A warning says when the browser cannot encode the codec asked for and the MP4 uses another, or when a deterministic render gets H.264, which is not reproducible
gif <script>Animated GIF, silent: --fps (15, at most 50: browsers show frames shorter than 20 ms for 100 ms), --width (960), --range a..b
frames <script> -o <dir>PNG sequence for a video editor, and the sound track as audio.wav (16-bit, 48 kHz) unless the clip is silent: --transparent, --fps, --range, --scale. The new sequence replaces the files of an earlier one only once it is complete (frames past its end go, and so does browser-clip's audio.wav when the clip is silent). An audio.wav that browser-clip did not write (a voice-over) is never replaced or removed: with a sound track, frames stops before any capture
still <script> --at <t>PNGs at seconds or markers (--at 2.5,save.act,end), with --transparent, --scale, --director (the whole frame with the camera's view drawn on it), --no-camera; without --at, the contact sheet
sheet <script>A contact sheet PNG of key moments: --max 12, --sheet-cols, --width
schemaThe JSON Schema of scripts (shipped as references/schema.json); -o writes it, making its folder
login <url> -o <name>.storage.jsonOpens a browser window to sign in by hand; Enter saves the session for capture.login. --save-after <s>, --headless, --force
studio <script>Opens the studio, a local page bound to the file: the live preview, the YAML editor and its diagnostics, forms, a timeline and an inspector. Exports go next to the script, named as render names them (video.name, or the script's name; a folder in it is made), and show what the script says: the text on screen, its take and its camera. The studio writes inside the script's folder only: a video.name that leaves it is refused there (render it with the CLI). Its picker (⌖, P) adds a step for an element clicked on the preview, with the targets from the take's snapshots. Edits there are saved to the file; edits to the file appear there. An edit that needs a new take is captured on request (the page's button, or ctl capture); until then the timeline only seeks. A missing mock or login file shows in the take banner and the diagnostics. A save from one open page appears on the others. A script that is not UTF-8 (a Windows-1252 or UTF-16 file) is shown but never saved over: save it as UTF-8 first. --port (a port in use stops it at once, exit 1), --no-open, --camera <mode> (the preview only, like its A/B compare); other overrides are refused. One studio per script (a second one exits 4). Ctrl+C stops it
ctl <script> <command>Drives the open studio's page (with several pages open, the one opened last): state (JSON, with sound: whether the page may play sound, and whether it does), seek <t>, still <t> (-o, default <script>-studio.png next to the script; --scale, --director; with the script's camera, as still), play, pause, reload (reads the file from disk), capture (waits as long as the capture may run, 10 minutes), camera [mode] (the preview only; no mode: the script's). The preview plays the sound only after a click or a key press on the page. Exit 4 when no studio answers for the script

Options for every command:

  • -o, --out <path>: the output file (default: next to the script, named after it); -o<path> works too;
  • --recapture: capture again before drawing;
  • --camera <mode>, --platform <p>, --format <f>, --url <base>, --viewport WxH, --seed <n>, --sound <s> (typing.sound);
  • --<section>.<key> <value> and --set <path>=<value> override any setting, e.g. --video.fps 30 or --set browser.colorScheme=dark. A number indexes a list: --set camera.shots.0.zoom=2.

Numbers (--fps, --scale, --width, --max, --sheet-cols, --port, --save-after, --keep), --codec, and a gif --fps above 50 are checked before anything runs (exit 2). --scale is at most 4, and a still or a frame at most 16384 px a side (the most the browser encodes), checked before any capture. render, gif, still, sheet and frames check their output files before any capture: a folder in a file's place, or a name Windows cannot hold, stops the command. Each file is written whole: to a temporary name next to it, then renamed. An MP4 is written there as it is rendered, so a long clip never waits in memory whole.

The script - reads standard input.

Environment:

  • CHROME_PATH: the browser's program file (chrome.exe, msedge, chromium…), used instead of searching the usual places. A path that is not a file is an error (exit code 4).
  • BROWSER_CLIP_NO_SANDBOX=1: run the browser without its sandbox, for a container that cannot provide one (the browser then exits at once, saying "No usable sandbox"). Run as root, the browser always goes without it.

Source: docs-src/pages/reference/script.md · browser-clip 0.1.0