sim-mirror 2.0.1__tar.gz → 2.2.0__tar.gz

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.
Files changed (451) hide show
  1. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/PKG-INFO +33 -15
  2. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/README.md +32 -14
  3. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/THIRD_PARTY_LICENSES.md +27 -0
  4. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/docs/README.md +2 -1
  5. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/README.md +3 -1
  6. sim_mirror-2.2.0/examples/android-app/README.md +37 -0
  7. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/claude-code-cli/README.md +2 -2
  8. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/codex/README.md +1 -1
  9. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/cursor/README.md +1 -1
  10. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/fastapi-embed/README.md +1 -1
  11. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Accessibility.swift +23 -7
  12. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Arguments.swift +2 -2
  13. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Input.swift +1 -1
  14. sim_mirror-2.2.0/helper/Sources/HelperCore/Orientation.swift +117 -0
  15. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Recording.swift +48 -0
  16. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Requests.swift +36 -3
  17. sim_mirror-2.2.0/helper/Sources/HelperCore/Rotation.swift +58 -0
  18. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Screen.swift +2 -1
  19. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Server.swift +15 -3
  20. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Wire.swift +1 -1
  21. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/AccessibilityReader.swift +56 -6
  22. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/CaptureDevice.swift +9 -1
  23. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/RecordingRenderer.swift +4 -3
  24. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/SimulatorDevice.swift +33 -10
  25. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/Fixtures/wire-vectors.json +8 -0
  26. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/AccessibilityTests.swift +8 -4
  27. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/ArgumentsTests.swift +1 -1
  28. sim_mirror-2.2.0/helper/Tests/HelperCoreTests/OrientationTests.swift +113 -0
  29. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/RecordingTests.swift +17 -0
  30. sim_mirror-2.2.0/helper/Tests/HelperCoreTests/RotationTests.swift +41 -0
  31. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/ServerTests.swift +47 -6
  32. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/README.md +6 -4
  33. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/hierarchy-alert.json +3 -1
  34. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/hierarchy.schema.json +2 -2
  35. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/client-input.schema.json +2 -2
  36. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/common.schema.json +16 -6
  37. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/constants.json +2 -1
  38. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/http.schema.json +26 -2
  39. sim_mirror-2.2.0/protocol/v1/input-refused.schema.json +12 -0
  40. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/status.schema.json +3 -3
  41. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/pyproject.toml +6 -0
  42. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/_version.py +1 -1
  43. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/api.py +5 -0
  44. sim_mirror-2.2.0/src/sim_mirror/build/checkouts.py +259 -0
  45. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/build/destination.py +34 -14
  46. sim_mirror-2.2.0/src/sim_mirror/build/gradle.py +483 -0
  47. sim_mirror-2.2.0/src/sim_mirror/build/gradle_log.py +73 -0
  48. sim_mirror-2.2.0/src/sim_mirror/build/junit.py +125 -0
  49. sim_mirror-2.2.0/src/sim_mirror/build/project_files.py +52 -0
  50. sim_mirror-2.2.0/src/sim_mirror/build/provider.py +275 -0
  51. sim_mirror-2.2.0/src/sim_mirror/build/runner.py +450 -0
  52. sim_mirror-2.2.0/src/sim_mirror/build/schemes.py +129 -0
  53. sim_mirror-2.2.0/src/sim_mirror/build/source.py +59 -0
  54. sim_mirror-2.2.0/src/sim_mirror/build/stamp.py +192 -0
  55. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/build/xcodebuild.py +302 -357
  56. sim_mirror-2.2.0/src/sim_mirror/build/xctestplan.py +105 -0
  57. sim_mirror-2.2.0/src/sim_mirror/cli/android.py +135 -0
  58. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/config_command.py +33 -2
  59. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/context.py +40 -1
  60. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/devices.py +17 -5
  61. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/doctor.py +3 -0
  62. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/main.py +4 -2
  63. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/mcp.py +2 -0
  64. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/record.py +35 -5
  65. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/serve.py +3 -1
  66. sim_mirror-2.2.0/src/sim_mirror/cli/stop.py +132 -0
  67. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/model.py +21 -0
  68. sim_mirror-2.2.0/src/sim_mirror/config/observe_languages.py +91 -0
  69. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/schema.py +181 -7
  70. sim_mirror-2.2.0/src/sim_mirror/connectors/android/__init__.py +2 -0
  71. sim_mirror-2.2.0/src/sim_mirror/connectors/android/adb_input.py +125 -0
  72. sim_mirror-2.2.0/src/sim_mirror/connectors/android/connector.py +230 -0
  73. sim_mirror-2.2.0/src/sim_mirror/connectors/android/geometry.py +115 -0
  74. sim_mirror-2.2.0/src/sim_mirror/connectors/android/keys.py +55 -0
  75. sim_mirror-2.2.0/src/sim_mirror/connectors/android/scrcpy.py +269 -0
  76. sim_mirror-2.2.0/src/sim_mirror/connectors/android/scrcpy_roles.py +202 -0
  77. sim_mirror-2.2.0/src/sim_mirror/connectors/android/scrcpy_source.py +204 -0
  78. sim_mirror-2.2.0/src/sim_mirror/connectors/android/screen.py +69 -0
  79. sim_mirror-2.2.0/src/sim_mirror/connectors/android/uiautomator.py +256 -0
  80. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/document.py +8 -6
  81. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/base.py +51 -7
  82. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/companion.py +3 -3
  83. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/connector.py +4 -2
  84. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/engine.py +56 -3
  85. sim_mirror-2.2.0/src/sim_mirror/connectors/idb/orientation.py +157 -0
  86. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/connector.py +9 -5
  87. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/wda_roles.py +7 -2
  88. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/native/client.py +24 -4
  89. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/native/connector.py +18 -4
  90. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/registry.py +26 -3
  91. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/actions.py +326 -46
  92. sim_mirror-2.2.0/src/sim_mirror/core/android_backend.py +254 -0
  93. sim_mirror-2.2.0/src/sim_mirror/core/android_control.py +424 -0
  94. sim_mirror-2.2.0/src/sim_mirror/core/android_devices.py +249 -0
  95. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/backends.py +56 -7
  96. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/control.py +70 -1
  97. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/device_changes.py +52 -15
  98. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/frames.py +23 -2
  99. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/instance.py +34 -1
  100. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/manager.py +205 -29
  101. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/recordings.py +135 -16
  102. sim_mirror-2.2.0/src/sim_mirror/core/refusals.py +255 -0
  103. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/runtime.py +75 -9
  104. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/screen_input.py +33 -11
  105. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/screen_relay.py +40 -6
  106. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/signing.py +4 -2
  107. sim_mirror-2.2.0/src/sim_mirror/core/touch.py +212 -0
  108. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/app.py +75 -1
  109. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/auth.py +21 -4
  110. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/lease.py +5 -0
  111. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/lifecycle.py +28 -0
  112. sim_mirror-2.2.0/src/sim_mirror/daemon/pauses.py +46 -0
  113. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/policy.py +3 -2
  114. sim_mirror-2.2.0/src/sim_mirror/doctor/android.py +118 -0
  115. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/checks.py +56 -10
  116. sim_mirror-2.2.0/src/sim_mirror/doctor/worktree.py +89 -0
  117. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/host_copy.py +56 -0
  118. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/hosting.py +32 -5
  119. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/mcp/launcher.py +122 -38
  120. sim_mirror-2.2.0/src/sim_mirror/mcp/relay.py +625 -0
  121. sim_mirror-2.2.0/src/sim_mirror/perception/bar_names.py +49 -0
  122. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/imagehash.py +12 -7
  123. sim_mirror-2.2.0/src/sim_mirror/perception/in_front.py +116 -0
  124. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/model.py +20 -1
  125. sim_mirror-2.2.0/src/sim_mirror/perception/occlusion.py +346 -0
  126. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/ocr.py +66 -20
  127. sim_mirror-2.2.0/src/sim_mirror/perception/orientation.py +177 -0
  128. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/readers.py +71 -13
  129. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/settle.py +52 -23
  130. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/snapshot.py +72 -15
  131. sim_mirror-2.2.0/src/sim_mirror/perception/system_alert.py +87 -0
  132. sim_mirror-2.2.0/src/sim_mirror/platform/adb.py +488 -0
  133. sim_mirror-2.2.0/src/sim_mirror/platform/android_sdk.py +164 -0
  134. sim_mirror-2.2.0/src/sim_mirror/platform/codesign.py +38 -0
  135. sim_mirror-2.2.0/src/sim_mirror/platform/emulator.py +268 -0
  136. sim_mirror-2.2.0/src/sim_mirror/platform/git.py +63 -0
  137. sim_mirror-2.2.0/src/sim_mirror/platform/identifiers.py +69 -0
  138. sim_mirror-2.2.0/src/sim_mirror/platform/java.py +71 -0
  139. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/simctl.py +16 -3
  140. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/xcode.py +10 -0
  141. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/protocol/__init__.py +2 -0
  142. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/protocol/_generated.py +54 -7
  143. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/protocol/messages.py +7 -1
  144. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/seams.py +15 -1
  145. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/http_routes.py +23 -6
  146. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/static/viewer/embed.html +1 -6
  147. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/static/viewer/index.html +1 -6
  148. sim_mirror-2.2.0/src/sim_mirror/server/static/viewer/start.js +35 -0
  149. sim_mirror-2.2.0/src/sim_mirror/testing/adb.py +422 -0
  150. sim_mirror-2.2.0/src/sim_mirror/testing/android_sdk.py +43 -0
  151. sim_mirror-2.2.0/src/sim_mirror/testing/checkouts.py +72 -0
  152. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/contract.py +2 -0
  153. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/daemon.py +3 -0
  154. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fakes.py +142 -6
  155. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-am-start-bad.txt +3 -0
  156. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-am-start.txt +8 -0
  157. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-logcat-uid.txt +12 -0
  158. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-package-missing.txt +1 -0
  159. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-package.txt +45 -0
  160. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-pm-uid.txt +2 -0
  161. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-uiautomator-landscape.xml +1 -0
  162. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-uiautomator-search.xml +1 -0
  163. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-uiautomator-settings.xml +1 -0
  164. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-window-search.txt +493 -0
  165. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/android-window-settings.txt +491 -0
  166. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-alert-ipad-landscape-left-ios26.json +551 -0
  167. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-alert-ipad-upside-down-ios26.json +491 -0
  168. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-alert-turned-left-ios26.json +457 -0
  169. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-alert-turned-right-ios26.json +491 -0
  170. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-alert-upright-ios26.json +469 -0
  171. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-files-navigation-bar-de-ios26.json +551 -0
  172. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-files-tab-bar-de-ios26.json +495 -0
  173. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-floating-button-ios26.json +541 -0
  174. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-health-tab-bar-ios26.json +488 -0
  175. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-native-files-navigation-bar-de-ios26.json +427 -0
  176. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-probe-ipad-upside-down-ios26.json +133 -0
  177. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-probe-rotated-left-ios26.json +133 -0
  178. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-settings-back-button-ios26.json +395 -0
  179. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-settings-search-field-ios26.json +426 -0
  180. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-settings-toolbar-ja-ios26.json +397 -0
  181. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-settings-under-navigation-bar-ios26.json +533 -0
  182. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-landscape-left-ios26.json +363 -0
  183. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-landscape-right-ios26.json +363 -0
  184. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-maps-alert-ios26.json +382 -0
  185. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-portrait-ios26.json +370 -0
  186. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-reminders-portrait-ios26.json +345 -0
  187. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-safari-landscape-left-ios26.json +1166 -0
  188. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/ax-turned-safari-landscape-right-ios26.json +1166 -0
  189. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/gradle-aapt-error.log +55 -0
  190. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/gradle-kotlin-error.log +51 -0
  191. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/gradle-output-metadata.json +21 -0
  192. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/gradle-test-failure.log +166 -0
  193. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/junit-android-failure.xml +48 -0
  194. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/xcodebuild-settings-test.json +55 -0
  195. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/xcscheme-staging.xcscheme +91 -0
  196. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/xcscheme-test-plans.xcscheme +86 -0
  197. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/xctestplan-everything.xctestplan +38 -0
  198. sim_mirror-2.2.0/src/sim_mirror/testing/fixtures/xctestplan-units-only.xctestplan +42 -0
  199. sim_mirror-2.2.0/src/sim_mirror/testing/gradle.py +123 -0
  200. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/guards.py +29 -5
  201. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/native.py +6 -0
  202. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/rig.py +28 -1
  203. sim_mirror-2.2.0/src/sim_mirror/testing/scrcpy.py +148 -0
  204. sim_mirror-2.2.0/src/sim_mirror/tools/act.py +50 -0
  205. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/app.py +62 -13
  206. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/context.py +10 -3
  207. sim_mirror-2.2.0/src/sim_mirror/tools/device.py +275 -0
  208. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/record.py +6 -1
  209. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/registry.py +9 -2
  210. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/results.py +2 -0
  211. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/schemas.py +99 -30
  212. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/tests/unit/protocol/test_messages.py +1 -0
  213. sim_mirror-2.0.1/src/sim_mirror/build/provider.py +0 -186
  214. sim_mirror-2.0.1/src/sim_mirror/core/touch.py +0 -99
  215. sim_mirror-2.0.1/src/sim_mirror/mcp/relay.py +0 -316
  216. sim_mirror-2.0.1/src/sim_mirror/platform/identifiers.py +0 -41
  217. sim_mirror-2.0.1/src/sim_mirror/server/static/viewer/start.js +0 -32
  218. sim_mirror-2.0.1/src/sim_mirror/tools/act.py +0 -31
  219. sim_mirror-2.0.1/src/sim_mirror/tools/device.py +0 -128
  220. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/.gitignore +0 -0
  221. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/LICENSE +0 -0
  222. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/NOTICE +0 -0
  223. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/docs/media/README.md +0 -0
  224. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/custom-connector/README.md +0 -0
  225. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/embed-host/README.md +0 -0
  226. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/iframe/README.md +0 -0
  227. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/token-budget/README.md +0 -0
  228. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/examples/web-component/README.md +0 -0
  229. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Package.swift +0 -0
  230. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Capture.swift +0 -0
  231. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/FrameScheduler.swift +0 -0
  232. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/JSON.swift +0 -0
  233. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperCore/Video.swift +0 -0
  234. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/Disclaimed.swift +0 -0
  235. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/Framebuffer.swift +0 -0
  236. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/Frameworks.swift +0 -0
  237. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/H264Stream.swift +0 -0
  238. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/HIDTransports.swift +0 -0
  239. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/Pictures.swift +0 -0
  240. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/PixelSource.swift +0 -0
  241. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/ScreenFeed.swift +0 -0
  242. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/SimDeviceHandle.swift +0 -0
  243. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/UnixSocket.swift +0 -0
  244. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/UsbCapture.swift +0 -0
  245. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/HelperPlatform/Warming.swift +0 -0
  246. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/ObjCGuard/ObjCGuard.m +0 -0
  247. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/ObjCGuard/include/ObjCGuard.h +0 -0
  248. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Sources/sim-mirror-helper/main.swift +0 -0
  249. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Support/Info.plist +0 -0
  250. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/CaptureTests.swift +0 -0
  251. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/InputTests.swift +0 -0
  252. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/PictureTests.swift +0 -0
  253. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/helper/Tests/HelperCoreTests/WireTests.swift +0 -0
  254. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/error-inactive.json +0 -0
  255. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/error-unauthorized.json +0 -0
  256. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/hierarchy-keyboard.json +0 -0
  257. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/hierarchy-swiftui.json +0 -0
  258. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/hierarchy-tagged.json +0 -0
  259. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/hierarchy-uikit.json +0 -0
  260. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/examples/listing.json +0 -0
  261. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/app-sdk/v1/listing.schema.json +0 -0
  262. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/agent-event.schema.json +0 -0
  263. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/close-codes.json +0 -0
  264. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/hello.schema.json +0 -0
  265. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/screen-text.schema.json +0 -0
  266. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/settings.schema.json +0 -0
  267. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/protocol/v1/stream.schema.json +0 -0
  268. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/scripts/hatch_build.py +0 -0
  269. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/__init__.py +0 -0
  270. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/__main__.py +0 -0
  271. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/build/__init__.py +0 -0
  272. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/build/wda.py +0 -0
  273. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/build/xcresult.py +0 -0
  274. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/__init__.py +0 -0
  275. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/app_command.py +0 -0
  276. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/device_command.py +0 -0
  277. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/helper.py +0 -0
  278. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/open_viewer.py +0 -0
  279. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/settings_command.py +0 -0
  280. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/tokens.py +0 -0
  281. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/tools.py +0 -0
  282. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/version.py +0 -0
  283. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/wda.py +0 -0
  284. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/cli/xcode.py +0 -0
  285. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/__init__.py +0 -0
  286. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/discovery.py +0 -0
  287. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/env.py +0 -0
  288. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/provenance.py +0 -0
  289. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/settings_store.py +0 -0
  290. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/toml_source.py +0 -0
  291. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/config/writer.py +0 -0
  292. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/__init__.py +0 -0
  293. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/__init__.py +0 -0
  294. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/client.py +0 -0
  295. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/discovery.py +0 -0
  296. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/errors.py +0 -0
  297. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/merge.py +0 -0
  298. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/reader.py +0 -0
  299. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/app/wire.py +0 -0
  300. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/helper_process.py +0 -0
  301. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/__init__.py +0 -0
  302. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/frameworks.py +0 -0
  303. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/proto/__init__.py +0 -0
  304. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/proto/idb.proto +0 -0
  305. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/proto/idb_grpc.py +0 -0
  306. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/idb/proto/idb_pb2.py +0 -0
  307. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/__init__.py +0 -0
  308. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/cable.py +0 -0
  309. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/capture.py +0 -0
  310. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/screen.py +0 -0
  311. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/wda.py +0 -0
  312. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/wda_client.py +0 -0
  313. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/wda_setup.py +0 -0
  314. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/iphone/wda_source.py +0 -0
  315. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/__init__.py +0 -0
  316. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/client.py +0 -0
  317. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/connector.py +0 -0
  318. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/hierarchy.py +0 -0
  319. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/merge.py +0 -0
  320. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/mcpbridge/reader.py +0 -0
  321. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/native/__init__.py +0 -0
  322. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/native/helper.py +0 -0
  323. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/native/wire.py +0 -0
  324. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/simctl/__init__.py +0 -0
  325. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/simctl/capture.py +0 -0
  326. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/connectors/simctl/connector.py +0 -0
  327. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/__init__.py +0 -0
  328. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/availability.py +0 -0
  329. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/device_logs.py +0 -0
  330. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/device_settings.py +0 -0
  331. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/devices.py +0 -0
  332. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/events.py +0 -0
  333. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/gestures.py +0 -0
  334. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/reaper.py +0 -0
  335. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/recording_sources.py +0 -0
  336. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/render.py +0 -0
  337. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/status.py +0 -0
  338. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/text_entry.py +0 -0
  339. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/text_overlay.py +0 -0
  340. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/core/tickets.py +0 -0
  341. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/__init__.py +0 -0
  342. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/confirmations.py +0 -0
  343. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/health.py +0 -0
  344. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/passes.py +0 -0
  345. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/daemon/tokens.py +0 -0
  346. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/__init__.py +0 -0
  347. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/macos.py +0 -0
  348. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/real_devices.py +0 -0
  349. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/report.py +0 -0
  350. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/doctor/tap.py +0 -0
  351. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/mcp/__init__.py +0 -0
  352. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/__init__.py +0 -0
  353. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/budget.py +0 -0
  354. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/vision/__init__.py +0 -0
  355. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/vision/helper.py +0 -0
  356. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/vision/probe.py +0 -0
  357. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/vision/text_recognizer.swift +0 -0
  358. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/perception/wait.py +0 -0
  359. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/__init__.py +0 -0
  360. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/developer_dir.py +0 -0
  361. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/device_data.py +0 -0
  362. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/devicectl.py +0 -0
  363. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/errors.py +0 -0
  364. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/images.py +0 -0
  365. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/json_lines.py +0 -0
  366. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/keyboard.py +0 -0
  367. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/keychain.py +0 -0
  368. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/lockdown.py +0 -0
  369. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/process.py +0 -0
  370. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/swift.py +0 -0
  371. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/swiftpm.py +0 -0
  372. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/usbmux.py +0 -0
  373. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/platform/xcrun.py +0 -0
  374. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/py.typed +0 -0
  375. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/scope.py +0 -0
  376. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/__init__.py +0 -0
  377. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/agent_routes.py +0 -0
  378. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/envelope.py +0 -0
  379. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/log_redaction.py +0 -0
  380. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/pages.py +0 -0
  381. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/security.py +0 -0
  382. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/settings_routes.py +0 -0
  383. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/settings_view.py +0 -0
  384. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/server/socket_routes.py +0 -0
  385. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/storage/__init__.py +0 -0
  386. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/storage/app_support.py +0 -0
  387. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/storage/claims.py +0 -0
  388. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/storage/private.py +0 -0
  389. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/__init__.py +0 -0
  390. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/app_sdk.py +0 -0
  391. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/asgi.py +0 -0
  392. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-alert-ios27.json +0 -0
  393. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-app-sdk-alert.json +0 -0
  394. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-app-sdk-keyboard.json +0 -0
  395. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-app-sdk-swiftui.json +0 -0
  396. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-app-sdk-tagged.json +0 -0
  397. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-app-sdk-uikit.json +0 -0
  398. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-home-complete.json +0 -0
  399. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-home-ios27.json +0 -0
  400. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-keyboard-ios27.json +0 -0
  401. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-probe-ios27.json +0 -0
  402. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-safari-ios27.json +0 -0
  403. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-safari-typed.json +0 -0
  404. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-settings-interactable.json +0 -0
  405. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/ax-settings-ios27.json +0 -0
  406. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/development-certificate.pem +0 -0
  407. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-appearance.json +0 -0
  408. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-apps.json +0 -0
  409. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-devices.json +0 -0
  410. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-displays.json +0 -0
  411. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-launch-missing.json +0 -0
  412. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-launch.json +0 -0
  413. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-lock-state.json +0 -0
  414. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-processes.json +0 -0
  415. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/devicectl-screenshot.json +0 -0
  416. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-answers.json +0 -0
  417. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-alert.txt +0 -0
  418. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-home.txt +0 -0
  419. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-keyboard.txt +0 -0
  420. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-probe.txt +0 -0
  421. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-safari.txt +0 -0
  422. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-settings.txt +0 -0
  423. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/simctl-devices.json +0 -0
  424. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/simctl-runtimes.json +0 -0
  425. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list-names.json +0 -0
  426. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list.json +0 -0
  427. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-settings.json +0 -0
  428. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans-none.json +0 -0
  429. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans.json +0 -0
  430. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-broken.json +0 -0
  431. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-ok.json +0 -0
  432. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-results-unbuilt.json +0 -0
  433. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-retries.json +0 -0
  434. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing-xcode27.json +0 -0
  435. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing.json +0 -0
  436. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-unbuilt.json +0 -0
  437. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary.json +0 -0
  438. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-retries.json +0 -0
  439. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing-xcode27.json +0 -0
  440. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing.json +0 -0
  441. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests.json +0 -0
  442. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/pictures.py +0 -0
  443. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/recording.py +0 -0
  444. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/usbmux.py +0 -0
  445. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/vision.py +0 -0
  446. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/testing/wda.py +0 -0
  447. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/__init__.py +0 -0
  448. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/screenshot.py +0 -0
  449. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/tools/snapshot.py +0 -0
  450. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/src/sim_mirror/validation.py +0 -0
  451. {sim_mirror-2.0.1 → sim_mirror-2.2.0}/viewer/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sim-mirror
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.
5
5
  Project-URL: Homepage, https://github.com/AndrewKochulab/sim-mirror
6
6
  Project-URL: Documentation, https://github.com/AndrewKochulab/sim-mirror/tree/main/docs
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
39
39
 
40
40
  # SimMirror
41
41
 
42
- **Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.**
42
+ **Mirror and drive iOS Simulators, real iPhones and Android devices from Claude Code, CLI agents and the browser.**
43
43
 
44
44
  [![CI](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml/badge.svg)](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml)
45
45
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
@@ -52,15 +52,18 @@ through its notes, then fills in and saves a new one, the agent's cursor gliding
52
52
  *A real session, sped up four times. [The whole recording](https://github.com/AndrewKochulab/sim-mirror/releases/download/v1.2.0/sim-mirror-plugin-demo.mp4)
53
53
  runs 1m 39s.*
54
54
 
55
- A live iOS Simulator -- or your own iPhone, over its cable -- in any browser tab or web page, an animated cursor that
55
+ A live iOS Simulator -- or your own iPhone, over its cable, or an Android emulator or phone -- in any browser tab or
56
+ web page, an animated cursor that
56
57
  shows exactly what your AI agent is about to tap, and token-efficient UI snapshots so agents read the screen as compact
57
58
  text instead of screenshots. Record any screen as an MP4 or a GIF for a pull request. Works with Claude Code, Codex,
58
59
  Cursor and any other MCP client.
59
60
 
60
- > **Status: 2.0.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
61
- > release to [what SimMirror promises not to break](docs/stability.md). 2.0 brings real iPhones and iPads beside the
62
- > simulators -- live over the cable, driven through WebDriverAgent -- and records any screen as an MP4 or a GIF. It
63
- > removes nothing 1.x promised.
61
+ > **Status: 2.2.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
62
+ > release to [what SimMirror promises not to break](docs/stability.md). 2.0 brought real iPhones and iPads beside the
63
+ > simulators -- live over the cable, driven through WebDriverAgent -- and records any screen as an MP4 or a GIF. 2.1
64
+ > reads and touches apps turned to landscape, keeps taps off what a tab bar covers, builds the worktree the agent works
65
+ > in, and adds observe mode for walkthroughs on accounts an agent must not change. 2.2 adds Android emulators and
66
+ > phones, with the same viewer, cursor and tools. It removes nothing 1.x promised.
64
67
 
65
68
  ## Why SimMirror
66
69
 
@@ -88,7 +91,7 @@ Cursor and any other MCP client.
88
91
  **Any MCP client**, as a stdio server:
89
92
 
90
93
  ```sh
91
- claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
94
+ claude mcp add sim-mirror -- uvx --from sim-mirror==2.2.0 sim-mirror mcp
92
95
  ```
93
96
 
94
97
  Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
@@ -97,7 +100,7 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
97
100
  **The command**, for `sim-mirror open`, `doctor` and the rest:
98
101
 
99
102
  ```sh
100
- uv tool install sim-mirror==2.0.1
103
+ uv tool install sim-mirror==2.2.0
101
104
  # or
102
105
  brew install andrewkochulab/tap/sim-mirror
103
106
  ```
@@ -141,8 +144,13 @@ the viewer's own pointer glides there first (`agent.cursor_lead_ms`). Never the
141
144
 
142
145
  **Screen understanding.** A snapshot is a few lines -- `e2 button "General" (201,319)` -- with refs that stay with their
143
146
  elements across scrolls, a digest, and diffs. `sim_act` plays up to 20 steps in one call and waits for text, its
144
- absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. See
145
- [Screen understanding](docs/screen-understanding.md).
147
+ absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. An app turned to landscape is
148
+ read and touched in its own points, `sim_device rotate` turns a simulator, and a tap on what a tab bar, a toolbar or
149
+ the keyboard covers is refused with how to reach it. See [Screen understanding](docs/screen-understanding.md).
150
+
151
+ **Observe mode.** For a walkthrough on an account the agent must not change, `agent.observe` refuses a tap on what
152
+ reads like Delete, Save, Send or Sign out -- in English and a dozen other languages, or styled destructive by its app --
153
+ unless the call confirms it. See [Security](docs/security.md).
146
154
 
147
155
  **Reading pixels.** A screen whose accessibility says nothing -- a game, a canvas, an app still loading -- reads as the
148
156
  text macOS's Vision finds in it, with refs to tap and text to wait for, and the viewer can outline what was read
@@ -159,8 +167,14 @@ location through Xcode's `devicectl`; and touching, typing and its element tree
159
167
  with a click and signed by your project's own team -- a tap lands in about a third of a second. It is reached only over
160
168
  the cable, and what SimMirror changes on it is put back when it is let go. See [Real devices](docs/real-devices.md).
161
169
 
170
+ **Android.** Android emulators and phones sit beside the simulators, with the same viewer, cursor and tools: adb
171
+ lists, starts and drives them, scrcpy's server -- set up once with a click -- shows the screen live, UI Automator reads
172
+ it for snapshots, and `sim_build_run` and `sim_test` build a Gradle project with its own wrapper. A phone is never
173
+ started or shut down, and what SimMirror changes on it is put back. See [Android](docs/android.md).
174
+
162
175
  **Recording.** The viewer's Record button, `sim_record` or `sim-mirror record` keep a device's screen as an MP4, a GIF
163
- or both, with each touch drawn where it landed and a 9:41 status bar. See [Recording](docs/recording.md).
176
+ or both, with each touch drawn where it landed and a 9:41 status bar, and `preset: "pr"` keeps one small enough for
177
+ a pull request. See [Recording](docs/recording.md).
164
178
 
165
179
  **Connectors.** SimMirror's own native helper gives full control with nothing to install; idb_companion still can,
166
180
  and the simctl connector mirrors the screen view-only when neither is there, and says why. `auto` uses the fastest that
@@ -183,7 +197,9 @@ scoped tokens, and one-shot codes in URL fragments. See [Security](docs/security
183
197
 
184
198
  **Build and test.** `sim_build_run` and `sim_test` build with xcodebuild for the agent's simulator, install and
185
199
  launch, and answer with only what failed and where -- each failing test named so it can be run again alone, and on
186
- another simulator when asked. Off by default, since they run commands: `sim-mirror config set build.tools true`.
200
+ another simulator when asked -- and with the folder, branch and commit they built. They build the git worktree the
201
+ agent works in, with the configuration its scheme runs. Off by default, since they run commands: `sim-mirror config
202
+ set build.tools true`.
187
203
 
188
204
  ## Measure the token cost
189
205
 
@@ -210,6 +226,7 @@ an image at about 750 pixels a token).
210
226
  | Xcode | 26 and 27 |
211
227
  | Connectors | native helper (full control), idb_companion 1.5 (full control), simctl (view-only) |
212
228
  | Real devices | an iPhone or iPad that Xcode's `devicectl` reaches, by cable (view-only without WebDriverAgent) |
229
+ | Android | emulators and phones adb reaches, with the Android SDK; scrcpy 5.0.1 for the live screen; Gradle builds |
213
230
  | App SDK | Debug builds on the iOS Simulator, iOS 16 or later |
214
231
  | Clients | Claude Code, Codex, Cursor, any stdio MCP client |
215
232
  | Browsers | Chrome, Safari, Firefox, Edge |
@@ -234,8 +251,9 @@ the [command line](docs/reference/cli.md) and the [protocol](docs/reference/prot
234
251
  | **v0.2** | Build, run and test tools, a settings panel, Xcode 27's UI hierarchy through `mcpbridge`, one shared daemon for many hosts |
235
252
  | **v1.0** | Stable protocol and embedding API, held by a check; PyPI, npm and the MCP Registry; a cursor that stays while the agent works |
236
253
  | **v1.2** | SimMirror's own native helper instead of idb_companion, screens read from their pixels and settling past endless animations, an optional in-app debug SDK, and a Homebrew tap |
237
- | **v2.0** (now) | Real iPhones and iPads: the live screen over the cable, apps, logs, look and location without signing; full control through WebDriverAgent; recording a screen as an MP4 or GIF |
238
- | **Android** | Android emulator support with the same viewer, cursor and tools |
254
+ | **v2.0** | Real iPhones and iPads: the live screen over the cable, apps, logs, look and location without signing; full control through WebDriverAgent; recording a screen as an MP4 or GIF |
255
+ | **v2.1** | Apps turned to landscape and `sim_device rotate`, taps kept off what bars cover, builds of the agent's worktree, observe mode, `sim-mirror stop`, and recordings sized for a pull request |
256
+ | **v2.2** (now) | Android emulators and phones with the same viewer, cursor and tools: adb, scrcpy's live screen, and Gradle builds |
239
257
  | **Website & launch** | Landing page, video tutorials and guides |
240
258
 
241
259
  See [ROADMAP.md](ROADMAP.md) and the [milestones](https://github.com/AndrewKochulab/sim-mirror/milestones). How
@@ -2,7 +2,7 @@
2
2
 
3
3
  # SimMirror
4
4
 
5
- **Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.**
5
+ **Mirror and drive iOS Simulators, real iPhones and Android devices from Claude Code, CLI agents and the browser.**
6
6
 
7
7
  [![CI](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml/badge.svg)](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml)
8
8
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
@@ -15,15 +15,18 @@ through its notes, then fills in and saves a new one, the agent's cursor gliding
15
15
  *A real session, sped up four times. [The whole recording](https://github.com/AndrewKochulab/sim-mirror/releases/download/v1.2.0/sim-mirror-plugin-demo.mp4)
16
16
  runs 1m 39s.*
17
17
 
18
- A live iOS Simulator -- or your own iPhone, over its cable -- in any browser tab or web page, an animated cursor that
18
+ A live iOS Simulator -- or your own iPhone, over its cable, or an Android emulator or phone -- in any browser tab or
19
+ web page, an animated cursor that
19
20
  shows exactly what your AI agent is about to tap, and token-efficient UI snapshots so agents read the screen as compact
20
21
  text instead of screenshots. Record any screen as an MP4 or a GIF for a pull request. Works with Claude Code, Codex,
21
22
  Cursor and any other MCP client.
22
23
 
23
- > **Status: 2.0.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
24
- > release to [what SimMirror promises not to break](docs/stability.md). 2.0 brings real iPhones and iPads beside the
25
- > simulators -- live over the cable, driven through WebDriverAgent -- and records any screen as an MP4 or a GIF. It
26
- > removes nothing 1.x promised.
24
+ > **Status: 2.2.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
25
+ > release to [what SimMirror promises not to break](docs/stability.md). 2.0 brought real iPhones and iPads beside the
26
+ > simulators -- live over the cable, driven through WebDriverAgent -- and records any screen as an MP4 or a GIF. 2.1
27
+ > reads and touches apps turned to landscape, keeps taps off what a tab bar covers, builds the worktree the agent works
28
+ > in, and adds observe mode for walkthroughs on accounts an agent must not change. 2.2 adds Android emulators and
29
+ > phones, with the same viewer, cursor and tools. It removes nothing 1.x promised.
27
30
 
28
31
  ## Why SimMirror
29
32
 
@@ -51,7 +54,7 @@ Cursor and any other MCP client.
51
54
  **Any MCP client**, as a stdio server:
52
55
 
53
56
  ```sh
54
- claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
57
+ claude mcp add sim-mirror -- uvx --from sim-mirror==2.2.0 sim-mirror mcp
55
58
  ```
56
59
 
57
60
  Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
@@ -60,7 +63,7 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
60
63
  **The command**, for `sim-mirror open`, `doctor` and the rest:
61
64
 
62
65
  ```sh
63
- uv tool install sim-mirror==2.0.1
66
+ uv tool install sim-mirror==2.2.0
64
67
  # or
65
68
  brew install andrewkochulab/tap/sim-mirror
66
69
  ```
@@ -104,8 +107,13 @@ the viewer's own pointer glides there first (`agent.cursor_lead_ms`). Never the
104
107
 
105
108
  **Screen understanding.** A snapshot is a few lines -- `e2 button "General" (201,319)` -- with refs that stay with their
106
109
  elements across scrolls, a digest, and diffs. `sim_act` plays up to 20 steps in one call and waits for text, its
107
- absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. See
108
- [Screen understanding](docs/screen-understanding.md).
110
+ absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. An app turned to landscape is
111
+ read and touched in its own points, `sim_device rotate` turns a simulator, and a tap on what a tab bar, a toolbar or
112
+ the keyboard covers is refused with how to reach it. See [Screen understanding](docs/screen-understanding.md).
113
+
114
+ **Observe mode.** For a walkthrough on an account the agent must not change, `agent.observe` refuses a tap on what
115
+ reads like Delete, Save, Send or Sign out -- in English and a dozen other languages, or styled destructive by its app --
116
+ unless the call confirms it. See [Security](docs/security.md).
109
117
 
110
118
  **Reading pixels.** A screen whose accessibility says nothing -- a game, a canvas, an app still loading -- reads as the
111
119
  text macOS's Vision finds in it, with refs to tap and text to wait for, and the viewer can outline what was read
@@ -122,8 +130,14 @@ location through Xcode's `devicectl`; and touching, typing and its element tree
122
130
  with a click and signed by your project's own team -- a tap lands in about a third of a second. It is reached only over
123
131
  the cable, and what SimMirror changes on it is put back when it is let go. See [Real devices](docs/real-devices.md).
124
132
 
133
+ **Android.** Android emulators and phones sit beside the simulators, with the same viewer, cursor and tools: adb
134
+ lists, starts and drives them, scrcpy's server -- set up once with a click -- shows the screen live, UI Automator reads
135
+ it for snapshots, and `sim_build_run` and `sim_test` build a Gradle project with its own wrapper. A phone is never
136
+ started or shut down, and what SimMirror changes on it is put back. See [Android](docs/android.md).
137
+
125
138
  **Recording.** The viewer's Record button, `sim_record` or `sim-mirror record` keep a device's screen as an MP4, a GIF
126
- or both, with each touch drawn where it landed and a 9:41 status bar. See [Recording](docs/recording.md).
139
+ or both, with each touch drawn where it landed and a 9:41 status bar, and `preset: "pr"` keeps one small enough for
140
+ a pull request. See [Recording](docs/recording.md).
127
141
 
128
142
  **Connectors.** SimMirror's own native helper gives full control with nothing to install; idb_companion still can,
129
143
  and the simctl connector mirrors the screen view-only when neither is there, and says why. `auto` uses the fastest that
@@ -146,7 +160,9 @@ scoped tokens, and one-shot codes in URL fragments. See [Security](docs/security
146
160
 
147
161
  **Build and test.** `sim_build_run` and `sim_test` build with xcodebuild for the agent's simulator, install and
148
162
  launch, and answer with only what failed and where -- each failing test named so it can be run again alone, and on
149
- another simulator when asked. Off by default, since they run commands: `sim-mirror config set build.tools true`.
163
+ another simulator when asked -- and with the folder, branch and commit they built. They build the git worktree the
164
+ agent works in, with the configuration its scheme runs. Off by default, since they run commands: `sim-mirror config
165
+ set build.tools true`.
150
166
 
151
167
  ## Measure the token cost
152
168
 
@@ -173,6 +189,7 @@ an image at about 750 pixels a token).
173
189
  | Xcode | 26 and 27 |
174
190
  | Connectors | native helper (full control), idb_companion 1.5 (full control), simctl (view-only) |
175
191
  | Real devices | an iPhone or iPad that Xcode's `devicectl` reaches, by cable (view-only without WebDriverAgent) |
192
+ | Android | emulators and phones adb reaches, with the Android SDK; scrcpy 5.0.1 for the live screen; Gradle builds |
176
193
  | App SDK | Debug builds on the iOS Simulator, iOS 16 or later |
177
194
  | Clients | Claude Code, Codex, Cursor, any stdio MCP client |
178
195
  | Browsers | Chrome, Safari, Firefox, Edge |
@@ -197,8 +214,9 @@ the [command line](docs/reference/cli.md) and the [protocol](docs/reference/prot
197
214
  | **v0.2** | Build, run and test tools, a settings panel, Xcode 27's UI hierarchy through `mcpbridge`, one shared daemon for many hosts |
198
215
  | **v1.0** | Stable protocol and embedding API, held by a check; PyPI, npm and the MCP Registry; a cursor that stays while the agent works |
199
216
  | **v1.2** | SimMirror's own native helper instead of idb_companion, screens read from their pixels and settling past endless animations, an optional in-app debug SDK, and a Homebrew tap |
200
- | **v2.0** (now) | Real iPhones and iPads: the live screen over the cable, apps, logs, look and location without signing; full control through WebDriverAgent; recording a screen as an MP4 or GIF |
201
- | **Android** | Android emulator support with the same viewer, cursor and tools |
217
+ | **v2.0** | Real iPhones and iPads: the live screen over the cable, apps, logs, look and location without signing; full control through WebDriverAgent; recording a screen as an MP4 or GIF |
218
+ | **v2.1** | Apps turned to landscape and `sim_device rotate`, taps kept off what bars cover, builds of the agent's worktree, observe mode, `sim-mirror stop`, and recordings sized for a pull request |
219
+ | **v2.2** (now) | Android emulators and phones with the same viewer, cursor and tools: adb, scrcpy's live screen, and Gradle builds |
202
220
  | **Website & launch** | Landing page, video tutorials and guides |
203
221
 
204
222
  See [ROADMAP.md](ROADMAP.md) and the [milestones](https://github.com/AndrewKochulab/sim-mirror/milestones). How
@@ -127,5 +127,32 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
127
127
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
128
128
  ```
129
129
 
130
+ ## scrcpy server
131
+
132
+ - **What:** the server SimMirror runs on an Android device to stream its screen as H.264 and take its touches, keys
133
+ and text ([Android](docs/android.md)). It is **not** shipped: a person's setup (Set up touch, or `sim-mirror android
134
+ setup`) fetches release 5.0.1's `scrcpy-server-v5.0.1`, checks its size and SHA-256
135
+ (`764eb6f79811d5211fe9df341120882ba9994c7a61b897d7bf3fb662e53bc536`), and keeps it on your Mac; it is pushed to a
136
+ device only while SimMirror shows it, and is unchanged.
137
+ - **Source:** [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
138
+ - **License:** Apache-2.0
139
+
140
+ ```
141
+ Copyright (C) 2018 Genymobile
142
+ Copyright (C) 2018-2026 Romain Vimont
143
+
144
+ Licensed under the Apache License, Version 2.0 (the "License");
145
+ you may not use this file except in compliance with the License.
146
+ You may obtain a copy of the License at
147
+
148
+ http://www.apache.org/licenses/LICENSE-2.0
149
+
150
+ Unless required by applicable law or agreed to in writing, software
151
+ distributed under the License is distributed on an "AS IS" BASIS,
152
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
153
+ See the License for the specific language governing permissions and
154
+ limitations under the License.
155
+ ```
156
+
130
157
  Runtime dependencies installed from PyPI and npm (FastAPI, Uvicorn, grpclib, protobuf, tomlkit, Pillow and others) are
131
158
  not redistributed here; each carries its own license in its package.
@@ -13,10 +13,11 @@
13
13
  - [Cursor](clients/cursor.md)
14
14
  - [Any other MCP client](clients/other-mcp.md)
15
15
 
16
- ## Use it with a real device, and record
16
+ ## Use it with a real device or Android, and record
17
17
 
18
18
  - [Real devices](real-devices.md) -- a cabled iPhone or iPad: its live screen, apps and logs, and WebDriverAgent for
19
19
  touch, typing and the element tree
20
+ - [Android](android.md) -- Android emulators and phones: adb, scrcpy's live screen, and Gradle builds and tests
20
21
  - [Recording](recording.md) -- a screen as an MP4 or a GIF, with each touch drawn, and a demo in a pull request
21
22
 
22
23
  ## Change its settings
@@ -14,7 +14,9 @@ Each folder is one way to use SimMirror, small enough to copy.
14
14
  | [custom-connector](custom-connector/) | A connector of your own, installed through the `sim_mirror.connectors` entry point, with contract tests |
15
15
  | [token-budget](token-budget/) | Measuring what each tool's answer costs an agent |
16
16
  | [app-sdk](app-sdk/) | An iOS app sharing its own views through SimMirrorKit: icon buttons, a tap gesture, a hand-drawn control with a describer, `.simMirror` tags |
17
+ | [android-app](android-app/) | An Android app and its instrumented tests, which `sim_build_run` and `sim_test` build with its Gradle wrapper |
17
18
 
18
- Every example assumes a Mac with Xcode and a booted-or-bootable simulator, and `sim-mirror doctor` passing. The
19
+ Every example assumes a Mac with Xcode and a booted-or-bootable simulator -- android-app, the Android SDK and an
20
+ emulator instead -- and `sim-mirror doctor` passing. The
19
21
  examples are tested with SimMirror's own suite (`tests/examples/`), without starting a simulator; app-sdk's hosted
20
22
  tests run on a simulator with `make sdk-app-test`.
@@ -0,0 +1,37 @@
1
+ # Android app
2
+
3
+ A grocery list in one Kotlin activity, with two instrumented tests: the smallest Android project `sim_build_run` and
4
+ `sim_test` build, install and test on an Android emulator or phone.
5
+
6
+ ```
7
+ examples/android-app/
8
+ ├── settings.gradle.kts the Gradle build SimMirror finds
9
+ ├── gradlew, gradle/wrapper/ its own wrapper: Gradle 9.3.1, checked by its SHA-256
10
+ └── app/ the one app module (AGP 9.1, Kotlin built in)
11
+ ├── src/main/ MainActivity: type an item, press Add
12
+ └── src/androidTest/ MainActivityTest: startsEmpty, addsAnItem
13
+ ```
14
+
15
+ ## Try it
16
+
17
+ With an Android emulator chosen (`sim_device list`, then `choose`) and `build.tools` on, point the build at this
18
+ folder and ask the agent to
19
+
20
+ - build and run it: `sim_build_run` with `folder: "examples/android-app"` builds `:app:assembleDebug`, installs
21
+ `app-debug.apk` and launches `dev.simmirror.example`;
22
+ - run its tests: `sim_test` runs `:app:connectedDebugAndroidTest` on the device and answers with what failed and where;
23
+ `only_testing: ["dev.simmirror.example.MainActivityTest/addsAnItem"]` runs one;
24
+ - launch it with an item already on the list: `sim_app` `launch` with `args: ["item=Milk"]` -- each launch argument is
25
+ a string extra of the launch intent.
26
+
27
+ Gradle needs a JDK (17 or later): Android Studio's own is found, or name one with `android.java_home`. The first build
28
+ fetches Gradle and the Android Gradle plugin, which takes a minute; later builds take seconds.
29
+
30
+ From a shell, the same build is
31
+
32
+ ```sh
33
+ cd examples/android-app
34
+ ./gradlew :app:assembleDebug
35
+ ```
36
+
37
+ SimMirror's own suite never builds this project; `make live-android` does, on an emulator.
@@ -16,7 +16,7 @@ Its tools are named `mcp__plugin_sim-mirror_sim-mirror__sim_snapshot` and so on.
16
16
  ## Without the plugin
17
17
 
18
18
  ```sh
19
- claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
19
+ claude mcp add sim-mirror -- uvx --from sim-mirror==2.2.0 sim-mirror mcp
20
20
  ```
21
21
 
22
22
  Add `--scope project` before the name to write it to the project's `.mcp.json` for everyone working on it. Its tools
@@ -30,7 +30,7 @@ simulator (its scope) and this folder as the only place builds and installs may
30
30
  ## Watch it
31
31
 
32
32
  ```sh
33
- uvx --from sim-mirror==2.0.1 sim-mirror open
33
+ uvx --from sim-mirror==2.2.0 sim-mirror open
34
34
  ```
35
35
 
36
36
  opens this folder's viewer in your browser. Every gesture the agent makes is drawn there as a cursor just before it
@@ -6,7 +6,7 @@ Add [config.toml](config.toml)'s table to `~/.codex/config.toml`, then start Cod
6
6
  and installs may reach. The commented `args` pin a project by name and path instead.
7
7
  - `tool_timeout_sec` is raised because `sim_build_run` and `sim_test` can take minutes (they are there only when build
8
8
  tools are switched on).
9
- - Watch and take over in a browser: `uvx --from sim-mirror==2.0.1 sim-mirror open`
9
+ - Watch and take over in a browser: `uvx --from sim-mirror==2.2.0 sim-mirror open`
10
10
  in the same folder.
11
11
 
12
12
  The tools are the same for every client; see [claude-code-cli](../claude-code-cli/) for a first prompt to try.
@@ -6,7 +6,7 @@ one), and enable the server in Cursor's MCP settings.
6
6
  - `sim-mirror mcp` gives the agent the simulator of the folder it starts in, and that folder as the only place builds
7
7
  and installs may reach. If the tools act on the wrong project, append `"--scope", "my-app", "--root",
8
8
  "/absolute/path/to/my-app"` to `args`.
9
- - Watch and take over in a browser: `uvx --from sim-mirror==2.0.1 sim-mirror open`
9
+ - Watch and take over in a browser: `uvx --from sim-mirror==2.2.0 sim-mirror open`
10
10
  in the same folder.
11
11
 
12
12
  The tools are the same for every client; see [claude-code-cli](../claude-code-cli/) for a first prompt to try.
@@ -5,7 +5,7 @@ A Python application with SimMirror inside it: [app.py](app.py). No daemon runs;
5
5
 
6
6
  ```sh
7
7
  cd examples/fastapi-embed
8
- EXAMPLE_KEY=change-me uv run --with "sim-mirror==2.0.1" \
8
+ EXAMPLE_KEY=change-me uv run --with "sim-mirror==2.2.0" \
9
9
  uvicorn --factory app:create_app --port 7484
10
10
  ```
11
11
 
@@ -36,12 +36,17 @@ public enum AccessibilityValue: Equatable, Sendable {
36
36
  /// One element of a screen's accessibility tree, read from the device.
37
37
  ///
38
38
  /// Only what SimMirror reads of an element is kept. Each attribute is a round trip to the simulator, and a snapshot
39
- /// reads every element, so an attribute nothing reads -- a role's description, help text, custom actions -- is not
40
- /// asked for at all.
39
+ /// reads every element, so an attribute nothing reads -- help text, custom actions -- is not asked for at all. A role's
40
+ /// description is asked of a group only: it is how a navigation bar says what it is.
41
41
  public struct AXNode: Equatable, Sendable {
42
+ /// The role of an element that only holds others, as a navigation bar, a tab bar and a toolbar are listed.
43
+ public static let groupRole = "AXGroup"
44
+
42
45
  /// Its role as the accessibility API says it, ``AXButton``.
43
46
  public var role: String?
44
47
  public var subrole: String?
48
+ /// What its role is called, in the device's language: ``Nav bar`` for a navigation bar UIKit lists as a group.
49
+ public var roleDescription: String?
45
50
  public var label: String?
46
51
  public var title: String?
47
52
  public var identifier: String?
@@ -53,12 +58,14 @@ public struct AXNode: Equatable, Sendable {
53
58
  public var children: [AXNode]
54
59
 
55
60
  public init(
56
- role: String? = nil, subrole: String? = nil, label: String? = nil, title: String? = nil,
57
- identifier: String? = nil, value: AccessibilityValue? = nil, frame: Rect = Rect(x: 0, y: 0, width: 0, height: 0),
58
- traits: UInt64? = nil, enabled: Bool = true, children: [AXNode] = []
61
+ role: String? = nil, subrole: String? = nil, roleDescription: String? = nil, label: String? = nil,
62
+ title: String? = nil, identifier: String? = nil, value: AccessibilityValue? = nil,
63
+ frame: Rect = Rect(x: 0, y: 0, width: 0, height: 0), traits: UInt64? = nil, enabled: Bool = true,
64
+ children: [AXNode] = []
59
65
  ) {
60
66
  self.role = role
61
67
  self.subrole = subrole
68
+ self.roleDescription = roleDescription
62
69
  self.label = label
63
70
  self.title = title
64
71
  self.identifier = identifier
@@ -111,12 +118,15 @@ public enum AXDocument {
111
118
  "Tab", "Key", "DisclosureTriangle",
112
119
  ]
113
120
 
114
- public static func make(root: AXNode, screen: ScreenGeometry) -> JSONValue {
115
- let elements = kept(root)
121
+ /// The document of a tree. Its frames are the interface's points, and its ``orientation`` says how that interface
122
+ /// lies on the screen, whose portrait size ``screen`` is.
123
+ public static func make(_ tree: AXTree, screen: ScreenGeometry) -> JSONValue {
124
+ let elements = kept(tree.root)
116
125
  return .object([
117
126
  "backend": .string("native"),
118
127
  "elements": .array(elements),
119
128
  "modal": .null,
129
+ "orientation": .string(tree.orientation.rawValue),
120
130
  "screen": .object([
121
131
  "coordinate_space": .string("screen"),
122
132
  "width": .int(screen.widthPt),
@@ -126,6 +136,11 @@ public enum AXDocument {
126
136
  ])
127
137
  }
128
138
 
139
+ /// What was found at points of the screen: each element alone, as the document writes one, or null.
140
+ public static func found(_ nodes: [AXNode?]) -> JSONValue {
141
+ .object(["elements": .array(nodes.map { $0.map { element($0, children: []) } ?? .null })])
142
+ }
143
+
129
144
  /// A role without its ``AX`` prefix: ``AXButton`` is ``Button``.
130
145
  public static func type(of role: String?) -> String? {
131
146
  guard let role else { return nil }
@@ -148,6 +163,7 @@ public enum AXDocument {
148
163
  .object([
149
164
  "type": .text(type(of: node.role)),
150
165
  "subrole": .text(node.subrole),
166
+ "role_description": .text(node.roleDescription),
151
167
  "label": .text(node.label),
152
168
  "title": .text(node.title),
153
169
  "identifier": .text(node.identifier),
@@ -142,7 +142,7 @@ public struct ServeOptions: Equatable, Sendable {
142
142
  /// What `sim-mirror-helper version` prints.
143
143
  public struct VersionReport: Equatable, Sendable, Encodable {
144
144
  /// What this helper can do besides serving a simulator, so SimMirror asks only a helper that can.
145
- public static let features = ["render", "capture"]
145
+ public static let features = ["render", "capture", "rotate", "at"]
146
146
 
147
147
  public let version: String
148
148
  public let wire: Int
@@ -217,7 +217,7 @@ public struct SelfCheckReport: Equatable, Sendable, Encodable {
217
217
  return "\(jpeg.width)x\(jpeg.height), \(jpeg.data.count) bytes"
218
218
  }
219
219
  await check("input") { "through \(try device.input().transport.name)" }
220
- await check("element tree") { "\(count(try await device.tree())) elements" }
220
+ await check("element tree") { "\(count(try await device.tree().root)) elements" }
221
221
  return SelfCheckReport(parts: parts)
222
222
  }
223
223
 
@@ -2,7 +2,7 @@
2
2
  import Foundation
3
3
 
4
4
  /// One input event, as SimMirror sends it: a finger at a point, a hardware button or a keyboard key, down or up.
5
- /// Points are the device's points, portrait.
5
+ /// Points are the screen's own portrait points, whichever way an app's interface is turned: so is a simulator's digitizer.
6
6
  public struct HIDEvent: Equatable, Sendable, Codable {
7
7
  public enum Kind: String, Sendable, Codable { case touch, button, key }
8
8
  public enum Phase: String, Sendable, Codable { case down, up }
@@ -0,0 +1,117 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import Foundation
3
+
4
+ /// How an app's interface lies on the device's screen, as UIKit names the four ways.
5
+ ///
6
+ /// A simulator's screen never turns: its framebuffer, its touches and `ScreenGeometry` are the device's portrait points
7
+ /// whichever way the interface is drawn. Accessibility answers in the interface's own points, so a tree read from an app
8
+ /// turned to landscape is 874 wide on a screen 402 wide. The orientation is what turns one into the other. The device
9
+ /// turned in Simulator.app is no different: measured on iOS 26.5, its framebuffer stays portrait too.
10
+ public enum InterfaceOrientation: String, Sendable, CaseIterable {
11
+ case portrait
12
+ case portraitUpsideDown = "portrait_upside_down"
13
+ case landscapeLeft = "landscape_left"
14
+ case landscapeRight = "landscape_right"
15
+
16
+ /// Whether the interface is the screen turned a quarter, so its width is the screen's height.
17
+ public var sideways: Bool {
18
+ self == .landscapeLeft || self == .landscapeRight
19
+ }
20
+
21
+ /// Where a point on the screen, in the screen's portrait points, is in the interface's points.
22
+ ///
23
+ /// Measured on an iPhone 17 Pro simulator: an app turned to landscape right is drawn with its top along the
24
+ /// screen's right edge, so the screen's top right corner is the interface's top left.
25
+ public func interfacePoint(x: Double, y: Double, on screen: ScreenGeometry) -> (x: Double, y: Double) {
26
+ let width = Double(screen.widthPt)
27
+ let height = Double(screen.heightPt)
28
+ switch self {
29
+ case .portrait: return (x, y)
30
+ case .portraitUpsideDown: return (width - x, height - y)
31
+ case .landscapeRight: return (y, width - x)
32
+ case .landscapeLeft: return (height - y, x)
33
+ }
34
+ }
35
+
36
+ /// Where a point of the interface, in its points, is on the screen, in the screen's portrait points.
37
+ public func screenPoint(x: Double, y: Double, on screen: ScreenGeometry) -> (x: Double, y: Double) {
38
+ let width = Double(screen.widthPt)
39
+ let height = Double(screen.heightPt)
40
+ switch self {
41
+ case .portrait: return (x, y)
42
+ case .portraitUpsideDown: return (width - x, height - y)
43
+ case .landscapeRight: return (width - y, x)
44
+ case .landscapeLeft: return (y, height - x)
45
+ }
46
+ }
47
+
48
+ /// How many of a tree's elements are looked for on the screen before points of it are.
49
+ static let elementsAsked = 4
50
+
51
+ /// The share of the screen probed for which way the interface is turned, across and down: inside the edges, and
52
+ /// nowhere two orientations put the same interface point.
53
+ static let probes: [(Double, Double)] = [
54
+ (0.3, 0.25), (0.7, 0.25), (0.3, 0.75), (0.7, 0.75), (0.5, 0.15), (0.5, 0.85), (0.2, 0.5), (0.8, 0.5),
55
+ ]
56
+
57
+ /// Which way a tree's interface is turned, from the size of its root -- the frontmost application -- and what is
58
+ /// found at points of the screen.
59
+ ///
60
+ /// An interface as wide as the screen is portrait or upside down, and one as wide as the screen is tall is one of the
61
+ /// landscapes. Which of the two is told by asking what is at a point of the screen (`hit`, answering the frame of
62
+ /// the element there in the interface's points, as the tree's are). The middle of one of the tree's small elements
63
+ /// is asked for where each of the two would put it on the screen: only the right one finds an element there. Then
64
+ /// points spread over the screen are asked: only the right one puts the point inside what is found. A point that
65
+ /// finds nothing, or an element both would put it in, tells nothing, and the next is asked. When none tells, the
66
+ /// first of the two: portrait, or landscape right -- where UIKit puts an app that takes either landscape.
67
+ public static func find(
68
+ _ root: AXNode, screen: ScreenGeometry, hit: (_ x: Double, _ y: Double) -> Rect?
69
+ ) -> InterfaceOrientation {
70
+ let interface = root.frame
71
+ let wideScreen = screen.widthPt > screen.heightPt
72
+ let quarter = interface.width > 0 && interface.height > 0 && (interface.width > interface.height) != wideScreen
73
+ let candidates: [InterfaceOrientation] = quarter ? [.landscapeRight, .landscapeLeft] : [.portrait, .portraitUpsideDown]
74
+ let most = Double(screen.widthPt * screen.heightPt) / 4
75
+ let small = leaves(root).filter { $0.width >= 4 && $0.height >= 4 && $0.width * $0.height <= most }
76
+ for frame in small.prefix(elementsAsked) {
77
+ let middle = (x: frame.x + frame.width / 2, y: frame.y + frame.height / 2)
78
+ let fitting = candidates.filter { candidate in
79
+ let point = candidate.screenPoint(x: middle.x, y: middle.y, on: screen)
80
+ return hit(point.x, point.y)?.contains(point: middle) ?? false
81
+ }
82
+ if fitting.count == 1 { return fitting[0] }
83
+ }
84
+ for (across, down) in probes {
85
+ let x = across * Double(screen.widthPt)
86
+ let y = down * Double(screen.heightPt)
87
+ guard let found = hit(x, y), found.width > 0, found.height > 0 else { continue }
88
+ let fitting = candidates.filter { found.contains(point: $0.interfacePoint(x: x, y: y, on: screen)) }
89
+ if fitting.count == 1 { return fitting[0] }
90
+ }
91
+ return candidates[0]
92
+ }
93
+
94
+ /// The frames of the elements with nothing inside them, in the tree's order.
95
+ static func leaves(_ node: AXNode) -> [Rect] {
96
+ node.children.isEmpty ? [node.frame] : node.children.flatMap(leaves)
97
+ }
98
+ }
99
+
100
+ extension Rect {
101
+ /// Whether a point is inside, edges included, give or take half a point.
102
+ func contains(point: (x: Double, y: Double)) -> Bool {
103
+ let slack = 0.5
104
+ return point.x >= x - slack && point.x <= x + width + slack && point.y >= y - slack && point.y <= y + height + slack
105
+ }
106
+ }
107
+
108
+ /// A screen's accessibility tree and the way its interface is turned, which its frames are measured in.
109
+ public struct AXTree: Equatable, Sendable {
110
+ public let root: AXNode
111
+ public let orientation: InterfaceOrientation
112
+
113
+ public init(root: AXNode, orientation: InterfaceOrientation = .portrait) {
114
+ self.root = root
115
+ self.orientation = orientation
116
+ }
117
+ }