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,130 @@
|
|
|
1
|
+
"""Audit each XML source read a verification driver actually makes, without another WDA request."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from urllib.parse import parse_qsl, urlencode, urlsplit
|
|
5
|
+
|
|
6
|
+
from .tree import parse_tree
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _xml_source(path):
|
|
10
|
+
if not isinstance(path, str):
|
|
11
|
+
return None
|
|
12
|
+
parts = urlsplit(path)
|
|
13
|
+
if parts.scheme or parts.netloc or parts.path != "/source":
|
|
14
|
+
return None
|
|
15
|
+
query = parse_qsl(parts.query, keep_blank_values=True)
|
|
16
|
+
if [value for key, value in query if key == "format"] != ["xml"]:
|
|
17
|
+
return None
|
|
18
|
+
# Keep the caller's other source options; only attributes omitted for fast grounding are restored.
|
|
19
|
+
query = [(key, value) for key, value in query if key != "excluded_attributes"]
|
|
20
|
+
query.append(("excluded_attributes", "accessible,index"))
|
|
21
|
+
return parts.path + "?" + urlencode(query, safe=",")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SourceObservation:
|
|
25
|
+
"""A private, run-owned adapter around a driver's bound call/observe methods."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, run):
|
|
28
|
+
self.run = run
|
|
29
|
+
self.driver = None
|
|
30
|
+
self._originals = {}
|
|
31
|
+
self._wrappers = {}
|
|
32
|
+
self._local = threading.local()
|
|
33
|
+
self._active = False
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def active(self):
|
|
37
|
+
return self._active
|
|
38
|
+
|
|
39
|
+
def attach(self, driver):
|
|
40
|
+
if self.driver is driver and self._active:
|
|
41
|
+
return True
|
|
42
|
+
if getattr(driver, "_verify_source_owner", None) is not None or not callable(getattr(driver, "call", None)):
|
|
43
|
+
self.run._incomplete_screen("The driver could not expose its accessibility source reads")
|
|
44
|
+
return False
|
|
45
|
+
try:
|
|
46
|
+
owned = vars(driver)
|
|
47
|
+
except TypeError:
|
|
48
|
+
# Restoring inherited methods requires knowing which attributes the instance owned before wrapping.
|
|
49
|
+
self.run._incomplete_screen("The driver could not expose its accessibility source reads")
|
|
50
|
+
return False
|
|
51
|
+
originals = {"call": ("call" in owned, driver.call)}
|
|
52
|
+
observe = getattr(driver, "observe", None)
|
|
53
|
+
if callable(observe):
|
|
54
|
+
originals["observe"] = ("observe" in owned, observe)
|
|
55
|
+
|
|
56
|
+
def call(method, path, *args, **kwargs):
|
|
57
|
+
source = _xml_source(path) if isinstance(method, str) and method.upper() == "GET" else None
|
|
58
|
+
if source is None:
|
|
59
|
+
return originals["call"][1](method, path, *args, **kwargs)
|
|
60
|
+
release = self.run._reserve_source_read()
|
|
61
|
+
try:
|
|
62
|
+
xml = originals["call"][1](method, source, *args, **kwargs)
|
|
63
|
+
if release is not None and self._active:
|
|
64
|
+
try:
|
|
65
|
+
tree = parse_tree(xml)
|
|
66
|
+
self.run._observe_screen(None, tree)
|
|
67
|
+
self._local.sources = getattr(self._local, "sources", 0) + 1
|
|
68
|
+
except Exception as error:
|
|
69
|
+
self.run.log(f"source audit skipped: {type(error).__name__}")
|
|
70
|
+
self.run._incomplete_screen("An accessibility source could not be audited")
|
|
71
|
+
return xml
|
|
72
|
+
except BaseException:
|
|
73
|
+
if release is not None and self._active:
|
|
74
|
+
self.run._incomplete_screen("An accessibility source request failed")
|
|
75
|
+
raise
|
|
76
|
+
finally:
|
|
77
|
+
if release is not None:
|
|
78
|
+
release()
|
|
79
|
+
|
|
80
|
+
wrappers = {"call": call}
|
|
81
|
+
if "observe" in originals:
|
|
82
|
+
def observed(*args, **kwargs):
|
|
83
|
+
before = getattr(self._local, "sources", 0)
|
|
84
|
+
release = self.run._reserve_source_read()
|
|
85
|
+
try:
|
|
86
|
+
snapshot = originals["observe"][1](*args, **kwargs)
|
|
87
|
+
if release is not None and self._active:
|
|
88
|
+
from ..state import PIXEL_SOURCES
|
|
89
|
+
if (getattr(snapshot, "source", None) in PIXEL_SOURCES
|
|
90
|
+
or getattr(self._local, "sources", 0) == before):
|
|
91
|
+
self.run._incomplete_screen("A screen read had no complete accessibility source")
|
|
92
|
+
return snapshot
|
|
93
|
+
except BaseException:
|
|
94
|
+
if release is not None and self._active:
|
|
95
|
+
self.run._incomplete_screen("A screen observation failed")
|
|
96
|
+
raise
|
|
97
|
+
finally:
|
|
98
|
+
if release is not None:
|
|
99
|
+
release()
|
|
100
|
+
|
|
101
|
+
wrappers["observe"] = observed
|
|
102
|
+
try:
|
|
103
|
+
for name, wrapper in wrappers.items():
|
|
104
|
+
setattr(driver, name, wrapper)
|
|
105
|
+
driver._verify_source_owner = self
|
|
106
|
+
except (AttributeError, TypeError):
|
|
107
|
+
for name, (own, original) in originals.items():
|
|
108
|
+
if getattr(driver, name, None) is wrappers.get(name):
|
|
109
|
+
if own:
|
|
110
|
+
setattr(driver, name, original)
|
|
111
|
+
else:
|
|
112
|
+
delattr(driver, name)
|
|
113
|
+
self.run._incomplete_screen("The driver could not expose its accessibility source reads")
|
|
114
|
+
return False
|
|
115
|
+
self.driver, self._originals, self._wrappers, self._active = driver, originals, wrappers, True
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
def detach(self, driver):
|
|
119
|
+
if self.driver is not driver:
|
|
120
|
+
return
|
|
121
|
+
self._active = False
|
|
122
|
+
if getattr(driver, "_verify_source_owner", None) is self:
|
|
123
|
+
for name, (own, original) in self._originals.items():
|
|
124
|
+
if getattr(driver, name, None) is self._wrappers[name]:
|
|
125
|
+
if own:
|
|
126
|
+
setattr(driver, name, original)
|
|
127
|
+
else:
|
|
128
|
+
delattr(driver, name)
|
|
129
|
+
delattr(driver, "_verify_source_owner")
|
|
130
|
+
self.driver = None
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Check steps for the system surfaces an app lives on outside its own window, driven and read on a simulator:
|
|
2
|
+
App Intents, Home Screen widgets, Live Activities, Control Center, App Clips, Spotlight and the share sheet.
|
|
3
|
+
|
|
4
|
+
Off unless ``MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES=1`` (docs/checks.mdx). A surface is driven from outside the app with
|
|
5
|
+
WebDriverAgent and read from the accessibility tree like any screen, so the assertions are the check language's own.
|
|
6
|
+
``spec`` is the check file's block, ``play`` runs it after the flow. They are imported by name because the check
|
|
7
|
+
loader needs the first before the run machinery the second leans on exists."""
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Run a check's surfaces on the simulator the run holds, after its flow."""
|
|
2
|
+
|
|
3
|
+
from ..smart import is_real_device
|
|
4
|
+
from ..verdict import CouldntRun
|
|
5
|
+
from . import spec
|
|
6
|
+
from .stage import Stage, SurfaceMiss
|
|
7
|
+
from .surfaces import DRIVERS
|
|
8
|
+
|
|
9
|
+
NOT_ON_SIMULATOR = "Not on a simulator: "
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Context:
|
|
13
|
+
"""What a driver needs from the simulator manager, with its errors as a check failing rather than the run."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, run):
|
|
16
|
+
self.run = run
|
|
17
|
+
|
|
18
|
+
def _try(self, what, *args):
|
|
19
|
+
try:
|
|
20
|
+
return what(self.run.target, *args)
|
|
21
|
+
except Exception as error:
|
|
22
|
+
raise SurfaceMiss(str(error) or type(error).__name__) from None
|
|
23
|
+
|
|
24
|
+
def open_url(self, url):
|
|
25
|
+
self._try(self.run.manager.open_url, url)
|
|
26
|
+
|
|
27
|
+
def launch(self, bundle, env):
|
|
28
|
+
self._try(self.run.manager.launch, bundle, (), env)
|
|
29
|
+
|
|
30
|
+
def install(self, path):
|
|
31
|
+
self._try(self.run.manager.install, path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def refuse_unsupported(run):
|
|
35
|
+
"""The run can't drive these, so it says so before it touches the simulator."""
|
|
36
|
+
if is_real_device(run.manager):
|
|
37
|
+
raise CouldntRun("environment", NOT_ON_SIMULATOR + "system surfaces are driven on a simulator. They press, "
|
|
38
|
+
"swipe and lock the screen, which Mobster never does on your iPhone.",
|
|
39
|
+
"Run the check with --sim.")
|
|
40
|
+
for surface in run.check.surfaces:
|
|
41
|
+
via = surface.params.get("via", "url")
|
|
42
|
+
if surface.kind == "app_clip" and via != "url":
|
|
43
|
+
raise CouldntRun("environment", NOT_ON_SIMULATOR + f"an App Clip card opened by {via} has no simulator "
|
|
44
|
+
"path. Only its invocation URL runs.",
|
|
45
|
+
"Use via: url, or test the card on a phone by hand.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def play(run, stage, ctx, surface):
|
|
49
|
+
"""One surface: drive it, tap its ``pick``, and read: (assertion results, the sentence that failed, its frame)."""
|
|
50
|
+
results, miss, frames = [], None, []
|
|
51
|
+
stage.shot = lambda: frames.append(run.frame(f"surface-{surface.kind}-{len(frames) + 1}"))
|
|
52
|
+
try:
|
|
53
|
+
stage.follow_the_front()
|
|
54
|
+
results, tree = DRIVERS[surface.kind](stage, surface, ctx)
|
|
55
|
+
if surface.pick is not None:
|
|
56
|
+
stage.tap_node(stage.wait_for(None, f"{surface.pick.render()} to pick", selector=surface.pick), "the pick")
|
|
57
|
+
after, tree = stage.settle(surface.then)
|
|
58
|
+
results = [*results, *after]
|
|
59
|
+
elif surface.kind == "share_sheet":
|
|
60
|
+
stage.tap(tree.size[0] / 2, tree.size[1] * .08, "outside the sheet to close it")
|
|
61
|
+
except SurfaceMiss as error:
|
|
62
|
+
miss = str(error)
|
|
63
|
+
# A CouldntRun (an action with no answer) skips this: nothing more is sent to a simulator in an unknown state.
|
|
64
|
+
stage.activate(run.app.bundle_id)
|
|
65
|
+
return results, miss, next((frame for frame in reversed(frames) if frame), None)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def finding(surface, text, frame, run):
|
|
69
|
+
frame = run._relative(frame)
|
|
70
|
+
return {"id": f"{surface.label()}", "guideline": None, "level": "fail", "frame": frame,
|
|
71
|
+
"message": f"{surface.label()}: {text}", "evidence": frame or "the surface"}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def drive(run):
|
|
75
|
+
"""Every surface in order. A surface that fails adds a fail finding and the next one still runs; a refused
|
|
76
|
+
environment or an action with no answer ends the run."""
|
|
77
|
+
surfaces = run.check.surfaces
|
|
78
|
+
if not surfaces:
|
|
79
|
+
return
|
|
80
|
+
if not spec.enabled():
|
|
81
|
+
raise CouldntRun("usage", f"The check has surfaces, which are off. Set {spec.FLAG}=1.")
|
|
82
|
+
refuse_unsupported(run)
|
|
83
|
+
from ..runner import _now # the run's own clock, so a test that sets it sets this one too
|
|
84
|
+
stage = Stage(run.driver, log=run.log, sleep=run._sleep, clock=_now)
|
|
85
|
+
ctx = Context(run)
|
|
86
|
+
run.surface_reports = []
|
|
87
|
+
with run.lock:
|
|
88
|
+
for surface in surfaces:
|
|
89
|
+
run._check_stop()
|
|
90
|
+
results, miss, frame = play(run, stage, ctx, surface)
|
|
91
|
+
failed = [item for item in results if not item.ok]
|
|
92
|
+
run.event({"event": "surface", "surface": surface.kind, "name": surface.label(),
|
|
93
|
+
"held": len(results) - len(failed), "failed": len(failed), "miss": miss})
|
|
94
|
+
run.log(f"surface {surface.label()}: {len(results) - len(failed)} of {len(results)} held"
|
|
95
|
+
+ (f"; {miss}" if miss else ""))
|
|
96
|
+
if miss:
|
|
97
|
+
run.findings.append(finding(surface, miss, frame, run))
|
|
98
|
+
for item in failed:
|
|
99
|
+
run.findings.append(finding(surface, f"{item.text}: {item.observed}", frame, run))
|
|
100
|
+
run.surface_reports.append({"surface": surface.kind, "name": surface.label(), "ok": not (miss or failed)})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def note(run):
|
|
104
|
+
"""The verdict's tail for a run that drove surfaces: how many held."""
|
|
105
|
+
reports = getattr(run, "surface_reports", ())
|
|
106
|
+
return f"; {sum(item['ok'] for item in reports)} of {len(reports)} system surfaces held" if reports else ""
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""The ``surfaces`` block of a check: which system surfaces to drive and what must show on each."""
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from ..assertions import CheckError, expand_assertion, parse_selector, reject_unknown_keys
|
|
9
|
+
|
|
10
|
+
FLAG = "MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES"
|
|
11
|
+
LIMIT = 10
|
|
12
|
+
WHERE = ("lock_screen", "dynamic_island")
|
|
13
|
+
# App Clip invocations that need a phone, a tag or a place; only the URL has a simulator path.
|
|
14
|
+
VIA_UNSUPPORTED = ("nfc", "qr", "banner", "maps")
|
|
15
|
+
VIA = ("url", *VIA_UNSUPPORTED)
|
|
16
|
+
|
|
17
|
+
COMMON = ("surface", "name", "expect")
|
|
18
|
+
# kind -> (keys it takes besides COMMON, keys it needs)
|
|
19
|
+
KINDS = {
|
|
20
|
+
"intent": (("shortcut", "siri", "input"), ()),
|
|
21
|
+
"widget": (("add",), ()),
|
|
22
|
+
"live_activity": (("where", "expanded"), ()),
|
|
23
|
+
"control_center": (("pick", "then"), ()),
|
|
24
|
+
"app_clip": (("bundle", "url", "via", "path"), ("bundle", "url")),
|
|
25
|
+
"spotlight": (("query", "pick", "then"), ("query",)),
|
|
26
|
+
"share_sheet": (("open", "pick", "then"), ("open",)),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def enabled():
|
|
31
|
+
"""Whether system surfaces are switched on (``MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES=1``)."""
|
|
32
|
+
return os.environ.get(FLAG, "").strip().casefold() in ("1", "true", "yes", "on")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Surface:
|
|
37
|
+
kind: str
|
|
38
|
+
name: str
|
|
39
|
+
params: dict # the validated keys as written, minus expect and then
|
|
40
|
+
expect: tuple = () # of assertions: what shows once the surface is up
|
|
41
|
+
then: tuple = () # of assertions: what shows after `pick`
|
|
42
|
+
pick: Optional[object] = None # assertions.Selector: the element to tap
|
|
43
|
+
open: Optional[object] = None # assertions.Selector: the in-app control that presents the surface
|
|
44
|
+
|
|
45
|
+
def label(self):
|
|
46
|
+
return self.name or self.kind.replace("_", " ")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _text(value, where):
|
|
50
|
+
if isinstance(value, bool) or not isinstance(value, str) or not value.strip():
|
|
51
|
+
raise CheckError(f"{where} must be text")
|
|
52
|
+
if len(value) > 500:
|
|
53
|
+
raise CheckError(f"{where} is longer than 500 characters")
|
|
54
|
+
return value.strip()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _selector(value, where):
|
|
58
|
+
"""A label as text, or a selector object."""
|
|
59
|
+
return parse_selector({"label": value} if isinstance(value, str) else value, where)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _assertions(value, where):
|
|
63
|
+
if value is None:
|
|
64
|
+
return ()
|
|
65
|
+
if not isinstance(value, list):
|
|
66
|
+
raise CheckError(f"{where} must be a list of assertions, such as [{{text: 12 day streak}}]")
|
|
67
|
+
out = []
|
|
68
|
+
for index, item in enumerate(value):
|
|
69
|
+
out.extend(expand_assertion(item, f"{where}[{index}]"))
|
|
70
|
+
return tuple(out)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _one(index, item, base_dir):
|
|
74
|
+
where = f"surfaces[{index}]"
|
|
75
|
+
if not isinstance(item, dict):
|
|
76
|
+
raise CheckError(f"{where} must be an object such as {{surface: widget, expect: [{{text: Streak}}]}}")
|
|
77
|
+
kind = item.get("surface")
|
|
78
|
+
if kind not in KINDS:
|
|
79
|
+
close = difflib.get_close_matches(str(kind), list(KINDS), n=1)
|
|
80
|
+
raise CheckError(f"{where}.surface {kind!r} isn't a system surface; "
|
|
81
|
+
+ (f"did you mean {close[0]}? " if close else "") + f"Use {', '.join(KINDS)}.")
|
|
82
|
+
keys, required = KINDS[kind]
|
|
83
|
+
reject_unknown_keys(item, (*COMMON, *keys), where)
|
|
84
|
+
for key in required:
|
|
85
|
+
if item.get(key) is None:
|
|
86
|
+
raise CheckError(f"{where}.{key} is required for a {kind.replace('_', ' ')}")
|
|
87
|
+
params = {key: item[key] for key in keys if item.get(key) is not None}
|
|
88
|
+
if kind == "intent" and ("shortcut" in params) == ("siri" in params):
|
|
89
|
+
raise CheckError(f"{where} needs exactly one of shortcut (a shortcut's name) and siri (a phrase to say)")
|
|
90
|
+
if "input" in params and "shortcut" not in params:
|
|
91
|
+
raise CheckError(f"{where}.input goes with shortcut")
|
|
92
|
+
if "where" in params and params["where"] not in WHERE:
|
|
93
|
+
raise CheckError(f"{where}.where must be one of {', '.join(WHERE)}")
|
|
94
|
+
if "expanded" in params and not isinstance(params["expanded"], bool):
|
|
95
|
+
raise CheckError(f"{where}.expanded must be true or false")
|
|
96
|
+
if "via" in params and params["via"] not in VIA:
|
|
97
|
+
raise CheckError(f"{where}.via must be one of {', '.join(VIA)}")
|
|
98
|
+
for key in ("shortcut", "siri", "input", "add", "query", "bundle", "url", "path"):
|
|
99
|
+
if key in params:
|
|
100
|
+
params[key] = _text(params[key], f"{where}.{key}")
|
|
101
|
+
if "path" in params and base_dir is not None and not os.path.isabs(params["path"]):
|
|
102
|
+
params["path"] = os.path.normpath(os.path.join(base_dir, params["path"]))
|
|
103
|
+
pick = _selector(params["pick"], f"{where}.pick") if "pick" in params else None
|
|
104
|
+
opens = _selector(params["open"], f"{where}.open") if "open" in params else None
|
|
105
|
+
then = _assertions(item.get("then"), f"{where}.then")
|
|
106
|
+
if then and pick is None:
|
|
107
|
+
raise CheckError(f"{where}.then is read after pick, and there is no pick")
|
|
108
|
+
expect = _assertions(item.get("expect"), f"{where}.expect")
|
|
109
|
+
if not (expect or then):
|
|
110
|
+
raise CheckError(f"{where} checks nothing. Add expect: what must show on the {kind.replace('_', ' ')}")
|
|
111
|
+
name = item.get("name")
|
|
112
|
+
return Surface(kind, _text(name, f"{where}.name") if name is not None else "", params,
|
|
113
|
+
expect, then, pick, opens)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def parse(value, base_dir=None):
|
|
117
|
+
"""The ``surfaces`` block as a tuple of Surface: () when absent. Raises CheckError; off unless the flag is on."""
|
|
118
|
+
if value is None:
|
|
119
|
+
return ()
|
|
120
|
+
if not enabled():
|
|
121
|
+
raise CheckError(f"surfaces is experimental and off. Set {FLAG}=1 to use it.")
|
|
122
|
+
if not isinstance(value, list) or not value:
|
|
123
|
+
raise CheckError("surfaces must list at least one surface, such as [{surface: widget, expect: [...]}]")
|
|
124
|
+
if len(value) > LIMIT:
|
|
125
|
+
raise CheckError(f"surfaces has more than {LIMIT} entries")
|
|
126
|
+
return tuple(_one(index, item, base_dir) for index, item in enumerate(value))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def to_dict(surfaces):
|
|
130
|
+
"""The file shape of ``surfaces``, as ``parse`` reads it."""
|
|
131
|
+
out = []
|
|
132
|
+
for surface in surfaces:
|
|
133
|
+
item = {"surface": surface.kind, **({"name": surface.name} if surface.name else {}), **surface.params}
|
|
134
|
+
if surface.expect:
|
|
135
|
+
item["expect"] = [assertion.to_dict() for assertion in surface.expect]
|
|
136
|
+
if surface.then:
|
|
137
|
+
item["then"] = [assertion.to_dict() for assertion in surface.then]
|
|
138
|
+
out.append(item)
|
|
139
|
+
return out
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The device side of a surface: WebDriverAgent gestures and reads on a simulator, with nothing retried after an
|
|
2
|
+
unknown outcome."""
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from ..assertions import evaluate_on, folded
|
|
7
|
+
from ..tree import read_tree
|
|
8
|
+
from ..verdict import CouldntRun
|
|
9
|
+
|
|
10
|
+
# WDA reads the app it was told is active while that app runs, even under Control Center or the Lock Screen, and the
|
|
11
|
+
# run names the app under test (drivers.py). A name no app has makes WDA read the app at its detection point, a
|
|
12
|
+
# system overlay included (measured 26 Oct 2026, iOS 26.4 simulator, WDA 16.12.10).
|
|
13
|
+
IN_FRONT = "auto"
|
|
14
|
+
READ_POLL = .5
|
|
15
|
+
TAP_MS = 40
|
|
16
|
+
HOLD_MS = 1200
|
|
17
|
+
DRAG_MS = 350
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SurfaceMiss(Exception):
|
|
21
|
+
"""The surface couldn't be reached or didn't show; the check fails with this sentence and goes on."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Stage:
|
|
25
|
+
"""One simulator's screen, driven from outside the app under test. Every call is logged before it is sent."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, driver, *, log, sleep=time.sleep, clock=time.monotonic):
|
|
28
|
+
self.driver, self._log, self.sleep, self.clock = driver, log, sleep, clock
|
|
29
|
+
self.size = (0.0, 0.0)
|
|
30
|
+
self.shot = None # called with no arguments when a settle ends: the frame of the surface as it stood
|
|
31
|
+
|
|
32
|
+
def act(self, what, method, path, body=None, timeout=20):
|
|
33
|
+
"""One action. An answer that never came is an unknown outcome: the run stops and nothing is sent again."""
|
|
34
|
+
self._log(f"surface: {what}")
|
|
35
|
+
try:
|
|
36
|
+
return self.driver.call(method, path, body, timeout)
|
|
37
|
+
except Exception as error:
|
|
38
|
+
raise CouldntRun("unknown_outcome",
|
|
39
|
+
f"WebDriverAgent didn't confirm whether this happened: {what} ({type(error).__name__}).",
|
|
40
|
+
"Look at the simulator before starting another run; Mobster didn't send it again.") \
|
|
41
|
+
from None
|
|
42
|
+
|
|
43
|
+
def follow_the_front(self):
|
|
44
|
+
"""Reads come from whatever is in front until ``activate`` names an app again."""
|
|
45
|
+
self.act("read whatever is in front", "POST", "/appium/settings",
|
|
46
|
+
{"settings": {"defaultActiveApplication": IN_FRONT}})
|
|
47
|
+
|
|
48
|
+
def read(self):
|
|
49
|
+
"""The accessibility tree of whatever is in front, the Home Screen and Lock Screen included."""
|
|
50
|
+
try:
|
|
51
|
+
tree = read_tree(self.driver, pause=self.sleep)
|
|
52
|
+
except Exception as error:
|
|
53
|
+
raise CouldntRun("wda", f"WebDriverAgent stopped answering while a surface was read "
|
|
54
|
+
f"({type(error).__name__}).", "Run `mobster sim doctor --fix`, then try again.") \
|
|
55
|
+
from None
|
|
56
|
+
self.size = tree.size
|
|
57
|
+
return tree
|
|
58
|
+
|
|
59
|
+
def settle(self, assertions, timeout=8):
|
|
60
|
+
"""Read until every assertion holds or ``timeout`` passes: (results, tree) from the last read."""
|
|
61
|
+
deadline = self.clock() + timeout
|
|
62
|
+
while True:
|
|
63
|
+
tree = self.read()
|
|
64
|
+
results = evaluate_on(assertions, tree)
|
|
65
|
+
if all(result.ok for result in results) or self.clock() >= deadline:
|
|
66
|
+
if self.shot is not None:
|
|
67
|
+
self.shot()
|
|
68
|
+
return results, tree
|
|
69
|
+
self.sleep(READ_POLL)
|
|
70
|
+
|
|
71
|
+
def find(self, tree, *needles):
|
|
72
|
+
"""The first shown node whose label or identifier contains one of ``needles``, ignoring case."""
|
|
73
|
+
wanted = [folded(needle) for needle in needles]
|
|
74
|
+
for node in tree.shown():
|
|
75
|
+
text = (folded(node.label), folded(node.identifier))
|
|
76
|
+
if any(needle and needle in part for needle in wanted for part in text):
|
|
77
|
+
return node
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def wait_for(self, needles, what, timeout=6, selector=None):
|
|
81
|
+
"""The node for ``needles`` (or ``selector``) once it shows; SurfaceMiss when it never does."""
|
|
82
|
+
deadline = self.clock() + timeout
|
|
83
|
+
while True:
|
|
84
|
+
tree = self.read()
|
|
85
|
+
node = (next(iter(tree.select(selector)), None) if selector is not None
|
|
86
|
+
else self.find(tree, *([needles] if isinstance(needles, str) else needles)))
|
|
87
|
+
if node is not None:
|
|
88
|
+
return node
|
|
89
|
+
if self.clock() >= deadline:
|
|
90
|
+
raise SurfaceMiss(f"{what} didn't show; the screen had {self._seen(tree)}")
|
|
91
|
+
self.sleep(READ_POLL)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _seen(tree):
|
|
95
|
+
labels = [node.label for node in tree.shown() if node.label.strip()][:6]
|
|
96
|
+
return ", ".join(f'"{label[:40]}"' for label in labels) or "no labelled elements"
|
|
97
|
+
|
|
98
|
+
def tap_node(self, node, what):
|
|
99
|
+
x, y, w, h = node.rect
|
|
100
|
+
self.tap(x + w / 2, y + h / 2, what)
|
|
101
|
+
|
|
102
|
+
def tap(self, x, y, what):
|
|
103
|
+
self._pointer(f"tap {what}", [{"type": "pointerMove", "duration": 0, "x": round(x), "y": round(y)},
|
|
104
|
+
{"type": "pointerDown"}, {"type": "pause", "duration": TAP_MS},
|
|
105
|
+
{"type": "pointerUp"}])
|
|
106
|
+
|
|
107
|
+
def hold(self, x, y, what):
|
|
108
|
+
self._pointer(f"press and hold {what}", [{"type": "pointerMove", "duration": 0, "x": round(x), "y": round(y)},
|
|
109
|
+
{"type": "pointerDown"}, {"type": "pause", "duration": HOLD_MS},
|
|
110
|
+
{"type": "pointerUp"}])
|
|
111
|
+
|
|
112
|
+
def drag(self, start, end, what):
|
|
113
|
+
self._pointer(f"swipe {what}", [{"type": "pointerMove", "duration": 0, "x": round(start[0]),
|
|
114
|
+
"y": round(start[1])}, {"type": "pointerDown"},
|
|
115
|
+
{"type": "pointerMove", "duration": DRAG_MS, "x": round(end[0]),
|
|
116
|
+
"y": round(end[1])}, {"type": "pointerUp"}])
|
|
117
|
+
|
|
118
|
+
def _pointer(self, what, steps):
|
|
119
|
+
self.act(what, "POST", "/actions", {"actions": [{"type": "pointer", "id": "finger",
|
|
120
|
+
"parameters": {"pointerType": "touch"}, "actions": steps}]})
|
|
121
|
+
|
|
122
|
+
def home(self):
|
|
123
|
+
self.act("press Home", "POST", "/wda/pressButton", {"name": "home"})
|
|
124
|
+
|
|
125
|
+
def lock(self):
|
|
126
|
+
self.act("lock the simulator", "POST", "/wda/lock")
|
|
127
|
+
|
|
128
|
+
def unlock(self):
|
|
129
|
+
self.act("unlock the simulator", "POST", "/wda/unlock")
|
|
130
|
+
|
|
131
|
+
def type(self, text):
|
|
132
|
+
self.act("type into the search field", "POST", "/wda/keys", {"value": list(text)})
|
|
133
|
+
|
|
134
|
+
def siri(self, phrase):
|
|
135
|
+
self.act("ask Siri", "POST", "/wda/siri/activate", {"text": phrase})
|
|
136
|
+
|
|
137
|
+
def activate(self, bundle):
|
|
138
|
+
self.act(f"bring {bundle} forward", "POST", "/wda/apps/activate", {"bundleId": bundle})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""One driver per system surface. Each leaves the surface up, reads it, and returns (results, tree); ``pick`` and
|
|
2
|
+
``then`` are handled once, by ``play``.
|
|
3
|
+
|
|
4
|
+
The labels these drivers look for (``Edit``, ``Add Widget``, ``Done``) are the strings iOS 26 shows in English; a
|
|
5
|
+
simulator in another language needs the same strings in its own."""
|
|
6
|
+
|
|
7
|
+
from urllib.parse import quote, urlencode
|
|
8
|
+
|
|
9
|
+
from ..assertions import parse_selector
|
|
10
|
+
from .stage import SurfaceMiss
|
|
11
|
+
|
|
12
|
+
ICON_ROLES = frozenset({"Icon", "Button", "Cell"})
|
|
13
|
+
SEARCH_FIELD = parse_selector({"role": "field"})
|
|
14
|
+
# A press and hold on the Home Screen opens edit mode with an Edit button (iOS 26.4) or a menu with Edit Home Screen;
|
|
15
|
+
# Edit's menu and the widget's page both say Add Widget. Selectors, so an icon named like them is never tapped.
|
|
16
|
+
EDIT_HOME = parse_selector({"label": "/^Edit( Home Screen)?$/"})
|
|
17
|
+
ADD_WIDGET = parse_selector({"label": "/^\\W*Add Widget$/", "role": "button"})
|
|
18
|
+
# iPhone Control Center opens from the top right; Spotlight from a swipe down on the Home Screen's middle.
|
|
19
|
+
CONTROL_CENTER_X = 25
|
|
20
|
+
HOME_ROWS = (.25, .75, .03)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def empty_point(tree):
|
|
24
|
+
"""A point on the Home Screen that holds no icon or button: where a press and hold opens its edit menu."""
|
|
25
|
+
width, height = tree.size
|
|
26
|
+
covered = [node.rect for node in tree.shown() if node.role in ICON_ROLES]
|
|
27
|
+
step = HOME_ROWS[2]
|
|
28
|
+
row = HOME_ROWS[0]
|
|
29
|
+
while row <= HOME_ROWS[1]:
|
|
30
|
+
for column in (.5, .15, .85):
|
|
31
|
+
x, y = width * column, height * row
|
|
32
|
+
if not any(rx <= x <= rx + rw and ry <= y <= ry + rh for rx, ry, rw, rh in covered):
|
|
33
|
+
return x, y
|
|
34
|
+
row += step
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def intent(stage, surface, ctx):
|
|
39
|
+
"""A shortcut that runs the app's App Intent (``shortcuts://run-shortcut``), or a phrase said to Siri."""
|
|
40
|
+
params = surface.params
|
|
41
|
+
if "siri" in params:
|
|
42
|
+
stage.siri(params["siri"])
|
|
43
|
+
else:
|
|
44
|
+
given = {"input": "text", "text": params["input"]} if "input" in params else {}
|
|
45
|
+
query = {"name": params["shortcut"], **given}
|
|
46
|
+
# %20, not +: Shortcuts reads a + in the name as a +, and finds no shortcut (iOS 26.4 simulator, 26 Oct 2026)
|
|
47
|
+
ctx.open_url("shortcuts://run-shortcut?" + urlencode(query, quote_via=quote))
|
|
48
|
+
return stage.settle(surface.expect)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def widget(stage, surface, ctx):
|
|
52
|
+
"""The Home Screen, after adding the app's widget from the gallery when ``add`` names the app."""
|
|
53
|
+
stage.home()
|
|
54
|
+
if "add" in surface.params:
|
|
55
|
+
_add_widget(stage, surface.params["add"])
|
|
56
|
+
return stage.settle(surface.expect)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _add_widget(stage, app):
|
|
60
|
+
point = empty_point(stage.read())
|
|
61
|
+
if point is None:
|
|
62
|
+
raise SurfaceMiss("the Home Screen has no empty spot to press and hold; clear a row of icons")
|
|
63
|
+
stage.hold(*point, "an empty spot on the Home Screen")
|
|
64
|
+
stage.tap_node(stage.wait_for(None, "the Home Screen's Edit button", selector=EDIT_HOME), "Edit")
|
|
65
|
+
stage.tap_node(stage.wait_for(None, "Add Widget in the Edit menu", selector=ADD_WIDGET), "Add Widget")
|
|
66
|
+
stage.tap_node(stage.wait_for(None, "the widget search field", selector=SEARCH_FIELD), "the search field")
|
|
67
|
+
stage.type(app)
|
|
68
|
+
listed = parse_selector({"label": app, "role": "cell"}) # not the app's own icon behind the gallery
|
|
69
|
+
stage.tap_node(stage.wait_for(None, f"{app} in the widget gallery", selector=listed), app)
|
|
70
|
+
stage.tap_node(stage.wait_for(None, f"the Add Widget button for {app}", selector=ADD_WIDGET), "Add Widget")
|
|
71
|
+
stage.tap_node(stage.wait_for("Done", "the Done button"), "Done")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def live_activity(stage, surface, ctx):
|
|
75
|
+
"""A Live Activity the app started, on the Lock Screen or in the Dynamic Island."""
|
|
76
|
+
if surface.params.get("where", "dynamic_island") == "lock_screen":
|
|
77
|
+
stage.lock()
|
|
78
|
+
try:
|
|
79
|
+
return stage.settle(surface.expect)
|
|
80
|
+
finally:
|
|
81
|
+
stage.unlock()
|
|
82
|
+
stage.home()
|
|
83
|
+
if surface.params.get("expanded"):
|
|
84
|
+
stage.hold(stage.read().size[0] / 2, 20, "the Dynamic Island")
|
|
85
|
+
return stage.settle(surface.expect)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def control_center(stage, surface, ctx):
|
|
89
|
+
width, height = stage.read().size
|
|
90
|
+
stage.drag((width - CONTROL_CENTER_X, 3), (width - CONTROL_CENTER_X, height * .6), "down into Control Center")
|
|
91
|
+
return stage.settle(surface.expect)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def app_clip(stage, surface, ctx):
|
|
95
|
+
"""The App Clip, launched the way Xcode's scheme does: with its invocation URL in ``_XCAppClipURL``."""
|
|
96
|
+
params = surface.params
|
|
97
|
+
if "path" in params:
|
|
98
|
+
ctx.install(params["path"])
|
|
99
|
+
ctx.launch(params["bundle"], {"_XCAppClipURL": params["url"]})
|
|
100
|
+
stage.activate(params["bundle"])
|
|
101
|
+
return stage.settle(surface.expect)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def spotlight(stage, surface, ctx):
|
|
105
|
+
stage.home()
|
|
106
|
+
width, height = stage.read().size
|
|
107
|
+
stage.drag((width / 2, height * .35), (width / 2, height * .75), "down on the Home Screen")
|
|
108
|
+
stage.tap_node(stage.wait_for(None, "the Spotlight search field", selector=SEARCH_FIELD), "the search field")
|
|
109
|
+
stage.type(surface.params["query"])
|
|
110
|
+
return stage.settle(surface.expect)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def share_sheet(stage, surface, ctx):
|
|
114
|
+
"""The app's own control opens the share sheet; ``expect`` reads the sheet."""
|
|
115
|
+
stage.tap_node(stage.wait_for(None, f"the control that opens the share sheet ({surface.open.render()})",
|
|
116
|
+
selector=surface.open), "the share control")
|
|
117
|
+
return stage.settle(surface.expect)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
DRIVERS = {"intent": intent, "widget": widget, "live_activity": live_activity, "control_center": control_center,
|
|
121
|
+
"app_clip": app_clip, "spotlight": spotlight, "share_sheet": share_sheet}
|