@ingcreators/annot-mcp 0.2.0 → 0.3.1
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/CHANGELOG.md +49 -0
- package/README.md +38 -0
- package/dist/browser/aria-snapshot.d.ts +37 -0
- package/dist/index.js +1704 -1209
- package/dist/tools/aria-snapshot.d.ts +73 -0
- package/dist/tools/draft-screen-spec.d.ts +75 -0
- package/dist/tools/propose-drift-fixes.d.ts +64 -0
- package/dist/tools/translate-screen-spec.d.ts +31 -0
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,1267 +1,1762 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { StdioServerTransport as
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
import { BBOX_ANNOTATION_SCHEMA as e, BBOX_REDACT_REGION_SCHEMA as t, DEFAULT_ENCODE_OPTIONS as n, SHARED_DEFS as r, arrowBetween as i, bboxAnnotationsToSvg as a, bboxAnnotationsToSvg as o, createAnnotator as s, decodeAndEncodeImage as c, rectForBoundingBox as l, textAt as u } from "@ingcreators/annot-annotator";
|
|
2
|
+
import { createCanvas as d, loadImage as f } from "@napi-rs/canvas";
|
|
3
|
+
import ee from "pixelmatch";
|
|
4
|
+
import { readFile as te, writeFile as p } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute as m } from "node:path";
|
|
6
|
+
import { Server as ne } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { CallToolRequestSchema as re, ListToolsRequestSchema as ie } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { detectDrift as ae, lintableScreens as oe, parseMdxFile as se, parseSnapshot as ce } from "@ingcreators/annot-product-docs";
|
|
9
|
+
import { StdioServerTransport as le } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
//#region src/browser/capture.ts
|
|
11
|
+
var ue = {
|
|
12
|
+
width: 1280,
|
|
13
|
+
height: 800,
|
|
14
|
+
deviceScaleFactor: 1
|
|
15
|
+
}, de = "load";
|
|
16
|
+
async function h(e, t) {
|
|
17
|
+
let n = {
|
|
18
|
+
...ue,
|
|
19
|
+
...t.viewport
|
|
20
|
+
}, r = t.waitFor ?? de, i = t.fullPage ?? !1, a = await e.acquire(), o, s = !1, c = () => {
|
|
21
|
+
s || (s = !0, e.release());
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
o = await a.newContext({
|
|
25
|
+
viewport: {
|
|
26
|
+
width: n.width,
|
|
27
|
+
height: n.height
|
|
28
|
+
},
|
|
29
|
+
deviceScaleFactor: n.deviceScaleFactor
|
|
30
|
+
});
|
|
31
|
+
let e = await o.newPage();
|
|
32
|
+
await e.goto(t.url, { waitUntil: r });
|
|
33
|
+
let l = await e.screenshot({
|
|
34
|
+
fullPage: i,
|
|
35
|
+
type: "png"
|
|
36
|
+
}), u = new Uint8Array(l.buffer, l.byteOffset, l.byteLength), d = o;
|
|
37
|
+
return {
|
|
38
|
+
pngBytes: u,
|
|
39
|
+
handle: {
|
|
40
|
+
page: e,
|
|
41
|
+
async close() {
|
|
42
|
+
if (!s) try {
|
|
43
|
+
await d.close();
|
|
44
|
+
} finally {
|
|
45
|
+
c();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
} catch (e) {
|
|
51
|
+
if (o) try {
|
|
52
|
+
await o.close();
|
|
53
|
+
} catch {}
|
|
54
|
+
throw c(), e;
|
|
55
|
+
}
|
|
44
56
|
}
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.#o && (clearTimeout(this.#o), this.#o = void 0);
|
|
101
|
-
}
|
|
102
|
-
#a() {
|
|
103
|
-
this.#n(), this.#o = setTimeout(() => {
|
|
104
|
-
const t = this.#e;
|
|
105
|
-
this.#e = void 0, this.#o = void 0, t?.close().catch(() => {
|
|
106
|
-
});
|
|
107
|
-
}, this.#i), this.#o.unref?.();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function ye(e = {}) {
|
|
111
|
-
return new ge(
|
|
112
|
-
{
|
|
113
|
-
async launch() {
|
|
114
|
-
let t;
|
|
115
|
-
try {
|
|
116
|
-
({ chromium: t } = await import("playwright-core"));
|
|
117
|
-
} catch (o) {
|
|
118
|
-
throw new q(
|
|
119
|
-
"Failed to load `playwright-core`. Reinstall `@ingcreators/annot-mcp` to restore the dependency.",
|
|
120
|
-
o
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
return await t.launch({ headless: !0 });
|
|
125
|
-
} catch (o) {
|
|
126
|
-
throw new q(
|
|
127
|
-
"Failed to launch Chromium. Run `npx playwright install chromium` to download the runtime, then retry.",
|
|
128
|
-
o
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
e
|
|
134
|
-
);
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/browser/pool.ts
|
|
59
|
+
var fe = 3e4, g = class {
|
|
60
|
+
#e;
|
|
61
|
+
#t;
|
|
62
|
+
#n;
|
|
63
|
+
#r;
|
|
64
|
+
#i = 0;
|
|
65
|
+
#a;
|
|
66
|
+
constructor(e, t = {}) {
|
|
67
|
+
this.#e = e, this.#t = t.idleTimeoutMs ?? fe;
|
|
68
|
+
}
|
|
69
|
+
async acquire() {
|
|
70
|
+
return this.#o(), this.#i += 1, this.#n ? this.#n : (this.#r ||= this.#e.launch().then((e) => (this.#n = e, this.#r = void 0, e)).catch((e) => {
|
|
71
|
+
throw this.#r = void 0, this.#i = Math.max(0, this.#i - 1), e;
|
|
72
|
+
}), this.#r);
|
|
73
|
+
}
|
|
74
|
+
release() {
|
|
75
|
+
this.#i !== 0 && (--this.#i, this.#i === 0 && this.#n && this.#s());
|
|
76
|
+
}
|
|
77
|
+
async shutdown() {
|
|
78
|
+
this.#o();
|
|
79
|
+
let e = this.#n;
|
|
80
|
+
this.#n = void 0, this.#i = 0, e && await e.close();
|
|
81
|
+
}
|
|
82
|
+
get activeBorrows() {
|
|
83
|
+
return this.#i;
|
|
84
|
+
}
|
|
85
|
+
get isLaunched() {
|
|
86
|
+
return this.#n !== void 0;
|
|
87
|
+
}
|
|
88
|
+
#o() {
|
|
89
|
+
this.#a &&= (clearTimeout(this.#a), void 0);
|
|
90
|
+
}
|
|
91
|
+
#s() {
|
|
92
|
+
this.#o(), this.#a = setTimeout(() => {
|
|
93
|
+
let e = this.#n;
|
|
94
|
+
this.#n = void 0, this.#a = void 0, e?.close().catch(() => {});
|
|
95
|
+
}, this.#t), this.#a.unref?.();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function pe(e = {}) {
|
|
99
|
+
return new g({ async launch() {
|
|
100
|
+
let e;
|
|
101
|
+
try {
|
|
102
|
+
({chromium: e} = await import("playwright-core"));
|
|
103
|
+
} catch (e) {
|
|
104
|
+
throw new _("Failed to load `playwright-core`. Reinstall `@ingcreators/annot-mcp` to restore the dependency.", e);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return await e.launch({ headless: !0 });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
throw new _("Failed to launch Chromium. Run `npx playwright install chromium` to download the runtime, then retry.", e);
|
|
110
|
+
}
|
|
111
|
+
} }, e);
|
|
135
112
|
}
|
|
136
|
-
class
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
113
|
+
var _ = class extends Error {
|
|
114
|
+
cause;
|
|
115
|
+
constructor(e, t) {
|
|
116
|
+
super(e), this.name = "ChromiumUnavailableError", this.cause = t;
|
|
117
|
+
}
|
|
118
|
+
}, v = class extends Error {
|
|
119
|
+
locator;
|
|
120
|
+
constructor(e, t) {
|
|
121
|
+
super(t), this.name = "LocatorResolutionError", this.locator = e;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
async function y(e, t) {
|
|
125
|
+
let n = await e.locator(t).boundingBox();
|
|
126
|
+
if (!n) throw new v(t, `Locator "${t}" resolved to no visible element. Check the selector matches at least one element and the element is inside the captured viewport.`);
|
|
127
|
+
return n;
|
|
141
128
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
async function me(e, t) {
|
|
130
|
+
switch (t.type) {
|
|
131
|
+
case "rect": {
|
|
132
|
+
let n = t.bbox ?? await y(e, x(t.locator));
|
|
133
|
+
return {
|
|
134
|
+
...S(t),
|
|
135
|
+
type: "rect",
|
|
136
|
+
bbox: n
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
case "circle": {
|
|
140
|
+
if (t.center !== void 0 && t.radius !== void 0) return {
|
|
141
|
+
...S(t),
|
|
142
|
+
type: "circle",
|
|
143
|
+
center: t.center,
|
|
144
|
+
radius: t.radius
|
|
145
|
+
};
|
|
146
|
+
let n = await y(e, x(t.locator));
|
|
147
|
+
return {
|
|
148
|
+
...S(t),
|
|
149
|
+
type: "circle",
|
|
150
|
+
center: b(n),
|
|
151
|
+
radius: Math.min(n.width, n.height) / 2
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
case "arrow": {
|
|
155
|
+
let n = t.from ?? b(await y(e, x(t.fromLocator))), r = t.to ?? b(await y(e, x(t.toLocator)));
|
|
156
|
+
return {
|
|
157
|
+
...S(t),
|
|
158
|
+
type: "arrow",
|
|
159
|
+
from: n,
|
|
160
|
+
to: r
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case "text": {
|
|
164
|
+
let n = t.at ?? _e(await y(e, x(t.locator)), t.fontSize), r = {
|
|
165
|
+
...S(t),
|
|
166
|
+
type: "text",
|
|
167
|
+
at: n,
|
|
168
|
+
content: t.content
|
|
169
|
+
};
|
|
170
|
+
return t.fontSize !== void 0 && (r.fontSize = t.fontSize), t.anchor !== void 0 && (r.anchor = t.anchor), r;
|
|
171
|
+
}
|
|
172
|
+
case "callout": {
|
|
173
|
+
let n = t.at ?? await ge(e, t.atLocator), r = t.targetBbox ?? await y(e, x(t.targetLocator));
|
|
174
|
+
return {
|
|
175
|
+
...S(t),
|
|
176
|
+
type: "callout",
|
|
177
|
+
at: n,
|
|
178
|
+
targetBbox: r,
|
|
179
|
+
content: t.content
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case "raw": return t;
|
|
183
|
+
}
|
|
147
184
|
}
|
|
148
|
-
async function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
t,
|
|
153
|
-
`Locator "${t}" resolved to no visible element. Check the selector matches at least one element and the element is inside the captured viewport.`
|
|
154
|
-
);
|
|
155
|
-
return o;
|
|
185
|
+
async function he(e, t) {
|
|
186
|
+
let n = [];
|
|
187
|
+
for (let r of t) n.push(await me(e, r));
|
|
188
|
+
return n;
|
|
156
189
|
}
|
|
157
|
-
async function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const o = t.bbox ?? await f(e, m(t.locator));
|
|
161
|
-
return { ...g(t), type: "rect", bbox: o };
|
|
162
|
-
}
|
|
163
|
-
case "circle": {
|
|
164
|
-
if (t.center !== void 0 && t.radius !== void 0)
|
|
165
|
-
return {
|
|
166
|
-
...g(t),
|
|
167
|
-
type: "circle",
|
|
168
|
-
center: t.center,
|
|
169
|
-
radius: t.radius
|
|
170
|
-
};
|
|
171
|
-
const o = await f(e, m(t.locator));
|
|
172
|
-
return {
|
|
173
|
-
...g(t),
|
|
174
|
-
type: "circle",
|
|
175
|
-
center: T(o),
|
|
176
|
-
radius: Math.min(o.width, o.height) / 2
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
case "arrow": {
|
|
180
|
-
const o = t.from ?? T(await f(e, m(t.fromLocator))), n = t.to ?? T(await f(e, m(t.toLocator)));
|
|
181
|
-
return {
|
|
182
|
-
...g(t),
|
|
183
|
-
type: "arrow",
|
|
184
|
-
from: o,
|
|
185
|
-
to: n
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
case "text": {
|
|
189
|
-
const o = t.at ?? $e(
|
|
190
|
-
await f(e, m(t.locator)),
|
|
191
|
-
t.fontSize
|
|
192
|
-
), n = {
|
|
193
|
-
...g(t),
|
|
194
|
-
type: "text",
|
|
195
|
-
at: o,
|
|
196
|
-
content: t.content
|
|
197
|
-
};
|
|
198
|
-
return t.fontSize !== void 0 && (n.fontSize = t.fontSize), t.anchor !== void 0 && (n.anchor = t.anchor), n;
|
|
199
|
-
}
|
|
200
|
-
case "callout": {
|
|
201
|
-
const o = t.at ?? await ve(e, t.atLocator), n = t.targetBbox ?? await f(e, m(t.targetLocator));
|
|
202
|
-
return {
|
|
203
|
-
...g(t),
|
|
204
|
-
type: "callout",
|
|
205
|
-
at: o,
|
|
206
|
-
targetBbox: n,
|
|
207
|
-
content: t.content
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
case "raw":
|
|
211
|
-
return t;
|
|
212
|
-
}
|
|
190
|
+
async function ge(e, t) {
|
|
191
|
+
if (!t) throw new v("<callout.at>", "callout annotations require either `at` or `atLocator`.");
|
|
192
|
+
return b(await y(e, t));
|
|
213
193
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
194
|
+
function b(e) {
|
|
195
|
+
return {
|
|
196
|
+
x: e.x + e.width / 2,
|
|
197
|
+
y: e.y + e.height / 2
|
|
198
|
+
};
|
|
219
199
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const o = await f(e, t);
|
|
227
|
-
return T(o);
|
|
200
|
+
function _e(e, t) {
|
|
201
|
+
let n = (t ?? 14) + 2;
|
|
202
|
+
return {
|
|
203
|
+
x: e.x,
|
|
204
|
+
y: Math.max(0, e.y - n)
|
|
205
|
+
};
|
|
228
206
|
}
|
|
229
|
-
function
|
|
230
|
-
|
|
207
|
+
function x(e) {
|
|
208
|
+
if (e === void 0) throw new v("<missing>", "Internal error: annotation passed schema validation without a locator or bbox. This is a bug.");
|
|
209
|
+
return e;
|
|
231
210
|
}
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
|
|
211
|
+
function S(e) {
|
|
212
|
+
if (e.type === "raw") return {};
|
|
213
|
+
let t = {};
|
|
214
|
+
return e.intent !== void 0 && (t.intent = e.intent), e.stroke !== void 0 && (t.stroke = e.stroke), e.strokeWidth !== void 0 && (t.strokeWidth = e.strokeWidth), e.fill !== void 0 && (t.fill = e.fill), e.color !== void 0 && (t.color = e.color), t;
|
|
235
215
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/compare/aggregate.ts
|
|
218
|
+
var ve = 4;
|
|
219
|
+
function ye(e, t, n) {
|
|
220
|
+
let r = new Uint8Array(t * n), i = [];
|
|
221
|
+
for (let a = 0; a < n; a++) for (let o = 0; o < t; o++) {
|
|
222
|
+
let s = a * t + o;
|
|
223
|
+
if (r[s] || !be(e, s)) continue;
|
|
224
|
+
let c = xe(e, r, o, a, t, n);
|
|
225
|
+
c.count >= ve && i.push({
|
|
226
|
+
x: c.minX,
|
|
227
|
+
y: c.minY,
|
|
228
|
+
width: c.maxX - c.minX + 1,
|
|
229
|
+
height: c.maxY - c.minY + 1
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return i;
|
|
243
233
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
return {};
|
|
247
|
-
const t = {};
|
|
248
|
-
return e.intent !== void 0 && (t.intent = e.intent), e.stroke !== void 0 && (t.stroke = e.stroke), e.strokeWidth !== void 0 && (t.strokeWidth = e.strokeWidth), e.fill !== void 0 && (t.fill = e.fill), e.color !== void 0 && (t.color = e.color), t;
|
|
234
|
+
function be(e, t) {
|
|
235
|
+
return e[t * 4 + 3] > 0;
|
|
249
236
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return s;
|
|
237
|
+
function xe(e, t, n, r, i, a) {
|
|
238
|
+
let o = {
|
|
239
|
+
count: 0,
|
|
240
|
+
minX: n,
|
|
241
|
+
minY: r,
|
|
242
|
+
maxX: n,
|
|
243
|
+
maxY: r
|
|
244
|
+
}, s = [n, r];
|
|
245
|
+
for (; s.length > 0;) {
|
|
246
|
+
let n = s.pop(), r = s.pop();
|
|
247
|
+
if (r < 0 || r >= i || n < 0 || n >= a) continue;
|
|
248
|
+
let c = n * i + r;
|
|
249
|
+
t[c] || be(e, c) && (t[c] = 1, o.count += 1, r < o.minX && (o.minX = r), n < o.minY && (o.minY = n), r > o.maxX && (o.maxX = r), n > o.maxY && (o.maxY = n), s.push(r + 1, n), s.push(r - 1, n), s.push(r, n + 1), s.push(r, n - 1));
|
|
250
|
+
}
|
|
251
|
+
return o;
|
|
266
252
|
}
|
|
267
|
-
|
|
268
|
-
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/compare/diff.ts
|
|
255
|
+
var Se = class extends Error {
|
|
256
|
+
constructor(e, t) {
|
|
257
|
+
super(`Cannot compare screenshots of different dimensions: before is ${e.width}×${e.height}, after is ${t.width}×${t.height}.`), this.name = "DimensionMismatchError";
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
async function C(e, t, n = {}) {
|
|
261
|
+
let [r, i] = await Promise.all([f(Buffer.from(e)), f(Buffer.from(t))]);
|
|
262
|
+
if (r.width !== i.width || r.height !== i.height) throw new Se({
|
|
263
|
+
width: r.width,
|
|
264
|
+
height: r.height
|
|
265
|
+
}, {
|
|
266
|
+
width: i.width,
|
|
267
|
+
height: i.height
|
|
268
|
+
});
|
|
269
|
+
let { width: a, height: o } = r, s = w(r, a, o), c = w(i, a, o), l = new Uint8Array(a * o * 4);
|
|
270
|
+
return {
|
|
271
|
+
mismatchedPixels: ee(s, c, l, a, o, {
|
|
272
|
+
threshold: n.threshold ?? .1,
|
|
273
|
+
includeAA: !1,
|
|
274
|
+
diffMask: !0
|
|
275
|
+
}),
|
|
276
|
+
regions: ye(l, a, o),
|
|
277
|
+
width: a,
|
|
278
|
+
height: o
|
|
279
|
+
};
|
|
269
280
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
maxX: o,
|
|
276
|
-
maxY: n
|
|
277
|
-
}, a = [o, n];
|
|
278
|
-
for (; a.length > 0; ) {
|
|
279
|
-
const i = a.pop(), d = a.pop();
|
|
280
|
-
if (d < 0 || d >= s || i < 0 || i >= c) continue;
|
|
281
|
-
const l = i * s + d;
|
|
282
|
-
t[l] || X(e, l) && (t[l] = 1, r.count += 1, d < r.minX && (r.minX = d), i < r.minY && (r.minY = i), d > r.maxX && (r.maxX = d), i > r.maxY && (r.maxY = i), a.push(d + 1, i), a.push(d - 1, i), a.push(d, i + 1), a.push(d, i - 1));
|
|
283
|
-
}
|
|
284
|
-
return r;
|
|
281
|
+
function w(e, t, n) {
|
|
282
|
+
let r = d(t, n).getContext("2d");
|
|
283
|
+
r.drawImage(e, 0, 0);
|
|
284
|
+
let i = r.getImageData(0, 0, t, n).data;
|
|
285
|
+
return new Uint8Array(i.buffer, i.byteOffset, i.byteLength);
|
|
285
286
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/dsl/schema.ts
|
|
289
|
+
var T = {
|
|
290
|
+
...r,
|
|
291
|
+
Locator: {
|
|
292
|
+
type: "string",
|
|
293
|
+
minLength: 1
|
|
294
|
+
}
|
|
295
|
+
}, E = { oneOf: [
|
|
296
|
+
{
|
|
297
|
+
type: "object",
|
|
298
|
+
required: ["type"],
|
|
299
|
+
additionalProperties: !1,
|
|
300
|
+
properties: {
|
|
301
|
+
type: { const: "rect" },
|
|
302
|
+
bbox: { $ref: "#/$defs/BBox" },
|
|
303
|
+
locator: { $ref: "#/$defs/Locator" },
|
|
304
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
305
|
+
stroke: { type: "string" },
|
|
306
|
+
strokeWidth: {
|
|
307
|
+
type: "number",
|
|
308
|
+
minimum: 0
|
|
309
|
+
},
|
|
310
|
+
fill: { type: "string" },
|
|
311
|
+
color: { type: "string" }
|
|
312
|
+
},
|
|
313
|
+
anyOf: [{ required: ["bbox"] }, { required: ["locator"] }]
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: "object",
|
|
317
|
+
required: ["type"],
|
|
318
|
+
additionalProperties: !1,
|
|
319
|
+
properties: {
|
|
320
|
+
type: { const: "circle" },
|
|
321
|
+
center: { $ref: "#/$defs/Point" },
|
|
322
|
+
radius: {
|
|
323
|
+
type: "number",
|
|
324
|
+
minimum: 0
|
|
325
|
+
},
|
|
326
|
+
locator: { $ref: "#/$defs/Locator" },
|
|
327
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
328
|
+
stroke: { type: "string" },
|
|
329
|
+
strokeWidth: {
|
|
330
|
+
type: "number",
|
|
331
|
+
minimum: 0
|
|
332
|
+
},
|
|
333
|
+
fill: { type: "string" },
|
|
334
|
+
color: { type: "string" }
|
|
335
|
+
},
|
|
336
|
+
anyOf: [{ required: ["center", "radius"] }, { required: ["locator"] }]
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
type: "object",
|
|
340
|
+
required: ["type"],
|
|
341
|
+
additionalProperties: !1,
|
|
342
|
+
properties: {
|
|
343
|
+
type: { const: "arrow" },
|
|
344
|
+
from: { $ref: "#/$defs/Point" },
|
|
345
|
+
fromLocator: { $ref: "#/$defs/Locator" },
|
|
346
|
+
to: { $ref: "#/$defs/Point" },
|
|
347
|
+
toLocator: { $ref: "#/$defs/Locator" },
|
|
348
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
349
|
+
stroke: { type: "string" },
|
|
350
|
+
strokeWidth: {
|
|
351
|
+
type: "number",
|
|
352
|
+
minimum: 0
|
|
353
|
+
},
|
|
354
|
+
color: { type: "string" }
|
|
355
|
+
},
|
|
356
|
+
allOf: [{ anyOf: [{ required: ["from"] }, { required: ["fromLocator"] }] }, { anyOf: [{ required: ["to"] }, { required: ["toLocator"] }] }]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
type: "object",
|
|
360
|
+
required: ["type", "content"],
|
|
361
|
+
additionalProperties: !1,
|
|
362
|
+
properties: {
|
|
363
|
+
type: { const: "text" },
|
|
364
|
+
at: { $ref: "#/$defs/Point" },
|
|
365
|
+
locator: { $ref: "#/$defs/Locator" },
|
|
366
|
+
content: { type: "string" },
|
|
367
|
+
fontSize: {
|
|
368
|
+
type: "number",
|
|
369
|
+
minimum: 0
|
|
370
|
+
},
|
|
371
|
+
anchor: {
|
|
372
|
+
type: "string",
|
|
373
|
+
enum: [
|
|
374
|
+
"start",
|
|
375
|
+
"middle",
|
|
376
|
+
"end"
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
380
|
+
color: { type: "string" }
|
|
381
|
+
},
|
|
382
|
+
anyOf: [{ required: ["at"] }, { required: ["locator"] }]
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
type: "object",
|
|
386
|
+
required: ["type", "content"],
|
|
387
|
+
additionalProperties: !1,
|
|
388
|
+
properties: {
|
|
389
|
+
type: { const: "callout" },
|
|
390
|
+
at: { $ref: "#/$defs/Point" },
|
|
391
|
+
atLocator: { $ref: "#/$defs/Locator" },
|
|
392
|
+
targetBbox: { $ref: "#/$defs/BBox" },
|
|
393
|
+
targetLocator: { $ref: "#/$defs/Locator" },
|
|
394
|
+
content: { type: "string" },
|
|
395
|
+
intent: { $ref: "#/$defs/Intent" },
|
|
396
|
+
stroke: { type: "string" },
|
|
397
|
+
color: { type: "string" }
|
|
398
|
+
},
|
|
399
|
+
allOf: [{ anyOf: [{ required: ["at"] }, { required: ["atLocator"] }] }, { anyOf: [{ required: ["targetBbox"] }, { required: ["targetLocator"] }] }]
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
type: "object",
|
|
403
|
+
required: ["type", "svgFragment"],
|
|
404
|
+
additionalProperties: !1,
|
|
405
|
+
properties: {
|
|
406
|
+
type: { const: "raw" },
|
|
407
|
+
svgFragment: { type: "string" }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
] }, D = {
|
|
411
|
+
type: "object",
|
|
412
|
+
additionalProperties: !1,
|
|
413
|
+
properties: {
|
|
414
|
+
format: {
|
|
415
|
+
type: "string",
|
|
416
|
+
enum: [
|
|
417
|
+
"smart",
|
|
418
|
+
"png",
|
|
419
|
+
"jpeg"
|
|
420
|
+
],
|
|
421
|
+
description: "Encoding strategy. `smart` (default) picks PNG-8 for UI-heavy content and PNG-32 / JPEG for photo-heavy content per `smartFallback`. `png` forces PNG-32 (lossless). `jpeg` forces JPEG at `jpegPercent` quality."
|
|
422
|
+
},
|
|
423
|
+
saveSizePreset: {
|
|
424
|
+
type: "string",
|
|
425
|
+
enum: [
|
|
426
|
+
"light",
|
|
427
|
+
"standard",
|
|
428
|
+
"highQuality",
|
|
429
|
+
"original"
|
|
430
|
+
],
|
|
431
|
+
description: "Max-width cap. `light` = 1280px, `standard` = 1920px (default), `highQuality` = 2560px, `original` = no resize. Aspect-preserving; never upscales."
|
|
432
|
+
},
|
|
433
|
+
smartFallback: {
|
|
434
|
+
type: "string",
|
|
435
|
+
enum: ["png", "jpeg"],
|
|
436
|
+
description: "Fallback format used by smart mode when the image is photo-heavy. Default `png`."
|
|
437
|
+
},
|
|
438
|
+
smartColorThreshold: {
|
|
439
|
+
type: "integer",
|
|
440
|
+
minimum: 1,
|
|
441
|
+
description: "Unique-colour count above which smart mode treats the image as photo-heavy. Default 15000."
|
|
442
|
+
},
|
|
443
|
+
jpegPercent: {
|
|
444
|
+
type: "integer",
|
|
445
|
+
minimum: 60,
|
|
446
|
+
maximum: 100,
|
|
447
|
+
description: "JPEG quality 60–100. Default 92."
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}, Ce = {
|
|
451
|
+
type: "object",
|
|
452
|
+
additionalProperties: !1,
|
|
453
|
+
properties: {
|
|
454
|
+
bbox: { $ref: "#/$defs/BBox" },
|
|
455
|
+
locator: { $ref: "#/$defs/Locator" },
|
|
456
|
+
style: {
|
|
457
|
+
type: "string",
|
|
458
|
+
enum: [
|
|
459
|
+
"solid",
|
|
460
|
+
"mosaic",
|
|
461
|
+
"blur"
|
|
462
|
+
]
|
|
463
|
+
},
|
|
464
|
+
color: { type: "string" }
|
|
465
|
+
},
|
|
466
|
+
anyOf: [{ required: ["bbox"] }, { required: ["locator"] }]
|
|
467
|
+
}, we = [
|
|
468
|
+
137,
|
|
469
|
+
80,
|
|
470
|
+
78,
|
|
471
|
+
71,
|
|
472
|
+
13,
|
|
473
|
+
10,
|
|
474
|
+
26,
|
|
475
|
+
10
|
|
476
|
+
], O = 12, Te = "IHDR", Ee = 16, De = 20, Oe = 24, k = class extends Error {
|
|
477
|
+
constructor(e) {
|
|
478
|
+
super(e), this.name = "InvalidPngError";
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
function A(e) {
|
|
482
|
+
if (e.byteLength < Oe) throw new k(`Expected at least ${Oe} bytes for a PNG header, got ${e.byteLength}.`);
|
|
483
|
+
for (let t = 0; t < we.length; t++) if (e[t] !== we[t]) throw new k("Input does not start with the PNG signature (89 50 4E 47 0D 0A 1A 0A). Confirm the input is a PNG — JPEG / WebP / other formats are not supported in v1.");
|
|
484
|
+
let t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = String.fromCharCode(e[O], e[O + 1], e[O + 2], e[O + 3]);
|
|
485
|
+
if (n !== Te) throw new k(`Expected IHDR chunk after PNG signature, found "${n}". The file may be a corrupt or non-standard PNG.`);
|
|
486
|
+
let r = t.getUint32(Ee, !1), i = t.getUint32(De, !1);
|
|
487
|
+
if (r === 0 || i === 0) throw new k(`PNG dimensions ${r}×${i} include a zero axis.`);
|
|
488
|
+
return {
|
|
489
|
+
width: r,
|
|
490
|
+
height: i
|
|
491
|
+
};
|
|
292
492
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
includeAA: !1,
|
|
306
|
-
diffMask: !0
|
|
307
|
-
}), v = xe(d, c, r);
|
|
308
|
-
return { mismatchedPixels: l, regions: v, width: c, height: r };
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/io/read-image.ts
|
|
495
|
+
var j = class extends Error {
|
|
496
|
+
constructor(e) {
|
|
497
|
+
super(e), this.name = "InvalidImageInputError";
|
|
498
|
+
}
|
|
499
|
+
}, ke = "data:", Ae = /^data:image\/png;base64,/i;
|
|
500
|
+
async function M(e) {
|
|
501
|
+
if (typeof e != "string" || e.length === 0) throw new j("`image` must be a non-empty string (data URL or absolute filesystem path).");
|
|
502
|
+
if (e.startsWith(ke)) return je(e);
|
|
503
|
+
if (!m(e)) throw new j(`Filesystem path "${e}" is not absolute. Pass an absolute path so resolution doesn't depend on the MCP server's working directory.`);
|
|
504
|
+
return Me(e);
|
|
309
505
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
506
|
+
function je(e) {
|
|
507
|
+
if (!Ae.test(e)) throw new j("Only `data:image/png;base64,...` URLs are supported. JPEG / WebP / SVG data URLs are not accepted in v1.");
|
|
508
|
+
let t = Pe(e.slice(e.indexOf(",") + 1));
|
|
509
|
+
return {
|
|
510
|
+
bytes: t,
|
|
511
|
+
dataUrl: e,
|
|
512
|
+
dimensions: Ne(t)
|
|
513
|
+
};
|
|
315
514
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
fill: { type: "string" },
|
|
331
|
-
color: { type: "string" }
|
|
332
|
-
},
|
|
333
|
-
anyOf: [{ required: ["bbox"] }, { required: ["locator"] }]
|
|
334
|
-
}, Ae = {
|
|
335
|
-
type: "object",
|
|
336
|
-
required: ["type"],
|
|
337
|
-
additionalProperties: !1,
|
|
338
|
-
properties: {
|
|
339
|
-
type: { const: "circle" },
|
|
340
|
-
center: { $ref: "#/$defs/Point" },
|
|
341
|
-
radius: { type: "number", minimum: 0 },
|
|
342
|
-
locator: { $ref: "#/$defs/Locator" },
|
|
343
|
-
intent: { $ref: "#/$defs/Intent" },
|
|
344
|
-
stroke: { type: "string" },
|
|
345
|
-
strokeWidth: { type: "number", minimum: 0 },
|
|
346
|
-
fill: { type: "string" },
|
|
347
|
-
color: { type: "string" }
|
|
348
|
-
},
|
|
349
|
-
anyOf: [{ required: ["center", "radius"] }, { required: ["locator"] }]
|
|
350
|
-
}, Te = {
|
|
351
|
-
type: "object",
|
|
352
|
-
required: ["type"],
|
|
353
|
-
additionalProperties: !1,
|
|
354
|
-
properties: {
|
|
355
|
-
type: { const: "arrow" },
|
|
356
|
-
from: { $ref: "#/$defs/Point" },
|
|
357
|
-
fromLocator: { $ref: "#/$defs/Locator" },
|
|
358
|
-
to: { $ref: "#/$defs/Point" },
|
|
359
|
-
toLocator: { $ref: "#/$defs/Locator" },
|
|
360
|
-
intent: { $ref: "#/$defs/Intent" },
|
|
361
|
-
stroke: { type: "string" },
|
|
362
|
-
strokeWidth: { type: "number", minimum: 0 },
|
|
363
|
-
color: { type: "string" }
|
|
364
|
-
},
|
|
365
|
-
allOf: [
|
|
366
|
-
{ anyOf: [{ required: ["from"] }, { required: ["fromLocator"] }] },
|
|
367
|
-
{ anyOf: [{ required: ["to"] }, { required: ["toLocator"] }] }
|
|
368
|
-
]
|
|
369
|
-
}, Re = {
|
|
370
|
-
type: "object",
|
|
371
|
-
required: ["type", "content"],
|
|
372
|
-
additionalProperties: !1,
|
|
373
|
-
properties: {
|
|
374
|
-
type: { const: "text" },
|
|
375
|
-
at: { $ref: "#/$defs/Point" },
|
|
376
|
-
locator: { $ref: "#/$defs/Locator" },
|
|
377
|
-
content: { type: "string" },
|
|
378
|
-
fontSize: { type: "number", minimum: 0 },
|
|
379
|
-
anchor: { type: "string", enum: ["start", "middle", "end"] },
|
|
380
|
-
intent: { $ref: "#/$defs/Intent" },
|
|
381
|
-
color: { type: "string" }
|
|
382
|
-
},
|
|
383
|
-
anyOf: [{ required: ["at"] }, { required: ["locator"] }]
|
|
384
|
-
}, Ie = {
|
|
385
|
-
type: "object",
|
|
386
|
-
required: ["type", "content"],
|
|
387
|
-
additionalProperties: !1,
|
|
388
|
-
properties: {
|
|
389
|
-
type: { const: "callout" },
|
|
390
|
-
at: { $ref: "#/$defs/Point" },
|
|
391
|
-
atLocator: { $ref: "#/$defs/Locator" },
|
|
392
|
-
targetBbox: { $ref: "#/$defs/BBox" },
|
|
393
|
-
targetLocator: { $ref: "#/$defs/Locator" },
|
|
394
|
-
content: { type: "string" },
|
|
395
|
-
intent: { $ref: "#/$defs/Intent" },
|
|
396
|
-
stroke: { type: "string" },
|
|
397
|
-
color: { type: "string" }
|
|
398
|
-
},
|
|
399
|
-
allOf: [
|
|
400
|
-
{ anyOf: [{ required: ["at"] }, { required: ["atLocator"] }] },
|
|
401
|
-
{ anyOf: [{ required: ["targetBbox"] }, { required: ["targetLocator"] }] }
|
|
402
|
-
]
|
|
403
|
-
}, _e = {
|
|
404
|
-
type: "object",
|
|
405
|
-
required: ["type", "svgFragment"],
|
|
406
|
-
additionalProperties: !1,
|
|
407
|
-
properties: {
|
|
408
|
-
type: { const: "raw" },
|
|
409
|
-
svgFragment: { type: "string" }
|
|
410
|
-
}
|
|
411
|
-
}, Ce = {
|
|
412
|
-
oneOf: [Le, Ae, Te, Re, Ie, _e]
|
|
413
|
-
}, P = {
|
|
414
|
-
type: "object",
|
|
415
|
-
additionalProperties: !1,
|
|
416
|
-
properties: {
|
|
417
|
-
format: {
|
|
418
|
-
type: "string",
|
|
419
|
-
enum: ["smart", "png", "jpeg"],
|
|
420
|
-
description: "Encoding strategy. `smart` (default) picks PNG-8 for UI-heavy content and PNG-32 / JPEG for photo-heavy content per `smartFallback`. `png` forces PNG-32 (lossless). `jpeg` forces JPEG at `jpegPercent` quality."
|
|
421
|
-
},
|
|
422
|
-
saveSizePreset: {
|
|
423
|
-
type: "string",
|
|
424
|
-
enum: ["light", "standard", "highQuality", "original"],
|
|
425
|
-
description: "Max-width cap. `light` = 1280px, `standard` = 1920px (default), `highQuality` = 2560px, `original` = no resize. Aspect-preserving; never upscales."
|
|
426
|
-
},
|
|
427
|
-
smartFallback: {
|
|
428
|
-
type: "string",
|
|
429
|
-
enum: ["png", "jpeg"],
|
|
430
|
-
description: "Fallback format used by smart mode when the image is photo-heavy. Default `png`."
|
|
431
|
-
},
|
|
432
|
-
smartColorThreshold: {
|
|
433
|
-
type: "integer",
|
|
434
|
-
minimum: 1,
|
|
435
|
-
description: "Unique-colour count above which smart mode treats the image as photo-heavy. Default 15000."
|
|
436
|
-
},
|
|
437
|
-
jpegPercent: {
|
|
438
|
-
type: "integer",
|
|
439
|
-
minimum: 60,
|
|
440
|
-
maximum: 100,
|
|
441
|
-
description: "JPEG quality 60–100. Default 92."
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}, Ue = {
|
|
445
|
-
type: "object",
|
|
446
|
-
additionalProperties: !1,
|
|
447
|
-
properties: {
|
|
448
|
-
bbox: { $ref: "#/$defs/BBox" },
|
|
449
|
-
locator: { $ref: "#/$defs/Locator" },
|
|
450
|
-
style: { type: "string", enum: ["solid", "mosaic", "blur"] },
|
|
451
|
-
color: { type: "string" }
|
|
452
|
-
},
|
|
453
|
-
anyOf: [{ required: ["bbox"] }, { required: ["locator"] }]
|
|
454
|
-
}, j = [137, 80, 78, 71, 13, 10, 26, 10], L = 12, Fe = "IHDR", Ne = 16, Be = 20, G = 24;
|
|
455
|
-
class x extends Error {
|
|
456
|
-
constructor(t) {
|
|
457
|
-
super(t), this.name = "InvalidPngError";
|
|
458
|
-
}
|
|
515
|
+
async function Me(e) {
|
|
516
|
+
let t;
|
|
517
|
+
try {
|
|
518
|
+
let n = await te(e);
|
|
519
|
+
t = new Uint8Array(n.buffer, n.byteOffset, n.byteLength);
|
|
520
|
+
} catch (t) {
|
|
521
|
+
throw new j(`Failed to read image at "${e}": ${t instanceof Error ? t.message : String(t)}`);
|
|
522
|
+
}
|
|
523
|
+
let n = Ne(t), r = `data:image/png;base64,${Fe(t)}`;
|
|
524
|
+
return {
|
|
525
|
+
bytes: t,
|
|
526
|
+
dataUrl: r,
|
|
527
|
+
dimensions: n
|
|
528
|
+
};
|
|
459
529
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (e[c] !== j[c])
|
|
467
|
-
throw new x(
|
|
468
|
-
"Input does not start with the PNG signature (89 50 4E 47 0D 0A 1A 0A). Confirm the input is a PNG — JPEG / WebP / other formats are not supported in v1."
|
|
469
|
-
);
|
|
470
|
-
const t = new DataView(e.buffer, e.byteOffset, e.byteLength), o = String.fromCharCode(
|
|
471
|
-
e[L],
|
|
472
|
-
e[L + 1],
|
|
473
|
-
e[L + 2],
|
|
474
|
-
e[L + 3]
|
|
475
|
-
);
|
|
476
|
-
if (o !== Fe)
|
|
477
|
-
throw new x(
|
|
478
|
-
`Expected IHDR chunk after PNG signature, found "${o}". The file may be a corrupt or non-standard PNG.`
|
|
479
|
-
);
|
|
480
|
-
const n = t.getUint32(Ne, !1), s = t.getUint32(Be, !1);
|
|
481
|
-
if (n === 0 || s === 0)
|
|
482
|
-
throw new x(`PNG dimensions ${n}×${s} include a zero axis.`);
|
|
483
|
-
return { width: n, height: s };
|
|
530
|
+
function Ne(e) {
|
|
531
|
+
try {
|
|
532
|
+
return A(e);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
throw e instanceof k ? new j(e.message) : e;
|
|
535
|
+
}
|
|
484
536
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
537
|
+
function Pe(e) {
|
|
538
|
+
let t = Buffer.from(e, "base64");
|
|
539
|
+
return new Uint8Array(t.buffer, t.byteOffset, t.byteLength);
|
|
489
540
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (typeof e != "string" || e.length === 0)
|
|
493
|
-
throw new w(
|
|
494
|
-
"`image` must be a non-empty string (data URL or absolute filesystem path)."
|
|
495
|
-
);
|
|
496
|
-
if (e.startsWith(De))
|
|
497
|
-
return ke(e);
|
|
498
|
-
if (!b(e))
|
|
499
|
-
throw new w(
|
|
500
|
-
`Filesystem path "${e}" is not absolute. Pass an absolute path so resolution doesn't depend on the MCP server's working directory.`
|
|
501
|
-
);
|
|
502
|
-
return je(e);
|
|
541
|
+
function Fe(e) {
|
|
542
|
+
return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
|
|
503
543
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/redact/burn.ts
|
|
546
|
+
var Ie = "solid", Le = "#000000", Re = 16, ze = 12;
|
|
547
|
+
async function N(e, t) {
|
|
548
|
+
if (t.length === 0) return e;
|
|
549
|
+
let n = await f(Buffer.from(e)), r = d(n.width, n.height), i = r.getContext("2d");
|
|
550
|
+
i.drawImage(n, 0, 0);
|
|
551
|
+
for (let e of t) if (!(e.bbox.width <= 0 || e.bbox.height <= 0)) switch (e.style ?? Ie) {
|
|
552
|
+
case "solid":
|
|
553
|
+
Be(i, e.bbox, e.color ?? Le);
|
|
554
|
+
break;
|
|
555
|
+
case "mosaic":
|
|
556
|
+
Ve(i, n, e.bbox);
|
|
557
|
+
break;
|
|
558
|
+
case "blur":
|
|
559
|
+
He(i, n, e.bbox);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
let a = r.toBuffer("image/png");
|
|
563
|
+
return new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
511
564
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
try {
|
|
515
|
-
const s = await ce(e);
|
|
516
|
-
t = new Uint8Array(s.buffer, s.byteOffset, s.byteLength);
|
|
517
|
-
} catch (s) {
|
|
518
|
-
const c = s instanceof Error ? s.message : String(s);
|
|
519
|
-
throw new w(`Failed to read image at "${e}": ${c}`);
|
|
520
|
-
}
|
|
521
|
-
const o = z(t), n = `data:image/png;base64,${Me(t)}`;
|
|
522
|
-
return { bytes: t, dataUrl: n, dimensions: o };
|
|
565
|
+
function Be(e, t, n) {
|
|
566
|
+
e.save(), e.fillStyle = n, e.fillRect(t.x, t.y, t.width, t.height), e.restore();
|
|
523
567
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
} catch (t) {
|
|
528
|
-
throw t instanceof x ? new w(t.message) : t;
|
|
529
|
-
}
|
|
568
|
+
function Ve(e, t, n) {
|
|
569
|
+
let r = Math.max(1, Math.round(n.width / Re)), i = Math.max(1, Math.round(n.height / Re)), a = d(r, i), o = a.getContext("2d");
|
|
570
|
+
o.imageSmoothingEnabled = !1, o.drawImage(t, n.x, n.y, n.width, n.height, 0, 0, r, i), e.save(), e.imageSmoothingEnabled = !1, e.drawImage(a, 0, 0, r, i, n.x, n.y, n.width, n.height), e.restore();
|
|
530
571
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
return new Uint8Array(t.buffer, t.byteOffset, t.byteLength);
|
|
572
|
+
function He(e, t, n) {
|
|
573
|
+
e.save(), e.beginPath(), e.rect(n.x, n.y, n.width, n.height), e.clip(), e.filter = `blur(${ze}px)`, e.drawImage(t, 0, 0), e.restore();
|
|
534
574
|
}
|
|
535
|
-
|
|
536
|
-
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/io/encode-output.ts
|
|
577
|
+
async function P(e, t, r) {
|
|
578
|
+
if (!r || Object.keys(r).length === 0) return {
|
|
579
|
+
bytes: e,
|
|
580
|
+
mimeType: "image/png",
|
|
581
|
+
chosen: "png",
|
|
582
|
+
width: t.width,
|
|
583
|
+
height: t.height
|
|
584
|
+
};
|
|
585
|
+
let i = await c(e, {
|
|
586
|
+
...n,
|
|
587
|
+
...r
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
bytes: i.bytes,
|
|
591
|
+
mimeType: i.chosen === "jpeg" ? "image/jpeg" : "image/png",
|
|
592
|
+
chosen: i.chosen,
|
|
593
|
+
width: i.width,
|
|
594
|
+
height: i.height,
|
|
595
|
+
...i.reason ? { reason: i.reason } : {}
|
|
596
|
+
};
|
|
537
597
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/tools/annotate-screenshot.ts
|
|
600
|
+
var F = "annot_annotate_screenshot", I = {
|
|
601
|
+
name: F,
|
|
602
|
+
description: "Overlay annotations (rectangles, circles, arrows, callouts, text) on a PNG screenshot. Annotation positions are specified as bounding boxes or points; pair with `annot_annotate_url` if you want locator-driven positioning. Returns the annotated PNG inline unless `output` is specified, in which case the PNG is written to that absolute path.",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
required: ["image", "annotations"],
|
|
606
|
+
additionalProperties: !1,
|
|
607
|
+
properties: {
|
|
608
|
+
image: {
|
|
609
|
+
type: "string",
|
|
610
|
+
description: "Source image. Either a `data:image/png;base64,...` URL or an absolute filesystem path to a PNG file."
|
|
611
|
+
},
|
|
612
|
+
annotations: {
|
|
613
|
+
type: "array",
|
|
614
|
+
items: { $ref: "#/$defs/BboxAnnotation" },
|
|
615
|
+
description: "Ordered list of annotations to overlay. Each annotation declares its shape, position, and (optionally) an `intent` shorthand for the colour theme (`info` | `warning` | `error` | `success` | `neutral`)."
|
|
616
|
+
},
|
|
617
|
+
output: {
|
|
618
|
+
type: "string",
|
|
619
|
+
description: "Optional absolute filesystem path. When set, the annotated PNG is written here and the tool returns a text confirmation; otherwise the PNG bytes are returned inline."
|
|
620
|
+
},
|
|
621
|
+
encode: {
|
|
622
|
+
$ref: "#/$defs/EncodeOptions",
|
|
623
|
+
description: "Optional encoder settings. When set, the output PNG-32 is decoded and re-encoded per `format` / `saveSizePreset` for size savings — `format: \"smart\"` + `saveSizePreset: \"standard\"` gives ~50–80% byte reduction typical for UI screenshots."
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
$defs: {
|
|
627
|
+
...T,
|
|
628
|
+
BboxAnnotation: e,
|
|
629
|
+
EncodeOptions: D
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
async function L(e, t) {
|
|
634
|
+
try {
|
|
635
|
+
let n = Ue(e), r = await M(n.image), i = o(n.annotations), a = await P(t.annotator.toPng({
|
|
636
|
+
originalDataUrl: r.dataUrl,
|
|
637
|
+
annotationsSvg: i,
|
|
638
|
+
width: r.dimensions.width,
|
|
639
|
+
height: r.dimensions.height
|
|
640
|
+
}), r.dimensions, n.encode);
|
|
641
|
+
if (n.output) {
|
|
642
|
+
await p(n.output, a.bytes);
|
|
643
|
+
let e = a.reason ? `, reason: ${a.reason}` : "";
|
|
644
|
+
return { content: [{
|
|
645
|
+
type: "text",
|
|
646
|
+
text: `Wrote ${a.bytes.byteLength}-byte ${a.chosen.toUpperCase()} to ${n.output} (${a.width}×${a.height}, ${n.annotations.length} annotation${n.annotations.length === 1 ? "" : "s"}${e}).`
|
|
647
|
+
}] };
|
|
648
|
+
}
|
|
649
|
+
return { content: [{
|
|
650
|
+
type: "image",
|
|
651
|
+
data: Ge(a.bytes),
|
|
652
|
+
mimeType: a.mimeType
|
|
653
|
+
}] };
|
|
654
|
+
} catch (e) {
|
|
655
|
+
return We(e);
|
|
656
|
+
}
|
|
563
657
|
}
|
|
564
|
-
function
|
|
565
|
-
|
|
566
|
-
|
|
658
|
+
function Ue(e) {
|
|
659
|
+
if (typeof e.image != "string") throw new j("`image` is required and must be a string.");
|
|
660
|
+
if (!Array.isArray(e.annotations)) throw new R("`annotations` is required and must be an array.");
|
|
661
|
+
let t = e.annotations, n;
|
|
662
|
+
if (e.output !== void 0) {
|
|
663
|
+
if (typeof e.output != "string") throw new R("`output` must be a string when provided.");
|
|
664
|
+
if (!m(e.output)) throw new R(`\`output\` path "${e.output}" must be absolute so the file lands at a predictable location.`);
|
|
665
|
+
n = e.output;
|
|
666
|
+
}
|
|
667
|
+
let r;
|
|
668
|
+
if (e.encode !== void 0) {
|
|
669
|
+
if (typeof e.encode != "object" || e.encode === null) throw new R("`encode` must be an object when provided.");
|
|
670
|
+
r = e.encode;
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
image: e.image,
|
|
674
|
+
annotations: t,
|
|
675
|
+
output: n,
|
|
676
|
+
encode: r
|
|
677
|
+
};
|
|
567
678
|
}
|
|
568
|
-
|
|
569
|
-
|
|
679
|
+
var R = class extends Error {
|
|
680
|
+
constructor(e) {
|
|
681
|
+
super(e), this.name = "InvalidToolInputError";
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
function We(e) {
|
|
685
|
+
return {
|
|
686
|
+
content: [{
|
|
687
|
+
type: "text",
|
|
688
|
+
text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}`
|
|
689
|
+
}],
|
|
690
|
+
isError: !0
|
|
691
|
+
};
|
|
570
692
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return {
|
|
574
|
-
bytes: e,
|
|
575
|
-
mimeType: "image/png",
|
|
576
|
-
chosen: "png",
|
|
577
|
-
width: t.width,
|
|
578
|
-
height: t.height
|
|
579
|
-
};
|
|
580
|
-
const n = { ...oe, ...o }, s = await re(e, n);
|
|
581
|
-
return {
|
|
582
|
-
bytes: s.bytes,
|
|
583
|
-
mimeType: s.chosen === "jpeg" ? "image/jpeg" : "image/png",
|
|
584
|
-
chosen: s.chosen,
|
|
585
|
-
width: s.width,
|
|
586
|
-
height: s.height,
|
|
587
|
-
...s.reason ? { reason: s.reason } : {}
|
|
588
|
-
};
|
|
693
|
+
function Ge(e) {
|
|
694
|
+
return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
|
|
589
695
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/tools/annotate-url.ts
|
|
698
|
+
var z = "annot_annotate_url", B = {
|
|
699
|
+
name: z,
|
|
700
|
+
description: "Open a URL in a headless browser, capture a screenshot, and overlay annotations whose positions are specified as Playwright locator strings (`button:has-text(\"Submit\")`, `[data-testid=\"email\"]`, `role=button[name=\"Sign in\"]`) or bounding boxes. Returns the annotated PNG inline unless `output` is specified.",
|
|
701
|
+
inputSchema: {
|
|
702
|
+
type: "object",
|
|
703
|
+
required: ["url", "annotations"],
|
|
704
|
+
additionalProperties: !1,
|
|
705
|
+
properties: {
|
|
706
|
+
url: {
|
|
707
|
+
type: "string",
|
|
708
|
+
format: "uri",
|
|
709
|
+
description: "Page URL to capture."
|
|
710
|
+
},
|
|
711
|
+
annotations: {
|
|
712
|
+
type: "array",
|
|
713
|
+
items: { $ref: "#/$defs/LocatorAnnotation" },
|
|
714
|
+
description: "Ordered list of annotations to overlay. Each annotation declares its shape and position via `bbox` / `from` / `to` / `at` (coordinate path) or `locator` / `fromLocator` / `toLocator` / `atLocator` (Playwright locator string)."
|
|
715
|
+
},
|
|
716
|
+
viewport: {
|
|
717
|
+
type: "object",
|
|
718
|
+
additionalProperties: !1,
|
|
719
|
+
properties: {
|
|
720
|
+
width: {
|
|
721
|
+
type: "integer",
|
|
722
|
+
minimum: 1,
|
|
723
|
+
default: 1280
|
|
724
|
+
},
|
|
725
|
+
height: {
|
|
726
|
+
type: "integer",
|
|
727
|
+
minimum: 1,
|
|
728
|
+
default: 800
|
|
729
|
+
},
|
|
730
|
+
deviceScaleFactor: {
|
|
731
|
+
type: "number",
|
|
732
|
+
minimum: .1,
|
|
733
|
+
default: 1
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
description: "Viewport size. Default 1280×800 at 1× device pixel ratio."
|
|
737
|
+
},
|
|
738
|
+
fullPage: {
|
|
739
|
+
type: "boolean",
|
|
740
|
+
default: !1,
|
|
741
|
+
description: "Capture the full scrollable page rather than just the visible viewport. Locator resolution still depends on the actual DOM layout — off-viewport locators only resolve when `fullPage` is true."
|
|
742
|
+
},
|
|
743
|
+
waitFor: {
|
|
744
|
+
type: "string",
|
|
745
|
+
enum: [
|
|
746
|
+
"load",
|
|
747
|
+
"domcontentloaded",
|
|
748
|
+
"networkidle"
|
|
749
|
+
],
|
|
750
|
+
default: "load"
|
|
751
|
+
},
|
|
752
|
+
output: {
|
|
753
|
+
type: "string",
|
|
754
|
+
description: "Optional absolute filesystem path. When set, the annotated PNG is written here and the tool returns a text confirmation."
|
|
755
|
+
},
|
|
756
|
+
encode: {
|
|
757
|
+
$ref: "#/$defs/EncodeOptions",
|
|
758
|
+
description: "Optional encoder settings (`format` / `saveSizePreset` / `jpegPercent`). Set to shrink the output for ingestion into issue trackers / docs."
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
$defs: {
|
|
762
|
+
...T,
|
|
763
|
+
LocatorAnnotation: E,
|
|
764
|
+
EncodeOptions: D
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}, V = class extends Error {
|
|
768
|
+
constructor(e) {
|
|
769
|
+
super(e), this.name = "InvalidAnnotateUrlInputError";
|
|
770
|
+
}
|
|
622
771
|
};
|
|
623
|
-
async function
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
772
|
+
async function Ke(e, t) {
|
|
773
|
+
try {
|
|
774
|
+
let n = qe(e), r = await h(t.pool, {
|
|
775
|
+
url: n.url,
|
|
776
|
+
viewport: n.viewport,
|
|
777
|
+
fullPage: n.fullPage,
|
|
778
|
+
waitFor: n.waitFor
|
|
779
|
+
}), i;
|
|
780
|
+
try {
|
|
781
|
+
i = await he(r.handle.page, n.annotations);
|
|
782
|
+
} finally {
|
|
783
|
+
await r.handle.close();
|
|
784
|
+
}
|
|
785
|
+
let a = A(r.pngBytes), s = o(i), c = await P(t.annotator.toPng({
|
|
786
|
+
originalDataUrl: `data:image/png;base64,${Ye(r.pngBytes)}`,
|
|
787
|
+
annotationsSvg: s,
|
|
788
|
+
width: a.width,
|
|
789
|
+
height: a.height
|
|
790
|
+
}), a, n.encode);
|
|
791
|
+
if (n.output) {
|
|
792
|
+
await p(n.output, c.bytes);
|
|
793
|
+
let e = c.reason ? `, reason: ${c.reason}` : "";
|
|
794
|
+
return { content: [{
|
|
795
|
+
type: "text",
|
|
796
|
+
text: `Wrote ${c.bytes.byteLength}-byte ${c.chosen.toUpperCase()} to ${n.output} (${c.width}×${c.height}, ${n.annotations.length} annotation${n.annotations.length === 1 ? "" : "s"}, captured from ${n.url}${e}).`
|
|
797
|
+
}] };
|
|
798
|
+
}
|
|
799
|
+
return { content: [{
|
|
800
|
+
type: "image",
|
|
801
|
+
data: Ye(c.bytes),
|
|
802
|
+
mimeType: c.mimeType
|
|
803
|
+
}] };
|
|
804
|
+
} catch (e) {
|
|
805
|
+
return Je(e);
|
|
806
|
+
}
|
|
655
807
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
808
|
+
function qe(e) {
|
|
809
|
+
if (typeof e.url != "string" || e.url.length === 0) throw new V("`url` is required and must be a non-empty string.");
|
|
810
|
+
if (!Array.isArray(e.annotations)) throw new V("`annotations` is required and must be an array.");
|
|
811
|
+
let t = e.annotations, n;
|
|
812
|
+
if (e.viewport !== void 0) {
|
|
813
|
+
if (typeof e.viewport != "object" || e.viewport === null) throw new V("`viewport` must be an object when provided.");
|
|
814
|
+
let t = e.viewport;
|
|
815
|
+
n = {
|
|
816
|
+
width: typeof t.width == "number" ? t.width : 1280,
|
|
817
|
+
height: typeof t.height == "number" ? t.height : 800,
|
|
818
|
+
deviceScaleFactor: typeof t.deviceScaleFactor == "number" ? t.deviceScaleFactor : 1
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
let r = e.fullPage === !0, i = e.waitFor === "domcontentloaded" || e.waitFor === "networkidle" ? e.waitFor : "load", a;
|
|
822
|
+
if (e.output !== void 0) {
|
|
823
|
+
if (typeof e.output != "string") throw new V("`output` must be a string when provided.");
|
|
824
|
+
if (!m(e.output)) throw new V(`\`output\` path "${e.output}" must be absolute.`);
|
|
825
|
+
a = e.output;
|
|
826
|
+
}
|
|
827
|
+
let o;
|
|
828
|
+
if (e.encode !== void 0) {
|
|
829
|
+
if (typeof e.encode != "object" || e.encode === null) throw new V("`encode` must be an object when provided.");
|
|
830
|
+
o = e.encode;
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
url: e.url,
|
|
834
|
+
annotations: t,
|
|
835
|
+
viewport: n,
|
|
836
|
+
fullPage: r,
|
|
837
|
+
waitFor: i,
|
|
838
|
+
output: a,
|
|
839
|
+
encode: o
|
|
840
|
+
};
|
|
679
841
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
842
|
+
function Je(e) {
|
|
843
|
+
return {
|
|
844
|
+
content: [{
|
|
845
|
+
type: "text",
|
|
846
|
+
text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}`
|
|
847
|
+
}],
|
|
848
|
+
isError: !0
|
|
849
|
+
};
|
|
684
850
|
}
|
|
685
|
-
function
|
|
686
|
-
|
|
687
|
-
content: [{ type: "text", text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}` }],
|
|
688
|
-
isError: !0
|
|
689
|
-
};
|
|
851
|
+
function Ye(e) {
|
|
852
|
+
return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
|
|
690
853
|
}
|
|
691
|
-
|
|
692
|
-
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region src/browser/aria-snapshot.ts
|
|
856
|
+
var Xe = {
|
|
857
|
+
width: 1280,
|
|
858
|
+
height: 800,
|
|
859
|
+
deviceScaleFactor: 1
|
|
860
|
+
}, Ze = "load", Qe = "body";
|
|
861
|
+
async function H(e, t) {
|
|
862
|
+
let n = {
|
|
863
|
+
...Xe,
|
|
864
|
+
...t.viewport
|
|
865
|
+
}, r = t.waitFor ?? Ze, i = t.rootSelector ?? Qe, a = await e.acquire(), o, s = !1, c = () => {
|
|
866
|
+
s || (s = !0, e.release());
|
|
867
|
+
};
|
|
868
|
+
try {
|
|
869
|
+
o = await a.newContext({
|
|
870
|
+
viewport: {
|
|
871
|
+
width: n.width,
|
|
872
|
+
height: n.height
|
|
873
|
+
},
|
|
874
|
+
deviceScaleFactor: n.deviceScaleFactor
|
|
875
|
+
});
|
|
876
|
+
let e = await o.newPage();
|
|
877
|
+
await e.goto(t.url, { waitUntil: r });
|
|
878
|
+
let s = await e.locator(i).ariaSnapshot({
|
|
879
|
+
mode: "ai",
|
|
880
|
+
...t.timeout === void 0 ? {} : { timeout: t.timeout }
|
|
881
|
+
});
|
|
882
|
+
return await o.close(), c(), { yaml: s };
|
|
883
|
+
} catch (e) {
|
|
884
|
+
if (o) try {
|
|
885
|
+
await o.close();
|
|
886
|
+
} catch {}
|
|
887
|
+
throw c(), e;
|
|
888
|
+
}
|
|
693
889
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/tools/aria-snapshot.ts
|
|
892
|
+
var $e = "annot_aria_snapshot", et = {
|
|
893
|
+
name: $e,
|
|
894
|
+
description: "Open a URL in a headless browser and return Playwright's AI-mode aria-snapshot (YAML format with `[ref=eN]` markers). The same primitive `playwright-mcp` and `playwright-cli` use. Refs are session-local — store `role + name` (not refs) as persistent identifiers across snapshots.",
|
|
895
|
+
inputSchema: {
|
|
896
|
+
type: "object",
|
|
897
|
+
required: ["url"],
|
|
898
|
+
additionalProperties: !1,
|
|
899
|
+
properties: {
|
|
900
|
+
url: {
|
|
901
|
+
type: "string",
|
|
902
|
+
format: "uri",
|
|
903
|
+
description: "Page URL to snapshot."
|
|
904
|
+
},
|
|
905
|
+
viewport: {
|
|
906
|
+
type: "object",
|
|
907
|
+
additionalProperties: !1,
|
|
908
|
+
properties: {
|
|
909
|
+
width: {
|
|
910
|
+
type: "integer",
|
|
911
|
+
minimum: 1,
|
|
912
|
+
default: 1280
|
|
913
|
+
},
|
|
914
|
+
height: {
|
|
915
|
+
type: "integer",
|
|
916
|
+
minimum: 1,
|
|
917
|
+
default: 800
|
|
918
|
+
},
|
|
919
|
+
deviceScaleFactor: {
|
|
920
|
+
type: "number",
|
|
921
|
+
minimum: .1,
|
|
922
|
+
default: 1
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
description: "Viewport size. Default 1280×800 at 1× device pixel ratio."
|
|
926
|
+
},
|
|
927
|
+
waitFor: {
|
|
928
|
+
type: "string",
|
|
929
|
+
enum: [
|
|
930
|
+
"load",
|
|
931
|
+
"domcontentloaded",
|
|
932
|
+
"networkidle"
|
|
933
|
+
],
|
|
934
|
+
default: "load",
|
|
935
|
+
description: "Page event to await before snapshotting."
|
|
936
|
+
},
|
|
937
|
+
rootSelector: {
|
|
938
|
+
type: "string",
|
|
939
|
+
default: "body",
|
|
940
|
+
description: "Selector for the locator whose subtree is snapshotted. Defaults to `\"body\"` (whole page). Narrow to a specific region (e.g. `\"main\"` or `'[data-testid=\"login-form\"]'`) when only part of the page is relevant."
|
|
941
|
+
},
|
|
942
|
+
timeout: {
|
|
943
|
+
type: "integer",
|
|
944
|
+
minimum: 0,
|
|
945
|
+
description: "Timeout in milliseconds for the underlying `locator.ariaSnapshot()` call. Defaults to Playwright's own default (30 s)."
|
|
946
|
+
},
|
|
947
|
+
output: {
|
|
948
|
+
type: "string",
|
|
949
|
+
description: "Optional absolute filesystem path. When set, the YAML is written here and the tool returns a text confirmation; otherwise the YAML is returned inline."
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
747
953
|
};
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
954
|
+
async function tt(e, t) {
|
|
955
|
+
if (typeof e.url != "string" || e.url.length === 0) return U("`url` is required (string).");
|
|
956
|
+
let n = nt(e.viewport), r = rt(e.waitFor), i = typeof e.rootSelector == "string" ? e.rootSelector : void 0, a = typeof e.timeout == "number" ? e.timeout : void 0, o = it(e.output);
|
|
957
|
+
if (o && !o.ok) return U(o.error);
|
|
958
|
+
let s;
|
|
959
|
+
try {
|
|
960
|
+
s = await H(t.pool, {
|
|
961
|
+
url: e.url,
|
|
962
|
+
viewport: n,
|
|
963
|
+
waitFor: r,
|
|
964
|
+
rootSelector: i,
|
|
965
|
+
timeout: a
|
|
966
|
+
});
|
|
967
|
+
} catch (e) {
|
|
968
|
+
return U(`Failed to capture aria-snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
969
|
+
}
|
|
970
|
+
if (o) {
|
|
971
|
+
try {
|
|
972
|
+
await p(o.path, s.yaml, "utf8");
|
|
973
|
+
} catch (e) {
|
|
974
|
+
return U(`Failed to write snapshot to ${o.path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
975
|
+
}
|
|
976
|
+
return { content: [{
|
|
977
|
+
type: "text",
|
|
978
|
+
text: `Wrote aria-snapshot (${s.yaml.length} bytes) to ${o.path}.`
|
|
979
|
+
}] };
|
|
980
|
+
}
|
|
981
|
+
return { content: [{
|
|
982
|
+
type: "text",
|
|
983
|
+
text: s.yaml
|
|
984
|
+
}] };
|
|
752
985
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
let s;
|
|
762
|
-
try {
|
|
763
|
-
s = await be(
|
|
764
|
-
n.handle.page,
|
|
765
|
-
o.annotations
|
|
766
|
-
);
|
|
767
|
-
} finally {
|
|
768
|
-
await n.handle.close();
|
|
769
|
-
}
|
|
770
|
-
const c = B(n.pngBytes), r = U(s), a = t.annotator.toPng({
|
|
771
|
-
originalDataUrl: `data:image/png;base64,${H(n.pngBytes)}`,
|
|
772
|
-
annotationsSvg: r,
|
|
773
|
-
width: c.width,
|
|
774
|
-
height: c.height
|
|
775
|
-
}), i = await S(a, c, o.encode);
|
|
776
|
-
if (o.output) {
|
|
777
|
-
await O(o.output, i.bytes);
|
|
778
|
-
const d = i.reason ? `, reason: ${i.reason}` : "";
|
|
779
|
-
return {
|
|
780
|
-
content: [
|
|
781
|
-
{
|
|
782
|
-
type: "text",
|
|
783
|
-
text: `Wrote ${i.bytes.byteLength}-byte ${i.chosen.toUpperCase()} to ${o.output} (${i.width}×${i.height}, ${o.annotations.length} annotation${o.annotations.length === 1 ? "" : "s"}, captured from ${o.url}${d}).`
|
|
784
|
-
}
|
|
785
|
-
]
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
return {
|
|
789
|
-
content: [
|
|
790
|
-
{
|
|
791
|
-
type: "image",
|
|
792
|
-
data: H(i.bytes),
|
|
793
|
-
mimeType: i.mimeType
|
|
794
|
-
}
|
|
795
|
-
]
|
|
796
|
-
};
|
|
797
|
-
} catch (o) {
|
|
798
|
-
return nt(o);
|
|
799
|
-
}
|
|
986
|
+
function nt(e) {
|
|
987
|
+
if (typeof e != "object" || !e) return;
|
|
988
|
+
let t = e, n = typeof t.width == "number" ? t.width : 1280, r = typeof t.height == "number" ? t.height : 800, i = typeof t.deviceScaleFactor == "number" ? t.deviceScaleFactor : void 0;
|
|
989
|
+
return {
|
|
990
|
+
width: n,
|
|
991
|
+
height: r,
|
|
992
|
+
...i === void 0 ? {} : { deviceScaleFactor: i }
|
|
993
|
+
};
|
|
800
994
|
}
|
|
801
995
|
function rt(e) {
|
|
802
|
-
|
|
803
|
-
throw new y("`url` is required and must be a non-empty string.");
|
|
804
|
-
if (!Array.isArray(e.annotations))
|
|
805
|
-
throw new y("`annotations` is required and must be an array.");
|
|
806
|
-
const t = e.annotations;
|
|
807
|
-
let o;
|
|
808
|
-
if (e.viewport !== void 0) {
|
|
809
|
-
if (typeof e.viewport != "object" || e.viewport === null)
|
|
810
|
-
throw new y("`viewport` must be an object when provided.");
|
|
811
|
-
const a = e.viewport, i = typeof a.width == "number" ? a.width : 1280, d = typeof a.height == "number" ? a.height : 800, l = typeof a.deviceScaleFactor == "number" ? a.deviceScaleFactor : 1;
|
|
812
|
-
o = { width: i, height: d, deviceScaleFactor: l };
|
|
813
|
-
}
|
|
814
|
-
const n = e.fullPage === !0, s = e.waitFor === "domcontentloaded" || e.waitFor === "networkidle" ? e.waitFor : "load";
|
|
815
|
-
let c;
|
|
816
|
-
if (e.output !== void 0) {
|
|
817
|
-
if (typeof e.output != "string")
|
|
818
|
-
throw new y("`output` must be a string when provided.");
|
|
819
|
-
if (!b(e.output))
|
|
820
|
-
throw new y(`\`output\` path "${e.output}" must be absolute.`);
|
|
821
|
-
c = e.output;
|
|
822
|
-
}
|
|
823
|
-
let r;
|
|
824
|
-
if (e.encode !== void 0) {
|
|
825
|
-
if (typeof e.encode != "object" || e.encode === null)
|
|
826
|
-
throw new y("`encode` must be an object when provided.");
|
|
827
|
-
r = e.encode;
|
|
828
|
-
}
|
|
829
|
-
return {
|
|
830
|
-
url: e.url,
|
|
831
|
-
annotations: t,
|
|
832
|
-
viewport: o,
|
|
833
|
-
fullPage: n,
|
|
834
|
-
waitFor: s,
|
|
835
|
-
output: c,
|
|
836
|
-
encode: r
|
|
837
|
-
};
|
|
996
|
+
if (e === "load" || e === "domcontentloaded" || e === "networkidle") return e;
|
|
838
997
|
}
|
|
839
|
-
function
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
998
|
+
function it(e) {
|
|
999
|
+
if (e != null) return typeof e != "string" || e.length === 0 ? {
|
|
1000
|
+
ok: !1,
|
|
1001
|
+
error: "`output` must be a non-empty string when set."
|
|
1002
|
+
} : m(e) ? {
|
|
1003
|
+
ok: !0,
|
|
1004
|
+
path: e
|
|
1005
|
+
} : {
|
|
1006
|
+
ok: !1,
|
|
1007
|
+
error: `\`output\` must be an absolute path; got ${JSON.stringify(e)}.`
|
|
1008
|
+
};
|
|
844
1009
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
1010
|
+
function U(e) {
|
|
1011
|
+
return {
|
|
1012
|
+
content: [{
|
|
1013
|
+
type: "text",
|
|
1014
|
+
text: e
|
|
1015
|
+
}],
|
|
1016
|
+
isError: !0
|
|
1017
|
+
};
|
|
847
1018
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region src/tools/compare-screenshots.ts
|
|
1021
|
+
var W = "annot_compare_screenshots", at = {
|
|
1022
|
+
name: W,
|
|
1023
|
+
description: "Compare two screenshots (must have identical dimensions) and return a PNG of the `after` image with changed regions highlighted as red rectangles. Useful for visual-diff PR review.",
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
type: "object",
|
|
1026
|
+
required: ["before", "after"],
|
|
1027
|
+
additionalProperties: !1,
|
|
1028
|
+
properties: {
|
|
1029
|
+
before: {
|
|
1030
|
+
type: "string",
|
|
1031
|
+
description: "`data:image/png;base64,...` URL or absolute filesystem path."
|
|
1032
|
+
},
|
|
1033
|
+
after: {
|
|
1034
|
+
type: "string",
|
|
1035
|
+
description: "`data:image/png;base64,...` URL or absolute filesystem path."
|
|
1036
|
+
},
|
|
1037
|
+
threshold: {
|
|
1038
|
+
type: "number",
|
|
1039
|
+
minimum: 0,
|
|
1040
|
+
maximum: 1,
|
|
1041
|
+
default: .1,
|
|
1042
|
+
description: "Pixelmatch sensitivity (0 = strict, 1 = permissive)."
|
|
1043
|
+
},
|
|
1044
|
+
includeChangeList: {
|
|
1045
|
+
type: "boolean",
|
|
1046
|
+
default: !1,
|
|
1047
|
+
description: "When true, append a text content block listing the changed-region bboxes alongside the annotated PNG."
|
|
1048
|
+
},
|
|
1049
|
+
output: { type: "string" },
|
|
1050
|
+
encode: { $ref: "#/$defs/EncodeOptions" }
|
|
1051
|
+
},
|
|
1052
|
+
$defs: { EncodeOptions: D }
|
|
1053
|
+
}
|
|
1054
|
+
}, G = class extends Error {
|
|
1055
|
+
constructor(e) {
|
|
1056
|
+
super(e), this.name = "InvalidCompareInputError";
|
|
1057
|
+
}
|
|
883
1058
|
};
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1059
|
+
async function ot(e, t) {
|
|
1060
|
+
try {
|
|
1061
|
+
if (typeof e.before != "string") throw new G("`before` is required and must be a string.");
|
|
1062
|
+
if (typeof e.after != "string") throw new G("`after` is required and must be a string.");
|
|
1063
|
+
let n = typeof e.threshold == "number" ? e.threshold : .1, r = e.includeChangeList === !0, i;
|
|
1064
|
+
if (e.output !== void 0) {
|
|
1065
|
+
if (typeof e.output != "string") throw new G("`output` must be a string when provided.");
|
|
1066
|
+
if (!m(e.output)) throw new G(`\`output\` path "${e.output}" must be absolute.`);
|
|
1067
|
+
i = e.output;
|
|
1068
|
+
}
|
|
1069
|
+
let a;
|
|
1070
|
+
if (e.encode !== void 0) {
|
|
1071
|
+
if (typeof e.encode != "object" || e.encode === null) throw new G("`encode` must be an object when provided.");
|
|
1072
|
+
a = e.encode;
|
|
1073
|
+
}
|
|
1074
|
+
let [s, c] = await Promise.all([M(e.before), M(e.after)]), l = await C(s.bytes, c.bytes, { threshold: n }), u = o(l.regions.map((e) => ({
|
|
1075
|
+
type: "rect",
|
|
1076
|
+
bbox: e,
|
|
1077
|
+
intent: "warning"
|
|
1078
|
+
}))), d = await P(t.annotator.toPng({
|
|
1079
|
+
originalDataUrl: c.dataUrl,
|
|
1080
|
+
annotationsSvg: u,
|
|
1081
|
+
width: l.width,
|
|
1082
|
+
height: l.height
|
|
1083
|
+
}), {
|
|
1084
|
+
width: l.width,
|
|
1085
|
+
height: l.height
|
|
1086
|
+
}, a);
|
|
1087
|
+
if (i) {
|
|
1088
|
+
await p(i, d.bytes);
|
|
1089
|
+
let e = d.reason ? `, reason: ${d.reason}` : "";
|
|
1090
|
+
return { content: [{
|
|
1091
|
+
type: "text",
|
|
1092
|
+
text: `Wrote ${d.bytes.byteLength}-byte diff-annotated ${d.chosen.toUpperCase()} to ${i} (${d.width}×${d.height}, ${l.regions.length} changed region${l.regions.length === 1 ? "" : "s"}, ${l.mismatchedPixels} mismatched pixel${l.mismatchedPixels === 1 ? "" : "s"}${e}).`
|
|
1093
|
+
}] };
|
|
1094
|
+
}
|
|
1095
|
+
let f = {
|
|
1096
|
+
type: "image",
|
|
1097
|
+
data: Buffer.from(d.bytes.buffer, d.bytes.byteOffset, d.bytes.byteLength).toString("base64"),
|
|
1098
|
+
mimeType: d.mimeType
|
|
1099
|
+
};
|
|
1100
|
+
return r ? { content: [f, {
|
|
1101
|
+
type: "text",
|
|
1102
|
+
text: `${l.regions.length} changed region${l.regions.length === 1 ? "" : "s"}, ${l.mismatchedPixels} mismatched pixel${l.mismatchedPixels === 1 ? "" : "s"}:\n` + l.regions.map((e, t) => ` [${t + 1}] x=${e.x} y=${e.y} w=${e.width} h=${e.height}`).join("\n")
|
|
1103
|
+
}] } : { content: [f] };
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
return {
|
|
1106
|
+
content: [{
|
|
1107
|
+
type: "text",
|
|
1108
|
+
text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}`
|
|
1109
|
+
}],
|
|
1110
|
+
isError: !0
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
//#endregion
|
|
1115
|
+
//#region src/tools/draft-screen-spec.ts
|
|
1116
|
+
var st = "annot_draft_screen_spec", ct = {
|
|
1117
|
+
name: st,
|
|
1118
|
+
description: "Given a URL, propose an `.mdx` skeleton for an annot product-docs screen spec. The tool opens the page in a headless browser, takes a Playwright `aria-snapshot`, and emits an MDX with frontmatter + a `<Screen>` block containing one `<Overlay match={{ role, name }}>` per interactive element. The agent fills in the prose body of each overlay; the structural skeleton is mechanical.",
|
|
1119
|
+
inputSchema: {
|
|
1120
|
+
type: "object",
|
|
1121
|
+
required: ["url", "id"],
|
|
1122
|
+
additionalProperties: !1,
|
|
1123
|
+
properties: {
|
|
1124
|
+
url: {
|
|
1125
|
+
type: "string",
|
|
1126
|
+
format: "uri",
|
|
1127
|
+
description: "Page URL to draft from."
|
|
1128
|
+
},
|
|
1129
|
+
id: {
|
|
1130
|
+
type: "string",
|
|
1131
|
+
description: "Screen ID — used as the MDX frontmatter `annot.id` and as the `<Screen id>` prop."
|
|
1132
|
+
},
|
|
1133
|
+
title: {
|
|
1134
|
+
type: "string",
|
|
1135
|
+
description: "Optional screen title (frontmatter `annot.title`)."
|
|
1136
|
+
},
|
|
1137
|
+
book: {
|
|
1138
|
+
type: "string",
|
|
1139
|
+
description: "Optional book name (frontmatter `xlsx.book`)."
|
|
1140
|
+
},
|
|
1141
|
+
screenImageSrc: {
|
|
1142
|
+
type: "string",
|
|
1143
|
+
default: "./shots/{id}.png",
|
|
1144
|
+
description: "Optional `<Screen src>` value. `{id}` is replaced with the screen id. Defaults to `./shots/<id>.png`."
|
|
1145
|
+
},
|
|
1146
|
+
viewport: {
|
|
1147
|
+
type: "object",
|
|
1148
|
+
additionalProperties: !1,
|
|
1149
|
+
properties: {
|
|
1150
|
+
width: {
|
|
1151
|
+
type: "integer",
|
|
1152
|
+
minimum: 1,
|
|
1153
|
+
default: 1280
|
|
1154
|
+
},
|
|
1155
|
+
height: {
|
|
1156
|
+
type: "integer",
|
|
1157
|
+
minimum: 1,
|
|
1158
|
+
default: 800
|
|
1159
|
+
},
|
|
1160
|
+
deviceScaleFactor: {
|
|
1161
|
+
type: "number",
|
|
1162
|
+
minimum: .1,
|
|
1163
|
+
default: 1
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
waitFor: {
|
|
1168
|
+
type: "string",
|
|
1169
|
+
enum: [
|
|
1170
|
+
"load",
|
|
1171
|
+
"domcontentloaded",
|
|
1172
|
+
"networkidle"
|
|
1173
|
+
],
|
|
1174
|
+
default: "load"
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}, lt = new Set([
|
|
1179
|
+
"button",
|
|
1180
|
+
"checkbox",
|
|
1181
|
+
"combobox",
|
|
1182
|
+
"link",
|
|
1183
|
+
"menuitem",
|
|
1184
|
+
"menuitemcheckbox",
|
|
1185
|
+
"menuitemradio",
|
|
1186
|
+
"option",
|
|
1187
|
+
"radio",
|
|
1188
|
+
"searchbox",
|
|
1189
|
+
"slider",
|
|
1190
|
+
"spinbutton",
|
|
1191
|
+
"switch",
|
|
1192
|
+
"tab",
|
|
1193
|
+
"textbox",
|
|
1194
|
+
"treeitem"
|
|
1195
|
+
]);
|
|
1196
|
+
async function ut(e, t) {
|
|
1197
|
+
if (typeof e.url != "string" || e.url.length === 0) return K("`url` is required (string).");
|
|
1198
|
+
if (typeof e.id != "string" || e.id.length === 0) return K("`id` is required (string).");
|
|
1199
|
+
let n = e.id, r = typeof e.title == "string" ? e.title : void 0, i = typeof e.book == "string" ? e.book : void 0, a = (typeof e.screenImageSrc == "string" ? e.screenImageSrc : "./shots/{id}.png").replace(/\{id\}/g, n), o;
|
|
1200
|
+
try {
|
|
1201
|
+
o = await H(t.pool, {
|
|
1202
|
+
url: e.url,
|
|
1203
|
+
viewport: dt(e.viewport),
|
|
1204
|
+
waitFor: ft(e.waitFor)
|
|
1205
|
+
});
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
return K(`Failed to capture aria-snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
1208
|
+
}
|
|
1209
|
+
let s = ce(o.yaml).filter((e) => lt.has(e.role)).map((e, t) => `<Overlay match={{ role: ${JSON.stringify(e.role)}, name: ${JSON.stringify(e.name)} }} number={${t + 1}}>
|
|
1210
|
+
TODO: describe ${e.role} "${e.name}".
|
|
1211
|
+
</Overlay>`);
|
|
1212
|
+
return { content: [{
|
|
1213
|
+
type: "text",
|
|
1214
|
+
text: `${[
|
|
1215
|
+
"---",
|
|
1216
|
+
"annot:",
|
|
1217
|
+
` id: ${n}`,
|
|
1218
|
+
r ? ` title: ${r}` : null,
|
|
1219
|
+
i ? " xlsx:" : null,
|
|
1220
|
+
i ? ` book: ${i}` : null,
|
|
1221
|
+
i ? " role: screen" : null,
|
|
1222
|
+
"---"
|
|
1223
|
+
].filter((e) => e !== null).join("\n")}\n\n${[
|
|
1224
|
+
"import Screen from \"@ingcreators/annot-product-docs-astro/components/Screen.astro\";",
|
|
1225
|
+
"import Overlay from \"@ingcreators/annot-product-docs-astro/components/Overlay.astro\";",
|
|
1226
|
+
"",
|
|
1227
|
+
`# ${r ?? n}`,
|
|
1228
|
+
"",
|
|
1229
|
+
`<Screen id="${n}" src="${a}">`,
|
|
1230
|
+
"",
|
|
1231
|
+
...s.flatMap((e) => [e, ""]),
|
|
1232
|
+
"</Screen>",
|
|
1233
|
+
"",
|
|
1234
|
+
"{/* annot:snapshot",
|
|
1235
|
+
o.yaml.trim(),
|
|
1236
|
+
"*/}"
|
|
1237
|
+
].join("\n")}\n`
|
|
1238
|
+
}] };
|
|
1239
|
+
}
|
|
1240
|
+
function dt(e) {
|
|
1241
|
+
if (typeof e != "object" || !e) return;
|
|
1242
|
+
let t = e, n = typeof t.width == "number" ? t.width : 1280, r = typeof t.height == "number" ? t.height : 800, i = typeof t.deviceScaleFactor == "number" ? t.deviceScaleFactor : void 0;
|
|
1243
|
+
return {
|
|
1244
|
+
width: n,
|
|
1245
|
+
height: r,
|
|
1246
|
+
...i === void 0 ? {} : { deviceScaleFactor: i }
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function ft(e) {
|
|
1250
|
+
if (e === "load" || e === "domcontentloaded" || e === "networkidle") return e;
|
|
888
1251
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
if (e.output !== void 0) {
|
|
898
|
-
if (typeof e.output != "string")
|
|
899
|
-
throw new $("`output` must be a string when provided.");
|
|
900
|
-
if (!b(e.output))
|
|
901
|
-
throw new $(`\`output\` path "${e.output}" must be absolute.`);
|
|
902
|
-
s = e.output;
|
|
903
|
-
}
|
|
904
|
-
let c;
|
|
905
|
-
if (e.encode !== void 0) {
|
|
906
|
-
if (typeof e.encode != "object" || e.encode === null)
|
|
907
|
-
throw new $("`encode` must be an object when provided.");
|
|
908
|
-
c = e.encode;
|
|
909
|
-
}
|
|
910
|
-
const [r, a] = await Promise.all([
|
|
911
|
-
R(e.before),
|
|
912
|
-
R(e.after)
|
|
913
|
-
]), i = await Se(r.bytes, a.bytes, { threshold: o }), d = i.regions.map((h) => ({
|
|
914
|
-
type: "rect",
|
|
915
|
-
bbox: h,
|
|
916
|
-
intent: "warning"
|
|
917
|
-
})), l = U(d), v = t.annotator.toPng({
|
|
918
|
-
originalDataUrl: a.dataUrl,
|
|
919
|
-
annotationsSvg: l,
|
|
920
|
-
width: i.width,
|
|
921
|
-
height: i.height
|
|
922
|
-
}), u = await S(
|
|
923
|
-
v,
|
|
924
|
-
{ width: i.width, height: i.height },
|
|
925
|
-
c
|
|
926
|
-
);
|
|
927
|
-
if (s) {
|
|
928
|
-
await O(s, u.bytes);
|
|
929
|
-
const h = u.reason ? `, reason: ${u.reason}` : "";
|
|
930
|
-
return { content: [{ type: "text", text: `Wrote ${u.bytes.byteLength}-byte diff-annotated ${u.chosen.toUpperCase()} to ${s} (${u.width}×${u.height}, ${i.regions.length} changed region${i.regions.length === 1 ? "" : "s"}, ${i.mismatchedPixels} mismatched pixel${i.mismatchedPixels === 1 ? "" : "s"}${h}).` }] };
|
|
931
|
-
}
|
|
932
|
-
const _ = {
|
|
933
|
-
type: "image",
|
|
934
|
-
data: Buffer.from(
|
|
935
|
-
u.bytes.buffer,
|
|
936
|
-
u.bytes.byteOffset,
|
|
937
|
-
u.bytes.byteLength
|
|
938
|
-
).toString("base64"),
|
|
939
|
-
mimeType: u.mimeType
|
|
940
|
-
};
|
|
941
|
-
if (!n)
|
|
942
|
-
return { content: [_] };
|
|
943
|
-
const ee = `${i.regions.length} changed region${i.regions.length === 1 ? "" : "s"}, ${i.mismatchedPixels} mismatched pixel${i.mismatchedPixels === 1 ? "" : "s"}:
|
|
944
|
-
` + i.regions.map((h, D) => ` [${D + 1}] x=${h.x} y=${h.y} w=${h.width} h=${h.height}`).join(`
|
|
945
|
-
`);
|
|
946
|
-
return {
|
|
947
|
-
content: [_, { type: "text", text: ee }]
|
|
948
|
-
};
|
|
949
|
-
} catch (o) {
|
|
950
|
-
return {
|
|
951
|
-
content: [{ type: "text", text: o instanceof Error ? `${o.name}: ${o.message}` : `Unknown error: ${String(o)}` }],
|
|
952
|
-
isError: !0
|
|
953
|
-
};
|
|
954
|
-
}
|
|
1252
|
+
function K(e) {
|
|
1253
|
+
return {
|
|
1254
|
+
content: [{
|
|
1255
|
+
type: "text",
|
|
1256
|
+
text: e
|
|
1257
|
+
}],
|
|
1258
|
+
isError: !0
|
|
1259
|
+
};
|
|
955
1260
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1261
|
+
//#endregion
|
|
1262
|
+
//#region src/tools/propose-drift-fixes.ts
|
|
1263
|
+
var pt = "annot_propose_drift_fixes", mt = {
|
|
1264
|
+
name: pt,
|
|
1265
|
+
description: "Given an MDX path + the live URL the screen renders at, run drift detection between the stored `<Overlay match>` keys and the current `aria-snapshot`. Returns a structured Markdown report: one section per finding with the kind / severity / message / suggested fix. The agent reads the report and applies edits via its own patch primitive.",
|
|
1266
|
+
inputSchema: {
|
|
1267
|
+
type: "object",
|
|
1268
|
+
required: ["mdxPath", "url"],
|
|
1269
|
+
additionalProperties: !1,
|
|
1270
|
+
properties: {
|
|
1271
|
+
mdxPath: {
|
|
1272
|
+
type: "string",
|
|
1273
|
+
description: "Absolute or cwd-relative path to the `.mdx` file with `annot:` frontmatter."
|
|
1274
|
+
},
|
|
1275
|
+
url: {
|
|
1276
|
+
type: "string",
|
|
1277
|
+
format: "uri",
|
|
1278
|
+
description: "URL to navigate to for the live snapshot."
|
|
1279
|
+
},
|
|
1280
|
+
screenId: {
|
|
1281
|
+
type: "string",
|
|
1282
|
+
description: "Optional `<Screen id>` filter. Default: run drift against every screen in the MDX."
|
|
1283
|
+
},
|
|
1284
|
+
viewport: {
|
|
1285
|
+
type: "object",
|
|
1286
|
+
additionalProperties: !1,
|
|
1287
|
+
properties: {
|
|
1288
|
+
width: {
|
|
1289
|
+
type: "integer",
|
|
1290
|
+
minimum: 1,
|
|
1291
|
+
default: 1280
|
|
1292
|
+
},
|
|
1293
|
+
height: {
|
|
1294
|
+
type: "integer",
|
|
1295
|
+
minimum: 1,
|
|
1296
|
+
default: 800
|
|
1297
|
+
},
|
|
1298
|
+
deviceScaleFactor: {
|
|
1299
|
+
type: "number",
|
|
1300
|
+
minimum: .1,
|
|
1301
|
+
default: 1
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
waitFor: {
|
|
1306
|
+
type: "string",
|
|
1307
|
+
enum: [
|
|
1308
|
+
"load",
|
|
1309
|
+
"domcontentloaded",
|
|
1310
|
+
"networkidle"
|
|
1311
|
+
],
|
|
1312
|
+
default: "load"
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
984
1316
|
};
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1317
|
+
async function ht(e, t) {
|
|
1318
|
+
if (typeof e.mdxPath != "string" || e.mdxPath.length === 0) return q("`mdxPath` is required (string).");
|
|
1319
|
+
if (typeof e.url != "string" || e.url.length === 0) return q("`url` is required (string).");
|
|
1320
|
+
let n = typeof e.screenId == "string" ? e.screenId : void 0, r = await se(e.mdxPath);
|
|
1321
|
+
if (!r) return q(`${e.mdxPath} has no \`annot:\` frontmatter.`);
|
|
1322
|
+
let i = oe(r.screens).filter((e) => !n || e.id === n);
|
|
1323
|
+
if (i.length === 0) return { content: [{
|
|
1324
|
+
type: "text",
|
|
1325
|
+
text: `No lintable screens in ${e.mdxPath}${n ? ` (screenId=${n})` : ""}.`
|
|
1326
|
+
}] };
|
|
1327
|
+
let a = [];
|
|
1328
|
+
for (let n of i) {
|
|
1329
|
+
let r;
|
|
1330
|
+
try {
|
|
1331
|
+
r = await H(t.pool, {
|
|
1332
|
+
url: e.url,
|
|
1333
|
+
viewport: _t(e.viewport),
|
|
1334
|
+
waitFor: vt(e.waitFor)
|
|
1335
|
+
});
|
|
1336
|
+
} catch (e) {
|
|
1337
|
+
return q(`Failed to capture aria-snapshot for screen=${n.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1338
|
+
}
|
|
1339
|
+
let i = ae({
|
|
1340
|
+
screen: n,
|
|
1341
|
+
liveSnapshot: ce(r.yaml)
|
|
1342
|
+
});
|
|
1343
|
+
for (let e of i) a.push({
|
|
1344
|
+
screenId: n.id,
|
|
1345
|
+
finding: e
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
return a.length === 0 ? { content: [{
|
|
1349
|
+
type: "text",
|
|
1350
|
+
text: `No drift detected in ${e.mdxPath} against ${e.url}.`
|
|
1351
|
+
}] } : { content: [{
|
|
1352
|
+
type: "text",
|
|
1353
|
+
text: gt(e.mdxPath, e.url, a)
|
|
1354
|
+
}] };
|
|
1355
|
+
}
|
|
1356
|
+
function gt(e, t, n) {
|
|
1357
|
+
let r = [];
|
|
1358
|
+
r.push(`# Drift report — ${e}`), r.push(""), r.push(`Live URL: ${t}`), r.push(`Findings: ${n.length}`), r.push("");
|
|
1359
|
+
for (let { screenId: e, finding: t } of n) r.push(`## ${t.severity.toUpperCase()} [${e}] ${t.kind}`), r.push(""), r.push(t.message), t.match && (r.push(""), r.push("Current match key:"), r.push("```ts"), r.push(JSON.stringify(t.match, null, 2)), r.push("```")), t.suggestion && (r.push(""), r.push("Suggested replacement:"), r.push("```ts"), r.push(JSON.stringify(t.suggestion, null, 2)), r.push("```")), r.push("");
|
|
1360
|
+
return r.join("\n");
|
|
1361
|
+
}
|
|
1362
|
+
function _t(e) {
|
|
1363
|
+
if (typeof e != "object" || !e) return;
|
|
1364
|
+
let t = e, n = typeof t.width == "number" ? t.width : 1280, r = typeof t.height == "number" ? t.height : 800, i = typeof t.deviceScaleFactor == "number" ? t.deviceScaleFactor : void 0;
|
|
1365
|
+
return {
|
|
1366
|
+
width: n,
|
|
1367
|
+
height: r,
|
|
1368
|
+
...i === void 0 ? {} : { deviceScaleFactor: i }
|
|
1369
|
+
};
|
|
989
1370
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (typeof e.image != "string")
|
|
993
|
-
throw new E("`image` is required and must be a string.");
|
|
994
|
-
if (!Array.isArray(e.regions))
|
|
995
|
-
throw new E("`regions` is required and must be an array.");
|
|
996
|
-
const t = e.regions;
|
|
997
|
-
let o;
|
|
998
|
-
if (e.output !== void 0) {
|
|
999
|
-
if (typeof e.output != "string")
|
|
1000
|
-
throw new E("`output` must be a string when provided.");
|
|
1001
|
-
if (!b(e.output))
|
|
1002
|
-
throw new E(`\`output\` path "${e.output}" must be absolute.`);
|
|
1003
|
-
o = e.output;
|
|
1004
|
-
}
|
|
1005
|
-
let n;
|
|
1006
|
-
if (e.encode !== void 0) {
|
|
1007
|
-
if (typeof e.encode != "object" || e.encode === null)
|
|
1008
|
-
throw new E("`encode` must be an object when provided.");
|
|
1009
|
-
n = e.encode;
|
|
1010
|
-
}
|
|
1011
|
-
const s = await R(e.image), c = await Y(s.bytes, t), r = await S(c, s.dimensions, n);
|
|
1012
|
-
if (o) {
|
|
1013
|
-
await O(o, r.bytes);
|
|
1014
|
-
const a = r.reason ? `, reason: ${r.reason}` : "";
|
|
1015
|
-
return {
|
|
1016
|
-
content: [
|
|
1017
|
-
{
|
|
1018
|
-
type: "text",
|
|
1019
|
-
text: `Wrote ${r.bytes.byteLength}-byte redacted ${r.chosen.toUpperCase()} to ${o} (${r.width}×${r.height}, ${t.length} region${t.length === 1 ? "" : "s"}${a}).`
|
|
1020
|
-
}
|
|
1021
|
-
]
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
return {
|
|
1025
|
-
content: [
|
|
1026
|
-
{
|
|
1027
|
-
type: "image",
|
|
1028
|
-
data: Buffer.from(
|
|
1029
|
-
r.bytes.buffer,
|
|
1030
|
-
r.bytes.byteOffset,
|
|
1031
|
-
r.bytes.byteLength
|
|
1032
|
-
).toString("base64"),
|
|
1033
|
-
mimeType: r.mimeType
|
|
1034
|
-
}
|
|
1035
|
-
]
|
|
1036
|
-
};
|
|
1037
|
-
} catch (t) {
|
|
1038
|
-
return {
|
|
1039
|
-
content: [{ type: "text", text: t instanceof Error ? `${t.name}: ${t.message}` : `Unknown error: ${String(t)}` }],
|
|
1040
|
-
isError: !0
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1371
|
+
function vt(e) {
|
|
1372
|
+
if (e === "load" || e === "domcontentloaded" || e === "networkidle") return e;
|
|
1043
1373
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1374
|
+
function q(e) {
|
|
1375
|
+
return {
|
|
1376
|
+
content: [{
|
|
1377
|
+
type: "text",
|
|
1378
|
+
text: e
|
|
1379
|
+
}],
|
|
1380
|
+
isError: !0
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
//#endregion
|
|
1384
|
+
//#region src/tools/redact-screenshot.ts
|
|
1385
|
+
var J = "annot_redact_screenshot", yt = {
|
|
1386
|
+
name: J,
|
|
1387
|
+
description: "Destructively burn redactions (solid / mosaic / blur) into a PNG screenshot. The original pixels under each region are irrecoverably replaced. Returns the redacted PNG inline unless `output` is specified.",
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
type: "object",
|
|
1390
|
+
required: ["image", "regions"],
|
|
1391
|
+
additionalProperties: !1,
|
|
1392
|
+
properties: {
|
|
1393
|
+
image: {
|
|
1394
|
+
type: "string",
|
|
1395
|
+
description: "`data:image/png;base64,...` URL or absolute filesystem path."
|
|
1396
|
+
},
|
|
1397
|
+
regions: {
|
|
1398
|
+
type: "array",
|
|
1399
|
+
items: { $ref: "#/$defs/BboxRedactRegion" }
|
|
1400
|
+
},
|
|
1401
|
+
output: { type: "string" },
|
|
1402
|
+
encode: {
|
|
1403
|
+
$ref: "#/$defs/EncodeOptions",
|
|
1404
|
+
description: "Optional encoder settings applied after the redactions are burned in. Useful when the redacted screenshot is destined for an issue tracker / docs and size matters."
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
$defs: {
|
|
1408
|
+
...T,
|
|
1409
|
+
BboxRedactRegion: t,
|
|
1410
|
+
EncodeOptions: D
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}, Y = class extends Error {
|
|
1414
|
+
constructor(e) {
|
|
1415
|
+
super(e), this.name = "InvalidRedactInputError";
|
|
1416
|
+
}
|
|
1081
1417
|
};
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1418
|
+
async function X(e) {
|
|
1419
|
+
try {
|
|
1420
|
+
if (typeof e.image != "string") throw new Y("`image` is required and must be a string.");
|
|
1421
|
+
if (!Array.isArray(e.regions)) throw new Y("`regions` is required and must be an array.");
|
|
1422
|
+
let t = e.regions, n;
|
|
1423
|
+
if (e.output !== void 0) {
|
|
1424
|
+
if (typeof e.output != "string") throw new Y("`output` must be a string when provided.");
|
|
1425
|
+
if (!m(e.output)) throw new Y(`\`output\` path "${e.output}" must be absolute.`);
|
|
1426
|
+
n = e.output;
|
|
1427
|
+
}
|
|
1428
|
+
let r;
|
|
1429
|
+
if (e.encode !== void 0) {
|
|
1430
|
+
if (typeof e.encode != "object" || e.encode === null) throw new Y("`encode` must be an object when provided.");
|
|
1431
|
+
r = e.encode;
|
|
1432
|
+
}
|
|
1433
|
+
let i = await M(e.image), a = await P(await N(i.bytes, t), i.dimensions, r);
|
|
1434
|
+
if (n) {
|
|
1435
|
+
await p(n, a.bytes);
|
|
1436
|
+
let e = a.reason ? `, reason: ${a.reason}` : "";
|
|
1437
|
+
return { content: [{
|
|
1438
|
+
type: "text",
|
|
1439
|
+
text: `Wrote ${a.bytes.byteLength}-byte redacted ${a.chosen.toUpperCase()} to ${n} (${a.width}×${a.height}, ${t.length} region${t.length === 1 ? "" : "s"}${e}).`
|
|
1440
|
+
}] };
|
|
1441
|
+
}
|
|
1442
|
+
return { content: [{
|
|
1443
|
+
type: "image",
|
|
1444
|
+
data: Buffer.from(a.bytes.buffer, a.bytes.byteOffset, a.bytes.byteLength).toString("base64"),
|
|
1445
|
+
mimeType: a.mimeType
|
|
1446
|
+
}] };
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
return {
|
|
1449
|
+
content: [{
|
|
1450
|
+
type: "text",
|
|
1451
|
+
text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}`
|
|
1452
|
+
}],
|
|
1453
|
+
isError: !0
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1086
1456
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1457
|
+
//#endregion
|
|
1458
|
+
//#region src/tools/redact-url.ts
|
|
1459
|
+
var Z = "annot_redact_url", bt = {
|
|
1460
|
+
name: Z,
|
|
1461
|
+
description: "Open a URL in a headless browser, capture a screenshot, and destructively burn redactions over regions identified by locator strings or bboxes. Returns the redacted PNG inline unless `output` is specified.",
|
|
1462
|
+
inputSchema: {
|
|
1463
|
+
type: "object",
|
|
1464
|
+
required: ["url", "regions"],
|
|
1465
|
+
additionalProperties: !1,
|
|
1466
|
+
properties: {
|
|
1467
|
+
url: {
|
|
1468
|
+
type: "string",
|
|
1469
|
+
format: "uri"
|
|
1470
|
+
},
|
|
1471
|
+
regions: {
|
|
1472
|
+
type: "array",
|
|
1473
|
+
items: { $ref: "#/$defs/LocatorRedactRegion" }
|
|
1474
|
+
},
|
|
1475
|
+
viewport: {
|
|
1476
|
+
type: "object",
|
|
1477
|
+
additionalProperties: !1,
|
|
1478
|
+
properties: {
|
|
1479
|
+
width: {
|
|
1480
|
+
type: "integer",
|
|
1481
|
+
minimum: 1,
|
|
1482
|
+
default: 1280
|
|
1483
|
+
},
|
|
1484
|
+
height: {
|
|
1485
|
+
type: "integer",
|
|
1486
|
+
minimum: 1,
|
|
1487
|
+
default: 800
|
|
1488
|
+
},
|
|
1489
|
+
deviceScaleFactor: {
|
|
1490
|
+
type: "number",
|
|
1491
|
+
minimum: .1,
|
|
1492
|
+
default: 1
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
fullPage: {
|
|
1497
|
+
type: "boolean",
|
|
1498
|
+
default: !1
|
|
1499
|
+
},
|
|
1500
|
+
waitFor: {
|
|
1501
|
+
type: "string",
|
|
1502
|
+
enum: [
|
|
1503
|
+
"load",
|
|
1504
|
+
"domcontentloaded",
|
|
1505
|
+
"networkidle"
|
|
1506
|
+
],
|
|
1507
|
+
default: "load"
|
|
1508
|
+
},
|
|
1509
|
+
output: { type: "string" },
|
|
1510
|
+
encode: { $ref: "#/$defs/EncodeOptions" }
|
|
1511
|
+
},
|
|
1512
|
+
$defs: {
|
|
1513
|
+
...T,
|
|
1514
|
+
LocatorRedactRegion: Ce,
|
|
1515
|
+
EncodeOptions: D
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}, Q = class extends Error {
|
|
1519
|
+
constructor(e) {
|
|
1520
|
+
super(e), this.name = "InvalidRedactUrlInputError";
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
async function xt(e, t) {
|
|
1524
|
+
try {
|
|
1525
|
+
let n = St(e), r = await h(t.pool, {
|
|
1526
|
+
url: n.url,
|
|
1527
|
+
viewport: n.viewport,
|
|
1528
|
+
fullPage: n.fullPage,
|
|
1529
|
+
waitFor: n.waitFor
|
|
1530
|
+
}), i;
|
|
1531
|
+
try {
|
|
1532
|
+
i = [];
|
|
1533
|
+
for (let e of n.regions) {
|
|
1534
|
+
let t = { bbox: await Ct(r.handle.page, e) };
|
|
1535
|
+
e.style !== void 0 && (t.style = e.style), e.color !== void 0 && (t.color = e.color), i.push(t);
|
|
1536
|
+
}
|
|
1537
|
+
} finally {
|
|
1538
|
+
await r.handle.close();
|
|
1539
|
+
}
|
|
1540
|
+
let a = await P(await N(r.pngBytes, i), A(r.pngBytes), n.encode);
|
|
1541
|
+
if (n.output) {
|
|
1542
|
+
await p(n.output, a.bytes);
|
|
1543
|
+
let e = a.reason ? `, reason: ${a.reason}` : "";
|
|
1544
|
+
return { content: [{
|
|
1545
|
+
type: "text",
|
|
1546
|
+
text: `Wrote ${a.bytes.byteLength}-byte redacted ${a.chosen.toUpperCase()} to ${n.output} (${a.width}×${a.height}, ${n.regions.length} region${n.regions.length === 1 ? "" : "s"}, captured from ${n.url}${e}).`
|
|
1547
|
+
}] };
|
|
1548
|
+
}
|
|
1549
|
+
return { content: [{
|
|
1550
|
+
type: "image",
|
|
1551
|
+
data: Buffer.from(a.bytes.buffer, a.bytes.byteOffset, a.bytes.byteLength).toString("base64"),
|
|
1552
|
+
mimeType: a.mimeType
|
|
1553
|
+
}] };
|
|
1554
|
+
} catch (e) {
|
|
1555
|
+
return {
|
|
1556
|
+
content: [{
|
|
1557
|
+
type: "text",
|
|
1558
|
+
text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}`
|
|
1559
|
+
}],
|
|
1560
|
+
isError: !0
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1137
1563
|
}
|
|
1138
|
-
function
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1564
|
+
function St(e) {
|
|
1565
|
+
if (typeof e.url != "string" || e.url.length === 0) throw new Q("`url` is required and must be a non-empty string.");
|
|
1566
|
+
if (!Array.isArray(e.regions)) throw new Q("`regions` is required and must be an array.");
|
|
1567
|
+
let t = e.regions, n;
|
|
1568
|
+
if (e.viewport !== void 0) {
|
|
1569
|
+
if (typeof e.viewport != "object" || e.viewport === null) throw new Q("`viewport` must be an object when provided.");
|
|
1570
|
+
let t = e.viewport;
|
|
1571
|
+
n = {
|
|
1572
|
+
width: typeof t.width == "number" ? t.width : 1280,
|
|
1573
|
+
height: typeof t.height == "number" ? t.height : 800,
|
|
1574
|
+
deviceScaleFactor: typeof t.deviceScaleFactor == "number" ? t.deviceScaleFactor : 1
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
let r = e.fullPage === !0, i = e.waitFor === "domcontentloaded" || e.waitFor === "networkidle" ? e.waitFor : "load", a;
|
|
1578
|
+
if (e.output !== void 0) {
|
|
1579
|
+
if (typeof e.output != "string") throw new Q("`output` must be a string when provided.");
|
|
1580
|
+
if (!m(e.output)) throw new Q(`\`output\` path "${e.output}" must be absolute.`);
|
|
1581
|
+
a = e.output;
|
|
1582
|
+
}
|
|
1583
|
+
let o;
|
|
1584
|
+
if (e.encode !== void 0) {
|
|
1585
|
+
if (typeof e.encode != "object" || e.encode === null) throw new Q("`encode` must be an object when provided.");
|
|
1586
|
+
o = e.encode;
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
url: e.url,
|
|
1590
|
+
regions: t,
|
|
1591
|
+
viewport: n,
|
|
1592
|
+
fullPage: r,
|
|
1593
|
+
waitFor: i,
|
|
1594
|
+
output: a,
|
|
1595
|
+
encode: o
|
|
1596
|
+
};
|
|
1167
1597
|
}
|
|
1168
|
-
async function
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1598
|
+
async function Ct(e, t) {
|
|
1599
|
+
if (t.bbox !== void 0) return t.bbox;
|
|
1600
|
+
if (t.locator !== void 0) return y(e, t.locator);
|
|
1601
|
+
throw new Q("redact region requires either `bbox` or `locator`.");
|
|
1172
1602
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/tools/translate-screen-spec.ts
|
|
1605
|
+
var wt = "annot_translate_screen_spec", Tt = {
|
|
1606
|
+
name: wt,
|
|
1607
|
+
description: "Given an MDX with `annot:` frontmatter and a target locale (e.g. `en-US` / `ja-JP` / `ko-KR`), emit a structured translation manifest listing every translatable string the agent should localise: frontmatter title / purpose, per-overlay Markdown bodies, per-transition prose. The agent translates the strings via its own LLM call and the host writes the localised MDX.",
|
|
1608
|
+
inputSchema: {
|
|
1609
|
+
type: "object",
|
|
1610
|
+
required: ["mdxPath", "targetLocale"],
|
|
1611
|
+
additionalProperties: !1,
|
|
1612
|
+
properties: {
|
|
1613
|
+
mdxPath: {
|
|
1614
|
+
type: "string",
|
|
1615
|
+
description: "Absolute or cwd-relative path to the source `.mdx`."
|
|
1616
|
+
},
|
|
1617
|
+
sourceLocale: {
|
|
1618
|
+
type: "string",
|
|
1619
|
+
description: "BCP-47 locale tag of the source MDX (e.g. `ja-JP`). Informational only — the tool doesn't enforce it."
|
|
1620
|
+
},
|
|
1621
|
+
targetLocale: {
|
|
1622
|
+
type: "string",
|
|
1623
|
+
description: "BCP-47 locale tag of the locale to produce (e.g. `en-US`)."
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
async function Et(e) {
|
|
1629
|
+
if (typeof e.mdxPath != "string" || e.mdxPath.length === 0) return $("`mdxPath` is required (string).");
|
|
1630
|
+
if (typeof e.targetLocale != "string" || e.targetLocale.length === 0) return $("`targetLocale` is required (string).");
|
|
1631
|
+
let t = await se(e.mdxPath);
|
|
1632
|
+
if (!t) return $(`${e.mdxPath} has no \`annot:\` frontmatter.`);
|
|
1633
|
+
let n = [];
|
|
1634
|
+
t.frontmatter.title && n.push({
|
|
1635
|
+
location: { kind: "frontmatter.title" },
|
|
1636
|
+
source: t.frontmatter.title
|
|
1637
|
+
}), t.frontmatter.purpose && n.push({
|
|
1638
|
+
location: { kind: "frontmatter.purpose" },
|
|
1639
|
+
source: t.frontmatter.purpose
|
|
1640
|
+
});
|
|
1641
|
+
for (let e of t.screens) {
|
|
1642
|
+
let t = 1;
|
|
1643
|
+
for (let r of e.overlays) {
|
|
1644
|
+
let i = r.number ?? t++;
|
|
1645
|
+
n.push({
|
|
1646
|
+
location: {
|
|
1647
|
+
kind: "overlay.body",
|
|
1648
|
+
screenId: e.id,
|
|
1649
|
+
overlayNumber: i,
|
|
1650
|
+
matchRole: r.match.role,
|
|
1651
|
+
matchName: r.match.name
|
|
1652
|
+
},
|
|
1653
|
+
source: r.body
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
for (let e of t.transitions) n.push({
|
|
1658
|
+
location: {
|
|
1659
|
+
kind: "transition.body",
|
|
1660
|
+
triggerRole: e.trigger.role,
|
|
1661
|
+
triggerName: e.trigger.name
|
|
1662
|
+
},
|
|
1663
|
+
source: e.body
|
|
1664
|
+
});
|
|
1665
|
+
for (let e of t.history) n.push({
|
|
1666
|
+
location: {
|
|
1667
|
+
kind: "history.body",
|
|
1668
|
+
version: e.version
|
|
1669
|
+
},
|
|
1670
|
+
source: e.body
|
|
1671
|
+
});
|
|
1672
|
+
let r = {
|
|
1673
|
+
mdxPath: e.mdxPath,
|
|
1674
|
+
sourceLocale: typeof e.sourceLocale == "string" ? e.sourceLocale : void 0,
|
|
1675
|
+
targetLocale: e.targetLocale,
|
|
1676
|
+
id: t.frontmatter.id,
|
|
1677
|
+
items: n
|
|
1678
|
+
};
|
|
1679
|
+
return { content: [{
|
|
1680
|
+
type: "text",
|
|
1681
|
+
text: JSON.stringify(r, null, 2)
|
|
1682
|
+
}] };
|
|
1683
|
+
}
|
|
1684
|
+
function $(e) {
|
|
1685
|
+
return {
|
|
1686
|
+
content: [{
|
|
1687
|
+
type: "text",
|
|
1688
|
+
text: e
|
|
1689
|
+
}],
|
|
1690
|
+
isError: !0
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
//#endregion
|
|
1694
|
+
//#region src/server.ts
|
|
1695
|
+
var Dt = "annot-mcp", Ot = "0.1.0", kt = [
|
|
1696
|
+
I,
|
|
1697
|
+
B,
|
|
1698
|
+
yt,
|
|
1699
|
+
bt,
|
|
1700
|
+
at,
|
|
1701
|
+
et,
|
|
1702
|
+
ct,
|
|
1703
|
+
mt,
|
|
1704
|
+
Tt
|
|
1179
1705
|
];
|
|
1180
|
-
function
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1706
|
+
function At(e = {}) {
|
|
1707
|
+
let t = new ne({
|
|
1708
|
+
name: Dt,
|
|
1709
|
+
version: e.version ?? Ot
|
|
1710
|
+
}, { capabilities: { tools: {} } }), n = e.annotator ?? s(e.annotatorOptions ?? {}), r = e.pool ?? pe();
|
|
1711
|
+
return t.setRequestHandler(ie, async () => ({ tools: kt })), t.setRequestHandler(re, async (e) => {
|
|
1712
|
+
let { name: t, arguments: i } = e.params, a;
|
|
1713
|
+
switch (t) {
|
|
1714
|
+
case F:
|
|
1715
|
+
a = await L(i ?? {}, { annotator: n });
|
|
1716
|
+
break;
|
|
1717
|
+
case z:
|
|
1718
|
+
a = await Ke(i ?? {}, {
|
|
1719
|
+
annotator: n,
|
|
1720
|
+
pool: r
|
|
1721
|
+
});
|
|
1722
|
+
break;
|
|
1723
|
+
case J:
|
|
1724
|
+
a = await X(i ?? {});
|
|
1725
|
+
break;
|
|
1726
|
+
case Z:
|
|
1727
|
+
a = await xt(i ?? {}, { pool: r });
|
|
1728
|
+
break;
|
|
1729
|
+
case W:
|
|
1730
|
+
a = await ot(i ?? {}, { annotator: n });
|
|
1731
|
+
break;
|
|
1732
|
+
case $e:
|
|
1733
|
+
a = await tt(i ?? {}, { pool: r });
|
|
1734
|
+
break;
|
|
1735
|
+
case st:
|
|
1736
|
+
a = await ut(i ?? {}, { pool: r });
|
|
1737
|
+
break;
|
|
1738
|
+
case pt:
|
|
1739
|
+
a = await ht(i ?? {}, { pool: r });
|
|
1740
|
+
break;
|
|
1741
|
+
case wt:
|
|
1742
|
+
a = await Et(i ?? {});
|
|
1743
|
+
break;
|
|
1744
|
+
default: a = {
|
|
1745
|
+
content: [{
|
|
1746
|
+
type: "text",
|
|
1747
|
+
text: `Unknown tool: ${t}`
|
|
1748
|
+
}],
|
|
1749
|
+
isError: !0
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
return a;
|
|
1753
|
+
}), t;
|
|
1219
1754
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1755
|
+
//#endregion
|
|
1756
|
+
//#region src/transport.ts
|
|
1757
|
+
async function jt() {
|
|
1758
|
+
let e = At(), t = new le();
|
|
1759
|
+
await e.connect(t);
|
|
1223
1760
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
J as ANNOTATE_URL_TOOL_NAME,
|
|
1227
|
-
At as BBOX_ANNOTATION_SCHEMA,
|
|
1228
|
-
Tt as BBOX_REDACT_REGION_SCHEMA,
|
|
1229
|
-
ge as BrowserPool,
|
|
1230
|
-
Q as COMPARE_SCREENSHOTS_TOOL_NAME,
|
|
1231
|
-
q as ChromiumUnavailableError,
|
|
1232
|
-
Pe as DimensionMismatchError,
|
|
1233
|
-
w as InvalidImageInputError,
|
|
1234
|
-
x as InvalidPngError,
|
|
1235
|
-
Ce as LOCATOR_ANNOTATION_SCHEMA,
|
|
1236
|
-
Ue as LOCATOR_REDACT_REGION_SCHEMA,
|
|
1237
|
-
N as LocatorResolutionError,
|
|
1238
|
-
K as REDACT_SCREENSHOT_TOOL_NAME,
|
|
1239
|
-
Z as REDACT_URL_TOOL_NAME,
|
|
1240
|
-
I as SHARED_DEFS,
|
|
1241
|
-
xe as aggregateDiffRegions,
|
|
1242
|
-
Je as annotateScreenshotTool,
|
|
1243
|
-
tt as annotateUrlTool,
|
|
1244
|
-
Rt as arrowBetween,
|
|
1245
|
-
It as bboxAnnotationsToSvg,
|
|
1246
|
-
Y as burnRedactions,
|
|
1247
|
-
W as capturePage,
|
|
1248
|
-
st as compareScreenshotsTool,
|
|
1249
|
-
ye as createChromiumPool,
|
|
1250
|
-
gt as createServer,
|
|
1251
|
-
Se as diffScreenshots,
|
|
1252
|
-
Qe as handleAnnotateScreenshot,
|
|
1253
|
-
ot as handleAnnotateUrl,
|
|
1254
|
-
it as handleCompareScreenshots,
|
|
1255
|
-
ct as handleRedactScreenshot,
|
|
1256
|
-
lt as handleRedactUrl,
|
|
1257
|
-
B as readPngDimensions,
|
|
1258
|
-
_t as rectForBoundingBox,
|
|
1259
|
-
at as redactScreenshotTool,
|
|
1260
|
-
dt as redactUrlTool,
|
|
1261
|
-
R as resolveImageInput,
|
|
1262
|
-
f as resolveLocator,
|
|
1263
|
-
we as resolveLocatorAnnotation,
|
|
1264
|
-
be as resolveLocatorAnnotations,
|
|
1265
|
-
Pt as runStdioServer,
|
|
1266
|
-
Ct as textAt
|
|
1267
|
-
};
|
|
1761
|
+
//#endregion
|
|
1762
|
+
export { F as ANNOTATE_SCREENSHOT_TOOL_NAME, z as ANNOTATE_URL_TOOL_NAME, e as BBOX_ANNOTATION_SCHEMA, t as BBOX_REDACT_REGION_SCHEMA, g as BrowserPool, W as COMPARE_SCREENSHOTS_TOOL_NAME, _ as ChromiumUnavailableError, Se as DimensionMismatchError, j as InvalidImageInputError, k as InvalidPngError, E as LOCATOR_ANNOTATION_SCHEMA, Ce as LOCATOR_REDACT_REGION_SCHEMA, v as LocatorResolutionError, J as REDACT_SCREENSHOT_TOOL_NAME, Z as REDACT_URL_TOOL_NAME, T as SHARED_DEFS, ye as aggregateDiffRegions, I as annotateScreenshotTool, B as annotateUrlTool, i as arrowBetween, a as bboxAnnotationsToSvg, N as burnRedactions, h as capturePage, at as compareScreenshotsTool, pe as createChromiumPool, At as createServer, C as diffScreenshots, L as handleAnnotateScreenshot, Ke as handleAnnotateUrl, ot as handleCompareScreenshots, X as handleRedactScreenshot, xt as handleRedactUrl, A as readPngDimensions, l as rectForBoundingBox, yt as redactScreenshotTool, bt as redactUrlTool, M as resolveImageInput, y as resolveLocator, me as resolveLocatorAnnotation, he as resolveLocatorAnnotations, jt as runStdioServer, u as textAt };
|