mobster-cli 0.4.3__py3-none-any.whl
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.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Distinguish the standalone CLI's entry point from the Mac app's frozen agent."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Only packaging/cli/entry.py selects this, before importing the command. A missing or damaged selection
|
|
7
|
+
# keeps the app's checks. Neither an environment variable nor renaming the app's agent selects the CLI.
|
|
8
|
+
STANDALONE_CLI = False
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def app_bundle():
|
|
12
|
+
"""Whether the frozen process must enforce the Mac app's plan and signature checks."""
|
|
13
|
+
if not getattr(sys, "frozen", False):
|
|
14
|
+
return False
|
|
15
|
+
if STANDALONE_CLI is not True:
|
|
16
|
+
return True
|
|
17
|
+
try:
|
|
18
|
+
# The CLI entry point cannot opt an executable inside an app out of the app's checks.
|
|
19
|
+
return any(parent.suffix == ".app" for parent in Path(sys.executable).resolve().parents)
|
|
20
|
+
except (OSError, RuntimeError, TypeError, ValueError):
|
|
21
|
+
return True
|
mobile_agent/cable_bt.py
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
"""Cable + Bluetooth: the iPhone's screen video over USB, and this Mac as its Bluetooth keyboard and mouse."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import queue
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from . import perception
|
|
12
|
+
from .drivers import grounded_target, register_driver
|
|
13
|
+
from .mirroring import HELPER_TIMEOUT, MirroringDriver, helper_path, typable
|
|
14
|
+
from .state import validate_input_text
|
|
15
|
+
from .transport import TransportError
|
|
16
|
+
|
|
17
|
+
SOURCE = "cable_bt"
|
|
18
|
+
ENV_FLAG = "MOBSTER_EXPERIMENTAL_CABLE_BT"
|
|
19
|
+
|
|
20
|
+
# While the phone is captured, iOS draws a demonstration status bar; this top share of the picture is blacked out and
|
|
21
|
+
# its text left unread (59 of 852 pt on an iPhone 15 Pro: the status bar and the Dynamic Island).
|
|
22
|
+
STATUS_BAR = .069
|
|
23
|
+
# The helper's video width in pixels (scaled in hardware): about Mirroring's capture and WDA's screenshots.
|
|
24
|
+
VIDEO_WIDTH = 590
|
|
25
|
+
|
|
26
|
+
# The keys MirroringDriver sends for Home and Spotlight (iPhone Mirroring's own); Phone turns them into the iPhone's.
|
|
27
|
+
MIRRORING_HOME, MIRRORING_SPOTLIGHT = "cmd+1", "cmd+3"
|
|
28
|
+
|
|
29
|
+
# The pointer. Homing: big steps into the top-left corner. Moving: a constant pace, so one gain holds.
|
|
30
|
+
HOME_COUNTS = 6000
|
|
31
|
+
HOME_STEP = 1500
|
|
32
|
+
HOME_INTERVAL_MS = 8
|
|
33
|
+
MOVE_STEP = 12
|
|
34
|
+
MOVE_INTERVAL_MS = 12
|
|
35
|
+
CALIBRATE_COUNTS = (240, 480)
|
|
36
|
+
# A click lands only within this distance of the target (points of a 393-point-wide screen).
|
|
37
|
+
TOLERANCE_PT = 9.0
|
|
38
|
+
MAX_CORRECTIONS = 2
|
|
39
|
+
# How long the video is watched for the pointer after a move, and how often.
|
|
40
|
+
FIND_SECONDS = .6
|
|
41
|
+
FIND_POLL_SECONDS = .04
|
|
42
|
+
# A pointer-sized change in the 148 x 320 difference grid (AssistiveTouch's pointer is about 7 cells across).
|
|
43
|
+
POINTER_AREA = (6, 220)
|
|
44
|
+
SETTLE_SECONDS = .06
|
|
45
|
+
# With a hardware keyboard the software keyboard doesn't appear: a tapped field gets this long to take focus.
|
|
46
|
+
FOCUS_SECONDS = .45
|
|
47
|
+
CLICK_MS = 50
|
|
48
|
+
LONG_PRESS_MS = 1500
|
|
49
|
+
# While no paired phone answers, the Mac shows itself for pairing this long.
|
|
50
|
+
ADVERTISE_SECONDS = 600
|
|
51
|
+
DRAG_STEP = 10
|
|
52
|
+
DRAG_INTERVAL_MS = 10
|
|
53
|
+
|
|
54
|
+
PHONE_WIDTH_PT = perception.PHONE_WIDTH_PT
|
|
55
|
+
|
|
56
|
+
# Lock and sleep, read from the picture: the phone must stay unlocked, and Mobster never unlocks it.
|
|
57
|
+
# The lock screen's clock is about 8% of the screen tall on iOS 26; app times are under 3%.
|
|
58
|
+
LOCK_CLOCK_HEIGHT = .05
|
|
59
|
+
LOCK_LINES = ("swipe up to open", "swipe up to unlock", "enter passcode", "touch id or enter passcode",
|
|
60
|
+
"face id", "press home to open", "press home to unlock")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def usb_locked(udid, *, tool=None, run=None):
|
|
64
|
+
if not udid:
|
|
65
|
+
return None
|
|
66
|
+
from . import mac_tools
|
|
67
|
+
tool, run = tool or mac_tools.tool, run or mac_tools.run
|
|
68
|
+
info = tool("ideviceinfo")
|
|
69
|
+
value = run([info, "-u", udid, "-k", "PasswordProtected"], timeout=3) if info else None
|
|
70
|
+
answer = (value or "").strip().casefold()
|
|
71
|
+
return {"true": True, "false": False}.get(answer)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _lock_clock(row):
|
|
75
|
+
"""The lock screen's big clock: a time alone on its line, drawn tall in the top third (an app's times are small)."""
|
|
76
|
+
import re
|
|
77
|
+
rect = row.get("rect") or (0, 1, 0, 0)
|
|
78
|
+
return (bool(re.fullmatch(r"\d{1,2}[:.]\d{2}", " ".join(str(row.get("text") or "").split())))
|
|
79
|
+
and float(rect[3]) >= LOCK_CLOCK_HEIGHT and float(rect[1]) < .35)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def enabled():
|
|
83
|
+
"""Whether the experimental Cable + Bluetooth connection is on (``MOBSTER_EXPERIMENTAL_CABLE_BT=1``)."""
|
|
84
|
+
return os.environ.get(ENV_FLAG, "").strip().casefold() in ("1", "true", "yes", "on")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CableBtError(TransportError):
|
|
88
|
+
"""A helper refused or failed."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, message, code="capture"):
|
|
91
|
+
super().__init__(message)
|
|
92
|
+
self.code = code
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
FIXES = {
|
|
96
|
+
"no_device": "Plug the iPhone into this Mac with a cable, unlock it and tap Trust if it asks, then try again.",
|
|
97
|
+
"ambiguous_device": "Mobster can't tell the plugged-in iPhones apart. Unplug the others, then try again.",
|
|
98
|
+
"permission": "Allow Camera for Mobster in System Settings > Privacy & Security, then try again.",
|
|
99
|
+
"no_bluetooth": "Turn Bluetooth on on this Mac, then try again.",
|
|
100
|
+
"bt_permission": "Allow Bluetooth for Mobster in System Settings > Privacy & Security, then try again.",
|
|
101
|
+
"no_host": "Pair the iPhone with this Mac: on the iPhone, open Settings > Bluetooth and tap Mobster Keyboard. "
|
|
102
|
+
"Then try again.",
|
|
103
|
+
"pointer": "Turn on AssistiveTouch on the iPhone (Settings > Accessibility > Touch), turn off Automatically Hide "
|
|
104
|
+
"Pointer (Accessibility > Pointer Control), then try again.",
|
|
105
|
+
"locked": "Unlock your iPhone, then try again. Mobster never unlocks it.",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def fix_for(error):
|
|
110
|
+
"""The sentence that says what to do about ``error`` (a CableBtError), or ""."""
|
|
111
|
+
return FIXES.get(getattr(error, "code", ""), "")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Link:
|
|
115
|
+
"""One helper process, spoken to in JSON lines."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, command, label, process=None):
|
|
118
|
+
self.label = label
|
|
119
|
+
self._process = process or subprocess.Popen([str(part) for part in command], stdin=subprocess.PIPE,
|
|
120
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True,
|
|
121
|
+
bufsize=1)
|
|
122
|
+
self._lock = threading.Lock()
|
|
123
|
+
self._waiting = {}
|
|
124
|
+
self._next = 0
|
|
125
|
+
self.closed = False
|
|
126
|
+
self._reader = threading.Thread(target=self._read, name=f"{label}-helper", daemon=True)
|
|
127
|
+
self._reader.start()
|
|
128
|
+
|
|
129
|
+
def _read(self):
|
|
130
|
+
for line in self._process.stdout:
|
|
131
|
+
try:
|
|
132
|
+
answer = json.loads(line)
|
|
133
|
+
except ValueError:
|
|
134
|
+
continue
|
|
135
|
+
ident = answer.get("id") if isinstance(answer, dict) else None
|
|
136
|
+
with self._lock:
|
|
137
|
+
box = self._waiting.pop(ident, None) if ident is not None else None
|
|
138
|
+
if box is not None:
|
|
139
|
+
box.put(answer)
|
|
140
|
+
with self._lock:
|
|
141
|
+
self.closed = True
|
|
142
|
+
waiting, self._waiting = self._waiting, {}
|
|
143
|
+
for box in waiting.values():
|
|
144
|
+
box.put(None)
|
|
145
|
+
|
|
146
|
+
def request(self, op, timeout=HELPER_TIMEOUT, **fields):
|
|
147
|
+
"""The helper's answer to ``op``; CableBtError when it refuses, fails, stops or doesn't answer in time."""
|
|
148
|
+
box = queue.Queue(maxsize=1)
|
|
149
|
+
with self._lock:
|
|
150
|
+
if self.closed:
|
|
151
|
+
raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped")
|
|
152
|
+
self._next += 1
|
|
153
|
+
ident = self._next
|
|
154
|
+
self._waiting[ident] = box
|
|
155
|
+
try:
|
|
156
|
+
self._process.stdin.write(json.dumps({"op": op, "id": ident, **fields}) + "\n")
|
|
157
|
+
self._process.stdin.flush()
|
|
158
|
+
except (OSError, ValueError):
|
|
159
|
+
self._waiting.pop(ident, None)
|
|
160
|
+
raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped") from None
|
|
161
|
+
try:
|
|
162
|
+
answer = box.get(timeout=timeout)
|
|
163
|
+
except queue.Empty:
|
|
164
|
+
with self._lock:
|
|
165
|
+
self._waiting.pop(ident, None)
|
|
166
|
+
raise CableBtError(f"Mobster's {self.label} helper didn't answer {op} in {timeout:g} s", "write") from None
|
|
167
|
+
if answer is None:
|
|
168
|
+
raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped")
|
|
169
|
+
if answer.get("ok") is not True:
|
|
170
|
+
raise CableBtError(str(answer.get("error") or f"The {self.label} helper refused"),
|
|
171
|
+
str(answer.get("code") or "capture"))
|
|
172
|
+
return answer
|
|
173
|
+
|
|
174
|
+
def close(self):
|
|
175
|
+
process, self._process = self._process, None
|
|
176
|
+
if process is None:
|
|
177
|
+
return
|
|
178
|
+
for step in (lambda: process.stdin.close(), lambda: process.wait(timeout=2)):
|
|
179
|
+
try:
|
|
180
|
+
step()
|
|
181
|
+
except Exception: # noqa: BLE001 -- closing is best effort; kill below
|
|
182
|
+
process.kill()
|
|
183
|
+
break
|
|
184
|
+
try:
|
|
185
|
+
process.stdout.close()
|
|
186
|
+
except Exception: # noqa: BLE001
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_HID = None
|
|
191
|
+
_HID_LOCK = threading.Lock()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class _SocketProcess:
|
|
195
|
+
"""A connection to the shared Bluetooth keyboard helper, shaped like the Popen that Link talks to."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, connection):
|
|
198
|
+
self._connection = connection
|
|
199
|
+
self.stdout = connection.makefile("r", encoding="utf-8", newline="\n")
|
|
200
|
+
self.stdin = connection.makefile("w", encoding="utf-8", newline="\n")
|
|
201
|
+
|
|
202
|
+
def wait(self, timeout=None):
|
|
203
|
+
self.kill()
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
def kill(self):
|
|
207
|
+
import socket
|
|
208
|
+
try:
|
|
209
|
+
self._connection.shutdown(socket.SHUT_RDWR) # the reader sees the end before its file is closed
|
|
210
|
+
except OSError:
|
|
211
|
+
pass
|
|
212
|
+
self._connection.close()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def socket_path():
|
|
216
|
+
"""Where the shared Bluetooth keyboard helper listens: this user's private temporary folder."""
|
|
217
|
+
import tempfile
|
|
218
|
+
return os.path.join(tempfile.gettempdir(), f"mobster-{os.getuid()}-bt-hid.sock")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _connect(path):
|
|
222
|
+
import socket
|
|
223
|
+
connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
224
|
+
try:
|
|
225
|
+
connection.connect(path)
|
|
226
|
+
except OSError:
|
|
227
|
+
connection.close()
|
|
228
|
+
return None
|
|
229
|
+
return Link(None, "Bluetooth keyboard", process=_SocketProcess(connection))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _start_shared(path, spawn=subprocess.Popen):
|
|
233
|
+
"""Start the shared helper (it outlives this process, and ends 2 minutes after its last client leaves)."""
|
|
234
|
+
process = spawn([str(helper_path("bt_hid")), "--socket", path], stdin=subprocess.DEVNULL,
|
|
235
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, start_new_session=True)
|
|
236
|
+
line = process.stdout.readline()
|
|
237
|
+
process.stdout.close()
|
|
238
|
+
try:
|
|
239
|
+
ready = json.loads(line or "{}")
|
|
240
|
+
except ValueError:
|
|
241
|
+
ready = {}
|
|
242
|
+
if not ready.get("ok"):
|
|
243
|
+
raise CableBtError("Mobster's Bluetooth keyboard helper didn't start: "
|
|
244
|
+
+ str(ready.get("error") or "no answer"),
|
|
245
|
+
"no_bluetooth")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# One helper for every Mobster process on this Mac: one HID service is what the iPhones see.
|
|
249
|
+
def hid_link(spawn=subprocess.Popen, path=None):
|
|
250
|
+
global _HID
|
|
251
|
+
with _HID_LOCK:
|
|
252
|
+
if _HID is not None and not _HID.closed:
|
|
253
|
+
return _HID
|
|
254
|
+
path = path or socket_path()
|
|
255
|
+
link = _connect(path)
|
|
256
|
+
if link is None:
|
|
257
|
+
_start_shared(path, spawn=spawn)
|
|
258
|
+
link = _connect(path)
|
|
259
|
+
if link is None:
|
|
260
|
+
raise CableBtError("Mobster couldn't reach its Bluetooth keyboard helper", "no_bluetooth")
|
|
261
|
+
deadline = time.monotonic() + 5
|
|
262
|
+
while True:
|
|
263
|
+
state = link.request("status", timeout=3).get("state")
|
|
264
|
+
if state == "on":
|
|
265
|
+
break
|
|
266
|
+
if state == "unauthorized" or time.monotonic() >= deadline:
|
|
267
|
+
link.close()
|
|
268
|
+
code = "bt_permission" if state == "unauthorized" else "no_bluetooth"
|
|
269
|
+
raise CableBtError("macOS doesn't let Mobster use Bluetooth" if code == "bt_permission"
|
|
270
|
+
else "Bluetooth isn't on on this Mac", code)
|
|
271
|
+
time.sleep(.1)
|
|
272
|
+
_HID = link
|
|
273
|
+
return link
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def open_screen(udid=None, name=None, phones=None):
|
|
277
|
+
# a capture device may not carry the phone's UDID: its name and the phone count go along, never a guess between two
|
|
278
|
+
if udid and name is None and phones is None:
|
|
279
|
+
from .listen import capture_hints
|
|
280
|
+
hints = capture_hints(udid)
|
|
281
|
+
hinted = dict(zip(hints[::2], hints[1::2]))
|
|
282
|
+
name = hinted.get("--name")
|
|
283
|
+
phones = int(hinted["--phones"]) if hinted.get("--phones", "").isdigit() else None
|
|
284
|
+
link = Link([helper_path("phone_screen")], "iPhone screen")
|
|
285
|
+
try:
|
|
286
|
+
link.request("open", timeout=25, udid=udid, name=name, phones=phones, width=VIDEO_WIDTH)
|
|
287
|
+
except Exception:
|
|
288
|
+
link.close()
|
|
289
|
+
raise
|
|
290
|
+
return link
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def calibration_path():
|
|
294
|
+
from .paths import user_data_dir
|
|
295
|
+
return user_data_dir() / "cable_bt.json"
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclass
|
|
299
|
+
class Calibration:
|
|
300
|
+
"""One phone's pairing and pointer: its Bluetooth host on this Mac and the pointer's gain per count."""
|
|
301
|
+
host: str = ""
|
|
302
|
+
gain: tuple = ()
|
|
303
|
+
home: str = "cmd+h"
|
|
304
|
+
spotlight: str = "cmd+space"
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def load(cls, udid, path=None):
|
|
308
|
+
try:
|
|
309
|
+
record = json.loads((path or calibration_path()).read_text()).get("phones", {}).get(udid or "", {})
|
|
310
|
+
except (OSError, ValueError, AttributeError):
|
|
311
|
+
record = {}
|
|
312
|
+
gain = record.get("gain")
|
|
313
|
+
ok = isinstance(gain, (list, tuple)) and len(gain) == 2 and all(isinstance(v, (int, float)) and 0 < v < 1
|
|
314
|
+
for v in gain)
|
|
315
|
+
return cls(host=str(record.get("host") or ""), gain=tuple(gain) if ok else (),
|
|
316
|
+
home=str(record.get("home") or "cmd+h"), spotlight=str(record.get("spotlight") or "cmd+space"))
|
|
317
|
+
|
|
318
|
+
def save(self, udid, path=None):
|
|
319
|
+
path = path or calibration_path()
|
|
320
|
+
try:
|
|
321
|
+
data = json.loads(path.read_text())
|
|
322
|
+
except (OSError, ValueError):
|
|
323
|
+
data = {}
|
|
324
|
+
phones = data.setdefault("phones", {})
|
|
325
|
+
phones[udid or ""] = {"host": self.host, "gain": list(self.gain), "home": self.home,
|
|
326
|
+
"spotlight": self.spotlight}
|
|
327
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
328
|
+
partial = path.with_suffix(".partial")
|
|
329
|
+
partial.write_text(json.dumps(data, indent=2))
|
|
330
|
+
os.chmod(partial, 0o600)
|
|
331
|
+
os.replace(partial, path)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class Pointer:
|
|
335
|
+
"""AssistiveTouch's pointer on one phone: put where a tap should land, and seen there in the video first."""
|
|
336
|
+
|
|
337
|
+
def __init__(self, screen, hid, calibration, *, aspect=2556 / 1179, save=None, sleep=time.sleep,
|
|
338
|
+
clock=time.monotonic):
|
|
339
|
+
self.screen, self.hid, self.cal = screen, hid, calibration
|
|
340
|
+
self.aspect = aspect # height / width
|
|
341
|
+
self._save = save or (lambda cal: None)
|
|
342
|
+
self._sleep, self._clock = sleep, clock
|
|
343
|
+
self.corrections = 0
|
|
344
|
+
self.taps = 0
|
|
345
|
+
|
|
346
|
+
def tolerance(self):
|
|
347
|
+
return TOLERANCE_PT / PHONE_WIDTH_PT, TOLERANCE_PT / (PHONE_WIDTH_PT * self.aspect)
|
|
348
|
+
|
|
349
|
+
def _move(self, dx, dy, *, step=MOVE_STEP, interval=MOVE_INTERVAL_MS, mask=0):
|
|
350
|
+
dx, dy = int(round(dx)), int(round(dy))
|
|
351
|
+
if not dx and not dy:
|
|
352
|
+
return
|
|
353
|
+
reports = max(abs(dx), abs(dy)) / max(step, 1)
|
|
354
|
+
self.hid.request("move", host=self.cal.host, dx=dx, dy=dy, step=step, interval_ms=interval, mask=mask,
|
|
355
|
+
timeout=5 + reports * (interval / 1000 + .02))
|
|
356
|
+
|
|
357
|
+
def home(self):
|
|
358
|
+
"""Into the top-left corner: big steps, far more than the screen."""
|
|
359
|
+
self._move(-HOME_COUNTS, -HOME_COUNTS, step=HOME_STEP, interval=HOME_INTERVAL_MS)
|
|
360
|
+
self._sleep(SETTLE_SECONDS)
|
|
361
|
+
|
|
362
|
+
def find(self, near, avoid=(0.0, 0.0)):
|
|
363
|
+
deadline = self._clock() + FIND_SECONDS
|
|
364
|
+
tx, ty = self.tolerance()
|
|
365
|
+
while True:
|
|
366
|
+
blobs = self.screen.request("blobs", timeout=3).get("blobs") or []
|
|
367
|
+
candidates = [b for b in blobs if POINTER_AREA[0] <= int(b.get("area") or 0) <= POINTER_AREA[1]
|
|
368
|
+
and not (abs(b["x"] - avoid[0]) < tx * 1.5 and abs(b["y"] - avoid[1]) < ty * 1.5)]
|
|
369
|
+
if candidates:
|
|
370
|
+
return min(candidates, key=lambda b: (b["x"] - near[0]) ** 2 + ((b["y"] - near[1]) * self.aspect) ** 2)
|
|
371
|
+
if self._clock() >= deadline:
|
|
372
|
+
return None
|
|
373
|
+
self._sleep(FIND_POLL_SECONDS)
|
|
374
|
+
|
|
375
|
+
def calibrate(self):
|
|
376
|
+
"""Measure the pointer's gain: from the corner, a known move, and where the video shows it."""
|
|
377
|
+
self.home()
|
|
378
|
+
self.screen.request("mark")
|
|
379
|
+
cx, cy = CALIBRATE_COUNTS
|
|
380
|
+
self._move(cx, cy)
|
|
381
|
+
seen = self.find(near=(.5, .5))
|
|
382
|
+
if seen is None:
|
|
383
|
+
raise CableBtError("Mobster couldn't see AssistiveTouch's pointer move on the iPhone; nothing was tapped",
|
|
384
|
+
"pointer")
|
|
385
|
+
if not (.03 < seen["x"] < .97 and .03 < seen["y"] < .97):
|
|
386
|
+
raise CableBtError("The pointer ran to the screen's edge while Mobster measured it; nothing was tapped. "
|
|
387
|
+
"Set Tracking Speed lower in AssistiveTouch, then try again.", "pointer")
|
|
388
|
+
self.cal.gain = (seen["x"] / cx, seen["y"] / cy)
|
|
389
|
+
self._save(self.cal)
|
|
390
|
+
return self.cal.gain
|
|
391
|
+
|
|
392
|
+
def aim(self, fx, fy):
|
|
393
|
+
if not (0 <= fx <= 1 and 0 <= fy <= 1):
|
|
394
|
+
raise ValueError("The point is outside the screen")
|
|
395
|
+
if not self.cal.gain:
|
|
396
|
+
self.calibrate()
|
|
397
|
+
gx, gy = self.cal.gain
|
|
398
|
+
tx, ty = self.tolerance()
|
|
399
|
+
self.home()
|
|
400
|
+
self.screen.request("mark")
|
|
401
|
+
self._move(fx / gx, fy / gy)
|
|
402
|
+
avoid, total = (0.0, 0.0), (fx / gx, fy / gy)
|
|
403
|
+
for attempt in range(MAX_CORRECTIONS + 1):
|
|
404
|
+
seen = self.find(near=(fx, fy), avoid=avoid)
|
|
405
|
+
if seen is None:
|
|
406
|
+
raise CableBtError("Mobster couldn't see the pointer after moving it; nothing was tapped", "pointer")
|
|
407
|
+
ex, ey = fx - seen["x"], fy - seen["y"]
|
|
408
|
+
if attempt == 0 and .05 < seen["x"] < .95 and .05 < seen["y"] < .95:
|
|
409
|
+
# Learn from a clean move out of the corner (no correction mixed in), slowly.
|
|
410
|
+
gx = .7 * gx + .3 * (seen["x"] / total[0]) if total[0] > 0 else gx
|
|
411
|
+
gy = .7 * gy + .3 * (seen["y"] / total[1]) if total[1] > 0 else gy
|
|
412
|
+
self.cal.gain = (gx, gy)
|
|
413
|
+
if abs(ex) <= tx and abs(ey) <= ty:
|
|
414
|
+
return seen
|
|
415
|
+
if attempt == MAX_CORRECTIONS:
|
|
416
|
+
break
|
|
417
|
+
self.corrections += 1
|
|
418
|
+
avoid = (seen["x"], seen["y"])
|
|
419
|
+
self.screen.request("mark")
|
|
420
|
+
self._move(ex / gx, ey / gy)
|
|
421
|
+
raise CableBtError("The pointer didn't land on the target; nothing was tapped", "pointer")
|
|
422
|
+
|
|
423
|
+
def tap(self, fx, fy, hold_ms=None):
|
|
424
|
+
self.aim(fx, fy)
|
|
425
|
+
self.hid.request("click", host=self.cal.host, ms=hold_ms or CLICK_MS, timeout=5 + (hold_ms or 0) / 1000)
|
|
426
|
+
self.taps += 1
|
|
427
|
+
if self.taps % 20 == 1:
|
|
428
|
+
self._save(self.cal)
|
|
429
|
+
|
|
430
|
+
def drag(self, x1, y1, x2, y2):
|
|
431
|
+
"""A touch drag: the pointer seen at the start, the button held through a constant-pace move, then let go."""
|
|
432
|
+
self.aim(x1, y1)
|
|
433
|
+
gx, gy = self.cal.gain
|
|
434
|
+
self.hid.request("buttons", host=self.cal.host, mask=1)
|
|
435
|
+
try:
|
|
436
|
+
self._move((x2 - x1) / gx, (y2 - y1) / gy, step=DRAG_STEP, interval=DRAG_INTERVAL_MS, mask=1)
|
|
437
|
+
finally:
|
|
438
|
+
self.hid.request("buttons", host=self.cal.host, mask=0)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class Phone:
|
|
442
|
+
"""One phone on this connection, speaking the protocol MirroringDriver's helper speaks."""
|
|
443
|
+
|
|
444
|
+
def __init__(self, screen, hid, calibration, *, udid="", sleep=time.sleep, clock=time.monotonic,
|
|
445
|
+
calibration_file=None, locked=None):
|
|
446
|
+
self.screen, self.hid, self.udid = screen, hid, udid
|
|
447
|
+
self.cal = calibration
|
|
448
|
+
self._file = calibration_file
|
|
449
|
+
self.pointer = Pointer(screen, hid, calibration, save=self.save, sleep=sleep, clock=clock)
|
|
450
|
+
self.locked = locked or usb_locked
|
|
451
|
+
self.window = None
|
|
452
|
+
|
|
453
|
+
def save(self, cal):
|
|
454
|
+
try:
|
|
455
|
+
cal.save(self.udid, self._file)
|
|
456
|
+
except OSError:
|
|
457
|
+
pass # a calibration not kept is measured again next time
|
|
458
|
+
|
|
459
|
+
def _capture(self, fields):
|
|
460
|
+
fields = dict(fields)
|
|
461
|
+
fields.setdefault("mask", STATUS_BAR)
|
|
462
|
+
answer = self.screen.request("capture", timeout=HELPER_TIMEOUT, **fields)
|
|
463
|
+
window = answer.get("window") or {}
|
|
464
|
+
if window.get("w") and window.get("h"):
|
|
465
|
+
self.window = window
|
|
466
|
+
self.pointer.aspect = float(window["h"]) / float(window["w"])
|
|
467
|
+
return answer
|
|
468
|
+
|
|
469
|
+
def state(self):
|
|
470
|
+
try:
|
|
471
|
+
seen = self._capture({"ocr": True, "fast": True})
|
|
472
|
+
except CableBtError as error:
|
|
473
|
+
return {"state": "no-video", "code": error.code, "said": [str(error)]}
|
|
474
|
+
if (seen.get("age") or 0) > 3000:
|
|
475
|
+
return {"state": "no-video", "code": "capture", "said": ["The iPhone's screen video stopped"]}
|
|
476
|
+
if float(seen.get("dark") or 0) > .97:
|
|
477
|
+
return {"state": "asleep", "code": "locked", "said": ["The iPhone's screen is off"]}
|
|
478
|
+
if self.locked(self.udid):
|
|
479
|
+
return {"state": "locked", "code": "locked", "said": ["The iPhone is locked"]}
|
|
480
|
+
rows = seen.get("rows") or ()
|
|
481
|
+
lines = [" ".join(str(row.get("text") or "").split()).casefold() for row in rows]
|
|
482
|
+
if any(line.startswith(lock) for line in lines for lock in LOCK_LINES) or any(map(_lock_clock, rows)):
|
|
483
|
+
return {"state": "locked", "code": "locked", "said": ["The iPhone is locked"]}
|
|
484
|
+
try:
|
|
485
|
+
hosts = self.hid.request("status", timeout=3).get("hosts") or []
|
|
486
|
+
except CableBtError as error:
|
|
487
|
+
return {"state": "no-keyboard", "code": error.code, "said": [str(error)]}
|
|
488
|
+
if not any(h.get("host") == self.cal.host and h.get("ready") for h in hosts):
|
|
489
|
+
return {"state": "no-keyboard", "code": "no_host",
|
|
490
|
+
"said": ["The iPhone isn't using this Mac as a keyboard"]}
|
|
491
|
+
return {"state": "ready"}
|
|
492
|
+
|
|
493
|
+
def _key(self, combo):
|
|
494
|
+
self.hid.request("key", host=self.cal.host, combo=combo)
|
|
495
|
+
|
|
496
|
+
def request(self, op, timeout=HELPER_TIMEOUT, **fields):
|
|
497
|
+
if op == "capture":
|
|
498
|
+
return self._capture(fields)
|
|
499
|
+
if op == "state":
|
|
500
|
+
return {"ok": True, **self.state()}
|
|
501
|
+
if op == "tap":
|
|
502
|
+
self.pointer.tap(fields["x"], fields["y"])
|
|
503
|
+
elif op == "press":
|
|
504
|
+
self.pointer.tap(fields["x"], fields["y"], hold_ms=fields.get("ms") or LONG_PRESS_MS)
|
|
505
|
+
elif op == "drag":
|
|
506
|
+
self.pointer.drag(fields["x1"], fields["y1"], fields["x2"], fields["y2"])
|
|
507
|
+
elif op == "scroll":
|
|
508
|
+
# MirroringDriver's scroll: dy window points, positive reveals what's below (the finger moves up).
|
|
509
|
+
height = float((self.window or {}).get("h") or 1)
|
|
510
|
+
span = max(min(abs(float(fields.get("dy") or 0)) / height, .6), .1)
|
|
511
|
+
x, y = float(fields.get("x", .5)), float(fields.get("y", .5))
|
|
512
|
+
top, bottom = y - span / 2, y + span / 2
|
|
513
|
+
if float(fields.get("dy") or 0) > 0:
|
|
514
|
+
self.pointer.drag(x, bottom, x, top)
|
|
515
|
+
else:
|
|
516
|
+
self.pointer.drag(x, top, x, bottom)
|
|
517
|
+
elif op == "key":
|
|
518
|
+
combo = fields.get("combo") or ""
|
|
519
|
+
# MirroringDriver sends iPhone Mirroring's own keys for Home (cmd+1) and Spotlight (cmd+3): the iPhone's are
|
|
520
|
+
# this phone's calibrated ones (cmd+H and cmd+space).
|
|
521
|
+
self._key({MIRRORING_HOME: self.cal.home, MIRRORING_SPOTLIGHT: self.cal.spotlight}.get(combo, combo))
|
|
522
|
+
elif op == "type":
|
|
523
|
+
if fields.get("mode") != "keys" or not typable(fields.get("text") or ""):
|
|
524
|
+
raise CableBtError("Cable + Bluetooth types US keyboard characters only (at most 80); nothing was "
|
|
525
|
+
"typed", "refused")
|
|
526
|
+
text = fields["text"]
|
|
527
|
+
self.hid.request("type", host=self.cal.host, text=text, timeout=5 + len(text) * .1)
|
|
528
|
+
elif op == "restore":
|
|
529
|
+
self.hid.request("release", host=self.cal.host, timeout=3)
|
|
530
|
+
else:
|
|
531
|
+
raise CableBtError(f"Cable + Bluetooth can't {op}", "usage")
|
|
532
|
+
return {"ok": True}
|
|
533
|
+
|
|
534
|
+
def close(self):
|
|
535
|
+
self.screen.close()
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def assign_host(screen, hid, sleep=time.sleep):
|
|
539
|
+
hosts = [h["host"] for h in hid.request("status", timeout=3).get("hosts") or () if h.get("ready")]
|
|
540
|
+
# moving a pointer touches nothing (only a click does), so nudging every free host is safe
|
|
541
|
+
for host in hosts:
|
|
542
|
+
hid.request("move", host=host, dx=-HOME_COUNTS, dy=-HOME_COUNTS, step=HOME_STEP, interval_ms=HOME_INTERVAL_MS)
|
|
543
|
+
sleep(SETTLE_SECONDS)
|
|
544
|
+
screen.request("mark")
|
|
545
|
+
hid.request("move", host=host, dx=200, dy=400, step=MOVE_STEP, interval_ms=MOVE_INTERVAL_MS, timeout=10)
|
|
546
|
+
sleep(.25)
|
|
547
|
+
if any(POINTER_AREA[0] <= int(b.get("area") or 0) <= POINTER_AREA[1]
|
|
548
|
+
for b in screen.request("blobs").get("blobs") or ()):
|
|
549
|
+
return host
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def open_phone(udid=None, name=None, phones=None, *, calibration_file=None):
|
|
554
|
+
hid = hid_link()
|
|
555
|
+
screen = open_screen(udid=udid, name=name, phones=phones)
|
|
556
|
+
try:
|
|
557
|
+
cal = Calibration.load(udid, calibration_file)
|
|
558
|
+
ready = [h["host"] for h in hid.request("status", timeout=3).get("hosts") or () if h.get("ready")]
|
|
559
|
+
if cal.host not in ready:
|
|
560
|
+
cal.host = assign_host(screen, hid) or ""
|
|
561
|
+
if not cal.host:
|
|
562
|
+
# Show the Mac as "Mobster Keyboard" in the iPhone's Bluetooth settings for a while, for pairing.
|
|
563
|
+
hid.request("advertise", seconds=ADVERTISE_SECONDS)
|
|
564
|
+
raise CableBtError("This iPhone isn't using this Mac as a Bluetooth keyboard and mouse", "no_host")
|
|
565
|
+
cal.gain = ()
|
|
566
|
+
phone = Phone(screen, hid, cal, udid=udid or "", calibration_file=calibration_file)
|
|
567
|
+
phone.save(cal)
|
|
568
|
+
return phone
|
|
569
|
+
except Exception:
|
|
570
|
+
screen.close()
|
|
571
|
+
raise
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _shows(snapshot, text):
|
|
575
|
+
"""Whether ``text`` (typed) is on the screen: its words in order, in one element's text, any case."""
|
|
576
|
+
wanted = " ".join(text.split()).casefold()
|
|
577
|
+
if not wanted:
|
|
578
|
+
return True
|
|
579
|
+
return any(wanted in " ".join(e.label.split()).casefold() for e in snapshot.elements)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class CableBtDriver(MirroringDriver):
|
|
583
|
+
"""The device contract over Cable + Bluetooth: MirroringDriver's, over a ``Phone`` (tests pass a fake)."""
|
|
584
|
+
|
|
585
|
+
source = SOURCE
|
|
586
|
+
|
|
587
|
+
def __init__(self, helper=None, *, udid=None, name=None, phones=None, app_names=None, frames_dir=None,
|
|
588
|
+
sleep=time.sleep, clock=time.monotonic, labeller=None, icon_memory=None):
|
|
589
|
+
if helper is None:
|
|
590
|
+
helper = open_phone(udid=udid, name=name, phones=phones)
|
|
591
|
+
super().__init__(helper, app_names=app_names, frames_dir=frames_dir, sleep=sleep, clock=clock,
|
|
592
|
+
labeller=labeller, icon_memory=icon_memory)
|
|
593
|
+
|
|
594
|
+
def observe(self, timeout=10):
|
|
595
|
+
result, started = self._capture(True, timeout)
|
|
596
|
+
return perception.snapshot_from(result, source=SOURCE, bundle=self.current_app, read_started=started,
|
|
597
|
+
icons=self._icons(result))
|
|
598
|
+
|
|
599
|
+
def _await_keyboard(self):
|
|
600
|
+
# A Bluetooth keyboard keeps the software keyboard away: wait for focus, then read the text back after typing.
|
|
601
|
+
self._sleep(FOCUS_SECONDS)
|
|
602
|
+
return True
|
|
603
|
+
|
|
604
|
+
def execute(self, operation, target, snapshot, text=None, timeout=10):
|
|
605
|
+
if operation not in ("TYPE", "TYPE_SUBMIT"):
|
|
606
|
+
return super().execute(operation, target, snapshot, text=text, timeout=timeout)
|
|
607
|
+
grounded_target(operation, target, snapshot)
|
|
608
|
+
validate_input_text(text, multiline=False)
|
|
609
|
+
if not typable(text):
|
|
610
|
+
raise ValueError("Cable + Bluetooth types US keyboard characters only (at most 80); nothing was typed")
|
|
611
|
+
self._check_current(snapshot, target=target)
|
|
612
|
+
x, y = target.tap_point
|
|
613
|
+
self._tap(x, y)
|
|
614
|
+
self._await_keyboard()
|
|
615
|
+
self._type(text)
|
|
616
|
+
if not _shows(self.observe(timeout=5), text):
|
|
617
|
+
raise TransportError("The typed text didn't show on the screen, so Return wasn't pressed")
|
|
618
|
+
if operation == "TYPE_SUBMIT":
|
|
619
|
+
self.helper.request("key", combo="return")
|
|
620
|
+
return None
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
register_driver(SOURCE, CableBtDriver)
|