pursor 0.2.0 → 0.3.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/README.md +654 -595
- package/bin/pursor.mjs +226 -190
- package/package.json +75 -73
- package/src/auth.js +92 -0
- package/src/har.js +159 -0
- package/src/index.js +94 -89
- package/src/runway.js +5 -2
- package/src/shoot.js +13 -1
- package/src/sweep.js +118 -104
package/bin/pursor.mjs
CHANGED
|
@@ -1,191 +1,227 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pursor CLI. Thin wrapper around src/* that mirrors the npm bin.
|
|
3
|
-
|
|
4
|
-
import { VERSION } from "../src/index.js";
|
|
5
|
-
import { runClick, runType, runWait, runSeq } from "../src/interact.js";
|
|
6
|
-
import { runEval } from "../src/eval.js";
|
|
7
|
-
import { runProbe } from "../src/probe.js";
|
|
8
|
-
import { runShot } from "../src/shot.js";
|
|
9
|
-
import { runShootWithSidecar } from "../src/shoot.js";
|
|
10
|
-
import { runHover } from "../src/hover.js";
|
|
11
|
-
import { runFrames } from "../src/frames.js";
|
|
12
|
-
import { runDiff } from "../src/diff.js";
|
|
13
|
-
import { runSweep } from "../src/sweep.js";
|
|
14
|
-
import { runEveryViewport } from "../src/every-viewport.js";
|
|
15
|
-
import { runAudit } from "../src/plugin-audit.js";
|
|
16
|
-
import { captureDomSnapshot } from "../src/dom-snapshot.js";
|
|
17
|
-
import { listViewports } from "../src/viewport.js";
|
|
18
|
-
import { parseFlags, asNum, readArg, makeOut, pickOutPath } from "../src/util.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
case "
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
case "
|
|
60
|
-
case "
|
|
61
|
-
case "
|
|
62
|
-
case "
|
|
63
|
-
case "
|
|
64
|
-
case "
|
|
65
|
-
case "
|
|
66
|
-
case "
|
|
67
|
-
case "
|
|
68
|
-
case "
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
// sub=
|
|
148
|
-
// sub=
|
|
149
|
-
// sub=
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pursor CLI. Thin wrapper around src/* that mirrors the npm bin.
|
|
3
|
+
|
|
4
|
+
import { VERSION } from "../src/index.js";
|
|
5
|
+
import { runClick, runType, runWait, runSeq } from "../src/interact.js";
|
|
6
|
+
import { runEval } from "../src/eval.js";
|
|
7
|
+
import { runProbe } from "../src/probe.js";
|
|
8
|
+
import { runShot } from "../src/shot.js";
|
|
9
|
+
import { runShootWithSidecar } from "../src/shoot.js";
|
|
10
|
+
import { runHover } from "../src/hover.js";
|
|
11
|
+
import { runFrames } from "../src/frames.js";
|
|
12
|
+
import { runDiff } from "../src/diff.js";
|
|
13
|
+
import { runSweep } from "../src/sweep.js";
|
|
14
|
+
import { runEveryViewport } from "../src/every-viewport.js";
|
|
15
|
+
import { runAudit } from "../src/plugin-audit.js";
|
|
16
|
+
import { captureDomSnapshot } from "../src/dom-snapshot.js";
|
|
17
|
+
import { listViewports } from "../src/viewport.js";
|
|
18
|
+
import { parseFlags, asNum, readArg, makeOut, pickOutPath } from "../src/util.js";
|
|
19
|
+
import { writeFileSync } from "node:fs";
|
|
20
|
+
import { readFileSync as _readFileSync } from "node:fs";
|
|
21
|
+
const readFile = _readFileSync;
|
|
22
|
+
import { loadPlugins, listPlugins, getFlagHelp } from "../src/plugin.js";
|
|
23
|
+
|
|
24
|
+
const USAGE = `usage:
|
|
25
|
+
v1: pursor {probe|shot|full|eval|click|type|wait|diff|seq} <url> [...]
|
|
26
|
+
v2: pursor {viewports|shoot|layer|frames|hover|sweep} <...>
|
|
27
|
+
flags: --preset <name> --width N --height N --dpr N
|
|
28
|
+
--zoom 1.5 --panX 200 --panY -100
|
|
29
|
+
--cursor pointer|grab|grabbing|crosshair|none
|
|
30
|
+
--layer entity|terrain|hud|ui
|
|
31
|
+
--grid --grid-tile 64 --grid-color rgba(255,0,255,0.35)
|
|
32
|
+
--no-animation --wait-frame 600 --full
|
|
33
|
+
@file prefix reads argv contents from file (UTF-8, newline trimmed).
|
|
34
|
+
plugins: pursor automatically loads built-in plugins from plugins/.
|
|
35
|
+
You can also pass --plugin <path> to load custom plugins (repeatable).`;
|
|
36
|
+
|
|
37
|
+
function die(msg, code = 2) {
|
|
38
|
+
console.error(JSON.stringify({ error: msg, usage: USAGE }, null, 2));
|
|
39
|
+
process.exit(code);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const argv = process.argv;
|
|
43
|
+
const [, , cmd, a, b, c, d] = argv;
|
|
44
|
+
const url = process.env.PURSOR_URL || a;
|
|
45
|
+
|
|
46
|
+
// Plugin loading: scan for --plugin <path> and built-in plugins/
|
|
47
|
+
const pluginPaths = [];
|
|
48
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === "--plugin" && i + 1 < argv.length) pluginPaths.push(argv[++i]);
|
|
49
|
+
await loadPlugins(pluginPaths);
|
|
50
|
+
|
|
51
|
+
(async () => {
|
|
52
|
+
try {
|
|
53
|
+
switch (cmd) {
|
|
54
|
+
case undefined: case "help": case "--help": case "-h": { console.log(JSON.stringify({ usage: USAGE }, null, 2)); break; }
|
|
55
|
+
case "version": case "--version": case "-v": {
|
|
56
|
+
console.log(JSON.stringify({ name: "pursor", version: VERSION, plugins: listPlugins() }, null, 2));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "probe": { if (!url) die("missing url"); const r = await runProbe(url); console.log(JSON.stringify(r, null, 2)); break; }
|
|
60
|
+
case "shot": { if (!url) die("missing url"); const out = b || makeOut("shot.png"); const r = await runShot(url, out, { fullPage: false }); console.log(JSON.stringify(r, null, 2)); break; }
|
|
61
|
+
case "full": { if (!url) die("missing url"); const out = b || makeOut("full.png"); const r = await runShot(url, out, { fullPage: true }); console.log(JSON.stringify(r, null, 2)); break; }
|
|
62
|
+
case "eval": { if (!url) die("missing url"); const js = readArg(b); if (!js) die("eval: missing <js> (or @file)"); const out = c || makeOut("eval.png"); const r = await runEval(url, js, out); console.log(JSON.stringify(r, null, 2)); break; }
|
|
63
|
+
case "click": { if (!url) die("missing url"); const sel = b; if (!sel) die("click: missing <selector>"); const out = c || makeOut(`click-${(sel||"").replace(/[^a-z0-9]+/gi, "_").slice(0, 32)}.png`); const r = await runClick(url, sel, out); console.log(JSON.stringify(r, null, 2)); break; }
|
|
64
|
+
case "type": { if (!url) die("missing url"); const sel = b; const text = readArg(c); if (!sel || text === undefined) die("type: missing <selector> or <text> (text can be @file)"); const out = d || makeOut(`type-${(sel||"").replace(/[^a-z0-9]+/gi, "_").slice(0, 32)}.png`); const r = await runType(url, sel, text, out); console.log(JSON.stringify(r, null, 2)); break; }
|
|
65
|
+
case "wait": { if (!url) die("missing url"); const sel = b; if (!sel) die("wait: missing <selector>"); const t = c !== undefined ? asNum(c, 30000) : 30000; const r = await runWait(url, sel, t); console.log(JSON.stringify(r, null, 2)); break; }
|
|
66
|
+
case "diff": { if (!url) die("missing url"); const ref = b; if (!ref) die("diff: missing <ref.png>"); const out = c || makeOut("diff.png"); const threshold = d !== undefined ? Number(d) : 0.1; const r = await runDiff(url, ref, out, threshold); console.log(JSON.stringify(r, null, 2)); break; }
|
|
67
|
+
case "seq": { if (!url) die("missing url"); const actions = readArg(b); if (!actions) die("seq: missing <actions.json> (or @file)"); const out = c || makeOut("seq.png"); const r = await runSeq(url, actions, out); console.log(JSON.stringify(r, null, 2)); break; }
|
|
68
|
+
case "viewports": { console.log(JSON.stringify(listViewports(), null, 2)); break; }
|
|
69
|
+
case "shoot": {
|
|
70
|
+
if (!url) die("missing url");
|
|
71
|
+
const out = (b && !b.startsWith("--")) ? b : pickOutPath(argv.slice(5)) || makeOut("shoot.png");
|
|
72
|
+
const r = await runShootWithSidecar({ url, out, flags: parseFlags(argv.slice(5)) });
|
|
73
|
+
console.log(JSON.stringify(r, null, 2));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "layer": {
|
|
77
|
+
if (!url) die("missing url");
|
|
78
|
+
const layerName = b; if (!layerName) die("layer: missing <name>");
|
|
79
|
+
const out = (c && !c.startsWith("--")) ? c : pickOutPath(argv.slice(6)) || makeOut(`layer-${layerName}.png`);
|
|
80
|
+
const flags = parseFlags(argv.slice(7)); flags.layer = layerName;
|
|
81
|
+
const r = await runShootWithSidecar({ url, out, flags });
|
|
82
|
+
console.log(JSON.stringify(r, null, 2));
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "frames": {
|
|
86
|
+
if (!url) die("missing url");
|
|
87
|
+
const count = asNum(b, 8);
|
|
88
|
+
const stepMs = asNum(c, 250);
|
|
89
|
+
const outDir = (d && !d.startsWith("--")) ? d : makeOut(`frames-${count}x${stepMs}ms`);
|
|
90
|
+
const r = await runFrames({ url, count, intervalMs: stepMs, outDir, flags: parseFlags(argv.slice(7)) });
|
|
91
|
+
console.log(JSON.stringify(r, null, 2));
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "hover": {
|
|
95
|
+
if (!url) die("missing url");
|
|
96
|
+
const sel = b; if (!sel) die("hover: missing <selector>");
|
|
97
|
+
const out = (c && !c.startsWith("--")) ? c : pickOutPath(argv.slice(6)) || makeOut(`hover-${(sel||"").replace(/[^a-z0-9]+/gi, "_").slice(0, 32)}.png`);
|
|
98
|
+
const r = await runHover({ url, selector: sel, out, flags: parseFlags(argv.slice(6)) });
|
|
99
|
+
console.log(JSON.stringify(r, null, 2));
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "sweep": {
|
|
103
|
+
const planPath = readArg(a);
|
|
104
|
+
if (!planPath) die("sweep: missing <plan.json> (or @file)");
|
|
105
|
+
const outDirArg = (b && !b.startsWith("--")) ? b : undefined;
|
|
106
|
+
const r = await runSweep(planPath, outDirArg);
|
|
107
|
+
console.log(JSON.stringify(r, null, 2));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "every-viewport": {
|
|
111
|
+
if (!url) die("missing url");
|
|
112
|
+
const outDir = (b && !b.startsWith("--")) ? b : undefined;
|
|
113
|
+
const viewports = c?.startsWith("--") ? undefined : c?.split(",");
|
|
114
|
+
const r = await runEveryViewport({ url, outDir, viewports });
|
|
115
|
+
console.log(JSON.stringify(r, null, 2));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "audit": {
|
|
119
|
+
if (!url) die("missing url");
|
|
120
|
+
const tags = (b && !b.startsWith("--")) ? b : undefined;
|
|
121
|
+
const outDir = (c && !c.startsWith("--")) ? c : undefined;
|
|
122
|
+
const r = await runAudit({ url, tags: tags?.split(",").map(t => t.trim()), outDir });
|
|
123
|
+
console.log(JSON.stringify(r, null, 2));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "dom-snapshot": case "dom": {
|
|
127
|
+
if (!url) die("missing url");
|
|
128
|
+
const out = (b && !b.startsWith("--")) ? b : undefined;
|
|
129
|
+
const r = await captureDomSnapshot({ url, out });
|
|
130
|
+
console.log(JSON.stringify({ url: r.url, title: r.title, elements: r.selectorMap?.length, domSize: r.dom?.length, out: r.url?.replace(/[^/]+$/, "") + "dom.json" }, null, 2));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "validate": {
|
|
134
|
+
const planPath = readArg(a);
|
|
135
|
+
if (!planPath) die("validate: missing <plan.json> (or @file)");
|
|
136
|
+
let plan;
|
|
137
|
+
try { plan = JSON.parse(readFile(planPath, "utf8")); }
|
|
138
|
+
catch (e) { die("validate: " + e.message); }
|
|
139
|
+
const { validateSweepPlan } = await import("../src/sweep-schema.js");
|
|
140
|
+
const v = validateSweepPlan(plan);
|
|
141
|
+
console.log(JSON.stringify({ valid: v.valid, errors: v.errors, plan: planPath }, null, 2));
|
|
142
|
+
if (!v.valid) process.exit(1);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "baseline": {
|
|
146
|
+
// pursor baseline <sub> [...args]
|
|
147
|
+
// sub=list -> list baselines
|
|
148
|
+
// sub=save <project> <png> <step> [--id <id>] [--url <u>] [--meta-json <file>]
|
|
149
|
+
// sub=approve <project> <png> <step> [--id <id>] [--url <u>]
|
|
150
|
+
// sub=show <project> <step> [--id <id>] [--url <u>]
|
|
151
|
+
const sub = a;
|
|
152
|
+
const { saveBaseline, listBaselines, loadBaseline, approveBaseline, diffKey } = await import("../src/baseline.js");
|
|
153
|
+
if (sub === "list") {
|
|
154
|
+
// baseline list [project]
|
|
155
|
+
const project = b;
|
|
156
|
+
console.log(JSON.stringify(listBaselines(project), null, 2));
|
|
157
|
+
} else if (sub === "save") {
|
|
158
|
+
if (!b || !c || !d) die("baseline save: <project> <png> <step> [--id <id>] [--url <u>] [--meta-json <file>]");
|
|
159
|
+
const project = b, png = c, step = d;
|
|
160
|
+
const flags = parseFlags(argv.slice(7));
|
|
161
|
+
let meta = null;
|
|
162
|
+
if (flags["meta-json"]) meta = JSON.parse(readFile(flags["meta-json"], "utf8"));
|
|
163
|
+
else if (flags.url) meta = { url: flags.url };
|
|
164
|
+
const id = flags.id || diffKey({ url: meta?.url || "", viewport: meta?.viewport, flags: meta?.flags || {} });
|
|
165
|
+
const result = saveBaseline({ project, id, step, png, meta });
|
|
166
|
+
console.log(JSON.stringify({ saved: true, ...result }, null, 2));
|
|
167
|
+
} else if (sub === "approve") {
|
|
168
|
+
if (!b || !c || !d) die("baseline approve: <project> <png> <step> [--id <id>] [--url <u>]");
|
|
169
|
+
const project = b, png = c, step = d;
|
|
170
|
+
const flags = parseFlags(argv.slice(7));
|
|
171
|
+
const id = flags.id || diffKey({ url: flags.url || "", flags: {} });
|
|
172
|
+
const result = approveBaseline({ project, id, step, fromPng: png });
|
|
173
|
+
console.log(JSON.stringify({ approved: true, ...result }, null, 2));
|
|
174
|
+
} else if (sub === "show") {
|
|
175
|
+
if (!b || !c) die("baseline show: <project> <step> [--id <id>] [--url <u>]");
|
|
176
|
+
const project = b, step = c;
|
|
177
|
+
const flags = parseFlags(argv.slice(5));
|
|
178
|
+
const id = flags.id || diffKey({ url: flags.url || "", flags: {} });
|
|
179
|
+
const r = loadBaseline({ project, id, step });
|
|
180
|
+
console.log(JSON.stringify(r, null, 2));
|
|
181
|
+
} else {
|
|
182
|
+
die("baseline subcommand: list | save | approve | show");
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "auth": {
|
|
187
|
+
// pursor auth <sub> [...args]
|
|
188
|
+
// save <project> <name> --from <state.json>
|
|
189
|
+
// load <project> <name> --out <state.json>
|
|
190
|
+
// list [project]
|
|
191
|
+
// delete <project> <name>
|
|
192
|
+
const sub = a;
|
|
193
|
+
const { saveAuthState, loadAuthState, listAuthStates, deleteAuthState } = await import("../src/auth.js");
|
|
194
|
+
if (sub === "list") {
|
|
195
|
+
const project = b;
|
|
196
|
+
console.log(JSON.stringify(listAuthStates(project), null, 2));
|
|
197
|
+
} else if (sub === "save") {
|
|
198
|
+
if (!b || !c) die("auth save: <project> <name> --from <state.json>");
|
|
199
|
+
const fromFile = argv[argv.indexOf("--from") + 1];
|
|
200
|
+
if (!fromFile) die("auth save: missing --from <state.json>");
|
|
201
|
+
const state = JSON.parse(readFile(fromFile, "utf8"));
|
|
202
|
+
const r = saveAuthState({ project: b, name: c, state });
|
|
203
|
+
console.log(JSON.stringify({ saved: true, ...r }, null, 2));
|
|
204
|
+
} else if (sub === "load") {
|
|
205
|
+
if (!b || !c) die("auth load: <project> <name> --out <state.json>");
|
|
206
|
+
const outFile = argv[argv.indexOf("--out") + 1];
|
|
207
|
+
if (!outFile) die("auth load: missing --out <state.json>");
|
|
208
|
+
const state = loadAuthState({ project: b, name: c });
|
|
209
|
+
if (!state) { console.error("not found"); process.exit(2); }
|
|
210
|
+
writeFileSync(outFile, JSON.stringify(state, null, 2), "utf8");
|
|
211
|
+
console.log(JSON.stringify({ loaded: true, file: outFile, cookies: state.cookies.length, origins: state.origins.length }, null, 2));
|
|
212
|
+
} else if (sub === "delete") {
|
|
213
|
+
if (!b || !c) die("auth delete: <project> <name>");
|
|
214
|
+
const ok = deleteAuthState({ project: b, name: c });
|
|
215
|
+
console.log(JSON.stringify({ deleted: ok }, null, 2));
|
|
216
|
+
} else {
|
|
217
|
+
die("auth subcommand: list | save | load | delete");
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
default: { die(`unknown subcommand: ${cmd}`); }
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(JSON.stringify({ error: e.message, stack: e.stack?.split("\n").slice(0, 3).join("\n") }, null, 2));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
191
227
|
})();
|
package/package.json
CHANGED
|
@@ -1,73 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pursor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"private": false,
|
|
5
|
-
"description": "Visual QA & audit & MCP for the browser. Capture, sweep, and review any web target with multi-viewport, layered, animated, hover, grid, and cursor states.",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"pursor": "./bin/pursor.mjs",
|
|
9
|
-
"pursor-mcp": "./bin/pursor-mcp.mjs"
|
|
10
|
-
},
|
|
11
|
-
"main": "./src/index.js",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": "./src/index.js",
|
|
14
|
-
"./plugin": "./src/plugin.js",
|
|
15
|
-
"./plugins/*": "./plugins/*.js",
|
|
16
|
-
"./util": "./src/util.js",
|
|
17
|
-
"./runway": "./src/runway.js",
|
|
18
|
-
"./selector": "./src/selector.js",
|
|
19
|
-
"./overlays": "./src/overlays.js",
|
|
20
|
-
"./viewport": "./src/viewport.js",
|
|
21
|
-
"./dom-snapshot": "./src/dom-snapshot.js",
|
|
22
|
-
"./plugin-audit": "./src/plugin-audit.js",
|
|
23
|
-
"./selector-heal": "./src/selector-heal.js",
|
|
24
|
-
"./ci-output": "./src/ci-output.js",
|
|
25
|
-
"./mcp": "./src/mcp.js",
|
|
26
|
-
"./baseline": "./src/baseline.js",
|
|
27
|
-
"./sweep-schema": "./src/sweep-schema.js",
|
|
28
|
-
"./mcp-resources": "./src/mcp-resources.js"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "pursor",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Visual QA & audit & MCP for the browser. Capture, sweep, and review any web target with multi-viewport, layered, animated, hover, grid, and cursor states.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pursor": "./bin/pursor.mjs",
|
|
9
|
+
"pursor-mcp": "./bin/pursor-mcp.mjs"
|
|
10
|
+
},
|
|
11
|
+
"main": "./src/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/index.js",
|
|
14
|
+
"./plugin": "./src/plugin.js",
|
|
15
|
+
"./plugins/*": "./plugins/*.js",
|
|
16
|
+
"./util": "./src/util.js",
|
|
17
|
+
"./runway": "./src/runway.js",
|
|
18
|
+
"./selector": "./src/selector.js",
|
|
19
|
+
"./overlays": "./src/overlays.js",
|
|
20
|
+
"./viewport": "./src/viewport.js",
|
|
21
|
+
"./dom-snapshot": "./src/dom-snapshot.js",
|
|
22
|
+
"./plugin-audit": "./src/plugin-audit.js",
|
|
23
|
+
"./selector-heal": "./src/selector-heal.js",
|
|
24
|
+
"./ci-output": "./src/ci-output.js",
|
|
25
|
+
"./mcp": "./src/mcp.js",
|
|
26
|
+
"./baseline": "./src/baseline.js",
|
|
27
|
+
"./sweep-schema": "./src/sweep-schema.js",
|
|
28
|
+
"./mcp-resources": "./src/mcp-resources.js",
|
|
29
|
+
"./har": "./src/har.js",
|
|
30
|
+
"./auth": "./src/auth.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"src",
|
|
35
|
+
"plugins",
|
|
36
|
+
"plans",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"start": "node bin/pursor.mjs",
|
|
42
|
+
"test": "node --test \"test/*.test.js\"",
|
|
43
|
+
"smoke": "node bin/pursor.mjs viewports"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"visual-qa",
|
|
50
|
+
"visual-regression",
|
|
51
|
+
"screenshot",
|
|
52
|
+
"audit",
|
|
53
|
+
"isometric",
|
|
54
|
+
"playwright",
|
|
55
|
+
"testing",
|
|
56
|
+
"devtools"
|
|
57
|
+
],
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"axe-core": "^4.12.1",
|
|
61
|
+
"pixelmatch": "^5.3.0",
|
|
62
|
+
"pngjs": "^7.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"playwright-core": "*"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"playwright-core": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"playwright-core": "^1.61.0"
|
|
74
|
+
}
|
|
75
|
+
}
|