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.
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/.gitignore +8 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/NOTICE +3 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/PKG-INFO +42 -22
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/README.md +40 -21
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/THIRD_PARTY_LICENSES.md +29 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/docs/README.md +2 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/README.md +3 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/claude-code-cli/README.md +2 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/codex/README.md +1 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/cursor/README.md +1 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/fastapi-embed/README.md +1 -1
- sim_mirror-1.2.0/helper/Package.swift +35 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Accessibility.swift +161 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Arguments.swift +202 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/FrameScheduler.swift +72 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Input.swift +171 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/JSON.swift +84 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Requests.swift +109 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Screen.swift +141 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Server.swift +186 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Video.swift +100 -0
- sim_mirror-1.2.0/helper/Sources/HelperCore/Wire.swift +122 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/AccessibilityReader.swift +154 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/Framebuffer.swift +163 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/Frameworks.swift +84 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/H264Stream.swift +288 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/HIDTransports.swift +149 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/Pictures.swift +73 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/SimDeviceHandle.swift +67 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/SimulatorDevice.swift +155 -0
- sim_mirror-1.2.0/helper/Sources/HelperPlatform/UnixSocket.swift +119 -0
- sim_mirror-1.2.0/helper/Sources/ObjCGuard/ObjCGuard.m +11 -0
- sim_mirror-1.2.0/helper/Sources/ObjCGuard/include/ObjCGuard.h +12 -0
- sim_mirror-1.2.0/helper/Sources/sim-mirror-helper/main.swift +85 -0
- sim_mirror-1.2.0/helper/Tests/Fixtures/wire-vectors.json +61 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/AccessibilityTests.swift +59 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/ArgumentsTests.swift +57 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/InputTests.swift +90 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/PictureTests.swift +129 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/ServerTests.swift +249 -0
- sim_mirror-1.2.0/helper/Tests/HelperCoreTests/WireTests.swift +121 -0
- sim_mirror-1.2.0/protocol/README.md +91 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/error-inactive.json +6 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/error-unauthorized.json +6 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-alert.json +156 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-keyboard.json +446 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-swiftui.json +234 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-tagged.json +254 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/hierarchy-uikit.json +375 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/examples/listing.json +12 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/hierarchy.schema.json +167 -0
- sim_mirror-1.2.0/protocol/app-sdk/v1/listing.schema.json +60 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/agent-event.schema.json +24 -3
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/common.schema.json +16 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/constants.json +3 -1
- sim_mirror-1.2.0/protocol/v1/http.schema.json +184 -0
- sim_mirror-1.2.0/protocol/v1/screen-text.schema.json +43 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/settings.schema.json +7 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/pyproject.toml +25 -1
- sim_mirror-1.2.0/scripts/hatch_build.py +74 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/_version.py +1 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/api.py +3 -3
- sim_mirror-1.2.0/src/sim_mirror/cli/app_command.py +129 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/context.py +3 -0
- sim_mirror-1.2.0/src/sim_mirror/cli/helper.py +122 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/main.py +6 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/model.py +20 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/schema.py +125 -9
- sim_mirror-1.2.0/src/sim_mirror/connectors/__init__.py +8 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/__init__.py +12 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/client.py +126 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/discovery.py +155 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/document.py +227 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/errors.py +30 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/merge.py +95 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/reader.py +218 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/app/wire.py +36 -0
- 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
- sim_mirror-1.2.0/src/sim_mirror/connectors/idb/companion.py +195 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/mcpbridge/client.py +138 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/native/__init__.py +2 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/native/client.py +240 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/native/connector.py +170 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/native/helper.py +249 -0
- sim_mirror-1.2.0/src/sim_mirror/connectors/native/wire.py +98 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/registry.py +27 -8
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/connector.py +4 -4
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/actions.py +117 -18
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/instance.py +12 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/manager.py +63 -9
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/runtime.py +35 -8
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/screen_relay.py +12 -1
- sim_mirror-1.2.0/src/sim_mirror/core/text_overlay.py +71 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/app.py +12 -4
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/checks.py +162 -5
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/tap.py +5 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/host_copy.py +50 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/hosting.py +19 -17
- sim_mirror-1.2.0/src/sim_mirror/perception/imagehash.py +76 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/model.py +9 -1
- sim_mirror-1.2.0/src/sim_mirror/perception/ocr.py +220 -0
- sim_mirror-1.2.0/src/sim_mirror/perception/readers.py +421 -0
- sim_mirror-1.2.0/src/sim_mirror/perception/settle.py +212 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/snapshot.py +53 -12
- sim_mirror-1.2.0/src/sim_mirror/perception/vision/__init__.py +3 -0
- sim_mirror-1.2.0/src/sim_mirror/perception/vision/helper.py +224 -0
- sim_mirror-1.2.0/src/sim_mirror/perception/vision/probe.py +24 -0
- sim_mirror-1.2.0/src/sim_mirror/perception/vision/text_recognizer.swift +113 -0
- sim_mirror-1.2.0/src/sim_mirror/platform/device_data.py +34 -0
- sim_mirror-1.2.0/src/sim_mirror/platform/json_lines.py +187 -0
- sim_mirror-1.2.0/src/sim_mirror/platform/swift.py +76 -0
- sim_mirror-1.2.0/src/sim_mirror/platform/swiftpm.py +52 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/__init__.py +6 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/_generated.py +236 -4
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/protocol/messages.py +29 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/settings_routes.py +1 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/settings_view.py +12 -3
- sim_mirror-1.2.0/src/sim_mirror/server/static/viewer/start.js +26 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/app_support.py +5 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/app_sdk.py +210 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/contract.py +47 -2
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fakes.py +47 -15
- sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-alert.json +137 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-keyboard.json +1257 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-swiftui.json +340 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-tagged.json +237 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/fixtures/ax-app-sdk-uikit.json +340 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/guards.py +20 -4
- sim_mirror-1.2.0/src/sim_mirror/testing/native.py +228 -0
- sim_mirror-1.2.0/src/sim_mirror/testing/pictures.py +58 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/rig.py +8 -4
- sim_mirror-1.2.0/src/sim_mirror/testing/vision.py +83 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/context.py +2 -1
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/tests/unit/protocol/test_messages.py +8 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/viewer/README.md +1 -0
- sim_mirror-0.2.0/packaging/homebrew/README.md +0 -16
- sim_mirror-0.2.0/protocol/README.md +0 -55
- sim_mirror-0.2.0/src/sim_mirror/connectors/__init__.py +0 -7
- sim_mirror-0.2.0/src/sim_mirror/connectors/mcpbridge/client.py +0 -264
- sim_mirror-0.2.0/src/sim_mirror/perception/readers.py +0 -204
- sim_mirror-0.2.0/src/sim_mirror/perception/settle.py +0 -93
- sim_mirror-0.2.0/src/sim_mirror/server/static/viewer/start.js +0 -25
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/LICENSE +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/docs/media/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/custom-connector/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/embed-host/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/iframe/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/token-budget/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/examples/web-component/README.md +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/client-input.schema.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/close-codes.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/hello.schema.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/status.schema.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/protocol/v1/stream.schema.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/__main__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/destination.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/provider.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/xcodebuild.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/build/xcresult.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/config_command.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/devices.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/doctor.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/mcp.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/open_viewer.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/serve.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/settings_command.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/tokens.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/tools.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/version.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/cli/xcode.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/discovery.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/env.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/provenance.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/settings_store.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/toml_source.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/config/writer.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/base.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/connector.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/engine.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/frameworks.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb.proto +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb_grpc.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/idb/proto/idb_pb2.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/connector.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/hierarchy.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/merge.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/mcpbridge/reader.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/connectors/simctl/capture.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/availability.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/devices.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/events.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/frames.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/gestures.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/reaper.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/screen_input.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/status.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/text_entry.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/core/tickets.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/auth.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/confirmations.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/health.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/lease.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/lifecycle.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/passes.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/policy.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/daemon/tokens.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/macos.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/doctor/report.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/launcher.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/mcp/relay.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/budget.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/perception/wait.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/developer_dir.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/images.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/keyboard.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/process.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/simctl.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/xcode.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/platform/xcrun.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/py.typed +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/scope.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/seams.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/agent_routes.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/envelope.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/http_routes.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/log_redaction.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/pages.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/security.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/socket_routes.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/static/viewer/embed.html +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/server/static/viewer/index.html +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/claims.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/storage/private.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/asgi.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-alert-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-home-complete.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-home-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-keyboard-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-probe-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-safari-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-safari-typed.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-settings-interactable.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/ax-settings-ios27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-answers.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-alert.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-home.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-keyboard.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-probe.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-safari.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-settings.txt +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/simctl-devices.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/simctl-runtimes.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list-names.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-list.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-settings.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans-none.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-broken.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-ok.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-build-results-unbuilt.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-retries.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing-xcode27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary-unbuilt.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-summary.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-retries.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing-xcode27.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/testing/fixtures/xcresult-test-tests.json +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/__init__.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/act.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/app.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/device.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/registry.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/results.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/schemas.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/screenshot.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/tools/snapshot.py +0 -0
- {sim_mirror-0.2.0 → sim_mirror-1.2.0}/src/sim_mirror/validation.py +0 -0
|
@@ -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:
|
|
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:
|
|
55
|
-
>
|
|
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
|
|
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
|
|
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:
|
|
91
|
-
[
|
|
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/)
|
|
94
|
-
|
|
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
|
|
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
|
-
**
|
|
124
|
-
and
|
|
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
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
| **
|
|
189
|
-
| **v1.
|
|
190
|
-
| **v1.2** |
|
|
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:
|
|
19
|
-
>
|
|
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
|
|
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
|
|
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:
|
|
55
|
-
[
|
|
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/)
|
|
58
|
-
|
|
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
|
|
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
|
-
**
|
|
88
|
-
and
|
|
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
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
| **
|
|
153
|
-
| **v1.
|
|
154
|
-
| **v1.2** |
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|