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,510 @@
|
|
|
1
|
+
"""The phone tools that are not taps: codes, notifications, the clipboard, Photos, buttons, display, installs, the lock
|
|
2
|
+
and the sound."""
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from contextlib import ExitStack
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .action_journal import record_action
|
|
12
|
+
from .codes import SECURE_MASK
|
|
13
|
+
from .protocol import ToolResult
|
|
14
|
+
from .session import node_name, node_target
|
|
15
|
+
from .support import (
|
|
16
|
+
SHUTTING_DOWN,
|
|
17
|
+
Phases,
|
|
18
|
+
ToolError,
|
|
19
|
+
cap_text,
|
|
20
|
+
changed_words,
|
|
21
|
+
device_request,
|
|
22
|
+
jsonable,
|
|
23
|
+
screen_changed,
|
|
24
|
+
screen_fingerprint,
|
|
25
|
+
sentence,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Dormant: only a build with lockscreen.CAN_UNLOCK on reaches these (this one doesn't, and the Mac app has no such
|
|
29
|
+
# setting). What unlock says when the phone's owner hasn't let scripts and MCP unlock it.
|
|
30
|
+
UNLOCK_OFF = ("Mobster may not unlock “{name}” from scripts or MCP. Its owner can allow it in the Mobster app: "
|
|
31
|
+
"Settings › iPhones › {name} › Unlock with passcode › Scripts and MCP may unlock.")
|
|
32
|
+
CANT_UNLOCK = "Mobster can't unlock an iPhone and never enters a passcode. Ask the user to unlock “{name}” themselves."
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PhoneTools:
|
|
36
|
+
def tool_use_code(self, args, call):
|
|
37
|
+
def work(session, phases):
|
|
38
|
+
from ..skills.codes import CodeRefused, Field, enter_code, find_code
|
|
39
|
+
driver = session.run.driver
|
|
40
|
+
tree, snapshot = self._current(session, phases)
|
|
41
|
+
node = self._resolve(session, tree, args)
|
|
42
|
+
width, height = tree.size
|
|
43
|
+
x, y, w, h = node.rect
|
|
44
|
+
field = Field(label=node.label or "", role=(node.role or "").removeprefix("XCUIElementType"),
|
|
45
|
+
rect=(x / width, y / height, w / width, h / height), identifier=node.identifier or "",
|
|
46
|
+
placeholder=getattr(node, "placeholder", "") or "", locator=node.path)
|
|
47
|
+
bundle = getattr(tree, "bundle_id", "") or getattr(snapshot, "bundle_id", "") or ""
|
|
48
|
+
snapshot = self._permit_code(session, bundle, node_name(node) or "the code field", snapshot, call)
|
|
49
|
+
found = None
|
|
50
|
+
try:
|
|
51
|
+
found = find_code(driver, field=field, bundle=bundle, snapshot=snapshot, hint=args.get("sender"))
|
|
52
|
+
phases("find")
|
|
53
|
+
rect = enter_code(driver, found)
|
|
54
|
+
except CodeRefused as refusal: # always before a key is sent
|
|
55
|
+
session.forget_outline()
|
|
56
|
+
raise ToolError(str(refusal)) from None
|
|
57
|
+
except Exception as error:
|
|
58
|
+
# Only the error's kind, in the result and the log: a driver's message could echo what it sent.
|
|
59
|
+
# A failure after the code was found may leave it in the field (a timeout after the keys landed),
|
|
60
|
+
# so it is masked and blacked out from now on as if it had been typed.
|
|
61
|
+
session.forget_outline()
|
|
62
|
+
name = type(error).__name__
|
|
63
|
+
self.log(f"Run {session.run_id}: use_code failed ({name}).")
|
|
64
|
+
if found is None:
|
|
65
|
+
raise ToolError(f"Mobster couldn't look for the code ({name}). Call screen to see where the "
|
|
66
|
+
"device is.") from None
|
|
67
|
+
self.codes.remember(session, found.code, found.frame)
|
|
68
|
+
raise ToolError(f"Mobster couldn't finish entering the code ({name}). Call screen to see the field "
|
|
69
|
+
"before trying again.") from None
|
|
70
|
+
self.codes.remember(session, found.code, rect)
|
|
71
|
+
phases("type")
|
|
72
|
+
text = f"Entered the code from {found.sender}"
|
|
73
|
+
return {"op": "USE_CODE", "text": text, "target": node_target(node), "typed": SECURE_MASK,
|
|
74
|
+
"changed": True, "line": text + " (it never leaves the phone).", "after": None, "log": text,
|
|
75
|
+
"extra": {"entered": True, "source": found.source, "sender": found.sender,
|
|
76
|
+
"age_s": found.age_s}}
|
|
77
|
+
return self._act(args, call, work, "USE_CODE")
|
|
78
|
+
|
|
79
|
+
def tool_read_notifications(self, args, call):
|
|
80
|
+
from ..harness_api import fence
|
|
81
|
+
from ..phone_io.photos import parse_since
|
|
82
|
+
from ..skills.notifications import NotificationCenter, describe, since_filter
|
|
83
|
+
try:
|
|
84
|
+
since = parse_since(args.get("since"))
|
|
85
|
+
except ValueError as error:
|
|
86
|
+
raise ToolError(sentence(error)) from None
|
|
87
|
+
session = self._target_session(args)
|
|
88
|
+
with self._locked(session), record_action(session.run.run_dir, "READ_NOTIFICATIONS"):
|
|
89
|
+
driver = session.run.driver
|
|
90
|
+
try:
|
|
91
|
+
snapshot = driver.observe(timeout=10)
|
|
92
|
+
except Exception as error:
|
|
93
|
+
raise ToolError(f"Mobster couldn't read the phone's screen in time ({type(error).__name__}), so it "
|
|
94
|
+
"didn't open Notification Center and read nothing. Call screen to see where the "
|
|
95
|
+
"phone is, then call read_notifications again.") from None
|
|
96
|
+
origin = getattr(snapshot, "bundle_id", "") or ""
|
|
97
|
+
center = NotificationCenter(driver)
|
|
98
|
+
try:
|
|
99
|
+
rows = center.read(origin=origin, size=snapshot, keep_partial=True)
|
|
100
|
+
except Exception as error:
|
|
101
|
+
session.forget_outline()
|
|
102
|
+
self.log(f"Run {session.run_id}: read_notifications read nothing ({type(error).__name__}).")
|
|
103
|
+
raise ToolError(
|
|
104
|
+
f"Notification Center didn't answer in time ({type(error).__name__}), so Mobster read no "
|
|
105
|
+
"notifications. " + ("It closed Notification Center and went back to where it was. Call "
|
|
106
|
+
"read_notifications again, or screen to check the phone."
|
|
107
|
+
if center.restored else
|
|
108
|
+
"It couldn't confirm Notification Center closed: call home, then call "
|
|
109
|
+
"read_notifications again.")) from None
|
|
110
|
+
session.forget_outline()
|
|
111
|
+
now = time.time()
|
|
112
|
+
rows = since_filter(rows, since, now)
|
|
113
|
+
rows, withheld = self._outside_notifications(session, rows)
|
|
114
|
+
mask = "•••••• (use use_code)"
|
|
115
|
+
public = [row.public(mask, now) for row in rows]
|
|
116
|
+
text = describe(rows).replace("•••••• (use USE_CODE)", mask)
|
|
117
|
+
if since is not None and not rows:
|
|
118
|
+
text = "No notifications since then."
|
|
119
|
+
elif rows:
|
|
120
|
+
text = fence("Notification Center on the phone", text) # a notification's text is data, never instructions
|
|
121
|
+
if withheld:
|
|
122
|
+
text += (f"\n{withheld} notification{'s' if withheld != 1 else ''} from apps you keep from outside "
|
|
123
|
+
"agents " + ("were" if withheld != 1 else "was") + " left out.")
|
|
124
|
+
if center.note:
|
|
125
|
+
text += "\n" + center.note
|
|
126
|
+
self.log(f"Run {session.run_id}: read {len(rows)} notification(s)" + (" (cut short)." if center.note else "."))
|
|
127
|
+
return ToolResult(cap_text(text), {"run_id": session.run_id, "notifications": public,
|
|
128
|
+
"partial": bool(center.note)})
|
|
129
|
+
|
|
130
|
+
def tool_set_clipboard(self, args, call):
|
|
131
|
+
from ..phone_io import PhoneIOError
|
|
132
|
+
from ..phone_io.clipboard import set_image, set_text
|
|
133
|
+
if bool(args.get("text")) == bool(args.get("image_path")):
|
|
134
|
+
raise ToolError("Pass text, or image_path for a picture to paste, not both.")
|
|
135
|
+
session = self._target_session(args)
|
|
136
|
+
with self._locked(session), record_action(session.run.run_dir, "SET_CLIPBOARD"):
|
|
137
|
+
driver = session.run.driver
|
|
138
|
+
request = device_request(driver)
|
|
139
|
+
try:
|
|
140
|
+
if args.get("image_path"):
|
|
141
|
+
picture = set_image(request, args["image_path"])
|
|
142
|
+
name = getattr(session.run, "device_name", None) or "the device"
|
|
143
|
+
self.log(f"Run {session.run_id}: put a picture on the clipboard ({picture['bytes']} bytes).")
|
|
144
|
+
return ToolResult(f"Put {picture['name']} on {name}'s clipboard. Paste it in a composer with a "
|
|
145
|
+
"long press › Paste.", {"run_id": session.run_id, **picture})
|
|
146
|
+
count = set_text(request, args["text"], driver=driver)
|
|
147
|
+
except PhoneIOError as error:
|
|
148
|
+
raise ToolError(sentence(error)) from None
|
|
149
|
+
name = getattr(session.run, "device_name", None) or "the device"
|
|
150
|
+
self.log(f"Run {session.run_id}: set the clipboard ({count} characters).")
|
|
151
|
+
return ToolResult(f"Put {count:,} character{'s' if count != 1 else ''} on {name}'s clipboard. Paste it with "
|
|
152
|
+
"a long press in a field, or type_text.", {"run_id": session.run_id, "characters": count})
|
|
153
|
+
|
|
154
|
+
def _deep_links(self, args):
|
|
155
|
+
from ..deeplinks import search
|
|
156
|
+
links = search(args.get("app"), args.get("query"))
|
|
157
|
+
from .. import intents
|
|
158
|
+
from ..intents import atlas
|
|
159
|
+
found = atlas.discovered(args) if intents.enabled() and not links else []
|
|
160
|
+
if found:
|
|
161
|
+
return ToolResult(cap_text(atlas.describe(found)), {"links": [], "discovered": found})
|
|
162
|
+
if not links:
|
|
163
|
+
return ToolResult("No known deep link fits. Navigate there with tap, or open the app's own https link with "
|
|
164
|
+
"open_url and check the screen: some web addresses open an app's home feed, not the "
|
|
165
|
+
"screen they name.", {"links": []})
|
|
166
|
+
lines = [f"{link.app}: {link.does}: {link.template}" + ("" if link.risk == "opens" else f" ({link.risk})")
|
|
167
|
+
+ (" (best effort)" if link.best_effort else "") for link in links]
|
|
168
|
+
return ToolResult(cap_text("Fill in each {placeholder} (URL-encoded) and call open_url:\n" + "\n".join(lines)),
|
|
169
|
+
{"links": [link.public() for link in links]})
|
|
170
|
+
|
|
171
|
+
def tool_send_media(self, args, call):
|
|
172
|
+
from ..phone_io import PhoneIOError
|
|
173
|
+
from ..phone_io.photos import MAX_SEND_BYTES, sniff
|
|
174
|
+
path = os.path.expanduser(args["path"])
|
|
175
|
+
if not os.path.isabs(path):
|
|
176
|
+
raise ToolError("Pass the absolute path of a photo or video on this Mac.")
|
|
177
|
+
session = self._target_session(args)
|
|
178
|
+
record = self._phone_record(session) or {}
|
|
179
|
+
name = getattr(session.run, "device_name", None) or record.get("name") or "the device"
|
|
180
|
+
if record.get("kind") == "simulator":
|
|
181
|
+
try:
|
|
182
|
+
sniff(path)
|
|
183
|
+
if os.path.getsize(path) > MAX_SEND_BYTES:
|
|
184
|
+
raise PhoneIOError(f"{os.path.basename(path)} is over 100 MB; Mobster moves files up to 100 MB.",
|
|
185
|
+
"usage")
|
|
186
|
+
except (PhoneIOError, OSError) as error:
|
|
187
|
+
raise ToolError(sentence(error)) from None
|
|
188
|
+
udid = record.get("udid") or record.get("id")
|
|
189
|
+
argv = ["xcrun", "simctl", "addmedia", udid, path]
|
|
190
|
+
with self._locked(session), record_action(session.run.run_dir, "SEND_MEDIA"):
|
|
191
|
+
done = (self._simctl or (lambda a: subprocess.run(a, capture_output=True, text=True, timeout=60)))(argv)
|
|
192
|
+
if done.returncode != 0:
|
|
193
|
+
raise ToolError(f"The simulator didn't take the file: {(done.stderr or done.stdout).strip()[:160]}")
|
|
194
|
+
return ToolResult(f"Added {Path(path).name} to Photos on {name}.", {"device": record.get("id"),
|
|
195
|
+
"name": Path(path).name})
|
|
196
|
+
from ..phone_io.transfer import Phone, Transfer, summary
|
|
197
|
+
from ..phone_io.transfer import run as run_transfer
|
|
198
|
+
paths = [path] + [os.path.expanduser(p) for p in args.get("paths") or ()]
|
|
199
|
+
if not all(os.path.isabs(p) for p in paths):
|
|
200
|
+
raise ToolError("Pass absolute paths of photos or videos on this Mac.")
|
|
201
|
+
with self._locked(session), record_action(session.run.run_dir, "SEND_MEDIA"):
|
|
202
|
+
driver = session.run.driver
|
|
203
|
+
|
|
204
|
+
@contextlib.contextmanager
|
|
205
|
+
def runner():
|
|
206
|
+
yield lambda method, p, body=None, timeout=15: driver.call(method, p, body, timeout=timeout)
|
|
207
|
+
phone = Phone({**record, "name": name}, runner=runner)
|
|
208
|
+
if self._phone_files:
|
|
209
|
+
phone.files = lambda rec, container=False: self._phone_files(rec)
|
|
210
|
+
transfer = Transfer("to_phone", "photos", phone, via=args.get("via") or "auto", paths=paths,
|
|
211
|
+
album=args.get("album", "Mobster") or None)
|
|
212
|
+
try:
|
|
213
|
+
run_transfer(transfer)
|
|
214
|
+
except PhoneIOError as error:
|
|
215
|
+
raise ToolError(sentence(error) + (" " + sentence(error.fix) if error.fix else "")) from None
|
|
216
|
+
session.forget_outline()
|
|
217
|
+
if transfer.state != "done":
|
|
218
|
+
error = transfer.error or {}
|
|
219
|
+
raise ToolError(summary(transfer) + (" " + sentence(error["fix"]) if error.get("fix") else ""))
|
|
220
|
+
self.log(f"Run {session.run_id}: added {len(transfer.items)} to Photos by {transfer.route}.")
|
|
221
|
+
return ToolResult(summary(transfer) + " Apps can attach them from the photo picker now.",
|
|
222
|
+
jsonable({"run_id": session.run_id, **transfer.public()}))
|
|
223
|
+
|
|
224
|
+
def tool_pull_media(self, args, call):
|
|
225
|
+
from ..phone_io.photos import CameraRoll, parse_since
|
|
226
|
+
record = self._record_for(args)
|
|
227
|
+
try:
|
|
228
|
+
since = parse_since(args.get("since"))
|
|
229
|
+
except ValueError as error:
|
|
230
|
+
raise ToolError(sentence(error)) from None
|
|
231
|
+
folder = Path(os.getcwd()) / "iphone-media"
|
|
232
|
+
from ..phone_io.transfer import Transfer, phone_from_record
|
|
233
|
+
from ..phone_io.transfer import run as run_transfer
|
|
234
|
+
phone = phone_from_record(record)
|
|
235
|
+
if self._phone_files:
|
|
236
|
+
phone.roll = lambda rec: CameraRoll(self._phone_files(rec))
|
|
237
|
+
transfer = Transfer("to_mac", "photos", phone, via=args.get("via") or "auto", folder=str(folder),
|
|
238
|
+
count=int(args.get("count") or 1), since=since,
|
|
239
|
+
convert_heic=args.get("jpeg", True) is not False)
|
|
240
|
+
run_transfer(transfer)
|
|
241
|
+
if transfer.state != "done" and not transfer.items:
|
|
242
|
+
error = transfer.error or {}
|
|
243
|
+
raise ToolError(sentence(error.get("message") or "Nothing moved.")
|
|
244
|
+
+ (" " + sentence(error["fix"]) if error.get("fix") else ""))
|
|
245
|
+
items = [{**i.public(), "path": i.path} for i in transfer.items if i.path]
|
|
246
|
+
if not items:
|
|
247
|
+
return ToolResult(f"Nothing in {record['name']}'s camera roll" + (" since then." if since else "."),
|
|
248
|
+
{"device": record["id"], "files": []})
|
|
249
|
+
pictures = [self.imager(path=item["path"]) for item in items[:3]
|
|
250
|
+
if item["kind"] == "photo" and item["path"].lower().endswith((".jpg", ".jpeg", ".png"))]
|
|
251
|
+
lines = [f"{item['path']} ({item['kind']}, {item['bytes']:,} bytes)" for item in items]
|
|
252
|
+
return ToolResult(f"Saved {len(items)} from {record['name']}'s camera roll, newest first:\n" + "\n".join(lines),
|
|
253
|
+
jsonable({"device": record["id"], "files": items}), [p for p in pictures if p])
|
|
254
|
+
|
|
255
|
+
def tool_press_button(self, args, call):
|
|
256
|
+
from ..phone_io import PhoneIOError
|
|
257
|
+
from ..phone_io.controls import LOCKED_AFTER, lock, press
|
|
258
|
+
button = args["button"]
|
|
259
|
+
|
|
260
|
+
def work(session, phases):
|
|
261
|
+
driver = session.run.driver
|
|
262
|
+
request = device_request(driver)
|
|
263
|
+
simulator = not self._on_phone(session)
|
|
264
|
+
snapshot = driver.observe(timeout=10)
|
|
265
|
+
try:
|
|
266
|
+
if button == "lock":
|
|
267
|
+
lock(request)
|
|
268
|
+
else:
|
|
269
|
+
press(request, button, simulator=simulator)
|
|
270
|
+
except PhoneIOError as error:
|
|
271
|
+
raise ToolError(sentence(error)) from None
|
|
272
|
+
if button == "lock":
|
|
273
|
+
text = "Locked the screen" + ("" if simulator else ". " + LOCKED_AFTER)
|
|
274
|
+
return {"op": "LOCK", "text": "Locked the screen", "changed": True, "line": text, "after": None,
|
|
275
|
+
"record": False}
|
|
276
|
+
after = self._settle(driver, snapshot)
|
|
277
|
+
changed = screen_changed(snapshot, after)
|
|
278
|
+
text = {"home": "Pressed Home", "volume_up": "Pressed Volume Up",
|
|
279
|
+
"volume_down": "Pressed Volume Down"}[button]
|
|
280
|
+
return {"op": "BUTTON", "text": text, "changed": changed, "line": text + changed_words(changed),
|
|
281
|
+
"after": screen_fingerprint(after)}
|
|
282
|
+
if button == "lock":
|
|
283
|
+
session = self._target_session(args)
|
|
284
|
+
with self._locked(session), record_action(session.run.run_dir, "LOCK"):
|
|
285
|
+
done = work(session, Phases())
|
|
286
|
+
return ToolResult(done["line"], {"run_id": session.run_id, "locked": True})
|
|
287
|
+
return self._act(args, call, work, "BUTTON")
|
|
288
|
+
|
|
289
|
+
def tool_set_display(self, args, call):
|
|
290
|
+
from ..phone_io import PhoneIOError
|
|
291
|
+
from ..phone_io.controls import set_appearance, set_orientation
|
|
292
|
+
if not args.get("appearance") and not args.get("orientation"):
|
|
293
|
+
raise ToolError("Pass appearance (light or dark), orientation, or both.")
|
|
294
|
+
|
|
295
|
+
def work(session, phases):
|
|
296
|
+
driver = session.run.driver
|
|
297
|
+
request = device_request(driver)
|
|
298
|
+
snapshot = driver.observe(timeout=10)
|
|
299
|
+
said, extra = [], {}
|
|
300
|
+
try:
|
|
301
|
+
if args.get("appearance"):
|
|
302
|
+
done = set_appearance(request, args["appearance"])
|
|
303
|
+
said.append(f"appearance {done['appearance']}" + (f" (was {done['before']})" if done["before"]
|
|
304
|
+
else ""))
|
|
305
|
+
extra["appearance_before"] = done["before"]
|
|
306
|
+
if args.get("orientation"):
|
|
307
|
+
done = set_orientation(request, args["orientation"])
|
|
308
|
+
said.append(f"orientation {done['orientation'].replace('_', ' ')}"
|
|
309
|
+
+ (f" (was {done['before'].replace('_', ' ')})" if done["before"] else ""))
|
|
310
|
+
extra["orientation_before"] = done["before"]
|
|
311
|
+
except PhoneIOError as error:
|
|
312
|
+
raise ToolError(sentence(error)) from None
|
|
313
|
+
after = self._settle(driver, snapshot, timeout=4, wait_seconds=1.0)
|
|
314
|
+
changed = screen_changed(snapshot, after)
|
|
315
|
+
text = "Set " + " and ".join(said)
|
|
316
|
+
return {"op": "DISPLAY", "text": text, "changed": changed, "line": text + changed_words(changed),
|
|
317
|
+
"after": screen_fingerprint(after), "extra": extra}
|
|
318
|
+
return self._act(args, call, work, "DISPLAY")
|
|
319
|
+
|
|
320
|
+
def tool_install_app(self, args, call):
|
|
321
|
+
from ..journal import Lease, LeaseHeld
|
|
322
|
+
from ..phone_io import PhoneIOError
|
|
323
|
+
from ..verify.runs import make_run_dir
|
|
324
|
+
path = args["path"]
|
|
325
|
+
if not os.path.isabs(os.path.expanduser(path)):
|
|
326
|
+
raise ToolError("Pass the absolute path of a local .app or .ipa.")
|
|
327
|
+
record = self._record_for(args)
|
|
328
|
+
if self._installer is not None:
|
|
329
|
+
installer = self._installer
|
|
330
|
+
else:
|
|
331
|
+
from ..phone_io.install import install as installer
|
|
332
|
+
with ExitStack() as held:
|
|
333
|
+
with self._lock:
|
|
334
|
+
if self._closing.is_set():
|
|
335
|
+
raise ToolError(SHUTTING_DOWN)
|
|
336
|
+
session = self._open_direct(record["id"])
|
|
337
|
+
if session is None:
|
|
338
|
+
if self._device_busy_with_check(record) is not None:
|
|
339
|
+
raise ToolError("A check is using this device. Stop it before installing an app.")
|
|
340
|
+
if not record.get("wdaUrl"):
|
|
341
|
+
raise ToolError("The device has no WebDriverAgent address to lock. "
|
|
342
|
+
"Set it up before installing.")
|
|
343
|
+
try:
|
|
344
|
+
lease = Lease.device(record["wdaUrl"])
|
|
345
|
+
except LeaseHeld:
|
|
346
|
+
raise ToolError("Another Mobster process is using this device. Stop its run before installing "
|
|
347
|
+
"an app.") from None
|
|
348
|
+
held.callback(lease.close)
|
|
349
|
+
folder = self.runs_dir if record.get("kind") == "simulator" else self.device_runs_dir
|
|
350
|
+
run_id, run_dir = make_run_dir(folder)
|
|
351
|
+
if session is not None:
|
|
352
|
+
held.enter_context(self._locked(session))
|
|
353
|
+
run_id, run_dir = session.run_id, session.run.run_dir
|
|
354
|
+
try:
|
|
355
|
+
with record_action(run_dir, "INSTALL_APP"):
|
|
356
|
+
result = installer(record, path, progress=self.log)
|
|
357
|
+
except PhoneIOError as error:
|
|
358
|
+
raise ToolError(sentence(error) + (" " + sentence(error.fix) if error.fix else "")) from None
|
|
359
|
+
return ToolResult(f"Installed {result.get('bundle_id') or Path(path).name} on {record['name']} in "
|
|
360
|
+
f"{result.get('seconds', 0):g} s. Open it with launch_app.",
|
|
361
|
+
jsonable({**result, "run_id": run_id, "run_dir": str(run_dir)}))
|
|
362
|
+
|
|
363
|
+
def tool_unlock_status(self, args, call):
|
|
364
|
+
from .direct import lockscreen_available, passcode_saved, unlock_settings
|
|
365
|
+
record = self._record_for(args)
|
|
366
|
+
phone = record.get("kind") != "simulator"
|
|
367
|
+
if not (phone and lockscreen_available()):
|
|
368
|
+
# This build never unlocks: the answer is whether the phone is locked and that the user has to unlock it.
|
|
369
|
+
locked = self._locked_now(record)
|
|
370
|
+
status = {"device": record["id"], "supported": False, "locked": locked}
|
|
371
|
+
if not phone:
|
|
372
|
+
return ToolResult(f"“{record['name']}” is a simulator: it has no passcode.", status)
|
|
373
|
+
from .. import owner_presence
|
|
374
|
+
if locked is True and owner_presence.is_owner_active(record.get("wdaUrl")):
|
|
375
|
+
# The owner is on the phone: nothing to ask of them, and nothing for the agent to wait on.
|
|
376
|
+
state, ask = "is in use by its owner", " Leave it alone and try again later."
|
|
377
|
+
status["owner"] = "active"
|
|
378
|
+
elif locked is True:
|
|
379
|
+
state, ask = "is locked", " Ask the user to unlock it."
|
|
380
|
+
elif locked is False:
|
|
381
|
+
state, ask = "is unlocked", ""
|
|
382
|
+
else:
|
|
383
|
+
state, ask = "may be locked: its runner didn't answer", " If it is, ask the user to unlock it."
|
|
384
|
+
return ToolResult(f"“{record['name']}” {state}. Mobster can't unlock an iPhone and never enters a "
|
|
385
|
+
f"passcode.{ask}", status)
|
|
386
|
+
settings = unlock_settings(record)
|
|
387
|
+
status = {"device": record["id"], "supported": True,
|
|
388
|
+
"enabled": bool(settings.get("enabled")), "passcode_saved": passcode_saved(record),
|
|
389
|
+
"scripts_may_unlock": bool(settings.get("scripts")),
|
|
390
|
+
"ask_before_unlocking": bool(settings.get("askBeforeUnlocking")),
|
|
391
|
+
"relock_after": bool(settings.get("relockAfter")), "needs_check": bool(settings.get("needsCheck")),
|
|
392
|
+
"locked": self._locked_now(record)}
|
|
393
|
+
words = [f"“{record['name']}”:", "unlocking with a passcode is " + ("on" if status["enabled"] else "off") + ";",
|
|
394
|
+
"a passcode is " + ("saved" if status["passcode_saved"] else "not saved") + ";",
|
|
395
|
+
"scripts and MCP " + ("may" if status["scripts_may_unlock"] else "may not") + " unlock it"]
|
|
396
|
+
if status["needs_check"]:
|
|
397
|
+
words.append("; the saved passcode failed once, so it must be entered again in the Mobster app")
|
|
398
|
+
if status["locked"] is not None:
|
|
399
|
+
words.append("; it is " + ("locked" if status["locked"] else "unlocked") + " now")
|
|
400
|
+
return ToolResult(" ".join(words).replace(" ;", ";") + ".", status)
|
|
401
|
+
|
|
402
|
+
def tool_device_status(self, args, call):
|
|
403
|
+
from .. import device_status
|
|
404
|
+
record = self._record_for(args)
|
|
405
|
+
phone = device_status.read_phone(record.get("kind"), udid=record.get("udid"), wda_url=record.get("wdaUrl"))
|
|
406
|
+
mac = device_status.read_mac()
|
|
407
|
+
holder, queue = self._holder_of(record)
|
|
408
|
+
status = {"device": record["id"], "phone": phone, "mac": mac, "holder": holder, "queue": queue}
|
|
409
|
+
text = " ".join(device_status.describe(record.get("name") or record["id"], phone, mac))
|
|
410
|
+
if holder:
|
|
411
|
+
text += f" {self._holder_words(holder, queue)}."
|
|
412
|
+
return ToolResult(text, status)
|
|
413
|
+
|
|
414
|
+
def tool_listen(self, args, call):
|
|
415
|
+
from .. import listen
|
|
416
|
+
from ..harness_api import fence
|
|
417
|
+
record = self._record_for(args)
|
|
418
|
+
if record.get("kind") != "usb":
|
|
419
|
+
raise ToolError(f"Mobster can hear only an iPhone on this Mac's USB; “{record['name']}” isn't one. A "
|
|
420
|
+
"simulator plays through this Mac's own speakers.")
|
|
421
|
+
seconds = args.get("seconds") or listen.DEFAULT_SECONDS
|
|
422
|
+
try:
|
|
423
|
+
result = listen.listen(seconds, udid=record.get("udid"), locale=args.get("locale"))
|
|
424
|
+
except listen.ListenError as error:
|
|
425
|
+
if error.code in listen.AFTER_CAPTURE:
|
|
426
|
+
self._after_capture(record)
|
|
427
|
+
raise ToolError(sentence(error)) from None
|
|
428
|
+
self._after_capture(record)
|
|
429
|
+
name = record["name"]
|
|
430
|
+
if result["text"]:
|
|
431
|
+
text = fence(f"What “{name}” played in {result['seconds']} s", result["text"])
|
|
432
|
+
elif result["silent"]:
|
|
433
|
+
text = f"Nothing was heard on “{name}” in {result['seconds']} s. {listen.silence_note(result)}"
|
|
434
|
+
else:
|
|
435
|
+
text = f"“{name}” played sound for {result['seconds']} s, but no words were recognized in it."
|
|
436
|
+
from ..watch import clock_text
|
|
437
|
+
sounds = [sound for sound in result.get("sounds") or () if sound["label"] != "speech" or not result["text"]]
|
|
438
|
+
if sounds:
|
|
439
|
+
text += "\n" + fence(f"The sounds “{name}” played, told apart on this Mac",
|
|
440
|
+
"\n".join(listen.sound_lines(sounds, clock=clock_text)))
|
|
441
|
+
return ToolResult(cap_text(text), {"device": record["id"], **result})
|
|
442
|
+
|
|
443
|
+
def _after_capture(self, record):
|
|
444
|
+
# listen's capture resets the phone's USB network link and ends its runner (drivers.WDA_LINK_BLIP_SECONDS).
|
|
445
|
+
from .. import drivers, native_helpers
|
|
446
|
+
url = (record.get("wdaUrl") or "").rstrip("/")
|
|
447
|
+
if not url or not native_helpers.devices_allowed():
|
|
448
|
+
return
|
|
449
|
+
session = self._open_direct(record["id"])
|
|
450
|
+
driver = getattr(getattr(session, "run", None), "driver", None) if session is not None else None
|
|
451
|
+
try:
|
|
452
|
+
if callable(getattr(driver, "after_capture", None)):
|
|
453
|
+
driver.after_capture()
|
|
454
|
+
return
|
|
455
|
+
probe = drivers.HTTP(url)
|
|
456
|
+
|
|
457
|
+
def status_ok(timeout):
|
|
458
|
+
try:
|
|
459
|
+
value = probe.request("GET", "/status", timeout=timeout).get("value")
|
|
460
|
+
return isinstance(value, dict) and value.get("ready", True) is not False
|
|
461
|
+
except Exception: # noqa: BLE001
|
|
462
|
+
probe.close()
|
|
463
|
+
return False
|
|
464
|
+
started = time.monotonic()
|
|
465
|
+
drivers.wait_out_capture(status_ok)
|
|
466
|
+
while time.monotonic() - started < drivers.WDA_RECOVER_SECONDS and not status_ok(3):
|
|
467
|
+
time.sleep(.5)
|
|
468
|
+
probe.close()
|
|
469
|
+
except Exception: # noqa: BLE001
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
def _locked_now(self, record):
|
|
473
|
+
url = (record.get("wdaUrl") or "").rstrip("/")
|
|
474
|
+
if not url:
|
|
475
|
+
return None
|
|
476
|
+
try:
|
|
477
|
+
value = self._wda_get(url + "/wda/locked", 2.0)
|
|
478
|
+
except Exception:
|
|
479
|
+
return None
|
|
480
|
+
return value if isinstance(value, bool) else None
|
|
481
|
+
|
|
482
|
+
def tool_unlock(self, args, call):
|
|
483
|
+
from .direct import PhoneStop, lockscreen_available, unlock_settings
|
|
484
|
+
record = self._record_for(args)
|
|
485
|
+
name = record["name"]
|
|
486
|
+
if record.get("kind") == "simulator":
|
|
487
|
+
raise ToolError(f"“{name}” is a simulator: it has no passcode to enter.")
|
|
488
|
+
if not lockscreen_available():
|
|
489
|
+
raise ToolError(CANT_UNLOCK.format(name=name))
|
|
490
|
+
settings = unlock_settings(record)
|
|
491
|
+
if not (settings.get("enabled") and settings.get("scripts")):
|
|
492
|
+
raise ToolError(UNLOCK_OFF.format(name=name))
|
|
493
|
+
if settings.get("needsCheck"):
|
|
494
|
+
raise ToolError(f"The saved passcode didn't unlock “{name}” last time, so Mobster won't try it again until "
|
|
495
|
+
"its owner re-enters it in the Mobster app: Settings › iPhones.")
|
|
496
|
+
session = self._open_direct(record["id"])
|
|
497
|
+
if session is not None and session.open:
|
|
498
|
+
with self._locked(session, check_phone=False):
|
|
499
|
+
try:
|
|
500
|
+
verdict = session.run.check_phone("resume")
|
|
501
|
+
except PhoneStop as stop:
|
|
502
|
+
raise ToolError(sentence(stop)) from None
|
|
503
|
+
else:
|
|
504
|
+
session = self._device_session(record["id"])
|
|
505
|
+
verdict = getattr(session.run, "preflight", None)
|
|
506
|
+
state = getattr(verdict, "state", None) or "ready"
|
|
507
|
+
unlocked = state == "unlocked"
|
|
508
|
+
session.forget_outline()
|
|
509
|
+
return ToolResult(f"Mobster unlocked “{name}”." if unlocked else f"“{name}” is unlocked.",
|
|
510
|
+
{"run_id": session.run_id, "device": record["id"], "unlocked": unlocked, "state": state})
|