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,535 @@
|
|
|
1
|
+
"""Which actions the person must approve first: independent signals that only add asks, never remove one. No I/O."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import unicodedata
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from ..contract_screen import APP_STORE
|
|
8
|
+
from .rules import CONTENT_ROLES, RETURN_OPS, ROW_WORDS, SEARCH_WORDS, TAP_OPS, _phrases, norm, words_of
|
|
9
|
+
|
|
10
|
+
FIELD_ROLES = frozenset({"TextField", "TextView", "SecureTextField", "SearchField"})
|
|
11
|
+
CONTAINERS = frozenset({"Alert", "Sheet", "Menu", "Popover", "ActionSheet"})
|
|
12
|
+
|
|
13
|
+
MESSAGE_APPS = frozenset({
|
|
14
|
+
"com.apple.MobileSMS", "com.apple.mobilemail", "net.whatsapp.WhatsApp", "com.facebook.Messenger",
|
|
15
|
+
"ph.telegra.Telegraph", "org.whispersystems.signal", "com.tinyspeck.chatlyio", "com.hammerandchisel.discord",
|
|
16
|
+
"com.google.Gmail", "com.microsoft.Office.Outlook", "com.burbn.instagram", "com.atebits.Tweetie2",
|
|
17
|
+
"com.linkedin.LinkedIn", "com.toyopagroup.picaboo", "jp.naver.line", "com.tencent.xin", "com.facebook.Facebook",
|
|
18
|
+
"com.cardify.tinder", "com.hinge.app", "com.bumble.app", "com.microsoft.skype.teams", "com.google.chat",
|
|
19
|
+
"com.apple.facetime"})
|
|
20
|
+
PAY_BUNDLES = frozenset({"com.apple.PassbookUIService", "com.apple.ios.StoreKitUIService"})
|
|
21
|
+
MAIL_APPS = frozenset({"com.apple.mobilemail", "com.google.Gmail", "com.microsoft.Office.Outlook"})
|
|
22
|
+
SHORTCUTS = "com.apple.shortcuts"
|
|
23
|
+
# Apps whose row swipe archives or deletes on its own, with no buttons to tap first.
|
|
24
|
+
SWIPE_ACT_APPS = frozenset({"com.google.Gmail", "com.microsoft.Office.Outlook", "com.readdle.smartemail",
|
|
25
|
+
"com.superhuman.Superhuman", "ch.protonmail.protonmail"})
|
|
26
|
+
# The card's line for a swipe that may itself be the act (no reveal step).
|
|
27
|
+
SWIPE_ACTS, SWIPE_MAY = "A swipe archives or deletes in this app", "A swipe may archive or delete here"
|
|
28
|
+
|
|
29
|
+
# (act, pattern) in order: the first that matches names the act. Words are matched on rules.norm's folded label.
|
|
30
|
+
_ACTS = (
|
|
31
|
+
("unsend", r"\b(?:unsend|undo send|delete for everyone|remove for everyone)\b"),
|
|
32
|
+
("erase", r"\b(?:erase|wipe)\b"),
|
|
33
|
+
("reset", r"\breset\b(?! (?:zoom|filters?|view|search)\b)"),
|
|
34
|
+
("delete", r"\b(?:delete|destroy|deactivate|close (?:my |your |the )?account|trash|move to (?:the )?(?:trash|bin)|"
|
|
35
|
+
r"send to (?:the )?(?:trash|bin)|discard|don.?t save)\b"),
|
|
36
|
+
("clear", r"\bclear\b(?! (?:text|search|query|field|filters?|selection|input)\b)"),
|
|
37
|
+
("remove", r"\b(?:remove|forget (?:this )?device|unpair)\b(?! (?:filters?|tag from search)\b)"),
|
|
38
|
+
("sign out", r"\b(?:sign|log) ?out\b"),
|
|
39
|
+
("unmatch", r"\b(?:unmatch|unfriend)\b"),
|
|
40
|
+
("leave", r"\b(?:leave|exit) (?:the )?(?:group|chat|conversation|channel|server|community|team|call)\b|^leave$"),
|
|
41
|
+
("end call", r"\b(?:end call|hang up)\b"),
|
|
42
|
+
("block", r"^(?:block|unblock)\b|\bblock (?:this|contact|caller|user|account)\b"),
|
|
43
|
+
("report", r"^report\b|\breport (?:this|post|user|account|spam|abuse|message|comment)\b"),
|
|
44
|
+
("junk", r"\bmark as (?:junk|spam)\b|^(?:junk|spam)$"),
|
|
45
|
+
("archive", r"^archive\b"),
|
|
46
|
+
("unfollow", r"^(?:unfollow|unsubscribe)\b"),
|
|
47
|
+
("pay", r"\b(?:pay|send money|transfer|donate|apple pay)\b|^tip\b"),
|
|
48
|
+
("buy", r"\b(?:buy|purchase|checkout|check out|place (?:your |the |this |my )?order|order now|rent)\b"),
|
|
49
|
+
("subscribe", r"\b(?:subscribe|start (?:my |your )?(?:free )?trial|try (?:it )?free|upgrade now)\b"),
|
|
50
|
+
("book", r"^(?:book|reserve)\b"),
|
|
51
|
+
("request", r"^request\b(?! (?:desktop|mobile) )"),
|
|
52
|
+
("send", r"\b(?:send|submit)\b"),
|
|
53
|
+
("post", r"\b(?:post|publish|tweet|repost|retweet|reblog)\b(?! (?:st|street)\b)"),
|
|
54
|
+
("call", r"^(?:call|facetime|dial)\b"),
|
|
55
|
+
("follow", r"^follow\b(?! up)"),
|
|
56
|
+
("react", r"^(?:like|unlike|react|super ?like|love)(?: (?:heart|thumbs? ?up|button|icon))?$|"
|
|
57
|
+
r"^(?:like|react to) (?:this|post|photo|video|comment)\b"),
|
|
58
|
+
)
|
|
59
|
+
ACTS = tuple((act, re.compile(pattern, re.I)) for act, pattern in _ACTS)
|
|
60
|
+
# The same acts in the largest iOS languages other than English: a phone set to Spanish says "Eliminar", not "Delete".
|
|
61
|
+
_LOCALIZED = {
|
|
62
|
+
"unsend": ("anular envío", "deshacer envío", "cancelar envío", "senden widerrufen", "annuler l’envoi",
|
|
63
|
+
"annuler l'envoi", "annulla invio", "送信を取り消す", "送信取消", "撤回", "보내기 취소"),
|
|
64
|
+
"delete": ("eliminar", "borrar", "excluir", "apagar", "löschen", "supprimer", "effacer", "elimina", "cancella",
|
|
65
|
+
"verwijderen", "radera", "ta bort", "usuń", "удалить", "削除", "删除", "刪除", "삭제"),
|
|
66
|
+
"sign out": ("cerrar sesión", "terminar sessão", "sair da conta", "abmelden", "se déconnecter", "déconnexion",
|
|
67
|
+
"esci", "afmelden", "logga ut", "wyloguj", "çıkış yap", "выйти", "サインアウト", "ログアウト",
|
|
68
|
+
"退出登录", "登出", "로그아웃"),
|
|
69
|
+
"block": ("bloquear", "blockieren", "bloquer", "blocca", "blokkeren", "blockera", "zablokuj", "engelle",
|
|
70
|
+
"заблокировать", "ブロック", "屏蔽", "封鎖", "차단"),
|
|
71
|
+
"report": ("denunciar", "reportar", "melden", "signaler", "segnala", "rapporteren", "anmäl", "zgłoś",
|
|
72
|
+
"şikayet et", "пожаловаться", "報告", "举报", "檢舉", "신고"),
|
|
73
|
+
"pay": ("pagar", "bezahlen", "payer", "paga", "betalen", "betala", "zapłać", "öde", "оплатить", "支払う",
|
|
74
|
+
"支払い", "支付", "付款", "결제"),
|
|
75
|
+
"buy": ("comprar", "kaufen", "acheter", "acquista", "kopen", "köp", "kup teraz", "satın al", "купить", "購入",
|
|
76
|
+
"购买", "購買", "구매"),
|
|
77
|
+
"subscribe": ("suscribirse", "assinar", "abonnieren", "s’abonner", "s'abonner", "iscriviti", "abonneren",
|
|
78
|
+
"prenumerera", "subskrybuj", "abone ol", "подписаться на", "登録する", "订阅", "訂閱", "구독"),
|
|
79
|
+
"send": ("enviar", "senden", "envoyer", "invia", "verzenden", "versturen", "skicka", "wyślij", "gönder",
|
|
80
|
+
"отправить", "送信", "发送", "傳送", "發送", "보내기", "전송"),
|
|
81
|
+
"post": ("publicar", "veröffentlichen", "posten", "publier", "pubblica", "plaatsen", "publicera", "opublikuj",
|
|
82
|
+
"paylaş", "опубликовать", "投稿", "发布", "發佈", "게시"),
|
|
83
|
+
"follow": ("seguir", "folgen", "suivre", "segui", "volgen", "följ", "obserwuj", "takip et", "подписаться",
|
|
84
|
+
"フォロー", "关注", "追蹤", "팔로우"),
|
|
85
|
+
"react": ("me gusta", "curtir", "gefällt mir", "j’aime", "j'aime", "mi piace", "vind ik leuk", "gilla",
|
|
86
|
+
"lubię to", "beğen", "нравится", "いいね", "赞", "讚", "좋아요"),
|
|
87
|
+
}
|
|
88
|
+
LOCALIZED = tuple((act, _phrases(words)) for act, words in _LOCALIZED.items())
|
|
89
|
+
# SF Symbols a control may be named by when it has no label of its own.
|
|
90
|
+
GLYPHS = (("send", re.compile(r"paperplane(?:\.fill)?|arrow\.up\.circle(?:\.fill)?|arrow\.up\.message(?:\.fill)?")),
|
|
91
|
+
("react", re.compile(r"(?:heart|hand\.thumbsup|hand\.thumbsdown|suit\.heart)(?:\.fill)?")),
|
|
92
|
+
("follow", re.compile(r"person\.(?:badge|crop\.circle\.badge)\.plus(?:\.fill)?")),
|
|
93
|
+
("delete", re.compile(r"(?:minus\.circle|xmark\.bin|trash\.circle|trash\.slash)(?:\.fill)?")))
|
|
94
|
+
# Bare labels that only open a composer, a sheet or a list: their own Send or Post asks in turn.
|
|
95
|
+
OPENERS = frozenset({"share", "share…", "share...", "comment", "comments", "reply", "reply all", "forward", "call",
|
|
96
|
+
"message", "messages", "new message", "compose", "post", "new post", "send"})
|
|
97
|
+
# ... except where the act happens on tap: a bare Send or Post is always the act (removed from OPENERS below).
|
|
98
|
+
OPENERS = OPENERS - {"send", "post", "call"}
|
|
99
|
+
# Rows named for a place, not an act: Mail's and Notes' folders.
|
|
100
|
+
FOLDERS = frozenset({"trash", "bin", "junk", "spam", "archive", "deleted", "recently deleted", "drafts", "sent",
|
|
101
|
+
"outbox", "blocked", "blocked contacts", "report", "reports"})
|
|
102
|
+
# Buttons that back out of a dialog: never the act.
|
|
103
|
+
SAFE = re.compile(r"^(?:cancel|close|not now|later|maybe later|no|no thanks|keep|keep editing|keep (?:it|note|"
|
|
104
|
+
r"photo|message|draft)|go back|back|dismiss|stay|don.?t (?:allow|delete|remove|leave|sign out|"
|
|
105
|
+
r"erase|reset|unsend|send|post)|skip|x|xmark|done)$", re.I)
|
|
106
|
+
# Words of a button that confirms whatever the dialog or screen says.
|
|
107
|
+
CONFIRM = re.compile(r"^(?:ok|okay|yes|continue|confirm|proceed|i understand|agree|accept|approve|"
|
|
108
|
+
r"erase now|delete now|erase iphone|confirm with side button|pay now|authori[sz]e|"
|
|
109
|
+
r"(?:\w+ )?anyway)$", re.I)
|
|
110
|
+
# What a dialog or a screen says it destroys or charges.
|
|
111
|
+
DESTROYS = re.compile(r"\b(?:delete[ds]?|deleting|erase[ds]?|erasing|remove[ds]?|removing|wipe[ds]?|"
|
|
112
|
+
r"(?:can.?t|cannot|can not) be (?:undone|recovered|reversed)|permanently|unsend|discard|"
|
|
113
|
+
r"sign(?:ing)? out|log(?:ging)? out|reset|leave (?:the )?(?:group|chat|conversation)|unmatch|"
|
|
114
|
+
r"block|report)\b", re.I)
|
|
115
|
+
CHARGES = re.compile(r"(?:[$€£¥₩₽₹]\s?\d|\d[\d.,]*\s?(?:€|zł|kr|usd|eur|gbp)\b|/\s?(?:mo|month|yr|year|wk|week)\b|"
|
|
116
|
+
r"\bper (?:month|year|week)\b|\bfree trial\b|\bsubscription\b|\bpurchase\b|\bapple pay\b)", re.I)
|
|
117
|
+
TAPBACKS = frozenset({"heart", "love", "thumbs up", "thumbs down", "like", "dislike", "haha", "ha ha", "emphasize",
|
|
118
|
+
"exclamation mark", "question mark", "!!", "?"})
|
|
119
|
+
# Controls beside a composer that never send: its own toolbar.
|
|
120
|
+
COMPOSER_TOOLS = re.compile(r"\b(?:camera|photos?|apps?|audio|record|emoji|gif|stickers?|attach\w*|add|plus|voice|"
|
|
121
|
+
r"microphone|mic|dictat\w*|expand|more|keyboard|gallery|location|files?|edit|cancel|"
|
|
122
|
+
r"close|back|search)\b", re.I)
|
|
123
|
+
COMPOSER = re.compile(r"\b(?:compose|message|imessage|text message|chat|comment|reply|write|tweet|caption|"
|
|
124
|
+
r"say something|on your mind|post)\b|\.\.\.$|…$", re.I)
|
|
125
|
+
NOT_MESSAGE_FIELD = re.compile(r"\b(?:search|find|query|filter|to|cc|bcc|subject|body|recipients?|address|url|website|"
|
|
126
|
+
r"name|email|phone|username|password|where to|destination|location|city|zip)\b", re.I)
|
|
127
|
+
PROTECTIONS = re.compile(r"\b(?:find my|stolen device protection|two.factor|2fa|passcode|face id|touch id|"
|
|
128
|
+
r"icloud backup|location services|erase data|lockdown mode|advanced data protection)\b", re.I)
|
|
129
|
+
# Shortcuts' own controls (tabs, folders, bars); every other tile there runs a shortcut.
|
|
130
|
+
SHORTCUTS_NAV = re.compile(r"^(?:shortcuts|automation|gallery|search|edit|select|done|back|cancel|add|new shortcut|"
|
|
131
|
+
r"\+|more|all shortcuts|share sheet|apple watch|my shortcuts|folders?|new folder|"
|
|
132
|
+
r"details|settings|library|sort|filter|close)$", re.I)
|
|
133
|
+
# A search field's words and a browser's address bar's names; a web page's own fields sit in a web view and never count.
|
|
134
|
+
ADDRESS_LABEL = re.compile(r"^(?:address(?: bar)?|address and search|web address|url)$", re.I)
|
|
135
|
+
BROWSERS = frozenset({"com.apple.mobilesafari", "com.google.chrome.ios", "org.mozilla.ios.Firefox",
|
|
136
|
+
"com.microsoft.msedge", "com.brave.ios.browser", "com.duckduckgo.mobile.ios",
|
|
137
|
+
"com.opera.OperaTouch"})
|
|
138
|
+
# A button that clears a field's text, as a search bar draws it: the whole label, nothing else on it.
|
|
139
|
+
CLEAR_BUTTON = re.compile(r"^clear$", re.I)
|
|
140
|
+
CLEAR_GAP = .12 # how far (screen width) a Clear button may sit past the end of the field it clears
|
|
141
|
+
|
|
142
|
+
SF_SYMBOL = re.compile(r"[a-z0-9]+(?:\.[a-z0-9]+)+")
|
|
143
|
+
TRASH_GLYPH = re.compile(r"(?:trash|xmark\.bin|bin\.xmark)(?:\.[a-z0-9]+)*")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class Reading:
|
|
148
|
+
act: str # the card's verb: "delete", "send", "sign out"...
|
|
149
|
+
signal: str # which signal found it
|
|
150
|
+
reason: str # the card's line, at most 12 words
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def label_act(label, role=None, rect=None, pixels=False):
|
|
154
|
+
"""The act a control's own label names, or None (``label`` signal)."""
|
|
155
|
+
text = " ".join(str(label or "").split())
|
|
156
|
+
if not text or role in FIELD_ROLES:
|
|
157
|
+
return None
|
|
158
|
+
if TRASH_GLYPH.fullmatch(text):
|
|
159
|
+
return "delete"
|
|
160
|
+
glyph = next((act for act, glyph in GLYPHS if glyph.fullmatch(text)), None)
|
|
161
|
+
if glyph is not None and role not in CONTENT_ROLES:
|
|
162
|
+
return glyph
|
|
163
|
+
if SF_SYMBOL.fullmatch(text):
|
|
164
|
+
return None
|
|
165
|
+
folded = norm(text)
|
|
166
|
+
if folded in OPENERS:
|
|
167
|
+
return None
|
|
168
|
+
# A folder's row is full width (Mail's Trash); a swipe action or a toolbar button named Trash is narrow.
|
|
169
|
+
folder = folded in FOLDERS and (not rect or rect[2] >= .6) and (role in CONTENT_ROLES or pixels)
|
|
170
|
+
if folder or role in CONTENT_ROLES and ("," in text or len(words_of(text)) > (6 if pixels else ROW_WORDS)
|
|
171
|
+
or re.search(r",\s*(?:incomplete|completed)$", text, re.I)):
|
|
172
|
+
return None
|
|
173
|
+
head = " ".join(words_of(text)[:6])
|
|
174
|
+
for act, pattern in ACTS:
|
|
175
|
+
found = pattern.search(head)
|
|
176
|
+
if found is None:
|
|
177
|
+
continue
|
|
178
|
+
if role in CONTENT_ROLES and found.start() != 0:
|
|
179
|
+
continue
|
|
180
|
+
return act
|
|
181
|
+
for act, pattern in LOCALIZED:
|
|
182
|
+
found = pattern.search(folded)
|
|
183
|
+
if found is not None and (role not in CONTENT_ROLES or found.start() == 0):
|
|
184
|
+
return act
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _inside(outer, inner):
|
|
189
|
+
x, y, w, h = outer
|
|
190
|
+
cx, cy = inner[0] + inner[2] / 2, inner[1] + inner[3] / 2
|
|
191
|
+
return x - 1e-6 <= cx <= x + w + 1e-6 and y - 1e-6 <= cy <= y + h + 1e-6
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _container(element, elements):
|
|
195
|
+
"""The smallest alert, sheet or menu that holds ``element``, or None."""
|
|
196
|
+
rect = getattr(element, "rect", None)
|
|
197
|
+
if rect is None:
|
|
198
|
+
return None
|
|
199
|
+
holders = [e for e in elements if e is not element and getattr(e, "role", None) in CONTAINERS
|
|
200
|
+
and _inside(e.rect, rect)]
|
|
201
|
+
return min(holders, key=lambda e: e.rect[2] * e.rect[3]) if holders else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _texts_in(container, elements):
|
|
205
|
+
out = [container.label or ""]
|
|
206
|
+
for e in elements:
|
|
207
|
+
if e is container or getattr(e, "role", None) not in ("StaticText", "Other", "Text"):
|
|
208
|
+
continue
|
|
209
|
+
if _inside(container.rect, e.rect):
|
|
210
|
+
out.append(e.label or "")
|
|
211
|
+
return " ".join(out)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _screen_texts(elements):
|
|
215
|
+
return " ".join((e.label or "") for e in elements if getattr(e, "role", None) in ("StaticText", "NavigationBar",
|
|
216
|
+
"Other", "Text"))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _emoji_only(label):
|
|
220
|
+
text = "".join(str(label or "").split())
|
|
221
|
+
if not text:
|
|
222
|
+
return False
|
|
223
|
+
return all(unicodedata.category(ch) in ("So", "Sk", "Mn", "Cf") or ch in "️" for ch in text) and any(
|
|
224
|
+
unicodedata.category(ch) == "So" for ch in text)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _composer(elements):
|
|
228
|
+
"""The message composer on the screen (an editable field named like one, not a search), or None."""
|
|
229
|
+
for e in elements:
|
|
230
|
+
if getattr(e, "role", None) in ("TextView", "TextField") and getattr(e, "editable", True):
|
|
231
|
+
words = f"{e.label or ''} {getattr(e, 'placeholder', '') or ''}"
|
|
232
|
+
if COMPOSER.search(words) and not NOT_MESSAGE_FIELD.search(words):
|
|
233
|
+
return e
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _beside_written_composer(element, elements):
|
|
238
|
+
"""Whether ``element`` sits right of the message composer on its line while the composer holds text of its own."""
|
|
239
|
+
composer = _composer(elements)
|
|
240
|
+
if composer is None or element is composer:
|
|
241
|
+
return False
|
|
242
|
+
value = (getattr(composer, "value", "") or "").strip()
|
|
243
|
+
if not value or value == (getattr(composer, "placeholder", "") or "").strip():
|
|
244
|
+
return False
|
|
245
|
+
line = composer.rect[1] + composer.rect[3] / 2
|
|
246
|
+
return (abs(element.rect[1] + element.rect[3] / 2 - line) <= .04
|
|
247
|
+
and element.rect[0] >= composer.rect[0] + composer.rect[2] - .01)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _act_from_text(text):
|
|
251
|
+
"""The act a dialog's or a screen's words say it performs."""
|
|
252
|
+
folded = norm(text)
|
|
253
|
+
for act, word in (("erase", r"\berase|\bwipe"), ("unsend", r"\bunsend"), ("sign out", r"\bsign(?:ing)? out|\blog"),
|
|
254
|
+
("leave", r"\bleave"), ("unmatch", r"\bunmatch"), ("block", r"\bblock"),
|
|
255
|
+
("report", r"\breport"), ("reset", r"\breset"), ("remove", r"\bremov"),
|
|
256
|
+
("delete", r"\bdelet|\bdiscard|permanently|undone|recovered|reversed")):
|
|
257
|
+
if re.search(word, folded):
|
|
258
|
+
return act
|
|
259
|
+
return "delete"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def read(action, elements=()):
|
|
263
|
+
"""The Reading for ``action`` on a screen of ``elements``, or None when no signal says it acts past the phone."""
|
|
264
|
+
op = (action.op or "").upper()
|
|
265
|
+
role = action.role
|
|
266
|
+
label = " ".join(str(action.label or "").split())
|
|
267
|
+
elements = tuple(elements or getattr(action, "around", ()) or ())
|
|
268
|
+
bundle = action.app or ""
|
|
269
|
+
if op in RETURN_OPS:
|
|
270
|
+
return _return(action, elements)
|
|
271
|
+
pixels = bool(getattr(action, "pixels", False))
|
|
272
|
+
rect = tuple(getattr(action, "rect", ()) or ()) or None
|
|
273
|
+
if op in ("SWIPE_LEFT", "SWIPE_RIGHT"):
|
|
274
|
+
# swipe: in apps where a row's swipe is the act itself (no reveal step).
|
|
275
|
+
if (role == "Cell" or pixels) and bundle in SWIPE_ACT_APPS:
|
|
276
|
+
return Reading("archive", "swipe", SWIPE_ACTS)
|
|
277
|
+
if pixels and not bundle and rect:
|
|
278
|
+
# Pixels in an app Mobster can't name: the swipe may be one that archives or deletes.
|
|
279
|
+
return Reading("archive", "swipe", SWIPE_MAY)
|
|
280
|
+
return None
|
|
281
|
+
if op not in TAP_OPS or role in FIELD_ROLES:
|
|
282
|
+
return None
|
|
283
|
+
element = _element(action, elements)
|
|
284
|
+
folded = norm(label)
|
|
285
|
+
if pixels and not bundle and any(norm(e.label) in ("shortcuts", "all shortcuts") and e.rect[1] < .15
|
|
286
|
+
for e in elements):
|
|
287
|
+
bundle = SHORTCUTS # pixels in an app Mobster can't name, whose title says it is Shortcuts
|
|
288
|
+
if keypad_key(label, role, elements, pixels):
|
|
289
|
+
return None # a calculator's or a dial pad's key: Clear, Delete
|
|
290
|
+
if clears_query_field(label, role, element, elements, bundle, pixels):
|
|
291
|
+
return None # a search bar's Clear: it empties a query, not a draft or a note
|
|
292
|
+
# glyph: an icon whose shape commits under a name that doesn't (a paper plane named "Direct" by a message box).
|
|
293
|
+
glyph = glyph_act(getattr(action, "glyph", ""), rect, elements, bundle)
|
|
294
|
+
if glyph is not None and label_act(label, role, rect, pixels) is None:
|
|
295
|
+
return Reading(glyph, "glyph", f"It looks like a {action.glyph}; it's called “{label[:24]}”")
|
|
296
|
+
# App Store: Get, Install, a price (on pixels in an app Mobster can't name, a bare Get or price too).
|
|
297
|
+
if ((bundle == APP_STORE or pixels and not bundle and re.fullmatch(r"get|[$€£¥]\s?[\d.,]+", folded))
|
|
298
|
+
and re.match(r"^(?:get|install|redownload|re-download|buy|purchase|[$€£¥]\s?\d)", folded)):
|
|
299
|
+
return Reading("buy", "label", "Gets an app from the App Store")
|
|
300
|
+
# dialog: a button in an alert, a sheet or a menu that says it destroys or charges.
|
|
301
|
+
container = _container(element, elements) if element is not None else None
|
|
302
|
+
if container is not None and not SAFE.match(folded):
|
|
303
|
+
said = _texts_in(container, elements)
|
|
304
|
+
if DESTROYS.search(said) and (CONFIRM.match(folded) or label_act(label, role) or not folded
|
|
305
|
+
or DESTROYS.search(label)):
|
|
306
|
+
return Reading(label_act(label, role) or _act_from_text(said), "dialog", "The dialog says what this does")
|
|
307
|
+
if CHARGES.search(said) and (CONFIRM.match(folded) or label_act(label, role) in ("pay", "buy", "subscribe")):
|
|
308
|
+
return Reading("pay", "dialog", "Spends money")
|
|
309
|
+
act = label_act(label, role, rect, pixels)
|
|
310
|
+
if act == "send" and folded in ("send", "submit") and not elements:
|
|
311
|
+
return Reading(act, "label", "Sends this")
|
|
312
|
+
if act is not None:
|
|
313
|
+
if act in ("book",) and bundle == APP_STORE:
|
|
314
|
+
act = "buy"
|
|
315
|
+
if act == "react" and container is None and role in CONTENT_ROLES and not pixels:
|
|
316
|
+
act = None
|
|
317
|
+
else:
|
|
318
|
+
return Reading(act, "label", REASONS.get(act, "Has an effect beyond your iPhone"))
|
|
319
|
+
# share: Share or Post with a caption the agent wrote.
|
|
320
|
+
if folded == "share" and pixels:
|
|
321
|
+
# On pixels a Share button can't be told from a share sheet's opener or a post's publish: unknown asks.
|
|
322
|
+
return Reading("post", "share", "This may publish what is on screen")
|
|
323
|
+
if folded in ("share", "post") and any(
|
|
324
|
+
getattr(e, "editable", False) and (getattr(e, "value", "") or "").strip()
|
|
325
|
+
and (getattr(e, "value", "") or "") != getattr(e, "placeholder", "")
|
|
326
|
+
and getattr(e, "role", None) != "SearchField" for e in elements):
|
|
327
|
+
return Reading("post", "share", "Publishes what you wrote")
|
|
328
|
+
# screen: a confirming button on a screen that says it destroys or charges.
|
|
329
|
+
if not SAFE.match(folded) and (CONFIRM.match(folded) or folded and bundle in PAY_BUNDLES):
|
|
330
|
+
said = _screen_texts(elements) + " " + str(action.context or "")
|
|
331
|
+
if bundle in PAY_BUNDLES or CHARGES.search(said) and re.search(r"\b(?:pay|buy|purchase|subscri|charge|"
|
|
332
|
+
r"trial|order|total)", said, re.I):
|
|
333
|
+
return Reading("pay", "screen", "Spends money")
|
|
334
|
+
if re.search(r"(?:will be|are|is) (?:permanently )?(?:erased|deleted|removed|wiped)|(?:can.?t|cannot|can not)"
|
|
335
|
+
r" be (?:undone|recovered|reversed)|permanently", said, re.I):
|
|
336
|
+
return Reading(_act_from_text(said), "screen", "The screen says this can't be undone")
|
|
337
|
+
# setting: switching off a protection, or any switch the request doesn't ask to change (the scope lock).
|
|
338
|
+
if role in ("Switch", "Toggle") and PROTECTIONS.search(label) and (action.value or "") in ("1", "on", "true"):
|
|
339
|
+
return Reading("turn off", "setting", "Turns off a protection")
|
|
340
|
+
if role in ("Switch", "Toggle") and not names_switch(action.goal, label):
|
|
341
|
+
return Reading("setting", "scope", "Your request doesn't name this setting")
|
|
342
|
+
if role not in ("Switch", "Toggle") and re.match(r"^turn off\b", folded) and PROTECTIONS.search(
|
|
343
|
+
label + " " + _screen_texts(elements)):
|
|
344
|
+
return Reading("turn off", "setting", "Turns off a protection")
|
|
345
|
+
# iOS names an unlabelled SF Symbol by its shape ("Up"): a control right of a written message box asks.
|
|
346
|
+
if element is not None and (role == "Button" or pixels) and _beside_written_composer(element, elements) \
|
|
347
|
+
and not COMPOSER_TOOLS.search(folded) and not SAFE.match(folded):
|
|
348
|
+
return Reading("send", "beside_composer", "It's beside your message; it may send it")
|
|
349
|
+
if bundle in MESSAGE_APPS or pixels and not bundle:
|
|
350
|
+
# reaction: an emoji button, or a Tapback (in a menu; on pixels,
|
|
351
|
+
# any Tapback word in Messages or an unknown app).
|
|
352
|
+
if _emoji_only(label) or (folded in TAPBACKS and (container is not None and bundle == "com.apple.MobileSMS"
|
|
353
|
+
or pixels and bundle in ("", "com.apple.MobileSMS"))):
|
|
354
|
+
return Reading("react", "reaction", "Sends a reaction")
|
|
355
|
+
# reply_chip: a sentence-like button just above the composer.
|
|
356
|
+
composer = _composer(elements)
|
|
357
|
+
if (composer is not None and element is not None and (role == "Button" or pixels) and len(words_of(label)) >= 2
|
|
358
|
+
and not COMPOSER_TOOLS.search(folded) and not SAFE.match(folded)
|
|
359
|
+
and 0 <= composer.rect[1] - (element.rect[1] + element.rect[3]) <= .15):
|
|
360
|
+
return Reading("send", "reply_chip", "A quick reply sends on tap")
|
|
361
|
+
elif _emoji_only(label) and _composer(elements) is not None:
|
|
362
|
+
return Reading("react", "reaction", "Sends a reaction")
|
|
363
|
+
if bundle == SHORTCUTS and (role in ("Button", "Cell") or pixels) and folded and not SHORTCUTS_NAV.match(folded) \
|
|
364
|
+
and not re.search(r",\s*\d+(?: shortcuts?)?$", label) and element is not None and element.rect[3] >= .06:
|
|
365
|
+
return Reading("run", "shortcut", "Runs this shortcut")
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# What a glyph that commits does (perception.COMMIT_GLYPHS, kept here so this check stands alone).
|
|
370
|
+
GLYPH_ACTS = {"paper plane": "send", "arrow up": "send", "heart": "react", "thumbs up": "react", "trash": "delete",
|
|
371
|
+
"person plus": "follow", "repost": "post"}
|
|
372
|
+
BAR_TOP, BAR_BOTTOM = .13, .9
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def glyph_act(glyph, rect, elements, bundle=""):
|
|
376
|
+
"""The act an icon's glyph performs where it is drawn, or None."""
|
|
377
|
+
act = GLYPH_ACTS.get(glyph or "")
|
|
378
|
+
if act is None:
|
|
379
|
+
return None
|
|
380
|
+
if not rect:
|
|
381
|
+
return act
|
|
382
|
+
cy = rect[1] + rect[3] / 2
|
|
383
|
+
bar = cy < BAR_TOP or cy > BAR_BOTTOM
|
|
384
|
+
# A message box on the icon's line: a field, a composer's words, or on pixels any words left of it on that line.
|
|
385
|
+
composer = any(abs(e.rect[1] + e.rect[3] / 2 - cy) <= .04 and e.rect[0] + e.rect[2] <= rect[0] + .01
|
|
386
|
+
and (getattr(e, "role", None) in ("TextField", "TextView") or COMPOSER.search(e.label or "")
|
|
387
|
+
or getattr(e, "role", None) == "StaticText" and len((e.label or "").strip()) >= 2)
|
|
388
|
+
for e in elements)
|
|
389
|
+
if glyph == "paper plane":
|
|
390
|
+
return act if composer or not bar else None
|
|
391
|
+
if glyph == "arrow up":
|
|
392
|
+
return act if composer or bundle in MESSAGE_APPS else None
|
|
393
|
+
if glyph in ("heart", "thumbs up"):
|
|
394
|
+
return None if bar else act
|
|
395
|
+
return act
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
KEYPAD = re.compile(r"^(?:clear|all clear|ac|c|delete|backspace|⌫)$", re.I)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def keypad_key(label, role, elements, pixels=False):
|
|
402
|
+
"""A keypad's Clear or Delete key (Calculator, a dial pad): six or more digit keys on the screen."""
|
|
403
|
+
keys = ("Button", "Key") + (("StaticText",) if pixels else ())
|
|
404
|
+
if role not in keys or not KEYPAD.match(norm(label)):
|
|
405
|
+
return False
|
|
406
|
+
digits = {e.label.strip() for e in elements if getattr(e, "role", None) in keys
|
|
407
|
+
and re.fullmatch(r"\d", (e.label or "").strip())}
|
|
408
|
+
return len(digits) >= 6
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def query_field(element, elements, bundle="", pixels=False):
|
|
412
|
+
"""Whether ``element`` is a search field or a browser's address bar, told with certainty."""
|
|
413
|
+
if element is None or pixels or getattr(element, "role", None) not in ("SearchField", "TextField"):
|
|
414
|
+
return False
|
|
415
|
+
if _send_on_line(element, elements):
|
|
416
|
+
return False
|
|
417
|
+
words = f"{element.label or ''} {getattr(element, 'placeholder', '') or ''}"
|
|
418
|
+
if element.role == "SearchField" or SEARCH_WORDS.search(words):
|
|
419
|
+
return True
|
|
420
|
+
return (bundle in BROWSERS and element.role == "TextField" and bool(ADDRESS_LABEL.match(norm(element.label or "")))
|
|
421
|
+
and "WebView" not in (getattr(element, "locator", "") or ""))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def query_return(action, elements=()):
|
|
425
|
+
"""Return (SUBMIT, TYPE_SUBMIT) in a search field or an address bar: it runs the search or loads the page."""
|
|
426
|
+
elements = tuple(elements or getattr(action, "around", ()) or ())
|
|
427
|
+
return query_field(_element(action, elements), elements, action.app or "", bool(getattr(action, "pixels", False)))
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def clears_query_field(label, role, element, elements, bundle="", pixels=False):
|
|
431
|
+
"""A bare Clear button at or just past the end of a search field or address bar: it empties the query."""
|
|
432
|
+
if pixels or role != "Button" or element is None or not CLEAR_BUTTON.match(norm(label)):
|
|
433
|
+
return False
|
|
434
|
+
y = element.rect[1] + element.rect[3] / 2
|
|
435
|
+
line = [e for e in elements if e is not element and getattr(e, "role", None) in FIELD_ROLES
|
|
436
|
+
and abs(e.rect[1] + e.rect[3] / 2 - y) <= .05]
|
|
437
|
+
if not line or not all(query_field(e, elements, bundle) for e in line):
|
|
438
|
+
return False
|
|
439
|
+
return any(e.rect[0] <= element.rect[0] <= e.rect[0] + e.rect[2] + CLEAR_GAP for e in line)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# A request that asks to change something (not only to look).
|
|
443
|
+
CHANGE_ASKED = re.compile(r"\b(?:turn|switch|set|change|enable|disable|toggle|allow|block|mute|unmute|activate|"
|
|
444
|
+
r"deactivate|start|stop|pause|resume|update|reset|clear|lock|unlock|hide)\b",
|
|
445
|
+
re.I)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _beside_send(element, elements):
|
|
449
|
+
"""A field with a Send or Post control on its line: Return there sends what it holds."""
|
|
450
|
+
return bool(getattr(element, "editable", False)) and _send_on_line(element, elements)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _send_on_line(element, elements):
|
|
454
|
+
"""Whether a Send or Post control sits on ``element``'s line (whatever the element's role)."""
|
|
455
|
+
y = element.rect[1] + element.rect[3] / 2
|
|
456
|
+
for other in elements:
|
|
457
|
+
if other is element or getattr(other, "editable", False):
|
|
458
|
+
continue
|
|
459
|
+
if label_act(other.label, other.role) in ("send", "post") and abs(other.rect[1] + other.rect[3] / 2 - y) <= .12:
|
|
460
|
+
return True
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
_STOP = frozenset({"the", "and", "for", "with", "my", "your", "this", "that", "turn", "on", "off", "set", "allow",
|
|
465
|
+
"show", "open", "settings", "setting", "switch", "toggle", "enable", "disable", "please"})
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def names_switch(goal, label):
|
|
469
|
+
"""Whether a request asks to change the switch ``label``."""
|
|
470
|
+
if not goal or not CHANGE_ASKED.search(re.sub(r"[^.!?]*\b(?:do not|don't|never|without)\b[^.!?]*", " ", goal)):
|
|
471
|
+
return False
|
|
472
|
+
stems = lambda text: {w[:5] for w in re.findall(r"[^\W\d_]{2,}", norm(text)) if w not in _STOP} # noqa: E731
|
|
473
|
+
return bool(stems(goal) & stems(label))
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _return(action, elements):
|
|
477
|
+
"""Return in a message composer sends (the ``return`` signal)."""
|
|
478
|
+
role = action.role
|
|
479
|
+
words = f"{action.label or ''} {action.hint or ''}"
|
|
480
|
+
if role == "SearchField" or SEARCH_WORDS.search(words):
|
|
481
|
+
return None
|
|
482
|
+
element = _element(action, elements)
|
|
483
|
+
if re.match(r"^\s*(?:to|cc|bcc|recipients?)\b", action.label or "", re.I):
|
|
484
|
+
return None
|
|
485
|
+
if element is not None and elements and _beside_send(element, elements):
|
|
486
|
+
return Reading("send", "return", "Return sends this message")
|
|
487
|
+
if COMPOSER.search(words) and not NOT_MESSAGE_FIELD.search(words):
|
|
488
|
+
return Reading("send", "return", "Return sends this message")
|
|
489
|
+
if (action.app or "") in MESSAGE_APPS - MAIL_APPS and not NOT_MESSAGE_FIELD.search(words):
|
|
490
|
+
return Reading("send", "return", "Return sends this message")
|
|
491
|
+
rect = tuple(getattr(action, "rect", ()) or ())
|
|
492
|
+
if (getattr(action, "pixels", False) and not action.app and not NOT_MESSAGE_FIELD.search(words)
|
|
493
|
+
and (not rect or rect[1] + rect[3] / 2 > .45)):
|
|
494
|
+
# Pixels in an app Mobster can't name: Return low on the screen, where a message box sits, may send.
|
|
495
|
+
return Reading("send", "return", "Return may send this")
|
|
496
|
+
return None
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _element(action, elements):
|
|
500
|
+
"""The screen element ``action`` names: the same frame, else the same label and role."""
|
|
501
|
+
rect = getattr(action, "rect", None)
|
|
502
|
+
if rect:
|
|
503
|
+
for e in elements:
|
|
504
|
+
if tuple(getattr(e, "rect", ()) or ()) == tuple(rect):
|
|
505
|
+
return e
|
|
506
|
+
for e in elements:
|
|
507
|
+
if (e.label or "") == (action.label or "") and getattr(e, "role", None) == action.role:
|
|
508
|
+
return e
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
REASONS = {"request": "Sends a request", "unsend": "Takes back a message others may see",
|
|
513
|
+
"erase": "Erases data for good",
|
|
514
|
+
"reset": "Resets settings or data", "delete": "Deletes something", "clear": "Clears data",
|
|
515
|
+
"remove": "Removes something", "sign out": "Signs you out", "unmatch": "Ends a match for good",
|
|
516
|
+
"leave": "Leaves a group others see", "end call": "Ends a call", "block": "Changes who can reach you",
|
|
517
|
+
"report": "Sends a report about someone", "junk": "Marks mail as junk", "archive": "Archives this",
|
|
518
|
+
"unfollow": "Others see this unfollow", "pay": "Spends money", "buy": "Spends money",
|
|
519
|
+
"subscribe": "Starts a charge", "book": "Makes a booking", "send": "Sends this",
|
|
520
|
+
"post": "Others see this post", "call": "Places a call", "follow": "Others see this follow",
|
|
521
|
+
"react": "Others see this reaction", "run": "Runs this shortcut", "turn off": "Turns off a protection"}
|
|
522
|
+
|
|
523
|
+
# A swipe's card names both acts it may do: which one a full swipe does is the app's setting.
|
|
524
|
+
REASON_TITLES = {SWIPE_ACTS: "Archive or delete this?", SWIPE_MAY: "Archive or delete this?"}
|
|
525
|
+
|
|
526
|
+
# The card's title for each act this module names.
|
|
527
|
+
TITLES = {"request": "Send this request?", "unsend": "Unsend this message?", "erase": "Erase this?",
|
|
528
|
+
"reset": "Reset this?", "delete": "Delete this?",
|
|
529
|
+
"clear": "Clear this?", "remove": "Remove this?", "sign out": "Sign out?", "unmatch": "Unmatch?",
|
|
530
|
+
"leave": "Leave this group?", "end call": "End this call?", "block": "Block this?",
|
|
531
|
+
"report": "Report this?", "junk": "Mark this as junk?", "archive": "Archive this?",
|
|
532
|
+
"unfollow": "Unfollow this account?", "pay": "Make this payment?", "buy": "Buy this?",
|
|
533
|
+
"subscribe": "Subscribe?", "book": "Make this booking?", "send": "Send this message?",
|
|
534
|
+
"post": "Post this?", "call": "Place this call?", "follow": "Follow this account?",
|
|
535
|
+
"react": "Send this reaction?", "run": "Run this shortcut?", "turn off": "Turn this off?"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Allow rules kept on this Mac; never for paying, buying, booking, deleting, a one-time code or an acting link."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .rules import norm
|
|
11
|
+
|
|
12
|
+
LIMIT = 500
|
|
13
|
+
SEP = "␟"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def key_of(app, label, op):
|
|
17
|
+
return SEP.join((app or "", norm(label), (op or "").upper()))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def remember_in(store, spec):
|
|
21
|
+
"""Keep in ``store`` what a card's "Don't ask again" or "Always allow" offered (``spec``)."""
|
|
22
|
+
apps = spec.get("apps")
|
|
23
|
+
entries = [one for one in apps if isinstance(one, dict)] if isinstance(apps, list) else [spec]
|
|
24
|
+
for entry in entries:
|
|
25
|
+
store.add(entry.get("app"), spec.get("label"), spec.get("op"), entry.get("app_name") or "")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Exceptions:
|
|
29
|
+
def __init__(self, path):
|
|
30
|
+
self.path = Path(path).expanduser()
|
|
31
|
+
self._lock = threading.Lock()
|
|
32
|
+
self._items = None
|
|
33
|
+
self._stamp = None
|
|
34
|
+
|
|
35
|
+
def _mtime(self):
|
|
36
|
+
try:
|
|
37
|
+
stat = self.path.stat()
|
|
38
|
+
return stat.st_mtime_ns, stat.st_size
|
|
39
|
+
except OSError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def _load(self):
|
|
43
|
+
stamp = self._mtime()
|
|
44
|
+
if self._items is None or stamp != self._stamp:
|
|
45
|
+
try:
|
|
46
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
47
|
+
self._items = {item["key"]: item for item in data if isinstance(item, dict) and item.get("key")}
|
|
48
|
+
except (OSError, ValueError, TypeError):
|
|
49
|
+
self._items = {}
|
|
50
|
+
self._stamp = stamp
|
|
51
|
+
return self._items
|
|
52
|
+
|
|
53
|
+
def _save(self, items):
|
|
54
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
handle, temporary = tempfile.mkstemp(dir=self.path.parent, prefix=".exceptions-")
|
|
56
|
+
try:
|
|
57
|
+
with os.fdopen(handle, "w", encoding="utf-8") as stream:
|
|
58
|
+
json.dump(list(items.values()), stream, ensure_ascii=False)
|
|
59
|
+
os.chmod(temporary, 0o600)
|
|
60
|
+
os.replace(temporary, self.path)
|
|
61
|
+
except BaseException:
|
|
62
|
+
Path(temporary).unlink(missing_ok=True)
|
|
63
|
+
raise
|
|
64
|
+
self._items, self._stamp = items, self._mtime()
|
|
65
|
+
|
|
66
|
+
def contains(self, app, label, op):
|
|
67
|
+
with self._lock:
|
|
68
|
+
return key_of(app, label, op) in self._load()
|
|
69
|
+
|
|
70
|
+
def add(self, app, label, op, app_name=""):
|
|
71
|
+
if not app or not norm(label):
|
|
72
|
+
raise ValueError("An exception names an app and a control")
|
|
73
|
+
with self._lock:
|
|
74
|
+
items = dict(self._load())
|
|
75
|
+
key = key_of(app, label, op)
|
|
76
|
+
items[key] = {"key": key, "app": app, "appName": " ".join(str(app_name or "").split())[:80],
|
|
77
|
+
"label": " ".join(str(label).split())[:120], "op": (op or "").upper(),
|
|
78
|
+
"addedAt": time.time() * 1000}
|
|
79
|
+
while len(items) > LIMIT:
|
|
80
|
+
items.pop(min(items, key=lambda k: items[k]["addedAt"]))
|
|
81
|
+
self._save(items)
|
|
82
|
+
return dict(items[key])
|
|
83
|
+
|
|
84
|
+
def remove(self, key):
|
|
85
|
+
with self._lock:
|
|
86
|
+
items = dict(self._load())
|
|
87
|
+
if items.pop(key, None) is None:
|
|
88
|
+
return False
|
|
89
|
+
self._save(items)
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def public(self):
|
|
93
|
+
with self._lock:
|
|
94
|
+
return sorted((dict(item) for item in self._load().values()), key=lambda item: -item["addedAt"])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def default_path():
|
|
98
|
+
from ..paths import user_data_dir
|
|
99
|
+
return user_data_dir() / "state" / "permission-exceptions.json"
|