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,306 @@
|
|
|
1
|
+
"""Finding the devices the tools may drive, who holds them, and the direct session on one."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from ..android.records import is_phone
|
|
6
|
+
from .protocol import ToolResult
|
|
7
|
+
from .session import Session
|
|
8
|
+
from .support import SHUTTING_DOWN, ToolError, cap_text, jsonable, sentence
|
|
9
|
+
|
|
10
|
+
DEVICE_LIST_TTL = 30.0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DeviceTools:
|
|
14
|
+
def _device_records(self, fresh=False, probe=None):
|
|
15
|
+
at, records = self._devices_cache
|
|
16
|
+
if records is None or fresh or time.monotonic() - at > DEVICE_LIST_TTL:
|
|
17
|
+
if self._devices_source is not None:
|
|
18
|
+
records = self._devices_source()
|
|
19
|
+
else:
|
|
20
|
+
from ..android.records import records as android_records
|
|
21
|
+
from ..devices import discover
|
|
22
|
+
records = discover(probe=fresh if probe is None else probe) + android_records()
|
|
23
|
+
self._devices_cache = (time.monotonic(), records)
|
|
24
|
+
return records
|
|
25
|
+
|
|
26
|
+
def _app_ownership(self):
|
|
27
|
+
if self._ownership_source is not None:
|
|
28
|
+
return self._ownership_source()
|
|
29
|
+
if self._devices_source is not None:
|
|
30
|
+
return {}
|
|
31
|
+
at, found = self._ownership_cache
|
|
32
|
+
if time.monotonic() - at < 3.0:
|
|
33
|
+
return found
|
|
34
|
+
found = {}
|
|
35
|
+
try:
|
|
36
|
+
from ..threads.client import Client
|
|
37
|
+
listing = Client.connect(origin="mcp").call("GET", "/api/devices", timeout=2.0)
|
|
38
|
+
for item in listing.get("devices") or ():
|
|
39
|
+
if isinstance(item, dict) and isinstance(item.get("id"), str):
|
|
40
|
+
found[item["id"]] = {"holder": item.get("holder"), "queue": item.get("queue") or []}
|
|
41
|
+
except Exception: # noqa: BLE001 -- no app, an older app, a refused token
|
|
42
|
+
found = {}
|
|
43
|
+
self._ownership_cache = (time.monotonic(), found)
|
|
44
|
+
return found
|
|
45
|
+
|
|
46
|
+
def _holder_of(self, record):
|
|
47
|
+
from .. import ownership
|
|
48
|
+
for session in self._all_sessions() + self._direct_sessions():
|
|
49
|
+
run = session.run
|
|
50
|
+
if session.open and getattr(run, "device_id", None) in (record.get("id"), record.get("udid")):
|
|
51
|
+
since = time.time() - max(0.0, self.clock() - session.started)
|
|
52
|
+
label = ownership.clean_client(self.client_name()) or "this MCP client"
|
|
53
|
+
return {"kind": "mcp", "label": label, "runId": session.run_id, "since": int(since * 1000),
|
|
54
|
+
"stopping": False}, []
|
|
55
|
+
if record.get("kind") == "simulator":
|
|
56
|
+
other = self._simulator_holder(record)
|
|
57
|
+
if other is not None:
|
|
58
|
+
return other, []
|
|
59
|
+
known = self._app_ownership().get(record.get("id")) or {}
|
|
60
|
+
return known.get("holder"), known.get("queue") or []
|
|
61
|
+
|
|
62
|
+
def _simulator_holder(self, record):
|
|
63
|
+
"""The holder of a Mobster simulator in another process (an agent's session, a test run), from its record."""
|
|
64
|
+
from ..sim import holders
|
|
65
|
+
data_dir = getattr(self._manager_instance, "data_dir", None)
|
|
66
|
+
if data_dir is None:
|
|
67
|
+
from ..devices import simulator_folder
|
|
68
|
+
data_dir = simulator_folder()
|
|
69
|
+
found = holders.read(data_dir, record.get("udid") or record.get("id"))
|
|
70
|
+
if found is None:
|
|
71
|
+
return None
|
|
72
|
+
return {"kind": "mcp", "label": f"{found['client']} ({found['session']})", "runId": None,
|
|
73
|
+
"since": int(found["since"] * 1000), "stopping": False}
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _holder_words(holder, queue):
|
|
77
|
+
from .. import ownership
|
|
78
|
+
if not holder:
|
|
79
|
+
return ""
|
|
80
|
+
minutes = max(0, int((time.time() * 1000 - holder["since"]) // 60000))
|
|
81
|
+
words = f"{ownership.holding_sentence(holder)}, {minutes} min" + (", stopping after this step"
|
|
82
|
+
if holder.get("stopping") else "")
|
|
83
|
+
return words + (f"; {len(queue)} waiting" if queue else "")
|
|
84
|
+
|
|
85
|
+
def _named_device(self, name, required=False):
|
|
86
|
+
from ..devices import AmbiguousDevice, DeviceNotFound, resolve
|
|
87
|
+
if not name:
|
|
88
|
+
return None
|
|
89
|
+
for attempt in range(2):
|
|
90
|
+
try:
|
|
91
|
+
return resolve(self._device_records(fresh=attempt > 0, probe=False), name)
|
|
92
|
+
except AmbiguousDevice as error:
|
|
93
|
+
raise ToolError(str(error)) from None
|
|
94
|
+
except DeviceNotFound as error:
|
|
95
|
+
if attempt == 0 and time.monotonic() - self._devices_cache[0] > 1.0:
|
|
96
|
+
continue
|
|
97
|
+
if required:
|
|
98
|
+
raise ToolError(f"{error} Call list_devices to see them.") from None
|
|
99
|
+
return None
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def _open_direct(self, name):
|
|
103
|
+
folded = str(name or "").strip().casefold()
|
|
104
|
+
if not folded:
|
|
105
|
+
return None
|
|
106
|
+
sessions = self._direct_sessions()
|
|
107
|
+
for session in sessions:
|
|
108
|
+
record = getattr(session.run, "record", None) or {}
|
|
109
|
+
if folded in {str(record.get("id") or "").casefold(), str(record.get("udid") or "").casefold()}:
|
|
110
|
+
return session
|
|
111
|
+
named = [session for session in sessions
|
|
112
|
+
if str((getattr(session.run, "record", None) or {}).get("name") or "").casefold() == folded]
|
|
113
|
+
return named[0] if len(named) == 1 else None
|
|
114
|
+
|
|
115
|
+
def _direct_sessions(self, open_only=True):
|
|
116
|
+
with self._lock:
|
|
117
|
+
return [s for s in self.device_sessions.values() if s.open or not open_only]
|
|
118
|
+
|
|
119
|
+
def _allowed(self, record):
|
|
120
|
+
if not is_phone(record) or self.allow_devices is None:
|
|
121
|
+
return True
|
|
122
|
+
names = {str(record.get(key) or "").strip().casefold() for key in ("id", "udid", "name")}
|
|
123
|
+
return bool(names & self.allow_devices)
|
|
124
|
+
|
|
125
|
+
def _refuse_unless_allowed(self, record):
|
|
126
|
+
if not self._allowed(record):
|
|
127
|
+
raise ToolError(f"“{record['name']}” is a real device, and this server may drive only the ones "
|
|
128
|
+
"`mobster mcp --allow-device` names. Ask the user to add it to the server's command.")
|
|
129
|
+
|
|
130
|
+
def _device_busy_with_check(self, record):
|
|
131
|
+
for session in self._all_sessions():
|
|
132
|
+
run = session.run
|
|
133
|
+
if (session.open
|
|
134
|
+
and getattr(getattr(run, "target", None), "udid", None) in (record.get("udid"), record["id"])):
|
|
135
|
+
return session
|
|
136
|
+
if session.open and getattr(run, "device_id", None) == record["id"]:
|
|
137
|
+
return session
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def _device_session(self, name):
|
|
141
|
+
session = self._open_direct(name)
|
|
142
|
+
if session is not None and session.open:
|
|
143
|
+
session.touch()
|
|
144
|
+
return session
|
|
145
|
+
record = self._named_device(name, required=True)
|
|
146
|
+
with self._lock:
|
|
147
|
+
session = self.device_sessions.get(record["id"])
|
|
148
|
+
if session is not None and session.open:
|
|
149
|
+
session.touch()
|
|
150
|
+
return session
|
|
151
|
+
if self._closing.is_set():
|
|
152
|
+
raise ToolError(SHUTTING_DOWN)
|
|
153
|
+
check = self._device_busy_with_check(record)
|
|
154
|
+
if check is not None:
|
|
155
|
+
raise ToolError(f"Run {check.run_id} is checking an app on {record['name']}. Pass run_id "
|
|
156
|
+
f"{check.run_id}, or stop that run first.")
|
|
157
|
+
self._refuse_unless_allowed(record)
|
|
158
|
+
if record.get("platform") == "android":
|
|
159
|
+
raise ToolError(f"“{record['name']}” is an Android device: use verify_start with device, or run_tests. "
|
|
160
|
+
"Driving it directly with tap and screen isn't available yet.")
|
|
161
|
+
if record.get("state") in ("needs_setup", "disconnected") or not record.get("wdaUrl"):
|
|
162
|
+
raise ToolError(f"“{record['name']}” isn't ready: {record.get('reason') or record['state']}")
|
|
163
|
+
from .direct import DeviceRun
|
|
164
|
+
factory = self._device_run or DeviceRun
|
|
165
|
+
folder = self.runs_dir if record.get("kind") == "simulator" else self.device_runs_dir
|
|
166
|
+
run = factory(record, runs_dir=folder)
|
|
167
|
+
session = self._open_direct_run(run, record)
|
|
168
|
+
self.log(f"Run {session.run_id}: driving {record['name']} ({record['kind']}) directly.")
|
|
169
|
+
return session
|
|
170
|
+
|
|
171
|
+
def _open_direct_run(self, run, record, *, own=False):
|
|
172
|
+
"""Prepare ``run`` and file it as the direct session on ``record``'s device. Call it under self._lock."""
|
|
173
|
+
try:
|
|
174
|
+
run.prepare()
|
|
175
|
+
except Exception as error:
|
|
176
|
+
run.close("It couldn't be opened.")
|
|
177
|
+
fix = getattr(error, "fix", "")
|
|
178
|
+
raise ToolError(sentence(error) + (" " + sentence(fix) if fix else "")) from None
|
|
179
|
+
if own:
|
|
180
|
+
# the simulator is known only once its lease is taken
|
|
181
|
+
udid, name = run.target.udid, run.target.name
|
|
182
|
+
record.update(id=udid, udid=udid, name=name)
|
|
183
|
+
run.device_id, run.device_name = udid, name
|
|
184
|
+
self._own_sim = udid
|
|
185
|
+
session = Session(run, "direct", clock=self.clock, image=True)
|
|
186
|
+
self.codes.seed(session, record["id"])
|
|
187
|
+
session.message = f"Driving {record['name']} directly."
|
|
188
|
+
session.set_state("ready")
|
|
189
|
+
self.device_sessions[record["id"]] = session
|
|
190
|
+
return session
|
|
191
|
+
|
|
192
|
+
def _yield_session_simulator(self, args):
|
|
193
|
+
"""A check that names no device takes a free simulator itself: hand back the one this session leased, so
|
|
194
|
+
one session never holds two."""
|
|
195
|
+
session = self.device_sessions.get(self._own_sim) if self._own_sim else None
|
|
196
|
+
if args.get("device") or self.device or session is None or not session.open or session.busy:
|
|
197
|
+
return
|
|
198
|
+
self._close_direct(session, "A check is starting, so Mobster handed this session's simulator to it.",
|
|
199
|
+
timeout=0)
|
|
200
|
+
|
|
201
|
+
def _session_simulator(self):
|
|
202
|
+
"""This session's own simulator: leased on the first call that names no device, kept until the session
|
|
203
|
+
ends or sits idle for 30 minutes, and never given to another session while held."""
|
|
204
|
+
with self._lock:
|
|
205
|
+
session = self.device_sessions.get(self._own_sim) if self._own_sim else None
|
|
206
|
+
if session is not None and session.open:
|
|
207
|
+
session.touch()
|
|
208
|
+
return session
|
|
209
|
+
self._own_sim = None
|
|
210
|
+
if self._closing.is_set():
|
|
211
|
+
raise ToolError(SHUTTING_DOWN)
|
|
212
|
+
from .direct import DeviceRun
|
|
213
|
+
record = {"id": "", "udid": "", "kind": "simulator", "name": "a Mobster simulator"}
|
|
214
|
+
# no queueing: when every simulator is held, the answer names the holders at once
|
|
215
|
+
run = (self._device_run or DeviceRun)(record, runs_dir=self.runs_dir, manager=self._manager(),
|
|
216
|
+
acquire_timeout=0)
|
|
217
|
+
session = self._open_direct_run(run, record, own=True)
|
|
218
|
+
self.log(f"Run {session.run_id}: leased {record['name']} for this session.")
|
|
219
|
+
return session
|
|
220
|
+
|
|
221
|
+
def _close_direct(self, session, reason, timeout=None):
|
|
222
|
+
if not session.open:
|
|
223
|
+
return True
|
|
224
|
+
locked = session.run.lock.acquire(timeout=self.lock_wait if timeout is None else max(0.0, timeout))
|
|
225
|
+
if not locked:
|
|
226
|
+
return False
|
|
227
|
+
try:
|
|
228
|
+
if session.open:
|
|
229
|
+
if self._own_sim and session.run.device_id == self._own_sim:
|
|
230
|
+
self._own_sim = None
|
|
231
|
+
session.set_state("finished", jsonable(session.run.close(reason)))
|
|
232
|
+
self.log(f"Run {session.run_id}: {reason}")
|
|
233
|
+
finally:
|
|
234
|
+
session.run.lock.release()
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
def _target_session(self, args):
|
|
238
|
+
run_id, device = args.get("run_id"), args.get("device")
|
|
239
|
+
if run_id:
|
|
240
|
+
session = self._ready_session(run_id)
|
|
241
|
+
if device:
|
|
242
|
+
record = self._named_device(device, required=True)
|
|
243
|
+
run = session.run
|
|
244
|
+
on = getattr(run, "device_id", None) or getattr(getattr(run, "target", None), "udid", None)
|
|
245
|
+
if on not in (record["id"], record.get("udid")):
|
|
246
|
+
raise ToolError(f"Run {run_id} isn't on {record['name']}. Leave out device, or pass the run_id "
|
|
247
|
+
"of a run on it.")
|
|
248
|
+
return session
|
|
249
|
+
if device:
|
|
250
|
+
name = device
|
|
251
|
+
elif self.device and self._open_direct(self.device) is not None:
|
|
252
|
+
name = self.device
|
|
253
|
+
else:
|
|
254
|
+
name = self.device if self._named_device(self.device) is not None else None
|
|
255
|
+
if not name:
|
|
256
|
+
return self._session_simulator()
|
|
257
|
+
return self._device_session(name)
|
|
258
|
+
|
|
259
|
+
def tool_list_devices(self, args, call):
|
|
260
|
+
from ..devices import describe
|
|
261
|
+
records = self._device_records(fresh=True)
|
|
262
|
+
open_runs = {s.run.device_id: s.run_id for s in self._all_sessions() + self._direct_sessions()
|
|
263
|
+
if s.open and getattr(s.run, "device_id", None)}
|
|
264
|
+
rows = []
|
|
265
|
+
for item in records:
|
|
266
|
+
row = {key: item.get(key) for key in ("id", "kind", "name", "model", "modelName", "ios", "state", "reason")}
|
|
267
|
+
row["platform"] = item.get("platform") or "ios"
|
|
268
|
+
row["run_id"] = open_runs.get(item["id"])
|
|
269
|
+
row["holder"], row["queue"] = self._holder_of(item)
|
|
270
|
+
row["yours"] = bool(self._own_sim) and item["id"] == self._own_sim
|
|
271
|
+
if not self._allowed(item):
|
|
272
|
+
row["state"] = "not_allowed"
|
|
273
|
+
row["reason"] = "this server may drive only the devices `mobster mcp --allow-device` names"
|
|
274
|
+
rows.append(row)
|
|
275
|
+
if not rows:
|
|
276
|
+
return ToolResult("No devices. Plug in an iPhone set up in the Mobster app, or prepare a simulator "
|
|
277
|
+
"(`mobster sim prepare`).", {"devices": []})
|
|
278
|
+
lines = [describe(item) + f" (id {item['id']})" + (f", open in run {row['run_id']}" if row["run_id"] else "")
|
|
279
|
+
+ (", not allowed: not named by --allow-device" if row["state"] == "not_allowed" else "")
|
|
280
|
+
+ (", yours" if row["yours"] else "")
|
|
281
|
+
+ (f". {self._holder_words(row['holder'], row['queue'])}" if row["holder"] and not row["yours"]
|
|
282
|
+
else "")
|
|
283
|
+
for item, row in zip(records, rows)]
|
|
284
|
+
return ToolResult(cap_text("\n".join(lines)), {"devices": rows})
|
|
285
|
+
|
|
286
|
+
def _phone_record(self, session):
|
|
287
|
+
record = getattr(session.run, "record", None)
|
|
288
|
+
if isinstance(record, dict):
|
|
289
|
+
return record
|
|
290
|
+
device_id = getattr(session.run, "device_id", None)
|
|
291
|
+
try:
|
|
292
|
+
return self._named_device(device_id) if device_id else None
|
|
293
|
+
except ToolError:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
def _on_phone(self, session):
|
|
297
|
+
record = self._phone_record(session)
|
|
298
|
+
return bool(record) and is_phone(record)
|
|
299
|
+
|
|
300
|
+
def _record_for(self, args):
|
|
301
|
+
name = args.get("device") or self.device
|
|
302
|
+
if not name:
|
|
303
|
+
raise ToolError("Pass device (from list_devices).")
|
|
304
|
+
record = self._named_device(name, required=True)
|
|
305
|
+
self._refuse_unless_allowed(record)
|
|
306
|
+
return record
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""A device the phone tools drive directly, outside a check (SPEC §3: MCP drives real USB iPhones too)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import urlsplit
|
|
8
|
+
|
|
9
|
+
FRAME_QUALITY = 75
|
|
10
|
+
EVALUATE_PAUSE = .5
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def url_app(url):
|
|
14
|
+
# An ordinary web link stays in Safari, so the person is asked about Safari before the page opens.
|
|
15
|
+
from ..deeplinks import app_for
|
|
16
|
+
return app_for(url) or ("com.apple.mobilesafari" if urlsplit(url).scheme.casefold() in ("http", "https") else "")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PhoneStop(Exception):
|
|
20
|
+
def __init__(self, verdict):
|
|
21
|
+
super().__init__(getattr(verdict, "message", "") or "Your iPhone isn't ready. No action was taken.")
|
|
22
|
+
self.code = getattr(verdict, "code", "") or "phone_locked"
|
|
23
|
+
self.verdict = verdict
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CHECK_FAILED = "Mobster couldn't check whether your iPhone is unlocked, so it stopped."
|
|
27
|
+
VERDICTS = ("ready", "unlocked", "stop")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def strict_check(guard, driver, cause):
|
|
31
|
+
from ..agent_hooks import GuardVerdict
|
|
32
|
+
try:
|
|
33
|
+
verdict = guard.check(driver, cause=cause)
|
|
34
|
+
except Exception:
|
|
35
|
+
verdict = None
|
|
36
|
+
if getattr(verdict, "state", None) not in VERDICTS:
|
|
37
|
+
return GuardVerdict("stop", "phone_locked", CHECK_FAILED)
|
|
38
|
+
return verdict
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _decline(*_args, **_kwargs):
|
|
42
|
+
# MCP has nobody to ask: an approval the guard would want is declined.
|
|
43
|
+
return "denied"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def default_guard(record, emit=None):
|
|
47
|
+
if record.get("kind") == "simulator":
|
|
48
|
+
return None
|
|
49
|
+
from ..lockscreen import guard_for
|
|
50
|
+
return guard_for(device_id=record["id"], wda_url=record.get("wdaUrl"), mode="scripts", approve=_decline,
|
|
51
|
+
emit=emit or (lambda event: None))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def lockscreen_available():
|
|
55
|
+
try:
|
|
56
|
+
from ..lockscreen import CAN_UNLOCK
|
|
57
|
+
except ImportError:
|
|
58
|
+
return False
|
|
59
|
+
return CAN_UNLOCK is True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def unlock_settings(record):
|
|
63
|
+
keys = {"enabled": False, "askBeforeUnlocking": True, "scripts": False, "relockAfter": True, "needsCheck": False}
|
|
64
|
+
settings = None
|
|
65
|
+
try:
|
|
66
|
+
from .. import lockscreen
|
|
67
|
+
reader = getattr(lockscreen, "unlock_settings", None)
|
|
68
|
+
if callable(reader):
|
|
69
|
+
settings = reader(record["id"])
|
|
70
|
+
except ImportError:
|
|
71
|
+
pass
|
|
72
|
+
except Exception:
|
|
73
|
+
settings = None
|
|
74
|
+
if not isinstance(settings, dict):
|
|
75
|
+
try:
|
|
76
|
+
from ..devices import DeviceStore
|
|
77
|
+
from ..paths import user_data_dir
|
|
78
|
+
entry = next((item for item in DeviceStore(user_data_dir()).phones()
|
|
79
|
+
if item.get("udid") in (record.get("udid"), record.get("id"))), None)
|
|
80
|
+
settings = (entry or {}).get("unlock")
|
|
81
|
+
except Exception:
|
|
82
|
+
settings = None
|
|
83
|
+
settings = settings if isinstance(settings, dict) else {}
|
|
84
|
+
return {key: settings.get(key) is True if not default else settings.get(key) is not False
|
|
85
|
+
for key, default in keys.items()}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def passcode_saved(record):
|
|
89
|
+
try:
|
|
90
|
+
from .. import passcode_vault
|
|
91
|
+
except ImportError:
|
|
92
|
+
return False
|
|
93
|
+
try:
|
|
94
|
+
return bool(passcode_vault.exists(record.get("udid") or record["id"]))
|
|
95
|
+
except Exception:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DeviceRun:
|
|
100
|
+
mode = "direct"
|
|
101
|
+
|
|
102
|
+
def __init__(self, record, *, runs_dir, manager=None, driver_factory=None, guard_factory=None, emit=None,
|
|
103
|
+
acquire_timeout=None):
|
|
104
|
+
from ..device_targets import WdaDevice
|
|
105
|
+
from ..verify.runs import make_run_dir
|
|
106
|
+
self.record = record
|
|
107
|
+
self.device_id = record["id"]
|
|
108
|
+
self.device_name = record.get("name") or record["id"]
|
|
109
|
+
self.manager = manager or WdaDevice(record)
|
|
110
|
+
self._driver_factory = driver_factory
|
|
111
|
+
self._acquire_timeout = acquire_timeout # None: the manager's own wait
|
|
112
|
+
self.runs_dir = Path(runs_dir).expanduser().resolve()
|
|
113
|
+
# Ignored in git and readable by this user only: the frames show the phone's screen.
|
|
114
|
+
self.run_id, self.run_dir = make_run_dir(self.runs_dir)
|
|
115
|
+
self.lock = threading.RLock()
|
|
116
|
+
self.steps, self.frames = [], []
|
|
117
|
+
self.state = "new"
|
|
118
|
+
self.driver = self.target = self.lease = self.app = None
|
|
119
|
+
self.result = None
|
|
120
|
+
self._guard_factory = guard_factory or default_guard
|
|
121
|
+
self._emit = emit or (lambda event: None)
|
|
122
|
+
self.guard = self.preflight = None
|
|
123
|
+
self._frame_n = 0
|
|
124
|
+
self._frame_width = None
|
|
125
|
+
self._t0 = time.monotonic()
|
|
126
|
+
self._release_lock = threading.Lock()
|
|
127
|
+
self.log(f"device {self.device_name} ({self.device_id}), driven directly")
|
|
128
|
+
|
|
129
|
+
def log(self, line):
|
|
130
|
+
try:
|
|
131
|
+
with open(self.run_dir / "run.log", "a", encoding="utf-8") as handle:
|
|
132
|
+
handle.write(f"{time.strftime('%H:%M:%S')} {line}\n")
|
|
133
|
+
except OSError:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
def prepare(self):
|
|
137
|
+
self.lease = (self.manager.acquire() if self._acquire_timeout is None
|
|
138
|
+
else self.manager.acquire(timeout=self._acquire_timeout))
|
|
139
|
+
self.target = self.lease.target
|
|
140
|
+
try:
|
|
141
|
+
if self._driver_factory is not None:
|
|
142
|
+
self.driver = self._driver_factory(self.target)
|
|
143
|
+
else:
|
|
144
|
+
from ..verify.runner import build_driver
|
|
145
|
+
self.driver = build_driver(self.target, self.manager, self.run_dir, "keyless")
|
|
146
|
+
self._check_phone("preflight")
|
|
147
|
+
except BaseException:
|
|
148
|
+
self._release()
|
|
149
|
+
raise
|
|
150
|
+
self.state = "ready"
|
|
151
|
+
|
|
152
|
+
def _check_phone(self, cause):
|
|
153
|
+
if self.guard is None and self.preflight is None:
|
|
154
|
+
self.guard = self._guard_factory(self.record, self._emit)
|
|
155
|
+
if self.guard is None:
|
|
156
|
+
return None
|
|
157
|
+
verdict = strict_check(self.guard, self.driver, cause)
|
|
158
|
+
if cause == "preflight":
|
|
159
|
+
self.preflight = verdict
|
|
160
|
+
self.log(f"phone check ({cause}): {getattr(verdict, 'state', '?')} {getattr(verdict, 'code', '')}".rstrip())
|
|
161
|
+
if getattr(verdict, "state", "") == "stop":
|
|
162
|
+
raise PhoneStop(verdict)
|
|
163
|
+
return verdict
|
|
164
|
+
|
|
165
|
+
def check_phone(self, cause="resume"):
|
|
166
|
+
with self.lock:
|
|
167
|
+
return self._check_phone(cause)
|
|
168
|
+
|
|
169
|
+
def read_tree(self):
|
|
170
|
+
from ..verify.tree import read_tree, tree_from_pixels
|
|
171
|
+
pixels = getattr(self.driver, "pixel_snapshot", None)
|
|
172
|
+
if not callable(pixels):
|
|
173
|
+
return read_tree(self.driver, pause=time.sleep)
|
|
174
|
+
snapshot = pixels()
|
|
175
|
+
if snapshot is not None:
|
|
176
|
+
return tree_from_pixels(snapshot)
|
|
177
|
+
from ..wda.observe import WDA_TREE_BUDGET_SECONDS
|
|
178
|
+
started = time.monotonic()
|
|
179
|
+
try:
|
|
180
|
+
return read_tree(self.driver, timeout=WDA_TREE_BUDGET_SECONDS, pause=time.sleep, retry_slow=False)
|
|
181
|
+
except Exception:
|
|
182
|
+
if time.monotonic() - started < WDA_TREE_BUDGET_SECONDS - .1:
|
|
183
|
+
raise
|
|
184
|
+
snapshot = pixels(mark=True)
|
|
185
|
+
if snapshot is None:
|
|
186
|
+
raise
|
|
187
|
+
return tree_from_pixels(snapshot)
|
|
188
|
+
|
|
189
|
+
def frame(self, label):
|
|
190
|
+
self._frame_n += 1
|
|
191
|
+
label = re.sub(r"[^a-z0-9-]+", "-", str(label).lower()).strip("-") or "frame"
|
|
192
|
+
path = self.run_dir / "frames" / f"{self._frame_n:02d}-{label}.jpg"
|
|
193
|
+
try:
|
|
194
|
+
self.manager.screenshot(self.target, path, max_width=self._frame_width, quality=FRAME_QUALITY)
|
|
195
|
+
if self._frame_width is None:
|
|
196
|
+
from PIL import Image
|
|
197
|
+
with Image.open(path) as image:
|
|
198
|
+
self._frame_width = max(1, image.width // 2)
|
|
199
|
+
except Exception as error:
|
|
200
|
+
self.log(f"no {label} frame: {type(error).__name__}")
|
|
201
|
+
return None
|
|
202
|
+
self.frames.append(path.relative_to(self.run_dir).as_posix())
|
|
203
|
+
return path
|
|
204
|
+
|
|
205
|
+
def record_step(self, *, op, text, target=None, typed=None, changed=None, frame=None):
|
|
206
|
+
from ..journal import append_jsonl
|
|
207
|
+
step = {"index": len(self.steps) + 1, "op": op, "text": text,
|
|
208
|
+
"target": ({"id": target.get("id"), "label": target.get("label"), "role": target.get("role")}
|
|
209
|
+
if target else None),
|
|
210
|
+
"typed": typed, "changed": changed,
|
|
211
|
+
"frame": Path(frame).relative_to(self.run_dir).as_posix() if frame else None,
|
|
212
|
+
"at_ms": round((time.monotonic() - self._t0) * 1000)}
|
|
213
|
+
append_jsonl(self.run_dir / "events.jsonl", {"event": "step", **step})
|
|
214
|
+
self.steps.append(step)
|
|
215
|
+
return step
|
|
216
|
+
|
|
217
|
+
def open_url(self, url):
|
|
218
|
+
from ..deeplinks import open_link
|
|
219
|
+
from ..verify.checks import URL_SCHEME, CheckError
|
|
220
|
+
if not isinstance(url, str) or not URL_SCHEME.match(url) or len(url) > 2000:
|
|
221
|
+
raise CheckError("the link needs a scheme, such as https://example.com or myapp://home")
|
|
222
|
+
with self.lock:
|
|
223
|
+
return open_link(self.driver, url, bundle=url_app(url) or None)
|
|
224
|
+
|
|
225
|
+
def launch_app(self, bundle_id):
|
|
226
|
+
with self.lock:
|
|
227
|
+
self.driver.call("POST", "/wda/apps/activate", {"bundleId": bundle_id}, timeout=30)
|
|
228
|
+
|
|
229
|
+
def home(self):
|
|
230
|
+
with self.lock:
|
|
231
|
+
self.driver.call("POST", "/wda/pressButton", {"name": "home"}, timeout=10)
|
|
232
|
+
|
|
233
|
+
def evaluate(self, assertions, *, timeout=5):
|
|
234
|
+
from ..verify.assertions import evaluate_on
|
|
235
|
+
deadline = time.monotonic() + max(0.0, timeout)
|
|
236
|
+
with self.lock:
|
|
237
|
+
while True:
|
|
238
|
+
results = evaluate_on(assertions, self.read_tree())
|
|
239
|
+
if all(result.ok for result in results) or time.monotonic() >= deadline:
|
|
240
|
+
return results
|
|
241
|
+
time.sleep(EVALUATE_PAUSE)
|
|
242
|
+
|
|
243
|
+
def _release(self):
|
|
244
|
+
with self._release_lock:
|
|
245
|
+
driver, self.driver = self.driver, None
|
|
246
|
+
lease, self.lease = self.lease, None
|
|
247
|
+
guard, self.guard = self.guard, None
|
|
248
|
+
if guard is not None and driver is not None:
|
|
249
|
+
try:
|
|
250
|
+
guard.finish(driver) # locks the phone again if this guard unlocked it and relock is on
|
|
251
|
+
except Exception as error:
|
|
252
|
+
self.log(f"the phone guard's finish failed ({type(error).__name__})")
|
|
253
|
+
if driver is not None:
|
|
254
|
+
try:
|
|
255
|
+
driver.close()
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
if lease is not None:
|
|
259
|
+
try:
|
|
260
|
+
lease.release()
|
|
261
|
+
except Exception:
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
def close(self, reason):
|
|
265
|
+
if self.result is None:
|
|
266
|
+
self._release()
|
|
267
|
+
self.state = "finished"
|
|
268
|
+
self.result = {"schema": "mobster.device/1", "run_id": self.run_id, "device": self.device_id,
|
|
269
|
+
"device_name": self.device_name, "status": "closed", "summary": reason,
|
|
270
|
+
"steps": list(self.steps), "frames": list(self.frames), "run_dir": str(self.run_dir)}
|
|
271
|
+
self.log(f"closed: {reason}")
|
|
272
|
+
return self.result
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""The Expo dev loop as MCP tools: ``expo_preview`` opens the app from Metro, ``expo_reload`` reloads it."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .. import harness_api
|
|
7
|
+
from ..devkit import DevError, expo, expo_dev
|
|
8
|
+
from .protocol import ToolResult
|
|
9
|
+
from .support import ToolError, cap_text
|
|
10
|
+
|
|
11
|
+
LOG_TAIL = 600
|
|
12
|
+
START_WAIT = 30
|
|
13
|
+
RELOAD_SETTLE = 2.0
|
|
14
|
+
MAX_EXPECT_WAIT = 30
|
|
15
|
+
BUNDLE_WAIT = 20
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def quoted(text, limit=LOG_TAIL):
|
|
19
|
+
# What Metro or a dev server said is data from the user's project, never instructions.
|
|
20
|
+
flat = " ".join(str(text or "").split())
|
|
21
|
+
return "“" + (flat if len(flat) <= limit else "…" + flat[-(limit - 1):]) + "”"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def metro_tail(app, limit=LOG_TAIL):
|
|
25
|
+
try:
|
|
26
|
+
text = (Path(app.root) / expo.STATE_DIR / "metro.log").read_text(encoding="utf-8", errors="replace")
|
|
27
|
+
except OSError:
|
|
28
|
+
return ""
|
|
29
|
+
text = text[-4000:].strip()
|
|
30
|
+
return harness_api.fence("Metro's log (the end of it)", text[-limit:]) if text else ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ensure_metro(toolset, app, port, wait=START_WAIT):
|
|
34
|
+
toolset.log(f"Checking Metro on port {port} for {app.name}")
|
|
35
|
+
try:
|
|
36
|
+
server, started = expo_dev.ensure(app, port, wait=wait)
|
|
37
|
+
return server["pid"] if started else None
|
|
38
|
+
except DevError as error:
|
|
39
|
+
tail = metro_tail(app)
|
|
40
|
+
raise DevError(error.message, (error.fix + (" " + tail if tail else "")).strip()) from None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _fail(error):
|
|
44
|
+
return ToolError(cap_text(error.line()))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def preview(toolset, args, call, sleep=time.sleep):
|
|
48
|
+
try:
|
|
49
|
+
app = expo.resolve_project(args.get("project"))
|
|
50
|
+
if not app.expo:
|
|
51
|
+
raise DevError("expo_preview opens Expo apps, and this React Native app has no Expo.",
|
|
52
|
+
"Run it from Xcode, or install a build with install_app.")
|
|
53
|
+
record = toolset._record_for(args)
|
|
54
|
+
port = int(args.get("port") or expo.DEFAULT_PORT)
|
|
55
|
+
path = args.get("path")
|
|
56
|
+
link = expo.app_link(app, path) if path and app.client == "dev-client" else None
|
|
57
|
+
if path and app.client == "dev-client" and link is None:
|
|
58
|
+
raise DevError("path needs the app's own URL scheme, and its Expo config has no scheme.",
|
|
59
|
+
"Add \"scheme\" to app.json, rebuild the dev client, then try again.")
|
|
60
|
+
started = ensure_metro(toolset, app, port)
|
|
61
|
+
broken = expo_dev.bundle_error(app, port, wait=BUNDLE_WAIT)
|
|
62
|
+
if broken:
|
|
63
|
+
where = f" in {broken['file']}:{broken['line']}" if broken.get("file") and broken.get("line") else ""
|
|
64
|
+
raise DevError(f"Metro can't bundle {app.name}{where}: {quoted(broken['message'])}",
|
|
65
|
+
"Fix the error, then call expo_preview again.")
|
|
66
|
+
url = expo.preview_url(app, expo.host_for(record), port, path)
|
|
67
|
+
except DevError as error:
|
|
68
|
+
raise _fail(error) from None
|
|
69
|
+
target = {"device": record["id"]}
|
|
70
|
+
opened = toolset.tool_open_url({**target, "url": url}, call)
|
|
71
|
+
lines = [f"Opened {app.name} from Metro on port {port} in "
|
|
72
|
+
f"{'its dev client' if app.client == 'dev-client' else 'Expo Go'} on {record['name']}."]
|
|
73
|
+
structured = {**(opened.structured or {}), "app": app.name, "client": app.client, "url": url, "port": port,
|
|
74
|
+
"metro_started": started is not None}
|
|
75
|
+
if link:
|
|
76
|
+
sleep(3) # the dev client loads the bundle first; its own link needs the app up
|
|
77
|
+
opened = toolset.tool_open_url({**target, "url": link}, call)
|
|
78
|
+
lines.append(f"Then opened its screen {quoted(link, 120)}.")
|
|
79
|
+
structured["link"] = link
|
|
80
|
+
if started is not None:
|
|
81
|
+
lines.append(f"Metro started in the background (log {expo.STATE_DIR}/metro.log). Call expo_reload after an "
|
|
82
|
+
"edit.")
|
|
83
|
+
expect = args.get("expect") or []
|
|
84
|
+
if expect:
|
|
85
|
+
waited = toolset.tool_wait_for({**target, "timeout_s": min(float(args.get("wait_s") or 20), MAX_EXPECT_WAIT),
|
|
86
|
+
"expect": [{"text": text} for text in expect]}, call)
|
|
87
|
+
structured["held"] = (waited.structured or {}).get("held")
|
|
88
|
+
return ToolResult(cap_text("\n".join(lines) + "\n" + waited.text), {**structured, **(waited.structured or {})})
|
|
89
|
+
return ToolResult(cap_text("\n".join(lines) + "\n" + opened.text), structured)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def reload(toolset, args, call, sleep=time.sleep):
|
|
93
|
+
port = int(args.get("port") or expo.DEFAULT_PORT)
|
|
94
|
+
try:
|
|
95
|
+
app = expo.resolve_project(args.get("project"))
|
|
96
|
+
record = toolset._record_for(args)
|
|
97
|
+
expo.reload(port)
|
|
98
|
+
except DevError as error:
|
|
99
|
+
raise _fail(error) from None
|
|
100
|
+
sleep(RELOAD_SETTLE)
|
|
101
|
+
screen = toolset.tool_screen({"device": record["id"], "image": bool(args.get("image", False))}, call)
|
|
102
|
+
head = f"Reloaded the JavaScript in every app connected to Metro on port {port}."
|
|
103
|
+
return ToolResult(cap_text(head + "\n" + screen.text),
|
|
104
|
+
{"app": app.name, "port": port, "reloaded": True,
|
|
105
|
+
"screen": (screen.structured or {}).get("screen")}, screen.images)
|