react-native-ai-debugger 1.0.4 → 1.0.6

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
@@ -10,7 +10,11 @@ listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, andr
10
10
  // Android UI Input (Phase 2)
11
11
  ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize,
12
12
  // iOS
13
- listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTerminateApp, iosBootSimulator } from "./core/index.js";
13
+ listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTerminateApp, iosBootSimulator,
14
+ // iOS IDB-based UI tools
15
+ iosTap, iosTapElement, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySequence, iosDescribeAll, iosDescribePoint, IOS_BUTTON_TYPES,
16
+ // Debug HTTP Server
17
+ startDebugHttpServer, getDebugServerPort } from "./core/index.js";
14
18
  // Create MCP server
15
19
  const server = new McpServer({
16
20
  name: "react-native-ai-debugger",
@@ -875,12 +879,20 @@ server.registerTool("ios_screenshot", {
875
879
  }
876
880
  // Include image data if available
877
881
  if (result.data) {
878
- // Build info text with scale factor for coordinate conversion
879
- let infoText = `Screenshot captured (${result.originalWidth}x${result.originalHeight})`;
882
+ // Build info text with coordinate guidance for iOS
883
+ // iOS simulators use points, not pixels. Retina displays are typically 2x or 3x.
884
+ const pixelWidth = result.originalWidth || 0;
885
+ const pixelHeight = result.originalHeight || 0;
886
+ // Assume 2x Retina scale for most iOS simulators
887
+ const pointWidth = Math.round(pixelWidth / 2);
888
+ const pointHeight = Math.round(pixelHeight / 2);
889
+ let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
890
+ infoText += `\n📱 iOS IDB coordinates use POINTS: ${pointWidth}x${pointHeight}`;
891
+ infoText += `\nTo convert image coords to IDB points: divide pixel coordinates by 2`;
880
892
  if (result.scaleFactor && result.scaleFactor > 1) {
881
- infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
882
- infoText += `\nTo tap/swipe: multiply image coordinates by ${result.scaleFactor.toFixed(3)} to get device coordinates.`;
893
+ infoText += `\n⚠️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
883
894
  }
895
+ infoText += `\n💡 Use ios_describe_all to get exact element coordinates`;
884
896
  return {
885
897
  content: [
886
898
  {
@@ -1010,8 +1022,277 @@ server.registerTool("ios_boot_simulator", {
1010
1022
  isError: !result.success
1011
1023
  };
1012
1024
  });
1025
+ // ============================================================================
1026
+ // iOS IDB-Based UI Tools (require Facebook IDB)
1027
+ // Install with: brew install idb-companion
1028
+ // ============================================================================
1029
+ // Tool: iOS tap
1030
+ server.registerTool("ios_tap", {
1031
+ description: "Tap at specific coordinates on an iOS simulator screen. Requires IDB to be installed (brew install idb-companion).",
1032
+ inputSchema: {
1033
+ x: z.number().describe("X coordinate in pixels"),
1034
+ y: z.number().describe("Y coordinate in pixels"),
1035
+ duration: z
1036
+ .number()
1037
+ .optional()
1038
+ .describe("Optional tap duration in seconds (for long press)"),
1039
+ udid: z
1040
+ .string()
1041
+ .optional()
1042
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1043
+ }
1044
+ }, async ({ x, y, duration, udid }) => {
1045
+ const result = await iosTap(x, y, { duration, udid });
1046
+ return {
1047
+ content: [
1048
+ {
1049
+ type: "text",
1050
+ text: result.success ? result.result : `Error: ${result.error}`
1051
+ }
1052
+ ],
1053
+ isError: !result.success
1054
+ };
1055
+ });
1056
+ // Tool: iOS tap element by label
1057
+ server.registerTool("ios_tap_element", {
1058
+ description: "Tap an element by its accessibility label. Automatically finds the element and taps its center. Requires IDB (brew install idb-companion).",
1059
+ inputSchema: {
1060
+ label: z
1061
+ .string()
1062
+ .optional()
1063
+ .describe("Exact accessibility label to match (e.g., 'Home', 'Settings')"),
1064
+ labelContains: z
1065
+ .string()
1066
+ .optional()
1067
+ .describe("Partial label match, case-insensitive (e.g., 'Circular' matches 'Circulars, 3, 12 total')"),
1068
+ index: z
1069
+ .number()
1070
+ .optional()
1071
+ .describe("If multiple elements match, tap the nth one (0-indexed, default: 0)"),
1072
+ duration: z
1073
+ .number()
1074
+ .optional()
1075
+ .describe("Optional tap duration in seconds (for long press)"),
1076
+ udid: z
1077
+ .string()
1078
+ .optional()
1079
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1080
+ }
1081
+ }, async ({ label, labelContains, index, duration, udid }) => {
1082
+ const result = await iosTapElement({ label, labelContains, index, duration, udid });
1083
+ return {
1084
+ content: [
1085
+ {
1086
+ type: "text",
1087
+ text: result.success ? result.result : `Error: ${result.error}`
1088
+ }
1089
+ ],
1090
+ isError: !result.success
1091
+ };
1092
+ });
1093
+ // Tool: iOS swipe
1094
+ server.registerTool("ios_swipe", {
1095
+ description: "Swipe gesture on an iOS simulator screen. Requires IDB to be installed (brew install idb-companion).",
1096
+ inputSchema: {
1097
+ startX: z.number().describe("Starting X coordinate in pixels"),
1098
+ startY: z.number().describe("Starting Y coordinate in pixels"),
1099
+ endX: z.number().describe("Ending X coordinate in pixels"),
1100
+ endY: z.number().describe("Ending Y coordinate in pixels"),
1101
+ duration: z.number().optional().describe("Optional swipe duration in seconds"),
1102
+ delta: z.number().optional().describe("Optional delta between touch events (step size)"),
1103
+ udid: z
1104
+ .string()
1105
+ .optional()
1106
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1107
+ }
1108
+ }, async ({ startX, startY, endX, endY, duration, delta, udid }) => {
1109
+ const result = await iosSwipe(startX, startY, endX, endY, { duration, delta, udid });
1110
+ return {
1111
+ content: [
1112
+ {
1113
+ type: "text",
1114
+ text: result.success ? result.result : `Error: ${result.error}`
1115
+ }
1116
+ ],
1117
+ isError: !result.success
1118
+ };
1119
+ });
1120
+ // Tool: iOS input text
1121
+ server.registerTool("ios_input_text", {
1122
+ description: "Type text into the active input field on an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1123
+ inputSchema: {
1124
+ text: z.string().describe("Text to type into the active input field"),
1125
+ udid: z
1126
+ .string()
1127
+ .optional()
1128
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1129
+ }
1130
+ }, async ({ text, udid }) => {
1131
+ const result = await iosInputText(text, udid);
1132
+ return {
1133
+ content: [
1134
+ {
1135
+ type: "text",
1136
+ text: result.success ? result.result : `Error: ${result.error}`
1137
+ }
1138
+ ],
1139
+ isError: !result.success
1140
+ };
1141
+ });
1142
+ // Tool: iOS button
1143
+ server.registerTool("ios_button", {
1144
+ description: "Press a hardware button on an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1145
+ inputSchema: {
1146
+ button: z
1147
+ .enum(IOS_BUTTON_TYPES)
1148
+ .describe("Hardware button to press: HOME, LOCK, SIDE_BUTTON, SIRI, or APPLE_PAY"),
1149
+ duration: z.number().optional().describe("Optional button press duration in seconds"),
1150
+ udid: z
1151
+ .string()
1152
+ .optional()
1153
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1154
+ }
1155
+ }, async ({ button, duration, udid }) => {
1156
+ const result = await iosButton(button, { duration, udid });
1157
+ return {
1158
+ content: [
1159
+ {
1160
+ type: "text",
1161
+ text: result.success ? result.result : `Error: ${result.error}`
1162
+ }
1163
+ ],
1164
+ isError: !result.success
1165
+ };
1166
+ });
1167
+ // Tool: iOS key event
1168
+ server.registerTool("ios_key_event", {
1169
+ description: "Send a key event to an iOS simulator by keycode. Requires IDB to be installed (brew install idb-companion).",
1170
+ inputSchema: {
1171
+ keycode: z.number().describe("iOS keycode to send"),
1172
+ duration: z.number().optional().describe("Optional key press duration in seconds"),
1173
+ udid: z
1174
+ .string()
1175
+ .optional()
1176
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1177
+ }
1178
+ }, async ({ keycode, duration, udid }) => {
1179
+ const result = await iosKeyEvent(keycode, { duration, udid });
1180
+ return {
1181
+ content: [
1182
+ {
1183
+ type: "text",
1184
+ text: result.success ? result.result : `Error: ${result.error}`
1185
+ }
1186
+ ],
1187
+ isError: !result.success
1188
+ };
1189
+ });
1190
+ // Tool: iOS key sequence
1191
+ server.registerTool("ios_key_sequence", {
1192
+ description: "Send a sequence of key events to an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
1193
+ inputSchema: {
1194
+ keycodes: z.array(z.number()).describe("Array of iOS keycodes to send in sequence"),
1195
+ udid: z
1196
+ .string()
1197
+ .optional()
1198
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1199
+ }
1200
+ }, async ({ keycodes, udid }) => {
1201
+ const result = await iosKeySequence(keycodes, udid);
1202
+ return {
1203
+ content: [
1204
+ {
1205
+ type: "text",
1206
+ text: result.success ? result.result : `Error: ${result.error}`
1207
+ }
1208
+ ],
1209
+ isError: !result.success
1210
+ };
1211
+ });
1212
+ // Tool: iOS describe all (accessibility tree)
1213
+ server.registerTool("ios_describe_all", {
1214
+ 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).",
1215
+ inputSchema: {
1216
+ udid: z
1217
+ .string()
1218
+ .optional()
1219
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1220
+ }
1221
+ }, async ({ udid }) => {
1222
+ const result = await iosDescribeAll(udid);
1223
+ return {
1224
+ content: [
1225
+ {
1226
+ type: "text",
1227
+ text: result.success ? result.result : `Error: ${result.error}`
1228
+ }
1229
+ ],
1230
+ isError: !result.success
1231
+ };
1232
+ });
1233
+ // Tool: iOS describe point
1234
+ server.registerTool("ios_describe_point", {
1235
+ 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).",
1236
+ inputSchema: {
1237
+ x: z.number().describe("X coordinate in pixels"),
1238
+ y: z.number().describe("Y coordinate in pixels"),
1239
+ udid: z
1240
+ .string()
1241
+ .optional()
1242
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
1243
+ }
1244
+ }, async ({ x, y, udid }) => {
1245
+ const result = await iosDescribePoint(x, y, udid);
1246
+ return {
1247
+ content: [
1248
+ {
1249
+ type: "text",
1250
+ text: result.success ? result.result : `Error: ${result.error}`
1251
+ }
1252
+ ],
1253
+ isError: !result.success
1254
+ };
1255
+ });
1256
+ // Tool: Get debug server info
1257
+ server.registerTool("get_debug_server", {
1258
+ description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
1259
+ inputSchema: {}
1260
+ }, async () => {
1261
+ const port = getDebugServerPort();
1262
+ if (!port) {
1263
+ return {
1264
+ content: [
1265
+ {
1266
+ type: "text",
1267
+ text: "Debug HTTP server is not running."
1268
+ }
1269
+ ],
1270
+ isError: true
1271
+ };
1272
+ }
1273
+ const info = {
1274
+ url: `http://localhost:${port}`,
1275
+ endpoints: {
1276
+ status: `http://localhost:${port}/api/status`,
1277
+ logs: `http://localhost:${port}/api/logs`,
1278
+ network: `http://localhost:${port}/api/network`,
1279
+ bundleErrors: `http://localhost:${port}/api/bundle-errors`,
1280
+ apps: `http://localhost:${port}/api/apps`
1281
+ }
1282
+ };
1283
+ return {
1284
+ content: [
1285
+ {
1286
+ type: "text",
1287
+ text: `Debug HTTP server running at:\n\n${JSON.stringify(info, null, 2)}`
1288
+ }
1289
+ ]
1290
+ };
1291
+ });
1013
1292
  // Main function
1014
1293
  async function main() {
1294
+ // Start debug HTTP server for buffer inspection (finds available port automatically)
1295
+ await startDebugHttpServer();
1015
1296
  const transport = new StdioServerTransport();
1016
1297
  await server.connect(transport);
1017
1298
  console.error("[rn-ai-debugger] Server started on stdio");