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,322 @@
|
|
|
1
|
+
"""`mobster wifi probe`: the owner's probe (SPEC §3.7, owner test plan items 1-4), whose result decides the release."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from functools import partial
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..devices import wda_url
|
|
12
|
+
from ..transport import request_once
|
|
13
|
+
from . import devicectl, exposure, network
|
|
14
|
+
from .relay import Relay, check_target
|
|
15
|
+
|
|
16
|
+
OK, WARN, FAIL, INFO = "ok", "warn", "fail", "info"
|
|
17
|
+
START_WAIT = 120.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def http_status(port, timeout=3.0):
|
|
21
|
+
"""WebDriverAgent's /status through 127.0.0.1:``port``: its JSON value, or None."""
|
|
22
|
+
try:
|
|
23
|
+
value = request_once(wda_url(port), "GET", "/status", timeout=timeout).get("value")
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
return value if isinstance(value, dict) else None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def stream_answers(port, timeout=3.0):
|
|
30
|
+
"""Whether the MJPEG port behind 127.0.0.1:``port`` sends anything back to a request."""
|
|
31
|
+
try:
|
|
32
|
+
with socket.create_connection(("127.0.0.1", port), timeout) as sock:
|
|
33
|
+
sock.sendall(b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
|
|
34
|
+
return bool(sock.recv(1))
|
|
35
|
+
except OSError:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def usbmux_network(udid, runner=None):
|
|
40
|
+
"""Whether ``idevice_id -n`` (read-only) lists the phone over the network: True, False, or None (no tool)."""
|
|
41
|
+
from ..mac_tools import tool
|
|
42
|
+
listing = tool("idevice_id")
|
|
43
|
+
if not listing:
|
|
44
|
+
return None
|
|
45
|
+
output = (runner or partial(network.read_output, timeout=5))([listing, "-n"])
|
|
46
|
+
if output is None:
|
|
47
|
+
return None
|
|
48
|
+
return udid.upper() in {line.strip().upper() for line in output.splitlines() if line.strip()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# It binds only 127.0.0.1, never enters a passcode, and changes nothing on the phone but --start's own helper.
|
|
52
|
+
class Probe:
|
|
53
|
+
"""One probe of one phone. ``steps``: [{"key", "state", "title", "detail"}]; ``fields``: what CoreDevice said."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, *, udid, name, slot_port=None, data_dir=None, start=False, manager=None, out=None,
|
|
56
|
+
network_check=None):
|
|
57
|
+
self.udid, self.name, self.slot_port = udid, name, slot_port
|
|
58
|
+
self.network_check = network_check
|
|
59
|
+
self.data_dir, self.start, self.manager = data_dir, start, manager
|
|
60
|
+
self.out = out
|
|
61
|
+
self.steps, self.fields, self.peer, self.address = [], {}, None, None
|
|
62
|
+
|
|
63
|
+
def step(self, key, state, title, detail=""):
|
|
64
|
+
item = {"key": key, "state": state, "title": title, "detail": detail}
|
|
65
|
+
self.steps.append(item)
|
|
66
|
+
if self.out is not None:
|
|
67
|
+
self.out(item)
|
|
68
|
+
return item
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def ok(self):
|
|
72
|
+
needed = {"coredevice", "tunnel", "wda", "lan"}
|
|
73
|
+
states = {item["key"]: item["state"] for item in self.steps}
|
|
74
|
+
return needed <= set(states) and all(states[key] == OK for key in needed)
|
|
75
|
+
|
|
76
|
+
def public(self):
|
|
77
|
+
return {"device": {"udid": self.udid, "name": self.name}, "ok": self.ok, "steps": self.steps,
|
|
78
|
+
"fields": self.fields, "tunnelAddress": self.address}
|
|
79
|
+
|
|
80
|
+
# -- the steps --------------------------------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
def run(self, usb_attached):
|
|
83
|
+
usb = usb_attached(self.udid)
|
|
84
|
+
self.step("usb", INFO, "USB", {True: "plugged in: unplug it to probe Wi-Fi", False: "not plugged in",
|
|
85
|
+
None: "couldn't tell"}[usb])
|
|
86
|
+
self._network()
|
|
87
|
+
if not self._coredevice():
|
|
88
|
+
return self
|
|
89
|
+
if not self._tunnel():
|
|
90
|
+
return self
|
|
91
|
+
self._readers()
|
|
92
|
+
wda = self._wda()
|
|
93
|
+
if not wda and self.start:
|
|
94
|
+
if self._launch():
|
|
95
|
+
wda = self._wda()
|
|
96
|
+
self._app_relay()
|
|
97
|
+
self._lan()
|
|
98
|
+
self._usbmux()
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def _network(self):
|
|
102
|
+
from .words import NETWORK_PROBLEMS, problem
|
|
103
|
+
try:
|
|
104
|
+
result = (self.network_check or network.peer_check)()
|
|
105
|
+
except Exception: # noqa: BLE001
|
|
106
|
+
result = {"state": "unknown"}
|
|
107
|
+
state = result.get("state")
|
|
108
|
+
code = NETWORK_PROBLEMS.get(state)
|
|
109
|
+
if code:
|
|
110
|
+
found = problem(code, self.name)
|
|
111
|
+
self.step("network", WARN, "This Mac's network", f"{found['message']} {found['fix']}")
|
|
112
|
+
elif state == "ok":
|
|
113
|
+
self.step("network", OK, "This Mac's network", f"devices on it can see each other "
|
|
114
|
+
f"({result.get('interface')})")
|
|
115
|
+
else:
|
|
116
|
+
self.step("network", INFO, "This Mac's network", {"offline": "this Mac isn't on a network"}.get(
|
|
117
|
+
state, "couldn't tell whether devices on it can see each other"))
|
|
118
|
+
|
|
119
|
+
def _coredevice(self):
|
|
120
|
+
try:
|
|
121
|
+
peers = devicectl.list_devices()
|
|
122
|
+
except devicectl.DevicectlError as error:
|
|
123
|
+
self.step("coredevice", FAIL, "CoreDevice", str(error))
|
|
124
|
+
return False
|
|
125
|
+
self.peer = peers.get(self.udid.upper())
|
|
126
|
+
if self.peer is None:
|
|
127
|
+
self.step("coredevice", FAIL, "CoreDevice", "doesn't list this iPhone at all. Pair it with the cable in "
|
|
128
|
+
"Setup, then try again.")
|
|
129
|
+
return False
|
|
130
|
+
self.fields = dict(self.peer.fields)
|
|
131
|
+
seen = ", ".join(f"{key}={value}" for key, value in sorted(self.fields.items())
|
|
132
|
+
if not isinstance(value, list)) or "no connection fields"
|
|
133
|
+
self.step("fields", INFO, "Fields CoreDevice reports", seen)
|
|
134
|
+
if self.peer.wired:
|
|
135
|
+
self.step("coredevice", WARN, "CoreDevice", "sees it on USB (transportType wired). Unplug it and probe "
|
|
136
|
+
"again.")
|
|
137
|
+
return False
|
|
138
|
+
if not self.peer.network:
|
|
139
|
+
self.step("coredevice", FAIL, "CoreDevice", f"doesn't see it over the network (transportType "
|
|
140
|
+
f"{self.peer.transport or 'missing'}). Turn on Wi-Fi with "
|
|
141
|
+
"`mobster wifi on` with the cable, keep the phone unlocked "
|
|
142
|
+
"and on the same Wi-Fi as this Mac.")
|
|
143
|
+
return False
|
|
144
|
+
self.step("coredevice", OK, "CoreDevice", "sees it over the network (transportType localNetwork"
|
|
145
|
+
+ (f", iOS {self.peer.os_version}" if self.peer.os_version else "") + ")")
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
def _tunnel(self):
|
|
149
|
+
peer = self.peer
|
|
150
|
+
if peer.tunnel != "up":
|
|
151
|
+
try:
|
|
152
|
+
found = devicectl.bring_up(self.udid)
|
|
153
|
+
peer = found.get(self.udid.upper()) or peer
|
|
154
|
+
if peer.tunnel != "up":
|
|
155
|
+
peer = devicectl.list_devices().get(self.udid.upper()) or peer
|
|
156
|
+
except devicectl.DevicectlError as error:
|
|
157
|
+
self.step("tunnel", FAIL, "Encrypted tunnel", f"couldn't be brought up: {error}")
|
|
158
|
+
return False
|
|
159
|
+
self.peer = peer
|
|
160
|
+
self.fields = dict(peer.fields) or self.fields
|
|
161
|
+
if peer.tunnel != "up":
|
|
162
|
+
self.step("tunnel", FAIL, "Encrypted tunnel", f"not up (tunnelState {peer.tunnel_state or 'missing'})")
|
|
163
|
+
return False
|
|
164
|
+
try:
|
|
165
|
+
address = check_target(peer.address)
|
|
166
|
+
except ValueError:
|
|
167
|
+
self.step("tunnel", FAIL, "Encrypted tunnel", f"up, but its address ({peer.address or 'missing'}) isn't "
|
|
168
|
+
"an fd00::/8 tunnel address, so Mobster won't use it")
|
|
169
|
+
return False
|
|
170
|
+
interface, problem = network.tunnel_route(address)
|
|
171
|
+
if problem:
|
|
172
|
+
self.step("tunnel", FAIL, "Encrypted tunnel", f"up, but {problem}, so Mobster won't use it")
|
|
173
|
+
return False
|
|
174
|
+
self.address = address
|
|
175
|
+
self.step("tunnel", OK, "Encrypted tunnel", f"up, the phone's end is {address} (through {interface})")
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
def _readers(self):
|
|
179
|
+
lines, clear = [], True
|
|
180
|
+
for name, understood in (("lockState", self._lock_line), ("apps", self._apps_line)):
|
|
181
|
+
try:
|
|
182
|
+
document = devicectl.run_json(["device", "info", name, "--device", self.udid], devicectl.APPS_TIMEOUT)
|
|
183
|
+
result = document.get("result") if isinstance(document, dict) else None
|
|
184
|
+
keys = ", ".join(sorted(result)) if isinstance(result, dict) else "no result"
|
|
185
|
+
line = understood(document)
|
|
186
|
+
except (devicectl.DevicectlError, RuntimeError) as error:
|
|
187
|
+
line, keys = str(error), "none"
|
|
188
|
+
if line is None:
|
|
189
|
+
clear = False
|
|
190
|
+
line = "unreadable"
|
|
191
|
+
lines.append(f"{name}: {line} (fields: {keys})")
|
|
192
|
+
self.step("readers", INFO if clear else WARN, "Reads through CoreDevice", "; ".join(lines))
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _lock_line(document):
|
|
196
|
+
locked = devicectl.lock_state(document)
|
|
197
|
+
return None if locked is None else "locked" if locked else "unlocked"
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _apps_line(document):
|
|
201
|
+
try:
|
|
202
|
+
return (
|
|
203
|
+
f"{len(devicectl.parse_apps(document))} user apps, {len(devicectl.parse_apps(document, 'system'))} "
|
|
204
|
+
"Apple apps")
|
|
205
|
+
except devicectl.DevicectlError:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def _wda(self):
|
|
209
|
+
"""WebDriverAgent on the tunnel address, through a relay on 127.0.0.1 (any free port)."""
|
|
210
|
+
relay = Relay(self.address, [(0, 8100), (0, 9100)], name="wifi-probe")
|
|
211
|
+
try:
|
|
212
|
+
relay.start()
|
|
213
|
+
except OSError as error:
|
|
214
|
+
self.step("wda", FAIL, "WebDriverAgent over the tunnel", f"the probe's relay couldn't start: {error}")
|
|
215
|
+
return False
|
|
216
|
+
try:
|
|
217
|
+
api, video = relay.ports
|
|
218
|
+
status = http_status(api)
|
|
219
|
+
if status is None:
|
|
220
|
+
self.step("wda", WARN, "WebDriverAgent over the tunnel",
|
|
221
|
+
"doesn't answer on [tunnel]:8100 (yet). Start Mobster's helper over Wi-Fi: the Mac app "
|
|
222
|
+
"with Wi-Fi for iPhones on, or this probe with --start.")
|
|
223
|
+
return False
|
|
224
|
+
ready = status.get("ready") is True
|
|
225
|
+
streams = stream_answers(video)
|
|
226
|
+
self.step("wda", OK if ready else WARN, "WebDriverAgent over the tunnel",
|
|
227
|
+
f"/status answers through 127.0.0.1 ({'ready' if ready else 'not ready'}); video port "
|
|
228
|
+
f"{'answers' if streams else 'does not answer'}")
|
|
229
|
+
return ready
|
|
230
|
+
finally:
|
|
231
|
+
relay.stop()
|
|
232
|
+
|
|
233
|
+
def _launch(self):
|
|
234
|
+
"""--start: launch Mobster's helper over Wi-Fi for this probe, and wait for it."""
|
|
235
|
+
from ..device_manager import wifi_runner_command
|
|
236
|
+
from ..mac_tools import tool
|
|
237
|
+
from . import xctestrun
|
|
238
|
+
manager = self.manager
|
|
239
|
+
if manager is None or not manager.built():
|
|
240
|
+
self.step("runner", FAIL, "Mobster's helper over Wi-Fi", "isn't built for this iPhone. Set it up with "
|
|
241
|
+
"the cable first.")
|
|
242
|
+
return False
|
|
243
|
+
xcodebuild = tool("xcodebuild")
|
|
244
|
+
if not xcodebuild:
|
|
245
|
+
self.step("runner", FAIL, "Mobster's helper over Wi-Fi", "Xcode isn't installed or set up")
|
|
246
|
+
return False
|
|
247
|
+
try:
|
|
248
|
+
plan = xctestrun.write(manager.derived / "Build" / "Products", manager.derived / xctestrun.FOLDER,
|
|
249
|
+
self.address)
|
|
250
|
+
except (LookupError, ValueError) as error:
|
|
251
|
+
self.step("runner", FAIL, "Mobster's helper over Wi-Fi", str(error))
|
|
252
|
+
return False
|
|
253
|
+
log_path = Path(manager.logs) / "wifi-probe.log"
|
|
254
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
with open(log_path, "ab") as log:
|
|
256
|
+
process = subprocess.Popen(wifi_runner_command(xcodebuild, plan, self.udid), stdout=log,
|
|
257
|
+
stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, start_new_session=True,
|
|
258
|
+
env=manager.runner_env())
|
|
259
|
+
self._process = process
|
|
260
|
+
relay = Relay(self.address, [(0, 8100)], name="wifi-probe")
|
|
261
|
+
relay.start()
|
|
262
|
+
try:
|
|
263
|
+
deadline = time.monotonic() + START_WAIT
|
|
264
|
+
while time.monotonic() < deadline and process.poll() is None:
|
|
265
|
+
if (http_status(relay.ports[0], timeout=2) or {}).get("ready") is True:
|
|
266
|
+
self.step("runner", OK, "Mobster's helper over Wi-Fi",
|
|
267
|
+
f"started with USE_IP={self.address} (log: {log_path.name})")
|
|
268
|
+
return True
|
|
269
|
+
time.sleep(2)
|
|
270
|
+
finally:
|
|
271
|
+
relay.stop()
|
|
272
|
+
self.step("runner", FAIL, "Mobster's helper over Wi-Fi",
|
|
273
|
+
f"didn't answer within {int(START_WAIT)} s. See {log_path} for xcodebuild's output.")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
def stop_launched(self):
|
|
277
|
+
process = getattr(self, "_process", None)
|
|
278
|
+
if process is None or process.poll() is not None:
|
|
279
|
+
return
|
|
280
|
+
for sig in (signal.SIGTERM, signal.SIGKILL):
|
|
281
|
+
try:
|
|
282
|
+
os.killpg(process.pid, sig)
|
|
283
|
+
process.wait(timeout=5)
|
|
284
|
+
return
|
|
285
|
+
except (ProcessLookupError, PermissionError):
|
|
286
|
+
return
|
|
287
|
+
except subprocess.TimeoutExpired:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
def _app_relay(self):
|
|
291
|
+
if not self.slot_port:
|
|
292
|
+
return
|
|
293
|
+
status = http_status(self.slot_port, timeout=2)
|
|
294
|
+
self.step("app_relay", INFO, "The Mac app's relay",
|
|
295
|
+
f"127.0.0.1:{self.slot_port} " + ("answers" if status is not None else "doesn't answer (fine when "
|
|
296
|
+
"the app isn't running it)"))
|
|
297
|
+
|
|
298
|
+
def _lan(self):
|
|
299
|
+
names = list(self.peer.hostnames) if self.peer is not None else []
|
|
300
|
+
addresses, hits = exposure.exposed(names)
|
|
301
|
+
if hits:
|
|
302
|
+
self.step("lan", FAIL, "The phone's Wi-Fi address",
|
|
303
|
+
"WebDriverAgent ANSWERS on " + ", ".join(hits) + ". Mobster stops a helper that does this "
|
|
304
|
+
"when it starts it. Turn Wi-Fi off for this phone (`mobster wifi off`) and report this.")
|
|
305
|
+
elif addresses:
|
|
306
|
+
self.step("lan", OK, "The phone's Wi-Fi address",
|
|
307
|
+
f"{', '.join(addresses)}: 8100 and 9100 don't answer (also check from another machine)")
|
|
308
|
+
else:
|
|
309
|
+
self.step("lan", WARN, "The phone's Wi-Fi address",
|
|
310
|
+
"couldn't be found. From another machine on the same Wi-Fi, check that `nc -vz <the phone's "
|
|
311
|
+
"Wi-Fi address> 8100` and 9100 fail.")
|
|
312
|
+
|
|
313
|
+
def _usbmux(self):
|
|
314
|
+
listed = usbmux_network(self.udid)
|
|
315
|
+
self.step("usbmux", INFO, "usbmuxd over the network",
|
|
316
|
+
{True: "lists the phone (evidence only; Mobster never uses it for WebDriverAgent)",
|
|
317
|
+
False: "doesn't list the phone", None: "couldn't tell (idevice_id is missing)"}[listed])
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def describe(item):
|
|
321
|
+
glyph = {OK: "✓", WARN: "!", FAIL: "✗", INFO: "·"}[item["state"]]
|
|
322
|
+
return f" {glyph} {item['title']:<32} {item['detail']}"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Wi-Fi's routes and transport monitor, and nothing a task sees: a task runs the same on the cable and on Wi-Fi."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def register(api):
|
|
5
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
6
|
+
from . import api as routes
|
|
7
|
+
routes.register_routes()
|
|
8
|
+
api.register_service(routes.start_service, routes.close_service)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""The local relay for a phone on Wi-Fi (SPEC §3.7 item 4): 127.0.0.1-only forwarders to the phone's tunnel."""
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
4
|
+
import socket
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from ..devices import LOOPBACK
|
|
8
|
+
|
|
9
|
+
TUNNEL_NETWORK = ipaddress.IPv6Network("fd00::/8")
|
|
10
|
+
NOT_AVAILABLE = "Wi-Fi isn't available for this iPhone yet."
|
|
11
|
+
CONNECT_TIMEOUT = 5.0
|
|
12
|
+
MAX_CONNECTIONS = 64
|
|
13
|
+
BUFFER = 65536
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Only the phone's end of the CoreDevice tunnel: the relay never carries WebDriverAgent over the plain LAN.
|
|
17
|
+
def check_target(address):
|
|
18
|
+
if not isinstance(address, str) or not address.strip() or "%" in address:
|
|
19
|
+
raise ValueError(NOT_AVAILABLE)
|
|
20
|
+
try:
|
|
21
|
+
parsed = ipaddress.ip_address(address.strip().strip("[]"))
|
|
22
|
+
except ValueError:
|
|
23
|
+
raise ValueError(NOT_AVAILABLE) from None
|
|
24
|
+
if not isinstance(parsed, ipaddress.IPv6Address) or parsed.ipv4_mapped is not None or parsed not in TUNNEL_NETWORK:
|
|
25
|
+
raise ValueError(NOT_AVAILABLE)
|
|
26
|
+
return parsed.compressed
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def require_tunnel(address, through=None):
|
|
30
|
+
target = check_target(address)
|
|
31
|
+
if through is None:
|
|
32
|
+
from .network import through_tunnel as through
|
|
33
|
+
if not through(target):
|
|
34
|
+
raise ValueError(NOT_AVAILABLE)
|
|
35
|
+
return target
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Relay:
|
|
39
|
+
"""Forwards 127.0.0.1:<local> to [target]:<remote> for each ``(local, remote)`` pair, until ``stop``."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, target, pairs, *, connect_timeout=CONNECT_TIMEOUT, name="wifi-relay"):
|
|
42
|
+
self.target = check_target(target)
|
|
43
|
+
self.pairs = []
|
|
44
|
+
for local, remote in pairs:
|
|
45
|
+
if not all(isinstance(port, int) and not isinstance(port, bool) for port in (local, remote)) \
|
|
46
|
+
or not 0 <= local <= 65535 or not 1 <= remote <= 65535:
|
|
47
|
+
raise ValueError("A relay forwards a port number to a port number")
|
|
48
|
+
self.pairs.append((local, remote))
|
|
49
|
+
if not self.pairs:
|
|
50
|
+
raise ValueError("A relay needs at least one port")
|
|
51
|
+
self.connect_timeout = connect_timeout
|
|
52
|
+
self.name = name
|
|
53
|
+
self.listeners = []
|
|
54
|
+
self.connections = set()
|
|
55
|
+
self.slots = threading.BoundedSemaphore(MAX_CONNECTIONS)
|
|
56
|
+
self.lock = threading.Lock()
|
|
57
|
+
self.stopping = threading.Event()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def running(self):
|
|
61
|
+
return bool(self.listeners) and not self.stopping.is_set()
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def ports(self):
|
|
65
|
+
"""The local ports, in the order of ``pairs`` (after ``start``)."""
|
|
66
|
+
return [listener.getsockname()[1] for listener, _ in self.listeners]
|
|
67
|
+
|
|
68
|
+
def start(self):
|
|
69
|
+
"""Listen on every local port, or none: OSError (a port in use) leaves nothing open."""
|
|
70
|
+
if self.running:
|
|
71
|
+
return self
|
|
72
|
+
self.stopping.clear()
|
|
73
|
+
opened = []
|
|
74
|
+
try:
|
|
75
|
+
for local, remote in self.pairs:
|
|
76
|
+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
77
|
+
opened.append((listener, remote))
|
|
78
|
+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
79
|
+
listener.bind((LOOPBACK, local))
|
|
80
|
+
listener.listen(16)
|
|
81
|
+
except OSError:
|
|
82
|
+
for listener, _ in opened:
|
|
83
|
+
listener.close()
|
|
84
|
+
raise
|
|
85
|
+
self.listeners = opened
|
|
86
|
+
for listener, remote in opened:
|
|
87
|
+
threading.Thread(target=self._accept, args=(listener, remote), daemon=True,
|
|
88
|
+
name=f"mobster-{self.name}-{listener.getsockname()[1]}").start()
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def stop(self):
|
|
92
|
+
self.stopping.set()
|
|
93
|
+
for listener, _ in self.listeners:
|
|
94
|
+
try:
|
|
95
|
+
listener.close()
|
|
96
|
+
except OSError:
|
|
97
|
+
pass
|
|
98
|
+
with self.lock:
|
|
99
|
+
live = list(self.connections)
|
|
100
|
+
self.connections.clear()
|
|
101
|
+
for sock in live:
|
|
102
|
+
_close(sock)
|
|
103
|
+
|
|
104
|
+
# -- forwarding ------------------------------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
def _accept(self, listener, remote):
|
|
107
|
+
while not self.stopping.is_set():
|
|
108
|
+
try:
|
|
109
|
+
client, _ = listener.accept()
|
|
110
|
+
except OSError:
|
|
111
|
+
return
|
|
112
|
+
if not self.slots.acquire(blocking=False):
|
|
113
|
+
_close(client)
|
|
114
|
+
continue
|
|
115
|
+
with self.lock:
|
|
116
|
+
if self.stopping.is_set():
|
|
117
|
+
self.slots.release()
|
|
118
|
+
_close(client)
|
|
119
|
+
continue
|
|
120
|
+
# Count and track clients before upstream connects: a missing
|
|
121
|
+
# tunnel must not leave unbounded pending threads or sockets.
|
|
122
|
+
self.connections.add(client)
|
|
123
|
+
threading.Thread(target=self._serve, args=(client, remote), daemon=True,
|
|
124
|
+
name=f"mobster-{self.name}-conn").start()
|
|
125
|
+
|
|
126
|
+
def _serve(self, client, remote):
|
|
127
|
+
upstream = None
|
|
128
|
+
try:
|
|
129
|
+
try:
|
|
130
|
+
upstream = socket.create_connection((self.target, remote), timeout=self.connect_timeout)
|
|
131
|
+
except OSError:
|
|
132
|
+
return
|
|
133
|
+
with self.lock:
|
|
134
|
+
if self.stopping.is_set():
|
|
135
|
+
return
|
|
136
|
+
upstream.settimeout(None)
|
|
137
|
+
client.settimeout(None)
|
|
138
|
+
self.connections.add(upstream)
|
|
139
|
+
other = threading.Thread(target=self._pump, args=(upstream, client), daemon=True,
|
|
140
|
+
name=f"mobster-{self.name}-pump")
|
|
141
|
+
other.start()
|
|
142
|
+
self._pump(client, upstream)
|
|
143
|
+
other.join()
|
|
144
|
+
finally:
|
|
145
|
+
with self.lock:
|
|
146
|
+
self.connections.discard(client)
|
|
147
|
+
self.connections.discard(upstream)
|
|
148
|
+
_close(client)
|
|
149
|
+
if upstream is not None:
|
|
150
|
+
_close(upstream)
|
|
151
|
+
self.slots.release()
|
|
152
|
+
|
|
153
|
+
def _pump(self, source, target):
|
|
154
|
+
"""Copy ``source`` to ``target`` until ``source`` ends; then end ``target``'s sending side."""
|
|
155
|
+
try:
|
|
156
|
+
while True:
|
|
157
|
+
data = source.recv(BUFFER)
|
|
158
|
+
if not data:
|
|
159
|
+
break
|
|
160
|
+
target.sendall(data)
|
|
161
|
+
except OSError:
|
|
162
|
+
_close(source)
|
|
163
|
+
_close(target)
|
|
164
|
+
return
|
|
165
|
+
try:
|
|
166
|
+
target.shutdown(socket.SHUT_WR)
|
|
167
|
+
except OSError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _close(sock):
|
|
172
|
+
try:
|
|
173
|
+
sock.shutdown(socket.SHUT_RDWR)
|
|
174
|
+
except OSError:
|
|
175
|
+
pass
|
|
176
|
+
try:
|
|
177
|
+
sock.close()
|
|
178
|
+
except OSError:
|
|
179
|
+
pass
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .probe import FAIL, WARN
|
|
9
|
+
|
|
10
|
+
FILE = "wifi-probe.json"
|
|
11
|
+
NOT_RUN = {"state": "not_run", "at": None, "reason": None}
|
|
12
|
+
KEEP = 20
|
|
13
|
+
UNFINISHED = "The check stopped before it could tell. Run it again with the phone unplugged."
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _path(data_dir):
|
|
17
|
+
return Path(data_dir) / FILE
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read(data_dir):
|
|
21
|
+
try:
|
|
22
|
+
value = json.loads(_path(data_dir).read_text())
|
|
23
|
+
except (OSError, ValueError):
|
|
24
|
+
return {}
|
|
25
|
+
return value if isinstance(value, dict) else {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _clean(value):
|
|
29
|
+
if not isinstance(value, dict) or value.get("state") not in ("passed", "failed"):
|
|
30
|
+
return dict(NOT_RUN)
|
|
31
|
+
at, reason = value.get("at"), value.get("reason")
|
|
32
|
+
return {"state": value["state"], "at": at if isinstance(at, int) and not isinstance(at, bool) else None,
|
|
33
|
+
"reason": reason[:300] if isinstance(reason, str) and reason else None}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def reason_of(steps):
|
|
37
|
+
"""Why a probe did not pass, in the words of the step that failed (else warned): "<title>: <detail>"."""
|
|
38
|
+
for state in (FAIL, WARN):
|
|
39
|
+
step = next((item for item in steps if item.get("state") == state), None)
|
|
40
|
+
if step is not None:
|
|
41
|
+
detail = str(step.get("detail") or "").strip()
|
|
42
|
+
return f"{step.get('title')}: {detail}" if detail else str(step.get("title"))
|
|
43
|
+
return UNFINISHED
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def record(data_dir, udid, public, now=None):
|
|
47
|
+
steps = public.get("steps") or []
|
|
48
|
+
usb = next((item for item in steps if item.get("key") == "usb"), None)
|
|
49
|
+
# a probe made with the cable plugged in says nothing about Wi-Fi, so it isn't kept
|
|
50
|
+
if usb is not None and str(usb.get("detail", "")).startswith("plugged in"):
|
|
51
|
+
return None
|
|
52
|
+
entry = {"state": "passed", "at": None, "reason": None} if public.get("ok") else \
|
|
53
|
+
{"state": "failed", "at": None, "reason": reason_of(steps)}
|
|
54
|
+
entry["at"] = int((time.time() if now is None else now) * 1000)
|
|
55
|
+
kept = _read(data_dir)
|
|
56
|
+
kept[str(udid).upper()] = entry
|
|
57
|
+
for old in sorted(kept, key=lambda key: _clean(kept[key])["at"] or 0)[:-KEEP]:
|
|
58
|
+
del kept[old]
|
|
59
|
+
Path(data_dir).mkdir(parents=True, exist_ok=True)
|
|
60
|
+
handle, temp = tempfile.mkstemp(dir=str(data_dir), prefix=".wifi-probe-")
|
|
61
|
+
try:
|
|
62
|
+
with os.fdopen(handle, "w") as out:
|
|
63
|
+
json.dump(kept, out, separators=(",", ":"))
|
|
64
|
+
os.chmod(temp, 0o600)
|
|
65
|
+
os.replace(temp, _path(data_dir))
|
|
66
|
+
except OSError:
|
|
67
|
+
try:
|
|
68
|
+
os.unlink(temp)
|
|
69
|
+
except OSError:
|
|
70
|
+
pass
|
|
71
|
+
return None
|
|
72
|
+
return entry
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get(data_dir, udid):
|
|
76
|
+
"""``udid``'s last probe, or not_run."""
|
|
77
|
+
return _clean(_read(data_dir).get(str(udid).upper()))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def latest(data_dir):
|
|
81
|
+
"""This Mac's most recent probe of any phone, with the phone's udid; not_run when none was kept."""
|
|
82
|
+
kept = {udid: _clean(value) for udid, value in _read(data_dir).items()}
|
|
83
|
+
kept = {udid: value for udid, value in kept.items() if value["state"] != "not_run"}
|
|
84
|
+
if not kept:
|
|
85
|
+
return dict(NOT_RUN)
|
|
86
|
+
udid = max(kept, key=lambda key: kept[key]["at"] or 0)
|
|
87
|
+
return {**kept[udid], "udid": udid}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Which phones may be reached over Wi-Fi: ``wifi: {enabled, enabledAt, via}`` per phone (SPEC §3.7). ``via`` says
|
|
2
|
+
who turned on the phone's own switch (Connect via network); with Wi-Fi off it is kept only while that switch is one
|
|
3
|
+
Mobster turned on and couldn't turn off yet, so a later `mobster wifi off` over the cable still can."""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
VIA = ("mobster", "xcode")
|
|
9
|
+
OFF = {"enabled": False, "enabledAt": None, "via": None}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _clean(value):
|
|
13
|
+
if not isinstance(value, dict):
|
|
14
|
+
return dict(OFF)
|
|
15
|
+
via = value.get("via") if value.get("via") in VIA else None
|
|
16
|
+
if value.get("enabled") is not True:
|
|
17
|
+
return {**OFF, "via": via}
|
|
18
|
+
at = value.get("enabledAt")
|
|
19
|
+
return {"enabled": True, "enabledAt": at if isinstance(at, int) and not isinstance(at, bool) else None,
|
|
20
|
+
"via": via}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _primary(data_dir):
|
|
24
|
+
from ..cli_constants import DEFAULT_WDA_URL
|
|
25
|
+
from ..device_manager import DeviceManager
|
|
26
|
+
return DeviceManager(Path(data_dir), DEFAULT_WDA_URL)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _same(a, b):
|
|
30
|
+
return isinstance(a, str) and isinstance(b, str) and a.upper() == b.upper()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get(data_dir, udid):
|
|
34
|
+
"""{"enabled", "enabledAt", "via"} for ``udid``; off when it isn't set up here or never was turned on."""
|
|
35
|
+
from ..devices import DeviceStore
|
|
36
|
+
entry = next((item for item in DeviceStore(data_dir).phones() if _same(item["udid"], udid)), None)
|
|
37
|
+
if entry is not None:
|
|
38
|
+
return _clean(entry.get("wifi"))
|
|
39
|
+
settings = _primary(data_dir).settings() if Path(data_dir, "device.json").exists() else {}
|
|
40
|
+
wifi = settings.get("wifi")
|
|
41
|
+
# the primary's entry keeps the UDID it belongs to, so choosing another primary phone never carries it over
|
|
42
|
+
if _same(settings.get("udid"), udid) and isinstance(wifi, dict) and _same(wifi.get("udid"), udid):
|
|
43
|
+
return _clean(wifi)
|
|
44
|
+
return dict(OFF)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def enabled(data_dir, udid):
|
|
48
|
+
return get(data_dir, udid)["enabled"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_set_up(data_dir, udid):
|
|
52
|
+
"""True when ``udid`` is the phone chosen in Setup or one added besides it."""
|
|
53
|
+
from ..devices import DeviceStore
|
|
54
|
+
if any(_same(item["udid"], udid) for item in DeviceStore(data_dir).phones()):
|
|
55
|
+
return True
|
|
56
|
+
return _same(_primary(data_dir).settings().get("udid"), udid)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def put(data_dir, udid, on, via=None):
|
|
60
|
+
from ..devices import DeviceStore
|
|
61
|
+
if via is not None and via not in VIA:
|
|
62
|
+
raise ValueError("via is mobster or xcode")
|
|
63
|
+
value = ({"enabled": True, "enabledAt": round(time.time() * 1000), "via": via} if on else {**OFF, "via": via})
|
|
64
|
+
kept = on or via is not None
|
|
65
|
+
store = DeviceStore(data_dir)
|
|
66
|
+
if any(_same(item["udid"], udid) for item in store.phones()):
|
|
67
|
+
store.set_wifi(udid, value if kept else None)
|
|
68
|
+
return _clean(value)
|
|
69
|
+
manager = _primary(data_dir)
|
|
70
|
+
if not _same(manager.settings().get("udid"), udid):
|
|
71
|
+
raise LookupError("not_set_up")
|
|
72
|
+
manager.save_settings(wifi={**value, "udid": udid} if kept else None)
|
|
73
|
+
return _clean(value)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def enabled_udids(data_dir):
|
|
77
|
+
"""The UDIDs (upper case) with Wi-Fi on, of every phone set up here."""
|
|
78
|
+
from ..devices import DeviceStore
|
|
79
|
+
found = {item["udid"].upper() for item in DeviceStore(data_dir).phones() if _clean(item.get("wifi"))["enabled"]}
|
|
80
|
+
if Path(data_dir, "device.json").exists():
|
|
81
|
+
settings = _primary(data_dir).settings()
|
|
82
|
+
wifi = settings.get("wifi")
|
|
83
|
+
if isinstance(wifi, dict) and _same(settings.get("udid"), wifi.get("udid")) and _clean(wifi)["enabled"]:
|
|
84
|
+
found.add(settings["udid"].upper())
|
|
85
|
+
return found
|