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,405 @@
|
|
|
1
|
+
"""`mobster expo`: an Expo or React Native app on a simulator or your iPhone (expo.py does the work)."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import DevError, expo_dev, main, say, subcommands
|
|
10
|
+
|
|
11
|
+
EXPECT_WAIT = 45
|
|
12
|
+
CHECK_EVERY = 3
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClientMissing(DevError):
|
|
16
|
+
"""The device couldn't open the dev client's link: it isn't installed there (or didn't answer)."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_arguments(parser, helpers):
|
|
20
|
+
add = subcommands(parser, "expo_command")
|
|
21
|
+
|
|
22
|
+
doctor = add("doctor", help="say what Mobster found in this Expo or React Native project",
|
|
23
|
+
description="Read package.json and the Expo config, and say what Mobster found: the app's name, "
|
|
24
|
+
"bundle ID and schemes, whether it opens in your dev client or Expo Go, whether the "
|
|
25
|
+
"ios folder exists, and whether Metro is running. Reads only.")
|
|
26
|
+
doctor.add_argument("--port", type=int, default=8081, metavar="N", help="Metro's port (default: 8081)")
|
|
27
|
+
doctor.add_argument("--json", action="store_true", help="print it as JSON")
|
|
28
|
+
|
|
29
|
+
preview = add("preview", help="open the app on a simulator or your iPhone from Metro, and check a screen",
|
|
30
|
+
description="Open your app on a simulator or your iPhone from the JavaScript Metro serves from "
|
|
31
|
+
"your working tree: in your dev client when the app has expo-dev-client, else in "
|
|
32
|
+
"Expo Go. Starts Metro in the background when nothing answers on the port. With "
|
|
33
|
+
"--expect, waits for the text and says whether it's on screen, with a screenshot.",
|
|
34
|
+
epilog="exit codes: 0 opened (and every expectation held), 1 an expectation didn't hold, "
|
|
35
|
+
"3 couldn't\n\nexamples:\n mobster expo preview\n"
|
|
36
|
+
" mobster expo preview --device \"Test iPhone\" --expect \"Welcome back\"\n"
|
|
37
|
+
" mobster expo preview --path settings/profile --expect Profile")
|
|
38
|
+
preview.add_argument("--device", metavar="NAME", help="a device from `mobster devices` (default: your iPhone, "
|
|
39
|
+
"else the only device)")
|
|
40
|
+
preview.add_argument("--port", type=int, default=8081, metavar="N", help="Metro's port (default: 8081)")
|
|
41
|
+
preview.add_argument("--path", metavar="PATH",
|
|
42
|
+
help="then open this screen by the app's own deep link, such as settings/profile")
|
|
43
|
+
preview.add_argument("--expect", action="append", metavar="TEXT",
|
|
44
|
+
help="text that must be on screen once the app loads; repeatable")
|
|
45
|
+
preview.add_argument("--wait", type=float, default=EXPECT_WAIT, metavar="S",
|
|
46
|
+
help=f"how long --expect waits for the bundle to load (default: {EXPECT_WAIT})")
|
|
47
|
+
preview.add_argument("--no-start", action="store_true", help="don't start Metro; fail when it isn't running")
|
|
48
|
+
preview.add_argument("--screenshot", metavar="FILE",
|
|
49
|
+
help="save the screen here (default with --expect: .mobster/expo/preview.png)")
|
|
50
|
+
preview.add_argument("--json", action="store_true", help="print the result as JSON")
|
|
51
|
+
|
|
52
|
+
dev = add("dev", help="run the dev loop: Metro, the dev client, the app open, a reload on every save",
|
|
53
|
+
description="Start this project's Metro (or reuse it), install your dev client on the device when "
|
|
54
|
+
"it isn't there, open the app, and reload it each time you save a source file. When "
|
|
55
|
+
"Metro can't bundle the app, it says so with Metro's error and keeps watching: the next "
|
|
56
|
+
"save tries again. ctrl+c stops the Metro this command started. --check runs saved "
|
|
57
|
+
"checks on the device once the app is up.",
|
|
58
|
+
epilog="exit codes: 0 stopped with ctrl+c (or --once: opened and every check held), 1 Metro can't "
|
|
59
|
+
"bundle the app or a check didn't hold, 3 couldn't\n\nexamples:\n"
|
|
60
|
+
" mobster expo dev --device \"Test iPhone\"\n"
|
|
61
|
+
" mobster expo dev --once --check .mobster/checks/home.yaml")
|
|
62
|
+
dev.add_argument("--device", metavar="NAME", help="a device from `mobster devices` (default: your iPhone, "
|
|
63
|
+
"else the only device)")
|
|
64
|
+
dev.add_argument("--port", type=int, metavar="N", help="Metro's port (default: 8081, else the next free one)")
|
|
65
|
+
dev.add_argument("--check", action="append", metavar="PATH",
|
|
66
|
+
help="a check file or folder to run on the device once the app is up; repeatable")
|
|
67
|
+
dev.add_argument("--no-install", action="store_true", help="don't build and install the dev "
|
|
68
|
+
"client when it is missing")
|
|
69
|
+
dev.add_argument("--once", action="store_true", help="open the app, run the checks, and stop; no watching")
|
|
70
|
+
dev.add_argument("--json", action="store_true", help="print one JSON line per event")
|
|
71
|
+
|
|
72
|
+
reload = add("reload", help="reload the JavaScript in every app connected to Metro",
|
|
73
|
+
description="Send Metro's reload to every app connected to it, as pressing r in `npx expo start` "
|
|
74
|
+
"does.")
|
|
75
|
+
reload.add_argument("--port", type=int, default=8081, metavar="N", help="Metro's port (default: 8081)")
|
|
76
|
+
|
|
77
|
+
build = add("build", help="build the app for a simulator or your iPhone",
|
|
78
|
+
description="Build the app. For a simulator: a Release build into .mobster/build with the "
|
|
79
|
+
"JavaScript inside, so it runs with no Metro (what checks and CI use), after `npx expo "
|
|
80
|
+
"prebuild` when there's no ios folder. For an iPhone: your dev client, signed with your "
|
|
81
|
+
"team and installed by `npx expo run:ios --device`. --eas builds an EAS profile on this "
|
|
82
|
+
"Mac with `eas build --local`.",
|
|
83
|
+
epilog="examples:\n mobster expo build\n mobster expo build --device \"iPhone 17 Pro\" --install\n"
|
|
84
|
+
" mobster expo build --device \"Test iPhone\"\n"
|
|
85
|
+
" mobster expo build --eas development --device \"Test iPhone\" --install")
|
|
86
|
+
build.add_argument("--device", metavar="NAME",
|
|
87
|
+
help="build for this device: an iPhone (installs your dev client) or a Mobster simulator")
|
|
88
|
+
build.add_argument("--install", action="store_true", help="install the build on --device")
|
|
89
|
+
build.add_argument("--debug", action="store_true",
|
|
90
|
+
help="a Debug build that loads its JavaScript from Metro (the dev client), not Release")
|
|
91
|
+
build.add_argument("--eas", metavar="PROFILE", help="build this EAS profile on this Mac (eas build --local)")
|
|
92
|
+
build.add_argument("--json", action="store_true", help="print the build as JSON")
|
|
93
|
+
|
|
94
|
+
add("stop", help="stop the Metro that `mobster expo preview` started",
|
|
95
|
+
description="Stop the Metro server `mobster expo preview` started in the background for this project.")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run(args, *, cwd=None, devices=None, wda=None):
|
|
99
|
+
if devices is None or wda is None:
|
|
100
|
+
from ..phone_io import choose_device
|
|
101
|
+
from ..phone_io import wda as phone_wda
|
|
102
|
+
devices, wda = devices or choose_device, wda or phone_wda
|
|
103
|
+
handlers = {"doctor": _doctor, "preview": _preview, "dev": _dev, "reload": _reload, "build": _build, "stop": _stop}
|
|
104
|
+
return main("expo", handlers, args, cwd=Path(cwd or Path.cwd()), devices=devices, wda=wda)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _doctor(args, cwd, devices, wda):
|
|
108
|
+
from . import expo
|
|
109
|
+
app = expo.find_app(cwd)
|
|
110
|
+
running = expo.metro_running(args.port, root=app.root)
|
|
111
|
+
info = {**app.public(), "metro": {"port": args.port, "running": running}}
|
|
112
|
+
if args.json:
|
|
113
|
+
say(json.dumps(info, ensure_ascii=False))
|
|
114
|
+
return 0
|
|
115
|
+
kind = f"Expo app ({app.sdk})" if app.expo else "React Native app"
|
|
116
|
+
lines = [f"{app.name}: {kind}",
|
|
117
|
+
f" Bundle ID {app.bundle or 'not set (ios.bundleIdentifier)'}",
|
|
118
|
+
f" Schemes {', '.join(app.schemes) or 'none (scheme)'}",
|
|
119
|
+
f" Opens in {'your dev client (expo-dev-client)' if app.client == 'dev-client' else 'Expo Go'}",
|
|
120
|
+
f" ios folder {'yes' if app.native else 'no: `mobster expo build` runs npx expo prebuild first'}",
|
|
121
|
+
f" Metro {'running' if running else 'not running'} on port {args.port}",
|
|
122
|
+
"", "Checks and CI build Release for the simulator, with the JavaScript inside: mobster expo build"]
|
|
123
|
+
lines += [f"Note: {note}" for note in app.notes]
|
|
124
|
+
say("\n".join(lines))
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _preview(args, cwd, devices, wda):
|
|
129
|
+
from . import expo
|
|
130
|
+
app = expo.find_app(cwd)
|
|
131
|
+
if not app.expo:
|
|
132
|
+
raise DevError("Preview opens Expo apps. This React Native app has no Expo.",
|
|
133
|
+
"Run it from Xcode, or `mobster expo build --device NAME --install`.")
|
|
134
|
+
record = devices(args.device)
|
|
135
|
+
started = None
|
|
136
|
+
if not expo.metro_running(args.port, root=app.root):
|
|
137
|
+
if args.no_start:
|
|
138
|
+
raise DevError(f"Metro isn't running on port {args.port}.", "Start it with `npx expo start`.")
|
|
139
|
+
say(f"Starting Metro on port {args.port}: npx expo start (log: {expo.STATE_DIR}/metro.log)", sys.stderr)
|
|
140
|
+
server, owned = expo_dev.ensure(app, args.port)
|
|
141
|
+
started = server["pid"] if owned else None
|
|
142
|
+
url = expo.preview_url(app, expo.host_for(record), args.port, args.path)
|
|
143
|
+
link = expo.app_link(app, args.path) if args.path and app.client == "dev-client" else None
|
|
144
|
+
if args.path and app.client == "dev-client" and link is None:
|
|
145
|
+
raise DevError("--path needs the app's own URL scheme, and its Expo config has no scheme.",
|
|
146
|
+
"Add \"scheme\" to app.json, then rebuild the dev client.")
|
|
147
|
+
result = preview(app, record, wda, url=url, link=link, expect=list(args.expect or ()), wait=args.wait,
|
|
148
|
+
screenshot=args.screenshot,
|
|
149
|
+
metro_error=lambda: expo_dev.bundle_error(app, args.port, wait=CHECK_EVERY))
|
|
150
|
+
result["metroStarted"] = started
|
|
151
|
+
if args.json:
|
|
152
|
+
say(json.dumps(result, ensure_ascii=False))
|
|
153
|
+
else:
|
|
154
|
+
lines = [f"Opened {app.name} on {record.get('name')} from Metro on port {args.port}"
|
|
155
|
+
f" ({'your dev client' if app.client == 'dev-client' else 'Expo Go'})."]
|
|
156
|
+
for item in result["expect"]:
|
|
157
|
+
lines.append(f"{'✓' if item['ok'] else '✗'} {item['text']}: {item['observed']}")
|
|
158
|
+
if result.get("error"):
|
|
159
|
+
lines.append(f"✗ The app failed to load: {result['error']}")
|
|
160
|
+
if result.get("screenshot"):
|
|
161
|
+
lines.append(f"Screenshot {result['screenshot']}")
|
|
162
|
+
if started:
|
|
163
|
+
lines.append("Metro keeps running in the background: `mobster expo reload` after a change, "
|
|
164
|
+
"`mobster expo stop` when you're done.")
|
|
165
|
+
say("\n".join(lines))
|
|
166
|
+
return 0 if all(item["ok"] for item in result["expect"]) and not result.get("error") else 1
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def preview(app, record, wda, *, url, link=None, expect=(), wait=EXPECT_WAIT, screenshot=None,
|
|
170
|
+
clock=time.monotonic, sleep=time.sleep, metro_error=None):
|
|
171
|
+
from ..deeplinks import LinkError, open_link
|
|
172
|
+
from ..phone_io import Calls, PhoneIOError
|
|
173
|
+
from ..verify.assertions import evaluate_on, expand_assertion
|
|
174
|
+
from ..verify.tree import read_tree
|
|
175
|
+
assertions = [item for text in expect for item in expand_assertion({"text": text}, "--expect")]
|
|
176
|
+
results = []
|
|
177
|
+
shot = None
|
|
178
|
+
failure = None
|
|
179
|
+
with wda(record) as request:
|
|
180
|
+
driver = Calls(request)
|
|
181
|
+
try:
|
|
182
|
+
open_link(driver, url)
|
|
183
|
+
if link:
|
|
184
|
+
sleep(3)
|
|
185
|
+
open_link(driver, link)
|
|
186
|
+
except (LinkError, PhoneIOError) as error:
|
|
187
|
+
client = "your dev client" if app.client == "dev-client" else "Expo Go"
|
|
188
|
+
fix = (f"Install it first: mobster expo build --device \"{record.get('name')}\" --install"
|
|
189
|
+
if app.client == "dev-client" else "Install Expo Go from the App Store on the iPhone.")
|
|
190
|
+
raise ClientMissing(f"{record.get('name')} couldn't open {url}: {client} isn't installed there, or "
|
|
191
|
+
f"didn't answer ({error}).", fix) from None
|
|
192
|
+
if assertions:
|
|
193
|
+
deadline = clock() + max(0.0, float(wait))
|
|
194
|
+
polls = 0
|
|
195
|
+
while True:
|
|
196
|
+
try:
|
|
197
|
+
tree = read_tree(driver)
|
|
198
|
+
results = evaluate_on(assertions, tree)
|
|
199
|
+
except Exception:
|
|
200
|
+
tree, results = None, []
|
|
201
|
+
if results and all(item.ok for item in results):
|
|
202
|
+
break
|
|
203
|
+
if tree is not None:
|
|
204
|
+
failure = expo_dev.screen_error([text for node in tree.shown()
|
|
205
|
+
for text in (node.label, node.value)])
|
|
206
|
+
if failure is None and metro_error is not None and polls % CHECK_EVERY == 0:
|
|
207
|
+
found = metro_error()
|
|
208
|
+
failure = found["message"] if found else None
|
|
209
|
+
polls += 1
|
|
210
|
+
if failure or clock() >= deadline:
|
|
211
|
+
break
|
|
212
|
+
sleep(1)
|
|
213
|
+
if not results:
|
|
214
|
+
results = [_Unread(text) for text in expect]
|
|
215
|
+
if not assertions:
|
|
216
|
+
try:
|
|
217
|
+
tree = read_tree(driver)
|
|
218
|
+
failure = expo_dev.screen_error([text for node in tree.shown() for text in (node.label, node.value)])
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
if failure is None and metro_error is not None:
|
|
222
|
+
found = metro_error()
|
|
223
|
+
failure = found["message"] if found else None
|
|
224
|
+
if screenshot or assertions:
|
|
225
|
+
target = Path(screenshot or Path(app.root) / ".mobster" / "expo" / "preview.png")
|
|
226
|
+
try:
|
|
227
|
+
data = request("GET", "/screenshot", None, 20)
|
|
228
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
229
|
+
target.write_bytes(base64.b64decode(data))
|
|
230
|
+
shot = str(target)
|
|
231
|
+
except Exception:
|
|
232
|
+
shot = None
|
|
233
|
+
return {"url": url, "link": link, "device": record.get("name"),
|
|
234
|
+
"expect": [{"text": item.text, "ok": bool(item.ok), "observed": item.observed} for item in results],
|
|
235
|
+
"screenshot": shot, "error": failure}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class _Unread:
|
|
239
|
+
"""An expectation whose screen couldn't be read before the wait ran out."""
|
|
240
|
+
|
|
241
|
+
ok, observed = False, "the screen couldn't be read"
|
|
242
|
+
|
|
243
|
+
def __init__(self, text):
|
|
244
|
+
self.text = f'text "{text}"'
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _event(args, kind, **fields):
|
|
248
|
+
if args.json:
|
|
249
|
+
say(json.dumps({"event": kind, **fields}, ensure_ascii=False))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _dev(args, cwd, devices, wda, *, sleep=time.sleep, watcher=None, rounds=None, run_checks=None):
|
|
253
|
+
from ..devtools import signals_as_interrupt
|
|
254
|
+
from . import expo
|
|
255
|
+
app = expo.find_app(cwd)
|
|
256
|
+
if not app.expo:
|
|
257
|
+
raise DevError("The dev loop opens Expo apps. This React Native app has no Expo.",
|
|
258
|
+
"Run it from Xcode, or `mobster expo build --device NAME --install`.")
|
|
259
|
+
record = devices(args.device)
|
|
260
|
+
with signals_as_interrupt():
|
|
261
|
+
server, started = expo_dev.ensure(app, args.port)
|
|
262
|
+
port = server["port"]
|
|
263
|
+
try:
|
|
264
|
+
_event(args, "metro", port=port, started=started)
|
|
265
|
+
if not args.json:
|
|
266
|
+
say(f"Metro on port {port}" + (f" (started; log: {expo.STATE_DIR}/metro.log)." if started else
|
|
267
|
+
" (already running for this project)."), sys.stderr)
|
|
268
|
+
failed = expo_dev.bundle_error(app, port)
|
|
269
|
+
if failed:
|
|
270
|
+
return _failed(args, failed)
|
|
271
|
+
result = _open(args, app, record, wda, port)
|
|
272
|
+
if result.get("error"):
|
|
273
|
+
return _failed(args, {"message": result["error"]})
|
|
274
|
+
_event(args, "opened", device=record.get("name"), screenshot=result.get("screenshot"))
|
|
275
|
+
if not args.json:
|
|
276
|
+
say(f"Opened {app.name} on {record.get('name')}.")
|
|
277
|
+
if args.check:
|
|
278
|
+
code = (run_checks or _run_checks)(record.get("name"), args.check)
|
|
279
|
+
_event(args, "checks", exit=code)
|
|
280
|
+
if code or args.once:
|
|
281
|
+
return code
|
|
282
|
+
if args.once:
|
|
283
|
+
return 0
|
|
284
|
+
if not args.json:
|
|
285
|
+
say("Watching for saves: ctrl+c stops.", sys.stderr)
|
|
286
|
+
watching = watcher or expo_dev.Watcher(app.root)
|
|
287
|
+
count = 0
|
|
288
|
+
while rounds is None or count < rounds:
|
|
289
|
+
count += 1
|
|
290
|
+
sleep(1)
|
|
291
|
+
if not watching.changed():
|
|
292
|
+
continue
|
|
293
|
+
expo.reload(port)
|
|
294
|
+
failed = expo_dev.bundle_error(app, port)
|
|
295
|
+
if failed:
|
|
296
|
+
_failed(args, failed, final=False)
|
|
297
|
+
else:
|
|
298
|
+
_event(args, "reloaded", port=port)
|
|
299
|
+
if not args.json:
|
|
300
|
+
say("Reloaded.")
|
|
301
|
+
if args.check:
|
|
302
|
+
code = (run_checks or _run_checks)(record.get("name"), args.check)
|
|
303
|
+
_event(args, "checks", exit=code)
|
|
304
|
+
return 0
|
|
305
|
+
except KeyboardInterrupt:
|
|
306
|
+
return 0
|
|
307
|
+
finally:
|
|
308
|
+
if started:
|
|
309
|
+
expo_dev.stop(app.root, expected_pid=server["pid"])
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _open(args, app, record, wda, port):
|
|
313
|
+
"""Open the app on the device. A dev client that isn't there is built and installed once, unless --no-install."""
|
|
314
|
+
from . import expo
|
|
315
|
+
url = expo.preview_url(app, expo.host_for(record), port)
|
|
316
|
+
show = lambda: expo_dev.bundle_error(app, port, wait=CHECK_EVERY) # noqa: E731
|
|
317
|
+
try:
|
|
318
|
+
return preview(app, record, wda, url=url, expect=[], metro_error=show)
|
|
319
|
+
except ClientMissing:
|
|
320
|
+
if args.no_install or app.client != "dev-client":
|
|
321
|
+
raise
|
|
322
|
+
from ..devtools import ProgressLines
|
|
323
|
+
progress = ProgressLines(sys.stderr)
|
|
324
|
+
try:
|
|
325
|
+
if record.get("kind") == "simulator":
|
|
326
|
+
built = expo.build_sim(app, configuration="Debug", progress=progress)
|
|
327
|
+
from ..phone_io.install import install
|
|
328
|
+
install(record, built["path"], progress=progress)
|
|
329
|
+
else:
|
|
330
|
+
if not record.get("udid"):
|
|
331
|
+
raise DevError(f"Mobster doesn't know {record.get('name')}'s UDID.", "Plug the iPhone in.")
|
|
332
|
+
expo.build_device(app, record["udid"], configuration="Debug", progress=progress)
|
|
333
|
+
finally:
|
|
334
|
+
progress.end()
|
|
335
|
+
return preview(app, record, wda, url=url, expect=[], metro_error=show)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _failed(args, error, final=True):
|
|
339
|
+
"""Say Metro can't bundle the app: its words, the file and the line when it names them."""
|
|
340
|
+
where = f" ({error['file']}:{error['line']})" if error.get("file") and error.get("line") else ""
|
|
341
|
+
_event(args, "failed", error=error)
|
|
342
|
+
say(f"mobster expo dev: Metro can't bundle the app: {error['message']}{where}" +
|
|
343
|
+
("" if final else " Fix it and save: the next save tries again."), sys.stderr)
|
|
344
|
+
return 1
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _run_checks(device_name, paths):
|
|
348
|
+
from ..cli_parser import build_parser
|
|
349
|
+
from ..testkit import cli as testkit_cli
|
|
350
|
+
return testkit_cli.run(build_parser().parse_args(["test", "--device", device_name, *paths]))
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _reload(args, cwd, devices, wda):
|
|
354
|
+
from . import expo
|
|
355
|
+
expo.reload(args.port)
|
|
356
|
+
say(f"Reloaded the apps connected to Metro on port {args.port}.")
|
|
357
|
+
return 0
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _build(args, cwd, devices, wda):
|
|
361
|
+
from ..devtools import ProgressLines
|
|
362
|
+
from . import expo
|
|
363
|
+
app = expo.find_app(cwd)
|
|
364
|
+
record = devices(args.device) if args.device else None
|
|
365
|
+
if args.install and record is None:
|
|
366
|
+
raise DevError("--install needs --device: the simulator or iPhone to install on.")
|
|
367
|
+
phone = record is not None and record.get("kind") != "simulator"
|
|
368
|
+
progress = ProgressLines(sys.stderr)
|
|
369
|
+
try:
|
|
370
|
+
if args.eas:
|
|
371
|
+
built = expo.build_eas(app, args.eas, simulator=not phone, progress=progress)
|
|
372
|
+
elif phone:
|
|
373
|
+
if not record.get("udid"):
|
|
374
|
+
raise DevError(f"Mobster doesn't know {record.get('name')}'s UDID.", "Plug the iPhone in.")
|
|
375
|
+
built = expo.build_device(app, record["udid"], configuration="Debug", progress=progress)
|
|
376
|
+
else:
|
|
377
|
+
built = expo.build_sim(app, configuration="Debug" if args.debug else "Release", progress=progress)
|
|
378
|
+
installed = False
|
|
379
|
+
if args.install and built.get("path"):
|
|
380
|
+
from ..phone_io.install import install
|
|
381
|
+
install(record, built["path"], progress=progress)
|
|
382
|
+
installed = True
|
|
383
|
+
elif phone and not args.eas:
|
|
384
|
+
installed = True # npx expo run:ios --device installs it
|
|
385
|
+
finally:
|
|
386
|
+
progress.end()
|
|
387
|
+
built = {**built, "installed": installed, "device": record.get("name") if record else None}
|
|
388
|
+
if args.json:
|
|
389
|
+
say(json.dumps(built, ensure_ascii=False))
|
|
390
|
+
return 0
|
|
391
|
+
where = built.get("path") or "the iPhone"
|
|
392
|
+
lines = [f"Built {app.name}: {where}"]
|
|
393
|
+
if installed:
|
|
394
|
+
lines.append(f"Installed on {record.get('name')}.")
|
|
395
|
+
if built["platform"] == "sim" and not args.debug and not args.eas:
|
|
396
|
+
lines.append("Release, with the JavaScript inside: it runs with no Metro. Point a check's app.path at it.")
|
|
397
|
+
say("\n".join(lines))
|
|
398
|
+
return 0
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _stop(args, cwd, devices, wda):
|
|
402
|
+
from . import expo
|
|
403
|
+
pid = expo_dev.stop(expo.project_root(cwd))
|
|
404
|
+
say(f"Stopped Metro (pid {pid})." if pid else "No Metro that Mobster started is running here.")
|
|
405
|
+
return 0
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""The Expo dev loop's state: one Metro per project, whether it works, and what to reload on a save."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import signal
|
|
7
|
+
import socket
|
|
8
|
+
import time
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .. import env_file
|
|
14
|
+
from . import DevError, expo
|
|
15
|
+
|
|
16
|
+
PORT_SPAN = 20
|
|
17
|
+
ERROR_LIMIT = 600
|
|
18
|
+
BUNDLE_WAIT = 90
|
|
19
|
+
IGNORED = frozenset({"node_modules", "ios", "android", ".git", ".mobster", ".expo", "Pods", "build", "dist"})
|
|
20
|
+
SOURCE = frozenset({".js", ".jsx", ".ts", ".tsx", ".json", ".mjs", ".cjs", ".css"})
|
|
21
|
+
MAX_FILES = 20_000
|
|
22
|
+
# Metro serves Expo's virtual entry for an Expo app; a plain React Native app has index.js.
|
|
23
|
+
ENTRY = {True: "/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true&hot=false&minify=false",
|
|
24
|
+
False: "/index.bundle?platform=ios&dev=true&minify=false"}
|
|
25
|
+
ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
|
|
26
|
+
# What a dev client's error screen and Metro's log say when the bundle failed.
|
|
27
|
+
SCREEN_MARKERS = ("Unable to resolve module", "SyntaxError", "Unexpected token", "Bundling failed",
|
|
28
|
+
"Unable to load script", "Could not connect to development server",
|
|
29
|
+
"There was a problem running the requested app", "Invariant Violation", "Uncaught Error",
|
|
30
|
+
"TransformError", "Cannot find module")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Metro's text is data from the project: cut short and never read as an instruction.
|
|
34
|
+
def _clean(text, limit=ERROR_LIMIT):
|
|
35
|
+
flat = " ".join(ANSI.sub("", str(text or "")).split())
|
|
36
|
+
return flat if len(flat) <= limit else flat[:limit - 1] + "…"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _alive(pid, kill=os.kill):
|
|
40
|
+
try:
|
|
41
|
+
kill(pid, 0)
|
|
42
|
+
return True
|
|
43
|
+
except ProcessLookupError:
|
|
44
|
+
return False
|
|
45
|
+
except OSError:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _state_file(root, name):
|
|
50
|
+
try:
|
|
51
|
+
return int((Path(root) / expo.STATE_DIR / name).read_text().strip())
|
|
52
|
+
except (OSError, ValueError):
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def port_free(port):
|
|
57
|
+
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
58
|
+
try:
|
|
59
|
+
probe.bind(("127.0.0.1", int(port)))
|
|
60
|
+
return True
|
|
61
|
+
except OSError:
|
|
62
|
+
return False
|
|
63
|
+
finally:
|
|
64
|
+
probe.close()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def pick_port(app, wanted=None, *, opener=None, free=port_free):
|
|
68
|
+
first = int(wanted or _state_file(app.root, "metro.port") or expo.DEFAULT_PORT)
|
|
69
|
+
for port in range(first, first + PORT_SPAN):
|
|
70
|
+
if expo.metro_running(port, opener=opener, root=app.root) or free(port):
|
|
71
|
+
return port
|
|
72
|
+
if wanted:
|
|
73
|
+
break
|
|
74
|
+
raise DevError(f"Port {first} is taken" + ("." if wanted else f", and so are the {PORT_SPAN - 1} after it."),
|
|
75
|
+
"Stop what is using it, or pass another --port.")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def log_tail(app, limit=2000):
|
|
79
|
+
try:
|
|
80
|
+
with (Path(app.root) / expo.STATE_DIR / "metro.log").open("rb") as handle:
|
|
81
|
+
handle.seek(max(0, os.fstat(handle.fileno()).st_size - limit))
|
|
82
|
+
text = handle.read(limit).decode("utf-8", errors="replace")
|
|
83
|
+
except OSError:
|
|
84
|
+
return ""
|
|
85
|
+
return ANSI.sub("", text[-limit:]).strip()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def bundle_error(app, port, *, opener=None, wait=BUNDLE_WAIT):
|
|
89
|
+
url = f"http://127.0.0.1:{int(port)}{ENTRY[bool(app.expo)]}"
|
|
90
|
+
try:
|
|
91
|
+
with (opener or urllib.request.urlopen)(url, timeout=wait) as response:
|
|
92
|
+
response.read(1)
|
|
93
|
+
return None
|
|
94
|
+
except urllib.error.HTTPError as error:
|
|
95
|
+
body = error.read(20_000).decode("utf-8", errors="replace")
|
|
96
|
+
except Exception:
|
|
97
|
+
return None
|
|
98
|
+
try:
|
|
99
|
+
data = json.loads(body)
|
|
100
|
+
except ValueError:
|
|
101
|
+
data = {}
|
|
102
|
+
first = next(iter(data.get("errors") or ()), None) if isinstance(data, dict) else None
|
|
103
|
+
first = first if isinstance(first, dict) else {}
|
|
104
|
+
message = (data.get("message") if isinstance(data, dict) else None) or first.get("description") or body
|
|
105
|
+
line = first.get("lineNumber")
|
|
106
|
+
return {"type": str(data.get("type") or "BundleError") if isinstance(data, dict) else "BundleError",
|
|
107
|
+
"message": _clean(message), "file": first.get("filename") if isinstance(first.get("filename"), str)
|
|
108
|
+
else None, "line": line if isinstance(line, int) else None}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def screen_error(texts):
|
|
112
|
+
"""The error on a dev client's error screen, from the text the screen shows, or None."""
|
|
113
|
+
for text in texts:
|
|
114
|
+
if any(marker in str(text) for marker in SCREEN_MARKERS):
|
|
115
|
+
return _clean(text)
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def status(app, *, opener=None, kill=os.kill, check_bundle=False):
|
|
120
|
+
pid, port = _state_file(app.root, "metro.pid"), _state_file(app.root, "metro.port") or expo.DEFAULT_PORT
|
|
121
|
+
live = pid is not None and expo.owns_metro(app.root, pid) and _alive(pid, kill)
|
|
122
|
+
answering = expo.metro_running(port, opener=opener, root=app.root)
|
|
123
|
+
info = {"state": "stopped", "port": port, "pid": pid if live else None, "error": None,
|
|
124
|
+
"log": f"{expo.STATE_DIR}/metro.log"}
|
|
125
|
+
if answering:
|
|
126
|
+
info["state"] = "running"
|
|
127
|
+
error = bundle_error(app, port, opener=opener) if check_bundle else None
|
|
128
|
+
if error:
|
|
129
|
+
info["state"], info["error"] = "failed", error
|
|
130
|
+
elif live:
|
|
131
|
+
info["state"] = "starting"
|
|
132
|
+
elif pid is not None:
|
|
133
|
+
tail = _clean(log_tail(app), ERROR_LIMIT)
|
|
134
|
+
info["state"], info["error"] = "failed", {"type": "MetroExited", "file": None, "line": None,
|
|
135
|
+
"message": tail or "Metro exited before it answered."}
|
|
136
|
+
return info
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# The second value is True only when this call started Metro: only that caller stops it.
|
|
140
|
+
def ensure(app, port=None, *, start=None, opener=None, kill=os.kill, free=port_free, wait=expo.METRO_WAIT):
|
|
141
|
+
with env_file.writer(Path(app.root) / expo.STATE_DIR / "admission"):
|
|
142
|
+
port = pick_port(app, port, opener=opener, free=free)
|
|
143
|
+
if expo.metro_running(port, opener=opener, root=app.root):
|
|
144
|
+
return {**status(app, opener=opener, kill=kill), "port": port}, False
|
|
145
|
+
if expo.metro_starting(app.root, kill=kill):
|
|
146
|
+
raise DevError(f"Metro is still starting on port {port}.", "Try again in a few seconds.")
|
|
147
|
+
pid = (start or expo.start_metro)(app, port, wait=wait, opener=opener, stop_on_fail=True)
|
|
148
|
+
return {"state": "running", "port": port, "pid": pid, "error": None, "log": f"{expo.STATE_DIR}/metro.log"}, True
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def stop(root, *, kill=os.kill, grace=5.0, sleep=time.sleep, clock=time.monotonic, expected_pid=None):
|
|
152
|
+
with env_file.writer(Path(root) / expo.STATE_DIR / "admission"):
|
|
153
|
+
return _stop_owned(root, kill=kill, grace=grace, sleep=sleep, clock=clock, expected_pid=expected_pid)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _stop_owned(root, *, kill=os.kill, grace=5.0, sleep=time.sleep, clock=time.monotonic, expected_pid=None):
|
|
157
|
+
"""Stop and reconcile a process group while the caller owns the project admission lock."""
|
|
158
|
+
pid = expo.stop_metro(root, kill=kill, expected_pid=expected_pid)
|
|
159
|
+
if pid is None:
|
|
160
|
+
return None
|
|
161
|
+
deadline = clock() + grace
|
|
162
|
+
while _alive(-pid, kill) and clock() < deadline:
|
|
163
|
+
sleep(0.1)
|
|
164
|
+
if _alive(-pid, kill):
|
|
165
|
+
try:
|
|
166
|
+
kill(-pid, signal.SIGKILL)
|
|
167
|
+
except ProcessLookupError:
|
|
168
|
+
return pid
|
|
169
|
+
deadline = clock() + 2
|
|
170
|
+
while _alive(-pid, kill) and clock() < deadline:
|
|
171
|
+
sleep(0.1)
|
|
172
|
+
if _alive(-pid, kill):
|
|
173
|
+
raise DevError("Metro's process group did not stop.", "Stop it from its terminal before starting another.")
|
|
174
|
+
return pid
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class Watcher:
|
|
178
|
+
"""Polls a project's source files (modified times only) and says when one was saved, added or removed."""
|
|
179
|
+
|
|
180
|
+
def __init__(self, root):
|
|
181
|
+
self.root = Path(root)
|
|
182
|
+
self.seen = self._scan()
|
|
183
|
+
|
|
184
|
+
def _scan(self):
|
|
185
|
+
found = {}
|
|
186
|
+
for folder, names, files in os.walk(self.root):
|
|
187
|
+
names[:] = [name for name in names if name not in IGNORED and not name.startswith(".")]
|
|
188
|
+
for name in files:
|
|
189
|
+
if os.path.splitext(name)[1] in SOURCE:
|
|
190
|
+
path = os.path.join(folder, name)
|
|
191
|
+
try:
|
|
192
|
+
found[path] = os.stat(path).st_mtime_ns
|
|
193
|
+
except OSError:
|
|
194
|
+
continue
|
|
195
|
+
if len(found) >= MAX_FILES:
|
|
196
|
+
return found
|
|
197
|
+
return found
|
|
198
|
+
|
|
199
|
+
def changed(self):
|
|
200
|
+
now = self._scan()
|
|
201
|
+
different = now != self.seen
|
|
202
|
+
self.seen = now
|
|
203
|
+
return different
|