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,223 @@
|
|
|
1
|
+
"""The evidence read: the app's accessibility tree as WebDriverAgent reports it, with each node's visibility."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from ..state import _parse_wda
|
|
10
|
+
from ..wda.observe import WDA_RICH_SOURCE_PATH
|
|
11
|
+
|
|
12
|
+
# Keeps ``visible`` and ``traits``: p50 0.76-1.09 s on Settings (28 Sep, n=20), under the spec's 1.5 s.
|
|
13
|
+
EVIDENCE_SOURCE_PATH = "/source?format=xml&excluded_attributes=accessible,index"
|
|
14
|
+
# No ``visible``: 0.14-0.5 s against 1.4-1.9 s (17 Oct). It says whether the screen moved; it never decides.
|
|
15
|
+
GLANCE_SOURCE_PATH = WDA_RICH_SOURCE_PATH
|
|
16
|
+
MAX_DEPTH = 64
|
|
17
|
+
MAX_NODES = 5000
|
|
18
|
+
TEXT_LIMIT = 4000
|
|
19
|
+
STRUCTURAL = frozenset({"Application", "Window"})
|
|
20
|
+
# Containers WDA reports not visible while their content shows: hidden_by_ancestor leaves them out.
|
|
21
|
+
SCROLL_CONTAINERS = frozenset({"CollectionView", "Table", "ScrollView", "WebView"})
|
|
22
|
+
TOGGLES = frozenset({"Switch", "Toggle"})
|
|
23
|
+
RETRY_PAUSE = .75 # seconds before the evidence read's one retry
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TreeError(ValueError):
|
|
27
|
+
"""The source could not be read as an accessibility tree (too large, too deep, unsafe or malformed)."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class AXNode:
|
|
32
|
+
path: str
|
|
33
|
+
role: str # the XCUIElementType name without its prefix: "Button", "StaticText"
|
|
34
|
+
identifier: str # the accessibility identifier: WDA's name when it differs from the label
|
|
35
|
+
label: str
|
|
36
|
+
value: str
|
|
37
|
+
placeholder: str
|
|
38
|
+
rect: tuple # points: x, y, w, h
|
|
39
|
+
enabled: bool
|
|
40
|
+
selected: bool
|
|
41
|
+
visible: bool # WDA's own visible attribute (True when the read leaves it out)
|
|
42
|
+
hidden_by_ancestor: bool = False # an ancestor with area, not a scroll container, is visible="false" (data only)
|
|
43
|
+
inner_toggle: bool = False # an unlabelled Switch with no identifier inside a Switch: its knob (never shown)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _number(value):
|
|
47
|
+
try:
|
|
48
|
+
number = float(value)
|
|
49
|
+
except (TypeError, ValueError):
|
|
50
|
+
return 0.0
|
|
51
|
+
return number if math.isfinite(number) else 0.0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _text(value):
|
|
55
|
+
value = value or ""
|
|
56
|
+
if len(value) > TEXT_LIMIT:
|
|
57
|
+
raise TreeError(f"The accessibility tree has text longer than {TEXT_LIMIT:,} characters")
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AXTree:
|
|
62
|
+
"""One read of the app's tree: ``nodes`` in document order."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, bundle_id, size, nodes):
|
|
65
|
+
self.bundle_id = bundle_id
|
|
66
|
+
self.size = tuple(size)
|
|
67
|
+
self.nodes = tuple(nodes)
|
|
68
|
+
self._shown = None
|
|
69
|
+
self.glance = False # read without ``visible``: it may order a re-read, never decide a verdict
|
|
70
|
+
|
|
71
|
+
def on_screen(self, node):
|
|
72
|
+
"""The node's frame overlaps the app's frame with positive area."""
|
|
73
|
+
x, y, w, h = node.rect
|
|
74
|
+
width, height = self.size
|
|
75
|
+
return min(width, x + w) - max(0.0, x) > 0 and min(height, y + h) - max(0.0, y) > 0
|
|
76
|
+
|
|
77
|
+
def is_laid_out(self, node):
|
|
78
|
+
"""Every ``shown`` rule except WDA's own ``visible``."""
|
|
79
|
+
return node.role not in STRUCTURAL and not node.inner_toggle and self.on_screen(node)
|
|
80
|
+
|
|
81
|
+
def is_shown(self, node):
|
|
82
|
+
# The node's own ``visible``: WDA calls containers not visible while their content is on screen.
|
|
83
|
+
return node.visible and self.is_laid_out(node)
|
|
84
|
+
|
|
85
|
+
def shown(self):
|
|
86
|
+
if self._shown is None:
|
|
87
|
+
self._shown = [node for node in self.nodes if self.is_shown(node)]
|
|
88
|
+
return list(self._shown)
|
|
89
|
+
|
|
90
|
+
def select(self, selector):
|
|
91
|
+
"""Shown nodes matching ``selector`` (an ``assertions.Selector``, or a selector object)."""
|
|
92
|
+
from .assertions import Selector, parse_selector
|
|
93
|
+
if not isinstance(selector, Selector):
|
|
94
|
+
selector = parse_selector(selector)
|
|
95
|
+
return [node for node in self.shown() if selector.matches(node)]
|
|
96
|
+
|
|
97
|
+
def by_path(self, path):
|
|
98
|
+
return next((node for node in self.nodes if node.path == path), None)
|
|
99
|
+
|
|
100
|
+
def _digest(self, nodes):
|
|
101
|
+
rows = [self.bundle_id, self.size, [
|
|
102
|
+
[node.path, node.role, node.identifier, node.label, node.value, node.placeholder,
|
|
103
|
+
node.enabled, node.selected, [round(node.rect[0]), round(node.rect[1]), *node.rect[2:]]]
|
|
104
|
+
for node in nodes]]
|
|
105
|
+
return hashlib.sha256(json.dumps(rows, ensure_ascii=False).encode()).hexdigest()
|
|
106
|
+
|
|
107
|
+
def _evidence_digest(self, nodes):
|
|
108
|
+
# These are the complete AXNode fields consumed by selectors and screen audits, including exact dimensions.
|
|
109
|
+
rows = [self.bundle_id, self.size, [vars(node) for node in nodes]]
|
|
110
|
+
return hashlib.sha256(json.dumps(rows, ensure_ascii=False).encode()).hexdigest()
|
|
111
|
+
|
|
112
|
+
def fingerprint(self):
|
|
113
|
+
"""Identity of the shown assertion inputs on a full evidence read."""
|
|
114
|
+
return self._evidence_digest(self.shown())
|
|
115
|
+
|
|
116
|
+
def audit_fingerprint(self):
|
|
117
|
+
"""Identity of every input a screen audit may read, including offscreen nodes."""
|
|
118
|
+
return self._evidence_digest(self.nodes)
|
|
119
|
+
|
|
120
|
+
def layout(self):
|
|
121
|
+
"""The fingerprint over the nodes that pass every ``shown`` rule except WDA's ``visible``."""
|
|
122
|
+
return self._digest([node for node in self.nodes if self.is_laid_out(node)])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_tree(xml):
|
|
126
|
+
"""The evidence read as an AXTree."""
|
|
127
|
+
try:
|
|
128
|
+
root = _parse_wda(xml)
|
|
129
|
+
except ValueError as error:
|
|
130
|
+
raise TreeError(f"The accessibility tree could not be read: {error}") from None
|
|
131
|
+
except Exception as error: # xml.etree.ElementTree.ParseError and friends
|
|
132
|
+
raise TreeError(f"The accessibility tree could not be read: {type(error).__name__}") from None
|
|
133
|
+
app = next((node for node in root.iter() if node.tag == "XCUIElementTypeApplication"), None)
|
|
134
|
+
if app is None:
|
|
135
|
+
raise TreeError("The accessibility tree has no application")
|
|
136
|
+
width, height = _number(app.attrib.get("width")), _number(app.attrib.get("height"))
|
|
137
|
+
if width <= 0 or height <= 0:
|
|
138
|
+
raise TreeError("The accessibility tree's application has no size")
|
|
139
|
+
nodes = []
|
|
140
|
+
|
|
141
|
+
def walk(node, path, depth, hidden, parent_role):
|
|
142
|
+
if depth > MAX_DEPTH:
|
|
143
|
+
raise TreeError(f"The accessibility tree is deeper than {MAX_DEPTH} levels")
|
|
144
|
+
if len(nodes) >= MAX_NODES:
|
|
145
|
+
raise TreeError(f"The accessibility tree has more than {MAX_NODES:,} elements")
|
|
146
|
+
a = node.attrib
|
|
147
|
+
role = node.tag.removeprefix("XCUIElementType")
|
|
148
|
+
x, y = _number(a.get("x")), _number(a.get("y"))
|
|
149
|
+
w, h = _number(a.get("width")), _number(a.get("height"))
|
|
150
|
+
visible = a.get("visible", "true") != "false"
|
|
151
|
+
label = _text(a.get("label"))
|
|
152
|
+
name = _text(a.get("name"))
|
|
153
|
+
traits = [trait.strip() for trait in (a.get("traits") or "").split(",")]
|
|
154
|
+
# A SwiftUI Toggle reads as a labelled Switch around an unlabelled knob (iOS 26.4): the knob is inner_toggle.
|
|
155
|
+
nodes.append(AXNode(
|
|
156
|
+
path=path, role=role, identifier=name if name and name != label else "", label=label,
|
|
157
|
+
value=_text(a.get("value")), placeholder=_text(a.get("placeholderValue")),
|
|
158
|
+
rect=(x, y, w, h), enabled=a.get("enabled", "true") != "false",
|
|
159
|
+
selected="Selected" in traits or a.get("selected") == "true", visible=visible,
|
|
160
|
+
hidden_by_ancestor=hidden,
|
|
161
|
+
inner_toggle=role in TOGGLES and parent_role in TOGGLES and not label.strip() and not name.strip()))
|
|
162
|
+
hides = not visible and w * h > 0 and role not in SCROLL_CONTAINERS # for hidden_by_ancestor only
|
|
163
|
+
counts = {}
|
|
164
|
+
for child in node:
|
|
165
|
+
counts[child.tag] = counts.get(child.tag, 0) + 1
|
|
166
|
+
walk(child, f"{path}/{child.tag}[{counts[child.tag]}]", depth + 1, hidden or hides, role)
|
|
167
|
+
|
|
168
|
+
walk(root, f"/{root.tag}", 0, False, None)
|
|
169
|
+
return AXTree(app.attrib.get("bundleId", "") or "", (width, height), nodes)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def read_source(driver, timeout=30, pause=None, retry_slow=True, path=EVIDENCE_SOURCE_PATH):
|
|
173
|
+
# Right after a relaunch WDA can answer "not running" for a moment, so the one retry waits RETRY_PAUSE. A read that
|
|
174
|
+
# used all of ``timeout`` was slow, not caught in a moment: with retry_slow False it is not tried again.
|
|
175
|
+
own = getattr(driver, "evidence_source", None) # a connection with its own tree read (Android)
|
|
176
|
+
if own is not None:
|
|
177
|
+
return own(timeout)
|
|
178
|
+
last = None
|
|
179
|
+
for attempt in range(2):
|
|
180
|
+
if attempt:
|
|
181
|
+
(pause or time.sleep)(RETRY_PAUSE)
|
|
182
|
+
started = time.monotonic()
|
|
183
|
+
try:
|
|
184
|
+
xml = driver.call("GET", path, timeout=timeout)
|
|
185
|
+
if not isinstance(xml, str):
|
|
186
|
+
raise TreeError("WebDriverAgent did not return the accessibility tree")
|
|
187
|
+
return xml
|
|
188
|
+
except TreeError:
|
|
189
|
+
raise
|
|
190
|
+
except Exception as error: # transport errors, timeouts, a runner that went away
|
|
191
|
+
last = error
|
|
192
|
+
if not retry_slow and time.monotonic() - started >= timeout - .1:
|
|
193
|
+
break
|
|
194
|
+
raise last
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def read_tree(driver, timeout=30, pause=None, retry_slow=True, path=EVIDENCE_SOURCE_PATH):
|
|
198
|
+
return parse_tree(read_source(driver, timeout=timeout, pause=pause, retry_slow=retry_slow, path=path))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def tree_from_pixels(snapshot):
|
|
202
|
+
# Paths start with /pixels, which no Element locator does, so a tap lands on the frame and never on another read's.
|
|
203
|
+
width, height = snapshot.width, snapshot.height
|
|
204
|
+
nodes = [AXNode(path=f"/pixels/{element.role}[{index}]", role=element.role, identifier="", label=element.label,
|
|
205
|
+
value=element.value, placeholder=element.placeholder,
|
|
206
|
+
rect=(element.rect[0] * width, element.rect[1] * height,
|
|
207
|
+
element.rect[2] * width, element.rect[3] * height),
|
|
208
|
+
enabled=element.enabled, selected=element.selected, visible=True)
|
|
209
|
+
for index, element in enumerate(snapshot.elements, 1)]
|
|
210
|
+
return AXTree(snapshot.bundle_id, (width, height), nodes)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def screen_title(tree):
|
|
214
|
+
"""The title a screen shows: its navigation bar's, else the top-most text in the upper third."""
|
|
215
|
+
shown = tree.shown()
|
|
216
|
+
for node in shown:
|
|
217
|
+
if node.role == "NavigationBar":
|
|
218
|
+
title = (node.label or node.identifier).strip()
|
|
219
|
+
if title:
|
|
220
|
+
return title[:120]
|
|
221
|
+
top = tree.size[1] / 3
|
|
222
|
+
texts = [node for node in shown if node.role == "StaticText" and node.label.strip() and 0 <= node.rect[1] < top]
|
|
223
|
+
return min(texts, key=lambda node: (node.rect[1], node.rect[0])).label.strip()[:120] if texts else ""
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import traceback
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .. import project_env
|
|
9
|
+
from . import perf, proof, snapshot_run
|
|
10
|
+
from .checks import dump_check, project_root
|
|
11
|
+
from .smart import VIEW_SERVICE, is_real_device
|
|
12
|
+
from .tree import read_tree as read_tree_from
|
|
13
|
+
from .tree import screen_title
|
|
14
|
+
|
|
15
|
+
SCHEMA = "mobster.verify/1"
|
|
16
|
+
EXIT_CODES = {"passed": 0, "failed": 1, "needs_review": 2, "couldnt_run": 3}
|
|
17
|
+
# Smart statuses that end the flow early without an error: an assertion failing after one is class "blocked".
|
|
18
|
+
BLOCKING = frozenset({"blocked", "timeout", "max_steps", "approval_denied", "approval_timeout"})
|
|
19
|
+
STEP_FRAME_CAP, STEP_FRAMES_HEAD, STEP_FRAMES_TAIL = 60, 10, 10
|
|
20
|
+
# couldnt_run classes a crash of the app explains: the run then failed, class app_crashed (the app's failure, not
|
|
21
|
+
# the machine's). A stop, a usage error, a budget or an unknown outcome stays what it is.
|
|
22
|
+
CRASH_EXPLAINS = frozenset({"wda", "launch", "replay"})
|
|
23
|
+
HELD_BEFORE_FLOW = ("Every expectation already held before the flow ran, so this run doesn't show the flow works. "
|
|
24
|
+
"Add an expectation that only holds after it.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CouldntRun(Exception):
|
|
28
|
+
def __init__(self, klass, message, fix=""):
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
self.klass, self.message, self.fix = klass, message, fix or ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def mobster_version():
|
|
34
|
+
from .. import __version__
|
|
35
|
+
return __version__
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _shell_quote(text):
|
|
39
|
+
return text if re.fullmatch(r"[A-Za-z0-9_@%+=:,./-]+", text) else "'" + text.replace("'", "'\\''") + "'"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Verdict:
|
|
43
|
+
def _decide(self):
|
|
44
|
+
"""{verdict, klass, message, fix, results, tree, stable, alert, foreground} by the first rule that applies."""
|
|
45
|
+
if self._ready_at is not None and "flow" not in self.timing and self.mode == "keyless":
|
|
46
|
+
self.timing["flow"] = round(time.monotonic() - self._ready_at, 2)
|
|
47
|
+
self._verdict_ms = self._ms()
|
|
48
|
+
self.perf = self._perf()
|
|
49
|
+
if self.error is not None and self.error["klass"] == "unknown_outcome":
|
|
50
|
+
if not self._left_app:
|
|
51
|
+
self._verdict_frames(None, ())
|
|
52
|
+
return None
|
|
53
|
+
action_failure = self._action_failure()
|
|
54
|
+
if action_failure is not None:
|
|
55
|
+
raise action_failure
|
|
56
|
+
if self.error is not None:
|
|
57
|
+
if not self._left_app:
|
|
58
|
+
self._verdict_frames(None, ())
|
|
59
|
+
crash = self._crash(suspect=True) if self.error["klass"] in CRASH_EXPLAINS else None
|
|
60
|
+
return self._crashed(crash) if crash else None
|
|
61
|
+
failure = self._smart_failure()
|
|
62
|
+
if failure is not None:
|
|
63
|
+
if not self._left_app: # another app is in front on someone's phone: no frame of it
|
|
64
|
+
self._verdict_frames(None, ())
|
|
65
|
+
crash = self._crash(suspect=True) if failure.klass in CRASH_EXPLAINS else None
|
|
66
|
+
if crash:
|
|
67
|
+
return self._crashed(crash)
|
|
68
|
+
raise failure
|
|
69
|
+
from .system_surfaces.play import note as surfaces_note
|
|
70
|
+
started = time.monotonic()
|
|
71
|
+
expect = self.check.expect
|
|
72
|
+
results, tree, stable = self._settle(expect, self.assert_timeout)
|
|
73
|
+
if self._wire is not None:
|
|
74
|
+
results = [*results, *self._wire.judge(self._conditions.net if self._conditions else None)]
|
|
75
|
+
self.timing["assert"] = round(time.monotonic() - started, 2)
|
|
76
|
+
frame = self._verdict_frames(tree, results)
|
|
77
|
+
self._observe_screen(self._relative(frame), tree)
|
|
78
|
+
self._check_pending_screens()
|
|
79
|
+
if self._a11y is not None and self._a11y.screens == 0:
|
|
80
|
+
self._incomplete_screen("No accessibility screen was audited")
|
|
81
|
+
static_findings = self._audit_findings()
|
|
82
|
+
snapshot_found = []
|
|
83
|
+
if self.check.snapshot is not None: # the screen against its baseline, outside the coverage lock
|
|
84
|
+
self.snapshot, snapshot_found = snapshot_run.take(self, tree, self._relative(frame))
|
|
85
|
+
with self._screen_coverage_lock:
|
|
86
|
+
self.findings.extend(self._missing_somewhere())
|
|
87
|
+
if self._a11y is not None:
|
|
88
|
+
self.findings.extend(self._a11y.findings())
|
|
89
|
+
if self._l10n is not None:
|
|
90
|
+
self.findings.extend(self._l10n.findings())
|
|
91
|
+
self.findings.extend(static_findings)
|
|
92
|
+
self.findings.extend(snapshot_found)
|
|
93
|
+
self._screen_verdict_audited = True
|
|
94
|
+
coverage = self._finalize_screen_coverage()
|
|
95
|
+
decided = {"results": results, "tree": tree, "stable": stable, "frame": frame, "alert": self._alert()}
|
|
96
|
+
foreground = self._foreground()
|
|
97
|
+
# A crash anywhere in the run fails it, even when the app came back and the expectations held.
|
|
98
|
+
crash = self._crash(suspect=foreground != self.app.bundle_id)
|
|
99
|
+
if crash:
|
|
100
|
+
return self._crashed(crash, decided)
|
|
101
|
+
failed = [result for result in results if not result.ok]
|
|
102
|
+
status = self._smart_status
|
|
103
|
+
still = "; the screen was still changing" if not stable else ""
|
|
104
|
+
if status == "budget" and failed:
|
|
105
|
+
return {**decided, "verdict": "couldnt_run", "klass": "budget",
|
|
106
|
+
"message": f"Smart reached its ${self.check.max_usd:.2f} spend cap and {len(failed)} of "
|
|
107
|
+
f"{len(results)} expectations failed{still}.",
|
|
108
|
+
"fix": "Raise --max-usd (at most 1.00), then run again."}
|
|
109
|
+
if self._replay_drift:
|
|
110
|
+
return {**decided, "verdict": "failed", "klass": "drift", "message": self._replay_drift,
|
|
111
|
+
"fix": "If the app changed on purpose, run `mobster test --fresh` to record the flow again; "
|
|
112
|
+
"without --no-heal, Smart heals the step and you review the diff."}
|
|
113
|
+
if not foreground:
|
|
114
|
+
return {**decided, "verdict": "couldnt_run", "klass": "wda",
|
|
115
|
+
"message": "WebDriverAgent could not confirm which app was in front at the end.",
|
|
116
|
+
"fix": "Check WebDriverAgent, then run the check again."}
|
|
117
|
+
if foreground and foreground != self.app.bundle_id:
|
|
118
|
+
alert = f"; an alert says \"{decided['alert']}\"" if decided["alert"] else ""
|
|
119
|
+
return {**decided, "verdict": "failed", "klass": "app_not_running",
|
|
120
|
+
"message": f"{self.app.name} wasn't in front at the end: {foreground} was{alert}.",
|
|
121
|
+
"fix": None, "foreground": foreground}
|
|
122
|
+
unavailable = self._observe_business(decided)
|
|
123
|
+
if unavailable is not None:
|
|
124
|
+
return unavailable
|
|
125
|
+
if failed:
|
|
126
|
+
message = f"{len(failed)} of {len(results)} expectations failed{still}"
|
|
127
|
+
if status in BLOCKING:
|
|
128
|
+
note = (self.agent or {}).get("note")
|
|
129
|
+
return {**decided, "verdict": "failed", "klass": "blocked",
|
|
130
|
+
"message": f"Smart ended {status}: " + (note or "no note") + f". {message}", "fix": None}
|
|
131
|
+
return {**decided, "verdict": "failed", "klass": "assertion", "message": message, "fix": None}
|
|
132
|
+
over, unmeasured = perf.judge(self.check.perf_budget, self.perf)
|
|
133
|
+
if over:
|
|
134
|
+
return {**decided, "verdict": "failed", "klass": "budget_exceeded",
|
|
135
|
+
"message": " ".join(item.message for item in over),
|
|
136
|
+
"fix": "Find what the app does while it launches or runs, or raise the budget if the change "
|
|
137
|
+
"is on purpose. The report shows the numbers over the last runs."}
|
|
138
|
+
broken = [item for item in self.findings if item["level"] == "fail"]
|
|
139
|
+
if broken:
|
|
140
|
+
return {**decided, "verdict": "failed", "klass": "finding",
|
|
141
|
+
"message": f"{len(broken)} finding{'' if len(broken) == 1 else 's'}: {broken[0]['message']}",
|
|
142
|
+
"fix": None}
|
|
143
|
+
business_count = len(self.business_binding.spec.expect) if self.business_binding is not None else 0
|
|
144
|
+
business_failed = [item for item in (self.business_result or {}).get("assertions", ()) if not item["ok"]]
|
|
145
|
+
if business_failed:
|
|
146
|
+
return {**decided, "verdict": "failed", "klass": "business",
|
|
147
|
+
"message": f"{len(business_failed)} of {business_count} app-state expectations failed.",
|
|
148
|
+
"fix": None}
|
|
149
|
+
if not coverage["complete"] and (self.check.throughout or self._a11y is not None
|
|
150
|
+
and self._a11y.level == "strict"):
|
|
151
|
+
return {**decided, "verdict": "needs_review", "klass": "screen_coverage",
|
|
152
|
+
"message": f"The run did not observe every required screen: {self._screen_coverage_issue}.",
|
|
153
|
+
"fix": "Check WebDriverAgent and run the check again."}
|
|
154
|
+
unsure = [item for item in self.findings if item["level"] == "review"]
|
|
155
|
+
if unsure:
|
|
156
|
+
return {**decided, "verdict": "needs_review", "klass": "finding",
|
|
157
|
+
"message": f"{len(unsure)} finding{'' if len(unsure) == 1 else 's'} for a person to judge: "
|
|
158
|
+
f"{unsure[0]['message']}", "fix": None}
|
|
159
|
+
if unmeasured:
|
|
160
|
+
return {**decided, "verdict": "needs_review", "klass": "budget_unmeasured",
|
|
161
|
+
"message": " ".join(item.message for item in unmeasured),
|
|
162
|
+
"fix": "Run the check on a simulator, or drop the budget key this run cannot measure."
|
|
163
|
+
if is_real_device(self.manager) else "Run the check again: the simulator gave no reading this "
|
|
164
|
+
"time, which says nothing about the budget."}
|
|
165
|
+
if not (expect or self.check.wire or self.check.throughout or self.check.somewhere or self.check.audit
|
|
166
|
+
or self.check.a11y or self.check.surfaces or business_count
|
|
167
|
+
or self.check.snapshot):
|
|
168
|
+
return {**decided, "verdict": "needs_review", "klass": "no_assertions",
|
|
169
|
+
"message": "The check has no expectations, so nothing decided the run. The frames show what "
|
|
170
|
+
"happened.", "fix": "Add expect: what must be true at the end."}
|
|
171
|
+
held_ax = all(self.baseline.get("held") or [False]) if expect else True
|
|
172
|
+
held_business = (all(self.baseline.get("businessHeld") or [False])
|
|
173
|
+
if business_count else True)
|
|
174
|
+
if (self.check.steps and not self.check.wire and self.baseline.get("taken") and (expect or business_count)
|
|
175
|
+
and held_ax and held_business):
|
|
176
|
+
return {**decided, "verdict": "needs_review", "klass": "held_before_flow", "message": HELD_BEFORE_FLOW,
|
|
177
|
+
"fix": None}
|
|
178
|
+
return {**decided, "verdict": "passed", "klass": None,
|
|
179
|
+
"message": f"All {len(results) + business_count} expectations held{still}{surfaces_note(self)}",
|
|
180
|
+
"fix": None}
|
|
181
|
+
|
|
182
|
+
def _alert(self):
|
|
183
|
+
try:
|
|
184
|
+
text = self.driver.call("GET", "/alert/text", timeout=3)
|
|
185
|
+
return text[:300] if isinstance(text, str) and text.strip() else None
|
|
186
|
+
except Exception:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def _verdict_frames(self, tree, results):
|
|
190
|
+
"""NN-verdict (the screen) and NN-verdict-ax (the same with the accessibility overlay)."""
|
|
191
|
+
if self.driver is None or self.target is None:
|
|
192
|
+
return None
|
|
193
|
+
if is_real_device(self.manager):
|
|
194
|
+
foreground = self._foreground()
|
|
195
|
+
if not foreground or (foreground != self.app.bundle_id and not VIEW_SERVICE.fullmatch(foreground)):
|
|
196
|
+
self.log("no verdict frame: the check's app is not confirmed in front")
|
|
197
|
+
return None
|
|
198
|
+
plain = self.frame("verdict")
|
|
199
|
+
if plain is None:
|
|
200
|
+
return None
|
|
201
|
+
self._give_after_frame(plain)
|
|
202
|
+
try:
|
|
203
|
+
if tree is None:
|
|
204
|
+
tree = read_tree_from(self.driver, pause=self._sleep)
|
|
205
|
+
from .overlay import draw_overlay
|
|
206
|
+
asserted = [(result, list(result.paths)) for result in results]
|
|
207
|
+
path = self._frame_path("verdict-ax")
|
|
208
|
+
draw_overlay(plain, tree, asserted, path)
|
|
209
|
+
self._register_frame(path, "verdict-ax")
|
|
210
|
+
except Exception as error:
|
|
211
|
+
self.log(f"no overlay frame: {type(error).__name__}: {error}")
|
|
212
|
+
return plain
|
|
213
|
+
|
|
214
|
+
def _cap_frames(self):
|
|
215
|
+
"""At most 60 step frames: the first 10, the last 10, and an even spread of the rest."""
|
|
216
|
+
frames = self.step_frames
|
|
217
|
+
if len(frames) <= STEP_FRAME_CAP:
|
|
218
|
+
return
|
|
219
|
+
head, tail, middle = frames[:STEP_FRAMES_HEAD], frames[-STEP_FRAMES_TAIL:], \
|
|
220
|
+
frames[STEP_FRAMES_HEAD:-STEP_FRAMES_TAIL]
|
|
221
|
+
room = STEP_FRAME_CAP - len(head) - len(tail)
|
|
222
|
+
stride = 2
|
|
223
|
+
while -(-len(middle) // stride) > room:
|
|
224
|
+
stride += 1
|
|
225
|
+
keep = set(head) | set(tail) | set(middle[::stride])
|
|
226
|
+
dropped = set(frames) - keep
|
|
227
|
+
for relative in dropped:
|
|
228
|
+
try:
|
|
229
|
+
(self.run_dir / relative).unlink()
|
|
230
|
+
except OSError:
|
|
231
|
+
pass
|
|
232
|
+
self.frames = [frame for frame in self.frames if frame not in dropped]
|
|
233
|
+
self.step_frames = [frame for frame in frames if frame in keep]
|
|
234
|
+
for step in self.steps:
|
|
235
|
+
if step.get("frame") in dropped:
|
|
236
|
+
step["frame"] = None
|
|
237
|
+
|
|
238
|
+
def _compose(self, decided):
|
|
239
|
+
coverage = self._finalize_screen_coverage()
|
|
240
|
+
check, app, target = self.check, self.app, self.target
|
|
241
|
+
self._cap_frames()
|
|
242
|
+
if decided is None:
|
|
243
|
+
error = self.error or {"klass": "internal", "message": "The run ended without a verdict.", "fix": ""}
|
|
244
|
+
verdict, klass, message, fix = "couldnt_run", error["klass"], error["message"], error.get("fix") or None
|
|
245
|
+
results, stable, alert, frame = [], None, None, None
|
|
246
|
+
summary = message
|
|
247
|
+
else:
|
|
248
|
+
verdict, klass, message, fix = decided["verdict"], decided["klass"], decided["message"], decided["fix"]
|
|
249
|
+
results, stable, alert, frame = decided["results"], decided["stable"], decided["alert"], decided["frame"]
|
|
250
|
+
failed = [result for result in results if not result.ok]
|
|
251
|
+
if verdict == "passed":
|
|
252
|
+
total = len(results) + len(check.business.expect if check.business is not None else ())
|
|
253
|
+
summary = f"{total} of {total} expectations held"
|
|
254
|
+
elif klass == "finding":
|
|
255
|
+
summary = message
|
|
256
|
+
elif klass in ("assertion", "blocked", "budget"):
|
|
257
|
+
summary = f"{failed[0].text} failed: {failed[0].observed}"
|
|
258
|
+
else:
|
|
259
|
+
summary = message
|
|
260
|
+
frame_rel = Path(frame).relative_to(self.run_dir).as_posix() if frame else None
|
|
261
|
+
assertions = [{"index": index, "assertion": assertion.to_dict(), "text": result.text, "ok": result.ok,
|
|
262
|
+
"observed": result.observed, "matches": [dict(match) for match in result.matches],
|
|
263
|
+
"frame": frame_rel}
|
|
264
|
+
for index, (assertion, result) in enumerate(zip((*check.expect, *check.wire), results))]
|
|
265
|
+
draft = self.run_dir / "check.yaml"
|
|
266
|
+
ax = next((f for f in reversed(self.frames) if f.endswith("-verdict-ax.jpg")), None)
|
|
267
|
+
plain = frame_rel or next((f for f in reversed(self.frames) if f.endswith("-verdict.jpg")), None)
|
|
268
|
+
last_step = self.step_frames[-1] if self.step_frames else None
|
|
269
|
+
proof = [str(self.run_dir / relative) for relative in (ax, plain, last_step) if relative]
|
|
270
|
+
result = {
|
|
271
|
+
"schema": SCHEMA,
|
|
272
|
+
"run_id": self.run_id,
|
|
273
|
+
"verdict": verdict,
|
|
274
|
+
"exit_code": EXIT_CODES[verdict],
|
|
275
|
+
"summary": self._scrub(summary),
|
|
276
|
+
"reason": {"class": klass, "message": self._scrub(message), "fix": fix},
|
|
277
|
+
"mode": self.mode,
|
|
278
|
+
"check": {"name": check.name, "steps": list(check.steps),
|
|
279
|
+
"expect": [assertion.to_dict() for assertion in (*check.expect, *check.wire)]
|
|
280
|
+
+ ([check.snapshot.to_dict()] if check.snapshot else []),
|
|
281
|
+
**({"throughout": [assertion.to_dict() for assertion in check.throughout]}
|
|
282
|
+
if check.throughout else {}),
|
|
283
|
+
**({"somewhere": [assertion.to_dict() for assertion in check.somewhere]}
|
|
284
|
+
if check.somewhere else {}),
|
|
285
|
+
**({"audit": list(check.audit)} if check.audit else {}),
|
|
286
|
+
**({"a11y": check.a11y} if check.a11y else {}), "source": check.source},
|
|
287
|
+
"app": ({"bundle_id": app.bundle_id, "name": app.name, "version": app.version,
|
|
288
|
+
"path": str(Path(app.path).resolve()) if app.path else check.app_path}
|
|
289
|
+
if app is not None else {"bundle_id": check.bundle_id, "name": None, "version": None,
|
|
290
|
+
"path": check.app_path}),
|
|
291
|
+
"device": ({"name": target.name, "udid": target.udid, "type": target.device_type,
|
|
292
|
+
"runtime": target.runtime} if target is not None
|
|
293
|
+
else {"name": None, "udid": None, "type": check.device, "runtime": check.runtime}),
|
|
294
|
+
"assertions": assertions,
|
|
295
|
+
**({"screen": {"title": screen_title(decided["tree"])}} if decided and decided.get("tree") else {}),
|
|
296
|
+
"findings": [dict(item) for item in self.findings],
|
|
297
|
+
**({"snapshot": dict(self.snapshot)} if self.snapshot else {}),
|
|
298
|
+
"screenCoverage": coverage,
|
|
299
|
+
**({"conditions": {**self._conditions.report(), "cleanup": self.conditions_cleanup}}
|
|
300
|
+
if self._conditions is not None else {}),
|
|
301
|
+
"perf": self.perf,
|
|
302
|
+
"baseline": dict(self.baseline),
|
|
303
|
+
"stable": stable,
|
|
304
|
+
"steps": [dict(step) for step in self.steps],
|
|
305
|
+
"agent": dict(self.agent) if self.mode == "smart" and self.agent is not None else None,
|
|
306
|
+
"frames": list(self.frames),
|
|
307
|
+
"proof": proof,
|
|
308
|
+
"alert": alert,
|
|
309
|
+
"report": str(self.run_dir / "report.html"),
|
|
310
|
+
"draft_check": str(draft),
|
|
311
|
+
"run_dir": str(self.run_dir),
|
|
312
|
+
"repro": f"mobster verify --check {_shell_quote(str(draft))}",
|
|
313
|
+
"seconds": round(time.monotonic() - self._t0, 1),
|
|
314
|
+
"timing": {key: self.timing[key] for key in ("simulator", "install", "launch", "flow", "assert", "report")
|
|
315
|
+
if key in self.timing},
|
|
316
|
+
"cost_usd": self.cost_usd if self.mode == "smart" else 0,
|
|
317
|
+
"mobster": {"version": mobster_version()},
|
|
318
|
+
}
|
|
319
|
+
evidence = self._evidence_summary(verdict)
|
|
320
|
+
if check.business is not None:
|
|
321
|
+
result["check"]["business"] = check.business.to_dict()
|
|
322
|
+
result["business"] = dict(self.business_result or {})
|
|
323
|
+
if evidence is not None:
|
|
324
|
+
result["evidence"] = evidence
|
|
325
|
+
step_proof = self._step_proof(verdict, klass, stable, decided, plain, ax)
|
|
326
|
+
if step_proof is not None:
|
|
327
|
+
result["step_proof"] = step_proof
|
|
328
|
+
return project_env.mask_deep(result, self.secrets)
|
|
329
|
+
|
|
330
|
+
def _step_proof(self, verdict, klass, stable, decided, plain, ax):
|
|
331
|
+
"""Each step's log lines and network rows, the frame and tree the verdict used, and the cause line."""
|
|
332
|
+
end = self._failed_ms if verdict == "couldnt_run" and self._failed_ms is not None else self._verdict_ms
|
|
333
|
+
end = self._ms() if end is None else end
|
|
334
|
+
try:
|
|
335
|
+
lines = self.evidence.lines() if self.evidence is not None else []
|
|
336
|
+
return proof.build(verdict=verdict, klass=klass, stable=stable, steps=self.steps, lines=lines, end_ms=end,
|
|
337
|
+
frame=plain, ax_frame=ax, tree=decided.get("tree") if decided else None,
|
|
338
|
+
verdict_ms=self._verdict_ms)
|
|
339
|
+
except Exception as error:
|
|
340
|
+
self.log(f"step proof: none ({type(error).__name__}: {error})")
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
def _evidence_summary(self, verdict):
|
|
344
|
+
if self.evidence is None:
|
|
345
|
+
return None
|
|
346
|
+
center = self._failed_ms if verdict == "couldnt_run" and self._failed_ms is not None else self._verdict_ms
|
|
347
|
+
try:
|
|
348
|
+
return self.evidence.summary(center_ms=center if center is not None else self._ms(), steps=self.steps,
|
|
349
|
+
failed=verdict != "passed")
|
|
350
|
+
except Exception as error:
|
|
351
|
+
self.log(f"evidence: no summary ({type(error).__name__}: {error})")
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
def _write(self, result):
|
|
355
|
+
"""check.yaml, result.json and report.html."""
|
|
356
|
+
started = time.monotonic()
|
|
357
|
+
root = project_root(self.run_dir) if self.runs_dir.parent.name == ".mobster" else None
|
|
358
|
+
try:
|
|
359
|
+
(self.run_dir / "check.yaml").write_text(dump_check(self.check, project_root=root), encoding="utf-8")
|
|
360
|
+
except Exception as error:
|
|
361
|
+
self.log(f"check.yaml not written: {type(error).__name__}: {error}")
|
|
362
|
+
from .report import write_report
|
|
363
|
+
try:
|
|
364
|
+
write_report(self.run_dir, result, scrub=self._scrub)
|
|
365
|
+
except Exception as error:
|
|
366
|
+
self.log("".join(traceback.format_exception(type(error), error, error.__traceback__)))
|
|
367
|
+
result["timing"]["report"] = round(time.monotonic() - started, 2)
|
|
368
|
+
result["seconds"] = round(time.monotonic() - self._t0, 1)
|
|
369
|
+
self._write_json(result)
|
|
370
|
+
if self.secrets:
|
|
371
|
+
project_env.sweep_files(self.run_dir, self.secrets)
|
|
372
|
+
|
|
373
|
+
def _write_json(self, result):
|
|
374
|
+
text = self._scrub(json.dumps(result, indent=1, ensure_ascii=False, allow_nan=False))
|
|
375
|
+
temporary = self.run_dir / "result.json.tmp"
|
|
376
|
+
temporary.write_text(text + "\n", encoding="utf-8")
|
|
377
|
+
os.replace(temporary, self.run_dir / "result.json")
|