pursr 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pursr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,440 @@
1
+ <!-- PROJECT_LOGO_START -->
2
+ <p align="center">
3
+ <img src="assets/social-preview.svg" alt="pursr - visual QA, audit, and MCP for the browser" width="100%">
4
+ </p>
5
+
6
+ <p align="center">
7
+ <img src="assets/logo.svg" alt="pursr" width="320">
8
+ </p>
9
+
10
+ <h1 align="center">pursr</h1>
11
+
12
+ <p align="center">
13
+ <strong>Visual QA, audit, and MCP for the browser.</strong><br>
14
+ Capture - sweep - diff - audit - repeat - from the CLI, an MCP server, or as a library.
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://www.npmjs.com/package/pursr"><img src="https://img.shields.io/npm/v/pursr.svg?style=for-the-badge&color=FF2EA6" alt="npm version"></a>
19
+ <a href="https://github.com/0xheycat/pursr/blob/main/LICENSE"><img src="https://img.shields.io/github/license/0xheycat/pursr.svg?style=for-the-badge" alt="license"></a>
20
+ <a href="https://www.npmjs.com/package/pursr"><img src="https://img.shields.io/npm/dm/pursr.svg?style=for-the-badge" alt="npm downloads"></a>
21
+ <a href="https://github.com/0xheycat/pursr/actions"><img src="https://img.shields.io/github/actions/workflow/status/0xheycat/pursr/ci.yml?style=for-the-badge" alt="CI"></a>
22
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/pursr.svg?style=for-the-badge" alt="node"></a>
23
+ </p>
24
+
25
+ <p align="center">
26
+ <a href="#install">Install</a> &middot; <a href="#30-seconds">30 seconds</a> &middot; <a href="#cli">CLI</a> &middot; <a href="#mcp-server">MCP</a> &middot; <a href="#library-api">Library</a> &middot; <a href="#plugins">Plugins</a> &middot; <a href="#roadmap">Roadmap</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## Why pursr?
32
+
33
+ Most teams need **four separate tools** to do visual QA: a screenshot CLI, a regression diff runner, an accessibility auditor, and a way to share captures with an AI assistant. **pursr is all four** - built as a single Node.js package with:
34
+
35
+ - **A unified CLI** (`pursr`) for every capture, diff, sweep, and audit.
36
+ - **An MCP stdio server** (`pursr-mcp`) so Claude Code, Cursor, and Continue can take screenshots, run sweeps, and inspect prior captures as MCP resources.
37
+ - **A library** with 30+ named exports and 16 subpath modules, so you can embed it in your own tooling.
38
+ - **A plugin system** for custom viewports, sweep ops, and capture hooks.
39
+ - **Zero browser bundled** - drives your system Chrome via Playwright. No 200 MB Chromium download.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install pursr
45
+ npm install --save-dev playwright-core # peer dep - bring your own Chrome
46
+ ```
47
+
48
+ Then verify:
49
+
50
+ ```bash
51
+ pursr viewports # list 10+ registered viewport presets
52
+ pursr probe https://example.com # health check
53
+ ```
54
+
55
+ ## 30 seconds
56
+
57
+ ```bash
58
+ # 1. Capture a screenshot with overlays
59
+ pursr shoot https://example.com shot.png \
60
+ --preset desktop-1280 --grid --grid-tile 64
61
+
62
+ # 2. Save it as a visual baseline
63
+ pursr baseline save myapp shot.png home --url https://example.com
64
+
65
+ # 3. Next time you run, compare against the baseline
66
+ pursr diff https://example.com \
67
+ ~/.pursor/baselines/myapp/<id>/home.png \
68
+ diff.png
69
+
70
+ # 4. Or: run a batched sweep + a11y audit + parallel workers
71
+ pursr sweep ./plan.json # see plans/ for an example
72
+ ```
73
+
74
+ ## Features
75
+
76
+ | Feature | Description | CLI flag |
77
+ | --- | --- | --- |
78
+ | Multi-viewport capture | 10+ presets (mobile, tablet, desktop, ultrawide) | `--preset mobile-375` |
79
+ | Layered states | entity / terrain / hud / ui isolation | `--layer entity` |
80
+ | Animation freeze | pause CSS/JS animations for stable frames | `--no-animation` |
81
+ | Cursor overlay | pointer / grab / grabbing / crosshair | `--cursor crosshair` |
82
+ | Grid overlay | spacing guides, custom color + tile size | `--grid --grid-tile 64` |
83
+ | Camera control | zoom + pan via mouse wheel/drag | `--zoom 1.5 --panX 200` |
84
+ | Frame timeline | N captures at intervalMs for animations | `pursr frames <url> 8 200` |
85
+ | Hover capture | text=/role=/aria=/placeholder= matchers | `pursr hover <url> "text=Login"` |
86
+ | Pixel diff | `pixelmatch` against any reference PNG | `pursr diff <url> <ref>` |
87
+ | Visual baselines | save / approve / diff with stable IDs | `pursr baseline save ...` |
88
+ | Parallel sweep | opt-in worker pool across independent steps | `{ "parallel": 4 }` |
89
+ | Accessibility audit | axe-core WCAG 2.1 AA + highlighted screenshot | `pursr audit <url>` |
90
+ | DOM snapshot | serialized HTML + computed styles + selector map | `pursr dom <url>` |
91
+ | Sweep plans | JSON-driven batch with per-step ops | `pursr sweep plan.json` |
92
+ | HTML report | dark-themed grid of every capture + meta | auto-generated `index.html` |
93
+ | CI output | JUnit XML, GitHub Actions annotations, Markdown | written on every sweep |
94
+ | Auto-heal selectors | fallback chain + named matchers | `["text=Login", "#login"]` |
95
+ | HAR capture | HAR 1.2 spec, written next to your shot | `--har ./req.har.json` |
96
+ | Auth state | Playwright storageState, reuse logged-in sessions | `--auth-state admin` |
97
+ | Plugins | custom viewports, sweep ops, before/after hooks | `pursr-plugin-*` |
98
+ | MCP server | 7 tools + resources/list & resources/read for Claude/Cursor | `npx pursr-mcp` |
99
+
100
+ ## CLI
101
+
102
+ ```bash
103
+ # Health check
104
+ pursr probe https://example.com
105
+
106
+ # Screenshot (simple)
107
+ pursr shot https://example.com ./out/shot.png
108
+
109
+ # Rich capture: viewport preset + cursor + grid
110
+ pursr shoot https://example.com \
111
+ --preset desktop-1280 \
112
+ --cursor crosshair \
113
+ --grid --grid-tile 64
114
+
115
+ # Isolate a layer
116
+ pursr layer https://example.com entity
117
+
118
+ # Animation timeline
119
+ pursr frames https://example.com 8 200 ./frames/
120
+
121
+ # Hover an element
122
+ pursr hover https://example.com "text=Login"
123
+
124
+ # Pixel diff vs reference
125
+ pursr diff https://example.com ./ref.png ./out/diff.png
126
+
127
+ # Batched plan
128
+ pursr sweep ./plan.json
129
+
130
+ # Accessibility audit
131
+ pursr audit https://example.com --tags wcag2a,wcag2aa
132
+
133
+ # DOM + selector map snapshot
134
+ pursr dom https://example.com
135
+
136
+ # HAR capture during a shoot
137
+ pursr shoot https://example.com shot.png --har ./req.har.json
138
+
139
+ # Auth state reuse
140
+ pursr shoot https://my.app/dashboard shot.png \
141
+ --auth-state admin --auth-project myapp
142
+
143
+ # Visual baselines
144
+ pursr baseline save myapp shot.png home --url https://example.com
145
+ pursr baseline list myapp
146
+ pursr baseline approve myapp ./new.png home --url https://example.com
147
+
148
+ # Plan validation
149
+ pursr validate ./plan.json
150
+ ```
151
+
152
+ ### Subcommands
153
+
154
+ | Subcommand | Purpose |
155
+ | --- | --- |
156
+ | `probe` | Health check (HTTP status, page title) |
157
+ | `shot` / `full` | Viewport / full-page screenshot |
158
+ | `eval` | Execute JS in the page, return result |
159
+ | `click` / `type` / `wait` / `seq` | Interaction primitives |
160
+ | `diff` | Pixel-level diff vs a reference PNG |
161
+ | `viewports` | List all registered viewport presets |
162
+ | `shoot` | Rich capture (overlays, freeze, camera, plugins) |
163
+ | `layer` | Capture one isolated layer (entity/hud/ui/terrain) |
164
+ | `frames` | N-frame animation timeline at interval |
165
+ | `hover` | Hover state capture |
166
+ | `sweep` | Batched capture plan -> HTML report + CI output |
167
+ | `audit` | axe-core WCAG accessibility audit + highlighted screenshot |
168
+ | `dom` / `dom-snapshot` | Serialized DOM + CSS selectors + XPath + bounding rects |
169
+ | `every-viewport` | Capture once per preset in parallel (3-wide pool) |
170
+ | `baseline` | save / list / approve / show visual baselines |
171
+ | `auth` | save / load / list / delete Playwright storageState |
172
+ | `validate` | Validate a sweep plan JSON without running it |
173
+
174
+ ## MCP Server
175
+
176
+ `pursr-mcp` exposes every capability as MCP tools over stdio - works with Claude Code, Cursor, Continue, and any MCP host.
177
+
178
+ ```bash
179
+ npx pursr-mcp
180
+ # or with verbose logging:
181
+ npx pursr-mcp --verbose
182
+ ```
183
+
184
+ ### Exposed Tools
185
+
186
+ | Tool | Description |
187
+ | --- | --- |
188
+ | `pursr_shoot` | Rich screenshot capture (viewport, grid, layer, cursor, camera, animation freeze, HAR) |
189
+ | `pursr_diff` | Pixel-diff a URL against a reference PNG |
190
+ | `pursr_sweep` | Execute a batch sweep plan |
191
+ | `pursr_frames` | Capture an N-frame animation timeline |
192
+ | `pursr_probe` | Health-check a URL |
193
+ | `pursr_audit` | axe-core WCAG audit + highlighted screenshot |
194
+ | `pursr_dom_snapshot` | Full DOM + selector map snapshot |
195
+
196
+ ### Exposed Resources
197
+
198
+ | URI | Description |
199
+ | --- | --- |
200
+ | `pursr://shoot/<url|preset>` | Last screenshot PNG (image/png) |
201
+ | `pursr://sweep/<plan-name>` | Last sweep summary JSON (application/json) |
202
+
203
+ Resources are persisted to `~/.pursor/mcp/mcp-index.json` (override with `PURSOR_MCP_STATE`).
204
+
205
+ ## Visual Regression Baselines
206
+
207
+ ```bash
208
+ pursr baseline save myapp ./out/shoot.png home --url https://my.app
209
+ pursr baseline approve myapp ./out/shoot.png home --url https://my.app
210
+ pursr baseline list myapp
211
+ pursr baseline show myapp home --url https://my.app
212
+ ```
213
+
214
+ Baselines live under `~/.pursor/baselines/<project>/<id>/<step>.png` + `manifest.json`. Override with `PURSOR_BASELINES_DIR`. The `id` is a 16-char SHA1 prefix of `url|viewport|flags` so re-running a sweep maps to the same slot deterministically.
215
+
216
+ ```js
217
+ import { diffKey, saveBaseline, loadBaseline } from "pursr/baseline";
218
+ const id = diffKey({ url: "https://my.app", viewport: { width: 1280, height: 800, dpr: 1 }, flags: { preset: "desktop-1280" } });
219
+ saveBaseline({ project: "myapp", id, step: "home", png: "./shot.png", meta: { url: "https://my.app" } });
220
+ ```
221
+
222
+ ## Sweep Plan Validation
223
+
224
+ ```bash
225
+ pursr validate ./plan.json
226
+ # { "valid": false, "errors": ["steps[2].frames.count: must be a number between 1 and 120"] }
227
+ ```
228
+
229
+ Catches: empty steps, unknown ops, out-of-range numbers, duplicate names, missing required fields. `pursr sweep` runs the same validator before executing - fail-fast.
230
+
231
+ ```json
232
+ {
233
+ "name": "homepage-matrix",
234
+ "base": "https://example.com",
235
+ "parallel": 4,
236
+ "steps": [
237
+ { "name": "baseline", "shoot": { "preset": "desktop-1280" } },
238
+ { "name": "grid-64", "shoot": { "preset": "desktop-1280", "grid": true, "grid-tile": 64 } },
239
+ { "name": "tablet", "shoot": { "preset": "tablet-768" } },
240
+ { "name": "mobile", "shoot": { "preset": "mobile-375" } },
241
+ { "name": "hover-cta", "hover": { "selector": ["text=Get started", "a.btn-primary"] } },
242
+ { "name": "audit", "audit": { "tags": "wcag2a,wcag2aa" } },
243
+ { "name": "diff", "diff": { "ref": "baseline" } }
244
+ ]
245
+ }
246
+ ```
247
+
248
+ ## HAR Capture
249
+
250
+ ```bash
251
+ pursr shoot https://example.com shot.png --har ./out/req.har.json
252
+ ```
253
+
254
+ ```js
255
+ import { startHarCapture, stopHarCapture, writeHar } from "pursr/har";
256
+ const state = await startHarCapture(page);
257
+ await page.goto(url);
258
+ const har = stopHarCapture(page);
259
+ await writeHar(har, "./out/req.har.json");
260
+ ```
261
+
262
+ Output is HAR 1.2 spec - pipe to `har-cli`, perf-tools, or any visualizer.
263
+
264
+ ## Auth State
265
+
266
+ ```bash
267
+ pursr auth save myapp admin --from ./playwright-state.json
268
+ pursr shoot https://my.app/dashboard shot.png --auth-state admin --auth-project myapp
269
+ pursr auth list myapp
270
+ pursr auth load myapp admin --out ./round-trip.json
271
+ pursr auth delete myapp admin
272
+ ```
273
+
274
+ States live in `~/.pursor/auth/<project>/<name>.json` (override with `PURSOR_AUTH_DIR`). The on-disk format is the standard Playwright `storageState` shape: `{ cookies, origins }`.
275
+
276
+ ## Parallel Sweep
277
+
278
+ Add `parallel: N` to your plan to run steps concurrently in a worker pool:
279
+
280
+ ```json
281
+ {
282
+ "name": "matrix",
283
+ "base": "https://my.app",
284
+ "parallel": 4,
285
+ "steps": [
286
+ { "name": "home", "shoot": { "preset": "desktop-1280" } },
287
+ { "name": "pricing", "shoot": { "preset": "desktop-1280" } },
288
+ { "name": "docs", "shoot": { "preset": "desktop-1280" } }
289
+ ]
290
+ }
291
+ ```
292
+
293
+ Steps run in a shared browser context; results are still ordered by index in the summary. Defaults to serial (`parallel: 1`) - opt in only when steps are independent.
294
+
295
+ ## Accessibility Audit
296
+
297
+ ```bash
298
+ pursr audit https://example.com --tags wcag2a,wcag2aa
299
+ # Writes: audit.json, audit-summary.md, audit-highlighted.png
300
+ ```
301
+
302
+ Injects axe-core, runs a configurable tag set (`wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `best-practice`), and overlays a red outline on every violating node with the rule id as a label. The summary Markdown includes per-rule failure snippets.
303
+
304
+ ## DOM Snapshot
305
+
306
+ ```bash
307
+ pursr dom https://example.com
308
+ # Writes: dom-snapshot-<ts>.dom.json
309
+ ```
310
+
311
+ Captures serialized HTML, computed CSS for every visible element, and a selector map (`id`, `role`, `accessible name`, `text`, `xpath`, `css selector`, viewport-relative `rect`). Great for regression diffing without re-running a browser.
312
+
313
+ ## CI Output
314
+
315
+ Every sweep writes three sidecar artifacts alongside `sweep.json`:
316
+
317
+ - `sweep.junit.xml` - JUnit XML for Jenkins / GitLab / CircleCI
318
+ - `sweep.github.json` - GitHub Actions annotation file
319
+ - `sweep.md` - Human-readable Markdown summary with diffs + failures
320
+
321
+ ## Library API
322
+
323
+ ```js
324
+ import {
325
+ runProbe, runShot, runShoot, runSweep, runDiff, runAudit,
326
+ captureDomSnapshot, resolveHealedSelector,
327
+ saveBaseline, diffKey,
328
+ startHarCapture, stopHarCapture, writeHar,
329
+ loadAuthState,
330
+ PursorMCPServer, loadMcpConfig,
331
+ validateSweepPlan,
332
+ listResources, readResource,
333
+ listViewports, resolveViewport, VIEWPORTS,
334
+ loadPlugins, registerPlugin, getSweepOp,
335
+ VERSION,
336
+ } from "pursr";
337
+ ```
338
+
339
+ ### Subpath exports
340
+
341
+ ```js
342
+ import { resolveLocator } from "pursr/selector";
343
+ import { launch } from "pursr/runway";
344
+ import { parseFlags, asNum } from "pursr/util";
345
+ import { overlayGrid } from "pursr/overlays";
346
+ import { captureDomSnapshot } from "pursr/dom-snapshot";
347
+ import { runAudit } from "pursr/plugin-audit";
348
+ import { resolveHealedSelector } from "pursr/selector-heal";
349
+ import { writeCiOutput } from "pursr/ci-output";
350
+ import { diffKey, saveBaseline, loadBaseline } from "pursr/baseline";
351
+ import { validateSweepPlan } from "pursr/sweep-schema";
352
+ import { startHarCapture, stopHarCapture } from "pursr/har";
353
+ import { saveAuthState, loadAuthState } from "pursr/auth";
354
+ import { listResources, readResource } from "pursr/mcp-resources";
355
+ import { PursorMCPServer } from "pursr/mcp";
356
+ ```
357
+
358
+ ## Plugins
359
+
360
+ A plugin is a plain ES module that exports a default object:
361
+
362
+ ```js
363
+ // plugins/my-plugin.js
364
+ export default {
365
+ name: "my-plugin",
366
+ viewport: { "my-laptop": { width: 1440, height: 900, dpr: 2, label: "MBP 14" } },
367
+ sweepOp: {
368
+ lighthouse: async (ctx, opts) => { /* ... */ },
369
+ },
370
+ beforeShoot: async (ctx) => { /* mutate ctx.flags / ctx.viewport */ },
371
+ afterShoot: async (ctx, meta) => { /* augment sidecar */ },
372
+ flagHelp: { "my-flag": "what it does" },
373
+ };
374
+ ```
375
+
376
+ Plugins are auto-loaded from `plugins/` (built-in) or via `--plugin <path>`.
377
+
378
+ ## Architecture
379
+
380
+ ```
381
+ src/
382
+ index.js - public library entry
383
+ mcp.js - MCP stdio server (JSON-RPC 2.0)
384
+ shoot.js - runShoot (overlays + camera + frame-stable)
385
+ sweep.js - runSweep (validated, parallel pool)
386
+ diff.js - pixelmatch wrapper
387
+ plugin-audit.js - axe-core injection + highlighted screenshot
388
+ dom-snapshot.js - full DOM + CSSOM + selector map
389
+ selector-heal.js - auto-heal chain resolver
390
+ ci-output.js - JUnit / GitHub / Markdown
391
+ baseline.js - visual regression storage
392
+ har.js - HAR 1.2 network capture
393
+ auth.js - Playwright storageState
394
+ sweep-schema.js - plan validator
395
+ mcp-resources.js - MCP resources adapter
396
+ overlays.js - page-side CSS overlays + camera
397
+ runway.js - Playwright launcher + system-Chrome detector
398
+ viewport.js - built-in viewport presets
399
+ selector.js - text=/role=/aria=/placeholder= parser
400
+ plugin.js - plugin registry + hook runner
401
+ util.js - flags, args, hashing, HTML escape, renderSweepHtml
402
+ every-viewport.js - one shot per preset in parallel
403
+ frames.js, hover.js, shot.js, eval.js, probe.js, interact.js
404
+ ```
405
+
406
+ ## Development
407
+
408
+ ```bash
409
+ git clone https://github.com/0xheycat/pursr
410
+ cd pursr
411
+ npm install
412
+ npm install --save-dev playwright-core
413
+ npm test
414
+ ```
415
+
416
+ `npm test` runs 47 unit + integration tests (Node's built-in test runner, zero test deps). Coverage includes: viewport resolution, flag parsing, selector parsing, HTML escaping, hashing, baseline storage, sweep-plan validation, MCP resources, HAR 1.2 shape, auth state, and end-to-end CLI smoke tests.
417
+
418
+ ```
419
+ src/ - 25 modules
420
+ test/ - 47 tests, 0 failures
421
+ plugins/ - 2 built-in plugins, auto-loaded
422
+ ```
423
+
424
+ ## Roadmap
425
+
426
+ - [x] Visual baselines (save / approve / diff)
427
+ - [x] Sweep plan schema validation
428
+ - [x] MCP resources (browse past captures from your AI host)
429
+ - [x] HAR 1.2 capture
430
+ - [x] Auth state (Playwright storageState)
431
+ - [x] Parallel sweep workers
432
+ - [ ] Watch mode (`pursr watch <url>`)
433
+ - [ ] Component-level snapshot (`pursr snap <selector>`)
434
+ - [ ] PDF report export
435
+ - [ ] Cloud output adapters (S3 / GCS)
436
+ - [ ] AI diff summary (vision model)
437
+
438
+ ## License
439
+
440
+ MIT (c) 2026 - [0xheycat](https://github.com/0xheycat)
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="pursr icon">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#FF2EA6"/>
5
+ <stop offset="1" stop-color="#FF5CC1"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="128" height="128" rx="24" fill="#0B0B0F"/>
9
+ <g transform="translate(8,8)">
10
+ <circle cx="56" cy="56" r="42" fill="none" stroke="url(#g)" stroke-width="7"/>
11
+ <g fill="none" stroke="url(#g)" stroke-width="7" stroke-linecap="round">
12
+ <path d="M56 22 L72 44"/>
13
+ <path d="M87 41 L74 59"/>
14
+ <path d="M87 75 L66 67"/>
15
+ <path d="M56 90 L46 69"/>
16
+ <path d="M25 75 L38 66"/>
17
+ <path d="M25 41 L39 51"/>
18
+ </g>
19
+ <path d="M56 28 L72 70 L60 66 L52 80 L48 70 L56 28 Z" fill="url(#g)"/>
20
+ </g>
21
+ </svg>
@@ -0,0 +1,29 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 96" role="img" aria-label="pursr">
2
+ <defs>
3
+ <linearGradient id="pursr-mark-grad" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#FF2EA6"/>
5
+ <stop offset="1" stop-color="#FF5CC1"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Mark: cursor arrowhead fused with camera aperture / lens iris -->
9
+ <g transform="translate(8,8)">
10
+ <!-- Outer iris ring -->
11
+ <circle cx="40" cy="40" r="34" fill="none" stroke="url(#pursr-mark-grad)" stroke-width="6"/>
12
+ <!-- Aperture blades (6) -->
13
+ <g fill="none" stroke="url(#pursr-mark-grad)" stroke-width="6" stroke-linecap="round">
14
+ <path d="M40 12 L52 30"/>
15
+ <path d="M64.4 25.6 L54.5 40"/>
16
+ <path d="M64.4 54.4 L48 48"/>
17
+ <path d="M40 68 L32 50"/>
18
+ <path d="M15.6 54.4 L26 47"/>
19
+ <path d="M15.6 25.6 L27 34"/>
20
+ </g>
21
+ <!-- Cursor chevron pointer on top of iris -->
22
+ <path d="M40 18 L52 50 L42 47 L36 58 L33 50 L40 18 Z" fill="url(#pursr-mark-grad)"/>
23
+ </g>
24
+ <!-- Wordmark: pursr -->
25
+ <g transform="translate(108,62)" font-family="-apple-system,BlinkMacSystemFont,Inter,Segoe UI,Roboto,sans-serif" font-weight="700" font-size="56" letter-spacing="-2">
26
+ <text fill="#0B0B0F" x="0" y="0">pursr</text>
27
+ <rect x="170" y="-12" width="10" height="10" fill="#FF2EA6"/>
28
+ </g>
29
+ </svg>
@@ -0,0 +1,77 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 640" role="img" aria-label="pursr — visual QA, audit, and MCP for the browser">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#0B0B0F"/>
5
+ <stop offset="0.6" stop-color="#15101A"/>
6
+ <stop offset="1" stop-color="#1F0A1A"/>
7
+ </linearGradient>
8
+ <linearGradient id="mark" x1="0" y1="0" x2="1" y2="1">
9
+ <stop offset="0" stop-color="#FF2EA6"/>
10
+ <stop offset="1" stop-color="#FF5CC1"/>
11
+ </linearGradient>
12
+ <pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
13
+ <path d="M48 0 L0 0 0 48" fill="none" stroke="#FF2EA6" stroke-opacity="0.08" stroke-width="1"/>
14
+ </pattern>
15
+ <radialGradient id="glow" cx="0.85" cy="0.15" r="0.6">
16
+ <stop offset="0" stop-color="#FF2EA6" stop-opacity="0.35"/>
17
+ <stop offset="1" stop-color="#FF2EA6" stop-opacity="0"/>
18
+ </radialGradient>
19
+ </defs>
20
+ <rect width="1280" height="640" fill="url(#bg)"/>
21
+ <rect width="1280" height="640" fill="url(#grid)"/>
22
+ <rect width="1280" height="640" fill="url(#glow)"/>
23
+
24
+ <!-- Decorative screenshot card on the right -->
25
+ <g transform="translate(720,160)">
26
+ <rect width="440" height="320" rx="16" fill="#101015" stroke="#2A2A30"/>
27
+ <rect x="0" y="0" width="440" height="40" rx="16" fill="#1B1B22"/>
28
+ <circle cx="20" cy="20" r="6" fill="#FF5F56"/>
29
+ <circle cx="40" cy="20" r="6" fill="#FFBD2E"/>
30
+ <circle cx="60" cy="20" r="6" fill="#27C93F"/>
31
+ <text x="220" y="25" fill="#666" font-family="monospace" font-size="12" text-anchor="middle">pursor.mjs shoot</text>
32
+ <line x1="20" y1="80" x2="200" y2="80" stroke="#FF2EA6" stroke-width="14" stroke-linecap="round"/>
33
+ <line x1="20" y1="120" x2="380" y2="120" stroke="#3A3A45" stroke-width="8" stroke-linecap="round"/>
34
+ <line x1="20" y1="160" x2="320" y2="160" stroke="#3A3A45" stroke-width="8" stroke-linecap="round"/>
35
+ <line x1="20" y1="200" x2="260" y2="200" stroke="#3A3A45" stroke-width="8" stroke-linecap="round"/>
36
+ <rect x="20" y="240" width="120" height="48" rx="6" fill="#FF2EA6"/>
37
+ <line x1="160" y1="264" x2="220" y2="264" stroke="#3A3A45" stroke-width="6" stroke-linecap="round"/>
38
+ <line x1="240" y1="252" x2="380" y2="252" stroke="#3A3A45" stroke-width="6" stroke-linecap="round"/>
39
+ <line x1="240" y1="276" x2="340" y2="276" stroke="#3A3A45" stroke-width="6" stroke-linecap="round"/>
40
+ </g>
41
+
42
+ <!-- Brand mark + wordmark on the left -->
43
+ <g transform="translate(80,200)">
44
+ <g transform="scale(1.5)">
45
+ <circle cx="40" cy="40" r="34" fill="none" stroke="url(#mark)" stroke-width="6"/>
46
+ <g fill="none" stroke="url(#mark)" stroke-width="6" stroke-linecap="round">
47
+ <path d="M40 12 L52 30"/>
48
+ <path d="M64.4 25.6 L54.5 40"/>
49
+ <path d="M64.4 54.4 L48 48"/>
50
+ <path d="M40 68 L32 50"/>
51
+ <path d="M15.6 54.4 L26 47"/>
52
+ <path d="M15.6 25.6 L27 34"/>
53
+ </g>
54
+ <path d="M40 18 L52 50 L42 47 L36 58 L33 50 L40 18 Z" fill="url(#mark)"/>
55
+ </g>
56
+ <text x="140" y="56" fill="#FFFFFF" font-family="-apple-system,BlinkMacSystemFont,Inter,sans-serif" font-weight="800" font-size="76" letter-spacing="-3">pursr</text>
57
+ <rect x="438" y="0" width="14" height="14" fill="#FF2EA6"/>
58
+ </g>
59
+
60
+ <!-- Tagline -->
61
+ <text x="80" y="380" fill="#FFFFFF" font-family="-apple-system,BlinkMacSystemFont,Inter,sans-serif" font-weight="600" font-size="34" letter-spacing="-1">Visual QA, audit &amp; MCP for the browser.</text>
62
+ <text x="80" y="430" fill="#A0A0AA" font-family="-apple-system,BlinkMacSystemFont,Inter,sans-serif" font-weight="400" font-size="22" letter-spacing="-0.5">Capture · sweep · diff · audit · repeat.</text>
63
+
64
+ <!-- Footer chip line -->
65
+ <g transform="translate(80,520)">
66
+ <rect width="180" height="36" rx="18" fill="#1A1A22" stroke="#2A2A30"/>
67
+ <text x="90" y="23" fill="#FF5CC1" font-family="monospace" font-size="14" text-anchor="middle">npm i pursr</text>
68
+ </g>
69
+ <g transform="translate(280,520)">
70
+ <rect width="200" height="36" rx="18" fill="#1A1A22" stroke="#2A2A30"/>
71
+ <text x="100" y="23" fill="#A0A0AA" font-family="monospace" font-size="14" text-anchor="middle">7 MCP tools · 32 tests</text>
72
+ </g>
73
+ <g transform="translate(500,520)">
74
+ <rect width="220" height="36" rx="18" fill="#1A1A22" stroke="#2A2A30"/>
75
+ <text x="110" y="23" fill="#A0A0AA" font-family="monospace" font-size="14" text-anchor="middle">HAR · baselines · parallel</text>
76
+ </g>
77
+ </svg>
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ // @purr/visual — MCP server binary.
3
+ //
4
+ // Runs the pursor MCP stdio server, exposing all capture/audit/sweep
5
+ // capabilities as MCP tools for Claude Code, Cursor, Continue, etc.
6
+ //
7
+ // Usage: pursor-mcp
8
+ // Config via PURSOR_MCP_CONFIG env or ~/.pursor/mcp-config.json
9
+ //
10
+ // echo '{"url":"https://example.com"}' | pursor-mcp
11
+
12
+ import { PursorMCPServer, loadConfig } from "../src/mcp.js";
13
+
14
+ const config = loadConfig();
15
+
16
+ // Verbose mode: --verbose or debug env
17
+ const verbose = process.argv.includes("--verbose") || !!process.env.PURSOR_DEBUG;
18
+ config.verbose = verbose;
19
+
20
+ const server = new PursorMCPServer(config);
21
+ await server.start();