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.
- package/README.md +145 -0
- package/build/core/httpServer.d.ts +14 -0
- package/build/core/httpServer.d.ts.map +1 -0
- package/build/core/httpServer.js +622 -0
- package/build/core/httpServer.js.map +1 -0
- package/build/core/index.d.ts +3 -1
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +8 -2
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +80 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +591 -1
- package/build/core/ios.js.map +1 -1
- package/build/core/telemetry.d.ts +4 -0
- package/build/core/telemetry.d.ts.map +1 -0
- package/build/core/telemetry.js +225 -0
- package/build/core/telemetry.js.map +1 -0
- package/build/index.js +286 -5
- package/build/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
879
|
-
|
|
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
|
|
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");
|