sim-mirror 2.0.0__tar.gz → 2.0.1__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-2.0.0 → sim_mirror-2.0.1}/PKG-INFO +27 -16
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/README.md +25 -14
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/claude-code-cli/README.md +2 -2
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/codex/README.md +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/cursor/README.md +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/fastapi-embed/README.md +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Wire.swift +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/pyproject.toml +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/_version.py +1 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/context.py +12 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/mcp.py +1 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/wda_roles.py +5 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/actions.py +9 -1
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/recordings.py +16 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/mcp/launcher.py +41 -3
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/.gitignore +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/LICENSE +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/NOTICE +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/THIRD_PARTY_LICENSES.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/docs/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/docs/media/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/custom-connector/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/embed-host/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/iframe/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/token-budget/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/examples/web-component/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Package.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Accessibility.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Arguments.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Capture.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/FrameScheduler.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Input.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/JSON.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Recording.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Requests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Screen.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Server.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperCore/Video.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/AccessibilityReader.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/CaptureDevice.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/Disclaimed.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/Framebuffer.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/Frameworks.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/H264Stream.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/HIDTransports.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/Pictures.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/PixelSource.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/RecordingRenderer.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/ScreenFeed.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/SimDeviceHandle.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/SimulatorDevice.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/UnixSocket.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/UsbCapture.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/Warming.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/ObjCGuard/ObjCGuard.m +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/ObjCGuard/include/ObjCGuard.h +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/sim-mirror-helper/main.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Support/Info.plist +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/Fixtures/wire-vectors.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/AccessibilityTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/ArgumentsTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/CaptureTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/InputTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/PictureTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/RecordingTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/ServerTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Tests/HelperCoreTests/WireTests.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/README.md +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/error-inactive.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/error-unauthorized.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/hierarchy-alert.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/hierarchy-keyboard.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/hierarchy-swiftui.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/hierarchy-tagged.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/hierarchy-uikit.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/examples/listing.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/hierarchy.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/app-sdk/v1/listing.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/agent-event.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/client-input.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/close-codes.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/common.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/constants.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/hello.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/http.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/screen-text.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/settings.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/status.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/protocol/v1/stream.schema.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/scripts/hatch_build.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/__main__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/api.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/destination.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/provider.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/wda.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/xcodebuild.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/build/xcresult.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/app_command.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/config_command.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/device_command.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/devices.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/doctor.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/helper.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/main.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/open_viewer.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/record.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/serve.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/settings_command.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/tokens.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/tools.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/version.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/wda.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/cli/xcode.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/discovery.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/env.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/model.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/provenance.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/schema.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/settings_store.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/toml_source.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/config/writer.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/client.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/discovery.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/document.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/errors.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/merge.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/reader.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/app/wire.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/base.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/helper_process.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/companion.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/connector.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/engine.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/frameworks.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/proto/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/proto/idb.proto +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/proto/idb_grpc.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/idb/proto/idb_pb2.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/cable.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/capture.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/connector.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/screen.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/wda.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/wda_client.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/wda_setup.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/iphone/wda_source.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/client.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/connector.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/hierarchy.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/merge.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/mcpbridge/reader.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/native/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/native/client.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/native/connector.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/native/helper.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/native/wire.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/registry.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/simctl/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/simctl/capture.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/connectors/simctl/connector.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/availability.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/backends.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/control.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/device_changes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/device_logs.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/device_settings.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/devices.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/events.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/frames.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/gestures.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/instance.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/manager.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/reaper.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/recording_sources.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/render.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/runtime.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/screen_input.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/screen_relay.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/signing.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/status.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/text_entry.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/text_overlay.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/tickets.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/core/touch.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/app.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/auth.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/confirmations.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/health.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/lease.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/lifecycle.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/passes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/policy.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/daemon/tokens.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/checks.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/macos.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/real_devices.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/report.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/doctor/tap.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/host_copy.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/hosting.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/mcp/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/mcp/relay.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/budget.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/imagehash.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/model.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/ocr.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/readers.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/settle.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/snapshot.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/vision/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/vision/helper.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/vision/probe.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/vision/text_recognizer.swift +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/perception/wait.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/developer_dir.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/device_data.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/devicectl.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/errors.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/identifiers.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/images.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/json_lines.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/keyboard.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/keychain.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/lockdown.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/process.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/simctl.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/swift.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/swiftpm.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/usbmux.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/xcode.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/platform/xcrun.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/protocol/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/protocol/_generated.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/protocol/messages.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/py.typed +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/scope.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/seams.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/agent_routes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/envelope.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/http_routes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/log_redaction.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/pages.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/security.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/settings_routes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/settings_view.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/socket_routes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/static/viewer/embed.html +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/static/viewer/index.html +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/server/static/viewer/start.js +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/storage/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/storage/app_support.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/storage/claims.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/storage/private.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/app_sdk.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/asgi.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/contract.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/daemon.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fakes.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-alert-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-app-sdk-alert.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-app-sdk-keyboard.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-app-sdk-swiftui.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-app-sdk-tagged.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-app-sdk-uikit.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-home-complete.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-home-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-keyboard-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-probe-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-safari-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-safari-typed.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-settings-interactable.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/ax-settings-ios27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/development-certificate.pem +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-appearance.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-apps.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-devices.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-displays.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-launch-missing.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-launch.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-lock-state.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-processes.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/devicectl-screenshot.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-answers.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-alert.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-home.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-keyboard.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-probe.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-safari.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/mcpbridge-hierarchy-settings.txt +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/simctl-devices.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/simctl-runtimes.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcodebuild-list-names.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcodebuild-list.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcodebuild-settings.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans-none.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcodebuild-test-plans.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-build-broken.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-build-ok.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-build-results-unbuilt.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-summary-retries.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing-xcode27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-summary-swift-testing.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-summary-unbuilt.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-summary.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-tests-retries.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing-xcode27.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-tests-swift-testing.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/fixtures/xcresult-test-tests.json +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/guards.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/native.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/pictures.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/recording.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/rig.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/usbmux.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/vision.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/testing/wda.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/__init__.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/act.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/app.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/context.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/device.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/record.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/registry.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/results.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/schemas.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/screenshot.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/tools/snapshot.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/src/sim_mirror/validation.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/tests/unit/protocol/test_messages.py +0 -0
- {sim_mirror-2.0.0 → sim_mirror-2.0.1}/viewer/README.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sim-mirror
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: Mirror and drive
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.
|
|
5
5
|
Project-URL: Homepage, https://github.com/AndrewKochulab/sim-mirror
|
|
6
6
|
Project-URL: Documentation, https://github.com/AndrewKochulab/sim-mirror/tree/main/docs
|
|
7
7
|
Project-URL: Issues, https://github.com/AndrewKochulab/sim-mirror/issues
|
|
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
# SimMirror
|
|
41
41
|
|
|
42
|
-
**Mirror and drive
|
|
42
|
+
**Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.**
|
|
43
43
|
|
|
44
44
|
[](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml)
|
|
45
45
|
[](LICENSE)
|
|
@@ -52,9 +52,10 @@ through its notes, then fills in and saves a new one, the agent's cursor gliding
|
|
|
52
52
|
*A real session, sped up four times. [The whole recording](https://github.com/AndrewKochulab/sim-mirror/releases/download/v1.2.0/sim-mirror-plugin-demo.mp4)
|
|
53
53
|
runs 1m 39s.*
|
|
54
54
|
|
|
55
|
-
A live iOS Simulator in any browser tab or web page, an animated cursor that
|
|
56
|
-
to tap, and token-efficient UI snapshots so agents read the screen as compact
|
|
57
|
-
|
|
55
|
+
A live iOS Simulator -- or your own iPhone, over its cable -- in any browser tab or web page, an animated cursor that
|
|
56
|
+
shows exactly what your AI agent is about to tap, and token-efficient UI snapshots so agents read the screen as compact
|
|
57
|
+
text instead of screenshots. Record any screen as an MP4 or a GIF for a pull request. Works with Claude Code, Codex,
|
|
58
|
+
Cursor and any other MCP client.
|
|
58
59
|
|
|
59
60
|
> **Status: 2.0.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
|
|
60
61
|
> release to [what SimMirror promises not to break](docs/stability.md). 2.0 brings real iPhones and iPads beside the
|
|
@@ -71,6 +72,9 @@ Claude Code, Codex, Cursor and any other MCP client.
|
|
|
71
72
|
in batches with waits, and reach for a screenshot only when a question is visual.
|
|
72
73
|
- **Read screens accessibility cannot.** A game or a canvas reads from its pixels, and an app under development can
|
|
73
74
|
share its own views, so icon-only buttons and hand-drawn controls read by name.
|
|
75
|
+
- **Test on a real iPhone.** Plug it in and it sits beside the simulators: live over the cable at up to 60 fps, touched
|
|
76
|
+
and typed on from the browser or by an agent, and given back everything SimMirror changed on it.
|
|
77
|
+
- **Show what happened.** Record a flow as an MP4 or a GIF with every touch drawn in, and put it in a pull request.
|
|
74
78
|
|
|
75
79
|
## Install
|
|
76
80
|
|
|
@@ -84,7 +88,7 @@ Claude Code, Codex, Cursor and any other MCP client.
|
|
|
84
88
|
**Any MCP client**, as a stdio server:
|
|
85
89
|
|
|
86
90
|
```sh
|
|
87
|
-
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.
|
|
91
|
+
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
|
|
@@ -93,7 +97,7 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
|
|
|
93
97
|
**The command**, for `sim-mirror open`, `doctor` and the rest:
|
|
94
98
|
|
|
95
99
|
```sh
|
|
96
|
-
uv tool install sim-mirror==2.0.
|
|
100
|
+
uv tool install sim-mirror==2.0.1
|
|
97
101
|
# or
|
|
98
102
|
brew install andrewkochulab/tap/sim-mirror
|
|
99
103
|
```
|
|
@@ -103,8 +107,8 @@ brew install andrewkochulab/tap/sim-mirror
|
|
|
103
107
|
[on PyPI](https://pypi.org/project/sim-mirror/) and in a [Homebrew tap](https://github.com/AndrewKochulab/homebrew-tap).
|
|
104
108
|
|
|
105
109
|
**Requirements:** an Apple Silicon Mac, Xcode 26 or later, and [uv](https://docs.astral.sh/uv/) or Homebrew. Nothing
|
|
106
|
-
else: SimMirror drives a simulator with its own helper, which comes in the package.
|
|
107
|
-
[Installation](docs/installation.md).
|
|
110
|
+
else: SimMirror drives a simulator with its own helper, which comes in the package. A real iPhone needs Developer Mode,
|
|
111
|
+
and an Apple development team to be touched. Details in [Installation](docs/installation.md).
|
|
108
112
|
|
|
109
113
|
## Quickstart
|
|
110
114
|
|
|
@@ -118,6 +122,12 @@ Then ask your agent:
|
|
|
118
122
|
> Open Settings → General → About and tell me which iOS version this simulator runs. Use sim_snapshot rather than
|
|
119
123
|
> screenshots.
|
|
120
124
|
|
|
125
|
+
With an iPhone plugged in, unlocked and in Developer Mode:
|
|
126
|
+
|
|
127
|
+
> Switch to my iPhone, build and run the app on it, walk through sign-up and record it as a GIF.
|
|
128
|
+
|
|
129
|
+
The first time, press **Set up touch** in the viewer: it builds WebDriverAgent with your project's own signing team.
|
|
130
|
+
|
|
121
131
|
[Getting started](docs/getting-started.md) explains what happens along the way.
|
|
122
132
|
|
|
123
133
|
## Features
|
|
@@ -143,10 +153,11 @@ in a Debug build on the simulator it shares its own UIKit and SwiftUI views, so
|
|
|
143
153
|
gesture or a hand-drawn control reads by name, and `.simMirror("Settings")` names a SwiftUI view. Optional, and nothing
|
|
144
154
|
of it is in a Release build. See [The app SDK](docs/app-sdk.md).
|
|
145
155
|
|
|
146
|
-
**Real devices.** A cabled iPhone or iPad shows up beside the simulators
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
156
|
+
**Real devices.** A cabled iPhone or iPad shows up beside the simulators, and an agent can switch to it
|
|
157
|
+
(`sim_device list` and `choose`): its live screen over the cable, at about 55 frames a second; its apps, logs, look and
|
|
158
|
+
location through Xcode's `devicectl`; and touching, typing and its element tree through WebDriverAgent, set up once
|
|
159
|
+
with a click and signed by your project's own team -- a tap lands in about a third of a second. It is reached only over
|
|
160
|
+
the cable, and what SimMirror changes on it is put back when it is let go. See [Real devices](docs/real-devices.md).
|
|
150
161
|
|
|
151
162
|
**Recording.** The viewer's Record button, `sim_record` or `sim-mirror record` keep a device's screen as an MP4, a GIF
|
|
152
163
|
or both, with each touch drawn where it landed and a 9:41 status bar. See [Recording](docs/recording.md).
|
|
@@ -206,8 +217,8 @@ an image at about 750 pixels a token).
|
|
|
206
217
|
Rows checked on real hardware are marked verified, with the date, in [Compatibility](docs/compatibility.md).
|
|
207
218
|
|
|
208
219
|
From 1.0 the tools, the protocol, the embedding API, the viewer package, settings and the command line are held to
|
|
209
|
-
[what SimMirror promises not to break](docs/stability.md): a release adds, and never breaks; 2.0 broke nothing.
|
|
210
|
-
exception, and pin a minor version.
|
|
220
|
+
[what SimMirror promises not to break](docs/stability.md): a release adds, and never breaks; 2.0 broke nothing.
|
|
221
|
+
Connectors are the exception, and pin a minor version.
|
|
211
222
|
|
|
212
223
|
## Documentation
|
|
213
224
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# SimMirror
|
|
4
4
|
|
|
5
|
-
**Mirror and drive
|
|
5
|
+
**Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser.**
|
|
6
6
|
|
|
7
7
|
[](https://github.com/AndrewKochulab/sim-mirror/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
@@ -15,9 +15,10 @@ through its notes, then fills in and saves a new one, the agent's cursor gliding
|
|
|
15
15
|
*A real session, sped up four times. [The whole recording](https://github.com/AndrewKochulab/sim-mirror/releases/download/v1.2.0/sim-mirror-plugin-demo.mp4)
|
|
16
16
|
runs 1m 39s.*
|
|
17
17
|
|
|
18
|
-
A live iOS Simulator in any browser tab or web page, an animated cursor that
|
|
19
|
-
to tap, and token-efficient UI snapshots so agents read the screen as compact
|
|
20
|
-
|
|
18
|
+
A live iOS Simulator -- or your own iPhone, over its cable -- in any browser tab or web page, an animated cursor that
|
|
19
|
+
shows exactly what your AI agent is about to tap, and token-efficient UI snapshots so agents read the screen as compact
|
|
20
|
+
text instead of screenshots. Record any screen as an MP4 or a GIF for a pull request. Works with Claude Code, Codex,
|
|
21
|
+
Cursor and any other MCP client.
|
|
21
22
|
|
|
22
23
|
> **Status: 2.0.** The agent tools, the viewer, the protocol and the embedding API are stable, and a check holds every
|
|
23
24
|
> release to [what SimMirror promises not to break](docs/stability.md). 2.0 brings real iPhones and iPads beside the
|
|
@@ -34,6 +35,9 @@ Claude Code, Codex, Cursor and any other MCP client.
|
|
|
34
35
|
in batches with waits, and reach for a screenshot only when a question is visual.
|
|
35
36
|
- **Read screens accessibility cannot.** A game or a canvas reads from its pixels, and an app under development can
|
|
36
37
|
share its own views, so icon-only buttons and hand-drawn controls read by name.
|
|
38
|
+
- **Test on a real iPhone.** Plug it in and it sits beside the simulators: live over the cable at up to 60 fps, touched
|
|
39
|
+
and typed on from the browser or by an agent, and given back everything SimMirror changed on it.
|
|
40
|
+
- **Show what happened.** Record a flow as an MP4 or a GIF with every touch drawn in, and put it in a pull request.
|
|
37
41
|
|
|
38
42
|
## Install
|
|
39
43
|
|
|
@@ -47,7 +51,7 @@ Claude Code, Codex, Cursor and any other MCP client.
|
|
|
47
51
|
**Any MCP client**, as a stdio server:
|
|
48
52
|
|
|
49
53
|
```sh
|
|
50
|
-
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.
|
|
54
|
+
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.md) and
|
|
@@ -56,7 +60,7 @@ Configurations for [Codex](docs/clients/codex.md), [Cursor](docs/clients/cursor.
|
|
|
56
60
|
**The command**, for `sim-mirror open`, `doctor` and the rest:
|
|
57
61
|
|
|
58
62
|
```sh
|
|
59
|
-
uv tool install sim-mirror==2.0.
|
|
63
|
+
uv tool install sim-mirror==2.0.1
|
|
60
64
|
# or
|
|
61
65
|
brew install andrewkochulab/tap/sim-mirror
|
|
62
66
|
```
|
|
@@ -66,8 +70,8 @@ brew install andrewkochulab/tap/sim-mirror
|
|
|
66
70
|
[on PyPI](https://pypi.org/project/sim-mirror/) and in a [Homebrew tap](https://github.com/AndrewKochulab/homebrew-tap).
|
|
67
71
|
|
|
68
72
|
**Requirements:** an Apple Silicon Mac, Xcode 26 or later, and [uv](https://docs.astral.sh/uv/) or Homebrew. Nothing
|
|
69
|
-
else: SimMirror drives a simulator with its own helper, which comes in the package.
|
|
70
|
-
[Installation](docs/installation.md).
|
|
73
|
+
else: SimMirror drives a simulator with its own helper, which comes in the package. A real iPhone needs Developer Mode,
|
|
74
|
+
and an Apple development team to be touched. Details in [Installation](docs/installation.md).
|
|
71
75
|
|
|
72
76
|
## Quickstart
|
|
73
77
|
|
|
@@ -81,6 +85,12 @@ Then ask your agent:
|
|
|
81
85
|
> Open Settings → General → About and tell me which iOS version this simulator runs. Use sim_snapshot rather than
|
|
82
86
|
> screenshots.
|
|
83
87
|
|
|
88
|
+
With an iPhone plugged in, unlocked and in Developer Mode:
|
|
89
|
+
|
|
90
|
+
> Switch to my iPhone, build and run the app on it, walk through sign-up and record it as a GIF.
|
|
91
|
+
|
|
92
|
+
The first time, press **Set up touch** in the viewer: it builds WebDriverAgent with your project's own signing team.
|
|
93
|
+
|
|
84
94
|
[Getting started](docs/getting-started.md) explains what happens along the way.
|
|
85
95
|
|
|
86
96
|
## Features
|
|
@@ -106,10 +116,11 @@ in a Debug build on the simulator it shares its own UIKit and SwiftUI views, so
|
|
|
106
116
|
gesture or a hand-drawn control reads by name, and `.simMirror("Settings")` names a SwiftUI view. Optional, and nothing
|
|
107
117
|
of it is in a Release build. See [The app SDK](docs/app-sdk.md).
|
|
108
118
|
|
|
109
|
-
**Real devices.** A cabled iPhone or iPad shows up beside the simulators
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
**Real devices.** A cabled iPhone or iPad shows up beside the simulators, and an agent can switch to it
|
|
120
|
+
(`sim_device list` and `choose`): its live screen over the cable, at about 55 frames a second; its apps, logs, look and
|
|
121
|
+
location through Xcode's `devicectl`; and touching, typing and its element tree through WebDriverAgent, set up once
|
|
122
|
+
with a click and signed by your project's own team -- a tap lands in about a third of a second. It is reached only over
|
|
123
|
+
the cable, and what SimMirror changes on it is put back when it is let go. See [Real devices](docs/real-devices.md).
|
|
113
124
|
|
|
114
125
|
**Recording.** The viewer's Record button, `sim_record` or `sim-mirror record` keep a device's screen as an MP4, a GIF
|
|
115
126
|
or both, with each touch drawn where it landed and a 9:41 status bar. See [Recording](docs/recording.md).
|
|
@@ -169,8 +180,8 @@ an image at about 750 pixels a token).
|
|
|
169
180
|
Rows checked on real hardware are marked verified, with the date, in [Compatibility](docs/compatibility.md).
|
|
170
181
|
|
|
171
182
|
From 1.0 the tools, the protocol, the embedding API, the viewer package, settings and the command line are held to
|
|
172
|
-
[what SimMirror promises not to break](docs/stability.md): a release adds, and never breaks; 2.0 broke nothing.
|
|
173
|
-
exception, and pin a minor version.
|
|
183
|
+
[what SimMirror promises not to break](docs/stability.md): a release adds, and never breaks; 2.0 broke nothing.
|
|
184
|
+
Connectors are the exception, and pin a minor version.
|
|
174
185
|
|
|
175
186
|
## Documentation
|
|
176
187
|
|
|
@@ -16,7 +16,7 @@ Its tools are named `mcp__plugin_sim-mirror_sim-mirror__sim_snapshot` and so on.
|
|
|
16
16
|
## Without the plugin
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.
|
|
19
|
+
claude mcp add sim-mirror -- uvx --from sim-mirror==2.0.1 sim-mirror mcp
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Add `--scope project` before the name to write it to the project's `.mcp.json` for everyone working on it. Its tools
|
|
@@ -30,7 +30,7 @@ simulator (its scope) and this folder as the only place builds and installs may
|
|
|
30
30
|
## Watch it
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
|
-
uvx --from sim-mirror==2.0.
|
|
33
|
+
uvx --from sim-mirror==2.0.1 sim-mirror open
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
opens this folder's viewer in your browser. Every gesture the agent makes is drawn there as a cursor just before it
|
|
@@ -6,7 +6,7 @@ Add [config.toml](config.toml)'s table to `~/.codex/config.toml`, then start Cod
|
|
|
6
6
|
and installs may reach. The commented `args` pin a project by name and path instead.
|
|
7
7
|
- `tool_timeout_sec` is raised because `sim_build_run` and `sim_test` can take minutes (they are there only when build
|
|
8
8
|
tools are switched on).
|
|
9
|
-
- Watch and take over in a browser: `uvx --from sim-mirror==2.0.
|
|
9
|
+
- Watch and take over in a browser: `uvx --from sim-mirror==2.0.1 sim-mirror open`
|
|
10
10
|
in the same folder.
|
|
11
11
|
|
|
12
12
|
The tools are the same for every client; see [claude-code-cli](../claude-code-cli/) for a first prompt to try.
|
|
@@ -6,7 +6,7 @@ one), and enable the server in Cursor's MCP settings.
|
|
|
6
6
|
- `sim-mirror mcp` gives the agent the simulator of the folder it starts in, and that folder as the only place builds
|
|
7
7
|
and installs may reach. If the tools act on the wrong project, append `"--scope", "my-app", "--root",
|
|
8
8
|
"/absolute/path/to/my-app"` to `args`.
|
|
9
|
-
- Watch and take over in a browser: `uvx --from sim-mirror==2.0.
|
|
9
|
+
- Watch and take over in a browser: `uvx --from sim-mirror==2.0.1 sim-mirror open`
|
|
10
10
|
in the same folder.
|
|
11
11
|
|
|
12
12
|
The tools are the same for every client; see [claude-code-cli](../claude-code-cli/) for a first prompt to try.
|
|
@@ -5,7 +5,7 @@ A Python application with SimMirror inside it: [app.py](app.py). No daemon runs;
|
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
cd examples/fastapi-embed
|
|
8
|
-
EXAMPLE_KEY=change-me uv run --with "sim-mirror==2.0.
|
|
8
|
+
EXAMPLE_KEY=change-me uv run --with "sim-mirror==2.0.1" \
|
|
9
9
|
uvicorn --factory app:create_app --port 7484
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sim-mirror"
|
|
3
3
|
dynamic = ["version"]
|
|
4
|
-
description = "Mirror and drive
|
|
4
|
+
description = "Mirror and drive iOS Simulators and real iPhones from Claude Code, CLI agents and the browser."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
license-files = ["LICENSE", "NOTICE", "THIRD_PARTY_LICENSES.md"]
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import copy
|
|
10
10
|
import os
|
|
11
|
+
import signal
|
|
11
12
|
import sys
|
|
12
13
|
import webbrowser
|
|
13
14
|
from collections.abc import Awaitable, Callable, Coroutine, Mapping, Sequence
|
|
@@ -80,6 +81,8 @@ class CliContext:
|
|
|
80
81
|
opener: relay.Opener | None = None
|
|
81
82
|
open_url: Callable[[str], bool] = webbrowser.open
|
|
82
83
|
spawn: Spawn = process.spawn
|
|
84
|
+
#: How a process is signalled: ``os.kill``, or a test's.
|
|
85
|
+
kill: Callable[[int, int], None] = os.kill
|
|
83
86
|
serve: Serve = serve_with_uvicorn
|
|
84
87
|
run: Runner = process.run
|
|
85
88
|
xcrun: XcrunRunner = run_xcrun
|
|
@@ -122,6 +125,15 @@ class CliContext:
|
|
|
122
125
|
def client(self, url: str | None = None) -> DaemonClient:
|
|
123
126
|
return DaemonClient(url or self.daemon_url(), self.tokens().admin_token(), opener=self.opener)
|
|
124
127
|
|
|
128
|
+
def retire_daemon(self) -> bool:
|
|
129
|
+
"""Ask the daemon this user runs to stop, as ``Ctrl-C`` would: its devices keep running. Whether one was
|
|
130
|
+
running to ask."""
|
|
131
|
+
info = read_info(self.state.run_dir())
|
|
132
|
+
if info is None:
|
|
133
|
+
return False
|
|
134
|
+
self.kill(info.pid, signal.SIGTERM)
|
|
135
|
+
return True
|
|
136
|
+
|
|
125
137
|
def start_daemon(self, *arguments: str) -> int:
|
|
126
138
|
"""Start ``sim-mirror serve`` detached, its output in the log folder. Answers its pid."""
|
|
127
139
|
argv = [self.python, "-m", "sim_mirror", "serve", "--foreground", *arguments]
|
|
@@ -30,6 +30,10 @@ from sim_mirror.core.gestures import CHARACTER_KEYS, COMMAND_KEY, KEYS, SHIFT_KE
|
|
|
30
30
|
|
|
31
31
|
#: How long the device's orientation is trusted before it is asked again.
|
|
32
32
|
ORIENTATION_TTL_S = 2.0
|
|
33
|
+
#: How long the element tree is waited for. A screen WebDriverAgent cannot read quickly -- a busy web page in Safari
|
|
34
|
+
#: took over 30 s on an iPhone 14 Pro -- is read from its pixels instead, and a wait for it to settle is not spent
|
|
35
|
+
#: waiting on one request.
|
|
36
|
+
SOURCE_TIMEOUT_S = 5.0
|
|
33
37
|
#: The most points a finger's stroke is sent with. WebDriverAgent looks each point up against the app on screen -- an
|
|
34
38
|
#: accessibility round trip of about 240 ms -- so a stroke's points cost more than its length: measured on an iPhone 14
|
|
35
39
|
#: Pro, a 0.4 s swipe took 7 to 11 s with 25 points, and 1.3 to 1.6 s with 2.
|
|
@@ -266,7 +270,7 @@ class WdaReader:
|
|
|
266
270
|
self._screen = screen
|
|
267
271
|
|
|
268
272
|
async def accessibility(self) -> dict[str, Any]:
|
|
269
|
-
source = await self._client.call("GET", "/source?format=json")
|
|
273
|
+
source = await self._client.call("GET", "/source?format=json", timeout_s=SOURCE_TIMEOUT_S)
|
|
270
274
|
turn = await self._orientation.turn()
|
|
271
275
|
return {
|
|
272
276
|
"backend": "wda",
|
|
@@ -125,6 +125,9 @@ class Gesture:
|
|
|
125
125
|
@dataclass
|
|
126
126
|
class _Memory:
|
|
127
127
|
snapshot: Snapshot | None = None
|
|
128
|
+
#: Whether a step played since the snapshot was taken, so what it shows may have moved or gone -- a keyboard
|
|
129
|
+
#: opened over it, a screen pushed -- and a ref is read again before it is touched.
|
|
130
|
+
stale: bool = False
|
|
128
131
|
ids: itertools.count[int] = field(default_factory=lambda: itertools.count(1))
|
|
129
132
|
|
|
130
133
|
|
|
@@ -282,6 +285,7 @@ class AgentActions:
|
|
|
282
285
|
memory.snapshot = build(
|
|
283
286
|
tree, device=instance.runtime, screen=instance.screen, max_elements=max_elements, previous=memory.snapshot
|
|
284
287
|
)
|
|
288
|
+
memory.stale = False
|
|
285
289
|
return memory.snapshot
|
|
286
290
|
|
|
287
291
|
def _announce(self, instance: DeviceInstance, caller: Caller, gesture: Gesture, lead_ms: int) -> str:
|
|
@@ -454,7 +458,8 @@ class AgentActions:
|
|
|
454
458
|
screen = instance.screen
|
|
455
459
|
assert screen is not None
|
|
456
460
|
if isinstance(value, str):
|
|
457
|
-
|
|
461
|
+
memory = self._remembered(instance, caller)
|
|
462
|
+
snapshot = None if memory.stale else memory.snapshot
|
|
458
463
|
element = snapshot.element(value) if snapshot is not None else None
|
|
459
464
|
if element is None:
|
|
460
465
|
snapshot = await self._read(instance, caller, max_elements)
|
|
@@ -617,6 +622,9 @@ class AgentActions:
|
|
|
617
622
|
try:
|
|
618
623
|
gesture = await self._gesture(instance, caller, kind, what, step, max_elements)
|
|
619
624
|
await self._play(instance, caller, gesture, lead_ms if cursor else 0)
|
|
625
|
+
# A ref in a later step is where the screen now has it: a step can open a keyboard over it, or
|
|
626
|
+
# take it away, and a ref touched where it was would touch whatever is there now.
|
|
627
|
+
self._remembered(instance, caller).stale = gesture.kind != "pause"
|
|
620
628
|
except ActionError as exc:
|
|
621
629
|
lines.append(f"error step {number}: {exc}")
|
|
622
630
|
failed = True
|
|
@@ -49,6 +49,8 @@ logger = logging.getLogger(__name__)
|
|
|
49
49
|
Format = Literal["mp4", "gif", "both"]
|
|
50
50
|
FORMATS: tuple[Format, ...] = ("mp4", "gif", "both")
|
|
51
51
|
SPEEDS = ("1", "1.5", "2", "4")
|
|
52
|
+
#: The largest GIF GitHub shows inline in a pull request or an issue, in bytes; a larger one is only a download.
|
|
53
|
+
GIF_INLINE_MAX = 10_000_000
|
|
52
54
|
#: A kept recording's file name: when it began, the device's name made safe for a file, and what it is.
|
|
53
55
|
FILE_NAME = re.compile(r"\A[0-9]{8}-[0-9]{6}-[a-z0-9-]{1,64}\.(mp4|gif)\Z")
|
|
54
56
|
_SLUG = re.compile(r"[^a-z0-9]+")
|
|
@@ -400,6 +402,7 @@ class Recordings:
|
|
|
400
402
|
finally:
|
|
401
403
|
_forget(run.source.raw)
|
|
402
404
|
files: list[RecordingFile] = [_file(made) for made in rendered]
|
|
405
|
+
notes = [*notes, *filter(None, (_too_big(file, run.options) for file in files))]
|
|
403
406
|
kept: Recording = {
|
|
404
407
|
"id": run.id,
|
|
405
408
|
"device": run.instance.name,
|
|
@@ -490,6 +493,19 @@ def _file(made: Rendered) -> RecordingFile:
|
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
|
|
496
|
+
def _too_big(file: RecordingFile, options: RecordingOptions) -> str:
|
|
497
|
+
"""What to do about a GIF too big for GitHub to show inline, where a recording is most often put -- or "" when it
|
|
498
|
+
is not too big."""
|
|
499
|
+
if file["format"] != "gif" or file["bytes"] <= GIF_INLINE_MAX:
|
|
500
|
+
return ""
|
|
501
|
+
faster = [speed for speed in SPEEDS if float(speed) > options.speed]
|
|
502
|
+
how = f"speed {faster[-1]}" if faster else "a shorter take"
|
|
503
|
+
return (
|
|
504
|
+
f"{file['name']} is {file['bytes'] / 1_000_000:.1f} MB, over the 10 MB GitHub shows inline: record again with "
|
|
505
|
+
f"{how} or a smaller recording.gif_width, or put the MP4 in a pull request instead"
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
493
509
|
async def _own_status_bar(instance: DeviceInstance, control: StatusBar) -> None:
|
|
494
510
|
try:
|
|
495
511
|
await control.clear_status_bar(instance.udid)
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
1. **The daemon**: asked whether it is up (``/healthz``) -- with a fresh nonce and no credential, and believed only when
|
|
5
5
|
it proves it holds the admin token (`daemon.health`). When nothing answers, it is started detached and waited for,
|
|
6
|
-
up to `HEALTH_WAIT_S`; when something else answers on its port, that is refused and sent nothing.
|
|
6
|
+
up to `HEALTH_WAIT_S`; when something else answers on its port, that is refused and sent nothing. A daemon of an
|
|
7
|
+
older SimMirror -- still running after the plugin was updated -- is stopped and this version's started in its place,
|
|
8
|
+
so the tools the client is offered are this version's; its viewers and other clients reconnect by themselves.
|
|
7
9
|
2. **A token**: the CLI runs as the person who installed SimMirror, so it reads the admin token and mints an agent token
|
|
8
10
|
for this project's scope -- naming the folders its builds and installs may reach (``--root``). The token reaches the
|
|
9
11
|
relay in its environment, never argv, and is revoked when the client goes.
|
|
@@ -25,6 +27,7 @@ from pathlib import Path
|
|
|
25
27
|
from typing import IO, Any
|
|
26
28
|
from urllib.parse import quote
|
|
27
29
|
|
|
30
|
+
from sim_mirror._version import __version__
|
|
28
31
|
from sim_mirror.daemon import health
|
|
29
32
|
from sim_mirror.daemon.lease import RENEW_S
|
|
30
33
|
from sim_mirror.mcp import relay
|
|
@@ -40,6 +43,23 @@ TOKEN_HEADER, SCOPE_HEADER, CLIENT_HEADER = "X-SimMirror-Token", "X-SimMirror-Sc
|
|
|
40
43
|
OURS, OTHER, NOTHING = "ours", "other", "nothing"
|
|
41
44
|
|
|
42
45
|
|
|
46
|
+
def version_of(server: object) -> tuple[int, ...] | None:
|
|
47
|
+
"""A SimMirror version from what a daemon calls itself -- ``sim-mirror/2.0.0`` -- or None."""
|
|
48
|
+
if not isinstance(server, str) or not server.startswith("sim-mirror/"):
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
return tuple(int(part) for part in server.removeprefix("sim-mirror/").split("+")[0].split("."))
|
|
52
|
+
except ValueError:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def older(server: object, mine: str = __version__) -> bool:
|
|
57
|
+
"""Whether a daemon calling itself `server` runs an older SimMirror than this one. One that says no version is
|
|
58
|
+
not called older: it is left as it is."""
|
|
59
|
+
theirs, ours = version_of(server), version_of(f"sim-mirror/{mine}")
|
|
60
|
+
return theirs is not None and ours is not None and theirs < ours
|
|
61
|
+
|
|
62
|
+
|
|
43
63
|
class DaemonUnavailable(Exception):
|
|
44
64
|
"""The daemon could not be reached or started, said so a person knows what to do."""
|
|
45
65
|
|
|
@@ -70,6 +90,8 @@ class DaemonClient:
|
|
|
70
90
|
self._token_id = token_id
|
|
71
91
|
self._open = opener or relay.direct_opener()
|
|
72
92
|
self._ours = False
|
|
93
|
+
#: What the daemon called itself when it last proved it is this user's: ``sim-mirror/<version>``.
|
|
94
|
+
self.server: str | None = None
|
|
73
95
|
|
|
74
96
|
def _send(self, method: str, path: str, payload: object, headers: Mapping[str, str]) -> Any:
|
|
75
97
|
data = None if payload is None else json.dumps(payload).encode("utf-8")
|
|
@@ -104,6 +126,9 @@ class DaemonClient:
|
|
|
104
126
|
self._ours = health.proves(self._token, nonce, data.get("proof"))
|
|
105
127
|
else:
|
|
106
128
|
self._ours = health.token_proves(self._token, nonce, data.get("token_proof"))
|
|
129
|
+
if self._ours:
|
|
130
|
+
server = data.get("server")
|
|
131
|
+
self.server = server if isinstance(server, str) else None
|
|
107
132
|
return OURS if self._ours else OTHER
|
|
108
133
|
|
|
109
134
|
def healthy(self) -> bool:
|
|
@@ -174,10 +199,22 @@ def ensure_daemon(
|
|
|
174
199
|
poll_s: float = HEALTH_POLL_S,
|
|
175
200
|
clock: Callable[[], float] = time.monotonic,
|
|
176
201
|
sleep: Callable[[float], None] = time.sleep,
|
|
202
|
+
retire: Callable[[], object] | None = None,
|
|
177
203
|
) -> None:
|
|
178
204
|
"""This user's daemon, running: started when nothing answers, and waited for. Something else on its port is
|
|
179
|
-
refused rather than started over or trusted.
|
|
205
|
+
refused rather than started over or trusted. With `retire`, a daemon of an older SimMirror is stopped by it and
|
|
206
|
+
this version's started instead; a newer one is used as it is."""
|
|
180
207
|
found = client.probe()
|
|
208
|
+
if found == OURS and retire is not None and older(client.server):
|
|
209
|
+
retire()
|
|
210
|
+
deadline = clock() + wait_s
|
|
211
|
+
while (found := client.probe()) == OURS:
|
|
212
|
+
if clock() >= deadline:
|
|
213
|
+
raise DaemonUnavailable(
|
|
214
|
+
f"the older SimMirror daemon ({client.server}) did not stop within {wait_s:g}s; stop it, and "
|
|
215
|
+
"this version starts in its place"
|
|
216
|
+
)
|
|
217
|
+
sleep(poll_s)
|
|
181
218
|
if found == OURS:
|
|
182
219
|
return
|
|
183
220
|
if found == OTHER:
|
|
@@ -223,6 +260,7 @@ def run(
|
|
|
223
260
|
*,
|
|
224
261
|
client: DaemonClient,
|
|
225
262
|
start_daemon: Callable[[], object],
|
|
263
|
+
retire_daemon: Callable[[], object] | None = None,
|
|
226
264
|
env: Mapping[str, str] | None = None,
|
|
227
265
|
stdin: IO[str] | None = None,
|
|
228
266
|
stdout: IO[str] | None = None,
|
|
@@ -230,7 +268,7 @@ def run(
|
|
|
230
268
|
opener: relay.Opener | None = None,
|
|
231
269
|
) -> int:
|
|
232
270
|
"""Serve MCP for this scope until the client closes stdin."""
|
|
233
|
-
ensure_daemon(client, start_daemon)
|
|
271
|
+
ensure_daemon(client, start_daemon, retire=retire_daemon)
|
|
234
272
|
label = f"sim-mirror mcp (pid {os.getpid()})"
|
|
235
273
|
token_id, token = client.mint_agent_token(scope.id, [str(root) for root in roots], label)
|
|
236
274
|
stop = threading.Event()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sim_mirror-2.0.0 → sim_mirror-2.0.1}/helper/Sources/HelperPlatform/AccessibilityReader.swift
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|