pursor 0.2.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 PurrVisual
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,595 @@
1
+ # pursor
2
+
3
+ > **Visual QA & audit CLI + library + MCP server for the browser.**
4
+ > Capture, diff, sweep, and audit any web target — with multi-viewport,
5
+ > layered states, hover, grid overlays, animation freeze, camera control,
6
+ > axe-core accessibility audit, CI output, and auto-healing selectors.
7
+
8
+ ```bash
9
+ npx pursor probe https://example.com
10
+ npx pursor shoot https://example.com --preset mobile-375 --grid
11
+ npx pursor sweep ./plan.json
12
+ npx pursor audit https://example.com --tags wcag2a,wcag2aa
13
+ npx pursor-mcp # MCP stdio server for Claude Code / Cursor
14
+ ```
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install pursor
20
+ npm install --save-dev playwright-core # peer dep — bring your own Chrome
21
+ ```
22
+
23
+ `pursor` does **not** bundle Chromium. It drives your system Chrome via
24
+ Playwright. No extra browser downloads.
25
+
26
+ ---
27
+
28
+ ## Table of Contents
29
+
30
+ - [CLI](#cli)
31
+ - [MCP Server](#mcp-server)
32
+ - [Accessibility Audit](#accessibility-audit)
33
+ - [DOM Snapshot](#dom-snapshot)
34
+ - [CI Output](#ci-output)
35
+ - [Auto-heal Selectors](#auto-heal-selectors)
36
+ - [Sweep Plans](#sweep-plans)
37
+ - [Plugin API](#plugin-api)
38
+ - [Library API](#library-api)
39
+ - [Development](#development)
40
+
41
+ ---
42
+
43
+ ## CLI
44
+
45
+ ```bash
46
+ # Health check
47
+ pursor probe https://example.com
48
+
49
+ # Screenshot (simple)
50
+ pursor shot https://example.com ./out/shot.png
51
+
52
+ # Rich capture: viewport preset + cursor + grid
53
+ pursor shoot https://example.com \
54
+ --preset desktop-1280 \
55
+ --cursor crosshair \
56
+ --grid --grid-tile 64
57
+
58
+ # Isolate a layer (entity / terrain / hud / ui)
59
+ pursor layer https://example.com entity
60
+
61
+ # Animation timeline: 8 frames at 200ms
62
+ pursor frames https://example.com 8 200 ./frames/
63
+
64
+ # Hover an element
65
+ pursor hover https://example.com "text=Login"
66
+
67
+ # Pixel diff vs a reference screenshot
68
+ pursor diff https://example.com ./ref.png ./out/diff.png
69
+
70
+ # Batched plan (see plans/ for examples)
71
+ pursor sweep ./plan.json
72
+
73
+ # Accessibility audit (requires: npm i axe-core)
74
+ pursor audit https://example.com --tags wcag2a,wcag2aa
75
+
76
+ # DOM + selector map snapshot
77
+ pursor dom https://example.com
78
+ ```
79
+
80
+ ### Subcommands
81
+
82
+ | Subcommand | Purpose |
83
+ |---|---|
84
+ | `probe` | Health check (HTTP status, page title) |
85
+ | `shot` / `full` | Viewport / full-page screenshot |
86
+ | `eval` | Execute JS in the page, return result |
87
+ | `click` / `type` / `wait` / `seq` | Interaction primitives |
88
+ | `diff` | Pixel-level diff vs a reference PNG |
89
+ | `viewports` | List all registered viewport presets |
90
+ | `shoot` | Rich capture (overlays, freeze, camera, plugins) |
91
+ | `layer` | Capture one isolated layer (entity/hud/ui/terrain) |
92
+ | `frames` | N-frame animation timeline at interval |
93
+ | `hover` | Hover state capture |
94
+ | `sweep` | Batched capture plan → HTML report + CI output |
95
+ | `audit` | ⭐ axe-core WCAG accessibility audit + highlighted screenshot |
96
+ | `dom` / `dom-snapshot` | ⭐ Serialized DOM + CSS selectors + XPath + bounding rects |
97
+
98
+ ---
99
+
100
+ ## MCP Server
101
+
102
+ `pursor-mcp` exposes every capability as MCP tools over stdio —
103
+ works with Claude Code, Cursor, Continue, and any MCP host.
104
+
105
+ ```bash
106
+ npx pursor-mcp
107
+ # or with verbose logging:
108
+ npx pursor-mcp --verbose
109
+ ```
110
+
111
+ ### Exposed Tools
112
+
113
+ | Tool | Description |
114
+ |---|---|
115
+ | `pursor_shoot` | Full screenshot with viewport, grid, layer, cursor, camera, freeze |
116
+ | `pursor_diff` | Pixel diff vs reference PNG + diff overlay |
117
+ | `pursor_sweep` | Execute a batch plan JSON → summary |
118
+ | `pursor_frames` | Animation frame timeline |
119
+ | `pursor_probe` | Health-check a URL |
120
+ | `pursor_audit` | axe-core accessibility audit |
121
+ | `pursor_dom_snapshot` | DOM + CSSOM + selector map + bounding rects |
122
+
123
+ ### Config
124
+
125
+ Config via `PURSOR_MCP_CONFIG` env var (inline JSON or file path)
126
+ or `~/.pursor/mcp-config.json`:
127
+
128
+ ```json
129
+ {
130
+ "plugins": ["./my-plugin.js"],
131
+ "defaultOutDir": "./mcp-output",
132
+ "verbose": true
133
+ }
134
+ ```
135
+
136
+ ### MCP Host Examples
137
+
138
+ **Claude Code:**
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "pursor": {
143
+ "command": "npx",
144
+ "args": ["pursor-mcp"]
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ **Cursor:**
151
+ ```json
152
+ {
153
+ "mcpServers": {
154
+ "pursor": {
155
+ "command": "npx",
156
+ "args": ["pursor-mcp", "--verbose"]
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Accessibility Audit
165
+
166
+ Run axe-core WCAG audits on any URL. Optionally captures a highlighted
167
+ screenshot with violated elements outlined in red.
168
+
169
+ ```bash
170
+ # Quick audit with default tags (wcag2a, wcag2aa, wcag21a, wcag21aa, best-practice)
171
+ pursor audit https://example.com
172
+
173
+ # Specific WCAG tags
174
+ pursor audit https://example.com --tags wcag2a,wcag2aa
175
+
176
+ # Custom output directory
177
+ pursor audit https://example.com ./audit-report/
178
+ ```
179
+
180
+ **Output:**
181
+ - `audit.json` — full axe-core results with violation summary
182
+ - `audit-summary.md` — readable Markdown report with severity breakdown
183
+ - `audit-highlighted.png` — screenshot with violations visibly marked
184
+
185
+ **Sweep plan usage:**
186
+ ```json
187
+ {
188
+ "name": "accessibility-check",
189
+ "base": "https://example.com",
190
+ "steps": [
191
+ {
192
+ "name": "wcag-audit",
193
+ "audit": {
194
+ "tags": "wcag2a,wcag2aa,wcag21aa",
195
+ "screenshot": true
196
+ }
197
+ }
198
+ ]
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## DOM Snapshot
205
+
206
+ Every capture can optionally produce a `.dom.json` sidecar with complete
207
+ page structure — useful for debugging visual regression without opening
208
+ a browser.
209
+
210
+ ```bash
211
+ pursor dom https://example.com
212
+ ```
213
+
214
+ **Captured data:**
215
+ - `dom` — `document.documentElement.outerHTML`
216
+ - `selectorMap[]` — every visible element with:
217
+ - `tag`, `id`, `css` (CSS selector), `xpath`
218
+ - `role`, `ariaLabel`, `ariaRole`, `text`, `placeholder`, `alt`, `href`, `src`
219
+ - `rect` — viewport-relative bounding box `{x, y, w, h}`
220
+ - `visible` — visibility flag
221
+ - `styles` — computed stylesheet rules keyed by selector
222
+ - `viewport` — current viewport dimensions + DPR
223
+
224
+ **Programmatic:**
225
+ ```js
226
+ import { captureDomSnapshot } from "pursor";
227
+
228
+ const snapshot = await captureDomSnapshot({
229
+ url: "https://example.com",
230
+ out: "./snapshot.dom.json",
231
+ });
232
+ console.log(snapshot.selectorMap.length, "elements found");
233
+ ```
234
+
235
+ ---
236
+
237
+ ## CI Output
238
+
239
+ Sweep plans automatically generate CI-compatible output files alongside
240
+ the HTML report — no extra config needed.
241
+
242
+ ```bash
243
+ pursor sweep ./plan.json
244
+ # Produces in the output directory:
245
+ # sweep.json — raw summary
246
+ # index.html — visual HTML dashboard
247
+ # sweep.junit.xml — JUnit XML (GitLab CI, Jenkins, CircleCI)
248
+ # sweep.github.json — GitHub Actions annotations format
249
+ # sweep.md — Markdown summary
250
+ ```
251
+
252
+ ### GitHub Actions integration
253
+
254
+ ```yaml
255
+ - name: Visual QA
256
+ run: npx pursor@latest sweep ./plan.json
257
+ - name: Annotate
258
+ uses: actions/github-script@v7
259
+ with:
260
+ script: |
261
+ const fs = require('fs');
262
+ const { annotations } = JSON.parse(fs.readFileSync('sweep-output/sweep.github.json'));
263
+ annotations.forEach(a => core.error(a.message, {file: a.filename, title: a.title}));
264
+ ```
265
+
266
+ ### JUnit in GitLab CI
267
+
268
+ ```yaml
269
+ visual-qa:
270
+ script: npx pursor@latest sweep ./plan.json
271
+ artifacts:
272
+ reports:
273
+ junit: sweep-output/sweep.junit.xml
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Auto-heal Selectors
279
+
280
+ In sweep plans, selectors can be an array of fallback strategies.
281
+ pursor tries each one in order until a visible element is found:
282
+
283
+ ```json
284
+ {
285
+ "name": "login-flow",
286
+ "base": "https://example.com",
287
+ "steps": [
288
+ {
289
+ "name": "click-login",
290
+ "hover": {
291
+ "selector": [
292
+ "text=Login",
293
+ "button[type=submit]",
294
+ "#login-btn",
295
+ "a[href*='login']"
296
+ ]
297
+ }
298
+ }
299
+ ]
300
+ }
301
+ ```
302
+
303
+ **Supported selector types:**
304
+ - `text=Login` — Playwright text locator (substring, or `text==Login` for exact)
305
+ - `text~regex` — regex text match
306
+ - `role=button|Submit` — ARIA role with accessible name
307
+ - `aria=label` — accessibility label
308
+ - `placeholder=Email` — placeholder text
309
+ - CSS selectors — any valid CSS selector as fallback
310
+
311
+ ---
312
+
313
+ ## Sweep Plans
314
+
315
+ Batch capture plans in JSON. Each step runs one operation.
316
+
317
+ ```json
318
+ {
319
+ "name": "checkout-flow",
320
+ "base": "https://example.com",
321
+ "outDir": "./sweep-checkout",
322
+ "steps": [
323
+ { "name": "homepage", "shoot": { "preset": "desktop-1280", "cursor": "default" } },
324
+ { "name": "mobile-view","shoot": { "preset": "mobile-375", "grid": true } },
325
+ { "name": "nav-hover", "hover": { "selector": "text=Products", "settleMs": 400 } },
326
+ { "name": "add-to-cart","frames": { "count": 6, "intervalMs": 200 } },
327
+ { "name": "diff", "diff": { "ref": "baseline" } }
328
+ ]
329
+ }
330
+ ```
331
+
332
+ **Step operations:** `shoot`, `hover`, `frames`, `diff`, `audit`, or any
333
+ registered plugin sweep-op.
334
+
335
+ ---
336
+
337
+ ## Plugin API
338
+
339
+ Extend `pursor` with custom viewport presets, sweep operations, or
340
+ capture hooks:
341
+
342
+ ```js
343
+ export default {
344
+ name: "my-plugin",
345
+ viewport: {
346
+ "my-laptop": { width: 1440, height: 900, dpr: 2, label: "MBP 14" },
347
+ },
348
+ sweepOp: {
349
+ lighthouse: async (ctx, opts) => {
350
+ // run lighthouse audit, write result at ctx.out
351
+ return { score: 95 };
352
+ },
353
+ },
354
+ beforeShoot: async (ctx) => { /* mutate ctx.flags */ },
355
+ afterShoot: async (ctx, meta) => { /* augment sidecar */ },
356
+ };
357
+ ```
358
+
359
+ ```bash
360
+ pursor shoot https://example.com --plugin ./my-plugin.js
361
+ ```
362
+
363
+ Publish as `pursor-plugin-*` for auto-discovery.
364
+
365
+ ### Built-in plugins
366
+
367
+ - **`plugin-audit`** — adds `audit` sweep-op (axe-core WCAG audit) and
368
+ `every-viewport` sweep-op (capture at every preset). Adds `audit-canvas`
369
+ viewport preset.
370
+ - **`plugin-demo`** — Reference implementation showing every plugin API
371
+ hook: viewport presets, `nav` sweep-op (navbar walker), `beforeShoot`/
372
+ `afterShoot` sidecar augmentation, and flag help.
373
+
374
+ ---
375
+
376
+ ## All CLI flags
377
+
378
+ | Flag | Type | Default | Description |
379
+ |---|---|---|---|
380
+ | `--preset` | string | `desktop-1280` | Named viewport preset |
381
+ | `--width` / `--height` | number | — | Custom viewport size |
382
+ | `--dpr` | number | `1` | Device pixel ratio |
383
+ | `--cursor` | string | `default` | `pointer`, `grab`, `crosshair`, `none` |
384
+ | `--grid` | bool | `false` | Overlay grid |
385
+ | `--grid-tile` | number | `64` | Grid tile size (px) |
386
+ | `--grid-color` | string | `rgba(255,0,255,0.35)` | Grid line color |
387
+ | `--layer` | string | `all` | `entity`, `terrain`, `hud`, `ui` |
388
+ | `--no-animation` | bool | `false` | Freeze CSS animations |
389
+ | `--no-hud` | bool | `false` | Hide HUD elements |
390
+ | `--wait-frame` | number | `600` | Wait ms for canvas stability |
391
+ | `--zoom` | number | `1` | Zoom level |
392
+ | `--panX` / `--panY` | number | `0` | Camera pan offset (px) |
393
+ | `--full` | bool | `false` | Full-page (not just viewport) |
394
+ | `--tags` | string | — | Comma-separated WCAG tags for audit |
395
+ | `--plugin` | path | — | Load a plugin file (repeatable) |
396
+ | `@file` | prefix | — | Read next arg from file |
397
+
398
+ ---
399
+
400
+ ## Library API
401
+
402
+ All functions available as named or default import:
403
+
404
+ ```js
405
+ import { runShoot, runSweep, runAudit, captureDomSnapshot } from "pursor";
406
+ // Or:
407
+ import PurrVisual from "pursor";
408
+ ```
409
+
410
+ ### Capture functions
411
+
412
+ | Function | Returns | Never throws |
413
+ |---|---|---|
414
+ | `runShoot({url, out, flags?, prepare?, browser?})` | `{ url, out, ts, status, title, viewport, flags, error? }` | ✅ |
415
+ | `runShot(url, out, opts?)` | `{ url, out, status, title, fullPage }` | — |
416
+ | `runProbe(url)` | `{ url, status, title, navError, viewport }` | — |
417
+ | `runFrames({url, count?, intervalMs?, outDir?, flags?, browser?})` | `{ url, files[], viewport, ... }` | — |
418
+ | `runHover({url, selector, out, flags?})` | `{ url, out, selector, viewport, ... }` | — |
419
+ | `runDiff(url, refPath, out, threshold?, browser?)` | `{ url, refPath, numDiff, diffPct, equal, error? }` | — |
420
+ | `runWait(url, selector, timeoutMs?)` | `{ url, selector, found, timeoutMs }` | — |
421
+ | `runClick(url, selector, out?)` | `{ url, selector, clicked, out }` | — |
422
+ | `runType(url, selector, text, out?)` | `{ url, selector, text, typed, out }` | — |
423
+ | `runSeq(url, actionsJson, out?)` | `{ url, out, steps[], failed? }` | — |
424
+ | `runEval(url, js, out?)` | `{ url, result, out, ... }` | — |
425
+ | `runSweep(planPath, outDir?)` | `{ name, steps[], outDir, ... }` | ✅ (per-step) |
426
+ | `runAudit({url, tags?, outDir?, screenshot?, flags?})` | `{ url, violations, violationSummary, highlightedScreenshot?, ... }` | — |
427
+ | `captureDomSnapshot({url, out, flags?})` | `{ url, title, dom, selectorMap[], styles, viewport }` | — |
428
+
429
+ ### Viewport helpers
430
+
431
+ | Export | Description |
432
+ |---|---|
433
+ | `listViewports()` | All registered presets (built-in + plugin) |
434
+ | `resolveViewport(flags)` | Resolve `--preset` / `--width` / `--height` to viewport object |
435
+ | `VIEWPORTS` | Built-in preset map |
436
+ | `applyCamera(page, opts)` | Zoom/pan via mouse wheel + drag on canvas |
437
+ | `waitForStableFrame(page, ms)` | Poll canvas until stable for `ms` |
438
+
439
+ ### Plugin system
440
+
441
+ | Export | Description |
442
+ |---|---|
443
+ | `loadPlugins(paths?)` | Auto-load built-in plugins + user paths |
444
+ | `registerPlugin(plugin)` | Register a plugin manually |
445
+ | `listPlugins()` | Names of loaded plugins |
446
+ | `getSweepOp(name)` | Get a registered sweep operation |
447
+ | `getViewportPreset(name)` | Get a registered viewport preset |
448
+ | `listViewportPresets()` | All plugin-registered presets |
449
+ | `getFlagHelp()` | All plugin-registered flag descriptions |
450
+
451
+ ### Selector healing
452
+
453
+ | Export | Description |
454
+ |---|---|
455
+ | `resolveHealedSelector(page, selector, opts?)` | Try selector chain, return first visible match |
456
+ | `healStepAction(page, action)` | Mutate action.selector → resolved selector |
457
+
458
+ ### CI output
459
+
460
+ | Export | Description |
461
+ |---|---|
462
+ | `writeCiOutput(summary, dir)` | Write JUnit XML + GitHub annotations + Markdown |
463
+
464
+ ### MCP Server
465
+
466
+ | Export | Description |
467
+ |---|---|
468
+ | `PurrVisualMCPServer` | MCP stdio server class |
469
+ | `loadMcpConfig()` | Load config from env or `~/.pursor/mcp-config.json` |
470
+ | `MCP_VERSION` | MCP protocol version string |
471
+
472
+ ### Low-level (plugin authors)
473
+
474
+ | Export | Source |
475
+ |---|---|
476
+ | `launch()` / `newPage(browser, viewport)` | `runway.js` |
477
+ | `resolveLocator(page, selector)` / `parseTextSelector(s)` | `selector.js` |
478
+ | `parseFlags(argv)` / `asNum(v, dflt)` / `asBool(v, dflt)` | `util.js` |
479
+ | `nowIso()` / `shortHash(buf)` / `escapeHtml(s)` | `util.js` |
480
+ | `readArg(arg)` / `makeOut(name)` / `findStepPng(dir, name)` | `util.js` |
481
+ | `renderSweepHtml(summary)` | `util.js` |
482
+
483
+ ### Subpath exports
484
+
485
+ ```js
486
+ import { resolveLocator } from "pursor/selector";
487
+ import { launch } from "pursor/runway";
488
+ import { parseFlags } from "pursor/util";
489
+ import { overlayGrid } from "pursor/overlays";
490
+ import { captureDomSnapshot } from "pursor/dom-snapshot";
491
+ import { runAudit } from "pursor/plugin-audit";
492
+ import { resolveHealedSelector } from "pursor/selector-heal";
493
+ import { writeCiOutput } from "pursor/ci-output";
494
+ import { PurrVisualMCPServer } from "pursor/mcp";
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Sidecar JSON
500
+
501
+ Every capture writes a `.json` sidecar next to its PNG with metadata
502
+ (url, viewport, flags, timestamp, file size, SHA1 hash). DOM snapshots
503
+ write `.dom.json` with full element map. Audit reports write full
504
+ axe-core results to `audit.json`.
505
+
506
+ ---
507
+
508
+ ## Development
509
+
510
+ ```bash
511
+ git clone <this repo>
512
+ cd pursor
513
+ npm install
514
+ npm install --save-dev playwright-core
515
+ npm test
516
+ ```
517
+
518
+ All 32 tests use Node's built-in test runner. Coverage: unit tests for
519
+ viewport resolution, flag parsing, selector parsing, HTML escaping, hashing,
520
+ and end-to-end smoke tests for the full CLI pipeline.
521
+
522
+ ```
523
+ src/ — 22 modules
524
+ test/ — 32 tests, 0 failures
525
+ plugins/ — 2 built-in plugins, auto-loaded
526
+ ```
527
+
528
+ ---
529
+
530
+ ## Visual Regression Baselines
531
+
532
+ ```bash
533
+ # Save a baseline (computed id from url+viewport+flags)
534
+ pursor baseline save myapp ./out/shoot.png home --url https://my.app
535
+
536
+ # Approve a new baseline from a current capture
537
+ pursor baseline approve myapp ./out/shoot.png home --url https://my.app
538
+
539
+ # List all baselines
540
+ pursor baseline list myapp
541
+
542
+ # Show a specific baseline
543
+ pursor baseline show myapp home --url https://my.app
544
+ ```
545
+
546
+ Baselines are stored under `~/.pursor/baselines/<project>/<id>/`. Override
547
+ with `PURSOR_BASELINES_DIR`. The `id` is a 16-char SHA1 prefix of
548
+ `url|viewport|flags` so re-running a sweep maps to the same slot
549
+ deterministically. Use in code:
550
+
551
+ ```js
552
+ import { diffKey, saveBaseline, loadBaseline } from "pursor/baseline";
553
+ const id = diffKey({ url: "https://my.app", viewport: { width: 1280, height: 800, dpr: 1 }, flags: { preset: "desktop-1280" } });
554
+ saveBaseline({ project: "myapp", id, step: "home", png: "./shot.png", meta: { url: "https://my.app" } });
555
+ ```
556
+
557
+ ## Sweep Plan Validation
558
+
559
+ ```bash
560
+ pursor validate ./plan.json
561
+ # { "valid": false, "errors": ["steps[2].frames.count: must be a number between 1 and 120"] }
562
+ ```
563
+
564
+ Catches: empty steps, unknown ops, out-of-range numbers, duplicate names,
565
+ missing required fields. `pursor sweep` runs the same validator before
566
+ executing — fail-fast.
567
+
568
+ ## MCP Resources
569
+
570
+ The MCP server now exposes `resources/list` and `resources/read` so
571
+ Claude Code / Cursor can browse past captures:
572
+
573
+ ```
574
+ uri: pursor://shoot/<url|preset>
575
+ uri: pursor://sweep/<plan-name>
576
+ ```
577
+
578
+ Resources are persisted to `~/.pursor/mcp/mcp-index.json` (override
579
+ with `PURSOR_MCP_STATE`) and re-listed on every server start.
580
+
581
+ ## Library additions (v0.2.0)
582
+
583
+ | Export | Description |
584
+ |---|---|
585
+ | `diffKey`, `saveBaseline`, `loadBaseline`, `listBaselines`, `approveBaseline` | Visual regression baseline storage |
586
+ | `validateSweepPlan`, `registerSweepOp` | Sweep plan schema validation |
587
+ | `listResources`, `readResource`, `recordResource` | MCP resource adapter |
588
+
589
+ Subpath exports: `pursor/baseline`, `pursor/sweep-schema`, `pursor/mcp-resources`.
590
+
591
+ ---
592
+
593
+ ## License
594
+
595
+ MIT
@@ -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();