react-native-ai-devtools 1.4.17 → 1.4.19

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 (41) hide show
  1. package/README.md +48 -22
  2. package/build/__tests__/integration/tools.test.js +9 -10
  3. package/build/__tests__/integration/tools.test.js.map +1 -1
  4. package/build/core/connection.d.ts +8 -0
  5. package/build/core/connection.d.ts.map +1 -1
  6. package/build/core/connection.js +77 -5
  7. package/build/core/connection.js.map +1 -1
  8. package/build/core/executor.d.ts +8 -6
  9. package/build/core/executor.d.ts.map +1 -1
  10. package/build/core/executor.js +447 -112
  11. package/build/core/executor.js.map +1 -1
  12. package/build/core/index.d.ts +18 -20
  13. package/build/core/index.d.ts.map +1 -1
  14. package/build/core/index.js +16 -20
  15. package/build/core/index.js.map +1 -1
  16. package/build/core/ios.d.ts +20 -7
  17. package/build/core/ios.d.ts.map +1 -1
  18. package/build/core/ios.js +453 -339
  19. package/build/core/ios.js.map +1 -1
  20. package/build/core/telemetry.d.ts +1 -1
  21. package/build/core/telemetry.d.ts.map +1 -1
  22. package/build/core/telemetry.js +5 -1
  23. package/build/core/telemetry.js.map +1 -1
  24. package/build/index.js +65 -130
  25. package/build/index.js.map +1 -1
  26. package/build/pro/tap.d.ts.map +1 -1
  27. package/build/pro/tap.js +159 -101
  28. package/build/pro/tap.js.map +1 -1
  29. package/package.json +2 -3
  30. package/build/core/httpServer.d.ts +0 -14
  31. package/build/core/httpServer.d.ts.map +0 -1
  32. package/build/core/httpServer.js +0 -2459
  33. package/build/core/httpServer.js.map +0 -1
  34. package/build/core/httpServerProcess.d.ts +0 -25
  35. package/build/core/httpServerProcess.d.ts.map +0 -1
  36. package/build/core/httpServerProcess.js +0 -153
  37. package/build/core/httpServerProcess.js.map +0 -1
  38. package/build/httpServerStandalone.d.ts +0 -7
  39. package/build/httpServerStandalone.d.ts.map +0 -1
  40. package/build/httpServerStandalone.js +0 -31
  41. package/build/httpServerStandalone.js.map +0 -1
package/build/index.js CHANGED
@@ -17,7 +17,7 @@ import { logBuffers, networkBuffers, getLogBuffer, getNetworkBuffer, getTotalLog
17
17
  // React Component Inspection
18
18
  getComponentTree, getScreenLayout, inspectComponent, findComponents, inspectAtPoint, toggleElementInspector, isInspectorActive, getInspectorSelection, getFirstConnectedApp, getLogs, searchLogs, getLogSummary, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
19
19
  // Connection state
20
- getAllConnectionStates, getAllConnectionMetadata, getRecentGaps, formatDuration, cancelAllReconnectionTimers, cancelReconnectionTimer, clearAllConnectionState, clearAllCDPMessageTimes, suppressReconnection, suppressReconnectionForKey, clearReconnectionSuppression,
20
+ getAllConnectionStates, getAllConnectionMetadata, getRecentGaps, formatDuration, cancelAllReconnectionTimers, cancelReconnectionTimer, clearAllConnectionState, clearAllCDPMessageTimes, suppressReconnection, suppressReconnectionForKey, clearReconnectionSuppression, purgeStaleConnectionsForPorts,
21
21
  // Context health tracking
22
22
  getContextHealth,
23
23
  // Connection resilience
@@ -42,8 +42,6 @@ listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTe
42
42
  iosTap, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySequence, iosDescribeAll, iosDescribePoint, IOS_BUTTON_TYPES,
43
43
  // iOS Element Finding (no screenshots)
44
44
  iosFindElement, iosWaitForElement,
45
- // Debug HTTP Server
46
- startDebugHttpServer, getDebugServerPort,
47
45
  // Telemetry
48
46
  initTelemetry, trackToolInvocation, getTargetPlatform,
49
47
  // Format utilities (TONL)
@@ -183,6 +181,7 @@ function registerToolWithTelemetry(toolName, config, handler, emptyResultDetecto
183
181
  let meaningful;
184
182
  let changeRate;
185
183
  let tapStrategy;
184
+ let iosDriver;
186
185
  let responsePreview;
187
186
  let emptyReason;
188
187
  try {
@@ -217,6 +216,8 @@ function registerToolWithTelemetry(toolName, config, handler, emptyResultDetecto
217
216
  changeRate = result._changeRate;
218
217
  if (result?._tapStrategy)
219
218
  tapStrategy = result._tapStrategy;
219
+ if (result?._iosDriver)
220
+ iosDriver = result._iosDriver;
220
221
  if (result?._emptyReason)
221
222
  emptyReason = result._emptyReason;
222
223
  if (Array.isArray(result?.content)) {
@@ -251,7 +252,7 @@ function registerToolWithTelemetry(toolName, config, handler, emptyResultDetecto
251
252
  }
252
253
  finally {
253
254
  const duration = Date.now() - startTime;
254
- trackToolInvocation(toolName, success, duration, errorMessage, errorContext, inputTokens, outputTokens, getTargetPlatform(), emptyResult, meaningful, changeRate, tapStrategy, responsePreview, emptyReason);
255
+ trackToolInvocation(toolName, success, duration, errorMessage, errorContext, inputTokens, outputTokens, getTargetPlatform(), emptyResult, meaningful, changeRate, tapStrategy, iosDriver, responsePreview, emptyReason);
255
256
  getPostHogClient()?.capture({
256
257
  distinctId: getInstallationId(),
257
258
  event: toolName,
@@ -329,6 +330,10 @@ registerToolWithTelemetry("scan_metro", {
329
330
  portDevices.set(port, debuggable);
330
331
  }
331
332
  }
333
+ // Phase 1.5: Purge stale connections for scanned ports
334
+ // When Metro restarts with a different app, device IDs change.
335
+ // Old connections remain in connectedApps — remove them before connecting new devices.
336
+ const purged = purgeStaleConnectionsForPorts(portDevices);
332
337
  // Phase 2: Assign each device to the best port
333
338
  // If a device appears on multiple ports, prefer the port where it has
334
339
  // the fewest OTHER unique devices (i.e. its dedicated Metro server)
@@ -367,6 +372,9 @@ registerToolWithTelemetry("scan_metro", {
367
372
  }
368
373
  // Phase 3: Connect devices to their assigned ports
369
374
  const results = [];
375
+ if (purged.length > 0) {
376
+ results.push(`Purged ${purged.length} stale connection(s): ${purged.join(", ")}`);
377
+ }
370
378
  for (const port of openPorts) {
371
379
  const devices = portDevices.get(port);
372
380
  if (!devices) {
@@ -1243,13 +1251,8 @@ registerToolWithTelemetry("inspect_global", {
1243
1251
  // ============================================================================
1244
1252
  // Tool: Get the React component tree
1245
1253
  registerToolWithTelemetry("get_component_tree", {
1246
- description: "Get the React component tree from the running app. **RECOMMENDED**: Use focusedOnly=true with structureOnly=true for a token-efficient overview of just the active screen (~1-2KB). This skips navigation wrappers and global overlays, showing only what's actually visible.",
1254
+ description: "Get the full React component tree from the running app. Shows the complete fiber hierarchy including providers, navigation wrappers, and internal components. For a screen overview with positions and text, use get_screen_layout instead. Use structureOnly=true for compact names-only output.",
1247
1255
  inputSchema: {
1248
- focusedOnly: z
1249
- .boolean()
1250
- .optional()
1251
- .default(false)
1252
- .describe("Return only the focused/active screen subtree, skipping navigation wrappers and overlays. Dramatically reduces output size. (Recommended: true)"),
1253
1256
  structureOnly: z
1254
1257
  .boolean()
1255
1258
  .optional()
@@ -1258,7 +1261,7 @@ registerToolWithTelemetry("get_component_tree", {
1258
1261
  maxDepth: z
1259
1262
  .number()
1260
1263
  .optional()
1261
- .describe("Maximum tree depth (default: 25 for focusedOnly+structureOnly, 40 for structureOnly, 100 for full mode)"),
1264
+ .describe("Maximum tree depth (default: 5000)"),
1262
1265
  includeProps: z
1263
1266
  .boolean()
1264
1267
  .optional()
@@ -1281,9 +1284,8 @@ registerToolWithTelemetry("get_component_tree", {
1281
1284
  .describe("Output format: 'json' or 'tonl' (default, compact indented tree). Ignored if structureOnly=true."),
1282
1285
  device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
1283
1286
  }
1284
- }, async ({ focusedOnly, structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format, device }) => {
1287
+ }, async ({ structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format, device }) => {
1285
1288
  const result = await getComponentTree({
1286
- focusedOnly,
1287
1289
  structureOnly,
1288
1290
  maxDepth,
1289
1291
  includeProps,
@@ -1314,37 +1316,22 @@ registerToolWithTelemetry("get_component_tree", {
1314
1316
  });
1315
1317
  // Tool: Get full screen layout (all components with layout styles)
1316
1318
  registerToolWithTelemetry("get_screen_layout", {
1317
- description: "Get layout information for all components on screen. **USE AFTER get_component_tree**: First use get_component_tree(structureOnly=true) to understand structure, then use this tool OR find_components with includeLayout=true to get layout details for specific areas. This tool returns full layout data which can be large for complex screens.",
1319
+ description: "Get a screen map showing visible components as an indented tree with actual screen positions. Uses measureInWindow for real coordinates and filters out off-screen components. Returns meaningful component names with text content and frame data (x,y width x height). Coordinates are in **points** (iOS) or **dp** (Android) — NOT screenshot pixels. Use tap(text=...) or tap(testID=...) to interact with discovered components. Use extended=true to include layout styles (padding, margin, flex, backgroundColor, etc.).",
1318
1320
  inputSchema: {
1319
- maxDepth: z
1320
- .number()
1321
- .optional()
1322
- .default(65)
1323
- .describe("Maximum tree depth to traverse (default: 65, balanced for most screens)"),
1324
- componentsOnly: z
1321
+ extended: z
1325
1322
  .boolean()
1326
1323
  .optional()
1327
1324
  .default(false)
1328
- .describe("Only show custom components, hide host components (View, Text, etc.)"),
1329
- shortPath: z
1330
- .boolean()
1331
- .optional()
1332
- .default(true)
1333
- .describe("Show only last 3 path segments instead of full path (default: true)"),
1325
+ .describe("Include layout styles (padding, margin, flex, backgroundColor, borderRadius, etc.) for each component. Default: false for compact output."),
1334
1326
  summary: z
1335
1327
  .boolean()
1336
1328
  .optional()
1337
1329
  .default(false)
1338
- .describe("Return only component counts by name instead of full element list (default: false)"),
1339
- format: z
1340
- .enum(["json", "tonl"])
1341
- .optional()
1342
- .default("tonl")
1343
- .describe("Output format: 'json' or 'tonl' (default, pipe-delimited rows, ~40% smaller)"),
1330
+ .describe("Return only component counts by name instead of full tree (default: false)"),
1344
1331
  device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
1345
1332
  }
1346
- }, async ({ maxDepth, componentsOnly, shortPath, summary, format, device }) => {
1347
- const result = await getScreenLayout({ maxDepth, componentsOnly, shortPath, summary, format, device });
1333
+ }, async ({ extended, summary, device }) => {
1334
+ const result = await getScreenLayout({ extended, summary, device });
1348
1335
  if (!result.success) {
1349
1336
  return {
1350
1337
  content: [
@@ -1367,7 +1354,7 @@ registerToolWithTelemetry("get_screen_layout", {
1367
1354
  });
1368
1355
  // Tool: Inspect a specific component by name
1369
1356
  registerToolWithTelemetry("inspect_component", {
1370
- description: "Inspect a specific React component by name. **DRILL-DOWN TOOL**: Use after get_component_tree(structureOnly=true) to inspect specific components. Returns props, style, state (hooks), and optionally children tree. Use childrenDepth to control how deep nested children go.",
1357
+ description: "Inspect a specific React component by name. **DRILL-DOWN TOOL**: Use after get_screen_layout or find_components to identify which component to inspect. Returns props, style, state (hooks), and optionally children tree. Use childrenDepth to control how deep nested children go.",
1371
1358
  inputSchema: {
1372
1359
  componentName: z
1373
1360
  .string()
@@ -1428,7 +1415,7 @@ registerToolWithTelemetry("inspect_component", {
1428
1415
  });
1429
1416
  // Tool: Find components matching a pattern
1430
1417
  registerToolWithTelemetry("find_components", {
1431
- description: "Find components matching a name pattern. **TARGETED SEARCH**: Use after get_component_tree(structureOnly=true) to find specific components by pattern and get their layout info. More efficient than get_screen_layout for targeted queries. Use includeLayout=true to get padding/margin/flex styles.",
1418
+ description: "Find components matching a name pattern. **TARGETED SEARCH**: Use after get_screen_layout or get_component_tree(structureOnly=true) to find specific components by pattern. Use includeLayout=true to get padding/margin/flex styles.",
1432
1419
  inputSchema: {
1433
1420
  pattern: z
1434
1421
  .string()
@@ -1597,6 +1584,7 @@ registerToolWithTelemetry("tap", {
1597
1584
  _meaningful: result.verification?.meaningful,
1598
1585
  _changeRate: result.verification?.changeRate,
1599
1586
  _tapStrategy: result.method,
1587
+ _iosDriver: result.platform === "ios" ? (process.env.IOS_DRIVER?.toLowerCase() || "idb") : undefined,
1600
1588
  };
1601
1589
  });
1602
1590
  // Tool: Toggle Element Inspector programmatically
@@ -3355,32 +3343,39 @@ registerToolWithTelemetry("ocr_screenshot", {
3355
3343
  .describe("Optional device ID (Android) or UDID (iOS). Uses first available device if not specified.")
3356
3344
  }
3357
3345
  }, async ({ platform, deviceId }) => {
3358
- // Call the HTTP endpoint for OCR (allows hot-reload without session restart)
3359
- // Prefer child process port, fall back to in-process port
3360
- const port = getDebugServerPort();
3361
- if (!port) {
3362
- return {
3363
- content: [
3364
- {
3365
- type: "text",
3366
- text: "Debug HTTP server not running"
3367
- }
3368
- ],
3369
- isError: true
3370
- };
3371
- }
3372
3346
  try {
3373
- const params = new URLSearchParams({ platform, engine: "auto" });
3374
- if (deviceId)
3375
- params.set("deviceId", deviceId);
3376
- const response = await fetch(`http://localhost:${port}/api/ocr?${params}`);
3377
- const ocrResult = await response.json();
3347
+ // Take screenshot
3348
+ const screenshotResult = platform === "android"
3349
+ ? await androidScreenshot(undefined, deviceId)
3350
+ : await iosScreenshot(undefined, deviceId);
3351
+ if (!screenshotResult.success || !screenshotResult.data) {
3352
+ return {
3353
+ content: [
3354
+ {
3355
+ type: "text",
3356
+ text: `Screenshot failed: ${screenshotResult.error || "No image data"}`
3357
+ }
3358
+ ],
3359
+ isError: true
3360
+ };
3361
+ }
3362
+ // Calculate device pixel ratio for iOS
3363
+ const devicePixelRatio = platform === "ios" && screenshotResult.originalWidth && screenshotResult.originalHeight
3364
+ ? inferIOSDevicePixelRatio(screenshotResult.originalWidth, screenshotResult.originalHeight)
3365
+ : 1;
3366
+ // Run OCR on the screenshot
3367
+ const scaleFactor = screenshotResult.scaleFactor || 1;
3368
+ const ocrResult = await recognizeText(screenshotResult.data, {
3369
+ scaleFactor,
3370
+ platform,
3371
+ devicePixelRatio
3372
+ });
3378
3373
  if (!ocrResult.success) {
3379
3374
  return {
3380
3375
  content: [
3381
3376
  {
3382
3377
  type: "text",
3383
- text: `OCR failed: ${ocrResult.error || "Unknown error"}`
3378
+ text: `OCR failed: no text recognized`
3384
3379
  }
3385
3380
  ],
3386
3381
  isError: true
@@ -3415,25 +3410,20 @@ registerToolWithTelemetry("ocr_screenshot", {
3415
3410
  catch {
3416
3411
  // Non-fatal: LogBox detection failure should not break OCR
3417
3412
  }
3418
- // Capture screenshot and store in image buffer
3413
+ // Store screenshot in image buffer
3419
3414
  try {
3420
- const screenshotResult = platform === "android"
3421
- ? await androidScreenshot(undefined, deviceId)
3422
- : await iosScreenshot(undefined, deviceId);
3423
- if (screenshotResult.success && screenshotResult.data) {
3424
- imageBuffer.add({
3425
- id: `ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3426
- image: screenshotResult.data,
3427
- timestamp: Date.now(),
3428
- source: "ocr_screenshot",
3429
- metadata: {
3430
- width: screenshotResult.originalWidth || 0,
3431
- height: screenshotResult.originalHeight || 0,
3432
- scaleFactor: screenshotResult.scaleFactor || 1,
3433
- platform,
3434
- },
3435
- });
3436
- }
3415
+ imageBuffer.add({
3416
+ id: `ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3417
+ image: screenshotResult.data,
3418
+ timestamp: Date.now(),
3419
+ source: "ocr_screenshot",
3420
+ metadata: {
3421
+ width: screenshotResult.originalWidth || 0,
3422
+ height: screenshotResult.originalHeight || 0,
3423
+ scaleFactor,
3424
+ platform,
3425
+ },
3426
+ });
3437
3427
  }
3438
3428
  catch {
3439
3429
  // Non-fatal: image buffer write failure should not break OCR response
@@ -3452,7 +3442,7 @@ registerToolWithTelemetry("ocr_screenshot", {
3452
3442
  content: [
3453
3443
  {
3454
3444
  type: "text",
3455
- text: `OCR request failed: ${error instanceof Error ? error.message : String(error)}`
3445
+ text: `OCR failed: ${error instanceof Error ? error.message : String(error)}`
3456
3446
  }
3457
3447
  ],
3458
3448
  isError: true
@@ -3826,57 +3816,6 @@ server.registerTool("ios_wait_for_element", {
3826
3816
  content: [{ type: "text", text: info }]
3827
3817
  };
3828
3818
  });
3829
- // Tool: Get debug server info
3830
- registerToolWithTelemetry("get_debug_server", {
3831
- description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
3832
- inputSchema: {}
3833
- }, async () => {
3834
- const port = getDebugServerPort();
3835
- if (!port) {
3836
- return {
3837
- content: [
3838
- {
3839
- type: "text",
3840
- text: "Debug HTTP server is not running."
3841
- }
3842
- ],
3843
- isError: true
3844
- };
3845
- }
3846
- const info = {
3847
- url: `http://localhost:${port}`,
3848
- endpoints: {
3849
- status: `http://localhost:${port}/api/status`,
3850
- logs: `http://localhost:${port}/api/logs`,
3851
- network: `http://localhost:${port}/api/network`,
3852
- bundleErrors: `http://localhost:${port}/api/bundle-errors`,
3853
- apps: `http://localhost:${port}/api/apps`
3854
- }
3855
- };
3856
- return {
3857
- content: [
3858
- {
3859
- type: "text",
3860
- text: `Debug HTTP server running at:\n\n${JSON.stringify(info, null, 2)}`
3861
- }
3862
- ]
3863
- };
3864
- });
3865
- // Tool: Restart HTTP server (hot-reload)
3866
- registerToolWithTelemetry("restart_http_server", {
3867
- description: "Note: HTTP server now runs in-process to share state. To apply code changes, restart the MCP session.",
3868
- inputSchema: {}
3869
- }, async () => {
3870
- const port = getDebugServerPort();
3871
- return {
3872
- content: [
3873
- {
3874
- type: "text",
3875
- text: `HTTP server is running in-process on port ${port}. To apply code changes, rebuild with 'npm run build' and restart the MCP session. The in-process mode is required for the dashboard to show logs, network requests, and connected apps.`
3876
- }
3877
- ]
3878
- };
3879
- });
3880
3819
  // Tool: Activate Pro license
3881
3820
  registerToolWithTelemetry("activate_license", getActivateLicenseConfig(), handleActivateLicense);
3882
3821
  // Tool: Delete account
@@ -3904,10 +3843,6 @@ async function main() {
3904
3843
  // License validation is lazy — runs on first tool use via ensureLicense()
3905
3844
  initTelemetry();
3906
3845
  identifyIfDevMode(getInstallationId());
3907
- // Start debug HTTP server in-process (shares state with MCP server)
3908
- // Note: Child process mode doesn't work because state (logs, network, apps) isn't shared
3909
- await startDebugHttpServer();
3910
- console.error("[rn-ai-debugger] HTTP server started in-process");
3911
3846
  // --- Eager usage check (pre-loads usage cache for tool-level gate) ---
3912
3847
  const { ensureLicense } = await import("./core/license.js");
3913
3848
  await ensureLicense();