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,427 @@
|
|
|
1
|
+
"""`mobster phone`: the phone's clipboard, files in an app's folder, Photos both ways, deep links, buttons, and
|
|
2
|
+
installing a build, and typing a password privately (devtools.py registers it).
|
|
3
|
+
|
|
4
|
+
``add_arguments(parser, helpers)`` adds the subcommands; ``run(args)`` runs one and returns its exit code:
|
|
5
|
+
0 done, 1 it failed, 2 usage, 3 no device (or no Xcode tools), 130 ctrl+c. It never raises.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from . import PhoneIOError, choose_device, exit_code
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add_arguments(parser, helpers):
|
|
15
|
+
path_type = helpers.get("path_type")
|
|
16
|
+
subs = parser.add_subparsers(dest="phone_command", metavar="<subcommand>")
|
|
17
|
+
subs.required = True
|
|
18
|
+
|
|
19
|
+
def common(sub):
|
|
20
|
+
sub.add_argument("--device", metavar="NAME",
|
|
21
|
+
help="the device: an id, UDID or name from `mobster devices` (default: your iPhone)")
|
|
22
|
+
sub.add_argument("--json", action="store_true", help="print one JSON object")
|
|
23
|
+
|
|
24
|
+
clipboard = subs.add_parser("clipboard", help="set or read the phone's clipboard (text, at most 64 KB)",
|
|
25
|
+
description="Set the phone's clipboard from the Mac, or print what it holds.")
|
|
26
|
+
actions = clipboard.add_subparsers(dest="clipboard_command", metavar="<set|get>")
|
|
27
|
+
actions.required = True
|
|
28
|
+
set_ = actions.add_parser("set", help="put text on the phone's clipboard",
|
|
29
|
+
description="Put text on the phone's clipboard: TEXT, a file's text with --file, or "
|
|
30
|
+
"standard input with -. At most 64 KB.")
|
|
31
|
+
set_.add_argument("text", nargs="?", metavar="TEXT", help="the text, or - for standard input")
|
|
32
|
+
set_.add_argument("--file", type=path_type, metavar="FILE", help="put this UTF-8 file's text on the clipboard")
|
|
33
|
+
common(set_)
|
|
34
|
+
get = actions.add_parser("get", help="print the phone's clipboard",
|
|
35
|
+
description="Print the phone's clipboard. WebDriverAgent's runner comes to the front for "
|
|
36
|
+
"a moment to read it (iOS lets only the front app read the clipboard). "
|
|
37
|
+
"Only the CLI reads it: no MCP tool does, because it may hold a password.")
|
|
38
|
+
common(get)
|
|
39
|
+
|
|
40
|
+
files = subs.add_parser("file", help="move files between this Mac and an app's folder on the iPhone (USB)",
|
|
41
|
+
description="Copy files between this Mac and the Documents folder of an iPhone app that "
|
|
42
|
+
"shares files (the folder Files shows under On My iPhone). Over the cable "
|
|
43
|
+
"only. Nothing else on the phone is reachable, the camera roll included.",
|
|
44
|
+
epilog="exit codes: 0 done, 1 it failed, 2 usage, 3 no iPhone on a cable or no iPhone "
|
|
45
|
+
"tools")
|
|
46
|
+
verbs = files.add_subparsers(dest="file_command", metavar="<put|get|ls>")
|
|
47
|
+
verbs.required = True
|
|
48
|
+
put = verbs.add_parser("put", help="copy a file from this Mac into an app's folder",
|
|
49
|
+
description="Copy a file (up to 100 MB) into an app's folder on the iPhone. A file with "
|
|
50
|
+
"the same name is kept: the copy gets a new name (menu 2.pdf) unless you pass "
|
|
51
|
+
"--replace.")
|
|
52
|
+
put.add_argument("path", type=path_type, metavar="PATH", help="the file on this Mac")
|
|
53
|
+
put.add_argument("--app", required=True, metavar="BUNDLE",
|
|
54
|
+
help="the app's bundle ID, such as com.apple.Pages (`mobster phone file ls` lists them)")
|
|
55
|
+
put.add_argument("--name", metavar="NAME", help="the name it gets on the iPhone (default: its own)")
|
|
56
|
+
put.add_argument("--replace", action="store_true", help="overwrite a file with the same name")
|
|
57
|
+
put.add_argument("--container", action="store_true",
|
|
58
|
+
help="the app's own Documents folder, for a development build of yours that doesn't share files")
|
|
59
|
+
common(put)
|
|
60
|
+
get_file = verbs.add_parser("get", help="copy a file from an app's folder to this Mac",
|
|
61
|
+
description="Copy a file from an app's folder on the iPhone to this Mac.")
|
|
62
|
+
get_file.add_argument("name", metavar="NAME", help="the file's name in the app's folder")
|
|
63
|
+
get_file.add_argument("--app", required=True, metavar="BUNDLE", help="the app's bundle ID")
|
|
64
|
+
get_file.add_argument("-o", "--output", metavar="PATH",
|
|
65
|
+
help="where to save it: a folder, or a file name (default: the current folder)")
|
|
66
|
+
get_file.add_argument("--container", action="store_true",
|
|
67
|
+
help="the app's own Documents folder, for a development build of yours")
|
|
68
|
+
common(get_file)
|
|
69
|
+
ls = verbs.add_parser("ls", help="list the apps that share files, or the files in one",
|
|
70
|
+
description="Without --app, list the iPhone's apps whose folders show in Files. With "
|
|
71
|
+
"--app, list the files in that app's folder.")
|
|
72
|
+
ls.add_argument("--app", metavar="BUNDLE", help="the app's bundle ID")
|
|
73
|
+
ls.add_argument("--container", action="store_true",
|
|
74
|
+
help="the app's own Documents folder, for a development build of yours")
|
|
75
|
+
common(ls)
|
|
76
|
+
|
|
77
|
+
media = subs.add_parser("photos",
|
|
78
|
+
help="add photos and videos to the iPhone's Photos, or copy the newest to this Mac",
|
|
79
|
+
description="Add a photo or video from this Mac to the iPhone's Photos library (Mobster's "
|
|
80
|
+
"runner saves it; the first time, iOS asks on the iPhone), or copy the newest "
|
|
81
|
+
"photos, screenshots and screen recordings from its camera roll to this Mac "
|
|
82
|
+
"(over the cable).",
|
|
83
|
+
epilog="exit codes: 0 done, 1 it failed, 2 usage, "
|
|
84
|
+
"3 no iPhone on a cable or no iPhone tools")
|
|
85
|
+
media_verbs = media.add_subparsers(dest="photos_command", metavar="<add|pull>")
|
|
86
|
+
media_verbs.required = True
|
|
87
|
+
add = media_verbs.add_parser("add", help="add photos and videos to Photos",
|
|
88
|
+
description="Add JPEGs, HEICs, PNGs, GIFs, WebPs, MOVs or MP4s (up to 4 GB each, 50 "
|
|
89
|
+
"at a time) to the iPhone's Photos library, into the album Mobster unless you name "
|
|
90
|
+
"another. Each one is checked by SHA-256 on the iPhone, and the library's count is "
|
|
91
|
+
"read before and after.")
|
|
92
|
+
add.add_argument("path", type=path_type, nargs="+", metavar="PATH", help="the photos or videos on this Mac")
|
|
93
|
+
add.add_argument("--album", metavar="NAME", default="Mobster",
|
|
94
|
+
help="the album they go into, made if it isn't there (default Mobster); --album '' for none")
|
|
95
|
+
add.add_argument("--via", choices=["auto", "cable", "wifi", "airdrop", "mirroring", "icloud"], default="auto",
|
|
96
|
+
metavar="ROUTE", help="auto (default), cable, wifi, airdrop, mirroring or icloud")
|
|
97
|
+
add.add_argument("--profile", metavar="PROFILE", help="send to this profile's iPhone (Settings › Profiles)")
|
|
98
|
+
common(add)
|
|
99
|
+
pull = media_verbs.add_parser("pull", help="copy the newest photos and recordings to this Mac",
|
|
100
|
+
description="Copy the newest photos, screenshots and screen recordings from the "
|
|
101
|
+
"iPhone, newest first, each checked by SHA-256 or size. Over the cable "
|
|
102
|
+
"by default; --via wifi reads them through Mobster's runner, --via "
|
|
103
|
+
"tunnel through Apple's own tunnel (needs pymobiledevice3), --via "
|
|
104
|
+
"icloud from this Mac's Photos library.")
|
|
105
|
+
pull.add_argument("-n", "--count", type=int, default=1, metavar="N", help="how many, 1 to 20 (default 1)")
|
|
106
|
+
pull.add_argument("--since", metavar="WHEN", help="only newer ones: 10m, 2h, 1d or an ISO time")
|
|
107
|
+
pull.add_argument("--jpeg", action="store_true", help="convert HEIC photos to JPEG (over the cable)")
|
|
108
|
+
pull.add_argument("-o", "--output", metavar="DIR", help="the folder to save into (default: the current folder)")
|
|
109
|
+
pull.add_argument("--via", choices=["auto", "cable", "wifi", "tunnel", "icloud"], default="auto", metavar="ROUTE",
|
|
110
|
+
help="auto (default), cable, wifi, tunnel or icloud")
|
|
111
|
+
pull.add_argument("--profile", metavar="PROFILE", help="copy from this profile's iPhone (Settings › Profiles)")
|
|
112
|
+
common(pull)
|
|
113
|
+
|
|
114
|
+
open_ = subs.add_parser("open", help="open a URL or deep link on the phone",
|
|
115
|
+
description="Open a URL or deep link on the phone, such as maps://?q=coffee or "
|
|
116
|
+
"App-prefs:WIFI. Links that act as they open (tel:, facetime:, running a "
|
|
117
|
+
"shortcut) ask for --yes.")
|
|
118
|
+
open_.add_argument("url", metavar="URL", help="the link")
|
|
119
|
+
open_.add_argument("--yes", action="store_true",
|
|
120
|
+
help="open a link that asks first: a call, a message, a shortcut run, or a new app's link")
|
|
121
|
+
common(open_)
|
|
122
|
+
|
|
123
|
+
button = subs.add_parser("button", help="press a hardware button, or lock the screen",
|
|
124
|
+
description="Press Home, Volume Up or Volume Down, or lock the screen. Mobster can't "
|
|
125
|
+
"unlock an iPhone: after lock, unlock it yourself.")
|
|
126
|
+
button.add_argument("name", choices=["home", "volume_up", "volume_down", "lock"], metavar="BUTTON",
|
|
127
|
+
help="home, volume_up, volume_down or lock")
|
|
128
|
+
common(button)
|
|
129
|
+
|
|
130
|
+
install = subs.add_parser("install", help="install a signed .app or .ipa on the device",
|
|
131
|
+
description="Install a build on a USB iPhone (xcrun devicectl) or a Mobster simulator "
|
|
132
|
+
"(xcrun simctl). The build must be signed for that iPhone, and the iPhone "
|
|
133
|
+
"needs Developer Mode on.",
|
|
134
|
+
epilog="exit codes: 0 installed, 1 the install failed, 2 usage, 3 no device or no Xcode")
|
|
135
|
+
install.add_argument("path", metavar="PATH", help="the .app folder or .ipa file")
|
|
136
|
+
common(install)
|
|
137
|
+
|
|
138
|
+
record = subs.add_parser("record", help="record a USB iPhone's screen to a movie on this Mac",
|
|
139
|
+
description="Record a paired iPhone's screen, and the audio it offers, over USB into a "
|
|
140
|
+
"new .mov file, without a WebDriverAgent session. It uses the capture "
|
|
141
|
+
"device QuickTime Player records from, so macOS asks once whether Mobster "
|
|
142
|
+
"may use the camera and microphone. iOS shows its demonstration status "
|
|
143
|
+
"bar while it records, and protected content may not be recorded. It "
|
|
144
|
+
"never overwrites a file.",
|
|
145
|
+
epilog="exit codes: 0 saved, 1 it failed, 2 usage, 3 no iPhone on a cable or no "
|
|
146
|
+
"native helper")
|
|
147
|
+
record.add_argument("--output", required=True, type=path_type, metavar="FILE", help="write a new .mov file")
|
|
148
|
+
record.add_argument("--seconds", type=float, default=10, metavar="N",
|
|
149
|
+
help="how long to record, 1 to 300 (default 10)")
|
|
150
|
+
common(record)
|
|
151
|
+
|
|
152
|
+
from . import network_cli
|
|
153
|
+
network_cli.add_arguments(subs, helpers)
|
|
154
|
+
from . import secrets
|
|
155
|
+
secrets.add_arguments(subs, helpers)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def run(args):
|
|
159
|
+
as_json = bool(getattr(args, "json", False))
|
|
160
|
+
try:
|
|
161
|
+
command = args.phone_command
|
|
162
|
+
if command == "password":
|
|
163
|
+
from . import secrets
|
|
164
|
+
return secrets.run(args)
|
|
165
|
+
if command == "network":
|
|
166
|
+
from . import network_cli
|
|
167
|
+
return network_cli.run(args)
|
|
168
|
+
if command == "clipboard" and args.clipboard_command == "set":
|
|
169
|
+
return _clipboard_set(args, as_json)
|
|
170
|
+
if command == "clipboard":
|
|
171
|
+
return _clipboard_get(args, as_json)
|
|
172
|
+
if command == "file":
|
|
173
|
+
return _file(args, as_json)
|
|
174
|
+
if command == "photos":
|
|
175
|
+
return _photos(args, as_json)
|
|
176
|
+
if command == "open":
|
|
177
|
+
return _open(args, as_json)
|
|
178
|
+
if command == "button":
|
|
179
|
+
return _button(args, as_json)
|
|
180
|
+
if command == "record":
|
|
181
|
+
return _record(args, as_json)
|
|
182
|
+
return _install(args, as_json)
|
|
183
|
+
except PhoneIOError as error:
|
|
184
|
+
return _fail(error, as_json)
|
|
185
|
+
except KeyboardInterrupt:
|
|
186
|
+
return 130
|
|
187
|
+
except Exception as error:
|
|
188
|
+
return _fail(PhoneIOError(f"Mobster hit an unexpected error ({type(error).__name__}).", "failed"), as_json)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _print(value, as_json, line):
|
|
192
|
+
try:
|
|
193
|
+
print(json.dumps(value, ensure_ascii=False) if as_json else line, flush=True)
|
|
194
|
+
except BrokenPipeError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _fail(error, as_json):
|
|
199
|
+
if as_json:
|
|
200
|
+
_print({"ok": False, "error": error.public()}, True, "")
|
|
201
|
+
else:
|
|
202
|
+
print(f"mobster phone: {error}" + (f" {error.fix}" if error.fix else ""), file=sys.stderr, flush=True)
|
|
203
|
+
return exit_code(error)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _clipboard_set(args, as_json):
|
|
207
|
+
from . import wda
|
|
208
|
+
from .clipboard import read_text_file, set_text
|
|
209
|
+
if args.file and args.text:
|
|
210
|
+
raise PhoneIOError("Pass TEXT or --file, not both.", "usage")
|
|
211
|
+
if args.file:
|
|
212
|
+
text = read_text_file(args.file)
|
|
213
|
+
elif args.text == "-":
|
|
214
|
+
text = sys.stdin.read()
|
|
215
|
+
elif args.text is not None:
|
|
216
|
+
text = args.text
|
|
217
|
+
else:
|
|
218
|
+
raise PhoneIOError("Pass the text, --file FILE, or - to read standard input.", "usage")
|
|
219
|
+
record = choose_device(args.device)
|
|
220
|
+
with wda(record) as request:
|
|
221
|
+
count = set_text(request, text)
|
|
222
|
+
_print({"ok": True, "device": record["id"], "characters": count}, as_json,
|
|
223
|
+
f"Put {count:,} character{'s' if count != 1 else ''} on {record['name']}'s clipboard.")
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _clipboard_get(args, as_json):
|
|
228
|
+
from . import wda
|
|
229
|
+
from .clipboard import get_text
|
|
230
|
+
record = choose_device(args.device)
|
|
231
|
+
with wda(record) as request:
|
|
232
|
+
text = get_text(request)
|
|
233
|
+
if as_json:
|
|
234
|
+
_print({"ok": True, "device": record["id"], "text": text}, True, "")
|
|
235
|
+
else:
|
|
236
|
+
try:
|
|
237
|
+
sys.stdout.write(text + ("" if text.endswith("\n") or not text else "\n"))
|
|
238
|
+
sys.stdout.flush()
|
|
239
|
+
except BrokenPipeError:
|
|
240
|
+
pass
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _install(args, as_json):
|
|
245
|
+
from ..journal import Lease, LeaseHeld
|
|
246
|
+
from .install import install
|
|
247
|
+
record = choose_device(args.device)
|
|
248
|
+
# A run holds the phone's lease for as long as it drives the phone; an install in the middle of one would
|
|
249
|
+
# replace the app under it. A phone with no WebDriverAgent address cannot be in a run.
|
|
250
|
+
try:
|
|
251
|
+
lease = Lease.device(record["wdaUrl"]) if record.get("wdaUrl") else None
|
|
252
|
+
except LeaseHeld:
|
|
253
|
+
raise PhoneIOError(f"Another Mobster process is using {record['name']}.", "device_busy",
|
|
254
|
+
"Stop its run, then install the app.") from None
|
|
255
|
+
|
|
256
|
+
def progress(line):
|
|
257
|
+
print(line, file=sys.stderr, flush=True)
|
|
258
|
+
try:
|
|
259
|
+
result = install(record, args.path, progress=progress)
|
|
260
|
+
finally:
|
|
261
|
+
if lease is not None:
|
|
262
|
+
lease.close()
|
|
263
|
+
_print(result, as_json, f"Installed {result.get('bundle_id') or args.path} on {record['name']} "
|
|
264
|
+
f"in {result['seconds']:g} s.")
|
|
265
|
+
return 0
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _record(args, as_json):
|
|
269
|
+
"""`mobster phone record`: the person's own command, naming the file, is the go-ahead."""
|
|
270
|
+
from .recording import record_video
|
|
271
|
+
if not 1 <= args.seconds <= 300:
|
|
272
|
+
raise PhoneIOError("--seconds is 1 to 300.", "usage")
|
|
273
|
+
record = choose_device(args.device)
|
|
274
|
+
if record.get("kind") != "usb" or not record.get("udid"):
|
|
275
|
+
raise PhoneIOError("Screen recording needs a paired iPhone connected by USB.", "needs_cable")
|
|
276
|
+
result = record_video(record["udid"], seconds=args.seconds, output=args.output)
|
|
277
|
+
_print(result, as_json, f"Saved {result['durationSeconds']:g} seconds of video to {result['path']}.")
|
|
278
|
+
return 0
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _file(args, as_json):
|
|
282
|
+
"""`mobster phone file put|get|ls`: the user's own command is the go-ahead for a copy it names."""
|
|
283
|
+
from pathlib import Path
|
|
284
|
+
|
|
285
|
+
from .files import PhoneFiles, land
|
|
286
|
+
from .install import describe_path
|
|
287
|
+
record = choose_device(args.device)
|
|
288
|
+
phone = PhoneFiles(record, container=bool(getattr(args, "container", False)))
|
|
289
|
+
verb = args.file_command
|
|
290
|
+
if verb == "ls":
|
|
291
|
+
if not args.app:
|
|
292
|
+
apps = phone.apps()
|
|
293
|
+
lines = [f"{app['name']} {app['bundleId']}" for app in apps] or [
|
|
294
|
+
f"No app on {phone.name} keeps files you can see in Files. Pages, Numbers and Keynote do."]
|
|
295
|
+
_print({"ok": True, "device": record["id"], "apps": apps}, as_json, "\n".join(lines))
|
|
296
|
+
return 0
|
|
297
|
+
entries = phone.ls(args.app)
|
|
298
|
+
from ..attachments.sniff import size_words
|
|
299
|
+
lines = [f"{e['name']}{'/' if e['folder'] else ''} {'' if e['folder'] else size_words(e['bytes'])}".rstrip()
|
|
300
|
+
for e in entries] or [f"{args.app}'s folder is empty."]
|
|
301
|
+
_print({"ok": True, "device": record["id"], "app": args.app, "files": entries}, as_json, "\n".join(lines))
|
|
302
|
+
return 0
|
|
303
|
+
if verb == "put":
|
|
304
|
+
result = phone.put(args.app, args.path, name=args.name, replace=args.replace)
|
|
305
|
+
_print({"ok": True, "device": record["id"], **result}, as_json,
|
|
306
|
+
f"Put {result['name']} in {args.app}'s folder on {phone.name}.")
|
|
307
|
+
return 0
|
|
308
|
+
output = Path(args.output).expanduser() if args.output else Path.cwd()
|
|
309
|
+
if args.output and not output.is_dir() and (output.suffix or not str(args.output).endswith("/")):
|
|
310
|
+
folder, rename = output.parent, output.name
|
|
311
|
+
else:
|
|
312
|
+
folder, rename = output, None
|
|
313
|
+
saved = phone.get(args.app, args.name, folder)
|
|
314
|
+
if rename and saved.name != rename:
|
|
315
|
+
target = folder / rename
|
|
316
|
+
try:
|
|
317
|
+
saved = land(saved, folder, rename, exact=True)
|
|
318
|
+
except FileExistsError:
|
|
319
|
+
saved.unlink(missing_ok=True)
|
|
320
|
+
raise PhoneIOError(f"{describe_path(target)} already exists.", "usage",
|
|
321
|
+
"Choose another name with -o.") from None
|
|
322
|
+
_print({"ok": True, "device": record["id"], "path": str(saved), "bytes": saved.stat().st_size}, as_json,
|
|
323
|
+
f"Saved {args.name} to {describe_path(saved)}.")
|
|
324
|
+
return 0
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _photos(args, as_json):
|
|
328
|
+
"""`mobster phone photos add|pull`: the user's own command is the go-ahead. Both go through transfer.py."""
|
|
329
|
+
from pathlib import Path
|
|
330
|
+
|
|
331
|
+
from .install import describe_path
|
|
332
|
+
from .photos import parse_since
|
|
333
|
+
from .transfer import Transfer, summary, target
|
|
334
|
+
from .transfer import run as run_transfer
|
|
335
|
+
if getattr(args, "profile", None):
|
|
336
|
+
phone = target(profile=args.profile, device=args.device)
|
|
337
|
+
else:
|
|
338
|
+
phone = target(device=args.device)
|
|
339
|
+
if args.photos_command == "add":
|
|
340
|
+
transfer = Transfer("to_phone", "photos", phone, via=args.via, paths=[str(p) for p in args.path],
|
|
341
|
+
album=args.album or None)
|
|
342
|
+
progress = _Progress(as_json)
|
|
343
|
+
transfer.listeners.append(progress)
|
|
344
|
+
run_transfer(transfer)
|
|
345
|
+
_print({"ok": transfer.state == "done", "device": phone.id, **transfer.public()}, as_json, summary(transfer))
|
|
346
|
+
return 0 if transfer.state == "done" else 1
|
|
347
|
+
if not 1 <= int(args.count) <= 20:
|
|
348
|
+
raise PhoneIOError("--count is 1 to 20.", "usage")
|
|
349
|
+
try:
|
|
350
|
+
since = parse_since(args.since)
|
|
351
|
+
except ValueError as error:
|
|
352
|
+
raise PhoneIOError(str(error), "usage") from None
|
|
353
|
+
folder = Path(args.output).expanduser() if args.output else Path.cwd()
|
|
354
|
+
transfer = Transfer("to_mac", "photos", phone, via=args.via, folder=str(folder), count=int(args.count),
|
|
355
|
+
since=since, convert_heic=bool(args.jpeg))
|
|
356
|
+
run_transfer(transfer)
|
|
357
|
+
if transfer.state != "done" and not transfer.items:
|
|
358
|
+
error = transfer.error or {}
|
|
359
|
+
raise PhoneIOError(error.get("message") or "Nothing moved.", error.get("code") or "failed",
|
|
360
|
+
error.get("fix") or "")
|
|
361
|
+
lines = [f"Saved {describe_path(Path(item.path))}" for item in transfer.items if item.path] or [
|
|
362
|
+
f"Nothing in {phone.name}'s camera roll" + (" since then." if since else ".")]
|
|
363
|
+
_print({"ok": transfer.state == "done", "device": phone.id, **transfer.public(),
|
|
364
|
+
"files": [i.public() for i in transfer.items if i.path]}, as_json, "\n".join(lines))
|
|
365
|
+
return 0 if transfer.state == "done" else 1
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class _Progress:
|
|
369
|
+
"""Each item's progress on standard error, one line per quarter, so a long video shows it's moving."""
|
|
370
|
+
|
|
371
|
+
def __init__(self, quiet):
|
|
372
|
+
self.quiet, self.shown = quiet, {}
|
|
373
|
+
|
|
374
|
+
def __call__(self, transfer):
|
|
375
|
+
if self.quiet:
|
|
376
|
+
return
|
|
377
|
+
for item in transfer.items:
|
|
378
|
+
if not item.bytes:
|
|
379
|
+
continue
|
|
380
|
+
quarter = int(4 * item.sent / item.bytes) if item.state == "sending" else None
|
|
381
|
+
if quarter is not None and quarter > self.shown.get(item.name, 0) and quarter < 4:
|
|
382
|
+
self.shown[item.name] = quarter
|
|
383
|
+
print(f"{item.name}: {quarter * 25}%", file=sys.stderr, flush=True)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _open(args, as_json):
|
|
387
|
+
from ..deeplinks import LinkError, app_for, decide, open_link
|
|
388
|
+
from ..permissions.exceptions import Exceptions, default_path
|
|
389
|
+
from . import Calls, wda
|
|
390
|
+
try:
|
|
391
|
+
decision = decide(args.url, Exceptions(default_path()).contains)
|
|
392
|
+
except LinkError as error:
|
|
393
|
+
raise PhoneIOError(str(error), "usage") from None
|
|
394
|
+
# deeplinks.decide's rule: --yes is your OK for a link that asks.
|
|
395
|
+
if decision.action == "ask" and not args.yes:
|
|
396
|
+
if decision.always:
|
|
397
|
+
raise PhoneIOError(f"{args.url} acts as soon as it opens (a call, FaceTime, a message, an email or a "
|
|
398
|
+
"shortcut).", "usage", "Pass --yes to open it.")
|
|
399
|
+
raise PhoneIOError(f"{args.url} opens {decision.app_name}, an app Mobster asks about before its first link.",
|
|
400
|
+
"usage", "Pass --yes to open it.")
|
|
401
|
+
record = choose_device(args.device)
|
|
402
|
+
with wda(record) as request:
|
|
403
|
+
driver = Calls(request)
|
|
404
|
+
opened = open_link(driver, args.url, bundle=app_for(args.url) or None)
|
|
405
|
+
said = f"Opened {args.url} on {record['name']}."
|
|
406
|
+
if opened.get("requested"): # deeplinks.APP_FORMS: the web address opens the home feed, so its app link opened
|
|
407
|
+
said = (f"Opened {opened['url']} on {record['name']}: the app link for {opened['requested']}, which opens "
|
|
408
|
+
"the home feed.")
|
|
409
|
+
_print({"ok": True, "device": record["id"], **opened}, as_json, said)
|
|
410
|
+
return 0
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _button(args, as_json):
|
|
414
|
+
from . import wda
|
|
415
|
+
from .controls import LOCKED_AFTER, lock, press
|
|
416
|
+
record = choose_device(args.device)
|
|
417
|
+
with wda(record) as request:
|
|
418
|
+
if args.name == "lock":
|
|
419
|
+
lock(request)
|
|
420
|
+
else:
|
|
421
|
+
press(request, args.name, simulator=record.get("kind") == "simulator")
|
|
422
|
+
words = {"lock": "Locked " + record["name"] + "."
|
|
423
|
+
+ ("" if record.get("kind") == "simulator" else " " + LOCKED_AFTER),
|
|
424
|
+
"home": "Pressed Home.", "volume_up": "Pressed Volume Up.", "volume_down": "Pressed Volume Down."}
|
|
425
|
+
_print({"ok": True, "device": record["id"], "button": args.name}, as_json, words[args.name])
|
|
426
|
+
return 0
|
|
427
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""The phone's clipboard through WebDriverAgent; reading it is CLI only, as it may hold a password."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
|
|
5
|
+
from . import PhoneIOError
|
|
6
|
+
|
|
7
|
+
LIMIT = 64 * 1024
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def encode(text):
|
|
11
|
+
"""``text`` as WDA's base64 content; PhoneIOError (usage) when it is empty, not text or over 64 KB."""
|
|
12
|
+
if not isinstance(text, str) or not text:
|
|
13
|
+
raise PhoneIOError("Give the clipboard some text.", "usage")
|
|
14
|
+
if "\x00" in text:
|
|
15
|
+
raise PhoneIOError("The clipboard takes text, not binary data.", "usage")
|
|
16
|
+
data = text.encode("utf-8")
|
|
17
|
+
if len(data) > LIMIT:
|
|
18
|
+
raise PhoneIOError(f"That is {len(data):,} bytes; the phone's clipboard takes at most 64 KB from Mobster.",
|
|
19
|
+
"usage")
|
|
20
|
+
return base64.b64encode(data).decode("ascii")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_text_file(path):
|
|
24
|
+
"""A file's text for `clipboard set --file`: UTF-8, at most 64 KB."""
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
file = Path(path).expanduser()
|
|
27
|
+
try:
|
|
28
|
+
size = file.stat().st_size
|
|
29
|
+
except OSError:
|
|
30
|
+
raise PhoneIOError(f"There is no file at {file}.", "usage") from None
|
|
31
|
+
if size > LIMIT:
|
|
32
|
+
raise PhoneIOError(f"{file.name} is {size:,} bytes; the phone's clipboard takes at most 64 KB from Mobster.",
|
|
33
|
+
"usage")
|
|
34
|
+
try:
|
|
35
|
+
return file.read_bytes().decode("utf-8")
|
|
36
|
+
except (OSError, UnicodeDecodeError):
|
|
37
|
+
raise PhoneIOError(f"{file.name} isn't UTF-8 text; the clipboard takes text only.", "usage") from None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
IMAGE_LIMIT = 10 * 1024 * 1024
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_text(request, text, driver=None):
|
|
44
|
+
"""Put ``text`` on the phone's clipboard; ``driver`` is noted, so the Allow Paste that follows is Mobster's."""
|
|
45
|
+
request("POST", "/wda/setPasteboard", {"content": encode(text), "contentType": "plaintext"}, 15)
|
|
46
|
+
if driver is not None:
|
|
47
|
+
from ..wda.system_dialogs import note_paste
|
|
48
|
+
note_paste(driver)
|
|
49
|
+
return len(text)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def set_image(request, path):
|
|
53
|
+
"""Put the PNG or JPEG at ``path`` on the phone's clipboard. Returns {"name", "bytes"}."""
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
file = Path(path).expanduser()
|
|
56
|
+
try:
|
|
57
|
+
data = file.read_bytes() if file.stat().st_size <= IMAGE_LIMIT else None
|
|
58
|
+
except OSError:
|
|
59
|
+
raise PhoneIOError(f"There is no file at {file}.", "usage") from None
|
|
60
|
+
if data is None:
|
|
61
|
+
raise PhoneIOError(f"{file.name} is over 10 MB; the clipboard takes pictures up to 10 MB from Mobster.",
|
|
62
|
+
"usage")
|
|
63
|
+
if not (data.startswith(b"\x89PNG\r\n\x1a\n") or data.startswith(b"\xff\xd8\xff")):
|
|
64
|
+
raise PhoneIOError(f"{file.name} isn't a PNG or a JPEG.", "usage")
|
|
65
|
+
request("POST", "/wda/setPasteboard", {"content": base64.b64encode(data).decode("ascii"), "contentType": "image"},
|
|
66
|
+
30)
|
|
67
|
+
return {"name": file.name, "bytes": len(data)}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_text(request):
|
|
71
|
+
"""The phone's clipboard as text ("" when it holds none)."""
|
|
72
|
+
value = request("POST", "/wda/getPasteboard", {"contentType": "plaintext"}, 20)
|
|
73
|
+
if not value:
|
|
74
|
+
return ""
|
|
75
|
+
try:
|
|
76
|
+
return base64.b64decode(value).decode("utf-8", "replace")
|
|
77
|
+
except (ValueError, TypeError):
|
|
78
|
+
raise PhoneIOError("The phone's clipboard answer wasn't readable.") from None
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Hardware buttons and system controls, for developers testing their app: what WebDriverAgent offers on a real
|
|
2
|
+
iPhone and a simulator.
|
|
3
|
+
|
|
4
|
+
- Buttons (POST /wda/pressButton): home, volume_up and volume_down (a real iPhone only: a simulator has no volume
|
|
5
|
+
buttons). The Action and Camera buttons are left out on purpose: each does whatever its owner set it to.
|
|
6
|
+
- Lock (POST /wda/lock): locks the screen. Mobster can't unlock an iPhone and never enters a passcode, so after a lock
|
|
7
|
+
the owner unlocks it (lockscreen.CAN_UNLOCK is False). On a simulator the lock screen has no passcode.
|
|
8
|
+
- Appearance (POST /wda/device/appearance, light or dark): changes the setting for the whole phone; the previous
|
|
9
|
+
value (GET /wda/device/info's userInterfaceStyle) comes back so it can be set back.
|
|
10
|
+
- Orientation (POST /orientation): portrait, landscape_left, landscape_right or portrait_upside_down. XCTest turns the
|
|
11
|
+
device the way a hand would (developer.apple.com/documentation/xcuiautomation/xcuidevice/orientation); an app that
|
|
12
|
+
supports one orientation stays in it.
|
|
13
|
+
|
|
14
|
+
Every call reports what it did in one plain sentence, and ``state`` reads appearance and orientation without
|
|
15
|
+
changing anything.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from . import PhoneIOError
|
|
19
|
+
|
|
20
|
+
BUTTONS = {"home": "home", "volume_up": "volumeup", "volume_down": "volumedown"}
|
|
21
|
+
APPEARANCES = ("light", "dark")
|
|
22
|
+
ORIENTATIONS = {"portrait": "PORTRAIT", "landscape_left": "LANDSCAPE",
|
|
23
|
+
"landscape_right": "UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT",
|
|
24
|
+
"portrait_upside_down": "UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN"}
|
|
25
|
+
ORIENTATION_NAMES = {"PORTRAIT": "portrait", "LANDSCAPE": "landscape_left",
|
|
26
|
+
"UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT": "landscape_right",
|
|
27
|
+
"UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN": "portrait_upside_down"}
|
|
28
|
+
LOCKED_AFTER = "Mobster can't unlock it again: the owner unlocks it."
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def press(call, button, *, simulator=False):
|
|
32
|
+
"""Press a hardware button. ``call(method, path, body, timeout)`` is a WebDriverAgent request."""
|
|
33
|
+
name = BUTTONS.get(button)
|
|
34
|
+
if name is None:
|
|
35
|
+
raise PhoneIOError(f"Mobster presses {', '.join(BUTTONS)}; not {button}.", "usage")
|
|
36
|
+
if simulator and button != "home":
|
|
37
|
+
raise PhoneIOError("A simulator has no volume buttons.", "usage")
|
|
38
|
+
call("POST", "/wda/pressButton", {"name": name}, 10)
|
|
39
|
+
return {"button": button}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lock(call):
|
|
43
|
+
call("POST", "/wda/lock", {}, 15)
|
|
44
|
+
return {"locked": True}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def state(call):
|
|
48
|
+
"""{"appearance": light | dark | None, "orientation": portrait | landscape_left | ... | None}."""
|
|
49
|
+
appearance = orientation = None
|
|
50
|
+
try:
|
|
51
|
+
info = call("GET", "/wda/device/info", None, 5)
|
|
52
|
+
style = (info or {}).get("userInterfaceStyle") if isinstance(info, dict) else None
|
|
53
|
+
appearance = style if style in APPEARANCES else None
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
try:
|
|
57
|
+
value = call("GET", "/orientation", None, 5)
|
|
58
|
+
orientation = ORIENTATION_NAMES.get(value) if isinstance(value, str) else None
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
return {"appearance": appearance, "orientation": orientation}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_appearance(call, value):
|
|
65
|
+
if value not in APPEARANCES:
|
|
66
|
+
raise PhoneIOError("Appearance is light or dark.", "usage")
|
|
67
|
+
before = state(call)["appearance"]
|
|
68
|
+
call("POST", "/wda/device/appearance", {"name": value}, 10)
|
|
69
|
+
return {"appearance": value, "before": before}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def set_orientation(call, value):
|
|
73
|
+
wanted = ORIENTATIONS.get(value)
|
|
74
|
+
if wanted is None:
|
|
75
|
+
raise PhoneIOError("Orientation is " + ", ".join(ORIENTATIONS) + ".", "usage")
|
|
76
|
+
before = state(call)["orientation"]
|
|
77
|
+
call("POST", "/orientation", {"orientation": wanted}, 15)
|
|
78
|
+
return {"orientation": value, "before": before}
|