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,571 @@
|
|
|
1
|
+
"""iOS's own dialogs (permission prompts, Allow Paste, tracking, Open in "App"?, Save Password) answered under the
|
|
2
|
+
user's permission mode, never by a blanket accept.
|
|
3
|
+
|
|
4
|
+
``read(driver)`` asks WebDriverAgent for the dialog in front (GET /alert/text and /wda/alert/buttons: its FBAlert
|
|
5
|
+
looks in SpringBoard first, then the app in front, then the iOS 18 contacts prompt). ``classify`` names what iOS is
|
|
6
|
+
asking from the text and the buttons. ``decide`` turns that into one of four answers:
|
|
7
|
+
|
|
8
|
+
- ``press``: Mobster presses one button itself, with a reason the user reads in the thread ("Don't Allow: a task
|
|
9
|
+
doesn't need notifications").
|
|
10
|
+
- ``ask``: the user chooses (the approval card, or the phone when nobody can be asked).
|
|
11
|
+
- ``stop``: nobody can be asked and the choice isn't Mobster's to make: the run ends and says what iOS asked, with
|
|
12
|
+
the dialog left up for the user.
|
|
13
|
+
- ``leave``: not one of iOS's dialogs (an app's own alert, "Delete this post?"): the agent handles it like any
|
|
14
|
+
screen, through the usual approvals.
|
|
15
|
+
|
|
16
|
+
Rules that hold in every mode:
|
|
17
|
+
- A grant of private data or hardware (location, photos, camera, microphone, contacts, calendars, Bluetooth, local
|
|
18
|
+
network, health, motion...) is the user's call: asked, never pressed, in full access too (as a one-time code the
|
|
19
|
+
agent types is: permissions.policy).
|
|
20
|
+
- Saving a password, signing in to an Apple Account, iOS asking for its password and Trust This Computer are never
|
|
21
|
+
pressed and never answered for the user. They are known by Apple's wording: an app's own "Incorrect password"
|
|
22
|
+
alert is the app's.
|
|
23
|
+
- Allow Paste is pressed only within PASTE_WINDOW_S of Mobster putting text on the clipboard itself
|
|
24
|
+
(``note_paste``). The clipboard's contents aren't read back: the window stands for "the text is Mobster's". Any
|
|
25
|
+
other paste prompt is refused ("Don't Allow Paste").
|
|
26
|
+
- Open in "App"? is pressed only right after Mobster opened that link itself (``note_open``).
|
|
27
|
+
- An iOS update nag gets "Later" (installing restarts the phone); a Screen Time limit stops the run (a limit is never
|
|
28
|
+
ignored for the user); Undo Typing (a shake) gets "Cancel", never "Undo"; a website sign-in asks.
|
|
29
|
+
- A connection that sees only pixels reads the alert from the screen (``from_screen``) and taps its button by name;
|
|
30
|
+
it recognizes only iOS's own kinds of alert, so an app's alert stays the agent's, under its approvals.
|
|
31
|
+
|
|
32
|
+
The mode is the user's permission mode (``permission_mode``): ask_all, ask_important (default) or dont_ask, read
|
|
33
|
+
the way ``permissions.policy`` reads it (MOBSTER_PERMISSION_MODE; MOBSTER_ASK_BEFORE_ACTING=0 as
|
|
34
|
+
dont_ask), and through that package when it is present. Runs an outside agent started (MCP, Remote) never go below
|
|
35
|
+
ask_important.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
import re
|
|
40
|
+
import threading
|
|
41
|
+
import time
|
|
42
|
+
import unicodedata
|
|
43
|
+
from dataclasses import dataclass, replace
|
|
44
|
+
|
|
45
|
+
ASK_ALL, ASK_IMPORTANT, DONT_ASK = "ask_all", "ask_important", "dont_ask"
|
|
46
|
+
MODES = (ASK_ALL, ASK_IMPORTANT, DONT_ASK)
|
|
47
|
+
OUTSIDE_ORIGINS = frozenset({"mcp", "remote", "hook", "trigger", "console"})
|
|
48
|
+
# How long after Mobster set the clipboard (or opened a link) the dialog that follows is taken to be its own.
|
|
49
|
+
PASTE_WINDOW_S = 20.0
|
|
50
|
+
OPEN_WINDOW_S = 15.0
|
|
51
|
+
READ_TIMEOUT = 3.0
|
|
52
|
+
|
|
53
|
+
# Privacy grants: what iOS asks to share, in the words the approval card uses ("Let Maps use your location?").
|
|
54
|
+
GRANTS = {
|
|
55
|
+
"location": "use your location",
|
|
56
|
+
"location_always": "use your location even when you're not using it",
|
|
57
|
+
"photos": "see your photos",
|
|
58
|
+
"photos_add": "add to your photos",
|
|
59
|
+
"camera": "use the camera",
|
|
60
|
+
"microphone": "use the microphone",
|
|
61
|
+
"contacts": "see your contacts",
|
|
62
|
+
"calendar": "use your calendar",
|
|
63
|
+
"reminders": "use your reminders",
|
|
64
|
+
"bluetooth": "use Bluetooth",
|
|
65
|
+
"local_network": "find devices on your local network",
|
|
66
|
+
"motion": "read your motion and fitness activity",
|
|
67
|
+
"speech": "use speech recognition",
|
|
68
|
+
"media": "use your Apple Music and media library",
|
|
69
|
+
"home": "use your Home data",
|
|
70
|
+
"health": "use your health data",
|
|
71
|
+
"face_id": "use Face ID",
|
|
72
|
+
"focus": "see your Focus status",
|
|
73
|
+
"permission": "use something private on your iPhone",
|
|
74
|
+
}
|
|
75
|
+
NEVER = {"save_password", "apple_account", "trust_computer", "password_field"}
|
|
76
|
+
|
|
77
|
+
_Q = "[\"“”'‘’]"
|
|
78
|
+
PATTERNS = (
|
|
79
|
+
# (kind, regex on the folded text). Order matters: the most restrictive first, then the more precise. What Mobster
|
|
80
|
+
# never answers (NEVER), a Screen Time limit, a purchase and Sign in with Apple come before every grant, so a
|
|
81
|
+
# password prompt or a purchase sheet that also names a grant ("an email address", "Apple Music") is never
|
|
82
|
+
# pressed as one.
|
|
83
|
+
("save_password", re.compile(r"save (?:this )?password|save to passwords|icloud keychain|update password\?",
|
|
84
|
+
re.I)),
|
|
85
|
+
# iOS's own request for an Apple Account password or sign-in wins over the sheets below: a purchase or Sign in
|
|
86
|
+
# with Apple sheet that asks for one is an Apple Account prompt, which Mobster never answers (NEVER). It is read
|
|
87
|
+
# from Apple's wording, because classify() gets only the alert's text and buttons, not the process that shows it:
|
|
88
|
+
# an app's own "Incorrect password" alert names no Apple Account and stays the app's.
|
|
89
|
+
("apple_account", re.compile(r"\b(?:apple (?:account|id)|icloud|itunes(?: store)?|app store) password\b|"
|
|
90
|
+
r"\bpassword for (?:your )?(?:apple (?:account|id)|icloud|itunes)\b|"
|
|
91
|
+
r"\benter (?:the |your )?(?:apple (?:account|id) )?password\b|"
|
|
92
|
+
r"\bapple (?:account|id) sign[- ]in (?:requested|required)\b|"
|
|
93
|
+
r"\bapple (?:account|id) e-?mail(?: address)?(?: or phone number)? and password\b|"
|
|
94
|
+
r"sign in to (?:icloud|itunes store|the app store|(?:your )?apple (?:account|id))\b",
|
|
95
|
+
re.I)),
|
|
96
|
+
("trust_computer", re.compile(r"trust this computer", re.I)),
|
|
97
|
+
("screen_time", re.compile(r"^time limit\b|reached your limit on|you['’]ve reached your limit", re.I)),
|
|
98
|
+
# The sheets an app presents through iOS: not alerts, so a check reads them from Apple's own process (SHEETS).
|
|
99
|
+
# The alert after a purchase counts only with StoreKit's environment tag: an app's own "You're all set" is the
|
|
100
|
+
# app's. A sheet that only names the Apple Account it uses stays a sheet.
|
|
101
|
+
("purchase", re.compile(r"confirm your (?:in-app )?purchase|\[environment: (?:sandbox|xcode)\]|"
|
|
102
|
+
r"do you want to (?:buy|subscribe)|confirm with (?:the )?(?:side button|passcode|face id|"
|
|
103
|
+
r"touch id)|double[- ]click to pay|pay with (?:passcode|face id|touch id)", re.I)),
|
|
104
|
+
("sign_in_with_apple", re.compile(r"sign in with apple|continue with apple|"
|
|
105
|
+
r"sign in to .{1,60} with your apple (?:account|id)", re.I)),
|
|
106
|
+
# Any other mention of an Apple Account, including one beside Face ID or Touch ID ("Apple Account. Continue with
|
|
107
|
+
# Face ID to sign in"), is an Apple Account prompt: it wins over every grant and over biometric below.
|
|
108
|
+
("apple_account", re.compile(r"apple account|apple id|itunes store|sign in to icloud|enter (?:the|your) password",
|
|
109
|
+
re.I)),
|
|
110
|
+
("paste", re.compile(r"would like to paste from|wants to paste from|paste from other apps", re.I)),
|
|
111
|
+
("tracking", re.compile(r"to track your activity across other companies", re.I)),
|
|
112
|
+
("open_in_app", re.compile(rf"^open in {_Q}?.+{_Q}?\??$", re.I)),
|
|
113
|
+
("location_always", re.compile(r"also use your location even when|change to always allow", re.I)),
|
|
114
|
+
("location", re.compile(r"use your location|access your location|your current location", re.I)),
|
|
115
|
+
("notifications", re.compile(r"would like to send you notifications|send you notifications", re.I)),
|
|
116
|
+
("photos_add", re.compile(r"would like to add to your photos|add to your photos|add photos", re.I)),
|
|
117
|
+
("photos", re.compile(r"access your photos|access to your photo|photo library|your photos", re.I)),
|
|
118
|
+
("camera", re.compile(r"access the camera|use the camera|access your camera", re.I)),
|
|
119
|
+
("microphone", re.compile(r"access the microphone|use the microphone|access your microphone", re.I)),
|
|
120
|
+
("contacts", re.compile(r"access (?:to )?your contacts|your contacts", re.I)),
|
|
121
|
+
("calendar", re.compile(r"your calendar|add events to", re.I)),
|
|
122
|
+
("reminders", re.compile(r"your reminders", re.I)),
|
|
123
|
+
("bluetooth", re.compile(r"use bluetooth|bluetooth", re.I)),
|
|
124
|
+
("local_network", re.compile(r"local network", re.I)),
|
|
125
|
+
("motion", re.compile(r"motion (?:&|and) fitness", re.I)),
|
|
126
|
+
("speech", re.compile(r"speech recognition", re.I)),
|
|
127
|
+
("media", re.compile(r"apple music|media library", re.I)),
|
|
128
|
+
("home", re.compile(r"your home data|homekit", re.I)),
|
|
129
|
+
("health", re.compile(r"health data|\bhealth\b", re.I)),
|
|
130
|
+
("focus", re.compile(r"focus status", re.I)),
|
|
131
|
+
("face_id", re.compile(r"to use face id", re.I)),
|
|
132
|
+
("biometric", re.compile(r"(?:authenticate|unlock|sign in|log in|continue) with (?:face|touch) id|"
|
|
133
|
+
r"try (?:face|touch) id again|(?:face|touch) id (?:not recognized|didn['’]t recognize)|"
|
|
134
|
+
r"(?:face|touch) id is (?:locked|disabled)", re.I)),
|
|
135
|
+
("software_update", re.compile(r"\b(?:ios|software) update\b|\bupdate is (?:now )?available|is now available|"
|
|
136
|
+
r"\binstall (?:now|tonight)\b", re.I)),
|
|
137
|
+
("web_sign_in", re.compile(r"(?:wants|would like) to use .{1,80} to sign in", re.I)),
|
|
138
|
+
("undo_typing", re.compile(r"^undo typing\b", re.I)),
|
|
139
|
+
("rating", re.compile(r"^enjoying .+\?|rate (?:this app|us)|tap a star to rate", re.I)),
|
|
140
|
+
("low_battery", re.compile(r"^low battery\b", re.I)),
|
|
141
|
+
)
|
|
142
|
+
ALLOW = re.compile(r"^(?:allow(?: once| while using(?: the)? app| full access| paste)?|ok|open|continue|"
|
|
143
|
+
r"change to always allow|save password)$", re.I)
|
|
144
|
+
DENY = re.compile(r"^(?:don['’]?t allow(?: paste)?|ask app not to track|cancel|not now|keep only while using|"
|
|
145
|
+
r"close|later|remind me later|don['’]?t trust|never for this website)$", re.I)
|
|
146
|
+
# The buttons of iOS's own alerts, as they read on screen: a connection that sees only pixels finds an alert by them.
|
|
147
|
+
BUTTONS = frozenset(name.casefold() for name in (
|
|
148
|
+
"Allow", "Don't Allow", "Allow Once", "Allow While Using App", "Allow While Using the App", "Ask App Not to Track",
|
|
149
|
+
"Allow Paste", "Don't Allow Paste", "OK", "Cancel", "Not Now", "Open", "Limit Access...", "Limit Access…",
|
|
150
|
+
"Allow Full Access", "Select Photos...", "Select Photos…", "Close", "Later", "Remind Me Later", "Install Now",
|
|
151
|
+
"Install Tonight", "Low Power Mode", "Trust", "Don't Trust", "Continue", "Keep Only While Using",
|
|
152
|
+
"Change to Always Allow", "Undo", "Save Password", "Never for This Website", "Ignore Limit", "Settings",
|
|
153
|
+
"Buy", "Subscribe", "Confirm", "Pay", "Continue with Passcode", "Use Passcode", "Enter Password",
|
|
154
|
+
"Try Face ID Again", "Try Touch ID Again"))
|
|
155
|
+
# The kinds iOS presents as sheets over an app. A check's policy (verify/dialogs.py) reads them from the screen of
|
|
156
|
+
# Apple's own process; an agent task never does, so its behavior is unchanged.
|
|
157
|
+
SHEETS = frozenset({"sign_in_with_apple", "purchase", "biometric"})
|
|
158
|
+
SHEET_NOT_TEXT = frozenset({"Button", "Cell", "Link", "Switch", "TextField", "SecureTextField"})
|
|
159
|
+
# The processes that show iOS's own dialogs over an app, by exact bundle, never a pattern: Safari's web content
|
|
160
|
+
# (com.apple.SafariViewService) is a view service too, and a web page writes what it likes. SpringBoard shows the
|
|
161
|
+
# alerts; StoreKit, AuthKit and CoreAuthUI each show one kind of sheet and that kind's own alerts.
|
|
162
|
+
SPRINGBOARD = "com.apple.springboard"
|
|
163
|
+
STOREKIT = "com.apple.ios.StoreKitUIService"
|
|
164
|
+
AUTHKIT = "com.apple.AuthKitUIService"
|
|
165
|
+
CORE_AUTH = "com.apple.CoreAuthUI"
|
|
166
|
+
SHEET_FRONT = {"purchase": STOREKIT, "sign_in_with_apple": AUTHKIT, "biometric": CORE_AUTH}
|
|
167
|
+
APPLE_DIALOGS = frozenset({SPRINGBOARD, *SHEET_FRONT.values()})
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def shows_alert(kind, bundle):
|
|
171
|
+
"""Whether ``bundle`` in front is the Apple process that shows a ``kind`` alert: SpringBoard, or for a purchase's
|
|
172
|
+
all-set alert StoreKit and for Face ID's own alerts CoreAuthUI."""
|
|
173
|
+
return bundle == SPRINGBOARD or (bundle is not None and SHEET_FRONT.get(kind) == bundle)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def shows_sheet(kind, bundle):
|
|
177
|
+
"""Whether ``bundle`` in front is the Apple process that shows a ``kind`` sheet. A sheet of what Mobster never
|
|
178
|
+
answers counts from any of them, since it only stops the run."""
|
|
179
|
+
if kind in SHEET_FRONT:
|
|
180
|
+
return SHEET_FRONT[kind] == bundle
|
|
181
|
+
return kind in NEVER and bundle in SHEET_FRONT.values()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def front(driver, timeout=2):
|
|
185
|
+
"""The bundle WebDriverAgent reports in front, or None when it can't say."""
|
|
186
|
+
active = getattr(driver, "active_app", None)
|
|
187
|
+
try:
|
|
188
|
+
bundle = active(timeout=timeout) if callable(active) else None
|
|
189
|
+
except Exception:
|
|
190
|
+
return None
|
|
191
|
+
return bundle if isinstance(bundle, str) and bundle else None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class DialogStop(Exception):
|
|
195
|
+
"""A dialog a check's run may not answer: ``str(error)`` is the sentence for the report."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, reason, dialog=None):
|
|
198
|
+
super().__init__(reason)
|
|
199
|
+
self.dialog = dialog
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def fold(text):
|
|
203
|
+
"""Plain text for matching: curly quotes and dashes straight, spaces single."""
|
|
204
|
+
text = unicodedata.normalize("NFKC", str(text or ""))
|
|
205
|
+
text = text.translate({0x2019: "'", 0x2018: "'", 0x201C: '"', 0x201D: '"', 0x2026: "...", 0x2013: "-",
|
|
206
|
+
0x2014: "-", 0xA0: " "})
|
|
207
|
+
return " ".join(text.split())
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def app_named(text):
|
|
211
|
+
"""The app a dialog names, from its quotes ("“Maps” Would Like…", "Allow “Maps” to…"); "" when none."""
|
|
212
|
+
found = re.search(r"[“\"]([^”\"]{1,60})[”\"]", str(text or ""))
|
|
213
|
+
return found.group(1).strip() if found else ""
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass(frozen=True)
|
|
217
|
+
class Dialog:
|
|
218
|
+
text: str
|
|
219
|
+
buttons: tuple
|
|
220
|
+
kind: str = "unknown" # a PATTERNS kind, "permission" (Allow / Don't Allow), or "unknown"
|
|
221
|
+
app: str = ""
|
|
222
|
+
on_screen: bool = False # read from the screen (a connection that sees only pixels), not from WDA
|
|
223
|
+
lines: tuple = () # WDA's alert text line by line: a title and a message are separate labels
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def grant(self):
|
|
227
|
+
return self.kind in GRANTS
|
|
228
|
+
|
|
229
|
+
def button(self, *wanted):
|
|
230
|
+
"""The dialog's own button name for the first of ``wanted`` (plain-text compare), or None."""
|
|
231
|
+
plain = {fold(b).casefold(): b for b in self.buttons}
|
|
232
|
+
for name in wanted:
|
|
233
|
+
if name and fold(name).casefold() in plain:
|
|
234
|
+
return plain[fold(name).casefold()]
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def allow_button(self):
|
|
238
|
+
"""The narrowest grant on offer: Allow Once before Allow While Using App, Limit Access before Full Access."""
|
|
239
|
+
return (self.button("Allow Once", "Allow While Using App", "Allow While Using the App", "Limit Access...",
|
|
240
|
+
"Select Photos...", "Select Contacts...", "Allow", "Allow Full Access", "OK", "Continue",
|
|
241
|
+
"Allow Paste", "Open")
|
|
242
|
+
or next((b for b in self.buttons if ALLOW.match(fold(b))), None))
|
|
243
|
+
|
|
244
|
+
def deny_button(self):
|
|
245
|
+
return (self.button("Don't Allow", "Ask App Not to Track", "Don't Allow Paste", "Not Now", "Cancel",
|
|
246
|
+
"Keep Only While Using", "Close", "Later", "Remind Me Later", "Don't Trust")
|
|
247
|
+
or next((b for b in self.buttons if DENY.match(fold(b))), None))
|
|
248
|
+
|
|
249
|
+
def public(self):
|
|
250
|
+
return {"kind": self.kind, "app": self.app or None, "text": self.text[:300], "buttons": list(self.buttons)}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def classify(text, buttons=()):
|
|
254
|
+
"""A Dialog for ``text`` and its ``buttons`` (strings, WDA's names kept as they are)."""
|
|
255
|
+
buttons = tuple(str(b)[:120] for b in buttons or () if isinstance(b, str) and b.strip())[:8]
|
|
256
|
+
plain = fold(text)
|
|
257
|
+
kind = next((name for name, pattern in PATTERNS if pattern.search(plain)), None)
|
|
258
|
+
if kind is None:
|
|
259
|
+
names = {fold(b).casefold() for b in buttons}
|
|
260
|
+
kind = "permission" if "allow" in names and any(n.startswith("don't allow") for n in names) else "unknown"
|
|
261
|
+
lines = tuple(line for line in (fold(part) for part in str(text or "").splitlines()) if line)[:8]
|
|
262
|
+
return Dialog(text=plain[:400], buttons=buttons, kind=kind, app=app_named(text), lines=lines)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def read(driver, timeout=READ_TIMEOUT, snapshot=None):
|
|
266
|
+
"""The dialog in front as a Dialog, or None. Never raises: a runner that doesn't answer reads as no dialog.
|
|
267
|
+
A connection without WebDriverAgent (iPhone Mirroring, Cable + Bluetooth) reads it from ``snapshot``
|
|
268
|
+
(``from_screen``)."""
|
|
269
|
+
if not callable(getattr(driver, "call", None)):
|
|
270
|
+
return from_screen(snapshot) if snapshot is not None else None
|
|
271
|
+
try:
|
|
272
|
+
text = driver.call("GET", "/alert/text", timeout=timeout)
|
|
273
|
+
except Exception:
|
|
274
|
+
return None
|
|
275
|
+
if not isinstance(text, str) or not text.strip():
|
|
276
|
+
return None
|
|
277
|
+
try:
|
|
278
|
+
buttons = driver.call("GET", "/wda/alert/buttons", timeout=timeout)
|
|
279
|
+
except Exception:
|
|
280
|
+
buttons = []
|
|
281
|
+
dialog = classify(text, buttons if isinstance(buttons, list) else [])
|
|
282
|
+
# WebDriverAgent's alert text has no fields: a password field in Apple's own tree makes it a password prompt.
|
|
283
|
+
if getattr(snapshot, "bundle_id", "") in APPLE_DIALOGS and _secure_field(snapshot):
|
|
284
|
+
return replace(dialog, kind="password_field")
|
|
285
|
+
return dialog
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def in_tree(dialog, snapshot):
|
|
289
|
+
"""Whether WebDriverAgent's alert text sits in ``snapshot``'s tree, the screen the agent reads."""
|
|
290
|
+
head = fold(dialog.text)[:40].casefold()
|
|
291
|
+
return not dialog.on_screen and bool(head) and any(
|
|
292
|
+
head in fold(getattr(e, "label", "") or "").casefold() for e in getattr(snapshot, "elements", ()) or ())
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def in_app(dialog, snapshot):
|
|
296
|
+
"""Whether WebDriverAgent's alert is the app's own: any of its lines (/alert/text joins a title and a message with
|
|
297
|
+
a line break, and the tree has them as separate labels) sits in the tree of a process that isn't Apple's dialogs.
|
|
298
|
+
Safari's web content counts as the app's: a web page's confirm() is no dialog of iOS's."""
|
|
299
|
+
if dialog.on_screen or getattr(snapshot, "bundle_id", "") in APPLE_DIALOGS:
|
|
300
|
+
return False
|
|
301
|
+
labels = [fold(getattr(e, "label", "") or "").casefold() for e in getattr(snapshot, "elements", ()) or ()]
|
|
302
|
+
wanted = {fold(dialog.text)[:40].casefold(), *(line.casefold() for line in dialog.lines)} - {""}
|
|
303
|
+
return any(line in label for line in wanted for label in labels)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _secure_field(snapshot, top=0.0, bottom=1.0):
|
|
307
|
+
"""Whether a password field sits between ``top`` and ``bottom`` (fractions of the screen's height)."""
|
|
308
|
+
return any(top <= y + h / 2 <= bottom for _, y, _, h in getattr(snapshot, "secure_fields", ()) or ())
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def from_screen(snapshot, kinds=None):
|
|
312
|
+
"""iOS's alert on a screen read as text, or None; only kinds iOS shows count, so an app's own OK stays the app's.
|
|
313
|
+
With ``kinds``, only those kinds count."""
|
|
314
|
+
elements = list(getattr(snapshot, "elements", ()) or ())
|
|
315
|
+
buttons = [e for e in elements if fold(e.label).casefold() in BUTTONS and .2 <= e.center[1] <= .95]
|
|
316
|
+
if not buttons:
|
|
317
|
+
return None
|
|
318
|
+
top = min(e.center[1] for e in buttons)
|
|
319
|
+
# A sheet's words are static text: an app's own "Sign in with Apple" button above its Continue is not the sheet.
|
|
320
|
+
lines = [e for e in elements if e not in buttons and top - .32 <= e.center[1] < top - .005
|
|
321
|
+
and .08 <= e.center[0] <= .92 and fold(e.label) and (kinds is None or e.role not in SHEET_NOT_TEXT)]
|
|
322
|
+
lines.sort(key=lambda e: (round(e.center[1], 3), e.center[0]))
|
|
323
|
+
text = " ".join(fold(e.label) for e in lines)
|
|
324
|
+
names = [e.label for e in sorted(buttons, key=lambda e: (round(e.center[1], 2), e.center[0]))]
|
|
325
|
+
dialog = classify(text, names)
|
|
326
|
+
if dialog.kind == "unknown" or (kinds is not None and dialog.kind not in kinds):
|
|
327
|
+
return None
|
|
328
|
+
# A password field among the dialog's lines and buttons makes it a password prompt, whatever its words say.
|
|
329
|
+
kind = "password_field" if _secure_field(snapshot, top - .32, max(e.center[1] for e in buttons)) else dialog.kind
|
|
330
|
+
return Dialog(text=dialog.text, buttons=dialog.buttons, kind=kind, app=dialog.app, on_screen=True)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def mentions_dialog(snapshot):
|
|
334
|
+
"""Whether a screen's text reads like one of iOS's own dialogs: a cheap look before asking WebDriverAgent."""
|
|
335
|
+
for element in getattr(snapshot, "elements", ()) or ():
|
|
336
|
+
text = fold(getattr(element, "label", "") or "")
|
|
337
|
+
if 12 <= len(text) <= 300 and any(pattern.search(text) for kind, pattern in PATTERNS
|
|
338
|
+
if kind not in ("rating", "low_battery", "health", "bluetooth")):
|
|
339
|
+
return True
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass
|
|
344
|
+
class Recent:
|
|
345
|
+
"""What Mobster itself just did that makes a following dialog its own: a clipboard it set, a link it opened."""
|
|
346
|
+
pasted_at: float | None = None
|
|
347
|
+
opened_at: float | None = None
|
|
348
|
+
opened_url: str = ""
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
_recent_lock = threading.Lock()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def recent(driver):
|
|
355
|
+
"""The Recent record owned by ``driver``, for that driver's lifetime."""
|
|
356
|
+
with _recent_lock:
|
|
357
|
+
found = getattr(driver, "recent_dialogs", None)
|
|
358
|
+
if found is None:
|
|
359
|
+
found = Recent()
|
|
360
|
+
driver.recent_dialogs = found
|
|
361
|
+
return found
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def note_paste(driver, clock=time.monotonic):
|
|
365
|
+
recent(driver).pasted_at = clock()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def note_open(driver, url, clock=time.monotonic):
|
|
369
|
+
record = recent(driver)
|
|
370
|
+
record.opened_at, record.opened_url = clock(), str(url or "")[:2000]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def permission_mode(origin=None, env=None):
|
|
374
|
+
"""ask_all | ask_important | dont_ask: the user's permission mode for a run of ``origin``.
|
|
375
|
+
|
|
376
|
+
Through PR #143's ``permissions.policy`` when it is in this build; else the same settings it reads:
|
|
377
|
+
MOBSTER_PERMISSION_MODE, then MOBSTER_ASK_BEFORE_ACTING=0 as dont_ask, else ask_important. A run an outside
|
|
378
|
+
agent started (MCP, Remote) never goes below ask_important."""
|
|
379
|
+
if env is None:
|
|
380
|
+
try:
|
|
381
|
+
from ..permissions import policy # noqa: F401 (PR #143; absent on builds without it)
|
|
382
|
+
settings = policy.from_env()
|
|
383
|
+
return policy.effective_mode(settings.mode, None, origin)
|
|
384
|
+
except Exception:
|
|
385
|
+
pass
|
|
386
|
+
env = os.environ if env is None else env
|
|
387
|
+
mode = (env.get("MOBSTER_PERMISSION_MODE") or "").strip()
|
|
388
|
+
if mode not in MODES:
|
|
389
|
+
mode = DONT_ASK if (env.get("MOBSTER_ASK_BEFORE_ACTING") or "1").strip() == "0" else ASK_IMPORTANT
|
|
390
|
+
if origin in OUTSIDE_ORIGINS and mode == DONT_ASK:
|
|
391
|
+
mode = ASK_IMPORTANT
|
|
392
|
+
return mode
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@dataclass(frozen=True)
|
|
396
|
+
class Decision:
|
|
397
|
+
action: str # "press" | "ask" | "stop" | "leave"
|
|
398
|
+
dialog: Dialog
|
|
399
|
+
button: str = "" # press: the button; ask: the one Mobster suggests (the narrowest grant)
|
|
400
|
+
deny: str = "" # ask: the button a decline presses ("" when there is none to press)
|
|
401
|
+
reason: str = "" # one plain sentence for the thread and the model
|
|
402
|
+
title: str = "" # ask: the approval card's question
|
|
403
|
+
perform: object = None # press: what answers the dialog when it is no button (a simulator's Face ID match)
|
|
404
|
+
|
|
405
|
+
def public(self):
|
|
406
|
+
return {"action": self.action, "kind": self.dialog.kind, "app": self.dialog.app or None,
|
|
407
|
+
"button": self.button or None, "reason": self.reason}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _who(dialog):
|
|
411
|
+
return f"“{dialog.app}”" if dialog.app else "An app"
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def decide(dialog, mode=ASK_IMPORTANT, *, can_ask=True, recent_=None, in_app=False, now=None, clock=time.monotonic):
|
|
415
|
+
"""What to do about ``dialog`` under ``mode``."""
|
|
416
|
+
if dialog is None:
|
|
417
|
+
return None
|
|
418
|
+
mode = mode if mode in MODES else ASK_IMPORTANT
|
|
419
|
+
now = clock() if now is None else now
|
|
420
|
+
kind, who = dialog.kind, _who(dialog)
|
|
421
|
+
allow, deny = dialog.allow_button(), dialog.deny_button()
|
|
422
|
+
|
|
423
|
+
def ask(title, suggested=None, why=""):
|
|
424
|
+
"""The user chooses on the approval card; with nobody to ask, the run stops and leaves the dialog up."""
|
|
425
|
+
if can_ask and dialog.buttons:
|
|
426
|
+
return Decision("ask", dialog, suggested or allow or dialog.buttons[0], deny or "", why or
|
|
427
|
+
"iOS is asking you, so Mobster asks you too.", title)
|
|
428
|
+
return Decision("stop", dialog, reason=f"{title.rstrip('?')}: iOS is asking, and Mobster leaves that to "
|
|
429
|
+
"you. Answer it on your iPhone, then run the task again.")
|
|
430
|
+
|
|
431
|
+
if kind == "sign_in_with_apple":
|
|
432
|
+
return Decision("stop", dialog, reason="Your iPhone asks to sign in with your Apple Account. Mobster never "
|
|
433
|
+
"answers that for you: answer it on your iPhone, then run the task "
|
|
434
|
+
"again.")
|
|
435
|
+
if kind == "purchase":
|
|
436
|
+
return Decision("stop", dialog, reason="Your iPhone asks you to confirm a purchase. Mobster never confirms one "
|
|
437
|
+
"for you: answer it on your iPhone, then run the task again.")
|
|
438
|
+
if kind in NEVER:
|
|
439
|
+
if kind == "save_password" and deny:
|
|
440
|
+
return Decision("press", dialog, deny, reason="Mobster never saves a password for you.")
|
|
441
|
+
if kind == "trust_computer":
|
|
442
|
+
return Decision("stop", dialog, reason="Your iPhone asks whether to trust a computer. Mobster never "
|
|
443
|
+
"answers that: answer it on your iPhone.")
|
|
444
|
+
return Decision("stop", dialog, reason="Your iPhone is asking for your Apple Account or a password. "
|
|
445
|
+
"Mobster never answers that: answer it on your iPhone, then run "
|
|
446
|
+
"the task again.")
|
|
447
|
+
if kind == "paste":
|
|
448
|
+
pasted_at = recent_.pasted_at if recent_ is not None else None
|
|
449
|
+
mine = pasted_at is not None and 0 <= now - pasted_at <= PASTE_WINDOW_S
|
|
450
|
+
if mine and allow:
|
|
451
|
+
return Decision("press", dialog, allow, reason="the text being pasted is what Mobster just typed.")
|
|
452
|
+
if mode == ASK_ALL and can_ask:
|
|
453
|
+
return ask(f"Let {who} paste from your clipboard?", deny,
|
|
454
|
+
"Mobster didn't put anything on your clipboard.")
|
|
455
|
+
if deny:
|
|
456
|
+
return Decision("press", dialog, deny, reason="Mobster didn't put anything on the clipboard, so it "
|
|
457
|
+
"stays private.")
|
|
458
|
+
return ask(f"Let {who} paste from your clipboard?")
|
|
459
|
+
if kind == "open_in_app":
|
|
460
|
+
opened_at = recent_.opened_at if recent_ is not None else None
|
|
461
|
+
mine = opened_at is not None and 0 <= now - opened_at <= OPEN_WINDOW_S
|
|
462
|
+
if mine and allow and mode != ASK_ALL:
|
|
463
|
+
return Decision("press", dialog, allow, reason="Mobster opened this link for the task.")
|
|
464
|
+
if not mine and deny and not can_ask:
|
|
465
|
+
return Decision("press", dialog, deny, reason="Mobster didn't open this link, so it stays closed.")
|
|
466
|
+
return ask(f"Open the link in {who}?")
|
|
467
|
+
if kind == "tracking" and deny:
|
|
468
|
+
if mode == ASK_ALL and can_ask:
|
|
469
|
+
return ask(f"Let {who} track you across other apps and websites?", deny)
|
|
470
|
+
return Decision("press", dialog, deny, reason="apps work without tracking you, so Mobster asked it not "
|
|
471
|
+
"to track.")
|
|
472
|
+
if kind == "software_update":
|
|
473
|
+
later = dialog.button("Later", "Remind Me Later", "Not Now")
|
|
474
|
+
if later and not (mode == ASK_ALL and can_ask):
|
|
475
|
+
return Decision("press", dialog, later, reason="installing iOS restarts the phone and isn't part of the "
|
|
476
|
+
"task, so Mobster chose later.")
|
|
477
|
+
return ask("Install the iOS update now?", later, "Installing restarts your iPhone.")
|
|
478
|
+
if kind == "screen_time":
|
|
479
|
+
return Decision("stop", dialog, reason="Screen Time's limit for this app is up. Mobster never ignores a limit "
|
|
480
|
+
"for you: extend it on your iPhone, then run the task again.")
|
|
481
|
+
if kind == "undo_typing":
|
|
482
|
+
cancel = dialog.button("Cancel")
|
|
483
|
+
if cancel:
|
|
484
|
+
return Decision("press", dialog, cancel, reason="it keeps the text as it is.")
|
|
485
|
+
return Decision("leave", dialog)
|
|
486
|
+
if kind == "web_sign_in":
|
|
487
|
+
return ask(f"Let {who} use the website's sign-in?", allow, "The app could see your sign-in for that site.")
|
|
488
|
+
if kind in ("rating", "low_battery") and deny:
|
|
489
|
+
return Decision("press", dialog, deny, reason="it changes nothing, so Mobster closed it.")
|
|
490
|
+
if kind == "notifications":
|
|
491
|
+
if (mode == DONT_ASK or not can_ask) and deny:
|
|
492
|
+
return Decision("press", dialog, deny, reason="a task doesn't need notifications. Turn them on in "
|
|
493
|
+
"Settings › Notifications if you want them.")
|
|
494
|
+
return ask(f"Let {who} send you notifications?", allow)
|
|
495
|
+
if dialog.grant:
|
|
496
|
+
return ask(f"Let {who} {GRANTS[kind]}?", allow)
|
|
497
|
+
if in_app or not dialog.buttons:
|
|
498
|
+
return Decision("leave", dialog)
|
|
499
|
+
return ask(f"{who} asks: “{dialog.text[:120]}”", None)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def choices(decision):
|
|
503
|
+
"""The approval card's choices: each of iOS's buttons, in iOS's order, as {"id", "label"}."""
|
|
504
|
+
return [{"id": f"b{index}", "label": button[:60]} for index, button in enumerate(decision.dialog.buttons[:5])]
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def answered(decision, answer):
|
|
508
|
+
"""The button to press for an approval ``answer``; None presses nothing and the run stops."""
|
|
509
|
+
if not isinstance(answer, str):
|
|
510
|
+
return None
|
|
511
|
+
if answer.startswith("choice:"):
|
|
512
|
+
picked = answer.split(":", 1)[1]
|
|
513
|
+
for item, button in zip(choices(decision), decision.dialog.buttons):
|
|
514
|
+
if item["id"] == picked:
|
|
515
|
+
return button
|
|
516
|
+
return None
|
|
517
|
+
if answer == "approved":
|
|
518
|
+
return decision.button or None
|
|
519
|
+
if answer == "denied":
|
|
520
|
+
return decision.deny or None
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def press(driver, decision, timeout=10, snapshot=None):
|
|
525
|
+
"""Press the decision's button by its name; raises when it can't, and nothing is pressed then."""
|
|
526
|
+
if decision.perform is not None:
|
|
527
|
+
decision.perform()
|
|
528
|
+
return
|
|
529
|
+
if callable(getattr(driver, "call", None)) and not decision.dialog.on_screen:
|
|
530
|
+
driver.call("POST", "/alert/accept", {"name": decision.button}, timeout=timeout)
|
|
531
|
+
return
|
|
532
|
+
wanted = fold(decision.button).casefold()
|
|
533
|
+
found = [e for e in getattr(snapshot, "elements", ()) or () if fold(e.label).casefold() == wanted]
|
|
534
|
+
if len(found) != 1:
|
|
535
|
+
raise ValueError(f"the alert's “{decision.button}” isn't on the screen once")
|
|
536
|
+
driver.execute("TAP", found[0], snapshot, timeout=timeout)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def line(decision, pressed=None, by_user=False):
|
|
540
|
+
"""What happened, in one sentence for the thread and the model."""
|
|
541
|
+
dialog = decision.dialog
|
|
542
|
+
asked = f"iOS asked “{dialog.text[:140]}”"
|
|
543
|
+
if decision.action == "press":
|
|
544
|
+
return f"{asked}. Mobster chose “{decision.button}”: {decision.reason}"
|
|
545
|
+
if decision.action == "ask":
|
|
546
|
+
if pressed:
|
|
547
|
+
return f"{asked}. You chose “{pressed}”."
|
|
548
|
+
return f"{asked}. Nothing was chosen, so it is still on the screen."
|
|
549
|
+
if decision.action == "stop":
|
|
550
|
+
return decision.reason
|
|
551
|
+
return asked
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
TOPICS = {"notifications": "notifications", "tracking": "tracking", "paste": "Allow Paste",
|
|
555
|
+
"open_in_app": "Open in app", "save_password": "Save Password", "rating": "a rating prompt",
|
|
556
|
+
"low_battery": "Low Battery", "location": "location", "location_always": "location", "photos": "Photos",
|
|
557
|
+
"photos_add": "Photos", "camera": "the camera", "microphone": "the microphone", "contacts": "contacts",
|
|
558
|
+
"calendar": "the calendar", "reminders": "reminders", "bluetooth": "Bluetooth",
|
|
559
|
+
"local_network": "the local network", "motion": "motion and fitness", "speech": "speech recognition",
|
|
560
|
+
"media": "the media library", "home": "Home data", "health": "health data", "face_id": "Face ID",
|
|
561
|
+
"focus": "Focus status", "sign_in_with_apple": "Sign in with Apple", "purchase": "a purchase",
|
|
562
|
+
"biometric": "Face ID or Touch ID", "software_update": "an iOS update", "screen_time": "a Screen Time limit",
|
|
563
|
+
"web_sign_in": "a website sign-in", "undo_typing": "Undo Typing"}
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def thread_text(kind, app, button, by="mobster"):
|
|
567
|
+
"""The thread's step line for an answered dialog."""
|
|
568
|
+
topic = TOPICS.get(kind, "a prompt")
|
|
569
|
+
where = f" for {app}" if app else ""
|
|
570
|
+
who = "you" if by == "you" else "Mobster"
|
|
571
|
+
return f"iOS asked about {topic}{where}: {who} chose “{button}”"
|