# CLI reference

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

```shell
squiggle                  # pick a command from a menu
squiggle --help           # every command, grouped
squiggle perf --help      # the commands under perf
squiggle perf sql --help  # one command's arguments, defaults and examples
squiggle --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

| Command | What it does |
| --- | --- |
| `init` | Sets Squiggle up in your app. See [Set up your app](https://squiggle.sh/docs/setup). |
| `add` | add 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](https://squiggle.sh/docs/run/templates). |
| `up` | Relaunches 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. |
| `doctor` | Checks the simulator, the app, screen tracking, source lines and your licence. --fix repairs what it can; --bundle writes a support file. |
| `activate · license` | Start the trial, activate a key, and check your licence. See [Licence and trial](https://squiggle.sh/docs/licence). |

## Run flows

| Command | What it does |
| --- | --- |
| `flow` | Runs one flow, by name or path. |
| `flows` | Runs every flow in a folder, split across simulators. |
| `watch` | Runs a flow again every time you save a file. |
| `bench` | Runs 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. |
| `bisect` | Finds 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](https://squiggle.sh/docs/run/bench#find-the-commit-that-slowed-a-flow) and the flags below. |
| `bakeoff` | Runs 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](https://squiggle.sh/docs/run/bench#compare-two-implementations). |
| `motion` | Records 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. |
| `crawl` | Explores 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. |
| `history` | Lists recent runs. history rerun <id> runs one again. |

### Flags for flow

| Flag | What it does |
| --- | --- |
| `--fresh` | Relaunches the app before the run. Use it after a failed run. |
| `--auto-scroll` | Scrolls a control into view when a step targets one that is off screen. |
| `--continue-on-fail` | Runs 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. |
| `--fuzz` | Runs 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. |
| `--race` | Searches 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. |
| `--boundaries` | On 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. |
| `--matrix` | Runs 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](https://squiggle.sh/docs/run/settings#check-every-screen-in-each-setup). |
| `--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-only` | Finds every control through the accessibility tree only, the way VoiceOver reaches it. See [Run settings](https://squiggle.sh/docs/run/settings#run-a-flow-the-way-voiceover-reaches-it). |
| `--settle-debug` | Prints 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. |
| `--events` | Prints one JSON line per step, for CI logs and scripts. |
| `--record` | Records a video of the run. --attach-pr adds it to the pull request for this branch. |
| `--needs-agent fail` | Exits 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](https://squiggle.sh/docs/run/settings) covers the flags that change how steps are delivered.

### Flags for bisect

```shell
squiggle bisect login --good v1.4.0
squiggle bisect login --good main~20 --budget-ms 3000 --runs 3 --hunks
```

| Flag | What 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. |
| `--hunks` | After 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

```shell
squiggle bakeoff checklist-scroll --against flatlist-variant
squiggle bakeoff login --against legend-store,zustand-store --runs 5
```

| Flag | What 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

```shell
squiggle motion sheet --tap open-sheet
squiggle motion nav-indicator --tap nav-pill-1,nav-pill-2 --json > nav.json
squiggle motion nav-indicator --tap nav-pill-1,nav-pill-2 --match nav.json
```

| Flag | What 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. |
| `--json` | Prints 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

| Command | What it does |
| --- | --- |
| `record` | Records the app until you press Ctrl-C, into .squiggle/recordings/. |
| `replay` | Replays a recording, and can check a flow against it with no device. With no file, lists recordings. |
| `diff` | Compares 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. |
| `contracts` | `contracts 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](https://squiggle.sh/docs/flows/assertions#propose-asserts-from-runs-that-passed). |

## Save and load app state

| Command | What it does |
| --- | --- |
| `save` | squiggle 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. |
| `load` | squiggle 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

| Command | What it does |
| --- | --- |
| `perf` | Performance data: `sql`, `top`, `saved`, `detector-stats`, `report`, `insights`, `watch`, `snapshot`, `diff`, `export`, `labels`, `budget`, `gc`, `profile`, `heap`, `heapdiff`, `crashes` and `symbolicate`. |
| `product` | Product analytics: `overview`, `screens`, `usage`, `affinity`, `releases`, `users`, `sessions`, `state`, `retention`, `friction` and `funnel`. |
| `probe` | Reads 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. |
| `reproject` | Rebuilds the performance data from your recordings. |

### The perf and product commands you’ll use most

| Command | What 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 budget` | Checks 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 --changed` | Runs 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 labels` | Writes 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

```shell
squiggle probe inputs
squiggle probe when 'fps < 45' 'scroll.moved == 1' --id janky-scroll
squiggle probe when 'render.watch0 >= 5' --component ProductCard --for 30
squiggle probe export 'fps < 45' 'scroll.moved == 1' --id janky-scroll --out src/detectors
```

| Flag | What 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. |
| `--any` | Fires 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

| Command | What it does |
| --- | --- |
| `mcp` | Starts the MCP server for your coding agent. See [Connect your agent](https://squiggle.sh/docs/agents). |
| `agent` | Runs and checks agent programs, and installs the agent kit (agent kit --install). |
| `legibility` | Scores how easily an agent can drive your app, and names the lines to fix, biggest gain first. --fix adds the missing testIDs. |
| `card` | The 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. |
| `benchmark` | Scores an agent on a set of tasks in your app. See [Benchmark an agent](https://squiggle.sh/docs/agents/benchmark). |

Your agent’s tools are listed in [MCP tools](https://squiggle.sh/docs/reference/mcp).

### Flags for card

What each row checks, and how to read one, is in [The report card](https://squiggle.sh/docs/run/card).

| Flag | What 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. |
| `--full` | Every remembered screen and every saved flow, not only what the change reaches. |
| `--no-tour` | Skips touring the touched screens and checking their legibility. |
| `--all-flows` | Runs every saved flow that covers a touched screen, instead of the fewest that reach them all. |
| `--dry-run` | Prints what the card would tour and run, and doesn’t touch the simulator. |
| `--live` | Runs the flows against your real backend. Use it when a flow checks text that stub placeholder values would break. |
| `--json` | Prints 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

| Command | What it does |
| --- | --- |
| `agent kit` | The skills, subagents and commands for your agent. --install writes them into the repo; -o <dir> writes everything, hooks included, to read first. |
| `agent rules` | An AGENTS.md section for this repo: the commands to use and the mistakes to avoid. --write writes AGENTS.md at the project root. |
| `agent config` | The deny rail and run limits. --init writes the defaults to .squiggle/agent.json. |
| `agent run` | Runs an intent program against the app. With --answers-stdin, another program answers its decisions on standard input, one JSON line each. |
| `agent validate` | Checks an intent program without running it. |
| `agent branch` | Replays 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 schema` | Prints a JSON schema: intent programs (the default), flows, or the config file. -o writes it to a file. |
| `agent library` | The saved flows your agent can replay. |
| `agent journal` | What the agent did in a run, and why. |
| `agent watch` | Turns new crashes, friction and findings into briefs as they appear, for your agent to pick up. |
| `agent stats` | Model turns, heals, escalations and cost across runs. --history shows the trend. |
| `agent briefing` | The working rules your agent is given, including what it has learned about this app. |
| `agent amend` | Only what the agent has learned, so you can review it in a pull request. |
| `agent eval` | Replays recorded agent runs and reports each outcome. |
| `agent skill` | Proposes a skill from decisions that keep coming up, and keeps it only if it measurably helps. |
| `agent patterns` | Popup handlers you can share between projects, with no app data in them. |
| `agent export` | The run history as JSONL training data, each run scored by whether it reached its goal. |

## Everything else

| Command | What it does |
| --- | --- |
| `explain` | Explains an exit code or error code. See [Exit codes and errors](https://squiggle.sh/docs/reference/errors). |
| `update` | Updates Squiggle. --check only checks; --to <version> installs a specific version. |
| `uninstall` | Removes Squiggle. --dry-run lists every file it would delete, with sizes. |
| `telemetry` | Shows what anonymous usage data is sent. telemetry off turns it off. |
| `redact` | Shows what would have to be removed from a file before you share it. --write removes it. |
| `completions` | Tab completion for zsh, bash or fish. |
| `licenses` | Third-party notices for what the binary includes. |
| `consumer` | Watches the app with no UI and records performance until you stop it. |
| `gate` | Checks that Squiggle keeps working when the app relaunches (gate relaunch) and that the MCP server works (gate mcp). |
| `gate app` | Fails 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](https://squiggle.sh/docs/run/ci#keep-the-app-from-getting-worse). |

## 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:

```shell
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.

> **VS Code needs one setting**
>
> Turn on images in the integrated terminal, then open a new terminal. It needs VS Code 1.94 or later.
>
> ```ts
> // .vscode/settings.json
> {
>   "terminal.integrated.enableImages": true
> }
> ```
