alanbox 0.1.1 → 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.
- package/0boxer/AGENTS.md +25 -0
- package/0boxer/src/AGENTS.md +16 -0
- package/0boxer/src/cli.js +53 -0
- package/0boxer/src/commands/AGENTS.md +15 -0
- package/{0commondflowv1 → 0boxer}/src/commands/install.js +9 -0
- package/{0commondflowv1 → 1swarmer}/AGENTS.md +11 -12
- package/{0commondflowv1 → 1swarmer}/src/AGENTS.md +6 -5
- package/{0commondflowv1 → 1swarmer}/src/args.js +1 -1
- package/{0commondflowv1 → 1swarmer}/src/cli.js +5 -18
- package/{0commondflowv1 → 1swarmer}/src/commands/AGENTS.md +7 -8
- package/{0commondflowv1 → 1swarmer}/src/commands/doctor.js +2 -2
- package/{0commondflowv1 → 1swarmer}/src/core/AGENTS.md +2 -2
- package/{0commondflowv1 → 1swarmer}/src/core/prompt-templates.js +1 -1
- package/{0commondflowv1 → 1swarmer}/src/core/storage.js +1 -1
- package/{0commondflowv1 → 1swarmer}/src/prompt/AGENTS.md +1 -1
- package/{0commondflowv1 → 1swarmer}/src/prompt/default.md +1 -1
- package/{0commondflowv1 → 1swarmer}/src/prompt/synthesizer.md +1 -1
- package/{0commondflowv1 → 1swarmer}/src/prompt/verifier.md +1 -1
- package/{0commondflowv1 → 1swarmer}/src/runner/AGENTS.md +3 -3
- package/2designer/LICENSE +21 -0
- package/2designer/README.md +39 -0
- package/2designer/dist/cdp-engine-A5WTMTVF.js +10 -0
- package/2designer/dist/cdp-engine-A5WTMTVF.js.map +1 -0
- package/2designer/dist/cdp-engine-JK2XVDHK.js +314 -0
- package/2designer/dist/cdp-engine-JK2XVDHK.js.map +1 -0
- package/2designer/dist/chunk-JVF26NXD.js +313 -0
- package/2designer/dist/chunk-JVF26NXD.js.map +1 -0
- package/2designer/dist/chunk-NLYFLQ3C.js +74 -0
- package/2designer/dist/chunk-NLYFLQ3C.js.map +1 -0
- package/2designer/dist/chunk-NQ3ASZUE.js +185 -0
- package/2designer/dist/chunk-NQ3ASZUE.js.map +1 -0
- package/2designer/dist/chunk-SKEIVBOU.js +58 -0
- package/2designer/dist/chunk-SKEIVBOU.js.map +1 -0
- package/2designer/dist/chunk-UVKSRKXR.js +71 -0
- package/2designer/dist/chunk-UVKSRKXR.js.map +1 -0
- package/2designer/dist/cli.js +498 -0
- package/2designer/dist/cli.js.map +1 -0
- package/2designer/dist/index.d.ts +129 -0
- package/2designer/dist/index.js +230 -0
- package/2designer/dist/index.js.map +1 -0
- package/2designer/dist/playwright-engine-3YKJOUNU.js +8 -0
- package/2designer/dist/playwright-engine-3YKJOUNU.js.map +1 -0
- package/2designer/dist/playwright-engine-YBRDIUHF.js +186 -0
- package/2designer/dist/playwright-engine-YBRDIUHF.js.map +1 -0
- package/2designer/dist/tint-I3FTT23O.js +60 -0
- package/2designer/dist/tint-I3FTT23O.js.map +1 -0
- package/2designer/dist/tint-RUSSUAWA.js +7 -0
- package/2designer/dist/tint-RUSSUAWA.js.map +1 -0
- package/2designer/package.json +56 -0
- package/README.md +14 -9
- package/bin/alanbox.js +11 -0
- package/bin/designer.js +10 -0
- package/bin/swarmer.js +11 -0
- package/cli.js +153 -0
- package/hooks/hooks.json +1 -1
- package/package.json +24 -10
- package/plugin/AGENTS.md +2 -2
- package/plugin/plugin.json +7 -7
- package/shared/AGENTS.md +15 -0
- package/shared/package-args.js +68 -0
- package/skills/AGENTS.md +9 -5
- package/skills/aitool/SKILL.md +36 -0
- package/skills/desginer/SKILL.md +122 -0
- package/skills/swarmer/SKILL.md +109 -0
- package/bin/multirunagent.js +0 -15
- package/skills/aibox-swam/SKILL.md +0 -77
- package/skills/sub-codex-doctor/SKILL.md +0 -27
- package/skills/sub-codex-swarm/SKILL.md +0 -56
- /package/{0commondflowv1 → 1swarmer}/res/three-lens-review.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/commands/info.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/auto.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/custom.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/index.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/handoff.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/prompt-builder.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/swarm-executor.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/workers.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/workflow-planner.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/core/workflow-storage.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/prompt/reviewer.md +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/runner/codex-runner.js +0 -0
- /package/{0commondflowv1 → 1swarmer}/src/runner/config.json +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
interface BBox {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
top: number;
|
|
7
|
+
right: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
left: number;
|
|
10
|
+
}
|
|
11
|
+
interface ChildElement {
|
|
12
|
+
tag: string;
|
|
13
|
+
className: string;
|
|
14
|
+
bbox: BBox;
|
|
15
|
+
text?: string;
|
|
16
|
+
children?: ChildElement[];
|
|
17
|
+
}
|
|
18
|
+
interface MeasureResult {
|
|
19
|
+
selector: string;
|
|
20
|
+
frameSelector?: string;
|
|
21
|
+
bbox: BBox;
|
|
22
|
+
computedStyle: Record<string, string>;
|
|
23
|
+
children?: ChildElement[];
|
|
24
|
+
}
|
|
25
|
+
interface OverlayParams {
|
|
26
|
+
designImagePath: string;
|
|
27
|
+
targetUrl: string;
|
|
28
|
+
offsetX: number;
|
|
29
|
+
offsetY: number;
|
|
30
|
+
scale: number;
|
|
31
|
+
opacity: number;
|
|
32
|
+
scrollY?: number;
|
|
33
|
+
}
|
|
34
|
+
interface ScreenshotOptions {
|
|
35
|
+
selector?: string;
|
|
36
|
+
fullPage?: boolean;
|
|
37
|
+
output?: string;
|
|
38
|
+
}
|
|
39
|
+
interface RuntimeEngine {
|
|
40
|
+
/** Take a screenshot, optionally of a specific element */
|
|
41
|
+
screenshot(options?: ScreenshotOptions): Promise<Buffer>;
|
|
42
|
+
/** Measure an element's bbox + computed style */
|
|
43
|
+
measure(selector: string, depth?: number, frameSelector?: string): Promise<MeasureResult>;
|
|
44
|
+
/** Execute JavaScript in the page context */
|
|
45
|
+
evaluate<T = unknown>(expression: string): Promise<T>;
|
|
46
|
+
/** Inject a design overlay image onto the page */
|
|
47
|
+
injectOverlay(params: OverlayParams): Promise<void>;
|
|
48
|
+
/** Capture the page with overlay applied */
|
|
49
|
+
captureOverlay(options?: ScreenshotOptions): Promise<Buffer>;
|
|
50
|
+
/** Clean up resources */
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface EngineOptions {
|
|
55
|
+
cdp?: string;
|
|
56
|
+
url: string;
|
|
57
|
+
viewport?: {
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
};
|
|
61
|
+
headless?: boolean;
|
|
62
|
+
}
|
|
63
|
+
declare function resolveEngineType(options: {
|
|
64
|
+
cdp?: string;
|
|
65
|
+
}): 'cdp' | 'playwright';
|
|
66
|
+
declare function createEngine(options: EngineOptions): Promise<RuntimeEngine>;
|
|
67
|
+
|
|
68
|
+
declare class CdpEngine implements RuntimeEngine {
|
|
69
|
+
private client;
|
|
70
|
+
private pageUrl;
|
|
71
|
+
private constructor();
|
|
72
|
+
static create(host: string, port: number, urlFilter?: string): Promise<CdpEngine>;
|
|
73
|
+
screenshot(options?: ScreenshotOptions): Promise<Buffer>;
|
|
74
|
+
measure(selector: string, depth?: number, frameSelector?: string): Promise<MeasureResult>;
|
|
75
|
+
evaluate<T = unknown>(expression: string): Promise<T>;
|
|
76
|
+
injectOverlay(params: OverlayParams): Promise<void>;
|
|
77
|
+
captureOverlay(options?: ScreenshotOptions): Promise<Buffer>;
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
private getViewport;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface PlaywrightEngineOptions {
|
|
83
|
+
headless?: boolean;
|
|
84
|
+
viewport?: {
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
};
|
|
88
|
+
deviceScaleFactor?: number;
|
|
89
|
+
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
|
|
90
|
+
waitTimeout?: number;
|
|
91
|
+
}
|
|
92
|
+
declare class PlaywrightEngine implements RuntimeEngine {
|
|
93
|
+
private browser;
|
|
94
|
+
private page;
|
|
95
|
+
private constructor();
|
|
96
|
+
static create(url: string, options?: PlaywrightEngineOptions): Promise<PlaywrightEngine>;
|
|
97
|
+
screenshot(options?: ScreenshotOptions): Promise<Buffer>;
|
|
98
|
+
measure(selector: string, depth?: number, frameSelector?: string): Promise<MeasureResult>;
|
|
99
|
+
evaluate<T = unknown>(expression: string): Promise<T>;
|
|
100
|
+
injectOverlay(params: OverlayParams): Promise<void>;
|
|
101
|
+
captureOverlay(options?: ScreenshotOptions): Promise<Buffer>;
|
|
102
|
+
close(): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface OverlayHtmlOptions {
|
|
106
|
+
targetUrl: string;
|
|
107
|
+
designImageBase64: string;
|
|
108
|
+
wsPort: number;
|
|
109
|
+
initialOpacity?: number;
|
|
110
|
+
initialScale?: number;
|
|
111
|
+
initialOffsetX?: number;
|
|
112
|
+
initialOffsetY?: number;
|
|
113
|
+
}
|
|
114
|
+
declare function generateOverlayHtml(options: OverlayHtmlOptions): string;
|
|
115
|
+
|
|
116
|
+
type TintMode = 'magenta' | 'ghost' | 'difference';
|
|
117
|
+
/**
|
|
118
|
+
* Process a design image for overlay compositing.
|
|
119
|
+
*
|
|
120
|
+
* Modes:
|
|
121
|
+
* - 'magenta': Tint non-white pixels magenta (best for white/light backgrounds)
|
|
122
|
+
* - 'ghost': Reduce opacity uniformly (works on any background color)
|
|
123
|
+
* - 'difference': No preprocessing — caller should use 'difference' blend mode
|
|
124
|
+
*
|
|
125
|
+
* Returns a PNG buffer with alpha channel.
|
|
126
|
+
*/
|
|
127
|
+
declare function tintDesignImage(imagePath: string, mode?: TintMode): Promise<Buffer>;
|
|
128
|
+
|
|
129
|
+
export { type BBox, CdpEngine, type ChildElement, type EngineOptions, type MeasureResult, type OverlayParams, PlaywrightEngine, type RuntimeEngine, type ScreenshotOptions, createEngine, generateOverlayHtml, resolveEngineType, tintDesignImage };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tintDesignImage
|
|
3
|
+
} from "./chunk-SKEIVBOU.js";
|
|
4
|
+
import {
|
|
5
|
+
CdpEngine
|
|
6
|
+
} from "./chunk-JVF26NXD.js";
|
|
7
|
+
import {
|
|
8
|
+
PlaywrightEngine
|
|
9
|
+
} from "./chunk-NQ3ASZUE.js";
|
|
10
|
+
import "./chunk-UVKSRKXR.js";
|
|
11
|
+
|
|
12
|
+
// src/engine/create-engine.ts
|
|
13
|
+
function resolveEngineType(options) {
|
|
14
|
+
return options.cdp ? "cdp" : "playwright";
|
|
15
|
+
}
|
|
16
|
+
async function createEngine(options) {
|
|
17
|
+
const type = resolveEngineType(options);
|
|
18
|
+
if (type === "cdp") {
|
|
19
|
+
const { CdpEngine: CdpEngine2 } = await import("./cdp-engine-A5WTMTVF.js");
|
|
20
|
+
const [host, portStr] = options.cdp.split(":");
|
|
21
|
+
const port = parseInt(portStr, 10);
|
|
22
|
+
return CdpEngine2.create(host, port, options.url);
|
|
23
|
+
}
|
|
24
|
+
const { PlaywrightEngine: PlaywrightEngine2 } = await import("./playwright-engine-3YKJOUNU.js");
|
|
25
|
+
return PlaywrightEngine2.create(options.url, {
|
|
26
|
+
headless: options.headless ?? true,
|
|
27
|
+
viewport: options.viewport
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/overlay/ui.ts
|
|
32
|
+
function generateOverlayHtml(options) {
|
|
33
|
+
const {
|
|
34
|
+
targetUrl,
|
|
35
|
+
designImageBase64,
|
|
36
|
+
wsPort,
|
|
37
|
+
initialOpacity = 50,
|
|
38
|
+
initialScale = 100,
|
|
39
|
+
initialOffsetX = 0,
|
|
40
|
+
initialOffsetY = 0
|
|
41
|
+
} = options;
|
|
42
|
+
return `<!DOCTYPE html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="UTF-8">
|
|
46
|
+
<title>designer overlay</title>
|
|
47
|
+
<style>
|
|
48
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
49
|
+
body { overflow: hidden; background: #1a1a1a; font-family: system-ui, sans-serif; }
|
|
50
|
+
|
|
51
|
+
#toolbar {
|
|
52
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 100000;
|
|
53
|
+
background: rgba(0,0,0,0.85); color: #fff; padding: 8px 16px;
|
|
54
|
+
display: flex; align-items: center; gap: 16px; font-size: 13px;
|
|
55
|
+
backdrop-filter: blur(8px);
|
|
56
|
+
}
|
|
57
|
+
#toolbar label { display: flex; align-items: center; gap: 6px; }
|
|
58
|
+
#toolbar input[type=range] { width: 120px; }
|
|
59
|
+
#toolbar .value { min-width: 40px; text-align: right; font-variant-numeric: tabular-nums; }
|
|
60
|
+
#confirm-btn {
|
|
61
|
+
margin-left: auto; padding: 6px 20px; background: #22c55e; color: #fff;
|
|
62
|
+
border: none; border-radius: 6px; font-size: 14px; font-weight: 600;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
}
|
|
65
|
+
#confirm-btn:hover { background: #16a34a; }
|
|
66
|
+
|
|
67
|
+
#viewport {
|
|
68
|
+
position: fixed; top: 40px; left: 0; right: 0; bottom: 0;
|
|
69
|
+
}
|
|
70
|
+
#target-frame {
|
|
71
|
+
width: 100%; height: 100%; border: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#overlay-img {
|
|
75
|
+
position: fixed; top: 40px; left: 0;
|
|
76
|
+
width: 100vw; height: auto;
|
|
77
|
+
pointer-events: none; z-index: 99999;
|
|
78
|
+
transform-origin: top left;
|
|
79
|
+
}
|
|
80
|
+
#overlay-img.draggable { pointer-events: auto; cursor: grab; }
|
|
81
|
+
#overlay-img.dragging { cursor: grabbing; }
|
|
82
|
+
|
|
83
|
+
#status {
|
|
84
|
+
position: fixed; bottom: 12px; right: 12px; z-index: 100001;
|
|
85
|
+
background: rgba(0,0,0,0.7); color: #aaa; padding: 4px 10px;
|
|
86
|
+
border-radius: 4px; font-size: 12px;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
</head>
|
|
90
|
+
<body>
|
|
91
|
+
|
|
92
|
+
<div id="toolbar">
|
|
93
|
+
<span style="font-weight:600">designer</span>
|
|
94
|
+
<label>
|
|
95
|
+
opacity
|
|
96
|
+
<input type="range" id="opacity-slider" min="0" max="100" value="${initialOpacity}">
|
|
97
|
+
<span class="value" id="opacity-val">${initialOpacity}%</span>
|
|
98
|
+
</label>
|
|
99
|
+
<label>
|
|
100
|
+
scale
|
|
101
|
+
<input type="range" id="scale-slider" min="10" max="300" value="${initialScale}">
|
|
102
|
+
<span class="value" id="scale-val">${initialScale}%</span>
|
|
103
|
+
</label>
|
|
104
|
+
<label>
|
|
105
|
+
<input type="checkbox" id="lock-cb" checked> lock
|
|
106
|
+
</label>
|
|
107
|
+
<button id="reset-btn" style="padding:4px 12px;background:#555;color:#fff;border:none;border-radius:4px;cursor:pointer">reset</button>
|
|
108
|
+
<button id="confirm-btn">confirm</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div id="viewport">
|
|
112
|
+
<iframe id="target-frame" src="${targetUrl}"></iframe>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<img id="overlay-img" src="data:image/png;base64,${designImageBase64}">
|
|
116
|
+
|
|
117
|
+
<div id="status">connecting...</div>
|
|
118
|
+
|
|
119
|
+
<script>
|
|
120
|
+
(() => {
|
|
121
|
+
const img = document.getElementById('overlay-img');
|
|
122
|
+
const opacitySlider = document.getElementById('opacity-slider');
|
|
123
|
+
const opacityVal = document.getElementById('opacity-val');
|
|
124
|
+
const scaleSlider = document.getElementById('scale-slider');
|
|
125
|
+
const scaleVal = document.getElementById('scale-val');
|
|
126
|
+
const lockCb = document.getElementById('lock-cb');
|
|
127
|
+
const resetBtn = document.getElementById('reset-btn');
|
|
128
|
+
const confirmBtn = document.getElementById('confirm-btn');
|
|
129
|
+
const status = document.getElementById('status');
|
|
130
|
+
|
|
131
|
+
let offsetX = ${initialOffsetX}, offsetY = ${initialOffsetY};
|
|
132
|
+
let scale = ${initialScale} / 100;
|
|
133
|
+
let opacity = ${initialOpacity} / 100;
|
|
134
|
+
let isDragging = false, dragStartX = 0, dragStartY = 0, dragStartOX = 0, dragStartOY = 0;
|
|
135
|
+
|
|
136
|
+
function updateTransform() {
|
|
137
|
+
img.style.opacity = String(opacity);
|
|
138
|
+
img.style.transform = \`translate(\${offsetX}px, \${offsetY}px) scale(\${scale})\`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
opacitySlider.addEventListener('input', () => {
|
|
142
|
+
opacity = opacitySlider.value / 100;
|
|
143
|
+
opacityVal.textContent = opacitySlider.value + '%';
|
|
144
|
+
updateTransform();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
scaleSlider.addEventListener('input', () => {
|
|
148
|
+
scale = scaleSlider.value / 100;
|
|
149
|
+
scaleVal.textContent = scaleSlider.value + '%';
|
|
150
|
+
updateTransform();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
lockCb.addEventListener('change', () => {
|
|
154
|
+
img.classList.toggle('draggable', !lockCb.checked);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
resetBtn.addEventListener('click', () => {
|
|
158
|
+
offsetX = 0; offsetY = 0; scale = 1; opacity = 0.5;
|
|
159
|
+
opacitySlider.value = '50'; opacityVal.textContent = '50%';
|
|
160
|
+
scaleSlider.value = '100'; scaleVal.textContent = '100%';
|
|
161
|
+
updateTransform();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Drag
|
|
165
|
+
img.addEventListener('mousedown', (e) => {
|
|
166
|
+
if (lockCb.checked) return;
|
|
167
|
+
isDragging = true;
|
|
168
|
+
dragStartX = e.clientX; dragStartY = e.clientY;
|
|
169
|
+
dragStartOX = offsetX; dragStartOY = offsetY;
|
|
170
|
+
img.classList.add('dragging');
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
document.addEventListener('mousemove', (e) => {
|
|
175
|
+
if (!isDragging) return;
|
|
176
|
+
offsetX = dragStartOX + (e.clientX - dragStartX);
|
|
177
|
+
offsetY = dragStartOY + (e.clientY - dragStartY);
|
|
178
|
+
updateTransform();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
document.addEventListener('mouseup', () => {
|
|
182
|
+
isDragging = false;
|
|
183
|
+
img.classList.remove('dragging');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Scroll zoom
|
|
187
|
+
document.addEventListener('wheel', (e) => {
|
|
188
|
+
if (lockCb.checked) return;
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
const delta = e.deltaY > 0 ? -2 : 2;
|
|
191
|
+
const newVal = Math.max(10, Math.min(300, parseInt(scaleSlider.value) + delta));
|
|
192
|
+
scaleSlider.value = String(newVal);
|
|
193
|
+
scale = newVal / 100;
|
|
194
|
+
scaleVal.textContent = newVal + '%';
|
|
195
|
+
updateTransform();
|
|
196
|
+
}, { passive: false });
|
|
197
|
+
|
|
198
|
+
// WebSocket to CLI
|
|
199
|
+
const ws = new WebSocket('ws://127.0.0.1:${wsPort}');
|
|
200
|
+
ws.onopen = () => { status.textContent = 'connected'; };
|
|
201
|
+
ws.onclose = () => { status.textContent = 'disconnected'; };
|
|
202
|
+
|
|
203
|
+
confirmBtn.addEventListener('click', () => {
|
|
204
|
+
const params = { offsetX, offsetY, scale, opacity, scrollY: 0 };
|
|
205
|
+
// Try to get iframe scroll position
|
|
206
|
+
try {
|
|
207
|
+
const frame = document.getElementById('target-frame');
|
|
208
|
+
params.scrollY = frame.contentWindow.scrollY || 0;
|
|
209
|
+
} catch(e) {}
|
|
210
|
+
ws.send(JSON.stringify({ type: 'confirm', params }));
|
|
211
|
+
status.textContent = 'saved!';
|
|
212
|
+
confirmBtn.textContent = 'saved!';
|
|
213
|
+
confirmBtn.style.background = '#666';
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
updateTransform();
|
|
217
|
+
})();
|
|
218
|
+
</script>
|
|
219
|
+
</body>
|
|
220
|
+
</html>`;
|
|
221
|
+
}
|
|
222
|
+
export {
|
|
223
|
+
CdpEngine,
|
|
224
|
+
PlaywrightEngine,
|
|
225
|
+
createEngine,
|
|
226
|
+
generateOverlayHtml,
|
|
227
|
+
resolveEngineType,
|
|
228
|
+
tintDesignImage
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engine/create-engine.ts","../src/overlay/ui.ts"],"sourcesContent":["import type { RuntimeEngine } from './types.js'\r\n\r\nexport interface EngineOptions {\r\n cdp?: string // host:port\r\n url: string\r\n viewport?: { width: number; height: number }\r\n headless?: boolean\r\n}\r\n\r\nexport function resolveEngineType(options: { cdp?: string }): 'cdp' | 'playwright' {\r\n return options.cdp ? 'cdp' : 'playwright'\r\n}\r\n\r\nexport async function createEngine(options: EngineOptions): Promise<RuntimeEngine> {\r\n const type = resolveEngineType(options)\r\n\r\n if (type === 'cdp') {\r\n const { CdpEngine } = await import('./cdp/cdp-engine.js')\r\n const [host, portStr] = options.cdp!.split(':')\r\n const port = parseInt(portStr, 10)\r\n return CdpEngine.create(host, port, options.url)\r\n }\r\n\r\n const { PlaywrightEngine } = await import('./playwright/playwright-engine.js')\r\n return PlaywrightEngine.create(options.url, {\r\n headless: options.headless ?? true,\r\n viewport: options.viewport,\r\n })\r\n}\r\n","export interface OverlayHtmlOptions {\r\n targetUrl: string\r\n designImageBase64: string\r\n wsPort: number\r\n initialOpacity?: number\r\n initialScale?: number\r\n initialOffsetX?: number\r\n initialOffsetY?: number\r\n}\r\n\r\nexport function generateOverlayHtml(options: OverlayHtmlOptions): string {\r\n const {\r\n targetUrl,\r\n designImageBase64,\r\n wsPort,\r\n initialOpacity = 50,\r\n initialScale = 100,\r\n initialOffsetX = 0,\r\n initialOffsetY = 0,\r\n } = options\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<title>designer overlay</title>\r\n<style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body { overflow: hidden; background: #1a1a1a; font-family: system-ui, sans-serif; }\r\n\r\n #toolbar {\r\n position: fixed; top: 0; left: 0; right: 0; z-index: 100000;\r\n background: rgba(0,0,0,0.85); color: #fff; padding: 8px 16px;\r\n display: flex; align-items: center; gap: 16px; font-size: 13px;\r\n backdrop-filter: blur(8px);\r\n }\r\n #toolbar label { display: flex; align-items: center; gap: 6px; }\r\n #toolbar input[type=range] { width: 120px; }\r\n #toolbar .value { min-width: 40px; text-align: right; font-variant-numeric: tabular-nums; }\r\n #confirm-btn {\r\n margin-left: auto; padding: 6px 20px; background: #22c55e; color: #fff;\r\n border: none; border-radius: 6px; font-size: 14px; font-weight: 600;\r\n cursor: pointer;\r\n }\r\n #confirm-btn:hover { background: #16a34a; }\r\n\r\n #viewport {\r\n position: fixed; top: 40px; left: 0; right: 0; bottom: 0;\r\n }\r\n #target-frame {\r\n width: 100%; height: 100%; border: none;\r\n }\r\n\r\n #overlay-img {\r\n position: fixed; top: 40px; left: 0;\r\n width: 100vw; height: auto;\r\n pointer-events: none; z-index: 99999;\r\n transform-origin: top left;\r\n }\r\n #overlay-img.draggable { pointer-events: auto; cursor: grab; }\r\n #overlay-img.dragging { cursor: grabbing; }\r\n\r\n #status {\r\n position: fixed; bottom: 12px; right: 12px; z-index: 100001;\r\n background: rgba(0,0,0,0.7); color: #aaa; padding: 4px 10px;\r\n border-radius: 4px; font-size: 12px;\r\n }\r\n</style>\r\n</head>\r\n<body>\r\n\r\n<div id=\"toolbar\">\r\n <span style=\"font-weight:600\">designer</span>\r\n <label>\r\n opacity\r\n <input type=\"range\" id=\"opacity-slider\" min=\"0\" max=\"100\" value=\"${initialOpacity}\">\r\n <span class=\"value\" id=\"opacity-val\">${initialOpacity}%</span>\r\n </label>\r\n <label>\r\n scale\r\n <input type=\"range\" id=\"scale-slider\" min=\"10\" max=\"300\" value=\"${initialScale}\">\r\n <span class=\"value\" id=\"scale-val\">${initialScale}%</span>\r\n </label>\r\n <label>\r\n <input type=\"checkbox\" id=\"lock-cb\" checked> lock\r\n </label>\r\n <button id=\"reset-btn\" style=\"padding:4px 12px;background:#555;color:#fff;border:none;border-radius:4px;cursor:pointer\">reset</button>\r\n <button id=\"confirm-btn\">confirm</button>\r\n</div>\r\n\r\n<div id=\"viewport\">\r\n <iframe id=\"target-frame\" src=\"${targetUrl}\"></iframe>\r\n</div>\r\n\r\n<img id=\"overlay-img\" src=\"data:image/png;base64,${designImageBase64}\">\r\n\r\n<div id=\"status\">connecting...</div>\r\n\r\n<script>\r\n(() => {\r\n const img = document.getElementById('overlay-img');\r\n const opacitySlider = document.getElementById('opacity-slider');\r\n const opacityVal = document.getElementById('opacity-val');\r\n const scaleSlider = document.getElementById('scale-slider');\r\n const scaleVal = document.getElementById('scale-val');\r\n const lockCb = document.getElementById('lock-cb');\r\n const resetBtn = document.getElementById('reset-btn');\r\n const confirmBtn = document.getElementById('confirm-btn');\r\n const status = document.getElementById('status');\r\n\r\n let offsetX = ${initialOffsetX}, offsetY = ${initialOffsetY};\r\n let scale = ${initialScale} / 100;\r\n let opacity = ${initialOpacity} / 100;\r\n let isDragging = false, dragStartX = 0, dragStartY = 0, dragStartOX = 0, dragStartOY = 0;\r\n\r\n function updateTransform() {\r\n img.style.opacity = String(opacity);\r\n img.style.transform = \\`translate(\\${offsetX}px, \\${offsetY}px) scale(\\${scale})\\`;\r\n }\r\n\r\n opacitySlider.addEventListener('input', () => {\r\n opacity = opacitySlider.value / 100;\r\n opacityVal.textContent = opacitySlider.value + '%';\r\n updateTransform();\r\n });\r\n\r\n scaleSlider.addEventListener('input', () => {\r\n scale = scaleSlider.value / 100;\r\n scaleVal.textContent = scaleSlider.value + '%';\r\n updateTransform();\r\n });\r\n\r\n lockCb.addEventListener('change', () => {\r\n img.classList.toggle('draggable', !lockCb.checked);\r\n });\r\n\r\n resetBtn.addEventListener('click', () => {\r\n offsetX = 0; offsetY = 0; scale = 1; opacity = 0.5;\r\n opacitySlider.value = '50'; opacityVal.textContent = '50%';\r\n scaleSlider.value = '100'; scaleVal.textContent = '100%';\r\n updateTransform();\r\n });\r\n\r\n // Drag\r\n img.addEventListener('mousedown', (e) => {\r\n if (lockCb.checked) return;\r\n isDragging = true;\r\n dragStartX = e.clientX; dragStartY = e.clientY;\r\n dragStartOX = offsetX; dragStartOY = offsetY;\r\n img.classList.add('dragging');\r\n e.preventDefault();\r\n });\r\n\r\n document.addEventListener('mousemove', (e) => {\r\n if (!isDragging) return;\r\n offsetX = dragStartOX + (e.clientX - dragStartX);\r\n offsetY = dragStartOY + (e.clientY - dragStartY);\r\n updateTransform();\r\n });\r\n\r\n document.addEventListener('mouseup', () => {\r\n isDragging = false;\r\n img.classList.remove('dragging');\r\n });\r\n\r\n // Scroll zoom\r\n document.addEventListener('wheel', (e) => {\r\n if (lockCb.checked) return;\r\n e.preventDefault();\r\n const delta = e.deltaY > 0 ? -2 : 2;\r\n const newVal = Math.max(10, Math.min(300, parseInt(scaleSlider.value) + delta));\r\n scaleSlider.value = String(newVal);\r\n scale = newVal / 100;\r\n scaleVal.textContent = newVal + '%';\r\n updateTransform();\r\n }, { passive: false });\r\n\r\n // WebSocket to CLI\r\n const ws = new WebSocket('ws://127.0.0.1:${wsPort}');\r\n ws.onopen = () => { status.textContent = 'connected'; };\r\n ws.onclose = () => { status.textContent = 'disconnected'; };\r\n\r\n confirmBtn.addEventListener('click', () => {\r\n const params = { offsetX, offsetY, scale, opacity, scrollY: 0 };\r\n // Try to get iframe scroll position\r\n try {\r\n const frame = document.getElementById('target-frame');\r\n params.scrollY = frame.contentWindow.scrollY || 0;\r\n } catch(e) {}\r\n ws.send(JSON.stringify({ type: 'confirm', params }));\r\n status.textContent = 'saved!';\r\n confirmBtn.textContent = 'saved!';\r\n confirmBtn.style.background = '#666';\r\n });\r\n\r\n updateTransform();\r\n})();\r\n</script>\r\n</body>\r\n</html>`\r\n}\r\n"],"mappings":";;;;;;;;;;;;AASO,SAAS,kBAAkB,SAAiD;AACjF,SAAO,QAAQ,MAAM,QAAQ;AAC/B;AAEA,eAAsB,aAAa,SAAgD;AACjF,QAAM,OAAO,kBAAkB,OAAO;AAEtC,MAAI,SAAS,OAAO;AAClB,UAAM,EAAE,WAAAA,WAAU,IAAI,MAAM,OAAO,0BAAqB;AACxD,UAAM,CAAC,MAAM,OAAO,IAAI,QAAQ,IAAK,MAAM,GAAG;AAC9C,UAAM,OAAO,SAAS,SAAS,EAAE;AACjC,WAAOA,WAAU,OAAO,MAAM,MAAM,QAAQ,GAAG;AAAA,EACjD;AAEA,QAAM,EAAE,kBAAAC,kBAAiB,IAAI,MAAM,OAAO,iCAAmC;AAC7E,SAAOA,kBAAiB,OAAO,QAAQ,KAAK;AAAA,IAC1C,UAAU,QAAQ,YAAY;AAAA,IAC9B,UAAU,QAAQ;AAAA,EACpB,CAAC;AACH;;;AClBO,SAAS,oBAAoB,SAAqC;AACvE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB,IAAI;AAEJ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uEAsD8D,cAAc;AAAA,2CAC1C,cAAc;AAAA;AAAA;AAAA;AAAA,sEAIa,YAAY;AAAA,yCACzC,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAUlB,SAAS;AAAA;AAAA;AAAA,mDAGO,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAgBlD,cAAc,eAAe,cAAc;AAAA,gBAC7C,YAAY;AAAA,kBACV,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAkEa,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBnD;","names":["CdpEngine","PlaywrightEngine"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,186 @@
|
|
|
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/playwright/playwright-engine.ts
|
|
11
|
+
var PlaywrightEngine = class _PlaywrightEngine {
|
|
12
|
+
browser;
|
|
13
|
+
page;
|
|
14
|
+
constructor(browser, page) {
|
|
15
|
+
this.browser = browser;
|
|
16
|
+
this.page = page;
|
|
17
|
+
}
|
|
18
|
+
static async create(url, options = {}) {
|
|
19
|
+
const { chromium } = await import("playwright");
|
|
20
|
+
const browser = await chromium.launch({
|
|
21
|
+
headless: options.headless ?? true
|
|
22
|
+
});
|
|
23
|
+
const context = await browser.newContext({
|
|
24
|
+
viewport: options.viewport ?? { width: 1920, height: 1080 },
|
|
25
|
+
deviceScaleFactor: options.deviceScaleFactor ?? 1
|
|
26
|
+
});
|
|
27
|
+
const page = await context.newPage();
|
|
28
|
+
await page.goto(url, {
|
|
29
|
+
waitUntil: options.waitUntil ?? "networkidle",
|
|
30
|
+
timeout: options.waitTimeout ?? 15e3
|
|
31
|
+
});
|
|
32
|
+
return new _PlaywrightEngine(browser, page);
|
|
33
|
+
}
|
|
34
|
+
async screenshot(options) {
|
|
35
|
+
if (options?.selector) {
|
|
36
|
+
const el = await this.page.waitForSelector(options.selector, {
|
|
37
|
+
timeout: 5e3,
|
|
38
|
+
state: "visible"
|
|
39
|
+
});
|
|
40
|
+
return el.screenshot({ type: "png" });
|
|
41
|
+
}
|
|
42
|
+
return this.page.screenshot({
|
|
43
|
+
type: "png",
|
|
44
|
+
fullPage: options?.fullPage ?? false
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async measure(selector, depth = 1, frameSelector) {
|
|
48
|
+
const maxDepth = Number.isFinite(depth) ? Math.max(0, depth) : 1;
|
|
49
|
+
const handle = await this.page.waitForFunction(
|
|
50
|
+
({
|
|
51
|
+
sel,
|
|
52
|
+
frameSel,
|
|
53
|
+
props,
|
|
54
|
+
requiredProps,
|
|
55
|
+
maxDepth: maxDepth2
|
|
56
|
+
}) => {
|
|
57
|
+
function toBBox(rect, origin) {
|
|
58
|
+
const originX = origin?.x ?? 0;
|
|
59
|
+
const originY = origin?.y ?? 0;
|
|
60
|
+
const originLeft = origin?.left ?? 0;
|
|
61
|
+
const originTop = origin?.top ?? 0;
|
|
62
|
+
return {
|
|
63
|
+
x: +(rect.x - originX).toFixed(1),
|
|
64
|
+
y: +(rect.y - originY).toFixed(1),
|
|
65
|
+
width: +rect.width.toFixed(1),
|
|
66
|
+
height: +rect.height.toFixed(1),
|
|
67
|
+
top: +(rect.top - originTop).toFixed(1),
|
|
68
|
+
right: +(rect.right - originLeft).toFixed(1),
|
|
69
|
+
bottom: +(rect.bottom - originTop).toFixed(1),
|
|
70
|
+
left: +(rect.left - originLeft).toFixed(1)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isValidBBox(bbox2) {
|
|
74
|
+
return Object.values(bbox2).every(Number.isFinite);
|
|
75
|
+
}
|
|
76
|
+
function collectStyle(win2, el2) {
|
|
77
|
+
const cs = win2.getComputedStyle(el2);
|
|
78
|
+
const style2 = {};
|
|
79
|
+
props.forEach((p) => {
|
|
80
|
+
style2[p] = p === "font" ? cs.font : cs.getPropertyValue(p);
|
|
81
|
+
});
|
|
82
|
+
return style2;
|
|
83
|
+
}
|
|
84
|
+
function hasRequiredStyle(style2) {
|
|
85
|
+
return requiredProps.every((p) => typeof style2[p] === "string" && style2[p] !== "");
|
|
86
|
+
}
|
|
87
|
+
function collectChildren(parent, parentRect, currentDepth, maxDep) {
|
|
88
|
+
if (currentDepth >= maxDep) return [];
|
|
89
|
+
const result2 = [];
|
|
90
|
+
for (const child of parent.children) {
|
|
91
|
+
const cr = child.getBoundingClientRect();
|
|
92
|
+
const node = {
|
|
93
|
+
tag: child.tagName.toLowerCase(),
|
|
94
|
+
className: (child.className || "").toString().substring(0, 120),
|
|
95
|
+
bbox: toBBox(cr, parentRect),
|
|
96
|
+
text: (child.textContent || "").substring(0, 80).trim() || void 0
|
|
97
|
+
};
|
|
98
|
+
if (currentDepth + 1 < maxDep && child.children.length > 0) {
|
|
99
|
+
node.children = collectChildren(child, cr, currentDepth + 1, maxDep);
|
|
100
|
+
}
|
|
101
|
+
result2.push(node);
|
|
102
|
+
}
|
|
103
|
+
return result2;
|
|
104
|
+
}
|
|
105
|
+
let doc = document;
|
|
106
|
+
let win = window;
|
|
107
|
+
if (frameSel) {
|
|
108
|
+
const frame = document.querySelector(frameSel);
|
|
109
|
+
if (!frame || !frame.contentDocument || !frame.contentWindow) return null;
|
|
110
|
+
doc = frame.contentDocument;
|
|
111
|
+
win = frame.contentWindow;
|
|
112
|
+
}
|
|
113
|
+
const el = doc.querySelector(sel);
|
|
114
|
+
if (!el) return null;
|
|
115
|
+
const r = el.getBoundingClientRect();
|
|
116
|
+
const bbox = toBBox(r);
|
|
117
|
+
const style = collectStyle(win, el);
|
|
118
|
+
if (!isValidBBox(bbox)) return null;
|
|
119
|
+
if (!hasRequiredStyle(style)) return null;
|
|
120
|
+
if (bbox.width <= 0 || bbox.height <= 0 || style.display === "none" || style.visibility === "hidden") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
bbox,
|
|
125
|
+
computedStyle: style,
|
|
126
|
+
children: maxDepth2 > 0 ? collectChildren(el, r, 0, maxDepth2) : []
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
sel: selector,
|
|
131
|
+
frameSel: frameSelector,
|
|
132
|
+
props: RELEVANT_PROPS,
|
|
133
|
+
requiredProps: REQUIRED_MEASURE_PROPS,
|
|
134
|
+
maxDepth
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
timeout: MEASURE_WAIT_TIMEOUT_MS,
|
|
138
|
+
polling: MEASURE_RETRY_INTERVAL_MS
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
const result = await handle.jsonValue();
|
|
142
|
+
return frameSelector ? { selector, frameSelector, ...result } : { selector, ...result };
|
|
143
|
+
}
|
|
144
|
+
async evaluate(expression) {
|
|
145
|
+
return this.page.evaluate(expression);
|
|
146
|
+
}
|
|
147
|
+
async injectOverlay(params) {
|
|
148
|
+
const { tintDesignImage } = await import("./tint-I3FTT23O.js");
|
|
149
|
+
const tintedBuf = await tintDesignImage(params.designImagePath);
|
|
150
|
+
const base64 = tintedBuf.toString("base64");
|
|
151
|
+
await this.page.evaluate(
|
|
152
|
+
({ b64, opacity, offsetX, offsetY, scale, scrollY }) => {
|
|
153
|
+
let overlay = document.getElementById("__design_ruler_overlay__");
|
|
154
|
+
if (!overlay) {
|
|
155
|
+
overlay = document.createElement("img");
|
|
156
|
+
overlay.id = "__design_ruler_overlay__";
|
|
157
|
+
overlay.style.cssText = "position:fixed;top:0;left:0;width:100vw;height:auto;pointer-events:none;z-index:999999;";
|
|
158
|
+
document.body.appendChild(overlay);
|
|
159
|
+
}
|
|
160
|
+
overlay.src = `data:image/png;base64,${b64}`;
|
|
161
|
+
overlay.style.opacity = String(opacity);
|
|
162
|
+
overlay.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
163
|
+
overlay.style.transformOrigin = "top left";
|
|
164
|
+
if (scrollY > 0) window.scrollTo(0, scrollY);
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
b64: base64,
|
|
168
|
+
opacity: params.opacity,
|
|
169
|
+
offsetX: params.offsetX,
|
|
170
|
+
offsetY: params.offsetY,
|
|
171
|
+
scale: params.scale,
|
|
172
|
+
scrollY: params.scrollY ?? 0
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
async captureOverlay(options) {
|
|
177
|
+
return this.screenshot(options);
|
|
178
|
+
}
|
|
179
|
+
async close() {
|
|
180
|
+
await this.browser.close();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
export {
|
|
184
|
+
PlaywrightEngine
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=playwright-engine-YBRDIUHF.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,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/overlay/tint.ts
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
async function tintDesignImage(imagePath, mode = "auto") {
|
|
7
|
+
const { data, info } = await sharp(imagePath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
8
|
+
const pixels = new Uint8Array(data.buffer);
|
|
9
|
+
const resolvedMode = mode === "auto" ? detectMode(pixels) : mode;
|
|
10
|
+
if (resolvedMode === "magenta") {
|
|
11
|
+
return tintMagenta(pixels, info);
|
|
12
|
+
}
|
|
13
|
+
if (resolvedMode === "difference") {
|
|
14
|
+
return sharp(imagePath).ensureAlpha().png().toBuffer();
|
|
15
|
+
}
|
|
16
|
+
return tintGhost(pixels, info, 0.4);
|
|
17
|
+
}
|
|
18
|
+
function detectMode(pixels) {
|
|
19
|
+
let brightCount = 0;
|
|
20
|
+
const sampleSize = Math.min(pixels.length / 4, 1e3);
|
|
21
|
+
const step = Math.floor(pixels.length / 4 / sampleSize);
|
|
22
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
23
|
+
const idx = i * step * 4;
|
|
24
|
+
const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3;
|
|
25
|
+
if (brightness > 220) brightCount++;
|
|
26
|
+
}
|
|
27
|
+
return brightCount / sampleSize > 0.5 ? "magenta" : "ghost";
|
|
28
|
+
}
|
|
29
|
+
var LIGHT_THRESHOLD = 230;
|
|
30
|
+
var MAGENTA = [220, 40, 160];
|
|
31
|
+
function tintMagenta(pixels, info) {
|
|
32
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
33
|
+
const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2];
|
|
34
|
+
const brightness = (r + g + b) / 3;
|
|
35
|
+
if (brightness > LIGHT_THRESHOLD) {
|
|
36
|
+
pixels[i + 3] = 0;
|
|
37
|
+
} else {
|
|
38
|
+
const darkness = 1 - brightness / 255;
|
|
39
|
+
pixels[i] = MAGENTA[0];
|
|
40
|
+
pixels[i + 1] = MAGENTA[1];
|
|
41
|
+
pixels[i + 2] = MAGENTA[2];
|
|
42
|
+
pixels[i + 3] = Math.round(darkness * 200);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return sharp(Buffer.from(pixels.buffer), {
|
|
46
|
+
raw: { width: info.width, height: info.height, channels: 4 }
|
|
47
|
+
}).png().toBuffer();
|
|
48
|
+
}
|
|
49
|
+
function tintGhost(pixels, info, opacity) {
|
|
50
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
51
|
+
pixels[i + 3] = Math.round(pixels[i + 3] * opacity);
|
|
52
|
+
}
|
|
53
|
+
return sharp(Buffer.from(pixels.buffer), {
|
|
54
|
+
raw: { width: info.width, height: info.height, channels: 4 }
|
|
55
|
+
}).png().toBuffer();
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
tintDesignImage
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=tint-I3FTT23O.js.map
|