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,193 @@
|
|
|
1
|
+
"""Argument definitions and usage errors for ``mobster test``."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
USAGE_EXIT = 3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def add_arguments(parser, helpers):
|
|
9
|
+
"""`mobster test`'s flags. ``helpers``: env_option, path_type and bounded from __main__."""
|
|
10
|
+
from . import repro, snapshots_cli
|
|
11
|
+
bounded = helpers["bounded"]
|
|
12
|
+
parser.add_argument("paths", nargs="*", metavar="PATH",
|
|
13
|
+
help="check files or folders (default: .mobster/checks); `record` saves a task; "
|
|
14
|
+
"`capsule` inspects evidence; `compare` reads suites; `scenario` runs ordered checks; "
|
|
15
|
+
"`minimize` tries a paired local reproducer")
|
|
16
|
+
where = parser.add_argument_group("where checks run")
|
|
17
|
+
where.add_argument("--sim", action="append", metavar="TYPE[@RUNTIME]",
|
|
18
|
+
help="a simulator device type, with an optional runtime, such as \"iPhone 17 Pro@iOS 26.4\"; "
|
|
19
|
+
"repeatable (default: each check's own, or .mobster/matrix.yaml)")
|
|
20
|
+
where.add_argument("--device", action="append", metavar="NAME",
|
|
21
|
+
help="a device from `mobster devices`, or all: every iPhone ready on this Mac, side by side "
|
|
22
|
+
"in one report; repeatable. On a real iPhone, checks drive only builds you installed, "
|
|
23
|
+
"never App Store apps")
|
|
24
|
+
from .. import android
|
|
25
|
+
android.add_test_flag(where)
|
|
26
|
+
where.add_argument("--testflight", action="append", metavar="BUNDLE",
|
|
27
|
+
help="with --device: your app's bundle ID, installed from TestFlight, which checks may "
|
|
28
|
+
"drive as your own build; repeatable")
|
|
29
|
+
where.add_argument("--matrix", metavar="FILE",
|
|
30
|
+
help="a YAML file of simulators to run every check on (simulators only)")
|
|
31
|
+
where.add_argument("--locale", action="append", metavar="LANG[,LANG]",
|
|
32
|
+
help="run every check once in each language, such as de,ja,ar: the app launches in that "
|
|
33
|
+
"language and region, and the screens are read for truncated text, untranslated "
|
|
34
|
+
"strings and, in a right-to-left language, a layout that didn't mirror")
|
|
35
|
+
where.add_argument("--spread", action="store_true",
|
|
36
|
+
help="with two or more devices: run each check once, on whichever device is free, instead of "
|
|
37
|
+
"on every one (the report groups checks by device)")
|
|
38
|
+
where.add_argument("--parallel", type=bounded(int, 1, 8), default=1, metavar="N",
|
|
39
|
+
help="simulators running checks at once (default: 1); each iPhone runs one check at a time")
|
|
40
|
+
how = parser.add_argument_group("retries and flakes")
|
|
41
|
+
how.add_argument("--retries", type=bounded(int, 0, 5), metavar="N",
|
|
42
|
+
help="attempts after a failure, from a fresh app (default: 1, or 0 with --repeat)")
|
|
43
|
+
how.add_argument("--repeat", type=bounded(int, 1, 20), default=1, metavar="K",
|
|
44
|
+
help="run each check K times and report its pass rate (flake hunting)")
|
|
45
|
+
how.add_argument("--strict", action="store_true", help="a flaky check fails the run")
|
|
46
|
+
how.add_argument("--quarantine", metavar="FILE",
|
|
47
|
+
help="checks that run and are reported but never fail the run (default: "
|
|
48
|
+
".mobster/quarantine.yaml)")
|
|
49
|
+
how.add_argument("--auto-quarantine", action="store_true",
|
|
50
|
+
help="quarantine for this run the checks whose last 20 results say flaky; without it Mobster "
|
|
51
|
+
"only suggests them")
|
|
52
|
+
which = parser.add_argument_group("which checks")
|
|
53
|
+
which.add_argument("--tag", action="append", metavar="TAG", help="only checks with this tag; repeatable")
|
|
54
|
+
which.add_argument("--audit", action="append", choices=("a11y",), metavar="NAME",
|
|
55
|
+
help="also audit what each check reads: a11y finds controls with no label, tap targets "
|
|
56
|
+
"under 44 pt, images with no label and text clipped at a large text size; no model "
|
|
57
|
+
"is called")
|
|
58
|
+
which.add_argument("--a11y", nargs="?", const="warn", choices=("warn", "strict"), metavar="LEVEL",
|
|
59
|
+
help="audit the screens each check reads for accessibility: warn (the default) reports "
|
|
60
|
+
"findings and passes, strict fails on a new one; findings in "
|
|
61
|
+
".mobster/a11y-baseline.json never fail")
|
|
62
|
+
which.add_argument("--update-a11y-baseline", action="store_true",
|
|
63
|
+
help="write this run's accessibility findings to .mobster/a11y-baseline.json, so later runs "
|
|
64
|
+
"fail only on new ones")
|
|
65
|
+
snapshots_cli.add_arguments(which)
|
|
66
|
+
which.add_argument("--shard", metavar="I/N", help="run the I-th of N even shards, such as 2/4")
|
|
67
|
+
which.add_argument("--keyless", action="store_true",
|
|
68
|
+
help="never call a model: checks with steps couldn't run")
|
|
69
|
+
replay = parser.add_argument_group("replay")
|
|
70
|
+
replay.add_argument("--replay", action="store_true",
|
|
71
|
+
help="replay each check's recorded path with no model, and heal a step that changed (the "
|
|
72
|
+
"default)")
|
|
73
|
+
replay.add_argument("--fresh", action="store_true",
|
|
74
|
+
help="run every step with Smart and record the path again")
|
|
75
|
+
replay.add_argument("--no-heal", action="store_true",
|
|
76
|
+
help="replay, and fail a check whose screen changed instead of healing it (strict CI)")
|
|
77
|
+
unit = parser.add_argument_group("your XCTest targets")
|
|
78
|
+
unit.add_argument("--xctest", action="store_true",
|
|
79
|
+
help="also build your Xcode project once and run its XCTest and Swift Testing targets on a "
|
|
80
|
+
"simulator beside the checks; failures join the report, the JUnit file, the history and "
|
|
81
|
+
"the PR comment. Pass --scheme and --project to name them")
|
|
82
|
+
unit.add_argument("--test-plan", metavar="NAME", help="with --xctest: the test plan to run (default: the "
|
|
83
|
+
"scheme's)")
|
|
84
|
+
unit.add_argument("--only-testing", action="append", metavar="ID",
|
|
85
|
+
help="with --xctest: run only this Target, Target/Class or Target/Class/test; repeatable")
|
|
86
|
+
out = parser.add_argument_group("reports")
|
|
87
|
+
out.add_argument("--junit", metavar="FILE", help="also write the JUnit XML here")
|
|
88
|
+
out.add_argument("--html", metavar="DIR", help="also write the HTML report (with each run's report) here")
|
|
89
|
+
out.add_argument("--video", action="store_true",
|
|
90
|
+
help="record each run's screen, on a simulator or an iPhone, into the report")
|
|
91
|
+
out.add_argument("--history", action="store_true",
|
|
92
|
+
help="print each check's last 20 results (pass rate, duration trend, flake score) and run "
|
|
93
|
+
"nothing")
|
|
94
|
+
out.add_argument("--json", action="store_true", help="print JSON on stdout instead of the summary")
|
|
95
|
+
out.add_argument("--include", action="append", dest="capsule_include", choices=("junit", "report", "visual"),
|
|
96
|
+
metavar="KIND", help="with capsule export: copy selected raw junit, report or visual evidence "
|
|
97
|
+
"locally; repeatable")
|
|
98
|
+
out.add_argument("--share", action="store_true",
|
|
99
|
+
help="also upload the report, sealed, to your team's hosted reports and print its link (Team "
|
|
100
|
+
"plan: one of the team's Macs, or MOBSTER_TEAM_KEY in CI)")
|
|
101
|
+
out.add_argument("--share-days", type=int, choices=(7, 30, 90), default=30, metavar="DAYS",
|
|
102
|
+
help="how long a shared report is kept: 7, 30 or 90 (default: 30)")
|
|
103
|
+
out.add_argument("--assert-timeout", type=bounded(float, 0, 60), default=5, metavar="S",
|
|
104
|
+
help="how long expectations may wait for the screen to settle (default: 5)")
|
|
105
|
+
record = parser.add_argument_group("mobster test record")
|
|
106
|
+
record.add_argument("--run", dest="record_run", metavar="RUN_ID", help="the finished task to make a check from")
|
|
107
|
+
record.add_argument("--name", dest="record_name", metavar="TEXT", help="the check's name (default: the task)")
|
|
108
|
+
record.add_argument("-o", "--output", dest="record_output", metavar="PATH",
|
|
109
|
+
help="with record: write the check here; with import: checks folder; with capsule export: "
|
|
110
|
+
"new output directory")
|
|
111
|
+
imp = parser.add_argument_group("mobster test import")
|
|
112
|
+
imp.add_argument("--bundle", metavar="ID",
|
|
113
|
+
help="the app's bundle ID for flows with no appId, and over the appId of the others")
|
|
114
|
+
imp.add_argument("--app", metavar="PATH", help="write this .app (built for the iOS Simulator) into each check")
|
|
115
|
+
imp.add_argument("--skip-unsupported", action="store_true",
|
|
116
|
+
help="drop commands Mobster can't run instead of turning each into a step marked TODO, and list "
|
|
117
|
+
"each one; an assertion is never dropped")
|
|
118
|
+
imp.add_argument("--dry-run", action="store_true", help="say what would be written, and write nothing")
|
|
119
|
+
imp.add_argument("--force", action="store_true", help="replace checks that already exist")
|
|
120
|
+
repro.add_arguments(parser)
|
|
121
|
+
add = parser.add_argument_group("mobster test add")
|
|
122
|
+
add.add_argument("--list", dest="add_list", action="store_true", help="list the recipes, one line each")
|
|
123
|
+
add.add_argument("--set", dest="add_set", action="append", metavar="KEY=VALUE",
|
|
124
|
+
help="a recipe param; repeatable (`mobster test add --list` shows each recipe's)")
|
|
125
|
+
pr = parser.add_argument_group("mobster test pr")
|
|
126
|
+
pr.add_argument("--base", metavar="REF",
|
|
127
|
+
help="the branch the PR merges into (default: $GITHUB_BASE_REF, else origin/main)")
|
|
128
|
+
pr.add_argument("--no-build", action="store_true",
|
|
129
|
+
help="don't build the app: test the builds the checks name")
|
|
130
|
+
pr.add_argument("--scheme", metavar="NAME", help="the Xcode scheme to build (default: the only one); also the "
|
|
131
|
+
"scheme --xctest builds")
|
|
132
|
+
pr.add_argument("--project", metavar="PATH",
|
|
133
|
+
help="the .xcodeproj or .xcworkspace to build (default: the one in the project); also the one "
|
|
134
|
+
"--xctest builds")
|
|
135
|
+
pr.add_argument("--drafts", choices=("run", "write", "off"), default="run",
|
|
136
|
+
help="checks for changed screens no check covers: write and run them (default), only write "
|
|
137
|
+
"them, or neither. Drafts never fail the PR")
|
|
138
|
+
pr.add_argument("--no-gifs", action="store_true", help="don't make a GIF of each failed check")
|
|
139
|
+
pr.add_argument("--tested-with", action="store_true",
|
|
140
|
+
help="end the PR comment with a \"Tested with Mobster\" line that links to mobster.dev")
|
|
141
|
+
pr.add_argument("--comment", metavar="FILE", help="also write the PR comment's Markdown here")
|
|
142
|
+
pr.add_argument("--github", choices=("check", "comment", "off"), default="check",
|
|
143
|
+
help="put the verdict on the pull request with Mobster Checks, the GitHub App: a check run "
|
|
144
|
+
"(check, the default), the check run and one comment kept up to date (comment), or nothing "
|
|
145
|
+
"(off)")
|
|
146
|
+
nightly = parser.add_argument_group("mobster test nightly")
|
|
147
|
+
nightly.add_argument("--at", metavar="HH:MM",
|
|
148
|
+
help="with nightly on: the time to run every night, on a 24-hour clock, such as 02:00")
|
|
149
|
+
heal = parser.add_argument_group("mobster test heal")
|
|
150
|
+
heal.add_argument("--accept", action="store_true", help="keep the healed steps of these checks (default: all)")
|
|
151
|
+
heal.add_argument("--reject", action="store_true",
|
|
152
|
+
help="keep failing: put the recorded steps back and fail when they change again")
|
|
153
|
+
reduce = parser.add_argument_group("mobster test minimize")
|
|
154
|
+
reduce.add_argument("--bad-app", dest="minimize_bad_app", metavar="PATH",
|
|
155
|
+
help="with minimize: the exact saved bad simulator .app build")
|
|
156
|
+
reduce.add_argument("--control-app", dest="minimize_control_app", metavar="PATH",
|
|
157
|
+
help="with minimize: the exact saved clean-control simulator .app build")
|
|
158
|
+
reduce.add_argument("--isolated", dest="minimize_isolated", action="store_true",
|
|
159
|
+
help="with minimize: confirm the external fixture is isolated for every trial")
|
|
160
|
+
reduce.add_argument("--max-attempts", dest="minimize_max_attempts", type=bounded(int, 5, 8), metavar="N",
|
|
161
|
+
help="with minimize: total bad and control suite dispatches, 5–8 (default: 8)")
|
|
162
|
+
helpers["env_option"](parser)
|
|
163
|
+
parser._option_string_actions["--env-file"].help = ("load KEY=VALUE lines, such as OPENAI_API_KEY or "
|
|
164
|
+
"ANTHROPIC_API_KEY for Smart steps; variables already set "
|
|
165
|
+
"win; default: $MOBSTER_ENV_FILE")
|
|
166
|
+
_usage_exits_3(parser)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _usage_exits_3(parser):
|
|
170
|
+
"""argparse's usage errors exit 2, which `test` uses for needs review: they exit 3 here."""
|
|
171
|
+
parse_known = parser.parse_known_args
|
|
172
|
+
|
|
173
|
+
def parse_known_args(args=None, namespace=None):
|
|
174
|
+
namespace, extras = parse_known(args, namespace)
|
|
175
|
+
stray = [item for item in extras if item.startswith("-")]
|
|
176
|
+
if stray:
|
|
177
|
+
parser.error(f"unrecognized arguments: {' '.join(stray)}")
|
|
178
|
+
if extras:
|
|
179
|
+
namespace.paths = list(getattr(namespace, "paths", None) or []) + extras
|
|
180
|
+
return namespace, []
|
|
181
|
+
|
|
182
|
+
def error(message):
|
|
183
|
+
parser.print_usage(sys.stderr)
|
|
184
|
+
hint = ""
|
|
185
|
+
try:
|
|
186
|
+
from ..devtools import option_hint
|
|
187
|
+
hint = option_hint(parser, message)
|
|
188
|
+
except ImportError:
|
|
189
|
+
pass
|
|
190
|
+
print(f"{parser.prog}: error: {message}{hint}", file=sys.stderr, flush=True)
|
|
191
|
+
raise SystemExit(USAGE_EXIT)
|
|
192
|
+
parser.parse_known_args = parse_known_args
|
|
193
|
+
parser.error = error
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Compare exact saved suites, distinguishing transitions from controlled app-state proof."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from . import capsule, reports
|
|
9
|
+
|
|
10
|
+
SCHEMA = "mobster.test-compare/1"
|
|
11
|
+
DIGEST = re.compile(r"[a-f0-9]{64}\Z")
|
|
12
|
+
REASON_CLASS = re.compile(r"[a-z][a-z0-9_]{0,39}\Z")
|
|
13
|
+
FIELD = re.compile(r"[a-z][a-z0-9_.-]{0,79}\Z")
|
|
14
|
+
STATUSES = frozenset({"passed", "failed", "needs_review", "couldnt_run"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ComparisonError(ValueError):
|
|
18
|
+
"""A named suite has no intact completed local report."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _read(results_dir, suite_id):
|
|
22
|
+
if not isinstance(suite_id, str) or not capsule.SUITE_ID.fullmatch(suite_id):
|
|
23
|
+
raise ComparisonError("A suite ID must name a saved Mobster test result.")
|
|
24
|
+
try:
|
|
25
|
+
return capsule.read_completed_suite(Path(results_dir) / suite_id)
|
|
26
|
+
except (capsule.CapsuleError, OSError) as error:
|
|
27
|
+
raise ComparisonError(f"Suite {suite_id} has no intact completed result.") from error
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def compare_suites(results_dir, base_suite_id, candidate_suite_id):
|
|
31
|
+
"""Read the two exact IDs under one local results directory."""
|
|
32
|
+
return compare(_read(results_dir, base_suite_id), _read(results_dir, candidate_suite_id))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _ref(text):
|
|
36
|
+
return hashlib.sha256(text.encode("utf-8", errors="surrogatepass")).hexdigest()[:12]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _digest(value):
|
|
40
|
+
return isinstance(value, str) and bool(DIGEST.fullmatch(value))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _frozen(value):
|
|
44
|
+
return value == "none" or _digest(value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _status(cell):
|
|
48
|
+
value = cell.get("status")
|
|
49
|
+
return value if isinstance(value, str) and value in STATUSES else None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _reason(cell):
|
|
53
|
+
source = cell.get("reason")
|
|
54
|
+
value = source.get("class") if isinstance(source, dict) else None
|
|
55
|
+
return value if isinstance(value, str) and REASON_CLASS.fullmatch(value) else None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _environment(cell):
|
|
59
|
+
source = cell.get("environment")
|
|
60
|
+
if not isinstance(source, dict) or source.get("kind") not in ("simulator", "iphone") or any(
|
|
61
|
+
not isinstance(source.get(key), str) or not 1 <= len(source[key]) <= 80
|
|
62
|
+
for key in ("model", "runtime")):
|
|
63
|
+
return None
|
|
64
|
+
return {key: source[key] for key in ("kind", "model", "runtime")}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _policy(doc):
|
|
68
|
+
value = doc.get("executionPolicy")
|
|
69
|
+
if not isinstance(value, dict) or set(value) != {"retries", "repeat", "replay", "keyless",
|
|
70
|
+
"assertTimeout", "mobster"}:
|
|
71
|
+
return None
|
|
72
|
+
if (type(value["retries"]) is not int or not 0 <= value["retries"] <= 5 or
|
|
73
|
+
type(value["repeat"]) is not int or not 1 <= value["repeat"] <= 20 or
|
|
74
|
+
value["replay"] not in ("off", "fresh", "strict", "auto") or
|
|
75
|
+
type(value["keyless"]) is not bool or
|
|
76
|
+
type(value["assertTimeout"]) not in (int, float) or
|
|
77
|
+
not math.isfinite(value["assertTimeout"]) or not 0 < value["assertTimeout"] <= 600 or
|
|
78
|
+
not isinstance(value["mobster"], str) or not 1 <= len(value["mobster"]) <= 40):
|
|
79
|
+
return None
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _verdict(doc):
|
|
84
|
+
value = doc.get("exitCode")
|
|
85
|
+
return value if type(value) is int and 0 <= value <= 255 else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cells(doc):
|
|
89
|
+
found, errors = {}, []
|
|
90
|
+
checks = doc.get("checks")
|
|
91
|
+
if not isinstance(checks, list):
|
|
92
|
+
return found, ["checks_missing"]
|
|
93
|
+
for check in checks:
|
|
94
|
+
if not isinstance(check, dict) or not isinstance(check.get("results"), list):
|
|
95
|
+
errors.append("check_malformed")
|
|
96
|
+
continue
|
|
97
|
+
identity = check.get("caseId") or check.get("file")
|
|
98
|
+
if not isinstance(identity, str) or not 1 <= len(identity) <= 4096:
|
|
99
|
+
errors.append("check_identity_missing")
|
|
100
|
+
continue
|
|
101
|
+
for cell in check["results"]:
|
|
102
|
+
if not isinstance(cell, dict) or not isinstance(cell.get("deviceKey"), str) or \
|
|
103
|
+
not 1 <= len(cell["deviceKey"]) <= 256:
|
|
104
|
+
errors.append("cell_malformed")
|
|
105
|
+
continue
|
|
106
|
+
key = identity, cell["deviceKey"]
|
|
107
|
+
if key in found:
|
|
108
|
+
errors.append("duplicate_case_target")
|
|
109
|
+
found[key] = None
|
|
110
|
+
else:
|
|
111
|
+
found[key] = (check, cell)
|
|
112
|
+
return found, sorted(set(errors))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _clean(cell):
|
|
116
|
+
reason = cell.get("reason")
|
|
117
|
+
return (type(cell.get("quarantined")) is bool and cell["quarantined"] is False and
|
|
118
|
+
type(cell.get("flaky")) is bool and cell["flaky"] is False and
|
|
119
|
+
type(cell.get("replayed")) is bool and cell["replayed"] is False and
|
|
120
|
+
cell.get("healed") == [] and isinstance(cell.get("assertions"), list) and
|
|
121
|
+
(reason is None or isinstance(reason, dict)) and not cell.get("safetyStop") and
|
|
122
|
+
_status(cell) in ("passed", "failed"))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _business_fields(check, cell):
|
|
126
|
+
"""Validate one selected run against its admitted binding and complete field inventory."""
|
|
127
|
+
binding = check.get("businessBindingSha256")
|
|
128
|
+
proof = cell.get("business")
|
|
129
|
+
runs = cell.get("runs")
|
|
130
|
+
if (not _digest(binding) or not isinstance(proof, dict) or proof.get("bindingSha256") != binding or
|
|
131
|
+
proof.get("setup") != "seeded" or proof.get("status") != "observed" or
|
|
132
|
+
proof.get("cleanup") != "removed" or type(proof.get("generation")) is not int or
|
|
133
|
+
proof["generation"] < 1 or not _digest(proof.get("sourceSha256")) or
|
|
134
|
+
type(cell.get("attempts")) is not int or cell["attempts"] != 1 or
|
|
135
|
+
not isinstance(runs, list) or len(runs) != 1 or not isinstance(runs[0], dict) or
|
|
136
|
+
runs[0].get("business") != proof or runs[0].get("verdict") != cell.get("status")):
|
|
137
|
+
return None
|
|
138
|
+
fields, passed, failed = (proof.get(key) for key in ("assertionFields", "passedFields", "failedFields"))
|
|
139
|
+
if (not isinstance(fields, list) or not 1 <= len(fields) <= 20 or
|
|
140
|
+
not all(isinstance(item, str) and FIELD.fullmatch(item) for item in fields) or
|
|
141
|
+
len(set(fields)) != len(fields) or not isinstance(passed, list) or not isinstance(failed, list) or
|
|
142
|
+
any(not isinstance(item, str) for item in passed + failed) or
|
|
143
|
+
len(passed) + len(failed) != len(fields) or set(passed + failed) != set(fields)):
|
|
144
|
+
return None
|
|
145
|
+
return fields, failed, passed
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _supporting_evidence(cell, visual_required):
|
|
149
|
+
if cell.get("findings", []) != []:
|
|
150
|
+
return False
|
|
151
|
+
conditions = cell.get("conditions")
|
|
152
|
+
if conditions is not None and (not isinstance(conditions, dict) or
|
|
153
|
+
conditions.get("cleanup") not in ("restored", "not_needed") or
|
|
154
|
+
not isinstance(conditions.get("applied"), list) or
|
|
155
|
+
conditions.get("skipped") != []):
|
|
156
|
+
return False
|
|
157
|
+
assertions = cell.get("assertions")
|
|
158
|
+
if not isinstance(assertions, list) or any(
|
|
159
|
+
not isinstance(item, dict) or type(item.get("index")) is not int or item["index"] < 0 or
|
|
160
|
+
not isinstance(item.get("text"), str) or item.get("ok") is not True for item in assertions):
|
|
161
|
+
return False
|
|
162
|
+
if len({item["index"] for item in assertions}) != len(assertions):
|
|
163
|
+
return False
|
|
164
|
+
visual = cell.get("visual")
|
|
165
|
+
return (visual is None and not visual_required or
|
|
166
|
+
isinstance(visual, dict) and visual.get("ok") is True)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _case(base, candidate, key, single_repeat):
|
|
170
|
+
before_check, before = base
|
|
171
|
+
after_check, after = candidate
|
|
172
|
+
reasons = []
|
|
173
|
+
if before_check.get("provenanceDrift") or after_check.get("provenanceDrift"):
|
|
174
|
+
reasons.append("provenance_drift")
|
|
175
|
+
for field, missing, changed in (("checkSpecDigest", "check_unknown", "check_changed"),
|
|
176
|
+
("baselineDigest", "baseline_unknown", "baseline_changed"),
|
|
177
|
+
("fixtureDigest", "fixture_unknown", "fixture_changed")):
|
|
178
|
+
valid = _digest if field == "checkSpecDigest" else _frozen
|
|
179
|
+
if not valid(before_check.get(field)) or not valid(after_check.get(field)):
|
|
180
|
+
reasons.append(missing)
|
|
181
|
+
elif before_check[field] != after_check[field]:
|
|
182
|
+
reasons.append(changed)
|
|
183
|
+
old_env, new_env = _environment(before), _environment(after)
|
|
184
|
+
if old_env is None or new_env is None:
|
|
185
|
+
reasons.append("environment_unknown")
|
|
186
|
+
elif old_env != new_env:
|
|
187
|
+
reasons.append("environment_changed")
|
|
188
|
+
if not _clean(before) or not _clean(after):
|
|
189
|
+
reasons.append("verdict_not_stable")
|
|
190
|
+
comparable = not reasons
|
|
191
|
+
before_status, after_status = _status(before), _status(after)
|
|
192
|
+
transition = f"{before_status}_to_{after_status}" if comparable else None
|
|
193
|
+
proof_reasons = []
|
|
194
|
+
verified = False
|
|
195
|
+
if transition == "failed_to_passed":
|
|
196
|
+
binding = before_check.get("businessBindingSha256")
|
|
197
|
+
prior, later = _business_fields(before_check, before), _business_fields(after_check, after)
|
|
198
|
+
if (not single_repeat or _reason(before) != "business" or after.get("reason") is not None or
|
|
199
|
+
before.get("business") is None or after.get("business") is None or
|
|
200
|
+
before_check.get("fixtureDigest") == "none" or
|
|
201
|
+
before_check.get("businessBindingSha256") != after_check.get("businessBindingSha256") or
|
|
202
|
+
not _digest(binding) or not prior or not later or prior[0] != later[0] or
|
|
203
|
+
not prior[1] or later[1] or not set(prior[1]).issubset(later[2]) or
|
|
204
|
+
before["runs"][0].get("class") != "business" or after["runs"][0].get("class") is not None or
|
|
205
|
+
old_env["kind"] != "simulator" or
|
|
206
|
+
before["business"]["sourceSha256"] == after["business"]["sourceSha256"] or
|
|
207
|
+
"visualBaselineSha256" not in before_check or "visualBaselineSha256" not in after_check or
|
|
208
|
+
before_check["visualBaselineSha256"] != after_check["visualBaselineSha256"] or
|
|
209
|
+
(before_check["visualBaselineSha256"] is not None and
|
|
210
|
+
not _digest(before_check["visualBaselineSha256"])) or
|
|
211
|
+
not _supporting_evidence(before, before_check.get("visualBaselineSha256") is not None) or
|
|
212
|
+
not _supporting_evidence(after, after_check.get("visualBaselineSha256") is not None)):
|
|
213
|
+
proof_reasons.append("controlled_fixture_unproven")
|
|
214
|
+
if not _digest(before.get("appDigest")) or not _digest(after.get("appDigest")):
|
|
215
|
+
proof_reasons.append("app_artifact_unknown")
|
|
216
|
+
elif before["appDigest"] == after["appDigest"]:
|
|
217
|
+
proof_reasons.append("app_artifact_unchanged")
|
|
218
|
+
verified = not proof_reasons
|
|
219
|
+
elif transition is not None:
|
|
220
|
+
proof_reasons.append("no_failed_to_passed_transition")
|
|
221
|
+
return {"caseRef": _ref(key[0]), "targetRef": _ref(key[1]),
|
|
222
|
+
"base": {"status": before_status, "reasonClass": _reason(before)},
|
|
223
|
+
"candidate": {"status": after_status, "reasonClass": _reason(after)},
|
|
224
|
+
"comparable": comparable, "observedTransition": transition,
|
|
225
|
+
"verifiedFix": verified, "proof": {"scope": "controlled_app_state" if verified else None},
|
|
226
|
+
"reasons": sorted(set(reasons)), "proofReasons": proof_reasons}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def compare(base, candidate):
|
|
230
|
+
"""Keep both actual verdicts and every incompatibility; no green-baseline search."""
|
|
231
|
+
if not isinstance(base, dict) or not isinstance(candidate, dict):
|
|
232
|
+
raise ComparisonError("Both saved suite reports must be objects.")
|
|
233
|
+
before, before_errors = _cells(base)
|
|
234
|
+
after, after_errors = _cells(candidate)
|
|
235
|
+
old_policy, new_policy = _policy(base), _policy(candidate)
|
|
236
|
+
rows = []
|
|
237
|
+
for key in sorted(before.keys() | after.keys()):
|
|
238
|
+
if key in before and key in after and before[key] is not None and after[key] is not None:
|
|
239
|
+
rows.append(_case(before[key], after[key], key, old_policy is not None and
|
|
240
|
+
new_policy is not None and old_policy["repeat"] == new_policy["repeat"] == 1))
|
|
241
|
+
continue
|
|
242
|
+
prior = before.get(key)
|
|
243
|
+
later = after.get(key)
|
|
244
|
+
ambiguous = key in before and prior is None or key in after and later is None
|
|
245
|
+
rows.append({"caseRef": _ref(key[0]), "targetRef": _ref(key[1]),
|
|
246
|
+
"base": {"status": _status(prior[1]) if prior else None, "reasonClass": None},
|
|
247
|
+
"candidate": {"status": _status(later[1]) if later else None, "reasonClass": None},
|
|
248
|
+
"comparable": False, "observedTransition": None, "verifiedFix": False,
|
|
249
|
+
"proof": {"scope": None}, "reasons": ["duplicate_case_target" if ambiguous else
|
|
250
|
+
"case_missing_from_candidate" if key in before else "case_missing_from_base"],
|
|
251
|
+
"proofReasons": []})
|
|
252
|
+
reasons = before_errors + after_errors
|
|
253
|
+
if base.get("scenario") is not None or candidate.get("scenario") is not None:
|
|
254
|
+
reasons.append("scenario_not_comparable")
|
|
255
|
+
if base.get("schema") != reports.SCHEMA or candidate.get("schema") != reports.SCHEMA:
|
|
256
|
+
reasons.append("report_schema_unknown")
|
|
257
|
+
if type(base.get("finishedAt")) is not int or type(candidate.get("finishedAt")) is not int:
|
|
258
|
+
reasons.append("suite_not_terminal")
|
|
259
|
+
if base.get("stopReason") or candidate.get("stopReason") or base.get("stopped") or candidate.get("stopped"):
|
|
260
|
+
reasons.append("suite_stopped")
|
|
261
|
+
if _verdict(base) is None or _verdict(candidate) is None:
|
|
262
|
+
reasons.append("suite_verdict_missing")
|
|
263
|
+
if old_policy is None or new_policy is None:
|
|
264
|
+
reasons.append("execution_policy_unknown")
|
|
265
|
+
elif old_policy != new_policy:
|
|
266
|
+
reasons.append("execution_policy_changed")
|
|
267
|
+
if not rows:
|
|
268
|
+
reasons.append("no_cases")
|
|
269
|
+
if any(not row["comparable"] for row in rows):
|
|
270
|
+
reasons.append("cases_incomparable")
|
|
271
|
+
invalid = {"scenario_not_comparable", "suite_not_terminal", "suite_stopped", "report_schema_unknown",
|
|
272
|
+
"suite_verdict_missing", "execution_policy_unknown", "execution_policy_changed"}
|
|
273
|
+
if before_errors or after_errors or invalid.intersection(reasons):
|
|
274
|
+
for row in rows:
|
|
275
|
+
row.update(comparable=False, observedTransition=None, verifiedFix=False, proof={"scope": None})
|
|
276
|
+
row["reasons"] = sorted(set(row["reasons"] + ["suite_not_comparable"]))
|
|
277
|
+
verified = (not reasons and _verdict(base) == 1 and _verdict(candidate) == 0 and
|
|
278
|
+
any(row["verifiedFix"] for row in rows) and all(
|
|
279
|
+
row["candidate"]["status"] == "passed" and
|
|
280
|
+
(row["base"]["status"] != "failed" or row["verifiedFix"]) for row in rows))
|
|
281
|
+
return {"schema": SCHEMA, "baseSuite": base.get("suite") if isinstance(base.get("suite"), str) and
|
|
282
|
+
capsule.SUITE_ID.fullmatch(base["suite"]) else None,
|
|
283
|
+
"candidateSuite": candidate.get("suite") if isinstance(candidate.get("suite"), str) and
|
|
284
|
+
capsule.SUITE_ID.fullmatch(candidate["suite"]) else None,
|
|
285
|
+
"baseVerdict": _verdict(base), "candidateVerdict": _verdict(candidate),
|
|
286
|
+
"comparable": not reasons, "verifiedFix": verified,
|
|
287
|
+
"proof": {"scope": "controlled_app_state" if verified else None},
|
|
288
|
+
"reasons": sorted(set(reasons)), "cases": rows}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Compare two explicitly named local terminal suite reports."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..verify.checks import project_root
|
|
8
|
+
from .comparison import ComparisonError, compare_suites
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run(args, operands, *, out=None, err=None, compare=compare_suites):
|
|
12
|
+
out, err = out or sys.stdout, err or sys.stderr
|
|
13
|
+
|
|
14
|
+
def refuse(message):
|
|
15
|
+
if getattr(args, "json", False):
|
|
16
|
+
print(json.dumps({"ok": False, "error": message}), file=out)
|
|
17
|
+
else:
|
|
18
|
+
print(f"mobster test compare: {message}", file=err)
|
|
19
|
+
return 3
|
|
20
|
+
|
|
21
|
+
if len(operands) != 2:
|
|
22
|
+
return refuse("name a base and candidate: mobster test compare BASE_SUITE_ID CANDIDATE_SUITE_ID")
|
|
23
|
+
private = project_root(Path.cwd()) / ".mobster"
|
|
24
|
+
results = private / "test-results"
|
|
25
|
+
if private.is_symlink() or results.is_symlink():
|
|
26
|
+
return refuse("the project's test-results folder must not be a symlink")
|
|
27
|
+
try:
|
|
28
|
+
result = compare(results, *operands)
|
|
29
|
+
except (ComparisonError, OSError) as error:
|
|
30
|
+
return refuse(str(error))
|
|
31
|
+
if getattr(args, "json", False):
|
|
32
|
+
print(json.dumps(result, ensure_ascii=False), file=out)
|
|
33
|
+
else:
|
|
34
|
+
print(f"Base {result['baseSuite']}: exit {result['baseVerdict']}; "
|
|
35
|
+
f"candidate {result['candidateSuite']}: exit {result['candidateVerdict']}.", file=out)
|
|
36
|
+
proof = "yes, for controlled app state" if result["verifiedFix"] else "no"
|
|
37
|
+
print(f"Comparable: {'yes' if result['comparable'] else 'no'}; verified fix: {proof}.", file=out)
|
|
38
|
+
if result["reasons"]:
|
|
39
|
+
print("Reasons: " + ", ".join(result["reasons"]), file=out)
|
|
40
|
+
print(f"Compared {len(result['cases'])} case-target pairs. Use --json for each transition.", file=out)
|
|
41
|
+
return 0
|