react-native-ai-debugger 1.6.16 → 1.8.0
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 +8 -1
- package/build/__tests__/unit/exec.test.d.ts +2 -0
- package/build/__tests__/unit/exec.test.d.ts.map +1 -0
- package/build/__tests__/unit/exec.test.js +54 -0
- package/build/__tests__/unit/exec.test.js.map +1 -0
- package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInput.test.js +58 -0
- package/build/__tests__/unit/focusedInput.test.js.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.js +79 -0
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
- package/build/__tests__/unit/tap.test.js +46 -0
- package/build/__tests__/unit/tap.test.js.map +1 -1
- package/build/core/android.d.ts +3 -3
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +21 -14
- package/build/core/android.js.map +1 -1
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +3 -2
- package/build/core/connection.js.map +1 -1
- package/build/core/errors.d.ts +14 -0
- package/build/core/errors.d.ts.map +1 -0
- package/build/core/errors.js +17 -0
- package/build/core/errors.js.map +1 -0
- package/build/core/exec.d.ts +22 -0
- package/build/core/exec.d.ts.map +1 -0
- package/build/core/exec.js +54 -0
- package/build/core/exec.js.map +1 -0
- package/build/core/executor.d.ts +2 -0
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +232 -2
- package/build/core/executor.js.map +1 -1
- package/build/core/focusedInput.d.ts +27 -0
- package/build/core/focusedInput.d.ts.map +1 -0
- package/build/core/focusedInput.js +110 -0
- package/build/core/focusedInput.js.map +1 -0
- package/build/core/focusedInputTools.d.ts +21 -0
- package/build/core/focusedInputTools.d.ts.map +1 -0
- package/build/core/focusedInputTools.js +51 -0
- package/build/core/focusedInputTools.js.map +1 -0
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +15 -2
- package/build/core/guides.js.map +1 -1
- package/build/core/index.d.ts +1 -1
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +1 -1
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +8 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +142 -2
- package/build/core/ios.js.map +1 -1
- package/build/core/ocr.d.ts +13 -0
- package/build/core/ocr.d.ts.map +1 -1
- package/build/core/ocr.js +37 -15
- package/build/core/ocr.js.map +1 -1
- package/build/core/posthog.d.ts.map +1 -1
- package/build/core/posthog.js +7 -1
- package/build/core/posthog.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +158 -31
- package/build/index.js.map +1 -1
- package/build/pro/tap.d.ts +51 -5
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +457 -54
- package/build/pro/tap.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import { getGuideOverview, getGuideByTopic, getAvailableTopics, DECISION_TREE }
|
|
|
9
9
|
import { getLicenseStatus, getDashboardUrl, getUsageInfo, getPricingInfo, formatPlanPrice } from "./core/license.js";
|
|
10
10
|
import { API_BASE_URL } from "./core/config.js";
|
|
11
11
|
import { getPostHogClient, identifyIfDevMode, shutdownPostHog } from "./core/posthog.js";
|
|
12
|
+
import { UserInputError } from "./core/errors.js";
|
|
13
|
+
import { clearFocusedInput, dismissKeyboard, inputTextWithReplace } from "./core/focusedInputTools.js";
|
|
12
14
|
import { getInstallationId, getServerVersion, getPackageName, isDevMode, TELEMETRY_JSONL_PATH, categorizeError } from "./core/telemetry.js";
|
|
13
15
|
import { isSDKInstalled, querySDKNetwork, getSDKNetworkEntry, getSDKNetworkStats, clearSDKNetwork, querySDKConsole, getSDKConsoleStats, clearSDKConsole } from "./core/sdkBridge.js";
|
|
14
16
|
import { reduxDispatch, reduxGetState } from "./core/redux.js";
|
|
@@ -36,7 +38,7 @@ ANDROID_KEY_EVENTS, androidLongPress, androidSwipe, androidInputText, androidKey
|
|
|
36
38
|
// iOS
|
|
37
39
|
listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTerminateApp, iosBootSimulator,
|
|
38
40
|
// iOS UI driver tools
|
|
39
|
-
isUiDriverAvailable, iosButton, iosDescribeAll, IOS_BUTTON_TYPES, getDevicePixelRatio, getIOSSafeAreaTop,
|
|
41
|
+
isUiDriverAvailable, iosButton, iosInputText, iosDescribeAll, detectIOSSystemOverlay, formatIOSSystemOverlayWarning, IOS_BUTTON_TYPES, getDevicePixelRatio, getIOSSafeAreaTop,
|
|
40
42
|
// Telemetry
|
|
41
43
|
initTelemetry, trackToolInvocation, getTargetPlatform,
|
|
42
44
|
// Format utilities (TONL)
|
|
@@ -50,7 +52,7 @@ function resolveLogBuffer(device) {
|
|
|
50
52
|
if (device) {
|
|
51
53
|
const app = getConnectedAppByDevice(device);
|
|
52
54
|
if (!app)
|
|
53
|
-
throw new
|
|
55
|
+
throw new UserInputError(`No connected device matches "${device}"`);
|
|
54
56
|
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
55
57
|
return getLogBuffer(deviceName);
|
|
56
58
|
}
|
|
@@ -68,7 +70,7 @@ function resolveNetworkBuffer(device) {
|
|
|
68
70
|
if (device) {
|
|
69
71
|
const app = getConnectedAppByDevice(device);
|
|
70
72
|
if (!app)
|
|
71
|
-
throw new
|
|
73
|
+
throw new UserInputError(`No connected device matches "${device}"`);
|
|
72
74
|
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
73
75
|
return getNetworkBuffer(deviceName);
|
|
74
76
|
}
|
|
@@ -331,7 +333,14 @@ function registerToolWithTelemetry(toolName, config, handler, emptyResultDetecto
|
|
|
331
333
|
catch (error) {
|
|
332
334
|
success = false;
|
|
333
335
|
errorMessage = error instanceof Error ? error.message : String(error);
|
|
334
|
-
|
|
336
|
+
// H2 (Step 9): UserInputError marks agent-input mistakes (unknown
|
|
337
|
+
// device, missing predicate, ambiguous match). They flow through
|
|
338
|
+
// telemetry's trackToolInvocation in the finally block; we just
|
|
339
|
+
// skip the dedicated error-tracking pipe so the dashboard surfaces
|
|
340
|
+
// real product bugs rather than validation noise.
|
|
341
|
+
if (!(error instanceof UserInputError)) {
|
|
342
|
+
getPostHogClient()?.captureException(error, getInstallationId(), { tool: toolName, server_version: getServerVersion(), package_name: getPackageName() });
|
|
343
|
+
}
|
|
335
344
|
throw error;
|
|
336
345
|
}
|
|
337
346
|
finally {
|
|
@@ -727,12 +736,14 @@ registerToolWithTelemetry("tap", {
|
|
|
727
736
|
.boolean()
|
|
728
737
|
.optional()
|
|
729
738
|
.default(true)
|
|
730
|
-
.describe("
|
|
739
|
+
.describe("Return post-tap image bytes in the response. Default true. Set to false to drop the PNG bytes — verification still runs (set verify=false to skip that too). Combine with verify=true to get the meaningful/changeRate signal without paying the ~1MB-per-tap bandwidth cost."),
|
|
731
740
|
verify: z
|
|
732
741
|
.boolean()
|
|
733
742
|
.optional()
|
|
734
743
|
.describe("Run before/after screenshot diff to detect if the tap had a meaningful visual effect. " +
|
|
735
|
-
"Default: true for coordinate/accessibility/ocr strategies, false for fiber.
|
|
744
|
+
"Default: true for coordinate/accessibility/ocr strategies, false for fiber. " +
|
|
745
|
+
"Independent of `screenshot` — verify can run with screenshot=false (the diff is computed internally; image bytes are dropped). " +
|
|
746
|
+
"When skipped, the response contains `verification: { skipped: true, skippedReason }` so callers can tell apart \"ran clean\" from \"never ran\"."),
|
|
736
747
|
burst: z
|
|
737
748
|
.boolean()
|
|
738
749
|
.optional()
|
|
@@ -1292,12 +1303,15 @@ registerToolWithTelemetry("ios_screenshot", {
|
|
|
1292
1303
|
// explicitly when they need the tree. The Pressable elements block below is
|
|
1293
1304
|
// the signal most consumers actually want.
|
|
1294
1305
|
let pressablesText = null;
|
|
1295
|
-
|
|
1296
|
-
//
|
|
1306
|
+
// Enrich with pressable elements. With targetApp present we use the
|
|
1307
|
+
// fiber path (most accurate). Without targetApp, getPressableElements
|
|
1308
|
+
// falls back to iOS accessibility tree (E1) when we pass platform.
|
|
1297
1309
|
try {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1310
|
+
const pressables = await getPressableElements({
|
|
1311
|
+
device: targetDeviceName,
|
|
1312
|
+
platform: "ios",
|
|
1313
|
+
udid: resolvedUdid ?? undefined
|
|
1314
|
+
});
|
|
1301
1315
|
if (pressables.success && pressables.parsedElements && pressables.parsedElements.length > 0) {
|
|
1302
1316
|
const screenshotScale = result.scaleFactor || 1;
|
|
1303
1317
|
pressablesText = pressables.parsedElements.map((el) => {
|
|
@@ -1338,7 +1352,7 @@ registerToolWithTelemetry("ios_screenshot", {
|
|
|
1338
1352
|
infoText += `\n • get_screen_layout — full component tree when you need more than pressables`;
|
|
1339
1353
|
}
|
|
1340
1354
|
else {
|
|
1341
|
-
if (!
|
|
1355
|
+
if (!targetApp && connectedApps.size > 0) {
|
|
1342
1356
|
infoText += `\n\nℹ️ Pressable enrichment skipped: no RN app is connected to simulator ${resolvedUdid}.`;
|
|
1343
1357
|
infoText += ` ${connectedApps.size} other app(s) are connected on different device(s) — their fiber data was intentionally not used to avoid mismatched output.`;
|
|
1344
1358
|
}
|
|
@@ -1349,7 +1363,7 @@ registerToolWithTelemetry("ios_screenshot", {
|
|
|
1349
1363
|
}
|
|
1350
1364
|
// Check for LogBox overlay — only on the matching RN app; skip otherwise
|
|
1351
1365
|
// to avoid surfacing warnings from a different simulator's app.
|
|
1352
|
-
if (
|
|
1366
|
+
if (targetApp) {
|
|
1353
1367
|
try {
|
|
1354
1368
|
const logBoxState = await detectLogBox(targetDeviceName);
|
|
1355
1369
|
if (logBoxState && logBoxState.total > 0) {
|
|
@@ -1360,6 +1374,20 @@ registerToolWithTelemetry("ios_screenshot", {
|
|
|
1360
1374
|
// Non-fatal: LogBox detection failure should not break screenshot
|
|
1361
1375
|
}
|
|
1362
1376
|
}
|
|
1377
|
+
// I1 (2026-05-16): detect native iOS system overlays (auth sheets, alerts,
|
|
1378
|
+
// permission dialogs) that sit on top of the RN app. The pressables list
|
|
1379
|
+
// reflects the RN screen underneath; without this warning the agent will
|
|
1380
|
+
// happily tap inert RN buttons and loop. Runs whether or not there's a
|
|
1381
|
+
// matching RN app — the overlay belongs to the simulator, not the app.
|
|
1382
|
+
try {
|
|
1383
|
+
const overlay = await detectIOSSystemOverlay(resolvedUdid ?? undefined);
|
|
1384
|
+
if (overlay) {
|
|
1385
|
+
infoText += formatIOSSystemOverlayWarning(overlay);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
catch {
|
|
1389
|
+
// Non-fatal: overlay detection failure should not break screenshot
|
|
1390
|
+
}
|
|
1363
1391
|
imageBuffer.add({
|
|
1364
1392
|
id: `ios-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1365
1393
|
image: result.data,
|
|
@@ -1464,25 +1492,30 @@ registerToolWithTelemetry("android_screenshot", {
|
|
|
1464
1492
|
catch {
|
|
1465
1493
|
// Use defaults
|
|
1466
1494
|
}
|
|
1467
|
-
// Enrich with screen layout data (component names + tap coordinates)
|
|
1468
|
-
//
|
|
1469
|
-
|
|
1470
|
-
//
|
|
1471
|
-
//
|
|
1472
|
-
|
|
1495
|
+
// Enrich with screen layout data (component names + tap coordinates).
|
|
1496
|
+
// On Bridgeless/Fabric Android (the only target architecture we support;
|
|
1497
|
+
// legacy arch is <5% of users and not a priority), both code paths that
|
|
1498
|
+
// feed this enrichment return DEVICE PIXELS:
|
|
1499
|
+
// - fiber path: React's measureInWindow on Fabric returns native pixels.
|
|
1500
|
+
// - a11y fallback: uiautomator's bounds are already device pixels.
|
|
1501
|
+
// The earlier formula multiplied by densityDpi/160 on the (incorrect)
|
|
1502
|
+
// assumption that fiber returned DP — inflating every coordinate by
|
|
1503
|
+
// ~2.6× on a 420dpi emulator and producing numbers like (1170, 4054)
|
|
1504
|
+
// for a button visually sitting near (445, 1370) in the JPEG. Drop the
|
|
1505
|
+
// density factor; only the scaleFactor downscale is needed.
|
|
1473
1506
|
let pressablesText = null;
|
|
1474
1507
|
// Screen Layout tree previously appended here was dropped — it was noisy
|
|
1475
1508
|
// (nested Svg/G/Path duplicates). Use get_screen_layout when the tree is needed.
|
|
1476
|
-
// Enrich with pressable elements
|
|
1509
|
+
// Enrich with pressable elements. With targetApp present we use the
|
|
1510
|
+
// fiber path (most accurate). Without targetApp, getPressableElements
|
|
1511
|
+
// falls back to the Android accessibility (uiautomator) tree (E1).
|
|
1477
1512
|
try {
|
|
1478
|
-
|
|
1479
|
-
throw new Error("skip");
|
|
1480
|
-
const pressables = await getPressableElements({ device: targetDeviceName });
|
|
1513
|
+
const pressables = await getPressableElements({ device: targetDeviceName, platform: "android" });
|
|
1481
1514
|
if (pressables.success && pressables.parsedElements && pressables.parsedElements.length > 0) {
|
|
1482
1515
|
const screenshotScale = result.scaleFactor || 1;
|
|
1483
1516
|
pressablesText = pressables.parsedElements.map((el) => {
|
|
1484
|
-
const px = Math.round(
|
|
1485
|
-
const py = Math.round(
|
|
1517
|
+
const px = Math.round(el.center.x / screenshotScale);
|
|
1518
|
+
const py = Math.round(el.center.y / screenshotScale);
|
|
1486
1519
|
const label = el.accessibilityLabel || el.text || el.testID || (el.intent ? `${el.intent} icon` : el.component);
|
|
1487
1520
|
const idPart = el.testID ? ` testID="${el.testID}"` : "";
|
|
1488
1521
|
const kindPart = el.isInput ? " [input]" : "";
|
|
@@ -1515,7 +1548,7 @@ registerToolWithTelemetry("android_screenshot", {
|
|
|
1515
1548
|
infoText += `\n • get_screen_layout — full component tree when you need more than pressables`;
|
|
1516
1549
|
}
|
|
1517
1550
|
else {
|
|
1518
|
-
if (!
|
|
1551
|
+
if (!targetApp && connectedApps.size > 0) {
|
|
1519
1552
|
infoText += `\n\nℹ️ Pressable enrichment skipped: no RN app is connected to device ${deviceId ?? "(default)"}.`;
|
|
1520
1553
|
infoText += ` ${connectedApps.size} other app(s) are connected on different device(s) — their fiber data was intentionally not used to avoid mismatched output.`;
|
|
1521
1554
|
}
|
|
@@ -1526,7 +1559,7 @@ registerToolWithTelemetry("android_screenshot", {
|
|
|
1526
1559
|
}
|
|
1527
1560
|
// Check for LogBox overlay — only on the matching RN app; skip otherwise
|
|
1528
1561
|
// to avoid surfacing warnings from a different device's app.
|
|
1529
|
-
if (
|
|
1562
|
+
if (targetApp) {
|
|
1530
1563
|
try {
|
|
1531
1564
|
const logBoxState = await detectLogBox(targetDeviceName);
|
|
1532
1565
|
if (logBoxState && logBoxState.total > 0) {
|
|
@@ -1768,7 +1801,7 @@ registerToolWithTelemetry("clear_logs", {
|
|
|
1768
1801
|
if (device) {
|
|
1769
1802
|
const app = getConnectedAppByDevice(device);
|
|
1770
1803
|
if (!app)
|
|
1771
|
-
throw new
|
|
1804
|
+
throw new UserInputError(`No connected device matches "${device}"`);
|
|
1772
1805
|
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
1773
1806
|
const count = getLogBuffer(deviceName).clear();
|
|
1774
1807
|
return { content: [{ type: "text", text: `Cleared ${count} log entries from ${deviceName}.` }] };
|
|
@@ -3190,7 +3223,7 @@ registerToolWithTelemetry("clear_network", {
|
|
|
3190
3223
|
if (device) {
|
|
3191
3224
|
const app = getConnectedAppByDevice(device);
|
|
3192
3225
|
if (!app)
|
|
3193
|
-
throw new
|
|
3226
|
+
throw new UserInputError(`No connected device matches "${device}"`);
|
|
3194
3227
|
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
3195
3228
|
totalCleared = getNetworkBuffer(deviceName).clear();
|
|
3196
3229
|
}
|
|
@@ -3863,16 +3896,25 @@ registerToolWithTelemetry("android_input_text", {
|
|
|
3863
3896
|
" The text will be input at the current focus point (tap an input field first)." +
|
|
3864
3897
|
"\nPURPOSE: Send keystrokes to whichever input currently has focus on Android — the tool does NOT focus a field itself." +
|
|
3865
3898
|
"\nWHEN TO USE: Only after an input is already focused, or when `tap(text=...)` on the input didn't take focus for some reason." +
|
|
3899
|
+
"\nREPLACE MODE: pass replace:true to clear the focused field first (via React onChangeText so controlled state stays consistent), then type the new value. Use for pre-filled fields where appending would corrupt the value." +
|
|
3866
3900
|
"\nSEE ALSO: call get_usage_guide(topic=\"interact\") for the full UI-interaction playbook.",
|
|
3867
3901
|
inputSchema: {
|
|
3868
3902
|
text: z.string().describe("Text to type"),
|
|
3903
|
+
replace: z
|
|
3904
|
+
.boolean()
|
|
3905
|
+
.optional()
|
|
3906
|
+
.describe("If true, clear the focused TextInput via React onChangeText before typing. Use to set a pre-filled field to an exact value without concatenation. Requires Bridgeless/Fabric."),
|
|
3907
|
+
device: z
|
|
3908
|
+
.string()
|
|
3909
|
+
.optional()
|
|
3910
|
+
.describe("Optional RN device name (substring match) — needed by replace:true when multiple RN apps are connected, to disambiguate which device's focused input to clear. Single-device sessions can omit."),
|
|
3869
3911
|
deviceId: z
|
|
3870
3912
|
.string()
|
|
3871
3913
|
.optional()
|
|
3872
3914
|
.describe("Optional device ID. Uses first available device if not specified.")
|
|
3873
3915
|
}
|
|
3874
|
-
}, async ({ text, deviceId }) => {
|
|
3875
|
-
const result = await
|
|
3916
|
+
}, async ({ text, replace, device, deviceId }) => {
|
|
3917
|
+
const result = await inputTextWithReplace(text, replace === true, (t) => androidInputText(t, deviceId), () => clearFocusedInput(device));
|
|
3876
3918
|
return {
|
|
3877
3919
|
content: [
|
|
3878
3920
|
{
|
|
@@ -4254,6 +4296,91 @@ server.registerTool("ios_button", {
|
|
|
4254
4296
|
isError: !result.success
|
|
4255
4297
|
};
|
|
4256
4298
|
});
|
|
4299
|
+
// Tool: Clear focused text input
|
|
4300
|
+
registerToolWithTelemetry("clear_focused_input", {
|
|
4301
|
+
description: "Clear the contents of the currently focused TextInput, updating React state correctly so controlled components (Formik, react-hook-form, useState) stay consistent." +
|
|
4302
|
+
"\nPURPOSE: Reset whatever TextInput has focus to empty, with the React state owner notified via onChangeText. Use BEFORE typing a replacement value into a pre-filled field." +
|
|
4303
|
+
"\nWHEN TO USE: After tap(testID=...) focuses an input that already has text. Pair with ios_input_text/android_input_text (or use their replace:true flag for one-shot)." +
|
|
4304
|
+
"\nLIMITATIONS: Requires Bridgeless/Fabric (RN new architecture). Returns 'no focused TextInput' if nothing is focused — does not silently no-op." +
|
|
4305
|
+
"\nSEE ALSO: dismiss_keyboard, ios_input_text({replace:true}), android_input_text({replace:true}). call get_usage_guide(topic=\"interact\") for the full UI-interaction playbook.",
|
|
4306
|
+
inputSchema: {
|
|
4307
|
+
device: z
|
|
4308
|
+
.string()
|
|
4309
|
+
.optional()
|
|
4310
|
+
.describe("Optional device name (substring match). Uses default device if not specified.")
|
|
4311
|
+
}
|
|
4312
|
+
}, async ({ device }) => {
|
|
4313
|
+
const result = await clearFocusedInput(device);
|
|
4314
|
+
return {
|
|
4315
|
+
content: [
|
|
4316
|
+
{
|
|
4317
|
+
type: "text",
|
|
4318
|
+
text: result.success ? `Cleared focused input (via ${result.via}).` : `Error: ${result.error}`
|
|
4319
|
+
}
|
|
4320
|
+
],
|
|
4321
|
+
isError: !result.success
|
|
4322
|
+
};
|
|
4323
|
+
});
|
|
4324
|
+
// Tool: Dismiss keyboard
|
|
4325
|
+
registerToolWithTelemetry("dismiss_keyboard", {
|
|
4326
|
+
description: "Blur the currently focused TextInput, dismissing the on-screen keyboard." +
|
|
4327
|
+
"\nPURPOSE: Close the keyboard when it's blocking content beneath the input, or move focus off an input before a tap that would otherwise be intercepted." +
|
|
4328
|
+
"\nWHEN TO USE: After typing into a field and before tapping a button that is hidden by the keyboard. Or to verify a 'tap outside dismisses' UX is wired up." +
|
|
4329
|
+
"\nLIMITATIONS: Requires Bridgeless/Fabric (RN new architecture). Returns 'no focused TextInput' if nothing is focused.",
|
|
4330
|
+
inputSchema: {
|
|
4331
|
+
device: z
|
|
4332
|
+
.string()
|
|
4333
|
+
.optional()
|
|
4334
|
+
.describe("Optional device name (substring match). Uses default device if not specified.")
|
|
4335
|
+
}
|
|
4336
|
+
}, async ({ device }) => {
|
|
4337
|
+
const result = await dismissKeyboard(device);
|
|
4338
|
+
return {
|
|
4339
|
+
content: [
|
|
4340
|
+
{
|
|
4341
|
+
type: "text",
|
|
4342
|
+
text: result.success
|
|
4343
|
+
? `Dismissed keyboard (nativeTag ${result.nativeTag}).`
|
|
4344
|
+
: `Error: ${result.error}`
|
|
4345
|
+
}
|
|
4346
|
+
],
|
|
4347
|
+
isError: !result.success
|
|
4348
|
+
};
|
|
4349
|
+
});
|
|
4350
|
+
// Tool: iOS input text
|
|
4351
|
+
registerToolWithTelemetry("ios_input_text", {
|
|
4352
|
+
description: "Type text on an iOS simulator." +
|
|
4353
|
+
platformFallbackBanner("`tap(text=...)` — it auto-focuses TextInput via the fiber tree") +
|
|
4354
|
+
" The text is typed into whichever field currently has focus (tap an input first). Mirrors `android_input_text` so cross-platform agents can use `<platform>_input_text` without branching on the iOS driver shell-out." +
|
|
4355
|
+
"\nPURPOSE: Send keystrokes to the focused field on an iOS simulator via the active UI driver (AXe — preferred — or IDB)." +
|
|
4356
|
+
"\nWHEN TO USE: Only after an input is already focused, or when `tap(testID=...)` on the input didn't take focus for some reason. Use the testID-first flow whenever possible — it's faster and survives UI repositioning." +
|
|
4357
|
+
"\nREPLACE MODE: pass replace:true to clear the focused field first (via React onChangeText so controlled state stays consistent), then type the new value. Use for pre-filled fields where appending would corrupt the value." +
|
|
4358
|
+
"\nLIMITATIONS: AXe types via the US-keyboard HID — non-ASCII characters (Cyrillic, CJK, Arabic) may not transmit correctly. If the active driver is AXe and the text contains non-ASCII chars, prefer pasting via the simulator pasteboard or setting IOS_DRIVER=idb." +
|
|
4359
|
+
"\nSEE ALSO: call get_usage_guide(topic=\"interact\") for the full UI-interaction playbook.",
|
|
4360
|
+
inputSchema: {
|
|
4361
|
+
text: z.string().describe("Text to type into the currently focused field."),
|
|
4362
|
+
replace: z
|
|
4363
|
+
.boolean()
|
|
4364
|
+
.optional()
|
|
4365
|
+
.describe("If true, clear the focused TextInput via React onChangeText before typing. Use to set a pre-filled field to an exact value without concatenation. Requires Bridgeless/Fabric."),
|
|
4366
|
+
device: z
|
|
4367
|
+
.string()
|
|
4368
|
+
.optional()
|
|
4369
|
+
.describe("Optional RN device name (substring match) — needed by replace:true when multiple RN apps are connected, to disambiguate which device's focused input to clear. Single-device sessions can omit."),
|
|
4370
|
+
udid: z.string().optional().describe("Optional simulator UDID (from list_ios_simulators). Uses booted simulator if not specified.")
|
|
4371
|
+
}
|
|
4372
|
+
}, async ({ text, replace, device, udid }) => {
|
|
4373
|
+
const result = await inputTextWithReplace(text, replace === true, (t) => iosInputText(t, udid), () => clearFocusedInput(device));
|
|
4374
|
+
return {
|
|
4375
|
+
content: [
|
|
4376
|
+
{
|
|
4377
|
+
type: "text",
|
|
4378
|
+
text: result.success ? result.result : `Error: ${result.error}`
|
|
4379
|
+
}
|
|
4380
|
+
],
|
|
4381
|
+
isError: !result.success
|
|
4382
|
+
};
|
|
4383
|
+
});
|
|
4257
4384
|
// Tool: Activate Pro license
|
|
4258
4385
|
registerToolWithTelemetry("activate_license", getActivateLicenseConfig(), handleActivateLicense);
|
|
4259
4386
|
// Tool: Delete account
|