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.
- package/README.md +161 -0
- package/build/core/android.d.ts +63 -0
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +391 -0
- package/build/core/android.js.map +1 -1
- package/build/core/index.d.ts +4 -2
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +9 -3
- 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 +348 -5
- package/build/index.js.map +1 -1
- package/package.json +2 -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
|
@@ -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
|
|
881
|
-
|
|
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
|
|
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.",
|