alanbox 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/0boxer/AGENTS.md +3 -2
  2. package/0boxer/src/commands/AGENTS.md +2 -1
  3. package/0boxer/src/commands/install.js +47 -0
  4. package/1swarmer/AGENTS.md +5 -2
  5. package/1swarmer/src/AGENTS.md +4 -3
  6. package/1swarmer/src/args.js +7 -0
  7. package/1swarmer/src/cli.js +26 -0
  8. package/1swarmer/src/commands/AGENTS.md +3 -0
  9. package/1swarmer/src/commands/review-file.js +997 -0
  10. package/1swarmer/src/runner/AGENTS.md +1 -0
  11. package/1swarmer/src/runner/codex-runner.js +23 -3
  12. package/2designer/README.md +3 -0
  13. package/2designer/dist/{cdp-engine-JK2XVDHK.js → cdp-engine-4AIWSWXO.js} +2 -2
  14. package/2designer/dist/{cdp-engine-A5WTMTVF.js → cdp-engine-SG4K2BCX.js} +2 -2
  15. package/2designer/dist/{chunk-NQ3ASZUE.js → chunk-7X7PTLZH.js} +2 -2
  16. package/2designer/dist/{chunk-JVF26NXD.js → chunk-DPOWNFOH.js} +2 -2
  17. package/2designer/dist/{chunk-SKEIVBOU.js → chunk-ISUUIOO7.js} +1 -1
  18. package/2designer/dist/chunk-ISUUIOO7.js.map +1 -0
  19. package/2designer/dist/cli.js +494 -244
  20. package/2designer/dist/cli.js.map +1 -1
  21. package/2designer/dist/index.d.ts +7 -18
  22. package/2designer/dist/index.js +5 -198
  23. package/2designer/dist/index.js.map +1 -1
  24. package/2designer/dist/{playwright-engine-YBRDIUHF.js → playwright-engine-YXBY3KEN.js} +2 -2
  25. package/2designer/dist/{playwright-engine-3YKJOUNU.js → playwright-engine-YXGDTSZ5.js} +2 -2
  26. package/2designer/dist/tint-UD4CJ7S2.js +7 -0
  27. package/2designer/dist/{tint-I3FTT23O.js → tint-YN63MLVN.js} +1 -1
  28. package/2designer/dist/tint-YN63MLVN.js.map +1 -0
  29. package/4reporter/README.md +24 -0
  30. package/4reporter/dist/cli.js +464 -0
  31. package/4reporter/dist/cli.js.map +1 -0
  32. package/4reporter/dist/index.d.ts +108 -0
  33. package/4reporter/dist/index.js +445 -0
  34. package/4reporter/dist/index.js.map +1 -0
  35. package/4reporter/package.json +39 -0
  36. package/README.md +13 -5
  37. package/bin/reporter.js +11 -0
  38. package/cli.js +31 -6
  39. package/mcp/README.md +7 -1
  40. package/mcp/config.toml +4 -0
  41. package/package.json +8 -4
  42. package/skills/AGENTS.md +3 -3
  43. package/skills/aitool/SKILL.md +1 -1
  44. package/skills/desginer/SKILL.md +65 -45
  45. package/skills/swarmer/SKILL.md +37 -0
  46. package/2designer/LICENSE +0 -21
  47. package/2designer/dist/chunk-SKEIVBOU.js.map +0 -1
  48. package/2designer/dist/tint-I3FTT23O.js.map +0 -1
  49. package/2designer/dist/tint-RUSSUAWA.js +0 -7
  50. /package/2designer/dist/{cdp-engine-JK2XVDHK.js.map → cdp-engine-4AIWSWXO.js.map} +0 -0
  51. /package/2designer/dist/{cdp-engine-A5WTMTVF.js.map → cdp-engine-SG4K2BCX.js.map} +0 -0
  52. /package/2designer/dist/{chunk-NQ3ASZUE.js.map → chunk-7X7PTLZH.js.map} +0 -0
  53. /package/2designer/dist/{chunk-JVF26NXD.js.map → chunk-DPOWNFOH.js.map} +0 -0
  54. /package/2designer/dist/{playwright-engine-YBRDIUHF.js.map → playwright-engine-YXBY3KEN.js.map} +0 -0
  55. /package/2designer/dist/{playwright-engine-3YKJOUNU.js.map → playwright-engine-YXGDTSZ5.js.map} +0 -0
  56. /package/2designer/dist/{tint-RUSSUAWA.js.map → tint-UD4CJ7S2.js.map} +0 -0
@@ -1 +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":"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","names":["printChildren","writeFile","mkdir","dirname","mkdir","dirname","writeFile"]}
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/commands/changelist.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { measure } from './commands/measure.js'\nimport { screenshot } from './commands/screenshot.js'\nimport { overlay } from './commands/overlay.js'\nimport { changelist } from './commands/changelist.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, build overlays, and list visual changes.',\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 4. changelist List changed regions between design/runtime screenshots\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 $ designer changelist --design design.png --runtime runtime.png --annotated changes.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 .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 Without --selector, provide at least --offset-x or --offset-y for direct full-page mode.\n`)\n\nprogram\n .command('changelist')\n .description('Detect changed regions between a design screenshot and a runtime screenshot.')\n .requiredOption('--design <path>', 'Path to design screenshot (PNG/JPG)')\n .option('--runtime <path>', 'Path to runtime screenshot; use this or --url')\n .option('--url <url>', 'Target page URL to capture as runtime screenshot; use this or --runtime')\n .option('--selector <selector>', 'CSS selector when capturing --url; prefix ~ for fuzzy class match')\n .option('--full-page', 'Capture full scrollable page when using --url')\n .option('--cdp <host:port>', 'CDP endpoint when using --url')\n .option('--output <path>', 'Write changelist JSON to file; default prints JSON')\n .option('--annotated <path>', 'Write side-by-side comparison PNG; both mode also writes per-mode PNG files')\n .option('--regions-dir <dir>', 'Write per-region design/runtime/compare PNGs and region JSON by scan mode')\n .option('--mode <mode>', 'both | word-sentence | graphic-large', 'both')\n .option('--threshold <n>', 'Override difference threshold for selected scans')\n .option('--group <px>', 'Override grouping distance for selected scans')\n .option('--min-area <px>', 'Override minimum region area for selected scans')\n .option('--max-area-percent <pct>', 'Override max region area percent for selected scans')\n .action(changelist)\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 { writeFile, mkdir } from 'fs/promises'\nimport { resolve, dirname } from 'path'\nimport { createEngine } from '../engine/create-engine.js'\nimport { resolveSelector } from '../engine/selector.js'\n\r\nexport interface OverlayCommandOptions {\n designImagePath: string\n targetUrl: string\n cdp?: string\n output?: string\n selector?: string\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 {\n designImagePath: resolve(raw.design),\n targetUrl: raw.url,\n cdp: raw.cdp,\n output: raw.output,\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 throw new Error('overlay requires --selector for component comparison, or --offset-x/--offset-y for full-page mode')\n}\n","import { readFile, writeFile, mkdir } from 'fs/promises'\nimport { basename, dirname, extname, join, resolve } from 'path'\nimport sharp from 'sharp'\nimport { createEngine } from '../engine/create-engine.js'\nimport { resolveSelector } from '../engine/selector.js'\n\nexport type ChangelistMode = 'word-sentence' | 'graphic-large'\nexport type ChangelistModeOption = ChangelistMode | 'both'\n\nexport interface ChangelistScanConfig {\n id: number\n mode: ChangelistMode\n label: string\n hiddenSlider: boolean\n threshold: number\n group: number\n minArea: number\n maxAreaPercent: number\n}\n\nexport interface ChangeRegion {\n id: number\n scanId: number\n mode: ChangelistMode\n x: number\n y: number\n width: number\n height: number\n area: number\n changedPixels: number\n changedPercent: number\n}\n\nexport interface ChangelistResult {\n design: string\n runtime: string\n size: { width: number; height: number }\n scans: Array<{\n id: number\n mode: ChangelistMode\n label: string\n params: Omit<ChangelistScanConfig, 'id' | 'mode' | 'label'>\n regions: ChangeRegion[]\n changedPixels: number\n changedPercent: number\n }>\n regions: ChangeRegion[]\n}\n\nexport interface ChangelistCommandOptions {\n designImagePath: string\n runtimeImagePath?: string\n runtimeUrl?: string\n selector?: string\n fullPage?: boolean\n cdp?: string\n output?: string\n annotated?: string\n regionsDir?: string\n mode: ChangelistModeOption\n threshold?: number\n group?: number\n minArea?: number\n maxAreaPercent?: number\n}\n\ninterface ImageData {\n data: Buffer\n width: number\n height: number\n}\n\ninterface Component {\n x: number\n y: number\n right: number\n bottom: number\n changedPixels: number\n}\n\ninterface AnnotatedOutput {\n path: string\n mode: ChangelistMode | 'combined'\n regions: number\n}\n\ninterface RegionExportOutput {\n dir: string\n mode: ChangelistMode\n regions: number\n}\n\nconst COMPARISON_GAP = 32\n\nconst DEFAULT_SCANS: ChangelistScanConfig[] = [\n {\n id: 1,\n mode: 'word-sentence',\n label: 'word / sentence',\n hiddenSlider: true,\n threshold: 20,\n group: 12,\n minArea: 114,\n maxAreaPercent: 15,\n },\n {\n id: 2,\n mode: 'graphic-large',\n label: 'graphic / large region',\n hiddenSlider: true,\n threshold: 25,\n group: 25,\n minArea: 761,\n maxAreaPercent: 50,\n },\n]\n\nexport function buildChangelistOptions(raw: Record<string, any>): ChangelistCommandOptions {\n if (!raw.design) throw new Error('--design is required (path to design screenshot)')\n if (raw.runtime && raw.url) throw new Error('Use either --runtime or --url, not both')\n if (!raw.runtime && !raw.url) throw new Error('--runtime or --url is required')\n\n return {\n designImagePath: resolve(raw.design),\n runtimeImagePath: raw.runtime ? resolve(raw.runtime) : undefined,\n runtimeUrl: raw.url,\n selector: raw.selector ? resolveSelector(raw.selector) : undefined,\n fullPage: raw.fullPage ?? false,\n cdp: raw.cdp,\n output: raw.output,\n annotated: raw.annotated,\n regionsDir: raw.regionsDir,\n mode: parseMode(raw.mode ?? 'both'),\n threshold: raw.threshold != null ? parseNumber(raw.threshold, '--threshold') : undefined,\n group: raw.group != null ? parseNumber(raw.group, '--group') : undefined,\n minArea: raw.minArea != null ? parseNumber(raw.minArea, '--min-area') : undefined,\n maxAreaPercent: raw.maxAreaPercent != null\n ? parsePercent(raw.maxAreaPercent, '--max-area-percent')\n : undefined,\n }\n}\n\nexport function resolveScanConfigs(opts: Pick<ChangelistCommandOptions, 'mode' | 'threshold' | 'group' | 'minArea' | 'maxAreaPercent'>): ChangelistScanConfig[] {\n const scans = DEFAULT_SCANS\n .filter(scan => opts.mode === 'both' || scan.mode === opts.mode)\n .map(scan => ({\n ...scan,\n threshold: opts.threshold ?? scan.threshold,\n group: opts.group ?? scan.group,\n minArea: opts.minArea ?? scan.minArea,\n maxAreaPercent: opts.maxAreaPercent ?? scan.maxAreaPercent,\n }))\n\n if (scans.length === 0) throw new Error(`Unsupported mode: ${opts.mode}`)\n return scans\n}\n\nexport async function detectChangelist(\n designImagePath: string,\n runtimeImage: string | Buffer,\n scans: ChangelistScanConfig[] = DEFAULT_SCANS,\n): Promise<ChangelistResult> {\n const design = await loadImage(designImagePath)\n const runtime = await loadImage(runtimeImage)\n\n if (design.width !== runtime.width || design.height !== runtime.height) {\n throw new Error(`Image sizes differ: design ${design.width}x${design.height}, runtime ${runtime.width}x${runtime.height}`)\n }\n\n const scanResults = scans.map(scan => {\n const mask = buildDiffMask(design, runtime, scan.threshold)\n const changedPixels = countMask(mask)\n const components = findComponents(mask, design.width, design.height)\n const grouped = mergeComponents(components, scan.group, maxRegionArea(scan, design.width, design.height))\n const regions = filterRegions(grouped, scan, design.width, design.height)\n\n return {\n id: scan.id,\n mode: scan.mode,\n label: scan.label,\n params: {\n hiddenSlider: scan.hiddenSlider,\n threshold: scan.threshold,\n group: scan.group,\n minArea: scan.minArea,\n maxAreaPercent: scan.maxAreaPercent,\n },\n regions,\n changedPixels,\n changedPercent: roundPercent(changedPixels / (design.width * design.height)),\n }\n })\n\n let nextId = 1\n const regions = scanResults\n .flatMap(scan => scan.regions.map(region => ({ ...region, id: nextId++ })))\n .sort((a, b) => b.area - a.area || a.y - b.y || a.x - b.x)\n .map((region, index) => ({ ...region, id: index + 1 }))\n\n return {\n design: designImagePath,\n runtime: typeof runtimeImage === 'string' ? runtimeImage : '<captured>',\n size: { width: design.width, height: design.height },\n scans: scanResults,\n regions,\n }\n}\n\nexport async function changelist(raw: Record<string, any>): Promise<void> {\n const opts = buildChangelistOptions(raw)\n const scans = resolveScanConfigs(opts)\n const runtime = opts.runtimeImagePath ?? await captureRuntime(opts)\n const result = await detectChangelist(opts.designImagePath, runtime, scans)\n let annotatedOutputs: AnnotatedOutput[] | undefined\n let regionOutputs: RegionExportOutput[] | undefined\n\n if (opts.annotated) {\n annotatedOutputs = await writeAnnotatedImages(opts.annotated, opts.designImagePath, runtime, scans, result)\n }\n\n if (opts.regionsDir) {\n regionOutputs = await writeRegionExports(opts.regionsDir, opts.designImagePath, runtime, scans, result)\n }\n\n if (opts.output) {\n await writeOutputFile(opts.output, Buffer.from(`${JSON.stringify(result, null, 2)}\\n`, 'utf8'))\n console.log(JSON.stringify({\n output: opts.output,\n annotated: annotatedOutputs ?? opts.annotated,\n regionsDir: regionOutputs ?? opts.regionsDir,\n regions: result.regions.length,\n scans: result.scans.map(scan => ({ id: scan.id, mode: scan.mode, regions: scan.regions.length })),\n }))\n return\n }\n\n console.log(JSON.stringify(result, null, 2))\n}\n\nasync function writeAnnotatedImages(\n annotatedPath: string,\n designImagePath: string,\n runtimeImage: string | Buffer,\n scans: ChangelistScanConfig[],\n result: ChangelistResult,\n): Promise<AnnotatedOutput[]> {\n const outputs: AnnotatedOutput[] = []\n\n if (scans.length > 1) {\n const combined = await renderComparisonImage(designImagePath, runtimeImage, scans, result.regions)\n await writeOutputFile(annotatedPath, combined)\n outputs.push({ path: annotatedPath, mode: 'combined', regions: result.regions.length })\n }\n\n for (const scan of scans) {\n const scanResult = result.scans.find(item => item.id === scan.id)\n const outputPath = scans.length === 1 ? annotatedPath : appendModeSuffix(annotatedPath, scan.mode)\n const image = await renderComparisonImage(designImagePath, runtimeImage, [scan], scanResult?.regions ?? [])\n await writeOutputFile(outputPath, image)\n outputs.push({ path: outputPath, mode: scan.mode, regions: scanResult?.regions.length ?? 0 })\n }\n\n return outputs\n}\n\nasync function writeRegionExports(\n outputRoot: string,\n designImagePath: string,\n runtimeImage: string | Buffer,\n scans: ChangelistScanConfig[],\n result: ChangelistResult,\n): Promise<RegionExportOutput[]> {\n const designBuffer = await readFile(designImagePath)\n const runtimeBuffer = typeof runtimeImage === 'string' ? await readFile(runtimeImage) : runtimeImage\n const outputs: RegionExportOutput[] = []\n\n for (const scan of scans) {\n const scanResult = result.scans.find(item => item.id === scan.id)\n const regions = scanResult?.regions ?? []\n const modeDir = join(outputRoot, regionDirName(scan.mode))\n\n for (const region of regions) {\n const regionDir = join(modeDir, String(region.id))\n const designCrop = await cropRegion(designBuffer, region)\n const runtimeCrop = await cropRegion(runtimeBuffer, region)\n const compare = await renderRegionPairImage(designCrop, runtimeCrop, region)\n const regionJson = {\n ...region,\n source: {\n design: designImagePath,\n runtime: typeof runtimeImage === 'string' ? runtimeImage : '<captured>',\n size: result.size,\n },\n scan: {\n id: scan.id,\n mode: scan.mode,\n label: scan.label,\n params: scanResult?.params,\n },\n }\n\n await writeOutputFile(join(regionDir, 'design.png'), designCrop)\n await writeOutputFile(join(regionDir, 'runtime.png'), runtimeCrop)\n await writeOutputFile(join(regionDir, 'compare.png'), compare)\n await writeOutputFile(join(regionDir, 'region.json'), Buffer.from(`${JSON.stringify(regionJson, null, 2)}\\n`, 'utf8'))\n }\n\n outputs.push({ dir: modeDir, mode: scan.mode, regions: regions.length })\n }\n\n return outputs\n}\n\nasync function captureRuntime(opts: ChangelistCommandOptions): Promise<Buffer> {\n if (!opts.runtimeUrl) throw new Error('--url is required when --runtime is not provided')\n\n const engine = await createEngine({ url: opts.runtimeUrl, cdp: opts.cdp })\n try {\n return await engine.screenshot({\n selector: opts.selector,\n fullPage: opts.fullPage,\n })\n } finally {\n await engine.close()\n }\n}\n\nasync function loadImage(input: string | Buffer): Promise<ImageData> {\n const source = typeof input === 'string' ? await readFile(input) : input\n const { data, info } = await sharp(source)\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n return {\n data,\n width: info.width,\n height: info.height,\n }\n}\n\nfunction buildDiffMask(design: ImageData, runtime: ImageData, threshold: number): Uint8Array {\n const total = design.width * design.height\n const mask = new Uint8Array(total)\n\n for (let pixel = 0, offset = 0; pixel < total; pixel++, offset += 4) {\n const diff = Math.max(\n Math.abs(design.data[offset] - runtime.data[offset]),\n Math.abs(design.data[offset + 1] - runtime.data[offset + 1]),\n Math.abs(design.data[offset + 2] - runtime.data[offset + 2]),\n Math.abs(design.data[offset + 3] - runtime.data[offset + 3]),\n )\n if (diff >= threshold) mask[pixel] = 1\n }\n\n return mask\n}\n\nfunction buildUnionDiffMask(design: ImageData, runtime: ImageData, scans: ChangelistScanConfig[]): Uint8Array {\n const total = design.width * design.height\n const mask = new Uint8Array(total)\n\n for (const scan of scans) {\n const scanMask = buildDiffMask(design, runtime, scan.threshold)\n for (let i = 0; i < total; i++) {\n if (scanMask[i]) mask[i] = 1\n }\n }\n\n return mask\n}\n\nfunction countMask(mask: Uint8Array): number {\n let count = 0\n for (const value of mask) count += value\n return count\n}\n\nfunction findComponents(mask: Uint8Array, width: number, height: number): Component[] {\n const visited = new Uint8Array(mask.length)\n const components: Component[] = []\n const queue: number[] = []\n\n for (let start = 0; start < mask.length; start++) {\n if (!mask[start] || visited[start]) continue\n\n visited[start] = 1\n queue.length = 0\n queue.push(start)\n\n let head = 0\n let changedPixels = 0\n let minX = width\n let minY = height\n let maxX = 0\n let maxY = 0\n\n while (head < queue.length) {\n const idx = queue[head++]\n const x = idx % width\n const y = Math.floor(idx / width)\n\n changedPixels++\n if (x < minX) minX = x\n if (y < minY) minY = y\n if (x > maxX) maxX = x\n if (y > maxY) maxY = y\n\n for (let dy = -1; dy <= 1; dy++) {\n const ny = y + dy\n if (ny < 0 || ny >= height) continue\n\n for (let dx = -1; dx <= 1; dx++) {\n if (dx === 0 && dy === 0) continue\n const nx = x + dx\n if (nx < 0 || nx >= width) continue\n\n const next = ny * width + nx\n if (!mask[next] || visited[next]) continue\n visited[next] = 1\n queue.push(next)\n }\n }\n }\n\n components.push({\n x: minX,\n y: minY,\n right: maxX + 1,\n bottom: maxY + 1,\n changedPixels,\n })\n }\n\n return components\n}\n\nfunction mergeComponents(components: Component[], group: number, maxArea: number): Component[] {\n const regions = components.map(component => ({ ...component }))\n let changed = true\n\n while (changed) {\n changed = false\n for (let i = 0; i < regions.length; i++) {\n for (let j = i + 1; j < regions.length; j++) {\n if (!shouldMerge(regions[i], regions[j], group)) continue\n const merged = mergeRegion(regions[i], regions[j])\n if (componentArea(merged) > maxArea) continue\n\n regions[i] = merged\n regions.splice(j, 1)\n changed = true\n j--\n }\n }\n }\n\n return regions\n}\n\nfunction maxRegionArea(scan: ChangelistScanConfig, width: number, height: number): number {\n return width * height * (scan.maxAreaPercent / 100)\n}\n\nfunction shouldMerge(a: Component, b: Component, gap: number): boolean {\n return !(\n a.right + gap < b.x ||\n b.right + gap < a.x ||\n a.bottom + gap < b.y ||\n b.bottom + gap < a.y\n )\n}\n\nfunction componentArea(component: Component): number {\n return (component.right - component.x) * (component.bottom - component.y)\n}\n\nfunction mergeRegion(a: Component, b: Component): Component {\n return {\n x: Math.min(a.x, b.x),\n y: Math.min(a.y, b.y),\n right: Math.max(a.right, b.right),\n bottom: Math.max(a.bottom, b.bottom),\n changedPixels: a.changedPixels + b.changedPixels,\n }\n}\n\nfunction filterRegions(\n components: Component[],\n scan: ChangelistScanConfig,\n width: number,\n height: number,\n): ChangeRegion[] {\n const imageArea = width * height\n return components\n .map(componentToRegion(scan, imageArea))\n .filter(region => region.area >= scan.minArea)\n .filter(region => region.changedPercent <= scan.maxAreaPercent)\n .sort((a, b) => b.area - a.area || a.y - b.y || a.x - b.x)\n .map((region, index) => ({ ...region, id: index + 1 }))\n}\n\nfunction componentToRegion(scan: ChangelistScanConfig, imageArea: number) {\n return (component: Component): ChangeRegion => {\n const width = component.right - component.x\n const height = component.bottom - component.y\n const area = width * height\n return {\n id: 0,\n scanId: scan.id,\n mode: scan.mode,\n x: component.x,\n y: component.y,\n width,\n height,\n area,\n changedPixels: component.changedPixels,\n changedPercent: roundPercent(area / imageArea),\n }\n }\n}\n\nasync function cropRegion(image: Buffer, region: ChangeRegion): Promise<Buffer> {\n return sharp(image)\n .extract({\n left: region.x,\n top: region.y,\n width: region.width,\n height: region.height,\n })\n .png()\n .toBuffer()\n}\n\nasync function renderRegionPairImage(designCrop: Buffer, runtimeCrop: Buffer, region: ChangeRegion): Promise<Buffer> {\n const designMeta = await sharp(designCrop).metadata()\n const runtimeMeta = await sharp(runtimeCrop).metadata()\n const width = designMeta.width ?? region.width\n const height = designMeta.height ?? region.height\n\n if (width !== runtimeMeta.width || height !== runtimeMeta.height) {\n throw new Error(`Region crop sizes differ for region ${region.id}: design ${width}x${height}, runtime ${runtimeMeta.width}x${runtimeMeta.height}`)\n }\n\n const runtimeOffsetX = width + COMPARISON_GAP\n const outputWidth = width * 2 + COMPARISON_GAP\n\n return sharp({\n create: {\n width: outputWidth,\n height,\n channels: 4,\n background: { r: 248, g: 250, b: 252, alpha: 1 },\n },\n })\n .composite([\n { input: designCrop, left: 0, top: 0 },\n { input: runtimeCrop, left: runtimeOffsetX, top: 0 },\n { input: Buffer.from(buildDividerSvg(outputWidth, height, runtimeOffsetX)), blend: 'over' },\n ])\n .png()\n .toBuffer()\n}\n\nexport async function renderComparisonImage(\n designImagePath: string,\n runtimeImage: string | Buffer,\n scans: ChangelistScanConfig[],\n regions: ChangeRegion[],\n): Promise<Buffer> {\n const design = await loadImage(designImagePath)\n const runtime = await loadImage(runtimeImage)\n\n if (design.width !== runtime.width || design.height !== runtime.height) {\n throw new Error(`Image sizes differ: design ${design.width}x${design.height}, runtime ${runtime.width}x${runtime.height}`)\n }\n\n const mask = buildUnionDiffMask(design, runtime, scans)\n const highlightedRuntime = Buffer.from(runtime.data)\n\n for (let pixel = 0, offset = 0; pixel < mask.length; pixel++, offset += 4) {\n if (!mask[pixel]) continue\n\n highlightedRuntime[offset] = 255\n highlightedRuntime[offset + 1] = Math.round(highlightedRuntime[offset + 1] * 0.35)\n highlightedRuntime[offset + 2] = Math.round(highlightedRuntime[offset + 2] * 0.35)\n highlightedRuntime[offset + 3] = 255\n }\n\n const runtimeOffsetX = design.width + COMPARISON_GAP\n const outputWidth = design.width * 2 + COMPARISON_GAP\n const designPanel = await imageDataToPng(design)\n const runtimePanel = await imageDataToPng({ ...runtime, data: highlightedRuntime })\n const svg = buildSideBySideRegionSvg(outputWidth, design.height, runtimeOffsetX, regions)\n\n return sharp({\n create: {\n width: outputWidth,\n height: design.height,\n channels: 4,\n background: { r: 248, g: 250, b: 252, alpha: 1 },\n },\n })\n .composite([\n { input: designPanel, left: 0, top: 0 },\n { input: runtimePanel, left: runtimeOffsetX, top: 0 },\n { input: Buffer.from(svg), blend: 'over' },\n ])\n .png()\n .toBuffer()\n}\n\nfunction imageDataToPng(image: ImageData): Promise<Buffer> {\n return sharp(image.data, {\n raw: {\n width: image.width,\n height: image.height,\n channels: 4,\n },\n })\n .png()\n .toBuffer()\n}\n\nfunction buildSideBySideRegionSvg(width: number, height: number, runtimeOffsetX: number, regions: ChangeRegion[]): string {\n const svg = [\n `<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">`,\n dividerSvgContent(height, runtimeOffsetX),\n ...regions.flatMap(region => [\n regionToSvg(region, 0),\n regionToSvg(region, runtimeOffsetX),\n ]),\n '</svg>',\n ].join('')\n\n return svg\n}\n\nfunction buildDividerSvg(width: number, height: number, runtimeOffsetX: number): string {\n return [\n `<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">`,\n dividerSvgContent(height, runtimeOffsetX),\n '</svg>',\n ].join('')\n}\n\nfunction dividerSvgContent(height: number, runtimeOffsetX: number): string {\n return [\n `<rect x=\"${runtimeOffsetX - COMPARISON_GAP}\" y=\"0\" width=\"${COMPARISON_GAP}\" height=\"${height}\" fill=\"#f8fafc\"/>`,\n `<line x1=\"${runtimeOffsetX - COMPARISON_GAP / 2}\" y1=\"0\" x2=\"${runtimeOffsetX - COMPARISON_GAP / 2}\" y2=\"${height}\" stroke=\"#d9e2ec\" stroke-width=\"1\"/>`,\n ].join('')\n}\n\nfunction regionToSvg(region: ChangeRegion, offsetX: number): string {\n const labelWidth = Math.max(14, String(region.id).length * 8 + 8)\n const labelX = Math.max(offsetX, offsetX + region.x + region.width - labelWidth)\n const labelY = Math.max(0, region.y - 12)\n return [\n `<rect x=\"${offsetX + region.x + 0.5}\" y=\"${region.y + 0.5}\" width=\"${Math.max(1, region.width - 1)}\" height=\"${Math.max(1, region.height - 1)}\" fill=\"none\" stroke=\"#ef3b2d\" stroke-width=\"1\"/>`,\n `<rect x=\"${labelX}\" y=\"${labelY}\" width=\"${labelWidth}\" height=\"14\" fill=\"#ef3b2d\"/>`,\n `<text x=\"${labelX + labelWidth / 2}\" y=\"${labelY + 10}\" text-anchor=\"middle\" font-family=\"Arial, sans-serif\" font-size=\"10\" font-weight=\"700\" fill=\"#fff\">${region.id}</text>`,\n ].join('')\n}\n\nasync function writeOutputFile(filePath: string, data: Buffer): Promise<void> {\n await mkdir(dirname(resolve(filePath)), { recursive: true }).catch(() => {})\n await writeFile(filePath, data)\n}\n\nfunction appendModeSuffix(filePath: string, mode: ChangelistMode): string {\n const ext = extname(filePath) || '.png'\n const name = extname(filePath) ? basename(filePath, extname(filePath)) : basename(filePath)\n return join(dirname(filePath), `${name}-${mode}${ext}`)\n}\n\nfunction regionDirName(mode: ChangelistMode): string {\n return mode === 'word-sentence' ? 'word' : 'graphic'\n}\n\nfunction parseMode(value: string): ChangelistModeOption {\n const normalized = String(value).trim().toLowerCase()\n if (['both', 'all'].includes(normalized)) return 'both'\n if (['word', 'words', 'sentence', 'sentences', 'word-sentence', 'word_sentence', 'text'].includes(normalized)) {\n return 'word-sentence'\n }\n if (['graphic', 'graphics', 'large', 'large-region', 'graphic-large', 'graphic_large'].includes(normalized)) {\n return 'graphic-large'\n }\n throw new Error(`invalid mode: ${value}. Use both, word-sentence, or graphic-large.`)\n}\n\nfunction parseNumber(value: unknown, flag: string): number {\n const n = Number(value)\n if (!Number.isFinite(n) || n < 0) throw new Error(`${flag} must be a non-negative number`)\n return n\n}\n\nfunction parsePercent(value: unknown, flag: string): number {\n const raw = String(value).trim().replace(/%$/, '')\n const n = Number(raw)\n if (!Number.isFinite(n) || n < 0 || n > 100) {\n throw new Error(`${flag} must be a number between 0 and 100`)\n }\n return n\n}\n\nfunction roundPercent(value: number): number {\n return Math.round(value * 10000) / 100\n}\n"],"mappings":"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","names":["printChildren","writeFile","mkdir","dirname","mkdir","dirname","writeFile","sharp","writeFile","mkdir","dirname","resolve","resolve","regions","mkdir","dirname","writeFile"]}
@@ -102,28 +102,17 @@ declare class PlaywrightEngine implements RuntimeEngine {
102
102
  close(): Promise<void>;
103
103
  }
104
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
105
  type TintMode = 'magenta' | 'ghost' | 'difference';
117
106
  /**
118
- * Process a design image for overlay compositing.
107
+ * 处理设计稿截图,生成适合叠到运行页上的 PNG 重影。
119
108
  *
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
109
+ * 模式说明:
110
+ * - 'magenta': 把非白色像素染成品红,适合白底或浅色背景的设计稿。
111
+ * - 'ghost': 只统一降低透明度,适合背景不是纯白的截图。
112
+ * - 'difference': 不预处理,由调用方使用 difference 混合模式。
124
113
  *
125
- * Returns a PNG buffer with alpha channel.
114
+ * 返回带 alpha 通道的 PNG buffer
126
115
  */
127
116
  declare function tintDesignImage(imagePath: string, mode?: TintMode): Promise<Buffer>;
128
117
 
129
- export { type BBox, CdpEngine, type ChildElement, type EngineOptions, type MeasureResult, type OverlayParams, PlaywrightEngine, type RuntimeEngine, type ScreenshotOptions, createEngine, generateOverlayHtml, resolveEngineType, tintDesignImage };
118
+ export { type BBox, CdpEngine, type ChildElement, type EngineOptions, type MeasureResult, type OverlayParams, PlaywrightEngine, type RuntimeEngine, type ScreenshotOptions, createEngine, resolveEngineType, tintDesignImage };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  tintDesignImage
3
- } from "./chunk-SKEIVBOU.js";
3
+ } from "./chunk-ISUUIOO7.js";
4
4
  import {
5
5
  CdpEngine
6
- } from "./chunk-JVF26NXD.js";
6
+ } from "./chunk-DPOWNFOH.js";
7
7
  import {
8
8
  PlaywrightEngine
9
- } from "./chunk-NQ3ASZUE.js";
9
+ } from "./chunk-7X7PTLZH.js";
10
10
  import "./chunk-UVKSRKXR.js";
11
11
 
12
12
  // src/engine/create-engine.ts
@@ -16,214 +16,21 @@ function resolveEngineType(options) {
16
16
  async function createEngine(options) {
17
17
  const type = resolveEngineType(options);
18
18
  if (type === "cdp") {
19
- const { CdpEngine: CdpEngine2 } = await import("./cdp-engine-A5WTMTVF.js");
19
+ const { CdpEngine: CdpEngine2 } = await import("./cdp-engine-SG4K2BCX.js");
20
20
  const [host, portStr] = options.cdp.split(":");
21
21
  const port = parseInt(portStr, 10);
22
22
  return CdpEngine2.create(host, port, options.url);
23
23
  }
24
- const { PlaywrightEngine: PlaywrightEngine2 } = await import("./playwright-engine-3YKJOUNU.js");
24
+ const { PlaywrightEngine: PlaywrightEngine2 } = await import("./playwright-engine-YXGDTSZ5.js");
25
25
  return PlaywrightEngine2.create(options.url, {
26
26
  headless: options.headless ?? true,
27
27
  viewport: options.viewport
28
28
  });
29
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
30
  export {
223
31
  CdpEngine,
224
32
  PlaywrightEngine,
225
33
  createEngine,
226
- generateOverlayHtml,
227
34
  resolveEngineType,
228
35
  tintDesignImage
229
36
  };
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/engine/create-engine.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"],"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;","names":["CdpEngine","PlaywrightEngine"]}
@@ -145,7 +145,7 @@ var PlaywrightEngine = class _PlaywrightEngine {
145
145
  return this.page.evaluate(expression);
146
146
  }
147
147
  async injectOverlay(params) {
148
- const { tintDesignImage } = await import("./tint-I3FTT23O.js");
148
+ const { tintDesignImage } = await import("./tint-YN63MLVN.js");
149
149
  const tintedBuf = await tintDesignImage(params.designImagePath);
150
150
  const base64 = tintedBuf.toString("base64");
151
151
  await this.page.evaluate(
@@ -183,4 +183,4 @@ var PlaywrightEngine = class _PlaywrightEngine {
183
183
  export {
184
184
  PlaywrightEngine
185
185
  };
186
- //# sourceMappingURL=playwright-engine-YBRDIUHF.js.map
186
+ //# sourceMappingURL=playwright-engine-YXBY3KEN.js.map
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  PlaywrightEngine
3
- } from "./chunk-NQ3ASZUE.js";
3
+ } from "./chunk-7X7PTLZH.js";
4
4
  import "./chunk-UVKSRKXR.js";
5
5
  export {
6
6
  PlaywrightEngine
7
7
  };
8
- //# sourceMappingURL=playwright-engine-3YKJOUNU.js.map
8
+ //# sourceMappingURL=playwright-engine-YXGDTSZ5.js.map
@@ -0,0 +1,7 @@
1
+ import {
2
+ tintDesignImage
3
+ } from "./chunk-ISUUIOO7.js";
4
+ export {
5
+ tintDesignImage
6
+ };
7
+ //# sourceMappingURL=tint-UD4CJ7S2.js.map
@@ -57,4 +57,4 @@ function tintGhost(pixels, info, opacity) {
57
57
  export {
58
58
  tintDesignImage
59
59
  };
60
- //# sourceMappingURL=tint-I3FTT23O.js.map
60
+ //# sourceMappingURL=tint-YN63MLVN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/overlay/tint.ts"],"sourcesContent":["import sharp from 'sharp'\r\n\r\nexport type TintMode = 'magenta' | 'ghost' | 'difference'\r\n\r\n/**\n * 处理设计稿截图,生成适合叠到运行页上的 PNG 重影。\n *\n * 模式说明:\n * - 'magenta': 把非白色像素染成品红,适合白底或浅色背景的设计稿。\n * - 'ghost': 只统一降低透明度,适合背景不是纯白的截图。\n * - 'difference': 不预处理,由调用方使用 difference 混合模式。\n *\n * 返回带 alpha 通道的 PNG buffer。\n */\nexport async function tintDesignImage(\n imagePath: string,\r\n mode: TintMode = 'auto' as any,\r\n): Promise<Buffer> {\r\n const { data, info } = await sharp(imagePath)\r\n .ensureAlpha()\r\n .raw()\r\n .toBuffer({ resolveWithObject: true })\r\n\r\n const pixels = new Uint8Array(data.buffer)\r\n\r\n // 自动模式只粗略判断背景亮度:白底走品红染色,深色/复杂背景走 ghost。\n const resolvedMode = mode === ('auto' as any) ? detectMode(pixels) : mode\n\r\n if (resolvedMode === 'magenta') {\r\n return tintMagenta(pixels, info)\r\n }\n\n if (resolvedMode === 'difference') {\n // difference 模式需要保留原图颜色,混合策略交给调用方处理。\n return sharp(imagePath).ensureAlpha().png().toBuffer()\n }\n\n // ghost 模式不改颜色,只统一压低 alpha,避免覆盖运行页细节。\n return tintGhost(pixels, info, 0.4)\n}\n\nfunction detectMode(pixels: Uint8Array): TintMode {\n // 采样前若干像素估算背景亮度;当前目标是快速区分浅底和非浅底,不做精确抠图。\n let brightCount = 0\n const sampleSize = Math.min(pixels.length / 4, 1000)\r\n const step = Math.floor(pixels.length / 4 / sampleSize)\r\n\r\n for (let i = 0; i < sampleSize; i++) {\r\n const idx = i * step * 4\r\n const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3\r\n if (brightness > 220) brightCount++\r\n }\r\n\r\n return (brightCount / sampleSize) > 0.5 ? 'magenta' : 'ghost'\r\n}\r\n\r\nconst LIGHT_THRESHOLD = 230\nconst MAGENTA: [number, number, number] = [220, 40, 160]\n\r\nfunction tintMagenta(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\n const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2]\n const brightness = (r + g + b) / 3\n\n if (brightness > LIGHT_THRESHOLD) {\n // 浅色背景直接透明化,避免白底盖住运行页。\n pixels[i + 3] = 0\n } else {\n // 像素越暗说明设计内容越明显,叠层 alpha 越高,便于观察 1-2px 偏差。\n const darkness = 1 - brightness / 255\n pixels[i] = MAGENTA[0]\n pixels[i + 1] = MAGENTA[1]\r\n pixels[i + 2] = MAGENTA[2]\r\n pixels[i + 3] = Math.round(darkness * 200)\r\n }\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n\r\nfunction tintGhost(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n opacity: number,\r\n): Promise<Buffer> {\n for (let i = 0; i < pixels.length; i += 4) {\n // 仅缩放 alpha,保留原图色相,用于非白底设计稿的低侵入叠加。\n pixels[i + 3] = Math.round(pixels[i + 3] * opacity)\n }\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n"],"mappings":";;;;AAAA,OAAO,WAAW;AAclB,eAAsB,gBACpB,WACA,OAAiB,QACA;AACjB,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAAM,SAAS,EACzC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,SAAS,IAAI,WAAW,KAAK,MAAM;AAGzC,QAAM,eAAe,SAAU,SAAiB,WAAW,MAAM,IAAI;AAErE,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,QAAQ,IAAI;AAAA,EACjC;AAEA,MAAI,iBAAiB,cAAc;AAEjC,WAAO,MAAM,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAAA,EACvD;AAGA,SAAO,UAAU,QAAQ,MAAM,GAAG;AACpC;AAEA,SAAS,WAAW,QAA8B;AAEhD,MAAI,cAAc;AAClB,QAAM,aAAa,KAAK,IAAI,OAAO,SAAS,GAAG,GAAI;AACnD,QAAM,OAAO,KAAK,MAAM,OAAO,SAAS,IAAI,UAAU;AAEtD,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK;AACvE,QAAI,aAAa,IAAK;AAAA,EACxB;AAEA,SAAQ,cAAc,aAAc,MAAM,YAAY;AACxD;AAEA,IAAM,kBAAkB;AACxB,IAAM,UAAoC,CAAC,KAAK,IAAI,GAAG;AAEvD,SAAS,YACP,QACA,MACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,UAAM,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC;AACxD,UAAM,cAAc,IAAI,IAAI,KAAK;AAEjC,QAAI,aAAa,iBAAiB;AAEhC,aAAO,IAAI,CAAC,IAAI;AAAA,IAClB,OAAO;AAEL,YAAM,WAAW,IAAI,aAAa;AAClC,aAAO,CAAC,IAAI,QAAQ,CAAC;AACrB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,KAAK,MAAM,WAAW,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;AAEA,SAAS,UACP,QACA,MACA,SACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AAEzC,WAAO,IAAI,CAAC,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO;AAAA,EACpD;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;","names":[]}
@@ -0,0 +1,24 @@
1
+ # reporter
2
+
3
+ `reporter` scans local Git history for a user and writes a weekly report in Chinese Markdown.
4
+
5
+ ## Usage
6
+
7
+ ```powershell
8
+ reporter weekly --path "C:\path\to\repo-or-file" --user "Your Git Name"
9
+ alanbox reporter weekly --path "C:\path\to\repo-or-file" --user "you@example.com" --output weekly.md
10
+ ```
11
+
12
+ If `--output` is omitted, reports are written under `C:\Users\lenovo\Desktop\all-project\.tmp\reporter`.
13
+
14
+ By default it scans all refs from local Monday 00:00 to now. Use `--current-branch` to restrict the scan, or `--since` / `--until` for a fixed range.
15
+ The scan is scoped to the file or folder passed with `--path`; use `--whole-repo` to scan every path in the Git repository.
16
+
17
+ ## Commands
18
+
19
+ ```powershell
20
+ reporter weekly --path "C:\repo" --user "Alice"
21
+ reporter weekly --path "C:\repo\src\index.ts" --user "Alice,alice@example.com" --format json
22
+ reporter weekly --path "C:\repo" --user "Alice" --since 2026-06-15 --until 2026-06-18 --output weekly.md
23
+ reporter weekly --path "C:\repo\packages\app" --user "Alice" --whole-repo
24
+ ```