react-native-ai-devtools 1.2.7 → 1.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.
- package/README.md +8 -1
- package/build/core/connection.d.ts +2 -1
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +47 -17
- package/build/core/connection.js.map +1 -1
- package/build/core/executor.d.ts +13 -7
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +239 -47
- package/build/core/executor.js.map +1 -1
- package/build/core/httpServer.js +9 -9
- package/build/core/httpServer.js.map +1 -1
- package/build/core/index.d.ts +3 -3
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +3 -3
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +3 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +12 -3
- package/build/core/ios.js.map +1 -1
- package/build/core/metro.d.ts +1 -0
- package/build/core/metro.d.ts.map +1 -1
- package/build/core/metro.js +3 -0
- package/build/core/metro.js.map +1 -1
- package/build/core/state.d.ts +7 -3
- package/build/core/state.d.ts.map +1 -1
- package/build/core/state.js +42 -4
- package/build/core/state.js.map +1 -1
- package/build/index.js +194 -107
- package/build/index.js.map +1 -1
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +81 -10
- package/build/pro/tap.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getGuideOverview, getGuideByTopic, getAvailableTopics } from "./core/gu
|
|
|
8
8
|
import { getLicenseStatus, getDashboardUrl } from "./core/license.js";
|
|
9
9
|
import { isSDKInstalled, readSDKNetworkRequests, readSDKNetworkRequest, readSDKNetworkStats, clearSDKNetwork } from "./core/sdkBridge.js";
|
|
10
10
|
import { tap } from "./pro/tap.js";
|
|
11
|
-
import {
|
|
11
|
+
import { logBuffers, networkBuffers, getLogBuffer, getNetworkBuffer, getTotalLogCount, getConnectedAppByDevice, LogBuffer, NetworkBuffer, bundleErrorBuffer, connectedApps, scanMetroPorts, fetchDevices, selectMainDevice, filterBridgelessDevices, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp,
|
|
12
12
|
// React Component Inspection
|
|
13
13
|
getComponentTree, getScreenLayout, inspectComponent, findComponents, inspectAtPoint, toggleElementInspector, isInspectorActive, getInspectorSelection, getFirstConnectedApp, getLogs, searchLogs, getLogSummary, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
|
|
14
14
|
// Connection state
|
|
@@ -43,6 +43,42 @@ startDebugHttpServer, getDebugServerPort,
|
|
|
43
43
|
initTelemetry, trackToolInvocation, getTargetPlatform,
|
|
44
44
|
// Format utilities (TONL)
|
|
45
45
|
formatLogsAsTonl, formatNetworkAsTonl } from "./core/index.js";
|
|
46
|
+
// Helper: resolve log buffer for a device (or create a merged buffer from all devices)
|
|
47
|
+
function resolveLogBuffer(device) {
|
|
48
|
+
if (device) {
|
|
49
|
+
const app = getConnectedAppByDevice(device);
|
|
50
|
+
if (!app)
|
|
51
|
+
throw new Error(`No connected device matches "${device}"`);
|
|
52
|
+
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
53
|
+
return getLogBuffer(deviceName);
|
|
54
|
+
}
|
|
55
|
+
// Merge all logs into a temporary buffer for read operations
|
|
56
|
+
const merged = new LogBuffer(5000);
|
|
57
|
+
for (const buffer of logBuffers.values()) {
|
|
58
|
+
for (const entry of buffer.getAll()) {
|
|
59
|
+
merged.add(entry);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
63
|
+
}
|
|
64
|
+
// Helper: resolve network buffer for a device (or create a merged buffer from all devices)
|
|
65
|
+
function resolveNetworkBuffer(device) {
|
|
66
|
+
if (device) {
|
|
67
|
+
const app = getConnectedAppByDevice(device);
|
|
68
|
+
if (!app)
|
|
69
|
+
throw new Error(`No connected device matches "${device}"`);
|
|
70
|
+
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
71
|
+
return getNetworkBuffer(deviceName);
|
|
72
|
+
}
|
|
73
|
+
// Merge all network requests into a temporary buffer for read operations
|
|
74
|
+
const merged = new NetworkBuffer(5000);
|
|
75
|
+
for (const buffer of networkBuffers.values()) {
|
|
76
|
+
for (const req of buffer.getAll({})) {
|
|
77
|
+
merged.set(`${Math.random()}`, req);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return merged;
|
|
81
|
+
}
|
|
46
82
|
// Create MCP server
|
|
47
83
|
const server = new McpServer({
|
|
48
84
|
name: "react-native-ai-debugger",
|
|
@@ -239,25 +275,29 @@ registerToolWithTelemetry("scan_metro", {
|
|
|
239
275
|
results.push(`Port ${port}: No devices found`);
|
|
240
276
|
continue;
|
|
241
277
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
278
|
+
const bridgelessDevices = filterBridgelessDevices(devices);
|
|
279
|
+
if (bridgelessDevices.length === 0) {
|
|
280
|
+
results.push(`Port ${port}: No debuggable devices found`);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
results.push(`Port ${port}: Found ${bridgelessDevices.length} device(s)`);
|
|
284
|
+
for (const device of bridgelessDevices) {
|
|
245
285
|
try {
|
|
246
|
-
const connectionResult = await connectToDevice(
|
|
286
|
+
const connectionResult = await connectToDevice(device, port);
|
|
247
287
|
results.push(` - ${connectionResult}`);
|
|
248
|
-
// Also connect to Metro build events for this port
|
|
249
|
-
try {
|
|
250
|
-
await connectMetroBuildEvents(port);
|
|
251
|
-
results.push(` - Connected to Metro build events`);
|
|
252
|
-
}
|
|
253
|
-
catch {
|
|
254
|
-
// Build events connection is optional, don't fail the scan
|
|
255
|
-
}
|
|
256
288
|
}
|
|
257
289
|
catch (error) {
|
|
258
|
-
results.push(` -
|
|
290
|
+
results.push(` - ${device.deviceName || device.title}: Failed - ${error}`);
|
|
259
291
|
}
|
|
260
292
|
}
|
|
293
|
+
// Connect to Metro build events for this port
|
|
294
|
+
try {
|
|
295
|
+
await connectMetroBuildEvents(port);
|
|
296
|
+
results.push(` - Connected to Metro build events`);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Build events connection is optional
|
|
300
|
+
}
|
|
261
301
|
}
|
|
262
302
|
return {
|
|
263
303
|
content: [
|
|
@@ -273,33 +313,34 @@ registerToolWithTelemetry("get_apps", {
|
|
|
273
313
|
description: "List currently connected React Native apps and their connection status. If no apps are connected, run scan_metro first to establish a connection.",
|
|
274
314
|
inputSchema: {}
|
|
275
315
|
}, async () => {
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
316
|
+
const apps = getConnectedApps();
|
|
317
|
+
if (apps.length === 0) {
|
|
278
318
|
return {
|
|
279
319
|
content: [
|
|
280
320
|
{
|
|
281
321
|
type: "text",
|
|
282
|
-
text:
|
|
322
|
+
text: "No connected devices. Run scan_metro to discover and connect to Metro servers."
|
|
283
323
|
}
|
|
284
324
|
]
|
|
285
325
|
};
|
|
286
326
|
}
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
327
|
+
const deviceLines = apps
|
|
328
|
+
.filter(({ isConnected }) => isConnected)
|
|
329
|
+
.map(({ app }, i) => {
|
|
330
|
+
const name = app.deviceInfo.deviceName || app.deviceInfo.title;
|
|
331
|
+
const appId = app.deviceInfo.appId || app.deviceInfo.title.split(" (")[0] || "unknown";
|
|
332
|
+
return ` ${i + 1}. ${name} — ${appId} (${app.platform}, port ${app.port})`;
|
|
290
333
|
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
334
|
+
const text = [
|
|
335
|
+
`Connected devices:`,
|
|
336
|
+
...deviceLines,
|
|
337
|
+
``,
|
|
338
|
+
`Use device="${apps[0].app.deviceInfo.deviceName}" to target a specific device.`,
|
|
339
|
+
``,
|
|
340
|
+
`Total logs in buffer: ${getTotalLogCount()}`
|
|
341
|
+
].join("\n");
|
|
296
342
|
return {
|
|
297
|
-
content: [
|
|
298
|
-
{
|
|
299
|
-
type: "text",
|
|
300
|
-
text: `Connected apps:\n${status.join("\n")}${simulatorInfo}\n\nTotal logs in buffer: ${logBuffer.size}`
|
|
301
|
-
}
|
|
302
|
-
]
|
|
343
|
+
content: [{ type: "text", text }]
|
|
303
344
|
};
|
|
304
345
|
});
|
|
305
346
|
// Tool: Get connection status (detailed health and gap tracking)
|
|
@@ -492,14 +533,15 @@ registerToolWithTelemetry("get_logs", {
|
|
|
492
533
|
.boolean()
|
|
493
534
|
.optional()
|
|
494
535
|
.default(false)
|
|
495
|
-
.describe("Return summary statistics instead of full logs (count by level + last 5 messages). Use for quick overview.")
|
|
536
|
+
.describe("Return summary statistics instead of full logs (count by level + last 5 messages). Use for quick overview."),
|
|
537
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
496
538
|
}
|
|
497
|
-
}, async ({ maxLogs, level, startFromText, maxMessageLength, verbose, format, summary }) => {
|
|
539
|
+
}, async ({ maxLogs, level, startFromText, maxMessageLength, verbose, format, summary, device }) => {
|
|
498
540
|
// Return summary if requested
|
|
499
541
|
if (summary) {
|
|
500
|
-
const summaryText = getLogSummary(
|
|
542
|
+
const summaryText = getLogSummary(resolveLogBuffer(device), { lastN: 5, maxMessageLength: 100 });
|
|
501
543
|
let connectionWarning = "";
|
|
502
|
-
if (
|
|
544
|
+
if (getTotalLogCount() === 0) {
|
|
503
545
|
const status = await checkAndEnsureConnection();
|
|
504
546
|
connectionWarning = status.message ? `\n\n${status.message}` : "";
|
|
505
547
|
}
|
|
@@ -512,7 +554,7 @@ registerToolWithTelemetry("get_logs", {
|
|
|
512
554
|
]
|
|
513
555
|
};
|
|
514
556
|
}
|
|
515
|
-
const { logs, count, formatted } = getLogs(
|
|
557
|
+
const { logs, count, formatted } = getLogs(resolveLogBuffer(device), {
|
|
516
558
|
maxLogs,
|
|
517
559
|
level,
|
|
518
560
|
startFromText,
|
|
@@ -569,7 +611,7 @@ registerToolWithTelemetry("get_logs", {
|
|
|
569
611
|
};
|
|
570
612
|
},
|
|
571
613
|
// Empty result detector: buffer has no entries at all
|
|
572
|
-
() =>
|
|
614
|
+
() => getTotalLogCount() === 0);
|
|
573
615
|
// Tool: Search logs
|
|
574
616
|
registerToolWithTelemetry("search_logs", {
|
|
575
617
|
description: "Search console logs for text (case-insensitive)",
|
|
@@ -590,10 +632,11 @@ registerToolWithTelemetry("search_logs", {
|
|
|
590
632
|
.enum(["text", "tonl"])
|
|
591
633
|
.optional()
|
|
592
634
|
.default("tonl")
|
|
593
|
-
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format)")
|
|
635
|
+
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format)"),
|
|
636
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
594
637
|
}
|
|
595
|
-
}, async ({ text, maxResults, maxMessageLength, verbose, format }) => {
|
|
596
|
-
const { logs, count, formatted } = searchLogs(
|
|
638
|
+
}, async ({ text, maxResults, maxMessageLength, verbose, format, device }) => {
|
|
639
|
+
const { logs, count, formatted } = searchLogs(resolveLogBuffer(device), text, { maxResults, maxMessageLength, verbose });
|
|
597
640
|
// Check connection health
|
|
598
641
|
let connectionWarning = "";
|
|
599
642
|
if (count === 0) {
|
|
@@ -630,17 +673,24 @@ registerToolWithTelemetry("search_logs", {
|
|
|
630
673
|
// Tool: Clear logs
|
|
631
674
|
registerToolWithTelemetry("clear_logs", {
|
|
632
675
|
description: "Clear the log buffer",
|
|
633
|
-
inputSchema: {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
676
|
+
inputSchema: {
|
|
677
|
+
device: z.string().optional().describe("Target device name (substring match). Omit to clear all devices. Run get_apps to see connected devices.")
|
|
678
|
+
}
|
|
679
|
+
}, async ({ device }) => {
|
|
680
|
+
if (device) {
|
|
681
|
+
const app = getConnectedAppByDevice(device);
|
|
682
|
+
if (!app)
|
|
683
|
+
throw new Error(`No connected device matches "${device}"`);
|
|
684
|
+
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
685
|
+
const count = getLogBuffer(deviceName).clear();
|
|
686
|
+
return { content: [{ type: "text", text: `Cleared ${count} log entries from ${deviceName}.` }] };
|
|
687
|
+
}
|
|
688
|
+
// Clear all
|
|
689
|
+
let total = 0;
|
|
690
|
+
for (const buffer of logBuffers.values()) {
|
|
691
|
+
total += buffer.clear();
|
|
692
|
+
}
|
|
693
|
+
return { content: [{ type: "text", text: `Cleared ${total} log entries from all devices.` }] };
|
|
644
694
|
});
|
|
645
695
|
// Tool: Connect to specific Metro port
|
|
646
696
|
registerToolWithTelemetry("connect_metro", {
|
|
@@ -776,10 +826,11 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
776
826
|
.boolean()
|
|
777
827
|
.optional()
|
|
778
828
|
.default(false)
|
|
779
|
-
.describe("Disable result truncation. Tip: Be cautious - Redux stores or large state can return 10KB+.")
|
|
829
|
+
.describe("Disable result truncation. Tip: Be cautious - Redux stores or large state can return 10KB+."),
|
|
830
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
780
831
|
}
|
|
781
|
-
}, async ({ expression, awaitPromise, maxResultLength, verbose }) => {
|
|
782
|
-
const result = await executeInApp(expression, awaitPromise);
|
|
832
|
+
}, async ({ expression, awaitPromise, maxResultLength, verbose, device }) => {
|
|
833
|
+
const result = await executeInApp(expression, awaitPromise, {}, device);
|
|
783
834
|
if (!result.success) {
|
|
784
835
|
let errorText = `Error: ${result.error}`;
|
|
785
836
|
// If the error is a ReferenceError (accessing a global that doesn't exist),
|
|
@@ -825,9 +876,11 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
825
876
|
// Tool: List debug globals available in the app
|
|
826
877
|
registerToolWithTelemetry("list_debug_globals", {
|
|
827
878
|
description: "List globally available debugging objects in the connected React Native app (Apollo Client, Redux store, React DevTools, etc.). Use this to discover what state management and debugging tools are available.",
|
|
828
|
-
inputSchema: {
|
|
829
|
-
|
|
830
|
-
|
|
879
|
+
inputSchema: {
|
|
880
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
881
|
+
}
|
|
882
|
+
}, async ({ device }) => {
|
|
883
|
+
const result = await listDebugGlobals(device);
|
|
831
884
|
if (!result.success) {
|
|
832
885
|
return {
|
|
833
886
|
content: [
|
|
@@ -854,10 +907,11 @@ registerToolWithTelemetry("inspect_global", {
|
|
|
854
907
|
inputSchema: {
|
|
855
908
|
objectName: z
|
|
856
909
|
.string()
|
|
857
|
-
.describe("Name of the global object to inspect (e.g., '__EXPO_ROUTER__', '__APOLLO_CLIENT__')")
|
|
910
|
+
.describe("Name of the global object to inspect (e.g., '__EXPO_ROUTER__', '__APOLLO_CLIENT__')"),
|
|
911
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
858
912
|
}
|
|
859
|
-
}, async ({ objectName }) => {
|
|
860
|
-
const result = await inspectGlobal(objectName);
|
|
913
|
+
}, async ({ objectName, device }) => {
|
|
914
|
+
const result = await inspectGlobal(objectName, device);
|
|
861
915
|
if (!result.success) {
|
|
862
916
|
let errorText = `Error: ${result.error}`;
|
|
863
917
|
// If the error is a ReferenceError (accessing a global that doesn't exist),
|
|
@@ -924,9 +978,10 @@ registerToolWithTelemetry("get_component_tree", {
|
|
|
924
978
|
.enum(["json", "tonl"])
|
|
925
979
|
.optional()
|
|
926
980
|
.default("tonl")
|
|
927
|
-
.describe("Output format: 'json' or 'tonl' (default, compact indented tree). Ignored if structureOnly=true.")
|
|
981
|
+
.describe("Output format: 'json' or 'tonl' (default, compact indented tree). Ignored if structureOnly=true."),
|
|
982
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
928
983
|
}
|
|
929
|
-
}, async ({ focusedOnly, structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format }) => {
|
|
984
|
+
}, async ({ focusedOnly, structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format, device }) => {
|
|
930
985
|
const result = await getComponentTree({
|
|
931
986
|
focusedOnly,
|
|
932
987
|
structureOnly,
|
|
@@ -934,7 +989,8 @@ registerToolWithTelemetry("get_component_tree", {
|
|
|
934
989
|
includeProps,
|
|
935
990
|
includeStyles,
|
|
936
991
|
hideInternals,
|
|
937
|
-
format
|
|
992
|
+
format,
|
|
993
|
+
device
|
|
938
994
|
});
|
|
939
995
|
if (!result.success) {
|
|
940
996
|
return {
|
|
@@ -984,10 +1040,11 @@ registerToolWithTelemetry("get_screen_layout", {
|
|
|
984
1040
|
.enum(["json", "tonl"])
|
|
985
1041
|
.optional()
|
|
986
1042
|
.default("tonl")
|
|
987
|
-
.describe("Output format: 'json' or 'tonl' (default, pipe-delimited rows, ~40% smaller)")
|
|
1043
|
+
.describe("Output format: 'json' or 'tonl' (default, pipe-delimited rows, ~40% smaller)"),
|
|
1044
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
988
1045
|
}
|
|
989
|
-
}, async ({ maxDepth, componentsOnly, shortPath, summary, format }) => {
|
|
990
|
-
const result = await getScreenLayout({ maxDepth, componentsOnly, shortPath, summary, format });
|
|
1046
|
+
}, async ({ maxDepth, componentsOnly, shortPath, summary, format, device }) => {
|
|
1047
|
+
const result = await getScreenLayout({ maxDepth, componentsOnly, shortPath, summary, format, device });
|
|
991
1048
|
if (!result.success) {
|
|
992
1049
|
return {
|
|
993
1050
|
content: [
|
|
@@ -1036,16 +1093,18 @@ registerToolWithTelemetry("inspect_component", {
|
|
|
1036
1093
|
.boolean()
|
|
1037
1094
|
.optional()
|
|
1038
1095
|
.default(true)
|
|
1039
|
-
.describe("Simplify hooks output by hiding effects and reducing depth (default: true)")
|
|
1096
|
+
.describe("Simplify hooks output by hiding effects and reducing depth (default: true)"),
|
|
1097
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1040
1098
|
}
|
|
1041
|
-
}, async ({ componentName, index, includeState, includeChildren, childrenDepth, shortPath, simplifyHooks }) => {
|
|
1099
|
+
}, async ({ componentName, index, includeState, includeChildren, childrenDepth, shortPath, simplifyHooks, device }) => {
|
|
1042
1100
|
const result = await inspectComponent(componentName, {
|
|
1043
1101
|
index,
|
|
1044
1102
|
includeState,
|
|
1045
1103
|
includeChildren,
|
|
1046
1104
|
childrenDepth,
|
|
1047
1105
|
shortPath,
|
|
1048
|
-
simplifyHooks
|
|
1106
|
+
simplifyHooks,
|
|
1107
|
+
device
|
|
1049
1108
|
});
|
|
1050
1109
|
if (!result.success) {
|
|
1051
1110
|
return {
|
|
@@ -1090,10 +1149,11 @@ registerToolWithTelemetry("find_components", {
|
|
|
1090
1149
|
.enum(["json", "tonl"])
|
|
1091
1150
|
.optional()
|
|
1092
1151
|
.default("tonl")
|
|
1093
|
-
.describe("Output format: 'json' or 'tonl' (default, pipe-delimited rows, ~40% smaller)")
|
|
1152
|
+
.describe("Output format: 'json' or 'tonl' (default, pipe-delimited rows, ~40% smaller)"),
|
|
1153
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1094
1154
|
}
|
|
1095
|
-
}, async ({ pattern, maxResults, includeLayout, shortPath, summary, format }) => {
|
|
1096
|
-
const result = await findComponents(pattern, { maxResults, includeLayout, shortPath, summary, format });
|
|
1155
|
+
}, async ({ pattern, maxResults, includeLayout, shortPath, summary, format, device }) => {
|
|
1156
|
+
const result = await findComponents(pattern, { maxResults, includeLayout, shortPath, summary, format, device });
|
|
1097
1157
|
if (!result.success) {
|
|
1098
1158
|
return {
|
|
1099
1159
|
content: [
|
|
@@ -1194,9 +1254,11 @@ registerToolWithTelemetry("tap", {
|
|
|
1194
1254
|
// Tool: Toggle Element Inspector programmatically
|
|
1195
1255
|
registerToolWithTelemetry("toggle_element_inspector", {
|
|
1196
1256
|
description: "Toggle React Native's Element Inspector overlay on/off. Rarely needed directly — get_inspector_selection auto-enables the inspector when called with coordinates. Use this only when you need manual control over the overlay visibility.",
|
|
1197
|
-
inputSchema: {
|
|
1198
|
-
|
|
1199
|
-
|
|
1257
|
+
inputSchema: {
|
|
1258
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1259
|
+
}
|
|
1260
|
+
}, async ({ device }) => {
|
|
1261
|
+
const result = await toggleElementInspector(device);
|
|
1200
1262
|
if (!result.success) {
|
|
1201
1263
|
return {
|
|
1202
1264
|
content: [
|
|
@@ -1252,21 +1314,22 @@ registerToolWithTelemetry("get_inspector_selection", {
|
|
|
1252
1314
|
y: z
|
|
1253
1315
|
.number()
|
|
1254
1316
|
.optional()
|
|
1255
|
-
.describe("Y coordinate (in points). If provided with x, auto-taps at this location.")
|
|
1317
|
+
.describe("Y coordinate (in points). If provided with x, auto-taps at this location."),
|
|
1318
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1256
1319
|
}
|
|
1257
|
-
}, async ({ x, y }) => {
|
|
1320
|
+
}, async ({ x, y, device }) => {
|
|
1258
1321
|
// If coordinates provided, do the full flow: enable inspector -> tap -> read
|
|
1259
1322
|
if (x !== undefined && y !== undefined) {
|
|
1260
1323
|
// Check if inspector is active
|
|
1261
|
-
const inspectorActive = await isInspectorActive();
|
|
1324
|
+
const inspectorActive = await isInspectorActive(device);
|
|
1262
1325
|
// Enable inspector if not active
|
|
1263
1326
|
if (!inspectorActive) {
|
|
1264
|
-
await toggleElementInspector();
|
|
1327
|
+
await toggleElementInspector(device);
|
|
1265
1328
|
// Wait for inspector to initialize
|
|
1266
1329
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1267
1330
|
}
|
|
1268
1331
|
// Detect platform from connected app
|
|
1269
|
-
const app = getFirstConnectedApp();
|
|
1332
|
+
const app = device ? getConnectedAppByDevice(device) : getFirstConnectedApp();
|
|
1270
1333
|
if (!app) {
|
|
1271
1334
|
return {
|
|
1272
1335
|
content: [{ type: "text", text: "No app connected. Run scan_metro first." }],
|
|
@@ -1301,7 +1364,7 @@ registerToolWithTelemetry("get_inspector_selection", {
|
|
|
1301
1364
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1302
1365
|
}
|
|
1303
1366
|
// Read the current selection
|
|
1304
|
-
const result = await getInspectorSelection();
|
|
1367
|
+
const result = await getInspectorSelection(device);
|
|
1305
1368
|
if (!result.success) {
|
|
1306
1369
|
return {
|
|
1307
1370
|
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
@@ -1355,10 +1418,11 @@ registerToolWithTelemetry("inspect_at_point", {
|
|
|
1355
1418
|
.boolean()
|
|
1356
1419
|
.optional()
|
|
1357
1420
|
.default(true)
|
|
1358
|
-
.describe("Include position/dimensions (frame) in the output (default: true)")
|
|
1421
|
+
.describe("Include position/dimensions (frame) in the output (default: true)"),
|
|
1422
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1359
1423
|
}
|
|
1360
|
-
}, async ({ x, y, includeProps, includeFrame }) => {
|
|
1361
|
-
const result = await inspectAtPoint(x, y, { includeProps, includeFrame });
|
|
1424
|
+
}, async ({ x, y, includeProps, includeFrame, device }) => {
|
|
1425
|
+
const result = await inspectAtPoint(x, y, { includeProps, includeFrame, device });
|
|
1362
1426
|
if (!result.success) {
|
|
1363
1427
|
return {
|
|
1364
1428
|
content: [
|
|
@@ -1422,9 +1486,10 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1422
1486
|
.boolean()
|
|
1423
1487
|
.optional()
|
|
1424
1488
|
.default(false)
|
|
1425
|
-
.describe("Return statistics only (count, methods, domains, status codes). Use for quick overview.")
|
|
1489
|
+
.describe("Return statistics only (count, methods, domains, status codes). Use for quick overview."),
|
|
1490
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
1426
1491
|
}
|
|
1427
|
-
}, async ({ maxRequests, method, urlPattern, status, format, summary }) => {
|
|
1492
|
+
}, async ({ maxRequests, method, urlPattern, status, format, summary, device }) => {
|
|
1428
1493
|
// Check if SDK is installed — prefer SDK data over CDP/interceptor buffer
|
|
1429
1494
|
const sdkAvailable = await isSDKInstalled();
|
|
1430
1495
|
if (sdkAvailable) {
|
|
@@ -1474,9 +1539,9 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1474
1539
|
// Fallback: read from in-process buffer (CDP/interceptor)
|
|
1475
1540
|
// Return summary if requested
|
|
1476
1541
|
if (summary) {
|
|
1477
|
-
const stats = getNetworkStats(
|
|
1542
|
+
const stats = getNetworkStats(resolveNetworkBuffer(device));
|
|
1478
1543
|
let connectionWarning = "";
|
|
1479
|
-
if (
|
|
1544
|
+
if (resolveNetworkBuffer(device).size === 0) {
|
|
1480
1545
|
const connStatus = await checkAndEnsureConnection();
|
|
1481
1546
|
connectionWarning = connStatus.message ? `\n\n${connStatus.message}` : "";
|
|
1482
1547
|
if (!sdkAvailable) {
|
|
@@ -1492,7 +1557,7 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1492
1557
|
]
|
|
1493
1558
|
};
|
|
1494
1559
|
}
|
|
1495
|
-
const { requests, count, formatted } = getNetworkRequests(
|
|
1560
|
+
const { requests, count, formatted } = getNetworkRequests(resolveNetworkBuffer(device), {
|
|
1496
1561
|
maxRequests,
|
|
1497
1562
|
method,
|
|
1498
1563
|
urlPattern,
|
|
@@ -1550,7 +1615,8 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1550
1615
|
};
|
|
1551
1616
|
},
|
|
1552
1617
|
// Empty result detector: buffer has no entries at all
|
|
1553
|
-
() =>
|
|
1618
|
+
() => { let total = 0; for (const b of networkBuffers.values())
|
|
1619
|
+
total += b.size; return total === 0; });
|
|
1554
1620
|
// Tool: Search network requests
|
|
1555
1621
|
registerToolWithTelemetry("search_network", {
|
|
1556
1622
|
description: "Search network requests by URL pattern (case-insensitive)",
|
|
@@ -1561,10 +1627,11 @@ registerToolWithTelemetry("search_network", {
|
|
|
1561
1627
|
.enum(["text", "tonl"])
|
|
1562
1628
|
.optional()
|
|
1563
1629
|
.default("tonl")
|
|
1564
|
-
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format)")
|
|
1630
|
+
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format)"),
|
|
1631
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
1565
1632
|
}
|
|
1566
|
-
}, async ({ urlPattern, maxResults, format }) => {
|
|
1567
|
-
const { requests, count, formatted } = searchNetworkRequests(
|
|
1633
|
+
}, async ({ urlPattern, maxResults, format, device }) => {
|
|
1634
|
+
const { requests, count, formatted } = searchNetworkRequests(resolveNetworkBuffer(device), urlPattern, maxResults);
|
|
1568
1635
|
// Check connection health
|
|
1569
1636
|
let connectionWarning = "";
|
|
1570
1637
|
if (count === 0) {
|
|
@@ -1612,9 +1679,10 @@ registerToolWithTelemetry("get_request_details", {
|
|
|
1612
1679
|
.boolean()
|
|
1613
1680
|
.optional()
|
|
1614
1681
|
.default(false)
|
|
1615
|
-
.describe("Disable body truncation. Tip: Use when you need to inspect full JSON payloads.")
|
|
1682
|
+
.describe("Disable body truncation. Tip: Use when you need to inspect full JSON payloads."),
|
|
1683
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
1616
1684
|
}
|
|
1617
|
-
}, async ({ requestId, maxBodyLength, verbose }) => {
|
|
1685
|
+
}, async ({ requestId, maxBodyLength, verbose, device }) => {
|
|
1618
1686
|
// Check SDK first — it has full headers and body
|
|
1619
1687
|
const sdkAvailable = await isSDKInstalled();
|
|
1620
1688
|
if (sdkAvailable) {
|
|
@@ -1662,7 +1730,7 @@ registerToolWithTelemetry("get_request_details", {
|
|
|
1662
1730
|
}
|
|
1663
1731
|
}
|
|
1664
1732
|
// Fallback: read from in-process buffer
|
|
1665
|
-
const request =
|
|
1733
|
+
const request = resolveNetworkBuffer(device).get(requestId);
|
|
1666
1734
|
if (!request) {
|
|
1667
1735
|
const status = await checkAndEnsureConnection();
|
|
1668
1736
|
const connectionNote = status.message ? `\n\n${status.message}` : "";
|
|
@@ -1688,12 +1756,14 @@ registerToolWithTelemetry("get_request_details", {
|
|
|
1688
1756
|
// Tool: Get network stats
|
|
1689
1757
|
registerToolWithTelemetry("get_network_stats", {
|
|
1690
1758
|
description: "Get statistics about captured network requests: counts by method, status code, and domain.",
|
|
1691
|
-
inputSchema: {
|
|
1692
|
-
|
|
1693
|
-
|
|
1759
|
+
inputSchema: {
|
|
1760
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
1761
|
+
}
|
|
1762
|
+
}, async ({ device }) => {
|
|
1763
|
+
const stats = getNetworkStats(resolveNetworkBuffer(device));
|
|
1694
1764
|
// Check connection health
|
|
1695
1765
|
let connectionWarning = "";
|
|
1696
|
-
if (
|
|
1766
|
+
if (resolveNetworkBuffer(device).size === 0) {
|
|
1697
1767
|
const status = await checkAndEnsureConnection();
|
|
1698
1768
|
connectionWarning = status.message ? `\n\n${status.message}` : "";
|
|
1699
1769
|
}
|
|
@@ -1713,13 +1783,28 @@ registerToolWithTelemetry("get_network_stats", {
|
|
|
1713
1783
|
};
|
|
1714
1784
|
},
|
|
1715
1785
|
// Empty result detector: buffer has no entries at all
|
|
1716
|
-
() =>
|
|
1786
|
+
() => { let total = 0; for (const b of networkBuffers.values())
|
|
1787
|
+
total += b.size; return total === 0; });
|
|
1717
1788
|
// Tool: Clear network requests
|
|
1718
1789
|
registerToolWithTelemetry("clear_network", {
|
|
1719
1790
|
description: "Clear the network request buffer",
|
|
1720
|
-
inputSchema: {
|
|
1721
|
-
|
|
1722
|
-
|
|
1791
|
+
inputSchema: {
|
|
1792
|
+
device: z.string().optional().describe("Target device name (substring match). Omit to clear all devices. Run get_apps to see connected devices.")
|
|
1793
|
+
}
|
|
1794
|
+
}, async ({ device }) => {
|
|
1795
|
+
let totalCleared = 0;
|
|
1796
|
+
if (device) {
|
|
1797
|
+
const app = getConnectedAppByDevice(device);
|
|
1798
|
+
if (!app)
|
|
1799
|
+
throw new Error(`No connected device matches "${device}"`);
|
|
1800
|
+
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
1801
|
+
totalCleared = getNetworkBuffer(deviceName).clear();
|
|
1802
|
+
}
|
|
1803
|
+
else {
|
|
1804
|
+
for (const buffer of networkBuffers.values()) {
|
|
1805
|
+
totalCleared += buffer.clear();
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1723
1808
|
// Also clear SDK buffer if available
|
|
1724
1809
|
const sdkAvailable = await isSDKInstalled();
|
|
1725
1810
|
if (sdkAvailable) {
|
|
@@ -1740,9 +1825,11 @@ registerToolWithTelemetry("clear_network", {
|
|
|
1740
1825
|
// Tool: Reload the app
|
|
1741
1826
|
registerToolWithTelemetry("reload_app", {
|
|
1742
1827
|
description: "Reload the React Native app (triggers JavaScript bundle reload like pressing 'r' in Metro). Will auto-connect to Metro if no connection exists. Note: After reload, the app may take a few seconds to fully restart and become responsive — wait before running other tools. IMPORTANT: React Native has Fast Refresh enabled by default - code changes are automatically applied without needing reload. Only use when: (1) logs/behavior don't reflect code changes after a few seconds, (2) app is in broken/error state, or (3) need to reset app state completely (navigation stack, context, etc.).",
|
|
1743
|
-
inputSchema: {
|
|
1744
|
-
|
|
1745
|
-
|
|
1828
|
+
inputSchema: {
|
|
1829
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for default device. Run get_apps to see connected devices.")
|
|
1830
|
+
}
|
|
1831
|
+
}, async ({ device }) => {
|
|
1832
|
+
const result = await reloadApp(device);
|
|
1746
1833
|
if (!result.success) {
|
|
1747
1834
|
return {
|
|
1748
1835
|
content: [
|