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
mobile_agent/sim/apps.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""The app under test: its Info.plist, and emptying its containers on a reset."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import plistlib
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .models import SimError
|
|
11
|
+
|
|
12
|
+
SIMULATOR_PLATFORM = "iPhoneSimulator"
|
|
13
|
+
BUILD_FOR_SIMULATOR = ("xcodebuild -scheme <Scheme> -destination 'generic/platform=iOS Simulator' "
|
|
14
|
+
"-derivedDataPath .mobster/build CODE_SIGNING_ALLOWED=NO build")
|
|
15
|
+
EMPTIED = ("Documents", "Library", "tmp")
|
|
16
|
+
# The icon a report shows beside the app's name: 2 times its 16 px, with room to spare for a 3x screen.
|
|
17
|
+
ICON_SIZE = 120
|
|
18
|
+
# An icon file name the asset catalog writes (AppIcon60x60, AppIcon76x76~ipad), never a path.
|
|
19
|
+
ICON_NAME = re.compile(r"[A-Za-z0-9 ._~-]{1,100}")
|
|
20
|
+
ICON_FILE_LIMIT = 2_000_000
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def devices_root():
|
|
24
|
+
return Path.home() / "Library" / "Developer" / "CoreSimulator" / "Devices"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def device_folder(udid):
|
|
28
|
+
return devices_root() / udid
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_info(app_path):
|
|
32
|
+
"""The .app's Info.plist as a dict. SimError("install") when it isn't a readable app bundle."""
|
|
33
|
+
path = Path(app_path).expanduser()
|
|
34
|
+
if not path.is_dir():
|
|
35
|
+
raise SimError("install", f"{path} isn't an .app folder.",
|
|
36
|
+
"Pass the .app that xcodebuild built for the iOS Simulator (a folder ending in .app).")
|
|
37
|
+
try:
|
|
38
|
+
with open(path / "Info.plist", "rb") as stream:
|
|
39
|
+
info = plistlib.load(stream)
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
raise SimError("install", f"{path} has no Info.plist, so it isn't an app bundle.",
|
|
42
|
+
"Pass the .app that xcodebuild built for the iOS Simulator.") from None
|
|
43
|
+
except (OSError, ValueError, plistlib.InvalidFileException):
|
|
44
|
+
raise SimError("install", f"{path}/Info.plist can't be read.", "Build the app again.") from None
|
|
45
|
+
if not isinstance(info, dict):
|
|
46
|
+
raise SimError("install", f"{path}/Info.plist isn't a dictionary.", "Build the app again.")
|
|
47
|
+
return info
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def version_text(info):
|
|
51
|
+
""" "1.0 (1)" from CFBundleShortVersionString and CFBundleVersion; either alone when that is all there is."""
|
|
52
|
+
short = str(info.get("CFBundleShortVersionString") or "").strip()
|
|
53
|
+
build = str(info.get("CFBundleVersion") or "").strip()
|
|
54
|
+
if short and build and short != build:
|
|
55
|
+
return f"{short} ({build})"
|
|
56
|
+
return short or build
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def display_name(info, bundle_id):
|
|
60
|
+
return str(info.get("CFBundleDisplayName") or info.get("CFBundleName") or bundle_id)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# A device build's PNGs are Apple's CgBI variant, which Pillow can't read, so a device build has no icon here.
|
|
64
|
+
def icon_png(app_path, info, size=ICON_SIZE):
|
|
65
|
+
from PIL import Image
|
|
66
|
+
|
|
67
|
+
folder = Path(app_path).expanduser()
|
|
68
|
+
names = list(info.get("CFBundleIconFiles") or ())
|
|
69
|
+
for key in ("CFBundleIcons", "CFBundleIcons~ipad"):
|
|
70
|
+
icons = info.get(key)
|
|
71
|
+
primary = icons.get("CFBundlePrimaryIcon") if isinstance(icons, dict) else None
|
|
72
|
+
if isinstance(primary, dict):
|
|
73
|
+
names += list(primary.get("CFBundleIconFiles") or ()) + [primary.get("CFBundleIconName")]
|
|
74
|
+
best = None
|
|
75
|
+
for name in {name for name in names if isinstance(name, str) and ICON_NAME.fullmatch(name) and ".." not in name}:
|
|
76
|
+
for file in folder.glob(f"{name}*.png"):
|
|
77
|
+
if file.parent != folder or not file.is_file() or file.stat().st_size > ICON_FILE_LIMIT:
|
|
78
|
+
continue
|
|
79
|
+
try:
|
|
80
|
+
with Image.open(file) as image:
|
|
81
|
+
image.verify()
|
|
82
|
+
with Image.open(file) as image:
|
|
83
|
+
area = image.width * image.height
|
|
84
|
+
if image.width == image.height and image.width <= 1024 and (best is None or area > best[0]):
|
|
85
|
+
best = (area, file)
|
|
86
|
+
except (OSError, ValueError, SyntaxError):
|
|
87
|
+
continue
|
|
88
|
+
if best is None:
|
|
89
|
+
return None
|
|
90
|
+
with Image.open(best[1]) as image:
|
|
91
|
+
icon = image.convert("RGBA").resize((size, size), Image.LANCZOS)
|
|
92
|
+
out = BytesIO()
|
|
93
|
+
icon.save(out, "PNG", optimize=True)
|
|
94
|
+
return out.getvalue()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def url_schemes(info):
|
|
98
|
+
"""The URL schemes the app declares (CFBundleURLTypes) as written, in order, without duplicates."""
|
|
99
|
+
schemes = []
|
|
100
|
+
for kind in info.get("CFBundleURLTypes") or ():
|
|
101
|
+
for scheme in (kind.get("CFBundleURLSchemes") or ()) if isinstance(kind, dict) else ():
|
|
102
|
+
scheme = str(scheme).strip()
|
|
103
|
+
if scheme and scheme not in schemes:
|
|
104
|
+
schemes.append(scheme)
|
|
105
|
+
return schemes
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def scheme_approvals(udid):
|
|
109
|
+
path = device_folder(udid) / "data" / "Library" / "Preferences" / "com.apple.launchservices.schemeapproval.plist"
|
|
110
|
+
try:
|
|
111
|
+
with open(path, "rb") as stream:
|
|
112
|
+
data = plistlib.load(stream)
|
|
113
|
+
except (OSError, ValueError, plistlib.InvalidFileException):
|
|
114
|
+
return {}
|
|
115
|
+
return data if isinstance(data, dict) else {}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def built_for_simulator(info):
|
|
119
|
+
platforms = info.get("CFBundleSupportedPlatforms")
|
|
120
|
+
if isinstance(platforms, list) and platforms:
|
|
121
|
+
return SIMULATOR_PLATFORM in platforms
|
|
122
|
+
return str(info.get("DTPlatformName") or "").lower() == "iphonesimulator"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def device_build_error(path):
|
|
126
|
+
return SimError("install", "This .app was built for a device. Build it for the iOS Simulator: "
|
|
127
|
+
f"{BUILD_FOR_SIMULATOR}",
|
|
128
|
+
f"Rebuild {Path(path).name} with -destination 'generic/platform=iOS Simulator'.")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def inside(path, root):
|
|
132
|
+
"""Whether ``path`` resolves (symlinks followed) to a place strictly inside ``root``."""
|
|
133
|
+
real, base = os.path.realpath(path), os.path.realpath(root)
|
|
134
|
+
return real != base and real.startswith(base.rstrip(os.sep) + os.sep)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def guard_container(container, udid):
|
|
138
|
+
"""The container's real path, or SimError("simulator") when it isn't inside this simulator's folder."""
|
|
139
|
+
folder = device_folder(udid)
|
|
140
|
+
if not folder.is_dir():
|
|
141
|
+
raise SimError("simulator", f"The simulator's folder {folder} is missing.",
|
|
142
|
+
"Delete it with `mobster sim delete` and run again.")
|
|
143
|
+
if not container or not inside(container, folder):
|
|
144
|
+
raise SimError("simulator", f"The container {container} resolves outside this simulator's folder "
|
|
145
|
+
f"({os.path.realpath(container) if container else 'no path'}), so Mobster "
|
|
146
|
+
"didn't empty it.",
|
|
147
|
+
"Use a simulator Mobster created (`mobster sim prepare`); a cloned one shares containers.")
|
|
148
|
+
root = Path(os.path.realpath(container))
|
|
149
|
+
for name in EMPTIED:
|
|
150
|
+
folder = root / name
|
|
151
|
+
if folder.is_symlink():
|
|
152
|
+
raise SimError("simulator", f"{folder} is a symbolic link, so Mobster didn't empty it.",
|
|
153
|
+
"Erase the simulator with `mobster sim erase`.")
|
|
154
|
+
return root
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def empty_container(container, udid):
|
|
158
|
+
root = guard_container(container, udid)
|
|
159
|
+
removed = 0
|
|
160
|
+
for name in EMPTIED:
|
|
161
|
+
folder = root / name
|
|
162
|
+
if not folder.is_dir():
|
|
163
|
+
continue
|
|
164
|
+
for entry in list(os.scandir(folder)):
|
|
165
|
+
# a symlink is removed as a link, never followed
|
|
166
|
+
path = Path(entry.path)
|
|
167
|
+
if entry.is_dir(follow_symlinks=False):
|
|
168
|
+
shutil.rmtree(path)
|
|
169
|
+
else:
|
|
170
|
+
path.unlink()
|
|
171
|
+
removed += 1
|
|
172
|
+
return removed
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Simulator biometric events through the notifyutil backend used by Appium."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from ..devices import wda_url
|
|
6
|
+
from .models import SimError
|
|
7
|
+
|
|
8
|
+
# Appium's names: https://github.com/appium/appium-ios-simulator/blob/f104016afea238b138054d7c9b868dfbec77be72/lib/extensions/biometric.ts
|
|
9
|
+
ENROLLMENT = "com.apple.BiometricKit.enrollmentChanged"
|
|
10
|
+
BIOMETRICS = {"face-id": "pearl", "touch-id": "fingerTouch"}
|
|
11
|
+
ACTIONS = ("status", "enroll", "unenroll", "match", "nonmatch")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def perform(manager, udid, action, biometric_type="face-id"):
|
|
15
|
+
# no acquire_udid: it dismisses the authentication prompt this command exercises
|
|
16
|
+
if action not in ACTIONS or biometric_type not in BIOMETRICS:
|
|
17
|
+
raise SimError("simulator", "Unknown biometric action or type.",
|
|
18
|
+
"See `mobster sim biometric --help`.")
|
|
19
|
+
# Claim the current port under the registry lock: acquire() may move a runner
|
|
20
|
+
# to a different port while preparing it. Never lock a stale address.
|
|
21
|
+
lease = None
|
|
22
|
+
try:
|
|
23
|
+
with manager.registry.edit() as entries:
|
|
24
|
+
entry = dict(manager._entry(udid, entries))
|
|
25
|
+
lease = manager._try_lease(wda_url(entry["wda_port"]))
|
|
26
|
+
if lease is None:
|
|
27
|
+
raise SimError("busy", f"{entry['name']} is running a check.",
|
|
28
|
+
"Wait for it to finish, or stop it, before sending biometric events.")
|
|
29
|
+
current = manager._guard_owned(entry, manager._devices())
|
|
30
|
+
if current is None or current.state != "Booted":
|
|
31
|
+
raise SimError("simulator", "Biometric testing needs a booted Mobster simulator.",
|
|
32
|
+
"Run `mobster sim prepare`, then use the UDID from `mobster sim list`.")
|
|
33
|
+
return _perform(manager, entry["udid"], action, biometric_type)
|
|
34
|
+
finally:
|
|
35
|
+
if lease is not None:
|
|
36
|
+
lease.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _notify(manager, udid, *args):
|
|
40
|
+
result = manager.simctl("spawn", udid, "notifyutil", *args, timeout=30)
|
|
41
|
+
if not result.ok:
|
|
42
|
+
raise SimError("simulator", f"The simulator biometric command did not complete: "
|
|
43
|
+
f"{result.last_line() or 'notifyutil failed'}",
|
|
44
|
+
"Inspect the app and `mobster sim biometric <UDID> status` before sending another event.")
|
|
45
|
+
return result.out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def deliver(manager, udid, action, biometric_type="face-id"):
|
|
49
|
+
"""``action`` on simulator ``udid`` without the lease check ``perform`` makes: for a check run that holds the
|
|
50
|
+
simulator itself (a Face ID match while its app waits for one). The caller owns the lease."""
|
|
51
|
+
if action not in ACTIONS or biometric_type not in BIOMETRICS:
|
|
52
|
+
raise SimError("simulator", "Unknown biometric action or type.", "See `mobster sim biometric --help`.")
|
|
53
|
+
return _perform(manager, udid, action, biometric_type)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _enrolled(manager, udid):
|
|
57
|
+
output = _notify(manager, udid, "-g", ENROLLMENT)
|
|
58
|
+
match = re.fullmatch(re.escape(ENROLLMENT) + r"\s+([01])", output.strip())
|
|
59
|
+
if match is None:
|
|
60
|
+
raise SimError("simulator", "The simulator did not report a recognized biometric enrollment state.",
|
|
61
|
+
"Use Simulator's Features menu to inspect enrollment; this runtime may be unsupported.")
|
|
62
|
+
return match[1] == "1"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _perform(manager, udid, action, biometric_type):
|
|
66
|
+
if action in ("enroll", "unenroll"):
|
|
67
|
+
enabled = action == "enroll"
|
|
68
|
+
_notify(manager, udid, "-s", ENROLLMENT, "1" if enabled else "0")
|
|
69
|
+
_notify(manager, udid, "-p", ENROLLMENT)
|
|
70
|
+
enrolled = _enrolled(manager, udid)
|
|
71
|
+
if enrolled != enabled:
|
|
72
|
+
raise SimError("simulator", "The simulator did not confirm the requested biometric enrollment.",
|
|
73
|
+
"Inspect enrollment in Simulator's Features menu before trying again.")
|
|
74
|
+
else:
|
|
75
|
+
enrolled = _enrolled(manager, udid)
|
|
76
|
+
if action in ("match", "nonmatch"):
|
|
77
|
+
if not enrolled:
|
|
78
|
+
raise SimError("simulator", "The simulator has no enrolled biometric.",
|
|
79
|
+
f"Run `mobster sim biometric {udid} enroll` before sending a match event.")
|
|
80
|
+
outcome = "match" if action == "match" else "nomatch"
|
|
81
|
+
notification = f"com.apple.BiometricKit_Sim.{BIOMETRICS[biometric_type]}.{outcome}"
|
|
82
|
+
_notify(manager, udid, "-p", notification)
|
|
83
|
+
return {"udid": udid, "action": action, "type": biometric_type, "enrolled": enrolled,
|
|
84
|
+
"event_sent": action in ("match", "nonmatch")}
|
mobile_agent/sim/cli.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""`mobster sim` (and `python -m mobile_agent.sim`): Mobster's own headless simulators."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .models import SimError
|
|
9
|
+
|
|
10
|
+
EXIT_OK, EXIT_FAILED, EXIT_USAGE = 0, 1, 2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def add_arguments(parser, helpers):
|
|
14
|
+
from .biometrics import ACTIONS, BIOMETRICS
|
|
15
|
+
formatter = getattr(parser, "formatter_class", argparse.HelpFormatter)
|
|
16
|
+
subs = parser.add_subparsers(dest="sim_command", metavar="<command>", required=True,
|
|
17
|
+
help="what to do with Mobster's simulators")
|
|
18
|
+
prog = parser.prog
|
|
19
|
+
|
|
20
|
+
def command(name, help, description):
|
|
21
|
+
return subs.add_parser(name, prog=f"{prog} {name}", help=help, description=description,
|
|
22
|
+
formatter_class=formatter)
|
|
23
|
+
|
|
24
|
+
listing = command("list", "list Mobster's simulators",
|
|
25
|
+
"List the simulators Mobster created, with their state and WebDriverAgent address.")
|
|
26
|
+
listing.add_argument("--json", action="store_true", help="print JSON instead of a table")
|
|
27
|
+
|
|
28
|
+
release = command("release", "ask the agents you started to let go of their simulators",
|
|
29
|
+
"Ask each `mobster mcp` server started by the same app as this shell, such as the agent that "
|
|
30
|
+
"runs this command, to release the simulator it holds. It lets go at its next sweep, within "
|
|
31
|
+
"15 seconds. A simulator another app's agent holds is left alone.")
|
|
32
|
+
release.add_argument("--all-mine", action="store_true", required=True,
|
|
33
|
+
help="every simulator held by an agent started from the same app as this shell")
|
|
34
|
+
|
|
35
|
+
prepare = command("prepare", "create, boot and start WebDriverAgent, timing each phase",
|
|
36
|
+
"Create the simulator if needed, boot it headless, build WebDriverAgent the first time, start "
|
|
37
|
+
"it on 127.0.0.1, and print how long each phase took. It stays up afterwards.")
|
|
38
|
+
prepare.add_argument("--device", metavar="NAME", help="the simulator device type (default: iPhone 17 Pro, else "
|
|
39
|
+
"the newest iPhone the runtime supports)")
|
|
40
|
+
prepare.add_argument("--runtime", metavar="NAME", help="the iOS runtime, such as \"iOS 26.4\" (default: the "
|
|
41
|
+
"newest installed)")
|
|
42
|
+
prepare.add_argument("--json", action="store_true", help="print the simulator and the phase times as JSON")
|
|
43
|
+
|
|
44
|
+
biometric = command("biometric", "test biometric enrollment and authentication on a simulator",
|
|
45
|
+
"Read or change biometric enrollment, or send a Face ID or Touch ID test event to an "
|
|
46
|
+
"idle, booted Mobster simulator. Real devices are never supported. A sent event does "
|
|
47
|
+
"not confirm that the app authenticated.")
|
|
48
|
+
biometric.add_argument("udid", metavar="UDID", help="the booted Mobster simulator")
|
|
49
|
+
biometric.add_argument("action", choices=ACTIONS,
|
|
50
|
+
help="read enrollment, enable or disable it, or send a match or nonmatch event")
|
|
51
|
+
biometric.add_argument("--type", choices=tuple(BIOMETRICS), default="face-id", dest="biometric_type",
|
|
52
|
+
help="the biometric event type (default: face-id; enrollment is shared)")
|
|
53
|
+
biometric.add_argument("--json", action="store_true", help="print enrollment and event delivery as JSON")
|
|
54
|
+
|
|
55
|
+
shutdown = command("shutdown", "stop WebDriverAgent and shut down Mobster's simulators",
|
|
56
|
+
"Stop WebDriverAgent and shut down every Mobster simulator that isn't running a check, or "
|
|
57
|
+
"the one named.")
|
|
58
|
+
shutdown.add_argument("udid", nargs="?", metavar="UDID", help="only this simulator")
|
|
59
|
+
|
|
60
|
+
erase = command("erase", "erase one Mobster simulator",
|
|
61
|
+
"Shut down and erase one Mobster simulator: its apps and data go, and the next run boots it "
|
|
62
|
+
"fresh.")
|
|
63
|
+
erase.add_argument("udid", metavar="UDID", help="the simulator to erase")
|
|
64
|
+
|
|
65
|
+
delete = command("delete", "delete Mobster's simulators",
|
|
66
|
+
"Shut down and delete Mobster's simulators. Only UDIDs in Mobster's registry whose names start "
|
|
67
|
+
"with “Mobster · ” are deleted; any other UDID is refused.")
|
|
68
|
+
which = delete.add_mutually_exclusive_group(required=True)
|
|
69
|
+
which.add_argument("udid", nargs="?", metavar="UDID", help="the simulator to delete")
|
|
70
|
+
which.add_argument("--all", action="store_true", help="delete every Mobster simulator")
|
|
71
|
+
|
|
72
|
+
prune = command("prune", "remove unused simulators and stale WebDriverAgent builds",
|
|
73
|
+
"Delete Mobster simulators unused for 14 days, WebDriverAgent builds for Xcode versions no "
|
|
74
|
+
"longer installed, and registry entries for simulators that are gone.")
|
|
75
|
+
prune.add_argument("--json", action="store_true", help="print what was removed as JSON")
|
|
76
|
+
|
|
77
|
+
from .. import android
|
|
78
|
+
if android.enabled():
|
|
79
|
+
droid = command("android", "list, boot or stop Android emulators and devices",
|
|
80
|
+
"List Android devices (emulators and USB phones), boot an emulator and hold it for a run, or "
|
|
81
|
+
"shut down the emulators Mobster booted. Emulators you started yourself are never stopped.")
|
|
82
|
+
droid.add_argument("action", choices=("list", "boot", "shutdown"), help="what to do")
|
|
83
|
+
droid.add_argument("avd", nargs="?", metavar="AVD", help="the virtual device to boot or stop")
|
|
84
|
+
droid.add_argument("--json", action="store_true", help="print JSON instead of a table")
|
|
85
|
+
|
|
86
|
+
doctor = command("doctor", "check what `mobster verify` needs",
|
|
87
|
+
"Check Xcode, the iOS runtime, the device type, git, disk space, WebDriverAgent, Mobster's "
|
|
88
|
+
"simulators, ports, and your OpenAI or Anthropic key for Smart, and print the fix for each "
|
|
89
|
+
"problem. It never runs sudo and never downloads a runtime.")
|
|
90
|
+
doctor.add_argument("--fix", action="store_true", help="prepare the simulator: create, boot and build "
|
|
91
|
+
"WebDriverAgent once")
|
|
92
|
+
doctor.add_argument("--json", action="store_true",
|
|
93
|
+
help="print {\"ok\", \"checks\": [{\"key\", \"label\", \"state\", \"detail\", \"fix\"}]}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run(args) -> int:
|
|
97
|
+
return _run(args, _progress_lines())
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _progress_lines():
|
|
101
|
+
try:
|
|
102
|
+
from ..devtools import ProgressLines
|
|
103
|
+
except ImportError:
|
|
104
|
+
return _progress
|
|
105
|
+
return ProgressLines(sys.stderr)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _run(args, progress) -> int:
|
|
109
|
+
from .api import SimulatorManager
|
|
110
|
+
as_json = bool(getattr(args, "json", False))
|
|
111
|
+
manager = SimulatorManager(progress=progress)
|
|
112
|
+
end = getattr(progress, "end", lambda: None) # a "Still …" line on a terminal
|
|
113
|
+
name = getattr(args, "sim_command", None)
|
|
114
|
+
try:
|
|
115
|
+
if name == "list":
|
|
116
|
+
return _list(manager, as_json)
|
|
117
|
+
if name == "release":
|
|
118
|
+
asked = manager.release_mine()
|
|
119
|
+
print(f"Asked {len(asked)} simulator{'s' if len(asked) != 1 else ''} to let go. They are free within "
|
|
120
|
+
"15 seconds." if asked else "None of your agents holds a simulator.")
|
|
121
|
+
return EXIT_OK
|
|
122
|
+
if name == "prepare":
|
|
123
|
+
return _prepare(manager, args, as_json, end)
|
|
124
|
+
if name == "biometric":
|
|
125
|
+
result = manager.biometric(args.udid, args.action, biometric_type=args.biometric_type)
|
|
126
|
+
if as_json:
|
|
127
|
+
_print_json(result)
|
|
128
|
+
elif result["event_sent"]:
|
|
129
|
+
print(f"Sent {args.biometric_type} {args.action} event. Check the app for the result.")
|
|
130
|
+
else:
|
|
131
|
+
print("Biometric enrollment: " + ("enabled" if result["enrolled"] else "disabled"))
|
|
132
|
+
return EXIT_OK
|
|
133
|
+
if name == "android":
|
|
134
|
+
return _android(manager, args, as_json)
|
|
135
|
+
if name == "shutdown":
|
|
136
|
+
manager.shutdown(args.udid)
|
|
137
|
+
if not manager.registry.read():
|
|
138
|
+
print("No Mobster simulators.")
|
|
139
|
+
return EXIT_OK
|
|
140
|
+
if name == "erase":
|
|
141
|
+
manager.erase(args.udid)
|
|
142
|
+
return EXIT_OK
|
|
143
|
+
if name == "delete":
|
|
144
|
+
deleted = manager.delete(None if args.all else args.udid)
|
|
145
|
+
if not deleted:
|
|
146
|
+
print("No Mobster simulators to delete.")
|
|
147
|
+
return EXIT_OK
|
|
148
|
+
if name == "prune":
|
|
149
|
+
removed = manager.prune()
|
|
150
|
+
if as_json:
|
|
151
|
+
_print_json(removed)
|
|
152
|
+
else:
|
|
153
|
+
lines = [f"Deleted {udid}" for udid in removed["simulators"]]
|
|
154
|
+
lines += [f"Forgot {udid}: it no longer exists" for udid in removed["entries"]]
|
|
155
|
+
lines += [f"Removed {path}" for path in removed["wda_builds"]]
|
|
156
|
+
print("\n".join(lines) or "Nothing to prune.")
|
|
157
|
+
return EXIT_OK
|
|
158
|
+
if name == "doctor":
|
|
159
|
+
from .doctor import problems, report
|
|
160
|
+
checks = manager.doctor(fix=bool(args.fix))
|
|
161
|
+
end()
|
|
162
|
+
if as_json:
|
|
163
|
+
_print_json({"ok": not problems(checks), "checks": checks})
|
|
164
|
+
else:
|
|
165
|
+
print(report(checks, color=_color()))
|
|
166
|
+
return EXIT_FAILED if problems(checks) else EXIT_OK
|
|
167
|
+
print(f"mobster sim: unknown command {name!r}", file=sys.stderr)
|
|
168
|
+
return EXIT_USAGE
|
|
169
|
+
except SimError as exc:
|
|
170
|
+
end()
|
|
171
|
+
if as_json:
|
|
172
|
+
_print_json({"ok": False, "error": {"kind": exc.kind, "message": str(exc), "fix": exc.fix}})
|
|
173
|
+
print(f"mobster sim: {exc}" + (f"\n {exc.fix}" if exc.fix else ""), file=sys.stderr)
|
|
174
|
+
return EXIT_FAILED
|
|
175
|
+
except KeyboardInterrupt:
|
|
176
|
+
end()
|
|
177
|
+
return 130
|
|
178
|
+
except Exception as exc: # never raise into mobster's main
|
|
179
|
+
end()
|
|
180
|
+
print(f"mobster sim: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
181
|
+
return EXIT_FAILED
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _progress(message):
|
|
185
|
+
print(message, file=sys.stderr, flush=True)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _print_json(value):
|
|
189
|
+
print(json.dumps(value, ensure_ascii=False, indent=2))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _color():
|
|
193
|
+
try:
|
|
194
|
+
from ..style import color_enabled
|
|
195
|
+
return color_enabled(sys.stdout)
|
|
196
|
+
except Exception:
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _list(manager, as_json):
|
|
201
|
+
rows = manager.list()
|
|
202
|
+
if as_json:
|
|
203
|
+
_print_json({"simulators": rows})
|
|
204
|
+
return EXIT_OK
|
|
205
|
+
if not rows:
|
|
206
|
+
print("No Mobster simulators yet. `mobster sim prepare` creates one.")
|
|
207
|
+
return EXIT_OK
|
|
208
|
+
table = [("NAME", "STATE", "WEBDRIVERAGENT", "HELD BY", "UDID")]
|
|
209
|
+
for row in rows:
|
|
210
|
+
wda = row["wda_url"].split("//")[-1] if row["wda"] == "ready" else "stopped"
|
|
211
|
+
holder = row["holder"]
|
|
212
|
+
held = (f"{holder['client']} ({holder['session']}), {_since(holder['since'])}" if holder
|
|
213
|
+
else "a Mobster process" if row["in_use"] else "-")
|
|
214
|
+
table.append((row["name"], row["state"], wda, held, row["udid"]))
|
|
215
|
+
widths = [max(len(line[column]) for line in table) for column in range(len(table[0]) - 1)]
|
|
216
|
+
for line in table:
|
|
217
|
+
print(" ".join(cell.ljust(width) for cell, width in zip(line, widths)) + " " + line[-1])
|
|
218
|
+
return EXIT_OK
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _android(manager, args, as_json):
|
|
222
|
+
from ..android.emulator import EmulatorManager
|
|
223
|
+
droid = EmulatorManager(manager.data_dir)
|
|
224
|
+
if args.action == "boot":
|
|
225
|
+
lease = droid.acquire(args.avd)
|
|
226
|
+
target = lease.target
|
|
227
|
+
lease.release() # the emulator stays up for the next run
|
|
228
|
+
print(f"Ready {target.name} ({target.serial})")
|
|
229
|
+
elif args.action == "shutdown":
|
|
230
|
+
stopped = droid.shutdown(args.avd)
|
|
231
|
+
print("\n".join(f"Stopped {name}" for name in stopped) or "No Mobster emulators to stop.")
|
|
232
|
+
else:
|
|
233
|
+
rows = droid.devices()
|
|
234
|
+
if as_json:
|
|
235
|
+
_print_json({"devices": rows})
|
|
236
|
+
elif not rows:
|
|
237
|
+
print("No Android devices. `mobster sim android boot` starts an emulator.")
|
|
238
|
+
for row in rows if not as_json else ():
|
|
239
|
+
print(f"{row['name']:<24} {row['kind']:<9} {row['state']:<12} {'in use' if row['in_use'] else 'idle':<7} "
|
|
240
|
+
f"{row['serial']}")
|
|
241
|
+
return EXIT_OK
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _since(epoch):
|
|
245
|
+
minutes = max(0, int((time.time() - float(epoch or time.time())) // 60))
|
|
246
|
+
return f"{minutes} min" if minutes < 60 else f"{minutes // 60} h {minutes % 60} min"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
PHASES = (("create", "create"), ("boot", "boot"), ("configure", "settings"),
|
|
250
|
+
("wda_build", "WebDriverAgent build"), ("wda_start", "WebDriverAgent start"))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _prepare(manager, args, as_json, end=lambda: None):
|
|
254
|
+
lease = manager.acquire(args.device, args.runtime)
|
|
255
|
+
try:
|
|
256
|
+
target, timing = lease.target, dict(lease.timing)
|
|
257
|
+
finally:
|
|
258
|
+
lease.release()
|
|
259
|
+
end()
|
|
260
|
+
if as_json:
|
|
261
|
+
_print_json({"udid": target.udid, "name": target.name, "device_type": target.device_type,
|
|
262
|
+
"runtime": target.runtime, "wda_url": target.wda_url, "mjpeg_url": target.mjpeg_url,
|
|
263
|
+
"xctestrun": target.xctestrun, "timing": timing})
|
|
264
|
+
return EXIT_OK
|
|
265
|
+
phases = [f"{label} {timing[key]:.1f} s" for key, label in PHASES if key in timing]
|
|
266
|
+
print(f"Ready {target.name}")
|
|
267
|
+
print(f" UDID {target.udid}")
|
|
268
|
+
print(f" WebDriverAgent {target.wda_url} (MJPEG {target.mjpeg_url})")
|
|
269
|
+
print(f" Took {timing.get('total', 0):.1f} s" + (f": {', '.join(phases)}" if phases else
|
|
270
|
+
", already up"))
|
|
271
|
+
return EXIT_OK
|