react-native-ai-devtools 1.4.18 → 1.4.20

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/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
@@ -27,7 +27,7 @@ connectMetroBuildEvents, disconnectMetroBuildEvents, getBundleErrors, getBundleS
27
27
  // Error screen parsing (OCR fallback)
28
28
  parseErrorScreenText, formatParsedError,
29
29
  // OCR
30
- recognizeText, inferIOSDevicePixelRatio,
30
+ recognizeText, inferIOSDevicePixelRatio, enrichScreenshotWithLayout,
31
31
  // Android
32
32
  listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, androidListPackages,
33
33
  // Android UI Input (Phase 2)
@@ -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)
@@ -332,6 +330,10 @@ registerToolWithTelemetry("scan_metro", {
332
330
  portDevices.set(port, debuggable);
333
331
  }
334
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);
335
337
  // Phase 2: Assign each device to the best port
336
338
  // If a device appears on multiple ports, prefer the port where it has
337
339
  // the fewest OTHER unique devices (i.e. its dedicated Metro server)
@@ -370,6 +372,9 @@ registerToolWithTelemetry("scan_metro", {
370
372
  }
371
373
  // Phase 3: Connect devices to their assigned ports
372
374
  const results = [];
375
+ if (purged.length > 0) {
376
+ results.push(`Purged ${purged.length} stale connection(s): ${purged.join(", ")}`);
377
+ }
373
378
  for (const port of openPorts) {
374
379
  const devices = portDevices.get(port);
375
380
  if (!devices) {
@@ -1246,13 +1251,8 @@ registerToolWithTelemetry("inspect_global", {
1246
1251
  // ============================================================================
1247
1252
  // Tool: Get the React component tree
1248
1253
  registerToolWithTelemetry("get_component_tree", {
1249
- 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.",
1250
1255
  inputSchema: {
1251
- focusedOnly: z
1252
- .boolean()
1253
- .optional()
1254
- .default(false)
1255
- .describe("Return only the focused/active screen subtree, skipping navigation wrappers and overlays. Dramatically reduces output size. (Recommended: true)"),
1256
1256
  structureOnly: z
1257
1257
  .boolean()
1258
1258
  .optional()
@@ -1261,7 +1261,7 @@ registerToolWithTelemetry("get_component_tree", {
1261
1261
  maxDepth: z
1262
1262
  .number()
1263
1263
  .optional()
1264
- .describe("Maximum tree depth (default: 25 for focusedOnly+structureOnly, 40 for structureOnly, 100 for full mode)"),
1264
+ .describe("Maximum tree depth (default: 5000)"),
1265
1265
  includeProps: z
1266
1266
  .boolean()
1267
1267
  .optional()
@@ -1284,9 +1284,8 @@ registerToolWithTelemetry("get_component_tree", {
1284
1284
  .describe("Output format: 'json' or 'tonl' (default, compact indented tree). Ignored if structureOnly=true."),
1285
1285
  device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
1286
1286
  }
1287
- }, async ({ focusedOnly, structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format, device }) => {
1287
+ }, async ({ structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format, device }) => {
1288
1288
  const result = await getComponentTree({
1289
- focusedOnly,
1290
1289
  structureOnly,
1291
1290
  maxDepth,
1292
1291
  includeProps,
@@ -1317,37 +1316,22 @@ registerToolWithTelemetry("get_component_tree", {
1317
1316
  });
1318
1317
  // Tool: Get full screen layout (all components with layout styles)
1319
1318
  registerToolWithTelemetry("get_screen_layout", {
1320
- 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.).",
1321
1320
  inputSchema: {
1322
- maxDepth: z
1323
- .number()
1324
- .optional()
1325
- .default(65)
1326
- .describe("Maximum tree depth to traverse (default: 65, balanced for most screens)"),
1327
- componentsOnly: z
1321
+ extended: z
1328
1322
  .boolean()
1329
1323
  .optional()
1330
1324
  .default(false)
1331
- .describe("Only show custom components, hide host components (View, Text, etc.)"),
1332
- shortPath: z
1333
- .boolean()
1334
- .optional()
1335
- .default(true)
1336
- .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."),
1337
1326
  summary: z
1338
1327
  .boolean()
1339
1328
  .optional()
1340
1329
  .default(false)
1341
- .describe("Return only component counts by name instead of full element list (default: false)"),
1342
- format: z
1343
- .enum(["json", "tonl"])
1344
- .optional()
1345
- .default("tonl")
1346
- .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)"),
1347
1331
  device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
1348
1332
  }
1349
- }, async ({ maxDepth, componentsOnly, shortPath, summary, format, device }) => {
1350
- const result = await getScreenLayout({ maxDepth, componentsOnly, shortPath, summary, format, device });
1333
+ }, async ({ extended, summary, device }) => {
1334
+ const result = await getScreenLayout({ extended, summary, device });
1351
1335
  if (!result.success) {
1352
1336
  return {
1353
1337
  content: [
@@ -1370,7 +1354,7 @@ registerToolWithTelemetry("get_screen_layout", {
1370
1354
  });
1371
1355
  // Tool: Inspect a specific component by name
1372
1356
  registerToolWithTelemetry("inspect_component", {
1373
- 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.",
1374
1358
  inputSchema: {
1375
1359
  componentName: z
1376
1360
  .string()
@@ -1431,7 +1415,7 @@ registerToolWithTelemetry("inspect_component", {
1431
1415
  });
1432
1416
  // Tool: Find components matching a pattern
1433
1417
  registerToolWithTelemetry("find_components", {
1434
- 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.",
1435
1419
  inputSchema: {
1436
1420
  pattern: z
1437
1421
  .string()
@@ -1486,7 +1470,7 @@ registerToolWithTelemetry("tap", {
1486
1470
  "- tap(text=\"Submit\") — finds and taps element with matching text\n" +
1487
1471
  "- tap(testID=\"login-btn\") — finds by testID\n" +
1488
1472
  "- tap(component=\"HamburgerIcon\") — finds by React component name\n" +
1489
- "- tap(x=300, y=600) — taps at pixel coordinates from screenshot (conversion handled internally)\n" +
1473
+ "- tap(x=300, y=600) — taps at pixel coordinates (LAST RESORT — only use if text/testID/component strategies all fail)\n" +
1490
1474
  "- tap(text=\"Menu\", strategy=\"ocr\") — forces OCR strategy only\n" +
1491
1475
  "- tap(x=300, y=600, native=true, platform=\"android\") — taps directly via ADB/simctl without React Native connection\n\n" +
1492
1476
  "Returns a post-tap screenshot by default (set screenshot=false to disable). " +
@@ -2664,7 +2648,7 @@ registerToolWithTelemetry("list_android_devices", {
2664
2648
  });
2665
2649
  // Tool: Android screenshot
2666
2650
  registerToolWithTelemetry("android_screenshot", {
2667
- description: "Take a screenshot from an Android device/emulator. Returns the image data that can be displayed.",
2651
+ description: "Take a screenshot from an Android device/emulator. Returns the image and a screen layout overlay showing visible React components with names, text content, hierarchy, and tap-ready pixel coordinates. Use component names for inspect_component/find_components. Use tap(text=...) for interaction — only fall back to tap(x,y) coordinates from the layout if all tap strategies fail.",
2668
2652
  inputSchema: {
2669
2653
  outputPath: z
2670
2654
  .string()
@@ -2721,6 +2705,16 @@ registerToolWithTelemetry("android_screenshot", {
2721
2705
  catch {
2722
2706
  // Use defaults
2723
2707
  }
2708
+ // Enrich with screen layout data (component names + tap coordinates)
2709
+ // Android: density ratio = densityDpi / 160 (160dpi = 1dp = 1px baseline)
2710
+ const androidPixelRatio = densityDpi / 160;
2711
+ let layoutText = null;
2712
+ try {
2713
+ layoutText = await enrichScreenshotWithLayout(androidPixelRatio, result.scaleFactor || 1);
2714
+ }
2715
+ catch {
2716
+ // Non-fatal: screenshot works without layout enrichment
2717
+ }
2724
2718
  infoText += `\n📱 Android uses PIXELS for all coordinates`;
2725
2719
  if (result.scaleFactor && result.scaleFactor > 1) {
2726
2720
  infoText += `\n🖼️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
@@ -2731,11 +2725,22 @@ registerToolWithTelemetry("android_screenshot", {
2731
2725
  }
2732
2726
  infoText += `\n⚠️ Status bar: ${statusBarPixels}px (${statusBarDp}dp) from top - app content starts below this`;
2733
2727
  infoText += `\n📊 Display density: ${densityDpi}dpi`;
2734
- infoText += `\n\n💡 Next steps:`;
2735
- infoText += `\n • tap(text="Button Label") — tap element by visible text`;
2736
- infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
2737
- infoText += `\n • android_describe_all — get full UI tree with exact tap coordinates`;
2738
- infoText += `\n • android_find_element(text="...") — find element coordinates without tapping`;
2728
+ if (layoutText) {
2729
+ infoText += `\n\n📋 Screen Layout (components with tap coordinates in pixels):`;
2730
+ infoText += `\n${layoutText}`;
2731
+ infoText += `\n\n💡 Next steps:`;
2732
+ infoText += `\n • tap(text="Button Label") — tap element by visible text (preferred)`;
2733
+ infoText += `\n • tap(testID="id") or tap(component="Name") — tap by testID or component name`;
2734
+ infoText += `\n • tap(x=<px>, y=<px>) — use tap coordinates from layout above ONLY if text/testID/component tap fails`;
2735
+ infoText += `\n • inspect_component("ComponentName") — inspect a component from the layout above`;
2736
+ }
2737
+ else {
2738
+ infoText += `\n\n💡 Next steps:`;
2739
+ infoText += `\n • tap(text="Button Label") — tap element by visible text`;
2740
+ infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
2741
+ infoText += `\n • android_describe_all — get full UI tree with exact tap coordinates`;
2742
+ infoText += `\n • android_find_element(text="...") — find element coordinates without tapping`;
2743
+ }
2739
2744
  // Check for LogBox overlay (uses default CDP device — native deviceId cannot be mapped to CDP device name)
2740
2745
  try {
2741
2746
  const logBoxState = await detectLogBox();
@@ -3205,7 +3210,7 @@ registerToolWithTelemetry("list_ios_simulators", {
3205
3210
  });
3206
3211
  // Tool: iOS screenshot
3207
3212
  registerToolWithTelemetry("ios_screenshot", {
3208
- description: "Take a screenshot from an iOS simulator. Returns the image data that can be displayed.",
3213
+ description: "Take a screenshot from an iOS simulator. Returns the image and a screen layout overlay showing visible React components with names, text content, hierarchy, and tap-ready pixel coordinates. Use component names for inspect_component/find_components. Use tap(text=...) for interaction — only fall back to tap(x,y) coordinates from the layout if all tap strategies fail.",
3209
3214
  inputSchema: {
3210
3215
  outputPath: z
3211
3216
  .string()
@@ -3289,6 +3294,14 @@ registerToolWithTelemetry("ios_screenshot", {
3289
3294
  pointHeight = Math.round(pixelHeight / scaleFactor);
3290
3295
  }
3291
3296
  const safeAreaOffsetPixels = safeAreaTop * scaleFactor;
3297
+ // Enrich with screen layout data (component names + tap coordinates)
3298
+ let layoutText = null;
3299
+ try {
3300
+ layoutText = await enrichScreenshotWithLayout(scaleFactor, result.scaleFactor || 1);
3301
+ }
3302
+ catch {
3303
+ // Non-fatal: screenshot works without layout enrichment
3304
+ }
3292
3305
  let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
3293
3306
  infoText += `\n📱 iOS screen: ${pointWidth}x${pointHeight} points (${scaleFactor}x scale)`;
3294
3307
  infoText += `\n📐 tap() handles pixel-to-point conversion automatically — pass pixel coords from this image directly`;
@@ -3296,11 +3309,22 @@ registerToolWithTelemetry("ios_screenshot", {
3296
3309
  if (result.scaleFactor && result.scaleFactor > 1) {
3297
3310
  infoText += `\n🖼️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
3298
3311
  }
3299
- infoText += `\n\n💡 Next steps:`;
3300
- infoText += `\n • tap(text="Button Label") — tap element by visible text`;
3301
- infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
3302
- infoText += `\n • ios_describe_all — get full UI tree with exact tap coordinates`;
3303
- infoText += `\n • ios_find_element(label="...") — find element coordinates without tapping`;
3312
+ if (layoutText) {
3313
+ infoText += `\n\n📋 Screen Layout (components with tap coordinates in pixels):`;
3314
+ infoText += `\n${layoutText}`;
3315
+ infoText += `\n\n💡 Next steps:`;
3316
+ infoText += `\n • tap(text="Button Label") — tap element by visible text (preferred)`;
3317
+ infoText += `\n • tap(testID="id") or tap(component="Name") — tap by testID or component name`;
3318
+ infoText += `\n • tap(x=<px>, y=<px>) — use tap coordinates from layout above ONLY if text/testID/component tap fails`;
3319
+ infoText += `\n • inspect_component("ComponentName") — inspect a component from the layout above`;
3320
+ }
3321
+ else {
3322
+ infoText += `\n\n💡 Next steps:`;
3323
+ infoText += `\n • tap(text="Button Label") — tap element by visible text`;
3324
+ infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
3325
+ infoText += `\n • ios_describe_all — get full UI tree with exact tap coordinates`;
3326
+ infoText += `\n • ios_find_element(label="...") — find element coordinates without tapping`;
3327
+ }
3304
3328
  // Check for LogBox overlay (uses default CDP device — native UDID cannot be mapped to CDP device name)
3305
3329
  try {
3306
3330
  const logBoxState = await detectLogBox();
@@ -3359,32 +3383,39 @@ registerToolWithTelemetry("ocr_screenshot", {
3359
3383
  .describe("Optional device ID (Android) or UDID (iOS). Uses first available device if not specified.")
3360
3384
  }
3361
3385
  }, async ({ platform, deviceId }) => {
3362
- // Call the HTTP endpoint for OCR (allows hot-reload without session restart)
3363
- // Prefer child process port, fall back to in-process port
3364
- const port = getDebugServerPort();
3365
- if (!port) {
3366
- return {
3367
- content: [
3368
- {
3369
- type: "text",
3370
- text: "Debug HTTP server not running"
3371
- }
3372
- ],
3373
- isError: true
3374
- };
3375
- }
3376
3386
  try {
3377
- const params = new URLSearchParams({ platform, engine: "auto" });
3378
- if (deviceId)
3379
- params.set("deviceId", deviceId);
3380
- const response = await fetch(`http://localhost:${port}/api/ocr?${params}`);
3381
- const ocrResult = await response.json();
3387
+ // Take screenshot
3388
+ const screenshotResult = platform === "android"
3389
+ ? await androidScreenshot(undefined, deviceId)
3390
+ : await iosScreenshot(undefined, deviceId);
3391
+ if (!screenshotResult.success || !screenshotResult.data) {
3392
+ return {
3393
+ content: [
3394
+ {
3395
+ type: "text",
3396
+ text: `Screenshot failed: ${screenshotResult.error || "No image data"}`
3397
+ }
3398
+ ],
3399
+ isError: true
3400
+ };
3401
+ }
3402
+ // Calculate device pixel ratio for iOS
3403
+ const devicePixelRatio = platform === "ios" && screenshotResult.originalWidth && screenshotResult.originalHeight
3404
+ ? inferIOSDevicePixelRatio(screenshotResult.originalWidth, screenshotResult.originalHeight)
3405
+ : 1;
3406
+ // Run OCR on the screenshot
3407
+ const scaleFactor = screenshotResult.scaleFactor || 1;
3408
+ const ocrResult = await recognizeText(screenshotResult.data, {
3409
+ scaleFactor,
3410
+ platform,
3411
+ devicePixelRatio
3412
+ });
3382
3413
  if (!ocrResult.success) {
3383
3414
  return {
3384
3415
  content: [
3385
3416
  {
3386
3417
  type: "text",
3387
- text: `OCR failed: ${ocrResult.error || "Unknown error"}`
3418
+ text: `OCR failed: no text recognized`
3388
3419
  }
3389
3420
  ],
3390
3421
  isError: true
@@ -3419,25 +3450,20 @@ registerToolWithTelemetry("ocr_screenshot", {
3419
3450
  catch {
3420
3451
  // Non-fatal: LogBox detection failure should not break OCR
3421
3452
  }
3422
- // Capture screenshot and store in image buffer
3453
+ // Store screenshot in image buffer
3423
3454
  try {
3424
- const screenshotResult = platform === "android"
3425
- ? await androidScreenshot(undefined, deviceId)
3426
- : await iosScreenshot(undefined, deviceId);
3427
- if (screenshotResult.success && screenshotResult.data) {
3428
- imageBuffer.add({
3429
- id: `ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3430
- image: screenshotResult.data,
3431
- timestamp: Date.now(),
3432
- source: "ocr_screenshot",
3433
- metadata: {
3434
- width: screenshotResult.originalWidth || 0,
3435
- height: screenshotResult.originalHeight || 0,
3436
- scaleFactor: screenshotResult.scaleFactor || 1,
3437
- platform,
3438
- },
3439
- });
3440
- }
3455
+ imageBuffer.add({
3456
+ id: `ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3457
+ image: screenshotResult.data,
3458
+ timestamp: Date.now(),
3459
+ source: "ocr_screenshot",
3460
+ metadata: {
3461
+ width: screenshotResult.originalWidth || 0,
3462
+ height: screenshotResult.originalHeight || 0,
3463
+ scaleFactor,
3464
+ platform,
3465
+ },
3466
+ });
3441
3467
  }
3442
3468
  catch {
3443
3469
  // Non-fatal: image buffer write failure should not break OCR response
@@ -3456,7 +3482,7 @@ registerToolWithTelemetry("ocr_screenshot", {
3456
3482
  content: [
3457
3483
  {
3458
3484
  type: "text",
3459
- text: `OCR request failed: ${error instanceof Error ? error.message : String(error)}`
3485
+ text: `OCR failed: ${error instanceof Error ? error.message : String(error)}`
3460
3486
  }
3461
3487
  ],
3462
3488
  isError: true
@@ -3830,57 +3856,6 @@ server.registerTool("ios_wait_for_element", {
3830
3856
  content: [{ type: "text", text: info }]
3831
3857
  };
3832
3858
  });
3833
- // Tool: Get debug server info
3834
- registerToolWithTelemetry("get_debug_server", {
3835
- description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
3836
- inputSchema: {}
3837
- }, async () => {
3838
- const port = getDebugServerPort();
3839
- if (!port) {
3840
- return {
3841
- content: [
3842
- {
3843
- type: "text",
3844
- text: "Debug HTTP server is not running."
3845
- }
3846
- ],
3847
- isError: true
3848
- };
3849
- }
3850
- const info = {
3851
- url: `http://localhost:${port}`,
3852
- endpoints: {
3853
- status: `http://localhost:${port}/api/status`,
3854
- logs: `http://localhost:${port}/api/logs`,
3855
- network: `http://localhost:${port}/api/network`,
3856
- bundleErrors: `http://localhost:${port}/api/bundle-errors`,
3857
- apps: `http://localhost:${port}/api/apps`
3858
- }
3859
- };
3860
- return {
3861
- content: [
3862
- {
3863
- type: "text",
3864
- text: `Debug HTTP server running at:\n\n${JSON.stringify(info, null, 2)}`
3865
- }
3866
- ]
3867
- };
3868
- });
3869
- // Tool: Restart HTTP server (hot-reload)
3870
- registerToolWithTelemetry("restart_http_server", {
3871
- description: "Note: HTTP server now runs in-process to share state. To apply code changes, restart the MCP session.",
3872
- inputSchema: {}
3873
- }, async () => {
3874
- const port = getDebugServerPort();
3875
- return {
3876
- content: [
3877
- {
3878
- type: "text",
3879
- 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.`
3880
- }
3881
- ]
3882
- };
3883
- });
3884
3859
  // Tool: Activate Pro license
3885
3860
  registerToolWithTelemetry("activate_license", getActivateLicenseConfig(), handleActivateLicense);
3886
3861
  // Tool: Delete account
@@ -3908,10 +3883,6 @@ async function main() {
3908
3883
  // License validation is lazy — runs on first tool use via ensureLicense()
3909
3884
  initTelemetry();
3910
3885
  identifyIfDevMode(getInstallationId());
3911
- // Start debug HTTP server in-process (shares state with MCP server)
3912
- // Note: Child process mode doesn't work because state (logs, network, apps) isn't shared
3913
- await startDebugHttpServer();
3914
- console.error("[rn-ai-debugger] HTTP server started in-process");
3915
3886
  // --- Eager usage check (pre-loads usage cache for tool-level gate) ---
3916
3887
  const { ensureLicense } = await import("./core/license.js");
3917
3888
  await ensureLicense();