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,467 @@
|
|
|
1
|
+
"""`mobster doctor --fix`: do the fix each finding's row carries (``states.ROWS``), and say what it did and what it
|
|
2
|
+
could not.
|
|
3
|
+
|
|
4
|
+
The row decides how far Mobster goes, not this module:
|
|
5
|
+
|
|
6
|
+
- ``Auto`` runs unasked: start Mobster's helper, refresh one that has expired. It touches only Mobster's own state, and
|
|
7
|
+
only on a phone no other Mobster is running a task on.
|
|
8
|
+
- ``Ask`` names the process or command first and runs only on a yes: free a port, stop a service that doesn't answer.
|
|
9
|
+
Without a terminal it names it and does nothing. It never signals a process whose command line changed since it asked,
|
|
10
|
+
never uses SIGKILL without a second question, and never kills by name.
|
|
11
|
+
- ``Guided`` hands over to the flow that asks its own questions (``mobster login``, ``mobster setup``), in a terminal.
|
|
12
|
+
- ``Yours``, ``Run`` and ``Wait`` are reported, not run: the iPhone's own taps, a command Mobster doesn't run for you, a
|
|
13
|
+
state that clears by itself.
|
|
14
|
+
|
|
15
|
+
Every action converges: run twice, or after a crash, it reads the state and does only what is left. A phone action whose
|
|
16
|
+
outcome is unknown is never retried here; the helper start reads the phone's answer and stops.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import functools
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import signal
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import NamedTuple
|
|
27
|
+
|
|
28
|
+
from . import states
|
|
29
|
+
from .states import Ask, Auto, Guided
|
|
30
|
+
|
|
31
|
+
FIXED, FAILED, REFUSED, DECLINED, NEEDS_TERMINAL, YOURS = ("fixed", "failed", "refused", "declined",
|
|
32
|
+
"needs_terminal", "yours")
|
|
33
|
+
|
|
34
|
+
# How long a started helper has to answer (Mobster's first start on a phone took 2.4 minutes on 17 Oct 2026, of which 60
|
|
35
|
+
# s was iOS's own timeout; a restarted helper answers in about 20 s), how long a refresh may build, and how long a
|
|
36
|
+
# stopped process has to exit after SIGTERM.
|
|
37
|
+
HELPER_WAIT_S = 90.0
|
|
38
|
+
BUILD_WAIT_S = 20 * 60.0
|
|
39
|
+
STOP_WAIT_S = 5.0
|
|
40
|
+
PROGRESS_EVERY_S = 15.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Holder(NamedTuple):
|
|
44
|
+
port: int
|
|
45
|
+
pid: int
|
|
46
|
+
command: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Result(NamedTuple):
|
|
50
|
+
outcome: str
|
|
51
|
+
detail: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Refused(Exception):
|
|
55
|
+
"""An action that leaves things alone, with the sentence that says why (``refusing`` turns it into a Result)."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Step:
|
|
60
|
+
"""One finding's fix: what it was, what happened, and why."""
|
|
61
|
+
cause: str
|
|
62
|
+
check: str
|
|
63
|
+
kind: str
|
|
64
|
+
outcome: str
|
|
65
|
+
detail: str
|
|
66
|
+
|
|
67
|
+
def public(self):
|
|
68
|
+
return {"cause": self.cause, "check": self.check, "kind": self.kind, "outcome": self.outcome,
|
|
69
|
+
"detail": self.detail}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# -- the world, replaceable in tests ---------------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
# Which program holds a port is in the kernel's socket list: `netstat -anv` gives it as process:pid in about half a
|
|
75
|
+
# second (scripts/measure_doctor_time.py). `lsof`, which walks every process's files, took 8 s at a load average of 70
|
|
76
|
+
# on 18 Oct 2026. A connect to the port answers in milliseconds, so the list is read only for a port something holds.
|
|
77
|
+
NETSTAT_S = 6.0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _netstat():
|
|
81
|
+
import subprocess
|
|
82
|
+
try:
|
|
83
|
+
done = subprocess.run(["/usr/sbin/netstat", "-anv", "-p", "tcp"], capture_output=True, text=True,
|
|
84
|
+
timeout=NETSTAT_S)
|
|
85
|
+
except (OSError, subprocess.SubprocessError):
|
|
86
|
+
return ""
|
|
87
|
+
return done.stdout
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_netstat(text):
|
|
91
|
+
"""[(port, pid)] of the listening sockets in `netstat -anv -p tcp`: a row is the protocol, two queues, the local
|
|
92
|
+
address (host.port), the foreign one, the state, two byte counts, two buffer sizes and process:pid. A row that
|
|
93
|
+
doesn't read that way is skipped, so a changed format names nobody and nothing is stopped."""
|
|
94
|
+
found = []
|
|
95
|
+
for line in (text or "").splitlines():
|
|
96
|
+
tokens = line.split()
|
|
97
|
+
if len(tokens) < 11 or not tokens[0].startswith("tcp") or tokens[5] != "LISTEN":
|
|
98
|
+
continue
|
|
99
|
+
owner = re.fullmatch(r"(.+):(\d+)", tokens[10])
|
|
100
|
+
port = tokens[3].rpartition(".")[2]
|
|
101
|
+
if owner and port.isdigit():
|
|
102
|
+
found.append((int(port), int(owner[2])))
|
|
103
|
+
return found
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def taken(ports, listening=None):
|
|
107
|
+
"""The ports something accepts connections on, read in milliseconds with no process table."""
|
|
108
|
+
from .devices import listening as probe
|
|
109
|
+
return [port for port in ports if (listening or probe)(port)]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def holders(ports, netstat=None):
|
|
113
|
+
"""The programs listening on ``ports`` (other than this process), each with its whole command line. A port that
|
|
114
|
+
something holds but the list couldn't name has no holder here: nothing is stopped for a program Mobster can't
|
|
115
|
+
name."""
|
|
116
|
+
from .supervised import command_of
|
|
117
|
+
wanted = {int(port) for port in ports}
|
|
118
|
+
found, seen = [], set()
|
|
119
|
+
for port, pid in parse_netstat((netstat or _netstat)()):
|
|
120
|
+
if port in wanted and pid != os.getpid() and (port, pid) not in seen:
|
|
121
|
+
seen.add((port, pid))
|
|
122
|
+
found.append(Holder(port, pid, command_of(pid) or str(pid)))
|
|
123
|
+
return found
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _alive(pid):
|
|
127
|
+
try:
|
|
128
|
+
os.kill(pid, 0)
|
|
129
|
+
except ProcessLookupError:
|
|
130
|
+
return False
|
|
131
|
+
except PermissionError:
|
|
132
|
+
return True
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Context:
|
|
137
|
+
"""What the actions work with. ``ask(question)`` is True or False for the person's answer, None without a terminal;
|
|
138
|
+
``say`` prints a progress line (nothing in a JSON run)."""
|
|
139
|
+
|
|
140
|
+
def __init__(self, args=None, *, ask=None, say=None, open_runtime=None, clock=time.monotonic, sleep=time.sleep,
|
|
141
|
+
netstat=None, listening=None, urls=None, read=None, kill=os.kill, alive=_alive):
|
|
142
|
+
self.args = args
|
|
143
|
+
self.ask = ask or (lambda question: None)
|
|
144
|
+
self.say = say or (lambda line: None)
|
|
145
|
+
self._open_runtime = open_runtime
|
|
146
|
+
self.clock, self.sleep, self.netstat, self.listening, self.kill, self.alive = (clock, sleep, netstat, listening,
|
|
147
|
+
kill, alive)
|
|
148
|
+
self._urls, self._read = urls, read
|
|
149
|
+
self.runtime = None
|
|
150
|
+
|
|
151
|
+
def manager(self):
|
|
152
|
+
"""The chosen phone's manager, from the runtime ``mobster setup`` uses (its helper outlives this process). Never
|
|
153
|
+
opened while a Mobster service answers: it manages the phone's helper itself, and a second manager would race it
|
|
154
|
+
for the phone's ports."""
|
|
155
|
+
if self.runtime is None:
|
|
156
|
+
if self.service_answers():
|
|
157
|
+
raise Refused("Mobster for Mac or mobster serve is running and manages the iPhone's helper itself. "
|
|
158
|
+
"Fix it from its Devices page, or quit it and run mobster doctor --fix again.")
|
|
159
|
+
from . import setup_cli
|
|
160
|
+
self.runtime = (self._open_runtime or setup_cli.open_runtime)()
|
|
161
|
+
device = getattr(self.args, "device", None)
|
|
162
|
+
if device:
|
|
163
|
+
setup_cli.choose(self.runtime, device)
|
|
164
|
+
return self.runtime.manager
|
|
165
|
+
|
|
166
|
+
def close(self):
|
|
167
|
+
if self.runtime is not None:
|
|
168
|
+
self.runtime.close(timeout=5)
|
|
169
|
+
self.runtime = None
|
|
170
|
+
|
|
171
|
+
def service_urls(self):
|
|
172
|
+
from .threads.client import service_urls
|
|
173
|
+
return service_urls() if self._urls is None else self._urls
|
|
174
|
+
|
|
175
|
+
def service_answers(self):
|
|
176
|
+
"""Whether a Mobster service (Mobster for Mac, `mobster serve`) answers on this Mac."""
|
|
177
|
+
from .engine_health import read_from_app
|
|
178
|
+
urls = self.service_urls()
|
|
179
|
+
if not urls:
|
|
180
|
+
return False
|
|
181
|
+
engine = (self._read or read_from_app)(urls[0])
|
|
182
|
+
return not (engine and engine.get("cause") in ("no_answer", "not_running"))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def interactive_ask(question):
|
|
186
|
+
"""The question on the terminal: True for yes, False for anything else, None when stdin isn't a terminal."""
|
|
187
|
+
if not sys.stdin.isatty():
|
|
188
|
+
return None
|
|
189
|
+
try:
|
|
190
|
+
answer = input(f"? {question} [y/N] ").strip().lower()
|
|
191
|
+
except EOFError:
|
|
192
|
+
return None
|
|
193
|
+
return answer in ("y", "yes")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# -- the actions ---------------------------------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
def _phone_name(manager):
|
|
199
|
+
name = manager.settings().get("device_name")
|
|
200
|
+
return name if isinstance(name, str) and name.strip() else "your iPhone"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def refusing(action):
|
|
204
|
+
"""``action`` that answers ``Refused`` with a refused Result."""
|
|
205
|
+
@functools.wraps(action)
|
|
206
|
+
def wrapped(ctx, check):
|
|
207
|
+
try:
|
|
208
|
+
return action(ctx, check)
|
|
209
|
+
except Refused as why:
|
|
210
|
+
return Result(REFUSED, str(why))
|
|
211
|
+
return wrapped
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _busy(manager):
|
|
215
|
+
"""The reason to leave this phone alone, or None: another Mobster is running a task on it, or it isn't plugged
|
|
216
|
+
in."""
|
|
217
|
+
from .journal import JournalError, Lease
|
|
218
|
+
try:
|
|
219
|
+
Lease.device(manager.wda_url).close()
|
|
220
|
+
except JournalError as error:
|
|
221
|
+
owner = getattr(error, "owner", None)
|
|
222
|
+
who = f"{owner} is" if owner else "Another Mobster is"
|
|
223
|
+
return f"{who} running a task on {_phone_name(manager)}, so Mobster left it alone."
|
|
224
|
+
if manager.attached() is not True:
|
|
225
|
+
message, step, _ = states.words(states.WAITING_FOR_PHONE, "unplugged", _phone_name(manager))
|
|
226
|
+
return f"{message} {step}"
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@refusing
|
|
231
|
+
def start_helper(ctx, check):
|
|
232
|
+
"""Start Mobster's helper on the chosen phone and wait for it to answer. Nothing is started on a phone another
|
|
233
|
+
Mobster holds, on one that isn't plugged in, or for a helper that isn't built or has expired (those are other
|
|
234
|
+
rows)."""
|
|
235
|
+
manager = ctx.manager()
|
|
236
|
+
name = _phone_name(manager)
|
|
237
|
+
if (why := _busy(manager)) is not None:
|
|
238
|
+
return Result(REFUSED, why)
|
|
239
|
+
if manager.expired():
|
|
240
|
+
return Result(REFUSED, "Its signature has expired, so it needs a refresh first.")
|
|
241
|
+
if not manager.built():
|
|
242
|
+
return Result(REFUSED, f"Mobster's helper isn't built for {name}. Run mobster setup.")
|
|
243
|
+
if manager.wda_ready():
|
|
244
|
+
return Result(FIXED, f"Mobster's helper already answers on {name}.")
|
|
245
|
+
try:
|
|
246
|
+
manager.start_runner()
|
|
247
|
+
except LookupError as error:
|
|
248
|
+
return Result(REFUSED, str(error))
|
|
249
|
+
return _await_helper(ctx, manager, name)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _await_helper(ctx, manager, name):
|
|
253
|
+
end = ctx.clock() + HELPER_WAIT_S
|
|
254
|
+
told = ctx.clock()
|
|
255
|
+
while ctx.clock() < end:
|
|
256
|
+
if manager.wda_ready():
|
|
257
|
+
return Result(FIXED, f"Started Mobster's helper on {name}. It answers.")
|
|
258
|
+
if ctx.clock() - told >= PROGRESS_EVERY_S:
|
|
259
|
+
ctx.say(f"Waiting for Mobster's helper on {name}...")
|
|
260
|
+
told = ctx.clock()
|
|
261
|
+
ctx.sleep(1.0)
|
|
262
|
+
manager.runner_hint()
|
|
263
|
+
cause = manager.runner_cause if manager.runner_cause in states.BY_CAUSE else "failing"
|
|
264
|
+
message, step, _ = states.words(states.row(cause).state, cause, name)
|
|
265
|
+
return Result(FAILED, f"Mobster's helper didn't answer in {int(HELPER_WAIT_S)} s. {message} {step}")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@refusing
|
|
269
|
+
def rebuild_helper(ctx, check):
|
|
270
|
+
"""Refresh the helper whose 7-day signature ran out, with the Apple Account it was built with, as Mobster for Mac
|
|
271
|
+
does while the phone is plugged in. It never picks an account: with none saved it points at ``mobster setup``."""
|
|
272
|
+
manager = ctx.manager()
|
|
273
|
+
name = _phone_name(manager)
|
|
274
|
+
if (why := _busy(manager)) is not None:
|
|
275
|
+
return Result(REFUSED, why)
|
|
276
|
+
if not manager.expired():
|
|
277
|
+
return start_helper(ctx, check)
|
|
278
|
+
try:
|
|
279
|
+
manager.start_build(renewal=True)
|
|
280
|
+
except (ValueError, LookupError) as error:
|
|
281
|
+
return Result(REFUSED, f"{error}. Run mobster setup.")
|
|
282
|
+
end = ctx.clock() + BUILD_WAIT_S
|
|
283
|
+
told = started = ctx.clock()
|
|
284
|
+
while manager.build["state"] == "running":
|
|
285
|
+
if ctx.clock() >= end:
|
|
286
|
+
return Result(FAILED, f"Refreshing the helper on {name} was still running after "
|
|
287
|
+
f"{int(BUILD_WAIT_S // 60)} minutes. It keeps going; run mobster doctor again.")
|
|
288
|
+
if ctx.clock() - told >= PROGRESS_EVERY_S:
|
|
289
|
+
ctx.say(f"Refreshing Mobster's helper on {name}... {int(ctx.clock() - started)} s")
|
|
290
|
+
told = ctx.clock()
|
|
291
|
+
ctx.sleep(2.0)
|
|
292
|
+
if manager.build["state"] != "succeeded":
|
|
293
|
+
return Result(FAILED, manager.build.get("error") or f"Refreshing the helper on {name} failed.")
|
|
294
|
+
try:
|
|
295
|
+
manager.start_runner()
|
|
296
|
+
except LookupError as error:
|
|
297
|
+
return Result(FAILED, f"Refreshed the helper, but it didn't start: {error}")
|
|
298
|
+
return _await_helper(ctx, manager, name)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _confirm(ctx, question):
|
|
302
|
+
"""The answer to ``question``, or the Result that ends the action: no terminal, or a no."""
|
|
303
|
+
answer = ctx.ask(question)
|
|
304
|
+
if answer is None:
|
|
305
|
+
return None, Result(NEEDS_TERMINAL, f"{question} Run mobster doctor --fix in a terminal to answer.")
|
|
306
|
+
if not answer:
|
|
307
|
+
return None, Result(DECLINED, "You said no, so Mobster left it running.")
|
|
308
|
+
return True, None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _stop(ctx, holder):
|
|
312
|
+
"""SIGTERM ``holder``, wait for it to exit, and only after a second yes SIGKILL it. Only the process Mobster named:
|
|
313
|
+
if its command line changed since, it is a different process and is left alone."""
|
|
314
|
+
from .supervised import command_of
|
|
315
|
+
if command_of(holder.pid) != holder.command:
|
|
316
|
+
return Result(REFUSED, f"Process {holder.pid} is no longer {_short(holder.command)}, so Mobster left it alone.")
|
|
317
|
+
try:
|
|
318
|
+
ctx.kill(holder.pid, signal.SIGTERM)
|
|
319
|
+
except ProcessLookupError:
|
|
320
|
+
return Result(FIXED, f"{_short(holder.command)} had already stopped.")
|
|
321
|
+
except PermissionError:
|
|
322
|
+
return Result(FAILED, f"{_short(holder.command)} (pid {holder.pid}) belongs to another user, so Mobster can't "
|
|
323
|
+
"stop it.")
|
|
324
|
+
end = ctx.clock() + STOP_WAIT_S
|
|
325
|
+
while ctx.clock() < end:
|
|
326
|
+
if not ctx.alive(holder.pid):
|
|
327
|
+
return Result(FIXED, f"Stopped {_short(holder.command)} (pid {holder.pid}).")
|
|
328
|
+
ctx.sleep(0.1)
|
|
329
|
+
question = (f"{_short(holder.command)} (pid {holder.pid}) is still running {int(STOP_WAIT_S)} s after it was "
|
|
330
|
+
"asked to stop. Force it to stop?")
|
|
331
|
+
answer = ctx.ask(question)
|
|
332
|
+
if not answer:
|
|
333
|
+
return Result(FAILED if answer is None else DECLINED,
|
|
334
|
+
f"{_short(holder.command)} (pid {holder.pid}) is still running. Mobster didn't force it.")
|
|
335
|
+
if command_of(holder.pid) != holder.command:
|
|
336
|
+
return Result(REFUSED, f"Process {holder.pid} changed, so Mobster left it alone.")
|
|
337
|
+
try:
|
|
338
|
+
ctx.kill(holder.pid, signal.SIGKILL)
|
|
339
|
+
except ProcessLookupError:
|
|
340
|
+
pass
|
|
341
|
+
return Result(FIXED, f"Stopped {_short(holder.command)} (pid {holder.pid}).")
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _short(command):
|
|
345
|
+
return command if len(command) <= 80 else command[:79] + "…"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@refusing
|
|
349
|
+
def free_port(ctx, check):
|
|
350
|
+
"""Free the ports Mobster's helper relays on, after naming the program that holds each and being told yes."""
|
|
351
|
+
manager = ctx.manager()
|
|
352
|
+
if (why := _busy(manager)) is not None:
|
|
353
|
+
return Result(REFUSED, why)
|
|
354
|
+
held = taken((manager.wda_port, manager.video_port), ctx.listening)
|
|
355
|
+
if not held:
|
|
356
|
+
return Result(FIXED, f"Ports {manager.wda_port} and {manager.video_port} are free.")
|
|
357
|
+
found = holders(held, ctx.netstat)
|
|
358
|
+
if not found:
|
|
359
|
+
return Result(REFUSED, f"Something holds port {held[0]}, and Mobster couldn't read which program, so it "
|
|
360
|
+
"left it alone.")
|
|
361
|
+
details = []
|
|
362
|
+
for holder in found:
|
|
363
|
+
question = (f"Port {holder.port} is held by {_short(holder.command)} (pid {holder.pid}). It may be another "
|
|
364
|
+
"Mobster's connection to a phone. Stop it?")
|
|
365
|
+
yes, ended = _confirm(ctx, question)
|
|
366
|
+
if ended is not None:
|
|
367
|
+
return ended
|
|
368
|
+
result = _stop(ctx, holder)
|
|
369
|
+
details.append(result.detail)
|
|
370
|
+
if result.outcome != FIXED:
|
|
371
|
+
return Result(result.outcome, " ".join(details))
|
|
372
|
+
return Result(FIXED, " ".join(details))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@refusing
|
|
376
|
+
def restart_service(ctx, check):
|
|
377
|
+
"""Stop Mobster for Mac's service when it accepts connections and never answers, so the Mac app starts it again. It
|
|
378
|
+
stops only a program that is listening where the service wrote its address and has Mobster in its command line."""
|
|
379
|
+
from urllib.parse import urlsplit
|
|
380
|
+
urls = ctx.service_urls()
|
|
381
|
+
if not urls:
|
|
382
|
+
return Result(REFUSED, "Mobster for Mac left no address, so there is no service to stop.")
|
|
383
|
+
port = urlsplit(urls[0]).port
|
|
384
|
+
if not taken((port,), ctx.listening):
|
|
385
|
+
message, step, _ = states.words(states.DOWN, "not_running")
|
|
386
|
+
return Result(REFUSED, f"{message} {step}")
|
|
387
|
+
found = holders((port,), ctx.netstat)
|
|
388
|
+
if not found:
|
|
389
|
+
return Result(REFUSED, f"Mobster couldn't read which program holds port {port}, so it left it alone.")
|
|
390
|
+
holder = found[0]
|
|
391
|
+
if "mobster" not in holder.command.lower():
|
|
392
|
+
return Result(REFUSED, f"The program on port {port} is {_short(holder.command)}, not Mobster, so Mobster "
|
|
393
|
+
"left it alone.")
|
|
394
|
+
question = (f"Mobster's service (pid {holder.pid}) isn't answering. Stop it so Mobster for Mac starts it "
|
|
395
|
+
"again? A task in flight is kept in History and is not resent.")
|
|
396
|
+
yes, ended = _confirm(ctx, question)
|
|
397
|
+
if ended is not None:
|
|
398
|
+
return ended
|
|
399
|
+
return _stop(ctx, holder)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
ACTIONS = {"start_helper": start_helper, "rebuild_helper": rebuild_helper, "free_port": free_port,
|
|
403
|
+
"restart_service": restart_service}
|
|
404
|
+
# Fixes that depend on each other run in this order: the port first, then the helper, then the service.
|
|
405
|
+
ORDER = ("free_port", "rebuild_helper", "start_helper", "restart_service")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# -- the plan and the run -------------------------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
def _rank(row):
|
|
411
|
+
match row.fix:
|
|
412
|
+
case Auto(action) | Ask(action):
|
|
413
|
+
return (0, ORDER.index(action))
|
|
414
|
+
case Guided():
|
|
415
|
+
return (1, 0)
|
|
416
|
+
return (2, 0)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def plan(checks):
|
|
420
|
+
"""[(check, row)] to act on: each WARN or FAIL finding that is a row, once per distinct fix, in the order the fixes
|
|
421
|
+
depend on each other. A finding that is not a row has nothing to run."""
|
|
422
|
+
from .doctor import FAIL, WARN
|
|
423
|
+
chosen, seen = [], set()
|
|
424
|
+
for check in checks:
|
|
425
|
+
if check.state not in (WARN, FAIL) or not check.cause:
|
|
426
|
+
continue
|
|
427
|
+
row = states.row(check.cause)
|
|
428
|
+
identity = (type(row.fix).__name__, getattr(row.fix, "action", None) or getattr(row.fix, "command", None)
|
|
429
|
+
or row.cause)
|
|
430
|
+
if identity not in seen:
|
|
431
|
+
seen.add(identity)
|
|
432
|
+
chosen.append((check, row))
|
|
433
|
+
return sorted(chosen, key=lambda item: _rank(item[1]))
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def guided(command, ctx):
|
|
437
|
+
"""Run the guided flow a ``Guided`` fix names. Each asks its own questions, so each needs a terminal."""
|
|
438
|
+
from argparse import Namespace
|
|
439
|
+
if command == "mobster login":
|
|
440
|
+
from .login import login
|
|
441
|
+
return login(Namespace(provider=None, with_key=False, skip_check=False, json=False))
|
|
442
|
+
if command == "mobster setup":
|
|
443
|
+
from .setup_cli import run
|
|
444
|
+
return run(Namespace(device=getattr(ctx.args, "device", None), team=None, json=False))
|
|
445
|
+
raise KeyError(command)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def run(checks, ctx, terminal=True):
|
|
449
|
+
"""Act on ``checks`` and return the ``Step`` for each. Order: the unasked and asked fixes, then the guided flows,
|
|
450
|
+
then the rest are reported as the person's."""
|
|
451
|
+
steps = []
|
|
452
|
+
for check, row in plan(checks):
|
|
453
|
+
kind = type(row.fix).__name__.lower()
|
|
454
|
+
match row.fix:
|
|
455
|
+
case Auto(action) | Ask(action):
|
|
456
|
+
result = ACTIONS[action](ctx, check)
|
|
457
|
+
case Guided(command):
|
|
458
|
+
if not terminal:
|
|
459
|
+
result = Result(NEEDS_TERMINAL, f"{command} asks you questions. Run it in a terminal.")
|
|
460
|
+
else:
|
|
461
|
+
code = guided(command, ctx)
|
|
462
|
+
result = Result(FIXED if code == 0 else FAILED,
|
|
463
|
+
f"{command} finished." if code == 0 else f"{command} stopped (exit {code}).")
|
|
464
|
+
case _:
|
|
465
|
+
result = Result(YOURS, states.how(row.fix))
|
|
466
|
+
steps.append(Step(row.cause, check.key, kind, result.outcome, result.detail))
|
|
467
|
+
return steps
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Saving what a task exports or renders into ~/Downloads: a result as text, a GIF, an MP4, a PDF."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .api_errors import APIError
|
|
7
|
+
|
|
8
|
+
EXPORT_MAX_BYTES = 8_000_000
|
|
9
|
+
EXPORT_TYPES = {"json", "csv", "yaml", "yml", "md", "txt"}
|
|
10
|
+
DOWNLOADS_DENIED = ("Mobster can’t save to Downloads. Allow it in System Settings › Privacy & Security › "
|
|
11
|
+
"Files and Folders.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def save_export(body, directory=None):
|
|
15
|
+
"""Save a result the dashboard exports into ~/Downloads, never overwriting."""
|
|
16
|
+
if not isinstance(body, dict) or set(body) != {"filename", "text"}:
|
|
17
|
+
raise ValueError("An export needs a filename and text")
|
|
18
|
+
name, text = body["filename"], body["text"]
|
|
19
|
+
if not isinstance(name, str) or not isinstance(text, str) or len(text.encode()) > EXPORT_MAX_BYTES:
|
|
20
|
+
raise ValueError("An export needs a filename and at most 8 MB of text")
|
|
21
|
+
stem, dot, extension = Path(name).name.rpartition(".")
|
|
22
|
+
stem = re.sub(r"[^A-Za-z0-9._ -]+", "-", stem).strip(" .-")[:80]
|
|
23
|
+
if not dot or extension.lower() not in EXPORT_TYPES or not stem:
|
|
24
|
+
raise ValueError("Exports are .json, .csv, .yaml, .md or .txt files")
|
|
25
|
+
return _save_new(directory, stem, extension.lower(), text.encode("utf-8"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Files the agent renders itself (a task's GIF and MP4): a name made from the task, never one a request chose.
|
|
29
|
+
MEDIA_TYPES = {"gif", "mp4", "pdf", "png", "md"}
|
|
30
|
+
def save_export_bytes(stem, extension, data, directory=None):
|
|
31
|
+
"""Save rendered media (``extension`` gif or mp4) into ~/Downloads as save_export does: a cleaned name (letters in
|
|
32
|
+
any script, no path separators), never overwriting, a 403 when macOS refuses."""
|
|
33
|
+
if extension not in MEDIA_TYPES or not isinstance(data, (bytes, bytearray)):
|
|
34
|
+
raise ValueError("Only a .gif, .mp4, .pdf, .png or .md is saved this way")
|
|
35
|
+
stem = re.sub(r"[^\w .,'’()&+–-]+", "-", str(stem or "")).strip(" .-")[:80] or "Mobster task"
|
|
36
|
+
return _save_new(directory, stem, extension, bytes(data))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _save_new(directory, stem, extension, data):
|
|
40
|
+
"""Write ``data`` as "{stem}.{extension}" in ``directory`` (default ~/Downloads), or "{stem} (1)…" when that name is
|
|
41
|
+
taken: an existing file is never touched."""
|
|
42
|
+
folder = Path(directory) if directory else Path.home() / "Downloads"
|
|
43
|
+
try:
|
|
44
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
for index in range(1000):
|
|
46
|
+
target = folder / (f"{stem}.{extension}" if index == 0 else f"{stem} ({index}).{extension}")
|
|
47
|
+
try:
|
|
48
|
+
with open(target, "xb") as stream:
|
|
49
|
+
stream.write(data)
|
|
50
|
+
return {"name": target.name, "path": str(target)}
|
|
51
|
+
except FileExistsError:
|
|
52
|
+
continue
|
|
53
|
+
except PermissionError:
|
|
54
|
+
raise APIError(DOWNLOADS_DENIED, 403, "downloads_denied") from None
|
|
55
|
+
raise ValueError("Too many files with that name in Downloads")
|