devlens-mcp 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -21
- package/docs/setup-guide.md +11 -31
- package/package.json +1 -1
- package/.claude/settings.json +0 -12
- package/.claude/settings.local.json +0 -17
- package/bin/cli.ts +0 -22
- package/bin/register.ts +0 -96
- package/dist/src/config/devlens-config.d.ts +0 -92
- package/dist/src/config/devlens-config.d.ts.map +0 -1
- package/dist/src/config/devlens-config.js +0 -70
- package/dist/src/config/devlens-config.js.map +0 -1
- package/dist/src/index.d.ts +0 -35
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -8
- package/dist/src/index.js.map +0 -1
- package/dist/src/metro/cdp-client.d.ts +0 -48
- package/dist/src/metro/cdp-client.d.ts.map +0 -1
- package/dist/src/metro/cdp-client.js +0 -127
- package/dist/src/metro/cdp-client.js.map +0 -1
- package/dist/src/metro/log-collector.d.ts +0 -30
- package/dist/src/metro/log-collector.d.ts.map +0 -1
- package/dist/src/metro/log-collector.js +0 -114
- package/dist/src/metro/log-collector.js.map +0 -1
- package/dist/src/metro/metro-bridge.d.ts +0 -56
- package/dist/src/metro/metro-bridge.d.ts.map +0 -1
- package/dist/src/metro/metro-bridge.js +0 -255
- package/dist/src/metro/metro-bridge.js.map +0 -1
- package/dist/src/metro/network-inspector.d.ts +0 -34
- package/dist/src/metro/network-inspector.d.ts.map +0 -1
- package/dist/src/metro/network-inspector.js +0 -100
- package/dist/src/metro/network-inspector.js.map +0 -1
- package/dist/src/platform/android/adb.d.ts +0 -50
- package/dist/src/platform/android/adb.d.ts.map +0 -1
- package/dist/src/platform/android/adb.js +0 -137
- package/dist/src/platform/android/adb.js.map +0 -1
- package/dist/src/platform/android/android-device.d.ts +0 -21
- package/dist/src/platform/android/android-device.d.ts.map +0 -1
- package/dist/src/platform/android/android-device.js +0 -94
- package/dist/src/platform/android/android-device.js.map +0 -1
- package/dist/src/platform/android/ui-automator.d.ts +0 -17
- package/dist/src/platform/android/ui-automator.d.ts.map +0 -1
- package/dist/src/platform/android/ui-automator.js +0 -126
- package/dist/src/platform/android/ui-automator.js.map +0 -1
- package/dist/src/platform/device-manager.d.ts +0 -28
- package/dist/src/platform/device-manager.d.ts.map +0 -1
- package/dist/src/platform/device-manager.js +0 -185
- package/dist/src/platform/device-manager.js.map +0 -1
- package/dist/src/platform/device.d.ts +0 -86
- package/dist/src/platform/device.d.ts.map +0 -1
- package/dist/src/platform/device.js +0 -7
- package/dist/src/platform/device.js.map +0 -1
- package/dist/src/platform/ios/accessibility.d.ts +0 -17
- package/dist/src/platform/ios/accessibility.d.ts.map +0 -1
- package/dist/src/platform/ios/accessibility.js +0 -159
- package/dist/src/platform/ios/accessibility.js.map +0 -1
- package/dist/src/platform/ios/ios-device.d.ts +0 -22
- package/dist/src/platform/ios/ios-device.d.ts.map +0 -1
- package/dist/src/platform/ios/ios-device.js +0 -97
- package/dist/src/platform/ios/ios-device.js.map +0 -1
- package/dist/src/platform/ios/simctl.d.ts +0 -54
- package/dist/src/platform/ios/simctl.d.ts.map +0 -1
- package/dist/src/platform/ios/simctl.js +0 -192
- package/dist/src/platform/ios/simctl.js.map +0 -1
- package/dist/src/server.d.ts +0 -3
- package/dist/src/server.d.ts.map +0 -1
- package/dist/src/server.js +0 -176
- package/dist/src/server.js.map +0 -1
- package/dist/src/snapshot/formatter.d.ts +0 -18
- package/dist/src/snapshot/formatter.d.ts.map +0 -1
- package/dist/src/snapshot/formatter.js +0 -86
- package/dist/src/snapshot/formatter.js.map +0 -1
- package/dist/src/snapshot/ref-registry.d.ts +0 -67
- package/dist/src/snapshot/ref-registry.d.ts.map +0 -1
- package/dist/src/snapshot/ref-registry.js +0 -169
- package/dist/src/snapshot/ref-registry.js.map +0 -1
- package/dist/src/snapshot/snapshot-differ.d.ts +0 -57
- package/dist/src/snapshot/snapshot-differ.d.ts.map +0 -1
- package/dist/src/snapshot/snapshot-differ.js +0 -153
- package/dist/src/snapshot/snapshot-differ.js.map +0 -1
- package/dist/src/tools/app-tools.d.ts +0 -71
- package/dist/src/tools/app-tools.d.ts.map +0 -1
- package/dist/src/tools/app-tools.js +0 -97
- package/dist/src/tools/app-tools.js.map +0 -1
- package/dist/src/tools/device-tools.d.ts +0 -53
- package/dist/src/tools/device-tools.d.ts.map +0 -1
- package/dist/src/tools/device-tools.js +0 -86
- package/dist/src/tools/device-tools.js.map +0 -1
- package/dist/src/tools/ds-tools.d.ts +0 -65
- package/dist/src/tools/ds-tools.d.ts.map +0 -1
- package/dist/src/tools/ds-tools.js +0 -314
- package/dist/src/tools/ds-tools.js.map +0 -1
- package/dist/src/tools/interaction-tools.d.ts +0 -248
- package/dist/src/tools/interaction-tools.d.ts.map +0 -1
- package/dist/src/tools/interaction-tools.js +0 -391
- package/dist/src/tools/interaction-tools.js.map +0 -1
- package/dist/src/tools/metro-tools.d.ts +0 -115
- package/dist/src/tools/metro-tools.d.ts.map +0 -1
- package/dist/src/tools/metro-tools.js +0 -270
- package/dist/src/tools/metro-tools.js.map +0 -1
- package/dist/src/tools/navigation-tools.d.ts +0 -36
- package/dist/src/tools/navigation-tools.d.ts.map +0 -1
- package/dist/src/tools/navigation-tools.js +0 -60
- package/dist/src/tools/navigation-tools.js.map +0 -1
- package/dist/src/tools/screenshot-tools.d.ts +0 -298
- package/dist/src/tools/screenshot-tools.d.ts.map +0 -1
- package/dist/src/tools/screenshot-tools.js +0 -565
- package/dist/src/tools/screenshot-tools.js.map +0 -1
- package/dist/src/tools/snapshot-tools.d.ts +0 -161
- package/dist/src/tools/snapshot-tools.d.ts.map +0 -1
- package/dist/src/tools/snapshot-tools.js +0 -479
- package/dist/src/tools/snapshot-tools.js.map +0 -1
- package/dist/src/utils/image-preprocess.d.ts +0 -49
- package/dist/src/utils/image-preprocess.d.ts.map +0 -1
- package/dist/src/utils/image-preprocess.js +0 -322
- package/dist/src/utils/image-preprocess.js.map +0 -1
- package/dist/src/utils/retry.d.ts +0 -21
- package/dist/src/utils/retry.d.ts.map +0 -1
- package/dist/src/utils/retry.js +0 -33
- package/dist/src/utils/retry.js.map +0 -1
- package/dist/src/visual/comparator.d.ts +0 -51
- package/dist/src/visual/comparator.d.ts.map +0 -1
- package/dist/src/visual/comparator.js +0 -119
- package/dist/src/visual/comparator.js.map +0 -1
- package/dist/src/visual/layout-analyzer.d.ts +0 -64
- package/dist/src/visual/layout-analyzer.d.ts.map +0 -1
- package/dist/src/visual/layout-analyzer.js +0 -198
- package/dist/src/visual/layout-analyzer.js.map +0 -1
- package/dist/src/visual/screenshot.d.ts +0 -17
- package/dist/src/visual/screenshot.d.ts.map +0 -1
- package/dist/src/visual/screenshot.js +0 -39
- package/dist/src/visual/screenshot.js.map +0 -1
- package/src/config/devlens-config.ts +0 -76
- package/src/index.ts +0 -5
- package/src/metro/cdp-client.ts +0 -160
- package/src/metro/log-collector.ts +0 -137
- package/src/metro/metro-bridge.ts +0 -307
- package/src/metro/network-inspector.ts +0 -134
- package/src/platform/android/adb.ts +0 -200
- package/src/platform/android/android-device.ts +0 -116
- package/src/platform/android/ui-automator.ts +0 -141
- package/src/platform/device-manager.ts +0 -229
- package/src/platform/device.ts +0 -110
- package/src/platform/ios/accessibility.ts +0 -189
- package/src/platform/ios/ios-device.ts +0 -116
- package/src/platform/ios/simctl.ts +0 -244
- package/src/server.ts +0 -228
- package/src/snapshot/formatter.ts +0 -102
- package/src/snapshot/ref-registry.ts +0 -230
- package/src/snapshot/snapshot-differ.ts +0 -220
- package/src/tools/app-tools.ts +0 -111
- package/src/tools/device-tools.ts +0 -96
- package/src/tools/ds-tools.ts +0 -395
- package/src/tools/interaction-tools.ts +0 -467
- package/src/tools/metro-tools.ts +0 -320
- package/src/tools/navigation-tools.ts +0 -71
- package/src/tools/screenshot-tools.ts +0 -698
- package/src/tools/snapshot-tools.ts +0 -585
- package/src/utils/image-preprocess.ts +0 -430
- package/src/utils/retry.ts +0 -51
- package/src/visual/comparator.ts +0 -191
- package/src/visual/layout-analyzer.ts +0 -283
- package/src/visual/screenshot.ts +0 -49
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { RefEntry } from "../snapshot/ref-registry.js";
|
|
2
|
-
/**
|
|
3
|
-
* Layout-aware comparison engine.
|
|
4
|
-
*
|
|
5
|
-
* Uses the accessibility tree (via RefRegistry) to identify semantic UI regions,
|
|
6
|
-
* scales those regions from device coordinate space into Figma image space,
|
|
7
|
-
* and compares each region independently via pixelmatch.
|
|
8
|
-
*
|
|
9
|
-
* This gives per-element similarity scores ("Header: 92%, ChatInput: 44%")
|
|
10
|
-
* instead of a single misleading global pixel-diff percentage.
|
|
11
|
-
*/
|
|
12
|
-
export interface RegionComparisonResult {
|
|
13
|
-
ref: string;
|
|
14
|
-
elementType: string;
|
|
15
|
-
text?: string;
|
|
16
|
-
label?: string;
|
|
17
|
-
/** Element bounds in device pixel space */
|
|
18
|
-
deviceBounds: {
|
|
19
|
-
left: number;
|
|
20
|
-
top: number;
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
};
|
|
24
|
-
/** Corresponding bounds in Figma image space (after scaling) */
|
|
25
|
-
figmaBounds: {
|
|
26
|
-
left: number;
|
|
27
|
-
top: number;
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
};
|
|
31
|
-
similarity: number;
|
|
32
|
-
diffPixels: number;
|
|
33
|
-
totalPixels: number;
|
|
34
|
-
verdict: "excellent" | "good" | "partial" | "poor";
|
|
35
|
-
}
|
|
36
|
-
export interface LayoutReport {
|
|
37
|
-
regions: RegionComparisonResult[];
|
|
38
|
-
/** Weighted average similarity across all analyzed regions */
|
|
39
|
-
overallSimilarity: number;
|
|
40
|
-
/** Number of refs skipped (too small, out of bounds, crop failed) */
|
|
41
|
-
skippedRefs: number;
|
|
42
|
-
deviceDimensions: {
|
|
43
|
-
width: number;
|
|
44
|
-
height: number;
|
|
45
|
-
};
|
|
46
|
-
figmaDimensions: {
|
|
47
|
-
width: number;
|
|
48
|
-
height: number;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Core function: for each ref with sufficient area, scale its device bounds
|
|
53
|
-
* into Figma image space, crop both images, and compare independently.
|
|
54
|
-
*
|
|
55
|
-
* @param deviceBuffer Full-screen device screenshot PNG
|
|
56
|
-
* @param figmaBuffer Preprocessed Figma image PNG (artifacts already removed)
|
|
57
|
-
* @param refs All RefEntry objects from RefRegistry.getAllRefs()
|
|
58
|
-
* @param deviceW Device screen width in pixels
|
|
59
|
-
* @param deviceH Device screen height in pixels
|
|
60
|
-
*/
|
|
61
|
-
export declare function buildLayoutReport(deviceBuffer: Buffer, figmaBuffer: Buffer, refs: RefEntry[], deviceW: number, deviceH: number): Promise<LayoutReport>;
|
|
62
|
-
/** Format layout report as human-readable text for MCP tool output */
|
|
63
|
-
export declare function formatLayoutReport(report: LayoutReport): string;
|
|
64
|
-
//# sourceMappingURL=layout-analyzer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layout-analyzer.d.ts","sourceRoot":"","sources":["../../../src/visual/layout-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D;;;;;;;;;GASG;AAEH,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,gEAAgE;IAChE,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;CACpD;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,8DAA8D;IAC9D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD;AA6FD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EAAE,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAyFvB;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CA4C/D"}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.buildLayoutReport = buildLayoutReport;
|
|
7
|
-
exports.formatLayoutReport = formatLayoutReport;
|
|
8
|
-
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
9
|
-
/** Minimum element area (px²) for inclusion in layout analysis */
|
|
10
|
-
const MIN_REGION_AREA = 3000;
|
|
11
|
-
/** Color difference threshold for pixelmatch (matches global comparator default) */
|
|
12
|
-
const REGION_THRESHOLD = 0.1;
|
|
13
|
-
function verdictFromSimilarity(s) {
|
|
14
|
-
if (s >= 0.92)
|
|
15
|
-
return "excellent";
|
|
16
|
-
if (s >= 0.80)
|
|
17
|
-
return "good";
|
|
18
|
-
if (s >= 0.60)
|
|
19
|
-
return "partial";
|
|
20
|
-
return "poor";
|
|
21
|
-
}
|
|
22
|
-
/** Scale device-space element bounds into Figma image pixel space */
|
|
23
|
-
function scaleToFigmaSpace(bounds, deviceW, deviceH, figmaW, figmaH) {
|
|
24
|
-
const scaleX = figmaW / deviceW;
|
|
25
|
-
const scaleY = figmaH / deviceH;
|
|
26
|
-
return {
|
|
27
|
-
left: Math.round(bounds.x * scaleX),
|
|
28
|
-
top: Math.round(bounds.y * scaleY),
|
|
29
|
-
width: Math.max(1, Math.round(bounds.width * scaleX)),
|
|
30
|
-
height: Math.max(1, Math.round(bounds.height * scaleY)),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
/** Clamp bounds to image dimensions. Returns null if there's no overlap. */
|
|
34
|
-
function clampBounds(b, maxW, maxH) {
|
|
35
|
-
const left = Math.max(0, b.left);
|
|
36
|
-
const top = Math.max(0, b.top);
|
|
37
|
-
const right = Math.min(maxW, b.left + b.width);
|
|
38
|
-
const bottom = Math.min(maxH, b.top + b.height);
|
|
39
|
-
if (right <= left || bottom <= top)
|
|
40
|
-
return null;
|
|
41
|
-
return { left, top, width: right - left, height: bottom - top };
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Crop a PNG buffer to a region and return raw RGBA bytes + dimensions.
|
|
45
|
-
* Uses sharp's raw() output so pixelmatch can consume it directly
|
|
46
|
-
* without a PNG encode/decode round-trip.
|
|
47
|
-
*/
|
|
48
|
-
async function cropToRaw(imageBuffer, bounds) {
|
|
49
|
-
try {
|
|
50
|
-
const sharp = (await import("sharp")).default;
|
|
51
|
-
const result = await sharp(imageBuffer)
|
|
52
|
-
.extract({ left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height })
|
|
53
|
-
.ensureAlpha()
|
|
54
|
-
.raw()
|
|
55
|
-
.toBuffer({ resolveWithObject: true });
|
|
56
|
-
return { data: result.data, width: result.info.width, height: result.info.height };
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Resize raw RGBA bytes to target dimensions.
|
|
64
|
-
* Used to match device crop and Figma crop pixel dimensions before pixelmatch.
|
|
65
|
-
*/
|
|
66
|
-
async function resizeRaw(raw, targetW, targetH) {
|
|
67
|
-
if (raw.width === targetW && raw.height === targetH)
|
|
68
|
-
return raw;
|
|
69
|
-
try {
|
|
70
|
-
const sharp = (await import("sharp")).default;
|
|
71
|
-
const result = await sharp(raw.data, {
|
|
72
|
-
raw: { width: raw.width, height: raw.height, channels: 4 },
|
|
73
|
-
})
|
|
74
|
-
.resize(targetW, targetH, { fit: "fill" })
|
|
75
|
-
.raw()
|
|
76
|
-
.toBuffer({ resolveWithObject: true });
|
|
77
|
-
return { data: result.data, width: result.info.width, height: result.info.height };
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Core function: for each ref with sufficient area, scale its device bounds
|
|
85
|
-
* into Figma image space, crop both images, and compare independently.
|
|
86
|
-
*
|
|
87
|
-
* @param deviceBuffer Full-screen device screenshot PNG
|
|
88
|
-
* @param figmaBuffer Preprocessed Figma image PNG (artifacts already removed)
|
|
89
|
-
* @param refs All RefEntry objects from RefRegistry.getAllRefs()
|
|
90
|
-
* @param deviceW Device screen width in pixels
|
|
91
|
-
* @param deviceH Device screen height in pixels
|
|
92
|
-
*/
|
|
93
|
-
async function buildLayoutReport(deviceBuffer, figmaBuffer, refs, deviceW, deviceH) {
|
|
94
|
-
const sharp = (await import("sharp")).default;
|
|
95
|
-
const figmaMeta = await sharp(figmaBuffer).metadata();
|
|
96
|
-
const figmaW = figmaMeta.width;
|
|
97
|
-
const figmaH = figmaMeta.height;
|
|
98
|
-
const regions = [];
|
|
99
|
-
let skippedRefs = 0;
|
|
100
|
-
for (const entry of refs) {
|
|
101
|
-
const { bounds } = entry;
|
|
102
|
-
const area = bounds.width * bounds.height;
|
|
103
|
-
if (area < MIN_REGION_AREA) {
|
|
104
|
-
skippedRefs++;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const deviceBounds = { left: bounds.x, top: bounds.y, width: bounds.width, height: bounds.height };
|
|
108
|
-
const figmaBounds = scaleToFigmaSpace(bounds, deviceW, deviceH, figmaW, figmaH);
|
|
109
|
-
const clampedDevice = clampBounds(deviceBounds, deviceW, deviceH);
|
|
110
|
-
const clampedFigma = clampBounds(figmaBounds, figmaW, figmaH);
|
|
111
|
-
if (!clampedDevice || !clampedFigma) {
|
|
112
|
-
skippedRefs++;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
// Crop both in parallel
|
|
116
|
-
const [deviceCrop, figmaCrop] = await Promise.all([
|
|
117
|
-
cropToRaw(deviceBuffer, clampedDevice),
|
|
118
|
-
cropToRaw(figmaBuffer, clampedFigma),
|
|
119
|
-
]);
|
|
120
|
-
if (!deviceCrop || !figmaCrop) {
|
|
121
|
-
skippedRefs++;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
// Resize Figma crop to exactly match device crop dimensions
|
|
125
|
-
const figmaResized = await resizeRaw(figmaCrop, deviceCrop.width, deviceCrop.height);
|
|
126
|
-
if (!figmaResized) {
|
|
127
|
-
skippedRefs++;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const totalPixels = deviceCrop.width * deviceCrop.height;
|
|
131
|
-
const diff = Buffer.alloc(totalPixels * 4);
|
|
132
|
-
const diffPixels = (0, pixelmatch_1.default)(deviceCrop.data, figmaResized.data, diff, deviceCrop.width, deviceCrop.height, { threshold: REGION_THRESHOLD, includeAA: false });
|
|
133
|
-
const similarity = 1 - diffPixels / totalPixels;
|
|
134
|
-
regions.push({
|
|
135
|
-
ref: entry.ref,
|
|
136
|
-
elementType: entry.node.type,
|
|
137
|
-
text: entry.node.text,
|
|
138
|
-
label: entry.node.label,
|
|
139
|
-
deviceBounds,
|
|
140
|
-
figmaBounds,
|
|
141
|
-
similarity,
|
|
142
|
-
diffPixels,
|
|
143
|
-
totalPixels,
|
|
144
|
-
verdict: verdictFromSimilarity(similarity),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
// Weighted average by region area
|
|
148
|
-
const totalWeight = regions.reduce((s, r) => s + r.totalPixels, 0);
|
|
149
|
-
const overallSimilarity = totalWeight > 0
|
|
150
|
-
? regions.reduce((s, r) => s + r.similarity * r.totalPixels, 0) / totalWeight
|
|
151
|
-
: 0;
|
|
152
|
-
return {
|
|
153
|
-
regions,
|
|
154
|
-
overallSimilarity,
|
|
155
|
-
skippedRefs,
|
|
156
|
-
deviceDimensions: { width: deviceW, height: deviceH },
|
|
157
|
-
figmaDimensions: { width: figmaW, height: figmaH },
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
/** Format layout report as human-readable text for MCP tool output */
|
|
161
|
-
function formatLayoutReport(report) {
|
|
162
|
-
const scaleX = (report.deviceDimensions.width / report.figmaDimensions.width).toFixed(2);
|
|
163
|
-
const scaleY = (report.deviceDimensions.height / report.figmaDimensions.height).toFixed(2);
|
|
164
|
-
const lines = [
|
|
165
|
-
"--- Layout Region Analysis ---",
|
|
166
|
-
`Device: ${report.deviceDimensions.width}x${report.deviceDimensions.height}px`,
|
|
167
|
-
`Figma: ${report.figmaDimensions.width}x${report.figmaDimensions.height}px`,
|
|
168
|
-
`Scale: ${scaleX}x (horizontal) ${scaleY}x (vertical)`,
|
|
169
|
-
`Regions: ${report.regions.length} analyzed, ${report.skippedRefs} too small to compare`,
|
|
170
|
-
"",
|
|
171
|
-
];
|
|
172
|
-
if (report.regions.length === 0) {
|
|
173
|
-
lines.push("No regions to analyze. Run devlens_snapshot first to populate element refs.");
|
|
174
|
-
return lines.join("\n");
|
|
175
|
-
}
|
|
176
|
-
// Sort ascending by similarity — problem areas at the bottom
|
|
177
|
-
const sorted = [...report.regions].sort((a, b) => a.similarity - b.similarity);
|
|
178
|
-
for (const r of sorted) {
|
|
179
|
-
const icon = r.verdict === "excellent" ? "✓"
|
|
180
|
-
: r.verdict === "good" ? "~"
|
|
181
|
-
: r.verdict === "partial" ? "!"
|
|
182
|
-
: "X";
|
|
183
|
-
const pct = (r.similarity * 100).toFixed(1);
|
|
184
|
-
const label = r.text
|
|
185
|
-
? `"${r.text.slice(0, 35)}"`
|
|
186
|
-
: r.label
|
|
187
|
-
? `label="${r.label.slice(0, 35)}"`
|
|
188
|
-
: r.elementType;
|
|
189
|
-
const suffix = (r.verdict === "poor" || r.verdict === "partial")
|
|
190
|
-
? ` <- investigate (${r.diffPixels.toLocaleString()} diff pixels)`
|
|
191
|
-
: "";
|
|
192
|
-
lines.push(` [${icon}] [${r.ref}] ${label}: ${pct}%${suffix}`);
|
|
193
|
-
}
|
|
194
|
-
lines.push("");
|
|
195
|
-
lines.push(`Overall (area-weighted): ${(report.overallSimilarity * 100).toFixed(1)}%`);
|
|
196
|
-
return lines.join("\n");
|
|
197
|
-
}
|
|
198
|
-
//# sourceMappingURL=layout-analyzer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layout-analyzer.js","sourceRoot":"","sources":["../../../src/visual/layout-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Device } from "../platform/device.js";
|
|
2
|
-
import type { Bounds } from "../platform/device.js";
|
|
3
|
-
/**
|
|
4
|
-
* Screenshot utilities — capture, encode, crop, and save screenshots.
|
|
5
|
-
*/
|
|
6
|
-
/** Take a screenshot and return it as base64-encoded PNG */
|
|
7
|
-
export declare function captureScreenshot(device: Device): Promise<{
|
|
8
|
-
buffer: Buffer;
|
|
9
|
-
base64: string;
|
|
10
|
-
}>;
|
|
11
|
-
/** Save a screenshot buffer to disk */
|
|
12
|
-
export declare function saveScreenshot(buffer: Buffer, filePath: string): Promise<void>;
|
|
13
|
-
/** Crop a screenshot to a specific element's bounds */
|
|
14
|
-
export declare function cropToElement(screenshotBuffer: Buffer, bounds: Bounds): Promise<Buffer>;
|
|
15
|
-
/** Generate a default screenshot filename with timestamp */
|
|
16
|
-
export declare function defaultScreenshotPath(): string;
|
|
17
|
-
//# sourceMappingURL=screenshot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../../src/visual/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD;;GAEG;AAEH,4DAA4D;AAC5D,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAID;AAED,uCAAuC;AACvC,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,uDAAuD;AACvD,wBAAsB,aAAa,CACjC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,IAAI,MAAM,CAG9C"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.captureScreenshot = captureScreenshot;
|
|
4
|
-
exports.saveScreenshot = saveScreenshot;
|
|
5
|
-
exports.cropToElement = cropToElement;
|
|
6
|
-
exports.defaultScreenshotPath = defaultScreenshotPath;
|
|
7
|
-
const promises_1 = require("fs/promises");
|
|
8
|
-
/**
|
|
9
|
-
* Screenshot utilities — capture, encode, crop, and save screenshots.
|
|
10
|
-
*/
|
|
11
|
-
/** Take a screenshot and return it as base64-encoded PNG */
|
|
12
|
-
async function captureScreenshot(device) {
|
|
13
|
-
const buffer = await device.takeScreenshot();
|
|
14
|
-
const base64 = buffer.toString("base64");
|
|
15
|
-
return { buffer, base64 };
|
|
16
|
-
}
|
|
17
|
-
/** Save a screenshot buffer to disk */
|
|
18
|
-
async function saveScreenshot(buffer, filePath) {
|
|
19
|
-
await (0, promises_1.writeFile)(filePath, buffer);
|
|
20
|
-
}
|
|
21
|
-
/** Crop a screenshot to a specific element's bounds */
|
|
22
|
-
async function cropToElement(screenshotBuffer, bounds) {
|
|
23
|
-
const sharp = (await import("sharp")).default;
|
|
24
|
-
return sharp(screenshotBuffer)
|
|
25
|
-
.extract({
|
|
26
|
-
left: Math.max(0, Math.round(bounds.x)),
|
|
27
|
-
top: Math.max(0, Math.round(bounds.y)),
|
|
28
|
-
width: Math.max(1, Math.round(bounds.width)),
|
|
29
|
-
height: Math.max(1, Math.round(bounds.height)),
|
|
30
|
-
})
|
|
31
|
-
.png()
|
|
32
|
-
.toBuffer();
|
|
33
|
-
}
|
|
34
|
-
/** Generate a default screenshot filename with timestamp */
|
|
35
|
-
function defaultScreenshotPath() {
|
|
36
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
37
|
-
return `/tmp/devlens-screenshot-${timestamp}.png`;
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=screenshot.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../../src/visual/screenshot.ts"],"names":[],"mappings":";;AASA,8CAOC;AAGD,wCAKC;AAGD,sCAeC;AAGD,sDAGC;AAhDD,0CAAwC;AAIxC;;GAEG;AAEH,4DAA4D;AACrD,KAAK,UAAU,iBAAiB,CAAC,MAAc;IAIpD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,uCAAuC;AAChC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,QAAgB;IAEhB,MAAM,IAAA,oBAAS,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,uDAAuD;AAChD,KAAK,UAAU,aAAa,CACjC,gBAAwB,EACxB,MAAc;IAEd,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAE9C,OAAO,KAAK,CAAC,gBAAgB,CAAC;SAC3B,OAAO,CAAC;QACP,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;KAC/C,CAAC;SACD,GAAG,EAAE;SACL,QAAQ,EAAE,CAAC;AAChB,CAAC;AAED,4DAA4D;AAC5D,SAAgB,qBAAqB;IACnC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,OAAO,2BAA2B,SAAS,MAAM,CAAC;AACpD,CAAC"}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* DevLens configuration file support.
|
|
7
|
-
*
|
|
8
|
-
* Loaded from (in priority order):
|
|
9
|
-
* 1. Path pointed to by DEVLENS_CONFIG env var
|
|
10
|
-
* 2. ./devlens.config.json in CWD
|
|
11
|
-
*
|
|
12
|
-
* Place devlens.config.json in your consumer app root and point to it
|
|
13
|
-
* via the MCP env block:
|
|
14
|
-
*
|
|
15
|
-
* "env": {
|
|
16
|
-
* "METRO_PORT": "8081",
|
|
17
|
-
* "FIGMA_TOKEN": "figd_xxx",
|
|
18
|
-
* "DEVLENS_CONFIG": "/path/to/your-app/devlens.config.json"
|
|
19
|
-
* }
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export const DesignSystemConfigSchema = z.object({
|
|
23
|
-
/** Design system identifier, e.g. "jds3" */
|
|
24
|
-
name: z.string(),
|
|
25
|
-
/** Absolute or CWD-relative path to the consumer app root */
|
|
26
|
-
projectRoot: z.string(),
|
|
27
|
-
/** Relative path from projectRoot to the tokens file, e.g. "src/constants/figmaTokens.ts" */
|
|
28
|
-
tokensFile: z.string(),
|
|
29
|
-
/** Relative path from projectRoot to the generated component interface directory */
|
|
30
|
-
componentsDir: z.string().optional(),
|
|
31
|
-
/** Glob pattern for component source files (default: "src/**\/*.tsx") */
|
|
32
|
-
componentsGlob: z.string().default("src/**/*.tsx"),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export const DevLensConfigSchema = z.object({
|
|
36
|
-
designSystem: DesignSystemConfigSchema.optional(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export type DevLensConfig = z.infer<typeof DevLensConfigSchema>;
|
|
40
|
-
export type DesignSystemConfig = z.infer<typeof DesignSystemConfigSchema>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Load DevLens config from DEVLENS_CONFIG env var path, or ./devlens.config.json in CWD.
|
|
44
|
-
* Returns empty config {} if neither is found or both are invalid.
|
|
45
|
-
* Never throws — invalid config is logged and skipped.
|
|
46
|
-
*/
|
|
47
|
-
export async function loadDevLensConfig(): Promise<DevLensConfig> {
|
|
48
|
-
const candidates: string[] = [];
|
|
49
|
-
|
|
50
|
-
const envPath = process.env.DEVLENS_CONFIG;
|
|
51
|
-
if (envPath) {
|
|
52
|
-
candidates.push(resolve(envPath));
|
|
53
|
-
}
|
|
54
|
-
candidates.push(resolve(process.cwd(), "devlens.config.json"));
|
|
55
|
-
|
|
56
|
-
for (const candidate of candidates) {
|
|
57
|
-
try {
|
|
58
|
-
const raw = await readFile(candidate, "utf-8");
|
|
59
|
-
const parsed = JSON.parse(raw);
|
|
60
|
-
const result = DevLensConfigSchema.safeParse(parsed);
|
|
61
|
-
if (result.success) {
|
|
62
|
-
console.error(`[devlens] Config loaded from: ${candidate}`);
|
|
63
|
-
return result.data;
|
|
64
|
-
} else {
|
|
65
|
-
console.error(
|
|
66
|
-
`[devlens] Config at ${candidate} is invalid:`,
|
|
67
|
-
JSON.stringify(result.error.format(), null, 2)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
// File not found or JSON parse error — try next candidate
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {};
|
|
76
|
-
}
|
package/src/index.ts
DELETED
package/src/metro/cdp-client.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Chrome DevTools Protocol (CDP) client for communicating with the
|
|
5
|
-
* Metro/Hermes debugger. Connects via WebSocket and sends JSON-RPC messages.
|
|
6
|
-
*
|
|
7
|
-
* Metro exposes the debugger endpoint at:
|
|
8
|
-
* ws://localhost:{METRO_PORT}/inspector/device?device=0&page=-1
|
|
9
|
-
*
|
|
10
|
-
* Through CDP we can:
|
|
11
|
-
* - Get console logs (Console.enable → Console.messageAdded)
|
|
12
|
-
* - Intercept network requests (Network.enable → Network.requestWillBeSent)
|
|
13
|
-
* - Execute JavaScript in the RN context (Runtime.evaluate)
|
|
14
|
-
* - Trigger hot reload (via Runtime.evaluate)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export interface CdpMessage {
|
|
18
|
-
id?: number;
|
|
19
|
-
method?: string;
|
|
20
|
-
params?: any;
|
|
21
|
-
result?: any;
|
|
22
|
-
error?: { message: string; code?: number };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type MessageHandler = (message: CdpMessage) => void;
|
|
26
|
-
|
|
27
|
-
export class CdpClient {
|
|
28
|
-
private ws: WebSocket | null = null;
|
|
29
|
-
private messageId: number = 0;
|
|
30
|
-
private pendingRequests: Map<
|
|
31
|
-
number,
|
|
32
|
-
{ resolve: (value: any) => void; reject: (error: Error) => void }
|
|
33
|
-
> = new Map();
|
|
34
|
-
private eventHandlers: Map<string, MessageHandler[]> = new Map();
|
|
35
|
-
private connected: boolean = false;
|
|
36
|
-
|
|
37
|
-
constructor(private endpoint: string) {}
|
|
38
|
-
|
|
39
|
-
/** Connect to the CDP endpoint */
|
|
40
|
-
async connect(): Promise<void> {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
this.ws = new WebSocket(this.endpoint);
|
|
43
|
-
|
|
44
|
-
const timeout = setTimeout(() => {
|
|
45
|
-
reject(new Error(`CDP connection timeout to ${this.endpoint}`));
|
|
46
|
-
}, 5000);
|
|
47
|
-
|
|
48
|
-
this.ws.on("open", () => {
|
|
49
|
-
clearTimeout(timeout);
|
|
50
|
-
this.connected = true;
|
|
51
|
-
resolve();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
this.ws.on("message", (data: WebSocket.Data) => {
|
|
55
|
-
try {
|
|
56
|
-
const message: CdpMessage = JSON.parse(data.toString());
|
|
57
|
-
this.handleMessage(message);
|
|
58
|
-
} catch {
|
|
59
|
-
// Ignore malformed messages
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
this.ws.on("close", () => {
|
|
64
|
-
this.connected = false;
|
|
65
|
-
// Reject all pending requests
|
|
66
|
-
for (const [, { reject }] of this.pendingRequests) {
|
|
67
|
-
reject(new Error("CDP connection closed"));
|
|
68
|
-
}
|
|
69
|
-
this.pendingRequests.clear();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
this.ws.on("error", (err) => {
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
if (!this.connected) {
|
|
75
|
-
reject(err);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Send a CDP command and wait for the response */
|
|
82
|
-
async send(method: string, params?: any): Promise<any> {
|
|
83
|
-
if (!this.ws || !this.connected) {
|
|
84
|
-
throw new Error("CDP client not connected");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const id = ++this.messageId;
|
|
88
|
-
const message = JSON.stringify({ id, method, params });
|
|
89
|
-
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
const timeout = setTimeout(() => {
|
|
92
|
-
this.pendingRequests.delete(id);
|
|
93
|
-
reject(new Error(`CDP request timeout: ${method}`));
|
|
94
|
-
}, 10000);
|
|
95
|
-
|
|
96
|
-
this.pendingRequests.set(id, {
|
|
97
|
-
resolve: (result) => {
|
|
98
|
-
clearTimeout(timeout);
|
|
99
|
-
resolve(result);
|
|
100
|
-
},
|
|
101
|
-
reject: (error) => {
|
|
102
|
-
clearTimeout(timeout);
|
|
103
|
-
reject(error);
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
this.ws!.send(message);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Register an event handler for CDP events */
|
|
112
|
-
on(method: string, handler: MessageHandler): void {
|
|
113
|
-
const handlers = this.eventHandlers.get(method) || [];
|
|
114
|
-
handlers.push(handler);
|
|
115
|
-
this.eventHandlers.set(method, handlers);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Remove event handlers for a method */
|
|
119
|
-
off(method: string): void {
|
|
120
|
-
this.eventHandlers.delete(method);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Check if connected */
|
|
124
|
-
isConnected(): boolean {
|
|
125
|
-
return this.connected;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Close the connection */
|
|
129
|
-
close(): void {
|
|
130
|
-
if (this.ws) {
|
|
131
|
-
this.ws.close();
|
|
132
|
-
this.ws = null;
|
|
133
|
-
this.connected = false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private handleMessage(message: CdpMessage): void {
|
|
138
|
-
// Response to a request
|
|
139
|
-
if (message.id !== undefined) {
|
|
140
|
-
const pending = this.pendingRequests.get(message.id);
|
|
141
|
-
if (pending) {
|
|
142
|
-
this.pendingRequests.delete(message.id);
|
|
143
|
-
if (message.error) {
|
|
144
|
-
pending.reject(new Error(message.error.message));
|
|
145
|
-
} else {
|
|
146
|
-
pending.resolve(message.result);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Event notification
|
|
153
|
-
if (message.method) {
|
|
154
|
-
const handlers = this.eventHandlers.get(message.method) || [];
|
|
155
|
-
for (const handler of handlers) {
|
|
156
|
-
handler(message);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|