@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/dist/index.js CHANGED
@@ -1,1267 +1,1762 @@
1
- import { SHARED_DEFS as te, DEFAULT_ENCODE_OPTIONS as oe, decodeAndEncodeImage as re, BBOX_ANNOTATION_SCHEMA as ne, bboxAnnotationsToSvg as U, BBOX_REDACT_REGION_SCHEMA as se, createAnnotator as ie } from "@ingcreators/annot-annotator";
2
- import { BBOX_ANNOTATION_SCHEMA as At, BBOX_REDACT_REGION_SCHEMA as Tt, arrowBetween as Rt, bboxAnnotationsToSvg as It, rectForBoundingBox as _t, textAt as Ct } from "@ingcreators/annot-annotator";
3
- import { loadImage as C, createCanvas as F } from "@napi-rs/canvas";
4
- import ae from "pixelmatch";
5
- import { readFile as ce, writeFile as O } from "node:fs/promises";
6
- import { isAbsolute as b } from "node:path";
7
- import { Server as de } from "@modelcontextprotocol/sdk/server/index.js";
8
- import { ListToolsRequestSchema as le, CallToolRequestSchema as ue } from "@modelcontextprotocol/sdk/types.js";
9
- import { StdioServerTransport as he } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- const fe = { width: 1280, height: 800, deviceScaleFactor: 1 }, pe = "load";
11
- async function W(e, t) {
12
- const o = { ...fe, ...t.viewport }, n = t.waitFor ?? pe, s = t.fullPage ?? !1, c = await e.acquire();
13
- let r, a = !1;
14
- const i = () => {
15
- a || (a = !0, e.release());
16
- };
17
- try {
18
- r = await c.newContext({
19
- viewport: { width: o.width, height: o.height },
20
- deviceScaleFactor: o.deviceScaleFactor
21
- });
22
- const d = await r.newPage();
23
- await d.goto(t.url, { waitUntil: n });
24
- const l = await d.screenshot({ fullPage: s, type: "png" }), v = new Uint8Array(l.buffer, l.byteOffset, l.byteLength), u = r;
25
- return { pngBytes: v, handle: {
26
- page: d,
27
- async close() {
28
- if (!a)
29
- try {
30
- await u.close();
31
- } finally {
32
- i();
33
- }
34
- }
35
- } };
36
- } catch (d) {
37
- if (r)
38
- try {
39
- await r.close();
40
- } catch {
41
- }
42
- throw i(), d;
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
- const me = 3e4;
46
- class ge {
47
- #s;
48
- #i;
49
- #e;
50
- #r;
51
- #t = 0;
52
- #o;
53
- constructor(t, o = {}) {
54
- this.#s = t, this.#i = o.idleTimeoutMs ?? me;
55
- }
56
- /**
57
- * Acquire a browser instance. The pool launches lazily on the
58
- * first call. Subsequent acquires return the same shared
59
- * instance until every borrow has been released and the idle
60
- * timeout has elapsed.
61
- *
62
- * The caller MUST call {@link release} (typically in a `finally`)
63
- * after they're done so the pool can track outstanding borrows
64
- * accurately.
65
- */
66
- async acquire() {
67
- return this.#n(), this.#t += 1, this.#e ? this.#e : (this.#r || (this.#r = this.#s.launch().then((t) => (this.#e = t, this.#r = void 0, t)).catch((t) => {
68
- throw this.#r = void 0, this.#t = Math.max(0, this.#t - 1), t;
69
- })), this.#r);
70
- }
71
- /**
72
- * Release a previously-acquired browser. Decrements the
73
- * refcount; when it reaches zero an idle timer starts. A
74
- * follow-up `acquire` within the idle window reuses the same
75
- * instance; otherwise the timer fires and the browser closes.
76
- */
77
- release() {
78
- this.#t !== 0 && (this.#t -= 1, this.#t === 0 && this.#e && this.#a());
79
- }
80
- /**
81
- * Close the pooled browser immediately, regardless of refcount.
82
- * Intended for explicit teardown (e.g. SIGINT / SIGTERM
83
- * handlers); regular flows should rely on the idle timer.
84
- */
85
- async shutdown() {
86
- this.#n();
87
- const t = this.#e;
88
- this.#e = void 0, this.#t = 0, t && await t.close();
89
- }
90
- /** Refcount of outstanding borrows. Exposed for tests / metrics. */
91
- get activeBorrows() {
92
- return this.#t;
93
- }
94
- /** Whether a browser is currently launched. Exposed for tests. */
95
- get isLaunched() {
96
- return this.#e !== void 0;
97
- }
98
- // ─── internals ────────────────────────────────────────────────
99
- #n() {
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 q extends Error {
137
- cause;
138
- constructor(t, o) {
139
- super(t), this.name = "ChromiumUnavailableError", this.cause = o;
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
- class N extends Error {
143
- locator;
144
- constructor(t, o) {
145
- super(o), this.name = "LocatorResolutionError", this.locator = t;
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 f(e, t) {
149
- const o = await e.locator(t).boundingBox();
150
- if (!o)
151
- throw new N(
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 we(e, t) {
158
- switch (t.type) {
159
- case "rect": {
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
- async function be(e, t) {
215
- const o = [];
216
- for (const n of t)
217
- o.push(await we(e, n));
218
- return o;
194
+ function b(e) {
195
+ return {
196
+ x: e.x + e.width / 2,
197
+ y: e.y + e.height / 2
198
+ };
219
199
  }
220
- async function ve(e, t) {
221
- if (!t)
222
- throw new N(
223
- "<callout.at>",
224
- "callout annotations require either `at` or `atLocator`."
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 T(e) {
230
- return { x: e.x + e.width / 2, y: e.y + e.height / 2 };
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 $e(e, t) {
233
- const o = (t ?? 14) + 2;
234
- return { x: e.x, y: Math.max(0, e.y - o) };
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
- function m(e) {
237
- if (e === void 0)
238
- throw new N(
239
- "<missing>",
240
- "Internal error: annotation passed schema validation without a locator or bbox. This is a bug."
241
- );
242
- return e;
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 g(e) {
245
- if (e.type === "raw")
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
- const Ee = 4;
251
- function xe(e, t, o) {
252
- const n = new Uint8Array(t * o), s = [];
253
- for (let c = 0; c < o; c++)
254
- for (let r = 0; r < t; r++) {
255
- const a = c * t + r;
256
- if (n[a] || !X(e, a)) continue;
257
- const i = Oe(e, n, r, c, t, o);
258
- i.count >= Ee && s.push({
259
- x: i.minX,
260
- y: i.minY,
261
- width: i.maxX - i.minX + 1,
262
- height: i.maxY - i.minY + 1
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
- function X(e, t) {
268
- return e[t * 4 + 3] > 0;
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 Oe(e, t, o, n, s, c) {
271
- const r = {
272
- count: 0,
273
- minX: o,
274
- minY: n,
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
- class Pe extends Error {
287
- constructor(t, o) {
288
- super(
289
- `Cannot compare screenshots of different dimensions: before is ${t.width}×${t.height}, after is ${o.width}×${o.height}.`
290
- ), this.name = "DimensionMismatchError";
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
- async function Se(e, t, o = {}) {
294
- const [n, s] = await Promise.all([
295
- C(Buffer.from(e)),
296
- C(Buffer.from(t))
297
- ]);
298
- if (n.width !== s.width || n.height !== s.height)
299
- throw new Pe(
300
- { width: n.width, height: n.height },
301
- { width: s.width, height: s.height }
302
- );
303
- const { width: c, height: r } = n, a = k(n, c, r), i = k(s, c, r), d = new Uint8Array(c * r * 4), l = ae(a, i, d, c, r, {
304
- threshold: o.threshold ?? 0.1,
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 k(e, t, o) {
311
- const s = F(t, o).getContext("2d");
312
- s.drawImage(e, 0, 0);
313
- const c = s.getImageData(0, 0, t, o).data;
314
- return new Uint8Array(c.buffer, c.byteOffset, c.byteLength);
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
- const I = {
317
- ...te,
318
- Locator: { type: "string", minLength: 1 }
319
- }, Le = {
320
- type: "object",
321
- required: ["type"],
322
- additionalProperties: !1,
323
- properties: {
324
- type: { const: "rect" },
325
- bbox: { $ref: "#/$defs/BBox" },
326
- locator: { $ref: "#/$defs/Locator" },
327
- intent: { $ref: "#/$defs/Intent" },
328
- stroke: { type: "string" },
329
- strokeWidth: { type: "number", minimum: 0 },
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 B(e) {
461
- if (e.byteLength < G)
462
- throw new x(
463
- `Expected at least ${G} bytes for a PNG header, got ${e.byteLength}.`
464
- );
465
- for (let c = 0; c < j.length; c++)
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
- class w extends Error {
486
- constructor(t) {
487
- super(t), this.name = "InvalidImageInputError";
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
- const De = "data:", qe = /^data:image\/png;base64,/i;
491
- async function R(e) {
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
- function ke(e) {
505
- if (!qe.test(e))
506
- throw new w(
507
- "Only `data:image/png;base64,...` URLs are supported. JPEG / WebP / SVG data URLs are not accepted in v1."
508
- );
509
- const t = e.slice(e.indexOf(",") + 1), o = Ge(t), n = z(o);
510
- return { bytes: o, dataUrl: e, dimensions: n };
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
- async function je(e) {
513
- let t;
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 z(e) {
525
- try {
526
- return B(e);
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 Ge(e) {
532
- const t = Buffer.from(e, "base64");
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
- function Me(e) {
536
- return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
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
- const He = "solid", We = "#000000", M = 16, Xe = 12;
539
- async function Y(e, t) {
540
- if (t.length === 0)
541
- return e;
542
- const o = await C(Buffer.from(e)), n = F(o.width, o.height), s = n.getContext("2d");
543
- s.drawImage(o, 0, 0);
544
- for (const r of t) {
545
- if (r.bbox.width <= 0 || r.bbox.height <= 0) continue;
546
- switch (r.style ?? He) {
547
- case "solid":
548
- ze(s, r.bbox, r.color ?? We);
549
- break;
550
- case "mosaic":
551
- Ye(s, o, r.bbox);
552
- break;
553
- case "blur":
554
- Ve(s, o, r.bbox);
555
- break;
556
- }
557
- }
558
- const c = n.toBuffer("image/png");
559
- return new Uint8Array(c.buffer, c.byteOffset, c.byteLength);
560
- }
561
- function ze(e, t, o) {
562
- e.save(), e.fillStyle = o, e.fillRect(t.x, t.y, t.width, t.height), e.restore();
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 Ye(e, t, o) {
565
- const n = Math.max(1, Math.round(o.width / M)), s = Math.max(1, Math.round(o.height / M)), c = F(n, s), r = c.getContext("2d");
566
- r.imageSmoothingEnabled = !1, r.drawImage(t, o.x, o.y, o.width, o.height, 0, 0, n, s), e.save(), e.imageSmoothingEnabled = !1, e.drawImage(c, 0, 0, n, s, o.x, o.y, o.width, o.height), e.restore();
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
- function Ve(e, t, o) {
569
- e.save(), e.beginPath(), e.rect(o.x, o.y, o.width, o.height), e.clip(), e.filter = `blur(${Xe}px)`, e.drawImage(t, 0, 0), e.restore();
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
- async function S(e, t, o) {
572
- if (!o || Object.keys(o).length === 0)
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
- const V = "annot_annotate_screenshot", Je = {
591
- name: V,
592
- 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.",
593
- inputSchema: {
594
- type: "object",
595
- required: ["image", "annotations"],
596
- additionalProperties: !1,
597
- properties: {
598
- image: {
599
- type: "string",
600
- description: "Source image. Either a `data:image/png;base64,...` URL or an absolute filesystem path to a PNG file."
601
- },
602
- annotations: {
603
- type: "array",
604
- items: { $ref: "#/$defs/BboxAnnotation" },
605
- 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`)."
606
- },
607
- output: {
608
- type: "string",
609
- 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."
610
- },
611
- encode: {
612
- $ref: "#/$defs/EncodeOptions",
613
- 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.'
614
- }
615
- },
616
- $defs: {
617
- ...I,
618
- BboxAnnotation: ne,
619
- EncodeOptions: P
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 Qe(e, t) {
624
- try {
625
- const o = Ke(e), n = await R(o.image), s = U(o.annotations), c = t.annotator.toPng({
626
- originalDataUrl: n.dataUrl,
627
- annotationsSvg: s,
628
- width: n.dimensions.width,
629
- height: n.dimensions.height
630
- }), r = await S(c, n.dimensions, o.encode);
631
- if (o.output) {
632
- await O(o.output, r.bytes);
633
- const a = r.reason ? `, reason: ${r.reason}` : "";
634
- return {
635
- content: [
636
- {
637
- type: "text",
638
- text: `Wrote ${r.bytes.byteLength}-byte ${r.chosen.toUpperCase()} to ${o.output} (${r.width}×${r.height}, ${o.annotations.length} annotation${o.annotations.length === 1 ? "" : "s"}${a}).`
639
- }
640
- ]
641
- };
642
- }
643
- return {
644
- content: [
645
- {
646
- type: "image",
647
- data: et(r.bytes),
648
- mimeType: r.mimeType
649
- }
650
- ]
651
- };
652
- } catch (o) {
653
- return Ze(o);
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 Ke(e) {
657
- if (typeof e.image != "string")
658
- throw new w("`image` is required and must be a string.");
659
- if (!Array.isArray(e.annotations))
660
- throw new A("`annotations` is required and must be an array.");
661
- const t = e.annotations;
662
- let o;
663
- if (e.output !== void 0) {
664
- if (typeof e.output != "string")
665
- throw new A("`output` must be a string when provided.");
666
- if (!b(e.output))
667
- throw new A(
668
- `\`output\` path "${e.output}" must be absolute so the file lands at a predictable location.`
669
- );
670
- o = e.output;
671
- }
672
- let n;
673
- if (e.encode !== void 0) {
674
- if (typeof e.encode != "object" || e.encode === null)
675
- throw new A("`encode` must be an object when provided.");
676
- n = e.encode;
677
- }
678
- return { image: e.image, annotations: t, output: o, encode: n };
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
- class A extends Error {
681
- constructor(t) {
682
- super(t), this.name = "InvalidToolInputError";
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 Ze(e) {
686
- return {
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
- function et(e) {
692
- return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
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
- const J = "annot_annotate_url", tt = {
695
- name: J,
696
- 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.',
697
- inputSchema: {
698
- type: "object",
699
- required: ["url", "annotations"],
700
- additionalProperties: !1,
701
- properties: {
702
- url: {
703
- type: "string",
704
- format: "uri",
705
- description: "Page URL to capture."
706
- },
707
- annotations: {
708
- type: "array",
709
- items: { $ref: "#/$defs/LocatorAnnotation" },
710
- 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)."
711
- },
712
- viewport: {
713
- type: "object",
714
- additionalProperties: !1,
715
- properties: {
716
- width: { type: "integer", minimum: 1, default: 1280 },
717
- height: { type: "integer", minimum: 1, default: 800 },
718
- deviceScaleFactor: { type: "number", minimum: 0.1, default: 1 }
719
- },
720
- description: "Viewport size. Default 1280×800 at 1× device pixel ratio."
721
- },
722
- fullPage: {
723
- type: "boolean",
724
- default: !1,
725
- 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."
726
- },
727
- waitFor: {
728
- type: "string",
729
- enum: ["load", "domcontentloaded", "networkidle"],
730
- default: "load"
731
- },
732
- output: {
733
- type: "string",
734
- description: "Optional absolute filesystem path. When set, the annotated PNG is written here and the tool returns a text confirmation."
735
- },
736
- encode: {
737
- $ref: "#/$defs/EncodeOptions",
738
- description: "Optional encoder settings (`format` / `saveSizePreset` / `jpegPercent`). Set to shrink the output for ingestion into issue trackers / docs."
739
- }
740
- },
741
- $defs: {
742
- ...I,
743
- LocatorAnnotation: Ce,
744
- EncodeOptions: P
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
- class y extends Error {
749
- constructor(t) {
750
- super(t), this.name = "InvalidAnnotateUrlInputError";
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
- async function ot(e, t) {
754
- try {
755
- const o = rt(e), n = await W(t.pool, {
756
- url: o.url,
757
- viewport: o.viewport,
758
- fullPage: o.fullPage,
759
- waitFor: o.waitFor
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
- if (typeof e.url != "string" || e.url.length === 0)
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 nt(e) {
840
- return {
841
- content: [{ type: "text", text: e instanceof Error ? `${e.name}: ${e.message}` : `Unknown error: ${String(e)}` }],
842
- isError: !0
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 H(e) {
846
- return Buffer.from(e.buffer, e.byteOffset, e.byteLength).toString("base64");
1010
+ function U(e) {
1011
+ return {
1012
+ content: [{
1013
+ type: "text",
1014
+ text: e
1015
+ }],
1016
+ isError: !0
1017
+ };
847
1018
  }
848
- const Q = "annot_compare_screenshots", st = {
849
- name: Q,
850
- 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.",
851
- inputSchema: {
852
- type: "object",
853
- required: ["before", "after"],
854
- additionalProperties: !1,
855
- properties: {
856
- before: {
857
- type: "string",
858
- description: "`data:image/png;base64,...` URL or absolute filesystem path."
859
- },
860
- after: {
861
- type: "string",
862
- description: "`data:image/png;base64,...` URL or absolute filesystem path."
863
- },
864
- threshold: {
865
- type: "number",
866
- minimum: 0,
867
- maximum: 1,
868
- default: 0.1,
869
- description: "Pixelmatch sensitivity (0 = strict, 1 = permissive)."
870
- },
871
- includeChangeList: {
872
- type: "boolean",
873
- default: !1,
874
- description: "When true, append a text content block listing the changed-region bboxes alongside the annotated PNG."
875
- },
876
- output: { type: "string" },
877
- encode: { $ref: "#/$defs/EncodeOptions" }
878
- },
879
- $defs: {
880
- EncodeOptions: P
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
- class $ extends Error {
885
- constructor(t) {
886
- super(t), this.name = "InvalidCompareInputError";
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
- async function it(e, t) {
890
- try {
891
- if (typeof e.before != "string")
892
- throw new $("`before` is required and must be a string.");
893
- if (typeof e.after != "string")
894
- throw new $("`after` is required and must be a string.");
895
- const o = typeof e.threshold == "number" ? e.threshold : 0.1, n = e.includeChangeList === !0;
896
- let s;
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
- const K = "annot_redact_screenshot", at = {
957
- name: K,
958
- 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.",
959
- inputSchema: {
960
- type: "object",
961
- required: ["image", "regions"],
962
- additionalProperties: !1,
963
- properties: {
964
- image: {
965
- type: "string",
966
- description: "`data:image/png;base64,...` URL or absolute filesystem path."
967
- },
968
- regions: {
969
- type: "array",
970
- items: { $ref: "#/$defs/BboxRedactRegion" }
971
- },
972
- output: { type: "string" },
973
- encode: {
974
- $ref: "#/$defs/EncodeOptions",
975
- 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."
976
- }
977
- },
978
- $defs: {
979
- ...I,
980
- BboxRedactRegion: se,
981
- EncodeOptions: P
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
- class E extends Error {
986
- constructor(t) {
987
- super(t), this.name = "InvalidRedactInputError";
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
- async function ct(e) {
991
- try {
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
- const Z = "annot_redact_url", dt = {
1045
- name: Z,
1046
- 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.",
1047
- inputSchema: {
1048
- type: "object",
1049
- required: ["url", "regions"],
1050
- additionalProperties: !1,
1051
- properties: {
1052
- url: { type: "string", format: "uri" },
1053
- regions: {
1054
- type: "array",
1055
- items: { $ref: "#/$defs/LocatorRedactRegion" }
1056
- },
1057
- viewport: {
1058
- type: "object",
1059
- additionalProperties: !1,
1060
- properties: {
1061
- width: { type: "integer", minimum: 1, default: 1280 },
1062
- height: { type: "integer", minimum: 1, default: 800 },
1063
- deviceScaleFactor: { type: "number", minimum: 0.1, default: 1 }
1064
- }
1065
- },
1066
- fullPage: { type: "boolean", default: !1 },
1067
- waitFor: {
1068
- type: "string",
1069
- enum: ["load", "domcontentloaded", "networkidle"],
1070
- default: "load"
1071
- },
1072
- output: { type: "string" },
1073
- encode: { $ref: "#/$defs/EncodeOptions" }
1074
- },
1075
- $defs: {
1076
- ...I,
1077
- LocatorRedactRegion: Ue,
1078
- EncodeOptions: P
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
- class p extends Error {
1083
- constructor(t) {
1084
- super(t), this.name = "InvalidRedactUrlInputError";
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
- async function lt(e, t) {
1088
- try {
1089
- const o = ut(e), n = await W(t.pool, {
1090
- url: o.url,
1091
- viewport: o.viewport,
1092
- fullPage: o.fullPage,
1093
- waitFor: o.waitFor
1094
- });
1095
- let s;
1096
- try {
1097
- s = [];
1098
- for (const i of o.regions) {
1099
- const l = { bbox: await ht(n.handle.page, i) };
1100
- i.style !== void 0 && (l.style = i.style), i.color !== void 0 && (l.color = i.color), s.push(l);
1101
- }
1102
- } finally {
1103
- await n.handle.close();
1104
- }
1105
- const c = await Y(n.pngBytes, s), r = B(n.pngBytes), a = await S(c, r, o.encode);
1106
- if (o.output) {
1107
- await O(o.output, a.bytes);
1108
- const i = a.reason ? `, reason: ${a.reason}` : "";
1109
- return {
1110
- content: [
1111
- {
1112
- type: "text",
1113
- text: `Wrote ${a.bytes.byteLength}-byte redacted ${a.chosen.toUpperCase()} to ${o.output} (${a.width}×${a.height}, ${o.regions.length} region${o.regions.length === 1 ? "" : "s"}, captured from ${o.url}${i}).`
1114
- }
1115
- ]
1116
- };
1117
- }
1118
- return {
1119
- content: [
1120
- {
1121
- type: "image",
1122
- data: Buffer.from(
1123
- a.bytes.buffer,
1124
- a.bytes.byteOffset,
1125
- a.bytes.byteLength
1126
- ).toString("base64"),
1127
- mimeType: a.mimeType
1128
- }
1129
- ]
1130
- };
1131
- } catch (o) {
1132
- return {
1133
- content: [{ type: "text", text: o instanceof Error ? `${o.name}: ${o.message}` : `Unknown error: ${String(o)}` }],
1134
- isError: !0
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 ut(e) {
1139
- if (typeof e.url != "string" || e.url.length === 0)
1140
- throw new p("`url` is required and must be a non-empty string.");
1141
- if (!Array.isArray(e.regions))
1142
- throw new p("`regions` is required and must be an array.");
1143
- const t = e.regions;
1144
- let o;
1145
- if (e.viewport !== void 0) {
1146
- if (typeof e.viewport != "object" || e.viewport === null)
1147
- throw new p("`viewport` must be an object when provided.");
1148
- 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;
1149
- o = { width: i, height: d, deviceScaleFactor: l };
1150
- }
1151
- const n = e.fullPage === !0, s = e.waitFor === "domcontentloaded" || e.waitFor === "networkidle" ? e.waitFor : "load";
1152
- let c;
1153
- if (e.output !== void 0) {
1154
- if (typeof e.output != "string")
1155
- throw new p("`output` must be a string when provided.");
1156
- if (!b(e.output))
1157
- throw new p(`\`output\` path "${e.output}" must be absolute.`);
1158
- c = e.output;
1159
- }
1160
- let r;
1161
- if (e.encode !== void 0) {
1162
- if (typeof e.encode != "object" || e.encode === null)
1163
- throw new p("`encode` must be an object when provided.");
1164
- r = e.encode;
1165
- }
1166
- return { url: e.url, regions: t, viewport: o, fullPage: n, waitFor: s, output: c, encode: r };
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 ht(e, t) {
1169
- if (t.bbox !== void 0) return t.bbox;
1170
- if (t.locator !== void 0) return f(e, t.locator);
1171
- throw new p("redact region requires either `bbox` or `locator`.");
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
- const ft = "annot-mcp", pt = "0.1.0", mt = [
1174
- Je,
1175
- tt,
1176
- at,
1177
- dt,
1178
- st
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 gt(e = {}) {
1181
- const t = new de(
1182
- {
1183
- name: ft,
1184
- version: e.version ?? pt
1185
- },
1186
- {
1187
- capabilities: {
1188
- tools: {}
1189
- }
1190
- }
1191
- ), o = e.annotator ?? ie(e.annotatorOptions ?? {}), n = e.pool ?? ye();
1192
- return t.setRequestHandler(le, async () => ({ tools: mt })), t.setRequestHandler(ue, async (s) => {
1193
- const { name: c, arguments: r } = s.params;
1194
- let a;
1195
- switch (c) {
1196
- case V:
1197
- a = await Qe(r ?? {}, { annotator: o });
1198
- break;
1199
- case J:
1200
- a = await ot(r ?? {}, { annotator: o, pool: n });
1201
- break;
1202
- case K:
1203
- a = await ct(r ?? {});
1204
- break;
1205
- case Z:
1206
- a = await lt(r ?? {}, { pool: n });
1207
- break;
1208
- case Q:
1209
- a = await it(r ?? {}, { annotator: o });
1210
- break;
1211
- default:
1212
- a = {
1213
- content: [{ type: "text", text: `Unknown tool: ${c}` }],
1214
- isError: !0
1215
- };
1216
- }
1217
- return a;
1218
- }), t;
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
- async function Pt() {
1221
- const e = gt(), t = new he();
1222
- await e.connect(t);
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
- export {
1225
- V as ANNOTATE_SCREENSHOT_TOOL_NAME,
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 };