22 commands, no guesswork
Two of them do almost everything: init once, then check forever. The rest are there for the days something is wrong, or CI needs to work differently.
npx norma-scope initNothing to install first — npx fetches it on the first run. Node 18 or newer is the whole list of requirements.
- 22
- commands
- 1
- config file
- 0
- accounts to run locally

Grouped by what they do
Answer a few questions,
get the commands to run
Your answers become the choices to make in init, the commands to run after it, and a preview of the .bridge/config.json that init writes.
.bridge/config.json — init writes this
{
"source": { "type": "images", "dir": "designs" },
"threshold": 5,
"app": { "baseUrl": "http://localhost:3000" },
"frames": [
{
"label": "pricing",
"screenshot": "pricing.png",
"width": 1440,
"height": 2400,
"viewport": { "width": 1440, "height": 900 },
"route": "/pricing",
"capture": "fullPage"
}
]
}init also adds a project id and one more line per screen: a short code like fr_k3m9…. They are different for every project, so they are not shown here. They are what let you rename pricing.png later without losing its history.
then run
$ npx norma-scope init # choose "A folder of PNGs", then "The whole page"
# set each screen's route in .bridge/config.json
# start your app, then:
$ npx norma-scope doctor
$ npx norma-scope check
$ open .bridge/reports/report.html
Reading these reference PNGs needs no token, network connection or account.
Before choosing whether to share usage analytics.
- Default collection is release-gated off. Preview opt-in uses NORMA_TELEMETRY=1.
- Reads
- Local telemetry preference.
- Writes
- Nothing; does not create an installation ID.
- Network
- None — it makes no request at all
- Can it spend money?
- No
- Can it fail a build?
- No.
Before choosing whether to share usage analytics.
- Default collection is release-gated off. Preview opt-in uses NORMA_TELEMETRY=1.
- Reads
- Local telemetry preference.
- Writes
- Nothing; does not create an installation ID.
- Network
- None — it makes no request at all
- Can it spend money?
- No
- Can it fail a build?
- No.
What each one touches
Everything here runs on your machine unless the network column says otherwise, and nothing spends money unless the money column says so.
| Command | Reads | Writes | Network | Spends |
|---|---|---|---|---|
| telemetry status | Local telemetry preference. | Nothing; does not create an installation ID. | None | Nothing |
| telemetry disable | Local telemetry preference. | Persistent opt-out; removes the saved installation ID. | None | Nothing |
| init | your answers; for Figma, the file's frame list; for a folder of PNGs, the folder | .bridge/config.json, the .bridge/ folders, .gitignore entries, a pre-commit hook, and .env.local for the Figma token when it is saved | Figma only, to check the token and list the frames | Nothing |
| doctor | config, token, design file, your app URL | nothing — diagnosis only | your design source and your running app, to check both answer | Nothing |
| capture | config + your running app | .bridge/screenshots/*.png | your own running app, in a headless browser | Nothing |
| compare | .bridge/screenshots/ + the reference your source points at | .bridge/diff/, report.html, and summary.json with --json. A project that sends its reports to Cloud writes summary.json, a saved copy of the run in .bridge/pending/ and the receipt .bridge/reports/cloud.json, and no report.html once Cloud holds the run | your design source, unless a snapshot or cache already has the reference; and Normascope Cloud, for a project that sends its reports there | Nothing, unless the project sends its reports to Cloud: then 1 Cloud credit per published run that measured something, as cloud publish |
| check | everything capture and compare read | everything capture and compare write | the same as capture and compare — your app, your design source, and Normascope Cloud for a project that sends its reports there | the same as compare |
| explain --local [frame] | screenshots, diffs, captured DOM context, optional source excerpts, your API key | findings in the terminal and in the report | your own model provider, under your own key. Nothing goes to Normascope Cloud | your own provider account |
| cloud publishCloud | .bridge/reports/summary.json and the images it names | .bridge/reports/cloud.json — the receipt, written on every run including a failed one | Normascope Cloud | 1 Cloud credit per published run that measured something, when credits are available. At zero credits, publishing continues and the timeline locks. Hosted analysis with --explain uses additional credits. |
| cloud validateCloud | .bridge/config.json | nothing — it exits 1 if it found something | None | Nothing |
| cloud statusCloud | .bridge/config.json | nothing | Normascope Cloud — one request with the project ID and whether the configured mode includes images; no run data | Nothing |
| cloud loginCloud | .bridge/config.json (its project ID, a digest of its bytes, and its threshold and frame names if you choose to share them). With no config, the new project an admin set up in the browser | the key, in this machine's credential store; .bridge/cloud-connection.json, which says where it is and holds no key; .bridge/config.json only if you accept a suggested name or threshold, keeping the old file as config.json.before-connect, or, with no config, only if you accept the new project's config, together with what init writes around it: the .gitignore lines, a Figma token in .env.local and the pre-commit hook | Normascope Cloud — the machine's name, the project ID and the config digest, and the threshold and frame names only if you say yes, then polls until an admin answers; no run data. A new project's addresses, folders and Figma token are never sent | Nothing |
| cloud logoutCloud | .bridge/cloud-connection.json and the key it names in this machine's credential store | removes that key and that file | Normascope Cloud — one request that ends the key it is sent with; no run data | Nothing |
| cloud pendingCloud | .bridge/pending/ and .bridge/config.json | nothing | None | Nothing |
| cloud retry <id>Cloud | the saved run in .bridge/pending/, .bridge/config.json, and the same credential as cloud publish | the saved run's own record in .bridge/pending/; never .bridge/reports/cloud.json | Normascope Cloud — the saved run's data, exactly as it was saved, and nothing else | 1 credit when the run is published for the first time and measured something, as with cloud publish. A run Cloud already has is not charged again |
| cloud discard <id>Cloud | the saved run in .bridge/pending/ | removes that run's files, and records that it was discarded | None | Nothing |
| frames listCloud | .bridge/config.json | nothing — it just prints | None | Nothing |
| frames adoptCloud | .bridge/config.json | .bridge/config.json — then commit it | None | Nothing |
| ci comment | .bridge/reports/summary.json, and .bridge/reports/cloud.json when a publish wrote one | markdown on stdout | None | Nothing |
| baseline approve | .bridge/screenshots/ | .bridge/baseline/ plus a manifest — commit it | None | Nothing |
| design snapshot | the design file | .bridge/design/ — commit it | your design source | Nothing |
| clean | — | empties screenshots/, diff/, reports/, context/ and the cache, which holds the MCP refusal log | None | Nothing |
The four upload modes
Set cloud.upload in .bridge/config.json, least disclosure first. This only ever applies to cloud publish — nothing else in the CLI sends anything.
| Mode | What it sends |
|---|---|
| off | Nothing at all. No request is made, and cloud publish becomes a no-op rather than an error. |
| metrics | The numbers and nothing else. No pixels leave your machine — and a run with no images cannot be explained by Cloud. |
| review | Full evidence for every frame over threshold, plus one preview for each clean frame. The default. |
| full | Full evidence for every compared frame. |
Every option, by command
Anything not on this list is refused. A misspelled flag never runs the command anyway — --dry-runn is an error, not a publish.
- --jsoncompare, check
- Also write summary.json (schema v11, published and validated)
- --jsondoctor
- Print the result as one line of JSON instead of a table, for a pipeline to read
- --fullcompare, check
- Embed full-resolution images in the report
- --freshcompare, check
- Bypass the design cache and refetch
- --strictcompare, check
- Exit 1 when a compared frame is over threshold or flagged by an enforced local visual check, or a configured frame was skipped because its page was the wrong one (an unexpected status or redirect); 2 when nothing was compared, as with a wrong page in zero-config
compare --target - --targetcompare
- Zero-config mode: the mock PNG to diff against. Requires --url
- --urlcompare
- The page to capture, in zero-config mode
- --selectorcompare
- Capture one element instead of the full page, in zero-config mode
- --expect-statuscompare
- The HTTP status the page must answer with, for a page that is an error on purpose. Default: any 2xx
- --expect-urlcompare
- Where the page must end up, for a page that redirects on purpose. A full URL or a path. Default: the same origin and path as --url
- --localexplain
- Required. Analyse here, on your own provider key — nothing goes to Normascope Cloud
- --allexplain
- Explain every compared frame, not just the flagged ones
- --deepexplain
- Use the stronger, more expensive model
- --modecloud publish
- Override the configured upload mode for one run: off, metrics, review or full
- --dry-runcloud publish
- List exactly what would be sent, and send none of it
- --full-evidencecloud publish
- Full images for every frame, not previews for the clean ones
- --explaincloud publish
- Ask Cloud to analyse the flagged frames after publishing. Spends Cloud credits
- --no-browsercloud login
- Print the link and code and open no browser, for a machine without one
- --file-storecloud login
- Keep the key in an owner-only file in ~/.normascope/credentials instead of the system's credential store
- --urlcloud login
- Which Normascope Cloud to connect to, by its address alone. Default: https://www.normascope.com
- --confirmcloud retry
- Send a run that may already be published. If it was deleted in Cloud since, Cloud refuses it and the saved run is removed here
- --localcompare, check
- Keep this run's report here, when the project's reports go to Normascope Cloud
- --checkdesign snapshot
- Has the live design drifted from the committed exports?
- --help, -hevery command
- Print what this command does and run nothing
Start from the problem
“Just tell me if this page matches this picture.”
compare --target x.png --url …
“Do that for five pages, on every commit.”
init, then check
“Take the screenshots for me.”
capture
“Capture and compare in one go.”
check
“Nothing loaded, or it says skipped.”
doctor
“Explain it using my own provider key.”
explain --local [frame]
“Explain it using the Cloud credits I bought.”
cloud publish --explain
“Will Cloud accept my config?”
cloud validate
“There's no designer — I just don't want surprises.”
mode: "baseline", then baseline approve
“CI shouldn't need a design token.”
design snapshot, and commit .bridge/design/
“Render the PR comment.”
ci comment
“Fail the build on a regression.”
compare --strict
Three things that trip people up
Start your app first
Normascope photographs a running app; it doesn't start one. Run your dev server, then run the command.
Nothing is ever blocked
A bad score is information. The git hook always succeeds and CI stays green unless you explicitly ask for the opposite.
Name the file after the page
The screenshot filename is what ties your design, your capture and the difference together. Keep them the same and everything lines up.
Every command ships with --help, and doctor explains anything that’s misconfigured in your project rather than making you go and look it up.