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,440 @@
|
|
|
1
|
+
"""Ordered local simulator checks with an exclusive lease for every actor across all phases."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..devkit import links
|
|
12
|
+
from ..verify.checks import project_root
|
|
13
|
+
from ..verify.fixtures import FixtureError, with_fixtures
|
|
14
|
+
from . import a11y_baseline
|
|
15
|
+
from .api import UsageError, prepare_entries
|
|
16
|
+
from .discover import DiscoveryError, discover
|
|
17
|
+
from .input_files import inside, read_bytes
|
|
18
|
+
from .provenance import case_provenance, require_case_provenance
|
|
19
|
+
from .suite import machine_parallel_cap
|
|
20
|
+
from .targets import Refused, TargetError, resolve, validate_record
|
|
21
|
+
|
|
22
|
+
MAX_MANIFEST_BYTES = 64_000
|
|
23
|
+
MAX_ACTORS = 4
|
|
24
|
+
MAX_PHASES = 12
|
|
25
|
+
_ROLE = re.compile(r"[a-z][a-z0-9_-]{0,39}\Z")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _manifest_bytes(path):
|
|
29
|
+
content = read_bytes(path, MAX_MANIFEST_BYTES)
|
|
30
|
+
return content, hashlib.sha256(content).hexdigest()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _manifest_digest(path):
|
|
34
|
+
return _manifest_bytes(path)[1]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class Member:
|
|
39
|
+
actor: str
|
|
40
|
+
entry: object
|
|
41
|
+
target: object
|
|
42
|
+
provenance: dict
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class Phase:
|
|
47
|
+
name: str
|
|
48
|
+
members: tuple[Member, ...]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class ScenarioPlan:
|
|
53
|
+
root: Path
|
|
54
|
+
path: Path
|
|
55
|
+
name: str
|
|
56
|
+
actors: dict
|
|
57
|
+
phases: tuple[Phase, ...]
|
|
58
|
+
manifest_digest: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def plan_scenario(path, *, cwd=None, named_device=None):
|
|
62
|
+
"""Admit one bounded scenario of saved checks and distinct named Mobster simulators."""
|
|
63
|
+
import yaml
|
|
64
|
+
|
|
65
|
+
class UniqueLoader(yaml.SafeLoader):
|
|
66
|
+
def construct_mapping(self, node, deep=False):
|
|
67
|
+
pairs = self.construct_pairs(node, deep=True)
|
|
68
|
+
result = {}
|
|
69
|
+
for key, value in pairs:
|
|
70
|
+
if not isinstance(key, str) or key in result:
|
|
71
|
+
raise UsageError("Scenario keys must be unique text.")
|
|
72
|
+
result[key] = value
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
cwd = Path(cwd or Path.cwd())
|
|
76
|
+
path = (cwd / path).absolute() if not Path(path).is_absolute() else Path(path).absolute()
|
|
77
|
+
root = project_root(path)
|
|
78
|
+
try:
|
|
79
|
+
path = inside(root, path, prefix=(".mobster", "scenarios"))
|
|
80
|
+
if path.suffix.lower() not in (".yaml", ".yml") or not path.is_file() \
|
|
81
|
+
or path.stat().st_size > MAX_MANIFEST_BYTES:
|
|
82
|
+
raise UsageError("Save a scenario YAML file under .mobster/scenarios/ (at most 64 KB).")
|
|
83
|
+
content, manifest_digest = _manifest_bytes(path)
|
|
84
|
+
data = yaml.load(content.decode("utf-8"), Loader=UniqueLoader)
|
|
85
|
+
if not isinstance(data, dict) or set(data) != {"version", "name", "actors", "phases"} \
|
|
86
|
+
or type(data["version"]) is not int or data["version"] != 1:
|
|
87
|
+
raise UsageError("A scenario needs version: 1, name, actors and phases.")
|
|
88
|
+
name = data["name"]
|
|
89
|
+
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
|
|
90
|
+
raise UsageError("A scenario name must be 1–80 characters.")
|
|
91
|
+
declared = data["actors"]
|
|
92
|
+
if not isinstance(declared, dict) or not 1 <= len(declared) <= MAX_ACTORS:
|
|
93
|
+
raise UsageError(f"A scenario needs 1–{MAX_ACTORS} named simulator actors.")
|
|
94
|
+
actors = {}
|
|
95
|
+
udids = set()
|
|
96
|
+
for role, sim_id in declared.items():
|
|
97
|
+
if not isinstance(role, str) or not _ROLE.fullmatch(role) or not isinstance(sim_id, str) \
|
|
98
|
+
or not 1 <= len(sim_id) <= 128:
|
|
99
|
+
raise UsageError("Each actor needs a lowercase role and an exact named simulator ID.")
|
|
100
|
+
target = resolve(devices=(sim_id,), root=root, named_device=named_device)[0]
|
|
101
|
+
record = target.record
|
|
102
|
+
if record is None or record.get("kind") != "simulator" or record.get("id") != sim_id:
|
|
103
|
+
raise UsageError(f"Actor {role!r} must name the exact ID of a managed simulator.")
|
|
104
|
+
validate_record(record)
|
|
105
|
+
if record["udid"].casefold() in udids:
|
|
106
|
+
raise UsageError("Two scenario actors resolve to the same simulator. Give each a distinct one.")
|
|
107
|
+
udids.add(record["udid"].casefold())
|
|
108
|
+
actors[role] = replace(target, record=dict(record))
|
|
109
|
+
cap = machine_parallel_cap()
|
|
110
|
+
if len(actors) > cap:
|
|
111
|
+
raise UsageError(f"This Mac runs at most {cap} simulators at once; the scenario names {len(actors)}. "
|
|
112
|
+
"Use fewer actors or run the scenario on a Mac with enough free capacity.")
|
|
113
|
+
declared_phases = data["phases"]
|
|
114
|
+
if not isinstance(declared_phases, list) or not 1 <= len(declared_phases) <= MAX_PHASES:
|
|
115
|
+
raise UsageError(f"A scenario needs 1–{MAX_PHASES} ordered phases.")
|
|
116
|
+
baseline = a11y_baseline.load(root)
|
|
117
|
+
phases = []
|
|
118
|
+
seen_actors = set()
|
|
119
|
+
for index, item in enumerate(declared_phases, 1):
|
|
120
|
+
if not isinstance(item, dict) or set(item) != {"name", "runs"} or \
|
|
121
|
+
not isinstance(item["name"], str) or not 1 <= len(item["name"].strip()) <= 80 or \
|
|
122
|
+
not isinstance(item["runs"], list) or not 1 <= len(item["runs"]) <= len(actors):
|
|
123
|
+
raise UsageError(f"Phase {index} needs a name and 1–{len(actors)} runs.")
|
|
124
|
+
members = []
|
|
125
|
+
phase_actors = set()
|
|
126
|
+
for run in item["runs"]:
|
|
127
|
+
if not isinstance(run, dict) or set(run) != {"actor", "check"} or \
|
|
128
|
+
not isinstance(run["actor"], str) or run["actor"] not in actors or \
|
|
129
|
+
run["actor"] in phase_actors or \
|
|
130
|
+
not isinstance(run["check"], str):
|
|
131
|
+
raise UsageError(f"Phase {index} needs one saved check per known actor.")
|
|
132
|
+
actor = run["actor"]
|
|
133
|
+
phase_actors.add(actor)
|
|
134
|
+
check_path = inside(root, run["check"], prefix=(".mobster", "checks"))
|
|
135
|
+
if not check_path.is_file() or check_path.suffix.lower() not in (".yaml", ".yml", ".json"):
|
|
136
|
+
raise UsageError(f"Phase {index} must name one saved check file under .mobster/checks/.")
|
|
137
|
+
entries = discover((str(check_path),), root, cwd=root)
|
|
138
|
+
if len(entries) != 1:
|
|
139
|
+
raise UsageError(f"Phase {index} must name exactly one saved check.")
|
|
140
|
+
prepare_entries(entries, root, baseline=baseline)
|
|
141
|
+
entry = entries[0]
|
|
142
|
+
if entry.check is None:
|
|
143
|
+
raise UsageError(f"{entry.file} cannot run: {entry.error}")
|
|
144
|
+
effective = with_fixtures(entry.check, root)
|
|
145
|
+
if actor in seen_actors and entry.check.reset != "none":
|
|
146
|
+
raise UsageError(f"{entry.file} follows an earlier {actor} phase; set reset: none explicitly.")
|
|
147
|
+
if entry.check.business is not None and actor in seen_actors:
|
|
148
|
+
raise UsageError(f"{entry.file} uses an app-state fixture after an earlier {actor} phase. "
|
|
149
|
+
"Put that fixture in the actor's first check; continuation cannot reseed it.")
|
|
150
|
+
if actor in seen_actors and (effective.seed or effective.conditions):
|
|
151
|
+
raise UsageError(f"{entry.file} would seed files, keychain, pasteboard or conditions after "
|
|
152
|
+
f"{actor} began. Remove destructive setup from continuation checks.")
|
|
153
|
+
if entry.check.business is not None and entry.check.reset_level() == "none":
|
|
154
|
+
raise UsageError(f"{entry.file} needs a data or reinstall reset before its app-state fixture.")
|
|
155
|
+
frozen = case_provenance(entry, root)
|
|
156
|
+
if frozen["sourceDigest"] is None or frozen["checkSpecDigest"] is None or \
|
|
157
|
+
(entry.check.app_path and frozen["appDigest"] is None) \
|
|
158
|
+
or frozen["baselineDigest"] is None or frozen["fixtureDigest"] is None:
|
|
159
|
+
raise UsageError(f"{entry.file} or its declared app, baseline or fixture is unreadable. Fix it "
|
|
160
|
+
"before running the scenario.")
|
|
161
|
+
members.append(Member(actor, entry, actors[actor], frozen))
|
|
162
|
+
seen_actors.update(phase_actors)
|
|
163
|
+
phases.append(Phase(item["name"].strip(), tuple(members)))
|
|
164
|
+
return ScenarioPlan(root, path, name.strip(), actors, tuple(phases), manifest_digest)
|
|
165
|
+
except UsageError:
|
|
166
|
+
raise
|
|
167
|
+
except (OSError, ValueError, DiscoveryError, TargetError, Refused, FixtureError,
|
|
168
|
+
a11y_baseline.BaselineError, links.LinksError,
|
|
169
|
+
yaml.YAMLError) as error:
|
|
170
|
+
raise UsageError(f"The scenario cannot be admitted: {error}") from None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _row(member, phase, number, result):
|
|
174
|
+
"""Give a saved check separate phase evidence each time it appears."""
|
|
175
|
+
entry = member.entry
|
|
176
|
+
row = dict(result)
|
|
177
|
+
row["name"] = f"{phase.name} · {member.actor} · {entry.name}"
|
|
178
|
+
row["classname"] = f"scenario.{number}.{member.actor}"
|
|
179
|
+
row["scenario"] = {"phase": number, "phaseName": phase.name, "actor": member.actor}
|
|
180
|
+
row["checkSpecDigest"] = member.provenance["checkSpecDigest"]
|
|
181
|
+
row["baselineDigest"] = member.provenance["baselineDigest"]
|
|
182
|
+
row["fixtureDigest"] = member.provenance["fixtureDigest"]
|
|
183
|
+
row["visualBaselineSha256"] = member.provenance["visualBaselineSha256"]
|
|
184
|
+
row["businessBindingSha256"] = member.provenance["businessBindingSha256"]
|
|
185
|
+
row["quarantined"] = False # a phase prerequisite cannot be waived by quarantine
|
|
186
|
+
for cell in row["results"]:
|
|
187
|
+
cell["appDigest"] = member.provenance["appDigest"]
|
|
188
|
+
return row
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _blocked(member, phase, number, klass, message):
|
|
192
|
+
target = member.target
|
|
193
|
+
row = {"name": member.entry.name, "file": member.entry.file, "results": [{
|
|
194
|
+
"device": target.label, "deviceKey": target.key, "status": "couldnt_run", "attempts": 0,
|
|
195
|
+
"durationMs": 0, "flaky": False, "reason": {"class": klass, "message": message},
|
|
196
|
+
"quarantined": False, "runs": [], "environment": {"kind": "simulator", "model": None,
|
|
197
|
+
"runtime": None}}]}
|
|
198
|
+
row = _row(member, phase, number, row)
|
|
199
|
+
row["blocked"] = True
|
|
200
|
+
return row
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def execute_scenario(planned, *, progress=None, stop=None, key=None, attempts=None, suite_id=None,
|
|
204
|
+
on_suite=None):
|
|
205
|
+
"""Reserve every actor, run ordered phase barriers, and write one terminal suite artifact."""
|
|
206
|
+
from .. import __version__
|
|
207
|
+
from ..verify.actions import ActionJournal
|
|
208
|
+
from ..verify.runs import ensure_gitignore
|
|
209
|
+
from .execute import Attempts
|
|
210
|
+
from .reports import document, make_suite_dir, write_all
|
|
211
|
+
from .suite import SAFETY_EXIT_STOPS, STOP_RANK, Suite
|
|
212
|
+
|
|
213
|
+
progress = progress or (lambda line: None)
|
|
214
|
+
stop = stop or getattr(attempts, "stop", None) or threading.Event()
|
|
215
|
+
root = planned.root
|
|
216
|
+
ensure_gitignore(root / ".mobster" / "runs")
|
|
217
|
+
suite_id, folder = make_suite_dir(root / ".mobster" / "test-results", suite_id)
|
|
218
|
+
attempts = attempts or Attempts(runs_dir=root / ".mobster" / "runs", key=key,
|
|
219
|
+
replay="strict", parallel=len(planned.actors), progress=progress, stop=stop)
|
|
220
|
+
if not isinstance(attempts, Attempts) or attempts.replay != "strict" or attempts.keyless \
|
|
221
|
+
or attempts.stop is not stop:
|
|
222
|
+
raise UsageError("Scenario attempts must own leased simulator reservations.")
|
|
223
|
+
if len(planned.actors) > machine_parallel_cap():
|
|
224
|
+
raise UsageError("This Mac can no longer reserve every scenario actor. Use fewer actors.")
|
|
225
|
+
journal_dir = folder / "reservation"
|
|
226
|
+
journal_dir.mkdir(mode=0o700)
|
|
227
|
+
journal = ActionJournal(journal_dir)
|
|
228
|
+
rows = []
|
|
229
|
+
devices = {target.key: {**target.public(), "key": target.key, "name": target.label}
|
|
230
|
+
for target in planned.actors.values()}
|
|
231
|
+
started = time.time()
|
|
232
|
+
stop_reason = None
|
|
233
|
+
block = None
|
|
234
|
+
phase_started = False
|
|
235
|
+
|
|
236
|
+
def mark_stop(candidate):
|
|
237
|
+
nonlocal stop_reason
|
|
238
|
+
if candidate in STOP_RANK and (stop_reason is None or STOP_RANK[candidate] > STOP_RANK[stop_reason]):
|
|
239
|
+
stop_reason = candidate
|
|
240
|
+
|
|
241
|
+
def current_entries(members):
|
|
242
|
+
paths = list(dict.fromkeys(member.entry.path for member in members))
|
|
243
|
+
entries = discover(tuple(map(str, paths)), root, cwd=root)
|
|
244
|
+
prepare_entries(entries, root, baseline=a11y_baseline.load(root))
|
|
245
|
+
return {entry.path: entry for entry in entries}
|
|
246
|
+
|
|
247
|
+
def check_member(member, entries, asset_cache=None):
|
|
248
|
+
current = entries.get(member.entry.path)
|
|
249
|
+
if current is None or current.check != member.entry.check:
|
|
250
|
+
from .targets import Refused
|
|
251
|
+
raise Refused("A saved check's links or accessibility baseline changed. Start a new scenario.",
|
|
252
|
+
"refused")
|
|
253
|
+
require_case_provenance(current, root, member.provenance, asset_cache=asset_cache)
|
|
254
|
+
|
|
255
|
+
def validate(phase=None):
|
|
256
|
+
if _manifest_digest(planned.path) != planned.manifest_digest:
|
|
257
|
+
raise UsageError("The scenario file changed during the run. Start a new scenario.")
|
|
258
|
+
asset_cache = {}
|
|
259
|
+
members = [member for current in ((phase,) if phase is not None else planned.phases)
|
|
260
|
+
for member in current.members]
|
|
261
|
+
entries = current_entries(members)
|
|
262
|
+
for member in members:
|
|
263
|
+
check_member(member, entries, asset_cache)
|
|
264
|
+
|
|
265
|
+
def run_member(member):
|
|
266
|
+
def check_entry(entry, check, record):
|
|
267
|
+
if _manifest_digest(planned.path) != planned.manifest_digest:
|
|
268
|
+
from .targets import Refused
|
|
269
|
+
raise Refused("The scenario file changed during admission. Start a new scenario.", "refused")
|
|
270
|
+
check_member(member, current_entries((member,)))
|
|
271
|
+
suite = Suite([member.entry], [member.target], attempt=attempts, retries=0, repeat=1,
|
|
272
|
+
parallel=1, stop=stop, on_cancel=attempts.abort_all,
|
|
273
|
+
before_attempt=check_entry, progress=progress)
|
|
274
|
+
if on_suite is not None:
|
|
275
|
+
on_suite(suite)
|
|
276
|
+
suite.run()
|
|
277
|
+
suite.seal()
|
|
278
|
+
return suite.results()
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
validate()
|
|
282
|
+
records = [target.record for target in planned.actors.values()]
|
|
283
|
+
with attempts.reserve_simulators(records, journal):
|
|
284
|
+
for number, phase in enumerate(planned.phases, 1):
|
|
285
|
+
if stop.is_set():
|
|
286
|
+
if stop_reason is None:
|
|
287
|
+
stop_reason = "stopped"
|
|
288
|
+
block = ("stopped", "The scenario was stopped before this phase.")
|
|
289
|
+
if block is None:
|
|
290
|
+
validate(phase)
|
|
291
|
+
phase_started = True
|
|
292
|
+
pool = ThreadPoolExecutor(max_workers=len(phase.members))
|
|
293
|
+
futures, outcomes = [], []
|
|
294
|
+
interrupted = False
|
|
295
|
+
try:
|
|
296
|
+
for member in phase.members:
|
|
297
|
+
futures.append(pool.submit(run_member, member))
|
|
298
|
+
except (KeyboardInterrupt, Exception) as error:
|
|
299
|
+
# submit may have queued a member before returning its Future. No later phase can assume
|
|
300
|
+
# that untracked member was effect-free.
|
|
301
|
+
stop.set()
|
|
302
|
+
attempts.abort_all()
|
|
303
|
+
interrupted = isinstance(error, KeyboardInterrupt)
|
|
304
|
+
outcomes.extend([error] * (len(phase.members) - len(futures)))
|
|
305
|
+
try:
|
|
306
|
+
completed = []
|
|
307
|
+
for future in futures:
|
|
308
|
+
while True:
|
|
309
|
+
try:
|
|
310
|
+
completed.append(future.result())
|
|
311
|
+
break
|
|
312
|
+
except KeyboardInterrupt as error:
|
|
313
|
+
stop.set()
|
|
314
|
+
attempts.abort_all()
|
|
315
|
+
interrupted = True
|
|
316
|
+
try:
|
|
317
|
+
child_error = future.exception() if future.done() else None
|
|
318
|
+
except BaseException:
|
|
319
|
+
child_error = None
|
|
320
|
+
if isinstance(child_error, KeyboardInterrupt):
|
|
321
|
+
completed.append(error)
|
|
322
|
+
break
|
|
323
|
+
except Exception as error:
|
|
324
|
+
stop.set()
|
|
325
|
+
attempts.abort_all()
|
|
326
|
+
completed.append(error)
|
|
327
|
+
break
|
|
328
|
+
outcomes = completed + outcomes
|
|
329
|
+
finally:
|
|
330
|
+
# ThreadPoolExecutor.__exit__ can itself be interrupted. Keep all root actor leases until
|
|
331
|
+
# every member thread has closed, even after repeated ctrl+c.
|
|
332
|
+
while True:
|
|
333
|
+
try:
|
|
334
|
+
pool.shutdown(wait=True, cancel_futures=stop.is_set())
|
|
335
|
+
break
|
|
336
|
+
except KeyboardInterrupt:
|
|
337
|
+
stop.set()
|
|
338
|
+
attempts.abort_all()
|
|
339
|
+
interrupted = True
|
|
340
|
+
phase_started = False
|
|
341
|
+
for member, outcome in zip(phase.members, outcomes):
|
|
342
|
+
if isinstance(outcome, BaseException):
|
|
343
|
+
klass = getattr(outcome, "klass", None)
|
|
344
|
+
if klass not in SAFETY_EXIT_STOPS:
|
|
345
|
+
klass = "unknown_outcome"
|
|
346
|
+
mark_stop(klass)
|
|
347
|
+
rows.append(_blocked(member, phase, number, klass, str(outcome)[:300]))
|
|
348
|
+
continue
|
|
349
|
+
row = _row(member, phase, number, outcome["checks"][0])
|
|
350
|
+
rows.append(row)
|
|
351
|
+
for device in outcome["devices"]:
|
|
352
|
+
devices[device["key"]] = device
|
|
353
|
+
mark_stop(outcome.get("stopReason"))
|
|
354
|
+
if interrupted or stop.is_set():
|
|
355
|
+
mark_stop("stopped")
|
|
356
|
+
if any(row["results"][0]["status"] != "passed" for row in rows[-len(phase.members):]):
|
|
357
|
+
block = ("prerequisite", f"Phase {number} did not pass; later phases cannot run.")
|
|
358
|
+
if stop_reason in STOP_RANK:
|
|
359
|
+
block = (stop_reason, f"The scenario stopped after phase {number}.")
|
|
360
|
+
else:
|
|
361
|
+
rows.extend(_blocked(member, phase, number, *block) for member in phase.members)
|
|
362
|
+
except KeyboardInterrupt:
|
|
363
|
+
stop.set()
|
|
364
|
+
attempts.abort_all()
|
|
365
|
+
if journal.klass in SAFETY_EXIT_STOPS:
|
|
366
|
+
stop_reason = journal.klass
|
|
367
|
+
block = (journal.klass, "Simulator acquisition may have taken effect before interruption.")
|
|
368
|
+
elif stop_reason is None:
|
|
369
|
+
stop_reason = "stopped"
|
|
370
|
+
block = ("stopped", "The scenario was stopped with ctrl+c.")
|
|
371
|
+
except Exception as error:
|
|
372
|
+
klass = journal.klass or getattr(error, "klass", None) or getattr(error, "kind", None)
|
|
373
|
+
if klass not in ("journal", "unknown_outcome", "stopped", "busy"):
|
|
374
|
+
klass = "unknown_outcome" if phase_started or journal.blocked else "refused"
|
|
375
|
+
stop_reason = klass if klass in SAFETY_EXIT_STOPS or klass == "stopped" else "refused"
|
|
376
|
+
block = (klass, str(error)[:300])
|
|
377
|
+
finally:
|
|
378
|
+
journal.request_stop()
|
|
379
|
+
for number, phase in enumerate(planned.phases, 1):
|
|
380
|
+
recorded = {row["scenario"]["actor"] for row in rows if row["scenario"]["phase"] == number}
|
|
381
|
+
rows.extend(_blocked(member, phase, number, *(block or ("prerequisite", "An earlier phase did not pass.")))
|
|
382
|
+
for member in phase.members if member.actor not in recorded)
|
|
383
|
+
try:
|
|
384
|
+
manifest_drift = _manifest_digest(planned.path) != planned.manifest_digest
|
|
385
|
+
except (OSError, ValueError):
|
|
386
|
+
manifest_drift = True
|
|
387
|
+
if manifest_drift and stop_reason not in ("unknown_outcome", "journal"):
|
|
388
|
+
stop_reason = "refused"
|
|
389
|
+
members = {(number, member.actor): member for number, phase in enumerate(planned.phases, 1)
|
|
390
|
+
for member in phase.members}
|
|
391
|
+
final_cache = {}
|
|
392
|
+
try:
|
|
393
|
+
latest = current_entries(tuple(members.values()))
|
|
394
|
+
except (OSError, ValueError, DiscoveryError, a11y_baseline.BaselineError, links.LinksError):
|
|
395
|
+
latest = {}
|
|
396
|
+
provenance_drift = manifest_drift
|
|
397
|
+
for row in rows:
|
|
398
|
+
source = members[row["scenario"]["phase"], row["scenario"]["actor"]]
|
|
399
|
+
fresh = latest.get(source.entry.path)
|
|
400
|
+
if manifest_drift or fresh is None or fresh.check != source.entry.check or \
|
|
401
|
+
case_provenance(fresh, root, asset_cache=final_cache) != source.provenance:
|
|
402
|
+
provenance_drift = True
|
|
403
|
+
row["provenanceDrift"] = True
|
|
404
|
+
row.update({name: None for name in ("checkSpecDigest", "baselineDigest", "fixtureDigest",
|
|
405
|
+
"visualBaselineSha256", "businessBindingSha256")})
|
|
406
|
+
row["results"][0]["appDigest"] = None
|
|
407
|
+
if provenance_drift and stop_reason not in ("unknown_outcome", "journal"):
|
|
408
|
+
stop_reason = "refused"
|
|
409
|
+
cells = [row["results"][0] for row in rows]
|
|
410
|
+
summary = {"passed": sum(cell["status"] == "passed" for cell in cells),
|
|
411
|
+
"failed": sum(cell["status"] == "failed" for cell in cells),
|
|
412
|
+
"needsReview": sum(cell["status"] == "needs_review" for cell in cells),
|
|
413
|
+
"couldntRun": sum(cell["status"] == "couldnt_run" for cell in cells),
|
|
414
|
+
"flaky": 0, "quarantined": 0, "total": len(cells),
|
|
415
|
+
"costUsd": round(sum(cell.get("costUsd") or 0 for cell in cells), 5)}
|
|
416
|
+
exit_code = 130 if stop_reason == "stopped" else \
|
|
417
|
+
3 if stop_reason in SAFETY_EXIT_STOPS else 1 if summary["failed"] else \
|
|
418
|
+
3 if summary["couldntRun"] else 2 if summary["needsReview"] else 0
|
|
419
|
+
now = time.time()
|
|
420
|
+
results = {"checks": rows, "devices": list(devices.values()), "summary": summary,
|
|
421
|
+
"stopReason": stop_reason, "exitCode": exit_code,
|
|
422
|
+
"startedAt": round(started * 1000), "finishedAt": round(now * 1000),
|
|
423
|
+
"durationMs": round((now - started) * 1000)}
|
|
424
|
+
doc = document(results, suite_id=suite_id,
|
|
425
|
+
options={"scenario": True, "retries": 0, "repeat": 1, "parallel": len(planned.actors),
|
|
426
|
+
"replay": "strict", "strict": True}, version=__version__)
|
|
427
|
+
if stop_reason == "stopped":
|
|
428
|
+
doc["stopped"] = True
|
|
429
|
+
doc["executionPolicy"] = {"retries": 0, "repeat": 1, "replay": "strict", "keyless": False,
|
|
430
|
+
"assertTimeout": 5, "mobster": __version__}
|
|
431
|
+
doc["scenario"] = {"version": 1, "name": planned.name,
|
|
432
|
+
"file": planned.path.relative_to(root).as_posix(),
|
|
433
|
+
"manifestDigest": planned.manifest_digest,
|
|
434
|
+
"provenanceDrift": provenance_drift,
|
|
435
|
+
"actors": {role: target.record["id"] for role, target in planned.actors.items()},
|
|
436
|
+
"phases": [{"name": phase.name, "index": index} for index, phase in
|
|
437
|
+
enumerate(planned.phases, 1)]}
|
|
438
|
+
paths = write_all(doc, folder)
|
|
439
|
+
paths["folder"] = folder
|
|
440
|
+
return doc, paths
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Run one local, versioned multi-simulator scenario with strict saved-check replay."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .api import UsageError
|
|
7
|
+
from .scenario import execute_scenario, plan_scenario
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(args, operands, *, out=None, err=None, plan=plan_scenario, execute=execute_scenario):
|
|
11
|
+
from ..devtools import ProgressLines
|
|
12
|
+
from .cli import summary_text
|
|
13
|
+
|
|
14
|
+
out, err = out or sys.stdout, err or sys.stderr
|
|
15
|
+
if len(operands) != 1:
|
|
16
|
+
print("mobster test scenario: name one YAML file under .mobster/scenarios", file=err)
|
|
17
|
+
return 3
|
|
18
|
+
try:
|
|
19
|
+
planned = plan(operands[0])
|
|
20
|
+
except UsageError as error:
|
|
21
|
+
print(f"mobster test scenario: {error}", file=err)
|
|
22
|
+
return 3
|
|
23
|
+
progress = ProgressLines(err)
|
|
24
|
+
progress(f"mobster test scenario: {planned.name}; {len(planned.actors)} simulators, "
|
|
25
|
+
f"{len(planned.phases)} ordered phases; strict saved-check replay")
|
|
26
|
+
try:
|
|
27
|
+
doc, paths = execute(planned, progress=progress)
|
|
28
|
+
except UsageError as error:
|
|
29
|
+
print(f"mobster test scenario: {error}", file=err)
|
|
30
|
+
return 3
|
|
31
|
+
finally:
|
|
32
|
+
progress.end()
|
|
33
|
+
if getattr(args, "json", False):
|
|
34
|
+
print(json.dumps(doc, ensure_ascii=False, allow_nan=False), file=out)
|
|
35
|
+
else:
|
|
36
|
+
print(summary_text(doc, paths), file=out)
|
|
37
|
+
return int(doc.get("exitCode", 3))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""`mobster test snapshots`: the project's screenshot baselines (verify/snapshots.py) and whether a check still
|
|
2
|
+
makes each."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SNAPSHOTS = "snapshots" # the subcommand: mobster test snapshots
|
|
9
|
+
VIEWS = ("list", "stale", "orphaned")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_arguments(which):
|
|
13
|
+
"""The suite's snapshot flags, in ``mobster test``'s check-selection group."""
|
|
14
|
+
which.add_argument("--check", action="append", metavar="NAME", dest="names",
|
|
15
|
+
help="only the check with this name or file name; repeatable")
|
|
16
|
+
which.add_argument("--update-snapshots", action="store_true",
|
|
17
|
+
help="write each snapshot's baseline from this run, in .mobster/snapshots/ (with --check, "
|
|
18
|
+
"only those checks'); the run doesn't fail on a changed screen")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def options(args):
|
|
22
|
+
"""The suite's options (api.Options) from the flags add_arguments defines."""
|
|
23
|
+
return {"update_snapshots": bool(getattr(args, "update_snapshots", False)),
|
|
24
|
+
"names": tuple(getattr(args, "names", None) or ())}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def snapshots(args, rest, *, out=None, cwd=None):
|
|
28
|
+
"""`mobster test snapshots [list|stale|orphaned]`: the project's screenshot baselines and whether a check still
|
|
29
|
+
makes each. Runs nothing."""
|
|
30
|
+
from ..verify import snapshots as store
|
|
31
|
+
from ..verify.checks import project_root
|
|
32
|
+
from .cli import _usage
|
|
33
|
+
from .discover import DiscoveryError, discover
|
|
34
|
+
out = out or sys.stdout
|
|
35
|
+
view = rest[0] if rest else "list"
|
|
36
|
+
if view not in VIEWS or len(rest) > 1:
|
|
37
|
+
return _usage(f"snapshots takes one of {', '.join(VIEWS)}: mobster test snapshots stale")
|
|
38
|
+
cwd = Path(cwd or Path.cwd())
|
|
39
|
+
root = project_root(cwd)
|
|
40
|
+
try:
|
|
41
|
+
entries = discover((), root, cwd=cwd)
|
|
42
|
+
except DiscoveryError:
|
|
43
|
+
entries = [] # no checks: every baseline is orphaned
|
|
44
|
+
checks = [(store.check_key(entry.path, root, entry.name), entry.check) for entry in entries
|
|
45
|
+
if entry.check is not None]
|
|
46
|
+
found = store.audit(root, checks)
|
|
47
|
+
if view != "list":
|
|
48
|
+
found = [item for item in found if item["state"] == view]
|
|
49
|
+
if args.json:
|
|
50
|
+
print(json.dumps({"snapshots": found}, ensure_ascii=False, allow_nan=False), file=out, flush=True)
|
|
51
|
+
return 0
|
|
52
|
+
if not found:
|
|
53
|
+
print("No screenshot baselines." if view == "list" and not store.baselines(root) else
|
|
54
|
+
f"No {view} screenshot baselines.", file=out, flush=True)
|
|
55
|
+
return 0
|
|
56
|
+
for item in found:
|
|
57
|
+
print(f"{item['state']:<8} {item['file']}", file=out)
|
|
58
|
+
print(f"{len(found)} baseline{'' if len(found) == 1 else 's'}. Delete an orphaned or stale file with git rm, or "
|
|
59
|
+
"run `mobster test --update-snapshots` to record the current screens.", file=out, flush=True)
|
|
60
|
+
return 0
|