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,638 @@
|
|
|
1
|
+
"""The tools that act on the screen: tap, type, swipe, gestures, alerts, links, launch and wait_for."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import traceback
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
from ..verify.assertions import normalize
|
|
10
|
+
from .action_journal import record_action
|
|
11
|
+
from .codes import SECURE_MASK
|
|
12
|
+
from .protocol import ToolResult
|
|
13
|
+
from .session import Outline, collapse, node_name, node_summary, node_target, role_name
|
|
14
|
+
from .support import (
|
|
15
|
+
Phases,
|
|
16
|
+
ToolError,
|
|
17
|
+
cap_text,
|
|
18
|
+
changed_words,
|
|
19
|
+
describe_assertion,
|
|
20
|
+
describe_selector,
|
|
21
|
+
element_at,
|
|
22
|
+
hit_point,
|
|
23
|
+
is_check_error,
|
|
24
|
+
jsonable,
|
|
25
|
+
navbar_hint,
|
|
26
|
+
quote,
|
|
27
|
+
screen_changed,
|
|
28
|
+
screen_fingerprint,
|
|
29
|
+
sentence,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Swipes with `until` stop before this, so the call stays inside CALL_BUDGET.
|
|
33
|
+
SWIPE_SECONDS = 30.0
|
|
34
|
+
|
|
35
|
+
LOGGED_TEXT = 40
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ActionTools:
|
|
39
|
+
def tool_launch_app(self, args, call):
|
|
40
|
+
bundle = args.get("bundle_id")
|
|
41
|
+
if bool(bundle) == bool(args.get("restart")):
|
|
42
|
+
raise ToolError("Pass bundle_id to open an app, or restart to quit the check's app and launch it again, "
|
|
43
|
+
"not both.")
|
|
44
|
+
if args.get("restart"):
|
|
45
|
+
return self._relaunch(args, call)
|
|
46
|
+
|
|
47
|
+
def work(session, phases):
|
|
48
|
+
run, driver = session.run, session.run.driver
|
|
49
|
+
snapshot = driver.observe(timeout=10)
|
|
50
|
+
self._permit_app(session, bundle)
|
|
51
|
+
self._check_action_cancelled(session, call)
|
|
52
|
+
if session.mode == "direct":
|
|
53
|
+
run.launch_app(bundle)
|
|
54
|
+
else:
|
|
55
|
+
driver.call("POST", "/wda/apps/activate", {"bundleId": bundle}, timeout=30)
|
|
56
|
+
after = self._settle(driver, snapshot, timeout=4, wait_seconds=1.0)
|
|
57
|
+
changed = screen_changed(snapshot, after)
|
|
58
|
+
text = f"Opened {bundle}"
|
|
59
|
+
return {"op": "LAUNCH", "text": text, "changed": changed, "line": text + changed_words(changed),
|
|
60
|
+
"after": screen_fingerprint(after)}
|
|
61
|
+
return self._act(args, call, work, "LAUNCH")
|
|
62
|
+
|
|
63
|
+
def tool_home(self, args, call):
|
|
64
|
+
def work(session, phases):
|
|
65
|
+
driver = session.run.driver
|
|
66
|
+
snapshot = driver.observe(timeout=10)
|
|
67
|
+
driver.call("POST", "/wda/pressButton", {"name": "home"}, timeout=10)
|
|
68
|
+
after = self._settle(driver, snapshot)
|
|
69
|
+
changed = screen_changed(snapshot, after)
|
|
70
|
+
return {"op": "HOME", "text": "Pressed Home", "changed": changed,
|
|
71
|
+
"line": "Pressed Home" + changed_words(changed), "after": screen_fingerprint(after)}
|
|
72
|
+
return self._act(args, call, work, "HOME", check=False)
|
|
73
|
+
|
|
74
|
+
def _act(self, args, call, work, operation, check=True):
|
|
75
|
+
session = self._target_session(args)
|
|
76
|
+
run = session.run
|
|
77
|
+
started = time.monotonic()
|
|
78
|
+
with self._locked(session), record_action(run.run_dir, operation):
|
|
79
|
+
phases = Phases()
|
|
80
|
+
if check:
|
|
81
|
+
self._front(session)
|
|
82
|
+
self._check_action_cancelled(session, call)
|
|
83
|
+
# The code fields blacked out when the step began: the after frame is taken while the outline is read,
|
|
84
|
+
# so it can still show a code the outline (read a moment later) no longer has.
|
|
85
|
+
rects_before = list(session.secret_rects)
|
|
86
|
+
try:
|
|
87
|
+
done = work(session, phases)
|
|
88
|
+
except ToolError:
|
|
89
|
+
raise
|
|
90
|
+
except Exception as error:
|
|
91
|
+
self.log(f"Run {session.run_id}: the action failed after {phases.spent}: "
|
|
92
|
+
+ "".join(traceback.format_exception(type(error), error, error.__traceback__, limit=6)))
|
|
93
|
+
raise ToolError(self._action_error(error)) from None
|
|
94
|
+
phases("act")
|
|
95
|
+
step, shot = None, {}
|
|
96
|
+
shooter = None
|
|
97
|
+
try:
|
|
98
|
+
if check:
|
|
99
|
+
# A link or tap may have switched apps: check before any frame of it is captured.
|
|
100
|
+
try:
|
|
101
|
+
self._front(session)
|
|
102
|
+
except ToolError as error:
|
|
103
|
+
raise ToolError(done["text"] + ". " + str(error)) from None
|
|
104
|
+
if done.get("record", True):
|
|
105
|
+
release_frame = (run.reserve_step_frame() if session.mode != "direct" else lambda: None)
|
|
106
|
+
if release_frame is None:
|
|
107
|
+
raise ToolError("This run is finishing, so it cannot capture the action's screen.")
|
|
108
|
+
|
|
109
|
+
def take():
|
|
110
|
+
try:
|
|
111
|
+
shot["frame"] = run.frame("step")
|
|
112
|
+
except Exception as error:
|
|
113
|
+
self.log(f"Run {session.run_id}: the step frame failed ({type(error).__name__}).")
|
|
114
|
+
finally:
|
|
115
|
+
release_frame()
|
|
116
|
+
shooter = threading.Thread(target=take, daemon=True, name=f"mobster-mcp-frame-{session.run_id}")
|
|
117
|
+
try:
|
|
118
|
+
shooter.start()
|
|
119
|
+
except BaseException:
|
|
120
|
+
release_frame()
|
|
121
|
+
raise
|
|
122
|
+
outline, alert = self._outline(session, phases, before=done.get("after"))
|
|
123
|
+
except ToolError as error:
|
|
124
|
+
# The action happened; what comes back says why the screen isn't shown (an app that isn't allowed
|
|
125
|
+
# came up), in the tool's own words.
|
|
126
|
+
raise ToolError(done["text"] + ". " + str(error)) from None
|
|
127
|
+
except Exception as error:
|
|
128
|
+
raise ToolError(done["text"] + ". " + self._action_error(error)) from None
|
|
129
|
+
finally:
|
|
130
|
+
if shooter is not None:
|
|
131
|
+
shooter.join(20)
|
|
132
|
+
phases("frame")
|
|
133
|
+
frame = shot.get("frame")
|
|
134
|
+
rects = rects_before + [rect for rect in session.secret_rects if rect not in rects_before]
|
|
135
|
+
if frame and rects:
|
|
136
|
+
if not self.codes.redact_file(session, str(frame), rects) or not os.path.isfile(str(frame)):
|
|
137
|
+
frame = None # it couldn't be redacted, so it was deleted
|
|
138
|
+
if shooter is not None:
|
|
139
|
+
step = run.record_step(op=done["op"], text=done["text"], target=done.get("target"),
|
|
140
|
+
typed=done.get("typed"), changed=done.get("changed"), frame=frame)
|
|
141
|
+
seconds = round(time.monotonic() - started, 2)
|
|
142
|
+
timing = dict(phases.spent)
|
|
143
|
+
self.log(f"Run {session.run_id}: {done.get('log', done['text'])} ({seconds:.2f} s: "
|
|
144
|
+
+ ", ".join(f"{name} {value:.2f}" for name, value in timing.items()) + ")")
|
|
145
|
+
picture = self.imager(path=str(frame)) if args.get("image") and frame else None
|
|
146
|
+
structured = {"ok": bool(done.get("ok", True)), "changed": done.get("changed"),
|
|
147
|
+
"step": jsonable(step) if step is not None else None, "screen": outline.structured(alert),
|
|
148
|
+
"seconds": seconds}
|
|
149
|
+
structured.update(done.get("extra") or {})
|
|
150
|
+
return ToolResult(cap_text(done["line"] + "\n" + outline.render(alert)), structured,
|
|
151
|
+
[picture] if picture else [])
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _action_error(error):
|
|
155
|
+
name = type(error).__name__
|
|
156
|
+
if is_check_error(error):
|
|
157
|
+
return sentence(error)
|
|
158
|
+
if hasattr(error, "klass") or hasattr(error, "kind"):
|
|
159
|
+
return f"The simulator couldn't do that: {error}. Call screen to see the current screen."
|
|
160
|
+
if name == "DriverRejection":
|
|
161
|
+
if getattr(error, "code", None) in getattr(error, "PRE_DISPATCH", ()):
|
|
162
|
+
return "The screen changed before the action. Call screen for current refs."
|
|
163
|
+
return "The action may have reached the device. Call screen to inspect it before another action."
|
|
164
|
+
if name in ("TransportError", "TimeoutError", "URLError", "ConnectionError"):
|
|
165
|
+
return "WebDriverAgent didn't answer in time. Call screen to inspect the device, or stop the run."
|
|
166
|
+
return f"The action failed ({name}: {str(error)[:160]}). Call screen to see the current screen."
|
|
167
|
+
|
|
168
|
+
def _resolve(self, session, tree, args, required=True):
|
|
169
|
+
ref, target = args.get("ref"), args.get("target")
|
|
170
|
+
if ref and target:
|
|
171
|
+
raise ToolError("Pass ref or target, not both.")
|
|
172
|
+
if not ref and not target:
|
|
173
|
+
if required:
|
|
174
|
+
raise ToolError("Pass ref (from the latest outline) or target (a selector) to say which element.")
|
|
175
|
+
return None
|
|
176
|
+
shown = {node.path: node for node in tree.shown()}
|
|
177
|
+
if ref:
|
|
178
|
+
# Refs are numbered across the run, so a ref from an earlier outline never names a node of this one.
|
|
179
|
+
outline = session.outline
|
|
180
|
+
if outline is None or ref not in outline.refs:
|
|
181
|
+
if ref in session.book.issued:
|
|
182
|
+
raise ToolError(f"{ref} is from an earlier screen. Call screen for current refs.")
|
|
183
|
+
raise ToolError(f"{ref} isn't on the latest outline. Call screen for current refs.")
|
|
184
|
+
if tree.fingerprint() != outline.fingerprint or outline.refs[ref] not in shown:
|
|
185
|
+
raise ToolError(f"{ref} is from an earlier screen. Call screen for current refs.")
|
|
186
|
+
return shown[outline.refs[ref]]
|
|
187
|
+
try:
|
|
188
|
+
selector = self.api.parse_selector(target)
|
|
189
|
+
except Exception as error:
|
|
190
|
+
if is_check_error(error):
|
|
191
|
+
raise ToolError(sentence(error)) from None
|
|
192
|
+
raise
|
|
193
|
+
matches = collapse([node for node in tree.select(selector) if node.path in shown])
|
|
194
|
+
if len(matches) == 1:
|
|
195
|
+
return matches[0]
|
|
196
|
+
same_tree = session.outline is not None and tree is session.outline.tree
|
|
197
|
+
session.outline = Outline(tree, session.book)
|
|
198
|
+
if not same_tree:
|
|
199
|
+
session.anchor = None
|
|
200
|
+
outline = session.outline
|
|
201
|
+
described = describe_selector(target)
|
|
202
|
+
if matches:
|
|
203
|
+
lines, named = outline.match_lines(matches)
|
|
204
|
+
if not lines:
|
|
205
|
+
raise ToolError(f"{len(matches)} elements match {described}, and none has a line in the outline. "
|
|
206
|
+
"Add id, label or role to the target to pick one.")
|
|
207
|
+
more = len(matches) - named
|
|
208
|
+
raise ToolError(f"{len(matches)} elements match {described}: " + "; ".join(lines)
|
|
209
|
+
+ (f"; and {more} more" if more else "")
|
|
210
|
+
+ ". Pass one of these refs, or add id or role to the target.")
|
|
211
|
+
lines = outline.candidates(near=[target.get("label"), target.get("id"), target.get("value")])
|
|
212
|
+
if not lines and target.get("role"):
|
|
213
|
+
lines = outline.candidates([node for node in outline.nodes
|
|
214
|
+
if role_name(node) == str(target["role"]).lower()])
|
|
215
|
+
raise ToolError(f"No shown element matches {described}."
|
|
216
|
+
+ (" Closest: " + "; ".join(lines) + "." if lines else "")
|
|
217
|
+
+ " Call screen to see what is shown.")
|
|
218
|
+
|
|
219
|
+
def tool_tap(self, args, call):
|
|
220
|
+
def work(session, phases):
|
|
221
|
+
driver = session.run.driver
|
|
222
|
+
tree, snapshot = self._current(session, phases)
|
|
223
|
+
node = self._resolve(session, tree, args)
|
|
224
|
+
element = element_at(snapshot, node.path)
|
|
225
|
+
if self._permit(session, "TAP", node, element, snapshot, call):
|
|
226
|
+
element, snapshot = self._rebind(session, element, snapshot, call)
|
|
227
|
+
if element is not None:
|
|
228
|
+
driver.execute("TAP", element, snapshot, timeout=10)
|
|
229
|
+
else:
|
|
230
|
+
x, y = hit_point(node, tree)
|
|
231
|
+
driver.tap_point(x, y, snapshot, timeout=10)
|
|
232
|
+
phases("dispatch")
|
|
233
|
+
after = self._settle(driver, snapshot)
|
|
234
|
+
phases("settle")
|
|
235
|
+
changed = screen_changed(snapshot, after)
|
|
236
|
+
text = f"Tapped {quote(node_name(node))}"
|
|
237
|
+
return {"op": "TAP", "text": text, "target": node_target(node), "changed": changed,
|
|
238
|
+
"line": text + changed_words(changed), "after": screen_fingerprint(after),
|
|
239
|
+
"extra": {"target": node_summary(node)}}
|
|
240
|
+
return self._act(args, call, work, "TAP")
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _check_gesture_cancelled(session, call):
|
|
244
|
+
if session.cancelled.is_set() or call.cancelled.is_set() or call.stop.is_set():
|
|
245
|
+
raise ToolError("This gesture was cancelled before dispatch. Nothing was sent.")
|
|
246
|
+
|
|
247
|
+
def _gesture_screen(self, session, grounded, gesture, call):
|
|
248
|
+
from ..wda.point_gestures import same_point_image
|
|
249
|
+
stale = "The gesture's screen changed. Call screen with image=true and choose its points again."
|
|
250
|
+
driver = session.run.driver
|
|
251
|
+
|
|
252
|
+
def read():
|
|
253
|
+
self._check_gesture_cancelled(session, call)
|
|
254
|
+
snapshot = driver.observe(timeout=5)
|
|
255
|
+
if snapshot.bundle_id != grounded["bundle"] or screen_fingerprint(snapshot) != grounded["fingerprint"]:
|
|
256
|
+
raise ToolError(stale)
|
|
257
|
+
return snapshot
|
|
258
|
+
|
|
259
|
+
read()
|
|
260
|
+
self._check_gesture_cancelled(session, call) # a cancel during the first read stops before the capture
|
|
261
|
+
picture = self._screenshot(session)
|
|
262
|
+
after = read()
|
|
263
|
+
self._check_gesture_cancelled(session, call)
|
|
264
|
+
parts = [{"image_url": {"url": "data:image/jpeg;base64," + value}}
|
|
265
|
+
for value in (grounded["picture"], picture or "")]
|
|
266
|
+
if not picture or not all(same_point_image(*parts, x * 100, y * 100) for x, y in gesture.points):
|
|
267
|
+
raise ToolError(stale)
|
|
268
|
+
return after
|
|
269
|
+
|
|
270
|
+
def tool_drag_point(self, args, call):
|
|
271
|
+
return self._point_gesture(args, call, "drag")
|
|
272
|
+
|
|
273
|
+
def tool_hold_point(self, args, call):
|
|
274
|
+
return self._point_gesture(args, call, "hold")
|
|
275
|
+
|
|
276
|
+
def tool_flick_point(self, args, call):
|
|
277
|
+
return self._point_gesture(args, call, "flick")
|
|
278
|
+
|
|
279
|
+
def _point_gesture(self, args, call, kind):
|
|
280
|
+
# Sent once: a failure after dispatch is never repeated.
|
|
281
|
+
from ..contract_asks import point_spot
|
|
282
|
+
from ..wda.point_gestures import GESTURE_SCHEMAS, check_gesture_path, validate_gesture_request
|
|
283
|
+
schema = GESTURE_SCHEMAS[kind]
|
|
284
|
+
try:
|
|
285
|
+
gesture = validate_gesture_request(kind, {key: args[key] for key in schema["properties"] if key in args})
|
|
286
|
+
except ValueError as error:
|
|
287
|
+
raise ToolError(str(error)) from None
|
|
288
|
+
operation = kind.upper() + "_POINT"
|
|
289
|
+
|
|
290
|
+
def work(session, phases):
|
|
291
|
+
if session.mode != "direct":
|
|
292
|
+
raise ToolError(f"{kind}_point needs a direct device session. Finish this app check, then pass device.")
|
|
293
|
+
grounded, session.point_screen = session.point_screen, None
|
|
294
|
+
if grounded is None:
|
|
295
|
+
raise ToolError(f"Call screen with image=true before each {kind}, then name its visible points.")
|
|
296
|
+
snapshot = self._gesture_screen(session, grounded, gesture, call)
|
|
297
|
+
check_gesture_path(snapshot, gesture)
|
|
298
|
+
endpoints = [(gesture.start, gesture.start_label)]
|
|
299
|
+
if gesture.end is not None:
|
|
300
|
+
endpoints.append((gesture.end, gesture.end_label))
|
|
301
|
+
node = None
|
|
302
|
+
for (x, y), label in endpoints:
|
|
303
|
+
_, element = point_spot(snapshot, x, y)
|
|
304
|
+
node = SimpleNamespace(label=label, role="Point", placeholder="", identifier="", value="")
|
|
305
|
+
self._permit(session, "TAP_XY", node, element, snapshot, call)
|
|
306
|
+
node.label = gesture.label
|
|
307
|
+
self._permit(session, operation, node, None, snapshot, call, forced=True)
|
|
308
|
+
self._front(session)
|
|
309
|
+
snapshot = self._gesture_screen(session, grounded, gesture, call)
|
|
310
|
+
check_gesture_path(snapshot, gesture)
|
|
311
|
+
self._check_gesture_cancelled(session, call)
|
|
312
|
+
positions = (*gesture.start, *(gesture.end or ()))
|
|
313
|
+
options = {"duration": gesture.duration, "timeout": 15 if kind == "hold" else 10}
|
|
314
|
+
if kind == "drag":
|
|
315
|
+
options["hold_seconds"] = gesture.hold_seconds
|
|
316
|
+
send = getattr(session.run.driver, kind + "_point", None)
|
|
317
|
+
if not callable(send):
|
|
318
|
+
raise ToolError(f"This device can't {kind} by point.")
|
|
319
|
+
try:
|
|
320
|
+
send(*positions, snapshot, **options)
|
|
321
|
+
except Exception as error:
|
|
322
|
+
if type(error).__name__ == "DriverRejection" and getattr(error, "code", None) in error.PRE_DISPATCH:
|
|
323
|
+
raise
|
|
324
|
+
raise ToolError(f"The {kind} may have reached the device. It was not repeated. "
|
|
325
|
+
"Call screen to inspect the result before another gesture.") from None
|
|
326
|
+
phases("dispatch")
|
|
327
|
+
verb = {"drag": "Dragged", "hold": "Held", "flick": "Flicked"}[kind]
|
|
328
|
+
text = f"{verb} {quote(gesture.start_label)}"
|
|
329
|
+
if gesture.end is not None:
|
|
330
|
+
text += f" to {quote(gesture.end_label)}"
|
|
331
|
+
return {"op": operation, "text": text, "line": text + ".", "changed": None}
|
|
332
|
+
return self._act(args, call, work, operation)
|
|
333
|
+
|
|
334
|
+
def tool_type_text(self, args, call):
|
|
335
|
+
text, append, submit = args["text"], bool(args.get("append")), bool(args.get("submit"))
|
|
336
|
+
|
|
337
|
+
def work(session, phases):
|
|
338
|
+
driver = session.run.driver
|
|
339
|
+
tree, snapshot = self._current(session, phases)
|
|
340
|
+
node = self._resolve(session, tree, args)
|
|
341
|
+
role = (node.role or "").removeprefix("XCUIElementType")
|
|
342
|
+
element = element_at(snapshot, node.path)
|
|
343
|
+
if role == "SecureTextField":
|
|
344
|
+
from ..state import validate_input_text
|
|
345
|
+
validate_input_text(text)
|
|
346
|
+
# Typing a one-time code asks in the Mac app, with or without Return (permissions.rules.code_entry);
|
|
347
|
+
# Return that commits asks too.
|
|
348
|
+
if self._permit(session, "TYPE_SUBMIT" if submit else "TYPE", node, element, snapshot, call, text=text):
|
|
349
|
+
element, snapshot = self._rebind(session, element, snapshot, call)
|
|
350
|
+
receipt = None
|
|
351
|
+
if role == "SecureTextField":
|
|
352
|
+
# The fast read leaves secure fields out, so there is no Element: tap, then type through WDA.
|
|
353
|
+
x, y = hit_point(node, tree)
|
|
354
|
+
driver.tap_point(x, y, snapshot, timeout=10)
|
|
355
|
+
self._await_focus(driver)
|
|
356
|
+
self._check_action_cancelled(session, call)
|
|
357
|
+
try:
|
|
358
|
+
driver.call("POST", "/wda/keys", {"value": [text]}, timeout=30)
|
|
359
|
+
if submit:
|
|
360
|
+
self._check_action_cancelled(session, call)
|
|
361
|
+
driver.call("POST", "/wda/keys", {"value": ["\n"]}, timeout=5)
|
|
362
|
+
except Exception as error:
|
|
363
|
+
# A driver's error can echo the password it tried to send.
|
|
364
|
+
raise ToolError(f"Mobster couldn't finish typing into the secure field ({type(error).__name__}). "
|
|
365
|
+
"Call screen before another action.") from None
|
|
366
|
+
typed, shown = SECURE_MASK, SECURE_MASK
|
|
367
|
+
elif element is not None and element.editable:
|
|
368
|
+
receipt = driver.write_text(element, snapshot, text, append=append, submit=submit,
|
|
369
|
+
timeout=min(36.0, 16 + len(text) / 20))
|
|
370
|
+
typed, shown = text, quote(text)
|
|
371
|
+
else:
|
|
372
|
+
raise ToolError(f"{args.get('ref') or 'The target'} is a {role_name(node)}, not a text field. "
|
|
373
|
+
"Pass a ref or target for a field.")
|
|
374
|
+
phases("type")
|
|
375
|
+
after = None
|
|
376
|
+
if submit:
|
|
377
|
+
try:
|
|
378
|
+
after = driver.observe_ready(timeout=5)
|
|
379
|
+
except Exception:
|
|
380
|
+
pass
|
|
381
|
+
phases("settle")
|
|
382
|
+
ok = receipt is None or receipt.get("value") is None or bool(receipt.get("matches"))
|
|
383
|
+
into = quote(node_name(node))
|
|
384
|
+
line = f"Typed {shown} into {into}" + (" and pressed Return" if submit else "")
|
|
385
|
+
if not ok:
|
|
386
|
+
line += f"; the field reads {quote(receipt.get('value'), 80)}, not the text typed"
|
|
387
|
+
logged = SECURE_MASK if typed == SECURE_MASK else quote(text, LOGGED_TEXT)
|
|
388
|
+
return {"op": "TYPE_SUBMIT" if submit else "TYPE", "text": f"Typed {shown} into {into}",
|
|
389
|
+
"target": node_target(node), "typed": typed, "changed": True, "ok": ok, "line": line + ".",
|
|
390
|
+
"after": screen_fingerprint(after),
|
|
391
|
+
"log": f"Typed {logged} into {into}",
|
|
392
|
+
"extra": {"field_reads": None if not receipt or receipt.get("value") is None
|
|
393
|
+
else str(receipt["value"])[:200]}}
|
|
394
|
+
return self._act(args, call, work, "TYPE_SUBMIT" if submit else "TYPE")
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def _await_focus(driver, seconds=2.0):
|
|
398
|
+
end = time.monotonic() + seconds
|
|
399
|
+
while time.monotonic() < end:
|
|
400
|
+
try:
|
|
401
|
+
if getattr(driver.observe(timeout=5), "keyboard", "") in ("visible", "parked"):
|
|
402
|
+
return
|
|
403
|
+
except Exception:
|
|
404
|
+
break
|
|
405
|
+
time.sleep(.2)
|
|
406
|
+
raise ToolError("Mobster couldn't confirm the secure field's keyboard, so it didn't type. Call screen "
|
|
407
|
+
"before another action.")
|
|
408
|
+
|
|
409
|
+
def tool_swipe(self, args, call):
|
|
410
|
+
direction = args["direction"]
|
|
411
|
+
until = self._parse_assertion(args["until"]) if args.get("until") is not None else None
|
|
412
|
+
limit = int(args.get("max_swipes") or (8 if until is not None else 1))
|
|
413
|
+
|
|
414
|
+
def holds(tree):
|
|
415
|
+
return bool(self.api.evaluate_on([until], tree)[0].ok)
|
|
416
|
+
|
|
417
|
+
def work(session, phases):
|
|
418
|
+
driver = session.run.driver
|
|
419
|
+
tree, _snapshot = self._current(session, phases)
|
|
420
|
+
node = self._resolve(session, tree, args, required=False)
|
|
421
|
+
# A row's swipe archives or deletes in some apps (Gmail, Outlook): the same rules as a tap, for every
|
|
422
|
+
# swipe. After the first, the next row may have slid under the same spot, and nobody approved that one.
|
|
423
|
+
row_swipe = node is not None and direction in ("left", "right")
|
|
424
|
+
if row_swipe:
|
|
425
|
+
if self._permit(session, "SWIPE_" + direction.upper(), node, element_at(_snapshot, node.path),
|
|
426
|
+
_snapshot, call):
|
|
427
|
+
self._rebind(session, None, _snapshot, call)
|
|
428
|
+
held = holds(tree) if until is not None else None
|
|
429
|
+
swipes, moved, stuck, after, refused = 0, False, False, None, None
|
|
430
|
+
aim = node
|
|
431
|
+
deadline = time.monotonic() + SWIPE_SECONDS
|
|
432
|
+
while not held and swipes < limit and time.monotonic() < deadline:
|
|
433
|
+
snapshot = driver.observe(timeout=10)
|
|
434
|
+
self._check_action_cancelled(session, call)
|
|
435
|
+
if swipes and row_swipe and self._real_phone(session) is not None:
|
|
436
|
+
aim = {n.path: n for n in self.codes.read_tree(session).shown()}.get(aim.path)
|
|
437
|
+
if aim is None:
|
|
438
|
+
break # the element is gone: nothing approved is left to swipe
|
|
439
|
+
try:
|
|
440
|
+
if self._permit(session, "SWIPE_" + direction.upper(), aim, element_at(snapshot, aim.path),
|
|
441
|
+
snapshot, call):
|
|
442
|
+
self._rebind(session, None, snapshot, call)
|
|
443
|
+
except ToolError as error:
|
|
444
|
+
refused = str(error)
|
|
445
|
+
break
|
|
446
|
+
if aim is None:
|
|
447
|
+
driver.execute("SWIPE_" + direction.upper(), None, snapshot, timeout=10)
|
|
448
|
+
else:
|
|
449
|
+
self._swipe_in(driver, aim, tree, direction)
|
|
450
|
+
swipes += 1
|
|
451
|
+
after = self._settle(driver, snapshot)
|
|
452
|
+
if screen_changed(snapshot, after) is False:
|
|
453
|
+
stuck = True
|
|
454
|
+
break
|
|
455
|
+
moved = True
|
|
456
|
+
if until is not None:
|
|
457
|
+
tree = self.codes.read_tree(session)
|
|
458
|
+
held = holds(tree)
|
|
459
|
+
where = f" on {quote(node_name(node))}" if node is not None else ""
|
|
460
|
+
count = "once" if swipes == 1 else f"{swipes} times"
|
|
461
|
+
text = f"Swiped {direction}{where} {count}" if swipes else f"No swipe{where}"
|
|
462
|
+
if until is not None:
|
|
463
|
+
line = (f"{describe_assertion(args['until'])} already held; no swipe needed." if held and not swipes
|
|
464
|
+
else f"{text}; the expectation holds now." if held
|
|
465
|
+
else f"{text}; the expectation still doesn't hold"
|
|
466
|
+
+ (" (the content stopped moving)." if stuck else "."))
|
|
467
|
+
else:
|
|
468
|
+
line = text + ("; the screen didn't move." if stuck and not moved else ".")
|
|
469
|
+
if refused:
|
|
470
|
+
line += f" It stopped before the next swipe: {refused}"
|
|
471
|
+
return {"op": "SWIPE_" + direction.upper(), "text": text,
|
|
472
|
+
"target": node_target(node) if node is not None else None, "changed": moved,
|
|
473
|
+
"ok": held if until is not None else moved, "line": line, "record": swipes > 0,
|
|
474
|
+
"after": screen_fingerprint(after),
|
|
475
|
+
"extra": {"swipes": swipes, "held": held}}
|
|
476
|
+
return self._act(args, call, work, "SWIPE_" + direction.upper())
|
|
477
|
+
|
|
478
|
+
@staticmethod
|
|
479
|
+
def _swipe_in(driver, node, tree, direction):
|
|
480
|
+
width, height = tree.size
|
|
481
|
+
x, y, w, h = node.rect
|
|
482
|
+
cx, cy = x + w / 2, y + h / 2
|
|
483
|
+
dx, dy = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0)}[direction]
|
|
484
|
+
x1, y1 = cx - dx * w * .3, cy - dy * h * .3
|
|
485
|
+
x2, y2 = cx + dx * w * .3, cy + dy * h * .3
|
|
486
|
+
clamp = lambda value, top: round(min(top - 1, max(1, value))) # noqa: E731
|
|
487
|
+
driver.call("POST", "/actions", {"actions": [{
|
|
488
|
+
"type": "pointer", "id": "finger", "parameters": {"pointerType": "touch"},
|
|
489
|
+
"actions": [{"type": "pointerMove", "duration": 0, "x": clamp(x1, width), "y": clamp(y1, height)},
|
|
490
|
+
{"type": "pointerDown"},
|
|
491
|
+
{"type": "pointerMove", "duration": 250, "x": clamp(x2, width), "y": clamp(y2, height)},
|
|
492
|
+
{"type": "pointerUp"}]}]}, timeout=10)
|
|
493
|
+
|
|
494
|
+
@staticmethod
|
|
495
|
+
def _alert_button(requested, buttons):
|
|
496
|
+
# iOS 26.4 names the notification prompt's button "Don’t Allow", with U+2019.
|
|
497
|
+
wanted = normalize(requested)
|
|
498
|
+
exact = [b for b in buttons if normalize(b) == wanted]
|
|
499
|
+
if exact:
|
|
500
|
+
return exact[0]
|
|
501
|
+
loose = [b for b in buttons if normalize(b).casefold() == wanted.casefold()]
|
|
502
|
+
return loose[0] if len(loose) == 1 else None
|
|
503
|
+
|
|
504
|
+
def tool_alert(self, args, call):
|
|
505
|
+
action, requested = args["action"], args.get("button")
|
|
506
|
+
|
|
507
|
+
def work(session, phases):
|
|
508
|
+
driver = session.run.driver
|
|
509
|
+
alert = self._alert(driver)
|
|
510
|
+
if alert is None:
|
|
511
|
+
raise ToolError("No alert is showing. Call screen to see the current screen.")
|
|
512
|
+
button = requested
|
|
513
|
+
if requested and alert["buttons"]:
|
|
514
|
+
button = self._alert_button(requested, alert["buttons"])
|
|
515
|
+
if button is None:
|
|
516
|
+
raise ToolError(f"The alert has no button {quote(requested)}. Its buttons: "
|
|
517
|
+
+ ", ".join(quote(b) for b in alert["buttons"]) + ".")
|
|
518
|
+
self._refuse_grant(session, alert, action, button)
|
|
519
|
+
self._permit_alert(session, alert, action, button, call)
|
|
520
|
+
snapshot = driver.observe(timeout=10)
|
|
521
|
+
self._check_action_cancelled(session, call)
|
|
522
|
+
# WebDriverAgent finds the button by its real name, curly apostrophe and all.
|
|
523
|
+
driver.call("POST", f"/alert/{action}", {"name": button} if button else {}, timeout=10)
|
|
524
|
+
after = self._settle(driver, snapshot)
|
|
525
|
+
changed = screen_changed(snapshot, after)
|
|
526
|
+
verb = "Accepted" if action == "accept" else "Dismissed"
|
|
527
|
+
text = f"{verb} the alert {quote(alert['text'], 80)}" + (f" with {quote(button)}" if button else "")
|
|
528
|
+
return {"op": "ALERT", "text": text, "changed": changed, "line": text + ".",
|
|
529
|
+
"after": screen_fingerprint(after),
|
|
530
|
+
"target": {"id": "", "label": button or alert["text"][:120], "role": "Alert"}}
|
|
531
|
+
return self._act(args, call, work, "ALERT")
|
|
532
|
+
|
|
533
|
+
def tool_open_url(self, args, call):
|
|
534
|
+
from ..deeplinks import LinkError, decide
|
|
535
|
+
from .direct import url_app
|
|
536
|
+
url = args.get("url")
|
|
537
|
+
if not url:
|
|
538
|
+
return self._deep_links(args)
|
|
539
|
+
try:
|
|
540
|
+
# The one rule for links (deeplinks.decide). An outside agent never uses "Don't ask again": a link that
|
|
541
|
+
# asks asks every time, on the card in the Mac app.
|
|
542
|
+
decision = decide(url, None)
|
|
543
|
+
except LinkError as error:
|
|
544
|
+
raise ToolError(str(error)) from None
|
|
545
|
+
|
|
546
|
+
def work(session, phases):
|
|
547
|
+
run, driver = session.run, session.run.driver
|
|
548
|
+
record = self._real_phone(session) if self._on_phone(session) else None
|
|
549
|
+
if record is not None:
|
|
550
|
+
target = url_app(url)
|
|
551
|
+
if target:
|
|
552
|
+
self._permit_app(session, target)
|
|
553
|
+
if decision.action == "ask":
|
|
554
|
+
from ..permissions import outside
|
|
555
|
+
try:
|
|
556
|
+
outside.check_link(url, decision.title, decision.app, decision.app_name,
|
|
557
|
+
device=record.get("id"), client_name=self.client_name())
|
|
558
|
+
except outside.Refused as refused:
|
|
559
|
+
raise ToolError(str(refused).replace("Nothing was done.", "Nothing was opened.")) from None
|
|
560
|
+
self._check_action_cancelled(session, call)
|
|
561
|
+
if target:
|
|
562
|
+
self._permit_app(session, target)
|
|
563
|
+
elif decision.action == "ask" and self._on_phone(session):
|
|
564
|
+
raise ToolError(f"Mobster asks before opening {quote(url, 80)}, and nobody here can answer, so it "
|
|
565
|
+
"didn't open it on the iPhone. Ask the user to open it.")
|
|
566
|
+
snapshot = driver.observe(timeout=10)
|
|
567
|
+
self._check_action_cancelled(session, call)
|
|
568
|
+
opened = run.open_url(url)
|
|
569
|
+
after = self._settle(driver, snapshot, timeout=4, wait_seconds=1.5)
|
|
570
|
+
changed = screen_changed(snapshot, after)
|
|
571
|
+
text = f"Opened {quote(url, 120)}"
|
|
572
|
+
app_link = opened.get("url") if isinstance(opened, dict) else None
|
|
573
|
+
if isinstance(app_link, str) and app_link != url:
|
|
574
|
+
text += (f" as {quote(app_link, 120)}, the app link for that screen (the web address opens the "
|
|
575
|
+
"home feed)")
|
|
576
|
+
return {"op": "OPEN_URL", "text": text, "changed": changed, "line": text + changed_words(changed),
|
|
577
|
+
"after": screen_fingerprint(after)}
|
|
578
|
+
return self._act(args, call, work, "OPEN_URL")
|
|
579
|
+
|
|
580
|
+
def tool_expo_preview(self, args, call):
|
|
581
|
+
from . import expo_tools
|
|
582
|
+
return expo_tools.preview(self, args, call)
|
|
583
|
+
|
|
584
|
+
def tool_expo_reload(self, args, call):
|
|
585
|
+
from . import expo_tools
|
|
586
|
+
return expo_tools.reload(self, args, call)
|
|
587
|
+
|
|
588
|
+
def _relaunch(self, args, call):
|
|
589
|
+
if not args.get("run_id") and args.get("device"):
|
|
590
|
+
raise ToolError("restart applies to a check's app. On a device you drive directly, pass bundle_id.")
|
|
591
|
+
|
|
592
|
+
def work(session, phases):
|
|
593
|
+
run, driver = session.run, session.run.driver
|
|
594
|
+
run.relaunch()
|
|
595
|
+
try:
|
|
596
|
+
after = driver.observe_ready(timeout=10)
|
|
597
|
+
except Exception:
|
|
598
|
+
after = None
|
|
599
|
+
name = getattr(getattr(run, "app", None), "name", "") or "the app"
|
|
600
|
+
text = f"Relaunched {name}, keeping its data"
|
|
601
|
+
return {"op": "RELAUNCH", "text": text, "changed": True, "line": text + ".",
|
|
602
|
+
"after": screen_fingerprint(after)}
|
|
603
|
+
return self._act(args, call, work, "RELAUNCH")
|
|
604
|
+
|
|
605
|
+
def tool_wait_for(self, args, call):
|
|
606
|
+
assertions = [self._parse_assertion(item) for item in args["expect"]]
|
|
607
|
+
asked = float(args.get("timeout_s", 10))
|
|
608
|
+
session = self._target_session(args)
|
|
609
|
+
started = time.monotonic()
|
|
610
|
+
with self._locked(session):
|
|
611
|
+
self._front(session)
|
|
612
|
+
# evaluate reads until its deadline, then its last read and the outline take their own time (3 to 4 s
|
|
613
|
+
# per read under load): the wait keeps EVALUATE_RESERVE of the call's budget for them.
|
|
614
|
+
timeout = min(asked, self._left(call, self.call_budget - self.evaluate_reserve))
|
|
615
|
+
try:
|
|
616
|
+
results = session.run.evaluate(assertions, timeout=timeout)
|
|
617
|
+
outline, alert = self._outline(session)
|
|
618
|
+
except ToolError:
|
|
619
|
+
raise
|
|
620
|
+
except Exception as error:
|
|
621
|
+
raise ToolError(self._action_error(error)) from None
|
|
622
|
+
seconds = time.monotonic() - started
|
|
623
|
+
held = all(result.ok for result in results)
|
|
624
|
+
head = (f"All {len(results)} hold ({seconds:.1f} s)." if held else
|
|
625
|
+
f"{sum(r.ok for r in results)} of {len(results)} hold after {seconds:.1f} s.")
|
|
626
|
+
shortened = timeout < asked
|
|
627
|
+
if shortened and not held: # when all hold, the cut changed nothing
|
|
628
|
+
head += (f" The wait was cut to {timeout:.1f} s of the {asked:g} s asked, so this call answers within "
|
|
629
|
+
f"{self.call_budget:.0f} s.")
|
|
630
|
+
lines = [head] + [f"{'✓' if r.ok else '✗'} {r.text}: {str(r.observed)[:200]}" for r in results]
|
|
631
|
+
lines += list(dict.fromkeys(hint for hint in (navbar_hint(item) for item, r in zip(args["expect"], results)
|
|
632
|
+
if not r.ok) if hint))
|
|
633
|
+
lines.append("This never changes the verdict: verify_finish judges the expectations from verify_start.")
|
|
634
|
+
structured = {"held": held, "seconds": round(seconds, 2), "timeout_s": round(timeout, 2),
|
|
635
|
+
"shortened": shortened,
|
|
636
|
+
"results": [{"ok": bool(r.ok), "text": r.text, "observed": r.observed} for r in results],
|
|
637
|
+
"screen": outline.structured(alert)}
|
|
638
|
+
return ToolResult(cap_text("\n".join(lines) + "\n" + outline.render(alert)), jsonable(structured))
|