alanbox 0.1.2 → 0.1.3

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.
Files changed (82) hide show
  1. package/0boxer/AGENTS.md +25 -0
  2. package/0boxer/src/AGENTS.md +16 -0
  3. package/0boxer/src/cli.js +53 -0
  4. package/0boxer/src/commands/AGENTS.md +15 -0
  5. package/{0commondflowv1 → 0boxer}/src/commands/install.js +9 -0
  6. package/{0commondflowv1 → 1swarmer}/AGENTS.md +11 -12
  7. package/{0commondflowv1 → 1swarmer}/src/AGENTS.md +6 -5
  8. package/{0commondflowv1 → 1swarmer}/src/args.js +1 -1
  9. package/{0commondflowv1 → 1swarmer}/src/cli.js +5 -21
  10. package/{0commondflowv1 → 1swarmer}/src/commands/AGENTS.md +7 -8
  11. package/{0commondflowv1 → 1swarmer}/src/commands/doctor.js +2 -2
  12. package/{0commondflowv1 → 1swarmer}/src/core/AGENTS.md +2 -2
  13. package/{0commondflowv1 → 1swarmer}/src/core/prompt-templates.js +1 -1
  14. package/{0commondflowv1 → 1swarmer}/src/core/storage.js +1 -1
  15. package/{0commondflowv1 → 1swarmer}/src/prompt/AGENTS.md +1 -1
  16. package/{0commondflowv1 → 1swarmer}/src/prompt/default.md +1 -1
  17. package/{0commondflowv1 → 1swarmer}/src/prompt/synthesizer.md +1 -1
  18. package/{0commondflowv1 → 1swarmer}/src/prompt/verifier.md +1 -1
  19. package/{0commondflowv1 → 1swarmer}/src/runner/AGENTS.md +3 -3
  20. package/2designer/LICENSE +21 -0
  21. package/2designer/README.md +39 -0
  22. package/2designer/dist/cdp-engine-A5WTMTVF.js +10 -0
  23. package/2designer/dist/cdp-engine-A5WTMTVF.js.map +1 -0
  24. package/2designer/dist/cdp-engine-JK2XVDHK.js +314 -0
  25. package/2designer/dist/cdp-engine-JK2XVDHK.js.map +1 -0
  26. package/2designer/dist/chunk-JVF26NXD.js +313 -0
  27. package/2designer/dist/chunk-JVF26NXD.js.map +1 -0
  28. package/2designer/dist/chunk-NLYFLQ3C.js +74 -0
  29. package/2designer/dist/chunk-NLYFLQ3C.js.map +1 -0
  30. package/2designer/dist/chunk-NQ3ASZUE.js +185 -0
  31. package/2designer/dist/chunk-NQ3ASZUE.js.map +1 -0
  32. package/2designer/dist/chunk-SKEIVBOU.js +58 -0
  33. package/2designer/dist/chunk-SKEIVBOU.js.map +1 -0
  34. package/2designer/dist/chunk-UVKSRKXR.js +71 -0
  35. package/2designer/dist/chunk-UVKSRKXR.js.map +1 -0
  36. package/2designer/dist/cli.js +498 -0
  37. package/2designer/dist/cli.js.map +1 -0
  38. package/2designer/dist/index.d.ts +129 -0
  39. package/2designer/dist/index.js +230 -0
  40. package/2designer/dist/index.js.map +1 -0
  41. package/2designer/dist/playwright-engine-3YKJOUNU.js +8 -0
  42. package/2designer/dist/playwright-engine-3YKJOUNU.js.map +1 -0
  43. package/2designer/dist/playwright-engine-YBRDIUHF.js +186 -0
  44. package/2designer/dist/playwright-engine-YBRDIUHF.js.map +1 -0
  45. package/2designer/dist/tint-I3FTT23O.js +60 -0
  46. package/2designer/dist/tint-I3FTT23O.js.map +1 -0
  47. package/2designer/dist/tint-RUSSUAWA.js +7 -0
  48. package/2designer/dist/tint-RUSSUAWA.js.map +1 -0
  49. package/2designer/package.json +56 -0
  50. package/README.md +11 -8
  51. package/bin/alanbox.js +11 -0
  52. package/bin/designer.js +10 -0
  53. package/bin/swarmer.js +11 -0
  54. package/cli.js +153 -0
  55. package/hooks/hooks.json +1 -1
  56. package/package.json +24 -11
  57. package/plugin/AGENTS.md +2 -2
  58. package/plugin/plugin.json +7 -7
  59. package/shared/AGENTS.md +15 -0
  60. package/shared/package-args.js +68 -0
  61. package/skills/AGENTS.md +9 -5
  62. package/skills/aitool/SKILL.md +36 -0
  63. package/skills/desginer/SKILL.md +122 -0
  64. package/skills/swarmer/SKILL.md +109 -0
  65. package/bin/multirunagent.js +0 -15
  66. package/skills/aibox-swam/SKILL.md +0 -77
  67. package/skills/sub-codex-doctor/SKILL.md +0 -27
  68. package/skills/sub-codex-swarm/SKILL.md +0 -56
  69. /package/{0commondflowv1 → 1swarmer}/res/three-lens-review.js +0 -0
  70. /package/{0commondflowv1 → 1swarmer}/src/commands/info.js +0 -0
  71. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/auto.js +0 -0
  72. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/custom.js +0 -0
  73. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/index.js +0 -0
  74. /package/{0commondflowv1 → 1swarmer}/src/core/handoff.js +0 -0
  75. /package/{0commondflowv1 → 1swarmer}/src/core/prompt-builder.js +0 -0
  76. /package/{0commondflowv1 → 1swarmer}/src/core/swarm-executor.js +0 -0
  77. /package/{0commondflowv1 → 1swarmer}/src/core/workers.js +0 -0
  78. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-planner.js +0 -0
  79. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-storage.js +0 -0
  80. /package/{0commondflowv1 → 1swarmer}/src/prompt/reviewer.md +0 -0
  81. /package/{0commondflowv1 → 1swarmer}/src/runner/codex-runner.js +0 -0
  82. /package/{0commondflowv1 → 1swarmer}/src/runner/config.json +0 -0
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ MEASURE_RETRY_INTERVAL_MS,
5
+ MEASURE_WAIT_TIMEOUT_MS,
6
+ RELEVANT_PROPS,
7
+ REQUIRED_MEASURE_PROPS
8
+ } from "./chunk-NLYFLQ3C.js";
9
+
10
+ // src/engine/cdp/cdp-client.ts
11
+ import WebSocket from "ws";
12
+ async function listTargets(host, port) {
13
+ const res = await fetch(`http://${host}:${port}/json`);
14
+ if (!res.ok) throw new Error(`CDP target list failed: ${res.status}`);
15
+ return res.json();
16
+ }
17
+ var CdpClient = class _CdpClient {
18
+ ws;
19
+ nextId = 1;
20
+ pending = /* @__PURE__ */ new Map();
21
+ ready;
22
+ constructor(wsUrl) {
23
+ this.ws = new WebSocket(wsUrl);
24
+ this.ready = new Promise((resolve, reject) => {
25
+ this.ws.once("open", resolve);
26
+ this.ws.once("error", reject);
27
+ });
28
+ this.ws.on("message", (data) => {
29
+ const payload = JSON.parse(data.toString());
30
+ if (payload.id != null) {
31
+ const p = this.pending.get(payload.id);
32
+ if (p) {
33
+ this.pending.delete(payload.id);
34
+ if (payload.error) {
35
+ p.reject(new Error(`CDP: ${payload.error.message}`));
36
+ } else {
37
+ p.resolve(payload.result);
38
+ }
39
+ }
40
+ }
41
+ });
42
+ this.ws.on("close", () => {
43
+ for (const p of this.pending.values()) {
44
+ p.reject(new Error("CDP connection closed"));
45
+ }
46
+ this.pending.clear();
47
+ });
48
+ }
49
+ static async connect(target) {
50
+ const client = new _CdpClient(target.webSocketDebuggerUrl);
51
+ await client.ready;
52
+ await client.call("DOM.enable");
53
+ await client.call("CSS.enable");
54
+ await client.call("Page.enable");
55
+ await client.call("Runtime.enable");
56
+ return client;
57
+ }
58
+ async call(method, params, timeout = 3e4) {
59
+ const id = this.nextId++;
60
+ return new Promise((resolve, reject) => {
61
+ const timer = setTimeout(() => {
62
+ this.pending.delete(id);
63
+ reject(new Error(`CDP call '${method}' timed out after ${timeout}ms`));
64
+ }, timeout);
65
+ this.pending.set(id, {
66
+ resolve: (v) => {
67
+ clearTimeout(timer);
68
+ resolve(v);
69
+ },
70
+ reject: (e) => {
71
+ clearTimeout(timer);
72
+ reject(e);
73
+ }
74
+ });
75
+ this.ws.send(JSON.stringify({ id, method, params }));
76
+ });
77
+ }
78
+ close() {
79
+ this.ws.close();
80
+ }
81
+ };
82
+ async function connectToPage(host, port, urlFilter) {
83
+ const targets = await listTargets(host, port);
84
+ const pages = targets.filter((t) => t.type === "page");
85
+ if (pages.length === 0) throw new Error("No page targets found");
86
+ const target = urlFilter ? pages.find((t) => t.url.includes(urlFilter)) ?? pages[0] : pages[0];
87
+ const client = await CdpClient.connect(target);
88
+ return { client, target };
89
+ }
90
+
91
+ // src/engine/cdp/cdp-engine.ts
92
+ function buildClip(model, viewport, padding = 0) {
93
+ const q = model.border?.quad ?? model.content.quad;
94
+ const minX = Math.min(q[0], q[2], q[4], q[6]);
95
+ const maxX = Math.max(q[0], q[2], q[4], q[6]);
96
+ const minY = Math.min(q[1], q[3], q[5], q[7]);
97
+ const maxY = Math.max(q[1], q[3], q[5], q[7]);
98
+ const x = Math.max(0, minX - padding);
99
+ const y = Math.max(0, minY - padding);
100
+ const width = Math.min(viewport.width, maxX + padding) - x;
101
+ const height = Math.min(viewport.height, maxY + padding) - y;
102
+ return { x, y, width, height };
103
+ }
104
+ var CdpEngine = class _CdpEngine {
105
+ constructor(client, pageUrl) {
106
+ this.client = client;
107
+ this.pageUrl = pageUrl;
108
+ }
109
+ client;
110
+ pageUrl;
111
+ static async create(host, port, urlFilter) {
112
+ const { client, target } = await connectToPage(host, port, urlFilter);
113
+ return new _CdpEngine(client, target.url);
114
+ }
115
+ async screenshot(options) {
116
+ if (options?.selector) {
117
+ const safeSelector = JSON.stringify(options.selector);
118
+ const { result } = await this.client.call("Runtime.evaluate", {
119
+ expression: `(() => {
120
+ const el = document.querySelector(${safeSelector});
121
+ if (!el) throw new Error('Element not found: ' + ${safeSelector});
122
+ return JSON.stringify(el.getBoundingClientRect());
123
+ })()`,
124
+ returnByValue: true
125
+ });
126
+ if (result.subtype === "error") {
127
+ throw new Error(result.description || `Element not found: ${options.selector}`);
128
+ }
129
+ const rect = JSON.parse(result.value);
130
+ const viewport = await this.getViewport();
131
+ const clip = {
132
+ x: Math.max(0, rect.x),
133
+ y: Math.max(0, rect.y),
134
+ width: Math.min(viewport.width - rect.x, rect.width),
135
+ height: Math.min(viewport.height - rect.y, rect.height),
136
+ scale: 1
137
+ };
138
+ const { data: data2 } = await this.client.call("Page.captureScreenshot", {
139
+ format: "png",
140
+ clip,
141
+ fromSurface: true
142
+ });
143
+ return Buffer.from(data2, "base64");
144
+ }
145
+ const { data } = await this.client.call("Page.captureScreenshot", {
146
+ format: "png",
147
+ fromSurface: true
148
+ });
149
+ return Buffer.from(data, "base64");
150
+ }
151
+ async measure(selector, depth = 1, frameSelector) {
152
+ const safeSelector = JSON.stringify(selector);
153
+ const safeFrameSelector = JSON.stringify(frameSelector);
154
+ const safeProps = JSON.stringify(RELEVANT_PROPS);
155
+ const safeRequiredProps = JSON.stringify(REQUIRED_MEASURE_PROPS);
156
+ const safeDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1;
157
+ const js = `
158
+ (() => {
159
+ function toBBox(rect, origin) {
160
+ const originX = origin ? origin.x : 0;
161
+ const originY = origin ? origin.y : 0;
162
+ const originLeft = origin ? origin.left : 0;
163
+ const originTop = origin ? origin.top : 0;
164
+ return {
165
+ x: +(rect.x - originX).toFixed(1),
166
+ y: +(rect.y - originY).toFixed(1),
167
+ width: +rect.width.toFixed(1),
168
+ height: +rect.height.toFixed(1),
169
+ top: +(rect.top - originTop).toFixed(1),
170
+ right: +(rect.right - originLeft).toFixed(1),
171
+ bottom: +(rect.bottom - originTop).toFixed(1),
172
+ left: +(rect.left - originLeft).toFixed(1),
173
+ };
174
+ }
175
+
176
+ function isValidBBox(bbox) {
177
+ return Object.values(bbox).every(Number.isFinite);
178
+ }
179
+
180
+ function collectStyle(win, el) {
181
+ const cs = win.getComputedStyle(el);
182
+ const style = {};
183
+ ${safeProps}.forEach(p => {
184
+ style[p] = p === 'font' ? cs.font : cs.getPropertyValue(p);
185
+ });
186
+ return style;
187
+ }
188
+
189
+ function hasRequiredStyle(style) {
190
+ return ${safeRequiredProps}.every(p => typeof style[p] === 'string' && style[p] !== '');
191
+ }
192
+
193
+ function collectChildren(parent, parentRect, currentDepth, maxDepth) {
194
+ if (currentDepth >= maxDepth) return [];
195
+ const result = [];
196
+ for (const child of parent.children) {
197
+ const cr = child.getBoundingClientRect();
198
+ const node = {
199
+ tag: child.tagName.toLowerCase(),
200
+ className: (child.className || '').toString().substring(0, 120),
201
+ bbox: toBBox(cr, parentRect),
202
+ text: (child.textContent || '').substring(0, 80).trim() || undefined,
203
+ };
204
+ if (currentDepth + 1 < maxDepth && child.children.length > 0) {
205
+ node.children = collectChildren(child, cr, currentDepth + 1, maxDepth);
206
+ }
207
+ result.push(node);
208
+ }
209
+ return result;
210
+ }
211
+
212
+ const sel = ${safeSelector};
213
+ const frameSel = ${safeFrameSelector};
214
+ let doc = document;
215
+ let win = window;
216
+ if (frameSel) {
217
+ const frame = document.querySelector(frameSel);
218
+ if (!frame || !frame.contentDocument || !frame.contentWindow) return JSON.stringify(null);
219
+ doc = frame.contentDocument;
220
+ win = frame.contentWindow;
221
+ }
222
+
223
+ const el = doc.querySelector(sel);
224
+ if (!el) return JSON.stringify(null);
225
+ const r = el.getBoundingClientRect();
226
+ const bbox = toBBox(r);
227
+ const style = collectStyle(win, el);
228
+ if (!isValidBBox(bbox)) return JSON.stringify(null);
229
+ if (!hasRequiredStyle(style)) return JSON.stringify(null);
230
+ if (bbox.width <= 0 || bbox.height <= 0 || style.display === 'none' || style.visibility === 'hidden') {
231
+ return JSON.stringify(null);
232
+ }
233
+
234
+ return JSON.stringify({
235
+ bbox,
236
+ computedStyle: style,
237
+ children: ${safeDepth} > 0 ? collectChildren(el, r, 0, ${safeDepth}) : [],
238
+ });
239
+ })()
240
+ `;
241
+ const deadline = Date.now() + MEASURE_WAIT_TIMEOUT_MS;
242
+ let lastError;
243
+ while (Date.now() <= deadline) {
244
+ try {
245
+ const { result, exceptionDetails } = await this.client.call("Runtime.evaluate", {
246
+ expression: js,
247
+ returnByValue: true
248
+ });
249
+ if (exceptionDetails || result.subtype === "error") {
250
+ throw new Error(result.description || exceptionDetails?.text || "measure failed");
251
+ }
252
+ const parsed = JSON.parse(result.value);
253
+ if (parsed) {
254
+ return frameSelector ? { selector, frameSelector, ...parsed } : { selector, ...parsed };
255
+ }
256
+ lastError = new Error(
257
+ frameSelector ? `Element not ready or incomplete: ${selector} in frame ${frameSelector}` : `Element not ready or incomplete: ${selector}`
258
+ );
259
+ } catch (error) {
260
+ lastError = error instanceof Error ? error : new Error(String(error));
261
+ }
262
+ await new Promise((resolve) => setTimeout(resolve, MEASURE_RETRY_INTERVAL_MS));
263
+ }
264
+ throw lastError ?? new Error("measure failed");
265
+ }
266
+ async evaluate(expression) {
267
+ const { result } = await this.client.call("Runtime.evaluate", {
268
+ expression,
269
+ returnByValue: true
270
+ });
271
+ return result.value;
272
+ }
273
+ async injectOverlay(params) {
274
+ const { tintDesignImage } = await import("./tint-I3FTT23O.js");
275
+ const tintedBuf = await tintDesignImage(params.designImagePath);
276
+ const base64 = tintedBuf.toString("base64");
277
+ const js = `
278
+ (() => {
279
+ let overlay = document.getElementById('__design_ruler_overlay__');
280
+ if (!overlay) {
281
+ overlay = document.createElement('img');
282
+ overlay.id = '__design_ruler_overlay__';
283
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:auto;pointer-events:none;z-index:999999;';
284
+ document.body.appendChild(overlay);
285
+ }
286
+ overlay.src = 'data:image/png;base64,' + ${JSON.stringify(base64)};
287
+ overlay.style.opacity = String(${Number(params.opacity)});
288
+ overlay.style.transform = 'translate(' + ${Number(params.offsetX)} + 'px, ' + ${Number(params.offsetY)} + 'px) scale(' + ${Number(params.scale)} + ')';
289
+ overlay.style.transformOrigin = 'top left';
290
+ if (${Number(params.scrollY ?? 0)} > 0) {
291
+ window.scrollTo(0, ${Number(params.scrollY ?? 0)});
292
+ }
293
+ return 'overlay injected';
294
+ })()
295
+ `;
296
+ await this.evaluate(js);
297
+ }
298
+ async captureOverlay(options) {
299
+ return this.screenshot(options);
300
+ }
301
+ async close() {
302
+ this.client.close();
303
+ }
304
+ async getViewport() {
305
+ return this.evaluate(
306
+ "JSON.parse(JSON.stringify({ width: window.innerWidth, height: window.innerHeight }))"
307
+ );
308
+ }
309
+ };
310
+ export {
311
+ CdpEngine,
312
+ buildClip
313
+ };
314
+ //# sourceMappingURL=cdp-engine-JK2XVDHK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/cdp/cdp-client.ts","../src/engine/cdp/cdp-engine.ts"],"sourcesContent":["import WebSocket from 'ws'\r\n\r\nexport interface CdpTarget {\r\n id: string\r\n title: string\r\n type: string\r\n url: string\r\n webSocketDebuggerUrl: string\r\n}\r\n\r\nexport async function listTargets(host: string, port: number): Promise<CdpTarget[]> {\r\n const res = await fetch(`http://${host}:${port}/json`)\r\n if (!res.ok) throw new Error(`CDP target list failed: ${res.status}`)\r\n return res.json() as Promise<CdpTarget[]>\r\n}\r\n\r\nexport class CdpClient {\r\n private ws: WebSocket\r\n private nextId = 1\r\n private pending = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>()\r\n private ready: Promise<void>\r\n\r\n private constructor(wsUrl: string) {\r\n this.ws = new WebSocket(wsUrl)\r\n this.ready = new Promise((resolve, reject) => {\r\n this.ws.once('open', resolve)\r\n this.ws.once('error', reject)\r\n })\r\n this.ws.on('message', (data: WebSocket.Data) => {\r\n const payload = JSON.parse(data.toString())\r\n if (payload.id != null) {\r\n const p = this.pending.get(payload.id)\r\n if (p) {\r\n this.pending.delete(payload.id)\r\n if (payload.error) {\r\n p.reject(new Error(`CDP: ${payload.error.message}`))\r\n } else {\r\n p.resolve(payload.result)\r\n }\r\n }\r\n }\r\n })\r\n this.ws.on('close', () => {\r\n for (const p of this.pending.values()) {\r\n p.reject(new Error('CDP connection closed'))\r\n }\r\n this.pending.clear()\r\n })\r\n }\r\n\r\n static async connect(target: CdpTarget): Promise<CdpClient> {\r\n const client = new CdpClient(target.webSocketDebuggerUrl)\r\n await client.ready\r\n await client.call('DOM.enable')\r\n await client.call('CSS.enable')\r\n await client.call('Page.enable')\r\n await client.call('Runtime.enable')\r\n return client\r\n }\r\n\r\n async call<T = any>(method: string, params?: Record<string, unknown>, timeout = 30000): Promise<T> {\r\n const id = this.nextId++\r\n return new Promise<T>((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n this.pending.delete(id)\r\n reject(new Error(`CDP call '${method}' timed out after ${timeout}ms`))\r\n }, timeout)\r\n this.pending.set(id, {\r\n resolve: (v) => { clearTimeout(timer); resolve(v) },\r\n reject: (e) => { clearTimeout(timer); reject(e) },\r\n })\r\n this.ws.send(JSON.stringify({ id, method, params }))\r\n })\r\n }\r\n\r\n close(): void {\r\n this.ws.close()\r\n }\r\n}\r\n\r\nexport async function connectToPage(\r\n host: string,\r\n port: number,\r\n urlFilter?: string,\r\n): Promise<{ client: CdpClient; target: CdpTarget }> {\r\n const targets = await listTargets(host, port)\r\n const pages = targets.filter(t => t.type === 'page')\r\n if (pages.length === 0) throw new Error('No page targets found')\r\n\r\n const target = urlFilter\r\n ? pages.find(t => t.url.includes(urlFilter)) ?? pages[0]\r\n : pages[0]\r\n\r\n const client = await CdpClient.connect(target)\r\n return { client, target }\r\n}\r\n","import type { RuntimeEngine, MeasureResult, ScreenshotOptions, OverlayParams } from '../types.js'\nimport {\n MEASURE_RETRY_INTERVAL_MS,\n MEASURE_WAIT_TIMEOUT_MS,\n RELEVANT_PROPS,\n REQUIRED_MEASURE_PROPS,\n} from '../constants.js'\nimport { CdpClient, connectToPage } from './cdp-client.js'\n\r\ninterface BoxModelQuad {\r\n quad: number[]\r\n}\r\n\r\nexport function buildClip(\r\n model: { content: BoxModelQuad; border?: BoxModelQuad },\r\n viewport: { width: number; height: number },\r\n padding = 0,\r\n): { x: number; y: number; width: number; height: number } {\r\n const q = model.border?.quad ?? model.content.quad\r\n const minX = Math.min(q[0], q[2], q[4], q[6])\r\n const maxX = Math.max(q[0], q[2], q[4], q[6])\r\n const minY = Math.min(q[1], q[3], q[5], q[7])\r\n const maxY = Math.max(q[1], q[3], q[5], q[7])\r\n\r\n const x = Math.max(0, minX - padding)\r\n const y = Math.max(0, minY - padding)\r\n const width = Math.min(viewport.width, maxX + padding) - x\r\n const height = Math.min(viewport.height, maxY + padding) - y\r\n\r\n return { x, y, width, height }\r\n}\r\n\r\nexport class CdpEngine implements RuntimeEngine {\r\n private constructor(\r\n private client: CdpClient,\r\n private pageUrl: string,\r\n ) {}\r\n\r\n static async create(host: string, port: number, urlFilter?: string): Promise<CdpEngine> {\r\n const { client, target } = await connectToPage(host, port, urlFilter)\r\n return new CdpEngine(client, target.url)\r\n }\r\n\r\n async screenshot(options?: ScreenshotOptions): Promise<Buffer> {\r\n if (options?.selector) {\r\n // Use JSON.stringify to safely escape the selector\r\n const safeSelector = JSON.stringify(options.selector)\r\n const { result } = await this.client.call<any>('Runtime.evaluate', {\r\n expression: `(() => {\r\n const el = document.querySelector(${safeSelector});\r\n if (!el) throw new Error('Element not found: ' + ${safeSelector});\r\n return JSON.stringify(el.getBoundingClientRect());\r\n })()`,\r\n returnByValue: true,\r\n })\r\n if (result.subtype === 'error') {\r\n throw new Error(result.description || `Element not found: ${options.selector}`)\r\n }\r\n const rect = JSON.parse(result.value)\r\n const viewport = await this.getViewport()\r\n const clip = {\r\n x: Math.max(0, rect.x),\r\n y: Math.max(0, rect.y),\r\n width: Math.min(viewport.width - rect.x, rect.width),\r\n height: Math.min(viewport.height - rect.y, rect.height),\r\n scale: 1,\r\n }\r\n const { data } = await this.client.call<{ data: string }>('Page.captureScreenshot', {\r\n format: 'png',\r\n clip,\r\n fromSurface: true,\r\n })\r\n return Buffer.from(data, 'base64')\r\n }\r\n\r\n const { data } = await this.client.call<{ data: string }>('Page.captureScreenshot', {\r\n format: 'png',\r\n fromSurface: true,\r\n })\r\n return Buffer.from(data, 'base64')\r\n }\r\n\r\n async measure(selector: string, depth = 1, frameSelector?: string): Promise<MeasureResult> {\n // All user inputs passed via JSON.stringify to prevent injection\n const safeSelector = JSON.stringify(selector)\n const safeFrameSelector = JSON.stringify(frameSelector)\n const safeProps = JSON.stringify(RELEVANT_PROPS)\n const safeRequiredProps = JSON.stringify(REQUIRED_MEASURE_PROPS)\n const safeDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1\n\n const js = `\n (() => {\n function toBBox(rect, origin) {\n const originX = origin ? origin.x : 0;\n const originY = origin ? origin.y : 0;\n const originLeft = origin ? origin.left : 0;\n const originTop = origin ? origin.top : 0;\n return {\n x: +(rect.x - originX).toFixed(1),\n y: +(rect.y - originY).toFixed(1),\n width: +rect.width.toFixed(1),\n height: +rect.height.toFixed(1),\n top: +(rect.top - originTop).toFixed(1),\n right: +(rect.right - originLeft).toFixed(1),\n bottom: +(rect.bottom - originTop).toFixed(1),\n left: +(rect.left - originLeft).toFixed(1),\n };\n }\n\n function isValidBBox(bbox) {\n return Object.values(bbox).every(Number.isFinite);\n }\n\n function collectStyle(win, el) {\n const cs = win.getComputedStyle(el);\n const style = {};\n ${safeProps}.forEach(p => {\n style[p] = p === 'font' ? cs.font : cs.getPropertyValue(p);\n });\n return style;\n }\n\n function hasRequiredStyle(style) {\n return ${safeRequiredProps}.every(p => typeof style[p] === 'string' && style[p] !== '');\n }\n\n function collectChildren(parent, parentRect, currentDepth, maxDepth) {\n if (currentDepth >= maxDepth) return [];\n const result = [];\n for (const child of parent.children) {\n const cr = child.getBoundingClientRect();\n const node = {\r\n tag: child.tagName.toLowerCase(),\n className: (child.className || '').toString().substring(0, 120),\n bbox: toBBox(cr, parentRect),\n text: (child.textContent || '').substring(0, 80).trim() || undefined,\n };\n if (currentDepth + 1 < maxDepth && child.children.length > 0) {\n node.children = collectChildren(child, cr, currentDepth + 1, maxDepth);\r\n }\r\n result.push(node);\r\n }\r\n return result;\n }\n\n const sel = ${safeSelector};\n const frameSel = ${safeFrameSelector};\n let doc = document;\n let win = window;\n if (frameSel) {\n const frame = document.querySelector(frameSel);\n if (!frame || !frame.contentDocument || !frame.contentWindow) return JSON.stringify(null);\n doc = frame.contentDocument;\n win = frame.contentWindow;\n }\n\n const el = doc.querySelector(sel);\n if (!el) return JSON.stringify(null);\n const r = el.getBoundingClientRect();\n const bbox = toBBox(r);\n const style = collectStyle(win, el);\n if (!isValidBBox(bbox)) return JSON.stringify(null);\n if (!hasRequiredStyle(style)) return JSON.stringify(null);\n if (bbox.width <= 0 || bbox.height <= 0 || style.display === 'none' || style.visibility === 'hidden') {\n return JSON.stringify(null);\n }\n\n return JSON.stringify({\n bbox,\n computedStyle: style,\n children: ${safeDepth} > 0 ? collectChildren(el, r, 0, ${safeDepth}) : [],\n });\n })()\n `\n\n const deadline = Date.now() + MEASURE_WAIT_TIMEOUT_MS\n let lastError: Error | undefined\n while (Date.now() <= deadline) {\n try {\n const { result, exceptionDetails } = await this.client.call<any>('Runtime.evaluate', {\n expression: js,\n returnByValue: true,\n })\n if (exceptionDetails || result.subtype === 'error') {\n throw new Error(result.description || exceptionDetails?.text || 'measure failed')\n }\n\n const parsed = JSON.parse(result.value)\n if (parsed) {\n return frameSelector ? { selector, frameSelector, ...parsed } : { selector, ...parsed }\n }\n lastError = new Error(\n frameSelector\n ? `Element not ready or incomplete: ${selector} in frame ${frameSelector}`\n : `Element not ready or incomplete: ${selector}`,\n )\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error))\n }\n await new Promise(resolve => setTimeout(resolve, MEASURE_RETRY_INTERVAL_MS))\n }\n\n throw lastError ?? new Error('measure failed')\n }\n\r\n async evaluate<T = unknown>(expression: string): Promise<T> {\r\n const { result } = await this.client.call<any>('Runtime.evaluate', {\r\n expression,\r\n returnByValue: true,\r\n })\r\n return result.value as T\r\n }\r\n\r\n async injectOverlay(params: OverlayParams): Promise<void> {\r\n const { tintDesignImage } = await import('../../overlay/tint.js')\r\n const tintedBuf = await tintDesignImage(params.designImagePath)\r\n const base64 = tintedBuf.toString('base64')\r\n // Numeric params are safe, base64 is safe, no user strings interpolated\r\n const js = `\r\n (() => {\r\n let overlay = document.getElementById('__design_ruler_overlay__');\r\n if (!overlay) {\r\n overlay = document.createElement('img');\r\n overlay.id = '__design_ruler_overlay__';\r\n overlay.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:auto;pointer-events:none;z-index:999999;';\r\n document.body.appendChild(overlay);\r\n }\r\n overlay.src = 'data:image/png;base64,' + ${JSON.stringify(base64)};\r\n overlay.style.opacity = String(${Number(params.opacity)});\r\n overlay.style.transform = 'translate(' + ${Number(params.offsetX)} + 'px, ' + ${Number(params.offsetY)} + 'px) scale(' + ${Number(params.scale)} + ')';\r\n overlay.style.transformOrigin = 'top left';\r\n if (${Number(params.scrollY ?? 0)} > 0) {\r\n window.scrollTo(0, ${Number(params.scrollY ?? 0)});\r\n }\r\n return 'overlay injected';\r\n })()\r\n `\r\n await this.evaluate(js)\r\n }\r\n\r\n async captureOverlay(options?: import('../types.js').ScreenshotOptions): Promise<Buffer> {\r\n return this.screenshot(options)\r\n }\r\n\r\n async close(): Promise<void> {\r\n this.client.close()\r\n }\r\n\r\n private async getViewport(): Promise<{ width: number; height: number }> {\r\n return this.evaluate<{ width: number; height: number }>(\r\n 'JSON.parse(JSON.stringify({ width: window.innerWidth, height: window.innerHeight }))'\r\n )\r\n }\r\n}\r\n"],"mappings":"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","names":["data"]}
@@ -0,0 +1,313 @@
1
+ import {
2
+ MEASURE_RETRY_INTERVAL_MS,
3
+ MEASURE_WAIT_TIMEOUT_MS,
4
+ RELEVANT_PROPS,
5
+ REQUIRED_MEASURE_PROPS
6
+ } from "./chunk-UVKSRKXR.js";
7
+
8
+ // src/engine/cdp/cdp-client.ts
9
+ import WebSocket from "ws";
10
+ async function listTargets(host, port) {
11
+ const res = await fetch(`http://${host}:${port}/json`);
12
+ if (!res.ok) throw new Error(`CDP target list failed: ${res.status}`);
13
+ return res.json();
14
+ }
15
+ var CdpClient = class _CdpClient {
16
+ ws;
17
+ nextId = 1;
18
+ pending = /* @__PURE__ */ new Map();
19
+ ready;
20
+ constructor(wsUrl) {
21
+ this.ws = new WebSocket(wsUrl);
22
+ this.ready = new Promise((resolve, reject) => {
23
+ this.ws.once("open", resolve);
24
+ this.ws.once("error", reject);
25
+ });
26
+ this.ws.on("message", (data) => {
27
+ const payload = JSON.parse(data.toString());
28
+ if (payload.id != null) {
29
+ const p = this.pending.get(payload.id);
30
+ if (p) {
31
+ this.pending.delete(payload.id);
32
+ if (payload.error) {
33
+ p.reject(new Error(`CDP: ${payload.error.message}`));
34
+ } else {
35
+ p.resolve(payload.result);
36
+ }
37
+ }
38
+ }
39
+ });
40
+ this.ws.on("close", () => {
41
+ for (const p of this.pending.values()) {
42
+ p.reject(new Error("CDP connection closed"));
43
+ }
44
+ this.pending.clear();
45
+ });
46
+ }
47
+ static async connect(target) {
48
+ const client = new _CdpClient(target.webSocketDebuggerUrl);
49
+ await client.ready;
50
+ await client.call("DOM.enable");
51
+ await client.call("CSS.enable");
52
+ await client.call("Page.enable");
53
+ await client.call("Runtime.enable");
54
+ return client;
55
+ }
56
+ async call(method, params, timeout = 3e4) {
57
+ const id = this.nextId++;
58
+ return new Promise((resolve, reject) => {
59
+ const timer = setTimeout(() => {
60
+ this.pending.delete(id);
61
+ reject(new Error(`CDP call '${method}' timed out after ${timeout}ms`));
62
+ }, timeout);
63
+ this.pending.set(id, {
64
+ resolve: (v) => {
65
+ clearTimeout(timer);
66
+ resolve(v);
67
+ },
68
+ reject: (e) => {
69
+ clearTimeout(timer);
70
+ reject(e);
71
+ }
72
+ });
73
+ this.ws.send(JSON.stringify({ id, method, params }));
74
+ });
75
+ }
76
+ close() {
77
+ this.ws.close();
78
+ }
79
+ };
80
+ async function connectToPage(host, port, urlFilter) {
81
+ const targets = await listTargets(host, port);
82
+ const pages = targets.filter((t) => t.type === "page");
83
+ if (pages.length === 0) throw new Error("No page targets found");
84
+ const target = urlFilter ? pages.find((t) => t.url.includes(urlFilter)) ?? pages[0] : pages[0];
85
+ const client = await CdpClient.connect(target);
86
+ return { client, target };
87
+ }
88
+
89
+ // src/engine/cdp/cdp-engine.ts
90
+ function buildClip(model, viewport, padding = 0) {
91
+ const q = model.border?.quad ?? model.content.quad;
92
+ const minX = Math.min(q[0], q[2], q[4], q[6]);
93
+ const maxX = Math.max(q[0], q[2], q[4], q[6]);
94
+ const minY = Math.min(q[1], q[3], q[5], q[7]);
95
+ const maxY = Math.max(q[1], q[3], q[5], q[7]);
96
+ const x = Math.max(0, minX - padding);
97
+ const y = Math.max(0, minY - padding);
98
+ const width = Math.min(viewport.width, maxX + padding) - x;
99
+ const height = Math.min(viewport.height, maxY + padding) - y;
100
+ return { x, y, width, height };
101
+ }
102
+ var CdpEngine = class _CdpEngine {
103
+ constructor(client, pageUrl) {
104
+ this.client = client;
105
+ this.pageUrl = pageUrl;
106
+ }
107
+ client;
108
+ pageUrl;
109
+ static async create(host, port, urlFilter) {
110
+ const { client, target } = await connectToPage(host, port, urlFilter);
111
+ return new _CdpEngine(client, target.url);
112
+ }
113
+ async screenshot(options) {
114
+ if (options?.selector) {
115
+ const safeSelector = JSON.stringify(options.selector);
116
+ const { result } = await this.client.call("Runtime.evaluate", {
117
+ expression: `(() => {
118
+ const el = document.querySelector(${safeSelector});
119
+ if (!el) throw new Error('Element not found: ' + ${safeSelector});
120
+ return JSON.stringify(el.getBoundingClientRect());
121
+ })()`,
122
+ returnByValue: true
123
+ });
124
+ if (result.subtype === "error") {
125
+ throw new Error(result.description || `Element not found: ${options.selector}`);
126
+ }
127
+ const rect = JSON.parse(result.value);
128
+ const viewport = await this.getViewport();
129
+ const clip = {
130
+ x: Math.max(0, rect.x),
131
+ y: Math.max(0, rect.y),
132
+ width: Math.min(viewport.width - rect.x, rect.width),
133
+ height: Math.min(viewport.height - rect.y, rect.height),
134
+ scale: 1
135
+ };
136
+ const { data: data2 } = await this.client.call("Page.captureScreenshot", {
137
+ format: "png",
138
+ clip,
139
+ fromSurface: true
140
+ });
141
+ return Buffer.from(data2, "base64");
142
+ }
143
+ const { data } = await this.client.call("Page.captureScreenshot", {
144
+ format: "png",
145
+ fromSurface: true
146
+ });
147
+ return Buffer.from(data, "base64");
148
+ }
149
+ async measure(selector, depth = 1, frameSelector) {
150
+ const safeSelector = JSON.stringify(selector);
151
+ const safeFrameSelector = JSON.stringify(frameSelector);
152
+ const safeProps = JSON.stringify(RELEVANT_PROPS);
153
+ const safeRequiredProps = JSON.stringify(REQUIRED_MEASURE_PROPS);
154
+ const safeDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1;
155
+ const js = `
156
+ (() => {
157
+ function toBBox(rect, origin) {
158
+ const originX = origin ? origin.x : 0;
159
+ const originY = origin ? origin.y : 0;
160
+ const originLeft = origin ? origin.left : 0;
161
+ const originTop = origin ? origin.top : 0;
162
+ return {
163
+ x: +(rect.x - originX).toFixed(1),
164
+ y: +(rect.y - originY).toFixed(1),
165
+ width: +rect.width.toFixed(1),
166
+ height: +rect.height.toFixed(1),
167
+ top: +(rect.top - originTop).toFixed(1),
168
+ right: +(rect.right - originLeft).toFixed(1),
169
+ bottom: +(rect.bottom - originTop).toFixed(1),
170
+ left: +(rect.left - originLeft).toFixed(1),
171
+ };
172
+ }
173
+
174
+ function isValidBBox(bbox) {
175
+ return Object.values(bbox).every(Number.isFinite);
176
+ }
177
+
178
+ function collectStyle(win, el) {
179
+ const cs = win.getComputedStyle(el);
180
+ const style = {};
181
+ ${safeProps}.forEach(p => {
182
+ style[p] = p === 'font' ? cs.font : cs.getPropertyValue(p);
183
+ });
184
+ return style;
185
+ }
186
+
187
+ function hasRequiredStyle(style) {
188
+ return ${safeRequiredProps}.every(p => typeof style[p] === 'string' && style[p] !== '');
189
+ }
190
+
191
+ function collectChildren(parent, parentRect, currentDepth, maxDepth) {
192
+ if (currentDepth >= maxDepth) return [];
193
+ const result = [];
194
+ for (const child of parent.children) {
195
+ const cr = child.getBoundingClientRect();
196
+ const node = {
197
+ tag: child.tagName.toLowerCase(),
198
+ className: (child.className || '').toString().substring(0, 120),
199
+ bbox: toBBox(cr, parentRect),
200
+ text: (child.textContent || '').substring(0, 80).trim() || undefined,
201
+ };
202
+ if (currentDepth + 1 < maxDepth && child.children.length > 0) {
203
+ node.children = collectChildren(child, cr, currentDepth + 1, maxDepth);
204
+ }
205
+ result.push(node);
206
+ }
207
+ return result;
208
+ }
209
+
210
+ const sel = ${safeSelector};
211
+ const frameSel = ${safeFrameSelector};
212
+ let doc = document;
213
+ let win = window;
214
+ if (frameSel) {
215
+ const frame = document.querySelector(frameSel);
216
+ if (!frame || !frame.contentDocument || !frame.contentWindow) return JSON.stringify(null);
217
+ doc = frame.contentDocument;
218
+ win = frame.contentWindow;
219
+ }
220
+
221
+ const el = doc.querySelector(sel);
222
+ if (!el) return JSON.stringify(null);
223
+ const r = el.getBoundingClientRect();
224
+ const bbox = toBBox(r);
225
+ const style = collectStyle(win, el);
226
+ if (!isValidBBox(bbox)) return JSON.stringify(null);
227
+ if (!hasRequiredStyle(style)) return JSON.stringify(null);
228
+ if (bbox.width <= 0 || bbox.height <= 0 || style.display === 'none' || style.visibility === 'hidden') {
229
+ return JSON.stringify(null);
230
+ }
231
+
232
+ return JSON.stringify({
233
+ bbox,
234
+ computedStyle: style,
235
+ children: ${safeDepth} > 0 ? collectChildren(el, r, 0, ${safeDepth}) : [],
236
+ });
237
+ })()
238
+ `;
239
+ const deadline = Date.now() + MEASURE_WAIT_TIMEOUT_MS;
240
+ let lastError;
241
+ while (Date.now() <= deadline) {
242
+ try {
243
+ const { result, exceptionDetails } = await this.client.call("Runtime.evaluate", {
244
+ expression: js,
245
+ returnByValue: true
246
+ });
247
+ if (exceptionDetails || result.subtype === "error") {
248
+ throw new Error(result.description || exceptionDetails?.text || "measure failed");
249
+ }
250
+ const parsed = JSON.parse(result.value);
251
+ if (parsed) {
252
+ return frameSelector ? { selector, frameSelector, ...parsed } : { selector, ...parsed };
253
+ }
254
+ lastError = new Error(
255
+ frameSelector ? `Element not ready or incomplete: ${selector} in frame ${frameSelector}` : `Element not ready or incomplete: ${selector}`
256
+ );
257
+ } catch (error) {
258
+ lastError = error instanceof Error ? error : new Error(String(error));
259
+ }
260
+ await new Promise((resolve) => setTimeout(resolve, MEASURE_RETRY_INTERVAL_MS));
261
+ }
262
+ throw lastError ?? new Error("measure failed");
263
+ }
264
+ async evaluate(expression) {
265
+ const { result } = await this.client.call("Runtime.evaluate", {
266
+ expression,
267
+ returnByValue: true
268
+ });
269
+ return result.value;
270
+ }
271
+ async injectOverlay(params) {
272
+ const { tintDesignImage } = await import("./tint-RUSSUAWA.js");
273
+ const tintedBuf = await tintDesignImage(params.designImagePath);
274
+ const base64 = tintedBuf.toString("base64");
275
+ const js = `
276
+ (() => {
277
+ let overlay = document.getElementById('__design_ruler_overlay__');
278
+ if (!overlay) {
279
+ overlay = document.createElement('img');
280
+ overlay.id = '__design_ruler_overlay__';
281
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:auto;pointer-events:none;z-index:999999;';
282
+ document.body.appendChild(overlay);
283
+ }
284
+ overlay.src = 'data:image/png;base64,' + ${JSON.stringify(base64)};
285
+ overlay.style.opacity = String(${Number(params.opacity)});
286
+ overlay.style.transform = 'translate(' + ${Number(params.offsetX)} + 'px, ' + ${Number(params.offsetY)} + 'px) scale(' + ${Number(params.scale)} + ')';
287
+ overlay.style.transformOrigin = 'top left';
288
+ if (${Number(params.scrollY ?? 0)} > 0) {
289
+ window.scrollTo(0, ${Number(params.scrollY ?? 0)});
290
+ }
291
+ return 'overlay injected';
292
+ })()
293
+ `;
294
+ await this.evaluate(js);
295
+ }
296
+ async captureOverlay(options) {
297
+ return this.screenshot(options);
298
+ }
299
+ async close() {
300
+ this.client.close();
301
+ }
302
+ async getViewport() {
303
+ return this.evaluate(
304
+ "JSON.parse(JSON.stringify({ width: window.innerWidth, height: window.innerHeight }))"
305
+ );
306
+ }
307
+ };
308
+
309
+ export {
310
+ buildClip,
311
+ CdpEngine
312
+ };
313
+ //# sourceMappingURL=chunk-JVF26NXD.js.map