alanbox 0.1.2 → 0.1.4

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 (94) hide show
  1. package/0boxer/AGENTS.md +26 -0
  2. package/0boxer/src/AGENTS.md +16 -0
  3. package/0boxer/src/cli.js +53 -0
  4. package/0boxer/src/commands/AGENTS.md +16 -0
  5. package/{0commondflowv1 → 0boxer}/src/commands/install.js +56 -0
  6. package/{0commondflowv1 → 1swarmer}/AGENTS.md +14 -12
  7. package/1swarmer/src/AGENTS.md +28 -0
  8. package/{0commondflowv1 → 1swarmer}/src/args.js +8 -1
  9. package/{0commondflowv1 → 1swarmer}/src/cli.js +27 -17
  10. package/1swarmer/src/commands/AGENTS.md +31 -0
  11. package/{0commondflowv1 → 1swarmer}/src/commands/doctor.js +2 -2
  12. package/1swarmer/src/commands/review-file.js +997 -0
  13. package/{0commondflowv1 → 1swarmer}/src/core/AGENTS.md +2 -2
  14. package/{0commondflowv1 → 1swarmer}/src/core/prompt-templates.js +1 -1
  15. package/{0commondflowv1 → 1swarmer}/src/core/storage.js +1 -1
  16. package/{0commondflowv1 → 1swarmer}/src/prompt/AGENTS.md +1 -1
  17. package/{0commondflowv1 → 1swarmer}/src/prompt/default.md +1 -1
  18. package/{0commondflowv1 → 1swarmer}/src/prompt/synthesizer.md +1 -1
  19. package/{0commondflowv1 → 1swarmer}/src/prompt/verifier.md +1 -1
  20. package/{0commondflowv1 → 1swarmer}/src/runner/AGENTS.md +4 -3
  21. package/{0commondflowv1 → 1swarmer}/src/runner/codex-runner.js +23 -3
  22. package/2designer/README.md +42 -0
  23. package/2designer/dist/cdp-engine-4AIWSWXO.js +314 -0
  24. package/2designer/dist/cdp-engine-4AIWSWXO.js.map +1 -0
  25. package/2designer/dist/cdp-engine-SG4K2BCX.js +10 -0
  26. package/2designer/dist/cdp-engine-SG4K2BCX.js.map +1 -0
  27. package/2designer/dist/chunk-7X7PTLZH.js +185 -0
  28. package/2designer/dist/chunk-7X7PTLZH.js.map +1 -0
  29. package/2designer/dist/chunk-DPOWNFOH.js +313 -0
  30. package/2designer/dist/chunk-DPOWNFOH.js.map +1 -0
  31. package/2designer/dist/chunk-ISUUIOO7.js +58 -0
  32. package/2designer/dist/chunk-ISUUIOO7.js.map +1 -0
  33. package/2designer/dist/chunk-NLYFLQ3C.js +74 -0
  34. package/2designer/dist/chunk-NLYFLQ3C.js.map +1 -0
  35. package/2designer/dist/chunk-UVKSRKXR.js +71 -0
  36. package/2designer/dist/chunk-UVKSRKXR.js.map +1 -0
  37. package/2designer/dist/cli.js +748 -0
  38. package/2designer/dist/cli.js.map +1 -0
  39. package/2designer/dist/index.d.ts +118 -0
  40. package/2designer/dist/index.js +37 -0
  41. package/2designer/dist/index.js.map +1 -0
  42. package/2designer/dist/playwright-engine-YXBY3KEN.js +186 -0
  43. package/2designer/dist/playwright-engine-YXBY3KEN.js.map +1 -0
  44. package/2designer/dist/playwright-engine-YXGDTSZ5.js +8 -0
  45. package/2designer/dist/playwright-engine-YXGDTSZ5.js.map +1 -0
  46. package/2designer/dist/tint-UD4CJ7S2.js +7 -0
  47. package/2designer/dist/tint-UD4CJ7S2.js.map +1 -0
  48. package/2designer/dist/tint-YN63MLVN.js +60 -0
  49. package/2designer/dist/tint-YN63MLVN.js.map +1 -0
  50. package/2designer/package.json +56 -0
  51. package/4reporter/README.md +24 -0
  52. package/4reporter/dist/cli.js +464 -0
  53. package/4reporter/dist/cli.js.map +1 -0
  54. package/4reporter/dist/index.d.ts +108 -0
  55. package/4reporter/dist/index.js +445 -0
  56. package/4reporter/dist/index.js.map +1 -0
  57. package/4reporter/package.json +39 -0
  58. package/README.md +20 -9
  59. package/bin/alanbox.js +11 -0
  60. package/bin/designer.js +10 -0
  61. package/bin/reporter.js +11 -0
  62. package/bin/swarmer.js +11 -0
  63. package/cli.js +178 -0
  64. package/hooks/hooks.json +1 -1
  65. package/mcp/README.md +7 -1
  66. package/mcp/config.toml +4 -0
  67. package/package.json +28 -11
  68. package/plugin/AGENTS.md +2 -2
  69. package/plugin/plugin.json +7 -7
  70. package/shared/AGENTS.md +15 -0
  71. package/shared/package-args.js +68 -0
  72. package/skills/AGENTS.md +9 -5
  73. package/skills/aitool/SKILL.md +36 -0
  74. package/skills/desginer/SKILL.md +142 -0
  75. package/skills/swarmer/SKILL.md +146 -0
  76. package/0commondflowv1/src/AGENTS.md +0 -26
  77. package/0commondflowv1/src/commands/AGENTS.md +0 -29
  78. package/bin/multirunagent.js +0 -15
  79. package/skills/aibox-swam/SKILL.md +0 -77
  80. package/skills/sub-codex-doctor/SKILL.md +0 -27
  81. package/skills/sub-codex-swarm/SKILL.md +0 -56
  82. /package/{0commondflowv1 → 1swarmer}/res/three-lens-review.js +0 -0
  83. /package/{0commondflowv1 → 1swarmer}/src/commands/info.js +0 -0
  84. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/auto.js +0 -0
  85. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/custom.js +0 -0
  86. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/index.js +0 -0
  87. /package/{0commondflowv1 → 1swarmer}/src/core/handoff.js +0 -0
  88. /package/{0commondflowv1 → 1swarmer}/src/core/prompt-builder.js +0 -0
  89. /package/{0commondflowv1 → 1swarmer}/src/core/swarm-executor.js +0 -0
  90. /package/{0commondflowv1 → 1swarmer}/src/core/workers.js +0 -0
  91. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-planner.js +0 -0
  92. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-storage.js +0 -0
  93. /package/{0commondflowv1 → 1swarmer}/src/prompt/reviewer.md +0 -0
  94. /package/{0commondflowv1 → 1swarmer}/src/runner/config.json +0 -0
@@ -0,0 +1,185 @@
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/playwright/playwright-engine.ts
9
+ var PlaywrightEngine = class _PlaywrightEngine {
10
+ browser;
11
+ page;
12
+ constructor(browser, page) {
13
+ this.browser = browser;
14
+ this.page = page;
15
+ }
16
+ static async create(url, options = {}) {
17
+ const { chromium } = await import("playwright");
18
+ const browser = await chromium.launch({
19
+ headless: options.headless ?? true
20
+ });
21
+ const context = await browser.newContext({
22
+ viewport: options.viewport ?? { width: 1920, height: 1080 },
23
+ deviceScaleFactor: options.deviceScaleFactor ?? 1
24
+ });
25
+ const page = await context.newPage();
26
+ await page.goto(url, {
27
+ waitUntil: options.waitUntil ?? "networkidle",
28
+ timeout: options.waitTimeout ?? 15e3
29
+ });
30
+ return new _PlaywrightEngine(browser, page);
31
+ }
32
+ async screenshot(options) {
33
+ if (options?.selector) {
34
+ const el = await this.page.waitForSelector(options.selector, {
35
+ timeout: 5e3,
36
+ state: "visible"
37
+ });
38
+ return el.screenshot({ type: "png" });
39
+ }
40
+ return this.page.screenshot({
41
+ type: "png",
42
+ fullPage: options?.fullPage ?? false
43
+ });
44
+ }
45
+ async measure(selector, depth = 1, frameSelector) {
46
+ const maxDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1;
47
+ const handle = await this.page.waitForFunction(
48
+ ({
49
+ sel,
50
+ frameSel,
51
+ props,
52
+ requiredProps,
53
+ maxDepth: maxDepth2
54
+ }) => {
55
+ function toBBox(rect, origin) {
56
+ const originX = origin?.x ?? 0;
57
+ const originY = origin?.y ?? 0;
58
+ const originLeft = origin?.left ?? 0;
59
+ const originTop = origin?.top ?? 0;
60
+ return {
61
+ x: +(rect.x - originX).toFixed(1),
62
+ y: +(rect.y - originY).toFixed(1),
63
+ width: +rect.width.toFixed(1),
64
+ height: +rect.height.toFixed(1),
65
+ top: +(rect.top - originTop).toFixed(1),
66
+ right: +(rect.right - originLeft).toFixed(1),
67
+ bottom: +(rect.bottom - originTop).toFixed(1),
68
+ left: +(rect.left - originLeft).toFixed(1)
69
+ };
70
+ }
71
+ function isValidBBox(bbox2) {
72
+ return Object.values(bbox2).every(Number.isFinite);
73
+ }
74
+ function collectStyle(win2, el2) {
75
+ const cs = win2.getComputedStyle(el2);
76
+ const style2 = {};
77
+ props.forEach((p) => {
78
+ style2[p] = p === "font" ? cs.font : cs.getPropertyValue(p);
79
+ });
80
+ return style2;
81
+ }
82
+ function hasRequiredStyle(style2) {
83
+ return requiredProps.every((p) => typeof style2[p] === "string" && style2[p] !== "");
84
+ }
85
+ function collectChildren(parent, parentRect, currentDepth, maxDep) {
86
+ if (currentDepth >= maxDep) return [];
87
+ const result2 = [];
88
+ for (const child of parent.children) {
89
+ const cr = child.getBoundingClientRect();
90
+ const node = {
91
+ tag: child.tagName.toLowerCase(),
92
+ className: (child.className || "").toString().substring(0, 120),
93
+ bbox: toBBox(cr, parentRect),
94
+ text: (child.textContent || "").substring(0, 80).trim() || void 0
95
+ };
96
+ if (currentDepth + 1 < maxDep && child.children.length > 0) {
97
+ node.children = collectChildren(child, cr, currentDepth + 1, maxDep);
98
+ }
99
+ result2.push(node);
100
+ }
101
+ return result2;
102
+ }
103
+ let doc = document;
104
+ let win = window;
105
+ if (frameSel) {
106
+ const frame = document.querySelector(frameSel);
107
+ if (!frame || !frame.contentDocument || !frame.contentWindow) return null;
108
+ doc = frame.contentDocument;
109
+ win = frame.contentWindow;
110
+ }
111
+ const el = doc.querySelector(sel);
112
+ if (!el) return null;
113
+ const r = el.getBoundingClientRect();
114
+ const bbox = toBBox(r);
115
+ const style = collectStyle(win, el);
116
+ if (!isValidBBox(bbox)) return null;
117
+ if (!hasRequiredStyle(style)) return null;
118
+ if (bbox.width <= 0 || bbox.height <= 0 || style.display === "none" || style.visibility === "hidden") {
119
+ return null;
120
+ }
121
+ return {
122
+ bbox,
123
+ computedStyle: style,
124
+ children: maxDepth2 > 0 ? collectChildren(el, r, 0, maxDepth2) : []
125
+ };
126
+ },
127
+ {
128
+ sel: selector,
129
+ frameSel: frameSelector,
130
+ props: RELEVANT_PROPS,
131
+ requiredProps: REQUIRED_MEASURE_PROPS,
132
+ maxDepth
133
+ },
134
+ {
135
+ timeout: MEASURE_WAIT_TIMEOUT_MS,
136
+ polling: MEASURE_RETRY_INTERVAL_MS
137
+ }
138
+ );
139
+ const result = await handle.jsonValue();
140
+ return frameSelector ? { selector, frameSelector, ...result } : { selector, ...result };
141
+ }
142
+ async evaluate(expression) {
143
+ return this.page.evaluate(expression);
144
+ }
145
+ async injectOverlay(params) {
146
+ const { tintDesignImage } = await import("./tint-UD4CJ7S2.js");
147
+ const tintedBuf = await tintDesignImage(params.designImagePath);
148
+ const base64 = tintedBuf.toString("base64");
149
+ await this.page.evaluate(
150
+ ({ b64, opacity, offsetX, offsetY, scale, scrollY }) => {
151
+ let overlay = document.getElementById("__design_ruler_overlay__");
152
+ if (!overlay) {
153
+ overlay = document.createElement("img");
154
+ overlay.id = "__design_ruler_overlay__";
155
+ overlay.style.cssText = "position:fixed;top:0;left:0;width:100vw;height:auto;pointer-events:none;z-index:999999;";
156
+ document.body.appendChild(overlay);
157
+ }
158
+ overlay.src = `data:image/png;base64,${b64}`;
159
+ overlay.style.opacity = String(opacity);
160
+ overlay.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
161
+ overlay.style.transformOrigin = "top left";
162
+ if (scrollY > 0) window.scrollTo(0, scrollY);
163
+ },
164
+ {
165
+ b64: base64,
166
+ opacity: params.opacity,
167
+ offsetX: params.offsetX,
168
+ offsetY: params.offsetY,
169
+ scale: params.scale,
170
+ scrollY: params.scrollY ?? 0
171
+ }
172
+ );
173
+ }
174
+ async captureOverlay(options) {
175
+ return this.screenshot(options);
176
+ }
177
+ async close() {
178
+ await this.browser.close();
179
+ }
180
+ };
181
+
182
+ export {
183
+ PlaywrightEngine
184
+ };
185
+ //# sourceMappingURL=chunk-7X7PTLZH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/playwright/playwright-engine.ts"],"sourcesContent":["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'\n\r\nexport interface PlaywrightEngineOptions {\r\n headless?: boolean\r\n viewport?: { width: number; height: number }\r\n deviceScaleFactor?: number\r\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'\r\n waitTimeout?: number\r\n}\r\n\r\nexport class PlaywrightEngine implements RuntimeEngine {\r\n private browser: any\r\n private page: any\r\n\r\n private constructor(browser: any, page: any) {\r\n this.browser = browser\r\n this.page = page\r\n }\r\n\r\n static async create(\r\n url: string,\r\n options: PlaywrightEngineOptions = {},\r\n ): Promise<PlaywrightEngine> {\r\n const { chromium } = await import('playwright')\r\n const browser = await chromium.launch({\r\n headless: options.headless ?? true,\r\n })\r\n const context = await browser.newContext({\r\n viewport: options.viewport ?? { width: 1920, height: 1080 },\r\n deviceScaleFactor: options.deviceScaleFactor ?? 1,\r\n })\r\n const page = await context.newPage()\r\n await page.goto(url, {\r\n waitUntil: options.waitUntil ?? 'networkidle',\r\n timeout: options.waitTimeout ?? 15000,\r\n })\r\n return new PlaywrightEngine(browser, page)\r\n }\r\n\r\n async screenshot(options?: ScreenshotOptions): Promise<Buffer> {\r\n if (options?.selector) {\r\n const el = await this.page.waitForSelector(options.selector, {\r\n timeout: 5000,\r\n state: 'visible',\r\n })\r\n return el.screenshot({ type: 'png' })\r\n }\r\n return this.page.screenshot({\r\n type: 'png',\r\n fullPage: options?.fullPage ?? false,\r\n })\r\n }\r\n\r\n async measure(selector: string, depth = 1, frameSelector?: string): Promise<MeasureResult> {\n const maxDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1\n const handle = await this.page.waitForFunction(\n (\n {\n sel,\n frameSel,\n props,\n requiredProps,\n maxDepth,\n }: { sel: string; frameSel?: string; props: string[]; requiredProps: string[]; maxDepth: number },\n ) => {\n function toBBox(rect: DOMRect, origin?: DOMRect) {\n const originX = origin?.x ?? 0\n const originY = origin?.y ?? 0\n const originLeft = origin?.left ?? 0\n const originTop = 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: Record<string, number>) {\n return Object.values(bbox).every(Number.isFinite)\n }\n\n function collectStyle(win: Window, el: Element) {\n const cs = win.getComputedStyle(el)\n const style: Record<string, string> = {}\n props.forEach(p => {\n style[p] = p === 'font' ? cs.font : cs.getPropertyValue(p)\n })\n return style\n }\n\n function hasRequiredStyle(style: Record<string, string>) {\n return requiredProps.every(p => typeof style[p] === 'string' && style[p] !== '')\n }\n\n function collectChildren(\n parent: Element,\n parentRect: DOMRect,\n currentDepth: number,\n maxDep: number,\n ): any[] {\n if (currentDepth >= maxDep) return []\n const result: any[] = []\n for (const child of parent.children) {\n const cr = child.getBoundingClientRect()\n const node: any = {\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 < maxDep && child.children.length > 0) {\n node.children = collectChildren(child, cr, currentDepth + 1, maxDep)\r\n }\r\n result.push(node)\r\n }\n return result\n }\n\n let doc: Document = document\n let win: Window = window\n if (frameSel) {\n const frame = document.querySelector(frameSel) as HTMLIFrameElement | null\n if (!frame || !frame.contentDocument || !frame.contentWindow) return null\n doc = frame.contentDocument\n win = frame.contentWindow\n }\n\n const el = doc.querySelector(sel)\n if (!el) return null\n const r = el.getBoundingClientRect()\n const bbox = toBBox(r)\n const style = collectStyle(win, el)\n if (!isValidBBox(bbox)) return null\n if (!hasRequiredStyle(style)) return null\n if (bbox.width <= 0 || bbox.height <= 0 || style.display === 'none' || style.visibility === 'hidden') {\n return null\n }\n\n return {\n bbox,\n computedStyle: style,\n children: maxDepth > 0 ? collectChildren(el, r, 0, maxDepth) : [],\n }\n },\n {\n sel: selector,\n frameSel: frameSelector,\n props: RELEVANT_PROPS,\n requiredProps: REQUIRED_MEASURE_PROPS,\n maxDepth,\n },\n {\n timeout: MEASURE_WAIT_TIMEOUT_MS,\n polling: MEASURE_RETRY_INTERVAL_MS,\n },\n )\n const result = await handle.jsonValue()\n return frameSelector ? { selector, frameSelector, ...result } : { selector, ...result }\n }\n\r\n async evaluate<T = unknown>(expression: string): Promise<T> {\r\n return this.page.evaluate(expression)\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 await this.page.evaluate(\r\n ({ b64, opacity, offsetX, offsetY, scale, scrollY }: any) => {\r\n let overlay = document.getElementById('__design_ruler_overlay__') as HTMLImageElement\r\n if (!overlay) {\r\n overlay = document.createElement('img')\r\n overlay.id = '__design_ruler_overlay__'\r\n overlay.style.cssText =\r\n '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,${b64}`\r\n overlay.style.opacity = String(opacity)\r\n overlay.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`\r\n overlay.style.transformOrigin = 'top left'\r\n if (scrollY > 0) window.scrollTo(0, scrollY)\r\n },\r\n {\r\n b64: base64,\r\n opacity: params.opacity,\r\n offsetX: params.offsetX,\r\n offsetY: params.offsetY,\r\n scale: params.scale,\r\n scrollY: params.scrollY ?? 0,\r\n },\r\n )\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 await this.browser.close()\r\n }\r\n}\r\n"],"mappings":"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","names":["maxDepth","bbox","win","el","style","result"]}
@@ -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-UD4CJ7S2.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-DPOWNFOH.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,58 @@
1
+ // src/overlay/tint.ts
2
+ import sharp from "sharp";
3
+ async function tintDesignImage(imagePath, mode = "auto") {
4
+ const { data, info } = await sharp(imagePath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
5
+ const pixels = new Uint8Array(data.buffer);
6
+ const resolvedMode = mode === "auto" ? detectMode(pixels) : mode;
7
+ if (resolvedMode === "magenta") {
8
+ return tintMagenta(pixels, info);
9
+ }
10
+ if (resolvedMode === "difference") {
11
+ return sharp(imagePath).ensureAlpha().png().toBuffer();
12
+ }
13
+ return tintGhost(pixels, info, 0.4);
14
+ }
15
+ function detectMode(pixels) {
16
+ let brightCount = 0;
17
+ const sampleSize = Math.min(pixels.length / 4, 1e3);
18
+ const step = Math.floor(pixels.length / 4 / sampleSize);
19
+ for (let i = 0; i < sampleSize; i++) {
20
+ const idx = i * step * 4;
21
+ const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3;
22
+ if (brightness > 220) brightCount++;
23
+ }
24
+ return brightCount / sampleSize > 0.5 ? "magenta" : "ghost";
25
+ }
26
+ var LIGHT_THRESHOLD = 230;
27
+ var MAGENTA = [220, 40, 160];
28
+ function tintMagenta(pixels, info) {
29
+ for (let i = 0; i < pixels.length; i += 4) {
30
+ const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2];
31
+ const brightness = (r + g + b) / 3;
32
+ if (brightness > LIGHT_THRESHOLD) {
33
+ pixels[i + 3] = 0;
34
+ } else {
35
+ const darkness = 1 - brightness / 255;
36
+ pixels[i] = MAGENTA[0];
37
+ pixels[i + 1] = MAGENTA[1];
38
+ pixels[i + 2] = MAGENTA[2];
39
+ pixels[i + 3] = Math.round(darkness * 200);
40
+ }
41
+ }
42
+ return sharp(Buffer.from(pixels.buffer), {
43
+ raw: { width: info.width, height: info.height, channels: 4 }
44
+ }).png().toBuffer();
45
+ }
46
+ function tintGhost(pixels, info, opacity) {
47
+ for (let i = 0; i < pixels.length; i += 4) {
48
+ pixels[i + 3] = Math.round(pixels[i + 3] * opacity);
49
+ }
50
+ return sharp(Buffer.from(pixels.buffer), {
51
+ raw: { width: info.width, height: info.height, channels: 4 }
52
+ }).png().toBuffer();
53
+ }
54
+
55
+ export {
56
+ tintDesignImage
57
+ };
58
+ //# sourceMappingURL=chunk-ISUUIOO7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/overlay/tint.ts"],"sourcesContent":["import sharp from 'sharp'\r\n\r\nexport type TintMode = 'magenta' | 'ghost' | 'difference'\r\n\r\n/**\n * 处理设计稿截图,生成适合叠到运行页上的 PNG 重影。\n *\n * 模式说明:\n * - 'magenta': 把非白色像素染成品红,适合白底或浅色背景的设计稿。\n * - 'ghost': 只统一降低透明度,适合背景不是纯白的截图。\n * - 'difference': 不预处理,由调用方使用 difference 混合模式。\n *\n * 返回带 alpha 通道的 PNG buffer。\n */\nexport async function tintDesignImage(\n imagePath: string,\r\n mode: TintMode = 'auto' as any,\r\n): Promise<Buffer> {\r\n const { data, info } = await sharp(imagePath)\r\n .ensureAlpha()\r\n .raw()\r\n .toBuffer({ resolveWithObject: true })\r\n\r\n const pixels = new Uint8Array(data.buffer)\r\n\r\n // 自动模式只粗略判断背景亮度:白底走品红染色,深色/复杂背景走 ghost。\n const resolvedMode = mode === ('auto' as any) ? detectMode(pixels) : mode\n\r\n if (resolvedMode === 'magenta') {\r\n return tintMagenta(pixels, info)\r\n }\n\n if (resolvedMode === 'difference') {\n // difference 模式需要保留原图颜色,混合策略交给调用方处理。\n return sharp(imagePath).ensureAlpha().png().toBuffer()\n }\n\n // ghost 模式不改颜色,只统一压低 alpha,避免覆盖运行页细节。\n return tintGhost(pixels, info, 0.4)\n}\n\nfunction detectMode(pixels: Uint8Array): TintMode {\n // 采样前若干像素估算背景亮度;当前目标是快速区分浅底和非浅底,不做精确抠图。\n let brightCount = 0\n const sampleSize = Math.min(pixels.length / 4, 1000)\r\n const step = Math.floor(pixels.length / 4 / sampleSize)\r\n\r\n for (let i = 0; i < sampleSize; i++) {\r\n const idx = i * step * 4\r\n const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3\r\n if (brightness > 220) brightCount++\r\n }\r\n\r\n return (brightCount / sampleSize) > 0.5 ? 'magenta' : 'ghost'\r\n}\r\n\r\nconst LIGHT_THRESHOLD = 230\nconst MAGENTA: [number, number, number] = [220, 40, 160]\n\r\nfunction tintMagenta(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\n const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2]\n const brightness = (r + g + b) / 3\n\n if (brightness > LIGHT_THRESHOLD) {\n // 浅色背景直接透明化,避免白底盖住运行页。\n pixels[i + 3] = 0\n } else {\n // 像素越暗说明设计内容越明显,叠层 alpha 越高,便于观察 1-2px 偏差。\n const darkness = 1 - brightness / 255\n pixels[i] = MAGENTA[0]\n pixels[i + 1] = MAGENTA[1]\r\n pixels[i + 2] = MAGENTA[2]\r\n pixels[i + 3] = Math.round(darkness * 200)\r\n }\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n\r\nfunction tintGhost(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n opacity: number,\r\n): Promise<Buffer> {\n for (let i = 0; i < pixels.length; i += 4) {\n // 仅缩放 alpha,保留原图色相,用于非白底设计稿的低侵入叠加。\n pixels[i + 3] = Math.round(pixels[i + 3] * opacity)\n }\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n"],"mappings":";AAAA,OAAO,WAAW;AAclB,eAAsB,gBACpB,WACA,OAAiB,QACA;AACjB,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAAM,SAAS,EACzC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,SAAS,IAAI,WAAW,KAAK,MAAM;AAGzC,QAAM,eAAe,SAAU,SAAiB,WAAW,MAAM,IAAI;AAErE,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,QAAQ,IAAI;AAAA,EACjC;AAEA,MAAI,iBAAiB,cAAc;AAEjC,WAAO,MAAM,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAAA,EACvD;AAGA,SAAO,UAAU,QAAQ,MAAM,GAAG;AACpC;AAEA,SAAS,WAAW,QAA8B;AAEhD,MAAI,cAAc;AAClB,QAAM,aAAa,KAAK,IAAI,OAAO,SAAS,GAAG,GAAI;AACnD,QAAM,OAAO,KAAK,MAAM,OAAO,SAAS,IAAI,UAAU;AAEtD,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK;AACvE,QAAI,aAAa,IAAK;AAAA,EACxB;AAEA,SAAQ,cAAc,aAAc,MAAM,YAAY;AACxD;AAEA,IAAM,kBAAkB;AACxB,IAAM,UAAoC,CAAC,KAAK,IAAI,GAAG;AAEvD,SAAS,YACP,QACA,MACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,UAAM,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC;AACxD,UAAM,cAAc,IAAI,IAAI,KAAK;AAEjC,QAAI,aAAa,iBAAiB;AAEhC,aAAO,IAAI,CAAC,IAAI;AAAA,IAClB,OAAO;AAEL,YAAM,WAAW,IAAI,aAAa;AAClC,aAAO,CAAC,IAAI,QAAQ,CAAC;AACrB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,KAAK,MAAM,WAAW,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;AAEA,SAAS,UACP,QACA,MACA,SACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AAEzC,WAAO,IAAI,CAAC,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO;AAAA,EACpD;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;","names":[]}