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,12 @@
|
|
|
1
|
+
"""Skills: operations the agent loop hands to code instead of to the model (agent_hooks.Skill)."""
|
|
2
|
+
|
|
3
|
+
from .codes import UseCode
|
|
4
|
+
from .notifications import ReadNotifications
|
|
5
|
+
from .open_url import OpenUrl
|
|
6
|
+
|
|
7
|
+
__all__ = ["UseCode", "ReadNotifications", "OpenUrl", "default_skills"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def default_skills():
|
|
11
|
+
"""The skills every agent run offers, in prompt order."""
|
|
12
|
+
return (UseCode(), ReadNotifications(), OpenUrl())
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"""USE_CODE: a verification code from the phone, typed into the code field by code, never seen by the model."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from dataclasses import field as dataclass_field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from ..agent_hooks import MASK, Prepared, SkillResult
|
|
11
|
+
|
|
12
|
+
MAX_AGE_S = 600
|
|
13
|
+
SPRINGBOARD = "com.apple.springboard"
|
|
14
|
+
MESSAGES = "com.apple.MobileSMS"
|
|
15
|
+
# Where a code is read, never typed: a code typed into a message, a mail or a note would send or keep it.
|
|
16
|
+
REFUSED_APPS = {MESSAGES: "Messages", "com.apple.mobilemail": "Mail", "com.apple.mobilenotes": "Notes",
|
|
17
|
+
SPRINGBOARD: "the Home Screen"}
|
|
18
|
+
|
|
19
|
+
# Words that make a message a verification code's. "passcode" here is a bank's one-time passcode in a text;
|
|
20
|
+
# a *field* named passcode is the device's or the account's, and is refused (SECRET_FIELD).
|
|
21
|
+
STRONG = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|passcode)\b", re.I)
|
|
22
|
+
# Masking reaches further: any number in a sign-in or security message is hidden from the model.
|
|
23
|
+
BROAD = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|passcode|log[- ]?in|"
|
|
24
|
+
r"sign[- ]?in|security|authenticat\w*)\b", re.I)
|
|
25
|
+
# A digit group that can be a code: 4 to 8 digits, or two halves (482 913, 482-913), alone or after a one- to
|
|
26
|
+
# three-letter prefix (G-482913). Not money ($1234), a phone number (+1 555…), a time (10:42), a decimal, a
|
|
27
|
+
# path or part of a word.
|
|
28
|
+
CANDIDATE = re.compile(r"(?:(?<=\b[A-Z]-)|(?<=\b[A-Z]{2}-)|(?<=\b[A-Z]{3}-)|(?<![\w$€£¥#+./:-]))"
|
|
29
|
+
r"(\d{3,4}[ -]\d{3,4}|\d{4,8})(?![\w%]|[.:,]\d)")
|
|
30
|
+
PHONE_BEFORE = re.compile(r"(?:\(\d{3}\)\s?|\d[-. ])$")
|
|
31
|
+
|
|
32
|
+
CODE_FIELD = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|digits?|"
|
|
33
|
+
r"confirmation|authenticat\w*)\b|onetimecode|one_time|otc", re.I)
|
|
34
|
+
SECRET_FIELD = re.compile(r"password|passcode|\bpin\b|card|cvv|cvc|csc|expir|ssn|social security|account number|"
|
|
35
|
+
r"routing", re.I)
|
|
36
|
+
NOT_VERIFICATION = re.compile(r"promo|discount|coupon|gift|referral|invit|zip|postal|country|area code|voucher|"
|
|
37
|
+
r"redeem|qr|bar ?code", re.I)
|
|
38
|
+
COMPOSER = re.compile(r"\b(?:message|imessage|text message|reply|comment|compose|post|tweet|caption|note|"
|
|
39
|
+
r"subject|to:|search|ask)\b", re.I)
|
|
40
|
+
PAYMENT_SCREEN = re.compile(r"card number|expiration|expiry|\bcvv\b|\bcvc\b|billing address", re.I)
|
|
41
|
+
QUICKTYPE = re.compile(r"\bfrom (messages|mail)\b", re.I)
|
|
42
|
+
EDITABLE = {"TextField", "SecureTextField"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Clock:
|
|
46
|
+
"""Time as the skills read it; tests pass their own."""
|
|
47
|
+
|
|
48
|
+
monotonic = staticmethod(time.monotonic)
|
|
49
|
+
sleep = staticmethod(time.sleep)
|
|
50
|
+
time = staticmethod(time.time)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
CLOCK = Clock()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CodeRefused(Exception):
|
|
57
|
+
"""One plain sentence: why no code was typed. It never holds a code."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class NoCodeYet(CodeRefused):
|
|
61
|
+
"""No code at all in any source (none stale, none from another sender): it may still be on its way."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# A sign-in sends its code as the person asks for it, so USE_CODE often looks before it arrives: with no code anywhere
|
|
65
|
+
# it watches the keyboard's suggestion (QuickType: "From Messages 482913", read from the screen it is on, nothing
|
|
66
|
+
# opened) every CODE_POLL_S seconds for up to CODE_WAIT_S, then looks everywhere once more.
|
|
67
|
+
CODE_WAIT_S = 60
|
|
68
|
+
CODE_POLL_S = 2.0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _candidates(text):
|
|
72
|
+
found = []
|
|
73
|
+
for match in CANDIDATE.finditer(text or ""):
|
|
74
|
+
start = match.start(1)
|
|
75
|
+
if PHONE_BEFORE.search(text[max(0, start - 6):start]):
|
|
76
|
+
continue
|
|
77
|
+
digits = re.sub(r"\D", "", match.group(1))
|
|
78
|
+
if 4 <= len(digits) <= 8:
|
|
79
|
+
found.append((start, match.end(1), digits))
|
|
80
|
+
if len(found) > 1: # a year beside a real code is not the code
|
|
81
|
+
found = [item for item in found if not re.fullmatch(r"(?:19|20)\d\d", item[2])] or found
|
|
82
|
+
return found
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_code(text, require_words=True):
|
|
86
|
+
"""The one code a message carries, or None: the digit group nearest a code word, a 6-digit one first."""
|
|
87
|
+
text = text or ""
|
|
88
|
+
words = [m.start() for m in STRONG.finditer(text)]
|
|
89
|
+
if require_words and not words:
|
|
90
|
+
return None
|
|
91
|
+
found = _candidates(text)
|
|
92
|
+
if not found:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def rank(item):
|
|
96
|
+
distance = min(abs(item[0] - at) for at in words) if words else 0
|
|
97
|
+
return (distance // 40, len(item[2]) != 6, distance, item[0])
|
|
98
|
+
return min(found, key=rank)[2]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def mask_codes(text, mask=MASK, *, context=None):
|
|
102
|
+
"""Mask possible codes when ``text`` (or its full message, ``context``) has sign-in or security wording."""
|
|
103
|
+
if not text or not BROAD.search(text if context is None else context):
|
|
104
|
+
return text
|
|
105
|
+
out = text
|
|
106
|
+
for start, end, _digits in sorted(_candidates(text), reverse=True):
|
|
107
|
+
out = out[:start] + mask + out[end:]
|
|
108
|
+
return out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def parse_age(text, now=None):
|
|
112
|
+
"""Seconds since a notification's or a message row's time (now, 2m ago, 10:42 AM, Yesterday)."""
|
|
113
|
+
lowered = (text or "").lower()
|
|
114
|
+
if re.search(r"\b(?:just now|now)\b", lowered):
|
|
115
|
+
return 0
|
|
116
|
+
for pattern, unit in ((r"\b(\d{1,3})\s*(?:s|sec|secs|seconds?)\b\.?\s*ago", 1),
|
|
117
|
+
(r"\b(\d{1,3})\s*(?:m|min|mins|minutes?)\b\.?\s*ago", 60),
|
|
118
|
+
(r"\b(\d{1,2})\s*(?:h|hr|hrs|hours?)\b\.?\s*ago", 3600)):
|
|
119
|
+
match = re.search(pattern, lowered)
|
|
120
|
+
if match:
|
|
121
|
+
return int(match.group(1)) * unit
|
|
122
|
+
if re.search(r"\byesterday\b|\b(?:mon|tues|wednes|thurs|fri|satur|sun)day\b", lowered):
|
|
123
|
+
return 86400
|
|
124
|
+
now = time.time() if now is None else now
|
|
125
|
+
local = datetime.datetime.fromtimestamp(now)
|
|
126
|
+
match = re.search(r"\b(\d{1,2}):([0-5]\d)\s*([ap])\.?\s*m\b\.?", lowered)
|
|
127
|
+
if match:
|
|
128
|
+
hour = int(match.group(1)) % 12 + (12 if match.group(3) == "p" else 0)
|
|
129
|
+
else:
|
|
130
|
+
match = re.search(r"\b([01]?\d|2[0-3]):([0-5]\d)\b", lowered)
|
|
131
|
+
if not match:
|
|
132
|
+
return None
|
|
133
|
+
hour = int(match.group(1))
|
|
134
|
+
then = local.replace(hour=hour, minute=int(match.group(2)), second=0, microsecond=0)
|
|
135
|
+
seconds = (local - then).total_seconds()
|
|
136
|
+
if seconds < -60:
|
|
137
|
+
seconds += 86400
|
|
138
|
+
return max(0, int(seconds))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def row_age(parts, now=None):
|
|
142
|
+
"""The age a row's own time part gives (the last part that parses), else its whole text's."""
|
|
143
|
+
for part in reversed(parts):
|
|
144
|
+
age = parse_age(part, now) if len(part) <= 24 else None
|
|
145
|
+
if age is not None:
|
|
146
|
+
return age
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class Field:
|
|
152
|
+
"""The field a code goes into: what the checks read, and where it is (screen fractions)."""
|
|
153
|
+
label: str
|
|
154
|
+
role: str
|
|
155
|
+
rect: tuple
|
|
156
|
+
identifier: str = ""
|
|
157
|
+
placeholder: str = ""
|
|
158
|
+
locator: str = ""
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def of(cls, element):
|
|
162
|
+
role = (getattr(element, "role", "") or "").removeprefix("XCUIElementType")
|
|
163
|
+
return cls(label=getattr(element, "label", "") or "", role=role, rect=tuple(element.rect),
|
|
164
|
+
identifier=str(getattr(element, "identifier", "") or ""),
|
|
165
|
+
placeholder=getattr(element, "placeholder", "") or "", locator=getattr(element, "locator", "") or "")
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def words(self):
|
|
169
|
+
return " ".join(part for part in (self.label, self.placeholder, self.identifier) if part)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def center(self):
|
|
173
|
+
x, y, w, h = self.rect
|
|
174
|
+
return x + w / 2, y + h / 2
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _role(element):
|
|
178
|
+
return (getattr(element, "role", "") or "").removeprefix("XCUIElementType")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def code_boxes(target, elements):
|
|
182
|
+
"""The row of 4 to 8 one-character boxes ``target`` is one of, left to right; () when it isn't."""
|
|
183
|
+
tx, ty, tw, th = target.rect
|
|
184
|
+
if tw >= .2:
|
|
185
|
+
return ()
|
|
186
|
+
row = [e for e in elements if _role(e) in EDITABLE and e.rect[2] < .2
|
|
187
|
+
and abs((e.rect[1] + e.rect[3] / 2) - (ty + th / 2)) < .03 and abs(e.rect[3] - th) < .03]
|
|
188
|
+
if not any(Field.of(e).rect == target.rect for e in row):
|
|
189
|
+
row.append(target)
|
|
190
|
+
return tuple(sorted(row, key=lambda e: e.rect[0])) if 4 <= len(row) <= 8 else ()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def check_app(bundle):
|
|
194
|
+
if not bundle:
|
|
195
|
+
# Without the app's name none of the checks below can hold: it might be Messages.
|
|
196
|
+
raise CodeRefused("Mobster couldn't tell which app is in front, so it typed no code.")
|
|
197
|
+
if bundle in REFUSED_APPS:
|
|
198
|
+
raise CodeRefused(f"Mobster never types a code into {REFUSED_APPS[bundle]}: it only goes into the code "
|
|
199
|
+
"field of the app you're signing in to.")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_field(target, snapshot):
|
|
203
|
+
"""Raise CodeRefused unless ``target`` looks like a verification-code field of the screen ``snapshot``."""
|
|
204
|
+
words = target.words
|
|
205
|
+
if target.role == "SearchField":
|
|
206
|
+
raise CodeRefused("That is a search field, not a code field, so Mobster typed nothing.")
|
|
207
|
+
if target.role == "TextView":
|
|
208
|
+
raise CodeRefused("That is a message or text box, not a code field, so Mobster typed nothing.")
|
|
209
|
+
if target.role not in EDITABLE:
|
|
210
|
+
raise CodeRefused("The target isn't a text field. Point USE_CODE at the code field.")
|
|
211
|
+
if SECRET_FIELD.search(words):
|
|
212
|
+
raise CodeRefused("That looks like a password, PIN or card field, not a code field, so Mobster typed "
|
|
213
|
+
"nothing.")
|
|
214
|
+
if PAYMENT_SCREEN.search(getattr(snapshot, "text", "") or ""):
|
|
215
|
+
raise CodeRefused("This looks like a payment form, so Mobster typed no code.")
|
|
216
|
+
if NOT_VERIFICATION.search(words):
|
|
217
|
+
raise CodeRefused("That is a promo, gift, postal or other code field, not a sign-in code field, so Mobster "
|
|
218
|
+
"typed nothing.")
|
|
219
|
+
if CODE_FIELD.search(words):
|
|
220
|
+
return ()
|
|
221
|
+
if COMPOSER.search(words):
|
|
222
|
+
raise CodeRefused("That is a message, comment or search box, not a code field, so Mobster typed nothing.")
|
|
223
|
+
boxes = code_boxes(target, getattr(snapshot, "elements", ()))
|
|
224
|
+
if boxes:
|
|
225
|
+
return boxes
|
|
226
|
+
raise CodeRefused("That field doesn't look like a verification-code field, so Mobster typed nothing.")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def app_name(bundle):
|
|
230
|
+
from ..catalog import APPS
|
|
231
|
+
for app in APPS:
|
|
232
|
+
if app.get("bundleId") == bundle:
|
|
233
|
+
return app["name"]
|
|
234
|
+
return "this app"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass(frozen=True)
|
|
238
|
+
class Candidate:
|
|
239
|
+
code: str
|
|
240
|
+
sender: str
|
|
241
|
+
age_s: Optional[int]
|
|
242
|
+
source: str # quicktype | notification | messages
|
|
243
|
+
text: str = dataclass_field(default="", repr=False)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True)
|
|
247
|
+
class Found:
|
|
248
|
+
"""A code, found for one field of one app. Held in memory only (Prepared.state)."""
|
|
249
|
+
code: str = dataclass_field(repr=False)
|
|
250
|
+
sender: str
|
|
251
|
+
age_s: Optional[int]
|
|
252
|
+
source: str
|
|
253
|
+
bundle: str
|
|
254
|
+
field: Field
|
|
255
|
+
found_at: float
|
|
256
|
+
boxes: tuple = ()
|
|
257
|
+
|
|
258
|
+
def __repr__(self): # never the code, even in a traceback's locals
|
|
259
|
+
return f"Found(source={self.source!r}, sender={self.sender!r}, age_s={self.age_s!r})"
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def frame(self):
|
|
263
|
+
"""Where the code shows once typed (screen fractions): the row of boxes, else the field."""
|
|
264
|
+
return _union(self.boxes) if self.boxes else tuple(self.field.rect)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _union(elements):
|
|
268
|
+
rects = [tuple(element.rect) for element in elements]
|
|
269
|
+
left, top = min(r[0] for r in rects), min(r[1] for r in rects)
|
|
270
|
+
right, bottom = max(r[0] + r[2] for r in rects), max(r[1] + r[3] for r in rects)
|
|
271
|
+
return (left, top, right - left, bottom - top)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _front(driver, snapshot=None):
|
|
275
|
+
active = getattr(driver, "active_app", None)
|
|
276
|
+
if callable(active):
|
|
277
|
+
try:
|
|
278
|
+
return active(timeout=5)
|
|
279
|
+
except Exception:
|
|
280
|
+
pass
|
|
281
|
+
snapshot = snapshot if snapshot is not None else driver.observe(timeout=5)
|
|
282
|
+
return getattr(snapshot, "bundle_id", "") or ""
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _activate(driver, bundle):
|
|
286
|
+
if bundle and bundle != SPRINGBOARD:
|
|
287
|
+
driver.call("POST", "/wda/apps/activate", {"bundleId": bundle}, timeout=15)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _texts(snapshot):
|
|
291
|
+
seen, out = set(), []
|
|
292
|
+
for element in getattr(snapshot, "elements", ()):
|
|
293
|
+
for text in (" ".join(p for p in (element.label, getattr(element, "value", "")) if p).strip(),):
|
|
294
|
+
if text and text not in seen:
|
|
295
|
+
seen.add(text)
|
|
296
|
+
out.append(text)
|
|
297
|
+
for line in (getattr(snapshot, "text", "") or "").splitlines():
|
|
298
|
+
if line.strip() and line.strip() not in seen:
|
|
299
|
+
seen.add(line.strip())
|
|
300
|
+
out.append(line.strip())
|
|
301
|
+
return out
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _clean_sender(sender, code):
|
|
305
|
+
sender = " ".join((sender or "").split())[:60]
|
|
306
|
+
if not sender or (code and code in re.sub(r"\D", "", sender)):
|
|
307
|
+
return "Messages"
|
|
308
|
+
return sender
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def quicktype_candidates(snapshot):
|
|
312
|
+
"""Codes the keyboard offers (QuickType: "From Messages 482913")."""
|
|
313
|
+
out = []
|
|
314
|
+
for text in _texts(snapshot):
|
|
315
|
+
match = QUICKTYPE.search(text)
|
|
316
|
+
if not match:
|
|
317
|
+
continue
|
|
318
|
+
code = extract_code(QUICKTYPE.sub(" ", text), require_words=False)
|
|
319
|
+
if code:
|
|
320
|
+
out.append(Candidate(code, match.group(1).title(), None, "quicktype", text))
|
|
321
|
+
return out
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def row_candidates(rows, source):
|
|
325
|
+
"""Codes in notification or message rows: (code, sender, age) per row with code wording."""
|
|
326
|
+
out = []
|
|
327
|
+
for row in rows:
|
|
328
|
+
code = extract_code(row.text)
|
|
329
|
+
if code:
|
|
330
|
+
out.append(Candidate(code, _clean_sender(row.sender, code), row.age_s, source, row.text))
|
|
331
|
+
return out
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _focus(driver, target, snapshot, clock, seconds=2.0):
|
|
335
|
+
"""Tap the field (only when no keyboard is up) and wait for the keyboard; the latest screen."""
|
|
336
|
+
if getattr(snapshot, "keyboard", "") == "visible":
|
|
337
|
+
return snapshot
|
|
338
|
+
x, y = target.center
|
|
339
|
+
driver.tap_point(x, y, snapshot, timeout=10)
|
|
340
|
+
end = clock.monotonic() + seconds
|
|
341
|
+
while True:
|
|
342
|
+
clock.sleep(.25)
|
|
343
|
+
current = driver.observe(timeout=5)
|
|
344
|
+
if getattr(current, "keyboard", "") or clock.monotonic() >= end:
|
|
345
|
+
return current
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def find_code(driver, *, field, bundle, snapshot, hint=None, clock=None,
|
|
349
|
+
sources=("quicktype", "notification", "messages")):
|
|
350
|
+
"""The one fresh code for ``field`` of ``bundle`` (Found), or CodeRefused."""
|
|
351
|
+
from .notifications import NotificationCenter, message_rows
|
|
352
|
+
clock = clock or CLOCK
|
|
353
|
+
check_app(bundle)
|
|
354
|
+
boxes = check_field(field, snapshot)
|
|
355
|
+
hint = " ".join(str(hint or "").split()).casefold() or None
|
|
356
|
+
stale = hinted_out = False
|
|
357
|
+
for source in sources:
|
|
358
|
+
try:
|
|
359
|
+
if source == "quicktype":
|
|
360
|
+
snapshot = _focus(driver, field, snapshot, clock)
|
|
361
|
+
found = quicktype_candidates(snapshot)
|
|
362
|
+
elif source == "notification":
|
|
363
|
+
found = row_candidates(NotificationCenter(driver, clock).read(origin=bundle, size=snapshot),
|
|
364
|
+
"notification")
|
|
365
|
+
else:
|
|
366
|
+
found = row_candidates(message_rows(driver, origin=bundle, clock=clock), "messages")
|
|
367
|
+
finally:
|
|
368
|
+
if source != "quicktype" and _front(driver) != bundle:
|
|
369
|
+
_activate(driver, bundle)
|
|
370
|
+
fresh = [c for c in found if c.source == "quicktype" or (c.age_s is not None and c.age_s <= MAX_AGE_S)]
|
|
371
|
+
if found and not fresh:
|
|
372
|
+
stale = True
|
|
373
|
+
continue
|
|
374
|
+
if hint:
|
|
375
|
+
matching = [c for c in fresh if hint in (c.sender + " " + c.text).casefold()]
|
|
376
|
+
hinted_out = hinted_out or (bool(fresh) and not matching)
|
|
377
|
+
fresh = matching
|
|
378
|
+
codes = {c.code for c in fresh}
|
|
379
|
+
if len(codes) > 1:
|
|
380
|
+
raise CodeRefused("Two different codes arrived in the last 10 minutes"
|
|
381
|
+
+ (" from that sender" if hint else "")
|
|
382
|
+
+ ", so Mobster typed neither. Name the sender in USE_CODE's text, or ask for a new "
|
|
383
|
+
"code.")
|
|
384
|
+
if codes:
|
|
385
|
+
best = min(fresh, key=lambda c: c.age_s if c.age_s is not None else 0)
|
|
386
|
+
return Found(code=best.code, sender=best.sender, age_s=best.age_s, source=best.source, bundle=bundle,
|
|
387
|
+
field=field, found_at=clock.monotonic(), boxes=boxes)
|
|
388
|
+
if hinted_out:
|
|
389
|
+
raise CodeRefused("No code from that sender arrived in the last 10 minutes. Ask for a new code, then try "
|
|
390
|
+
"again.")
|
|
391
|
+
if stale:
|
|
392
|
+
raise CodeRefused("The newest code is older than 10 minutes, so Mobster won't use it. Ask for a new code, "
|
|
393
|
+
"then try again.")
|
|
394
|
+
raise NoCodeYet("No verification code arrived in the last 10 minutes (keyboard suggestions, Notification "
|
|
395
|
+
"Center and Messages). Ask for a new code, then try again.")
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def await_code(driver, *, field, bundle, snapshot, hint=None, clock=None, seconds=CODE_WAIT_S, cancelled=None,
|
|
399
|
+
emit=None):
|
|
400
|
+
"""find_code, and when nothing has arrived yet (NoCodeYet), wait for it."""
|
|
401
|
+
clock = clock or CLOCK
|
|
402
|
+
try:
|
|
403
|
+
return find_code(driver, field=field, bundle=bundle, snapshot=snapshot, hint=hint, clock=clock)
|
|
404
|
+
except NoCodeYet as first:
|
|
405
|
+
refusal = first
|
|
406
|
+
hint = " ".join(str(hint or "").split()).casefold() or None
|
|
407
|
+
_say(emit, {"event": "code_waiting", "seconds": int(seconds)})
|
|
408
|
+
started = clock.monotonic()
|
|
409
|
+
found = None
|
|
410
|
+
try:
|
|
411
|
+
# The other sources brought the app back to the front without the keyboard: focus the field again, as the
|
|
412
|
+
# first look did, so the suggestion can show.
|
|
413
|
+
_focus(driver, field, driver.observe(timeout=5), clock)
|
|
414
|
+
except Exception: # noqa: BLE001 - the polls below read whatever is there
|
|
415
|
+
pass
|
|
416
|
+
while found is None and clock.monotonic() - started < seconds and not _stopped(cancelled):
|
|
417
|
+
clock.sleep(CODE_POLL_S)
|
|
418
|
+
try:
|
|
419
|
+
current = driver.observe(timeout=5)
|
|
420
|
+
except Exception: # noqa: BLE001 - a read that failed: look again next time
|
|
421
|
+
continue
|
|
422
|
+
if getattr(current, "bundle_id", bundle) not in (bundle, "", None):
|
|
423
|
+
break # the person moved to another app: stop waiting, never read another app's keyboard
|
|
424
|
+
offered = [c for c in quicktype_candidates(current)
|
|
425
|
+
if not hint or hint in (c.sender + " " + c.text).casefold()]
|
|
426
|
+
if len({c.code for c in offered}) == 1:
|
|
427
|
+
found = offered[0]
|
|
428
|
+
try:
|
|
429
|
+
if found is not None:
|
|
430
|
+
return Found(code=found.code, sender=found.sender, age_s=found.age_s, source=found.source, bundle=bundle,
|
|
431
|
+
field=field, found_at=clock.monotonic(), boxes=check_field(field, snapshot))
|
|
432
|
+
if _stopped(cancelled):
|
|
433
|
+
raise refusal
|
|
434
|
+
return find_code(driver, field=field, bundle=bundle, snapshot=driver.observe(timeout=5), hint=hint,
|
|
435
|
+
clock=clock)
|
|
436
|
+
finally:
|
|
437
|
+
_say(emit, {"event": "code_wait_finished", "ok": found is not None,
|
|
438
|
+
"waited_s": round(max(0.0, clock.monotonic() - started), 1)})
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _stopped(cancelled):
|
|
442
|
+
try:
|
|
443
|
+
return bool(cancelled()) if callable(cancelled) else False
|
|
444
|
+
except Exception: # noqa: BLE001 - can't tell whether Stop was pressed: stop waiting
|
|
445
|
+
return True
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _say(emit, event):
|
|
449
|
+
if callable(emit):
|
|
450
|
+
try:
|
|
451
|
+
emit(event)
|
|
452
|
+
except Exception: # noqa: BLE001
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def say_age(seconds):
|
|
457
|
+
"""A code's age as people say it: "just now", "40 seconds ago", "2 minutes ago"."""
|
|
458
|
+
seconds = max(0, int(seconds))
|
|
459
|
+
if seconds < 5:
|
|
460
|
+
return "just now"
|
|
461
|
+
if seconds < 60:
|
|
462
|
+
return f"{seconds} seconds ago"
|
|
463
|
+
minutes = seconds // 60
|
|
464
|
+
return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def approval_title(found, app):
|
|
468
|
+
"""The approval question: the sender, the age, the app; never the code."""
|
|
469
|
+
if found.age_s is None:
|
|
470
|
+
return f"Use the code the keyboard suggests from {found.sender} to sign in to {app}?"
|
|
471
|
+
return f"Use the code {found.sender} sent {say_age(found.age_s)} to sign in to {app}?"
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _refind(snapshot, target):
|
|
475
|
+
elements = list(getattr(snapshot, "elements", ()))
|
|
476
|
+
if target.locator:
|
|
477
|
+
for element in elements:
|
|
478
|
+
if getattr(element, "locator", "") == target.locator:
|
|
479
|
+
return element
|
|
480
|
+
for element in elements:
|
|
481
|
+
if _role(element) == target.role and all(abs(a - b) < .02 for a, b in zip(element.rect, target.rect)):
|
|
482
|
+
return element
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def enter_code(driver, found, *, clock=None):
|
|
487
|
+
"""Type ``found``'s code into its field, after checking the app in front and the code's age again."""
|
|
488
|
+
clock = clock or CLOCK
|
|
489
|
+
fresh = driver.observe(timeout=5)
|
|
490
|
+
if _front(driver, fresh) != found.bundle:
|
|
491
|
+
raise CodeRefused("The app in front changed since Mobster found the code, so it typed nothing.")
|
|
492
|
+
if found.age_s is not None and found.age_s + (clock.monotonic() - found.found_at) > MAX_AGE_S:
|
|
493
|
+
raise CodeRefused("The code is older than 10 minutes now, so Mobster typed nothing. Ask for a new code, "
|
|
494
|
+
"then try again.")
|
|
495
|
+
element = _refind(fresh, found.field)
|
|
496
|
+
if element is None:
|
|
497
|
+
raise CodeRefused("The code field is no longer on the screen, so Mobster typed nothing.")
|
|
498
|
+
target = Field.of(element)
|
|
499
|
+
boxes = check_field(target, fresh)
|
|
500
|
+
if boxes:
|
|
501
|
+
target = Field.of(boxes[0])
|
|
502
|
+
# Always the code field's own tap: a keyboard that is up may belong to another field (the email above it),
|
|
503
|
+
# and keys go to whichever field has the focus.
|
|
504
|
+
keyboard = getattr(fresh, "keyboard", "") == "visible"
|
|
505
|
+
x, y = target.center
|
|
506
|
+
driver.tap_point(x, y, fresh, timeout=10)
|
|
507
|
+
if keyboard:
|
|
508
|
+
clock.sleep(.15)
|
|
509
|
+
else:
|
|
510
|
+
end = clock.monotonic() + 2.0
|
|
511
|
+
while clock.monotonic() < end:
|
|
512
|
+
clock.sleep(.2)
|
|
513
|
+
if getattr(driver.observe(timeout=5), "keyboard", ""):
|
|
514
|
+
break
|
|
515
|
+
driver.call("POST", "/wda/keys", {"value": [found.code]}, timeout=10)
|
|
516
|
+
return _union(boxes) if boxes else tuple(element.rect)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@dataclass(frozen=True)
|
|
520
|
+
class _Pending:
|
|
521
|
+
found: Optional[Found] = None
|
|
522
|
+
refusal: str = ""
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _target(act, snapshot):
|
|
526
|
+
element = act.get("element") if isinstance(act, dict) else None
|
|
527
|
+
if element is not None:
|
|
528
|
+
return element
|
|
529
|
+
elements = list(getattr(snapshot, "elements", ()))
|
|
530
|
+
wanted = act.get("target") if isinstance(act, dict) else None
|
|
531
|
+
if wanted:
|
|
532
|
+
for element in elements:
|
|
533
|
+
if element.id == wanted:
|
|
534
|
+
return element
|
|
535
|
+
label = (act.get("target_label") if isinstance(act, dict) else None) or None
|
|
536
|
+
if label:
|
|
537
|
+
named = [e for e in elements if e.label == label and _role(e) in EDITABLE | {"SearchField", "TextView"}]
|
|
538
|
+
if len(named) == 1:
|
|
539
|
+
return named[0]
|
|
540
|
+
return None
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class UseCode:
|
|
544
|
+
"""agent_hooks.Skill: USE_CODE."""
|
|
545
|
+
|
|
546
|
+
op = "USE_CODE"
|
|
547
|
+
prompt = ("- USE_CODE target [text]: a sign-in asks for a verification code sent by text or email: Mobster finds "
|
|
548
|
+
"it itself (keyboard suggestion, Notification Center, Messages), asks the user, and types it into "
|
|
549
|
+
"target, the code field; text may name the sender (\"Chase\"). You never see the code, so never open "
|
|
550
|
+
"Messages or Mail to read one.")
|
|
551
|
+
needs_target = True
|
|
552
|
+
|
|
553
|
+
def __init__(self, clock=None):
|
|
554
|
+
self.clock = clock or CLOCK
|
|
555
|
+
|
|
556
|
+
def available(self, ctx):
|
|
557
|
+
driver = ctx.driver
|
|
558
|
+
return callable(getattr(driver, "call", None)) and callable(getattr(driver, "observe", None))
|
|
559
|
+
|
|
560
|
+
def prepare(self, ctx, act, snapshot):
|
|
561
|
+
element = _target(act, snapshot)
|
|
562
|
+
if element is None:
|
|
563
|
+
return Prepared(None, _Pending(refusal="USE_CODE needs the code field as its target."))
|
|
564
|
+
bundle = getattr(snapshot, "bundle_id", "") or ctx.app_bundle or ""
|
|
565
|
+
hint = act.get("text") if isinstance(act, dict) else None
|
|
566
|
+
run = getattr(ctx, "run", None)
|
|
567
|
+
try:
|
|
568
|
+
found = await_code(ctx.driver, field=Field.of(element), bundle=bundle, snapshot=snapshot, hint=hint,
|
|
569
|
+
clock=self.clock, cancelled=getattr(run, "cancelled", None), emit=ctx.emit)
|
|
570
|
+
except CodeRefused as refusal:
|
|
571
|
+
return Prepared(None, _Pending(refusal=str(refusal)))
|
|
572
|
+
except Exception as error:
|
|
573
|
+
return Prepared(None, _Pending(refusal=f"Mobster couldn't look for the code ({type(error).__name__})."))
|
|
574
|
+
return Prepared(approval_title(found, app_name(bundle)), _Pending(found=found))
|
|
575
|
+
|
|
576
|
+
def perform(self, ctx, prepared, act, snapshot):
|
|
577
|
+
pending = (prepared.state
|
|
578
|
+
if isinstance(prepared.state, _Pending) else _Pending(refusal="USE_CODE found no code."))
|
|
579
|
+
if pending.found is None:
|
|
580
|
+
return SkillResult(feedback=f"USE_CODE typed nothing: {pending.refusal}")
|
|
581
|
+
found = pending.found
|
|
582
|
+
try:
|
|
583
|
+
rect = enter_code(ctx.driver, found, clock=self.clock)
|
|
584
|
+
except CodeRefused as refusal:
|
|
585
|
+
return SkillResult(feedback=f"USE_CODE typed nothing: {refusal}")
|
|
586
|
+
except Exception as error:
|
|
587
|
+
# Only the error's kind: a driver's message is never echoed next to a code. The code may be in the
|
|
588
|
+
# field already (a timeout after the keys landed), so it is masked and its field blurred all the same.
|
|
589
|
+
return SkillResult(feedback=f"USE_CODE couldn't type the code ({type(error).__name__}). Look at the "
|
|
590
|
+
"field before trying again.", changed=True, secrets=(found.code,),
|
|
591
|
+
secret_rects=(found.frame,))
|
|
592
|
+
try:
|
|
593
|
+
ctx.emit({"event": "code_used", "source": found.source, "sender": found.sender, "age_s": found.age_s})
|
|
594
|
+
except Exception:
|
|
595
|
+
pass
|
|
596
|
+
return SkillResult(feedback=f"Entered the code from {found.sender}.", changed=True,
|
|
597
|
+
secrets=(found.code,), secret_rects=(tuple(rect),))
|