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,497 @@
|
|
|
1
|
+
"""Where a `mobster test` run's checks run. A phone is reached only by --device, for a developer's own build."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import plistlib
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import threading
|
|
9
|
+
import zipfile
|
|
10
|
+
from dataclasses import dataclass, replace
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from urllib.parse import urlsplit
|
|
14
|
+
from xml.parsers.expat import ExpatError
|
|
15
|
+
|
|
16
|
+
MATRIX_FILE = "matrix.yaml"
|
|
17
|
+
MATRIX_LIMIT = 20
|
|
18
|
+
NAME_LIMIT = 100
|
|
19
|
+
# devicectl's flag on an app a developer built and installed. OWNER TEST PLAN item: confirm its name on a phone.
|
|
20
|
+
DEVELOPER_FIELD = "builtByDeveloper"
|
|
21
|
+
DEVICECTL_TIMEOUT = 60
|
|
22
|
+
APP_STORE = "Mobster tests your own apps on your iPhone. {app} is from the App Store."
|
|
23
|
+
PART_OF_IOS = "Mobster tests your own apps on your iPhone. {app} is part of iOS."
|
|
24
|
+
NOT_THIS_APP = "{path} is {built}, not {bundle}, the app the check names."
|
|
25
|
+
PLIST_LIMIT = 1_000_000 # an Info.plist read from inside an .ipa
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TargetError(Exception):
|
|
29
|
+
"""A usage problem with --sim, --device or the matrix file: one plain sentence."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Refused(Exception):
|
|
33
|
+
"""A check that can't run on this target: couldn't run, before anything is tapped. ``klass`` names why."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message, klass="device", fix=""):
|
|
36
|
+
super().__init__(message)
|
|
37
|
+
self.message, self.klass, self.fix = message, klass, fix or ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_record(record):
|
|
41
|
+
"""Reject unknown or contradictory device routes before constructing a driver."""
|
|
42
|
+
if not isinstance(record, dict) or record.get("kind") not in ("simulator", "usb", "wda", "android"):
|
|
43
|
+
raise Refused("Mobster has no driver for this target.", "device")
|
|
44
|
+
kind = record["kind"]
|
|
45
|
+
udid = record.get("udid")
|
|
46
|
+
if kind in ("simulator", "usb") and (not isinstance(udid, str) or not udid.strip() or
|
|
47
|
+
record.get("id") is not None and
|
|
48
|
+
str(record["id"]).casefold() != udid.casefold()):
|
|
49
|
+
raise Refused("This device's ID or UDID is invalid. Discover it again before starting a run.", "device")
|
|
50
|
+
if kind in ("usb", "wda"):
|
|
51
|
+
try:
|
|
52
|
+
route = urlsplit(record.get("wdaUrl") or "")
|
|
53
|
+
valid = route.scheme in ("http", "https") and bool(route.hostname) and route.port != 0
|
|
54
|
+
except (TypeError, ValueError):
|
|
55
|
+
valid = False
|
|
56
|
+
if not valid:
|
|
57
|
+
raise Refused("This device has no valid WebDriverAgent address. Set it up before starting a run.",
|
|
58
|
+
"device")
|
|
59
|
+
return record
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Target:
|
|
64
|
+
key: str # "default" | "sim:<type>@<runtime>" | "device:<id>"
|
|
65
|
+
kind: str # "default" | "simulator" | "device"
|
|
66
|
+
device_type: Optional[str] = None
|
|
67
|
+
runtime: Optional[str] = None
|
|
68
|
+
record: Optional[dict] = None # devices.record: a named device (a real one, or a Mobster simulator by name)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def label(self):
|
|
72
|
+
if self.record is not None:
|
|
73
|
+
return self.record.get("name") or self.record.get("id") or "device"
|
|
74
|
+
if self.kind == "default":
|
|
75
|
+
return "Each check's simulator"
|
|
76
|
+
return " · ".join(filter(None, [self.device_type or "Default simulator", self.runtime]))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def real(self):
|
|
80
|
+
return self.kind == "device" and self.record is not None and self.record.get("kind") not in ("simulator",
|
|
81
|
+
"android")
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def lane(self):
|
|
85
|
+
"""Which worker pool runs it: the shared simulator slots, or one lane of its own for a named device (one check
|
|
86
|
+
at a time on it)."""
|
|
87
|
+
return "sims" if self.record is None else self.key
|
|
88
|
+
|
|
89
|
+
def public(self):
|
|
90
|
+
if (self.record or {}).get("kind") == "android":
|
|
91
|
+
return {"key": self.key, "name": self.label, "kind": "android"}
|
|
92
|
+
out = {"key": self.key, "name": self.label, "kind": "simulator" if self.kind != "device" or (
|
|
93
|
+
self.record or {}).get("kind") == "simulator" else (self.record or {}).get("kind") or "device"}
|
|
94
|
+
if self.device_type:
|
|
95
|
+
out["type"] = self.device_type
|
|
96
|
+
if self.runtime:
|
|
97
|
+
out["runtime"] = self.runtime
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
DEFAULT = Target("default", "default")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def parse_sim(text, where="--sim"):
|
|
105
|
+
"""``TYPE[@RUNTIME]`` as (type, runtime or None)."""
|
|
106
|
+
text = " ".join(str(text or "").split())
|
|
107
|
+
device, _, runtime = text.partition("@")
|
|
108
|
+
device, runtime = device.strip(), runtime.strip() or None
|
|
109
|
+
if not device:
|
|
110
|
+
raise TargetError(f"{where} {text!r}: name a simulator device type, such as \"iPhone 17 Pro\" or "
|
|
111
|
+
"\"iPhone 17 Pro@iOS 26.4\"")
|
|
112
|
+
if len(device) > NAME_LIMIT or (runtime and len(runtime) > NAME_LIMIT):
|
|
113
|
+
raise TargetError(f"{where} {text!r} is longer than {NAME_LIMIT} characters")
|
|
114
|
+
return device, runtime
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def simulator(device, runtime=None):
|
|
118
|
+
return Target(f"sim:{device}@{runtime or ''}", "simulator", device_type=device, runtime=runtime)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load_matrix(path):
|
|
122
|
+
# Simulator device types only: entries are never looked up as devices, and a ``devices:`` key is refused.
|
|
123
|
+
path = Path(path)
|
|
124
|
+
import yaml
|
|
125
|
+
try:
|
|
126
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
127
|
+
except FileNotFoundError:
|
|
128
|
+
raise TargetError(f"{path} doesn't exist.") from None
|
|
129
|
+
except (OSError, UnicodeDecodeError) as error:
|
|
130
|
+
raise TargetError(f"{path} can't be read ({type(error).__name__}).") from None
|
|
131
|
+
except yaml.YAMLError as error:
|
|
132
|
+
mark = getattr(error, "problem_mark", None)
|
|
133
|
+
line = f" line {mark.line + 1}" if mark is not None else ""
|
|
134
|
+
raise TargetError(f"{path}{line}: {getattr(error, 'problem', None) or 'not valid YAML'}") from None
|
|
135
|
+
if isinstance(data, dict):
|
|
136
|
+
if "devices" in data:
|
|
137
|
+
raise TargetError(f"{path} names devices. A matrix file names simulators only; pass a real iPhone with "
|
|
138
|
+
"--device on the command line.")
|
|
139
|
+
unknown = [key for key in data if key != "simulators"]
|
|
140
|
+
if unknown:
|
|
141
|
+
raise TargetError(f"{path} has an unknown key {unknown[0]!r}; list them under simulators:")
|
|
142
|
+
data = data.get("simulators")
|
|
143
|
+
if not isinstance(data, list) or not data:
|
|
144
|
+
raise TargetError(f"{path} must list simulators, such as simulators: [\"iPhone 17 Pro@iOS 26.4\"]")
|
|
145
|
+
if len(data) > MATRIX_LIMIT:
|
|
146
|
+
raise TargetError(f"{path} lists more than {MATRIX_LIMIT} simulators")
|
|
147
|
+
out = []
|
|
148
|
+
for index, item in enumerate(data):
|
|
149
|
+
where = f"{path.name} item {index + 1}"
|
|
150
|
+
if isinstance(item, dict):
|
|
151
|
+
unknown = [key for key in item if key not in ("device", "runtime")]
|
|
152
|
+
if unknown or not isinstance(item.get("device"), str) or not isinstance(item.get("runtime", ""), str):
|
|
153
|
+
raise TargetError(f"{where}: write \"TYPE@RUNTIME\" or {{device: TYPE, runtime: RUNTIME}}")
|
|
154
|
+
text = item["device"] + ("@" + item["runtime"] if item.get("runtime") else "")
|
|
155
|
+
elif isinstance(item, str):
|
|
156
|
+
text = item
|
|
157
|
+
else:
|
|
158
|
+
raise TargetError(f"{where}: write \"TYPE@RUNTIME\", such as \"iPhone 17 Pro@iOS 26.4\"")
|
|
159
|
+
out.append(simulator(*parse_sim(text, where)))
|
|
160
|
+
return out
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
ALL = "all"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _every_phone():
|
|
167
|
+
"""Every real iPhone that is ready now (``--device all``)."""
|
|
168
|
+
from .. import devices
|
|
169
|
+
return [record for record in devices.discover() if record.get("kind") != "simulator"
|
|
170
|
+
and record.get("state") == "ready"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def android_record(name=None):
|
|
174
|
+
"""A device record for Android: ``name`` is an AVD or an adb serial, else any idle emulator."""
|
|
175
|
+
return {"kind": "android", "id": f"android:{name or 'any'}", "name": name or "Android", "avd": name or None}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_android(check):
|
|
179
|
+
return bool(check.app_path and str(check.app_path).lower().endswith(".apk")) or \
|
|
180
|
+
str(check.runtime or "").casefold().startswith("android")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def resolve(*, sims=(), devices=(), matrix=None, root=None, named_device=None, every_phone=None, android=None):
|
|
184
|
+
targets = [simulator(*parse_sim(text)) for text in sims or ()]
|
|
185
|
+
if android is not None:
|
|
186
|
+
record = android_record(android or None)
|
|
187
|
+
targets.append(Target(record["id"], "device", record=record))
|
|
188
|
+
if matrix is not None:
|
|
189
|
+
targets += load_matrix(matrix)
|
|
190
|
+
devices = list(devices or ())
|
|
191
|
+
if any(str(name).strip().casefold() == ALL for name in devices):
|
|
192
|
+
phones = (every_phone or _every_phone)()
|
|
193
|
+
if not phones:
|
|
194
|
+
raise TargetError("--device all found no iPhone ready on this Mac. Plug one in, unlock it, and set it up "
|
|
195
|
+
"in Mobster; `mobster devices` lists them.")
|
|
196
|
+
devices = [name for name in devices if str(name).strip().casefold() != ALL] + \
|
|
197
|
+
[record.get("id") or record.get("name") for record in phones]
|
|
198
|
+
for name in devices:
|
|
199
|
+
name = " ".join(str(name or "").split())
|
|
200
|
+
if not name or len(name) > 128:
|
|
201
|
+
raise TargetError("--device needs a device's name or id, as `mobster devices` lists them")
|
|
202
|
+
record = _named(name, named_device)
|
|
203
|
+
if record is None:
|
|
204
|
+
raise TargetError(f"No device is named {name!r}. `mobster devices` lists them; for a simulator device "
|
|
205
|
+
"type, use --sim.")
|
|
206
|
+
targets.append(Target(f"device:{record.get('id') or name}", "device", record=record))
|
|
207
|
+
if not targets and root is not None and (Path(root) / ".mobster" / MATRIX_FILE).is_file():
|
|
208
|
+
targets = load_matrix(Path(root) / ".mobster" / MATRIX_FILE)
|
|
209
|
+
return list({target.key: target for target in targets}.values()) or [DEFAULT]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _named(name, named_device):
|
|
213
|
+
from ..devices import AmbiguousDevice
|
|
214
|
+
if named_device is None:
|
|
215
|
+
from ..device_targets import named_device
|
|
216
|
+
try:
|
|
217
|
+
return named_device(name)
|
|
218
|
+
except AmbiguousDevice as error:
|
|
219
|
+
raise TargetError(str(error)) from None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def for_target(check, target, *, named_device=None, gate=None):
|
|
223
|
+
"""(the check as it runs on ``target``, notes, the device record it runs on or None). Raises Refused."""
|
|
224
|
+
notes = []
|
|
225
|
+
if target.record is not None and target.record.get("kind") == "android":
|
|
226
|
+
return _on_android(check), notes, target.record
|
|
227
|
+
if target.kind == "default" and is_android(check):
|
|
228
|
+
return _on_android(check), notes, android_record()
|
|
229
|
+
if target.kind == "simulator" and target.record is None:
|
|
230
|
+
return replace(check, device=target.device_type, runtime=target.runtime), notes, None
|
|
231
|
+
if target.kind == "default":
|
|
232
|
+
if not check.device:
|
|
233
|
+
return check, notes, None
|
|
234
|
+
record = _named(check.device, named_device) if check.device else None
|
|
235
|
+
if record is None:
|
|
236
|
+
return check, notes, None # a simulator device type, as verify reads it
|
|
237
|
+
validate_record(record)
|
|
238
|
+
if record.get("kind") != "simulator":
|
|
239
|
+
name = record.get("name") or check.device
|
|
240
|
+
raise Refused(f"The check names “{name}”, a real device, and a check file may name only a simulator. To "
|
|
241
|
+
f"run it there, pass --device \"{name}\".", "usage")
|
|
242
|
+
return check, notes, record
|
|
243
|
+
record = validate_record(target.record)
|
|
244
|
+
if record.get("kind") == "simulator":
|
|
245
|
+
return replace(check, device=None, runtime=None), notes, record
|
|
246
|
+
if gate is None:
|
|
247
|
+
raise Refused("Mobster can't tell whether the app is your own build on this device.", "device")
|
|
248
|
+
return gate.preflight(check, record)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _on_android(check):
|
|
252
|
+
if check.app_path and not str(check.app_path).lower().endswith(".apk"):
|
|
253
|
+
raise Refused(f"{Path(check.app_path).name} is not an .apk, so it can't run on Android.", "usage",
|
|
254
|
+
"Name an .apk in app.path, or an installed package in app.bundle.")
|
|
255
|
+
return check
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def bundle_of(app_path):
|
|
259
|
+
"""The bundle ID in an .app's Info.plist, or in the one app an .ipa holds (Payload/X.app/Info.plist); else None."""
|
|
260
|
+
info = _info(app_path)
|
|
261
|
+
value = info.get("CFBundleIdentifier") if info else None
|
|
262
|
+
return value if isinstance(value, str) else None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _info(app_path):
|
|
266
|
+
if not app_path:
|
|
267
|
+
return None
|
|
268
|
+
text = str(app_path)
|
|
269
|
+
try:
|
|
270
|
+
if text.lower().endswith(".ipa"):
|
|
271
|
+
return _ipa_info(text)
|
|
272
|
+
if not text.endswith(".app"):
|
|
273
|
+
return None
|
|
274
|
+
with open(Path(app_path) / "Info.plist", "rb") as handle:
|
|
275
|
+
data = plistlib.load(handle)
|
|
276
|
+
return data if isinstance(data, dict) else None
|
|
277
|
+
except (OSError, plistlib.InvalidFileException, ExpatError, ValueError, zipfile.BadZipFile):
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _ipa_info(path):
|
|
282
|
+
"""The Info.plist of the one app in an .ipa, read without unpacking it; None when there isn't exactly one."""
|
|
283
|
+
with zipfile.ZipFile(path) as archive:
|
|
284
|
+
plists = [item for item in archive.infolist()
|
|
285
|
+
if re.fullmatch(r"Payload/[^/]+\.app/Info\.plist", item.filename)]
|
|
286
|
+
if len(plists) != 1 or plists[0].file_size > PLIST_LIMIT:
|
|
287
|
+
return None
|
|
288
|
+
data = plistlib.loads(archive.read(plists[0]))
|
|
289
|
+
return data if isinstance(data, dict) else None
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def device_build(app_path):
|
|
293
|
+
if not app_path:
|
|
294
|
+
return None
|
|
295
|
+
if str(app_path).lower().endswith(".ipa"):
|
|
296
|
+
return True
|
|
297
|
+
info = _info(app_path)
|
|
298
|
+
if not info:
|
|
299
|
+
return None
|
|
300
|
+
platform = str(info.get("DTPlatformName") or "").lower()
|
|
301
|
+
supported = [str(item).lower() for item in info.get("CFBundleSupportedPlatforms") or ()]
|
|
302
|
+
if platform == "iphonesimulator" or "iphonesimulator" in supported:
|
|
303
|
+
return False
|
|
304
|
+
if platform == "iphoneos" or "iphoneos" in supported:
|
|
305
|
+
return True
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class DeviceGate:
|
|
310
|
+
def __init__(self, *, runner=None, installer=None, progress=None, allowed=None, testflight=()):
|
|
311
|
+
self.runner = runner or _run
|
|
312
|
+
# Bundle IDs named with --testflight: the person's own TestFlight app. Only ever from the command line.
|
|
313
|
+
self.testflight = frozenset(str(item) for item in testflight or ())
|
|
314
|
+
self.installer = installer
|
|
315
|
+
self.progress = progress or (lambda line: None)
|
|
316
|
+
self.allowed = allowed
|
|
317
|
+
self._apps = {} # device id -> {bundle: app dict}
|
|
318
|
+
self._installed = {} # (device id, app path) -> bundle
|
|
319
|
+
self._lock = threading.Lock()
|
|
320
|
+
|
|
321
|
+
def preflight(self, check, record):
|
|
322
|
+
"""Validate a phone check using local metadata only."""
|
|
323
|
+
self._preflight(check, record)
|
|
324
|
+
return check, [], record
|
|
325
|
+
|
|
326
|
+
def normalized_check(self, check, record):
|
|
327
|
+
"""The check a phone will run, before the phone is contacted."""
|
|
328
|
+
_, bundle, _ = self._preflight(check, record)
|
|
329
|
+
return self._normalized(check, bundle)
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def _normalized(check, bundle):
|
|
333
|
+
return replace(check, app_path=None, bundle_id=bundle, reset="none", device=None, runtime=None)
|
|
334
|
+
|
|
335
|
+
def _preflight(self, check, record):
|
|
336
|
+
if getattr(check, "business", None) is not None:
|
|
337
|
+
raise Refused("App-state fixtures run only on managed simulators.", "business")
|
|
338
|
+
name = record.get("name") or record.get("id") or "the iPhone"
|
|
339
|
+
built = bundle_of(check.app_path)
|
|
340
|
+
if check.bundle_id and built and built != check.bundle_id:
|
|
341
|
+
raise Refused(NOT_THIS_APP.format(path=Path(check.app_path).name, built=built, bundle=check.bundle_id),
|
|
342
|
+
"usage", "Point app.path at the build of the app the check names.")
|
|
343
|
+
bundle = check.bundle_id or built
|
|
344
|
+
if bundle and bundle.startswith("com.apple."):
|
|
345
|
+
raise Refused(PART_OF_IOS.format(app=_apple_name(bundle)), "not_developer_build")
|
|
346
|
+
if record.get("kind") != "usb":
|
|
347
|
+
raise Refused(f"Mobster can't tell which apps on “{name}” are your own builds: it can check an iPhone "
|
|
348
|
+
"connected to this Mac, or a simulator.", "device",
|
|
349
|
+
"Plug the iPhone in, or run the check on a simulator with --sim.")
|
|
350
|
+
build = device_build(check.app_path)
|
|
351
|
+
if check.app_path and build is not False and not built:
|
|
352
|
+
raise Refused(f"Mobster can't read the bundle ID in {Path(check.app_path).name}; it won't install an "
|
|
353
|
+
"unidentified app on your iPhone.", "usage",
|
|
354
|
+
"Point app.path at a device build with a readable Info.plist.")
|
|
355
|
+
if not check.app_path and not bundle:
|
|
356
|
+
raise Refused("The check doesn't say which app to test: add app.bundle.", "usage")
|
|
357
|
+
if not self._devices_allowed():
|
|
358
|
+
raise Refused("Devices are off in this process (MOBSTER_NO_DEVICES=1).", "environment")
|
|
359
|
+
return name, bundle, build
|
|
360
|
+
|
|
361
|
+
def admit(self, check, record, *, cancelled=None, actions=None):
|
|
362
|
+
"""Prepare the phone while the caller holds its canonical device lease."""
|
|
363
|
+
notes = []
|
|
364
|
+
name, bundle, build = self._preflight(check, record)
|
|
365
|
+
self._check_cancelled(cancelled)
|
|
366
|
+
if check.app_path and build is not False:
|
|
367
|
+
installed = self._install(record, check.app_path, bundle, cancelled=cancelled, actions=actions)
|
|
368
|
+
# What landed on the phone is the app the check drives: the same rules, read from the install.
|
|
369
|
+
self._admit_installed(installed, check, bundle)
|
|
370
|
+
bundle = installed
|
|
371
|
+
notes.append(f"Installed {Path(check.app_path).name} on {name} for this run.")
|
|
372
|
+
else:
|
|
373
|
+
if check.app_path:
|
|
374
|
+
notes.append(f"{Path(check.app_path).name} is a simulator build, so the check used the build of "
|
|
375
|
+
f"{bundle or 'the app'} already on {name}.")
|
|
376
|
+
if not bundle:
|
|
377
|
+
raise Refused("The check doesn't say which app to test: add app.bundle.", "usage")
|
|
378
|
+
self._check_cancelled(cancelled)
|
|
379
|
+
app = self._apps_on(record).get(bundle)
|
|
380
|
+
if app is None:
|
|
381
|
+
raise Refused(f"{bundle} isn't on “{name}”. Install your build there from Xcode, or give the check an "
|
|
382
|
+
".ipa or a device build in app.path.", "install")
|
|
383
|
+
if app.get(DEVELOPER_FIELD) is not True:
|
|
384
|
+
label = app.get("name") or bundle
|
|
385
|
+
if bundle not in self.testflight:
|
|
386
|
+
raise Refused(APP_STORE.format(app=label), "not_developer_build",
|
|
387
|
+
"Install your own build of it from Xcode, then run the check again. For your "
|
|
388
|
+
"TestFlight build, pass --testflight with its bundle ID.")
|
|
389
|
+
notes.append(f"Ran on the build of {label} on “{name}” that --testflight names as yours.")
|
|
390
|
+
reset = check.reset
|
|
391
|
+
if reset in ("data", "reinstall"):
|
|
392
|
+
notes.append(f"Mobster never clears an app's data on an iPhone, so this ran with reset: none instead of "
|
|
393
|
+
f"{reset}.")
|
|
394
|
+
return self._normalized(check, bundle), notes, record
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def _check_cancelled(cancelled):
|
|
398
|
+
if cancelled is not None and cancelled():
|
|
399
|
+
raise Refused("The suite was stopped before this ran.", "stopped")
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def _admit_installed(installed, check, expected):
|
|
403
|
+
if installed.startswith("com.apple."):
|
|
404
|
+
raise Refused(PART_OF_IOS.format(app=_apple_name(installed)), "not_developer_build")
|
|
405
|
+
if expected and installed != expected:
|
|
406
|
+
raise Refused(NOT_THIS_APP.format(path=Path(check.app_path).name, built=installed, bundle=expected),
|
|
407
|
+
"usage", "Point app.path at the build of the app the check names.")
|
|
408
|
+
|
|
409
|
+
def _devices_allowed(self):
|
|
410
|
+
if self.allowed is not None:
|
|
411
|
+
return self.allowed()
|
|
412
|
+
from ..native_helpers import devices_allowed
|
|
413
|
+
return devices_allowed()
|
|
414
|
+
|
|
415
|
+
def _apps_on(self, record):
|
|
416
|
+
key = record.get("id") or record.get("udid")
|
|
417
|
+
with self._lock:
|
|
418
|
+
if key in self._apps:
|
|
419
|
+
return self._apps[key]
|
|
420
|
+
if not self._devices_allowed():
|
|
421
|
+
raise Refused("Devices are off in this process (MOBSTER_NO_DEVICES=1).", "environment")
|
|
422
|
+
udid = record.get("udid") or record.get("id")
|
|
423
|
+
with tempfile.TemporaryDirectory(prefix="mobster-test-apps-") as folder:
|
|
424
|
+
out = Path(folder) / "apps.json"
|
|
425
|
+
code, output = self.runner(["xcrun", "devicectl", "device", "info", "apps", "--device", str(udid),
|
|
426
|
+
"--json-output", str(out)])
|
|
427
|
+
try:
|
|
428
|
+
data = json.loads(out.read_text()) if out.is_file() else None
|
|
429
|
+
except (OSError, ValueError):
|
|
430
|
+
data = None
|
|
431
|
+
if code != 0 or not isinstance(data, dict):
|
|
432
|
+
last = next((line.strip() for line in reversed((output or "").splitlines()) if line.strip()), "")
|
|
433
|
+
raise Refused(f"Mobster couldn't list the apps on “{record.get('name') or udid}”"
|
|
434
|
+
+ (f": {last[:200]}" if last else "") + ".", "device",
|
|
435
|
+
"Keep the iPhone unlocked and connected, then try again.")
|
|
436
|
+
apps = (data.get("result") or {}).get("apps") if isinstance(data.get("result"), dict) else None
|
|
437
|
+
found = {}
|
|
438
|
+
for app in apps if isinstance(apps, list) else ():
|
|
439
|
+
if isinstance(app, dict) and isinstance(app.get("bundleIdentifier"), str):
|
|
440
|
+
found[app["bundleIdentifier"]] = app
|
|
441
|
+
with self._lock:
|
|
442
|
+
self._apps[key] = found
|
|
443
|
+
return found
|
|
444
|
+
|
|
445
|
+
def _install(self, record, app_path, bundle, *, cancelled=None, actions=None):
|
|
446
|
+
key = (record.get("id"), str(app_path))
|
|
447
|
+
with self._lock:
|
|
448
|
+
if key in self._installed:
|
|
449
|
+
return self._installed[key]
|
|
450
|
+
if not self._devices_allowed():
|
|
451
|
+
raise Refused("Devices are off in this process (MOBSTER_NO_DEVICES=1).", "environment")
|
|
452
|
+
self._check_cancelled(cancelled)
|
|
453
|
+
installer = self.installer
|
|
454
|
+
if installer is None:
|
|
455
|
+
from ..phone_io.install import install as installer
|
|
456
|
+
try:
|
|
457
|
+
if actions is None:
|
|
458
|
+
self.progress(f"Installing {Path(app_path).name} on {record.get('name') or record.get('id')}")
|
|
459
|
+
result = installer(record, app_path)
|
|
460
|
+
else:
|
|
461
|
+
with actions.action("INSTALL"):
|
|
462
|
+
self.progress(f"Installing {Path(app_path).name} on {record.get('name') or record.get('id')}")
|
|
463
|
+
result = installer(record, app_path)
|
|
464
|
+
except Exception as error: # PhoneIOError and the like: one sentence, and its fix
|
|
465
|
+
from ..verify.actions import ActionError
|
|
466
|
+
if actions is not None and actions.klass is not None:
|
|
467
|
+
raise ActionError(actions.klass) from None
|
|
468
|
+
if isinstance(error, ActionError):
|
|
469
|
+
raise
|
|
470
|
+
raise Refused(str(error) or f"The install failed ({type(error).__name__}).", "install",
|
|
471
|
+
getattr(error, "fix", "") or "") from None
|
|
472
|
+
installed = (result or {}).get("bundle_id") or bundle
|
|
473
|
+
if not installed:
|
|
474
|
+
raise Refused(f"Mobster installed {Path(app_path).name} but couldn't read its bundle ID.", "install")
|
|
475
|
+
with self._lock:
|
|
476
|
+
self._installed[key] = installed
|
|
477
|
+
# Installed by Mobster for this run: a developer's build, whatever the phone's list says.
|
|
478
|
+
self._apps.setdefault(record.get("id") or record.get("udid"), {})[installed] = {
|
|
479
|
+
"bundleIdentifier": installed, DEVELOPER_FIELD: True}
|
|
480
|
+
return installed
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _apple_name(bundle):
|
|
484
|
+
tail = bundle.rsplit(".", 1)[-1]
|
|
485
|
+
return {"Preferences": "Settings", "mobilesafari": "Safari", "MobileSMS": "Messages",
|
|
486
|
+
"mobilenotes": "Notes", "mobilemail": "Mail"}.get(tail, re.sub(r"^mobile", "", tail) or bundle)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _run(argv):
|
|
490
|
+
try:
|
|
491
|
+
result = subprocess.run(argv, capture_output=True, text=True, errors="replace", timeout=DEVICECTL_TIMEOUT,
|
|
492
|
+
stdin=subprocess.DEVNULL)
|
|
493
|
+
except FileNotFoundError:
|
|
494
|
+
return 127, "Xcode's command-line tools aren't installed."
|
|
495
|
+
except subprocess.TimeoutExpired:
|
|
496
|
+
return 124, f"devicectl didn't answer in {DEVICECTL_TIMEOUT} s."
|
|
497
|
+
return result.returncode, (result.stdout or "") + (result.stderr or "")
|