react-native-ai-debugger 1.0.5 → 1.0.7

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.
@@ -0,0 +1,4 @@
1
+ export declare function initTelemetry(): void;
2
+ export declare function isTelemetryEnabled(): boolean;
3
+ export declare function trackToolInvocation(toolName: string, success: boolean, durationMs: number): void;
4
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/core/telemetry.ts"],"names":[],"mappings":"AAgIA,wBAAgB,aAAa,IAAI,IAAI,CA8CpC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AA2BD,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACnB,IAAI,CAiBN"}
@@ -0,0 +1,225 @@
1
+ import { randomUUID } from "crypto";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ // ============================================================================
6
+ // Configuration
7
+ // ============================================================================
8
+ // TODO: Update these after deploying your Cloudflare Worker
9
+ const TELEMETRY_ENDPOINT = "https://rn-debugger-telemetry.YOUR_SUBDOMAIN.workers.dev";
10
+ const TELEMETRY_API_KEY = "YOUR_API_KEY_HERE";
11
+ const BATCH_SIZE = 10;
12
+ const BATCH_INTERVAL_MS = 30_000; // 30 seconds
13
+ const REQUEST_TIMEOUT_MS = 5_000;
14
+ const CONFIG_DIR = join(homedir(), ".rn-ai-debugger");
15
+ const CONFIG_FILE = join(CONFIG_DIR, "telemetry.json");
16
+ const SERVER_VERSION = "1.0.5";
17
+ // ============================================================================
18
+ // State
19
+ // ============================================================================
20
+ let telemetryEnabled = true;
21
+ let config = null;
22
+ let eventQueue = [];
23
+ let batchTimer = null;
24
+ let sessionStartTime = null;
25
+ let isFirstRunSession = false;
26
+ // ============================================================================
27
+ // Configuration Management
28
+ // ============================================================================
29
+ function loadOrCreateConfig() {
30
+ if (config)
31
+ return config;
32
+ // Try to load existing config
33
+ try {
34
+ if (existsSync(CONFIG_FILE)) {
35
+ const data = readFileSync(CONFIG_FILE, "utf-8");
36
+ const parsed = JSON.parse(data);
37
+ // Mark as not first run for subsequent sessions
38
+ config = { ...parsed, isFirstRun: false };
39
+ isFirstRunSession = false;
40
+ return config;
41
+ }
42
+ }
43
+ catch {
44
+ // Config file corrupted or unreadable, create new one
45
+ }
46
+ // Create new installation
47
+ const newConfig = {
48
+ installationId: randomUUID(),
49
+ firstRunTimestamp: Date.now(),
50
+ isFirstRun: true
51
+ };
52
+ try {
53
+ if (!existsSync(CONFIG_DIR)) {
54
+ mkdirSync(CONFIG_DIR, { recursive: true });
55
+ }
56
+ writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
57
+ // Re-read to handle race condition with concurrent sessions
58
+ // The file on disk is the source of truth
59
+ try {
60
+ const data = readFileSync(CONFIG_FILE, "utf-8");
61
+ const persistedConfig = JSON.parse(data);
62
+ config = persistedConfig;
63
+ isFirstRunSession = persistedConfig.isFirstRun;
64
+ return config;
65
+ }
66
+ catch {
67
+ // If re-read fails, use the config we created
68
+ config = newConfig;
69
+ isFirstRunSession = true;
70
+ return config;
71
+ }
72
+ }
73
+ catch {
74
+ // Failed to save config, continue with in-memory config
75
+ config = newConfig;
76
+ isFirstRunSession = true;
77
+ return config;
78
+ }
79
+ }
80
+ function getInstallationId() {
81
+ return loadOrCreateConfig().installationId;
82
+ }
83
+ function isFirstRun() {
84
+ loadOrCreateConfig();
85
+ return isFirstRunSession;
86
+ }
87
+ // ============================================================================
88
+ // Telemetry Control
89
+ // ============================================================================
90
+ export function initTelemetry() {
91
+ // Check environment variable for opt-out
92
+ const envValue = process.env.RN_DEBUGGER_TELEMETRY;
93
+ if (envValue === "false" || envValue === "0" || envValue === "off") {
94
+ telemetryEnabled = false;
95
+ console.error("[rn-ai-debugger] Telemetry disabled via RN_DEBUGGER_TELEMETRY");
96
+ return;
97
+ }
98
+ // Check if endpoint is configured
99
+ if (TELEMETRY_ENDPOINT.includes("YOUR_SUBDOMAIN") || TELEMETRY_API_KEY === "YOUR_API_KEY_HERE") {
100
+ telemetryEnabled = false;
101
+ // Silently disable - endpoint not configured yet
102
+ return;
103
+ }
104
+ // Load/create config (generates installation ID)
105
+ loadOrCreateConfig();
106
+ sessionStartTime = Date.now();
107
+ // Track session start
108
+ trackEvent("session_start", {
109
+ isFirstRun: isFirstRun()
110
+ });
111
+ // Start batch timer
112
+ startBatchTimer();
113
+ // Flush on process exit
114
+ process.on("beforeExit", () => {
115
+ flushSync();
116
+ });
117
+ // Track session end on SIGINT/SIGTERM
118
+ const handleExit = () => {
119
+ if (sessionStartTime) {
120
+ trackEvent("session_end", {
121
+ duration: Date.now() - sessionStartTime
122
+ });
123
+ flushSync();
124
+ }
125
+ process.exit(0);
126
+ };
127
+ process.on("SIGINT", handleExit);
128
+ process.on("SIGTERM", handleExit);
129
+ }
130
+ export function isTelemetryEnabled() {
131
+ return telemetryEnabled;
132
+ }
133
+ // ============================================================================
134
+ // Event Tracking
135
+ // ============================================================================
136
+ function trackEvent(name, properties) {
137
+ if (!telemetryEnabled)
138
+ return;
139
+ const event = {
140
+ name,
141
+ timestamp: Date.now(),
142
+ isFirstRun: isFirstRun(),
143
+ properties
144
+ };
145
+ eventQueue.push(event);
146
+ // Flush immediately if batch size reached
147
+ if (eventQueue.length >= BATCH_SIZE) {
148
+ flush();
149
+ }
150
+ }
151
+ export function trackToolInvocation(toolName, success, durationMs) {
152
+ if (!telemetryEnabled)
153
+ return;
154
+ const event = {
155
+ name: "tool_invocation",
156
+ timestamp: Date.now(),
157
+ toolName,
158
+ success,
159
+ duration: durationMs,
160
+ isFirstRun: isFirstRun()
161
+ };
162
+ eventQueue.push(event);
163
+ if (eventQueue.length >= BATCH_SIZE) {
164
+ flush();
165
+ }
166
+ }
167
+ // ============================================================================
168
+ // Batch Sending
169
+ // ============================================================================
170
+ function startBatchTimer() {
171
+ if (batchTimer)
172
+ return;
173
+ batchTimer = setInterval(() => {
174
+ flush();
175
+ }, BATCH_INTERVAL_MS);
176
+ // Unref so it doesn't keep the process alive
177
+ batchTimer.unref();
178
+ }
179
+ async function flush() {
180
+ if (!telemetryEnabled || eventQueue.length === 0)
181
+ return;
182
+ const eventsToSend = [...eventQueue];
183
+ eventQueue = [];
184
+ try {
185
+ await sendEvents(eventsToSend);
186
+ }
187
+ catch {
188
+ // Silently fail - telemetry should never impact the user
189
+ }
190
+ }
191
+ function flushSync() {
192
+ if (!telemetryEnabled || eventQueue.length === 0)
193
+ return;
194
+ const eventsToSend = [...eventQueue];
195
+ eventQueue = [];
196
+ // Use a synchronous-ish approach for exit handlers
197
+ // This won't actually wait, but queues the request
198
+ sendEvents(eventsToSend).catch(() => { });
199
+ }
200
+ async function sendEvents(events) {
201
+ const payload = {
202
+ installationId: getInstallationId(),
203
+ serverVersion: SERVER_VERSION,
204
+ nodeVersion: process.version,
205
+ platform: process.platform,
206
+ events
207
+ };
208
+ const controller = new AbortController();
209
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
210
+ try {
211
+ await fetch(TELEMETRY_ENDPOINT, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ "X-API-Key": TELEMETRY_API_KEY
216
+ },
217
+ body: JSON.stringify(payload),
218
+ signal: controller.signal
219
+ });
220
+ }
221
+ finally {
222
+ clearTimeout(timeoutId);
223
+ }
224
+ }
225
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/core/telemetry.ts"],"names":[],"mappings":"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"}
package/build/index.js CHANGED
@@ -9,8 +9,12 @@ connectMetroBuildEvents, getBundleErrors, getBundleStatusWithErrors,
9
9
  listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, androidListPackages,
10
10
  // Android UI Input (Phase 2)
11
11
  ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize,
12
+ // Android Accessibility (UI Hierarchy)
13
+ androidDescribeAll, androidDescribePoint, androidTapElement,
12
14
  // iOS
13
15
  listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTerminateApp, iosBootSimulator,
16
+ // iOS IDB-based UI tools
17
+ iosTap, iosTapElement, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySequence, iosDescribeAll, iosDescribePoint, IOS_BUTTON_TYPES,
14
18
  // Debug HTTP Server
15
19
  startDebugHttpServer, getDebugServerPort } from "./core/index.js";
16
20
  // Create MCP server
@@ -664,7 +668,7 @@ server.registerTool("android_list_packages", {
664
668
  // ============================================================================
665
669
  // Tool: Android tap
666
670
  server.registerTool("android_tap", {
667
- description: "Tap at specific coordinates on an Android device/emulator screen",
671
+ 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.",
668
672
  inputSchema: {
669
673
  x: z.number().describe("X coordinate in pixels"),
670
674
  y: z.number().describe("Y coordinate in pixels"),
@@ -825,6 +829,106 @@ server.registerTool("android_get_screen_size", {
825
829
  };
826
830
  });
827
831
  // ============================================================================
832
+ // Android Accessibility Tools (UI Hierarchy)
833
+ // ============================================================================
834
+ // Tool: Android describe all (UI hierarchy)
835
+ server.registerTool("android_describe_all", {
836
+ description: "Get the full UI accessibility tree from the Android device using uiautomator. Returns a hierarchical view of all UI elements with their text, content-description, resource-id, bounds, and tap coordinates.",
837
+ inputSchema: {
838
+ deviceId: z
839
+ .string()
840
+ .optional()
841
+ .describe("Optional device ID. Uses first available device if not specified.")
842
+ }
843
+ }, async ({ deviceId }) => {
844
+ const result = await androidDescribeAll(deviceId);
845
+ return {
846
+ content: [
847
+ {
848
+ type: "text",
849
+ text: result.success ? result.formatted : `Error: ${result.error}`
850
+ }
851
+ ],
852
+ isError: !result.success
853
+ };
854
+ });
855
+ // Tool: Android describe point
856
+ server.registerTool("android_describe_point", {
857
+ description: "Get UI element info at specific coordinates on an Android device. Returns the element's text, content-description, resource-id, bounds, and state flags.",
858
+ inputSchema: {
859
+ x: z.number().describe("X coordinate in pixels"),
860
+ y: z.number().describe("Y coordinate in pixels"),
861
+ deviceId: z
862
+ .string()
863
+ .optional()
864
+ .describe("Optional device ID. Uses first available device if not specified.")
865
+ }
866
+ }, async ({ x, y, deviceId }) => {
867
+ const result = await androidDescribePoint(x, y, deviceId);
868
+ return {
869
+ content: [
870
+ {
871
+ type: "text",
872
+ text: result.success ? result.formatted : `Error: ${result.error}`
873
+ }
874
+ ],
875
+ isError: !result.success
876
+ };
877
+ });
878
+ // Tool: Android tap element
879
+ server.registerTool("android_tap_element", {
880
+ description: "PREFERRED: Tap an element by its text, content-description, or resource-id. More reliable than coordinate-based tapping. Automatically finds the element using uiautomator and taps its center.",
881
+ inputSchema: {
882
+ text: z
883
+ .string()
884
+ .optional()
885
+ .describe("Exact text match for the element"),
886
+ textContains: z
887
+ .string()
888
+ .optional()
889
+ .describe("Partial text match (case-insensitive)"),
890
+ contentDesc: z
891
+ .string()
892
+ .optional()
893
+ .describe("Exact content-description match"),
894
+ contentDescContains: z
895
+ .string()
896
+ .optional()
897
+ .describe("Partial content-description match (case-insensitive)"),
898
+ resourceId: z
899
+ .string()
900
+ .optional()
901
+ .describe("Resource ID match (e.g., 'com.app:id/button' or just 'button')"),
902
+ index: z
903
+ .number()
904
+ .optional()
905
+ .describe("If multiple elements match, tap the nth one (0-indexed, default: 0)"),
906
+ deviceId: z
907
+ .string()
908
+ .optional()
909
+ .describe("Optional device ID. Uses first available device if not specified.")
910
+ }
911
+ }, async ({ text, textContains, contentDesc, contentDescContains, resourceId, index, deviceId }) => {
912
+ const result = await androidTapElement({
913
+ text,
914
+ textContains,
915
+ contentDesc,
916
+ contentDescContains,
917
+ resourceId,
918
+ index,
919
+ deviceId
920
+ });
921
+ return {
922
+ content: [
923
+ {
924
+ type: "text",
925
+ text: result.success ? result.result : `Error: ${result.error}`
926
+ }
927
+ ],
928
+ isError: !result.success
929
+ };
930
+ });
931
+ // ============================================================================
828
932
  // iOS Simulator Tools
829
933
  // ============================================================================
830
934
  // Tool: List iOS simulators
@@ -877,12 +981,20 @@ server.registerTool("ios_screenshot", {
877
981
  }
878
982
  // Include image data if available
879
983
  if (result.data) {
880
- // Build info text with scale factor for coordinate conversion
881
- let infoText = `Screenshot captured (${result.originalWidth}x${result.originalHeight})`;
984
+ // Build info text with coordinate guidance for iOS
985
+ // iOS simulators use points, not pixels. Retina displays are typically 2x or 3x.
986
+ const pixelWidth = result.originalWidth || 0;
987
+ const pixelHeight = result.originalHeight || 0;
988
+ // Assume 2x Retina scale for most iOS simulators
989
+ const pointWidth = Math.round(pixelWidth / 2);
990
+ const pointHeight = Math.round(pixelHeight / 2);
991
+ let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
992
+ infoText += `\n📱 iOS IDB coordinates use POINTS: ${pointWidth}x${pointHeight}`;
993
+ infoText += `\nTo convert image coords to IDB points: divide pixel coordinates by 2`;
882
994
  if (result.scaleFactor && result.scaleFactor > 1) {
883
- infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
884
- infoText += `\nTo tap/swipe: multiply image coordinates by ${result.scaleFactor.toFixed(3)} to get device coordinates.`;
995
+ infoText += `\n⚠️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
885
996
  }
997
+ infoText += `\n💡 Use ios_describe_all to get exact element coordinates`;
886
998
  return {
887
999
  content: [
888
1000
  {
@@ -1012,6 +1124,237 @@ server.registerTool("ios_boot_simulator", {
1012
1124
  isError: !result.success
1013
1125
  };
1014
1126
  });
1127
+ // ============================================================================
1128
+ // iOS IDB-Based UI Tools (require Facebook IDB)
1129
+ // Install with: brew install idb-companion
1130
+ // ============================================================================
1131
+ // Tool: iOS tap
1132
+ server.registerTool("ios_tap", {
1133
+ description: "Tap at specific coordinates on an iOS simulator screen. NOTE: Prefer using ios_tap_element instead, which finds elements by accessibility label and is more reliable. Requires IDB (brew install idb-companion).",
1134
+ inputSchema: {
1135
+ x: z.number().describe("X coordinate in pixels"),
1136
+ y: z.number().describe("Y coordinate in pixels"),
1137
+ duration: z
1138
+ .number()
1139
+ .optional()
1140
+ .describe("Optional tap duration in seconds (for long press)"),
1141
+ udid: z
1142
+ .string()
1143
+ .optional()
1144
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1145
+ }
1146
+ }, async ({ x, y, duration, udid }) => {
1147
+ const result = await iosTap(x, y, { duration, udid });
1148
+ return {
1149
+ content: [
1150
+ {
1151
+ type: "text",
1152
+ text: result.success ? result.result : `Error: ${result.error}`
1153
+ }
1154
+ ],
1155
+ isError: !result.success
1156
+ };
1157
+ });
1158
+ // Tool: iOS tap element by label
1159
+ server.registerTool("ios_tap_element", {
1160
+ description: "PREFERRED: Tap an element by its accessibility label. More reliable than coordinate-based tapping. Automatically finds the element and taps its center. Requires IDB (brew install idb-companion).",
1161
+ inputSchema: {
1162
+ label: z
1163
+ .string()
1164
+ .optional()
1165
+ .describe("Exact accessibility label to match (e.g., 'Home', 'Settings')"),
1166
+ labelContains: z
1167
+ .string()
1168
+ .optional()
1169
+ .describe("Partial label match, case-insensitive (e.g., 'Circular' matches 'Circulars, 3, 12 total')"),
1170
+ index: z
1171
+ .number()
1172
+ .optional()
1173
+ .describe("If multiple elements match, tap the nth one (0-indexed, default: 0)"),
1174
+ duration: z
1175
+ .number()
1176
+ .optional()
1177
+ .describe("Optional tap duration in seconds (for long press)"),
1178
+ udid: z
1179
+ .string()
1180
+ .optional()
1181
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1182
+ }
1183
+ }, async ({ label, labelContains, index, duration, udid }) => {
1184
+ const result = await iosTapElement({ label, labelContains, index, duration, udid });
1185
+ return {
1186
+ content: [
1187
+ {
1188
+ type: "text",
1189
+ text: result.success ? result.result : `Error: ${result.error}`
1190
+ }
1191
+ ],
1192
+ isError: !result.success
1193
+ };
1194
+ });
1195
+ // Tool: iOS swipe
1196
+ server.registerTool("ios_swipe", {
1197
+ description: "Swipe gesture on an iOS simulator screen. Requires IDB to be installed (brew install idb-companion).",
1198
+ inputSchema: {
1199
+ startX: z.number().describe("Starting X coordinate in pixels"),
1200
+ startY: z.number().describe("Starting Y coordinate in pixels"),
1201
+ endX: z.number().describe("Ending X coordinate in pixels"),
1202
+ endY: z.number().describe("Ending Y coordinate in pixels"),
1203
+ duration: z.number().optional().describe("Optional swipe duration in seconds"),
1204
+ delta: z.number().optional().describe("Optional delta between touch events (step size)"),
1205
+ udid: z
1206
+ .string()
1207
+ .optional()
1208
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1209
+ }
1210
+ }, async ({ startX, startY, endX, endY, duration, delta, udid }) => {
1211
+ const result = await iosSwipe(startX, startY, endX, endY, { duration, delta, udid });
1212
+ return {
1213
+ content: [
1214
+ {
1215
+ type: "text",
1216
+ text: result.success ? result.result : `Error: ${result.error}`
1217
+ }
1218
+ ],
1219
+ isError: !result.success
1220
+ };
1221
+ });
1222
+ // Tool: iOS input text
1223
+ server.registerTool("ios_input_text", {
1224
+ description: "Type text into the active input field on an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1225
+ inputSchema: {
1226
+ text: z.string().describe("Text to type into the active input field"),
1227
+ udid: z
1228
+ .string()
1229
+ .optional()
1230
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1231
+ }
1232
+ }, async ({ text, udid }) => {
1233
+ const result = await iosInputText(text, udid);
1234
+ return {
1235
+ content: [
1236
+ {
1237
+ type: "text",
1238
+ text: result.success ? result.result : `Error: ${result.error}`
1239
+ }
1240
+ ],
1241
+ isError: !result.success
1242
+ };
1243
+ });
1244
+ // Tool: iOS button
1245
+ server.registerTool("ios_button", {
1246
+ description: "Press a hardware button on an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1247
+ inputSchema: {
1248
+ button: z
1249
+ .enum(IOS_BUTTON_TYPES)
1250
+ .describe("Hardware button to press: HOME, LOCK, SIDE_BUTTON, SIRI, or APPLE_PAY"),
1251
+ duration: z.number().optional().describe("Optional button press duration in seconds"),
1252
+ udid: z
1253
+ .string()
1254
+ .optional()
1255
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1256
+ }
1257
+ }, async ({ button, duration, udid }) => {
1258
+ const result = await iosButton(button, { duration, udid });
1259
+ return {
1260
+ content: [
1261
+ {
1262
+ type: "text",
1263
+ text: result.success ? result.result : `Error: ${result.error}`
1264
+ }
1265
+ ],
1266
+ isError: !result.success
1267
+ };
1268
+ });
1269
+ // Tool: iOS key event
1270
+ server.registerTool("ios_key_event", {
1271
+ description: "Send a key event to an iOS simulator by keycode. Requires IDB to be installed (brew install idb-companion).",
1272
+ inputSchema: {
1273
+ keycode: z.number().describe("iOS keycode to send"),
1274
+ duration: z.number().optional().describe("Optional key press duration in seconds"),
1275
+ udid: z
1276
+ .string()
1277
+ .optional()
1278
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1279
+ }
1280
+ }, async ({ keycode, duration, udid }) => {
1281
+ const result = await iosKeyEvent(keycode, { duration, udid });
1282
+ return {
1283
+ content: [
1284
+ {
1285
+ type: "text",
1286
+ text: result.success ? result.result : `Error: ${result.error}`
1287
+ }
1288
+ ],
1289
+ isError: !result.success
1290
+ };
1291
+ });
1292
+ // Tool: iOS key sequence
1293
+ server.registerTool("ios_key_sequence", {
1294
+ description: "Send a sequence of key events to an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1295
+ inputSchema: {
1296
+ keycodes: z.array(z.number()).describe("Array of iOS keycodes to send in sequence"),
1297
+ udid: z
1298
+ .string()
1299
+ .optional()
1300
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1301
+ }
1302
+ }, async ({ keycodes, udid }) => {
1303
+ const result = await iosKeySequence(keycodes, udid);
1304
+ return {
1305
+ content: [
1306
+ {
1307
+ type: "text",
1308
+ text: result.success ? result.result : `Error: ${result.error}`
1309
+ }
1310
+ ],
1311
+ isError: !result.success
1312
+ };
1313
+ });
1314
+ // Tool: iOS describe all (accessibility tree)
1315
+ server.registerTool("ios_describe_all", {
1316
+ description: "Get accessibility information for the entire iOS simulator screen. Returns a nested tree of UI elements with labels, values, and frames. Requires IDB to be installed (brew install idb-companion).",
1317
+ inputSchema: {
1318
+ udid: z
1319
+ .string()
1320
+ .optional()
1321
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1322
+ }
1323
+ }, async ({ udid }) => {
1324
+ const result = await iosDescribeAll(udid);
1325
+ return {
1326
+ content: [
1327
+ {
1328
+ type: "text",
1329
+ text: result.success ? result.result : `Error: ${result.error}`
1330
+ }
1331
+ ],
1332
+ isError: !result.success
1333
+ };
1334
+ });
1335
+ // Tool: iOS describe point
1336
+ server.registerTool("ios_describe_point", {
1337
+ description: "Get accessibility information for the UI element at a specific point on the iOS simulator screen. Requires IDB to be installed (brew install idb-companion).",
1338
+ inputSchema: {
1339
+ x: z.number().describe("X coordinate in pixels"),
1340
+ y: z.number().describe("Y coordinate in pixels"),
1341
+ udid: z
1342
+ .string()
1343
+ .optional()
1344
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1345
+ }
1346
+ }, async ({ x, y, udid }) => {
1347
+ const result = await iosDescribePoint(x, y, udid);
1348
+ return {
1349
+ content: [
1350
+ {
1351
+ type: "text",
1352
+ text: result.success ? result.result : `Error: ${result.error}`
1353
+ }
1354
+ ],
1355
+ isError: !result.success
1356
+ };
1357
+ });
1015
1358
  // Tool: Get debug server info
1016
1359
  server.registerTool("get_debug_server", {
1017
1360
  description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",