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,243 @@
|
|
|
1
|
+
"""A watchdog for a wedged WebDriverAgent runner or relay, and the record of actions in flight.
|
|
2
|
+
|
|
3
|
+
``DeviceManager.probe`` feeds it one result per probe (``GET /wda/locked``, bounded at 3 s). After
|
|
4
|
+
FAILURES_TO_WEDGE failures in a row the phone is ``wedged``; once it has stayed wedged for WEDGED_RESTART_SECONDS the
|
|
5
|
+
watchdog restarts the runner, subject to the rules below. They are the safety case, and tests/test_wda_watchdog.py
|
|
6
|
+
has one test for each:
|
|
7
|
+
|
|
8
|
+
1. **An action in flight is never re-sent, and never restarted under.** ``ActionTracker`` records every WDA action
|
|
9
|
+
(tap, swipe, key, button, element value, app activate) from just before it is sent until it answers or fails.
|
|
10
|
+
A restart waits while one is in flight (``admit_restart`` checks that, the unknown outcomes and the task, and blocks,
|
|
11
|
+
in one step). When one ends without an answer its outcome is *unknown*: it is
|
|
12
|
+
recorded, the transport never replays it (transport.HTTP), the effect ledger keeps blocking the same effect
|
|
13
|
+
(effect_ledger.py), and the run ends or goes on from a fresh look. The watchdog sends nothing.
|
|
14
|
+
2. **While a restart runs, no new action leaves.** ``admit_restart``, ``run_started`` and ``begin`` share one lock: an
|
|
15
|
+
action that starts
|
|
16
|
+
after the restart began is refused before it is sent ("nothing sent"), a known outcome.
|
|
17
|
+
3. **A restart with a task running happens only between actions** (nothing in flight, no unknown outcome waiting to
|
|
18
|
+
be settled), never on a dead runner: a runner that is not running ends the run with a plain sentence instead
|
|
19
|
+
(``on_dead_runner``), because there is nothing to restart into a task safely.
|
|
20
|
+
4. **Backoff, no storm.** The gap between restarts doubles for each restart in a row (MIN_RESTART_GAP, 2x, 4x...
|
|
21
|
+
up to MAX_RESTART_GAP), and resets after HEALTHY_SECONDS of passing probes.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import threading
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
from ..transport import NotSent
|
|
28
|
+
from .timeouts import is_action
|
|
29
|
+
|
|
30
|
+
FAILURES_TO_WEDGE = 3
|
|
31
|
+
WEDGED_RESTART_SECONDS = 90.0
|
|
32
|
+
MIN_RESTART_GAP = 180.0
|
|
33
|
+
MAX_RESTART_GAP = 1800.0
|
|
34
|
+
HEALTHY_SECONDS = 120.0
|
|
35
|
+
|
|
36
|
+
DEAD_RUNNER = ("Mobster's helper on your iPhone stopped, so this task ended. No further action was taken. "
|
|
37
|
+
"Check that your iPhone is connected and unlocked, then run the task again.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ActionBlocked(NotSent):
|
|
41
|
+
"""An action refused before it was sent, because the runner is restarting. Nothing happened on the phone."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ActionTracker:
|
|
45
|
+
UNKNOWN_KEPT = 20
|
|
46
|
+
|
|
47
|
+
def __init__(self, clock=time.time):
|
|
48
|
+
self.clock = clock
|
|
49
|
+
self.lock = threading.Lock()
|
|
50
|
+
self.flying = {} # token -> (url, method, path, started, run_id)
|
|
51
|
+
self.unknown = {} # url -> [(method, path, at, run_id)]
|
|
52
|
+
self.blocked = set()
|
|
53
|
+
self.runs = {} # url -> run id of the task holding the phone
|
|
54
|
+
self.last_action = {} # url -> when the last action was sent (wall clock)
|
|
55
|
+
self.next_token = 0
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _key(url):
|
|
59
|
+
return (url or "").rstrip("/")
|
|
60
|
+
|
|
61
|
+
def run_started(self, url, run_id):
|
|
62
|
+
"""A task takes the phone. Raises ActionBlocked while a restart runs: the start is refused, nothing sent."""
|
|
63
|
+
url = self._key(url)
|
|
64
|
+
with self.lock:
|
|
65
|
+
if url in self.blocked:
|
|
66
|
+
raise ActionBlocked("Mobster's helper is restarting; nothing sent")
|
|
67
|
+
self.runs[url] = run_id
|
|
68
|
+
|
|
69
|
+
def run_ended(self, url, run_id):
|
|
70
|
+
"""The task is over: its journal holds what it left unknown, so the tracker forgets those outcomes."""
|
|
71
|
+
url = self._key(url)
|
|
72
|
+
with self.lock:
|
|
73
|
+
if self.runs.get(url) != run_id:
|
|
74
|
+
return # a waiting or failed task cannot retire the actual holder's unknown outcome
|
|
75
|
+
self.runs.pop(url, None)
|
|
76
|
+
self.unknown.pop(url, None)
|
|
77
|
+
|
|
78
|
+
def has_run(self, url):
|
|
79
|
+
with self.lock:
|
|
80
|
+
return self._key(url) in self.runs
|
|
81
|
+
|
|
82
|
+
def begin(self, url, method, path):
|
|
83
|
+
"""Record an action about to be sent; None for a read. Raises ActionBlocked while a restart runs."""
|
|
84
|
+
if not is_action(method, path):
|
|
85
|
+
return None
|
|
86
|
+
url = self._key(url)
|
|
87
|
+
with self.lock:
|
|
88
|
+
if url in self.blocked:
|
|
89
|
+
raise ActionBlocked("Mobster's helper is restarting; nothing sent")
|
|
90
|
+
self.next_token += 1
|
|
91
|
+
self.flying[self.next_token] = (url, method, path, self.clock(), self.runs.get(url))
|
|
92
|
+
self.last_action[url] = self.clock()
|
|
93
|
+
return self.next_token
|
|
94
|
+
|
|
95
|
+
def end(self, token, outcome="done"):
|
|
96
|
+
"""outcome: "done" (answered), "not_sent" (refused before it left) or "unknown" (it left; no answer)."""
|
|
97
|
+
if token is None:
|
|
98
|
+
return
|
|
99
|
+
with self.lock:
|
|
100
|
+
entry = self.flying.pop(token, None)
|
|
101
|
+
if outcome == "unknown" and entry is not None:
|
|
102
|
+
url, method, path, _, run_id = entry
|
|
103
|
+
self.unknown.setdefault(url, []).append((method, path, self.clock(), run_id))
|
|
104
|
+
del self.unknown[url][:-self.UNKNOWN_KEPT]
|
|
105
|
+
|
|
106
|
+
def last_action_at(self, url):
|
|
107
|
+
with self.lock:
|
|
108
|
+
return self.last_action.get(self._key(url))
|
|
109
|
+
|
|
110
|
+
def in_flight(self, url):
|
|
111
|
+
url = self._key(url)
|
|
112
|
+
with self.lock:
|
|
113
|
+
return [(m, p) for u, m, p, *_ in self.flying.values() if u == url]
|
|
114
|
+
|
|
115
|
+
def unknown_outcomes(self, url):
|
|
116
|
+
with self.lock:
|
|
117
|
+
return [(m, p, at) for m, p, at, _ in self.unknown.get(self._key(url), ())]
|
|
118
|
+
|
|
119
|
+
def admit_restart(self, url):
|
|
120
|
+
"""Decide and block in one step: None when new actions are now blocked, else why the restart may not go."""
|
|
121
|
+
url = self._key(url)
|
|
122
|
+
with self.lock:
|
|
123
|
+
if url in self.blocked:
|
|
124
|
+
return "restarting"
|
|
125
|
+
if any(u == url for u, *_ in self.flying.values()):
|
|
126
|
+
return "action_in_flight"
|
|
127
|
+
if url in self.runs and self.unknown.get(url):
|
|
128
|
+
return "unknown_outcome"
|
|
129
|
+
self.blocked.add(url)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def unblock(self, url):
|
|
133
|
+
with self.lock:
|
|
134
|
+
self.blocked.discard(self._key(url))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
tracker = ActionTracker()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def send(url, method, path, send_request):
|
|
141
|
+
"""Run ``send_request()`` for a WDA request, recording it when it is an action. Never retries."""
|
|
142
|
+
token = tracker.begin(url, method, path)
|
|
143
|
+
try:
|
|
144
|
+
result = send_request()
|
|
145
|
+
except NotSent:
|
|
146
|
+
tracker.end(token, "not_sent")
|
|
147
|
+
raise
|
|
148
|
+
except Exception:
|
|
149
|
+
tracker.end(token, "unknown")
|
|
150
|
+
raise
|
|
151
|
+
tracker.end(token, "done")
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Watchdog:
|
|
156
|
+
def __init__(self, restart, *, url=lambda: None, busy=lambda: False, alive=lambda: True, on_dead_runner=None,
|
|
157
|
+
actions=tracker, wall=time.time):
|
|
158
|
+
"""``restart`` stops and starts the runner and relay; ``on_dead_runner(message)`` ends the running task."""
|
|
159
|
+
self.restart, self.url, self.busy, self.alive = restart, url, busy, alive
|
|
160
|
+
self.on_dead_runner, self.actions, self.wall = on_dead_runner, actions, wall
|
|
161
|
+
self.state, self.restarts, self.last_restart = "ok", 0, None
|
|
162
|
+
self.failures, self.wedged_since = 0, None
|
|
163
|
+
self.last_restart_at = None
|
|
164
|
+
self.streak = 0 # restarts in a row, for the backoff
|
|
165
|
+
self.healthy_since = None
|
|
166
|
+
self.deferred = None # why the last restart did not happen: a short id
|
|
167
|
+
|
|
168
|
+
def probe_result(self, ok, now):
|
|
169
|
+
"""One probe's result at monotonic time ``now``. Returns True when it restarted the runner."""
|
|
170
|
+
if ok:
|
|
171
|
+
self.failures, self.wedged_since, self.deferred = 0, None, None
|
|
172
|
+
if self.state != "restarting":
|
|
173
|
+
self.state = "ok"
|
|
174
|
+
if self.healthy_since is None:
|
|
175
|
+
self.healthy_since = now
|
|
176
|
+
elif now - self.healthy_since >= HEALTHY_SECONDS:
|
|
177
|
+
self.streak = 0
|
|
178
|
+
return False
|
|
179
|
+
self.healthy_since = None
|
|
180
|
+
self.failures += 1
|
|
181
|
+
if self.wedged_since is None:
|
|
182
|
+
self.wedged_since = now
|
|
183
|
+
if self.failures >= FAILURES_TO_WEDGE:
|
|
184
|
+
self.state = "wedged"
|
|
185
|
+
else:
|
|
186
|
+
return False
|
|
187
|
+
if now - self.wedged_since < WEDGED_RESTART_SECONDS:
|
|
188
|
+
return False
|
|
189
|
+
return self._try_restart(now)
|
|
190
|
+
|
|
191
|
+
def reset_failures(self):
|
|
192
|
+
"""The runner is stopped on purpose or still starting: missed checks count for nothing."""
|
|
193
|
+
self.failures, self.wedged_since = 0, None
|
|
194
|
+
if self.state == "wedged":
|
|
195
|
+
self.state = "ok"
|
|
196
|
+
|
|
197
|
+
def gap(self):
|
|
198
|
+
return min(MAX_RESTART_GAP, MIN_RESTART_GAP * 2 ** max(0, self.streak - 1))
|
|
199
|
+
|
|
200
|
+
def admit(self, now):
|
|
201
|
+
"""Every automatic restart asks here: None to go (actions blocked until ``done``), else why it must not."""
|
|
202
|
+
url = self.url()
|
|
203
|
+
if self.last_restart_at is not None and now - self.last_restart_at < self.gap():
|
|
204
|
+
return "backoff"
|
|
205
|
+
reason = self.actions.admit_restart(url)
|
|
206
|
+
if reason is not None:
|
|
207
|
+
return reason
|
|
208
|
+
self.state = "restarting"
|
|
209
|
+
self.last_restart_at = now
|
|
210
|
+
self.streak += 1
|
|
211
|
+
self.restarts += 1
|
|
212
|
+
self.last_restart = int(self.wall() * 1000)
|
|
213
|
+
self.failures, self.wedged_since = 0, None
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def done(self, ok=True):
|
|
217
|
+
self.actions.unblock(self.url())
|
|
218
|
+
self.state = "ok" if ok else "wedged"
|
|
219
|
+
|
|
220
|
+
def _try_restart(self, now):
|
|
221
|
+
url = self.url()
|
|
222
|
+
if not self.alive():
|
|
223
|
+
# Nothing to restart into: with a task running it ends the task with a plain sentence. Without one the
|
|
224
|
+
# supervisor's gated relaunch and autostart bring it back.
|
|
225
|
+
if (self.actions.has_run(url) or self.busy()) and callable(self.on_dead_runner):
|
|
226
|
+
self.deferred = "dead_runner_with_task"
|
|
227
|
+
self.on_dead_runner(DEAD_RUNNER)
|
|
228
|
+
return False
|
|
229
|
+
reason = self.admit(now)
|
|
230
|
+
if reason is not None:
|
|
231
|
+
self.deferred = reason
|
|
232
|
+
return False
|
|
233
|
+
self.deferred = None
|
|
234
|
+
try:
|
|
235
|
+
self.restart()
|
|
236
|
+
except Exception: # noqa: BLE001 -- the next probes decide; backoff holds the next try
|
|
237
|
+
self.done(False)
|
|
238
|
+
return False
|
|
239
|
+
self.done(True)
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
def snapshot(self):
|
|
243
|
+
return {"state": self.state, "restarts": self.restarts, "last_restart": self.last_restart}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Track `wireless`: Wi-Fi after one cable pairing, through Apple's encrypted CoreDevice tunnel (SPEC §3.7)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# What an unset MOBSTER_WIFI_TRANSPORT means. This is the one line to flip, and only after `mobster wifi probe`
|
|
6
|
+
# passes on a phone with its cable out (read_screen, launch_app, install_app and unlock_status over Wi-Fi too).
|
|
7
|
+
DEFAULT_ON = False
|
|
8
|
+
GATE = "MOBSTER_WIFI_TRANSPORT"
|
|
9
|
+
ON_WORDS = ("1", "true", "yes", "on")
|
|
10
|
+
OFF_WORDS = ("0", "false", "no", "off")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def transport_on(env=None):
|
|
14
|
+
value = str((os.environ if env is None else env).get(GATE, "")).strip().lower()
|
|
15
|
+
return True if value in ON_WORDS else False if value in OFF_WORDS else DEFAULT_ON
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Wi-Fi's HTTP routes (seam S3) and the monitor service (SPEC §3.7, API)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from urllib.parse import unquote
|
|
7
|
+
|
|
8
|
+
from .. import api_routes
|
|
9
|
+
from ..api_routes import Response, Route
|
|
10
|
+
from . import GATE, result, store, switch, transport, transport_on, words
|
|
11
|
+
from .monitor import Monitor, task_on
|
|
12
|
+
|
|
13
|
+
# id(runtime) -> (runtime, Monitor): only for runtimes that manage USB phones, made when Wi-Fi for iPhones is on.
|
|
14
|
+
_monitors = {}
|
|
15
|
+
_monitors_lock = threading.Lock()
|
|
16
|
+
# GET /api/wireless/network's last answer: (monotonic time, result). One check at a time; reused for NETWORK_FRESH s.
|
|
17
|
+
NETWORK_FRESH = 15.0
|
|
18
|
+
_network = {"at": None, "result": None}
|
|
19
|
+
_network_lock = threading.Lock()
|
|
20
|
+
# The Wi-Fi checks Add a device starts (POST /api/wireless/probe): UDID -> the running or last one. A check still
|
|
21
|
+
# running after PROBE_SECONDS reads as timed out, and its late end changes nothing.
|
|
22
|
+
PROBE_SECONDS = 180.0
|
|
23
|
+
_probes = {}
|
|
24
|
+
_probes_lock = threading.Lock()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _managed(runtime):
|
|
28
|
+
return getattr(runtime, "manager", None) is not None and getattr(runtime, "fleet", None) is not None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# -- the service ----------------------------------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
def start_service(runtime):
|
|
34
|
+
if _managed(runtime) and transport_on():
|
|
35
|
+
monitor_for(runtime, create=True).start()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def close_service(runtime):
|
|
39
|
+
with _monitors_lock:
|
|
40
|
+
entry = _monitors.pop(id(runtime), None)
|
|
41
|
+
if entry is not None and entry[0] is runtime:
|
|
42
|
+
entry[1].stop()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def monitor_for(runtime, create=False):
|
|
46
|
+
"""The runtime's monitor; with ``create``, made when missing (a runtime that manages USB phones only)."""
|
|
47
|
+
with _monitors_lock:
|
|
48
|
+
entry = _monitors.get(id(runtime))
|
|
49
|
+
if entry is not None and entry[0] is runtime:
|
|
50
|
+
return entry[1]
|
|
51
|
+
if not create or not _managed(runtime):
|
|
52
|
+
return None
|
|
53
|
+
monitor = Monitor(runtime)
|
|
54
|
+
_monitors[id(runtime)] = (runtime, monitor)
|
|
55
|
+
return monitor
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# -- what a phone's Wi-Fi looks like ------------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def _phones(runtime):
|
|
61
|
+
return runtime.fleet.usb_managers() if _managed(runtime) else []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _name(phone, manager):
|
|
65
|
+
name = None
|
|
66
|
+
try:
|
|
67
|
+
name = manager.settings().get("device_name") if phone.primary else None
|
|
68
|
+
except Exception:
|
|
69
|
+
name = None
|
|
70
|
+
return phone.name or (name if isinstance(name, str) and name else None)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def row(runtime, phone, udid, manager):
|
|
74
|
+
"""One phone's Wi-Fi, as GET /api/wireless lists it."""
|
|
75
|
+
data_dir = runtime.fleet.data_dir
|
|
76
|
+
settings = store.get(data_dir, udid)
|
|
77
|
+
link = transport.link(udid) if transport_on() else None
|
|
78
|
+
name = _name(phone, manager)
|
|
79
|
+
running = manager.runner is not None and manager.runner.running
|
|
80
|
+
now = (link.transport if link is not None else
|
|
81
|
+
getattr(manager, "transport", None) if running else None)
|
|
82
|
+
problem = link.problem if link is not None and settings["enabled"] else None
|
|
83
|
+
return {"id": udid, "udid": udid, "name": name, "primary": bool(getattr(phone, "primary", False)),
|
|
84
|
+
"enabled": settings["enabled"], "transport": now,
|
|
85
|
+
"tunnel": link.tunnel if link is not None else "unknown",
|
|
86
|
+
"reachable": bool(link is not None and link.reachable),
|
|
87
|
+
"lastSeenAt": link.last_seen_at if link is not None else None,
|
|
88
|
+
"probe": _probe_view(udid, result.get(data_dir, udid)),
|
|
89
|
+
"lastCheck": _check_view(udid),
|
|
90
|
+
"problem": words.problem(problem, name) if problem in words.PROBLEMS else None}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _probe_view(udid, kept):
|
|
94
|
+
"""The kept probe, or while a check runs: ``{state: "running", step, at}`` (the step it is on)."""
|
|
95
|
+
check = _current(udid)
|
|
96
|
+
if check is not None and check["state"] == "running":
|
|
97
|
+
return {"state": "running", "at": kept["at"], "reason": kept["reason"], "step": check["step"]}
|
|
98
|
+
return kept
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _check_view(udid):
|
|
102
|
+
"""The check Add a device started on this phone and how it ended, or None."""
|
|
103
|
+
check = _current(udid)
|
|
104
|
+
return None if check is None else {key: check[key] for key in ("state", "step", "startedAt", "reason", "next")}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _current(udid, clock=time.monotonic):
|
|
108
|
+
with _probes_lock:
|
|
109
|
+
check = _probes.get(str(udid).upper())
|
|
110
|
+
if check is not None and check["state"] == "running" and clock() - check["began"] >= PROBE_SECONDS:
|
|
111
|
+
check.update(state="timeout", reason=None, next=words.problem("probe_timeout", check["name"]))
|
|
112
|
+
return check
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# The step that failed -> what to tell the person (words.PROBLEMS).
|
|
116
|
+
STEP_PROBLEMS = {"coredevice": "not_seen", "tunnel": "tunnel_down", "wda": "runner", "lan": "not_seen"}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _next_step(probe, name):
|
|
120
|
+
"""The problem the check found, with its one fix: from the step that failed (else warned), else None."""
|
|
121
|
+
usb = next((item for item in probe.steps if item["key"] == "usb"), None)
|
|
122
|
+
if usb is not None and usb["detail"].startswith("plugged in"):
|
|
123
|
+
return words.problem("plugged_in", name)
|
|
124
|
+
for state in ("fail", "warn"):
|
|
125
|
+
step = next((item for item in probe.steps if item["state"] == state), None)
|
|
126
|
+
if step is not None:
|
|
127
|
+
code = STEP_PROBLEMS.get(step["key"])
|
|
128
|
+
return words.problem(code, name) if code else None
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def network_view(result):
|
|
133
|
+
"""``{state, problem}`` for a network.peer_check result (problem: null or {code, message, fix}); None for None."""
|
|
134
|
+
if not isinstance(result, dict):
|
|
135
|
+
return None
|
|
136
|
+
code = words.NETWORK_PROBLEMS.get(result.get("state"))
|
|
137
|
+
return {"state": result.get("state") or "unknown", "problem": words.problem(code) if code else None}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def overview(runtime):
|
|
141
|
+
monitor = monitor_for(runtime) if transport_on() else None
|
|
142
|
+
with _network_lock:
|
|
143
|
+
checked = _network["result"]
|
|
144
|
+
data_dir = getattr(getattr(runtime, "fleet", None), "data_dir", None)
|
|
145
|
+
return {"wifiTransport": transport_on(),
|
|
146
|
+
"probe": result.latest(data_dir) if data_dir else dict(result.NOT_RUN),
|
|
147
|
+
"network": network_view(monitor.network if monitor is not None and monitor.network else checked),
|
|
148
|
+
"devices": [row(runtime, phone, udid, manager) for phone, udid, manager in _phones(runtime)]}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _find(runtime, ident):
|
|
152
|
+
"""(phone, UDID, manager) for a USB phone set up here, or raises a Response-shaped APIError."""
|
|
153
|
+
from ..api_errors import APIError
|
|
154
|
+
from ..devices import AmbiguousDevice
|
|
155
|
+
if not _managed(runtime):
|
|
156
|
+
raise APIError("This Mobster doesn't manage USB iPhones.", 409, "not_managed")
|
|
157
|
+
fleet = runtime.fleet
|
|
158
|
+
try:
|
|
159
|
+
phone = fleet.lookup(unquote(ident))
|
|
160
|
+
except AmbiguousDevice as error:
|
|
161
|
+
raise APIError(str(error), 400, "device_ambiguous") from None
|
|
162
|
+
except LookupError as error:
|
|
163
|
+
raise APIError(str(error), 404, "device_not_found") from None
|
|
164
|
+
for candidate, udid, manager in fleet.usb_managers():
|
|
165
|
+
if candidate is phone or (phone.udid and phone.udid.upper() == udid.upper()):
|
|
166
|
+
return candidate, udid, manager
|
|
167
|
+
problem = words.problem("not_set_up", phone.name)
|
|
168
|
+
raise APIError(problem["message"], 409, "not_set_up", fix=problem["fix"])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _refuse(code, name, status=409, **details):
|
|
172
|
+
problem = words.problem(code, name)
|
|
173
|
+
return Response.error(problem["message"], status, code, fix=problem["fix"], **details)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# -- handlers -------------------------------------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
def get_wireless(request):
|
|
179
|
+
return Response.json(overview(request.runtime))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def check_network(request, *, check=None, clock=time.monotonic):
|
|
183
|
+
from . import network
|
|
184
|
+
with _network_lock:
|
|
185
|
+
now = clock()
|
|
186
|
+
if _network["at"] is None or now - _network["at"] >= NETWORK_FRESH or _network["result"] is None:
|
|
187
|
+
try:
|
|
188
|
+
_network["result"] = (check or network.peer_check)()
|
|
189
|
+
except Exception: # noqa: BLE001 -- a check that fails says nothing
|
|
190
|
+
_network["result"] = {"state": "unknown", "interface": None, "peers": 0}
|
|
191
|
+
_network["at"] = now
|
|
192
|
+
result = _network["result"]
|
|
193
|
+
return Response.json({"network": network_view(result)})
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def set_transport(request):
|
|
197
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
198
|
+
if set(body) != {"wifiTransport"} or type(body["wifiTransport"]) is not bool:
|
|
199
|
+
raise ValueError("Send {\"wifiTransport\": true} or false")
|
|
200
|
+
on = body["wifiTransport"]
|
|
201
|
+
runtime = request.runtime
|
|
202
|
+
setup = getattr(runtime, "setup", None)
|
|
203
|
+
env_file = getattr(setup, "env_file", None) or getattr(runtime.config, "env_file", None)
|
|
204
|
+
if env_file:
|
|
205
|
+
from ..env_secrets import update_env_file
|
|
206
|
+
update_env_file(env_file, {GATE: "1" if on else "0"})
|
|
207
|
+
os.environ[GATE] = "1" if on else "0"
|
|
208
|
+
monitor = monitor_for(runtime, create=on)
|
|
209
|
+
if monitor is not None:
|
|
210
|
+
if on:
|
|
211
|
+
monitor.start()
|
|
212
|
+
monitor.kick()
|
|
213
|
+
else:
|
|
214
|
+
monitor.stop()
|
|
215
|
+
monitor.retire()
|
|
216
|
+
return Response.json(overview(runtime))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def set_device_wifi(request):
|
|
220
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
221
|
+
if set(body) != {"enabled"} or type(body["enabled"]) is not bool:
|
|
222
|
+
raise ValueError("Send {\"enabled\": true} or false")
|
|
223
|
+
runtime = request.runtime
|
|
224
|
+
phone, udid, manager = _find(runtime, request.params["device"])
|
|
225
|
+
name = _name(phone, manager)
|
|
226
|
+
data_dir = runtime.fleet.data_dir
|
|
227
|
+
listing = next((item for item in _listing(manager) if item.get("udid", "").upper() == udid.upper()), None)
|
|
228
|
+
following = None
|
|
229
|
+
if not body["enabled"]:
|
|
230
|
+
link = transport.link(udid)
|
|
231
|
+
running_on_wifi = ((link is not None and link.transport == "wifi")
|
|
232
|
+
or getattr(manager, "transport", None) == "wifi")
|
|
233
|
+
if running_on_wifi and task_on(runtime, phone, udid):
|
|
234
|
+
return _refuse("busy", name)
|
|
235
|
+
try:
|
|
236
|
+
if body["enabled"]:
|
|
237
|
+
if switch.turn_on(data_dir, udid, listing) == "xcode":
|
|
238
|
+
following = words.problem("needs_xcode", name)
|
|
239
|
+
elif left_on := switch.turn_off(data_dir, udid, listing):
|
|
240
|
+
following = words.problem(left_on, name) # off in Mobster; the phone's own switch is still on
|
|
241
|
+
except switch.SwitchRefused as error:
|
|
242
|
+
return _refuse(error.code, name)
|
|
243
|
+
except RuntimeError:
|
|
244
|
+
return Response.error("Devices are off in this Mobster (MOBSTER_NO_DEVICES).", 503, "devices_off")
|
|
245
|
+
monitor = monitor_for(runtime)
|
|
246
|
+
if monitor is not None:
|
|
247
|
+
monitor.kick(udid)
|
|
248
|
+
answer = {"device": row(runtime, phone, udid, manager)}
|
|
249
|
+
if following is not None:
|
|
250
|
+
answer["next"] = following
|
|
251
|
+
return Response.json(answer)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _listing(manager):
|
|
255
|
+
try:
|
|
256
|
+
return manager.devices()
|
|
257
|
+
except Exception:
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def reconnect(request):
|
|
262
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
263
|
+
if set(body) != {"device"} or not isinstance(body["device"], str):
|
|
264
|
+
raise ValueError("Send {\"device\": \"<id, UDID or name>\"}")
|
|
265
|
+
runtime = request.runtime
|
|
266
|
+
phone, udid, manager = _find(runtime, body["device"])
|
|
267
|
+
if not transport_on():
|
|
268
|
+
return _refuse("off", _name(phone, manager))
|
|
269
|
+
monitor = monitor_for(runtime)
|
|
270
|
+
if monitor is not None:
|
|
271
|
+
monitor.kick(udid)
|
|
272
|
+
return Response.json({"device": udid, "accepted": True}, 202)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _run_probe(runtime, check, probe, manager_udid):
|
|
276
|
+
from ..mac_tools import usb_attached
|
|
277
|
+
try:
|
|
278
|
+
probe.run(usb_attached)
|
|
279
|
+
except Exception as error: # noqa: BLE001 -- the check ends with its words, never a trace
|
|
280
|
+
probe.step("probe", "fail", "Wi-Fi check", f"stopped: {type(error).__name__}")
|
|
281
|
+
finally:
|
|
282
|
+
try:
|
|
283
|
+
probe.stop_launched()
|
|
284
|
+
except Exception: # noqa: BLE001
|
|
285
|
+
pass
|
|
286
|
+
with _probes_lock:
|
|
287
|
+
if check["state"] != "running":
|
|
288
|
+
return # it timed out: a late end changes nothing
|
|
289
|
+
usb = next((item for item in probe.steps if item["key"] == "usb"), None)
|
|
290
|
+
if usb is not None and usb["detail"].startswith("plugged in"):
|
|
291
|
+
state = "plugged_in"
|
|
292
|
+
else:
|
|
293
|
+
state = "passed" if probe.ok else "failed"
|
|
294
|
+
result.record(runtime.fleet.data_dir, manager_udid, probe.public())
|
|
295
|
+
check.update(state=state, reason=None if probe.ok else result.reason_of(probe.steps),
|
|
296
|
+
next=None if probe.ok else _next_step(probe, check["name"]))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def probe_wifi(request):
|
|
300
|
+
"""Check one phone's Wi-Fi in the background, the way ``mobster wifi probe`` does (wireless/probe.py)."""
|
|
301
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
302
|
+
if set(body) - {"device", "start"} or not isinstance(body.get("device", ""), str) \
|
|
303
|
+
or type(body.get("start", False)) is not bool:
|
|
304
|
+
raise ValueError("Send {\"device\": \"<id, UDID or name>\"} (optional) and {\"start\": true} (optional)")
|
|
305
|
+
runtime = request.runtime
|
|
306
|
+
from .. import native_helpers
|
|
307
|
+
if not native_helpers.devices_allowed():
|
|
308
|
+
return Response.error("Devices are off in this Mobster (MOBSTER_NO_DEVICES).", 503, "devices_off")
|
|
309
|
+
if body.get("device"):
|
|
310
|
+
phone, udid, manager = _find(runtime, body["device"])
|
|
311
|
+
else:
|
|
312
|
+
phones = _phones(runtime)
|
|
313
|
+
if len(phones) != 1:
|
|
314
|
+
from ..api_errors import APIError
|
|
315
|
+
raise APIError("Name the iPhone to check." if phones else "Mobster sees no iPhone set up here.",
|
|
316
|
+
400 if phones else 409, "device_ambiguous" if phones else "no_device")
|
|
317
|
+
phone, udid, manager = phones[0]
|
|
318
|
+
name = _name(phone, manager)
|
|
319
|
+
if task_on(runtime, phone, udid):
|
|
320
|
+
return _refuse("busy", name)
|
|
321
|
+
from .. import devices
|
|
322
|
+
from .probe import Probe
|
|
323
|
+
key = udid.upper()
|
|
324
|
+
with _probes_lock:
|
|
325
|
+
others = list(_probes)
|
|
326
|
+
if any(check["state"] == "running" for check in map(_current, others) if check):
|
|
327
|
+
return _refuse("probe_running", name)
|
|
328
|
+
check = {"state": "running", "step": None, "startedAt": int(time.time() * 1000), "began": time.monotonic(),
|
|
329
|
+
"reason": None, "next": None, "name": name or "your iPhone", "udid": udid}
|
|
330
|
+
probe = Probe(
|
|
331
|
+
udid=udid, name=name or "your iPhone", slot_port=devices.port_of(getattr(phone, "wda_url", None)),
|
|
332
|
+
data_dir=runtime.fleet.data_dir, start=bool(body.get("start")), manager=manager,
|
|
333
|
+
out=lambda step: check.update(step=step["title"]))
|
|
334
|
+
with _probes_lock:
|
|
335
|
+
_probes[key] = check
|
|
336
|
+
threading.Thread(target=_run_probe, args=(runtime, check, probe, udid), name="mobster-wifi-probe",
|
|
337
|
+
daemon=True).start()
|
|
338
|
+
return Response.json({"device": udid, "accepted": True, "check": _check_view(udid)}, 202)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# Every route needs the API token, like every other, and none grants anything an approval guards.
|
|
342
|
+
ROUTES = (
|
|
343
|
+
Route("wireless", "GET", "/api/wireless", get_wireless, body="none"),
|
|
344
|
+
Route("wireless", "GET", "/api/wireless/network", check_network, body="none"),
|
|
345
|
+
Route("wireless", "POST", "/api/wireless", set_transport, max_bytes=1024),
|
|
346
|
+
Route("wireless", "POST", "/api/devices/{device}/wifi", set_device_wifi, max_bytes=1024),
|
|
347
|
+
Route("wireless", "POST", "/api/wireless/reconnect", reconnect, max_bytes=1024),
|
|
348
|
+
Route("wireless", "POST", "/api/wireless/probe", probe_wifi, max_bytes=1024),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def register_routes():
|
|
353
|
+
for route in ROUTES:
|
|
354
|
+
api_routes.register(route)
|