CLI reference

For AI agents: this page is Markdown at https://squiggle.sh/docs/reference/cli.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.

The squiggle command has 39 commands, grouped here by what you use them for, with the flags you'll use most. squiggle --help lists every command and flag.

Find any command

Terminal
squiggle                  # pick a command from a menusquiggle --help           # every command, groupedsquiggle perf --help      # the commands under perfsquiggle perf sql --help  # one command's arguments, defaults and examplessquiggle --schema-json    # every command, flag and exit code, as JSON

For tab completion, run squiggle completions zsh --install (or bash, or fish).

Set up and check

CommandWhat it does
initSets Squiggle up in your app. See Set up your app.
addadd screen <kind> [name] writes a screen from a template, with its route, flow, declared controls and budget. The kind is list, form, auth, detail, settings or onboarding; the name is its route, such as orders (default: the kind). --dry-run prints every file it would write, --force overwrites files that already exist, and --app-dir points at the app when it isn’t the current folder. See Start a screen from a template.
upRelaunches the app with Squiggle attached, and checks it. The first up in a project also watches the app for a few seconds and keeps a perf baseline named first, so perf diff --against first has something to compare with from day one. --no-baseline skips it; delete .squiggle/baselines/first.json to take it again.
doctorChecks the simulator, the app, screen tracking, source lines and your licence. --fix repairs what it can; --bundle writes a support file.
activate · licenseStart the trial, activate a key, and check your licence. See Licence and trial.

Run flows

CommandWhat it does
flowRuns one flow, by name or path.
flowsRuns every flow in a folder, split across simulators.
watchRuns a flow again every time you save a file.
benchRuns a flow and compares its speed with the last benched run that passed. --runs <N> runs it N times and records one: the median run, or, when any run failed, the failed run, so the command exits 1 and that row is never compared against. --history shows the trend.
bisectFinds the commit that broke a flow by running it on each commit, served through Metro with no rebuild. See Find the commit that slowed a flow and the flags below.
bakeoffRuns one flow against your current commit and the revisions you name with --against (comma-separated), served through Metro with no rebuild, taking turns run by run. Prints one table: passes, flow time, renders per interaction, dropped frames, memory growth and startup. --runs sets the runs per revision (default 3). A revision that changes native code or dependencies is refused. See Compare two implementations.
motionRecords how a view moves after a tap and fits the spring that reproduces it: damping ratio, stiffness, settling time, and the withSpring config to paste. A motion that is not a plain spring is reported as one. Flags below.
crawlExplores the app on its own and reports the screens it reached and any crashes. --tour opens every screen the app declares; --changed tours only the screens your branch touches, and --files <a.tsx,b.tsx> does the same for the files you name. --only <paths> adds paths to the tour, even ones Squiggle has never seen. --legibility, with --tour or --changed, scores each toured screen for how easily an agent can drive it.
historyLists recent runs. history rerun <id> runs one again.

Flags for flow

FlagWhat it does
--freshRelaunches the app before the run. Use it after a failed run.
--auto-scrollScrolls a control into view when a step targets one that is off screen.
--continue-on-failRuns every step, instead of stopping at the first failure.
--chaos <profile>Runs the flow on a bad network: slow, flaky, truncated or offline-midstep. none runs it on a working network, even when the flow declares a profile.
--stubs <run>Answers every request from stubs built from an earlier run (its id, or latest): the same status, latency and response shape, with placeholder values.
--twin <profile>Runs the flow on a working network, then under a chaos profile (or all), and reports what the app did differently. Exits 0 when every run completes, whatever it found.
--fuzzRuns the flow, then breaks each endpoint it called one at a time (500, empty, cut off, 5 s late, 404). --fuzz-max caps the runs (default 10). --twin, --fuzz and --race each choose the network themselves, so they can’t be combined with each other, --chaos or --stubs.
--cache-twins <cold>,<warm>Runs the flow from two save slots, one taken before anything was cached and one after, and lists each endpoint with both runs’ request counts: cached, refetched, more, or warm only. Can’t be combined with --twin, --fuzz or --fresh.
--bisect-state <green>,<failing>Finds the app state that makes the flow fail. Give it two save slots, one the flow passes from and one it fails from: it loads the first with some of the second’s values put in, runs the flow, and narrows to the smallest set of values that still fails. With --race, the race search runs from the failing slot, and each race it finds is searched the same way after its steps are shortened. Exits 1 unless it finds them. Can’t be combined with --fresh, --cache-twins, --tune, --twin, --fuzz or --matrix.
--raceSearches for races. Every request is answered 2 s late from a recording, then the same tap is fired again, or Back pressed, at points while it is pending. Reports a request sent twice or a screen that appears after Back, each with its shortest reproduction. --race-max caps the runs (default 16). Exits 1 when it finds a race.
--race-random <seed>With --race: places the repeated taps at random points instead, from this seed, to compare against the default search.
--boundariesOn each screen the flow reaches, checks a point 1 pt inside and outside every edge of each control that has a testID or label, and reports an edge whose touches land on something else. A dead edge fails the run. Needs the app launched with squiggle up.
--boundaries-random <seed>With --boundaries: checks 400 random points instead of the control edges, from this seed, to compare against the default search.
--cliff <VAR=lo..hi>Runs the flow with its env VAR at sizes from lo to hi, and reports the size where dropped frames stop being flat, with what rendered most there. Exits 1 when it finds one. Each sweep is saved in .squiggle/gate/cliffs.jsonl, which the report card reads against the sizes a screen declares.
--tune <name>Chooses the value of a tunable the app declared with defineTunable. Runs the flow at every value the tunable allows, without relaunching, scores each run on what its assert: { scroll } measured, and proposes the lowest value when it beats the default by more than the noise of five runs. Saves the result for propose_fix. Can’t be combined with --fresh.
--tune-weights <dropped=1,blank=0.05,ms=0>With --tune: the score of one dropped frame, one point of blank band and one millisecond of the run.
--tune-reps <n>With --tune: runs at each value (default 3).
--tune-slot <slot>With --tune: starts every run from this save slot instead of the flow’s prerequisite.
--matrixRuns the flow once in each simulator setup (as found, the other appearance, the largest text size) and reports the overlapping, cut-off or off-screen controls each setup adds, including a control the first setup showed that another pushes off a screen that can’t scroll it back. Exits 1 when one adds any. iOS simulator only. See Run settings.
--repair <name,name>With --matrix: on the flow’s last screen, searches these declared layout tunables for values that leave no overlapping, cut-off, off-screen or under-44 pt control in any setup, moves them onto the tokens each tunable declares, and saves the result for propose_fix. Exits 1 when nothing it tries clears every setup.
--repair-search <descent|grid|random:seed>With --repair: how to search. The default is the one that needed the fewest measurements on the example app; the others are there to check it against.
--repair-control <seeds>With --repair: also measures what the grid, random search over this many seeds, and step-by-step descent each spend to reach a layout with no problem in any setup, on one set of measurements, and saves it with the repair.
--ax-onlyFinds every control through the accessibility tree only, the way VoiceOver reaches it. See Run settings.
--settle-debugPrints every wait under its step and, when a wait runs long, names what was still animating: the component and its file:line.
--junit <file>Writes JUnit XML, which most CI systems display.
--eventsPrints one JSON line per step, for CI logs and scripts.
--recordRecords a video of the run. --attach-pr adds it to the pull request for this branch.
--needs-agent failExits 1 instead of 6 when an agent step needs an agent.
--inline <yaml>Runs a flow given as text. flow - reads it from standard input.

squiggle flow --help lists the rest, and Run settings covers the flags that change how steps are delivered.

Flags for bisect

Terminal
squiggle bisect login --good v1.4.0squiggle bisect login --good main~20 --budget-ms 3000 --runs 3 --hunks
FlagWhat it does
--good <ref>A commit where the flow passes. Required.
--bad <ref>A commit where it fails. Default: HEAD.
--budget-ms <N>A run slower than N ms counts as bad too, so you bisect a slowdown instead of a failure.
--runs <N>Runs per commit, judged by the median. Default: 1. Use 3 or more with --budget-ms.
--hunksAfter finding the commit, reverts each change in it one at a time and reports how much of the slowdown each one accounts for.
--app-dir <path>The Expo app’s folder, when it isn’t the project root.

bisect needs a booted simulator running an Expo development build whose app.json sets expo.scheme, and a range of commits that only change JavaScript. It exits 0 when it finds a confirmed commit, 1 when the result isn’t confirmed or there is nothing to bisect (the bad end passes, or the good end already fails), 2 when the range is invalid or changes native code (E_BISECT_NATIVE), and 5 when no simulator is booted.

Flags for bakeoff

Terminal
squiggle bakeoff checklist-scroll --against flatlist-variantsquiggle bakeoff login --against legend-store,zustand-store --runs 5
FlagWhat it does
--against <ref,ref>The branches or commits to compare with your current commit, comma-separated. Required.
--runs <N>Runs per revision, taken in turns and judged by the median. Default: 3.
--app-dir <path>The Expo app’s folder, when it isn’t the project root.

bakeoff needs the same setup as bisect. It exits 0 when every run of every revision passed, 1 when a run failed or a revision never ran (E_BAKEOFF_NO_RUN), 2 for bad arguments or a revision that changes native code (E_BAKEOFF_NATIVE), and 5 when no simulator is booted.

Flags for motion

Terminal
squiggle motion sheet --tap open-sheetsquiggle motion nav-indicator --tap nav-pill-1,nav-pill-2 --json > nav.jsonsquiggle motion nav-indicator --tap nav-pill-1,nav-pill-2 --match nav.json
FlagWhat it does
<view>The testID of the view that moves. Its on-screen position, size and opacity are recorded.
--tap <testID,…>What to tap to start the motion. Required. With several, they are tapped in turn, so a motion that goes there and back is recorded both ways.
--ms <N>How long to record after each tap. Default: 800.
--runs <N>How many recordings. The first half are fitted and the rest are held out to check the fit. Default: 6.
--match <file>Scores the recordings against a fit saved by an earlier --json run, to check that a changed config still produces the same motion.
--jsonPrints the fit and every recording.
--udid <id>The simulator, when several are booted.

Each moving property gets its own line. When the held-out recordings miss the fitted spring by more than 0.25% of the distance travelled, it says NOT a plain spring from rest and prints no config, for example because the motion is retargeted while it runs. Otherwise it prints a withSpring config with mass: 1: Reanimated’s default mass is 4, so keep the mass when you paste it. Where Reanimated can solve it, it also prints the dampingRatio and duration form, with the duration in milliseconds.

motion needs an iOS simulator and the app launched with squiggle up. It exits 0 with a fit, 1 when nothing moved, a tap target wasn’t on screen, or --match doesn’t match, 2 when the --match file can’t be read, and 5 when there is no simulator or the app runs without Squiggle attached.

Record and replay

CommandWhat it does
recordRecords the app until you press Ctrl-C, into .squiggle/recordings/.
replayReplays a recording, and can check a flow against it with no device. With no file, lists recordings.
diffCompares two recordings: renders, screens, network requests and timings. With --journey <intent> (that program’s runs that passed) or --cohort <pattern>, compares one recording with many and lists every number outside the range of all of them.
contractscontracts propose proposes asserts for what your screens have always shown, from the recordings of runs that passed. Prints the proposal; --write adds the asserts to your flows and --pr opens a pull request with them. --min-confidence sets the share of passing visits an assert must have held in (default 0.9), and --app picks the app when your recordings are of more than one. See Assertions.

Save and load app state

CommandWhat it does
savesquiggle save <name> saves the screen the app is on and the data behind it as a named slot, in .squiggle/agent/fixtures/<name>.json. --description adds a note that load lists. It prints what it saved and any paths it left out.
loadsquiggle load <name> puts a saved slot back in under a second, instead of tapping through screens to rebuild it. With no name, lists slots, with no device needed.

Both take --udid <id> to pick the simulator whose app to use when several are running; without it, they use the app launched most recently with Squiggle attached. Both take --json too. A name starts with a letter or digit and has up to 80 letters, digits, dots, dashes and underscores; any other name exits 2. A load the app refuses exits 1 with the reason, for example a slot saved by an older version of the app: save it again. MMKV data stays on the simulator the slot was saved on.

Read the data

CommandWhat it does
perfPerformance data: sql, top, saved, detector-stats, report, insights, watch, snapshot, diff, export, labels, budget, gc, profile, heap, heapdiff, crashes and symbolicate.
productProduct analytics: overview, screens, usage, affinity, releases, users, sessions, state, retention, friction and funnel.
probeReads a running app through Metro, with no rebuild: renders, animations, timers and CPU. probe when runs your own rules inside the app and probe export keeps them as a tested detector; probe inputs lists what a rule can read, with each input’s range. probe replay and probe when need a licence to drive, probe export one to analyze; the rest are free. Flags below.
reprojectRebuilds the performance data from your recordings.

The perf and product commands you’ll use most

CommandWhat it does
perf diff --against <name>Compares the newest session (or --session) with a baseline saved by perf snapshot. --against first compares with the baseline the first squiggle up took. --assert <file> exits 1 on any breach of its thresholds.
perf budgetChecks every screen with a budget against the newest session (or --session <id>), with the margin to each limit. Exits 1 on any breach.
perf budget --changedRuns the fewest flows that reach the screens this branch changed, and checks only those screens. --base <ref> sets what the branch is compared with (default: where it left origin/main). A changed screen no flow reaches is listed under no flow covers, and doesn’t fail the check.
perf labelsWrites every labelled run (outcome, heals, escalations, frame windows, and the app state) as JSONL, to .squiggle/agent/labels.jsonl. --out picks another file, which must be inside .squiggle/. Runs under --chaos, --stubs, --fuzz or --race are marked synthetic, with what each broke.
product state [path]With no path, lists the state paths the app has sent, such as zustand:useCart.items#len. With a path, shows its values per screen; --screen <route> keeps one screen. --coverage lists the states real sessions reach that no flow or save slot does, each with a proposed slot; --save-gap <n> --as <name> saves gap n’s slot. --drift compares two releases (--from and --to pick them; default the latest against the one before). --perf reads performance findings against the state they happened in.
product … --where <rule>Every product report takes --where, such as --where "zustand:useCart.items#len >= 3", and splits its numbers between the sessions that reached that state and the rest. product friction --state also adds the app state behind each friction cluster.

Flags for probe when and probe export

Terminal
squiggle probe inputssquiggle probe when 'fps < 45' 'scroll.moved == 1' --id janky-scrollsquiggle probe when 'render.watch0 >= 5' --component ProductCard --for 30squiggle probe export 'fps < 45' 'scroll.moved == 1' --id janky-scroll --out src/detectors
FlagWhat it does
<rule> …One or more rules, each an input, a comparison and a number, such as render.max >= 30. probe inputs lists the inputs.
--anyFires when any rule holds. Default: when all of them do.
--component <name>A component whose renders render.watch0 to render.watch7 count, in the order given. Repeatable.
--id <name>The probe’s name, which probe export finds its firings by. Default: the rules, joined.
--for <seconds>probe when: stops after this long. Default: runs until Ctrl-C.
--udid <id>probe when: the simulator whose app to probe, when several are booted.
--session <id>probe export: the session whose firings to use. Default: the newest that has any.
--out <dir>probe export: where to write the detector, its test and the firings it is tested on. Default: the current folder.
--runner <bun|vitest|jest>probe export: the test runner the test is written for. Default: bun.

probe export takes the same rules, --any and --component the probe ran with, and refuses others (E_PROBE_MISMATCH).

Agents

CommandWhat it does
mcpStarts the MCP server for your coding agent. See Connect your agent.
agentRuns and checks agent programs, and installs the agent kit (agent kit --install).
legibilityScores how easily an agent can drive your app, and names the lines to fix, biggest gain first. --fix adds the missing testIDs.
cardThe feature report card: starts the app cold, runs the flows over the screens your change touched and checks their budgets, startup, findings, legibility, wasted renders, render growth and each screen’s requests against the last green run. Each flow runs against stubs built from its last green run, so the card needs no backend. Exits 1 until every row passes. Flags below.
benchmarkScores an agent on a set of tasks in your app. See Benchmark an agent.

Your agent’s tools are listed in MCP tools.

Flags for card

What each row checks, and how to read one, is in The report card.

FlagWhat it does
--files <a.tsx,b.tsx>The files the change touched. Default: what this branch changed since --base.
--base <ref>Without --files, what the branch is compared with. Default: where it left origin/main.
--fullEvery remembered screen and every saved flow, not only what the change reaches.
--no-tourSkips touring the touched screens and checking their legibility.
--all-flowsRuns every saved flow that covers a touched screen, instead of the fewest that reach them all.
--dry-runPrints what the card would tour and run, and doesn’t touch the simulator.
--liveRuns the flows against your real backend. Use it when a flow checks text that stub placeholder values would break.
--jsonPrints the whole card.

Every card is saved to .squiggle/runs/card-<time>.json. When it ran any flows, its numbers are also recorded for squiggle gate app.

The agent commands

CommandWhat it does
agent kitThe skills, subagents and commands for your agent. --install writes them into the repo; -o <dir> writes everything, hooks included, to read first.
agent rulesAn AGENTS.md section for this repo: the commands to use and the mistakes to avoid. --write writes AGENTS.md at the project root.
agent configThe deny rail and run limits. --init writes the defaults to .squiggle/agent.json.
agent runRuns an intent program against the app. With --answers-stdin, another program answers its decisions on standard input, one JSON line each.
agent validateChecks an intent program without running it.
agent branchReplays a program’s first steps (--at <n>), then tries each alternative (--alt, repeatable) from there. With --from-slot, it runs those steps once, saves a slot there and loads it before each alternative, instead of relaunching and replaying; the steps must end on a known screen (a waitFor or assert on the route). If the slot can’t be used, every branch replays and the result says why. --order margin runs the alternative whose first tap most surely matches a control on the branch screen first, and --first-win stops at the first alternative that reaches its goal. Exits 0 when an alternative reaches its goal.
agent schemaPrints a JSON schema: intent programs (the default), flows, or the config file. -o writes it to a file.
agent libraryThe saved flows your agent can replay.
agent journalWhat the agent did in a run, and why.
agent watchTurns new crashes, friction and findings into briefs as they appear, for your agent to pick up.
agent statsModel turns, heals, escalations and cost across runs. --history shows the trend.
agent briefingThe working rules your agent is given, including what it has learned about this app.
agent amendOnly what the agent has learned, so you can review it in a pull request.
agent evalReplays recorded agent runs and reports each outcome.
agent skillProposes a skill from decisions that keep coming up, and keeps it only if it measurably helps.
agent patternsPopup handlers you can share between projects, with no app data in them.
agent exportThe run history as JSONL training data, each run scored by whether it reached its goal.

Everything else

CommandWhat it does
explainExplains an exit code or error code. See Exit codes and errors.
updateUpdates Squiggle. --check only checks; --to <version> installs a specific version.
uninstallRemoves Squiggle. --dry-run lists every file it would delete, with sizes.
telemetryShows what anonymous usage data is sent. telemetry off turns it off.
redactShows what would have to be removed from a file before you share it. --write removes it.
completionsTab completion for zsh, bash or fish.
licensesThird-party notices for what the binary includes.
consumerWatches the app with no UI and records performance until you stop it.
gateChecks that Squiggle keeps working when the app relaunches (gate relaunch) and that the MCP server works (gate mcp).
gate appFails when the newest card, or the app’s bundle size, is worse than on main by more than its budget allows. --base <ref> picks what it compares with (default origin/main). In CI, --bundle <dir> measures the iOS bundle an expo export wrote there, and --base-bundle <dir> the base’s, built in the same job. Needs no licence. See Run in CI.

Output for scripts and CI

With --json, a command prints only its data on standard output, and its progress on standard error, so you can pipe it:

Terminal
squiggle flow checkout --json | jq '.steps[] | select(.status == "fail")'squiggle perf sql "select * from spans limit 5" --json | jq '.[]'

Every status has a symbol as well as a colour. --no-color, --no-unicode, NO_COLOR and CI switch to plain output.

Screenshots of failed runs

When a flow fails, Squiggle saves a screenshot of the simulator to .squiggle/runs/ and prints its path under the failing step. Terminals that can show images (iTerm2, kitty, WezTerm, Ghostty, VS Code) show the screenshot itself. SQUIGGLE_NO_IMAGES=1 prints only the path.