@blazediff/agent
v0.12.0
Published
Agentic visual regression for BlazeDiff. Auto-discovers routes, captures deterministic screenshots, runs CI checks.
Maintainers
Readme
@blazediff/agent
Visual regression testing your coding agent can judge. Discovers routes, screenshots them with Playwright, diffs against committed baselines, and hands ambiguous diffs to Claude Code, Codex, or Cursor as compact region tiles, not full PNGs. Deterministic CLI, no embedded LLM, no API key.
Features:
- Deterministic CLI. No embedded LLM, no API key required
- Source-walking route discovery for Next.js / Vite / Remix (BFS fallback)
- Heuristic verdict:
regression-likely | intentional-likely | noise-likely | ambiguous - LangGraph pipeline with per-entry subgraphs, suspendable via
interrupt()and resumable from an on-disk checkpoint - Region-tile handoff to coding agents (10 to 100x smaller than full PNGs)
- Auto-masking via
data-blazediff-agent-maskattribute - Auth-protected route capture via a codegen-recorded login harness; credentials live in env vars, never in LLM context
Installation
npm install --save-dev @blazediff/agentQuickstart
blazediff-agent onboard # interactive: config + chromium + playbook + baselines
blazediff-agent check --judge agent # CI: re-capture, diff, judge
blazediff-agent rewrite home # accept an intentional changeonboard writes .blazediff/config.json, installs bundled Chromium, installs the
playbook for your coding agent, and offers to capture baselines on the spot. The
second command (check, or capture if you declined the baseline offer) starts
capturing. Commit .blazediff/ (config + manifest + baselines). All commands
accept --json; under --json/CI, onboard runs non-interactively (no prompts,
no capture) — a scriptable config + chromium step.
Commands
Pass --cwd <abs-path> to target a sub-package in a monorepo.
Onboard options
onboard auto-detects your coding agent; override or scope the playbook with --stack:
blazediff-agent onboard --stack codex # explicit
blazediff-agent onboard --stack all # claude + codex + cursor
blazediff-agent onboard --stack local # local judge, no coding agent (Moondream + Qwen)
blazediff-agent onboard --no-browsers --no-capture # config + playbook onlyOther flags: --url <baseUrl> (external/running server), --dev-command <cmd> /
--port <n> / --dev-script <name> (override detection), --yes (accept prompts),
--force (rewrite config + playbook).
For coding-agent stacks, writes the playbook and sets config.judge: "agent":
- Claude Code →
<project>/.claude/skills/blazediff/SKILL.md - Codex →
~/.codex/skills/blazediff/SKILL.md - Cursor →
<project>/.cursor/rules/blazediff.mdc
--stack local writes no skill file. It sets config.judge: "local" so check
judges diffs locally in two steps: Moondream 2 describes the change, then
Qwen3.5-0.8B classifies it using that description plus the deterministic
interpret summary (both via the optional peer dependency
@huggingface/transformers; install it with npm i @huggingface/transformers).
Each model loads once on the first judgment and is reused for the rest of the run.
--model picks the model the local judge reads regions with. It applies only to
--judge local; passing it to another backend is an error.
| --model | Reader | Notes |
| --- | --- | --- |
| moondream-2-2b-onnx (default) | in-process ONNX | Runs anywhere, no setup. ~11s per region read. |
| moondream-3-9b-mlx | Moondream Station | Apple Silicon only, ~2s per read, needs ~16GB RAM. |
The MLX model needs no setup either: the judge installs Station if missing (via
uv), launches it, selects the model, warms it, and stops it on exit — or
attaches to a Station you already have running and leaves that one alone.
Station reaches the GPU, which onnxruntime-node cannot for this model. Qwen still
classifies in-process, and the prompts and region diffing are shared.
local cannot be combined with the coding-agent stacks.
Masking
Mark non-deterministic content (carousels, clocks, randomized avatars) in source:
<div data-blazediff-agent-mask>...</div>
<div data-blazediff-agent-mask="report-carousel">...</div>For third-party embeds you can't annotate, use a per-entry manifest.entries[].mask CSS selector and re-capture.
Judging
Every non-match routes through the configured judge. With --judge agent the judge node interrupt()s the LangGraph pipeline, writes a JudgmentRequest (region tiles + locator thumbnail) to .blazediff/judgments/<id>/, and the suspended graph is checkpointed to .blazediff/checkpoints/. The coding agent reads the tiles, writes verdict.json, and check --apply-judgments resumes the same graph with the verdicts. No re-capture, no re-diff.
Configuration
.blazediff/config.json:
{
"devServer": { "command": "pnpm dev", "port": 3000, "readyTimeoutMs": 60000 },
"framework": "next",
"packageManager": "pnpm",
"baseUrl": "http://127.0.0.1:3000"
}.blazediff/manifest.json is written by capture; don't edit it directly.
Harnesses
A harness is a pluggable script in .blazediff/harnesses/<name>.js, attached to
an entry via harnesses: [{ name, params? }]. A setup harness runs before
navigation (e.g. login); an interact harness (the default) runs after the
base screenshot and may drive the page and emit extra named screenshots — each
becomes its own baseline entry (<id>__<name>). Harnesses default-export a
Harness and are ESM .js/.mjs (TypeScript is not auto-transpiled):
/** @type {import("@blazediff/agent").Harness} */
export default {
async run({ page, screenshot }) {
await page.getByRole("button", { name: "More options" }).click();
await screenshot("menu"); // -> baseline "<entry>__menu"
},
};Auth-protected routes
Login is just a phase:"setup" harness. For a simple form login, the agent writes
.blazediff/harnesses/auth.js directly — a goto(/login) → fill from
process.env.BLAZEDIFF_AUTH_<PERSONA>_EMAIL / ..._PASSWORD → submit → assert it
left /login. No credentials ever live in the file, only env refs. For flows it
can't author (OAuth/SSO, MFA, captcha), blazediff-agent harness record auth --login
records the login interactively via Playwright codegen and rewrites the typed creds
to env refs.
Attach it with "harnesses": [{ "name": "auth", "params": { "persona": "default" } }],
then put BLAZEDIFF_AUTH_<PERSONA>_EMAIL / ..._PASSWORD in an env file the CLI
auto-loads from the target dir — .blazediff/.env[.local] (blazediff-scoped,
auto-gitignored) or the project-root .env[.local] — or export them. Real env
wins; .blazediff/ files beat the root. check does the rest.
Full walkthrough: Auth-protected routes.
CI
Only check is allowed under CI=1. Exit codes:
0: all passed1: regression, intentional, or pending judgment- non-zero with structured error JSON on infra failures
