devlens-mcp 0.3.0

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 (175) hide show
  1. package/.claude/settings.json +12 -0
  2. package/.claude/settings.local.json +17 -0
  3. package/INSTALLATION_GUIDE.md +354 -0
  4. package/QUICK_START.md +153 -0
  5. package/README.md +354 -0
  6. package/bin/cli.ts +22 -0
  7. package/bin/register.ts +96 -0
  8. package/dist/bin/cli.d.ts +3 -0
  9. package/dist/bin/cli.d.ts.map +1 -0
  10. package/dist/bin/cli.js +20 -0
  11. package/dist/bin/cli.js.map +1 -0
  12. package/dist/bin/register.d.ts +10 -0
  13. package/dist/bin/register.d.ts.map +1 -0
  14. package/dist/bin/register.js +92 -0
  15. package/dist/bin/register.js.map +1 -0
  16. package/dist/src/config/devlens-config.d.ts +92 -0
  17. package/dist/src/config/devlens-config.d.ts.map +1 -0
  18. package/dist/src/config/devlens-config.js +70 -0
  19. package/dist/src/config/devlens-config.js.map +1 -0
  20. package/dist/src/index.d.ts +35 -0
  21. package/dist/src/index.d.ts.map +1 -0
  22. package/dist/src/index.js +8 -0
  23. package/dist/src/index.js.map +1 -0
  24. package/dist/src/metro/cdp-client.d.ts +48 -0
  25. package/dist/src/metro/cdp-client.d.ts.map +1 -0
  26. package/dist/src/metro/cdp-client.js +127 -0
  27. package/dist/src/metro/cdp-client.js.map +1 -0
  28. package/dist/src/metro/log-collector.d.ts +30 -0
  29. package/dist/src/metro/log-collector.d.ts.map +1 -0
  30. package/dist/src/metro/log-collector.js +114 -0
  31. package/dist/src/metro/log-collector.js.map +1 -0
  32. package/dist/src/metro/metro-bridge.d.ts +56 -0
  33. package/dist/src/metro/metro-bridge.d.ts.map +1 -0
  34. package/dist/src/metro/metro-bridge.js +255 -0
  35. package/dist/src/metro/metro-bridge.js.map +1 -0
  36. package/dist/src/metro/network-inspector.d.ts +34 -0
  37. package/dist/src/metro/network-inspector.d.ts.map +1 -0
  38. package/dist/src/metro/network-inspector.js +100 -0
  39. package/dist/src/metro/network-inspector.js.map +1 -0
  40. package/dist/src/platform/android/adb.d.ts +50 -0
  41. package/dist/src/platform/android/adb.d.ts.map +1 -0
  42. package/dist/src/platform/android/adb.js +137 -0
  43. package/dist/src/platform/android/adb.js.map +1 -0
  44. package/dist/src/platform/android/android-device.d.ts +21 -0
  45. package/dist/src/platform/android/android-device.d.ts.map +1 -0
  46. package/dist/src/platform/android/android-device.js +94 -0
  47. package/dist/src/platform/android/android-device.js.map +1 -0
  48. package/dist/src/platform/android/ui-automator.d.ts +17 -0
  49. package/dist/src/platform/android/ui-automator.d.ts.map +1 -0
  50. package/dist/src/platform/android/ui-automator.js +126 -0
  51. package/dist/src/platform/android/ui-automator.js.map +1 -0
  52. package/dist/src/platform/device-manager.d.ts +28 -0
  53. package/dist/src/platform/device-manager.d.ts.map +1 -0
  54. package/dist/src/platform/device-manager.js +185 -0
  55. package/dist/src/platform/device-manager.js.map +1 -0
  56. package/dist/src/platform/device.d.ts +86 -0
  57. package/dist/src/platform/device.d.ts.map +1 -0
  58. package/dist/src/platform/device.js +7 -0
  59. package/dist/src/platform/device.js.map +1 -0
  60. package/dist/src/platform/ios/accessibility.d.ts +17 -0
  61. package/dist/src/platform/ios/accessibility.d.ts.map +1 -0
  62. package/dist/src/platform/ios/accessibility.js +159 -0
  63. package/dist/src/platform/ios/accessibility.js.map +1 -0
  64. package/dist/src/platform/ios/ios-device.d.ts +22 -0
  65. package/dist/src/platform/ios/ios-device.d.ts.map +1 -0
  66. package/dist/src/platform/ios/ios-device.js +97 -0
  67. package/dist/src/platform/ios/ios-device.js.map +1 -0
  68. package/dist/src/platform/ios/simctl.d.ts +54 -0
  69. package/dist/src/platform/ios/simctl.d.ts.map +1 -0
  70. package/dist/src/platform/ios/simctl.js +192 -0
  71. package/dist/src/platform/ios/simctl.js.map +1 -0
  72. package/dist/src/server.d.ts +3 -0
  73. package/dist/src/server.d.ts.map +1 -0
  74. package/dist/src/server.js +176 -0
  75. package/dist/src/server.js.map +1 -0
  76. package/dist/src/snapshot/formatter.d.ts +18 -0
  77. package/dist/src/snapshot/formatter.d.ts.map +1 -0
  78. package/dist/src/snapshot/formatter.js +86 -0
  79. package/dist/src/snapshot/formatter.js.map +1 -0
  80. package/dist/src/snapshot/ref-registry.d.ts +67 -0
  81. package/dist/src/snapshot/ref-registry.d.ts.map +1 -0
  82. package/dist/src/snapshot/ref-registry.js +169 -0
  83. package/dist/src/snapshot/ref-registry.js.map +1 -0
  84. package/dist/src/snapshot/snapshot-differ.d.ts +57 -0
  85. package/dist/src/snapshot/snapshot-differ.d.ts.map +1 -0
  86. package/dist/src/snapshot/snapshot-differ.js +153 -0
  87. package/dist/src/snapshot/snapshot-differ.js.map +1 -0
  88. package/dist/src/tools/app-tools.d.ts +71 -0
  89. package/dist/src/tools/app-tools.d.ts.map +1 -0
  90. package/dist/src/tools/app-tools.js +97 -0
  91. package/dist/src/tools/app-tools.js.map +1 -0
  92. package/dist/src/tools/device-tools.d.ts +53 -0
  93. package/dist/src/tools/device-tools.d.ts.map +1 -0
  94. package/dist/src/tools/device-tools.js +86 -0
  95. package/dist/src/tools/device-tools.js.map +1 -0
  96. package/dist/src/tools/ds-tools.d.ts +65 -0
  97. package/dist/src/tools/ds-tools.d.ts.map +1 -0
  98. package/dist/src/tools/ds-tools.js +314 -0
  99. package/dist/src/tools/ds-tools.js.map +1 -0
  100. package/dist/src/tools/interaction-tools.d.ts +248 -0
  101. package/dist/src/tools/interaction-tools.d.ts.map +1 -0
  102. package/dist/src/tools/interaction-tools.js +391 -0
  103. package/dist/src/tools/interaction-tools.js.map +1 -0
  104. package/dist/src/tools/metro-tools.d.ts +115 -0
  105. package/dist/src/tools/metro-tools.d.ts.map +1 -0
  106. package/dist/src/tools/metro-tools.js +270 -0
  107. package/dist/src/tools/metro-tools.js.map +1 -0
  108. package/dist/src/tools/navigation-tools.d.ts +36 -0
  109. package/dist/src/tools/navigation-tools.d.ts.map +1 -0
  110. package/dist/src/tools/navigation-tools.js +60 -0
  111. package/dist/src/tools/navigation-tools.js.map +1 -0
  112. package/dist/src/tools/screenshot-tools.d.ts +298 -0
  113. package/dist/src/tools/screenshot-tools.d.ts.map +1 -0
  114. package/dist/src/tools/screenshot-tools.js +565 -0
  115. package/dist/src/tools/screenshot-tools.js.map +1 -0
  116. package/dist/src/tools/snapshot-tools.d.ts +161 -0
  117. package/dist/src/tools/snapshot-tools.d.ts.map +1 -0
  118. package/dist/src/tools/snapshot-tools.js +479 -0
  119. package/dist/src/tools/snapshot-tools.js.map +1 -0
  120. package/dist/src/utils/image-preprocess.d.ts +49 -0
  121. package/dist/src/utils/image-preprocess.d.ts.map +1 -0
  122. package/dist/src/utils/image-preprocess.js +322 -0
  123. package/dist/src/utils/image-preprocess.js.map +1 -0
  124. package/dist/src/utils/retry.d.ts +21 -0
  125. package/dist/src/utils/retry.d.ts.map +1 -0
  126. package/dist/src/utils/retry.js +33 -0
  127. package/dist/src/utils/retry.js.map +1 -0
  128. package/dist/src/visual/comparator.d.ts +51 -0
  129. package/dist/src/visual/comparator.d.ts.map +1 -0
  130. package/dist/src/visual/comparator.js +119 -0
  131. package/dist/src/visual/comparator.js.map +1 -0
  132. package/dist/src/visual/layout-analyzer.d.ts +64 -0
  133. package/dist/src/visual/layout-analyzer.d.ts.map +1 -0
  134. package/dist/src/visual/layout-analyzer.js +198 -0
  135. package/dist/src/visual/layout-analyzer.js.map +1 -0
  136. package/dist/src/visual/screenshot.d.ts +17 -0
  137. package/dist/src/visual/screenshot.d.ts.map +1 -0
  138. package/dist/src/visual/screenshot.js +39 -0
  139. package/dist/src/visual/screenshot.js.map +1 -0
  140. package/docs/figma-workflow.md +289 -0
  141. package/docs/setup-guide.md +360 -0
  142. package/docs/tool-reference.md +622 -0
  143. package/package.json +57 -0
  144. package/src/config/devlens-config.ts +76 -0
  145. package/src/index.ts +5 -0
  146. package/src/metro/cdp-client.ts +160 -0
  147. package/src/metro/log-collector.ts +137 -0
  148. package/src/metro/metro-bridge.ts +307 -0
  149. package/src/metro/network-inspector.ts +134 -0
  150. package/src/platform/android/adb.ts +200 -0
  151. package/src/platform/android/android-device.ts +116 -0
  152. package/src/platform/android/ui-automator.ts +141 -0
  153. package/src/platform/device-manager.ts +229 -0
  154. package/src/platform/device.ts +110 -0
  155. package/src/platform/ios/accessibility.ts +189 -0
  156. package/src/platform/ios/ios-device.ts +116 -0
  157. package/src/platform/ios/simctl.ts +244 -0
  158. package/src/server.ts +228 -0
  159. package/src/snapshot/formatter.ts +102 -0
  160. package/src/snapshot/ref-registry.ts +230 -0
  161. package/src/snapshot/snapshot-differ.ts +220 -0
  162. package/src/tools/app-tools.ts +111 -0
  163. package/src/tools/device-tools.ts +96 -0
  164. package/src/tools/ds-tools.ts +395 -0
  165. package/src/tools/interaction-tools.ts +467 -0
  166. package/src/tools/metro-tools.ts +320 -0
  167. package/src/tools/navigation-tools.ts +71 -0
  168. package/src/tools/screenshot-tools.ts +698 -0
  169. package/src/tools/snapshot-tools.ts +585 -0
  170. package/src/utils/image-preprocess.ts +430 -0
  171. package/src/utils/retry.ts +51 -0
  172. package/src/visual/comparator.ts +191 -0
  173. package/src/visual/layout-analyzer.ts +283 -0
  174. package/src/visual/screenshot.ts +49 -0
  175. package/tsconfig.json +20 -0
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startServer = startServer;
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const device_manager_js_1 = require("./platform/device-manager.js");
8
+ const ref_registry_js_1 = require("./snapshot/ref-registry.js");
9
+ const devlens_config_js_1 = require("./config/devlens-config.js");
10
+ const ds_tools_js_1 = require("./tools/ds-tools.js");
11
+ const device_tools_js_1 = require("./tools/device-tools.js");
12
+ const snapshot_tools_js_1 = require("./tools/snapshot-tools.js");
13
+ const interaction_tools_js_1 = require("./tools/interaction-tools.js");
14
+ const screenshot_tools_js_1 = require("./tools/screenshot-tools.js");
15
+ const app_tools_js_1 = require("./tools/app-tools.js");
16
+ const metro_tools_js_1 = require("./tools/metro-tools.js");
17
+ const navigation_tools_js_1 = require("./tools/navigation-tools.js");
18
+ async function startServer() {
19
+ const server = new index_js_1.Server({
20
+ name: "devlens-mcp",
21
+ version: "0.3.0",
22
+ }, {
23
+ capabilities: {
24
+ tools: {},
25
+ },
26
+ });
27
+ // Load config (from DEVLENS_CONFIG env var or ./devlens.config.json)
28
+ const devlensConfig = await (0, devlens_config_js_1.loadDevLensConfig)();
29
+ // Shared state
30
+ const deviceManager = new device_manager_js_1.DeviceManager();
31
+ const refRegistry = new ref_registry_js_1.RefRegistry();
32
+ const metroPort = parseInt(process.env.METRO_PORT || "8081", 10);
33
+ // Create tool handlers
34
+ const dsHandlers = (0, ds_tools_js_1.createDsToolHandlers)(devlensConfig.designSystem);
35
+ const deviceHandlers = (0, device_tools_js_1.createDeviceToolHandlers)(deviceManager);
36
+ const { handlers: snapshotHandlers, resetPreviousSnapshot } = (0, snapshot_tools_js_1.createSnapshotToolHandlers)(deviceManager, refRegistry);
37
+ const interactionHandlers = (0, interaction_tools_js_1.createInteractionToolHandlers)(deviceManager, refRegistry, resetPreviousSnapshot);
38
+ const screenshotHandlers = (0, screenshot_tools_js_1.createScreenshotToolHandlers)(deviceManager, refRegistry);
39
+ const appHandlers = (0, app_tools_js_1.createAppToolHandlers)(deviceManager);
40
+ const metroHandlers = (0, metro_tools_js_1.createMetroToolHandlers)(metroPort, deviceManager);
41
+ const navigationHandlers = (0, navigation_tools_js_1.createNavigationToolHandlers)(deviceManager, refRegistry, resetPreviousSnapshot);
42
+ // Merge all tool schemas
43
+ const allSchemas = {
44
+ ...ds_tools_js_1.dsToolSchemas,
45
+ ...device_tools_js_1.deviceToolSchemas,
46
+ ...snapshot_tools_js_1.snapshotToolSchemas,
47
+ ...interaction_tools_js_1.interactionToolSchemas,
48
+ ...screenshot_tools_js_1.screenshotToolSchemas,
49
+ ...app_tools_js_1.appToolSchemas,
50
+ ...metro_tools_js_1.metroToolSchemas,
51
+ ...navigation_tools_js_1.navigationToolSchemas,
52
+ };
53
+ // Merge all handlers
54
+ const allHandlers = {
55
+ ...dsHandlers,
56
+ ...deviceHandlers,
57
+ ...snapshotHandlers,
58
+ ...interactionHandlers,
59
+ ...screenshotHandlers,
60
+ ...appHandlers,
61
+ ...metroHandlers,
62
+ ...navigationHandlers,
63
+ };
64
+ // Register tools/list handler
65
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
66
+ return {
67
+ tools: Object.entries(allSchemas).map(([name, schema]) => ({
68
+ name,
69
+ description: schema.description,
70
+ inputSchema: zodToJsonSchema(schema.parameters),
71
+ })),
72
+ };
73
+ });
74
+ // Register tools/call handler
75
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
76
+ const { name, arguments: args } = request.params;
77
+ const handler = allHandlers[name];
78
+ if (!handler) {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Unknown tool: ${name}`,
84
+ },
85
+ ],
86
+ isError: true,
87
+ };
88
+ }
89
+ try {
90
+ // Validate params with Zod schema
91
+ const schema = allSchemas[name];
92
+ const validatedArgs = schema.parameters.parse(args || {});
93
+ return await handler(validatedArgs);
94
+ }
95
+ catch (error) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: `Error: ${error.message || String(error)}`,
101
+ },
102
+ ],
103
+ isError: true,
104
+ };
105
+ }
106
+ });
107
+ // Connect via stdio transport
108
+ const transport = new stdio_js_1.StdioServerTransport();
109
+ await server.connect(transport);
110
+ return server;
111
+ }
112
+ /**
113
+ * Convert a Zod schema to JSON Schema for MCP tool registration.
114
+ * This is a simplified converter that handles the common cases.
115
+ */
116
+ function zodToJsonSchema(schema) {
117
+ // Use Zod's built-in JSON schema support if available
118
+ if (schema._def) {
119
+ return convertZodDef(schema._def);
120
+ }
121
+ return { type: "object", properties: {} };
122
+ }
123
+ function convertZodDef(def) {
124
+ switch (def.typeName) {
125
+ case "ZodObject": {
126
+ const properties = {};
127
+ const required = [];
128
+ for (const [key, value] of Object.entries(def.shape())) {
129
+ const propDef = value._def;
130
+ if (propDef.typeName === "ZodOptional" || propDef.typeName === "ZodDefault") {
131
+ properties[key] = convertZodDef(propDef.typeName === "ZodDefault" ? propDef.innerType._def : propDef.innerType._def);
132
+ }
133
+ else {
134
+ properties[key] = convertZodDef(propDef);
135
+ required.push(key);
136
+ }
137
+ // Add description from Zod describe()
138
+ if (propDef.description) {
139
+ properties[key].description = propDef.description;
140
+ }
141
+ else if (value.description) {
142
+ properties[key].description = value.description;
143
+ }
144
+ }
145
+ return {
146
+ type: "object",
147
+ properties,
148
+ ...(required.length > 0 ? { required } : {}),
149
+ };
150
+ }
151
+ case "ZodString":
152
+ return { type: "string", ...(def.description ? { description: def.description } : {}) };
153
+ case "ZodNumber":
154
+ return { type: "number", ...(def.description ? { description: def.description } : {}) };
155
+ case "ZodBoolean":
156
+ return { type: "boolean", ...(def.description ? { description: def.description } : {}) };
157
+ case "ZodEnum":
158
+ return { type: "string", enum: def.values, ...(def.description ? { description: def.description } : {}) };
159
+ case "ZodArray":
160
+ return {
161
+ type: "array",
162
+ items: convertZodDef(def.type._def),
163
+ ...(def.description ? { description: def.description } : {}),
164
+ };
165
+ case "ZodDefault":
166
+ return {
167
+ ...convertZodDef(def.innerType._def),
168
+ default: def.defaultValue(),
169
+ };
170
+ case "ZodOptional":
171
+ return convertZodDef(def.innerType._def);
172
+ default:
173
+ return {};
174
+ }
175
+ }
176
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { SnapshotNode } from "../platform/device.js";
2
+ /**
3
+ * Formats a SnapshotNode tree into a YAML-like text representation
4
+ * that LLMs can easily parse and reason about.
5
+ *
6
+ * Output format (inspired by Playwright MCP):
7
+ *
8
+ * - FrameLayout
9
+ * - LinearLayout
10
+ * - TextView "Hello World" [ref=e1]
11
+ * - Button "Submit" [ref=e2]
12
+ * - EditText "Email" [ref=e3] [focused]
13
+ * - RecyclerView [ref=e4]
14
+ * - CardView
15
+ * - TextView "Item 1" [ref=e5]
16
+ */
17
+ export declare function formatSnapshot(root: SnapshotNode, nodeToRef: Map<SnapshotNode, string>): string;
18
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/snapshot/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,GACnC,MAAM,CAIR"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatSnapshot = formatSnapshot;
4
+ /**
5
+ * Formats a SnapshotNode tree into a YAML-like text representation
6
+ * that LLMs can easily parse and reason about.
7
+ *
8
+ * Output format (inspired by Playwright MCP):
9
+ *
10
+ * - FrameLayout
11
+ * - LinearLayout
12
+ * - TextView "Hello World" [ref=e1]
13
+ * - Button "Submit" [ref=e2]
14
+ * - EditText "Email" [ref=e3] [focused]
15
+ * - RecyclerView [ref=e4]
16
+ * - CardView
17
+ * - TextView "Item 1" [ref=e5]
18
+ */
19
+ function formatSnapshot(root, nodeToRef) {
20
+ const lines = [];
21
+ formatNode(root, nodeToRef, 0, lines);
22
+ return lines.join("\n");
23
+ }
24
+ function formatNode(node, nodeToRef, depth, lines) {
25
+ const indent = " ".repeat(depth);
26
+ const parts = [];
27
+ // Type
28
+ parts.push(node.type);
29
+ // Text content
30
+ if (node.text) {
31
+ parts.push(`"${truncate(node.text, 60)}"`);
32
+ }
33
+ // Accessibility label (if different from text)
34
+ if (node.label && node.label !== node.text) {
35
+ parts.push(`label="${truncate(node.label, 40)}"`);
36
+ }
37
+ // Annotations
38
+ const annotations = [];
39
+ // Ref
40
+ const ref = nodeToRef.get(node);
41
+ if (ref) {
42
+ annotations.push(`ref=${ref}`);
43
+ }
44
+ // State annotations
45
+ if (node.focused)
46
+ annotations.push("focused");
47
+ if (node.value)
48
+ annotations.push(`value="${node.value}"`);
49
+ if (node.enabled === false)
50
+ annotations.push("disabled");
51
+ if (node.resourceId) {
52
+ const shortId = node.resourceId.includes("/")
53
+ ? node.resourceId.split("/").pop()
54
+ : node.resourceId;
55
+ annotations.push(`id="${shortId}"`);
56
+ }
57
+ if (annotations.length > 0) {
58
+ parts.push(`[${annotations.join("] [")}]`);
59
+ }
60
+ // Build the line
61
+ const line = `${indent}- ${parts.join(" ")}`;
62
+ // Only include nodes that have refs, text, labels, or children with refs
63
+ if (ref || node.text || node.label || hasRefInSubtree(node, nodeToRef)) {
64
+ lines.push(line);
65
+ }
66
+ // Recurse into children
67
+ for (const child of node.children) {
68
+ formatNode(child, nodeToRef, depth + 1, lines);
69
+ }
70
+ }
71
+ /** Check if any descendant has a ref assigned */
72
+ function hasRefInSubtree(node, nodeToRef) {
73
+ for (const child of node.children) {
74
+ if (nodeToRef.has(child))
75
+ return true;
76
+ if (hasRefInSubtree(child, nodeToRef))
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+ function truncate(str, maxLen) {
82
+ if (str.length <= maxLen)
83
+ return str;
84
+ return str.slice(0, maxLen - 3) + "...";
85
+ }
86
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../../src/snapshot/formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { SnapshotNode, Bounds } from "../platform/device.js";
2
+ /**
3
+ * RefRegistry — the Playwright-style ref assignment system.
4
+ *
5
+ * Assigns monotonically increasing refs (e1, e2, ...) to every interactive
6
+ * or meaningful node in the accessibility tree. Maintains a map from ref
7
+ * to node info (bounds, center coords) so interaction tools can resolve
8
+ * "tap ref=e5" to actual screen coordinates.
9
+ */
10
+ export interface RefEntry {
11
+ ref: string;
12
+ node: SnapshotNode;
13
+ centerX: number;
14
+ centerY: number;
15
+ bounds: Bounds;
16
+ }
17
+ export interface ValidationIssue {
18
+ type: "zero-size" | "invisible" | "empty-container";
19
+ nodeType: string;
20
+ text?: string;
21
+ label?: string;
22
+ resourceId?: string;
23
+ bounds: Bounds;
24
+ detail: string;
25
+ }
26
+ export interface ValidationReport {
27
+ issues: ValidationIssue[];
28
+ totalNodes: number;
29
+ visibleNodes: number;
30
+ zeroSizeNodes: number;
31
+ interactiveNodes: number;
32
+ }
33
+ export declare class RefRegistry {
34
+ private refMap;
35
+ private counter;
36
+ /** Clear all refs and reset counter (call before each new snapshot) */
37
+ reset(): void;
38
+ /**
39
+ * Walk the snapshot tree and assign refs to all interactive or content-bearing nodes.
40
+ * Returns a new tree with refs attached (as annotations in the formatter output).
41
+ */
42
+ assignRefs(root: SnapshotNode): Map<SnapshotNode, string>;
43
+ private walkAndAssign;
44
+ /** Determine if a node should get a ref */
45
+ private shouldAssignRef;
46
+ /** Resolve a ref string to its entry (coordinates + node info) */
47
+ resolve(ref: string): RefEntry | undefined;
48
+ /** Get all current ref entries */
49
+ getAllRefs(): RefEntry[];
50
+ /** Get the total number of assigned refs */
51
+ get size(): number;
52
+ /**
53
+ * Validate the snapshot tree for visibility issues.
54
+ * Flags zero-size elements that should be visible, explicitly invisible elements,
55
+ * and empty containers that might indicate missing content.
56
+ */
57
+ validateTree(root: SnapshotNode): ValidationReport;
58
+ /**
59
+ * Search the full snapshot tree (including zero-size nodes) for text/label matches.
60
+ * Used by find_element to detect invisible elements that the refMap skips.
61
+ */
62
+ findInTree(root: SnapshotNode, query: {
63
+ text?: string;
64
+ label?: string;
65
+ }): SnapshotNode[];
66
+ }
67
+ //# sourceMappingURL=ref-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-registry.d.ts","sourceRoot":"","sources":["../../../src/snapshot/ref-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;;;;;GAOG;AAEH,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,iBAAiB,CAAC;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,OAAO,CAAa;IAE5B,uEAAuE;IACvE,KAAK,IAAI,IAAI;IAKb;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC;IAOzD,OAAO,CAAC,aAAa;IAwBrB,2CAA2C;IAC3C,OAAO,CAAC,eAAe;IAiBvB,kEAAkE;IAClE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAI1C,kCAAkC;IAClC,UAAU,IAAI,QAAQ,EAAE;IAIxB,4CAA4C;IAC5C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB;IA4ElD;;;OAGG;IACH,UAAU,CACR,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACvC,YAAY,EAAE;CA0BlB"}
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RefRegistry = void 0;
4
+ class RefRegistry {
5
+ refMap = new Map();
6
+ counter = 0;
7
+ /** Clear all refs and reset counter (call before each new snapshot) */
8
+ reset() {
9
+ this.refMap.clear();
10
+ this.counter = 0;
11
+ }
12
+ /**
13
+ * Walk the snapshot tree and assign refs to all interactive or content-bearing nodes.
14
+ * Returns a new tree with refs attached (as annotations in the formatter output).
15
+ */
16
+ assignRefs(root) {
17
+ this.reset();
18
+ const nodeToRef = new Map();
19
+ this.walkAndAssign(root, nodeToRef);
20
+ return nodeToRef;
21
+ }
22
+ walkAndAssign(node, nodeToRef) {
23
+ if (this.shouldAssignRef(node)) {
24
+ const ref = `e${++this.counter}`;
25
+ const centerX = node.bounds.x + node.bounds.width / 2;
26
+ const centerY = node.bounds.y + node.bounds.height / 2;
27
+ this.refMap.set(ref, {
28
+ ref,
29
+ node,
30
+ centerX,
31
+ centerY,
32
+ bounds: node.bounds,
33
+ });
34
+ nodeToRef.set(node, ref);
35
+ }
36
+ for (const child of node.children) {
37
+ this.walkAndAssign(child, nodeToRef);
38
+ }
39
+ }
40
+ /** Determine if a node should get a ref */
41
+ shouldAssignRef(node) {
42
+ // Skip invisible/zero-size elements
43
+ if (node.bounds.width === 0 || node.bounds.height === 0)
44
+ return false;
45
+ // Always assign refs to interactive elements
46
+ if (node.interactive)
47
+ return true;
48
+ // Assign refs to elements with meaningful text content
49
+ if (node.text && node.text.trim().length > 0)
50
+ return true;
51
+ // Assign refs to elements with accessibility labels
52
+ if (node.label && node.label.trim().length > 0)
53
+ return true;
54
+ // Skip purely structural containers
55
+ return false;
56
+ }
57
+ /** Resolve a ref string to its entry (coordinates + node info) */
58
+ resolve(ref) {
59
+ return this.refMap.get(ref);
60
+ }
61
+ /** Get all current ref entries */
62
+ getAllRefs() {
63
+ return Array.from(this.refMap.values());
64
+ }
65
+ /** Get the total number of assigned refs */
66
+ get size() {
67
+ return this.refMap.size;
68
+ }
69
+ /**
70
+ * Validate the snapshot tree for visibility issues.
71
+ * Flags zero-size elements that should be visible, explicitly invisible elements,
72
+ * and empty containers that might indicate missing content.
73
+ */
74
+ validateTree(root) {
75
+ const issues = [];
76
+ let totalNodes = 0;
77
+ let visibleNodes = 0;
78
+ let interactiveNodes = 0;
79
+ let zeroSizeNodes = 0;
80
+ const walk = (node) => {
81
+ totalNodes++;
82
+ const isZeroSize = node.bounds.width === 0 || node.bounds.height === 0;
83
+ if (isZeroSize) {
84
+ zeroSizeNodes++;
85
+ // Only flag if the node has content that SHOULD be visible
86
+ if (node.text || node.label || node.interactive) {
87
+ issues.push({
88
+ type: "zero-size",
89
+ nodeType: node.type,
90
+ text: node.text,
91
+ label: node.label,
92
+ resourceId: node.resourceId,
93
+ bounds: node.bounds,
94
+ detail: `${node.type}${node.text ? ` "${node.text}"` : ""}${node.label ? ` label="${node.label}"` : ""}` +
95
+ ` has zero size (${node.bounds.width}x${node.bounds.height}) — renders as invisible`,
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ visibleNodes++;
101
+ }
102
+ if (node.visible === false) {
103
+ issues.push({
104
+ type: "invisible",
105
+ nodeType: node.type,
106
+ text: node.text,
107
+ label: node.label,
108
+ resourceId: node.resourceId,
109
+ bounds: node.bounds,
110
+ detail: `${node.type}${node.text ? ` "${node.text}"` : ""} has visible=false`,
111
+ });
112
+ }
113
+ if (node.interactive)
114
+ interactiveNodes++;
115
+ // Empty container with a resource ID — likely an intentional component
116
+ // that should have content (e.g., missing icon, empty image)
117
+ if (node.children.length === 0 &&
118
+ node.resourceId &&
119
+ !node.text &&
120
+ !node.label &&
121
+ !isZeroSize) {
122
+ issues.push({
123
+ type: "empty-container",
124
+ nodeType: node.type,
125
+ resourceId: node.resourceId,
126
+ bounds: node.bounds,
127
+ detail: `${node.type} id="${node.resourceId}" is an empty container (no children, no text) — may be a missing icon or placeholder`,
128
+ });
129
+ }
130
+ for (const child of node.children) {
131
+ walk(child);
132
+ }
133
+ };
134
+ walk(root);
135
+ return { issues, totalNodes, visibleNodes, zeroSizeNodes, interactiveNodes };
136
+ }
137
+ /**
138
+ * Search the full snapshot tree (including zero-size nodes) for text/label matches.
139
+ * Used by find_element to detect invisible elements that the refMap skips.
140
+ */
141
+ findInTree(root, query) {
142
+ const matches = [];
143
+ const walk = (node) => {
144
+ const isZeroSize = node.bounds.width === 0 || node.bounds.height === 0;
145
+ if (isZeroSize) {
146
+ let matched = false;
147
+ if (query.text && node.text) {
148
+ matched = node.text
149
+ .toLowerCase()
150
+ .includes(query.text.toLowerCase());
151
+ }
152
+ if (query.label && node.label) {
153
+ matched =
154
+ matched ||
155
+ node.label.toLowerCase().includes(query.label.toLowerCase());
156
+ }
157
+ if (matched)
158
+ matches.push(node);
159
+ }
160
+ for (const child of node.children) {
161
+ walk(child);
162
+ }
163
+ };
164
+ walk(root);
165
+ return matches;
166
+ }
167
+ }
168
+ exports.RefRegistry = RefRegistry;
169
+ //# sourceMappingURL=ref-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-registry.js","sourceRoot":"","sources":["../../../src/snapshot/ref-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { SnapshotNode } from "../platform/device.js";
2
+ /**
3
+ * Incremental snapshot differ — compares two snapshot trees and returns
4
+ * only the changed portions. Saves 60-80% tokens in iterative workflows.
5
+ */
6
+ export interface SnapshotDiff {
7
+ /** Nodes that were added since the last snapshot */
8
+ added: SnapshotNode[];
9
+ /** Nodes that were removed since the last snapshot */
10
+ removed: SnapshotNode[];
11
+ /** Nodes whose properties changed (text, value, focus, etc.) */
12
+ changed: Array<{
13
+ node: SnapshotNode;
14
+ changes: string[];
15
+ }>;
16
+ /** Elements that are the same content but moved to a different position */
17
+ moved: Array<{
18
+ node: SnapshotNode;
19
+ from: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ to: {
24
+ x: number;
25
+ y: number;
26
+ };
27
+ deltaX: number;
28
+ deltaY: number;
29
+ }>;
30
+ /** Elements that kept position but changed size */
31
+ resized: Array<{
32
+ node: SnapshotNode;
33
+ from: {
34
+ width: number;
35
+ height: number;
36
+ };
37
+ to: {
38
+ width: number;
39
+ height: number;
40
+ };
41
+ }>;
42
+ /** Whether the tree structure changed significantly */
43
+ structuralChange: boolean;
44
+ }
45
+ /**
46
+ * Compute a diff between two snapshot trees.
47
+ *
48
+ * Identity matching: uses content signature (type + resourceId + text + label).
49
+ * Elements with matching content but different position are reported as "moved".
50
+ * Elements with matching content+position but different size are reported as "resized".
51
+ * Elements present in current but not previous are "added".
52
+ * Elements present in previous but not current are "removed".
53
+ */
54
+ export declare function diffSnapshots(previous: SnapshotNode, current: SnapshotNode): SnapshotDiff;
55
+ /** Format a diff for LLM consumption */
56
+ export declare function formatDiff(diff: SnapshotDiff): string;
57
+ //# sourceMappingURL=snapshot-differ.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-differ.d.ts","sourceRoot":"","sources":["../../../src/snapshot/snapshot-differ.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,sDAAsD;IACtD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,YAAY,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC,CAAC;IACH,2EAA2E;IAC3E,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,YAAY,CAAC;QACnB,IAAI,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,EAAE,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7B,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,mDAAmD;IACnD,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,YAAY,CAAC;QACnB,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACxC,EAAE,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACvC,CAAC,CAAC;IACH,uDAAuD;IACvD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,YAAY,GACpB,YAAY,CAyEd;AAkCD,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAuDrD"}