Skip to content
trawler
Use Trawler

The local runner

The runner is the program that actually uses your product: it opens the browser, plays the people, replays the defects and asks the judge. The same program carries out hosted runs for the app, and it can run entirely on your own machine — no Trawler account, no Trawler server, and your product reachable wherever your machine can reach it: localhost, a VPN, staging behind a password.

It is trawler-runner, in the public repository. The command itself, in apps/runner, is licensed Apache-2.0; the engine it runs, in packages/core, is FSL-1.1-ALv2, which becomes Apache-2.0 two years after each release. It is not published as a package yet; you run it from a checkout.

  • Node.js 24 or later.
  • Chromium, installed through Playwright.
  • An OpenRouter API key. The local runner calls models through OpenRouter only.
Terminal window
git clone https://github.com/usetrawler/trawler.git
cd trawler
npm ci
npx playwright install chromium
export OPENROUTER_API_KEY=sk-or-…

On Linux, add --with-deps to the Playwright command so it installs the system libraries Chromium needs.

Terminal window
npx trawler-runner setup https://staging.example.com --focus "the new team-invite flow"

This reads the page and proposes a name, a description, up to four people and up to six goals, the same way setup in the app does, and writes them to project.yaml. It prints what it wrote and what it cost, such as wrote project.yaml: 4 personas, 5 goals, $0.004.

Option Default
--docs <url> none A documentation page to read as well. Its origin is added to the allowed origins, and the people are told where the documentation is.
--focus <text> none What to concentrate on. Anything past 500 characters is cut.
--model <id> deepseek/deepseek-v4.1-flash The OpenRouter model that writes the proposal.
--out <file> project.yaml Where to write it.
--force Overwrite an existing file; without it setup refuses.

Setup makes a single model call with at most 4,000 tokens of output — a fraction of a cent with the default model, more with an expensive --model. Unlike the app, the local runner fetches private and localhost addresses: it reads whatever your machine can reach. It also does not add the origin a redirect ended on; if your product redirects to another origin, list it under allowedOrigins.

project.yaml is plain YAML you can edit — and it is where everything the app does not offer yet can be set. Because it holds passwords, setup writes it readable by you alone.

name: Example
targetUrl: https://staging.example.com/
description: What the product is and who it is for.
allowedOrigins:
- https://auth.example.com
personas:
- id: ana
name: Ana
brief: You run a small bakery and have ten minutes between orders. You give up on anything that needs a manual.
accountRef: owner
goals:
- id: invite-teammate
instruction: Invite a teammate to your workspace.
accounts:
- ref: owner
username: ana@example.com
password: a-long-test-password
httpCredentials:
username: staging
password: the-staging-password
extraHeaders:
X-Test-Run: trawler
secretHeaders:
CF-Access-Client-Secret: the-secret-value
Field
targetUrl Where the people start. http or https, with no username or password in it.
allowedOrigins Other origins the browser may open, such as a sign-in provider. The target’s own origin is always allowed; at most 20 in all.
docsUrl Optional. A documentation page the people are told about, to read if and when they would. If it is on another origin, list that origin under allowedOrigins too — setup --docs does this for you.
personas 1 to 12 people: an id of lowercase letters, digits and dashes, a name of up to 100 characters, a brief of up to 2,000, and optionally the accountRef of an account.
goals 1 to 20, each an id and an instruction of up to 1,000 characters.
accounts Up to 20 test accounts, each a ref, a username and a password of 8 characters or more.
httpCredentials HTTP basic auth in front of the whole product. The username may not contain a colon; the password is 8 characters or more.
extraHeaders Headers sent with every request. Their values are not treated as secrets.
secretHeaders Headers sent with every request and treated as secrets — values of 8 characters or more, masked as ••• wherever they could surface. Use these for tokens such as an access-service secret.

A misspelt field is an error, not something quietly ignored. A YAML syntax error is reported by line and column; any other error by the field’s path, such as personas.0.brief. Neither echoes a password or a header value.

Terminal window
npx trawler-runner run --config project.yaml

Every person takes a session in turn, then every defect is replayed and judged — the same check as in the app, all under one budget.

Option Default
--config <file> required The project file.
--model <id> deepseek/deepseek-v4.1-flash The model that plays the people and carries out the replays.
--judge-model <id> the same as --model The model that judges.
--budget <usd> 5 The cap for the whole run, in dollars. It is checked after every step, so the last step can take the total a little past it.
--max-steps <n> 120 Steps for each person’s session.
--replay-steps <n> 40 Steps for each replay.
--headed Show the browser instead of running it hidden.

Progress goes to the terminal as it happens — role:ana started, role:ana found a defect: …, judge:f1 confirmed — and the run is written to runs/<date and time, UTC>/ in the current directory:

  • report.md — the report: the cost against the budget, a table of the sessions, replays and judge calls, each person’s goals, then the defects grouped as confirmed, inconclusive, refuted and not judged, then friction. A defect whose judge gave no verdict is listed with the others not judged, with a line saying Judge: could not be judged. and why — unless the budget ran out first.
  • summary.json — the same results as data.
  • events.jsonl — everything that happened, one event per line, written as it happens.
Exit code Means
0 The run finished, and at least one person’s session did not fail.
1 Every person’s session failed (the report says why), or something else went wrong; the message starts with error:.
2 A usage mistake, an invalid project file, or OPENROUTER_API_KEY is not set.

Model calls go from your machine to OpenRouter, asking the providers behind it not to keep or train on the data; nothing goes to Trawler.

trawler-runner work is how Trawler’s hosted runners take jobs from the app. It needs the runner token of the server it works for, so it is not something you can point at your own workspace yet.