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/README.md +12 -9
- package/build/__tests__/integration/tools.test.js +9 -10
- package/build/__tests__/integration/tools.test.js.map +1 -1
- package/build/core/connection.d.ts +8 -0
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +77 -5
- package/build/core/connection.js.map +1 -1
- package/build/core/executor.d.ts +44 -7
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +603 -121
- package/build/core/executor.js.map +1 -1
- package/build/core/index.d.ts +2 -4
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +2 -6
- package/build/core/index.js.map +1 -1
- package/build/index.js +114 -143
- package/build/index.js.map +1 -1
- package/build/pro/tap.js +2 -2
- package/package.json +2 -3
- package/build/core/httpServer.d.ts +0 -14
- package/build/core/httpServer.d.ts.map +0 -1
- package/build/core/httpServer.js +0 -2459
- package/build/core/httpServer.js.map +0 -1
- package/build/core/httpServerProcess.d.ts +0 -25
- package/build/core/httpServerProcess.d.ts.map +0 -1
- package/build/core/httpServerProcess.js +0 -153
- package/build/core/httpServerProcess.js.map +0 -1
- package/build/httpServerStandalone.d.ts +0 -7
- package/build/httpServerStandalone.d.ts.map +0 -1
- package/build/httpServerStandalone.js +0 -31
- 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
|
|
@@ -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.
|
|
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:
|
|
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 ({
|
|
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
|
|
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
|
-
|
|
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("
|
|
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
|
|
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 ({
|
|
1350
|
-
const result = await getScreenLayout({
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
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
|
|
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
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
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
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
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:
|
|
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
|
-
//
|
|
3453
|
+
// Store screenshot in image buffer
|
|
3423
3454
|
try {
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
:
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
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
|
|
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();
|