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.
- package/README.md +48 -22
- 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 +8 -6
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +447 -112
- package/build/core/executor.js.map +1 -1
- package/build/core/index.d.ts +18 -20
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +16 -20
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +20 -7
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +453 -339
- package/build/core/ios.js.map +1 -1
- package/build/core/telemetry.d.ts +1 -1
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +5 -1
- package/build/core/telemetry.js.map +1 -1
- package/build/index.js +65 -130
- package/build/index.js.map +1 -1
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +159 -101
- package/build/pro/tap.js.map +1 -1
- 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
|
|
@@ -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.
|
|
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:
|
|
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 ({
|
|
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
|
|
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
|
-
|
|
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("
|
|
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
|
|
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 ({
|
|
1347
|
-
const result = await getScreenLayout({
|
|
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
|
|
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
|
|
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
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
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:
|
|
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
|
-
//
|
|
3413
|
+
// Store screenshot in image buffer
|
|
3419
3414
|
try {
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
:
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
|
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();
|