CLI
The screenci CLI keeps the workflow small: initialize a project, iterate
locally with screenci test, refine videos with screenci preview and the web
editor, export the finished videos with screenci export, and manage public
delivery when needed. Run
commands from inside your screenci/ project directory. Most commands resolve
screenci.config.ts from the current directory unless you pass
--config <path>.
Command overview
| Command | Purpose |
|---|---|
screenci init [name] |
Scaffold a ScreenCI project |
screenci test [playwrightArgs] |
Run .screenci.ts files locally without final recording |
screenci preview [pattern] |
Record live previews and print their links; with --watch, connect this machine as one video’s code-sync bridge |
screenci export [patterns...] |
Produce finished videos: re-record every requested video, render, and download |
screenci info |
Print the last record run’s URLs and render status |
screenci make-public <videoId> |
Enable public delivery for a video |
screenci make-private <videoId> |
Disable public delivery for a video |
screenci delete <videoId> |
Permanently delete a video and its renders |
screenci init
Create a new ScreenCI project in the current directory:
npm init screenci@latestpnpm create screenciyarn create screenciThe project name defaults to the repository root directory name.
Connecting to an existing account
No account or secret is needed to scaffold, record, or preview: without one,
preview uploads under a local, anonymous trial session and prints the web
preview link, then you sign up to keep it. Recording an anonymous trial agrees
to the Terms, which the CLI prints before it
starts. Exporting requires an account with an active paid subscription.
To connect the project to an account you already have, pass your
SCREENCI_SECRET (from your secrets page) as init’s positional argument, and
it writes the secret straight into screenci/.env:
npm init screenci@latest PASTE_YOUR_SCREENCI_SECRET_HEREYou can also skip this and copy SCREENCI_SECRET into screenci/.env by hand
at any point before recording.
When using npm init, pass extra initializer flags after --:
npm init screenci@latest -- --yes --package-manager pnpmnpm init screenci@latest -- --yes --package-manager yarnThe package manager is auto-detected from the npm_config_user_agent environment
variable (set automatically when you run pnpm create or yarn create), lockfile
presence (pnpm-lock.yaml, yarn.lock), or the packageManager field in
package.json. Use --package-manager to override.
To keep setup fast, init only prompts for the choices that genuinely vary:
the project name, whether to add the GitHub Actions workflow, and whether to
install AI agent skills (the ScreenCI skill plus playwright-cli). Everything
else is applied at a sensible default and can be steered with a flag.
Common options:
-y, --yesaccepts all defaults and skips every prompt--package-manager <npm|pnpm|yarn>overrides auto-detected package manager--agent <name>passes an agent name to the selected skills install command-v, --verboseprints underlying command output
Flags to override an auto-applied default without going interactive:
--no-github-workflowskips the GitHub Actions workflow (and its prompt)--no-skillsskips both AI agent skills (and their prompt)--no-playwright-clikeeps the ScreenCI skill but dropsplaywright-cli(the skill and the@playwright/clidev dependency)--no-reactskips React overlay support (react/react-dom and JSX)--no-playwright-browsersskips installing the Chromium shell--playwright-os-depsinstalls Playwright operating system dependencies (off by default because it may require sudo)
Applied defaults: create the GitHub Actions workflow, install dependencies,
add React overlay support, install the Chromium shell, skip OS dependency
installation, and install the AI agent skills (ScreenCI + playwright-cli).
The AI skills install is best effort: if it fails (for example on an older
Node.js runtime), init warns, prints the command to retry it later, and
continues with the rest of the setup.
Use this command in Manual Setup & First Video.
screenci test [playwrightArgs...]
Run videos locally without the final recording pipeline:
npx screenci testnpx screenci test recordings/onboarding.screenci.tsnpx screenci test --grep "billing"npx screenci test --uipnpm exec screenci testpnpm exec screenci test recordings/onboarding.screenci.tspnpm exec screenci test --grep "billing"pnpm exec screenci test --uiyarn screenci testyarn screenci test recordings/onboarding.screenci.tsyarn screenci test --grep "billing"yarn screenci test --uiUse this during normal authoring. Most trailing arguments are forwarded to Playwright.
Plain screenci test disables recording-style waits so authoring runs stay
fast. That includes ScreenCI cursor/camera pauses and
page.waitForTimeout(...), which is collapsed to 0ms. Use Playwright locator
or load-state waits for real application readiness. Use --mock-record when you
need to preview the same pacing that a real recording will capture.
Common Playwright examples that also work here:
npx screenci test --project=chromiumnpx screenci test --grep "onboarding"npx screenci test --uipnpm exec screenci test --project=chromiumpnpm exec screenci test --grep "onboarding"pnpm exec screenci test --uiyarn screenci test --project=chromiumyarn screenci test --grep "onboarding"yarn screenci test --ui--mock-record
npx screenci test --mock-recordpnpm exec screenci test --mock-recordyarn screenci test --mock-recordThis keeps recording-like pacing enabled without starting the real recording
capture path. Use it when test passes but a real recording exposes timing
differences.
If you want that behavior by default for a project, set
test.mockRecord: true in screenci.config.ts.
screenci export [patterns...]
Produce finished videos and download them. export is the one-shot command
(also the one CI runs): it pulls any queued editor edits into the sources,
re-records every requested video (sources can carry changes a hash check would
miss, so export always captures fresh footage), waits for the renders to
finish, and downloads the outputs into ./exports/:
npx screenci exportnpx screenci export "Onboarding"npx screenci export --grep "billing"npx screenci export --languages fi,enpnpm exec screenci exportpnpm exec screenci export "Onboarding"pnpm exec screenci export --grep "billing"pnpm exec screenci export --languages fi,enyarn screenci exportyarn screenci export "Onboarding"yarn screenci export --grep "billing"yarn screenci export --languages fi,enPositional arguments are title patterns (multiple are OR-combined, like
playwright test <pattern>); with no patterns, every video is exported in
every language. Downloads are named <title>.<lang>.mp4.
The command polls render status every 5 seconds for up to 30 minutes
and exits 0 only when every requested video rendered and downloaded. After a
run it prints the run’s page in the app (<app>/export/<recordId>), which the
project page lists under Exports.
--languages <langs>
Export only specific language versions of videos declared with
video.languages([...]). Pass a comma-separated list:
npx screenci export --languages finpx screenci export --languages fi,enPer-language videos record and render only the requested languages, so a run
never produces more than you asked for. Videos that do not declare those
languages are skipped. A shared-mode recording ({ mode: 'shared' }) is a
single capture and is not split by this filter. See
Languages for the recording API.
-o, --output <dir>
Directory for the downloaded files. Defaults to exports:
npx screenci export -o dist/videos--no-wait
Start the renders and exit immediately, without waiting for them or downloading files. Use this when the finished videos are consumed from the web (public URLs or embeds) and the run only needs to publish fresh footage:
npx screenci export --no-waitThe command still records, uploads, and dispatches the renders, and prints
the results URL. It exits 0 as soon as the upload succeeds; render failures
are visible on the results page, not in the exit code. Without this flag,
export waits for every render and exits non-zero if any of them fails.
--force
Deprecated no-op, kept so existing scripts do not break: export always
re-records every requested video.
--no-sync
Deprecated no-op: export never applies queued browser edits. An export
renders exactly what your sources say. When edits are still queued in the
editor, export prints a warning naming the count and proceeds without them;
apply them first with screenci sync or screenci preview. (On
screenci preview the flag is real: there it disables the edit sync, see
preview --no-sync.)
Behavior:
- enables recording timing for every requested video
- writes local output into
.screenci/ - holds a per-project run lock at
.screenci/.record.lockwhile recording and uploading. If anotherscreenci previeworscreenci exportrun is already active, the second run exits non-zero instead of sharing the same.screencioutput. Stale locks are reclaimed automatically when the pid is gone or the lock is older than the maximum run TTL - requires an account with an active paid subscription: without a
SCREENCI_SECRET,exportrefuses before recording and prints a sign-up link (the anonymous trial is preview-only; usescreenci previewfor the free live preview, see Anonymous Trial). Once you have signed up, re-runningexportin the same folder links your account automatically. SetSCREENCI_SECRETfrom the project.env, the environment, or by runninginitwith it - uploads only the recordings requested by this run (for example the videos matched by patterns,
--grep, or--languages), with or withoutSCREENCI_SECRET - exits non-zero unless every requested video rendered and downloaded. If a requested video is missing its
recording.mp4or another failure occurs,exportdoes not save.screenci/last-record.jsonand does not print a success URL for unrelated output - prints any informational notices returned by the service in cyan after upload (occasional, non-error status messages such as maintenance or timing notes). These never affect the exit code
- new accounts choose a plan before exporting: exports need an active Starter, Pro, or Business subscription. Higher tiers raise export and active-video limits and unlock multiple languages
Relevant options:
-c, --config <path>-v, --verbose-g, --grep <pattern>(same filter as Playwright’s--grep)
Important restriction:
--retriesis not supported because ScreenCI forces retries to0
screenci preview [pattern]
Records live previews and prints their links. One-shot preview (no
--watch) syncs any queued web edits into your .screenci.ts sources
(skipped with --no-sync, see the option below),
re-records and uploads the live preview of every matched video that is stale
(all managed videos when no pattern is given), prints the link, and exits.
When exactly one video was recorded it prints that video’s preview page
(<app>/project/<projectId>/video/<videoId>/preview); when several were
recorded it prints the run listing page (<app>/preview/<recordId>), which
links each video’s preview page. Edits made in the web editor while no
machine is connected queue server-side and are written into your sources by
the next screenci sync, test, or preview (export never applies edits;
it warns about queued ones and renders your sources as they are).
With --watch, the command instead stays running as the live code-sync
bridge for exactly one video: every web edit is written back into your
sources within seconds, and the editor can trigger a local record of the
video on demand. It keeps polling the service for edit and record requests
until you stop it with Ctrl-C. See Editor for the editing
model.
screenci previewFilter videos by passing a title pattern, the same way
playwright test <pattern> does; --grep does the same and takes precedence
when both are given. With --watch, the pattern must match exactly one
video: a pattern that matches zero or several videos makes the command exit
with an error (“screenci preview –watch manages one video at a time”) that
lists the candidate titles.
screenci preview "Auto-zoom"Right before recording a preview, the CLI sends a best-effort “preview recording started” notice to the backend, so an open web preview page shows a live “Recording preview…” indicator and refreshes automatically when the new preview lands.
A one-shot run prints one line per phase: the sync summary, the record announcement, the upload results, one line per video whose editor-uploaded media (overlays, audio, narration audio, cloned voices) shapes the render, and the preview link:
Synced 3 editor edits into your sources.Recording 1 video: Search for Alfa Forni Moderno 2...✔ Uploaded "Search for Alfa Forni Moderno 2 [en]"✔ Uploaded "Search for Alfa Forni Moderno 2 [et]"Editor-uploaded media for "Search for Alfa Forni Moderno 2" applies at render time; recordings always run from code.
Open the live preview for "Search for Alfa Forni Moderno 2" at:<app>/project/<projectId>/video/<videoId>/previewPer-edit sync details are not printed by the one-shot run (the editor’s
pending list shows each edit and who queued it); a live --watch session
logs every edit as it is written. Edits an apply skips or fails always log.
Options:
-w, --watch: stay connected as the live bridge (see above) instead of the one-shot default.-c, --config <path>: path to the ScreenCI config file.--token <token>: personal editor token. Defaults toSCREENCI_EDIT_TOKENfrom your project env file; when neither is set and aSCREENCI_SECRETis configured, the CLI mints a machine token from it automatically.-g, --grep <pattern>: only manage videos whose title matches this pattern (the same filter as Playwright’s--grep, and the same as a positional pattern above). The startup check and any startup records are limited to the matching videos.--force-record: re-record every managed video at startup even when the kept recordings are up to date.--record-kill-window <seconds>: while connected, a running record younger than this is killed and replaced when a newer record request arrives; an older one finishes first and the new request runs right after (only the latest queued request is kept). Default: 10.--no-watch: stay connected like--watch, but without the source-file watcher (see below).--no-sync: do not pull queued browser edits into the sources. Meant for CI runners, whose checkouts must stay read-only (the generated GitHub workflow uses it, see CI Setup); edits stay queued for your next localprevieworsync, and the run prints a short note when edits are pending.-v, --verbose: verbose output.
Startup check: without a pattern or --grep, screenci preview records every
declared video at startup, so new and renamed videos are always picked up.
With a pattern, a freshness check decides instead: the CLI keeps each
recording’s data.json on disk after upload, along with a hash of the test
source file, and on startup compares that hash with the current source and
verifies every editable action carries an editId. Up-to-date videos skip
recording entirely; anything stale gets its missing editId slugs stamped
into the source and is re-recorded as a preview (no render) before the
session starts serving the editor.
Edits queued in the editor for a video name that is no longer declared in
your code (usually a rename) are marked failed in the editor; the CLI only
logs about them when your own --grep (or pattern) matches that name.
Editor codegen: while connected with --watch, every edit made in the web
editor is sent to this machine as a codegen request and written directly into the
.screenci.ts source (the code is the single source of truth). Applying an
edit never re-records on its own: render-time edits re-baseline the kept
recording’s source hash so it stays fresh, while a capture-affecting edit
leaves the recording stale and takes effect at the next preview or export.
An edit the codegen cannot apply is reported back and the editor reverts it.
Besides timeline edits this covers the editor’s option panels: render and
record option changes are merged into the video’s .renderOptions({...}) /
.recordOptions({...}) builder calls (the call is added when the section does
not exist yet), and narration text edits are merged into the
video.narration(...) declaration. Editing a narration text in a non-default
language converts a flat declaration to the language-major form (the existing
values move under default). A names-only narration declaration
(.narration(['intro'])) stays app-managed: its content lives in the web app
and is never codegen’d.
Source watching: while connected with --watch, screenci preview watches the test source files
backing the managed videos, plus screenci.config.ts. Saving a real change to
a source file automatically re-records that file’s videos as a preview (the
same freshness hash as the startup check filters out no-op saves and the
CLI’s own codegen writes); changing the config re-records every managed video.
Watching follows the -g/--grep filter and can be turned off with
--no-watch.
Setup (with an account): none beyond SCREENCI_SECRET. With the secret in
your project env file, the CLI exchanges it for this machine’s personal
editor token automatically on first use and saves it as
SCREENCI_EDIT_TOKEN (exchanged tokens are named after the machine and stay
listed and revocable on the Secrets page, up to five per user; you can still
create one by hand there and set it yourself). Run screenci preview --watch in
your project and the editor header shows your machine as connected (for
example you@laptop) and offers “Record … on laptop” in the Export
button’s record menu.
Without an account, no setup is needed: screenci preview runs under a local,
anonymous trial session (see
Anonymous Trial), and the anon session token
doubles as the editor token. The trial’s editor page stays editable even
while no machine is connected: edits queue server-side and land in your
sources on the next screenci command. Signing up claims the trial: a
running --watch session picks up your new SCREENCI_SECRET and a
claim-minted SCREENCI_EDIT_TOKEN (written into screenci/.env) and
reconnects without restarting.
While connected:
- Only your own account can trigger records on your machine. Teammates see whose machine is connected but cannot use it.
- A triggered record runs exactly one video in one language (the one open in the editor), then uploads as a normal record run: it refreshes the live preview, and renders only when the trigger was an export. The editor’s record menu also offers “Record raw preview footage”, which records without rendering at all.
- Web edits are applied to your sources as you make them in the editor; the editor shows a syncing state on your machine’s chip while a change is being written.
- The regular record run lock applies: if another recording run is already active on the machine, the request is reported back to the editor as failed.
- Stopping the command (Ctrl-C) disconnects the machine; the editor updates within about fifteen seconds.
Editor tokens are separate from SCREENCI_SECRET: the secret authenticates your
project to the service, the editor token additionally proves which user and
machine is listening. Revoking the token on the Secrets page immediately
disconnects the machine.
screenci sync
Pulls queued web edits into your .screenci.ts sources in one shot and
exits: it connects briefly, claims every edit queued while no machine was
connected, writes each into the source by its editId, and reports the
result back so the editor’s pending queue clears. This is the editing loop in
its smallest form: edit in the web editor, edits queue, and the next sync
writes them into your code. test and preview run the same sync
automatically on start; export deliberately does not (it warns about queued
edits and renders your sources exactly as they are), so run sync or
preview first when you want those edits in the export.
screenci syncOptions:
-c, --config <path>: path to the ScreenCI config file.
screenci info
npx screenci infopnpm exec screenci infoyarn screenci infoLists every video in the project with its public URLs as JSON, keyed by
video name and language code. It is where you get the remote videoId for
make-public, make-private, and delete.
When this machine has recorded a successful run, info also reports that run.
Each screenci export upload stores its unique record id in
.screenci/last-record.json; info then attaches, only to the videos
produced by that run, a per-language latestRecord with the run’s render
status and its record-pinned URLs, plus a latestRecordId on the video. Videos
that were not part of the run are still listed, with static URLs only. This is
the command to use in CI to gate on rendering or grab links right after an
export.
{ "projectName": "My Product", "videos": { "Onboarding": { "videoId": "kh74…", "latestRecordId": "5f1c…", "isPublic": true, "languages": { "en": { "static": { "video": "https://api.screenci.com/public/kh74…/en/video", "thumbnail": "https://api.screenci.com/public/kh74…/en/thumbnail", "subtitle": "https://api.screenci.com/public/kh74…/en/subtitle", "screenshot": "https://api.screenci.com/public/kh74…/en/screenshot" }, "download": { "video": "https://api.screenci.com/cli/download/kh74…/en/video", "thumbnail": "https://api.screenci.com/cli/download/kh74…/en/thumbnail", "subtitle": "https://api.screenci.com/cli/download/kh74…/en/subtitle", "screenshot": "https://api.screenci.com/cli/download/kh74…/en/screenshot" }, "latestRecord": { "status": "finished", "video": "https://api.screenci.com/public/kh74…/records/5f1c…/en/video", "thumbnail": "https://api.screenci.com/public/kh74…/records/5f1c…/en/thumbnail", "subtitle": "https://api.screenci.com/public/kh74…/records/5f1c…/en/subtitle", "screenshot": "https://api.screenci.com/public/kh74…/records/5f1c…/en/screenshot", "download": { "video": "https://api.screenci.com/cli/download/kh74…/records/5f1c…/en/video", "thumbnail": "https://api.screenci.com/cli/download/kh74…/records/5f1c…/en/thumbnail", "subtitle": "https://api.screenci.com/cli/download/kh74…/records/5f1c…/en/subtitle", "screenshot": "https://api.screenci.com/cli/download/kh74…/records/5f1c…/en/screenshot" } } } } } }}Each language exposes up to three URL sets:
static: stable, public URLs that always follow the currently selected versiondownload: authenticated download URLs for the selected version (see below)latestRecord: this run’s render status plus public URLs pinned to it via arecords/<recordId>path segment (an immutable contract: the exact run, or404, see resolution rules). Once finished, it also carries record-pinneddownloadURLs.
Each URL set carries one entry per asset: video, thumbnail, subtitle, and
screenshot. A version is either a video or a screenshot, never both, so use the
video asset for video recordings and the screenshot asset for screenshot
recordings: the asset that does not match the version’s type 404s when fetched.
static is null for videos without public delivery enabled. latestRecord
carries the render status even for private videos, but its public URLs are then
null.
The download URLs are private: they require your X-ScreenCI-Secret
header, so they are not embeddable, and they work even for private videos. Use
them to archive a render permanently, before older versions are pruned (see
Keep a render forever):
curl -H "X-ScreenCI-Secret: $SCREENCI_SECRET" \ "https://api.screenci.com/cli/download/kh74…/records/5f1c…/en/video" \ -o video.mp4Render status (under latestRecord.status) is one of:
finished: a render finished and is servablerendering: a render is still in progress (or has not started)failed: a render failed and none for that language is still in progress
Because rendering happens after upload, poll info until each latestRecord
reaches finished (or gate your pipeline on failed).
Not recorded on this machine? If there is no .screenci/last-record.json
(for example a fresh checkout that has never run screenci export), info does
not fail. It simply omits latestRecordId and the latestRecord fields and
prints the project-wide listing with static URLs only.
screenci make-public <videoId>
npx screenci make-public kh74…pnpm exec screenci make-public kh74…yarn screenci make-public kh74…Enables public delivery for a video. Get the ID from screenci info.
When you make a video public, ScreenCI starts it in the same mode as the app:
- public delivery is enabled for the video
- auto-select latest is enabled
- the latest finished render for each language becomes the active public output
That means make-public is the CLI equivalent of turning on Enable public
URL in the dashboard.
screenci make-private <videoId>
npx screenci make-private kh74…pnpm exec screenci make-private kh74…yarn screenci make-private kh74…Disables public delivery for a video.
This is the CLI equivalent of turning off Enable public URL in the dashboard.
screenci delete <videoId>
npx screenci delete kh74…pnpm exec screenci delete kh74…yarn screenci delete kh74…Permanently deletes a video and all of its renders. Use the same videoId that
make-public and make-private accept (copy it from screenci info).
This is irreversible, so the command prints the video’s name and asks you to
confirm before deleting. Pass -y (or --yes) to skip the prompt in CI or
scripts:
npx screenci delete kh74… --yesYou can only delete videos that belong to your own project’s organization.
What the CLI does not do
The CLI currently covers:
- project scaffolding with
init - project auth setup (handled automatically on first
export) - local iteration with
test - live previews and editing sessions with
preview - finished videos with
export - per-run URLs and render status with
info - public visibility changes with
make-publicandmake-private - permanent video deletion with
delete
Manual version pinning is currently handled in the app UI:
- turn off Auto-select latest version
- open a language section
- choose the version to mark as Selected
Shared environment and config behavior
These commands support --config <path>:
testpreviewexportinfomake-publicmake-privatedelete
SCREENCI_SECRET is used for:
- auth bootstrap and persistence
- uploads
- per-run URLs and render status (
info) - public delivery changes
- video deletion (
delete)
If envFile is configured in screenci.config.ts, the CLI loads it
automatically. Otherwise it falls back to the project .env. The CLI resolves
envFile by evaluating the config the same way Playwright does, so a dynamic
value like envFile: isLocal ? '.env.local' : '.env' picks the right file at
run time.
That env file is the recommended place to keep SCREENCI_SECRET and other
runtime variables your setup needs. Your ElevenLabs key is not kept here: add it
on the Secrets page in the app instead (see
Narration).
Related pages
- Configuration for
screenci.config.ts.