react-native-ai-debugger 1.0.8 → 1.0.10
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 +31 -0
- package/build/core/android.d.ts +19 -0
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +98 -1
- package/build/core/android.js.map +1 -1
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +6 -1
- package/build/core/executor.js.map +1 -1
- package/build/core/httpServer.d.ts.map +1 -1
- package/build/core/httpServer.js +695 -5
- package/build/core/httpServer.js.map +1 -1
- package/build/core/index.d.ts +2 -1
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +3 -1
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +1 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +4 -2
- package/build/core/ios.js.map +1 -1
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +19 -8
- package/build/core/telemetry.js.map +1 -1
- package/build/index.js +152 -50
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -8,7 +8,7 @@ connectMetroBuildEvents, getBundleErrors, getBundleStatusWithErrors,
|
|
|
8
8
|
// Android
|
|
9
9
|
listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, androidListPackages,
|
|
10
10
|
// Android UI Input (Phase 2)
|
|
11
|
-
ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize,
|
|
11
|
+
ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize, androidGetDensity, androidGetStatusBarHeight,
|
|
12
12
|
// Android Accessibility (UI Hierarchy)
|
|
13
13
|
androidDescribeAll, androidDescribePoint, androidTapElement,
|
|
14
14
|
// Android Element Finding (no screenshots)
|
|
@@ -20,14 +20,43 @@ iosTap, iosTapElement, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySeq
|
|
|
20
20
|
// iOS Element Finding (no screenshots)
|
|
21
21
|
iosFindElement, iosWaitForElement,
|
|
22
22
|
// Debug HTTP Server
|
|
23
|
-
startDebugHttpServer, getDebugServerPort
|
|
23
|
+
startDebugHttpServer, getDebugServerPort,
|
|
24
|
+
// Telemetry
|
|
25
|
+
initTelemetry, trackToolInvocation } from "./core/index.js";
|
|
24
26
|
// Create MCP server
|
|
25
27
|
const server = new McpServer({
|
|
26
28
|
name: "react-native-ai-debugger",
|
|
27
29
|
version: "1.0.0"
|
|
28
30
|
});
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Telemetry Wrapper
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
35
|
+
function registerToolWithTelemetry(toolName, config, handler) {
|
|
36
|
+
server.registerTool(toolName, config, async (args) => {
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
let success = true;
|
|
39
|
+
try {
|
|
40
|
+
const result = await handler(args);
|
|
41
|
+
// Check if result indicates an error
|
|
42
|
+
if (result?.isError) {
|
|
43
|
+
success = false;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
success = false;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
const duration = Date.now() - startTime;
|
|
53
|
+
trackToolInvocation(toolName, success, duration);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
29
58
|
// Tool: Scan for Metro servers
|
|
30
|
-
|
|
59
|
+
registerToolWithTelemetry("scan_metro", {
|
|
31
60
|
description: "Scan for running Metro bundler servers on common ports",
|
|
32
61
|
inputSchema: {
|
|
33
62
|
startPort: z.number().optional().default(8081).describe("Start port for scanning (default: 8081)"),
|
|
@@ -83,7 +112,7 @@ server.registerTool("scan_metro", {
|
|
|
83
112
|
};
|
|
84
113
|
});
|
|
85
114
|
// Tool: Get connected apps
|
|
86
|
-
|
|
115
|
+
registerToolWithTelemetry("get_apps", {
|
|
87
116
|
description: "List connected React Native apps and Metro server status",
|
|
88
117
|
inputSchema: {}
|
|
89
118
|
}, async () => {
|
|
@@ -112,7 +141,7 @@ server.registerTool("get_apps", {
|
|
|
112
141
|
};
|
|
113
142
|
});
|
|
114
143
|
// Tool: Get console logs
|
|
115
|
-
|
|
144
|
+
registerToolWithTelemetry("get_logs", {
|
|
116
145
|
description: "Retrieve console logs from connected React Native app",
|
|
117
146
|
inputSchema: {
|
|
118
147
|
maxLogs: z.number().optional().default(50).describe("Maximum number of logs to return (default: 50)"),
|
|
@@ -136,7 +165,7 @@ server.registerTool("get_logs", {
|
|
|
136
165
|
};
|
|
137
166
|
});
|
|
138
167
|
// Tool: Search logs
|
|
139
|
-
|
|
168
|
+
registerToolWithTelemetry("search_logs", {
|
|
140
169
|
description: "Search console logs for text (case-insensitive)",
|
|
141
170
|
inputSchema: {
|
|
142
171
|
text: z.string().describe("Text to search for in log messages"),
|
|
@@ -154,7 +183,7 @@ server.registerTool("search_logs", {
|
|
|
154
183
|
};
|
|
155
184
|
});
|
|
156
185
|
// Tool: Clear logs
|
|
157
|
-
|
|
186
|
+
registerToolWithTelemetry("clear_logs", {
|
|
158
187
|
description: "Clear the log buffer",
|
|
159
188
|
inputSchema: {}
|
|
160
189
|
}, async () => {
|
|
@@ -169,7 +198,7 @@ server.registerTool("clear_logs", {
|
|
|
169
198
|
};
|
|
170
199
|
});
|
|
171
200
|
// Tool: Connect to specific Metro port
|
|
172
|
-
|
|
201
|
+
registerToolWithTelemetry("connect_metro", {
|
|
173
202
|
description: "Connect to a specific Metro server port",
|
|
174
203
|
inputSchema: {
|
|
175
204
|
port: z.number().default(8081).describe("Metro server port (default: 8081)")
|
|
@@ -226,8 +255,8 @@ server.registerTool("connect_metro", {
|
|
|
226
255
|
}
|
|
227
256
|
});
|
|
228
257
|
// Tool: Execute JavaScript in app
|
|
229
|
-
|
|
230
|
-
description: "Execute JavaScript code in the connected React Native app and return the result. Use this for REPL-style interactions, inspecting app state, or running diagnostic code.",
|
|
258
|
+
registerToolWithTelemetry("execute_in_app", {
|
|
259
|
+
description: "Execute JavaScript code in the connected React Native app and return the result. Use this for REPL-style interactions, inspecting app state, or running diagnostic code. Hermes compatible: 'global' is automatically polyfilled to 'globalThis', so both global.__REDUX_STORE__ and globalThis.__REDUX_STORE__ work.",
|
|
231
260
|
inputSchema: {
|
|
232
261
|
expression: z.string().describe("JavaScript expression to execute in the app"),
|
|
233
262
|
awaitPromise: z.boolean().optional().default(true).describe("Whether to await promises (default: true)")
|
|
@@ -255,7 +284,7 @@ server.registerTool("execute_in_app", {
|
|
|
255
284
|
};
|
|
256
285
|
});
|
|
257
286
|
// Tool: List debug globals available in the app
|
|
258
|
-
|
|
287
|
+
registerToolWithTelemetry("list_debug_globals", {
|
|
259
288
|
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.",
|
|
260
289
|
inputSchema: {}
|
|
261
290
|
}, async () => {
|
|
@@ -281,7 +310,7 @@ server.registerTool("list_debug_globals", {
|
|
|
281
310
|
};
|
|
282
311
|
});
|
|
283
312
|
// Tool: Inspect a global object to see its properties and types
|
|
284
|
-
|
|
313
|
+
registerToolWithTelemetry("inspect_global", {
|
|
285
314
|
description: "Inspect a global object to see its properties, types, and whether they are callable functions. Use this BEFORE calling methods on unfamiliar objects to avoid errors.",
|
|
286
315
|
inputSchema: {
|
|
287
316
|
objectName: z
|
|
@@ -311,7 +340,7 @@ server.registerTool("inspect_global", {
|
|
|
311
340
|
};
|
|
312
341
|
});
|
|
313
342
|
// Tool: Get network requests
|
|
314
|
-
|
|
343
|
+
registerToolWithTelemetry("get_network_requests", {
|
|
315
344
|
description: "Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing.",
|
|
316
345
|
inputSchema: {
|
|
317
346
|
maxRequests: z
|
|
@@ -349,7 +378,7 @@ server.registerTool("get_network_requests", {
|
|
|
349
378
|
};
|
|
350
379
|
});
|
|
351
380
|
// Tool: Search network requests
|
|
352
|
-
|
|
381
|
+
registerToolWithTelemetry("search_network", {
|
|
353
382
|
description: "Search network requests by URL pattern (case-insensitive)",
|
|
354
383
|
inputSchema: {
|
|
355
384
|
urlPattern: z.string().describe("URL pattern to search for"),
|
|
@@ -371,7 +400,7 @@ server.registerTool("search_network", {
|
|
|
371
400
|
};
|
|
372
401
|
});
|
|
373
402
|
// Tool: Get request details
|
|
374
|
-
|
|
403
|
+
registerToolWithTelemetry("get_request_details", {
|
|
375
404
|
description: "Get full details of a specific network request including headers, body, and timing. Use get_network_requests first to find the request ID.",
|
|
376
405
|
inputSchema: {
|
|
377
406
|
requestId: z.string().describe("The request ID to get details for")
|
|
@@ -399,7 +428,7 @@ server.registerTool("get_request_details", {
|
|
|
399
428
|
};
|
|
400
429
|
});
|
|
401
430
|
// Tool: Get network stats
|
|
402
|
-
|
|
431
|
+
registerToolWithTelemetry("get_network_stats", {
|
|
403
432
|
description: "Get statistics about captured network requests: counts by method, status code, and domain.",
|
|
404
433
|
inputSchema: {}
|
|
405
434
|
}, async () => {
|
|
@@ -414,7 +443,7 @@ server.registerTool("get_network_stats", {
|
|
|
414
443
|
};
|
|
415
444
|
});
|
|
416
445
|
// Tool: Clear network requests
|
|
417
|
-
|
|
446
|
+
registerToolWithTelemetry("clear_network", {
|
|
418
447
|
description: "Clear the network request buffer",
|
|
419
448
|
inputSchema: {}
|
|
420
449
|
}, async () => {
|
|
@@ -429,7 +458,7 @@ server.registerTool("clear_network", {
|
|
|
429
458
|
};
|
|
430
459
|
});
|
|
431
460
|
// Tool: Reload the app
|
|
432
|
-
|
|
461
|
+
registerToolWithTelemetry("reload_app", {
|
|
433
462
|
description: "Reload the connected React Native app. Triggers a JavaScript bundle reload (like pressing 'r' in Metro or shaking the device).",
|
|
434
463
|
inputSchema: {}
|
|
435
464
|
}, async () => {
|
|
@@ -458,7 +487,7 @@ server.registerTool("reload_app", {
|
|
|
458
487
|
// Bundle/Build Error Tools
|
|
459
488
|
// ============================================================================
|
|
460
489
|
// Tool: Get bundle status
|
|
461
|
-
|
|
490
|
+
registerToolWithTelemetry("get_bundle_status", {
|
|
462
491
|
description: "Get the current Metro bundler status including build state and any recent bundling errors. Use this to check if there are compilation/bundling errors that prevent the app from loading.",
|
|
463
492
|
inputSchema: {}
|
|
464
493
|
}, async () => {
|
|
@@ -473,7 +502,7 @@ server.registerTool("get_bundle_status", {
|
|
|
473
502
|
};
|
|
474
503
|
});
|
|
475
504
|
// Tool: Get bundle errors
|
|
476
|
-
|
|
505
|
+
registerToolWithTelemetry("get_bundle_errors", {
|
|
477
506
|
description: "Retrieve captured Metro bundling/compilation errors. These are errors that occur during the bundle build process (import resolution, syntax errors, transform errors) that prevent the app from loading.",
|
|
478
507
|
inputSchema: {
|
|
479
508
|
maxErrors: z
|
|
@@ -494,7 +523,7 @@ server.registerTool("get_bundle_errors", {
|
|
|
494
523
|
};
|
|
495
524
|
});
|
|
496
525
|
// Tool: Clear bundle errors
|
|
497
|
-
|
|
526
|
+
registerToolWithTelemetry("clear_bundle_errors", {
|
|
498
527
|
description: "Clear the bundle error buffer",
|
|
499
528
|
inputSchema: {}
|
|
500
529
|
}, async () => {
|
|
@@ -512,7 +541,7 @@ server.registerTool("clear_bundle_errors", {
|
|
|
512
541
|
// Android Tools
|
|
513
542
|
// ============================================================================
|
|
514
543
|
// Tool: List Android devices
|
|
515
|
-
|
|
544
|
+
registerToolWithTelemetry("list_android_devices", {
|
|
516
545
|
description: "List connected Android devices and emulators via ADB",
|
|
517
546
|
inputSchema: {}
|
|
518
547
|
}, async () => {
|
|
@@ -528,7 +557,7 @@ server.registerTool("list_android_devices", {
|
|
|
528
557
|
};
|
|
529
558
|
});
|
|
530
559
|
// Tool: Android screenshot
|
|
531
|
-
|
|
560
|
+
registerToolWithTelemetry("android_screenshot", {
|
|
532
561
|
description: "Take a screenshot from an Android device/emulator. Returns the image data that can be displayed.",
|
|
533
562
|
inputSchema: {
|
|
534
563
|
outputPath: z
|
|
@@ -555,12 +584,38 @@ server.registerTool("android_screenshot", {
|
|
|
555
584
|
}
|
|
556
585
|
// Include image data if available
|
|
557
586
|
if (result.data) {
|
|
558
|
-
// Build info text with
|
|
559
|
-
|
|
587
|
+
// Build info text with coordinate conversion guidance
|
|
588
|
+
const pixelWidth = result.originalWidth || 0;
|
|
589
|
+
const pixelHeight = result.originalHeight || 0;
|
|
590
|
+
let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
|
|
591
|
+
// Get status bar height for coordinate guidance
|
|
592
|
+
let statusBarPixels = 63; // Default fallback
|
|
593
|
+
let statusBarDp = 24;
|
|
594
|
+
let densityDpi = 440; // Common default
|
|
595
|
+
try {
|
|
596
|
+
const statusBarResult = await androidGetStatusBarHeight(deviceId);
|
|
597
|
+
if (statusBarResult.success && statusBarResult.heightPixels) {
|
|
598
|
+
statusBarPixels = statusBarResult.heightPixels;
|
|
599
|
+
statusBarDp = statusBarResult.heightDp || 24;
|
|
600
|
+
}
|
|
601
|
+
const densityResult = await androidGetDensity(deviceId);
|
|
602
|
+
if (densityResult.success && densityResult.density) {
|
|
603
|
+
densityDpi = densityResult.density;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
// Use defaults
|
|
608
|
+
}
|
|
609
|
+
infoText += `\n📱 Android uses PIXELS for tap coordinates (same as screenshot)`;
|
|
560
610
|
if (result.scaleFactor && result.scaleFactor > 1) {
|
|
561
611
|
infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
|
|
562
|
-
infoText += `\
|
|
612
|
+
infoText += `\n📐 To convert image coords to tap coords: multiply by ${result.scaleFactor.toFixed(3)}`;
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
infoText += `\n📐 Screenshot coords = tap coords (no conversion needed)`;
|
|
563
616
|
}
|
|
617
|
+
infoText += `\n⚠️ Status bar: ${statusBarPixels}px (${statusBarDp}dp) from top - app content starts below this`;
|
|
618
|
+
infoText += `\n📊 Display density: ${densityDpi}dpi`;
|
|
564
619
|
return {
|
|
565
620
|
content: [
|
|
566
621
|
{
|
|
@@ -585,7 +640,7 @@ server.registerTool("android_screenshot", {
|
|
|
585
640
|
};
|
|
586
641
|
});
|
|
587
642
|
// Tool: Android install app
|
|
588
|
-
|
|
643
|
+
registerToolWithTelemetry("android_install_app", {
|
|
589
644
|
description: "Install an APK on an Android device/emulator",
|
|
590
645
|
inputSchema: {
|
|
591
646
|
apkPath: z.string().describe("Path to the APK file to install"),
|
|
@@ -617,7 +672,7 @@ server.registerTool("android_install_app", {
|
|
|
617
672
|
};
|
|
618
673
|
});
|
|
619
674
|
// Tool: Android launch app
|
|
620
|
-
|
|
675
|
+
registerToolWithTelemetry("android_launch_app", {
|
|
621
676
|
description: "Launch an app on an Android device/emulator by package name",
|
|
622
677
|
inputSchema: {
|
|
623
678
|
packageName: z.string().describe("Package name of the app (e.g., com.example.myapp)"),
|
|
@@ -643,7 +698,7 @@ server.registerTool("android_launch_app", {
|
|
|
643
698
|
};
|
|
644
699
|
});
|
|
645
700
|
// Tool: Android list packages
|
|
646
|
-
|
|
701
|
+
registerToolWithTelemetry("android_list_packages", {
|
|
647
702
|
description: "List installed packages on an Android device/emulator",
|
|
648
703
|
inputSchema: {
|
|
649
704
|
deviceId: z
|
|
@@ -671,7 +726,7 @@ server.registerTool("android_list_packages", {
|
|
|
671
726
|
// Android UI Input Tools (Phase 2)
|
|
672
727
|
// ============================================================================
|
|
673
728
|
// Tool: Android tap
|
|
674
|
-
|
|
729
|
+
registerToolWithTelemetry("android_tap", {
|
|
675
730
|
description: "Tap at specific coordinates on an Android device/emulator screen. NOTE: Prefer using android_tap_element instead, which finds elements by text/content-desc and is more reliable.",
|
|
676
731
|
inputSchema: {
|
|
677
732
|
x: z.number().describe("X coordinate in pixels"),
|
|
@@ -694,7 +749,7 @@ server.registerTool("android_tap", {
|
|
|
694
749
|
};
|
|
695
750
|
});
|
|
696
751
|
// Tool: Android long press
|
|
697
|
-
|
|
752
|
+
registerToolWithTelemetry("android_long_press", {
|
|
698
753
|
description: "Long press at specific coordinates on an Android device/emulator screen",
|
|
699
754
|
inputSchema: {
|
|
700
755
|
x: z.number().describe("X coordinate in pixels"),
|
|
@@ -722,7 +777,7 @@ server.registerTool("android_long_press", {
|
|
|
722
777
|
};
|
|
723
778
|
});
|
|
724
779
|
// Tool: Android swipe
|
|
725
|
-
|
|
780
|
+
registerToolWithTelemetry("android_swipe", {
|
|
726
781
|
description: "Swipe from one point to another on an Android device/emulator screen",
|
|
727
782
|
inputSchema: {
|
|
728
783
|
startX: z.number().describe("Starting X coordinate in pixels"),
|
|
@@ -752,7 +807,7 @@ server.registerTool("android_swipe", {
|
|
|
752
807
|
};
|
|
753
808
|
});
|
|
754
809
|
// Tool: Android input text
|
|
755
|
-
|
|
810
|
+
registerToolWithTelemetry("android_input_text", {
|
|
756
811
|
description: "Type text on an Android device/emulator. The text will be input at the current focus point (tap an input field first).",
|
|
757
812
|
inputSchema: {
|
|
758
813
|
text: z.string().describe("Text to type"),
|
|
@@ -774,7 +829,7 @@ server.registerTool("android_input_text", {
|
|
|
774
829
|
};
|
|
775
830
|
});
|
|
776
831
|
// Tool: Android key event
|
|
777
|
-
|
|
832
|
+
registerToolWithTelemetry("android_key_event", {
|
|
778
833
|
description: `Send a key event to an Android device/emulator. Common keys: ${Object.keys(ANDROID_KEY_EVENTS).join(", ")}`,
|
|
779
834
|
inputSchema: {
|
|
780
835
|
key: z
|
|
@@ -802,7 +857,7 @@ server.registerTool("android_key_event", {
|
|
|
802
857
|
};
|
|
803
858
|
});
|
|
804
859
|
// Tool: Android get screen size
|
|
805
|
-
|
|
860
|
+
registerToolWithTelemetry("android_get_screen_size", {
|
|
806
861
|
description: "Get the screen size (resolution) of an Android device/emulator",
|
|
807
862
|
inputSchema: {
|
|
808
863
|
deviceId: z
|
|
@@ -1075,7 +1130,7 @@ server.registerTool("android_wait_for_element", {
|
|
|
1075
1130
|
// iOS Simulator Tools
|
|
1076
1131
|
// ============================================================================
|
|
1077
1132
|
// Tool: List iOS simulators
|
|
1078
|
-
|
|
1133
|
+
registerToolWithTelemetry("list_ios_simulators", {
|
|
1079
1134
|
description: "List available iOS simulators",
|
|
1080
1135
|
inputSchema: {
|
|
1081
1136
|
onlyBooted: z
|
|
@@ -1097,7 +1152,7 @@ server.registerTool("list_ios_simulators", {
|
|
|
1097
1152
|
};
|
|
1098
1153
|
});
|
|
1099
1154
|
// Tool: iOS screenshot
|
|
1100
|
-
|
|
1155
|
+
registerToolWithTelemetry("ios_screenshot", {
|
|
1101
1156
|
description: "Take a screenshot from an iOS simulator. Returns the image data that can be displayed.",
|
|
1102
1157
|
inputSchema: {
|
|
1103
1158
|
outputPath: z
|
|
@@ -1125,17 +1180,62 @@ server.registerTool("ios_screenshot", {
|
|
|
1125
1180
|
// Include image data if available
|
|
1126
1181
|
if (result.data) {
|
|
1127
1182
|
// Build info text with coordinate guidance for iOS
|
|
1128
|
-
// iOS simulators use points, not pixels. Retina displays are typically 2x or 3x.
|
|
1129
1183
|
const pixelWidth = result.originalWidth || 0;
|
|
1130
1184
|
const pixelHeight = result.originalHeight || 0;
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1185
|
+
// Try to get actual screen dimensions and safe area from accessibility tree
|
|
1186
|
+
let pointWidth = 0;
|
|
1187
|
+
let pointHeight = 0;
|
|
1188
|
+
let scaleFactor = 3; // Default to 3x for modern iPhones
|
|
1189
|
+
let safeAreaTop = 59; // Default safe area offset
|
|
1190
|
+
try {
|
|
1191
|
+
const describeResult = await iosDescribeAll(udid);
|
|
1192
|
+
if (describeResult.success && describeResult.elements && describeResult.elements.length > 0) {
|
|
1193
|
+
// First element is typically the Application with full screen frame
|
|
1194
|
+
const rootElement = describeResult.elements[0];
|
|
1195
|
+
// Try parsed frame first, then parse AXFrame string
|
|
1196
|
+
if (rootElement.frame) {
|
|
1197
|
+
pointWidth = Math.round(rootElement.frame.width);
|
|
1198
|
+
pointHeight = Math.round(rootElement.frame.height);
|
|
1199
|
+
// The frame.y of the root element indicates where content starts (after status bar)
|
|
1200
|
+
if (rootElement.frame.y > 0) {
|
|
1201
|
+
safeAreaTop = Math.round(rootElement.frame.y);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
else if (rootElement.AXFrame) {
|
|
1205
|
+
// Parse format: "{{x, y}, {width, height}}"
|
|
1206
|
+
const match = rootElement.AXFrame.match(/\{\{([\d.]+),\s*([\d.]+)\},\s*\{([\d.]+),\s*([\d.]+)\}\}/);
|
|
1207
|
+
if (match) {
|
|
1208
|
+
const frameY = parseFloat(match[2]);
|
|
1209
|
+
pointWidth = Math.round(parseFloat(match[3]));
|
|
1210
|
+
pointHeight = Math.round(parseFloat(match[4]));
|
|
1211
|
+
if (frameY > 0) {
|
|
1212
|
+
safeAreaTop = Math.round(frameY);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
// Calculate actual scale factor
|
|
1217
|
+
if (pointWidth > 0) {
|
|
1218
|
+
scaleFactor = Math.round(pixelWidth / pointWidth);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
// Fallback: use 3x scale for modern devices
|
|
1224
|
+
}
|
|
1225
|
+
// Fallback if we couldn't get dimensions
|
|
1226
|
+
if (pointWidth === 0) {
|
|
1227
|
+
pointWidth = Math.round(pixelWidth / scaleFactor);
|
|
1228
|
+
pointHeight = Math.round(pixelHeight / scaleFactor);
|
|
1229
|
+
}
|
|
1230
|
+
const safeAreaOffsetPixels = safeAreaTop * scaleFactor;
|
|
1134
1231
|
let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
|
|
1135
|
-
infoText += `\n📱 iOS
|
|
1136
|
-
infoText += `\
|
|
1232
|
+
infoText += `\n📱 iOS tap coordinates use POINTS: ${pointWidth}x${pointHeight}`;
|
|
1233
|
+
infoText += `\n📐 To convert screenshot coords to tap points:`;
|
|
1234
|
+
infoText += `\n tap_x = pixel_x / ${scaleFactor}`;
|
|
1235
|
+
infoText += `\n tap_y = pixel_y / ${scaleFactor}`;
|
|
1236
|
+
infoText += `\n⚠️ Status bar + safe area: ${safeAreaTop} points (${safeAreaOffsetPixels} pixels) from top`;
|
|
1137
1237
|
if (result.scaleFactor && result.scaleFactor > 1) {
|
|
1138
|
-
infoText += `\n
|
|
1238
|
+
infoText += `\n🖼️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
|
|
1139
1239
|
}
|
|
1140
1240
|
infoText += `\n💡 Use ios_describe_all to get exact element coordinates`;
|
|
1141
1241
|
return {
|
|
@@ -1162,7 +1262,7 @@ server.registerTool("ios_screenshot", {
|
|
|
1162
1262
|
};
|
|
1163
1263
|
});
|
|
1164
1264
|
// Tool: iOS install app
|
|
1165
|
-
|
|
1265
|
+
registerToolWithTelemetry("ios_install_app", {
|
|
1166
1266
|
description: "Install an app bundle (.app) on an iOS simulator",
|
|
1167
1267
|
inputSchema: {
|
|
1168
1268
|
appPath: z.string().describe("Path to the .app bundle to install"),
|
|
@@ -1184,7 +1284,7 @@ server.registerTool("ios_install_app", {
|
|
|
1184
1284
|
};
|
|
1185
1285
|
});
|
|
1186
1286
|
// Tool: iOS launch app
|
|
1187
|
-
|
|
1287
|
+
registerToolWithTelemetry("ios_launch_app", {
|
|
1188
1288
|
description: "Launch an app on an iOS simulator by bundle ID",
|
|
1189
1289
|
inputSchema: {
|
|
1190
1290
|
bundleId: z.string().describe("Bundle ID of the app (e.g., com.example.myapp)"),
|
|
@@ -1206,7 +1306,7 @@ server.registerTool("ios_launch_app", {
|
|
|
1206
1306
|
};
|
|
1207
1307
|
});
|
|
1208
1308
|
// Tool: iOS open URL
|
|
1209
|
-
|
|
1309
|
+
registerToolWithTelemetry("ios_open_url", {
|
|
1210
1310
|
description: "Open a URL in the iOS simulator (opens in default handler or Safari)",
|
|
1211
1311
|
inputSchema: {
|
|
1212
1312
|
url: z.string().describe("URL to open (e.g., https://example.com or myapp://path)"),
|
|
@@ -1228,7 +1328,7 @@ server.registerTool("ios_open_url", {
|
|
|
1228
1328
|
};
|
|
1229
1329
|
});
|
|
1230
1330
|
// Tool: iOS terminate app
|
|
1231
|
-
|
|
1331
|
+
registerToolWithTelemetry("ios_terminate_app", {
|
|
1232
1332
|
description: "Terminate a running app on an iOS simulator",
|
|
1233
1333
|
inputSchema: {
|
|
1234
1334
|
bundleId: z.string().describe("Bundle ID of the app to terminate"),
|
|
@@ -1250,7 +1350,7 @@ server.registerTool("ios_terminate_app", {
|
|
|
1250
1350
|
};
|
|
1251
1351
|
});
|
|
1252
1352
|
// Tool: iOS boot simulator
|
|
1253
|
-
|
|
1353
|
+
registerToolWithTelemetry("ios_boot_simulator", {
|
|
1254
1354
|
description: "Boot an iOS simulator by UDID. Use list_ios_simulators to find available simulators.",
|
|
1255
1355
|
inputSchema: {
|
|
1256
1356
|
udid: z.string().describe("UDID of the simulator to boot (from list_ios_simulators)")
|
|
@@ -1637,7 +1737,7 @@ server.registerTool("ios_wait_for_element", {
|
|
|
1637
1737
|
};
|
|
1638
1738
|
});
|
|
1639
1739
|
// Tool: Get debug server info
|
|
1640
|
-
|
|
1740
|
+
registerToolWithTelemetry("get_debug_server", {
|
|
1641
1741
|
description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
|
|
1642
1742
|
inputSchema: {}
|
|
1643
1743
|
}, async () => {
|
|
@@ -1674,6 +1774,8 @@ server.registerTool("get_debug_server", {
|
|
|
1674
1774
|
});
|
|
1675
1775
|
// Main function
|
|
1676
1776
|
async function main() {
|
|
1777
|
+
// Initialize telemetry (checks opt-out env var, loads/creates installation ID)
|
|
1778
|
+
initTelemetry();
|
|
1677
1779
|
// Start debug HTTP server for buffer inspection (finds available port automatically)
|
|
1678
1780
|
await startDebugHttpServer();
|
|
1679
1781
|
const transport = new StdioServerTransport();
|