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,257 @@
|
|
|
1
|
+
"""Which checks a `mobster test` run covers: discovery, the quarantine list, tags and shards."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass, field, replace
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
CHECK_SUFFIXES = (".yaml", ".yml", ".json")
|
|
9
|
+
NOT_CHECKS = frozenset({"matrix.yaml", "matrix.yml", "quarantine.yaml", "quarantine.yml"})
|
|
10
|
+
# What Mobster writes in .mobster/ (run results, reports, builds, the cache): never checks, though it holds .json.
|
|
11
|
+
OUTPUTS = frozenset({"runs", "test-results", "build", "cache"})
|
|
12
|
+
MAX_CHECKS = 500
|
|
13
|
+
SHARD = re.compile(r"(\d{1,3})/(\d{1,3})")
|
|
14
|
+
QUARANTINE_LIMIT = 200
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DiscoveryError(Exception):
|
|
18
|
+
"""A usage problem with the paths, the quarantine file, tags or the shard: one plain sentence."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Entry:
|
|
23
|
+
"""One check file in the suite."""
|
|
24
|
+
path: Path # absolute
|
|
25
|
+
file: str # as reports show it: relative to the project root when inside it
|
|
26
|
+
check: object = None # verify.checks.Check, or None when the file can't be read
|
|
27
|
+
error: Optional[str] = None # why it can't be read
|
|
28
|
+
quarantined: Optional[str] = None # None, or the reason it is quarantined ("" when none was given)
|
|
29
|
+
classname: str = ""
|
|
30
|
+
keys: tuple = field(default=()) # every name the quarantine file may use for it
|
|
31
|
+
locale: Optional[str] = None # `--locale`: the language this copy of the check runs in
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def name(self):
|
|
35
|
+
return self.check.name if self.check is not None else Path(self.file).stem
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def tags(self):
|
|
39
|
+
return tuple(getattr(self.check, "tags", ()) or ())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def in_locales(entries, locales):
|
|
43
|
+
out = []
|
|
44
|
+
for entry in entries:
|
|
45
|
+
if entry.check is None:
|
|
46
|
+
out.append(entry)
|
|
47
|
+
continue
|
|
48
|
+
for locale in locales:
|
|
49
|
+
check = replace(entry.check, name=f"{entry.check.name} [{locale}]", l10n=True,
|
|
50
|
+
conditions={**(entry.check.conditions or {}), "locale": locale})
|
|
51
|
+
out.append(replace(entry, check=check, locale=locale, classname=f"{entry.classname}.{locale}"))
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def in_matrix(entries):
|
|
56
|
+
"""A check whose conditions list appearances, text sizes or locales becomes one entry per combination."""
|
|
57
|
+
from ..verify import conditions
|
|
58
|
+
out = []
|
|
59
|
+
for entry in entries:
|
|
60
|
+
if entry.check is None or not conditions.is_matrix(entry.check.conditions):
|
|
61
|
+
out.append(entry)
|
|
62
|
+
continue
|
|
63
|
+
for combo in conditions.combinations(entry.check.conditions):
|
|
64
|
+
tag = ", ".join(str(combo[key]) for key in conditions.MATRIX_KEYS
|
|
65
|
+
if key in combo and isinstance(entry.check.conditions[key], list))
|
|
66
|
+
slug = re.sub(r"[^A-Za-z0-9_-]+", "_", tag.replace(", ", "."))
|
|
67
|
+
out.append(replace(entry, check=replace(entry.check, name=f"{entry.check.name} [{tag}]",
|
|
68
|
+
conditions=combo),
|
|
69
|
+
classname=f"{entry.classname}.{slug}"))
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def with_names(entries, names):
|
|
74
|
+
"""The entries whose check name or file name (without its suffix) is one of ``names``; a check that can't be read
|
|
75
|
+
is kept, so its error still shows."""
|
|
76
|
+
if not names:
|
|
77
|
+
return list(entries)
|
|
78
|
+
wanted = {name.strip().casefold() for name in names}
|
|
79
|
+
return [entry for entry in entries if entry.check is None
|
|
80
|
+
or wanted & {entry.check.name.casefold(), Path(entry.file).stem.casefold(),
|
|
81
|
+
Path(entry.file).with_suffix("").as_posix().casefold()}]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _relative(path, root):
|
|
85
|
+
try:
|
|
86
|
+
return path.relative_to(root).as_posix()
|
|
87
|
+
except ValueError:
|
|
88
|
+
return path.as_posix()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _classname(path, root):
|
|
92
|
+
"""``checks.<file>``: the file's path without its suffix, from the checks folder when it is in one."""
|
|
93
|
+
checks = root / ".mobster" / "checks"
|
|
94
|
+
try:
|
|
95
|
+
stem = path.relative_to(checks)
|
|
96
|
+
except ValueError:
|
|
97
|
+
try:
|
|
98
|
+
stem = path.relative_to(root)
|
|
99
|
+
except ValueError:
|
|
100
|
+
stem = Path(path.name)
|
|
101
|
+
parts = [re.sub(r"[^A-Za-z0-9_-]+", "_", part) for part in stem.with_suffix("").parts]
|
|
102
|
+
return "checks." + ".".join(part for part in parts if part)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def check_files(folder):
|
|
106
|
+
out = []
|
|
107
|
+
for path in sorted(folder.rglob("*")):
|
|
108
|
+
relative = path.relative_to(folder)
|
|
109
|
+
if any(part.startswith(".") for part in relative.parts):
|
|
110
|
+
continue
|
|
111
|
+
if not path.is_file() or path.suffix.lower() not in CHECK_SUFFIXES:
|
|
112
|
+
continue
|
|
113
|
+
if path.name in NOT_CHECKS or path.name.endswith((".heal.yaml", ".heal.yml")):
|
|
114
|
+
continue
|
|
115
|
+
if any(parent == ".mobster" and child in OUTPUTS for parent, child in zip(path.parts, path.parts[1:])):
|
|
116
|
+
continue
|
|
117
|
+
out.append(path)
|
|
118
|
+
return out
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def discover(paths, root, *, cwd=None, loader=None):
|
|
122
|
+
"""Every check under ``paths`` (files or folders), else ``root``/.mobster/checks, as Entries sorted by file."""
|
|
123
|
+
if loader is None:
|
|
124
|
+
from ..verify.checks import load_check as loader
|
|
125
|
+
from ..verify.checks import CheckError
|
|
126
|
+
root = Path(root).resolve()
|
|
127
|
+
cwd = Path(cwd or Path.cwd())
|
|
128
|
+
files = []
|
|
129
|
+
if not paths:
|
|
130
|
+
folder = root / ".mobster" / "checks"
|
|
131
|
+
if not folder.is_dir():
|
|
132
|
+
raise DiscoveryError(f"There's no .mobster/checks folder in {root}. Save a check there with "
|
|
133
|
+
"`mobster verify … --save NAME`, or pass a check file or folder.")
|
|
134
|
+
files = check_files(folder)
|
|
135
|
+
if not files:
|
|
136
|
+
raise DiscoveryError(f"{_relative(folder, cwd.resolve())} has no checks (.yaml, .yml or .json files).")
|
|
137
|
+
for given in paths or ():
|
|
138
|
+
path = Path(given).expanduser()
|
|
139
|
+
path = (path if path.is_absolute() else cwd / path).resolve()
|
|
140
|
+
if not path.exists():
|
|
141
|
+
raise DiscoveryError(f"{given} doesn't exist.")
|
|
142
|
+
if path.is_dir():
|
|
143
|
+
found = check_files(path)
|
|
144
|
+
if not found:
|
|
145
|
+
raise DiscoveryError(f"{given} has no checks (.yaml, .yml or .json files).")
|
|
146
|
+
files.extend(found)
|
|
147
|
+
elif path.suffix.lower() not in CHECK_SUFFIXES:
|
|
148
|
+
raise DiscoveryError(f"{given} isn't a check file: checks are .yaml, .yml or .json.")
|
|
149
|
+
else:
|
|
150
|
+
files.append(path)
|
|
151
|
+
unique = list(dict.fromkeys(path.resolve() for path in files))
|
|
152
|
+
if len(unique) > MAX_CHECKS:
|
|
153
|
+
raise DiscoveryError(f"That's {len(unique)} checks; a run takes at most {MAX_CHECKS}. Pass fewer paths, "
|
|
154
|
+
"or split them with --shard.")
|
|
155
|
+
entries = []
|
|
156
|
+
for path in unique:
|
|
157
|
+
file = _relative(path, root)
|
|
158
|
+
try:
|
|
159
|
+
# A file that can't be read is kept with its error, so the suite reports it as couldn't run, not skipped.
|
|
160
|
+
check, error = loader(path), None
|
|
161
|
+
except CheckError as problem:
|
|
162
|
+
check, error = None, str(problem)
|
|
163
|
+
except (OSError, ValueError, RecursionError) as problem:
|
|
164
|
+
check, error = None, f"{file} can't be read ({type(problem).__name__})"
|
|
165
|
+
entry = Entry(path=path, file=file, check=check, error=error, classname=_classname(path, root))
|
|
166
|
+
checks = root / ".mobster" / "checks"
|
|
167
|
+
keys = {file, path.stem, Path(file).with_suffix("").as_posix()}
|
|
168
|
+
try:
|
|
169
|
+
keys.add(path.relative_to(checks).with_suffix("").as_posix())
|
|
170
|
+
except ValueError:
|
|
171
|
+
pass
|
|
172
|
+
if check is not None:
|
|
173
|
+
keys.add(check.name)
|
|
174
|
+
entry.keys = tuple(sorted(key.casefold() for key in keys))
|
|
175
|
+
entries.append(entry)
|
|
176
|
+
entries.sort(key=lambda entry: entry.file)
|
|
177
|
+
return entries
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def load_quarantine(path, *, required=False):
|
|
181
|
+
path = Path(path)
|
|
182
|
+
if not path.exists():
|
|
183
|
+
if required:
|
|
184
|
+
raise DiscoveryError(f"{path} doesn't exist.")
|
|
185
|
+
return {}
|
|
186
|
+
import yaml
|
|
187
|
+
try:
|
|
188
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
189
|
+
except (OSError, UnicodeDecodeError) as error:
|
|
190
|
+
raise DiscoveryError(f"{path} can't be read ({type(error).__name__}).") from None
|
|
191
|
+
except yaml.YAMLError as error:
|
|
192
|
+
mark = getattr(error, "problem_mark", None)
|
|
193
|
+
line = f" line {mark.line + 1}" if mark is not None else ""
|
|
194
|
+
raise DiscoveryError(f"{path}{line}: {getattr(error, 'problem', None) or 'not valid YAML'}") from None
|
|
195
|
+
if data is None:
|
|
196
|
+
return {}
|
|
197
|
+
if isinstance(data, dict):
|
|
198
|
+
unknown = [key for key in data if key != "quarantine"]
|
|
199
|
+
if unknown:
|
|
200
|
+
raise DiscoveryError(f"{path} has an unknown key {unknown[0]!r}; list the checks under quarantine:")
|
|
201
|
+
data = data.get("quarantine") or []
|
|
202
|
+
if not isinstance(data, list):
|
|
203
|
+
raise DiscoveryError(f"{path} must be a list of checks, such as - paywall")
|
|
204
|
+
if len(data) > QUARANTINE_LIMIT:
|
|
205
|
+
raise DiscoveryError(f"{path} lists more than {QUARANTINE_LIMIT} checks")
|
|
206
|
+
out = {}
|
|
207
|
+
for index, item in enumerate(data):
|
|
208
|
+
reason = ""
|
|
209
|
+
if isinstance(item, dict):
|
|
210
|
+
unknown = [key for key in item if key not in ("check", "reason")]
|
|
211
|
+
if unknown or not isinstance(item.get("check"), str):
|
|
212
|
+
raise DiscoveryError(f"{path} item {index + 1}: write {{check: NAME, reason: WHY}}")
|
|
213
|
+
reason = item.get("reason") or ""
|
|
214
|
+
if not isinstance(reason, str):
|
|
215
|
+
raise DiscoveryError(f"{path} item {index + 1}: reason must be text")
|
|
216
|
+
item = item["check"]
|
|
217
|
+
if not isinstance(item, str) or not item.strip():
|
|
218
|
+
raise DiscoveryError(f"{path} item {index + 1} must name a check: its file name, path or name")
|
|
219
|
+
out[item.strip().casefold()] = " ".join(reason.split())[:300]
|
|
220
|
+
return out
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def apply_quarantine(entries, listed):
|
|
224
|
+
"""Mark the entries ``listed`` names; returns the names that matched no check."""
|
|
225
|
+
used = set()
|
|
226
|
+
for entry in entries:
|
|
227
|
+
for key in entry.keys:
|
|
228
|
+
if key in listed:
|
|
229
|
+
entry.quarantined = listed[key]
|
|
230
|
+
used.add(key)
|
|
231
|
+
break
|
|
232
|
+
return [key for key in listed if key not in used]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def with_tags(entries, tags):
|
|
236
|
+
"""The entries carrying any of ``tags``; a check that can't be read is kept, so its error still shows."""
|
|
237
|
+
if not tags:
|
|
238
|
+
return list(entries)
|
|
239
|
+
wanted = {tag.strip().casefold() for tag in tags}
|
|
240
|
+
return [entry for entry in entries if entry.check is None or wanted & {tag.casefold() for tag in entry.tags}]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def parse_shard(text):
|
|
244
|
+
"""``i/n`` as (i, n), 1 <= i <= n <= 100. Raises DiscoveryError."""
|
|
245
|
+
match = SHARD.fullmatch(str(text or "").strip())
|
|
246
|
+
if not match:
|
|
247
|
+
raise DiscoveryError(f"--shard {text!r}: write I/N, such as 2/4 for the second of four shards")
|
|
248
|
+
index, count = int(match[1]), int(match[2])
|
|
249
|
+
if not 1 <= index <= count <= 100:
|
|
250
|
+
raise DiscoveryError(f"--shard {text!r}: I is from 1 to N, and N at most 100")
|
|
251
|
+
return index, count
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def shard(entries, index, count):
|
|
255
|
+
# Every count-th check in file order: the shards split evenly and every machine running the same files agrees.
|
|
256
|
+
return [entry for position, entry in enumerate(sorted(entries, key=lambda e: e.file))
|
|
257
|
+
if position % count == index - 1]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""The editor extension's routes on `mobster serve`: a project's checks, a suite started from the editor, and heals.
|
|
2
|
+
|
|
3
|
+
A suite started here runs on simulators only, never on a real iPhone: that stays `mobster test --device` and
|
|
4
|
+
run_tests with a device the user allowed. One suite runs at a time per service.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .jobs import PATTERN_SUITE, Busy, PrJob, SuiteJob, Suites
|
|
10
|
+
|
|
11
|
+
SUITE = r"(?P<suite>" + PATTERN_SUITE.strip("^$") + ")"
|
|
12
|
+
MAX_COVERING_BYTES = 500_000
|
|
13
|
+
NO_CHECKS = ("This project has no saved checks yet. Ask your agent to verify a screen with Mobster and save it "
|
|
14
|
+
"with save_check, or run `mobster verify … --save NAME`.")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_routes(add_route, Route):
|
|
18
|
+
add_route(Route("testkit", "GET", "/api/tests/project", project, body="none"))
|
|
19
|
+
add_route(Route("testkit", "POST", "/api/tests/suites", start, max_bytes=16384))
|
|
20
|
+
add_route(Route("testkit", "GET", f"/api/tests/suites/{SUITE}", suite, body="none"))
|
|
21
|
+
add_route(Route("testkit", "POST", f"/api/tests/suites/{SUITE}/stop", stop, max_bytes=1024))
|
|
22
|
+
add_route(Route("testkit", "POST", "/api/tests/heals", heals, max_bytes=16384))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def start_service(runtime):
|
|
26
|
+
runtime.editor_suites = Suites()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def close_service(runtime):
|
|
30
|
+
found = getattr(runtime, "editor_suites", None)
|
|
31
|
+
if found is not None:
|
|
32
|
+
found.close()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ------------------------------------------------------------------------------------------------- the routes
|
|
36
|
+
|
|
37
|
+
def project(request):
|
|
38
|
+
"""The project's checks with each one's last verdict, the heals waiting, the suite going now, and with ``file``
|
|
39
|
+
(a source file) the checks that cover its screen."""
|
|
40
|
+
from ..api_routes import Response
|
|
41
|
+
from .discover import DiscoveryError, discover
|
|
42
|
+
root, bad = _root((request.query.get("root") or [None])[0])
|
|
43
|
+
if bad is not None:
|
|
44
|
+
return bad
|
|
45
|
+
out = {"root": str(root), "checks": [], "heals": [], "problem": None, "covering": None,
|
|
46
|
+
"suite": _latest(request.runtime, root)}
|
|
47
|
+
if not (root / ".mobster" / "checks").is_dir():
|
|
48
|
+
return Response.json({**out, "problem": NO_CHECKS})
|
|
49
|
+
try:
|
|
50
|
+
entries = discover([], root)
|
|
51
|
+
except DiscoveryError as error:
|
|
52
|
+
return Response.json({**out, "problem": str(error)})
|
|
53
|
+
last = _last_verdicts(root)
|
|
54
|
+
out["checks"] = [_entry_row(entry, last.get(entry.file)) for entry in entries]
|
|
55
|
+
out["heals"], out["problem"] = _waiting(root)
|
|
56
|
+
wanted = (request.query.get("file") or [None])[0]
|
|
57
|
+
if wanted is not None:
|
|
58
|
+
source, bad = _inside(root, wanted, "file")
|
|
59
|
+
if bad is not None:
|
|
60
|
+
return bad
|
|
61
|
+
out["covering"] = covering(root, source, entries)
|
|
62
|
+
return Response.json(out)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def start(request):
|
|
66
|
+
"""Start a suite: the checks named (absolute paths in the project), every check, or with ``changed`` the checks
|
|
67
|
+
that cover what changed since the last commit, after building the app."""
|
|
68
|
+
from ..api_routes import Response
|
|
69
|
+
from .api import Options
|
|
70
|
+
from .prrun import PrOptions
|
|
71
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
72
|
+
if set(body) - {"root", "checks", "changed", "sim", "heal"}:
|
|
73
|
+
return _bad("Send root and, if you like, checks, changed, sim and heal.")
|
|
74
|
+
root, bad = _root(body.get("root"))
|
|
75
|
+
if bad is not None:
|
|
76
|
+
return bad
|
|
77
|
+
checks, changed, sim, heal = body.get("checks"), body.get("changed", False), body.get("sim"), body.get("heal", True)
|
|
78
|
+
if type(changed) is not bool or type(heal) is not bool:
|
|
79
|
+
return _bad("changed and heal are true or false.")
|
|
80
|
+
if sim is not None and not (isinstance(sim, str) and 0 < len(sim) <= 128):
|
|
81
|
+
return _bad("sim is a kind of simulator, such as iPhone 17 Pro.")
|
|
82
|
+
if checks is not None:
|
|
83
|
+
if changed:
|
|
84
|
+
return _bad("Send checks or changed, not both: changed picks the checks itself.")
|
|
85
|
+
if not isinstance(checks, list) or not 0 < len(checks) <= 50:
|
|
86
|
+
return _bad("checks is a list of 1 to 50 check files.")
|
|
87
|
+
paths = []
|
|
88
|
+
for item in checks:
|
|
89
|
+
path, bad = _inside(root, item, "checks")
|
|
90
|
+
if bad is not None:
|
|
91
|
+
return bad
|
|
92
|
+
paths.append(str(path))
|
|
93
|
+
gate = getattr(request.runtime, "plan_gate", None)
|
|
94
|
+
refusal = gate.refusal() if gate is not None else None
|
|
95
|
+
if refusal is not None:
|
|
96
|
+
return Response.error(refusal, 403, "plan_required")
|
|
97
|
+
sims = (sim,) if sim else ()
|
|
98
|
+
if changed:
|
|
99
|
+
def make():
|
|
100
|
+
return PrJob(PrOptions(base="HEAD", drafts="off", github="off", gifs=False),
|
|
101
|
+
Options(sims=sims, no_heal=not heal, command="editor: test on save"), root)
|
|
102
|
+
else:
|
|
103
|
+
def make():
|
|
104
|
+
return SuiteJob(Options(paths=tuple(paths) if checks else (str(root / ".mobster" / "checks"),),
|
|
105
|
+
sims=sims, no_heal=not heal, command="editor"), root=root)
|
|
106
|
+
try:
|
|
107
|
+
job = request.runtime.editor_suites.start(make)
|
|
108
|
+
except Busy as busy:
|
|
109
|
+
return Response.error(f"A suite is still running ({busy.job.public()['message']}). Wait for it, or stop it.",
|
|
110
|
+
409, "suite_running", suite=busy.job.public())
|
|
111
|
+
return Response.json({"suite": job.public()}, 202)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def suite(request):
|
|
115
|
+
from ..api_routes import Response
|
|
116
|
+
job = request.runtime.editor_suites.get(request.params["suite"])
|
|
117
|
+
if job is None:
|
|
118
|
+
return Response.error("Mobster has no suite with that id. It keeps the last ten.", 404, "suite_not_found")
|
|
119
|
+
return Response.json({"suite": job.public()})
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def stop(request):
|
|
123
|
+
"""Stop the suite: the check going now ends, and the rest don't start."""
|
|
124
|
+
from ..api_routes import Response
|
|
125
|
+
job = request.runtime.editor_suites.get(request.params["suite"])
|
|
126
|
+
if job is None:
|
|
127
|
+
return Response.error("Mobster has no suite with that id. It keeps the last ten.", 404, "suite_not_found")
|
|
128
|
+
job.stop.set()
|
|
129
|
+
return Response.json({"suite": job.public()})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def heals(request):
|
|
133
|
+
"""Accept or reject the heals of the checks named, or of every check with heals waiting."""
|
|
134
|
+
from ..api_routes import Response
|
|
135
|
+
from ..replay_core.heal import HealError
|
|
136
|
+
from . import heal_review
|
|
137
|
+
from .discover import DiscoveryError
|
|
138
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
139
|
+
if "project" in body: # the Mac app's project page
|
|
140
|
+
return heal_review.settled(request)
|
|
141
|
+
if set(body) - {"root", "action", "checks"} or body.get("action") not in ("accept", "reject"):
|
|
142
|
+
return _bad("Send root, action (accept or reject) and, if you like, checks.")
|
|
143
|
+
root, bad = _root(body.get("root"))
|
|
144
|
+
if bad is not None:
|
|
145
|
+
return bad
|
|
146
|
+
paths = []
|
|
147
|
+
for item in body.get("checks") or ():
|
|
148
|
+
path, bad = _inside(root, item, "checks")
|
|
149
|
+
if bad is not None:
|
|
150
|
+
return bad
|
|
151
|
+
paths.append(str(path))
|
|
152
|
+
if not (root / ".mobster" / "checks").is_dir():
|
|
153
|
+
return Response.error(NO_CHECKS, 404, "no_checks")
|
|
154
|
+
try:
|
|
155
|
+
settled = [{"check": str(entry.path), "steps": count}
|
|
156
|
+
for entry, count in heal_review.settle(root, heal_review.pending(root, paths), body["action"])]
|
|
157
|
+
except (DiscoveryError, HealError) as error:
|
|
158
|
+
return Response.error(str(error), 409, "heals_unreadable")
|
|
159
|
+
return Response.json({"action": body["action"], "settled": settled, "heals": _waiting(root)[0]})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ------------------------------------------------------------------------------------------------ the pieces
|
|
163
|
+
|
|
164
|
+
def covering(root, source, entries):
|
|
165
|
+
"""The checks that cover the screen in ``source``, as `mobster test pr` would pick them if the whole file had
|
|
166
|
+
changed: {screen, checks: [{path, reasons}]}; None for a file that shows no screen."""
|
|
167
|
+
from . import pr
|
|
168
|
+
if source.suffix.lower() not in pr.UI_SUFFIXES + pr.STRING_SUFFIXES:
|
|
169
|
+
return None
|
|
170
|
+
try:
|
|
171
|
+
lines = source.read_text(encoding="utf-8", errors="replace")[:MAX_COVERING_BYTES].splitlines()
|
|
172
|
+
except OSError:
|
|
173
|
+
return None
|
|
174
|
+
relative = source.relative_to(root).as_posix()
|
|
175
|
+
changes = pr.Changes(base="", merge_base="", head="", top=root,
|
|
176
|
+
files=[pr.Change(path=relative, status="M", added=lines)])
|
|
177
|
+
screens = pr.screens_in(changes, root)
|
|
178
|
+
if not screens:
|
|
179
|
+
return None
|
|
180
|
+
picked = pr.pick(entries, screens)
|
|
181
|
+
return {"screen": screens[0].name,
|
|
182
|
+
"checks": [{"path": str(item.entry.path), "name": item.entry.name, "reasons": item.reasons[:4]}
|
|
183
|
+
for item in picked]}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _root(value):
|
|
187
|
+
"""The project folder ``value`` names, or (None, Response 400)."""
|
|
188
|
+
if not isinstance(value, str) or not value or len(value) > 4096 or not Path(value).is_absolute():
|
|
189
|
+
return None, _bad("root is the project's absolute path.")
|
|
190
|
+
root = Path(value).resolve()
|
|
191
|
+
if not root.is_dir():
|
|
192
|
+
return None, _bad(f"{root} isn't a folder.")
|
|
193
|
+
return root, None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _inside(root, value, field):
|
|
197
|
+
"""``value`` as an absolute path inside ``root``, or (None, Response 400)."""
|
|
198
|
+
if not isinstance(value, str) or not value or len(value) > 4096 or not Path(value).is_absolute():
|
|
199
|
+
return None, _bad(f"{field} takes absolute paths.")
|
|
200
|
+
path = Path(value).resolve()
|
|
201
|
+
if not path.is_relative_to(root) or not path.is_file():
|
|
202
|
+
return None, _bad(f"{value} isn't a file in {root}.")
|
|
203
|
+
return path, None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _bad(message):
|
|
207
|
+
from ..api_routes import Response
|
|
208
|
+
return Response.error(message, 400, "bad_request")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _last_verdicts(root):
|
|
212
|
+
"""{check file: its last verdict}, the device that didn't pass first when it ran on several."""
|
|
213
|
+
from .history import report
|
|
214
|
+
last = {}
|
|
215
|
+
for row in report(root)["checks"]:
|
|
216
|
+
known = last.get(row["file"])
|
|
217
|
+
if known is None or (known["status"] == "passed" and row["lastStatus"] != "passed"):
|
|
218
|
+
last[row["file"]] = {"status": row["lastStatus"], "state": row["state"], "device": row["device"],
|
|
219
|
+
"runs": row["runs"]}
|
|
220
|
+
return last
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _entry_row(entry, last):
|
|
224
|
+
check = entry.check
|
|
225
|
+
return {"path": str(entry.path), "file": entry.file, "name": entry.name, "error": entry.error,
|
|
226
|
+
"app": getattr(check, "bundle_id", None), "tags": list(entry.tags),
|
|
227
|
+
"steps": len(check.steps) if check is not None else 0, "last": last}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _waiting(root):
|
|
231
|
+
"""(the heals waiting, None) or ([], why they can't be read)."""
|
|
232
|
+
from ..replay_core.heal import HealError
|
|
233
|
+
from . import heal_review
|
|
234
|
+
from .discover import DiscoveryError
|
|
235
|
+
try:
|
|
236
|
+
return heal_review.rows(heal_review.pending(root)), None
|
|
237
|
+
except (DiscoveryError, HealError) as error:
|
|
238
|
+
return [], str(error)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _latest(runtime, root):
|
|
242
|
+
job = runtime.editor_suites.latest()
|
|
243
|
+
return job.public() if job is not None and job.root is not None and Path(job.root) == root else None
|