sim-mirror 0.2.0__tar.gz → 1.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 (296) hide show
  1. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/.gitignore +8 -0
  2. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/NOTICE +3 -0
  3. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/PKG-INFO +42 -22
  4. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/README.md +40 -21
  5. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/THIRD_PARTY_LICENSES.md +29 -2
  6. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/docs/README.md +2 -0
  7. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/README.md +3 -1
  8. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/claude-code-cli/README.md +2 -2
  9. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/codex/README.md +1 -1
  10. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/cursor/README.md +1 -1
  11. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/fastapi-embed/README.md +1 -1
  12. sim_mirror-1.2.0/helper/Package.swift +35 -0
  13. sim_mirror-1.2.0/helper/Sources/HelperCore/Accessibility.swift +161 -0
  14. sim_mirror-1.2.0/helper/Sources/HelperCore/Arguments.swift +202 -0
  15. sim_mirror-1.2.0/helper/Sources/HelperCore/FrameScheduler.swift +72 -0
  16. sim_mirror-1.2.0/helper/Sources/HelperCore/Input.swift +171 -0
  17. sim_mirror-1.2.0/helper/Sources/HelperCore/JSON.swift +84 -0
  18. sim_mirror-1.2.0/helper/Sources/HelperCore/Requests.swift +109 -0
  19. sim_mirror-1.2.0/helper/Sources/HelperCore/Screen.swift +141 -0
  20. sim_mirror-1.2.0/helper/Sources/HelperCore/Server.swift +186 -0
  21. sim_mirror-1.2.0/helper/Sources/HelperCore/Video.swift +100 -0
  22. sim_mirror-1.2.0/helper/Sources/HelperCore/Wire.swift +122 -0
  23. sim_mirror-1.2.0/helper/Sources/HelperPlatform/AccessibilityReader.swift +154 -0
  24. sim_mirror-1.2.0/helper/Sources/HelperPlatform/Framebuffer.swift +163 -0
  25. sim_mirror-1.2.0/helper/Sources/HelperPlatform/Frameworks.swift +84 -0
  26. sim_mirror-1.2.0/helper/Sources/HelperPlatform/H264Stream.swift +288 -0
  27. sim_mirror-1.2.0/helper/Sources/HelperPlatform/HIDTransports.swift +149 -0
  28. sim_mirror-1.2.0/helper/Sources/HelperPlatform/Pictures.swift +73 -0
  29. sim_mirror-1.2.0/helper/Sources/HelperPlatform/SimDeviceHandle.swift +67 -0
  30. sim_mirror-1.2.0/helper/Sources/HelperPlatform/SimulatorDevice.swift +155 -0
  31. sim_mirror-1.2.0/helper/Sources/HelperPlatform/UnixSocket.swift +119 -0
  32. sim_mirror-1.2.0/helper/Sources/ObjCGuard/ObjCGuard.m +11 -0
  33. sim_mirror-1.2.0/helper/Sources/ObjCGuard/include/ObjCGuard.h +12 -0
  34. sim_mirror-1.2.0/helper/Sources/sim-mirror-helper/main.swift +85 -0
  35. sim_mirror-1.2.0/helper/Tests/Fixtures/wire-vectors.json +61 -0
  36. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/AccessibilityTests.swift +59 -0
  37. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/ArgumentsTests.swift +57 -0
  38. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/InputTests.swift +90 -0
  39. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/PictureTests.swift +129 -0
  40. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/ServerTests.swift +249 -0
  41. sim_mirror-1.2.0/helper/Tests/HelperCoreTests/WireTests.swift +121 -0
  42. sim_mirror-1.2.0/protocol/README.md +91 -0
  43. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/error-inactive.json +6 -0
  44. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/error-unauthorized.json +6 -0
  45. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-alert.json +156 -0
  46. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-keyboard.json +446 -0
  47. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-swiftui.json +234 -0
  48. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-tagged.json +254 -0
  49. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-uikit.json +375 -0
  50. sim_mirror-1.2.0/protocol/app-sdk/v1/examples/listing.json +12 -0
  51. sim_mirror-1.2.0/protocol/app-sdk/v1/hierarchy.schema.json +167 -0
  52. sim_mirror-1.2.0/protocol/app-sdk/v1/listing.schema.json +60 -0
  53. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/agent-event.schema.json +24 -3
  54. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/common.schema.json +16 -2
  55. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/constants.json +3 -1
  56. sim_mirror-1.2.0/protocol/v1/http.schema.json +184 -0
  57. sim_mirror-1.2.0/protocol/v1/screen-text.schema.json +43 -0
  58. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/settings.schema.json +7 -2
  59. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/pyproject.toml +25 -1
  60. sim_mirror-1.2.0/scripts/hatch_build.py +74 -0
  61. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/_version.py +1 -1
  62. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/api.py +3 -3
  63. sim_mirror-1.2.0/src/sim_mirror/cli/app_command.py +129 -0
  64. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/context.py +3 -0
  65. sim_mirror-1.2.0/src/sim_mirror/cli/helper.py +122 -0
  66. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/main.py +6 -1
  67. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/model.py +20 -0
  68. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/schema.py +125 -9
  69. sim_mirror-1.2.0/src/sim_mirror/connectors/__init__.py +8 -0
  70. sim_mirror-1.2.0/src/sim_mirror/connectors/app/__init__.py +12 -0
  71. sim_mirror-1.2.0/src/sim_mirror/connectors/app/client.py +126 -0
  72. sim_mirror-1.2.0/src/sim_mirror/connectors/app/discovery.py +155 -0
  73. sim_mirror-1.2.0/src/sim_mirror/connectors/app/document.py +227 -0
  74. sim_mirror-1.2.0/src/sim_mirror/connectors/app/errors.py +30 -0
  75. sim_mirror-1.2.0/src/sim_mirror/connectors/app/merge.py +95 -0
  76. sim_mirror-1.2.0/src/sim_mirror/connectors/app/reader.py +218 -0
  77. sim_mirror-1.2.0/src/sim_mirror/connectors/app/wire.py +36 -0
  78. sim_mirror-0.2.0/src/sim_mirror/connectors/idb/companion.py → sim_mirror-1.2.0/src/sim_mirror/connectors/helper_process.py +105 -131
  79. sim_mirror-1.2.0/src/sim_mirror/connectors/idb/companion.py +195 -0
  80. sim_mirror-1.2.0/src/sim_mirror/connectors/mcpbridge/client.py +138 -0
  81. sim_mirror-1.2.0/src/sim_mirror/connectors/native/__init__.py +2 -0
  82. sim_mirror-1.2.0/src/sim_mirror/connectors/native/client.py +240 -0
  83. sim_mirror-1.2.0/src/sim_mirror/connectors/native/connector.py +170 -0
  84. sim_mirror-1.2.0/src/sim_mirror/connectors/native/helper.py +249 -0
  85. sim_mirror-1.2.0/src/sim_mirror/connectors/native/wire.py +98 -0
  86. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/registry.py +27 -8
  87. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/connector.py +4 -4
  88. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/actions.py +117 -18
  89. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/instance.py +12 -1
  90. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/manager.py +63 -9
  91. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/runtime.py +35 -8
  92. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/screen_relay.py +12 -1
  93. sim_mirror-1.2.0/src/sim_mirror/core/text_overlay.py +71 -0
  94. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/app.py +12 -4
  95. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/checks.py +162 -5
  96. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/tap.py +5 -2
  97. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/host_copy.py +50 -0
  98. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/hosting.py +19 -17
  99. sim_mirror-1.2.0/src/sim_mirror/perception/imagehash.py +76 -0
  100. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/model.py +9 -1
  101. sim_mirror-1.2.0/src/sim_mirror/perception/ocr.py +220 -0
  102. sim_mirror-1.2.0/src/sim_mirror/perception/readers.py +421 -0
  103. sim_mirror-1.2.0/src/sim_mirror/perception/settle.py +212 -0
  104. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/snapshot.py +53 -12
  105. sim_mirror-1.2.0/src/sim_mirror/perception/vision/__init__.py +3 -0
  106. sim_mirror-1.2.0/src/sim_mirror/perception/vision/helper.py +224 -0
  107. sim_mirror-1.2.0/src/sim_mirror/perception/vision/probe.py +24 -0
  108. sim_mirror-1.2.0/src/sim_mirror/perception/vision/text_recognizer.swift +113 -0
  109. sim_mirror-1.2.0/src/sim_mirror/platform/device_data.py +34 -0
  110. sim_mirror-1.2.0/src/sim_mirror/platform/json_lines.py +187 -0
  111. sim_mirror-1.2.0/src/sim_mirror/platform/swift.py +76 -0
  112. sim_mirror-1.2.0/src/sim_mirror/platform/swiftpm.py +52 -0
  113. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/__init__.py +6 -0
  114. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/_generated.py +236 -4
  115. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/messages.py +29 -1
  116. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/settings_routes.py +1 -0
  117. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/settings_view.py +12 -3
  118. sim_mirror-1.2.0/src/sim_mirror/server/static/viewer/start.js +26 -0
  119. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/app_support.py +5 -0
  120. sim_mirror-1.2.0/src/sim_mirror/testing/app_sdk.py +210 -0
  121. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/contract.py +47 -2
  122. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fakes.py +47 -15
  123. sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-alert.json +137 -0
  124. sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-keyboard.json +1257 -0
  125. sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-swiftui.json +340 -0
  126. sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-tagged.json +237 -0
  127. sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-uikit.json +340 -0
  128. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/guards.py +20 -4
  129. sim_mirror-1.2.0/src/sim_mirror/testing/native.py +228 -0
  130. sim_mirror-1.2.0/src/sim_mirror/testing/pictures.py +58 -0
  131. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/rig.py +8 -4
  132. sim_mirror-1.2.0/src/sim_mirror/testing/vision.py +83 -0
  133. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/context.py +2 -1
  134. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/tests/unit/protocol/test_messages.py +8 -0
  135. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/viewer/README.md +1 -0
  136. sim_mirror-0.2.0/packaging/homebrew/README.md +0 -16
  137. sim_mirror-0.2.0/protocol/README.md +0 -55
  138. sim_mirror-0.2.0/src/sim_mirror/connectors/__init__.py +0 -7
  139. sim_mirror-0.2.0/src/sim_mirror/connectors/mcpbridge/client.py +0 -264
  140. sim_mirror-0.2.0/src/sim_mirror/perception/readers.py +0 -204
  141. sim_mirror-0.2.0/src/sim_mirror/perception/settle.py +0 -93
  142. sim_mirror-0.2.0/src/sim_mirror/server/static/viewer/start.js +0 -25
  143. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/LICENSE +0 -0
  144. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/docs/media/README.md +0 -0
  145. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/custom-connector/README.md +0 -0
  146. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/embed-host/README.md +0 -0
  147. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/iframe/README.md +0 -0
  148. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/token-budget/README.md +0 -0
  149. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/web-component/README.md +0 -0
  150. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/client-input.schema.json +0 -0
  151. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/close-codes.json +0 -0
  152. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/hello.schema.json +0 -0
  153. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/status.schema.json +0 -0
  154. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/stream.schema.json +0 -0
  155. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/__init__.py +0 -0
  156. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/__main__.py +0 -0
  157. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/__init__.py +0 -0
  158. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/destination.py +0 -0
  159. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/provider.py +0 -0
  160. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/xcodebuild.py +0 -0
  161. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/xcresult.py +0 -0
  162. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/__init__.py +0 -0
  163. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/config_command.py +0 -0
  164. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/devices.py +0 -0
  165. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/doctor.py +0 -0
  166. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/mcp.py +0 -0
  167. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/open_viewer.py +0 -0
  168. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/serve.py +0 -0
  169. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/settings_command.py +0 -0
  170. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/tokens.py +0 -0
  171. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/tools.py +0 -0
  172. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/version.py +0 -0
  173. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/xcode.py +0 -0
  174. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/__init__.py +0 -0
  175. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/discovery.py +0 -0
  176. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/env.py +0 -0
  177. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/provenance.py +0 -0
  178. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/settings_store.py +0 -0
  179. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/toml_source.py +0 -0
  180. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/writer.py +0 -0
  181. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/base.py +0 -0
  182. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/__init__.py +0 -0
  183. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/connector.py +0 -0
  184. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/engine.py +0 -0
  185. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/frameworks.py +0 -0
  186. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/__init__.py +0 -0
  187. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb.proto +0 -0
  188. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb_grpc.py +0 -0
  189. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb_pb2.py +0 -0
  190. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/__init__.py +0 -0
  191. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/connector.py +0 -0
  192. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/hierarchy.py +0 -0
  193. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/merge.py +0 -0
  194. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/reader.py +0 -0
  195. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/__init__.py +0 -0
  196. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/capture.py +0 -0
  197. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/__init__.py +0 -0
  198. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/availability.py +0 -0
  199. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/devices.py +0 -0
  200. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/events.py +0 -0
  201. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/frames.py +0 -0
  202. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/gestures.py +0 -0
  203. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/reaper.py +0 -0
  204. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/screen_input.py +0 -0
  205. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/status.py +0 -0
  206. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/text_entry.py +0 -0
  207. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/tickets.py +0 -0
  208. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/__init__.py +0 -0
  209. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/auth.py +0 -0
  210. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/confirmations.py +0 -0
  211. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/health.py +0 -0
  212. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/lease.py +0 -0
  213. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/lifecycle.py +0 -0
  214. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/passes.py +0 -0
  215. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/policy.py +0 -0
  216. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/tokens.py +0 -0
  217. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/__init__.py +0 -0
  218. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/macos.py +0 -0
  219. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/report.py +0 -0
  220. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/__init__.py +0 -0
  221. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/launcher.py +0 -0
  222. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/relay.py +0 -0
  223. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/__init__.py +0 -0
  224. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/budget.py +0 -0
  225. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/wait.py +0 -0
  226. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/__init__.py +0 -0
  227. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/developer_dir.py +0 -0
  228. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/images.py +0 -0
  229. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/keyboard.py +0 -0
  230. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/process.py +0 -0
  231. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/simctl.py +0 -0
  232. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/xcode.py +0 -0
  233. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/xcrun.py +0 -0
  234. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/py.typed +0 -0
  235. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/scope.py +0 -0
  236. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/seams.py +0 -0
  237. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/__init__.py +0 -0
  238. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/agent_routes.py +0 -0
  239. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/envelope.py +0 -0
  240. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/http_routes.py +0 -0
  241. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/log_redaction.py +0 -0
  242. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/pages.py +0 -0
  243. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/security.py +0 -0
  244. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/socket_routes.py +0 -0
  245. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/static/viewer/embed.html +0 -0
  246. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/static/viewer/index.html +0 -0
  247. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/__init__.py +0 -0
  248. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/claims.py +0 -0
  249. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/private.py +0 -0
  250. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/__init__.py +0 -0
  251. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/asgi.py +0 -0
  252. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-alert-ios27.json +0 -0
  253. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-home-complete.json +0 -0
  254. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-home-ios27.json +0 -0
  255. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-keyboard-ios27.json +0 -0
  256. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-probe-ios27.json +0 -0
  257. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-safari-ios27.json +0 -0
  258. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-safari-typed.json +0 -0
  259. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-settings-interactable.json +0 -0
  260. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-settings-ios27.json +0 -0
  261. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-answers.json +0 -0
  262. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-alert.txt +0 -0
  263. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-home.txt +0 -0
  264. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-keyboard.txt +0 -0
  265. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-probe.txt +0 -0
  266. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-safari.txt +0 -0
  267. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-settings.txt +0 -0
  268. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/simctl-devices.json +0 -0
  269. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/simctl-runtimes.json +0 -0
  270. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list-names.json +0 -0
  271. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list.json +0 -0
  272. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-settings.json +0 -0
  273. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans-none.json +0 -0
  274. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans.json +0 -0
  275. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-broken.json +0 -0
  276. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-ok.json +0 -0
  277. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-results-unbuilt.json +0 -0
  278. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-retries.json +0 -0
  279. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing-xcode27.json +0 -0
  280. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing.json +0 -0
  281. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-unbuilt.json +0 -0
  282. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary.json +0 -0
  283. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-retries.json +0 -0
  284. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing-xcode27.json +0 -0
  285. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing.json +0 -0
  286. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests.json +0 -0
  287. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/__init__.py +0 -0
  288. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/act.py +0 -0
  289. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/app.py +0 -0
  290. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/device.py +0 -0
  291. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/registry.py +0 -0
  292. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/results.py +0 -0
  293. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/schemas.py +0 -0
  294. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/screenshot.py +0 -0
  295. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/snapshot.py +0 -0
  296. {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/validation.py +0 -0
@@ -14,6 +14,14 @@ coverage.json
14
14
  coverage.xml
15
15
  htmlcov/
16
16
 
17
+ # Swift
18
+ helper/.build/
19
+ helper/.swiftpm/
20
+ /.build/
21
+ /.swiftpm/
22
+ xcuserdata/
23
+ *.xcresult
24
+
17
25
  # Node
18
26
  node_modules/
19
27
  viewer/dist/
@@ -6,6 +6,9 @@ This product is licensed under the Apache License, Version 2.0 (see LICENSE).
6
6
  It includes third-party material under its own licenses; see THIRD_PARTY_LICENSES.md:
7
7
  - idb_companion's gRPC protocol definition (facebook/idb), MIT License,
8
8
  Copyright (c) Meta Platforms, Inc. and affiliates.
9
+ - In the native helper, simulator input and accessibility bridges written after facebook/idb's
10
+ FBSimulatorControl (MIT License, Copyright (c) Meta Platforms, Inc. and affiliates) and
11
+ EvanBacon/serve-sim (Apache License 2.0, Copyright 2026 Evan Bacon).
9
12
  - Icon paths from Lucide (lucide.dev), ISC License,
10
13
  Copyright (c) Lucide Contributors 2022; portions Copyright (c) Cole Bemis 2013-2022 (Feather, MIT).
11
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sim-mirror
3
- Version: 0.2.0
3
+ Version: 1.2.0
4
4
  Summary: Mirror and drive the iOS Simulator 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
@@ -28,6 +28,7 @@ Classifier: Typing :: Typed
28
28
  Requires-Python: >=3.10
29
29
  Requires-Dist: fastapi<1,>=0.115
30
30
  Requires-Dist: grpclib<0.5,>=0.4.9
31
+ Requires-Dist: pillow<13,>=11.1
31
32
  Requires-Dist: protobuf<8,>=7.36
32
33
  Requires-Dist: tomli>=2; python_version < '3.11'
33
34
  Requires-Dist: tomlkit>=0.13
@@ -51,8 +52,9 @@ A live iOS Simulator in any browser tab or web page, an animated cursor that sho
51
52
  to tap, and token-efficient UI snapshots so agents read the screen as compact text instead of screenshots. Works with
52
53
  Claude Code, Codex, Cursor and any other MCP client.
53
54
 
54
- > **Status: first public preview (v0.1).** The tools, the viewer and the protocol work today; the embedding API and
55
- > protocol become stable with v1.0.
55
+ > **Status: 1.2.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
56
+ > 1.x release to [what SimMirror promises not to break](docs/stability.md). 1.2 drives simulators with SimMirror's own
57
+ > helper, reads a screen from its pixels when accessibility says nothing, and lets an app share its own views.
56
58
 
57
59
  ## Why SimMirror
58
60
 
@@ -62,6 +64,8 @@ Claude Code, Codex, Cursor and any other MCP client.
62
64
  the spot first -- so you can follow along, and step in: an agent waits for your hand to be still.
63
65
  - **Spend fewer tokens.** Agents read accessibility snapshots with stable element refs and diffs between screens, act
64
66
  in batches with waits, and reach for a screenshot only when a question is visual.
67
+ - **Read screens accessibility cannot.** A game or a canvas reads from its pixels, and an app under development can
68
+ share its own views, so icon-only buttons and hand-drawn controls read by name.
65
69
 
66
70
  ## Install
67
71
 
@@ -75,7 +79,7 @@ Claude Code, Codex, Cursor and any other MCP client.
75
79
  **Any MCP client**, as a stdio server:
76
80
 
77
81
  ```sh
78
- claude mcp add sim-mirror -- uvx --from git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror mcp
82
+ claude mcp add sim-mirror -- uvx --from sim-mirror==1.2.0 sim-mirror mcp
79
83
  ```
80
84
 
81
85
  Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
@@ -84,14 +88,18 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
84
88
  **The command**, for `sim-mirror open`, `doctor` and the rest:
85
89
 
86
90
  ```sh
87
- uv tool install git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0
91
+ uv tool install sim-mirror==1.2.0
92
+ # or
93
+ brew install andrewkochulab/tap/sim-mirror
88
94
  ```
89
95
 
90
- **The viewer library**, for your own pages: the npm package `@andrewkochulab/sim-mirror`, attached to each
91
- [release](https://github.com/AndrewKochulab/sim-mirror/releases). Homebrew and PyPI come with v1.0.
96
+ **The viewer library**, for your own pages: `npm install @andrewkochulab/sim-mirror`
97
+ ([npm](https://www.npmjs.com/package/@andrewkochulab/sim-mirror)). SimMirror itself is
98
+ [on PyPI](https://pypi.org/project/sim-mirror/) and in a [Homebrew tap](https://github.com/AndrewKochulab/homebrew-tap).
92
99
 
93
- **Requirements:** an Apple Silicon Mac, Xcode 26 or later, [uv](https://docs.astral.sh/uv/), and for touching the
94
- screen, idb_companion (`brew install facebook/fb/idb-companion`). Details in [Installation](docs/installation.md).
100
+ **Requirements:** an Apple Silicon Mac, Xcode 26 or later, and [uv](https://docs.astral.sh/uv/) or Homebrew. Nothing
101
+ else: SimMirror drives a simulator with its own helper, which comes in the package. Details in
102
+ [Installation](docs/installation.md).
95
103
 
96
104
  ## Quickstart
97
105
 
@@ -118,10 +126,22 @@ the viewer's own pointer glides there first (`agent.cursor_lead_ms`). Never the
118
126
 
119
127
  **Screen understanding.** A snapshot is a few lines -- `e2 button "General" (201,319)` -- with refs that stay with their
120
128
  elements across scrolls, a digest, and diffs. `sim_act` plays up to 20 steps in one call and waits for text, its
121
- absence, or the screen to settle. See [Screen understanding](docs/screen-understanding.md).
129
+ absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. See
130
+ [Screen understanding](docs/screen-understanding.md).
122
131
 
123
- **Connectors.** idb_companion gives full control; without it the simctl connector still mirrors the screen, view-only,
124
- and says why. More connectors plug in through an entry point. See [Connectors](docs/connectors.md).
132
+ **Reading pixels.** A screen whose accessibility says nothing -- a game, a canvas, an app still loading -- reads as the
133
+ text macOS's Vision finds in it, with refs to tap and text to wait for, and the viewer can outline what was read
134
+ (`perception.ocr`). See [Read from pixels](docs/screen-understanding.md#read-from-pixels).
135
+
136
+ **App SDK.** An app under development can link **SimMirrorKit**, a small Swift package, and call `SimMirror.start()`:
137
+ in a Debug build on the simulator it shares its own UIKit and SwiftUI views, so an icon-only button, a card with a tap
138
+ gesture or a hand-drawn control reads by name, and `.simMirror("Settings")` names a SwiftUI view. Optional, and nothing
139
+ of it is in a Release build. See [The app SDK](docs/app-sdk.md).
140
+
141
+ **Connectors.** SimMirror's own native helper gives full control with nothing to install; idb_companion still can,
142
+ and the simctl connector mirrors the screen view-only when neither is there, and says why. `auto` uses the fastest that
143
+ works and falls back when one fails. More connectors plug in through an entry point. See
144
+ [Connectors](docs/connectors.md).
125
145
 
126
146
  **Embedding.** An [iframe](docs/embedding/iframe.md) with a one-shot ticket, the
127
147
  [`<sim-mirror>` element](docs/embedding/web-component.md) themed with CSS custom properties, or SimMirror
@@ -131,8 +151,8 @@ and says why. More connectors plug in through an entry point. See [Connectors](d
131
151
  saved for one project or all of them and applied before it says so. Settings that decide what runs or who may reach
132
152
  the daemon wait for a code from `sim-mirror settings confirm`. See [The settings panel](docs/settings.md).
133
153
 
134
- **Doctor.** Checks Xcode, its Simulator frameworks, runtimes, idb_companion, Device Hub and the desktop session, then
135
- proves a tap reaches a simulator. See [the doctor](docs/doctor.md).
154
+ **Doctor.** Checks Xcode, its Simulator frameworks, runtimes, the native helper and idb_companion, Device Hub and the
155
+ desktop session, the text reader and an app's shared hierarchy, then proves a tap reaches a simulator. See [the doctor](docs/doctor.md).
136
156
 
137
157
  **Security.** Loopback only, a Host allowlist, exact Origin checks, CORS and framing only for origins you list, hashed
138
158
  scoped tokens, and one-shot codes in URL fragments. See [Security](docs/security.md).
@@ -164,15 +184,16 @@ an image at about 750 pixels a token).
164
184
  |---|---|
165
185
  | macOS | 26 (15 with Xcode 16.4, view-only) |
166
186
  | Xcode | 26 and 27 |
167
- | Connectors | idb_companion 1.5 (full control), simctl (view-only) |
187
+ | Connectors | native helper (full control), idb_companion 1.5 (full control), simctl (view-only) |
188
+ | App SDK | Debug builds on the iOS Simulator, iOS 16 or later |
168
189
  | Clients | Claude Code, Codex, Cursor, any stdio MCP client |
169
190
  | Browsers | Chrome, Safari, Firefox, Edge |
170
191
 
171
192
  Rows checked on real hardware are marked verified, with the date, in [Compatibility](docs/compatibility.md).
172
193
 
173
- While SimMirror is `0.x`, the tools, the protocol and the embedding API can still change in a minor release, and the
174
- changelog says when they do; from 1.0 they are held to [what SimMirror promises not to break](docs/stability.md). Pin
175
- an exact version either way.
194
+ From 1.0 the tools, the protocol, the embedding API, the viewer package, settings and the command line are held to
195
+ [what SimMirror promises not to break](docs/stability.md): a 1.x release adds, and never breaks. Connectors are the
196
+ exception, and pin a minor version.
176
197
 
177
198
  ## Documentation
178
199
 
@@ -185,12 +206,11 @@ the [command line](docs/reference/cli.md) and the [protocol](docs/reference/prot
185
206
  | Milestone | What it brings |
186
207
  |---|---|
187
208
  | **v0.1** (preview) | Live viewer, agent cursor, person and agent control, MCP tools, `sim-mirror doctor`, idb and simctl connectors, build and test as a preview |
188
- | **v1.0** | Stable protocol and embedding API, PyPI and npm packages, the MCP registry |
189
- | **v1.1** | Build, run and test tools become stable |
190
- | **v1.2** | Richer screen understanding, a native Swift helper, Xcode 27 `mcpbridge` connector, one shared daemon for many hosts |
209
+ | **v0.2** | Build, run and test tools, a settings panel, Xcode 27's UI hierarchy through `mcpbridge`, one shared daemon for many hosts |
210
+ | **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 |
211
+ | **v1.2** (now) | 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 |
191
212
  | **v2.0** | Real iPhones: view, install and launch without signing; full control through WebDriverAgent |
192
213
  | **Android** | Android emulator support with the same viewer, cursor and tools |
193
- | **Homebrew** | `brew install andrewkochulab/tap/sim-mirror`, right after v1.0 |
194
214
  | **Website & launch** | Landing page, video tutorials and guides |
195
215
 
196
216
  See [ROADMAP.md](ROADMAP.md) and the [milestones](https://github.com/AndrewKochulab/sim-mirror/milestones). How
@@ -15,8 +15,9 @@ A live iOS Simulator in any browser tab or web page, an animated cursor that sho
15
15
  to tap, and token-efficient UI snapshots so agents read the screen as compact text instead of screenshots. Works with
16
16
  Claude Code, Codex, Cursor and any other MCP client.
17
17
 
18
- > **Status: first public preview (v0.1).** The tools, the viewer and the protocol work today; the embedding API and
19
- > protocol become stable with v1.0.
18
+ > **Status: 1.2.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
19
+ > 1.x release to [what SimMirror promises not to break](docs/stability.md). 1.2 drives simulators with SimMirror's own
20
+ > helper, reads a screen from its pixels when accessibility says nothing, and lets an app share its own views.
20
21
 
21
22
  ## Why SimMirror
22
23
 
@@ -26,6 +27,8 @@ Claude Code, Codex, Cursor and any other MCP client.
26
27
  the spot first -- so you can follow along, and step in: an agent waits for your hand to be still.
27
28
  - **Spend fewer tokens.** Agents read accessibility snapshots with stable element refs and diffs between screens, act
28
29
  in batches with waits, and reach for a screenshot only when a question is visual.
30
+ - **Read screens accessibility cannot.** A game or a canvas reads from its pixels, and an app under development can
31
+ share its own views, so icon-only buttons and hand-drawn controls read by name.
29
32
 
30
33
  ## Install
31
34
 
@@ -39,7 +42,7 @@ Claude Code, Codex, Cursor and any other MCP client.
39
42
  **Any MCP client**, as a stdio server:
40
43
 
41
44
  ```sh
42
- claude mcp add sim-mirror -- uvx --from git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror mcp
45
+ claude mcp add sim-mirror -- uvx --from sim-mirror==1.2.0 sim-mirror mcp
43
46
  ```
44
47
 
45
48
  Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
@@ -48,14 +51,18 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
48
51
  **The command**, for `sim-mirror open`, `doctor` and the rest:
49
52
 
50
53
  ```sh
51
- uv tool install git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0
54
+ uv tool install sim-mirror==1.2.0
55
+ # or
56
+ brew install andrewkochulab/tap/sim-mirror
52
57
  ```
53
58
 
54
- **The viewer library**, for your own pages: the npm package `@andrewkochulab/sim-mirror`, attached to each
55
- [release](https://github.com/AndrewKochulab/sim-mirror/releases). Homebrew and PyPI come with v1.0.
59
+ **The viewer library**, for your own pages: `npm install @andrewkochulab/sim-mirror`
60
+ ([npm](https://www.npmjs.com/package/@andrewkochulab/sim-mirror)). SimMirror itself is
61
+ [on PyPI](https://pypi.org/project/sim-mirror/) and in a [Homebrew tap](https://github.com/AndrewKochulab/homebrew-tap).
56
62
 
57
- **Requirements:** an Apple Silicon Mac, Xcode 26 or later, [uv](https://docs.astral.sh/uv/), and for touching the
58
- screen, idb_companion (`brew install facebook/fb/idb-companion`). Details in [Installation](docs/installation.md).
63
+ **Requirements:** an Apple Silicon Mac, Xcode 26 or later, and [uv](https://docs.astral.sh/uv/) or Homebrew. Nothing
64
+ else: SimMirror drives a simulator with its own helper, which comes in the package. Details in
65
+ [Installation](docs/installation.md).
59
66
 
60
67
  ## Quickstart
61
68
 
@@ -82,10 +89,22 @@ the viewer's own pointer glides there first (`agent.cursor_lead_ms`). Never the
82
89
 
83
90
  **Screen understanding.** A snapshot is a few lines -- `e2 button "General" (201,319)` -- with refs that stay with their
84
91
  elements across scrolls, a digest, and diffs. `sim_act` plays up to 20 steps in one call and waits for text, its
85
- absence, or the screen to settle. See [Screen understanding](docs/screen-understanding.md).
92
+ absence, or the screen to settle -- past a spinner or a pulsing dot that never stops. See
93
+ [Screen understanding](docs/screen-understanding.md).
86
94
 
87
- **Connectors.** idb_companion gives full control; without it the simctl connector still mirrors the screen, view-only,
88
- and says why. More connectors plug in through an entry point. See [Connectors](docs/connectors.md).
95
+ **Reading pixels.** A screen whose accessibility says nothing -- a game, a canvas, an app still loading -- reads as the
96
+ text macOS's Vision finds in it, with refs to tap and text to wait for, and the viewer can outline what was read
97
+ (`perception.ocr`). See [Read from pixels](docs/screen-understanding.md#read-from-pixels).
98
+
99
+ **App SDK.** An app under development can link **SimMirrorKit**, a small Swift package, and call `SimMirror.start()`:
100
+ in a Debug build on the simulator it shares its own UIKit and SwiftUI views, so an icon-only button, a card with a tap
101
+ gesture or a hand-drawn control reads by name, and `.simMirror("Settings")` names a SwiftUI view. Optional, and nothing
102
+ of it is in a Release build. See [The app SDK](docs/app-sdk.md).
103
+
104
+ **Connectors.** SimMirror's own native helper gives full control with nothing to install; idb_companion still can,
105
+ and the simctl connector mirrors the screen view-only when neither is there, and says why. `auto` uses the fastest that
106
+ works and falls back when one fails. More connectors plug in through an entry point. See
107
+ [Connectors](docs/connectors.md).
89
108
 
90
109
  **Embedding.** An [iframe](docs/embedding/iframe.md) with a one-shot ticket, the
91
110
  [`<sim-mirror>` element](docs/embedding/web-component.md) themed with CSS custom properties, or SimMirror
@@ -95,8 +114,8 @@ and says why. More connectors plug in through an entry point. See [Connectors](d
95
114
  saved for one project or all of them and applied before it says so. Settings that decide what runs or who may reach
96
115
  the daemon wait for a code from `sim-mirror settings confirm`. See [The settings panel](docs/settings.md).
97
116
 
98
- **Doctor.** Checks Xcode, its Simulator frameworks, runtimes, idb_companion, Device Hub and the desktop session, then
99
- proves a tap reaches a simulator. See [the doctor](docs/doctor.md).
117
+ **Doctor.** Checks Xcode, its Simulator frameworks, runtimes, the native helper and idb_companion, Device Hub and the
118
+ desktop session, the text reader and an app's shared hierarchy, then proves a tap reaches a simulator. See [the doctor](docs/doctor.md).
100
119
 
101
120
  **Security.** Loopback only, a Host allowlist, exact Origin checks, CORS and framing only for origins you list, hashed
102
121
  scoped tokens, and one-shot codes in URL fragments. See [Security](docs/security.md).
@@ -128,15 +147,16 @@ an image at about 750 pixels a token).
128
147
  |---|---|
129
148
  | macOS | 26 (15 with Xcode 16.4, view-only) |
130
149
  | Xcode | 26 and 27 |
131
- | Connectors | idb_companion 1.5 (full control), simctl (view-only) |
150
+ | Connectors | native helper (full control), idb_companion 1.5 (full control), simctl (view-only) |
151
+ | App SDK | Debug builds on the iOS Simulator, iOS 16 or later |
132
152
  | Clients | Claude Code, Codex, Cursor, any stdio MCP client |
133
153
  | Browsers | Chrome, Safari, Firefox, Edge |
134
154
 
135
155
  Rows checked on real hardware are marked verified, with the date, in [Compatibility](docs/compatibility.md).
136
156
 
137
- While SimMirror is `0.x`, the tools, the protocol and the embedding API can still change in a minor release, and the
138
- changelog says when they do; from 1.0 they are held to [what SimMirror promises not to break](docs/stability.md). Pin
139
- an exact version either way.
157
+ From 1.0 the tools, the protocol, the embedding API, the viewer package, settings and the command line are held to
158
+ [what SimMirror promises not to break](docs/stability.md): a 1.x release adds, and never breaks. Connectors are the
159
+ exception, and pin a minor version.
140
160
 
141
161
  ## Documentation
142
162
 
@@ -149,12 +169,11 @@ the [command line](docs/reference/cli.md) and the [protocol](docs/reference/prot
149
169
  | Milestone | What it brings |
150
170
  |---|---|
151
171
  | **v0.1** (preview) | Live viewer, agent cursor, person and agent control, MCP tools, `sim-mirror doctor`, idb and simctl connectors, build and test as a preview |
152
- | **v1.0** | Stable protocol and embedding API, PyPI and npm packages, the MCP registry |
153
- | **v1.1** | Build, run and test tools become stable |
154
- | **v1.2** | Richer screen understanding, a native Swift helper, Xcode 27 `mcpbridge` connector, one shared daemon for many hosts |
172
+ | **v0.2** | Build, run and test tools, a settings panel, Xcode 27's UI hierarchy through `mcpbridge`, one shared daemon for many hosts |
173
+ | **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 |
174
+ | **v1.2** (now) | 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 |
155
175
  | **v2.0** | Real iPhones: view, install and launch without signing; full control through WebDriverAgent |
156
176
  | **Android** | Android emulator support with the same viewer, cursor and tools |
157
- | **Homebrew** | `brew install andrewkochulab/tap/sim-mirror`, right after v1.0 |
158
177
  | **Website & launch** | Landing page, video tutorials and guides |
159
178
 
160
179
  See [ROADMAP.md](ROADMAP.md) and the [milestones](https://github.com/AndrewKochulab/sim-mirror/milestones). How
@@ -33,6 +33,33 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
33
  SOFTWARE.
34
34
  ```
35
35
 
36
+ ## The native helper's simulator bridges
37
+
38
+ - **What:** in the native helper, how it reaches a simulator's input and accessibility through Apple's private
39
+ frameworks: the DTUHID message shapes, the Indigo message builder signatures and the accessibility translator
40
+ bridge (`helper/Sources/HelperPlatform/HIDTransports.swift`, `AccessibilityReader.swift`), written for SimMirror in
41
+ Swift after these projects' Objective-C and Swift.
42
+ - **Sources:** [facebook/idb](https://github.com/facebook/idb)'s FBSimulatorControl, under the MIT License quoted
43
+ above (Copyright (c) Meta Platforms, Inc. and affiliates); and [EvanBacon/serve-sim](https://github.com/EvanBacon/serve-sim),
44
+ under the Apache License 2.0, the same license as SimMirror's own (see LICENSE).
45
+ - **License:** MIT; Apache-2.0
46
+
47
+ ```
48
+ Copyright 2026 Evan Bacon
49
+
50
+ Licensed under the Apache License, Version 2.0 (the "License");
51
+ you may not use this file except in compliance with the License.
52
+ You may obtain a copy of the License at
53
+
54
+ http://www.apache.org/licenses/LICENSE-2.0
55
+
56
+ Unless required by applicable law or agreed to in writing, software
57
+ distributed under the License is distributed on an "AS IS" BASIS,
58
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
59
+ See the License for the specific language governing permissions and
60
+ limitations under the License.
61
+ ```
62
+
36
63
  ## Lucide icons
37
64
 
38
65
  - **What:** the SVG path data of the icons drawn by the viewer (`viewer/src/icons.ts`).
@@ -57,5 +84,5 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
57
84
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
58
85
  ```
59
86
 
60
- Runtime dependencies installed from PyPI and npm (FastAPI, Uvicorn, grpclib, protobuf, tomlkit and others) are not
61
- redistributed here; each carries its own license in its package.
87
+ Runtime dependencies installed from PyPI and npm (FastAPI, Uvicorn, grpclib, protobuf, tomlkit, Pillow and others) are
88
+ not redistributed here; each carries its own license in its package.
@@ -30,6 +30,8 @@
30
30
 
31
31
  - [Screen understanding](screen-understanding.md) -- snapshots, refs, diffs, waits, and what each costs
32
32
  - [Connectors](connectors.md) -- how SimMirror reaches a device, and what each can do
33
+ - [The app SDK](app-sdk.md) -- an app under development shares its own UIKit and SwiftUI views, so screens
34
+ without accessibility labels read well
33
35
  - [Security](security.md) -- the threat model and what keeps the daemon to itself
34
36
  - [Architecture](architecture.md) -- the parts, the seams and the rules between them
35
37
  - [Compatibility](compatibility.md) -- where SimMirror is expected to work, and where it was checked
@@ -13,6 +13,8 @@ Each folder is one way to use SimMirror, small enough to copy.
13
13
  | [fastapi-embed](fastapi-embed/) | A Python host application mounting SimMirror's routers under its own paths, with its own sign-in |
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
+ | [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 |
16
17
 
17
18
  Every example assumes a Mac with Xcode and a booted-or-bootable simulator, and `sim-mirror doctor` passing. The
18
- examples are tested with SimMirror's own suite (`tests/examples/`), without starting a simulator.
19
+ examples are tested with SimMirror's own suite (`tests/examples/`), without starting a simulator; app-sdk's hosted
20
+ tests run on a simulator with `make sdk-app-test`.
@@ -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 git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror mcp
19
+ claude mcp add sim-mirror -- uvx --from sim-mirror==1.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 git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror open
33
+ uvx --from sim-mirror==1.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 git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror open`
9
+ - Watch and take over in a browser: `uvx --from sim-mirror==1.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 git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0 sim-mirror open`
9
+ - Watch and take over in a browser: `uvx --from sim-mirror==1.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 @ git+https://github.com/AndrewKochulab/sim-mirror@v0.2.0" \
8
+ EXAMPLE_KEY=change-me uv run --with "sim-mirror==1.2.0" \
9
9
  uvicorn --factory app:create_app --port 7484
10
10
  ```
11
11
 
@@ -0,0 +1,35 @@
1
+ // swift-tools-version:6.0
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // SimMirror's native helper: the screen, input and element tree of one booted simulator, served to SimMirror on a unix
5
+ // socket -- without idb_companion.
6
+ //
7
+ // `HelperCore` is everything that can be decided without a simulator: the wire protocol, what a screenshot or a stream
8
+ // is cut to, how input maps onto each HID transport, and how an accessibility tree becomes SimMirror's document. It is
9
+ // unit-tested to the per-file coverage bar. `HelperPlatform` is the thin layer that reaches Apple's private simulator
10
+ // frameworks through the Objective-C runtime; it is exercised against real simulators by SimMirror's live suite.
11
+
12
+ import PackageDescription
13
+
14
+ let package = Package(
15
+ name: "SimMirrorHelper",
16
+ platforms: [.macOS(.v14)],
17
+ products: [
18
+ .executable(name: "sim-mirror-helper", targets: ["sim-mirror-helper"]),
19
+ ],
20
+ targets: [
21
+ .target(name: "HelperCore"),
22
+ .target(name: "ObjCGuard"),
23
+ .target(
24
+ name: "HelperPlatform",
25
+ dependencies: ["HelperCore", "ObjCGuard"],
26
+ swiftSettings: [.swiftLanguageMode(.v5)]
27
+ ),
28
+ .executableTarget(
29
+ name: "sim-mirror-helper",
30
+ dependencies: ["HelperCore", "HelperPlatform"],
31
+ swiftSettings: [.swiftLanguageMode(.v5)]
32
+ ),
33
+ .testTarget(name: "HelperCoreTests", dependencies: ["HelperCore"]),
34
+ ]
35
+ )
@@ -0,0 +1,161 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import Foundation
3
+
4
+ /// A rectangle in points, from the top left.
5
+ public struct Rect: Equatable, Sendable {
6
+ public let x: Double
7
+ public let y: Double
8
+ public let width: Double
9
+ public let height: Double
10
+
11
+ public init(x: Double, y: Double, width: Double, height: Double) {
12
+ self.x = x
13
+ self.y = y
14
+ self.width = width
15
+ self.height = height
16
+ }
17
+
18
+ var json: JSONValue {
19
+ .object(["x": .number(x), "y": .number(y), "width": .number(width), "height": .number(height)])
20
+ }
21
+ }
22
+
23
+ /// An accessibility value: text, a number, or a switch's state.
24
+ public enum AccessibilityValue: Equatable, Sendable {
25
+ case text(String)
26
+ case number(Double)
27
+
28
+ var json: JSONValue {
29
+ switch self {
30
+ case .text(let text): return .string(text)
31
+ case .number(let number): return .number(number)
32
+ }
33
+ }
34
+ }
35
+
36
+ /// One element of a screen's accessibility tree, read from the device.
37
+ ///
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.
41
+ public struct AXNode: Equatable, Sendable {
42
+ /// Its role as the accessibility API says it, ``AXButton``.
43
+ public var role: String?
44
+ public var subrole: String?
45
+ public var label: String?
46
+ public var title: String?
47
+ public var identifier: String?
48
+ public var value: AccessibilityValue?
49
+ public var frame: Rect
50
+ /// Its traits as the bits iOS keeps them in; nil when it does not say.
51
+ public var traits: UInt64?
52
+ public var enabled: Bool
53
+ public var children: [AXNode]
54
+
55
+ 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] = []
59
+ ) {
60
+ self.role = role
61
+ self.subrole = subrole
62
+ self.label = label
63
+ self.title = title
64
+ self.identifier = identifier
65
+ self.value = value
66
+ self.frame = frame
67
+ self.traits = traits
68
+ self.enabled = enabled
69
+ self.children = children
70
+ }
71
+ }
72
+
73
+ /// The names of the traits iOS gives an element, bit by bit.
74
+ ///
75
+ /// The bits are AXRuntime's ``kAX*Trait`` constants, which run from 0 upward in this order; the names are the constants
76
+ /// less their prefix and suffix, which is how idb_companion names them too.
77
+ public enum AXTraits {
78
+ public static let names = [
79
+ "Button", "Link", "Image", "Selected", "PlaysSound", "KeyboardKey", "StaticText", "SummaryElement",
80
+ "NotEnabled", "UpdatesFrequently", "SearchField", "StartsMediaSession", "Adjustable",
81
+ "AllowsDirectInteraction", "CausesPageTurn", "TabBar", "Header", "WebContent", "TextEntry", "PickerElement",
82
+ "RadioButton", "IsEditing", "LaunchIcon", "StatusBarElement", "SecureTextField", "Inactive", "Footer",
83
+ "BackButton", "TabButton", "AutoCorrectCandidate", "DeleteKey", "SelectionDismissesItem", "Visited",
84
+ "Scrollable", "Spacer", "TableIndex", "Map", "TextOperationsAvailable", "Draggable", "GesturePracticeRegion",
85
+ "PopupButton", "AllowsNativeSliding", "MathEquation", "ContainedByTable", "ContainedByList", "TouchContainer",
86
+ "SupportsZoom", "TextArea", "BookContent", "ContainedByLandmark", "FolderIcon", "ReadOnly", "MenuItem",
87
+ "Toggle", "IgnoreItemChooser", "SupportsTrackingDetail", "Alert", "ContainedByFieldset",
88
+ "AllowsLayoutChangeInStatusBar",
89
+ ]
90
+
91
+ /// The names of the bits set: ``None`` for none, and ``Unknown`` for bits no name is known for.
92
+ public static func names(_ mask: UInt64) -> [String] {
93
+ guard mask != 0 else { return ["None"] }
94
+ var found = names.indices.filter { mask & (1 << UInt64($0)) != 0 }.map { names[$0] }
95
+ if mask >> UInt64(names.count) != 0 { found.append("Unknown") }
96
+ return found
97
+ }
98
+ }
99
+
100
+ /// The document SimMirror reads a screen from: the tree of the elements a person or an agent can act on or read.
101
+ ///
102
+ /// Its shape is idb_companion's consolidated accessibility document, with the keys SimMirror reads, so SimMirror reads
103
+ /// either one the same way. The
104
+ /// tree keeps what is worth reporting -- an element with a label, an identifier, or a role that is acted on -- and an
105
+ /// element that is not has its kept descendants take its place, so a button inside an unlabelled group is kept.
106
+ public enum AXDocument {
107
+ /// The roles, less their ``AX`` prefix, that are kept whatever they say.
108
+ public static let actionableRoles: Set<String> = [
109
+ "Button", "Cell", "TextField", "SecureTextField", "SearchField", "Switch", "Toggle", "Link", "MenuItem",
110
+ "Slider", "CheckBox", "RadioButton", "SegmentedControl", "Stepper", "PopUpButton", "Picker", "PickerWheel",
111
+ "Tab", "Key", "DisclosureTriangle",
112
+ ]
113
+
114
+ public static func make(root: AXNode, screen: ScreenGeometry) -> JSONValue {
115
+ let elements = kept(root)
116
+ return .object([
117
+ "backend": .string("native"),
118
+ "elements": .array(elements),
119
+ "modal": .null,
120
+ "screen": .object([
121
+ "coordinate_space": .string("screen"),
122
+ "width": .int(screen.widthPt),
123
+ "height": .int(screen.heightPt),
124
+ ]),
125
+ "truncated": .bool(false),
126
+ ])
127
+ }
128
+
129
+ /// A role without its ``AX`` prefix: ``AXButton`` is ``Button``.
130
+ public static func type(of role: String?) -> String? {
131
+ guard let role else { return nil }
132
+ return role.hasPrefix("AX") ? String(role.dropFirst(2)) : role
133
+ }
134
+
135
+ static func keeps(_ node: AXNode) -> Bool {
136
+ if let label = node.label, !label.isEmpty { return true }
137
+ if let identifier = node.identifier, !identifier.isEmpty { return true }
138
+ return type(of: node.role).map(actionableRoles.contains) ?? false
139
+ }
140
+
141
+ static func kept(_ node: AXNode) -> [JSONValue] {
142
+ let children = node.children.flatMap(kept)
143
+ guard keeps(node) else { return children }
144
+ return [element(node, children: children)]
145
+ }
146
+
147
+ static func element(_ node: AXNode, children: [JSONValue]) -> JSONValue {
148
+ .object([
149
+ "type": .text(type(of: node.role)),
150
+ "subrole": .text(node.subrole),
151
+ "label": .text(node.label),
152
+ "title": .text(node.title),
153
+ "identifier": .text(node.identifier),
154
+ "value": node.value?.json ?? .null,
155
+ "frame": node.frame.json,
156
+ "traits": node.traits.map { .array(AXTraits.names($0).map(JSONValue.string)) } ?? .null,
157
+ "enabled": .bool(node.enabled),
158
+ "children": .array(children),
159
+ ])
160
+ }
161
+ }