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,148 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import plistlib
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
BUILD_DIR = "build" # .mobster/build: the derived data folder
|
|
7
|
+
LOG_NAME = "pr-build.log"
|
|
8
|
+
BUILD_TIMEOUT = 45 * 60
|
|
9
|
+
LIST_TIMEOUT = 120
|
|
10
|
+
SDKS = {"sim": "iphonesimulator", "device": "iphoneos"}
|
|
11
|
+
DESTINATIONS = {"sim": "generic/platform=iOS Simulator", "device": "generic/platform=iOS"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BuildError(Exception):
|
|
15
|
+
"""The build couldn't be made: one plain sentence, and the fix."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message, fix=""):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.message, self.fix = message, fix
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_project(root):
|
|
23
|
+
"""The workspace or project to build: one at the project root, else one a folder below it."""
|
|
24
|
+
root = Path(root)
|
|
25
|
+
folders = [root]
|
|
26
|
+
if root.is_dir():
|
|
27
|
+
folders += sorted(p for p in root.iterdir() if p.is_dir() and not p.name.startswith(".")
|
|
28
|
+
and p.suffix not in (".xcodeproj", ".xcworkspace"))
|
|
29
|
+
for folder in folders:
|
|
30
|
+
workspaces = [p for p in folder.glob("*.xcworkspace") if p.is_dir()]
|
|
31
|
+
projects = [p for p in folder.glob("*.xcodeproj") if p.is_dir()]
|
|
32
|
+
if len(workspaces) > 1 or (not workspaces and len(projects) > 1):
|
|
33
|
+
names = ", ".join(p.name for p in (workspaces or projects))
|
|
34
|
+
raise BuildError(f"{folder} has more than one Xcode project ({names}).",
|
|
35
|
+
"Pass the one to build with --project.")
|
|
36
|
+
if workspaces:
|
|
37
|
+
return "workspace", workspaces[0]
|
|
38
|
+
if projects:
|
|
39
|
+
return "project", projects[0]
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def project_arg(path):
|
|
44
|
+
"""("workspace" | "project", path) for a path given with --project."""
|
|
45
|
+
path = Path(path)
|
|
46
|
+
if path.suffix == ".xcworkspace":
|
|
47
|
+
return "workspace", path
|
|
48
|
+
if path.suffix == ".xcodeproj":
|
|
49
|
+
return "project", path
|
|
50
|
+
raise BuildError(f"{path} isn't an .xcodeproj or an .xcworkspace.", "Pass --project YourApp.xcodeproj.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_command(argv, cwd, log=None, timeout=BUILD_TIMEOUT):
|
|
54
|
+
"""(exit code, output); output goes to ``log`` when given."""
|
|
55
|
+
try:
|
|
56
|
+
if log is not None:
|
|
57
|
+
Path(log).parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
with open(log, "wb") as handle:
|
|
59
|
+
result = subprocess.run(argv, cwd=str(cwd), stdout=handle, stderr=subprocess.STDOUT,
|
|
60
|
+
stdin=subprocess.DEVNULL, timeout=timeout)
|
|
61
|
+
return result.returncode, Path(log).read_text(encoding="utf-8", errors="replace")[-200_000:]
|
|
62
|
+
result = subprocess.run(argv, cwd=str(cwd), capture_output=True, stdin=subprocess.DEVNULL, timeout=timeout)
|
|
63
|
+
return result.returncode, (result.stdout + result.stderr).decode("utf-8", errors="replace")
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
return 127, "xcodebuild isn't installed."
|
|
66
|
+
except subprocess.TimeoutExpired:
|
|
67
|
+
return 124, f"xcodebuild didn't finish in {timeout // 60} minutes."
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def schemes(found, *, runner=None, cwd=None):
|
|
71
|
+
kind, path = found
|
|
72
|
+
code, output = (runner or run_command)(
|
|
73
|
+
["xcodebuild", "-list", "-json", f"-{kind}", str(path)], cwd or path.parent)
|
|
74
|
+
# stderr follows the JSON (xcodebuild's warnings, CocoaPods' notes), so read one object.
|
|
75
|
+
start = output.find("{")
|
|
76
|
+
try:
|
|
77
|
+
data = json.JSONDecoder().raw_decode(output[start:])[0] if start >= 0 else {}
|
|
78
|
+
except ValueError:
|
|
79
|
+
data = {}
|
|
80
|
+
block = data.get(kind) or data.get("project") or data.get("workspace") or {}
|
|
81
|
+
return [name for name in block.get("schemes") or () if isinstance(name, str)]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def pick_scheme(found, *, runner=None, cwd=None):
|
|
85
|
+
"""The only scheme, or the one named like the project; else BuildError (pass --scheme)."""
|
|
86
|
+
names = schemes(found, runner=runner, cwd=cwd)
|
|
87
|
+
stem = Path(found[1]).stem
|
|
88
|
+
if len(names) == 1:
|
|
89
|
+
return names[0]
|
|
90
|
+
if stem in names:
|
|
91
|
+
return stem
|
|
92
|
+
listed = ", ".join(names[:8]) or "none"
|
|
93
|
+
raise BuildError(f"Mobster can't tell which scheme to build (schemes: {listed}).", "Pass --scheme NAME.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build(root, *, scheme, project, platform="sim", configuration="Debug", runner=None, progress=None,
|
|
97
|
+
bundle=None, for_testing=False, test_plan=None):
|
|
98
|
+
"""Build ``scheme`` for ``platform`` ("sim" or "device") into .mobster/build. With ``for_testing`` the same build
|
|
99
|
+
also makes the test bundles and the .xctestrun file that ``xctest.run`` runs (build-for-testing)."""
|
|
100
|
+
root = Path(root)
|
|
101
|
+
kind, path = project
|
|
102
|
+
derived = root / ".mobster" / BUILD_DIR
|
|
103
|
+
log = derived / LOG_NAME
|
|
104
|
+
argv = ["xcodebuild", f"-{kind}", str(path), "-scheme", scheme, "-configuration", configuration,
|
|
105
|
+
"-destination", DESTINATIONS[platform], "-derivedDataPath", str(derived)]
|
|
106
|
+
if platform == "sim":
|
|
107
|
+
argv.append("CODE_SIGNING_ALLOWED=NO")
|
|
108
|
+
else:
|
|
109
|
+
argv.append("-allowProvisioningUpdates")
|
|
110
|
+
if for_testing and test_plan:
|
|
111
|
+
argv += ["-testPlan", test_plan]
|
|
112
|
+
argv.append("build-for-testing" if for_testing else "build")
|
|
113
|
+
(progress or (lambda line: None))(f"Building {scheme} for the {'simulator' if platform == 'sim' else 'iPhone'}")
|
|
114
|
+
code, output = (runner or run_command)(argv, root, log)
|
|
115
|
+
if code != 0:
|
|
116
|
+
errors = [line.strip() for line in output.splitlines() if "error:" in line][:3]
|
|
117
|
+
detail = ("; ".join(errors))[:600] if errors else f"xcodebuild exited {code}"
|
|
118
|
+
fix = "Fix the build, or pass --no-build to test the builds the checks already name."
|
|
119
|
+
if platform == "device" and "signing" in output.lower():
|
|
120
|
+
fix = "Open the project in Xcode and pick a team under Signing & Capabilities, then run it again."
|
|
121
|
+
raise BuildError(f"The build failed: {detail}.", fix)
|
|
122
|
+
built = {**product(derived, configuration, platform, scheme=scheme, bundle=bundle), "log": str(log)}
|
|
123
|
+
from ..memory import writer
|
|
124
|
+
writer.from_build(root, scheme, built.get("bundle")) # the project's scheme and bundle become memory
|
|
125
|
+
return built
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def product(derived, configuration, platform, *, scheme=None, bundle=None):
|
|
129
|
+
"""The .app the build made: the one with the checks' bundle ID, else the scheme's name, else the only one."""
|
|
130
|
+
folder = Path(derived) / "Build" / "Products" / f"{configuration}-{SDKS[platform]}"
|
|
131
|
+
apps = sorted(p for p in folder.glob("*.app") if p.is_dir())
|
|
132
|
+
found = [(app, _bundle(app)) for app in apps]
|
|
133
|
+
choice = next((app for app, bid in found if bundle and bid == bundle), None) or \
|
|
134
|
+
next((app for app, _ in found if scheme and app.stem == scheme), None) or \
|
|
135
|
+
(apps[0] if len(apps) == 1 else None)
|
|
136
|
+
if choice is None:
|
|
137
|
+
raise BuildError(f"The build finished, but Mobster couldn't tell which app in {folder} to test.",
|
|
138
|
+
"Pass --scheme with the app's scheme.")
|
|
139
|
+
return {"bundle": _bundle(choice), "path": str(choice), "platform": platform}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _bundle(app):
|
|
143
|
+
try:
|
|
144
|
+
with open(Path(app) / "Info.plist", "rb") as handle:
|
|
145
|
+
value = plistlib.load(handle).get("CFBundleIdentifier")
|
|
146
|
+
return value if isinstance(value, str) else None
|
|
147
|
+
except (OSError, plistlib.InvalidFileException, ValueError, AttributeError):
|
|
148
|
+
return None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""The step proof in `mobster test`'s reports: the cause line and the failing step's requests and log for JUnit,
|
|
2
|
+
and each step's clip, log lines and requests for the HTML report (verify/proof.py builds the proof)."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def cause_html(proof):
|
|
6
|
+
"""The cause line for the HTML report (verify/report.py draws it)."""
|
|
7
|
+
from ..verify.report import cause_line
|
|
8
|
+
return cause_line(proof)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cause_lines(proof):
|
|
12
|
+
cause = (proof or {}).get("cause")
|
|
13
|
+
return [f"Cause ({cause.get('side')}): {cause.get('line')}"] if isinstance(cause, dict) else []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def step_lines(proof, evidence):
|
|
17
|
+
"""The failing step's span for <system-out>: its text, the requests the log shows and the lines the app logged."""
|
|
18
|
+
from ..verify.evidence import log_text
|
|
19
|
+
from ..verify.proof import failing_entry
|
|
20
|
+
entry = failing_entry(proof, (evidence or {}).get("step")) if proof else None
|
|
21
|
+
if entry is None:
|
|
22
|
+
return []
|
|
23
|
+
out = [f"Step {entry.get('index')}: {entry.get('text')} ({entry.get('fromMs', 0) / 1000:.3f} to "
|
|
24
|
+
f"{entry.get('toMs', 0) / 1000:.3f} s)"]
|
|
25
|
+
rows = (entry.get("network") or {}).get("rows") or []
|
|
26
|
+
for row in rows:
|
|
27
|
+
out.append("Request: " + " ".join(str(row[key]) for key in ("method", "host", "status", "durationMs")
|
|
28
|
+
if row.get(key) is not None))
|
|
29
|
+
lines = (entry.get("log") or {}).get("lines") or []
|
|
30
|
+
if lines:
|
|
31
|
+
out += [f"The app's log in this step ({len(lines)} of {(entry.get('log') or {}).get('count')} lines):",
|
|
32
|
+
log_text(lines)]
|
|
33
|
+
return out
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def proof_steps(result):
|
|
37
|
+
"""Each step's clip, log lines and requests, the failing step open."""
|
|
38
|
+
from ..verify.proof import clip, failing_entry
|
|
39
|
+
from ..verify.report import proof_pane
|
|
40
|
+
proof = result.get("stepProof") or {}
|
|
41
|
+
entries = proof.get("steps") or []
|
|
42
|
+
if not entries:
|
|
43
|
+
return ""
|
|
44
|
+
failing = failing_entry(proof, (result.get("evidence") or {}).get("step"))
|
|
45
|
+
video, offset = result.get("video"), result.get("videoOffsetMs")
|
|
46
|
+
panes = [proof_pane(entry, clip=clip(entry, offset), video=video,
|
|
47
|
+
title=f"Step {entry.get('index')}: {_short(entry.get('text'))}",
|
|
48
|
+
opened=entry is failing) for entry in entries]
|
|
49
|
+
panes = [pane for pane in panes if pane]
|
|
50
|
+
return '<div class="proof-steps">' + "".join(panes) + "</div>" if panes else ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _short(text, limit=80):
|
|
54
|
+
text = " ".join(str(text or "").split())
|
|
55
|
+
return text if len(text) <= limit else text[:limit - 1] + "…"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Frozen local check inputs for comparing completed suite evidence."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..devkit import links
|
|
9
|
+
from ..replay_core.heal import HealError, InputChanged, proof_assets
|
|
10
|
+
from ..verify.checks import FILE_LIMIT, check_to_dict
|
|
11
|
+
from ..verify.fixtures import FixtureError, with_fixtures
|
|
12
|
+
|
|
13
|
+
FIELDS = ("checkSpecDigest", "baselineDigest", "fixtureDigest", "visualBaselineSha256",
|
|
14
|
+
"businessBindingSha256", "appDigest")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _hash(value):
|
|
18
|
+
return hashlib.sha256(json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False,
|
|
19
|
+
separators=(",", ":")).encode("utf-8")).hexdigest()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _source_digest(path):
|
|
23
|
+
from .input_files import digest
|
|
24
|
+
return digest(Path(path), FILE_LIMIT)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _app_digest(path, cache):
|
|
28
|
+
if path is None:
|
|
29
|
+
return None
|
|
30
|
+
path = Path(path)
|
|
31
|
+
if cache is not None and path in cache:
|
|
32
|
+
return cache[path]
|
|
33
|
+
# Project revisions use the same bounded, streaming file/tree digest for immutable prebuilt apps.
|
|
34
|
+
from .input_files import MAX_APP_BYTES, digest
|
|
35
|
+
try:
|
|
36
|
+
value = digest(path, MAX_APP_BYTES)
|
|
37
|
+
except (OSError, ValueError):
|
|
38
|
+
value = None
|
|
39
|
+
if cache is not None:
|
|
40
|
+
cache[path] = value
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def app_digest(path):
|
|
45
|
+
"""Bounded fingerprint of a locally preserved app artifact, or unknown."""
|
|
46
|
+
return _app_digest(path, None)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def case_provenance(entry, root, *, asset_cache=None):
|
|
50
|
+
"""Capture the effective parsed check and external proof at this admission boundary.
|
|
51
|
+
|
|
52
|
+
`app.path` bytes are an intentional comparison variable; launch and assertion semantics remain in the spec.
|
|
53
|
+
Each input freezes on its own: one that can't be read is null rather than an invented comparable identity, and the
|
|
54
|
+
others stay pinned. `inputChanged` says an input differs from what the check was admitted with, which refuses it.
|
|
55
|
+
"""
|
|
56
|
+
unknown = {**dict.fromkeys(FIELDS), "sourceDigest": None, "inputChanged": False}
|
|
57
|
+
check = entry.check
|
|
58
|
+
if check is None:
|
|
59
|
+
return unknown
|
|
60
|
+
try:
|
|
61
|
+
source_digest = _source_digest(entry.path)
|
|
62
|
+
except (OSError, ValueError):
|
|
63
|
+
source_digest = None
|
|
64
|
+
frozen = {**unknown, "sourceDigest": source_digest, "appDigest": _app_digest(check.app_path, asset_cache)}
|
|
65
|
+
try:
|
|
66
|
+
effective = with_fixtures(check, root)
|
|
67
|
+
if check.fixture_origin is not None:
|
|
68
|
+
# A prepared Check retains its source. Re-resolve only fixture-owned fields here: PR builds may
|
|
69
|
+
# intentionally replace app.path, while fixture YAML and link-registry edits must still refuse.
|
|
70
|
+
try:
|
|
71
|
+
current = with_fixtures(check.fixture_origin, root)
|
|
72
|
+
if any(links.OPEN_STEP.match(step) for step in current.steps):
|
|
73
|
+
current = replace(current,
|
|
74
|
+
steps=links.expand_steps(current.steps, links.discover(root, deep=False)))
|
|
75
|
+
except (FixtureError, links.LinksError) as error:
|
|
76
|
+
raise InputChanged(str(error)) from None # it resolved when the check was prepared
|
|
77
|
+
if (current.steps, current.reset, current.launch_args, current.launch_env, current.seed) != (
|
|
78
|
+
check.steps, check.reset, check.launch_args, check.launch_env, check.seed):
|
|
79
|
+
raise InputChanged("A fixture or linked step changed during this suite")
|
|
80
|
+
spec = check_to_dict(replace(effective, fixture_origin=None), project_root=root)
|
|
81
|
+
spec["app"].pop("path", None)
|
|
82
|
+
seed = effective.seed
|
|
83
|
+
spec["effectiveSeed"] = {"files": [(str(Path(source).relative_to(root)) if Path(source).is_relative_to(root)
|
|
84
|
+
else str(source), destination)
|
|
85
|
+
for source, destination in seed.files],
|
|
86
|
+
"keychain": seed.keychain, "pasteboard": seed.pasteboard,
|
|
87
|
+
"risky": list(seed.risky)}
|
|
88
|
+
spec["l10n"] = effective.l10n
|
|
89
|
+
spec["a11yScope"] = effective.a11y_scope
|
|
90
|
+
proof = proof_assets(effective)
|
|
91
|
+
baseline = {key: proof[key] for key in ("visualSha256", "mediaSha256", "pushSha256", "a11ySha256")}
|
|
92
|
+
fixture = {key: proof[key] for key in ("businessSha256", "seedSha256")}
|
|
93
|
+
baseline_digest = _hash(baseline) if any(value is not None for value in baseline.values()) else "none"
|
|
94
|
+
fixture_digest = _hash(fixture) if effective.business is not None or effective.seed else "none"
|
|
95
|
+
return {**frozen, "checkSpecDigest": _hash(spec), "baselineDigest": baseline_digest,
|
|
96
|
+
"visualBaselineSha256": proof["visualSha256"],
|
|
97
|
+
"fixtureDigest": fixture_digest, "businessBindingSha256": proof["businessSha256"]}
|
|
98
|
+
except InputChanged:
|
|
99
|
+
return {**frozen, "inputChanged": True}
|
|
100
|
+
except (OSError, ValueError, TypeError, FixtureError, HealError, links.LinksError):
|
|
101
|
+
return frozen
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def unpinned(frozen, check):
|
|
105
|
+
"""What of a check was never frozen, as a note names it, so its report can't claim that identity."""
|
|
106
|
+
return [name for name, missing in (("check file", frozen["sourceDigest"] is None),
|
|
107
|
+
("fixtures or assets", frozen["checkSpecDigest"] is None),
|
|
108
|
+
("app", bool(check.app_path) and frozen["appDigest"] is None)) if missing]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def require_case_provenance(entry, root, frozen, *, asset_cache=None):
|
|
112
|
+
"""Refuse an input known to have changed, before another physical effect. A field that could not be read pins
|
|
113
|
+
nothing: the check runs, and its null keeps the report and `mobster test compare` from claiming an identity."""
|
|
114
|
+
from .targets import Refused
|
|
115
|
+
current = case_provenance(entry, root, asset_cache=asset_cache)
|
|
116
|
+
if frozen["inputChanged"] or any(pinned is not None and current[field] != pinned
|
|
117
|
+
for field, pinned in frozen.items()):
|
|
118
|
+
raise Refused(f"{entry.file}, its app, or a fixture or assertion asset it uses changed during this suite. "
|
|
119
|
+
"Start a new suite.", "refused")
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, replace
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from . import pr as prmod
|
|
6
|
+
from . import pr_report
|
|
7
|
+
from .prbuild import BuildError
|
|
8
|
+
|
|
9
|
+
SMOKE = "smoke"
|
|
10
|
+
DRAFT_MODES = ("run", "write", "off")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class PrOptions:
|
|
15
|
+
base: Optional[str] = None # default: the branch the PR merges into (pr.default_base)
|
|
16
|
+
build: bool = True
|
|
17
|
+
scheme: Optional[str] = None
|
|
18
|
+
project: Optional[str] = None # an .xcodeproj or .xcworkspace; default the one in the project
|
|
19
|
+
configuration: str = "Debug"
|
|
20
|
+
drafts: str = "run" # run | write | off
|
|
21
|
+
gifs: bool = True
|
|
22
|
+
tested_with: bool = False # end the PR comment with "Tested with Mobster" (opt-in)
|
|
23
|
+
github: str = "check" # check | comment | off: Mobster Checks on the PR (github_checks.py)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class PrResult:
|
|
28
|
+
pr: dict
|
|
29
|
+
doc: dict
|
|
30
|
+
paths: dict
|
|
31
|
+
exit_code: int
|
|
32
|
+
notes: list = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def with_build(entries, built, testflight):
|
|
36
|
+
"""Point each check of the built app at the new build; a TestFlight app's checks use the build on the phone."""
|
|
37
|
+
out = []
|
|
38
|
+
for entry in entries:
|
|
39
|
+
check = entry.check
|
|
40
|
+
if check is None:
|
|
41
|
+
out.append(entry)
|
|
42
|
+
continue
|
|
43
|
+
if check.bundle_id and check.bundle_id in testflight:
|
|
44
|
+
entry = replace(entry, check=replace(check, app_path=None))
|
|
45
|
+
elif built and built.get("path") and (
|
|
46
|
+
(check.bundle_id and check.bundle_id == built.get("bundle")) or
|
|
47
|
+
(not check.bundle_id and check.app_path and Path(check.app_path).name == Path(built["path"]).name)):
|
|
48
|
+
entry = replace(entry, check=replace(check, app_path=built["path"]))
|
|
49
|
+
out.append(entry)
|
|
50
|
+
return out
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _all_checks(root):
|
|
54
|
+
from .discover import DiscoveryError, discover
|
|
55
|
+
if not (Path(root) / ".mobster" / "checks").is_dir():
|
|
56
|
+
return []
|
|
57
|
+
try:
|
|
58
|
+
return discover((), root)
|
|
59
|
+
except DiscoveryError:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def plan_pr(root, pr_options, *, git=prmod.run_git, entries=None, env=None):
|
|
64
|
+
"""(PrPlan without drafts, every saved check). Raises pr.PrError."""
|
|
65
|
+
changes = prmod.read_changes(root, pr_options.base, git=git, env=env)
|
|
66
|
+
screens = prmod.screens_in(changes)
|
|
67
|
+
entries = _all_checks(root) if entries is None else entries
|
|
68
|
+
picked = prmod.pick(entries, screens, changes)
|
|
69
|
+
fallback = None
|
|
70
|
+
if not picked and not screens:
|
|
71
|
+
smoke = [entry for entry in entries if SMOKE in {tag.casefold() for tag in entry.tags}]
|
|
72
|
+
picked = [prmod.Pick(entry, ["tagged smoke"], []) for entry in smoke]
|
|
73
|
+
fallback = SMOKE if smoke else None
|
|
74
|
+
return prmod.PrPlan(changes=changes, screens=screens, picked=picked, fallback=fallback), entries
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_app(root, pr_options, options, *, runner=None, progress=None, bundle=None):
|
|
78
|
+
from . import prbuild
|
|
79
|
+
if not pr_options.build:
|
|
80
|
+
return None
|
|
81
|
+
platform = "device" if any(True for _ in options.devices) else "sim"
|
|
82
|
+
if not pr_options.project and not options.xctest:
|
|
83
|
+
built = _build_react_native(root, pr_options, platform, runner=runner, progress=progress, bundle=bundle)
|
|
84
|
+
if built is not None:
|
|
85
|
+
return built
|
|
86
|
+
found = prbuild.project_arg(Path(root) / pr_options.project if not Path(pr_options.project).is_absolute()
|
|
87
|
+
else pr_options.project) if pr_options.project else prbuild.find_project(root)
|
|
88
|
+
if found is None:
|
|
89
|
+
return None
|
|
90
|
+
scheme = pr_options.scheme or prbuild.pick_scheme(found, runner=runner, cwd=root)
|
|
91
|
+
if options.xctest: # one build-for-testing makes the app the checks drive and the tests
|
|
92
|
+
from . import xctest
|
|
93
|
+
return xctest.build(root, scheme=scheme, project=found, test_plan=options.xctest_plan,
|
|
94
|
+
configuration=pr_options.configuration, runner=runner, progress=progress, bundle=bundle)
|
|
95
|
+
return prbuild.build(root, scheme=scheme, project=found, platform=platform,
|
|
96
|
+
configuration=pr_options.configuration, runner=runner, progress=progress, bundle=bundle)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _build_react_native(root, pr_options, platform, *, runner=None, progress=None, bundle=None):
|
|
100
|
+
"""An Expo or React Native app's build, or None for any other project."""
|
|
101
|
+
# A Debug React Native build loads its JavaScript from Metro, which a test run doesn't start: Debug becomes Release.
|
|
102
|
+
from ..devkit import DevError, expo
|
|
103
|
+
app = expo.detect(root)
|
|
104
|
+
if app is None or not (app.expo or app.native):
|
|
105
|
+
return None
|
|
106
|
+
configuration = "Release" if pr_options.configuration == "Debug" else pr_options.configuration
|
|
107
|
+
(progress or (lambda line: None))(f"{app.name} is a{'n Expo' if app.expo else ' React Native'} app: building "
|
|
108
|
+
f"{configuration}, with the JavaScript inside")
|
|
109
|
+
try:
|
|
110
|
+
return expo.build_sim(app, configuration=configuration, platform=platform, runner=runner, progress=progress,
|
|
111
|
+
bundle=bundle)
|
|
112
|
+
except DevError as error:
|
|
113
|
+
raise BuildError(error.message, error.fix) from None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_pr(pr_options, options, *, root, progress=None, git=prmod.run_git, env=None, github=None, **kwargs):
|
|
117
|
+
"""Run the PR's checks."""
|
|
118
|
+
from .github_checks import GITHUB_MODES_ERROR, MODES, Poster
|
|
119
|
+
if pr_options.github not in MODES:
|
|
120
|
+
from .api import UsageError
|
|
121
|
+
raise UsageError(GITHUB_MODES_ERROR)
|
|
122
|
+
poster = github or Poster(Path(root), pr_options.github, progress, environ=env, git=git)
|
|
123
|
+
try:
|
|
124
|
+
result = _test_pr(pr_options, options, root=root, progress=progress, git=git, env=env, on_plan=poster.start,
|
|
125
|
+
**kwargs)
|
|
126
|
+
except BaseException:
|
|
127
|
+
poster.abandon()
|
|
128
|
+
raise
|
|
129
|
+
poster.finish(result)
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _test_pr(pr_options, options, *, root, progress=None, stop=None, key=None, suite_id=None, on_suite=None,
|
|
134
|
+
git=prmod.run_git, build_runner=None, execute=None, gif=None, env=None, named_device=None,
|
|
135
|
+
attempts=None, gate=None, on_plan=None):
|
|
136
|
+
from .api import Planned, UsageError, check_options, resolve_targets
|
|
137
|
+
from .discover import DiscoveryError, apply_quarantine, discover, load_quarantine
|
|
138
|
+
from .reports import make_suite_dir
|
|
139
|
+
progress = progress or (lambda line: None)
|
|
140
|
+
if pr_options.drafts not in DRAFT_MODES:
|
|
141
|
+
raise UsageError(f"--drafts is one of {', '.join(DRAFT_MODES)}")
|
|
142
|
+
if options.testflight and not options.devices:
|
|
143
|
+
raise UsageError("--testflight goes with --device: a TestFlight build is on an iPhone, not a simulator.")
|
|
144
|
+
check_options(options)
|
|
145
|
+
root = Path(root)
|
|
146
|
+
# before on_plan starts the PR's check run, so a usage error leaves none behind marked cancelled
|
|
147
|
+
targets = resolve_targets(options, root=root, named_device=named_device)
|
|
148
|
+
plan, entries = plan_pr(root, pr_options, git=git, env=env)
|
|
149
|
+
if on_plan is not None:
|
|
150
|
+
on_plan(plan.changes)
|
|
151
|
+
notes = []
|
|
152
|
+
if plan.changes.truncated:
|
|
153
|
+
notes.append(f"This PR changes more than {prmod.MAX_FILES} files; Mobster read the first {prmod.MAX_FILES}.")
|
|
154
|
+
progress(_plan_line(plan))
|
|
155
|
+
preferred = next((p.entry.check.bundle_id for p in plan.picked if p.entry.check is not None
|
|
156
|
+
and p.entry.check.bundle_id), None)
|
|
157
|
+
tested_on_phone = set(options.testflight or ())
|
|
158
|
+
every_testflight = bool(options.devices) and plan.picked and all(
|
|
159
|
+
p.entry.check is not None and p.entry.check.bundle_id in tested_on_phone for p in plan.picked)
|
|
160
|
+
built = None
|
|
161
|
+
if not every_testflight and (plan.picked or plan.screens or options.xctest):
|
|
162
|
+
try:
|
|
163
|
+
built = build_app(root, pr_options, options, runner=build_runner, progress=progress, bundle=preferred)
|
|
164
|
+
except BuildError as error:
|
|
165
|
+
return _failed_build(plan, error, root, options, suite_id, pr_options, make_suite_dir)
|
|
166
|
+
picked_entries = with_build([p.entry for p in plan.picked], built, tested_on_phone)
|
|
167
|
+
listed = load_quarantine(root / ".mobster" / "quarantine.yaml")
|
|
168
|
+
apply_quarantine(picked_entries, listed)
|
|
169
|
+
if pr_options.drafts != "off":
|
|
170
|
+
app = prmod.app_of(entries, built)
|
|
171
|
+
plan.drafts = prmod.draft_checks(plan.screens, plan.picked, app=app, root=root)
|
|
172
|
+
draft_entries = []
|
|
173
|
+
if plan.drafts and pr_options.drafts == "run":
|
|
174
|
+
try:
|
|
175
|
+
draft_entries = discover([str(path) for _, path in plan.drafts], root)
|
|
176
|
+
except DiscoveryError:
|
|
177
|
+
draft_entries = []
|
|
178
|
+
for entry in draft_entries:
|
|
179
|
+
entry.quarantined = pr_report.DRAFT_REASON
|
|
180
|
+
draft_entries = with_build(draft_entries, built, tested_on_phone)
|
|
181
|
+
run_entries = picked_entries + draft_entries
|
|
182
|
+
plan_public = plan.public(root)
|
|
183
|
+
head = plan.changes.head
|
|
184
|
+
if not run_entries and not options.xctest:
|
|
185
|
+
suite_id, folder = make_suite_dir(root / ".mobster" / "test-results", suite_id)
|
|
186
|
+
doc = {"suite": suite_id, "checks": [], "exitCode": 0, "summary": {}}
|
|
187
|
+
result = pr_report.document(plan_public, doc, folder=folder, frames={}, gifs={}, built=_public_build(built),
|
|
188
|
+
head=head, tested_with=pr_options.tested_with, notes=notes)
|
|
189
|
+
paths = {"folder": folder, **pr_report.write(result, folder)}
|
|
190
|
+
return PrResult(result, doc, paths, 0, notes)
|
|
191
|
+
planned = Planned(root=root, entries=run_entries, targets=targets, options=options, notes=list(notes),
|
|
192
|
+
xctest_build=built if options.xctest else None,
|
|
193
|
+
changed=tuple(change.path for change in plan.changes.files) if options.xctest else None)
|
|
194
|
+
if execute is None:
|
|
195
|
+
from .api import execute
|
|
196
|
+
kwargs = {"progress": progress, "stop": stop, "key": key, "suite_id": suite_id, "on_suite": on_suite}
|
|
197
|
+
if named_device is not None:
|
|
198
|
+
kwargs["named_device"] = named_device
|
|
199
|
+
if attempts is not None:
|
|
200
|
+
kwargs["attempts"] = attempts
|
|
201
|
+
if gate is not None:
|
|
202
|
+
kwargs["gate"] = gate
|
|
203
|
+
doc, paths = execute(planned, **kwargs)
|
|
204
|
+
folder = Path(paths["folder"])
|
|
205
|
+
frames = pr_report.copy_frames(doc, folder)
|
|
206
|
+
gifs = pr_report.make_gifs(doc, folder, **({"build": gif} if gif else {})) if pr_options.gifs else {}
|
|
207
|
+
result = pr_report.document(plan_public, doc, folder=folder, frames=frames, gifs=gifs,
|
|
208
|
+
built=_public_build(built), head=head, tested_with=pr_options.tested_with,
|
|
209
|
+
notes=list(dict.fromkeys(notes + list(doc.get("notes") or ()))))
|
|
210
|
+
paths.update(pr_report.write(result, folder))
|
|
211
|
+
return PrResult(result, doc, paths, int(doc.get("exitCode", 3)), notes)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _plan_line(plan):
|
|
215
|
+
checks = len(plan.picked)
|
|
216
|
+
noun = "check" if checks == 1 else "checks"
|
|
217
|
+
if plan.screens:
|
|
218
|
+
screens = len(plan.screens)
|
|
219
|
+
return (f"mobster test pr: {screens} changed screen{'' if screens == 1 else 's'}, {checks} saved {noun} "
|
|
220
|
+
"cover them")
|
|
221
|
+
if plan.fallback == SMOKE:
|
|
222
|
+
return f"mobster test pr: no screen changed, so the {checks} {noun} tagged smoke run"
|
|
223
|
+
return "mobster test pr: no screen changed and no check is tagged smoke"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _public_build(built):
|
|
227
|
+
if not built:
|
|
228
|
+
return None
|
|
229
|
+
return {"bundle": built.get("bundle"), "app": Path(built["path"]).name if built.get("path") else None,
|
|
230
|
+
"platform": built.get("platform")}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _failed_build(plan, error, root, options, suite_id, pr_options, make_suite_dir):
|
|
234
|
+
suite_id, folder = make_suite_dir(Path(root) / ".mobster" / "test-results", suite_id)
|
|
235
|
+
doc = {"suite": suite_id, "checks": [], "exitCode": 3, "summary": {}}
|
|
236
|
+
note = f"{error.message} {error.fix}".strip()
|
|
237
|
+
result = pr_report.document(plan.public(root), doc, folder=folder, frames={}, gifs={}, built=None,
|
|
238
|
+
head=plan.changes.head, tested_with=pr_options.tested_with, notes=[note])
|
|
239
|
+
result["headline"] = "Mobster checks: the build failed"
|
|
240
|
+
paths = {"folder": folder, **pr_report.write(result, folder)}
|
|
241
|
+
return PrResult(result, doc, paths, 3, [note])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
test_pr.__test__ = False # a function named test_* that unittest must not collect
|