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.
Files changed (82) hide show
  1. package/0boxer/AGENTS.md +25 -0
  2. package/0boxer/src/AGENTS.md +16 -0
  3. package/0boxer/src/cli.js +53 -0
  4. package/0boxer/src/commands/AGENTS.md +15 -0
  5. package/{0commondflowv1 → 0boxer}/src/commands/install.js +9 -0
  6. package/{0commondflowv1 → 1swarmer}/AGENTS.md +11 -12
  7. package/{0commondflowv1 → 1swarmer}/src/AGENTS.md +6 -5
  8. package/{0commondflowv1 → 1swarmer}/src/args.js +1 -1
  9. package/{0commondflowv1 → 1swarmer}/src/cli.js +5 -18
  10. package/{0commondflowv1 → 1swarmer}/src/commands/AGENTS.md +7 -8
  11. package/{0commondflowv1 → 1swarmer}/src/commands/doctor.js +2 -2
  12. package/{0commondflowv1 → 1swarmer}/src/core/AGENTS.md +2 -2
  13. package/{0commondflowv1 → 1swarmer}/src/core/prompt-templates.js +1 -1
  14. package/{0commondflowv1 → 1swarmer}/src/core/storage.js +1 -1
  15. package/{0commondflowv1 → 1swarmer}/src/prompt/AGENTS.md +1 -1
  16. package/{0commondflowv1 → 1swarmer}/src/prompt/default.md +1 -1
  17. package/{0commondflowv1 → 1swarmer}/src/prompt/synthesizer.md +1 -1
  18. package/{0commondflowv1 → 1swarmer}/src/prompt/verifier.md +1 -1
  19. package/{0commondflowv1 → 1swarmer}/src/runner/AGENTS.md +3 -3
  20. package/2designer/LICENSE +21 -0
  21. package/2designer/README.md +39 -0
  22. package/2designer/dist/cdp-engine-A5WTMTVF.js +10 -0
  23. package/2designer/dist/cdp-engine-A5WTMTVF.js.map +1 -0
  24. package/2designer/dist/cdp-engine-JK2XVDHK.js +314 -0
  25. package/2designer/dist/cdp-engine-JK2XVDHK.js.map +1 -0
  26. package/2designer/dist/chunk-JVF26NXD.js +313 -0
  27. package/2designer/dist/chunk-JVF26NXD.js.map +1 -0
  28. package/2designer/dist/chunk-NLYFLQ3C.js +74 -0
  29. package/2designer/dist/chunk-NLYFLQ3C.js.map +1 -0
  30. package/2designer/dist/chunk-NQ3ASZUE.js +185 -0
  31. package/2designer/dist/chunk-NQ3ASZUE.js.map +1 -0
  32. package/2designer/dist/chunk-SKEIVBOU.js +58 -0
  33. package/2designer/dist/chunk-SKEIVBOU.js.map +1 -0
  34. package/2designer/dist/chunk-UVKSRKXR.js +71 -0
  35. package/2designer/dist/chunk-UVKSRKXR.js.map +1 -0
  36. package/2designer/dist/cli.js +498 -0
  37. package/2designer/dist/cli.js.map +1 -0
  38. package/2designer/dist/index.d.ts +129 -0
  39. package/2designer/dist/index.js +230 -0
  40. package/2designer/dist/index.js.map +1 -0
  41. package/2designer/dist/playwright-engine-3YKJOUNU.js +8 -0
  42. package/2designer/dist/playwright-engine-3YKJOUNU.js.map +1 -0
  43. package/2designer/dist/playwright-engine-YBRDIUHF.js +186 -0
  44. package/2designer/dist/playwright-engine-YBRDIUHF.js.map +1 -0
  45. package/2designer/dist/tint-I3FTT23O.js +60 -0
  46. package/2designer/dist/tint-I3FTT23O.js.map +1 -0
  47. package/2designer/dist/tint-RUSSUAWA.js +7 -0
  48. package/2designer/dist/tint-RUSSUAWA.js.map +1 -0
  49. package/2designer/package.json +56 -0
  50. package/README.md +14 -9
  51. package/bin/alanbox.js +11 -0
  52. package/bin/designer.js +10 -0
  53. package/bin/swarmer.js +11 -0
  54. package/cli.js +153 -0
  55. package/hooks/hooks.json +1 -1
  56. package/package.json +24 -10
  57. package/plugin/AGENTS.md +2 -2
  58. package/plugin/plugin.json +7 -7
  59. package/shared/AGENTS.md +15 -0
  60. package/shared/package-args.js +68 -0
  61. package/skills/AGENTS.md +9 -5
  62. package/skills/aitool/SKILL.md +36 -0
  63. package/skills/desginer/SKILL.md +122 -0
  64. package/skills/swarmer/SKILL.md +109 -0
  65. package/bin/multirunagent.js +0 -15
  66. package/skills/aibox-swam/SKILL.md +0 -77
  67. package/skills/sub-codex-doctor/SKILL.md +0 -27
  68. package/skills/sub-codex-swarm/SKILL.md +0 -56
  69. /package/{0commondflowv1 → 1swarmer}/res/three-lens-review.js +0 -0
  70. /package/{0commondflowv1 → 1swarmer}/src/commands/info.js +0 -0
  71. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/auto.js +0 -0
  72. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/custom.js +0 -0
  73. /package/{0commondflowv1 → 1swarmer}/src/commands/swarm/index.js +0 -0
  74. /package/{0commondflowv1 → 1swarmer}/src/core/handoff.js +0 -0
  75. /package/{0commondflowv1 → 1swarmer}/src/core/prompt-builder.js +0 -0
  76. /package/{0commondflowv1 → 1swarmer}/src/core/swarm-executor.js +0 -0
  77. /package/{0commondflowv1 → 1swarmer}/src/core/workers.js +0 -0
  78. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-planner.js +0 -0
  79. /package/{0commondflowv1 → 1swarmer}/src/core/workflow-storage.js +0 -0
  80. /package/{0commondflowv1 → 1swarmer}/src/prompt/reviewer.md +0 -0
  81. /package/{0commondflowv1 → 1swarmer}/src/runner/codex-runner.js +0 -0
  82. /package/{0commondflowv1 → 1swarmer}/src/runner/config.json +0 -0
@@ -0,0 +1,498 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ // src/cli.ts
5
+ import { Command } from "commander";
6
+
7
+ // src/engine/create-engine.ts
8
+ function resolveEngineType(options) {
9
+ return options.cdp ? "cdp" : "playwright";
10
+ }
11
+ async function createEngine(options) {
12
+ const type = resolveEngineType(options);
13
+ if (type === "cdp") {
14
+ const { CdpEngine } = await import("./cdp-engine-JK2XVDHK.js");
15
+ const [host, portStr] = options.cdp.split(":");
16
+ const port = parseInt(portStr, 10);
17
+ return CdpEngine.create(host, port, options.url);
18
+ }
19
+ const { PlaywrightEngine } = await import("./playwright-engine-YBRDIUHF.js");
20
+ return PlaywrightEngine.create(options.url, {
21
+ headless: options.headless ?? true,
22
+ viewport: options.viewport
23
+ });
24
+ }
25
+
26
+ // src/engine/selector.ts
27
+ function resolveSelector(input) {
28
+ if (input.startsWith("~")) {
29
+ const keyword = input.slice(1).trim();
30
+ return `[class*="${keyword}"]`;
31
+ }
32
+ return input;
33
+ }
34
+
35
+ // src/commands/measure.ts
36
+ function buildMeasureOptions(raw) {
37
+ if (!raw.url) throw new Error("--url is required");
38
+ if (!raw.selector) throw new Error("--selector is required");
39
+ return {
40
+ url: raw.url,
41
+ selector: resolveSelector(raw.selector),
42
+ frame: raw.frame ? resolveSelector(raw.frame) : void 0,
43
+ depth: raw.depth != null ? parseInt(raw.depth, 10) : 1,
44
+ cdp: raw.cdp,
45
+ format: raw.format ?? "json",
46
+ pick: raw.pick
47
+ };
48
+ }
49
+ function formatMeasureResult(result, format) {
50
+ if (format === "table") {
51
+ const lines = [];
52
+ lines.push(`Selector: ${result.selector}`);
53
+ lines.push(`BBox: x=${result.bbox.x} y=${result.bbox.y} w=${result.bbox.width} h=${result.bbox.height}`);
54
+ lines.push("");
55
+ lines.push("Property".padEnd(30) + "Value");
56
+ lines.push("-".repeat(60));
57
+ for (const [key, val] of Object.entries(result.computedStyle)) {
58
+ lines.push(key.padEnd(30) + val);
59
+ }
60
+ if (result.children?.length) {
61
+ let printChildren2 = function(children, indent) {
62
+ for (const c of children) {
63
+ const pad = " ".repeat(indent);
64
+ lines.push(`${pad}<${c.tag}> .${c.className} [${c.bbox.width}x${c.bbox.height}] ${c.text ?? ""}`);
65
+ if (c.children?.length) {
66
+ printChildren2(c.children, indent + 2);
67
+ }
68
+ }
69
+ };
70
+ var printChildren = printChildren2;
71
+ lines.push("");
72
+ lines.push(`Children (${result.children.length}):`);
73
+ printChildren2(result.children, 2);
74
+ }
75
+ return lines.join("\n");
76
+ }
77
+ return JSON.stringify(result, null, 2);
78
+ }
79
+ function pickFields(result, pick) {
80
+ if (pick === "bbox") return JSON.stringify(result.bbox);
81
+ if (pick === "children") return JSON.stringify(result.children ?? []);
82
+ const props = pick.split(",").map((p) => p.trim());
83
+ const picked = {};
84
+ for (const p of props) {
85
+ if (result.computedStyle[p] != null) {
86
+ picked[p] = result.computedStyle[p];
87
+ }
88
+ }
89
+ return JSON.stringify(picked);
90
+ }
91
+ async function measure(raw) {
92
+ const opts = buildMeasureOptions(raw);
93
+ const engine = await createEngine({ url: opts.url, cdp: opts.cdp });
94
+ try {
95
+ const result = await engine.measure(opts.selector, opts.depth, opts.frame);
96
+ if (opts.pick) {
97
+ console.log(pickFields(result, opts.pick));
98
+ } else {
99
+ console.log(formatMeasureResult(result, opts.format ?? "json"));
100
+ }
101
+ } finally {
102
+ await engine.close();
103
+ }
104
+ }
105
+
106
+ // src/commands/screenshot.ts
107
+ import { writeFile, mkdir } from "fs/promises";
108
+ import { dirname } from "path";
109
+ function buildScreenshotOptions(raw) {
110
+ if (!raw.url) throw new Error("--url is required");
111
+ return {
112
+ url: raw.url,
113
+ selector: raw.selector ? resolveSelector(raw.selector) : void 0,
114
+ output: raw.output ?? `screenshot-${Date.now()}.png`,
115
+ fullPage: raw.fullPage ?? false,
116
+ cdp: raw.cdp
117
+ };
118
+ }
119
+ async function screenshot(raw) {
120
+ const opts = buildScreenshotOptions(raw);
121
+ const engine = await createEngine({ url: opts.url, cdp: opts.cdp });
122
+ try {
123
+ const buf = await engine.screenshot({
124
+ selector: opts.selector,
125
+ fullPage: opts.fullPage
126
+ });
127
+ await mkdir(dirname(opts.output), { recursive: true }).catch(() => {
128
+ });
129
+ await writeFile(opts.output, buf);
130
+ console.log(JSON.stringify({ output: opts.output, bytes: buf.length }));
131
+ } finally {
132
+ await engine.close();
133
+ }
134
+ }
135
+
136
+ // src/commands/overlay.ts
137
+ import { createServer } from "http";
138
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
139
+ import { resolve, dirname as dirname2 } from "path";
140
+ import { WebSocketServer } from "ws";
141
+
142
+ // src/overlay/ui.ts
143
+ function generateOverlayHtml(options) {
144
+ const {
145
+ targetUrl,
146
+ designImageBase64,
147
+ wsPort,
148
+ initialOpacity = 50,
149
+ initialScale = 100,
150
+ initialOffsetX = 0,
151
+ initialOffsetY = 0
152
+ } = options;
153
+ return `<!DOCTYPE html>
154
+ <html lang="en">
155
+ <head>
156
+ <meta charset="UTF-8">
157
+ <title>designer overlay</title>
158
+ <style>
159
+ * { margin: 0; padding: 0; box-sizing: border-box; }
160
+ body { overflow: hidden; background: #1a1a1a; font-family: system-ui, sans-serif; }
161
+
162
+ #toolbar {
163
+ position: fixed; top: 0; left: 0; right: 0; z-index: 100000;
164
+ background: rgba(0,0,0,0.85); color: #fff; padding: 8px 16px;
165
+ display: flex; align-items: center; gap: 16px; font-size: 13px;
166
+ backdrop-filter: blur(8px);
167
+ }
168
+ #toolbar label { display: flex; align-items: center; gap: 6px; }
169
+ #toolbar input[type=range] { width: 120px; }
170
+ #toolbar .value { min-width: 40px; text-align: right; font-variant-numeric: tabular-nums; }
171
+ #confirm-btn {
172
+ margin-left: auto; padding: 6px 20px; background: #22c55e; color: #fff;
173
+ border: none; border-radius: 6px; font-size: 14px; font-weight: 600;
174
+ cursor: pointer;
175
+ }
176
+ #confirm-btn:hover { background: #16a34a; }
177
+
178
+ #viewport {
179
+ position: fixed; top: 40px; left: 0; right: 0; bottom: 0;
180
+ }
181
+ #target-frame {
182
+ width: 100%; height: 100%; border: none;
183
+ }
184
+
185
+ #overlay-img {
186
+ position: fixed; top: 40px; left: 0;
187
+ width: 100vw; height: auto;
188
+ pointer-events: none; z-index: 99999;
189
+ transform-origin: top left;
190
+ }
191
+ #overlay-img.draggable { pointer-events: auto; cursor: grab; }
192
+ #overlay-img.dragging { cursor: grabbing; }
193
+
194
+ #status {
195
+ position: fixed; bottom: 12px; right: 12px; z-index: 100001;
196
+ background: rgba(0,0,0,0.7); color: #aaa; padding: 4px 10px;
197
+ border-radius: 4px; font-size: 12px;
198
+ }
199
+ </style>
200
+ </head>
201
+ <body>
202
+
203
+ <div id="toolbar">
204
+ <span style="font-weight:600">designer</span>
205
+ <label>
206
+ opacity
207
+ <input type="range" id="opacity-slider" min="0" max="100" value="${initialOpacity}">
208
+ <span class="value" id="opacity-val">${initialOpacity}%</span>
209
+ </label>
210
+ <label>
211
+ scale
212
+ <input type="range" id="scale-slider" min="10" max="300" value="${initialScale}">
213
+ <span class="value" id="scale-val">${initialScale}%</span>
214
+ </label>
215
+ <label>
216
+ <input type="checkbox" id="lock-cb" checked> lock
217
+ </label>
218
+ <button id="reset-btn" style="padding:4px 12px;background:#555;color:#fff;border:none;border-radius:4px;cursor:pointer">reset</button>
219
+ <button id="confirm-btn">confirm</button>
220
+ </div>
221
+
222
+ <div id="viewport">
223
+ <iframe id="target-frame" src="${targetUrl}"></iframe>
224
+ </div>
225
+
226
+ <img id="overlay-img" src="data:image/png;base64,${designImageBase64}">
227
+
228
+ <div id="status">connecting...</div>
229
+
230
+ <script>
231
+ (() => {
232
+ const img = document.getElementById('overlay-img');
233
+ const opacitySlider = document.getElementById('opacity-slider');
234
+ const opacityVal = document.getElementById('opacity-val');
235
+ const scaleSlider = document.getElementById('scale-slider');
236
+ const scaleVal = document.getElementById('scale-val');
237
+ const lockCb = document.getElementById('lock-cb');
238
+ const resetBtn = document.getElementById('reset-btn');
239
+ const confirmBtn = document.getElementById('confirm-btn');
240
+ const status = document.getElementById('status');
241
+
242
+ let offsetX = ${initialOffsetX}, offsetY = ${initialOffsetY};
243
+ let scale = ${initialScale} / 100;
244
+ let opacity = ${initialOpacity} / 100;
245
+ let isDragging = false, dragStartX = 0, dragStartY = 0, dragStartOX = 0, dragStartOY = 0;
246
+
247
+ function updateTransform() {
248
+ img.style.opacity = String(opacity);
249
+ img.style.transform = \`translate(\${offsetX}px, \${offsetY}px) scale(\${scale})\`;
250
+ }
251
+
252
+ opacitySlider.addEventListener('input', () => {
253
+ opacity = opacitySlider.value / 100;
254
+ opacityVal.textContent = opacitySlider.value + '%';
255
+ updateTransform();
256
+ });
257
+
258
+ scaleSlider.addEventListener('input', () => {
259
+ scale = scaleSlider.value / 100;
260
+ scaleVal.textContent = scaleSlider.value + '%';
261
+ updateTransform();
262
+ });
263
+
264
+ lockCb.addEventListener('change', () => {
265
+ img.classList.toggle('draggable', !lockCb.checked);
266
+ });
267
+
268
+ resetBtn.addEventListener('click', () => {
269
+ offsetX = 0; offsetY = 0; scale = 1; opacity = 0.5;
270
+ opacitySlider.value = '50'; opacityVal.textContent = '50%';
271
+ scaleSlider.value = '100'; scaleVal.textContent = '100%';
272
+ updateTransform();
273
+ });
274
+
275
+ // Drag
276
+ img.addEventListener('mousedown', (e) => {
277
+ if (lockCb.checked) return;
278
+ isDragging = true;
279
+ dragStartX = e.clientX; dragStartY = e.clientY;
280
+ dragStartOX = offsetX; dragStartOY = offsetY;
281
+ img.classList.add('dragging');
282
+ e.preventDefault();
283
+ });
284
+
285
+ document.addEventListener('mousemove', (e) => {
286
+ if (!isDragging) return;
287
+ offsetX = dragStartOX + (e.clientX - dragStartX);
288
+ offsetY = dragStartOY + (e.clientY - dragStartY);
289
+ updateTransform();
290
+ });
291
+
292
+ document.addEventListener('mouseup', () => {
293
+ isDragging = false;
294
+ img.classList.remove('dragging');
295
+ });
296
+
297
+ // Scroll zoom
298
+ document.addEventListener('wheel', (e) => {
299
+ if (lockCb.checked) return;
300
+ e.preventDefault();
301
+ const delta = e.deltaY > 0 ? -2 : 2;
302
+ const newVal = Math.max(10, Math.min(300, parseInt(scaleSlider.value) + delta));
303
+ scaleSlider.value = String(newVal);
304
+ scale = newVal / 100;
305
+ scaleVal.textContent = newVal + '%';
306
+ updateTransform();
307
+ }, { passive: false });
308
+
309
+ // WebSocket to CLI
310
+ const ws = new WebSocket('ws://127.0.0.1:${wsPort}');
311
+ ws.onopen = () => { status.textContent = 'connected'; };
312
+ ws.onclose = () => { status.textContent = 'disconnected'; };
313
+
314
+ confirmBtn.addEventListener('click', () => {
315
+ const params = { offsetX, offsetY, scale, opacity, scrollY: 0 };
316
+ // Try to get iframe scroll position
317
+ try {
318
+ const frame = document.getElementById('target-frame');
319
+ params.scrollY = frame.contentWindow.scrollY || 0;
320
+ } catch(e) {}
321
+ ws.send(JSON.stringify({ type: 'confirm', params }));
322
+ status.textContent = 'saved!';
323
+ confirmBtn.textContent = 'saved!';
324
+ confirmBtn.style.background = '#666';
325
+ });
326
+
327
+ updateTransform();
328
+ })();
329
+ </script>
330
+ </body>
331
+ </html>`;
332
+ }
333
+
334
+ // src/commands/overlay.ts
335
+ function buildOverlayOptions(raw) {
336
+ if (!raw.design) throw new Error("--design is required (path to design screenshot)");
337
+ if (!raw.url) throw new Error("--url is required (target page URL)");
338
+ return {
339
+ designImagePath: resolve(raw.design),
340
+ targetUrl: raw.url,
341
+ port: raw.port ? parseInt(raw.port, 10) : 9876,
342
+ cdp: raw.cdp,
343
+ output: raw.output,
344
+ selector: raw.selector ? resolveSelector(raw.selector) : void 0,
345
+ offsetX: raw.offsetX != null ? parseFloat(raw.offsetX) : void 0,
346
+ offsetY: raw.offsetY != null ? parseFloat(raw.offsetY) : void 0,
347
+ scale: raw.scale != null ? parseFloat(raw.scale) : void 0,
348
+ opacity: raw.opacity != null ? parseFloat(raw.opacity) : void 0,
349
+ fullPage: raw.fullPage ?? false
350
+ };
351
+ }
352
+ async function captureGhost(opts, params) {
353
+ const engine = await createEngine({ url: opts.targetUrl, cdp: opts.cdp });
354
+ try {
355
+ await engine.injectOverlay({
356
+ designImagePath: opts.designImagePath,
357
+ targetUrl: opts.targetUrl,
358
+ offsetX: params.offsetX,
359
+ offsetY: params.offsetY,
360
+ scale: params.scale,
361
+ opacity: params.opacity
362
+ });
363
+ const buf = await engine.captureOverlay({ fullPage: opts.fullPage });
364
+ const outputPath = opts.output ?? `overlay-${Date.now()}.png`;
365
+ await mkdir2(dirname2(resolve(outputPath)), { recursive: true }).catch(() => {
366
+ });
367
+ await writeFile2(outputPath, buf);
368
+ console.log(JSON.stringify({ output: outputPath, bytes: buf.length }));
369
+ } finally {
370
+ await engine.close();
371
+ }
372
+ }
373
+ async function compositeGhost(opts) {
374
+ const sharp = (await import("sharp")).default;
375
+ const { tintDesignImage } = await import("./tint-I3FTT23O.js");
376
+ const engine = await createEngine({ url: opts.targetUrl, cdp: opts.cdp });
377
+ let elementBuf;
378
+ try {
379
+ elementBuf = await engine.screenshot({ selector: opts.selector, fullPage: opts.fullPage });
380
+ } finally {
381
+ await engine.close();
382
+ }
383
+ const elementMeta = await sharp(elementBuf).metadata();
384
+ const ew = elementMeta.width;
385
+ const eh = elementMeta.height;
386
+ const tintedBuf = await tintDesignImage(opts.designImagePath);
387
+ const tintedResized = await sharp(tintedBuf).resize(ew, eh, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }).toBuffer();
388
+ const ghostBuf = await sharp(elementBuf).composite([{ input: tintedResized, blend: "over" }]).png().toBuffer();
389
+ const outputPath = opts.output ?? `overlay-${Date.now()}.png`;
390
+ await mkdir2(dirname2(resolve(outputPath)), { recursive: true }).catch(() => {
391
+ });
392
+ await writeFile2(outputPath, ghostBuf);
393
+ console.log(JSON.stringify({ output: outputPath, bytes: ghostBuf.length, selector: opts.selector, elementSize: { width: ew, height: eh } }));
394
+ }
395
+ async function overlay(raw) {
396
+ const opts = buildOverlayOptions(raw);
397
+ if (opts.selector) {
398
+ await compositeGhost(opts);
399
+ return;
400
+ }
401
+ if (opts.offsetX != null || opts.offsetY != null) {
402
+ await captureGhost(opts, {
403
+ offsetX: opts.offsetX ?? 0,
404
+ offsetY: opts.offsetY ?? 0,
405
+ scale: opts.scale ?? 1,
406
+ opacity: opts.opacity ?? 1
407
+ });
408
+ return;
409
+ }
410
+ const { tintDesignImage } = await import("./tint-I3FTT23O.js");
411
+ const tintedBuf = await tintDesignImage(opts.designImagePath);
412
+ const imageBase64 = tintedBuf.toString("base64");
413
+ const wsPort = opts.port + 1;
414
+ const html = generateOverlayHtml({
415
+ targetUrl: opts.targetUrl,
416
+ designImageBase64: imageBase64,
417
+ wsPort
418
+ });
419
+ const server = createServer((req, res) => {
420
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
421
+ res.end(html);
422
+ });
423
+ const wss = new WebSocketServer({ port: wsPort });
424
+ await new Promise((resolvePromise) => {
425
+ wss.on("connection", (ws) => {
426
+ ws.on("message", async (data) => {
427
+ try {
428
+ const msg = JSON.parse(data.toString());
429
+ if (msg.type === "confirm") {
430
+ console.log(JSON.stringify({ confirmed: true, params: msg.params }));
431
+ ws.send(JSON.stringify({ type: "saved" }));
432
+ wss.close();
433
+ server.close();
434
+ resolvePromise();
435
+ }
436
+ } catch (e) {
437
+ console.error("WebSocket error:", e);
438
+ }
439
+ });
440
+ });
441
+ server.listen(opts.port, () => {
442
+ const url = `http://127.0.0.1:${opts.port}`;
443
+ console.error(`Overlay UI: ${url}`);
444
+ console.error("Adjust the overlay, then click confirm.");
445
+ import("child_process").then(({ exec }) => {
446
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
447
+ exec(`${cmd} ${url}`);
448
+ });
449
+ });
450
+ });
451
+ }
452
+
453
+ // src/cli.ts
454
+ var program = new Command();
455
+ program.name("designer").description(
456
+ "Runtime UI measurement CLI for AI agents: measure CSS/layout, capture screenshots, and build design overlays."
457
+ ).version("0.1.0");
458
+ program.addHelpText("after", `
459
+ Workflow (agent):
460
+ 1. measure Read element bbox and computed CSS as JSON
461
+ 2. screenshot Capture design/runtime component images
462
+ 3. overlay Compare design image against runtime page
463
+
464
+ Engine selection:
465
+ Default: Playwright (launches headless Chromium)
466
+ --cdp <host:port>: Connect to an existing Chrome/WebView
467
+
468
+ Examples:
469
+ $ designer measure --url http://localhost:3000 --selector ".dialog"
470
+ $ designer measure --url http://127.0.0.1:32767/start.html --frame "#mainFrame" --selector "#u0"
471
+ $ designer screenshot --url http://localhost:3000 --selector ".dialog" --output runtime.png
472
+ $ designer overlay --design design.png --url http://localhost:3000 --selector ".dialog" --output overlay.png
473
+ `);
474
+ var measureCmd = program.command("measure").description("Measure an element bbox, computed style, and optional child tree.").requiredOption("--url <url>", "Target page URL").requiredOption("--selector <selector>", "CSS selector to measure; prefix ~ for fuzzy class match").option("--frame <selector>", "Iframe CSS selector; measure --selector inside this frame document").option("--depth <n>", "Child element depth (0=no children)", "1").option("--cdp <host:port>", "CDP endpoint").option("--pick <fields>", "Pick bbox, children, or CSS property names (comma-separated)").option("--format <format>", "Output format: json | table", "json").action(measure);
475
+ measureCmd.addHelpText("after", `
476
+ Output (json):
477
+ {
478
+ "selector": ".dialog",
479
+ "bbox": { "x": 100, "y": 200, "width": 400, "height": 300 },
480
+ "computedStyle": { "border-radius": "12px", "padding": "24px" },
481
+ "children": []
482
+ }
483
+ `);
484
+ program.command("screenshot").description("Capture a PNG screenshot of the full page or a specific element.").requiredOption("--url <url>", "Target page URL").option("--selector <selector>", "CSS selector; captures element only, prefix ~ for fuzzy class match").option("--output <path>", "Output file path (default: screenshot-<timestamp>.png)").option("--full-page", "Capture full scrollable page").option("--cdp <host:port>", "CDP endpoint").action(screenshot);
485
+ var overlayCmd = program.command("overlay").description("Generate a magenta design ghost overlay on top of a live page screenshot.").requiredOption("--design <path>", "Path to design screenshot (PNG/JPG)").requiredOption("--url <url>", "Target page URL").option("--selector <selector>", "CSS selector; composite ghost on element, prefix ~ for fuzzy class match").option("--full-page", "Capture full scrollable page").option("--output <path>", "Output file path (default: overlay-<timestamp>.png)").option("--offset-x <px>", "Horizontal offset of design overlay (full-page mode)").option("--offset-y <px>", "Vertical offset of design overlay (full-page mode)").option("--scale <ratio>", "Scale factor for design overlay (1 = 100%)").option("--opacity <0-1>", "Opacity of design overlay").option("--cdp <host:port>", "CDP endpoint").option("--port <port>", "Local server port for interactive UI", "9876").action(overlay);
486
+ overlayCmd.addHelpText("after", `
487
+ Modes:
488
+ Selector:
489
+ $ designer overlay --design design.png --url http://localhost:3000 --selector ".dialog" --output overlay.png
490
+
491
+ Direct full-page:
492
+ $ designer overlay --design spec.png --url http://localhost:3000 --offset-x 0 --offset-y 0 --output overlay.png
493
+
494
+ Interactive:
495
+ $ designer overlay --design spec.png --url http://localhost:3000
496
+ `);
497
+ program.parse();
498
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/engine/create-engine.ts","../src/engine/selector.ts","../src/commands/measure.ts","../src/commands/screenshot.ts","../src/commands/overlay.ts","../src/overlay/ui.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { measure } from './commands/measure.js'\nimport { screenshot } from './commands/screenshot.js'\nimport { overlay } from './commands/overlay.js'\n\nconst program = new Command()\n\nprogram\n .name('designer')\n .description(\n 'Runtime UI measurement CLI for AI agents: measure CSS/layout, capture screenshots, and build design overlays.',\n )\n .version('0.1.0')\n\nprogram.addHelpText('after', `\nWorkflow (agent):\n 1. measure Read element bbox and computed CSS as JSON\n 2. screenshot Capture design/runtime component images\n 3. overlay Compare design image against runtime page\n\nEngine selection:\n Default: Playwright (launches headless Chromium)\n --cdp <host:port>: Connect to an existing Chrome/WebView\n\nExamples:\n $ designer measure --url http://localhost:3000 --selector \".dialog\"\n $ designer measure --url http://127.0.0.1:32767/start.html --frame \"#mainFrame\" --selector \"#u0\"\n $ designer screenshot --url http://localhost:3000 --selector \".dialog\" --output runtime.png\n $ designer overlay --design design.png --url http://localhost:3000 --selector \".dialog\" --output overlay.png\n`)\n\nconst measureCmd = program\n .command('measure')\n .description('Measure an element bbox, computed style, and optional child tree.')\n .requiredOption('--url <url>', 'Target page URL')\n .requiredOption('--selector <selector>', 'CSS selector to measure; prefix ~ for fuzzy class match')\n .option('--frame <selector>', 'Iframe CSS selector; measure --selector inside this frame document')\n .option('--depth <n>', 'Child element depth (0=no children)', '1')\n .option('--cdp <host:port>', 'CDP endpoint')\n .option('--pick <fields>', 'Pick bbox, children, or CSS property names (comma-separated)')\n .option('--format <format>', 'Output format: json | table', 'json')\n .action(measure)\n\nmeasureCmd.addHelpText('after', `\nOutput (json):\n {\n \"selector\": \".dialog\",\n \"bbox\": { \"x\": 100, \"y\": 200, \"width\": 400, \"height\": 300 },\n \"computedStyle\": { \"border-radius\": \"12px\", \"padding\": \"24px\" },\n \"children\": []\n }\n`)\n\nprogram\n .command('screenshot')\n .description('Capture a PNG screenshot of the full page or a specific element.')\n .requiredOption('--url <url>', 'Target page URL')\n .option('--selector <selector>', 'CSS selector; captures element only, prefix ~ for fuzzy class match')\n .option('--output <path>', 'Output file path (default: screenshot-<timestamp>.png)')\n .option('--full-page', 'Capture full scrollable page')\n .option('--cdp <host:port>', 'CDP endpoint')\n .action(screenshot)\n\nconst overlayCmd = program\n .command('overlay')\n .description('Generate a magenta design ghost overlay on top of a live page screenshot.')\n .requiredOption('--design <path>', 'Path to design screenshot (PNG/JPG)')\n .requiredOption('--url <url>', 'Target page URL')\n .option('--selector <selector>', 'CSS selector; composite ghost on element, prefix ~ for fuzzy class match')\n .option('--full-page', 'Capture full scrollable page')\n .option('--output <path>', 'Output file path (default: overlay-<timestamp>.png)')\n .option('--offset-x <px>', 'Horizontal offset of design overlay (full-page mode)')\n .option('--offset-y <px>', 'Vertical offset of design overlay (full-page mode)')\n .option('--scale <ratio>', 'Scale factor for design overlay (1 = 100%)')\n .option('--opacity <0-1>', 'Opacity of design overlay')\n .option('--cdp <host:port>', 'CDP endpoint')\n .option('--port <port>', 'Local server port for interactive UI', '9876')\n .action(overlay)\n\noverlayCmd.addHelpText('after', `\nModes:\n Selector:\n $ designer overlay --design design.png --url http://localhost:3000 --selector \".dialog\" --output overlay.png\n\n Direct full-page:\n $ designer overlay --design spec.png --url http://localhost:3000 --offset-x 0 --offset-y 0 --output overlay.png\n\n Interactive:\n $ designer overlay --design spec.png --url http://localhost:3000\n`)\n\nprogram.parse()\n","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","/**\r\n * Resolve selector: if starts with ~, convert to [class*=\"...\"] fuzzy match.\r\n * Returns a standard CSS selector.\r\n */\r\nexport function resolveSelector(input: string): string {\r\n if (input.startsWith('~')) {\r\n const keyword = input.slice(1).trim()\r\n return `[class*=\"${keyword}\"]`\r\n }\r\n return input\r\n}\r\n","import type { MeasureResult, ChildElement } from '../engine/types.js'\r\nimport { createEngine } from '../engine/create-engine.js'\r\nimport { resolveSelector } from '../engine/selector.js'\r\n\r\nexport interface MeasureCommandOptions {\n url: string\n selector: string\n frame?: string\n depth: number\n cdp?: string\n format?: 'json' | 'table'\n pick?: string\n}\r\n\r\nexport function buildMeasureOptions(raw: Record<string, any>): MeasureCommandOptions {\r\n if (!raw.url) throw new Error('--url is required')\r\n if (!raw.selector) throw new Error('--selector is required')\r\n return {\r\n url: raw.url,\n selector: resolveSelector(raw.selector),\n frame: raw.frame ? resolveSelector(raw.frame) : undefined,\n depth: raw.depth != null ? parseInt(raw.depth, 10) : 1,\n cdp: raw.cdp,\n format: raw.format ?? 'json',\n pick: raw.pick,\r\n }\r\n}\r\n\r\nexport function formatMeasureResult(result: MeasureResult, format: 'json' | 'table'): string {\r\n if (format === 'table') {\r\n const lines: string[] = []\r\n lines.push(`Selector: ${result.selector}`)\r\n lines.push(`BBox: x=${result.bbox.x} y=${result.bbox.y} w=${result.bbox.width} h=${result.bbox.height}`)\r\n lines.push('')\r\n lines.push('Property'.padEnd(30) + 'Value')\r\n lines.push('-'.repeat(60))\r\n for (const [key, val] of Object.entries(result.computedStyle)) {\r\n lines.push(key.padEnd(30) + val)\r\n }\r\n if (result.children?.length) {\r\n lines.push('')\r\n lines.push(`Children (${result.children.length}):`)\r\n function printChildren(children: ChildElement[], indent: number) {\r\n for (const c of children) {\r\n const pad = ' '.repeat(indent)\r\n lines.push(`${pad}<${c.tag}> .${c.className} [${c.bbox.width}x${c.bbox.height}] ${c.text ?? ''}`)\r\n if (c.children?.length) {\r\n printChildren(c.children, indent + 2)\r\n }\r\n }\r\n }\r\n printChildren(result.children, 2)\r\n }\r\n return lines.join('\\n')\r\n }\r\n return JSON.stringify(result, null, 2)\r\n}\r\n\r\nexport function pickFields(result: MeasureResult, pick: string): string {\r\n if (pick === 'bbox') return JSON.stringify(result.bbox)\r\n if (pick === 'children') return JSON.stringify(result.children ?? [])\r\n // Otherwise treat as comma-separated CSS property names\r\n const props = pick.split(',').map(p => p.trim())\r\n const picked: Record<string, string> = {}\r\n for (const p of props) {\r\n if (result.computedStyle[p] != null) {\r\n picked[p] = result.computedStyle[p]\r\n }\r\n }\r\n return JSON.stringify(picked)\r\n}\r\n\r\nexport async function measure(raw: Record<string, any>): Promise<void> {\r\n const opts = buildMeasureOptions(raw)\r\n const engine = await createEngine({ url: opts.url, cdp: opts.cdp })\r\n try {\r\n const result = await engine.measure(opts.selector, opts.depth, opts.frame)\n if (opts.pick) {\r\n console.log(pickFields(result, opts.pick))\r\n } else {\r\n console.log(formatMeasureResult(result, opts.format ?? 'json'))\r\n }\r\n } finally {\r\n await engine.close()\r\n }\r\n}\r\n","import { writeFile, mkdir } from 'fs/promises'\r\nimport { dirname } from 'path'\r\nimport { createEngine } from '../engine/create-engine.js'\r\nimport { resolveSelector } from '../engine/selector.js'\r\n\r\nexport interface ScreenshotCommandOptions {\r\n url: string\r\n selector?: string\r\n output: string\r\n fullPage?: boolean\r\n cdp?: string\r\n}\r\n\r\nexport function buildScreenshotOptions(raw: Record<string, any>): ScreenshotCommandOptions {\r\n if (!raw.url) throw new Error('--url is required')\r\n return {\r\n url: raw.url,\r\n selector: raw.selector ? resolveSelector(raw.selector) : undefined,\r\n output: raw.output ?? `screenshot-${Date.now()}.png`,\r\n fullPage: raw.fullPage ?? false,\r\n cdp: raw.cdp,\r\n }\r\n}\r\n\r\nexport async function screenshot(raw: Record<string, any>): Promise<void> {\r\n const opts = buildScreenshotOptions(raw)\r\n const engine = await createEngine({ url: opts.url, cdp: opts.cdp })\r\n try {\r\n const buf = await engine.screenshot({\r\n selector: opts.selector,\r\n fullPage: opts.fullPage,\r\n })\r\n await mkdir(dirname(opts.output), { recursive: true }).catch(() => {})\r\n await writeFile(opts.output, buf)\r\n console.log(JSON.stringify({ output: opts.output, bytes: buf.length }))\r\n } finally {\r\n await engine.close()\r\n }\r\n}\r\n","import { createServer } from 'http'\r\nimport { writeFile, mkdir } from 'fs/promises'\r\nimport { resolve, dirname } from 'path'\r\nimport WebSocket, { WebSocketServer } from 'ws'\r\nimport { generateOverlayHtml } from '../overlay/ui.js'\r\nimport { createEngine } from '../engine/create-engine.js'\r\nimport { resolveSelector } from '../engine/selector.js'\r\n\r\nexport interface OverlayCommandOptions {\r\n designImagePath: string\r\n targetUrl: string\r\n port: number\r\n cdp?: string\r\n output?: string\r\n selector?: string\r\n fullPage?: boolean\r\n offsetX?: number\r\n offsetY?: number\r\n scale?: number\r\n opacity?: number\r\n}\r\n\r\nexport function buildOverlayOptions(raw: Record<string, any>): OverlayCommandOptions {\r\n if (!raw.design) throw new Error('--design is required (path to design screenshot)')\r\n if (!raw.url) throw new Error('--url is required (target page URL)')\r\n\r\n return {\r\n designImagePath: resolve(raw.design),\r\n targetUrl: raw.url,\r\n port: raw.port ? parseInt(raw.port, 10) : 9876,\r\n cdp: raw.cdp,\r\n output: raw.output,\r\n selector: raw.selector ? resolveSelector(raw.selector) : undefined,\r\n offsetX: raw.offsetX != null ? parseFloat(raw.offsetX) : undefined,\r\n offsetY: raw.offsetY != null ? parseFloat(raw.offsetY) : undefined,\r\n scale: raw.scale != null ? parseFloat(raw.scale) : undefined,\r\n opacity: raw.opacity != null ? parseFloat(raw.opacity) : undefined,\r\n fullPage: raw.fullPage ?? false,\r\n }\r\n}\r\n\r\nasync function captureGhost(opts: OverlayCommandOptions, params: { offsetX: number; offsetY: number; scale: number; opacity: number }): Promise<void> {\r\n const engine = await createEngine({ url: opts.targetUrl, cdp: opts.cdp })\r\n try {\r\n await engine.injectOverlay({\r\n designImagePath: opts.designImagePath,\r\n targetUrl: opts.targetUrl,\r\n offsetX: params.offsetX,\r\n offsetY: params.offsetY,\r\n scale: params.scale,\r\n opacity: params.opacity,\r\n })\r\n const buf = await engine.captureOverlay({ fullPage: opts.fullPage })\r\n const outputPath = opts.output ?? `overlay-${Date.now()}.png`\r\n await mkdir(dirname(resolve(outputPath)), { recursive: true }).catch(() => {})\r\n await writeFile(outputPath, buf)\r\n console.log(JSON.stringify({ output: outputPath, bytes: buf.length }))\r\n } finally {\r\n await engine.close()\r\n }\r\n}\r\n\r\n/**\r\n * Selector mode: screenshot element + Sharp composite with tinted design.\r\n * No CSS positioning — pure pixel-level compositing for zero precision loss.\r\n */\r\nasync function compositeGhost(opts: OverlayCommandOptions): Promise<void> {\r\n const sharp = (await import('sharp')).default\r\n const { tintDesignImage } = await import('../overlay/tint.js')\r\n\r\n const engine = await createEngine({ url: opts.targetUrl, cdp: opts.cdp })\r\n let elementBuf: Buffer\r\n try {\r\n elementBuf = await engine.screenshot({ selector: opts.selector, fullPage: opts.fullPage })\r\n } finally {\r\n await engine.close()\r\n }\r\n\r\n const elementMeta = await sharp(elementBuf).metadata()\r\n const ew = elementMeta.width!\r\n const eh = elementMeta.height!\r\n\r\n const tintedBuf = await tintDesignImage(opts.designImagePath)\r\n const tintedResized = await sharp(tintedBuf)\r\n .resize(ew, eh, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })\r\n .toBuffer()\r\n\r\n const ghostBuf = await sharp(elementBuf)\r\n .composite([{ input: tintedResized, blend: 'over' }])\r\n .png()\r\n .toBuffer()\r\n\r\n const outputPath = opts.output ?? `overlay-${Date.now()}.png`\r\n await mkdir(dirname(resolve(outputPath)), { recursive: true }).catch(() => {})\r\n await writeFile(outputPath, ghostBuf)\r\n console.log(JSON.stringify({ output: outputPath, bytes: ghostBuf.length, selector: opts.selector, elementSize: { width: ew, height: eh } }))\r\n}\r\n\r\nexport async function overlay(raw: Record<string, any>): Promise<void> {\r\n const opts = buildOverlayOptions(raw)\r\n\r\n // Selector mode — Sharp composite, pixel-precise\r\n if (opts.selector) {\r\n await compositeGhost(opts)\r\n return\r\n }\r\n\r\n // Direct params — headless, no interaction\r\n if (opts.offsetX != null || opts.offsetY != null) {\r\n await captureGhost(opts, {\r\n offsetX: opts.offsetX ?? 0,\r\n offsetY: opts.offsetY ?? 0,\r\n scale: opts.scale ?? 1,\r\n opacity: opts.opacity ?? 1,\r\n })\r\n return\r\n }\r\n\r\n // Interactive alignment UI\r\n const { tintDesignImage } = await import('../overlay/tint.js')\r\n const tintedBuf = await tintDesignImage(opts.designImagePath)\r\n const imageBase64 = tintedBuf.toString('base64')\r\n const wsPort = opts.port + 1\r\n\r\n const html = generateOverlayHtml({\r\n targetUrl: opts.targetUrl,\r\n designImageBase64: imageBase64,\r\n wsPort,\r\n })\r\n\r\n const server = createServer((req, res) => {\r\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })\r\n res.end(html)\r\n })\r\n\r\n const wss = new WebSocketServer({ port: wsPort })\r\n\r\n await new Promise<void>((resolvePromise) => {\r\n wss.on('connection', (ws) => {\r\n ws.on('message', async (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString())\r\n if (msg.type === 'confirm') {\r\n console.log(JSON.stringify({ confirmed: true, params: msg.params }))\r\n ws.send(JSON.stringify({ type: 'saved' }))\r\n wss.close()\r\n server.close()\r\n resolvePromise()\r\n }\r\n } catch (e) {\r\n console.error('WebSocket error:', e)\r\n }\r\n })\r\n })\r\n\r\n server.listen(opts.port, () => {\r\n const url = `http://127.0.0.1:${opts.port}`\r\n console.error(`Overlay UI: ${url}`)\r\n console.error('Adjust the overlay, then click confirm.')\r\n import('child_process').then(({ exec }) => {\r\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'\r\n exec(`${cmd} ${url}`)\r\n })\r\n })\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":";;;;AAAA,SAAS,eAAe;;;ACSjB,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,UAAU,IAAI,MAAM,OAAO,0BAAqB;AACxD,UAAM,CAAC,MAAM,OAAO,IAAI,QAAQ,IAAK,MAAM,GAAG;AAC9C,UAAM,OAAO,SAAS,SAAS,EAAE;AACjC,WAAO,UAAU,OAAO,MAAM,MAAM,QAAQ,GAAG;AAAA,EACjD;AAEA,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,iCAAmC;AAC7E,SAAO,iBAAiB,OAAO,QAAQ,KAAK;AAAA,IAC1C,UAAU,QAAQ,YAAY;AAAA,IAC9B,UAAU,QAAQ;AAAA,EACpB,CAAC;AACH;;;ACxBO,SAAS,gBAAgB,OAAuB;AACrD,MAAI,MAAM,WAAW,GAAG,GAAG;AACzB,UAAM,UAAU,MAAM,MAAM,CAAC,EAAE,KAAK;AACpC,WAAO,YAAY,OAAO;AAAA,EAC5B;AACA,SAAO;AACT;;;ACIO,SAAS,oBAAoB,KAAiD;AACnF,MAAI,CAAC,IAAI,IAAK,OAAM,IAAI,MAAM,mBAAmB;AACjD,MAAI,CAAC,IAAI,SAAU,OAAM,IAAI,MAAM,wBAAwB;AAC3D,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,UAAU,gBAAgB,IAAI,QAAQ;AAAA,IACtC,OAAO,IAAI,QAAQ,gBAAgB,IAAI,KAAK,IAAI;AAAA,IAChD,OAAO,IAAI,SAAS,OAAO,SAAS,IAAI,OAAO,EAAE,IAAI;AAAA,IACrD,KAAK,IAAI;AAAA,IACT,QAAQ,IAAI,UAAU;AAAA,IACtB,MAAM,IAAI;AAAA,EACZ;AACF;AAEO,SAAS,oBAAoB,QAAuB,QAAkC;AAC3F,MAAI,WAAW,SAAS;AACtB,UAAM,QAAkB,CAAC;AACzB,UAAM,KAAK,aAAa,OAAO,QAAQ,EAAE;AACzC,UAAM,KAAK,WAAW,OAAO,KAAK,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO,KAAK,MAAM,EAAE;AACvG,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,WAAW,OAAO,EAAE,IAAI,OAAO;AAC1C,UAAM,KAAK,IAAI,OAAO,EAAE,CAAC;AACzB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,OAAO,aAAa,GAAG;AAC7D,YAAM,KAAK,IAAI,OAAO,EAAE,IAAI,GAAG;AAAA,IACjC;AACA,QAAI,OAAO,UAAU,QAAQ;AAG3B,UAASA,iBAAT,SAAuB,UAA0B,QAAgB;AAC/D,mBAAW,KAAK,UAAU;AACxB,gBAAM,MAAM,IAAI,OAAO,MAAM;AAC7B,gBAAM,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,MAAM,EAAE,SAAS,KAAK,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,MAAM,KAAK,EAAE,QAAQ,EAAE,EAAE;AAChG,cAAI,EAAE,UAAU,QAAQ;AACtB,YAAAA,eAAc,EAAE,UAAU,SAAS,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AARS,0BAAAA;AAFT,YAAM,KAAK,EAAE;AACb,YAAM,KAAK,aAAa,OAAO,SAAS,MAAM,IAAI;AAUlD,MAAAA,eAAc,OAAO,UAAU,CAAC;AAAA,IAClC;AACA,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AACA,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;AAEO,SAAS,WAAW,QAAuB,MAAsB;AACtE,MAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,OAAO,IAAI;AACtD,MAAI,SAAS,WAAY,QAAO,KAAK,UAAU,OAAO,YAAY,CAAC,CAAC;AAEpE,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC;AAC/C,QAAM,SAAiC,CAAC;AACxC,aAAW,KAAK,OAAO;AACrB,QAAI,OAAO,cAAc,CAAC,KAAK,MAAM;AACnC,aAAO,CAAC,IAAI,OAAO,cAAc,CAAC;AAAA,IACpC;AAAA,EACF;AACA,SAAO,KAAK,UAAU,MAAM;AAC9B;AAEA,eAAsB,QAAQ,KAAyC;AACrE,QAAM,OAAO,oBAAoB,GAAG;AACpC,QAAM,SAAS,MAAM,aAAa,EAAE,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;AAClE,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,QAAQ,KAAK,UAAU,KAAK,OAAO,KAAK,KAAK;AACzE,QAAI,KAAK,MAAM;AACb,cAAQ,IAAI,WAAW,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC3C,OAAO;AACL,cAAQ,IAAI,oBAAoB,QAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,IAChE;AAAA,EACF,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;;;ACrFA,SAAS,WAAW,aAAa;AACjC,SAAS,eAAe;AAYjB,SAAS,uBAAuB,KAAoD;AACzF,MAAI,CAAC,IAAI,IAAK,OAAM,IAAI,MAAM,mBAAmB;AACjD,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,UAAU,IAAI,WAAW,gBAAgB,IAAI,QAAQ,IAAI;AAAA,IACzD,QAAQ,IAAI,UAAU,cAAc,KAAK,IAAI,CAAC;AAAA,IAC9C,UAAU,IAAI,YAAY;AAAA,IAC1B,KAAK,IAAI;AAAA,EACX;AACF;AAEA,eAAsB,WAAW,KAAyC;AACxE,QAAM,OAAO,uBAAuB,GAAG;AACvC,QAAM,SAAS,MAAM,aAAa,EAAE,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;AAClE,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,WAAW;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,UAAM,MAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACrE,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,YAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,KAAK,QAAQ,OAAO,IAAI,OAAO,CAAC,CAAC;AAAA,EACxE,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;;;ACtCA,SAAS,oBAAoB;AAC7B,SAAS,aAAAC,YAAW,SAAAC,cAAa;AACjC,SAAS,SAAS,WAAAC,gBAAe;AACjC,SAAoB,uBAAuB;;;ACOpC,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;;;ADlLO,SAAS,oBAAoB,KAAiD;AACnF,MAAI,CAAC,IAAI,OAAQ,OAAM,IAAI,MAAM,kDAAkD;AACnF,MAAI,CAAC,IAAI,IAAK,OAAM,IAAI,MAAM,qCAAqC;AAEnE,SAAO;AAAA,IACL,iBAAiB,QAAQ,IAAI,MAAM;AAAA,IACnC,WAAW,IAAI;AAAA,IACf,MAAM,IAAI,OAAO,SAAS,IAAI,MAAM,EAAE,IAAI;AAAA,IAC1C,KAAK,IAAI;AAAA,IACT,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI,WAAW,gBAAgB,IAAI,QAAQ,IAAI;AAAA,IACzD,SAAS,IAAI,WAAW,OAAO,WAAW,IAAI,OAAO,IAAI;AAAA,IACzD,SAAS,IAAI,WAAW,OAAO,WAAW,IAAI,OAAO,IAAI;AAAA,IACzD,OAAO,IAAI,SAAS,OAAO,WAAW,IAAI,KAAK,IAAI;AAAA,IACnD,SAAS,IAAI,WAAW,OAAO,WAAW,IAAI,OAAO,IAAI;AAAA,IACzD,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AAEA,eAAe,aAAa,MAA6B,QAA6F;AACpJ,QAAM,SAAS,MAAM,aAAa,EAAE,KAAK,KAAK,WAAW,KAAK,KAAK,IAAI,CAAC;AACxE,MAAI;AACF,UAAM,OAAO,cAAc;AAAA,MACzB,iBAAiB,KAAK;AAAA,MACtB,WAAW,KAAK;AAAA,MAChB,SAAS,OAAO;AAAA,MAChB,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,SAAS,OAAO;AAAA,IAClB,CAAC;AACD,UAAM,MAAM,MAAM,OAAO,eAAe,EAAE,UAAU,KAAK,SAAS,CAAC;AACnE,UAAM,aAAa,KAAK,UAAU,WAAW,KAAK,IAAI,CAAC;AACvD,UAAMC,OAAMC,SAAQ,QAAQ,UAAU,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC7E,UAAMC,WAAU,YAAY,GAAG;AAC/B,YAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,YAAY,OAAO,IAAI,OAAO,CAAC,CAAC;AAAA,EACvE,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;AAMA,eAAe,eAAe,MAA4C;AACxE,QAAM,SAAS,MAAM,OAAO,OAAO,GAAG;AACtC,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,oBAAoB;AAE7D,QAAM,SAAS,MAAM,aAAa,EAAE,KAAK,KAAK,WAAW,KAAK,KAAK,IAAI,CAAC;AACxE,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,OAAO,WAAW,EAAE,UAAU,KAAK,UAAU,UAAU,KAAK,SAAS,CAAC;AAAA,EAC3F,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AAEA,QAAM,cAAc,MAAM,MAAM,UAAU,EAAE,SAAS;AACrD,QAAM,KAAK,YAAY;AACvB,QAAM,KAAK,YAAY;AAEvB,QAAM,YAAY,MAAM,gBAAgB,KAAK,eAAe;AAC5D,QAAM,gBAAgB,MAAM,MAAM,SAAS,EACxC,OAAO,IAAI,IAAI,EAAE,KAAK,WAAW,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,EAAE,EAAE,CAAC,EAC7E,SAAS;AAEZ,QAAM,WAAW,MAAM,MAAM,UAAU,EACpC,UAAU,CAAC,EAAE,OAAO,eAAe,OAAO,OAAO,CAAC,CAAC,EACnD,IAAI,EACJ,SAAS;AAEZ,QAAM,aAAa,KAAK,UAAU,WAAW,KAAK,IAAI,CAAC;AACvD,QAAMF,OAAMC,SAAQ,QAAQ,UAAU,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAC7E,QAAMC,WAAU,YAAY,QAAQ;AACpC,UAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,YAAY,OAAO,SAAS,QAAQ,UAAU,KAAK,UAAU,aAAa,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,CAAC,CAAC;AAC7I;AAEA,eAAsB,QAAQ,KAAyC;AACrE,QAAM,OAAO,oBAAoB,GAAG;AAGpC,MAAI,KAAK,UAAU;AACjB,UAAM,eAAe,IAAI;AACzB;AAAA,EACF;AAGA,MAAI,KAAK,WAAW,QAAQ,KAAK,WAAW,MAAM;AAChD,UAAM,aAAa,MAAM;AAAA,MACvB,SAAS,KAAK,WAAW;AAAA,MACzB,SAAS,KAAK,WAAW;AAAA,MACzB,OAAO,KAAK,SAAS;AAAA,MACrB,SAAS,KAAK,WAAW;AAAA,IAC3B,CAAC;AACD;AAAA,EACF;AAGA,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,oBAAoB;AAC7D,QAAM,YAAY,MAAM,gBAAgB,KAAK,eAAe;AAC5D,QAAM,cAAc,UAAU,SAAS,QAAQ;AAC/C,QAAM,SAAS,KAAK,OAAO;AAE3B,QAAM,OAAO,oBAAoB;AAAA,IAC/B,WAAW,KAAK;AAAA,IAChB,mBAAmB;AAAA,IACnB;AAAA,EACF,CAAC;AAED,QAAM,SAAS,aAAa,CAAC,KAAK,QAAQ;AACxC,QAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,QAAI,IAAI,IAAI;AAAA,EACd,CAAC;AAED,QAAM,MAAM,IAAI,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAEhD,QAAM,IAAI,QAAc,CAAC,mBAAmB;AAC1C,QAAI,GAAG,cAAc,CAAC,OAAO;AAC3B,SAAG,GAAG,WAAW,OAAO,SAAS;AAC/B,YAAI;AACF,gBAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,cAAI,IAAI,SAAS,WAAW;AAC1B,oBAAQ,IAAI,KAAK,UAAU,EAAE,WAAW,MAAM,QAAQ,IAAI,OAAO,CAAC,CAAC;AACnE,eAAG,KAAK,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC,CAAC;AACzC,gBAAI,MAAM;AACV,mBAAO,MAAM;AACb,2BAAe;AAAA,UACjB;AAAA,QACF,SAAS,GAAG;AACV,kBAAQ,MAAM,oBAAoB,CAAC;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,WAAO,OAAO,KAAK,MAAM,MAAM;AAC7B,YAAM,MAAM,oBAAoB,KAAK,IAAI;AACzC,cAAQ,MAAM,eAAe,GAAG,EAAE;AAClC,cAAQ,MAAM,yCAAyC;AACvD,aAAO,eAAe,EAAE,KAAK,CAAC,EAAE,KAAK,MAAM;AACzC,cAAM,MAAM,QAAQ,aAAa,WAAW,SAAS,QAAQ,aAAa,UAAU,UAAU;AAC9F,aAAK,GAAG,GAAG,IAAI,GAAG,EAAE;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;;;ALhKA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,UAAU,EACf;AAAA,EACC;AACF,EACC,QAAQ,OAAO;AAElB,QAAQ,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAe5B;AAED,IAAM,aAAa,QAChB,QAAQ,SAAS,EACjB,YAAY,mEAAmE,EAC/E,eAAe,eAAe,iBAAiB,EAC/C,eAAe,yBAAyB,yDAAyD,EACjG,OAAO,sBAAsB,oEAAoE,EACjG,OAAO,eAAe,uCAAuC,GAAG,EAChE,OAAO,qBAAqB,cAAc,EAC1C,OAAO,mBAAmB,8DAA8D,EACxF,OAAO,qBAAqB,+BAA+B,MAAM,EACjE,OAAO,OAAO;AAEjB,WAAW,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAQ/B;AAED,QACG,QAAQ,YAAY,EACpB,YAAY,kEAAkE,EAC9E,eAAe,eAAe,iBAAiB,EAC/C,OAAO,yBAAyB,qEAAqE,EACrG,OAAO,mBAAmB,wDAAwD,EAClF,OAAO,eAAe,8BAA8B,EACpD,OAAO,qBAAqB,cAAc,EAC1C,OAAO,UAAU;AAEpB,IAAM,aAAa,QAChB,QAAQ,SAAS,EACjB,YAAY,2EAA2E,EACvF,eAAe,mBAAmB,qCAAqC,EACvE,eAAe,eAAe,iBAAiB,EAC/C,OAAO,yBAAyB,0EAA0E,EAC1G,OAAO,eAAe,8BAA8B,EACpD,OAAO,mBAAmB,qDAAqD,EAC/E,OAAO,mBAAmB,sDAAsD,EAChF,OAAO,mBAAmB,oDAAoD,EAC9E,OAAO,mBAAmB,4CAA4C,EACtE,OAAO,mBAAmB,2BAA2B,EACrD,OAAO,qBAAqB,cAAc,EAC1C,OAAO,iBAAiB,wCAAwC,MAAM,EACtE,OAAO,OAAO;AAEjB,WAAW,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAU/B;AAED,QAAQ,MAAM;","names":["printChildren","writeFile","mkdir","dirname","mkdir","dirname","writeFile"]}