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,57 @@
|
|
|
1
|
+
"""The run folder: ``.mobster/runs/<run id>/`` and the ``.gitignore`` that keeps it out of git."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import secrets
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def new_run_id():
|
|
10
|
+
"""YYYYMMDD-HHMMSS-xxxx: the local time plus 4 random hex characters."""
|
|
11
|
+
return time.strftime("%Y%m%d-%H%M%S") + "-" + secrets.token_hex(2)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Runs and test results show whatever was on the screen, and the cache is per machine.
|
|
15
|
+
GITIGNORE = "runs/\nbuild/\ncache/\ntest-results/\n"
|
|
16
|
+
OLD_GITIGNORES = ("runs/\nbuild/\n",)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def ensure_gitignore(runs_dir):
|
|
20
|
+
# Never over the user's own file; one an earlier Mobster wrote, word for word, is brought up to date.
|
|
21
|
+
runs_dir = Path(runs_dir)
|
|
22
|
+
if runs_dir.parent.name != ".mobster":
|
|
23
|
+
return
|
|
24
|
+
folder = runs_dir.parent
|
|
25
|
+
ignore = folder / ".gitignore"
|
|
26
|
+
if ignore.exists():
|
|
27
|
+
try:
|
|
28
|
+
if ignore.is_file() and not ignore.is_symlink() and ignore.read_text(encoding="utf-8") in OLD_GITIGNORES:
|
|
29
|
+
temporary = folder / ".gitignore.tmp"
|
|
30
|
+
temporary.write_text(GITIGNORE, encoding="utf-8")
|
|
31
|
+
os.replace(temporary, ignore)
|
|
32
|
+
except (OSError, UnicodeDecodeError):
|
|
33
|
+
pass # the user's file, or one we can't read: left as it is
|
|
34
|
+
return
|
|
35
|
+
try:
|
|
36
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
with open(ignore, "x", encoding="utf-8") as handle:
|
|
38
|
+
handle.write(GITIGNORE)
|
|
39
|
+
except FileExistsError:
|
|
40
|
+
pass # another run wrote it first
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def make_run_dir(runs_dir):
|
|
44
|
+
# 0700, as the task journal's are: a run's frames and steps hold what was on the screen.
|
|
45
|
+
runs_dir = Path(runs_dir)
|
|
46
|
+
ensure_gitignore(runs_dir)
|
|
47
|
+
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
while True:
|
|
49
|
+
run_id = new_run_id()
|
|
50
|
+
run_dir = runs_dir / run_id
|
|
51
|
+
try:
|
|
52
|
+
run_dir.mkdir(mode=0o700)
|
|
53
|
+
except FileExistsError:
|
|
54
|
+
continue
|
|
55
|
+
os.chmod(run_dir, 0o700) # whatever the umask
|
|
56
|
+
(run_dir / "frames").mkdir(mode=0o700)
|
|
57
|
+
return run_id, run_dir
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Observe required intermediate accessibility screens and retain incomplete-coverage evidence."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from .assertions import evaluate_on
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ScreenCoverage:
|
|
10
|
+
def _incomplete_screen(self, reason):
|
|
11
|
+
with self._screen_coverage_lock:
|
|
12
|
+
if self._screen_result_sealed:
|
|
13
|
+
return
|
|
14
|
+
if self._screen_coverage_issue is None:
|
|
15
|
+
self._screen_coverage_issue = reason
|
|
16
|
+
self.log(f"screen coverage incomplete: {reason}")
|
|
17
|
+
|
|
18
|
+
@contextmanager
|
|
19
|
+
def _screen_frame(self, label):
|
|
20
|
+
tracked = label in ("launch", "step")
|
|
21
|
+
if tracked:
|
|
22
|
+
with self._screen_coverage_lock:
|
|
23
|
+
admitted = not self._screen_frames_sealed
|
|
24
|
+
if admitted:
|
|
25
|
+
self._screen_frames_pending += 1
|
|
26
|
+
else:
|
|
27
|
+
admitted = True
|
|
28
|
+
try:
|
|
29
|
+
yield admitted
|
|
30
|
+
finally:
|
|
31
|
+
if tracked and admitted:
|
|
32
|
+
with self._screen_coverage_lock:
|
|
33
|
+
self._screen_frames_pending -= 1
|
|
34
|
+
|
|
35
|
+
def _seal_screen_frames(self):
|
|
36
|
+
with self._screen_coverage_lock:
|
|
37
|
+
self._screen_frames_sealed = True
|
|
38
|
+
self._verdict_thread = threading.get_ident()
|
|
39
|
+
if self._screen_frames_pending and self._screen_coverage_issue is None:
|
|
40
|
+
self._screen_coverage_issue = "An intermediate screen observation was in flight at the verdict"
|
|
41
|
+
|
|
42
|
+
def _reserve_source_read(self):
|
|
43
|
+
"""Admit a source request before it starts, including reads nested inside driver.observe."""
|
|
44
|
+
with self._screen_coverage_lock:
|
|
45
|
+
if self._screen_result_sealed:
|
|
46
|
+
return None
|
|
47
|
+
if self._screen_frames_sealed and threading.get_ident() != self._verdict_thread:
|
|
48
|
+
reason = "A screen read began after the final verdict started"
|
|
49
|
+
if self._screen_coverage_issue is None:
|
|
50
|
+
self._screen_coverage_issue = reason
|
|
51
|
+
rejected = True
|
|
52
|
+
else:
|
|
53
|
+
self._screen_frames_pending += 1
|
|
54
|
+
rejected = False
|
|
55
|
+
if rejected:
|
|
56
|
+
self.log(f"screen coverage incomplete: {reason}")
|
|
57
|
+
return None
|
|
58
|
+
released = False
|
|
59
|
+
|
|
60
|
+
def release():
|
|
61
|
+
nonlocal released
|
|
62
|
+
with self._screen_coverage_lock:
|
|
63
|
+
if not released:
|
|
64
|
+
released = True
|
|
65
|
+
self._screen_frames_pending -= 1
|
|
66
|
+
|
|
67
|
+
return release
|
|
68
|
+
|
|
69
|
+
def _late_screen_frame(self, label, path):
|
|
70
|
+
with self._screen_coverage_lock:
|
|
71
|
+
late = label in ("launch", "step") and self._screen_frames_sealed
|
|
72
|
+
if late:
|
|
73
|
+
try:
|
|
74
|
+
path.unlink(missing_ok=True)
|
|
75
|
+
except OSError as error:
|
|
76
|
+
self.log(f"late screen frame could not be removed: {type(error).__name__}")
|
|
77
|
+
return late
|
|
78
|
+
|
|
79
|
+
def reserve_step_frame(self):
|
|
80
|
+
"""Count a keyless step before its capture thread is scheduled; return its once-only release."""
|
|
81
|
+
with self._screen_coverage_lock:
|
|
82
|
+
if self._screen_frames_sealed:
|
|
83
|
+
return None
|
|
84
|
+
self._screen_frames_pending += 1
|
|
85
|
+
released = False
|
|
86
|
+
|
|
87
|
+
def release():
|
|
88
|
+
nonlocal released
|
|
89
|
+
with self._screen_coverage_lock:
|
|
90
|
+
if not released:
|
|
91
|
+
released = True
|
|
92
|
+
self._screen_frames_pending -= 1
|
|
93
|
+
|
|
94
|
+
return release
|
|
95
|
+
|
|
96
|
+
def _check_pending_screens(self):
|
|
97
|
+
with self._screen_coverage_lock:
|
|
98
|
+
pending = self._screen_frames_pending
|
|
99
|
+
if pending:
|
|
100
|
+
self._incomplete_screen("An intermediate screen capture was still in flight at the verdict")
|
|
101
|
+
|
|
102
|
+
def _observe_screen(self, frame, tree=None):
|
|
103
|
+
"""Use one AX read for every requested per-screen check, with or without an image."""
|
|
104
|
+
if not (self.check.throughout or self.check.somewhere or self._a11y or self._l10n):
|
|
105
|
+
return
|
|
106
|
+
try:
|
|
107
|
+
tree = tree or self.read_tree()
|
|
108
|
+
except Exception as error:
|
|
109
|
+
self.log(f"screen observation skipped: {type(error).__name__}")
|
|
110
|
+
self._incomplete_screen("An accessibility screen could not be read or evaluated")
|
|
111
|
+
return
|
|
112
|
+
with self._screen_coverage_lock:
|
|
113
|
+
if self._screen_result_sealed or (self._screen_frames_sealed
|
|
114
|
+
and threading.get_ident() != self._verdict_thread):
|
|
115
|
+
return
|
|
116
|
+
try:
|
|
117
|
+
self._sweep(frame, tree)
|
|
118
|
+
self._audit_screen(frame, tree)
|
|
119
|
+
except Exception as error:
|
|
120
|
+
self.log(f"screen observation skipped: {type(error).__name__}")
|
|
121
|
+
self._incomplete_screen("An accessibility screen could not be read or evaluated")
|
|
122
|
+
|
|
123
|
+
def _finalize_screen_coverage(self):
|
|
124
|
+
"""Freeze the reported marker with collector writes and pending reads under one short lock."""
|
|
125
|
+
with self._screen_coverage_lock:
|
|
126
|
+
if self._screen_coverage_final is not None:
|
|
127
|
+
return dict(self._screen_coverage_final)
|
|
128
|
+
if self._screen_frames_pending and self._screen_coverage_issue is None:
|
|
129
|
+
self._screen_coverage_issue = "A screen observation was still in flight at the verdict"
|
|
130
|
+
self._screen_result_sealed = True
|
|
131
|
+
self._screen_coverage_final = {
|
|
132
|
+
"complete": self._screen_verdict_audited and self._screen_coverage_issue is None
|
|
133
|
+
and (self._a11y is None or self._a11y.screens > 0),
|
|
134
|
+
"a11yScreens": self._a11y.screens if self._a11y is not None else 0,
|
|
135
|
+
}
|
|
136
|
+
return dict(self._screen_coverage_final)
|
|
137
|
+
|
|
138
|
+
def _audit_screen(self, frame, tree):
|
|
139
|
+
"""Add the already-read screen to the accessibility audit."""
|
|
140
|
+
collectors = [item for item in (self._a11y, self._l10n, self._ax_device) if item is not None]
|
|
141
|
+
for collector in collectors:
|
|
142
|
+
if collector.add(tree, frame) is False:
|
|
143
|
+
self._incomplete_screen("A screen audit reached its 60-screen limit")
|
|
144
|
+
|
|
145
|
+
def _sweep(self, frame, tree):
|
|
146
|
+
check = self.check
|
|
147
|
+
if not (check.throughout or check.somewhere):
|
|
148
|
+
return
|
|
149
|
+
frame = self._relative(frame)
|
|
150
|
+
fingerprint = tree.audit_fingerprint()
|
|
151
|
+
for assertion, result in zip(check.throughout, evaluate_on(check.throughout, tree)):
|
|
152
|
+
text = assertion.name or result.text
|
|
153
|
+
if result.ok:
|
|
154
|
+
continue
|
|
155
|
+
prior = self._unframed_findings.get((fingerprint, text)) if frame else None
|
|
156
|
+
if prior is not None:
|
|
157
|
+
prior["frame"] = prior["evidence"] = frame
|
|
158
|
+
del self._unframed_findings[(fingerprint, text)]
|
|
159
|
+
elif not any(item["id"] == text and item["frame"] == frame for item in self.findings):
|
|
160
|
+
finding = {"id": text, "guideline": None, "level": "fail", "frame": frame,
|
|
161
|
+
"message": f"{text}: {result.observed}",
|
|
162
|
+
"evidence": frame or "an observed accessibility screen"}
|
|
163
|
+
self.findings.append(finding)
|
|
164
|
+
if frame is None:
|
|
165
|
+
self._unframed_findings[(fingerprint, text)] = finding
|
|
166
|
+
for index, result in enumerate(evaluate_on(check.somewhere, tree)):
|
|
167
|
+
if result.ok:
|
|
168
|
+
self._held_somewhere.add(index)
|
|
169
|
+
|
|
170
|
+
def _missing_somewhere(self):
|
|
171
|
+
"""A finding for each ``somewhere`` assertion no screen held."""
|
|
172
|
+
# The run may not have reached that screen, so a person looks (needs_review) rather than the check failing.
|
|
173
|
+
out = []
|
|
174
|
+
for index, assertion in enumerate(self.check.somewhere):
|
|
175
|
+
if index not in self._held_somewhere:
|
|
176
|
+
text = assertion.name or assertion.render()
|
|
177
|
+
out.append({"id": text, "guideline": None, "level": "review", "frame": None,
|
|
178
|
+
"message": f"{text}: no screen the run visited showed it. Open the screens in the "
|
|
179
|
+
"report; if the app has it, add a step that goes there.",
|
|
180
|
+
"evidence": f"{len(self.frames)} frames in the run"})
|
|
181
|
+
return out
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
NO_KEY_FIX = ("Set OPENAI_API_KEY or ANTHROPIC_API_KEY for Smart, or let your coding agent drive through "
|
|
5
|
+
"`mobster mcp`.")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def no_smart_key(env=None):
|
|
9
|
+
"""(why, fix) when Smart has no key for its model."""
|
|
10
|
+
from .. import engines
|
|
11
|
+
env = os.environ if env is None else env
|
|
12
|
+
model = engines.smart_model(env)
|
|
13
|
+
from ..ai_plans import providers
|
|
14
|
+
routed = providers.get(engines.model_provider(model))
|
|
15
|
+
if routed is not None:
|
|
16
|
+
return (f"{routed.name} has no key or sign-in here", f"Run `mobster login {routed.id}`, or `mobster login` "
|
|
17
|
+
"for an API key.")
|
|
18
|
+
if (env.get("MOBSTER_SMART_MODEL") or "").strip() != model:
|
|
19
|
+
return "no OpenAI or Anthropic key is set", NO_KEY_FIX
|
|
20
|
+
variable = "ANTHROPIC_API_KEY" if engines.model_provider(model) == "anthropic" else "OPENAI_API_KEY"
|
|
21
|
+
return (f"MOBSTER_SMART_MODEL is {model}, which needs {variable}, and it isn't set",
|
|
22
|
+
f"Set {variable} for Smart on {model}, or let your coding agent drive through `mobster mcp`.")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Acts that name no commit: none at all, the replay's "act" for a step it couldn't read, and contract_screen's "none".
|
|
26
|
+
UNCLASSIFIED_ACTS = frozenset({"", "act", "none"})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def approve_on_simulator(request):
|
|
30
|
+
"""Smart's approver on a simulator: the app under test may buy, send, post and delete, because money and people
|
|
31
|
+
don't exist there. A commit the frontier could not classify is refused. The simulator's dialogs, including the
|
|
32
|
+
purchase sheet, answer to the check's ``dialogs``."""
|
|
33
|
+
act = request.get("act") if isinstance(request, dict) else None
|
|
34
|
+
if not isinstance(act, str) or act.strip().casefold() in UNCLASSIFIED_ACTS:
|
|
35
|
+
return "denied"
|
|
36
|
+
return "approved"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def deny_commits(request):
|
|
40
|
+
"""Smart's approver in a check on a real device (a USB iPhone, a WDA address): every declared commit is refused."""
|
|
41
|
+
# The phone is someone's own: a send, delete, post or purchase there is real, and a check never asks first.
|
|
42
|
+
return "denied"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_real_device(manager):
|
|
46
|
+
"""Whether ``manager`` drives someone's phone. Fails closed: only ``real_device = False`` is a simulator."""
|
|
47
|
+
return getattr(manager, "real_device", None) is not False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_approver(manager):
|
|
51
|
+
return deny_commits if is_real_device(manager) else approve_on_simulator
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# System view services an app presents over itself (a share sheet, Sign in with Apple, a purchase sheet): part of
|
|
55
|
+
# the app under test, not another app. SpringBoard (alerts, the Home Screen) the frontier never stops for.
|
|
56
|
+
VIEW_SERVICE = re.compile(r"com\.apple\.[A-Za-z0-9.]*(?:ViewService|UIService)")
|
|
57
|
+
LEFT_APP = ("On your iPhone a check stays in {app}, and Smart opened {other}, so the check stopped there. Nothing "
|
|
58
|
+
"was read from {other} for the report.")
|
|
59
|
+
LEFT_APP_FIX = "Keep the check's steps inside the app, or run it on a simulator with --sim."
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class StayInApp:
|
|
63
|
+
"""The phone guard for a Smart check on a real device: only the app under test, so no step reads their data."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, bundle, inner=None):
|
|
66
|
+
self.bundle, self.inner, self.left = bundle, inner, None
|
|
67
|
+
|
|
68
|
+
def check(self, driver, *, cause):
|
|
69
|
+
return self.inner.check(driver, cause=cause) if self.inner is not None else None
|
|
70
|
+
|
|
71
|
+
def attached(self):
|
|
72
|
+
return self.inner.attached() if self.inner is not None else None
|
|
73
|
+
|
|
74
|
+
def allows_app(self, bundle_id):
|
|
75
|
+
if not bundle_id or VIEW_SERVICE.fullmatch(bundle_id):
|
|
76
|
+
return True
|
|
77
|
+
if bundle_id == self.bundle: # the user's own list of apps Mobster never opens still applies
|
|
78
|
+
allows = getattr(self.inner, "allows_app", None)
|
|
79
|
+
return bool(allows(bundle_id)) if callable(allows) else True
|
|
80
|
+
self.left = bundle_id
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def finish(self, driver):
|
|
84
|
+
if self.inner is not None:
|
|
85
|
+
self.inner.finish(driver)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_guard(manager, bundle):
|
|
89
|
+
if not is_real_device(manager):
|
|
90
|
+
return None
|
|
91
|
+
record = getattr(manager, "record", None) or {}
|
|
92
|
+
inner = None
|
|
93
|
+
try:
|
|
94
|
+
from ..lockscreen import guard_for
|
|
95
|
+
inner = guard_for(device_id=record.get("udid") or record.get("id"), wda_url=record.get("wdaUrl"),
|
|
96
|
+
mode="scripts", usb=record.get("kind") == "usb")
|
|
97
|
+
except Exception:
|
|
98
|
+
inner = None # no lock detection: the app restriction still holds
|
|
99
|
+
return StayInApp(bundle, inner)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def smart_request(app_name, steps):
|
|
103
|
+
"""What Smart is asked: "In <App>: <the step>", or the steps numbered."""
|
|
104
|
+
if len(steps) == 1:
|
|
105
|
+
return f"In {app_name}: {steps[0]}"
|
|
106
|
+
return f"In {app_name}: " + " ".join(f"{index}. {step}" for index, step in enumerate(steps, 1))
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""The snapshot step of one verify run: take the screen, compare it to its baseline, record or update the baseline."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from . import snapshots
|
|
7
|
+
from .checks import project_root
|
|
8
|
+
|
|
9
|
+
LEVEL_MESSAGES = {
|
|
10
|
+
snapshots.CHANGED: "{pct} of the screen changed; the limit is {limit}. Open the diff, and run "
|
|
11
|
+
"`mobster test --update-snapshots` if the new screen is right.",
|
|
12
|
+
snapshots.SIZE_CHANGED: "the screen is {size} px and the baseline is {base} px. Run the check on the baseline's "
|
|
13
|
+
"device, or `mobster test --update-snapshots` to record this size.",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _percent(value):
|
|
18
|
+
return f"{value:.2f} %" if value >= .01 or value == 0 else "under 0.01 %"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def project_of(run):
|
|
22
|
+
"""The folder holding .mobster/ for this run, or None."""
|
|
23
|
+
if run.runs_dir.parent.name == ".mobster":
|
|
24
|
+
return run.runs_dir.parent.parent
|
|
25
|
+
return project_root(run.check.source) if run.check.source else None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _finding(name, level, message, frame, evidence):
|
|
29
|
+
return {"id": f"snapshot {name}", "audit": "snapshot", "guideline": None, "level": level, "frame": frame,
|
|
30
|
+
"message": f"snapshot {name}: {message}", "evidence": evidence}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def take(run, tree, frame):
|
|
34
|
+
"""Compare the screen to the check's baseline. Returns (the result dict or None, findings). Never raises: a
|
|
35
|
+
snapshot that can't be taken is a finding for a person, not a crash."""
|
|
36
|
+
from PIL import Image
|
|
37
|
+
check = run.check
|
|
38
|
+
spec = check.snapshot
|
|
39
|
+
name = spec.name
|
|
40
|
+
root = project_of(run)
|
|
41
|
+
if root is None:
|
|
42
|
+
return None, [_finding(name, "review", "Mobster has no project folder to keep the baseline in. Run the check "
|
|
43
|
+
"from a folder with .mobster/.", frame, "no .mobster folder")]
|
|
44
|
+
target = run.target
|
|
45
|
+
device = getattr(target, "device_type", None) or "iphone"
|
|
46
|
+
key = snapshots.check_key(check.source, root, check.name)
|
|
47
|
+
baseline = snapshots.baseline_file(root, key, name, device, check.conditions)
|
|
48
|
+
folder = run.run_dir / snapshots.FOLDER
|
|
49
|
+
current_path = folder / f"{name}-current.png"
|
|
50
|
+
try:
|
|
51
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
run._call(run.manager.screenshot, target, current_path, quality=100)
|
|
53
|
+
with Image.open(current_path) as opened:
|
|
54
|
+
current = opened.convert("RGB")
|
|
55
|
+
except Exception as error:
|
|
56
|
+
run.log(f"snapshot: no screenshot ({type(error).__name__})")
|
|
57
|
+
return None, [_finding(name, "review", "Mobster couldn't take the screenshot, so nothing was compared.",
|
|
58
|
+
frame, "no screenshot")]
|
|
59
|
+
rects = snapshots.mask_rects(spec, tree, current.width, status_bar_set=run._status_bar_set)
|
|
60
|
+
relative = baseline.relative_to(root).as_posix()
|
|
61
|
+
result = {"name": name, "key": relative, "status": None, "thresholdPercent": spec.threshold,
|
|
62
|
+
"size": [current.width, current.height], "masks": [[round(v) for v in rect] for rect in rects],
|
|
63
|
+
"regions": [], "changedPercent": 0.0, "changedPixels": 0, "comparedPixels": 0, "diffMs": None,
|
|
64
|
+
"baseline": None, "current": current_path.relative_to(run.run_dir).as_posix(), "diff": None,
|
|
65
|
+
"device": device, "appearance": (check.conditions or {}).get("appearance"),
|
|
66
|
+
"contentSize": (check.conditions or {}).get("content_size"),
|
|
67
|
+
"locale": (check.conditions or {}).get("locale"), "statusBarSet": run._status_bar_set}
|
|
68
|
+
existed = baseline.is_file()
|
|
69
|
+
if not existed and not check.snapshot_record:
|
|
70
|
+
return None, [_finding(name, "fail", f"there's no baseline at {relative}, and an approved CI suite never "
|
|
71
|
+
"records one. Record it with `mobster test --update-snapshots`, then "
|
|
72
|
+
"approve a new revision.", frame, relative)]
|
|
73
|
+
if check.snapshot_update or not existed:
|
|
74
|
+
snapshots.save_png(current, baseline)
|
|
75
|
+
result["status"] = snapshots.UPDATED if existed else snapshots.RECORDED
|
|
76
|
+
result["baseline"] = _copy_baseline(baseline, folder, name, run.run_dir)
|
|
77
|
+
run.log(f"snapshot {name}: baseline {result['status']} at {relative}")
|
|
78
|
+
if check.snapshot_update:
|
|
79
|
+
return result, []
|
|
80
|
+
return result, [_finding(name, "review", f"baseline recorded at {relative}. Review the screen, commit the "
|
|
81
|
+
"file, and the next run compares to it.", frame, relative)]
|
|
82
|
+
try:
|
|
83
|
+
with Image.open(baseline) as opened:
|
|
84
|
+
old = opened.convert("RGB")
|
|
85
|
+
except Exception as error:
|
|
86
|
+
run.log(f"snapshot: unreadable baseline ({type(error).__name__})")
|
|
87
|
+
return result, [_finding(name, "review", f"the baseline {relative} can't be read as an image. Run "
|
|
88
|
+
"`mobster test --update-snapshots` to write it again.", frame,
|
|
89
|
+
relative)]
|
|
90
|
+
result["baseline"] = _copy_baseline(baseline, folder, name, run.run_dir)
|
|
91
|
+
found = snapshots.compare(old, current, rects, spec.threshold)
|
|
92
|
+
result.update(status=found.status, changedPercent=round(found.changed_percent, 4),
|
|
93
|
+
changedPixels=found.changed_pixels, comparedPixels=found.compared_pixels, regions=found.regions,
|
|
94
|
+
diffMs=round(found.seconds * 1000))
|
|
95
|
+
run.log(f"snapshot {name}: {found.status}, {_percent(found.changed_percent)} changed, {result['diffMs']} ms")
|
|
96
|
+
if found.status == snapshots.MATCHED:
|
|
97
|
+
return result, []
|
|
98
|
+
evidence = relative
|
|
99
|
+
if found.diff is not None:
|
|
100
|
+
diff_path = folder / f"{name}-diff.png"
|
|
101
|
+
snapshots.save_png(found.diff, diff_path)
|
|
102
|
+
result["diff"] = evidence = diff_path.relative_to(run.run_dir).as_posix()
|
|
103
|
+
message = LEVEL_MESSAGES[found.status].format(
|
|
104
|
+
pct=_percent(found.changed_percent), limit=f"{spec.threshold:g} %", size="x".join(map(str, current.size)),
|
|
105
|
+
base="x".join(map(str, old.size)))
|
|
106
|
+
return result, [_finding(name, "fail", message, frame, evidence)]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _copy_baseline(baseline, folder, name, run_dir):
|
|
110
|
+
"""A copy of the baseline in the run's folder, so a shared report carries all three images."""
|
|
111
|
+
copy = Path(folder) / f"{name}-baseline.png"
|
|
112
|
+
copy.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
shutil.copyfile(baseline, copy)
|
|
114
|
+
return copy.relative_to(run_dir).as_posix()
|