execbro 0.0.1-reserve → 1.10.13
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/LICENSE +32 -0
- package/README.md +496 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts +56 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -0
- package/build/__tests__/helpers/fake-cdp-server.js +120 -0
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts +36 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.js +130 -0
- package/build/__tests__/helpers/tap-test-helpers.js.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts +2 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.js +118 -0
- package/build/__tests__/integration/auto-reconnect.test.js.map +1 -0
- package/build/__tests__/integration/connection-health.test.d.ts +2 -0
- package/build/__tests__/integration/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/integration/connection-health.test.js +157 -0
- package/build/__tests__/integration/connection-health.test.js.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts +2 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.js +124 -0
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts +2 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.js +103 -0
- package/build/__tests__/integration/per-call-timeout.test.js.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts +2 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.js +330 -0
- package/build/__tests__/integration/tap-strategies.test.js.map +1 -0
- package/build/__tests__/integration/tools.test.d.ts +2 -0
- package/build/__tests__/integration/tools.test.d.ts.map +1 -0
- package/build/__tests__/integration/tools.test.js +227 -0
- package/build/__tests__/integration/tools.test.js.map +1 -0
- package/build/__tests__/setup.d.ts +2 -0
- package/build/__tests__/setup.d.ts.map +1 -0
- package/build/__tests__/setup.js +15 -0
- package/build/__tests__/setup.js.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts +2 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js +98 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -0
- package/build/__tests__/unit/buildToken.test.d.ts +2 -0
- package/build/__tests__/unit/buildToken.test.d.ts.map +1 -0
- package/build/__tests__/unit/buildToken.test.js +22 -0
- package/build/__tests__/unit/buildToken.test.js.map +1 -0
- package/build/__tests__/unit/bundle.test.d.ts +2 -0
- package/build/__tests__/unit/bundle.test.d.ts.map +1 -0
- package/build/__tests__/unit/bundle.test.js +53 -0
- package/build/__tests__/unit/bundle.test.js.map +1 -0
- package/build/__tests__/unit/burst.test.d.ts +2 -0
- package/build/__tests__/unit/burst.test.d.ts.map +1 -0
- package/build/__tests__/unit/burst.test.js +54 -0
- package/build/__tests__/unit/burst.test.js.map +1 -0
- package/build/__tests__/unit/connection-health.test.d.ts +2 -0
- package/build/__tests__/unit/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/unit/connection-health.test.js +38 -0
- package/build/__tests__/unit/connection-health.test.js.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts +2 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.js +14 -0
- package/build/__tests__/unit/debugGlobals.test.js.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts +2 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.js +119 -0
- package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts +2 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.js +261 -0
- package/build/__tests__/unit/deviceResolver.test.js.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts +2 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.js +76 -0
- package/build/__tests__/unit/escapeNonAscii.test.js.map +1 -0
- 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/executor.test.d.ts +2 -0
- package/build/__tests__/unit/executor.test.d.ts.map +1 -0
- package/build/__tests__/unit/executor.test.js +220 -0
- package/build/__tests__/unit/executor.test.js.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts +2 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.js +207 -0
- package/build/__tests__/unit/failureArtifact.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js +68 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.js +112 -0
- package/build/__tests__/unit/fastRefreshTools.test.js.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts +2 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.js +114 -0
- package/build/__tests__/unit/fiberMatch.test.js.map +1 -0
- package/build/__tests__/unit/fingerprint.test.d.ts +2 -0
- package/build/__tests__/unit/fingerprint.test.d.ts.map +1 -0
- package/build/__tests__/unit/fingerprint.test.js +99 -0
- package/build/__tests__/unit/fingerprint.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/iconSemantics.test.d.ts +2 -0
- package/build/__tests__/unit/iconSemantics.test.d.ts.map +1 -0
- package/build/__tests__/unit/iconSemantics.test.js +80 -0
- package/build/__tests__/unit/iconSemantics.test.js.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts +2 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.js +128 -0
- package/build/__tests__/unit/imageBuffer.test.js.map +1 -0
- package/build/__tests__/unit/logs.test.d.ts +2 -0
- package/build/__tests__/unit/logs.test.d.ts.map +1 -0
- package/build/__tests__/unit/logs.test.js +90 -0
- package/build/__tests__/unit/logs.test.js.map +1 -0
- package/build/__tests__/unit/measureComponent.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponent.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponent.test.js +66 -0
- package/build/__tests__/unit/measureComponent.test.js.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.js +94 -0
- package/build/__tests__/unit/measureComponentTools.test.js.map +1 -0
- package/build/__tests__/unit/metro.test.d.ts +2 -0
- package/build/__tests__/unit/metro.test.d.ts.map +1 -0
- package/build/__tests__/unit/metro.test.js +61 -0
- package/build/__tests__/unit/metro.test.js.map +1 -0
- package/build/__tests__/unit/network.test.d.ts +2 -0
- package/build/__tests__/unit/network.test.d.ts.map +1 -0
- package/build/__tests__/unit/network.test.js +102 -0
- package/build/__tests__/unit/network.test.js.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts +2 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.js +104 -0
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -0
- package/build/__tests__/unit/probe.test.d.ts +2 -0
- package/build/__tests__/unit/probe.test.d.ts.map +1 -0
- package/build/__tests__/unit/probe.test.js +62 -0
- package/build/__tests__/unit/probe.test.js.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts +2 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js +29 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -0
- package/build/__tests__/unit/screenState.test.d.ts +2 -0
- package/build/__tests__/unit/screenState.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenState.test.js +384 -0
- package/build/__tests__/unit/screenState.test.js.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts +2 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.js +97 -0
- package/build/__tests__/unit/screenshot-diff.test.js.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts +2 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.js +89 -0
- package/build/__tests__/unit/tap-evidence.test.js.map +1 -0
- package/build/__tests__/unit/tap.test.d.ts +2 -0
- package/build/__tests__/unit/tap.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap.test.js +767 -0
- package/build/__tests__/unit/tap.test.js.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts +2 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.js +17 -0
- package/build/__tests__/unit/timeoutClamp.test.js.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts +2 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.js +145 -0
- package/build/__tests__/unit/toolDescriptions.test.js.map +1 -0
- package/build/__tests__/unit/transportClassify.test.d.ts +2 -0
- package/build/__tests__/unit/transportClassify.test.d.ts.map +1 -0
- package/build/__tests__/unit/transportClassify.test.js +60 -0
- package/build/__tests__/unit/transportClassify.test.js.map +1 -0
- package/build/__tests__/unit/verifyAction.test.d.ts +2 -0
- package/build/__tests__/unit/verifyAction.test.d.ts.map +1 -0
- package/build/__tests__/unit/verifyAction.test.js +26 -0
- package/build/__tests__/unit/verifyAction.test.js.map +1 -0
- package/build/core/android.d.ts +299 -0
- package/build/core/android.d.ts.map +1 -0
- package/build/core/android.js +1582 -0
- package/build/core/android.js.map +1 -0
- package/build/core/appDetection.d.ts +20 -0
- package/build/core/appDetection.d.ts.map +1 -0
- package/build/core/appDetection.js +167 -0
- package/build/core/appDetection.js.map +1 -0
- package/build/core/bundle.d.ts +49 -0
- package/build/core/bundle.d.ts.map +1 -0
- package/build/core/bundle.js +356 -0
- package/build/core/bundle.js.map +1 -0
- package/build/core/componentSearch.d.ts +41 -0
- package/build/core/componentSearch.d.ts.map +1 -0
- package/build/core/componentSearch.js +588 -0
- package/build/core/componentSearch.js.map +1 -0
- package/build/core/componentTree.d.ts +13 -0
- package/build/core/componentTree.d.ts.map +1 -0
- package/build/core/componentTree.js +275 -0
- package/build/core/componentTree.js.map +1 -0
- package/build/core/config.d.ts +9 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +28 -0
- package/build/core/config.js.map +1 -0
- package/build/core/connection.d.ts +102 -0
- package/build/core/connection.d.ts.map +1 -0
- package/build/core/connection.js +1710 -0
- package/build/core/connection.js.map +1 -0
- package/build/core/connectionState.d.ts +122 -0
- package/build/core/connectionState.d.ts.map +1 -0
- package/build/core/connectionState.js +313 -0
- package/build/core/connectionState.js.map +1 -0
- package/build/core/debugGlobals.d.ts +6 -0
- package/build/core/debugGlobals.d.ts.map +1 -0
- package/build/core/debugGlobals.js +370 -0
- package/build/core/debugGlobals.js.map +1 -0
- package/build/core/deviceDiscovery.d.ts +52 -0
- package/build/core/deviceDiscovery.d.ts.map +1 -0
- package/build/core/deviceDiscovery.js +74 -0
- package/build/core/deviceDiscovery.js.map +1 -0
- package/build/core/deviceResolver.d.ts +44 -0
- package/build/core/deviceResolver.d.ts.map +1 -0
- package/build/core/deviceResolver.js +225 -0
- package/build/core/deviceResolver.js.map +1 -0
- package/build/core/errorScreenParser.d.ts +30 -0
- package/build/core/errorScreenParser.d.ts.map +1 -0
- package/build/core/errorScreenParser.js +198 -0
- package/build/core/errorScreenParser.js.map +1 -0
- 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/escapeNonAscii.d.ts +18 -0
- package/build/core/escapeNonAscii.d.ts.map +1 -0
- package/build/core/escapeNonAscii.js +352 -0
- package/build/core/escapeNonAscii.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 +9 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +12 -0
- package/build/core/executor.js.map +1 -0
- package/build/core/failureArtifact.d.ts +120 -0
- package/build/core/failureArtifact.d.ts.map +1 -0
- package/build/core/failureArtifact.js +113 -0
- package/build/core/failureArtifact.js.map +1 -0
- package/build/core/fastRefreshRecorder.d.ts +23 -0
- package/build/core/fastRefreshRecorder.d.ts.map +1 -0
- package/build/core/fastRefreshRecorder.js +83 -0
- package/build/core/fastRefreshRecorder.js.map +1 -0
- package/build/core/fastRefreshTools.d.ts +19 -0
- package/build/core/fastRefreshTools.d.ts.map +1 -0
- package/build/core/fastRefreshTools.js +71 -0
- package/build/core/fastRefreshTools.js.map +1 -0
- package/build/core/feedback.d.ts +18 -0
- package/build/core/feedback.d.ts.map +1 -0
- package/build/core/feedback.js +73 -0
- package/build/core/feedback.js.map +1 -0
- package/build/core/fiberMatch.d.ts +25 -0
- package/build/core/fiberMatch.d.ts.map +1 -0
- package/build/core/fiberMatch.js +35 -0
- package/build/core/fiberMatch.js.map +1 -0
- package/build/core/fingerprint.d.ts +4 -0
- package/build/core/fingerprint.d.ts.map +1 -0
- package/build/core/fingerprint.js +47 -0
- package/build/core/fingerprint.js.map +1 -0
- 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/format.d.ts +8 -0
- package/build/core/format.d.ts.map +1 -0
- package/build/core/format.js +34 -0
- package/build/core/format.js.map +1 -0
- package/build/core/guides.d.ts +21 -0
- package/build/core/guides.d.ts.map +1 -0
- package/build/core/guides.js +397 -0
- package/build/core/guides.js.map +1 -0
- package/build/core/iconSemantics.d.ts +18 -0
- package/build/core/iconSemantics.d.ts.map +1 -0
- package/build/core/iconSemantics.js +114 -0
- package/build/core/iconSemantics.js.map +1 -0
- package/build/core/imageBuffer.d.ts +37 -0
- package/build/core/imageBuffer.d.ts.map +1 -0
- package/build/core/imageBuffer.js +58 -0
- package/build/core/imageBuffer.js.map +1 -0
- package/build/core/index.d.ts +37 -0
- package/build/core/index.d.ts.map +1 -0
- package/build/core/index.js +66 -0
- package/build/core/index.js.map +1 -0
- package/build/core/inspector.d.ts +55 -0
- package/build/core/inspector.d.ts.map +1 -0
- package/build/core/inspector.js +603 -0
- package/build/core/inspector.js.map +1 -0
- package/build/core/ios.d.ts +263 -0
- package/build/core/ios.d.ts.map +1 -0
- package/build/core/ios.js +1705 -0
- package/build/core/ios.js.map +1 -0
- package/build/core/jsExecute.d.ts +62 -0
- package/build/core/jsExecute.d.ts.map +1 -0
- package/build/core/jsExecute.js +696 -0
- package/build/core/jsExecute.js.map +1 -0
- package/build/core/license.d.ts +48 -0
- package/build/core/license.d.ts.map +1 -0
- package/build/core/license.js +267 -0
- package/build/core/license.js.map +1 -0
- package/build/core/logbox.d.ts +58 -0
- package/build/core/logbox.d.ts.map +1 -0
- package/build/core/logbox.js +330 -0
- package/build/core/logbox.js.map +1 -0
- package/build/core/logs.d.ts +44 -0
- package/build/core/logs.d.ts.map +1 -0
- package/build/core/logs.js +149 -0
- package/build/core/logs.js.map +1 -0
- package/build/core/measureComponent.d.ts +19 -0
- package/build/core/measureComponent.d.ts.map +1 -0
- package/build/core/measureComponent.js +94 -0
- package/build/core/measureComponent.js.map +1 -0
- package/build/core/measureComponentTools.d.ts +6 -0
- package/build/core/measureComponentTools.d.ts.map +1 -0
- package/build/core/measureComponentTools.js +43 -0
- package/build/core/measureComponentTools.js.map +1 -0
- package/build/core/metro.d.ts +31 -0
- package/build/core/metro.d.ts.map +1 -0
- package/build/core/metro.js +124 -0
- package/build/core/metro.js.map +1 -0
- package/build/core/nativeOnlyHints.d.ts +14 -0
- package/build/core/nativeOnlyHints.d.ts.map +1 -0
- package/build/core/nativeOnlyHints.js +58 -0
- package/build/core/nativeOnlyHints.js.map +1 -0
- package/build/core/network.d.ts +43 -0
- package/build/core/network.d.ts.map +1 -0
- package/build/core/network.js +217 -0
- package/build/core/network.js.map +1 -0
- package/build/core/networkInterceptor.d.ts +30 -0
- package/build/core/networkInterceptor.d.ts.map +1 -0
- package/build/core/networkInterceptor.js +211 -0
- package/build/core/networkInterceptor.js.map +1 -0
- package/build/core/ocr.d.ts +87 -0
- package/build/core/ocr.d.ts.map +1 -0
- package/build/core/ocr.js +335 -0
- package/build/core/ocr.js.map +1 -0
- package/build/core/paths.d.ts +2 -0
- package/build/core/paths.d.ts.map +1 -0
- package/build/core/paths.js +28 -0
- package/build/core/paths.js.map +1 -0
- package/build/core/posthog.d.ts +5 -0
- package/build/core/posthog.d.ts.map +1 -0
- package/build/core/posthog.js +52 -0
- package/build/core/posthog.js.map +1 -0
- package/build/core/pressables.d.ts +46 -0
- package/build/core/pressables.d.ts.map +1 -0
- package/build/core/pressables.js +1719 -0
- package/build/core/pressables.js.map +1 -0
- package/build/core/probe.d.ts +12 -0
- package/build/core/probe.d.ts.map +1 -0
- package/build/core/probe.js +56 -0
- package/build/core/probe.js.map +1 -0
- package/build/core/redux.d.ts +22 -0
- package/build/core/redux.d.ts.map +1 -0
- package/build/core/redux.js +116 -0
- package/build/core/redux.js.map +1 -0
- package/build/core/register.d.ts +8 -0
- package/build/core/register.d.ts.map +1 -0
- package/build/core/register.js +202 -0
- package/build/core/register.js.map +1 -0
- package/build/core/rnGlobalsBootstrap.d.ts +16 -0
- package/build/core/rnGlobalsBootstrap.d.ts.map +1 -0
- package/build/core/rnGlobalsBootstrap.js +116 -0
- package/build/core/rnGlobalsBootstrap.js.map +1 -0
- package/build/core/screenLayout.d.ts +53 -0
- package/build/core/screenLayout.d.ts.map +1 -0
- package/build/core/screenLayout.js +878 -0
- package/build/core/screenLayout.js.map +1 -0
- package/build/core/screenState.d.ts +166 -0
- package/build/core/screenState.d.ts.map +1 -0
- package/build/core/screenState.js +1352 -0
- package/build/core/screenState.js.map +1 -0
- package/build/core/sdkBridge.d.ts +67 -0
- package/build/core/sdkBridge.d.ts.map +1 -0
- package/build/core/sdkBridge.js +148 -0
- package/build/core/sdkBridge.js.map +1 -0
- package/build/core/state.d.ts +37 -0
- package/build/core/state.d.ts.map +1 -0
- package/build/core/state.js +124 -0
- package/build/core/state.js.map +1 -0
- package/build/core/telemetry.d.ts +50 -0
- package/build/core/telemetry.d.ts.map +1 -0
- package/build/core/telemetry.js +653 -0
- package/build/core/telemetry.js.map +1 -0
- package/build/core/toolHelpers.d.ts +24 -0
- package/build/core/toolHelpers.d.ts.map +1 -0
- package/build/core/toolHelpers.js +118 -0
- package/build/core/toolHelpers.js.map +1 -0
- package/build/core/types.d.ts +162 -0
- package/build/core/types.d.ts.map +1 -0
- package/build/core/types.js +2 -0
- package/build/core/types.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +132 -0
- package/build/index.js.map +1 -0
- package/build/pro/screenshot-diff.d.ts +10 -0
- package/build/pro/screenshot-diff.d.ts.map +1 -0
- package/build/pro/screenshot-diff.js +38 -0
- package/build/pro/screenshot-diff.js.map +1 -0
- package/build/pro/tap.d.ts +291 -0
- package/build/pro/tap.d.ts.map +1 -0
- package/build/pro/tap.js +1839 -0
- package/build/pro/tap.js.map +1 -0
- package/build/pro/verifyAction.d.ts +50 -0
- package/build/pro/verifyAction.d.ts.map +1 -0
- package/build/pro/verifyAction.js +285 -0
- package/build/pro/verifyAction.js.map +1 -0
- package/build/tools/_deviceArg.d.ts +45 -0
- package/build/tools/_deviceArg.d.ts.map +1 -0
- package/build/tools/_deviceArg.js +58 -0
- package/build/tools/_deviceArg.js.map +1 -0
- package/build/tools/accountTools.d.ts +46 -0
- package/build/tools/accountTools.d.ts.map +1 -0
- package/build/tools/accountTools.js +223 -0
- package/build/tools/accountTools.js.map +1 -0
- package/build/tools/bundleTools.d.ts +3 -0
- package/build/tools/bundleTools.d.ts.map +1 -0
- package/build/tools/bundleTools.js +375 -0
- package/build/tools/bundleTools.js.map +1 -0
- package/build/tools/componentTools.d.ts +3 -0
- package/build/tools/componentTools.d.ts.map +1 -0
- package/build/tools/componentTools.js +746 -0
- package/build/tools/componentTools.js.map +1 -0
- package/build/tools/connectionTools.d.ts +3 -0
- package/build/tools/connectionTools.d.ts.map +1 -0
- package/build/tools/connectionTools.js +625 -0
- package/build/tools/connectionTools.js.map +1 -0
- package/build/tools/deviceTools.d.ts +3 -0
- package/build/tools/deviceTools.d.ts.map +1 -0
- package/build/tools/deviceTools.js +423 -0
- package/build/tools/deviceTools.js.map +1 -0
- package/build/tools/executionTools.d.ts +3 -0
- package/build/tools/executionTools.d.ts.map +1 -0
- package/build/tools/executionTools.js +243 -0
- package/build/tools/executionTools.js.map +1 -0
- package/build/tools/interactionTools.d.ts +3 -0
- package/build/tools/interactionTools.d.ts.map +1 -0
- package/build/tools/interactionTools.js +539 -0
- package/build/tools/interactionTools.js.map +1 -0
- package/build/tools/logTools.d.ts +3 -0
- package/build/tools/logTools.d.ts.map +1 -0
- package/build/tools/logTools.js +371 -0
- package/build/tools/logTools.js.map +1 -0
- package/build/tools/metaTools.d.ts +7 -0
- package/build/tools/metaTools.d.ts.map +1 -0
- package/build/tools/metaTools.js +186 -0
- package/build/tools/metaTools.js.map +1 -0
- package/build/tools/networkTools.d.ts +3 -0
- package/build/tools/networkTools.d.ts.map +1 -0
- package/build/tools/networkTools.js +478 -0
- package/build/tools/networkTools.js.map +1 -0
- package/build/tools/reduxTools.d.ts +3 -0
- package/build/tools/reduxTools.d.ts.map +1 -0
- package/build/tools/reduxTools.js +83 -0
- package/build/tools/reduxTools.js.map +1 -0
- package/build/tools/screenshotTools.d.ts +3 -0
- package/build/tools/screenshotTools.d.ts.map +1 -0
- package/build/tools/screenshotTools.js +703 -0
- package/build/tools/screenshotTools.js.map +1 -0
- package/package.json +63 -2
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerToolWithTelemetry } from "../core/register.js";
|
|
3
|
+
import { resolveAndroidDeviceId, resolveIosUdid } from "./_deviceArg.js";
|
|
4
|
+
import { iconLabel } from "../core/iconSemantics.js";
|
|
5
|
+
import { iosScreenshot, androidScreenshot, detectLogBox, formatLogBoxWarning, recognizeText, inferIOSDevicePixelRatio, getPressableElements, getScreenState, formatScreenStateSummary, imageBuffer, getActiveOrBootedSimulatorUdid, connectedApps, getConnectedAppBySimulatorUdid, getConnectedAppByAndroidDeviceId, iosDescribeAll, detectIOSSystemOverlay, formatIOSSystemOverlayWarning, androidGetStatusBarHeight, androidGetDensity, } from "../core/index.js";
|
|
6
|
+
export function registerScreenshotTools(server) {
|
|
7
|
+
// Tool: iOS screenshot
|
|
8
|
+
registerToolWithTelemetry(server, "ios_screenshot", {
|
|
9
|
+
description: "Take a screenshot from an iOS simulator. Returns the image plus a screen-state summary: active route (name + navigation stack), overlay-grouped tappable elements (pressables behind an open sheet/modal are excluded), component names as JSX tags, labels, testIDs, and frames — all in ready-to-tap pixel coordinates. Prefer tap(text=\"...\") when text is exact and unique; otherwise use tap(x, y) with coordinates from the list — this is the most reliable way to tap icons or visually-identified elements. Use component names for inspect_component/find_components.\n" +
|
|
10
|
+
"PURPOSE: Snapshot what the user sees on iOS AND get tap-ready pressables + a structured component map in one call.\n" +
|
|
11
|
+
"WHEN TO USE: Any visual verification, before/after comparison, or as the starting point for tapping UI by coordinates.\n" +
|
|
12
|
+
"WORKFLOW: ios_screenshot -> pick element from pressables -> tap(x, y) or tap(testID=...) -> ios_screenshot to verify.\n" +
|
|
13
|
+
"LIMITATIONS: Requires a booted iOS simulator (simctl). For physical devices or system dialogs without RN, combine with tap(..., native=true).\n" +
|
|
14
|
+
"GOOD: ios_screenshot()\n" +
|
|
15
|
+
"BAD: ios_screenshot({ udid: \"guess\" }) with a made-up UDID — run list_ios_simulators first.\n" +
|
|
16
|
+
"SEE ALSO: call get_usage_guide(topic=\"interact\") for the full device-interaction playbook.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
outputPath: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional path to save the screenshot. If not provided, saves to temp directory."),
|
|
22
|
+
udid: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Optional iOS target. Accepts a simulator UDID, the simulator name (e.g. 'iPhone 17 Pro'), or a substring of the connected RN device name. Uses booted simulator if not specified.")
|
|
26
|
+
}
|
|
27
|
+
}, async ({ outputPath, udid }) => {
|
|
28
|
+
const resolved = await resolveIosUdid(udid);
|
|
29
|
+
if (!resolved.ok)
|
|
30
|
+
return resolved.response;
|
|
31
|
+
const result = await iosScreenshot(outputPath, resolved.udid);
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `Error: ${result.error}`
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
isError: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Include image data if available
|
|
44
|
+
if (result.data) {
|
|
45
|
+
// Build info text with coordinate guidance for iOS
|
|
46
|
+
const pixelWidth = result.originalWidth || 0;
|
|
47
|
+
const pixelHeight = result.originalHeight || 0;
|
|
48
|
+
// Resolve the RN app running on THIS simulator so enrichment pulls
|
|
49
|
+
// fiber/layout data from the right app (not the first-connected one,
|
|
50
|
+
// which may belong to a different simulator).
|
|
51
|
+
const resolvedUdid = udid || (await getActiveOrBootedSimulatorUdid());
|
|
52
|
+
const targetApp = resolvedUdid ? getConnectedAppBySimulatorUdid(resolvedUdid) : null;
|
|
53
|
+
const targetDeviceName = targetApp?.deviceInfo.deviceName;
|
|
54
|
+
// Store screenshot metadata on the matching app (not an arbitrary one)
|
|
55
|
+
if (targetApp) {
|
|
56
|
+
targetApp.lastScreenshot = {
|
|
57
|
+
originalWidth: pixelWidth,
|
|
58
|
+
originalHeight: pixelHeight,
|
|
59
|
+
scaleFactor: result.scaleFactor || 1,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Try to get actual screen dimensions and safe area from accessibility tree
|
|
63
|
+
let pointWidth = 0;
|
|
64
|
+
let pointHeight = 0;
|
|
65
|
+
let scaleFactor = 3; // Default to 3x for modern iPhones
|
|
66
|
+
let safeAreaTop = 59; // Default safe area offset
|
|
67
|
+
try {
|
|
68
|
+
const describeResult = await iosDescribeAll(udid);
|
|
69
|
+
if (describeResult.success && describeResult.elements && describeResult.elements.length > 0) {
|
|
70
|
+
// First element is typically the Application with full screen frame
|
|
71
|
+
const rootElement = describeResult.elements[0];
|
|
72
|
+
// Try parsed frame first, then parse AXFrame string
|
|
73
|
+
if (rootElement.frame) {
|
|
74
|
+
pointWidth = Math.round(rootElement.frame.width);
|
|
75
|
+
pointHeight = Math.round(rootElement.frame.height);
|
|
76
|
+
// The frame.y of the root element indicates where content starts (after status bar)
|
|
77
|
+
if (rootElement.frame.y > 0) {
|
|
78
|
+
safeAreaTop = Math.round(rootElement.frame.y);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (rootElement.AXFrame) {
|
|
82
|
+
// Parse format: "{{x, y}, {width, height}}"
|
|
83
|
+
const match = rootElement.AXFrame.match(/\{\{([\d.]+),\s*([\d.]+)\},\s*\{([\d.]+),\s*([\d.]+)\}\}/);
|
|
84
|
+
if (match) {
|
|
85
|
+
const frameY = parseFloat(match[2]);
|
|
86
|
+
pointWidth = Math.round(parseFloat(match[3]));
|
|
87
|
+
pointHeight = Math.round(parseFloat(match[4]));
|
|
88
|
+
if (frameY > 0) {
|
|
89
|
+
safeAreaTop = Math.round(frameY);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Calculate actual scale factor
|
|
94
|
+
if (pointWidth > 0) {
|
|
95
|
+
scaleFactor = Math.round(pixelWidth / pointWidth);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Fallback: use 3x scale for modern devices
|
|
101
|
+
}
|
|
102
|
+
// Fallback if we couldn't get dimensions
|
|
103
|
+
if (pointWidth === 0) {
|
|
104
|
+
pointWidth = Math.round(pixelWidth / scaleFactor);
|
|
105
|
+
pointHeight = Math.round(pixelHeight / scaleFactor);
|
|
106
|
+
}
|
|
107
|
+
const safeAreaOffsetPixels = safeAreaTop * scaleFactor;
|
|
108
|
+
// The Screen Layout tree was previously appended here but produced huge noisy
|
|
109
|
+
// output (nested Svg/G/Path duplicates). Agents should use get_screen_layout
|
|
110
|
+
// explicitly when they need the tree. The Pressable elements block below is
|
|
111
|
+
// the signal most consumers actually want.
|
|
112
|
+
let pressablesText = null;
|
|
113
|
+
let pressablesIsScreenState = false;
|
|
114
|
+
// Enrich with the screen-state summary (route + overlay-grouped pressables —
|
|
115
|
+
// same engine as get_screen_state, so blocked pressables behind sheets are
|
|
116
|
+
// excluded). Requires a connected RN app; otherwise fall back to the flat
|
|
117
|
+
// pressables list (which degrades further to the iOS accessibility tree).
|
|
118
|
+
if (targetApp) {
|
|
119
|
+
try {
|
|
120
|
+
const ssResult = await getScreenState({ device: targetDeviceName });
|
|
121
|
+
if (ssResult.success && ssResult.screenState) {
|
|
122
|
+
const screenshotScale = result.scaleFactor || 1;
|
|
123
|
+
const toPx = (v) => Math.round((v * scaleFactor) / screenshotScale);
|
|
124
|
+
pressablesText = formatScreenStateSummary(ssResult.screenState, (p) => {
|
|
125
|
+
// See enrichScreenshotWithLayout: shift y when fiber reports the
|
|
126
|
+
// element inside the safe-area band (react-native-screens modals).
|
|
127
|
+
const yShift = safeAreaTop > 0 && p.center.y < safeAreaTop ? safeAreaTop : 0;
|
|
128
|
+
return {
|
|
129
|
+
center: { x: toPx(p.center.x), y: toPx(p.center.y + yShift) },
|
|
130
|
+
frame: {
|
|
131
|
+
x: toPx(p.bounds.x),
|
|
132
|
+
y: toPx(p.bounds.y + yShift),
|
|
133
|
+
width: toPx(p.bounds.width),
|
|
134
|
+
height: toPx(p.bounds.height),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
pressablesIsScreenState = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Non-fatal: fall through to the flat pressables list below
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!pressablesText) {
|
|
146
|
+
try {
|
|
147
|
+
const pressables = await getPressableElements({
|
|
148
|
+
device: targetDeviceName,
|
|
149
|
+
platform: "ios",
|
|
150
|
+
udid: resolvedUdid ?? undefined
|
|
151
|
+
});
|
|
152
|
+
if (pressables.success && pressables.parsedElements && pressables.parsedElements.length > 0) {
|
|
153
|
+
const screenshotScale = result.scaleFactor || 1;
|
|
154
|
+
pressablesText = pressables.parsedElements.map((el) => {
|
|
155
|
+
// See enrichScreenshotWithLayout: shift y when fiber reports the element
|
|
156
|
+
// inside the safe-area band (a react-native-screens modal artifact).
|
|
157
|
+
let centerYPoints = el.center.y;
|
|
158
|
+
if (safeAreaTop > 0 && centerYPoints < safeAreaTop) {
|
|
159
|
+
centerYPoints += safeAreaTop;
|
|
160
|
+
}
|
|
161
|
+
const px = Math.round((el.center.x * scaleFactor) / screenshotScale);
|
|
162
|
+
const py = Math.round((centerYPoints * scaleFactor) / screenshotScale);
|
|
163
|
+
const label = el.accessibilityLabel || el.text || el.testID || iconLabel(el.component, el.icon) || (el.intent ? `${el.intent} icon` : el.component);
|
|
164
|
+
const idPart = el.testID ? ` testID="${el.testID}"` : "";
|
|
165
|
+
const kindPart = el.isInput ? " [input]" : "";
|
|
166
|
+
const wrapPart = el.isWrapper ? " [wrapper — skip]" : "";
|
|
167
|
+
const nearPart = !el.text && !el.accessibilityLabel && el.nearbyText ? ` near "${el.nearbyText}"` : "";
|
|
168
|
+
return ` (${px}, ${py}) ${el.component}: "${label}"${nearPart}${idPart}${kindPart}${wrapPart}`;
|
|
169
|
+
}).join("\n");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Non-fatal: screenshot works without pressables enrichment
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const deliveredWidth = result.scaleFactor && result.scaleFactor > 1
|
|
177
|
+
? Math.round(pixelWidth / result.scaleFactor)
|
|
178
|
+
: pixelWidth;
|
|
179
|
+
const deliveredHeight = result.scaleFactor && result.scaleFactor > 1
|
|
180
|
+
? Math.round(pixelHeight / result.scaleFactor)
|
|
181
|
+
: pixelHeight;
|
|
182
|
+
let infoText;
|
|
183
|
+
if (result.scaleFactor && result.scaleFactor > 1) {
|
|
184
|
+
infoText = `Screenshot: raw ${pixelWidth}x${pixelHeight} px → delivered ${deliveredWidth}x${deliveredHeight} px (downscaled ${(1 / result.scaleFactor).toFixed(3)}× to fit API limits). Pressable coordinates below are in delivered-image pixels.`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
|
|
188
|
+
}
|
|
189
|
+
infoText += `\n📱 iOS screen: ${pointWidth}x${pointHeight} points (${scaleFactor}x scale)`;
|
|
190
|
+
infoText += `\n📐 tap() handles pixel-to-point conversion automatically — pass pixel coords from this image directly`;
|
|
191
|
+
infoText += `\n⚠️ Status bar + safe area: ${safeAreaTop} points (${safeAreaOffsetPixels} pixels) from top`;
|
|
192
|
+
if (pressablesText) {
|
|
193
|
+
infoText += pressablesIsScreenState
|
|
194
|
+
? `\n\n🧭 Screen state (route + tappable elements, coordinates in screenshot pixels):\n`
|
|
195
|
+
: `\n\n🎯 Pressable elements (ready-to-tap, coordinates in screenshot pixels):`;
|
|
196
|
+
infoText += `\n${pressablesText}`;
|
|
197
|
+
infoText += `\n\n💡 Next steps:`;
|
|
198
|
+
infoText += `\n • tap(text="Button Label") — when text is exact and unique`;
|
|
199
|
+
infoText += `\n • tap(testID="id") or tap(component="Name") — when you know the identifier`;
|
|
200
|
+
infoText += `\n • tap(x=<px>, y=<px>) — use coordinates from the pressable elements list above (reliable for icons and ambiguous elements)`;
|
|
201
|
+
infoText += `\n • get_screen_layout — full component tree when you need more than pressables`;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (!targetApp && connectedApps.size > 0) {
|
|
205
|
+
infoText += `\n\nℹ️ Pressable enrichment skipped: no RN app is connected to simulator ${resolvedUdid}.`;
|
|
206
|
+
infoText += ` ${connectedApps.size} other app(s) are connected on different device(s) — their fiber data was intentionally not used to avoid mismatched output.`;
|
|
207
|
+
}
|
|
208
|
+
infoText += `\n\n💡 Next steps:`;
|
|
209
|
+
infoText += `\n • tap(text="Button Label") — tap element by visible text`;
|
|
210
|
+
infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
|
|
211
|
+
infoText += `\n • get_screen_layout — get full UI tree with real on-screen positions`;
|
|
212
|
+
}
|
|
213
|
+
// Check for LogBox overlay — only on the matching RN app; skip otherwise
|
|
214
|
+
// to avoid surfacing warnings from a different simulator's app.
|
|
215
|
+
if (targetApp) {
|
|
216
|
+
try {
|
|
217
|
+
const logBoxState = await detectLogBox(targetDeviceName);
|
|
218
|
+
if (logBoxState && logBoxState.total > 0) {
|
|
219
|
+
infoText += formatLogBoxWarning(logBoxState);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Non-fatal: LogBox detection failure should not break screenshot
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// I1 (2026-05-16): detect native iOS system overlays (auth sheets, alerts,
|
|
227
|
+
// permission dialogs) that sit on top of the RN app. The pressables list
|
|
228
|
+
// reflects the RN screen underneath; without this warning the agent will
|
|
229
|
+
// happily tap inert RN buttons and loop. Runs whether or not there's a
|
|
230
|
+
// matching RN app — the overlay belongs to the simulator, not the app.
|
|
231
|
+
try {
|
|
232
|
+
const overlay = await detectIOSSystemOverlay(resolvedUdid ?? undefined);
|
|
233
|
+
if (overlay) {
|
|
234
|
+
infoText += formatIOSSystemOverlayWarning(overlay);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Non-fatal: overlay detection failure should not break screenshot
|
|
239
|
+
}
|
|
240
|
+
imageBuffer.add({
|
|
241
|
+
id: `ios-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
242
|
+
image: result.data,
|
|
243
|
+
timestamp: Date.now(),
|
|
244
|
+
source: "ios_screenshot",
|
|
245
|
+
metadata: {
|
|
246
|
+
width: result.originalWidth || 0,
|
|
247
|
+
height: result.originalHeight || 0,
|
|
248
|
+
scaleFactor: result.scaleFactor || 1,
|
|
249
|
+
platform: "ios",
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: infoText
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: "image",
|
|
260
|
+
data: result.data.toString("base64"),
|
|
261
|
+
mimeType: "image/jpeg"
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: `Screenshot saved to: ${result.result}`
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
// Tool: Android screenshot
|
|
276
|
+
registerToolWithTelemetry(server, "android_screenshot", {
|
|
277
|
+
description: "Take a screenshot from an Android device/emulator. Returns the image plus a screen-state summary: active route (name + navigation stack), overlay-grouped tappable elements (pressables behind an open sheet/modal are excluded), component names as JSX tags, labels, testIDs, and frames — all in ready-to-tap pixel coordinates. Prefer tap(text=\"...\") when text is exact and unique; otherwise use tap(x, y) with coordinates from the list — this is the most reliable way to tap icons or visually-identified elements. Use component names for inspect_component/find_components.\n" +
|
|
278
|
+
"PURPOSE: Snapshot what the user sees on Android AND get tap-ready pressables + a structured component map in one call.\n" +
|
|
279
|
+
"WHEN TO USE: Any visual verification, before/after comparison, or as the starting point for tapping UI by coordinates on Android.\n" +
|
|
280
|
+
"WORKFLOW: android_screenshot -> pick element from pressables -> tap(x, y) or tap(testID=...) -> android_screenshot to verify.\n" +
|
|
281
|
+
"LIMITATIONS: Requires adb in PATH and a running device/emulator. For non-RN surfaces (system dialogs, permission prompts), combine with tap(..., native=true).\n" +
|
|
282
|
+
"GOOD: android_screenshot()\n" +
|
|
283
|
+
"BAD: android_screenshot({ deviceId: \"guess\" }) with a made-up serial — run list_android_devices first.\n" +
|
|
284
|
+
"SEE ALSO: call get_usage_guide(topic=\"interact\") for the full device-interaction playbook.",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
outputPath: z
|
|
287
|
+
.string()
|
|
288
|
+
.optional()
|
|
289
|
+
.describe("Optional path to save the screenshot. If not provided, saves to temp directory."),
|
|
290
|
+
deviceId: z
|
|
291
|
+
.string()
|
|
292
|
+
.optional()
|
|
293
|
+
.describe("Optional Android target. Accepts an adb serial (e.g. 'emulator-5554', 'RFCX20CLX3F'), an emulator name, or a substring of the connected RN device name (e.g. 'sdk_gphone'). Uses first available device if not specified.")
|
|
294
|
+
}
|
|
295
|
+
}, async ({ outputPath, deviceId }) => {
|
|
296
|
+
const resolved = await resolveAndroidDeviceId(deviceId);
|
|
297
|
+
if (!resolved.ok)
|
|
298
|
+
return resolved.response;
|
|
299
|
+
const result = await androidScreenshot(outputPath, resolved.serial);
|
|
300
|
+
if (!result.success) {
|
|
301
|
+
return {
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: `Error: ${result.error}`
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
isError: true
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Include image data if available
|
|
312
|
+
if (result.data) {
|
|
313
|
+
// Build info text with coordinate conversion guidance
|
|
314
|
+
const pixelWidth = result.originalWidth || 0;
|
|
315
|
+
const pixelHeight = result.originalHeight || 0;
|
|
316
|
+
// Resolve the RN app running on THIS Android device so enrichment
|
|
317
|
+
// pulls data from the right app (not whichever app is "first").
|
|
318
|
+
const targetApp = getConnectedAppByAndroidDeviceId(deviceId);
|
|
319
|
+
const targetDeviceName = targetApp?.deviceInfo.deviceName;
|
|
320
|
+
// Store screenshot metadata on the matching app (not an arbitrary one)
|
|
321
|
+
if (targetApp) {
|
|
322
|
+
targetApp.lastScreenshot = {
|
|
323
|
+
originalWidth: pixelWidth,
|
|
324
|
+
originalHeight: pixelHeight,
|
|
325
|
+
scaleFactor: result.scaleFactor || 1,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const androidDeliveredW = result.scaleFactor && result.scaleFactor > 1
|
|
329
|
+
? Math.round(pixelWidth / result.scaleFactor)
|
|
330
|
+
: pixelWidth;
|
|
331
|
+
const androidDeliveredH = result.scaleFactor && result.scaleFactor > 1
|
|
332
|
+
? Math.round(pixelHeight / result.scaleFactor)
|
|
333
|
+
: pixelHeight;
|
|
334
|
+
let infoText = result.scaleFactor && result.scaleFactor > 1
|
|
335
|
+
? `Screenshot: raw ${pixelWidth}x${pixelHeight} px → delivered ${androidDeliveredW}x${androidDeliveredH} px (downscaled ${(1 / result.scaleFactor).toFixed(3)}× to fit API limits). Pressable coordinates below are in delivered-image pixels.`
|
|
336
|
+
: `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
|
|
337
|
+
// Get status bar height for coordinate guidance
|
|
338
|
+
let statusBarPixels = 63; // Default fallback
|
|
339
|
+
let statusBarDp = 24;
|
|
340
|
+
let densityDpi = 440; // Common default
|
|
341
|
+
try {
|
|
342
|
+
const statusBarResult = await androidGetStatusBarHeight(deviceId);
|
|
343
|
+
if (statusBarResult.success && statusBarResult.heightPixels) {
|
|
344
|
+
statusBarPixels = statusBarResult.heightPixels;
|
|
345
|
+
statusBarDp = statusBarResult.heightDp || 24;
|
|
346
|
+
}
|
|
347
|
+
const densityResult = await androidGetDensity(deviceId);
|
|
348
|
+
if (densityResult.success && densityResult.density) {
|
|
349
|
+
densityDpi = densityResult.density;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Use defaults
|
|
354
|
+
}
|
|
355
|
+
// Enrich with screen layout data (component names + tap coordinates).
|
|
356
|
+
// On Bridgeless/Fabric Android (the only target architecture we support;
|
|
357
|
+
// legacy arch is <5% of users and not a priority), both code paths that
|
|
358
|
+
// feed this enrichment return DEVICE PIXELS:
|
|
359
|
+
// - fiber path: React's measureInWindow on Fabric returns native pixels.
|
|
360
|
+
// - a11y fallback: uiautomator's bounds are already device pixels.
|
|
361
|
+
// The earlier formula multiplied by densityDpi/160 on the (incorrect)
|
|
362
|
+
// assumption that fiber returned DP — inflating every coordinate by
|
|
363
|
+
// ~2.6× on a 420dpi emulator and producing numbers like (1170, 4054)
|
|
364
|
+
// for a button visually sitting near (445, 1370) in the JPEG. Drop the
|
|
365
|
+
// density factor; only the scaleFactor downscale is needed.
|
|
366
|
+
let pressablesText = null;
|
|
367
|
+
let pressablesIsScreenState = false;
|
|
368
|
+
// Screen Layout tree previously appended here was dropped — it was noisy
|
|
369
|
+
// (nested Svg/G/Path duplicates). Use get_screen_layout when the tree is needed.
|
|
370
|
+
// Prefer the screen-state summary (route + overlay-grouped pressables).
|
|
371
|
+
// Coordinates are fiber dp scaled by density + status-bar offset — the same
|
|
372
|
+
// best-effort conversion as the pressables fallback path (see pressables.ts);
|
|
373
|
+
// tap(text=)/tap(testID=) remain the precise options.
|
|
374
|
+
if (targetApp) {
|
|
375
|
+
try {
|
|
376
|
+
const ssResult = await getScreenState({ device: targetDeviceName });
|
|
377
|
+
if (ssResult.success && ssResult.screenState) {
|
|
378
|
+
const screenshotScale = result.scaleFactor || 1;
|
|
379
|
+
const densityScale = densityDpi / 160;
|
|
380
|
+
const toPx = (v) => Math.round((v * densityScale) / screenshotScale);
|
|
381
|
+
const toPxY = (v) => Math.round((v * densityScale + statusBarPixels) / screenshotScale);
|
|
382
|
+
pressablesText = formatScreenStateSummary(ssResult.screenState, (p) => ({
|
|
383
|
+
center: { x: toPx(p.center.x), y: toPxY(p.center.y) },
|
|
384
|
+
frame: {
|
|
385
|
+
x: toPx(p.bounds.x),
|
|
386
|
+
y: toPxY(p.bounds.y),
|
|
387
|
+
width: toPx(p.bounds.width),
|
|
388
|
+
height: toPx(p.bounds.height),
|
|
389
|
+
},
|
|
390
|
+
}));
|
|
391
|
+
pressablesIsScreenState = true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Non-fatal: fall through to the flat pressables list below
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Fallback: flat pressable elements. With targetApp present this uses the
|
|
399
|
+
// fiber path (uiautomator-reconciled coords). Without targetApp it degrades
|
|
400
|
+
// to the Android accessibility (uiautomator) tree (E1).
|
|
401
|
+
try {
|
|
402
|
+
const pressables = pressablesText ? null : await getPressableElements({ device: targetDeviceName, platform: "android" });
|
|
403
|
+
if (pressables && pressables.success && pressables.parsedElements && pressables.parsedElements.length > 0) {
|
|
404
|
+
const screenshotScale = result.scaleFactor || 1;
|
|
405
|
+
pressablesText = pressables.parsedElements.map((el) => {
|
|
406
|
+
const px = Math.round(el.center.x / screenshotScale);
|
|
407
|
+
const py = Math.round(el.center.y / screenshotScale);
|
|
408
|
+
const label = el.accessibilityLabel || el.text || el.testID || iconLabel(el.component, el.icon) || (el.intent ? `${el.intent} icon` : el.component);
|
|
409
|
+
const idPart = el.testID ? ` testID="${el.testID}"` : "";
|
|
410
|
+
const kindPart = el.isInput ? " [input]" : "";
|
|
411
|
+
const wrapPart = el.isWrapper ? " [wrapper — skip]" : "";
|
|
412
|
+
const nearPart = !el.text && !el.accessibilityLabel && el.nearbyText ? ` near "${el.nearbyText}"` : "";
|
|
413
|
+
return ` (${px}, ${py}) ${el.component}: "${label}"${nearPart}${idPart}${kindPart}${wrapPart}`;
|
|
414
|
+
}).join("\n");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// Non-fatal: screenshot works without pressables enrichment
|
|
419
|
+
}
|
|
420
|
+
infoText += `\n📱 Android uses PIXELS for all coordinates`;
|
|
421
|
+
if (result.scaleFactor && result.scaleFactor > 1) {
|
|
422
|
+
infoText += `\n📐 tap() handles coordinate conversion automatically — pass pixel coords from this image directly`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
infoText += `\n📐 Screenshot coords = tap coords (no conversion needed)`;
|
|
426
|
+
}
|
|
427
|
+
infoText += `\n⚠️ Status bar: ${statusBarPixels}px (${statusBarDp}dp) from top - app content starts below this`;
|
|
428
|
+
infoText += `\n📊 Display density: ${densityDpi}dpi`;
|
|
429
|
+
if (pressablesText) {
|
|
430
|
+
infoText += pressablesIsScreenState
|
|
431
|
+
? `\n\n🧭 Screen state (route + tappable elements, coordinates in screenshot pixels):\n`
|
|
432
|
+
: `\n\n🎯 Pressable elements (ready-to-tap, coordinates in screenshot pixels):`;
|
|
433
|
+
infoText += `\n${pressablesText}`;
|
|
434
|
+
infoText += `\n\n💡 Next steps:`;
|
|
435
|
+
infoText += `\n • tap(text="Button Label") — when text is exact and unique`;
|
|
436
|
+
infoText += `\n • tap(testID="id") or tap(component="Name") — when you know the identifier`;
|
|
437
|
+
infoText += `\n • tap(x=<px>, y=<px>) — use coordinates from the pressable elements list above (reliable for icons and ambiguous elements)`;
|
|
438
|
+
infoText += `\n • get_screen_layout — full component tree when you need more than pressables`;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
if (!targetApp && connectedApps.size > 0) {
|
|
442
|
+
infoText += `\n\nℹ️ Pressable enrichment skipped: no RN app is connected to device ${deviceId ?? "(default)"}.`;
|
|
443
|
+
infoText += ` ${connectedApps.size} other app(s) are connected on different device(s) — their fiber data was intentionally not used to avoid mismatched output.`;
|
|
444
|
+
}
|
|
445
|
+
infoText += `\n\n💡 Next steps:`;
|
|
446
|
+
infoText += `\n • tap(text="Button Label") — tap element by visible text`;
|
|
447
|
+
infoText += `\n • tap(x=<px>, y=<px>) — tap at coordinates from this screenshot`;
|
|
448
|
+
infoText += `\n • get_screen_layout — get full UI tree with real on-screen positions`;
|
|
449
|
+
}
|
|
450
|
+
// Check for LogBox overlay — only on the matching RN app; skip otherwise
|
|
451
|
+
// to avoid surfacing warnings from a different device's app.
|
|
452
|
+
if (targetApp) {
|
|
453
|
+
try {
|
|
454
|
+
const logBoxState = await detectLogBox(targetDeviceName);
|
|
455
|
+
if (logBoxState && logBoxState.total > 0) {
|
|
456
|
+
infoText += formatLogBoxWarning(logBoxState);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// Non-fatal: LogBox detection failure should not break screenshot
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
imageBuffer.add({
|
|
464
|
+
id: `android-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
465
|
+
image: result.data,
|
|
466
|
+
timestamp: Date.now(),
|
|
467
|
+
source: "android_screenshot",
|
|
468
|
+
metadata: {
|
|
469
|
+
width: result.originalWidth || 0,
|
|
470
|
+
height: result.originalHeight || 0,
|
|
471
|
+
scaleFactor: result.scaleFactor || 1,
|
|
472
|
+
platform: "android",
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
content: [
|
|
477
|
+
{
|
|
478
|
+
type: "text",
|
|
479
|
+
text: infoText
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
type: "image",
|
|
483
|
+
data: result.data.toString("base64"),
|
|
484
|
+
mimeType: "image/jpeg"
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
content: [
|
|
491
|
+
{
|
|
492
|
+
type: "text",
|
|
493
|
+
text: `Screenshot saved to: ${result.result}`
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
// Tool: Get images from shared image buffer
|
|
499
|
+
registerToolWithTelemetry(server, "get_images", {
|
|
500
|
+
description: "Access the shared image buffer containing screenshots from all tools (ios_screenshot, android_screenshot, ocr_screenshot, tap verification). Returns metadata only by default — use id or groupId+frameIndex to retrieve actual image data. Tap burst verification stores frame groups here when burst=true is used.\n" +
|
|
501
|
+
"PURPOSE: Retrieve prior screenshots — especially tap burst frames — without re-taking them, for visual diffing or reviewing transient UI states.\n" +
|
|
502
|
+
"WHEN TO USE: After tap(burst=true) reports transientChangeDetected, or to compare before/after frames without another screenshot round-trip.\n" +
|
|
503
|
+
"WORKFLOW: tap(burst=true) -> note verification.burstGroupId -> get_images(groupId, frameIndex=N) to inspect individual frames.\n" +
|
|
504
|
+
"LIMITATIONS: Circular buffer (50 entries) — old images are evicted. Metadata is cheap; fetching image data is not — request specific ids, not bulk.\n" +
|
|
505
|
+
"GOOD: get_images({ list: true }); get_images({ groupId: \"burst-abc\", frameIndex: 2 })\n" +
|
|
506
|
+
"BAD: get_images() with no filter when buffer is full — floods context. Use list:true or last:N first.\n" +
|
|
507
|
+
"SEE ALSO: call get_usage_guide(topic=\"interact\") for the full interaction playbook.",
|
|
508
|
+
inputSchema: {
|
|
509
|
+
list: z.boolean().optional().describe("List all entries and groups (metadata only, no image data)"),
|
|
510
|
+
id: z.string().optional().describe("Retrieve a specific image by ID (returns image data)"),
|
|
511
|
+
groupId: z.string().optional().describe("List frames in a group (metadata only), or combine with frameIndex to retrieve a specific frame"),
|
|
512
|
+
frameIndex: z.coerce.number().optional().describe("Retrieve a specific frame from a group (requires groupId)"),
|
|
513
|
+
last: z.coerce.number().optional().describe("Return the N most recent entries (metadata only)"),
|
|
514
|
+
source: z.string().optional().describe("Filter entries by source"),
|
|
515
|
+
clear: z.boolean().optional().describe("Clear the buffer")
|
|
516
|
+
}
|
|
517
|
+
}, async ({ id, groupId, frameIndex, last, source, clear }) => {
|
|
518
|
+
if (clear) {
|
|
519
|
+
const count = imageBuffer.clear();
|
|
520
|
+
return {
|
|
521
|
+
content: [{ type: "text", text: `Cleared ${count} images from buffer.` }]
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (id) {
|
|
525
|
+
const entry = imageBuffer.getById(id);
|
|
526
|
+
if (!entry) {
|
|
527
|
+
return {
|
|
528
|
+
content: [{ type: "text", text: `No image found with id "${id}".` }],
|
|
529
|
+
isError: true
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const { image, ...meta } = entry;
|
|
533
|
+
return {
|
|
534
|
+
content: [
|
|
535
|
+
{ type: "text", text: JSON.stringify(meta, null, 2) },
|
|
536
|
+
{ type: "image", data: image.toString("base64"), mimeType: "image/png" }
|
|
537
|
+
]
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (groupId !== undefined && frameIndex !== undefined) {
|
|
541
|
+
const entry = imageBuffer.getByGroupFrame(groupId, frameIndex);
|
|
542
|
+
if (!entry) {
|
|
543
|
+
return {
|
|
544
|
+
content: [{ type: "text", text: `No frame ${frameIndex} found in group "${groupId}".` }],
|
|
545
|
+
isError: true
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const { image, ...meta } = entry;
|
|
549
|
+
return {
|
|
550
|
+
content: [
|
|
551
|
+
{ type: "text", text: JSON.stringify(meta, null, 2) },
|
|
552
|
+
{ type: "image", data: image.toString("base64"), mimeType: "image/png" }
|
|
553
|
+
]
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const entries = imageBuffer.listEntries({ source, groupId, last });
|
|
557
|
+
const groups = imageBuffer.listGroups();
|
|
558
|
+
return {
|
|
559
|
+
content: [
|
|
560
|
+
{
|
|
561
|
+
type: "text",
|
|
562
|
+
text: JSON.stringify({ entries, groups, total: imageBuffer.size }, null, 2)
|
|
563
|
+
}
|
|
564
|
+
]
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
// Tool: OCR Screenshot - Extract text with coordinates from screenshot
|
|
568
|
+
registerToolWithTelemetry(server, "ocr_screenshot", {
|
|
569
|
+
description: "RECOMMENDED: Use this tool FIRST when you need to find and tap UI elements. Takes a screenshot and extracts all visible text with tap-ready coordinates using OCR. " +
|
|
570
|
+
"ADVANTAGES over accessibility trees: (1) Works on ANY visible text regardless of accessibility labels, (2) Returns ready-to-use tapX/tapY coordinates - no conversion needed, (3) Faster than parsing accessibility hierarchies, (4) Works consistently across iOS and Android. " +
|
|
571
|
+
"USE THIS FOR: Finding buttons, labels, menu items, tab bars, or any text you need to tap. Simply find the text in the results and use its tapX/tapY with the tap command.\n" +
|
|
572
|
+
"PURPOSE: Visually locate text on screen and return coordinates safe to pass straight into tap.\n" +
|
|
573
|
+
"WHEN TO USE: Non-RN surfaces, third-party WebViews, accessibility-poor screens, or when fiber/testID strategies have failed.\n" +
|
|
574
|
+
"WORKFLOW: ocr_screenshot(platform=\"ios\") -> scan results for the label -> tap(x=tapX, y=tapY) -> ios_screenshot to verify.\n" +
|
|
575
|
+
"LIMITATIONS: OCR accuracy degrades on very small or stylized text; icons with no label won't appear — use tap(component=...) instead.\n" +
|
|
576
|
+
"GOOD: ocr_screenshot({ platform: \"ios\" })\n" +
|
|
577
|
+
"BAD: ocr_screenshot used just to view the screen — plain ios_screenshot / android_screenshot is cheaper when you don't need OCR text.\n" +
|
|
578
|
+
"SEE ALSO: call get_usage_guide(topic=\"interact\") for the full device-interaction playbook.",
|
|
579
|
+
inputSchema: {
|
|
580
|
+
platform: z.enum(["ios", "android"]).describe("Platform to capture screenshot from"),
|
|
581
|
+
deviceId: z
|
|
582
|
+
.string()
|
|
583
|
+
.optional()
|
|
584
|
+
.describe("Optional device ID (Android) or UDID (iOS). Uses first available device if not specified.")
|
|
585
|
+
}
|
|
586
|
+
}, async ({ platform, deviceId }) => {
|
|
587
|
+
try {
|
|
588
|
+
let screenshotResult;
|
|
589
|
+
if (platform === "android") {
|
|
590
|
+
const r = await resolveAndroidDeviceId(deviceId);
|
|
591
|
+
if (!r.ok)
|
|
592
|
+
return r.response;
|
|
593
|
+
screenshotResult = await androidScreenshot(undefined, r.serial);
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
const r = await resolveIosUdid(deviceId);
|
|
597
|
+
if (!r.ok)
|
|
598
|
+
return r.response;
|
|
599
|
+
screenshotResult = await iosScreenshot(undefined, r.udid);
|
|
600
|
+
}
|
|
601
|
+
if (!screenshotResult.success || !screenshotResult.data) {
|
|
602
|
+
return {
|
|
603
|
+
content: [
|
|
604
|
+
{
|
|
605
|
+
type: "text",
|
|
606
|
+
text: `Screenshot failed: ${screenshotResult.error || "No image data"}`
|
|
607
|
+
}
|
|
608
|
+
],
|
|
609
|
+
isError: true
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
// Calculate device pixel ratio for iOS
|
|
613
|
+
const devicePixelRatio = platform === "ios" && screenshotResult.originalWidth && screenshotResult.originalHeight
|
|
614
|
+
? inferIOSDevicePixelRatio(screenshotResult.originalWidth, screenshotResult.originalHeight)
|
|
615
|
+
: 1;
|
|
616
|
+
// Run OCR on the screenshot
|
|
617
|
+
const scaleFactor = screenshotResult.scaleFactor || 1;
|
|
618
|
+
const ocrResult = await recognizeText(screenshotResult.data, {
|
|
619
|
+
scaleFactor,
|
|
620
|
+
platform,
|
|
621
|
+
devicePixelRatio
|
|
622
|
+
});
|
|
623
|
+
if (!ocrResult.success) {
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: `OCR failed: no text recognized`
|
|
629
|
+
}
|
|
630
|
+
],
|
|
631
|
+
isError: true
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
// Format results for MCP tool output
|
|
635
|
+
const elements = ocrResult.words
|
|
636
|
+
.filter((w) => w.confidence > 50 && w.text.trim().length > 0)
|
|
637
|
+
.map((w) => ({
|
|
638
|
+
text: w.text,
|
|
639
|
+
confidence: Math.round(w.confidence),
|
|
640
|
+
tapX: w.tapCenter.x,
|
|
641
|
+
tapY: w.tapCenter.y
|
|
642
|
+
}));
|
|
643
|
+
const result = {
|
|
644
|
+
platform,
|
|
645
|
+
engine: ocrResult.engine || "unknown",
|
|
646
|
+
processingTimeMs: ocrResult.processingTimeMs,
|
|
647
|
+
fullText: ocrResult.fullText?.trim() || "",
|
|
648
|
+
confidence: Math.round(ocrResult.confidence || 0),
|
|
649
|
+
elementCount: elements.length,
|
|
650
|
+
elements,
|
|
651
|
+
note: "tapX/tapY are in device pixels — pass directly to tap(x, y) for automatic platform conversion"
|
|
652
|
+
};
|
|
653
|
+
// Check for LogBox overlay (uses default CDP device — native deviceId cannot be mapped to CDP device name)
|
|
654
|
+
try {
|
|
655
|
+
const logBoxState = await detectLogBox();
|
|
656
|
+
if (logBoxState && logBoxState.total > 0) {
|
|
657
|
+
result.logBoxWarning = formatLogBoxWarning(logBoxState).trim();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
// Non-fatal: LogBox detection failure should not break OCR
|
|
662
|
+
}
|
|
663
|
+
// Store screenshot in image buffer
|
|
664
|
+
try {
|
|
665
|
+
imageBuffer.add({
|
|
666
|
+
id: `ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
667
|
+
image: screenshotResult.data,
|
|
668
|
+
timestamp: Date.now(),
|
|
669
|
+
source: "ocr_screenshot",
|
|
670
|
+
metadata: {
|
|
671
|
+
width: screenshotResult.originalWidth || 0,
|
|
672
|
+
height: screenshotResult.originalHeight || 0,
|
|
673
|
+
scaleFactor,
|
|
674
|
+
platform,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
// Non-fatal: image buffer write failure should not break OCR response
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
content: [
|
|
683
|
+
{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: JSON.stringify(result, null, 2)
|
|
686
|
+
}
|
|
687
|
+
]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
return {
|
|
692
|
+
content: [
|
|
693
|
+
{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: `OCR failed: ${error instanceof Error ? error.message : String(error)}`
|
|
696
|
+
}
|
|
697
|
+
],
|
|
698
|
+
isError: true
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
//# sourceMappingURL=screenshotTools.js.map
|