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,293 @@
|
|
|
1
|
+
"""`mobster sim doctor`: what `mobster verify` needs on this Mac, and the command that fixes each gap."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .. import doctor as mobster_doctor
|
|
9
|
+
from .. import states
|
|
10
|
+
from .models import SimError
|
|
11
|
+
from .registry import SLOTS, free_slot, port_base, taken_ports
|
|
12
|
+
from .xcode import parse_version
|
|
13
|
+
|
|
14
|
+
OK, WARN, FAIL, SKIP = mobster_doctor.OK, mobster_doctor.WARN, mobster_doctor.FAIL, mobster_doctor.SKIP
|
|
15
|
+
MIN_FREE_BYTES = 5 * 1024 ** 3
|
|
16
|
+
# Measured on 28 Sep (iOS 26.4, Xcode 26.4): a freshly booted iPhone 17 Pro simulator's folder is 1.6 GB, and
|
|
17
|
+
# the WebDriverAgent build 209 MB. Two simulators (MOBSTER_MAX_SIMS' default) and the build come to 3.4 GB.
|
|
18
|
+
DISK_FIX = "Free at least 5 GB: each booted simulator takes 1.6 GB, and the WebDriverAgent build 0.2 GB"
|
|
19
|
+
KEY_LABEL = "Smart key"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check(key, label, state, detail, fix=""):
|
|
23
|
+
return mobster_doctor.Check(key, label, state, detail, fix).public()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_arch(manager):
|
|
27
|
+
machine = platform.machine()
|
|
28
|
+
if machine != "arm64":
|
|
29
|
+
translated = manager.run(["/usr/sbin/sysctl", "-n", "hw.optional.arm64"], timeout=5).out.strip() == "1"
|
|
30
|
+
if not translated:
|
|
31
|
+
return check("arch", "Apple silicon", FAIL, f"this Mac is {machine}",
|
|
32
|
+
"Mobster's simulators need a Mac with Apple silicon")
|
|
33
|
+
return check("arch", "Apple silicon", WARN, "running under Rosetta",
|
|
34
|
+
"Run the arm64 build of mobster: brew reinstall mobster")
|
|
35
|
+
return check("arch", "Apple silicon", OK, "arm64")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_xcode(manager):
|
|
39
|
+
from .. import mac_tools
|
|
40
|
+
xcode, selected = mac_tools.developer_dir()
|
|
41
|
+
if not xcode:
|
|
42
|
+
return check("xcode", "Xcode", FAIL, "not installed (the Command Line Tools alone can't run simulators)",
|
|
43
|
+
"Install Xcode from the App Store, then open it once")
|
|
44
|
+
app = str(Path(xcode).parent.parent)
|
|
45
|
+
env = {**os.environ, "DEVELOPER_DIR": xcode}
|
|
46
|
+
result = manager.run([str(Path(xcode) / "usr" / "bin" / "xcodebuild"), "-version"], timeout=60, env=env)
|
|
47
|
+
text = result.out + result.err
|
|
48
|
+
if not result.ok:
|
|
49
|
+
if "license" in text.lower():
|
|
50
|
+
return check("xcode", "Xcode", FAIL, "its license isn't accepted", "sudo xcodebuild -license accept")
|
|
51
|
+
return check("xcode", "Xcode", FAIL, f"xcodebuild failed: {result.last_line() or 'no output'}",
|
|
52
|
+
"Open Xcode once to finish its setup")
|
|
53
|
+
version, build = parse_version(result.out)
|
|
54
|
+
detail = f"Xcode {version} ({build}) at {mobster_doctor._home(app)}"
|
|
55
|
+
if selected != xcode:
|
|
56
|
+
where = mobster_doctor._home(selected) if selected else "nothing"
|
|
57
|
+
return check("xcode", "Xcode", FAIL, f"{detail}, but xcode-select points at {where}",
|
|
58
|
+
f"sudo xcode-select -s {app}")
|
|
59
|
+
return check("xcode", "Xcode", OK, detail)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_first_launch(manager):
|
|
63
|
+
from .. import mac_tools
|
|
64
|
+
xcode, _ = mac_tools.developer_dir()
|
|
65
|
+
if not xcode:
|
|
66
|
+
return check("first_launch", "Xcode components", SKIP, "needs Xcode")
|
|
67
|
+
env = {**os.environ, "DEVELOPER_DIR": xcode}
|
|
68
|
+
result = manager.run([str(Path(xcode) / "usr" / "bin" / "xcodebuild"), "-checkFirstLaunchStatus"],
|
|
69
|
+
timeout=60, env=env)
|
|
70
|
+
if result.ok:
|
|
71
|
+
return check("first_launch", "Xcode components", OK, "installed")
|
|
72
|
+
return check("first_launch", "Xcode components", FAIL, "Xcode's first-launch components aren't installed",
|
|
73
|
+
"sudo xcodebuild -runFirstLaunch")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check_catalog(manager):
|
|
77
|
+
"""The runtime and device type checks, from one `simctl list -j`."""
|
|
78
|
+
try:
|
|
79
|
+
catalog = manager._catalog()
|
|
80
|
+
except SimError as exc:
|
|
81
|
+
return [check("runtime", "iOS runtime", FAIL, str(exc), exc.fix),
|
|
82
|
+
check("device_type", "Device type", SKIP, "needs simctl")]
|
|
83
|
+
from . import simctl
|
|
84
|
+
try:
|
|
85
|
+
runtime = simctl.choose_runtime(catalog[0])
|
|
86
|
+
except SimError as exc:
|
|
87
|
+
missing = str(exc) == states.row("no_runtime").message
|
|
88
|
+
return [mobster_doctor.finding("runtime", "iOS runtime", FAIL, "no_runtime").public() if missing
|
|
89
|
+
else check("runtime", "iOS runtime", FAIL, str(exc), exc.fix),
|
|
90
|
+
check("device_type", "Device type", SKIP, "needs an iOS runtime")]
|
|
91
|
+
runtime_check = check("runtime", "iOS runtime", OK, runtime.name)
|
|
92
|
+
try:
|
|
93
|
+
device_type = simctl.choose_device_type(catalog[1], runtime)
|
|
94
|
+
except SimError as exc:
|
|
95
|
+
return [runtime_check, check("device_type", "Device type", FAIL, str(exc), exc.fix)]
|
|
96
|
+
return [runtime_check, check("device_type", "Device type", OK, device_type.name)]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_git(manager, built):
|
|
100
|
+
from .. import mac_tools
|
|
101
|
+
git = mac_tools.xcode_tool("git")
|
|
102
|
+
if git:
|
|
103
|
+
return check("git", "git", OK, git)
|
|
104
|
+
if built:
|
|
105
|
+
return check("git", "git", SKIP, "not installed; not needed while WebDriverAgent is built")
|
|
106
|
+
return check("git", "git", FAIL, "not installed; the first WebDriverAgent build downloads its source with it",
|
|
107
|
+
"Install Xcode, which includes git")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _existing(path):
|
|
111
|
+
path = Path(path)
|
|
112
|
+
while not path.exists() and path != path.parent:
|
|
113
|
+
path = path.parent
|
|
114
|
+
return path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def check_disk(manager):
|
|
118
|
+
folder = _existing(manager.data_dir)
|
|
119
|
+
free = shutil.disk_usage(folder).free
|
|
120
|
+
detail = f"{free / 1024 ** 3:.0f} GB free for {mobster_doctor._home(manager.data_dir)}"
|
|
121
|
+
if free < MIN_FREE_BYTES:
|
|
122
|
+
return check("disk", "Free space", FAIL, detail, DISK_FIX)
|
|
123
|
+
return check("disk", "Free space", OK, detail)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def wda_plan(manager):
|
|
127
|
+
from .wda import build_dir, find_xctestrun
|
|
128
|
+
try:
|
|
129
|
+
tools = manager.tools()
|
|
130
|
+
except Exception:
|
|
131
|
+
return None
|
|
132
|
+
return find_xctestrun(build_dir(manager.data_dir, tools.build))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def check_wda_build(manager):
|
|
136
|
+
plan = wda_plan(manager)
|
|
137
|
+
if plan:
|
|
138
|
+
return check("wda_build", "WebDriverAgent", OK,
|
|
139
|
+
f"built for this Xcode ({mobster_doctor._home(plan.parents[2])})")
|
|
140
|
+
return check("wda_build", "WebDriverAgent", WARN, "not built yet: the first run builds it, once per Xcode version",
|
|
141
|
+
"mobster sim doctor --fix")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def check_simulators(manager):
|
|
145
|
+
rows = manager.list()
|
|
146
|
+
if not rows:
|
|
147
|
+
return check("simulators", "Simulators", SKIP, "none yet: the first run creates one")
|
|
148
|
+
parts, stopped = [], []
|
|
149
|
+
for row in rows:
|
|
150
|
+
if row["state"] == "Booted":
|
|
151
|
+
state = "booted, WebDriverAgent " + ("ready" if row["wda"] == "ready" else "stopped")
|
|
152
|
+
if row["wda"] != "ready":
|
|
153
|
+
stopped.append(row)
|
|
154
|
+
else:
|
|
155
|
+
state = row["state"].lower()
|
|
156
|
+
parts.append(f"{row['name']} ({state}{', running a check' if row['in_use'] else ''})")
|
|
157
|
+
detail = "; ".join(parts)
|
|
158
|
+
if stopped:
|
|
159
|
+
return check("simulators", "Simulators", WARN, detail, "mobster sim prepare")
|
|
160
|
+
return check("simulators", "Simulators", OK, detail)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def check_ports(manager):
|
|
164
|
+
base = port_base()
|
|
165
|
+
entries = manager.registry.read()
|
|
166
|
+
blocked = []
|
|
167
|
+
for entry in entries:
|
|
168
|
+
port = entry["wda_port"]
|
|
169
|
+
if manager.wda.listening(port) and manager.wda.owner(port) != entry["udid"]:
|
|
170
|
+
blocked.append(port)
|
|
171
|
+
span = f"{base}–{base + SLOTS - 1}"
|
|
172
|
+
try:
|
|
173
|
+
wda_port, _ = free_slot(base, taken_ports(entries), busy=manager.wda.listening)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
return check("ports", "Ports", FAIL, str(exc), getattr(exc, "fix", ""))
|
|
176
|
+
if blocked:
|
|
177
|
+
return check("ports", "Ports", WARN, f"another program listens on {', '.join(map(str, blocked))}: the next "
|
|
178
|
+
f"run moves that simulator to {wda_port}")
|
|
179
|
+
return check("ports", "Ports", OK, f"WebDriverAgent on 127.0.0.1:{span}, MJPEG on +1000; {wda_port} is free")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# a key is reported as set or not set, never shown
|
|
183
|
+
def check_key():
|
|
184
|
+
from .. import engines
|
|
185
|
+
model = engines.smart_model()
|
|
186
|
+
provider = engines.provider_name(model)
|
|
187
|
+
variable = "ANTHROPIC_API_KEY" if provider == "Anthropic" else "OPENAI_API_KEY"
|
|
188
|
+
from ..ai_plans import providers
|
|
189
|
+
routed = providers.get(engines.model_provider(model))
|
|
190
|
+
if routed is not None:
|
|
191
|
+
if engines.smart_key():
|
|
192
|
+
return check("key", KEY_LABEL, OK,
|
|
193
|
+
f"{routed.name} is set: Smart runs flows on {engines.model_words(model)}")
|
|
194
|
+
return check("key", KEY_LABEL, WARN, f"you chose {routed.name}, and its key or sign-in is missing",
|
|
195
|
+
f"Run mobster login {routed.id}, or mobster login for an API key")
|
|
196
|
+
if engines.smart_key():
|
|
197
|
+
return check("key", KEY_LABEL, OK, f"your {provider} key is set: Smart runs flows on {model}")
|
|
198
|
+
none_needed = "not set: launch-only and key-less checks need none"
|
|
199
|
+
if (os.environ.get("MOBSTER_SMART_MODEL") or "").strip() != model:
|
|
200
|
+
return check("key", KEY_LABEL, SKIP, f"{none_needed}; Smart needs your OpenAI or Anthropic key",
|
|
201
|
+
"For Smart, put OPENAI_API_KEY=… or ANTHROPIC_API_KEY=… in an env file and pass --env-file PATH")
|
|
202
|
+
# MOBSTER_SMART_MODEL chose the model, and its provider's key is missing.
|
|
203
|
+
other, other_key = (("OpenAI", engines.openai_smart_key()) if provider == "Anthropic"
|
|
204
|
+
else ("Anthropic", engines.anthropic_key()))
|
|
205
|
+
if not other_key:
|
|
206
|
+
return check("key", KEY_LABEL, SKIP, f"{none_needed}; Smart on {model} needs your {provider} key",
|
|
207
|
+
f"For Smart, put {variable}=… in an env file and pass --env-file PATH")
|
|
208
|
+
unset = {name: value for name, value in os.environ.items() if name != "MOBSTER_SMART_MODEL"}
|
|
209
|
+
return check("key", KEY_LABEL, WARN, f"MOBSTER_SMART_MODEL is {model}, which needs {variable}; only your {other} "
|
|
210
|
+
"key is set, so Smart can't run",
|
|
211
|
+
f"Add {variable}=… to your env file, or remove MOBSTER_SMART_MODEL to run Smart on "
|
|
212
|
+
f"{engines.smart_model(unset)}")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _safe(key, label, function, *args):
|
|
216
|
+
try:
|
|
217
|
+
return function(*args)
|
|
218
|
+
except Exception as exc: # a doctor reports; it never crashes
|
|
219
|
+
return check(key, label, FAIL, f"the check failed: {exc}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def run_checks(manager, fix=False):
|
|
223
|
+
checks = [_safe("arch", "Apple silicon", check_arch, manager),
|
|
224
|
+
_safe("xcode", "Xcode", check_xcode, manager),
|
|
225
|
+
_safe("first_launch", "Xcode components", check_first_launch, manager)]
|
|
226
|
+
xcode_ok = checks[1]["state"] == OK
|
|
227
|
+
if xcode_ok:
|
|
228
|
+
try:
|
|
229
|
+
checks += check_catalog(manager)
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
checks += [check("runtime", "iOS runtime", FAIL, f"the check failed: {exc}"),
|
|
232
|
+
check("device_type", "Device type", SKIP, "needs simctl")]
|
|
233
|
+
else:
|
|
234
|
+
checks += [check("runtime", "iOS runtime", SKIP, "needs Xcode"),
|
|
235
|
+
check("device_type", "Device type", SKIP, "needs Xcode")]
|
|
236
|
+
checks.append(_safe("git", "git", check_git, manager, bool(xcode_ok and wda_plan(manager))))
|
|
237
|
+
checks.append(_safe("disk", "Free space", check_disk, manager))
|
|
238
|
+
# --fix only prepares a simulator: sudo and runtime downloads stay printed commands, never run
|
|
239
|
+
if fix:
|
|
240
|
+
checks.append(_prepare(manager, [item for item in checks if item["state"] == FAIL]))
|
|
241
|
+
if xcode_ok:
|
|
242
|
+
checks.append(_safe("wda_build", "WebDriverAgent", check_wda_build, manager))
|
|
243
|
+
checks.append(_safe("simulators", "Simulators", check_simulators, manager))
|
|
244
|
+
checks.append(_safe("ports", "Ports", check_ports, manager))
|
|
245
|
+
checks.append(_safe("key", KEY_LABEL, check_key))
|
|
246
|
+
from .. import android
|
|
247
|
+
if android.enabled():
|
|
248
|
+
from ..android import doctor as android_doctor
|
|
249
|
+
from ..android.emulator import EmulatorManager
|
|
250
|
+
try:
|
|
251
|
+
checks += android_doctor.run_checks(EmulatorManager(manager.data_dir))
|
|
252
|
+
except Exception as exc:
|
|
253
|
+
checks.append(check("android", "Android", FAIL, f"the check failed: {exc}"))
|
|
254
|
+
return checks
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _prepare(manager, failures):
|
|
258
|
+
if failures:
|
|
259
|
+
return check("prepare", "Prepare", SKIP, "fix the problems above first")
|
|
260
|
+
try:
|
|
261
|
+
lease = manager.acquire()
|
|
262
|
+
except SimError as exc:
|
|
263
|
+
return check("prepare", "Prepare", FAIL, str(exc), exc.fix)
|
|
264
|
+
try:
|
|
265
|
+
target, seconds = lease.target, lease.timing.get("total", 0)
|
|
266
|
+
finally:
|
|
267
|
+
lease.release()
|
|
268
|
+
return check("prepare", "Prepare", OK, f"{target.name} is booted, with WebDriverAgent on "
|
|
269
|
+
f"{target.wda_url.split('//')[-1]} ({seconds:.1f} s)")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def problems(checks):
|
|
273
|
+
return [item for item in checks if item["state"] == FAIL]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def report(checks, color=False):
|
|
277
|
+
"""One line per check, the fix under each problem, then a verdict line."""
|
|
278
|
+
from ..style import Palette
|
|
279
|
+
paint = Palette(color)
|
|
280
|
+
roles = {OK: "green", WARN: "amber", FAIL: "coral", SKIP: "tertiary"} # the brand's, as `mobster doctor`
|
|
281
|
+
lines = ["Mobster sim doctor", ""]
|
|
282
|
+
for item in checks:
|
|
283
|
+
glyph = paint(mobster_doctor.GLYPHS[item["state"]], roles[item["state"]])
|
|
284
|
+
lines.append(f" {glyph} {item['label']:<17} {item['detail']}")
|
|
285
|
+
if item["fix"] and item["state"] in {WARN, FAIL}:
|
|
286
|
+
lines.append(f" {'':<17} {paint('→ ' + item['fix'], 'tertiary')}")
|
|
287
|
+
failed = problems(checks)
|
|
288
|
+
lines.append("")
|
|
289
|
+
if failed:
|
|
290
|
+
lines.append(f"{len(failed)} problem{'s' if len(failed) != 1 else ''} to fix before `mobster verify` can run.")
|
|
291
|
+
else:
|
|
292
|
+
lines.append("Ready for `mobster verify`.")
|
|
293
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""How much the Mac can take: whether another simulator may boot, and when to wait instead of starting heavy work."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from .models import SimError
|
|
10
|
+
|
|
11
|
+
GB = 1 << 30
|
|
12
|
+
SIM_GB = 4 # free memory one more simulator needs
|
|
13
|
+
MAX_SIMS_CEILING = 4 # simulators per device type and runtime when MOBSTER_MAX_SIMS is unset
|
|
14
|
+
LOAD_WAIT = 30.0 # how long a boot or a WebDriverAgent build waits for the Mac to settle
|
|
15
|
+
LOAD_POLL = 2.0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Pressure:
|
|
20
|
+
load: float # the 1-minute load average
|
|
21
|
+
cores: int
|
|
22
|
+
free_gb: float # memory that can be handed out now: free, inactive and speculative pages
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_pressure(run=subprocess.run):
|
|
26
|
+
cores = os.cpu_count() or 1
|
|
27
|
+
try:
|
|
28
|
+
load = os.getloadavg()[0]
|
|
29
|
+
except OSError:
|
|
30
|
+
load = 0.0
|
|
31
|
+
free = None
|
|
32
|
+
try:
|
|
33
|
+
out = run(["vm_stat"], capture_output=True, text=True, timeout=5).stdout
|
|
34
|
+
size = int(re.search(r"page size of (\d+) bytes", out).group(1))
|
|
35
|
+
pages = sum(int(re.search(rf"Pages {name}:\s+(\d+)", out).group(1))
|
|
36
|
+
for name in ("free", "inactive", "speculative"))
|
|
37
|
+
free = pages * size / GB
|
|
38
|
+
except (OSError, subprocess.SubprocessError, AttributeError, ValueError):
|
|
39
|
+
pass
|
|
40
|
+
# memory that can't be read counts as plenty: the load check and the simulator ceiling still apply
|
|
41
|
+
return Pressure(load, cores, free if free is not None else float(MAX_SIMS_CEILING * SIM_GB))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_limit(environ=None):
|
|
45
|
+
"""The 1-minute load at which the Mac counts as saturated: one runnable task per core, or $MOBSTER_MAX_LOAD."""
|
|
46
|
+
environ = os.environ if environ is None else environ
|
|
47
|
+
text = str(environ.get("MOBSTER_MAX_LOAD") or "").strip()
|
|
48
|
+
if not text:
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
value = float(text)
|
|
52
|
+
except ValueError:
|
|
53
|
+
value = 0.0
|
|
54
|
+
if value <= 0:
|
|
55
|
+
raise SimError("environment", f"MOBSTER_MAX_LOAD is {text!r}; it must be a load average above 0.",
|
|
56
|
+
"Unset it to treat the Mac as saturated at one runnable task per core.")
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Governor:
|
|
61
|
+
"""Reads the Mac's load and memory (``sample``) and decides. Tests pass a ``sample`` that returns a Pressure."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, sample=read_pressure, sleep=time.sleep, clock=time.monotonic):
|
|
64
|
+
self.sample, self.sleep, self.clock = sample, sleep, clock
|
|
65
|
+
|
|
66
|
+
def saturated(self, pressure=None):
|
|
67
|
+
pressure = pressure or self.sample()
|
|
68
|
+
return pressure.load >= (load_limit() or pressure.cores)
|
|
69
|
+
|
|
70
|
+
def memory_room(self, running, pressure=None, starting=0):
|
|
71
|
+
"""Whether another simulator may boot while ``running`` are up: one needs 4 GB free; the first always may.
|
|
72
|
+
``starting`` of them are still booting and haven't taken their memory yet, so SIM_GB is set aside for each."""
|
|
73
|
+
return running < 1 or (pressure or self.sample()).free_gb - SIM_GB * starting >= SIM_GB
|
|
74
|
+
|
|
75
|
+
def wait_for_calm(self, say, what, *, timeout=LOAD_WAIT):
|
|
76
|
+
"""Return once the Mac isn't saturated. After ``timeout`` seconds, refuse: never start anyway."""
|
|
77
|
+
pressure = self.sample()
|
|
78
|
+
if not self.saturated(pressure):
|
|
79
|
+
return 0.0
|
|
80
|
+
began = self.clock()
|
|
81
|
+
say(f"Waiting to {what}: the Mac is busy (load {pressure.load:.0f} on {pressure.cores} cores)")
|
|
82
|
+
while self.clock() - began < timeout:
|
|
83
|
+
self.sleep(LOAD_POLL)
|
|
84
|
+
pressure = self.sample()
|
|
85
|
+
if not self.saturated(pressure):
|
|
86
|
+
return self.clock() - began
|
|
87
|
+
raise SimError("busy", f"The Mac is busy (load {pressure.load:.0f} on {pressure.cores} cores), so Mobster "
|
|
88
|
+
f"didn't {what}. Nothing was started.",
|
|
89
|
+
"Try again in a minute, or stop other heavy jobs. Set MOBSTER_MAX_LOAD to change when the Mac "
|
|
90
|
+
"counts as busy.")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Who holds each simulator: one small file per simulator, written while a process holds its lease.
|
|
2
|
+
|
|
3
|
+
The lease itself is a file lock the system frees when its owner dies, so a record whose process is gone is ignored.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import contextlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
FOLDER = "holders"
|
|
13
|
+
RELEASE_SUFFIX = ".release"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _path(data_dir, udid, suffix=".json"):
|
|
17
|
+
return Path(data_dir) / FOLDER / f"{udid}{suffix}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def alive(pid):
|
|
21
|
+
try:
|
|
22
|
+
os.kill(int(pid), 0)
|
|
23
|
+
except PermissionError:
|
|
24
|
+
return True
|
|
25
|
+
except (OSError, ValueError, TypeError):
|
|
26
|
+
return False
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def claim(data_dir, udid, who):
|
|
31
|
+
"""Record this process as the holder. ``who``: {"client", "session"}."""
|
|
32
|
+
record = {"pid": os.getpid(), "ppid": os.getppid(), "client": str(who.get("client") or "mobster")[:40],
|
|
33
|
+
"session": str(who.get("session") or f"pid {os.getpid()}")[:80], "since": time.time()}
|
|
34
|
+
with contextlib.suppress(OSError):
|
|
35
|
+
folder = _path(data_dir, udid).parent
|
|
36
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
_path(data_dir, udid).write_text(json.dumps(record))
|
|
38
|
+
_path(data_dir, udid, RELEASE_SUFFIX).unlink(missing_ok=True)
|
|
39
|
+
return record
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def read(data_dir, udid):
|
|
43
|
+
"""The live holder's record, or None."""
|
|
44
|
+
try:
|
|
45
|
+
record = json.loads(_path(data_dir, udid).read_text())
|
|
46
|
+
except (OSError, ValueError):
|
|
47
|
+
return None
|
|
48
|
+
return record if isinstance(record, dict) and alive(record.get("pid")) else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def clear(data_dir, udid):
|
|
52
|
+
"""Forget the holder, only when it is this process."""
|
|
53
|
+
record = read(data_dir, udid)
|
|
54
|
+
if record is not None and record.get("pid") != os.getpid():
|
|
55
|
+
return
|
|
56
|
+
with contextlib.suppress(OSError):
|
|
57
|
+
_path(data_dir, udid).unlink(missing_ok=True)
|
|
58
|
+
_path(data_dir, udid, RELEASE_SUFFIX).unlink(missing_ok=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def words(record):
|
|
62
|
+
minutes = max(0, int((time.time() - float(record.get("since") or time.time())) // 60))
|
|
63
|
+
return f"{record.get('client')} ({record.get('session')}), {minutes} min"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ancestors(pid=None):
|
|
67
|
+
"""The process ids from ``pid`` up to launchd, found with ps."""
|
|
68
|
+
import subprocess
|
|
69
|
+
chain, pid = [], pid or os.getpid()
|
|
70
|
+
while pid and pid > 1 and len(chain) < 32:
|
|
71
|
+
chain.append(pid)
|
|
72
|
+
try:
|
|
73
|
+
out = subprocess.run(["ps", "-o", "ppid=", "-p", str(pid)], capture_output=True, text=True,
|
|
74
|
+
timeout=5).stdout.strip()
|
|
75
|
+
pid = int(out)
|
|
76
|
+
except (OSError, ValueError, subprocess.SubprocessError):
|
|
77
|
+
break
|
|
78
|
+
return chain
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def request_release(data_dir, udid):
|
|
82
|
+
"""Ask the holder to let go; its server notices at its next sweep."""
|
|
83
|
+
with contextlib.suppress(OSError):
|
|
84
|
+
_path(data_dir, udid, RELEASE_SUFFIX).write_text("")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def release_requested(data_dir, udid):
|
|
88
|
+
return _path(data_dir, udid, RELEASE_SUFFIX).exists() and read(data_dir, udid) is not None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""The values the simulator manager hands out and raises; ``api`` and the package re-export them."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ..errors import SimError as SimError
|
|
8
|
+
|
|
9
|
+
SIM_KINDS = ("environment", "simulator", "wda", "install", "launch", "busy")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class AppInfo:
|
|
14
|
+
bundle_id: str
|
|
15
|
+
name: str # CFBundleDisplayName, else CFBundleName, else the bundle id
|
|
16
|
+
version: str # "1.0 (1)"
|
|
17
|
+
path: Optional[str] # the .app, or None for an app looked up by bundle id
|
|
18
|
+
icon: Optional[bytes] = None # its home screen icon as a PNG (apps.icon_png), when its bundle has one to read
|
|
19
|
+
executable: Optional[str] = None # CFBundleExecutable: the process name its log and crash reports carry
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class SimTarget:
|
|
24
|
+
udid: str
|
|
25
|
+
name: str # "Mobster · iPhone 17 Pro · iOS 26.4"
|
|
26
|
+
device_type: str # "iPhone 17 Pro"
|
|
27
|
+
runtime: str # "iOS 26.4"
|
|
28
|
+
wda_url: str # "http://127.0.0.1:8310"
|
|
29
|
+
mjpeg_url: str # "http://127.0.0.1:9310"
|
|
30
|
+
xctestrun: str # the built runner plan (for the restarter)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SimLease:
|
|
34
|
+
"""Holds one simulator's device lease; ``release()`` is idempotent."""
|
|
35
|
+
target: SimTarget
|
|
36
|
+
|
|
37
|
+
def __init__(self, target: SimTarget, leases=(), timing: Optional[dict] = None, on_release=None):
|
|
38
|
+
self.target = target
|
|
39
|
+
self._on_release = on_release
|
|
40
|
+
self.timing = dict(timing or {})
|
|
41
|
+
self._leases = list(leases)
|
|
42
|
+
self._lock = threading.Lock()
|
|
43
|
+
|
|
44
|
+
def release(self) -> None:
|
|
45
|
+
with self._lock:
|
|
46
|
+
leases, self._leases = self._leases, []
|
|
47
|
+
on_release, self._on_release = self._on_release, None
|
|
48
|
+
try:
|
|
49
|
+
if on_release is not None:
|
|
50
|
+
on_release()
|
|
51
|
+
finally:
|
|
52
|
+
for lease in leases:
|
|
53
|
+
try:
|
|
54
|
+
lease.close()
|
|
55
|
+
except OSError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def __enter__(self) -> SimTarget:
|
|
59
|
+
return self.target
|
|
60
|
+
|
|
61
|
+
def __exit__(self, *exc) -> None:
|
|
62
|
+
self.release()
|