Command reference
Every command the CLI answers to.
Run ff help for this list in the terminal, or ff help <command> for one command
in detail. Both read from the same list in the CLI source, so they cannot disagree with each other —
though they can drift from this page, which is written by hand.
For commands and agent prompts in execution order, see From sign-in to a published flow.
/ff-context is an agent skill that writes APP_CONTEXT.md; it is not a CLI subcommand.
Setup
| Command | What it does |
|---|---|
ff init | Create the .fixtureframe workspace directory in this folder. |
ff doctor | Check this workspace's configuration and report what is missing. |
ff session init | Sign in to the target app interactively and save authentication state. |
ff session status | Inspect saved-session metadata without showing cookie values. |
ff session clear | Remove the workspace's saved session. |
ff agent install | Install or refresh the coding-agent skills. |
ff adapter list | init | inspect | List, scaffold, or inspect auth adapters. |
Authoring
| Command | What it does |
|---|---|
ff record [adapter] | Record yourself using the application and save it as a flow. |
ff deck <deck.pptx> | Turn a slide deck into a draft flow: one step per slide, notes as narration. |
ff media <file...> | Turn pictures and clips you already have into a flow: one step per file. |
ff publish <flow.json> | Save a local flow file as a flow, or as the next version of one. |
ff survey [adapter] | Survey an environment read-only and store the map as authoring evidence. |
ff discover <adapter> | Explore an application with an adapter and save what it found. |
ff scenario create | preview <adapter> | Create or preview a capture scenario for an adapter. |
ff sample interactive | Create a sample interactive demo to exercise the player. |
deck takes four shapes: a .pptx file, a .pdf file, a Google Slides link, and a public link to
a deck anywhere else. Files are uploaded from here; a link is handed over and fetched by the control
plane, which is the only side that can refuse one safely. Only the conversion happens there — laying
a deck out faithfully needs an office suite.
media needs none of that, so it works locally: .png, .jpg, .webp, .svg, .mp4 and
.webm, in the order given.
publish uploads any asset the file names by path and saves it as a URL, so a flow written by hand
— pictures beside it, narration and card text written in it — is complete rather than dependent on
the machine that wrote it. See Flow files.
record and script both photograph each step as they capture it, and publish those frames with the
flow. Frames normally come from a run — a recording proves the steps, and only a replay proves they
still resolve — but a flow nothing has run yet would otherwise open in Studio as a storyboard of rows
saying "No frame", which is unreviewable for want of a picture the browser was already showing. A
run's own frames replace them the moment one exists.
record --narrate records your microphone while you walk. The words are transcribed and written onto
the steps as narration, so a walkthrough explained aloud arrives narrated rather than silent. It is
off by default — a microphone is not visible the way a screen is — and it pauses when the recording
does, so nothing said while you sign in by hand is kept.
Data
| Command | What it does |
|---|---|
ff profile create <name> | Write a data profile from what a flow actually needs. |
ff profile list | The profiles in this workspace, and what each one fills. |
A flow declares parameters, a profile supplies their values, and the parameter name is the join between them — so a profile written by hand is a guess at names some flow chose, and when the guess is wrong the two simply fail to meet. No error, no run, just a value nobody filled.
profile create --from <flow.json> reads the flow and works out what it needs. A flow that declares
parameters keeps them, because a declaration is a decision somebody already made. Otherwise the
values are proposed from the flow itself — deterministically, with no browser and no model, so the
same flow gives the same answer every time and the reason for each is printed beside it.
Names already used in the organisation are preferred over new ones, which is the whole point of the
join: a merchant in one flow and a vendor in another are never filled by the same profile, and
nothing reports it when they fail to.
Three kinds of value are named and then left out. A generated one differs per run by design. A secret is supplied by the runner's environment and never written to a file that travels. A route is where a flow runs rather than who it runs for, and belongs to the environment.
The profile is written to .fixtureframe/profiles/data/<name>.json and the record is saved to your
organisation. By default only the parameter names are sent: the values stay in the file, which is
what lets demo data that may not leave a network stay inside it. --inline sends the values too, so
Studio can show and edit them; --local-only publishes nothing.
Keeping a repository of flows in sync
| Command | What it does |
|---|---|
ff pull | Fetch the control plane's flows into this working copy. |
ff push | Save changed flow files to the control plane as new versions. |
publish saves one file you were just told about. pull and push are for a directory of flows
kept under review: they work out which side of each flow moved since the two last agreed, and carry
only that. Running either twice does nothing the second time, which is what makes them safe from a
merge hook.
Both refuse rather than guess when both sides moved, and both exit non-zero when anything was left
alone — a conflict reported only on stdout is a conflict a pipeline never notices. --force picks a
side: pull --force takes the control plane's, push --force takes the file's.
What the two sides last agreed on is recorded in .fixtureframe/flow-sync.json, beside
config.json. Commit it with the flow files. It stores two hashes per flow — the file's and the
saved flow's — which is what lets an edit be told from a conflict; without it, every difference
looks like both. Two hashes rather than one because a flow file that names pictures by path is
meant to differ from the saved flow, which carries the URLs they were uploaded to.
ff pull --dry-run # what would come down
ff pull # take it
ff push --dry-run # what would go up
ff push # save itBoth group every file by outcome and end with one summary line, so nothing is left implicit — a file that was considered and found unchanged is listed as such, rather than being absent and indistinguishable from one the command never reached.
Would create (1):
.fixtureframe/flows/onboarding.flow.json Onboarding
Would save as a new version (1):
.fixtureframe/flows/submit-an-expense.flow.json Submit an expense v2
Left alone (1):
.fixtureframe/flows/download-a-report.flow.json — "Download a report" changed on the control plane. Pull it, or push --force to overwrite.
3 flow files: 1 created, 1 saved, 1 left alone.What actually travels
Only the flow definition. pull writes *.flow.json and nothing else; push sends the same file
back as a new version.
Run outputs — story plans, render plans, transcripts, captions, video, captured frames, DOM
captures — are not synced in either direction. They belong to a run rather than to a flow: a
run produces them, they are uploaded as artifacts under the run that made them, and they are read
back with ff status <run-id> or in Studio's gallery. A flow file is the input; those are the
evidence of one execution of it.
Two things travel alongside the definition, in one direction each:
- Up: any asset the definition names by
path— a picture, a clip, a music bed.pushuploads it to your organisation's library and saves the flow with a URL, exactly aspublishdoes, and only for the flows it is actually saving.--keep-pathsdeclines. - Neither:
.fixtureframe/flow-sync.jsonis written locally and never sent. It is bookkeeping for this checkout, and it belongs in git rather than in the control plane.
pull does not download asset files. A pulled definition carries the library URLs, which is what
makes it renderable anywhere; the originals stay wherever their author kept them.
Checking, before anything costs anything
These are free. They open no browser, spend nothing, and change nothing — which is the point: each answers a question that would otherwise be answered by a capture, two to four minutes later.
| Command | What it does |
|---|---|
ff brief <brief.yaml> | Check a brief, and show who agreed to what. |
ff lint <walk.mjs> | Check a walk against the surveyed product without running it. |
ff suggest | Read the repository for the walkthroughs it has not written yet. |
ff diagnose <run-id> | What went wrong in a run, for whoever is going to fix it. |
lint never surveys. Without a survey of the environment it says so and stops, because five of its
eight rules need one — and a rule that cannot match reports nothing, which reads exactly like a rule
that found nothing wrong. ff survey fills it in, once per environment.
brief and lint both exit 2 when the thing they checked has problems, and 1 when the command
itself did not run. Every command here takes --json.
Coding agents
| Command | What it does |
|---|---|
ff agent install | Install the skills and the permission table for the agents this repository uses. |
See the agentic pipeline for what these instruct, and installing the skills for what lands where.
Execution
| Command | What it does |
|---|---|
ff validate <flow.json> | Check a flow file against the canonical schema without running it. |
ff script <walk.mjs> | Record a flow by running a Playwright script rather than walking it yourself. |
ff run [flow.json] | Execute a governed run and publish what it produces. |
ff status [run-id] | Show what a run is doing, or what it produced. |
script reads a brief named after the walk before it runs anything. If that brief's beats are not
confirmed, the walk is refused — capturing from beats nobody agreed to spends a vendor call and
leaves state behind for a story nobody has read. No brief at all is a warning, unless the project
sets require_brief in .fixtureframe/config.json.
Flags every control-plane command takes
| Flag | Meaning |
|---|---|
--api-url <url> | Control plane to talk to. Defaults to FIXTUREFRAME_API_URL. |
--environment <name> | Environment by name, as shown in Settings. |
--environment-id <id> | Environment by id, when two share a name. |
--directory <path> | Workspace directory holding .fixtureframe. Defaults to the current one. |
ff suggest is the one command that also reads a second directory — the product's code, which is
routinely a different checkout from the workspace holding the credentials.
| Flag | Meaning |
|---|---|
--source <path> | The code to read for end-to-end tests. Defaults to source in .fixtureframe/config.json, then the current directory. |
--include <path> | Another directory holding browser tests, for a project that keeps them somewhere unconventional. Repeat for each. |
--catalog <path> | An existing catalogue whose decisions — including every rejection — must be honoured. |
The source fallback resolves against the current directory rather than the workspace, so pointing
--directory at one place and standing in another scans where you are standing. See
Working in a session.
Flags the asset-uploading commands take
record, script, media, publish and push all put files in your organisation's library.
record and script upload the frames they photographed — and, for record --narrate, the take
they recorded — through the same route a hand-written flow's pictures take. The flow file keeps its
relative paths, which is what makes it re-publishable; the copy that leaves carries URLs.
| Flag | Meaning |
|---|---|
--organization <id> | Whose library to upload to. Derived from the API key; needed only when the key names several. |
--assets-dir <path> | publish and push: where local asset paths resolve from. Defaults to each flow file's own folder. |
--keep-paths | publish and push: save local paths as written, without uploading. |
--local-only | Write the flow file and save nothing. media still uploads its files; the others upload nothing. |
push uploads only for the flows it is actually saving, so an unchanged flow that names a picture
costs nothing.