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,128 @@
|
|
|
1
|
+
"""A check's conditions on Android: the ones with an Android equivalent are set, the rest make the check couldn't-run
|
|
2
|
+
(it never runs in the wrong state)."""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from ..verify.conditions import ConditionError
|
|
7
|
+
from .adb import AdbError
|
|
8
|
+
|
|
9
|
+
# iOS privacy service -> Android runtime permissions.
|
|
10
|
+
PERMISSIONS = {
|
|
11
|
+
"calendar": ("android.permission.READ_CALENDAR", "android.permission.WRITE_CALENDAR"),
|
|
12
|
+
"contacts": ("android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"),
|
|
13
|
+
"contacts-limited": ("android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"),
|
|
14
|
+
"location": ("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"),
|
|
15
|
+
"location-always": ("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION",
|
|
16
|
+
"android.permission.ACCESS_BACKGROUND_LOCATION"),
|
|
17
|
+
"microphone": ("android.permission.RECORD_AUDIO",),
|
|
18
|
+
"photos": ("android.permission.READ_MEDIA_IMAGES", "android.permission.READ_MEDIA_VIDEO"),
|
|
19
|
+
}
|
|
20
|
+
NOT_ON_ANDROID = {
|
|
21
|
+
"push": "conditions.push sends an APNs payload, which Android has no equivalent of here",
|
|
22
|
+
"content_size": "conditions.content_size has no Android mapping yet",
|
|
23
|
+
"increase_contrast": "conditions.increase_contrast has no Android mapping yet",
|
|
24
|
+
"route": "conditions.route has no Android mapping yet",
|
|
25
|
+
"media": "conditions.media has no Android mapping yet",
|
|
26
|
+
"status_bar": "conditions.status_bar has no Android mapping yet",
|
|
27
|
+
"network": "conditions.network shapes traffic through an iOS simulator's proxy, which Android has no equivalent of "
|
|
28
|
+
"here",
|
|
29
|
+
}
|
|
30
|
+
SUPPORTED = ("appearance", "locale", "location", "permissions") # what _apply sets; any other key is refused
|
|
31
|
+
FIX = "Remove that condition, or run the check on an iOS simulator with --sim."
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AndroidConditions:
|
|
35
|
+
"""Same shape as verify.conditions.Applier: before_launch, after_launch, launch_args, launch_env, restore, report,
|
|
36
|
+
and ``net``, which is always None: network conditions and request stubs are an iOS simulator's (netrun.py)."""
|
|
37
|
+
|
|
38
|
+
net = None
|
|
39
|
+
|
|
40
|
+
def __init__(self, spec, adb, *, bundle, emulator, actions=None):
|
|
41
|
+
self.spec, self.adb, self.bundle, self.emulator = dict(spec), adb, bundle, emulator
|
|
42
|
+
self.actions = actions # the run's action journal (verify/actions.py): every change is journaled first
|
|
43
|
+
self.applied, self.skipped, self._undo = [], [], []
|
|
44
|
+
|
|
45
|
+
def _change(self, operation, call, *args, **kwargs):
|
|
46
|
+
"""One device change, journaled before it is sent; an answer that never came stops the run, never retried."""
|
|
47
|
+
if self.actions is None:
|
|
48
|
+
raise ConditionError("Device condition changes need an action journal.", FIX)
|
|
49
|
+
with self.actions.action(operation):
|
|
50
|
+
return call(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def launch_args(self):
|
|
53
|
+
return ()
|
|
54
|
+
|
|
55
|
+
def launch_env(self):
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
def before_launch(self):
|
|
59
|
+
for key in self.spec: # a key added for iOS later is refused here too, never skipped unseen
|
|
60
|
+
if key not in SUPPORTED:
|
|
61
|
+
why = NOT_ON_ANDROID.get(key, f"conditions.{key} has no Android mapping yet")
|
|
62
|
+
raise ConditionError(f"{why}, so this check can't run on Android.", FIX)
|
|
63
|
+
try:
|
|
64
|
+
self._apply()
|
|
65
|
+
except AdbError as error:
|
|
66
|
+
raise ConditionError(f"Mobster couldn't set a condition on the Android device: {error}",
|
|
67
|
+
"Run `mobster sim doctor`, then try again. Remove the condition to run the check "
|
|
68
|
+
"without it.") from None
|
|
69
|
+
|
|
70
|
+
def _apply(self):
|
|
71
|
+
spec = self.spec
|
|
72
|
+
if "appearance" in spec:
|
|
73
|
+
text = self.adb.shell("cmd", "uimode", "night", timeout=10, check=False).text
|
|
74
|
+
old = re.search(r"Night mode: (yes|no)", text)
|
|
75
|
+
self._change("CONDITION_SET", self.adb.shell, "cmd", "uimode", "night",
|
|
76
|
+
"yes" if spec["appearance"] == "dark" else "no", timeout=10)
|
|
77
|
+
if old:
|
|
78
|
+
self._undo.append(("cmd", "uimode", "night", old.group(1)))
|
|
79
|
+
self.applied.append("appearance")
|
|
80
|
+
if "locale" in spec:
|
|
81
|
+
tag = spec["locale"].replace("_", "-")
|
|
82
|
+
def set_locale():
|
|
83
|
+
result = self.adb.shell("cmd", "locale", "set-app-locales", self.bundle, "--locales", tag, timeout=10,
|
|
84
|
+
check=False)
|
|
85
|
+
if result.code != 0 or b"xception" in result.out + result.err:
|
|
86
|
+
raise AdbError("per-app languages need Android 13 or later")
|
|
87
|
+
self._change("CONDITION_SET", set_locale)
|
|
88
|
+
self._undo.append(("cmd", "locale", "set-app-locales", self.bundle, "--locales", ""))
|
|
89
|
+
self.applied.append("locale")
|
|
90
|
+
if "location" in spec:
|
|
91
|
+
if not self.emulator:
|
|
92
|
+
raise ConditionError("conditions.location sets an emulator's GPS fix, and a USB Android phone has no "
|
|
93
|
+
"such control, so this check can't run on it.", FIX)
|
|
94
|
+
place = spec["location"]
|
|
95
|
+
self._change("CONDITION_SET", self.adb.adb, "emu", "geo", "fix", f"{place['longitude']:g}",
|
|
96
|
+
f"{place['latitude']:g}", timeout=10)
|
|
97
|
+
self.applied.append("location")
|
|
98
|
+
if spec.get("permissions") and not self.emulator:
|
|
99
|
+
# As on an iPhone: a phone's permissions are someone's own, so a check never changes them there.
|
|
100
|
+
self.skipped.append({"key": "permissions", "why": "Mobster never changes an app's permissions on a phone."})
|
|
101
|
+
return
|
|
102
|
+
for service, action in (spec.get("permissions") or {}).items():
|
|
103
|
+
names = PERMISSIONS.get(service)
|
|
104
|
+
if names is None:
|
|
105
|
+
raise ConditionError(f"conditions.permissions.{service} has no Android permission.", FIX)
|
|
106
|
+
for name in names:
|
|
107
|
+
def change(name=name, action=action):
|
|
108
|
+
try:
|
|
109
|
+
self.adb.permission(self.bundle, name, action == "grant")
|
|
110
|
+
except AdbError:
|
|
111
|
+
# A permission the app does not declare, or the OS version lacks, cannot be revoked.
|
|
112
|
+
if action == "grant":
|
|
113
|
+
raise
|
|
114
|
+
self._change("CONDITION_SET", change)
|
|
115
|
+
self.applied.append("permissions")
|
|
116
|
+
|
|
117
|
+
def after_launch(self):
|
|
118
|
+
"""Nothing is sent after launch on Android."""
|
|
119
|
+
|
|
120
|
+
def restore(self, actions=None):
|
|
121
|
+
"""Undo each change under the device lease, journaled; stop at the first that isn't confirmed, never retried."""
|
|
122
|
+
if actions is not None:
|
|
123
|
+
self.actions = actions
|
|
124
|
+
while self._undo:
|
|
125
|
+
self._change("CONDITION_RESTORE", self.adb.shell, *self._undo.pop(), timeout=10)
|
|
126
|
+
|
|
127
|
+
def report(self):
|
|
128
|
+
return {"applied": list(dict.fromkeys(self.applied)), "skipped": list(self.skipped)}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""`mobster sim doctor` rows for Android: SDK, platform-tools, emulator, an AVD, acceleration, devices."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from ..sim.doctor import FAIL, OK, SKIP, WARN, check
|
|
6
|
+
from . import adb as adb_module
|
|
7
|
+
|
|
8
|
+
SDK_FIX = ("Install Android Studio, open it once, and let it install the SDK (about 3 GB with platform-tools and the "
|
|
9
|
+
"emulator)")
|
|
10
|
+
EMULATOR_FIX = "In Android Studio: Settings > SDK Manager > SDK Tools > Android Emulator"
|
|
11
|
+
AVD_FIX = "In Android Studio: Device Manager > Create Virtual Device (an arm64 system image on Apple silicon)"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_checks(manager):
|
|
15
|
+
"""Rows for the SDK in use. ``manager`` is an EmulatorManager."""
|
|
16
|
+
root = manager.root
|
|
17
|
+
if root is None:
|
|
18
|
+
return [check("android_sdk", "Android SDK", FAIL, "not found (no ANDROID_HOME, no ~/Library/Android/sdk)",
|
|
19
|
+
SDK_FIX)]
|
|
20
|
+
rows = [check("android_sdk", "Android SDK", OK, str(root))]
|
|
21
|
+
adb_path = adb_module.tool("adb", root)
|
|
22
|
+
if not adb_path:
|
|
23
|
+
rows.append(check("android_adb", "adb", FAIL, "platform-tools is missing",
|
|
24
|
+
"SDK Manager > SDK Tools > Platform-Tools"))
|
|
25
|
+
else:
|
|
26
|
+
version = manager._run([adb_path, "version"], 10, None)
|
|
27
|
+
first = version.text.splitlines()[0] if version.code == 0 and version.text else ""
|
|
28
|
+
rows.append(check("android_adb", "adb", OK if first else FAIL, first or "adb did not run",
|
|
29
|
+
"" if first else "Reinstall platform-tools from the SDK Manager"))
|
|
30
|
+
emulator = adb_module.tool("emulator", root)
|
|
31
|
+
rows.append(check("android_emulator", "Android emulator", OK if emulator else WARN,
|
|
32
|
+
emulator or "not installed: USB phones still work", "" if emulator else EMULATOR_FIX))
|
|
33
|
+
if not emulator:
|
|
34
|
+
rows.append(check("android_avd", "Android virtual device", SKIP, "needs the emulator"))
|
|
35
|
+
else:
|
|
36
|
+
avds = manager.avds()
|
|
37
|
+
rows.append(check("android_avd", "Android virtual device", OK if avds else WARN,
|
|
38
|
+
", ".join(avds) if avds else "none yet", "" if avds else AVD_FIX))
|
|
39
|
+
machine = platform.machine()
|
|
40
|
+
rows.append(check("android_accel", "Hardware acceleration", OK if machine == "arm64" else WARN,
|
|
41
|
+
"Apple silicon (Hypervisor.framework)" if machine == "arm64"
|
|
42
|
+
else f"{machine}: the emulator needs HVF and an x86_64 image",
|
|
43
|
+
"" if machine == "arm64" else "Use an x86_64 system image"))
|
|
44
|
+
try:
|
|
45
|
+
devices = manager.devices()
|
|
46
|
+
except Exception as error:
|
|
47
|
+
return rows + [check("android_devices", "Android devices", FAIL, f"adb failed: {error}",
|
|
48
|
+
"Run `adb kill-server`, then `adb devices -l`")]
|
|
49
|
+
blocked = [row for row in devices if row["state"] != "device"]
|
|
50
|
+
detail = "; ".join(f"{row['name']} ({row['kind']}, {row['state']})" for row in devices) or "none connected"
|
|
51
|
+
fix = ("Unlock the phone and accept the USB debugging prompt" if any(r["state"] == "unauthorized" for r in blocked)
|
|
52
|
+
else "Reconnect the device" if blocked else "")
|
|
53
|
+
rows.append(check("android_devices", "Android devices", WARN if blocked else OK if devices else SKIP, detail, fix))
|
|
54
|
+
return rows
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""The Android driver: reads the screen with uiautomator, acts with `adb shell input`, over one adb connection.
|
|
2
|
+
|
|
3
|
+
It hands the agent and verify the same Snapshot and AXTree an iPhone does. Nothing here talks to the Appium
|
|
4
|
+
UIAutomator2 server yet: a dump costs about a second where the server's tree costs a few hundred ms, and the server's
|
|
5
|
+
APKs need a pinned download (see docs/android.mdx).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from ..drivers import Driver, register_driver
|
|
11
|
+
from ..state import HOST_KEY_OPERATIONS, TEXT_OPERATIONS, from_wda_root
|
|
12
|
+
from ..transport import Deadline, TransportError
|
|
13
|
+
from ..wda.gestures import _swipe_points
|
|
14
|
+
from ..wda.guards import DriverRejection, grounded_target
|
|
15
|
+
from . import adb as adb_module
|
|
16
|
+
from .tree import to_wda
|
|
17
|
+
|
|
18
|
+
FRESH_SECONDS = 1.0
|
|
19
|
+
SWIPE_MILLIS = 350
|
|
20
|
+
KEYCODES = {"HOME": 3, "BACK": 4, "VOLUME_UP": 24, "VOLUME_DOWN": 25, "SUBMIT": 66}
|
|
21
|
+
SOURCE = "android"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AndroidDriver(Driver):
|
|
25
|
+
capabilities = frozenset({"active_app", "capture_preview", "effects", "evidence_source", "launch", "long_press",
|
|
26
|
+
"open_url", "tap_point"})
|
|
27
|
+
# Every method that changes the device: a verify run journals each call (verify/actions.py, guard_driver).
|
|
28
|
+
effects = ("execute", "launch", "open_url", "tap_point", "long_press")
|
|
29
|
+
can_type = True
|
|
30
|
+
|
|
31
|
+
def __init__(self, serial, adb=None):
|
|
32
|
+
self.adb = adb or adb_module.Adb(serial)
|
|
33
|
+
self.serial = serial
|
|
34
|
+
self._screen = None
|
|
35
|
+
self.last_image = None
|
|
36
|
+
|
|
37
|
+
def _guard(self, call, *args, **kwargs):
|
|
38
|
+
try:
|
|
39
|
+
return call(*args, **kwargs)
|
|
40
|
+
except adb_module.AdbError as error:
|
|
41
|
+
raise TransportError(str(error)) from None
|
|
42
|
+
|
|
43
|
+
def screen(self):
|
|
44
|
+
"""(width px, height px, dpi), read once: a rotation or a density change needs a new driver."""
|
|
45
|
+
if self._screen is None:
|
|
46
|
+
self._screen = self._guard(self.adb.screen_px)
|
|
47
|
+
return self._screen
|
|
48
|
+
|
|
49
|
+
def evidence_source(self, timeout=15):
|
|
50
|
+
"""The tree as WDA-shaped XML, for verify's evidence read."""
|
|
51
|
+
width, height, density = self.screen()
|
|
52
|
+
xml = self._guard(self.adb.dump_ui, timeout)
|
|
53
|
+
try:
|
|
54
|
+
return to_wda(xml, width, height, density)
|
|
55
|
+
except ValueError as error:
|
|
56
|
+
raise TransportError(f"The Android accessibility tree could not be read: {error}") from None
|
|
57
|
+
|
|
58
|
+
def observe(self, timeout=10):
|
|
59
|
+
from ..state import _parse_wda
|
|
60
|
+
snapshot = from_wda_root(_parse_wda(self.evidence_source(timeout)))
|
|
61
|
+
snapshot.source = SOURCE
|
|
62
|
+
return snapshot
|
|
63
|
+
|
|
64
|
+
def active_app(self, timeout=5):
|
|
65
|
+
return self._guard(self.adb.front_package)
|
|
66
|
+
|
|
67
|
+
def capture_preview(self, timeout=3):
|
|
68
|
+
return self._guard(self.adb.screenshot_data_uri, timeout)
|
|
69
|
+
|
|
70
|
+
def launch(self, package, timeout=20):
|
|
71
|
+
self._guard(self.adb.launch, package)
|
|
72
|
+
return self.observe(timeout)
|
|
73
|
+
|
|
74
|
+
def open_url(self, url):
|
|
75
|
+
self._guard(self.adb.open_url, url)
|
|
76
|
+
|
|
77
|
+
def tap_point(self, x, y):
|
|
78
|
+
"""Tap a screen point given as fractions of the screen."""
|
|
79
|
+
width, height, _ = self.screen()
|
|
80
|
+
self._guard(self.adb.tap, x * width, y * height)
|
|
81
|
+
|
|
82
|
+
def long_press(self, x, y):
|
|
83
|
+
width, height, _ = self.screen()
|
|
84
|
+
self._guard(self.adb.long_press, x * width, y * height)
|
|
85
|
+
|
|
86
|
+
def _check_current(self, snapshot, deadline):
|
|
87
|
+
if time.monotonic() - snapshot.captured_at <= FRESH_SECONDS:
|
|
88
|
+
return
|
|
89
|
+
if self.observe(timeout=deadline.remaining()).fingerprint != snapshot.fingerprint:
|
|
90
|
+
raise DriverRejection("stale_revision")
|
|
91
|
+
|
|
92
|
+
def execute(self, operation, target, snapshot, text=None, timeout=10):
|
|
93
|
+
deadline = Deadline(timeout)
|
|
94
|
+
width, height, _ = self.screen()
|
|
95
|
+
if operation in HOST_KEY_OPERATIONS or operation == "BACK":
|
|
96
|
+
self._guard(self.adb.key, KEYCODES[operation])
|
|
97
|
+
return {"dispatch_attempted": True}
|
|
98
|
+
if operation == "LAUNCH_APP":
|
|
99
|
+
self._guard(self.adb.launch, target)
|
|
100
|
+
return {}
|
|
101
|
+
if operation == "SUBMIT":
|
|
102
|
+
grounded_target(operation, target, snapshot)
|
|
103
|
+
if "SUBMIT" not in target.actions:
|
|
104
|
+
raise ValueError("SUBMIT needs a keyboard and an editable field")
|
|
105
|
+
self._check_current(snapshot, deadline)
|
|
106
|
+
self._guard(self.adb.key, KEYCODES["SUBMIT"])
|
|
107
|
+
elif operation == "TAP":
|
|
108
|
+
grounded_target(operation, target, snapshot)
|
|
109
|
+
self._check_current(snapshot, deadline)
|
|
110
|
+
x, y = target.tap_point
|
|
111
|
+
self._guard(self.adb.tap, x * width, y * height)
|
|
112
|
+
elif operation in TEXT_OPERATIONS:
|
|
113
|
+
grounded_target(operation, target, snapshot)
|
|
114
|
+
self._check_current(snapshot, deadline)
|
|
115
|
+
if not isinstance(text, str) or not text:
|
|
116
|
+
raise ValueError("TYPE requires text")
|
|
117
|
+
x, y = target.tap_point
|
|
118
|
+
self._guard(self.adb.tap, x * width, y * height)
|
|
119
|
+
self._guard(self.adb.text, text)
|
|
120
|
+
if operation == "TYPE_SUBMIT":
|
|
121
|
+
self._guard(self.adb.key, KEYCODES["SUBMIT"])
|
|
122
|
+
elif operation in {"SWIPE_UP", "SWIPE_DOWN", "SWIPE_LEFT", "SWIPE_RIGHT"}:
|
|
123
|
+
aimed = None
|
|
124
|
+
if target is not None and operation in {"SWIPE_LEFT", "SWIPE_RIGHT"}:
|
|
125
|
+
grounded_target(operation, target, snapshot)
|
|
126
|
+
aimed = target.rect
|
|
127
|
+
x1, y1, x2, y2 = _swipe_points(operation, width, height, aimed)
|
|
128
|
+
self._guard(self.adb.swipe, x1, y1, x2, y2, SWIPE_MILLIS)
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError("Unsupported Android operation")
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
def close(self):
|
|
134
|
+
"""Nothing is held open: adb runs one command per call."""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
register_driver("android", AndroidDriver)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Android emulators Mobster boots: one lease per run, a cap like the iOS simulators', and only its own are stopped."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..sim.models import SimError, SimLease
|
|
12
|
+
from . import adb as adb_module
|
|
13
|
+
from .adb import Adb, AdbError
|
|
14
|
+
|
|
15
|
+
DEFAULT_MAX = 2
|
|
16
|
+
BOOT_TIMEOUT = 240
|
|
17
|
+
FIRST_PORT, LAST_PORT = 5554, 5584 # emulator console ports are even; adb sees emulator-<port>
|
|
18
|
+
EMULATOR_ARGS = ("-no-window", "-no-audio", "-no-snapshot-save", "-no-boot-anim", "-gpu", "swiftshader_indirect")
|
|
19
|
+
AVD_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._\-]{0,63}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class AndroidTarget:
|
|
24
|
+
serial: str
|
|
25
|
+
name: str # "Pixel_8" or the phone's model
|
|
26
|
+
kind: str # "emulator" or "usb"
|
|
27
|
+
avd: str = ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _lease(identity):
|
|
31
|
+
from ..journal import Lease
|
|
32
|
+
return Lease.device(identity)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _spawn(argv):
|
|
36
|
+
return subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
37
|
+
start_new_session=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EmulatorManager:
|
|
41
|
+
"""``adb`` lists devices, ``spawn`` starts the emulator process, ``lease`` takes the per-device lock."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, data_dir, adb=None, run=adb_module.run_process, spawn=_spawn, lease=_lease, root=None,
|
|
44
|
+
sleep=time.sleep, clock=time.monotonic):
|
|
45
|
+
self.data_dir = Path(data_dir).expanduser() / "android"
|
|
46
|
+
self.root = root if root is not None else adb_module.sdk_root()
|
|
47
|
+
self.adb = adb or Adb(run=run, binary=adb_module.tool("adb", self.root) or "adb")
|
|
48
|
+
self._run, self._spawn, self._lease, self.sleep, self.clock = run, spawn, lease, sleep, clock
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def max_emulators(self):
|
|
52
|
+
text = str(os.environ.get("MOBSTER_MAX_SIMS") or "").strip()
|
|
53
|
+
if not text:
|
|
54
|
+
return DEFAULT_MAX
|
|
55
|
+
if not text.isdigit() or int(text) < 1:
|
|
56
|
+
raise SimError("environment", f"MOBSTER_MAX_SIMS is {text!r}; it must be a whole number of 1 or more.",
|
|
57
|
+
"Unset it to use 2.")
|
|
58
|
+
return int(text)
|
|
59
|
+
|
|
60
|
+
# -- what exists
|
|
61
|
+
|
|
62
|
+
def avds(self):
|
|
63
|
+
emulator = adb_module.tool("emulator", self.root)
|
|
64
|
+
if not emulator:
|
|
65
|
+
return []
|
|
66
|
+
result = self._run([emulator, "-list-avds"], 20, None)
|
|
67
|
+
return [line.strip() for line in result.text.splitlines() if AVD_NAME.fullmatch(line.strip())]
|
|
68
|
+
|
|
69
|
+
def avd_of(self, serial):
|
|
70
|
+
result = Adb(serial, self._run, self.adb.binary).adb("emu", "avd", "name", timeout=10, check=False)
|
|
71
|
+
lines = [line.strip() for line in result.text.splitlines() if line.strip() and line.strip() != "OK"]
|
|
72
|
+
return lines[0] if lines else ""
|
|
73
|
+
|
|
74
|
+
def devices(self):
|
|
75
|
+
"""Android devices as rows: serial, name, kind, state, avd, owned (a Mobster-booted emulator), in_use."""
|
|
76
|
+
rows = []
|
|
77
|
+
for device in self.adb.devices():
|
|
78
|
+
avd = self.avd_of(device.serial) if device.kind == "emulator" and device.ready else ""
|
|
79
|
+
identity = self._identity(device.serial, avd)
|
|
80
|
+
rows.append({"platform": "android", "serial": device.serial, "name": avd or device.model or device.serial,
|
|
81
|
+
"kind": device.kind, "state": device.state, "avd": avd,
|
|
82
|
+
"owned": self._pid_file(avd).exists() if avd else False,
|
|
83
|
+
"in_use": self._in_use(identity)})
|
|
84
|
+
return rows
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _identity(serial, avd):
|
|
88
|
+
return f"android:avd:{avd}" if avd else f"android:{serial}"
|
|
89
|
+
|
|
90
|
+
def _in_use(self, identity):
|
|
91
|
+
try:
|
|
92
|
+
held = self._lease(identity)
|
|
93
|
+
except Exception:
|
|
94
|
+
return True
|
|
95
|
+
held.close()
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def _pid_file(self, avd):
|
|
99
|
+
return self.data_dir / f"{avd}.json"
|
|
100
|
+
|
|
101
|
+
# -- acquire and release
|
|
102
|
+
|
|
103
|
+
def acquire(self, avd=None, serial=None, timeout=BOOT_TIMEOUT):
|
|
104
|
+
"""A lease on an idle ready device: ``serial`` (a USB phone or running emulator), else a running emulator of
|
|
105
|
+
``avd``, else a booted one. SimError("busy") when everything is in use and the cap is reached."""
|
|
106
|
+
rows = self.devices()
|
|
107
|
+
if serial:
|
|
108
|
+
rows = [row for row in rows if row["serial"] == serial]
|
|
109
|
+
if not rows:
|
|
110
|
+
raise SimError("environment", f"No Android device {serial} is connected.", "Run `adb devices -l`.")
|
|
111
|
+
elif avd:
|
|
112
|
+
rows = [row for row in rows if row["avd"] == avd]
|
|
113
|
+
else:
|
|
114
|
+
rows = [row for row in rows if row["kind"] == "emulator"] # a USB phone is used only when named
|
|
115
|
+
for row in rows:
|
|
116
|
+
if row["state"] != "device":
|
|
117
|
+
continue
|
|
118
|
+
lease = self._try_lease(self._identity(row["serial"], row["avd"]))
|
|
119
|
+
if lease is not None:
|
|
120
|
+
target = AndroidTarget(row["serial"], row["name"], row["kind"], row["avd"])
|
|
121
|
+
return SimLease(target, [lease])
|
|
122
|
+
if serial:
|
|
123
|
+
raise SimError("busy", f"{serial} is running a check.", "Wait for it to finish.")
|
|
124
|
+
return self._boot_and_lease(avd, timeout)
|
|
125
|
+
|
|
126
|
+
def _try_lease(self, identity):
|
|
127
|
+
try:
|
|
128
|
+
return self._lease(identity)
|
|
129
|
+
except Exception:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def _boot_and_lease(self, avd, timeout):
|
|
133
|
+
running = [row for row in self.devices() if row["owned"]]
|
|
134
|
+
if len(running) >= self.max_emulators:
|
|
135
|
+
raise SimError("busy", f"All {self.max_emulators} of Mobster's Android emulators are running checks.",
|
|
136
|
+
"Wait for one to finish, or set MOBSTER_MAX_SIMS higher.")
|
|
137
|
+
names = self.avds()
|
|
138
|
+
if not names:
|
|
139
|
+
raise SimError("environment", "There is no Android virtual device (AVD).",
|
|
140
|
+
"Create one in Android Studio's Device Manager, or with avdmanager.")
|
|
141
|
+
if avd and avd not in names:
|
|
142
|
+
raise SimError("environment", f"There is no AVD named {avd}; there are {', '.join(names)}.",
|
|
143
|
+
"Pick one of those, or create it in Android Studio's Device Manager.")
|
|
144
|
+
avd = avd or names[0]
|
|
145
|
+
lease = self._try_lease(self._identity("", avd))
|
|
146
|
+
if lease is None:
|
|
147
|
+
raise SimError("busy", f"{avd} is running a check.", "Wait for it to finish.")
|
|
148
|
+
try:
|
|
149
|
+
serial = self._boot(avd, timeout)
|
|
150
|
+
except BaseException:
|
|
151
|
+
lease.close()
|
|
152
|
+
raise
|
|
153
|
+
return SimLease(AndroidTarget(serial, avd, "emulator", avd), [lease])
|
|
154
|
+
|
|
155
|
+
def _free_port(self):
|
|
156
|
+
taken = {int(row["serial"].split("-")[1]) for row in self.adb.devices() if row.kind == "emulator"}
|
|
157
|
+
for port in range(FIRST_PORT, LAST_PORT + 1, 2):
|
|
158
|
+
if port not in taken:
|
|
159
|
+
return port
|
|
160
|
+
raise SimError("simulator", "Every emulator port from 5554 to 5584 is in use.",
|
|
161
|
+
"Stop an emulator you started by hand.")
|
|
162
|
+
|
|
163
|
+
def _boot(self, avd, timeout):
|
|
164
|
+
emulator = adb_module.tool("emulator", self.root)
|
|
165
|
+
if not emulator:
|
|
166
|
+
raise SimError("environment", "The Android emulator is not installed.",
|
|
167
|
+
"Install it from Android Studio's SDK Manager.")
|
|
168
|
+
port = self._free_port()
|
|
169
|
+
serial = f"emulator-{port}"
|
|
170
|
+
process = self._spawn([emulator, "-avd", avd, "-port", str(port), *EMULATOR_ARGS])
|
|
171
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
self._pid_file(avd).write_text(json.dumps({"pid": process.pid, "serial": serial}))
|
|
173
|
+
guest = Adb(serial, self._run, self.adb.binary)
|
|
174
|
+
end = self.clock() + timeout
|
|
175
|
+
while self.clock() < end:
|
|
176
|
+
if process.poll() is not None:
|
|
177
|
+
self._pid_file(avd).unlink(missing_ok=True)
|
|
178
|
+
raise SimError("simulator", f"The emulator for {avd} exited while booting.",
|
|
179
|
+
"Start it once from Android Studio to see why.")
|
|
180
|
+
try:
|
|
181
|
+
if serial in {d.serial for d in self.adb.devices() if d.ready} and guest.booted():
|
|
182
|
+
return serial
|
|
183
|
+
except AdbError:
|
|
184
|
+
pass
|
|
185
|
+
self.sleep(2)
|
|
186
|
+
self._stop(avd, serial, process.pid)
|
|
187
|
+
raise SimError("simulator", f"{avd} did not finish booting in {timeout} s.",
|
|
188
|
+
"Close other heavy jobs and try again.")
|
|
189
|
+
|
|
190
|
+
def _stop(self, avd, serial, pid):
|
|
191
|
+
Adb(serial, self._run, self.adb.binary).adb("emu", "kill", timeout=10, check=False)
|
|
192
|
+
end = self.clock() + 15
|
|
193
|
+
while self.clock() < end:
|
|
194
|
+
try:
|
|
195
|
+
os.kill(pid, 0)
|
|
196
|
+
except OSError:
|
|
197
|
+
break
|
|
198
|
+
self.sleep(.5)
|
|
199
|
+
else:
|
|
200
|
+
try:
|
|
201
|
+
os.kill(pid, 15)
|
|
202
|
+
except OSError:
|
|
203
|
+
pass
|
|
204
|
+
self._pid_file(avd).unlink(missing_ok=True)
|
|
205
|
+
|
|
206
|
+
def shutdown(self, avd=None):
|
|
207
|
+
"""Stop the emulators Mobster booted (all, or ``avd``) that are not running a check. Others are never
|
|
208
|
+
touched."""
|
|
209
|
+
stopped = []
|
|
210
|
+
for row in self.devices():
|
|
211
|
+
if not row["owned"] or row["in_use"] or (avd and row["avd"] != avd):
|
|
212
|
+
continue
|
|
213
|
+
info = json.loads(self._pid_file(row["avd"]).read_text())
|
|
214
|
+
self._stop(row["avd"], row["serial"], int(info["pid"]))
|
|
215
|
+
stopped.append(row["avd"])
|
|
216
|
+
return stopped
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""logcat and crashes. Every line passes the secret filter before it is kept."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .. import secret_filter
|
|
7
|
+
from .adb import Adb, valid_package
|
|
8
|
+
|
|
9
|
+
THREADTIME = re.compile(r"^(\d\d-\d\d \d\d:\d\d:\d\d\.\d+)\s+(\d+)\s+(\d+)\s+([VDIWEF])\s+(.*?)\s*:\s?(.*)$")
|
|
10
|
+
FATAL = re.compile(r"^FATAL EXCEPTION: (.*)$")
|
|
11
|
+
PROCESS = re.compile(r"^Process: ([A-Za-z0-9_.]+), PID: (\d+)")
|
|
12
|
+
ANR = re.compile(r"^ANR in ([A-Za-z0-9_.]+)(?: \(([^)]*)\))?")
|
|
13
|
+
EXCEPTION = re.compile(r"^([A-Za-z0-9_.$]+(?:Exception|Error)): ?(.*)$")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Crash:
|
|
18
|
+
kind: str # "crash" or "anr"
|
|
19
|
+
package: str
|
|
20
|
+
pid: int
|
|
21
|
+
thread: str
|
|
22
|
+
exception: str # the first exception line, e.g. "java.lang.IllegalStateException: boom"
|
|
23
|
+
lines: tuple # the block's lines, filtered
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def read_logcat(adb: Adb, since=None, limit=4000):
|
|
27
|
+
"""Filtered logcat lines (`adb logcat -d -v threadtime`), newest ``limit`` of them; ``since`` is
|
|
28
|
+
"MM-DD HH:MM:SS.mmm"."""
|
|
29
|
+
args = ["logcat", "-d", "-v", "threadtime", *(["-T", since] if since else [])]
|
|
30
|
+
text = adb.adb(*args, timeout=30).text
|
|
31
|
+
return [secret_filter.redact(line) for line in text.splitlines()[-limit:]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_crashes(lines, package=None):
|
|
35
|
+
"""The crashes and ANRs in threadtime lines. ``package`` keeps only that app's."""
|
|
36
|
+
crashes, block, kind = [], [], None
|
|
37
|
+
last_key = None
|
|
38
|
+
|
|
39
|
+
def close():
|
|
40
|
+
nonlocal block, kind
|
|
41
|
+
if block:
|
|
42
|
+
crash = _crash(kind, block)
|
|
43
|
+
if crash and (package is None or crash.package == package):
|
|
44
|
+
crashes.append(crash)
|
|
45
|
+
block, kind = [], None
|
|
46
|
+
|
|
47
|
+
for raw in lines:
|
|
48
|
+
match = THREADTIME.match(raw)
|
|
49
|
+
if not match:
|
|
50
|
+
continue
|
|
51
|
+
_time, pid, _tid, level, tag, message = match.groups()
|
|
52
|
+
key = (pid, tag)
|
|
53
|
+
if kind and (key != last_key or level not in {"E", "F"}):
|
|
54
|
+
close()
|
|
55
|
+
if not kind:
|
|
56
|
+
if tag == "AndroidRuntime" and FATAL.match(message):
|
|
57
|
+
kind = "crash"
|
|
58
|
+
elif tag == "ActivityManager" and ANR.match(message):
|
|
59
|
+
kind = "anr"
|
|
60
|
+
if kind:
|
|
61
|
+
block.append(message)
|
|
62
|
+
last_key = key
|
|
63
|
+
close()
|
|
64
|
+
return crashes
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _crash(kind, block):
|
|
68
|
+
package, pid, thread, exception = "", 0, "", ""
|
|
69
|
+
for message in block:
|
|
70
|
+
fatal, process, anr = FATAL.match(message), PROCESS.match(message), ANR.match(message)
|
|
71
|
+
if fatal:
|
|
72
|
+
thread = fatal.group(1)
|
|
73
|
+
elif process:
|
|
74
|
+
package, pid = process.group(1), int(process.group(2))
|
|
75
|
+
elif anr:
|
|
76
|
+
package, thread = anr.group(1), anr.group(2) or ""
|
|
77
|
+
elif not exception and EXCEPTION.match(message):
|
|
78
|
+
exception = message
|
|
79
|
+
if not package:
|
|
80
|
+
return None
|
|
81
|
+
return Crash(kind, package, pid, thread, exception, tuple(block))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def crashes_of(adb, package, since=None):
|
|
85
|
+
return parse_crashes(read_logcat(adb, since), valid_package(package))
|