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,562 @@
|
|
|
1
|
+
"""`mobster mcp`'s testing tools: simulators, or with pr a phone only when `mobster mcp --allow-device` names it."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..android import enabled as android_enabled
|
|
8
|
+
from .jobs import PATTERN_SUITE, Busy, PrJob, SuiteJob, Suites
|
|
9
|
+
|
|
10
|
+
WAIT_DEFAULT, WAIT_MAX = 30.0, 38.0
|
|
11
|
+
LOCAL = {"readOnlyHint": False, "destructiveHint": False, "openWorldHint": False}
|
|
12
|
+
PR_ONLY = frozenset({"base", "device", "build", "scheme", "testflight", "drafts",
|
|
13
|
+
"github"}) # run_tests arguments for pr
|
|
14
|
+
SUITE_ONLY = frozenset({"parallel", "retries", "tags", "fresh", "no_heal", "locales",
|
|
15
|
+
"a11y"}) # and the ones pr can't use
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestTools:
|
|
19
|
+
name = "testkit"
|
|
20
|
+
__test__ = False # not a unittest class, whatever its name
|
|
21
|
+
|
|
22
|
+
def __init__(self, *, execute=None, plan=None, clock=time.monotonic, pr_run=None):
|
|
23
|
+
self._execute, self._plan = execute, plan
|
|
24
|
+
self._pr_run = pr_run
|
|
25
|
+
self.clock = clock
|
|
26
|
+
self.suites = Suites()
|
|
27
|
+
|
|
28
|
+
def definitions(self, toolset):
|
|
29
|
+
from .mcp_comparison import definition as compare_definition
|
|
30
|
+
return [
|
|
31
|
+
{"name": "run_tests",
|
|
32
|
+
"description": "Run the project's saved checks on simulators; returns the summary and JUnit/HTML reports, "
|
|
33
|
+
"or a suite_id to wait on. With pr, test what this branch changed and answer with the "
|
|
34
|
+
"verdict, frames and GIFs.",
|
|
35
|
+
"inputSchema": {"type": "object", "properties": {
|
|
36
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
37
|
+
"description": "absolute path of a check file or a folder of checks; default the "
|
|
38
|
+
"project's .mobster/checks. With pr: the project (the folder holding "
|
|
39
|
+
".mobster or the git repository), default the server's"},
|
|
40
|
+
"sims": {"type": "array", "maxItems": 8, "items": {"type": "string", "minLength": 1,
|
|
41
|
+
"maxLength": 128},
|
|
42
|
+
"description": "simulators to run on, each a device type with an optional runtime, such "
|
|
43
|
+
"as \"iPhone 17 Pro@iOS 26.4\"; default each check's own"},
|
|
44
|
+
**({"android": {"type": "string", "maxLength": 128,
|
|
45
|
+
"description": "run on an Android emulator: an AVD name, or an empty string for any "
|
|
46
|
+
"idle emulator (experimental)"}} if android_enabled() else {}),
|
|
47
|
+
"parallel": {"type": "integer", "minimum": 1, "maximum": 4,
|
|
48
|
+
"description": "simulators running checks at once (default 1)"},
|
|
49
|
+
"retries": {"type": "integer", "minimum": 0, "maximum": 3,
|
|
50
|
+
"description": "attempts after a failure (default 1)"},
|
|
51
|
+
"tags": {"type": "array", "maxItems": 10, "items": {"type": "string", "minLength": 1,
|
|
52
|
+
"maxLength": 40},
|
|
53
|
+
"description": "run only the checks with any of these tags"},
|
|
54
|
+
"locales": {"type": "array", "maxItems": 12, "items": {"type": "string", "minLength": 2,
|
|
55
|
+
"maxLength": 12},
|
|
56
|
+
"description": "run every check once in each of these languages, such as [\"de\", "
|
|
57
|
+
"\"ja\", \"ar\"] (`mobster test --locale`): the app launches in the "
|
|
58
|
+
"language and the screens are read for truncated text, untranslated "
|
|
59
|
+
"strings and a right-to-left layout that didn't mirror"},
|
|
60
|
+
"a11y": {"type": "string", "enum": ["warn", "strict"],
|
|
61
|
+
"description": "audit the screens each check reads for accessibility (`mobster test "
|
|
62
|
+
"--a11y`): warn reports findings and passes, strict fails the check"},
|
|
63
|
+
"fresh": {"type": "boolean",
|
|
64
|
+
"description": "run every step with Smart and record the path again, instead of "
|
|
65
|
+
"replaying the recorded one with no model (default false)"},
|
|
66
|
+
"no_heal": {"type": "boolean",
|
|
67
|
+
"description": "fail a check whose screen changed instead of healing the step with "
|
|
68
|
+
"Smart (strict CI; default false)"},
|
|
69
|
+
"pr": {"type": "boolean",
|
|
70
|
+
"description": "test what this branch changed (default false); base, device, build, scheme, "
|
|
71
|
+
"testflight, drafts and github go with it, and parallel, retries, tags, fresh "
|
|
72
|
+
"and no_heal don't"},
|
|
73
|
+
"base": {"type": "string", "minLength": 1, "maxLength": 200,
|
|
74
|
+
"description": "with pr: the branch the PR merges into (default origin/main)"},
|
|
75
|
+
"device": {"type": "string", "minLength": 1, "maxLength": 128,
|
|
76
|
+
"description": "with pr: run on this iPhone instead of simulators: a name from "
|
|
77
|
+
"list_devices that `mobster mcp --allow-device` names"},
|
|
78
|
+
"build": {"type": "boolean", "description": "with pr: build the app first (default true)"},
|
|
79
|
+
"scheme": {"type": "string", "minLength": 1, "maxLength": 200,
|
|
80
|
+
"description": "with pr: the Xcode scheme to build (default the only one)"},
|
|
81
|
+
"testflight": {"type": "string", "minLength": 1, "maxLength": 200,
|
|
82
|
+
"description": "with pr and device: the bundle ID of the user's TestFlight build to "
|
|
83
|
+
"test; only one `mobster mcp --allow-testflight` names"},
|
|
84
|
+
"drafts": {"type": "string", "enum": ["run", "write", "off"],
|
|
85
|
+
"description": "with pr: draft checks for uncovered screens and run them (default), only "
|
|
86
|
+
"write them, or neither"},
|
|
87
|
+
"github": {"type": "string", "enum": ["check", "comment", "off"],
|
|
88
|
+
"description": "with pr: put the verdict on the pull request with Mobster Checks, the "
|
|
89
|
+
"GitHub App: a check run (default), the check run and one comment, or "
|
|
90
|
+
"nothing"},
|
|
91
|
+
"suite_id": {"type": "string", "pattern": PATTERN_SUITE,
|
|
92
|
+
"description": "a suite run_tests started: wait for it instead of starting one"},
|
|
93
|
+
"timeout_s": {"type": "number", "minimum": 1, "maximum": WAIT_MAX,
|
|
94
|
+
"description": f"how long to wait for the suite (default {WAIT_DEFAULT:g})"}},
|
|
95
|
+
"required": [], "additionalProperties": False}},
|
|
96
|
+
{"name": "test_history",
|
|
97
|
+
"description": "Read the project's test history: each check's last 20 results per device, pass rate "
|
|
98
|
+
"and flake score, flaky first.",
|
|
99
|
+
"inputSchema": {"type": "object", "properties": {
|
|
100
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
101
|
+
"description": "absolute path of the project (the folder holding .mobster); default the "
|
|
102
|
+
"server's"},
|
|
103
|
+
"check": {"type": "string", "minLength": 1, "maxLength": 200,
|
|
104
|
+
"description": "only checks whose name or file contains this text"},
|
|
105
|
+
"flaky_only": {"type": "boolean", "description": "only checks the history calls flaky"},
|
|
106
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 200,
|
|
107
|
+
"description": "most rows to return (default 50)"}},
|
|
108
|
+
"required": [], "additionalProperties": False}},
|
|
109
|
+
{"name": "import_maestro",
|
|
110
|
+
"description": "Turn Maestro flows into checks in the project's .mobster/checks. A check that exists is "
|
|
111
|
+
"kept unless force; dry_run writes nothing; a flow Mobster can't run is refused.",
|
|
112
|
+
"inputSchema": {"type": "object", "properties": {
|
|
113
|
+
"flows": {"type": "array", "minItems": 1, "maxItems": 20,
|
|
114
|
+
"items": {"type": "string", "minLength": 1, "maxLength": 4096},
|
|
115
|
+
"description": "absolute paths of Maestro flow files or folders of them"},
|
|
116
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
117
|
+
"description": "absolute path of the project (the folder holding .mobster); default the "
|
|
118
|
+
"server's"},
|
|
119
|
+
"bundle": {"type": "string", "minLength": 3, "maxLength": 155,
|
|
120
|
+
"description": "the app's bundle ID, when the flows don't carry an appId"},
|
|
121
|
+
"app": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
122
|
+
"description": "absolute path of the simulator build (.app) the checks launch"},
|
|
123
|
+
"skip_unsupported": {"type": "boolean",
|
|
124
|
+
"description": "drop a command Mobster can't run instead of refusing the flow"},
|
|
125
|
+
"dry_run": {"type": "boolean", "description": "write nothing; say what would be written"},
|
|
126
|
+
"force": {"type": "boolean", "description": "replace a check that exists"}},
|
|
127
|
+
"required": ["flows"], "additionalProperties": False}},
|
|
128
|
+
{"name": "test_schedule",
|
|
129
|
+
"description": "list: the project's nightly run, when it runs and last night's verdict. run: run it "
|
|
130
|
+
"now on simulators; a schedule that names an iPhone is refused.",
|
|
131
|
+
"inputSchema": {"type": "object", "properties": {
|
|
132
|
+
"action": {"type": "string", "enum": ["list", "run"], "description": "list (default) or run"},
|
|
133
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
134
|
+
"description": "absolute path of the project (the folder holding .mobster); default the "
|
|
135
|
+
"server's"},
|
|
136
|
+
"suite_id": {"type": "string", "pattern": PATTERN_SUITE,
|
|
137
|
+
"description": "with run: a suite this tool started, to wait for it again"},
|
|
138
|
+
"timeout_s": {"type": "number", "minimum": 1, "maximum": WAIT_MAX,
|
|
139
|
+
"description": f"with run: how long to wait (default {WAIT_DEFAULT:g})"}},
|
|
140
|
+
"required": [], "additionalProperties": False}},
|
|
141
|
+
{"name": "repro_crash",
|
|
142
|
+
"description": "Turn a crash (.ips, Sentry event JSON, TestFlight or Xcode crash log, or a brief) into a "
|
|
143
|
+
"check that reproduces it on a simulator, recorded. Run it after your fix: it passes "
|
|
144
|
+
"once the app stops crashing.",
|
|
145
|
+
"inputSchema": {"type": "object", "properties": {
|
|
146
|
+
"ips_path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
147
|
+
"description": "absolute path of a .ips crash report"},
|
|
148
|
+
"sentry_json": {"type": "string", "minLength": 2, "maxLength": 1000000,
|
|
149
|
+
"description": "a Sentry event as JSON text, as its API or MCP returns it"},
|
|
150
|
+
"crash_log": {"type": "string", "minLength": 1, "maxLength": 1000000,
|
|
151
|
+
"description": "a TestFlight or Xcode Organizer crash log in Apple's text format"},
|
|
152
|
+
"brief": {"type": "object", "additionalProperties": False, "properties": {
|
|
153
|
+
"bundle": {"type": "string", "maxLength": 255}, "type": {"type": "string", "maxLength": 80},
|
|
154
|
+
"signal": {"type": "string", "maxLength": 20}, "message": {"type": "string", "maxLength": 300},
|
|
155
|
+
"version": {"type": "string", "maxLength": 40}, "device": {"type": "string", "maxLength": 60},
|
|
156
|
+
"os": {"type": "string", "maxLength": 60}, "link": {"type": "string", "maxLength": 2000},
|
|
157
|
+
"steps": {"type": "array", "maxItems": 20, "items": {"type": "string", "maxLength": 200}},
|
|
158
|
+
"frames": {"type": "array", "maxItems": 12, "items": {
|
|
159
|
+
"type": "object", "additionalProperties": False, "properties": {
|
|
160
|
+
"symbol": {"type": "string", "maxLength": 200}, "own": {"type": "boolean"},
|
|
161
|
+
"file": {"type": "string", "maxLength": 120}, "line": {"type": "integer"}}}}},
|
|
162
|
+
"description": "a crash from Crashlytics, Datadog or anywhere else: the app's bundle, "
|
|
163
|
+
"exception type, frames (symbol, own), steps in plain words, and a link"},
|
|
164
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
165
|
+
"description": "absolute path of the project (the folder holding .mobster); default the "
|
|
166
|
+
"server's"},
|
|
167
|
+
"bundle": {"type": "string", "minLength": 1, "maxLength": 255,
|
|
168
|
+
"description": "the app's bundle ID when the crash doesn't name it"},
|
|
169
|
+
"app": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
170
|
+
"description": "absolute path of a .app built for the simulator, to install first"},
|
|
171
|
+
"launch_args": {"type": "array", "maxItems": 20, "items": {"type": "string", "minLength": 1,
|
|
172
|
+
"maxLength": 200},
|
|
173
|
+
"description": "launch arguments the crash needs"},
|
|
174
|
+
"link": {"type": "string", "minLength": 1, "maxLength": 2000,
|
|
175
|
+
"description": "a link that opens the app on the crashing screen"},
|
|
176
|
+
"sims": {"type": "array", "maxItems": 1, "items": {"type": "string", "minLength": 1,
|
|
177
|
+
"maxLength": 128},
|
|
178
|
+
"description": "the simulator to use, such as \"iPhone 17 Pro@iOS 26.4\""},
|
|
179
|
+
"suite_id": {"type": "string", "pattern": PATTERN_SUITE,
|
|
180
|
+
"description": "wait for the reproduction a first call started; pass it alone"},
|
|
181
|
+
"timeout_s": {"type": "number", "minimum": 1, "maximum": WAIT_MAX,
|
|
182
|
+
"description": "seconds to wait for the answer (default 30)"}},
|
|
183
|
+
"required": [], "additionalProperties": False}},
|
|
184
|
+
{"name": "review_heals",
|
|
185
|
+
"description": "List the healed steps waiting for review (each with the step before and after, and "
|
|
186
|
+
"frames), or settle them: accept keeps them; reject restores the recorded steps. A heal "
|
|
187
|
+
"never changes a check's expectations.",
|
|
188
|
+
"inputSchema": {"type": "object", "properties": {
|
|
189
|
+
"path": {"type": "string", "minLength": 1, "maxLength": 4096,
|
|
190
|
+
"description": "absolute path of the project's .mobster/checks folder or a check file; "
|
|
191
|
+
"default the project's"},
|
|
192
|
+
"action": {"type": "string", "enum": ["list", "accept", "reject"],
|
|
193
|
+
"description": "list (default), accept or reject"},
|
|
194
|
+
"checks": {"type": "array", "maxItems": 50, "items": {"type": "string", "minLength": 1,
|
|
195
|
+
"maxLength": 4096},
|
|
196
|
+
"description": "absolute paths of the check files to accept or reject; default every "
|
|
197
|
+
"check with a heal waiting"}},
|
|
198
|
+
"required": [], "additionalProperties": False}},
|
|
199
|
+
] + [compare_definition()]
|
|
200
|
+
|
|
201
|
+
def annotations(self):
|
|
202
|
+
# run_tests with pr can post the verdict on the pull request (github_checks.py): it reaches GitHub.
|
|
203
|
+
return {"run_tests": {**LOCAL, "openWorldHint": True}, "review_heals": dict(LOCAL),
|
|
204
|
+
"test_history": {"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True,
|
|
205
|
+
"openWorldHint": False},
|
|
206
|
+
"import_maestro": dict(LOCAL), "test_schedule": dict(LOCAL), "repro_crash": dict(LOCAL),
|
|
207
|
+
"compare_test_suites": {"readOnlyHint": True, "destructiveHint": False,
|
|
208
|
+
"idempotentHint": True, "openWorldHint": False}}
|
|
209
|
+
|
|
210
|
+
def instructions(self):
|
|
211
|
+
# One sentence, at most 300 characters: ToolSet.instructions leaves out a longer one.
|
|
212
|
+
return ("run_tests runs the project's saved checks on simulators (JUnit, HTML), replaying recorded steps with "
|
|
213
|
+
"no model once a check passed, or with pr tests what the branch changed; repro_crash turns a crash "
|
|
214
|
+
"report into a check that reproduces it.")
|
|
215
|
+
|
|
216
|
+
def call(self, name, arguments, call, toolset):
|
|
217
|
+
if name == "compare_test_suites":
|
|
218
|
+
from .mcp_comparison import call as compare
|
|
219
|
+
return compare(arguments, toolset)
|
|
220
|
+
if name == "run_tests":
|
|
221
|
+
return self.run_tests(arguments, call, toolset)
|
|
222
|
+
if name == "repro_crash":
|
|
223
|
+
return self.repro_crash(arguments, call, toolset)
|
|
224
|
+
if name == "review_heals":
|
|
225
|
+
return self.review_heals(arguments, toolset)
|
|
226
|
+
if name == "test_history":
|
|
227
|
+
return self.test_history(arguments, toolset)
|
|
228
|
+
if name == "import_maestro":
|
|
229
|
+
return self.import_maestro(arguments, toolset)
|
|
230
|
+
if name == "test_schedule":
|
|
231
|
+
return self.test_schedule(arguments, call, toolset)
|
|
232
|
+
from ..mcp_server.tools import ToolError
|
|
233
|
+
raise ToolError(f"Mobster has no tool named {name}.")
|
|
234
|
+
|
|
235
|
+
def run_tests(self, args, call, toolset):
|
|
236
|
+
from ..mcp_server.tools import ToolError
|
|
237
|
+
pr = bool(args.get("pr"))
|
|
238
|
+
stray = (PR_ONLY if not pr else SUITE_ONLY) & args.keys()
|
|
239
|
+
if stray and not args.get("suite_id"):
|
|
240
|
+
raise ToolError(f"{', '.join(sorted(stray))} {'go with pr: true' if not pr else 'go without pr'}.")
|
|
241
|
+
key = self._key(toolset)
|
|
242
|
+
return self._start_or_wait(args, call, toolset, (lambda: PrJob(
|
|
243
|
+
*self._pr_options(args, toolset), key=key, run=self._pr_run)) if pr else (lambda: SuiteJob(
|
|
244
|
+
self._options(args, toolset), key=key, execute=self._execute, plan=self._plan)))
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def _key(toolset):
|
|
248
|
+
"""Smart's key for the suite's heals: the server's, never with --keyless."""
|
|
249
|
+
return None if getattr(toolset, "keyless", False) else getattr(toolset, "_key", None)
|
|
250
|
+
|
|
251
|
+
def repro_crash(self, args, call, toolset):
|
|
252
|
+
from ..mcp_server.tools import ToolError
|
|
253
|
+
from . import repro
|
|
254
|
+
if args.get("suite_id"):
|
|
255
|
+
return self._start_or_wait(args, call, toolset, None)
|
|
256
|
+
sources = [(key, kind) for key, kind in (("ips_path", "ips"), ("sentry_json", "sentry"),
|
|
257
|
+
("crash_log", "text"), ("brief", "brief")) if args.get(key)]
|
|
258
|
+
if len(sources) != 1:
|
|
259
|
+
raise ToolError("Pass the crash as exactly one of ips_path, sentry_json, crash_log or brief.")
|
|
260
|
+
root = self._project(args, toolset)
|
|
261
|
+
key, kind = sources[0]
|
|
262
|
+
if key == "ips_path" and not Path(args[key]).is_absolute():
|
|
263
|
+
raise ToolError("ips_path must be absolute, because MCP clients start servers in different folders.")
|
|
264
|
+
if args.get("app") and not Path(args["app"]).is_absolute():
|
|
265
|
+
raise ToolError("app must be an absolute path.")
|
|
266
|
+
try:
|
|
267
|
+
content = Path(args[key]) if key == "ips_path" else args[key]
|
|
268
|
+
if kind == "sentry":
|
|
269
|
+
content = json.loads(content)
|
|
270
|
+
brief = repro.from_content(kind, content)
|
|
271
|
+
check, _, _ = repro.write(brief, root, bundle=args.get("bundle"), app=args.get("app"),
|
|
272
|
+
args=tuple(args.get("launch_args") or ()), link=args.get("link"))
|
|
273
|
+
except ValueError:
|
|
274
|
+
raise ToolError("sentry_json isn't JSON. Pass the event's text.") from None
|
|
275
|
+
except repro.ReproError as error:
|
|
276
|
+
raise ToolError(str(error)) from None
|
|
277
|
+
from .api import Options
|
|
278
|
+
options = Options(paths=(str(check),), sims=tuple(args.get("sims") or ()), retries=0, video=True,
|
|
279
|
+
keyless=bool(getattr(toolset, "keyless", False)), command="repro_crash (MCP)")
|
|
280
|
+
return self._start_or_wait(args, call, toolset, lambda: ReproJob(
|
|
281
|
+
options, brief, root, check, key=self._key(toolset), execute=self._execute, plan=self._plan))
|
|
282
|
+
|
|
283
|
+
def _start_or_wait(self, args, call, toolset, make):
|
|
284
|
+
from ..mcp_server.tools import ToolError
|
|
285
|
+
# Every wait ends by WAIT_MAX from the request's arrival, inside the ToolSet's 44 s budget for the call.
|
|
286
|
+
started = getattr(call, "started", None)
|
|
287
|
+
started = started if isinstance(started, (int, float)) and not isinstance(started, bool) else time.monotonic()
|
|
288
|
+
deadline = started + min(float(args.get("timeout_s", WAIT_DEFAULT)), WAIT_MAX)
|
|
289
|
+
|
|
290
|
+
def left():
|
|
291
|
+
return max(0.0, deadline - time.monotonic())
|
|
292
|
+
if args.get("suite_id"):
|
|
293
|
+
if set(args) - {"suite_id", "timeout_s"}:
|
|
294
|
+
raise ToolError("Pass suite_id alone to wait for a suite, or leave it out to start one.")
|
|
295
|
+
job = self.suites.get(args["suite_id"])
|
|
296
|
+
if job is None:
|
|
297
|
+
raise ToolError(f"This server has no suite {args['suite_id']}. Start one with run_tests.")
|
|
298
|
+
job.wait(left(), getattr(call, "stop", None))
|
|
299
|
+
return job.result()
|
|
300
|
+
try:
|
|
301
|
+
job = self.suites.start(make)
|
|
302
|
+
except Busy as busy:
|
|
303
|
+
raise ToolError(f"Suite {busy.job.suite_id} is still running. Call run_tests "
|
|
304
|
+
f"with suite_id {busy.job.suite_id} to wait for it.") from None
|
|
305
|
+
job.wait(left(), getattr(call, "stop", None))
|
|
306
|
+
return job.result()
|
|
307
|
+
|
|
308
|
+
def _options(self, args, toolset):
|
|
309
|
+
from ..mcp_server.tools import ToolError
|
|
310
|
+
from .api import Options
|
|
311
|
+
path = args.get("path")
|
|
312
|
+
if path is not None and not Path(path).is_absolute():
|
|
313
|
+
raise ToolError("path must be absolute, because MCP clients start servers in different folders.")
|
|
314
|
+
if path is None:
|
|
315
|
+
root = Path(toolset.runs_dir).resolve().parent.parent # <project>/.mobster/runs
|
|
316
|
+
path = str(root / ".mobster" / "checks")
|
|
317
|
+
if not Path(path).is_dir():
|
|
318
|
+
raise ToolError("Pass path: the absolute path of the project's .mobster/checks folder or a check "
|
|
319
|
+
"file.")
|
|
320
|
+
from .api import UsageError, parse_locales
|
|
321
|
+
try:
|
|
322
|
+
locales = parse_locales(args.get("locales"))
|
|
323
|
+
except UsageError as error:
|
|
324
|
+
raise ToolError(str(error)) from None
|
|
325
|
+
android = args.get("android")
|
|
326
|
+
if android is not None:
|
|
327
|
+
self._check_android(android, toolset)
|
|
328
|
+
return Options(paths=(path,), sims=tuple(args.get("sims") or ()), android=android,
|
|
329
|
+
parallel=int(args.get("parallel", 1)),
|
|
330
|
+
retries=args.get("retries"), tags=tuple(args.get("tags") or ()),
|
|
331
|
+
keyless=bool(getattr(toolset, "keyless", False)),
|
|
332
|
+
fresh=bool(args.get("fresh")), no_heal=bool(args.get("no_heal")),
|
|
333
|
+
a11y=args.get("a11y"), locales=locales, command="run_tests (MCP)")
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _check_android(name, toolset):
|
|
337
|
+
"""An Android phone (not an emulator) is driven only when the user's server command names it."""
|
|
338
|
+
from ..android.records import records
|
|
339
|
+
from ..mcp_server.tools import ToolError
|
|
340
|
+
if not android_enabled():
|
|
341
|
+
raise ToolError("Android is off: the user sets MOBSTER_EXPERIMENTAL_ANDROID=1 to turn it on.")
|
|
342
|
+
for record in records():
|
|
343
|
+
if name in (record["serial"], record["id"]) and not record["emulator"]:
|
|
344
|
+
allowed = getattr(toolset, "allow_devices", None) or frozenset()
|
|
345
|
+
if not {record["id"].casefold(), record["serial"].casefold()} & set(allowed):
|
|
346
|
+
raise ToolError(f"“{record['name']}” is an Android phone: run_tests drives it only when the user "
|
|
347
|
+
"starts the server with `mobster mcp --allow-device` naming it.")
|
|
348
|
+
|
|
349
|
+
def _project(self, args, toolset):
|
|
350
|
+
from ..mcp_server.tools import ToolError
|
|
351
|
+
path = args.get("path")
|
|
352
|
+
if path is not None and not Path(path).is_absolute():
|
|
353
|
+
raise ToolError("path must be absolute, because MCP clients start servers in different folders.")
|
|
354
|
+
if path is None:
|
|
355
|
+
path = Path(toolset.runs_dir).resolve().parent.parent # <project>/.mobster/runs
|
|
356
|
+
if not Path(path).is_dir():
|
|
357
|
+
raise ToolError("Pass path: the absolute path of the project, the folder holding .mobster.")
|
|
358
|
+
return Path(path)
|
|
359
|
+
|
|
360
|
+
def _pr_options(self, args, toolset):
|
|
361
|
+
from ..mcp_server.tools import ToolError
|
|
362
|
+
from .api import Options
|
|
363
|
+
from .prrun import PrOptions
|
|
364
|
+
root = self._project(args, toolset)
|
|
365
|
+
device, testflight = args.get("device"), args.get("testflight")
|
|
366
|
+
if testflight and not device:
|
|
367
|
+
raise ToolError("testflight goes with device: a TestFlight build is on an iPhone, not a simulator.")
|
|
368
|
+
if device:
|
|
369
|
+
from ..device_targets import named_device
|
|
370
|
+
from ..devices import AmbiguousDevice
|
|
371
|
+
try:
|
|
372
|
+
record = named_device(device)
|
|
373
|
+
except AmbiguousDevice as error:
|
|
374
|
+
raise ToolError(str(error)) from None
|
|
375
|
+
if record is None:
|
|
376
|
+
raise ToolError(f"No device is named {device!r}. Call list_devices.")
|
|
377
|
+
if record.get("kind") != "simulator":
|
|
378
|
+
names = {str(record.get(key) or "").strip().casefold() for key in ("id", "udid", "name")}
|
|
379
|
+
allowed = getattr(toolset, "allow_devices", None)
|
|
380
|
+
if not allowed or not names & allowed:
|
|
381
|
+
raise ToolError(f"pr runs on “{record.get('name') or device}” only when the user starts "
|
|
382
|
+
"the server with `mobster mcp --allow-device` naming it. Ask the user, or run "
|
|
383
|
+
"on a simulator.")
|
|
384
|
+
if testflight and testflight not in getattr(toolset, "allow_testflight", frozenset()):
|
|
385
|
+
raise ToolError(f"Mobster tests the TestFlight build of {testflight} only when the user starts the server "
|
|
386
|
+
"with `mobster mcp --allow-testflight` naming it. Ask the user.")
|
|
387
|
+
options = Options(sims=tuple(args.get("sims") or ()), devices=(device,) if device else (),
|
|
388
|
+
keyless=bool(getattr(toolset, "keyless", False)), command="run_tests pr (MCP)",
|
|
389
|
+
testflight=(testflight,) if testflight else ())
|
|
390
|
+
pr_options = PrOptions(base=args.get("base"), build=bool(args.get("build", True)), scheme=args.get("scheme"),
|
|
391
|
+
drafts=args.get("drafts") or "run", github=args.get("github") or "check")
|
|
392
|
+
return pr_options, options, root
|
|
393
|
+
|
|
394
|
+
def test_history(self, args, toolset):
|
|
395
|
+
from ..mcp_server.protocol import ToolResult
|
|
396
|
+
from . import history
|
|
397
|
+
root = self._project(args, toolset)
|
|
398
|
+
found = history.report(root)
|
|
399
|
+
needle = (args.get("check") or "").casefold()
|
|
400
|
+
rows = [row for row in found["checks"]
|
|
401
|
+
if (not needle or needle in str(row.get("name")).casefold()
|
|
402
|
+
or needle in str(row.get("file")).casefold())
|
|
403
|
+
and (not args.get("flaky_only") or row.get("state") == "flaky")]
|
|
404
|
+
limit = int(args.get("limit", 50))
|
|
405
|
+
shown = rows[:limit]
|
|
406
|
+
lines = [f"{len(rows)} check/device histories" + (f", showing {limit}" if len(rows) > limit else "") + "."
|
|
407
|
+
+ ("" if found["ok"] else " The history file couldn't be read, so it starts again with the next run.")]
|
|
408
|
+
for row in shown:
|
|
409
|
+
lines.append(f"- {row.get('name')} on {row.get('device')}: {row.get('state')}, "
|
|
410
|
+
f"{row.get('passed')}/{row.get('runs')} passed")
|
|
411
|
+
return ToolResult("\n".join(lines[:60]), {"ok": found["ok"], "total": len(rows), "checks": shown})
|
|
412
|
+
|
|
413
|
+
def import_maestro(self, args, toolset):
|
|
414
|
+
from ..mcp_server.protocol import ToolResult
|
|
415
|
+
from ..mcp_server.tools import ToolError
|
|
416
|
+
from . import maestro
|
|
417
|
+
from .cli import _relative, _write_imports, import_text
|
|
418
|
+
root = self._project(args, toolset)
|
|
419
|
+
sources = list(args["flows"])
|
|
420
|
+
if any(not Path(item).is_absolute() for item in sources):
|
|
421
|
+
raise ToolError("flows must be absolute paths, because MCP clients start servers in different folders.")
|
|
422
|
+
app = args.get("app")
|
|
423
|
+
if app is not None and (not Path(app).is_absolute() or not str(app).endswith(".app")):
|
|
424
|
+
raise ToolError("app must be the absolute path of a simulator build (.app).")
|
|
425
|
+
bundle = args.get("bundle")
|
|
426
|
+
if bundle:
|
|
427
|
+
from ..state import validate_bundle_id
|
|
428
|
+
try:
|
|
429
|
+
validate_bundle_id(bundle)
|
|
430
|
+
except ValueError:
|
|
431
|
+
raise ToolError(f"bundle {bundle!r} is not a bundle ID such as com.example.app.") from None
|
|
432
|
+
folder = root / ".mobster" / "checks"
|
|
433
|
+
try:
|
|
434
|
+
results = maestro.convert_tree([Path(item) for item in sources], bundle=bundle, app_path=app,
|
|
435
|
+
skip_unsupported=bool(args.get("skip_unsupported")))
|
|
436
|
+
except maestro.MaestroError as error:
|
|
437
|
+
raise ToolError(str(error)) from None
|
|
438
|
+
dry, force = bool(args.get("dry_run")), bool(args.get("force"))
|
|
439
|
+
written, refused = _write_imports(results, folder, root, dry=dry, force=force)
|
|
440
|
+
return ToolResult(import_text(results, folder, root, dry=dry),
|
|
441
|
+
{"flows": [item.to_dict() for item in results], "folder": _relative(folder, root),
|
|
442
|
+
"written": written, "refused": refused, "dry_run": dry})
|
|
443
|
+
|
|
444
|
+
SCHEDULE_FLAGS = {"--sim": "sims", "--tag": "tags"}
|
|
445
|
+
|
|
446
|
+
def test_schedule(self, args, call, toolset):
|
|
447
|
+
from ..devkit import nightly
|
|
448
|
+
from ..mcp_server.protocol import ToolResult
|
|
449
|
+
from ..mcp_server.tools import ToolError
|
|
450
|
+
action = args.get("action") or "list"
|
|
451
|
+
if args.get("suite_id") and action != "run":
|
|
452
|
+
raise ToolError("suite_id goes with run.")
|
|
453
|
+
root = self._project(args, toolset)
|
|
454
|
+
state = nightly.status(root)
|
|
455
|
+
if action == "list":
|
|
456
|
+
text = (f"The nightly run is on at {state['at']}"
|
|
457
|
+
+ (f" ({' '.join(state['flags'])})" if state["flags"] else "")
|
|
458
|
+
+ "." if state["on"] else "No nightly run is set up for this project. Turn it on: "
|
|
459
|
+
"mobster test nightly on --at 02:00")
|
|
460
|
+
if state["last"]:
|
|
461
|
+
text += f" Last run {state['last'].get('date')}: {state['last'].get('headline')}."
|
|
462
|
+
return ToolResult(text, state)
|
|
463
|
+
if args.get("suite_id"):
|
|
464
|
+
return self._start_or_wait({k: v for k, v in args.items() if k in ("suite_id", "timeout_s")}, call, toolset,
|
|
465
|
+
None)
|
|
466
|
+
if not state["on"]:
|
|
467
|
+
raise ToolError("No nightly run is set up for this project, so there is no schedule to run. Turn it on: "
|
|
468
|
+
"mobster test nightly on --at 02:00")
|
|
469
|
+
phone = next((flag for flag in state["flags"] if flag in ("--device", "--testflight")), None)
|
|
470
|
+
if phone:
|
|
471
|
+
raise ToolError(f"This schedule names an iPhone ({phone}), and an agent never reaches a phone from here. "
|
|
472
|
+
"Run it yourself: mobster test nightly run")
|
|
473
|
+
options = self._schedule_options(state["flags"], root, toolset)
|
|
474
|
+
return self._start_or_wait({k: v for k, v in args.items() if k == "timeout_s"}, call, toolset,
|
|
475
|
+
lambda: SuiteJob(options, root=root, key=self._key(toolset), execute=self._execute,
|
|
476
|
+
plan=self._plan))
|
|
477
|
+
|
|
478
|
+
def _schedule_options(self, flags, root, toolset):
|
|
479
|
+
from ..mcp_server.tools import ToolError
|
|
480
|
+
from .api import Options
|
|
481
|
+
sims, tags, parallel, retries, keyless, spread = [], [], 1, None, False, False
|
|
482
|
+
it = iter(flags)
|
|
483
|
+
for flag in it:
|
|
484
|
+
if flag == "--sim":
|
|
485
|
+
sims.append(next(it, ""))
|
|
486
|
+
elif flag == "--tag":
|
|
487
|
+
tags.append(next(it, ""))
|
|
488
|
+
elif flag == "--parallel":
|
|
489
|
+
parallel = int(next(it, "1") or 1)
|
|
490
|
+
elif flag == "--retries":
|
|
491
|
+
retries = int(next(it, "0") or 0)
|
|
492
|
+
elif flag == "--keyless":
|
|
493
|
+
keyless = True
|
|
494
|
+
elif flag == "--spread":
|
|
495
|
+
spread = True
|
|
496
|
+
else:
|
|
497
|
+
raise ToolError(f"The schedule has a flag ({flag}) this tool doesn't run. Run it yourself: "
|
|
498
|
+
"mobster test nightly run")
|
|
499
|
+
return Options(paths=(str(root / ".mobster" / "checks"),), sims=tuple(sims), tags=tuple(tags),
|
|
500
|
+
parallel=min(parallel, 4), retries=retries, spread=spread,
|
|
501
|
+
keyless=keyless or bool(getattr(toolset, "keyless", False)),
|
|
502
|
+
command="test_schedule run (MCP)")
|
|
503
|
+
|
|
504
|
+
def review_heals(self, args, toolset):
|
|
505
|
+
from ..mcp_server.protocol import ToolResult
|
|
506
|
+
from ..mcp_server.tools import ToolError
|
|
507
|
+
from ..replay_core.heal import HealError
|
|
508
|
+
from ..verify.checks import project_root
|
|
509
|
+
from . import heal_review
|
|
510
|
+
from .discover import DiscoveryError
|
|
511
|
+
action = args.get("action") or "list"
|
|
512
|
+
path = args.get("path")
|
|
513
|
+
if path is not None and not Path(path).is_absolute():
|
|
514
|
+
raise ToolError("path must be absolute, because MCP clients start servers in different folders.")
|
|
515
|
+
root = project_root(Path(path)) if path else Path(toolset.runs_dir).resolve().parent.parent
|
|
516
|
+
given = list(args.get("checks") or ())
|
|
517
|
+
if any(not Path(item).is_absolute() for item in given):
|
|
518
|
+
raise ToolError("checks must be absolute paths of check files.")
|
|
519
|
+
if given and action == "list":
|
|
520
|
+
raise ToolError("checks go with accept or reject; list shows every check's heals.")
|
|
521
|
+
try:
|
|
522
|
+
found = heal_review.pending(root, given or ([path] if path else []))
|
|
523
|
+
except (DiscoveryError, HealError) as error:
|
|
524
|
+
raise ToolError(str(error)) from None
|
|
525
|
+
if action == "list":
|
|
526
|
+
items = [{key: row[key] for key in ("check", "diff", "heals")} for row in heal_review.rows(found)]
|
|
527
|
+
lines = [f"{sum(len(item['heals']) for item in items)} healed step(s) wait for review."]
|
|
528
|
+
for item in items:
|
|
529
|
+
for heal in item["heals"]:
|
|
530
|
+
lines.append(f"- {Path(item['check']).name} step {heal['step']}: {heal['from']} → {heal['to']}"
|
|
531
|
+
+ (f" ({heal['why']})" if heal.get("why") else ""))
|
|
532
|
+
if items:
|
|
533
|
+
lines.append("Accept with action accept, or keep the check failing there with action reject.")
|
|
534
|
+
return ToolResult("\n".join(lines), {"heals": items})
|
|
535
|
+
settled = [{"check": str(entry.path), "steps": count}
|
|
536
|
+
for entry, count in heal_review.settle(root, found, action)]
|
|
537
|
+
word = "Accepted" if action == "accept" else "Kept failing"
|
|
538
|
+
text = (f"{word}: " + ", ".join(f"{Path(item['check']).name} ({item['steps']})" for item in settled)
|
|
539
|
+
if settled else "No heals wait for review.")
|
|
540
|
+
return ToolResult(text, {"action": action, "settled": settled})
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class ReproJob(SuiteJob):
|
|
544
|
+
"""repro_crash's one run of the repro check, judged against the crash it came from."""
|
|
545
|
+
|
|
546
|
+
kind = "repro_crash"
|
|
547
|
+
|
|
548
|
+
def __init__(self, options, brief, root, check, *, key=None, execute=None, plan=None):
|
|
549
|
+
super().__init__(options, root=root, key=key, execute=execute, plan=plan)
|
|
550
|
+
self.brief, self.check = brief, check
|
|
551
|
+
|
|
552
|
+
def result(self):
|
|
553
|
+
from ..mcp_server.protocol import ToolResult
|
|
554
|
+
from . import repro
|
|
555
|
+
if self.error is not None or not self.finished.is_set():
|
|
556
|
+
return super().result()
|
|
557
|
+
found = repro.save(self.brief, self.root, repro.outcome(self.brief, self.doc, check=self.check,
|
|
558
|
+
folder=self.paths.get("folder")))
|
|
559
|
+
return ToolResult("\n".join(repro.summary_lines(self.brief, found)),
|
|
560
|
+
{"status": "finished", "suite_id": self.suite_id, "reproduction": found["status"],
|
|
561
|
+
"brief": self.brief.public(), **{k: found[k] for k in ("check", "stepsTried", "crash",
|
|
562
|
+
"video", "report", "note")}})
|