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,483 @@
|
|
|
1
|
+
"""A check's device conditions. A real iPhone gets only appearance and locale; each other key is skipped and named."""
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .assertions import CheckError
|
|
11
|
+
|
|
12
|
+
KEYS = ("appearance", "content_size", "increase_contrast", "locale", "location", "route", "permissions", "media",
|
|
13
|
+
"status_bar", "push", "biometric", "network")
|
|
14
|
+
ON_A_PHONE = ("appearance", "locale")
|
|
15
|
+
MATRIX_KEYS = ("appearance", "content_size", "locale") # a list here makes the check run once per combination
|
|
16
|
+
MATRIX_LIMIT = 24
|
|
17
|
+
APPEARANCES = ("light", "dark")
|
|
18
|
+
NETWORKS = ("offline", "slow-3g", "lte", "flaky") # netproxy.PROFILES; a test keeps the two in step
|
|
19
|
+
CONTRASTS = ("enabled", "disabled")
|
|
20
|
+
BIOMETRICS = ("enrolled", "not_enrolled")
|
|
21
|
+
WIFI_MODES = ("searching", "failed", "active")
|
|
22
|
+
CELLULAR_MODES = ("notSupported", "searching", "failed", "active")
|
|
23
|
+
ROUTE_POINTS = (2, 50) # simctl needs two waypoints at least
|
|
24
|
+
CONTENT_SIZES = ("extra-small", "small", "medium", "large", "extra-large", "extra-extra-large",
|
|
25
|
+
"extra-extra-extra-large", "accessibility-medium", "accessibility-large",
|
|
26
|
+
"accessibility-extra-large", "accessibility-extra-extra-large",
|
|
27
|
+
"accessibility-extra-extra-extra-large")
|
|
28
|
+
SERVICES = ("all", "calendar", "contacts-limited", "contacts", "location", "location-always", "photos-add",
|
|
29
|
+
"photos", "media-library", "microphone", "motion", "reminders", "siri")
|
|
30
|
+
ACTIONS = ("grant", "revoke", "reset")
|
|
31
|
+
MEDIA_SUFFIXES = (".jpg", ".jpeg", ".png", ".heic", ".gif", ".mov", ".mp4", ".m4v", ".vcf")
|
|
32
|
+
MEDIA_LIMIT = 20
|
|
33
|
+
PAYLOAD_LIMIT = 4096 # APNs' limit
|
|
34
|
+
BATTERY_STATES = ("charging", "charged", "discharging")
|
|
35
|
+
DATA_NETWORKS = ("hide", "wifi", "3g", "4g", "lte", "lte-a", "lte+", "5g", "5g+", "5g-uwb", "5g-uc")
|
|
36
|
+
STATUS_BAR_KEYS = ("time", "battery_level", "battery_state", "wifi_mode", "wifi_bars", "cellular_mode", "cellular_bars",
|
|
37
|
+
"data_network", "operator_name")
|
|
38
|
+
LOCALE = re.compile(r"([a-z]{2,3})(?:[-_]([A-Za-z]{4}))?(?:[-_]([A-Z]{2}|[0-9]{3}))?")
|
|
39
|
+
NETWORK_ON_A_PHONE = ("{what} shapes the traffic of a simulator, and Mobster can't do that to a real iPhone. Run the "
|
|
40
|
+
"check on a simulator.")
|
|
41
|
+
WIRE_ON_A_PHONE = ("expect: request, no_request and storage read a simulator's proxy and app container, and Mobster "
|
|
42
|
+
"doesn't set up the proxy on a real iPhone.")
|
|
43
|
+
ON_A_PHONE_ONLY = ("{key} changes the state of a phone, so Mobster leaves it alone on a real iPhone. Run the check "
|
|
44
|
+
"on a simulator to use it.")
|
|
45
|
+
SIMCTL_TIMEOUT = 60
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConditionError(Exception):
|
|
49
|
+
"""A condition that couldn't be set: what happened, then what to do."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, message, fix=""):
|
|
52
|
+
super().__init__(message)
|
|
53
|
+
self.message, self.fix = message, fix
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _choice(value, key, options):
|
|
57
|
+
if value not in options:
|
|
58
|
+
close = difflib.get_close_matches(str(value), options, n=1)
|
|
59
|
+
raise CheckError(f"conditions.{key} {value!r} isn't one Mobster knows; "
|
|
60
|
+
+ (f"did you mean {close[0]}? " if close else "") + f"Use one of {', '.join(options)}.")
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _integer(value, where, low, high):
|
|
65
|
+
if isinstance(value, bool) or not isinstance(value, int) or not low <= value <= high:
|
|
66
|
+
raise CheckError(f"{where} must be a whole number from {low} to {high}.")
|
|
67
|
+
return value
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _file(value, where, base_dir, suffixes=None):
|
|
71
|
+
if isinstance(value, bool) or not isinstance(value, str) or not value.strip():
|
|
72
|
+
raise CheckError(f"{where} must be a file path. Write it as text, relative to the project.")
|
|
73
|
+
path = Path(value.strip()).expanduser()
|
|
74
|
+
path = (path if path.is_absolute() else Path(base_dir or Path.cwd()) / path).resolve()
|
|
75
|
+
if suffixes and path.suffix.lower() not in suffixes:
|
|
76
|
+
raise CheckError(f"{where} {value!r} isn't a kind of file Mobster adds. Use {', '.join(suffixes)}.")
|
|
77
|
+
if not path.is_file():
|
|
78
|
+
raise CheckError(f"{where} {value!r} doesn't exist. Check the path, which is relative to the project.")
|
|
79
|
+
return str(path)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _location(value, where="conditions.location"):
|
|
83
|
+
if isinstance(value, str):
|
|
84
|
+
parts = value.split(",")
|
|
85
|
+
try:
|
|
86
|
+
value = {"latitude": float(parts[0]), "longitude": float(parts[1])} if len(parts) == 2 else None
|
|
87
|
+
except ValueError:
|
|
88
|
+
value = None
|
|
89
|
+
if value is None:
|
|
90
|
+
raise CheckError(f"{where} must be {{latitude: 52.52, longitude: 13.405}} or \"52.52,13.405\".")
|
|
91
|
+
if not isinstance(value, dict) or set(value) != {"latitude", "longitude"}:
|
|
92
|
+
raise CheckError(f"{where} needs a latitude and a longitude, such as {{latitude: 52.52, longitude: 13.405}}.")
|
|
93
|
+
out = {}
|
|
94
|
+
for key, limit in (("latitude", 90), ("longitude", 180)):
|
|
95
|
+
number = value[key]
|
|
96
|
+
if isinstance(number, bool) or not isinstance(number, (int, float)) or not math.isfinite(number) \
|
|
97
|
+
or abs(number) > limit:
|
|
98
|
+
raise CheckError(f"{where}.{key} must be a number from -{limit} to {limit}.")
|
|
99
|
+
out[key] = float(number)
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _route(value):
|
|
104
|
+
if not isinstance(value, dict) or set(value) - {"waypoints", "speed"} or "waypoints" not in value:
|
|
105
|
+
raise CheckError("conditions.route needs waypoints and may have a speed, such as "
|
|
106
|
+
"{waypoints: [\"52.52,13.40\", \"52.51,13.38\"], speed: 20}.")
|
|
107
|
+
points = value["waypoints"]
|
|
108
|
+
low, high = ROUTE_POINTS
|
|
109
|
+
if not isinstance(points, list) or not low <= len(points) <= high:
|
|
110
|
+
raise CheckError(f"conditions.route.waypoints must list {low} to {high} places.")
|
|
111
|
+
speed = value.get("speed", 20)
|
|
112
|
+
if isinstance(speed, bool) or not isinstance(speed, (int, float)) or not 0 < speed <= 1000:
|
|
113
|
+
raise CheckError("conditions.route.speed must be a number of metres per second, above 0 and up to 1000.")
|
|
114
|
+
return {"waypoints": [_location(point, f"conditions.route.waypoints[{index}]")
|
|
115
|
+
for index, point in enumerate(points)],
|
|
116
|
+
"speed": float(speed)}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _locale(value):
|
|
120
|
+
found = LOCALE.fullmatch(value) if isinstance(value, str) else None
|
|
121
|
+
if not found:
|
|
122
|
+
raise CheckError(f"conditions.locale {value!r} isn't a locale. Write a language and region, such as de_DE "
|
|
123
|
+
"or fr-CA.")
|
|
124
|
+
language, script, region = found.groups()
|
|
125
|
+
return "_".join(filter(None, [language, script, region]))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _permissions(value):
|
|
129
|
+
if not isinstance(value, dict) or not value:
|
|
130
|
+
raise CheckError("conditions.permissions must list services, such as {photos: grant, location: revoke}.")
|
|
131
|
+
out = {}
|
|
132
|
+
for service, action in value.items():
|
|
133
|
+
if service not in SERVICES:
|
|
134
|
+
close = difflib.get_close_matches(str(service), SERVICES, n=1)
|
|
135
|
+
raise CheckError(f"conditions.permissions.{service} isn't a service simctl knows; "
|
|
136
|
+
+ (f"did you mean {close[0]}? " if close else "") + f"Use one of {', '.join(SERVICES)}.")
|
|
137
|
+
out[service] = _choice(action, f"permissions.{service}", ACTIONS)
|
|
138
|
+
return out
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _status_bar(value):
|
|
142
|
+
if not isinstance(value, dict) or not value:
|
|
143
|
+
raise CheckError("conditions.status_bar must set something, such as {time: \"9:41\", battery_level: 100}.")
|
|
144
|
+
for key in value:
|
|
145
|
+
if key not in STATUS_BAR_KEYS:
|
|
146
|
+
close = difflib.get_close_matches(str(key), STATUS_BAR_KEYS, n=1)
|
|
147
|
+
raise CheckError(f"conditions.status_bar.{key} isn't a setting; "
|
|
148
|
+
+ (f"did you mean {close[0]}? " if close else "") + f"Use {', '.join(STATUS_BAR_KEYS)}.")
|
|
149
|
+
out = {}
|
|
150
|
+
for key, item in value.items():
|
|
151
|
+
where = f"conditions.status_bar.{key}"
|
|
152
|
+
if key == "battery_level":
|
|
153
|
+
out[key] = _integer(item, where, 0, 100)
|
|
154
|
+
elif key == "wifi_bars":
|
|
155
|
+
out[key] = _integer(item, where, 0, 3)
|
|
156
|
+
elif key == "cellular_bars":
|
|
157
|
+
out[key] = _integer(item, where, 0, 4)
|
|
158
|
+
elif key == "battery_state":
|
|
159
|
+
out[key] = _choice(item, "status_bar.battery_state", BATTERY_STATES)
|
|
160
|
+
elif key == "wifi_mode":
|
|
161
|
+
out[key] = _choice(item, "status_bar.wifi_mode", WIFI_MODES)
|
|
162
|
+
elif key == "cellular_mode":
|
|
163
|
+
out[key] = _choice(item, "status_bar.cellular_mode", CELLULAR_MODES)
|
|
164
|
+
elif key == "data_network":
|
|
165
|
+
out[key] = _choice(item, "status_bar.data_network", DATA_NETWORKS)
|
|
166
|
+
else:
|
|
167
|
+
if isinstance(item, bool) or not isinstance(item, str) or not item.strip() or len(item) > 40:
|
|
168
|
+
raise CheckError(f"{where} must be text of up to 40 characters; quote it, as in \"9:41\".")
|
|
169
|
+
out[key] = item.strip()
|
|
170
|
+
return out
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _payload(value, base_dir):
|
|
174
|
+
path = _file(value, "conditions.push", base_dir, (".json", ".apns"))
|
|
175
|
+
if Path(path).stat().st_size > PAYLOAD_LIMIT:
|
|
176
|
+
raise CheckError(f"conditions.push {value!r} is larger than {PAYLOAD_LIMIT:,} bytes, the most a push may "
|
|
177
|
+
"carry. Trim the payload.")
|
|
178
|
+
try:
|
|
179
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
180
|
+
except (OSError, ValueError):
|
|
181
|
+
raise CheckError(f"conditions.push {value!r} isn't valid JSON. Fix the file.") from None
|
|
182
|
+
if not isinstance(data, dict) or not isinstance(data.get("aps"), dict):
|
|
183
|
+
raise CheckError(f"conditions.push {value!r} has no \"aps\" object. A push payload looks like "
|
|
184
|
+
"{\"aps\": {\"alert\": \"Your order shipped\"}}.")
|
|
185
|
+
return path
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _matrix(key, items):
|
|
189
|
+
if not items:
|
|
190
|
+
raise CheckError(f"conditions.{key} lists nothing. List at least one, or write one value.")
|
|
191
|
+
values = []
|
|
192
|
+
for item in items:
|
|
193
|
+
one = parse({key: item})[key]
|
|
194
|
+
if one not in values:
|
|
195
|
+
values.append(one)
|
|
196
|
+
return values
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def combinations(conditions):
|
|
200
|
+
"""The scalar conditions of each run a check's matrix makes (appearance, then text size, then locale): one dict for
|
|
201
|
+
a check with no lists."""
|
|
202
|
+
conditions = conditions or {}
|
|
203
|
+
out = [dict(conditions)]
|
|
204
|
+
for key in MATRIX_KEYS:
|
|
205
|
+
if isinstance(conditions.get(key), list):
|
|
206
|
+
out = [{**one, key: value} for one in out for value in conditions[key]]
|
|
207
|
+
return out
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def is_matrix(conditions):
|
|
211
|
+
return any(isinstance(value, list) for key, value in (conditions or {}).items() if key in MATRIX_KEYS)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def parse(value, base_dir=None):
|
|
215
|
+
"""The normalized ``conditions`` block of a check file: {} when absent. Raises CheckError."""
|
|
216
|
+
if value is None:
|
|
217
|
+
return {}
|
|
218
|
+
if not isinstance(value, dict) or not value:
|
|
219
|
+
raise CheckError("conditions must set something, such as {appearance: dark, locale: de_DE}.")
|
|
220
|
+
for key in value:
|
|
221
|
+
if key not in KEYS:
|
|
222
|
+
close = difflib.get_close_matches(str(key), KEYS, n=1)
|
|
223
|
+
raise CheckError(f"conditions.{key} isn't a condition; "
|
|
224
|
+
+ (f"did you mean {close[0]}? " if close else "") + f"Use {', '.join(KEYS)}.")
|
|
225
|
+
if "location" in value and "route" in value:
|
|
226
|
+
raise CheckError("conditions.location and conditions.route both set where the device is. Keep one.")
|
|
227
|
+
out = {}
|
|
228
|
+
for key in KEYS:
|
|
229
|
+
if key not in value:
|
|
230
|
+
continue
|
|
231
|
+
item = value[key]
|
|
232
|
+
if key in MATRIX_KEYS and isinstance(item, list):
|
|
233
|
+
out[key] = _matrix(key, item)
|
|
234
|
+
continue
|
|
235
|
+
if key == "appearance":
|
|
236
|
+
out[key] = _choice(item, key, APPEARANCES)
|
|
237
|
+
elif key == "content_size":
|
|
238
|
+
out[key] = _choice(item, key, CONTENT_SIZES)
|
|
239
|
+
elif key == "locale":
|
|
240
|
+
out[key] = _locale(item)
|
|
241
|
+
elif key == "increase_contrast":
|
|
242
|
+
out[key] = _choice(item, key, CONTRASTS)
|
|
243
|
+
elif key == "location":
|
|
244
|
+
out[key] = _location(item)
|
|
245
|
+
elif key == "route":
|
|
246
|
+
out[key] = _route(item)
|
|
247
|
+
elif key == "permissions":
|
|
248
|
+
out[key] = _permissions(item)
|
|
249
|
+
elif key == "status_bar":
|
|
250
|
+
out[key] = _status_bar(item)
|
|
251
|
+
elif key == "push":
|
|
252
|
+
out[key] = _payload(item, base_dir)
|
|
253
|
+
elif key == "biometric":
|
|
254
|
+
out[key] = _choice(item, key, BIOMETRICS)
|
|
255
|
+
elif key == "network":
|
|
256
|
+
out[key] = _choice(item, key, NETWORKS)
|
|
257
|
+
else:
|
|
258
|
+
if not isinstance(item, list) or not item:
|
|
259
|
+
raise CheckError("conditions.media must list files, such as [fixtures/receipt.jpg].")
|
|
260
|
+
if len(item) > MEDIA_LIMIT:
|
|
261
|
+
raise CheckError(f"conditions.media has more than {MEDIA_LIMIT} files. Add fewer.")
|
|
262
|
+
out[key] = [_file(path, f"conditions.media[{index}]", base_dir, MEDIA_SUFFIXES)
|
|
263
|
+
for index, path in enumerate(item)]
|
|
264
|
+
if len(combinations(out)) > MATRIX_LIMIT:
|
|
265
|
+
raise CheckError(f"conditions lists {len(combinations(out))} combinations; the most is {MATRIX_LIMIT}. "
|
|
266
|
+
"Split the check.")
|
|
267
|
+
return out
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def to_dict(conditions, project_root=None):
|
|
271
|
+
"""The file shape of ``conditions``, with paths relative to ``project_root`` where they sit inside it."""
|
|
272
|
+
def relative(path):
|
|
273
|
+
try:
|
|
274
|
+
return str(Path(path).relative_to(Path(project_root).resolve()))
|
|
275
|
+
except (ValueError, TypeError):
|
|
276
|
+
return path
|
|
277
|
+
out = {}
|
|
278
|
+
for key, value in conditions.items():
|
|
279
|
+
if key == "push":
|
|
280
|
+
out[key] = relative(value)
|
|
281
|
+
elif key == "media":
|
|
282
|
+
out[key] = [relative(path) for path in value]
|
|
283
|
+
else:
|
|
284
|
+
out[key] = json.loads(json.dumps(value))
|
|
285
|
+
return out
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def simctl(args):
|
|
289
|
+
"""(exit code, output) of `xcrun simctl ...`."""
|
|
290
|
+
try:
|
|
291
|
+
done = subprocess.run(["xcrun", "simctl", *map(str, args)], capture_output=True, text=True, errors="replace",
|
|
292
|
+
timeout=SIMCTL_TIMEOUT, stdin=subprocess.DEVNULL)
|
|
293
|
+
except FileNotFoundError:
|
|
294
|
+
return 127, "Xcode's command-line tools aren't installed."
|
|
295
|
+
except subprocess.TimeoutExpired:
|
|
296
|
+
return 124, f"simctl didn't answer in {SIMCTL_TIMEOUT} s."
|
|
297
|
+
return done.returncode, ((done.stdout or "") + (done.stderr or "")).strip()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class _Notifier:
|
|
301
|
+
"""What sim.biometrics needs of a manager (``simctl``), over this module's ``run(args) -> (code, output)``."""
|
|
302
|
+
|
|
303
|
+
def __init__(self, run):
|
|
304
|
+
self._run = run
|
|
305
|
+
|
|
306
|
+
def simctl(self, *args, timeout=60):
|
|
307
|
+
from ..sim.simctl import Result
|
|
308
|
+
code, output = self._run(list(args))
|
|
309
|
+
return Result(code, output if code == 0 else "", "" if code == 0 else output)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class Applier:
|
|
313
|
+
"""Sets one run's conditions; restoration is a journaled device action, not best-effort cleanup."""
|
|
314
|
+
|
|
315
|
+
def __init__(self, spec, *, udid, bundle, device=None, run=simctl, actions=None, stubs=(), net=None, watch=None):
|
|
316
|
+
self.spec, self.udid, self.bundle, self.device, self.run = dict(spec), udid, bundle, device, run
|
|
317
|
+
self.actions, self.stubs, self._net_factory, self.net = actions, tuple(stubs), net, None
|
|
318
|
+
self.watch = watch # host patterns of wire assertions (wire.py): None when the check has none
|
|
319
|
+
self.real = device is not None
|
|
320
|
+
self.applied, self.skipped, self._undo, self._env = [], [], [], {}
|
|
321
|
+
for key in KEYS:
|
|
322
|
+
if key in self.spec and self.real and key not in ON_A_PHONE and key != "network":
|
|
323
|
+
self.skipped.append({"key": key, "why": ON_A_PHONE_ONLY.format(key=key)})
|
|
324
|
+
|
|
325
|
+
def _wanted(self, key):
|
|
326
|
+
return key in self.spec and not any(item["key"] == key for item in self.skipped)
|
|
327
|
+
|
|
328
|
+
def launch_args(self):
|
|
329
|
+
"""The app's language and region, as the arguments iOS reads at launch."""
|
|
330
|
+
if not self._wanted("locale"):
|
|
331
|
+
return ()
|
|
332
|
+
locale = self.spec["locale"]
|
|
333
|
+
return ("-AppleLanguages", f"({locale.split('_')[0]})", "-AppleLocale", locale)
|
|
334
|
+
|
|
335
|
+
def _simctl(self, args, what, operation="CONDITION_SET"):
|
|
336
|
+
if self.actions is None:
|
|
337
|
+
raise ConditionError("Device condition changes need an action journal.")
|
|
338
|
+
with self.actions.action(operation):
|
|
339
|
+
code, output = self.run(args)
|
|
340
|
+
if code != 0:
|
|
341
|
+
last = next((line.strip() for line in reversed(output.splitlines()) if line.strip()), "")
|
|
342
|
+
raise ConditionError(f"Mobster couldn't set {what} on the simulator"
|
|
343
|
+
+ (f": {last[:200]}" if last else "") + ".",
|
|
344
|
+
"Run `mobster sim doctor`, then try again. Remove the condition to run "
|
|
345
|
+
"the check without it.")
|
|
346
|
+
return output
|
|
347
|
+
|
|
348
|
+
def launch_env(self):
|
|
349
|
+
"""The environment the app launches with: the shim that points it at the run's proxy, when there is one."""
|
|
350
|
+
return dict(self._env)
|
|
351
|
+
|
|
352
|
+
def _start_network(self):
|
|
353
|
+
if not (self.spec.get("network") or self.stubs or self.watch is not None):
|
|
354
|
+
return
|
|
355
|
+
from . import netrun
|
|
356
|
+
if self.actions is None:
|
|
357
|
+
raise ConditionError("Device condition changes need an action journal.")
|
|
358
|
+
try:
|
|
359
|
+
make = self._net_factory or netrun.NetRun
|
|
360
|
+
extra = {} if self.watch is None else {"watch": self.watch}
|
|
361
|
+
net = make(self.spec.get("network"), self.stubs, udid=self.udid, run=self.run, **extra)
|
|
362
|
+
self.net = net
|
|
363
|
+
with self.actions.action("CONDITION_SET"):
|
|
364
|
+
self._env = net.start()
|
|
365
|
+
except netrun.NetError as error:
|
|
366
|
+
raise ConditionError(error.message, error.fix) from None
|
|
367
|
+
self.applied += [key for key, on in (("network", self.spec.get("network")), ("stubs", self.stubs)) if on]
|
|
368
|
+
|
|
369
|
+
def before_launch(self):
|
|
370
|
+
if self.real:
|
|
371
|
+
if self.spec.get("network") or self.stubs:
|
|
372
|
+
what = "conditions.network" if self.spec.get("network") else "stubs"
|
|
373
|
+
raise ConditionError(NETWORK_ON_A_PHONE.format(what=what), "Run the check on a simulator.")
|
|
374
|
+
if self.watch is not None:
|
|
375
|
+
raise ConditionError(WIRE_ON_A_PHONE, "Run the check on a simulator, or see Wire assertions on a "
|
|
376
|
+
"real iPhone in docs/testing.mdx.")
|
|
377
|
+
if self._wanted("appearance"):
|
|
378
|
+
self._phone_appearance(self.spec["appearance"])
|
|
379
|
+
else:
|
|
380
|
+
for key in ("appearance", "content_size", "increase_contrast"):
|
|
381
|
+
if key in self.spec:
|
|
382
|
+
word = "ui"
|
|
383
|
+
previous = self.run([word, self.udid, key])
|
|
384
|
+
old = previous[1].strip() if previous[0] == 0 else None
|
|
385
|
+
self._simctl([word, self.udid, key, self.spec[key]], f"the {key.replace('_', ' ')}")
|
|
386
|
+
if old and old != self.spec[key] and old.split()[-1] not in ("unsupported", "unknown"):
|
|
387
|
+
self._undo.append([word, self.udid, key, old])
|
|
388
|
+
self.applied.append(key)
|
|
389
|
+
if "location" in self.spec:
|
|
390
|
+
place = self.spec["location"]
|
|
391
|
+
self._simctl(["location", self.udid, "set", f"{place['latitude']},{place['longitude']}"],
|
|
392
|
+
"the location")
|
|
393
|
+
self._undo.append(["location", self.udid, "clear"])
|
|
394
|
+
self.applied.append("location")
|
|
395
|
+
if "route" in self.spec:
|
|
396
|
+
route = self.spec["route"]
|
|
397
|
+
self._simctl(["location", self.udid, "start", f"--speed={route['speed']:g}",
|
|
398
|
+
*(f"{p['latitude']},{p['longitude']}" for p in route["waypoints"])], "the route")
|
|
399
|
+
self._undo.append(["location", self.udid, "clear"])
|
|
400
|
+
self.applied.append("route")
|
|
401
|
+
if "permissions" in self.spec:
|
|
402
|
+
for service, action in self.spec["permissions"].items():
|
|
403
|
+
self._simctl(["privacy", self.udid, action, service, self.bundle], f"the {service} permission")
|
|
404
|
+
if action != "reset":
|
|
405
|
+
self._undo.append(["privacy", self.udid, "reset", service, self.bundle])
|
|
406
|
+
self.applied.append("permissions")
|
|
407
|
+
if "media" in self.spec:
|
|
408
|
+
self._simctl(["addmedia", self.udid, *self.spec["media"]], "the media")
|
|
409
|
+
self.applied.append("media")
|
|
410
|
+
if "biometric" in self.spec:
|
|
411
|
+
self._biometric(self.spec["biometric"])
|
|
412
|
+
if "status_bar" in self.spec:
|
|
413
|
+
flags = []
|
|
414
|
+
for key, value in self.spec["status_bar"].items():
|
|
415
|
+
flags += ["--" + re.sub(r"_(.)", lambda m: m.group(1).upper(), key), str(value)]
|
|
416
|
+
self._simctl(["status_bar", self.udid, "override", *flags], "the status bar")
|
|
417
|
+
self._undo.append(["status_bar", self.udid, "clear"])
|
|
418
|
+
self.applied.append("status_bar")
|
|
419
|
+
self._start_network()
|
|
420
|
+
if self._wanted("locale"):
|
|
421
|
+
self.applied.append("locale")
|
|
422
|
+
|
|
423
|
+
def _biometric(self, wanted):
|
|
424
|
+
"""Enroll or unenroll Face ID before the launch; the run puts the enrollment back."""
|
|
425
|
+
from ..sim import biometrics
|
|
426
|
+
from ..sim.models import SimError
|
|
427
|
+
notifier = _Notifier(self.run)
|
|
428
|
+
if self.actions is None:
|
|
429
|
+
raise ConditionError("Device condition changes need an action journal.")
|
|
430
|
+
try:
|
|
431
|
+
before = biometrics.deliver(notifier, self.udid, "status")["enrolled"]
|
|
432
|
+
if before != (wanted == "enrolled"):
|
|
433
|
+
with self.actions.action("CONDITION_SET"): # journaled before it is sent, as every condition change
|
|
434
|
+
biometrics.deliver(notifier, self.udid, "enroll" if wanted == "enrolled" else "unenroll")
|
|
435
|
+
self._undo.append(("biometric", before))
|
|
436
|
+
except SimError as error:
|
|
437
|
+
raise ConditionError(f"Mobster couldn't set Face ID enrollment on the simulator: {error}",
|
|
438
|
+
"Run `mobster sim doctor`, then try again. Remove the condition to run the check "
|
|
439
|
+
"without it.") from None
|
|
440
|
+
self.applied.append("biometric")
|
|
441
|
+
|
|
442
|
+
def after_launch(self):
|
|
443
|
+
if "push" in self.spec and not self.real:
|
|
444
|
+
self._simctl(["push", self.udid, self.bundle, self.spec["push"]], "the push", "PUSH")
|
|
445
|
+
self.applied.append("push")
|
|
446
|
+
|
|
447
|
+
def _phone_appearance(self, name):
|
|
448
|
+
if self.actions is None:
|
|
449
|
+
raise ConditionError("Device condition changes need an action journal.")
|
|
450
|
+
try:
|
|
451
|
+
previous = self.device.appearance()
|
|
452
|
+
with self.actions.action("CONDITION_SET"):
|
|
453
|
+
self.device.appearance(name)
|
|
454
|
+
except Exception as error:
|
|
455
|
+
raise ConditionError(f"Mobster couldn't set the appearance on the iPhone ({type(error).__name__}).",
|
|
456
|
+
"Keep the iPhone unlocked and connected, or remove the condition.") from None
|
|
457
|
+
if previous and previous != name:
|
|
458
|
+
self._undo.append(previous)
|
|
459
|
+
self.applied.append("appearance")
|
|
460
|
+
|
|
461
|
+
def restore(self, actions):
|
|
462
|
+
"""Restore under the device lease; stop at the first unconfirmed change and never retry it."""
|
|
463
|
+
if self.net is not None:
|
|
464
|
+
try:
|
|
465
|
+
self.net.stop()
|
|
466
|
+
except Exception:
|
|
467
|
+
pass
|
|
468
|
+
while self._undo:
|
|
469
|
+
with actions.action("CONDITION_RESTORE"):
|
|
470
|
+
step = self._undo.pop()
|
|
471
|
+
if isinstance(step, str):
|
|
472
|
+
self.device.appearance(step)
|
|
473
|
+
elif step and step[0] == "biometric":
|
|
474
|
+
from ..sim import biometrics
|
|
475
|
+
biometrics.deliver(_Notifier(self.run), self.udid, "enroll" if step[1] else "unenroll")
|
|
476
|
+
else:
|
|
477
|
+
code, _ = self.run(step)
|
|
478
|
+
if code != 0:
|
|
479
|
+
raise ConditionError("Mobster couldn't confirm that a simulator condition was restored.")
|
|
480
|
+
|
|
481
|
+
def report(self):
|
|
482
|
+
return {"applied": list(dict.fromkeys(self.applied)), "skipped": list(self.skipped),
|
|
483
|
+
**({"network": self.net.summary()} if self.net is not None else {})}
|