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,246 @@
|
|
|
1
|
+
// transfer: hands files from this Mac to an iPhone without a cable, for mobile_agent/phone_io/handoff.py.
|
|
2
|
+
//
|
|
3
|
+
// transfer airdrop --to NAME [--timeout S] FILE...
|
|
4
|
+
// AirDrop's own panel (NSSharingService), and a press on the one recipient whose name is exactly NAME. Another
|
|
5
|
+
// name, two devices with that name, or none in time: Cancel is pressed and nothing is sent.
|
|
6
|
+
// {"ok":true,"sent":N,"seconds":S} | {"ok":false,"code":"not_found"|"ambiguous","seen":[names]}
|
|
7
|
+
// transfer drop --x FX --y FY FILE...
|
|
8
|
+
// Drags the files from a 32-point spot of this helper's onto the iPhone Mirroring window, at a point given as
|
|
9
|
+
// fractions of the window, as a person drags from Finder. iPhone Mirroring hands them to the app in front on
|
|
10
|
+
// the phone. {"ok":true,"operation":N} once the window took them.
|
|
11
|
+
//
|
|
12
|
+
// One JSON line on standard output. Errors: {"ok":false,"code","error"}: "usage", "permission" (Accessibility is
|
|
13
|
+
// missing), "no_window" (iPhone Mirroring isn't showing the phone), "refused" (the window didn't take the drop),
|
|
14
|
+
// "failed" (AirDrop said it failed or the person on the phone declined), "timeout". Nothing is retried here.
|
|
15
|
+
//
|
|
16
|
+
// The Mac app carries it as Contents/Resources/transfer; a source checkout builds it with
|
|
17
|
+
// native_helpers.build("transfer").
|
|
18
|
+
|
|
19
|
+
import AppKit
|
|
20
|
+
import ApplicationServices
|
|
21
|
+
import CoreGraphics
|
|
22
|
+
import Foundation
|
|
23
|
+
|
|
24
|
+
let mirroringBundle = "com.apple.ScreenContinuity"
|
|
25
|
+
|
|
26
|
+
func emit(_ value: [String: Any]) -> Never {
|
|
27
|
+
let data = (try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])) ?? Data("{}".utf8)
|
|
28
|
+
FileHandle.standardOutput.write(data)
|
|
29
|
+
FileHandle.standardOutput.write(Data("\n".utf8))
|
|
30
|
+
exit(value["ok"] as? Bool == true ? 0 : 1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func fail(_ code: String, _ message: String, _ extra: [String: Any] = [:]) -> Never {
|
|
34
|
+
var out: [String: Any] = ["ok": false, "code": code, "error": message]
|
|
35
|
+
for (key, value) in extra { out[key] = value }
|
|
36
|
+
emit(out)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// MARK: - arguments
|
|
40
|
+
|
|
41
|
+
var arguments = Array(CommandLine.arguments.dropFirst())
|
|
42
|
+
guard let op = arguments.first, ["airdrop", "drop"].contains(op) else {
|
|
43
|
+
fail("usage", "transfer airdrop --to NAME FILE... | transfer drop --x FX --y FY FILE...")
|
|
44
|
+
}
|
|
45
|
+
arguments.removeFirst()
|
|
46
|
+
var options: [String: String] = [:]
|
|
47
|
+
var paths: [String] = []
|
|
48
|
+
var index = 0
|
|
49
|
+
while index < arguments.count {
|
|
50
|
+
let word = arguments[index]
|
|
51
|
+
if word.hasPrefix("--"), index + 1 < arguments.count {
|
|
52
|
+
options[String(word.dropFirst(2))] = arguments[index + 1]
|
|
53
|
+
index += 2
|
|
54
|
+
} else {
|
|
55
|
+
paths.append(word)
|
|
56
|
+
index += 1
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let files = paths.map { URL(fileURLWithPath: $0) }
|
|
60
|
+
if files.isEmpty || files.contains(where: { !FileManager.default.isReadableFile(atPath: $0.path) }) {
|
|
61
|
+
fail("usage", "Name files that exist and can be read")
|
|
62
|
+
}
|
|
63
|
+
if !AXIsProcessTrusted() {
|
|
64
|
+
fail("permission", "Mobster needs Accessibility to do this. Turn it on in System Settings › Privacy & Security › Accessibility.")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let app = NSApplication.shared
|
|
68
|
+
app.setActivationPolicy(.accessory)
|
|
69
|
+
|
|
70
|
+
// MARK: - AirDrop
|
|
71
|
+
|
|
72
|
+
func attribute(_ element: AXUIElement, _ name: String) -> AnyObject? {
|
|
73
|
+
var value: AnyObject?
|
|
74
|
+
return AXUIElementCopyAttributeValue(element, name as CFString, &value) == .success ? value : nil
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// The AirDrop panel's recipients ("dd.nodeBody" buttons, named by their description) and its Cancel button.
|
|
78
|
+
func panel() -> (recipients: [(name: String, element: AXUIElement)], cancel: AXUIElement?) {
|
|
79
|
+
var recipients: [(String, AXUIElement)] = []
|
|
80
|
+
var cancel: AXUIElement?
|
|
81
|
+
func walk(_ node: AXUIElement, _ depth: Int) {
|
|
82
|
+
if depth > 16 { return }
|
|
83
|
+
let role = attribute(node, kAXRoleAttribute) as? String ?? ""
|
|
84
|
+
let name = attribute(node, kAXDescriptionAttribute) as? String ?? ""
|
|
85
|
+
let identifier = attribute(node, kAXIdentifierAttribute) as? String ?? ""
|
|
86
|
+
if role == kAXButtonRole {
|
|
87
|
+
if identifier == "dd.nodeBody", !name.isEmpty { recipients.append((name, node)) }
|
|
88
|
+
if identifier.isEmpty, name == "Cancel" { cancel = node }
|
|
89
|
+
}
|
|
90
|
+
for child in (attribute(node, kAXChildrenAttribute) as? [AXUIElement]) ?? [] { walk(child, depth + 1) }
|
|
91
|
+
}
|
|
92
|
+
walk(AXUIElementCreateApplication(getpid()), 0)
|
|
93
|
+
return (recipients, cancel)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
final class Sharing: NSObject, NSSharingServiceDelegate {
|
|
97
|
+
let started = Date()
|
|
98
|
+
func sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) {
|
|
99
|
+
emit(["ok": true, "sent": items.count, "seconds": (Date().timeIntervalSince(started) * 10).rounded() / 10])
|
|
100
|
+
}
|
|
101
|
+
func sharingService(_ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error) {
|
|
102
|
+
let cancelled = (error as NSError).code == NSUserCancelledError
|
|
103
|
+
fail(cancelled ? "cancelled" : "failed", cancelled ? "The AirDrop was cancelled" : error.localizedDescription)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func airdrop() {
|
|
108
|
+
guard let recipient = options["to"], !recipient.isEmpty else { fail("usage", "Name the iPhone with --to") }
|
|
109
|
+
let waitForRecipient = Double(options["timeout"] ?? "30") ?? 30
|
|
110
|
+
let waitForTransfer = Double(options["transfer-timeout"] ?? "900") ?? 900
|
|
111
|
+
guard let service = NSSharingService(named: .sendViaAirDrop), service.canPerform(withItems: files) else {
|
|
112
|
+
fail("failed", "AirDrop isn't available on this Mac. Turn on Wi-Fi and Bluetooth.")
|
|
113
|
+
}
|
|
114
|
+
let delegate = Sharing()
|
|
115
|
+
service.delegate = delegate
|
|
116
|
+
service.perform(withItems: files)
|
|
117
|
+
var pressed = false
|
|
118
|
+
var seen: Set<String> = []
|
|
119
|
+
let opened = Date()
|
|
120
|
+
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
|
121
|
+
let (recipients, cancel) = panel()
|
|
122
|
+
seen.formUnion(recipients.map(\.name))
|
|
123
|
+
let matching = recipients.filter { $0.name == recipient }
|
|
124
|
+
if !pressed && matching.count > 1 {
|
|
125
|
+
if let cancel { AXUIElementPerformAction(cancel, kAXPressAction as CFString) }
|
|
126
|
+
fail("ambiguous", "More than one device nearby is called \(recipient)", ["seen": Array(seen).sorted()])
|
|
127
|
+
}
|
|
128
|
+
if !pressed, let only = matching.first {
|
|
129
|
+
// Wait one more beat: a second device with the same name can show up a moment later.
|
|
130
|
+
if Date().timeIntervalSince(opened) < 2.5 { return }
|
|
131
|
+
pressed = AXUIElementPerformAction(only.element, kAXPressAction as CFString) == .success
|
|
132
|
+
if !pressed { fail("failed", "AirDrop didn't take the press on \(recipient)") }
|
|
133
|
+
timer.invalidate()
|
|
134
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + waitForTransfer) {
|
|
135
|
+
fail("timeout", "AirDrop didn't finish within \(Int(waitForTransfer)) seconds")
|
|
136
|
+
}
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if Date().timeIntervalSince(opened) > waitForRecipient {
|
|
140
|
+
if let cancel { AXUIElementPerformAction(cancel, kAXPressAction as CFString) }
|
|
141
|
+
fail("not_found", "AirDrop didn't show \(recipient) within \(Int(waitForRecipient)) seconds",
|
|
142
|
+
["seen": Array(seen).sorted()])
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// MARK: - a drop on iPhone Mirroring
|
|
148
|
+
|
|
149
|
+
func post(_ type: CGEventType, _ point: CGPoint) {
|
|
150
|
+
CGEvent(mouseEventSource: nil, mouseType: type, mouseCursorPosition: point, mouseButton: .left)?.post(tap: .cghidEventTap)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
final class DragSource: NSView, NSDraggingSource {
|
|
154
|
+
var finished: ((NSDragOperation) -> Void)?
|
|
155
|
+
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
|
|
156
|
+
.copy
|
|
157
|
+
}
|
|
158
|
+
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
|
159
|
+
override func mouseDown(with event: NSEvent) {
|
|
160
|
+
let items = files.map { url -> NSDraggingItem in
|
|
161
|
+
let item = NSDraggingItem(pasteboardWriter: url as NSURL)
|
|
162
|
+
item.setDraggingFrame(bounds, contents: NSWorkspace.shared.icon(forFile: url.path))
|
|
163
|
+
return item
|
|
164
|
+
}
|
|
165
|
+
beginDraggingSession(with: items, event: event, source: self)
|
|
166
|
+
}
|
|
167
|
+
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
|
|
168
|
+
finished?(operation)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func mirroringWindow() -> (app: NSRunningApplication, frame: CGRect)? {
|
|
173
|
+
guard let running = NSRunningApplication.runningApplications(withBundleIdentifier: mirroringBundle).first else {
|
|
174
|
+
return nil
|
|
175
|
+
}
|
|
176
|
+
let windows = (CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID)
|
|
177
|
+
as? [[String: Any]]) ?? []
|
|
178
|
+
for window in windows where (window[kCGWindowOwnerPID as String] as? pid_t) == running.processIdentifier
|
|
179
|
+
&& (window[kCGWindowLayer as String] as? Int) == 0 {
|
|
180
|
+
guard let bounds = window[kCGWindowBounds as String] as? [String: Double] else { continue }
|
|
181
|
+
let frame = CGRect(x: bounds["X"] ?? 0, y: bounds["Y"] ?? 0, width: bounds["Width"] ?? 0,
|
|
182
|
+
height: bounds["Height"] ?? 0)
|
|
183
|
+
if frame.width > 150, frame.height > 300 { return (running, frame) }
|
|
184
|
+
}
|
|
185
|
+
return nil
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func drop() {
|
|
189
|
+
guard let fx = Double(options["x"] ?? ""), let fy = Double(options["y"] ?? ""), (0...1).contains(fx),
|
|
190
|
+
(0...1).contains(fy) else {
|
|
191
|
+
fail("usage", "--x and --y are fractions of the window, 0 to 1")
|
|
192
|
+
}
|
|
193
|
+
guard let (mirroring, frame) = mirroringWindow() else {
|
|
194
|
+
fail("no_window", "iPhone Mirroring isn't showing the phone")
|
|
195
|
+
}
|
|
196
|
+
let front = NSWorkspace.shared.frontmostApplication
|
|
197
|
+
mirroring.activate()
|
|
198
|
+
let target = CGPoint(x: frame.minX + frame.width * fx, y: frame.minY + frame.height * fy)
|
|
199
|
+
// The drag starts beside the window (left of it, or right when it sits at the screen's left edge).
|
|
200
|
+
let screen = NSScreen.screens.first?.frame ?? CGRect(x: 0, y: 0, width: 1440, height: 900)
|
|
201
|
+
let startX = frame.minX > 80 ? frame.minX - 48 : min(frame.maxX + 48, screen.width - 24)
|
|
202
|
+
let start = CGPoint(x: startX, y: target.y)
|
|
203
|
+
let window = NSWindow(contentRect: NSRect(x: start.x - 16, y: screen.height - start.y - 16, width: 32, height: 32),
|
|
204
|
+
styleMask: .borderless, backing: .buffered, defer: false)
|
|
205
|
+
window.level = .screenSaver
|
|
206
|
+
window.isOpaque = false
|
|
207
|
+
window.backgroundColor = NSColor.white.withAlphaComponent(0.02)
|
|
208
|
+
let source = DragSource(frame: NSRect(x: 0, y: 0, width: 32, height: 32))
|
|
209
|
+
window.contentView = source
|
|
210
|
+
window.orderFrontRegardless()
|
|
211
|
+
let saved = CGEvent(source: nil)?.location
|
|
212
|
+
func finish(_ value: [String: Any]) -> Never {
|
|
213
|
+
if let saved { CGWarpMouseCursorPosition(saved) }
|
|
214
|
+
if let front, front.bundleIdentifier != mirroringBundle { front.activate() }
|
|
215
|
+
emit(value)
|
|
216
|
+
}
|
|
217
|
+
source.finished = { operation in
|
|
218
|
+
if operation.isEmpty {
|
|
219
|
+
finish(["ok": false, "code": "refused", "error": "iPhone Mirroring didn't take the files"])
|
|
220
|
+
}
|
|
221
|
+
finish(["ok": true, "operation": Int(operation.rawValue)])
|
|
222
|
+
}
|
|
223
|
+
DispatchQueue.global().asyncAfter(deadline: .now() + 0.4) {
|
|
224
|
+
post(.mouseMoved, start)
|
|
225
|
+
usleep(60_000)
|
|
226
|
+
post(.leftMouseDown, start)
|
|
227
|
+
usleep(120_000)
|
|
228
|
+
for step in 1...30 {
|
|
229
|
+
let t = Double(step) / 30
|
|
230
|
+
post(.leftMouseDragged, CGPoint(x: start.x + (target.x - start.x) * t, y: start.y + (target.y - start.y) * t))
|
|
231
|
+
usleep(20_000)
|
|
232
|
+
}
|
|
233
|
+
// Held over the window a moment so it can say it takes them, then let go.
|
|
234
|
+
for _ in 0..<12 {
|
|
235
|
+
post(.leftMouseDragged, target)
|
|
236
|
+
usleep(50_000)
|
|
237
|
+
}
|
|
238
|
+
post(.leftMouseUp, target)
|
|
239
|
+
}
|
|
240
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
|
|
241
|
+
finish(["ok": false, "code": "timeout", "error": "The drag didn't finish"])
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
DispatchQueue.main.async { op == "airdrop" ? airdrop() : drop() }
|
|
246
|
+
app.run()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Vision
|
|
3
|
+
|
|
4
|
+
// Reads one image on stdin. Output rects use normalized, top-left coordinates.
|
|
5
|
+
do {
|
|
6
|
+
let data = FileHandle.standardInput.readDataToEndOfFile()
|
|
7
|
+
let request = VNRecognizeTextRequest()
|
|
8
|
+
request.recognitionLevel = .fast
|
|
9
|
+
request.usesLanguageCorrection = false
|
|
10
|
+
let handler = VNImageRequestHandler(data: data, options: [:])
|
|
11
|
+
try handler.perform([request])
|
|
12
|
+
let rows: [[String: Any]] = (request.results ?? []).compactMap { observation in
|
|
13
|
+
guard let candidate = observation.topCandidates(1).first else { return nil }
|
|
14
|
+
let box = observation.boundingBox
|
|
15
|
+
return ["text": candidate.string, "confidence": candidate.confidence,
|
|
16
|
+
"rect": [box.minX, 1 - box.maxY, box.width, box.height]]
|
|
17
|
+
}
|
|
18
|
+
FileHandle.standardOutput.write(try JSONSerialization.data(withJSONObject: rows))
|
|
19
|
+
} catch {
|
|
20
|
+
FileHandle.standardError.write(Data("OCR failed\n".utf8))
|
|
21
|
+
exit(1)
|
|
22
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Typed user-policy questions; inferred output never grants action authority."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RiskTier(StrEnum):
|
|
9
|
+
"""Named dispatch risk for confidence-gated actions."""
|
|
10
|
+
SCROLL = "scroll"
|
|
11
|
+
NAVIGATION = "navigation"
|
|
12
|
+
TYPE = "type"
|
|
13
|
+
SIDE_EFFECT = "side_effect"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# AUTHORITY_RULES spirit: send/post/buy/follow/like are never implicit.
|
|
17
|
+
SIDE_EFFECT_REQUEST = re.compile(
|
|
18
|
+
r"\b(send|post|like|follow|buy|purchase|order|pay|submit|publish|comment|share|"
|
|
19
|
+
r"delete|remove|confirm|checkout|transfer|message|email|install|upload|redeem|"
|
|
20
|
+
r"book|reserve|donate|tip|subscribe|set|save|create|update|apply|"
|
|
21
|
+
r"sign[ -]?up|log[ -]?in)\b",
|
|
22
|
+
re.I)
|
|
23
|
+
|
|
24
|
+
SIDE_EFFECT_CONTROL = re.compile(
|
|
25
|
+
r"\b(send|post|like|follow|buy|purchase|order|pay|submit|save|publish|comment|"
|
|
26
|
+
r"share|delete|remove|confirm|checkout|done|transfer|donate|tip|subscribe|"
|
|
27
|
+
r"accept|approve|install|upload|redeem|book|reserve|create|sign[ -]?up|log[ -]?in)\b",
|
|
28
|
+
re.I)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Roles whose tap only moves between screens.
|
|
32
|
+
NAVIGATION_TAP_ROLES = frozenset({"Button", "Cell", "Link", "StaticText", "Image", "Icon", "Other"})
|
|
33
|
+
# Verbs that make even a plain button state-changing; any match keeps the verifier.
|
|
34
|
+
STATE_CHANGING_LABEL = re.compile(
|
|
35
|
+
r"\b(erase|reset|turn\s+(on|off)|enable|disable|sign\s*(in|out|up)|log\s*(in|out)|restart|"
|
|
36
|
+
r"shut\s*down|power|clear|delete|remove|allow|deny|block|unblock|forget|disconnect|connect|"
|
|
37
|
+
r"pair|unpair|update|install|download|upgrade|agree|accept|decline|join|leave|end|stop|start|"
|
|
38
|
+
r"call|dial|emergency|sos|pay|buy|purchase|send|share|trust|approve|confirm|submit|save|"
|
|
39
|
+
r"done|add|create|new|edit|change|set|reply|post|follow|like|subscribe|unsubscribe|report|"
|
|
40
|
+
r"archive|mute|unmute|hide|lock|unlock|restore|backup|sync|transfer|verify)\b",
|
|
41
|
+
re.I)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Keys of an on-screen keypad (Calculator, a PIN or dial pad): each press is input, not an effect to de-duplicate.
|
|
45
|
+
KEYPAD_KEY = re.compile(r"^(\d|[+\-×x*÷/=.%,]|plus|minus|add|subtract|multiply|times|divide|equals|decimal|point|"
|
|
46
|
+
r"percent|clear|all clear|delete|backspace|negate|change sign|"
|
|
47
|
+
r"zero|one|two|three|four|five|six|seven|eight|nine)$", re.I)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_keypad_key(operation, role, label):
|
|
51
|
+
return operation == "TAP" and role in ("Key", "Button") and bool(KEYPAD_KEY.match((label or "").strip()))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# SpringBoard's passcode keypad: lockscreen.py handles the lock screen, and every model tap there is refused.
|
|
55
|
+
SPRINGBOARD_BUNDLE = "com.apple.springboard"
|
|
56
|
+
_DIGIT_KEY = re.compile(r"^\s*(\d)(?!\d)")
|
|
57
|
+
_PASSCODE_MARKER = re.compile(r"\b(enter passcode|passcode|emergency|touch id|face id)\b", re.I)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def springboard_keypad(snapshot):
|
|
61
|
+
"""True when ``snapshot`` is SpringBoard showing a digit keypad. Errs toward True: it only refuses."""
|
|
62
|
+
if getattr(snapshot, "bundle_id", "") != SPRINGBOARD_BUNDLE:
|
|
63
|
+
return False
|
|
64
|
+
digits, marked = set(), False
|
|
65
|
+
for element in getattr(snapshot, "elements", ()) or ():
|
|
66
|
+
label = element.label or ""
|
|
67
|
+
found = _DIGIT_KEY.match(label)
|
|
68
|
+
if found and element.role in ("Button", "Key", "Other") and len(label) <= 12:
|
|
69
|
+
digits.add(found.group(1))
|
|
70
|
+
elif _PASSCODE_MARKER.search(label):
|
|
71
|
+
marked = True
|
|
72
|
+
return len(digits) >= 10 or len(digits) >= 6 and marked
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Controls whose tap commits something the user would want to see first: money, messages, publishing and deletion.
|
|
76
|
+
COMMIT_CONTROL_WORDS = (
|
|
77
|
+
"send", "post", "publish", "reply", "comment", "share", "tweet", "buy", "purchase", "order", "pay", "checkout",
|
|
78
|
+
"place order", "transfer", "donate", "tip", "subscribe", "book", "reserve", "delete", "erase", "remove", "reset",
|
|
79
|
+
"install", "upload", "redeem", "call", "dial", "facetime", "follow", "sign[ -]?up", "submit", "confirm")
|
|
80
|
+
COMMIT_CONTROL = re.compile(r"\b(" + "|".join(COMMIT_CONTROL_WORDS) + r")\b", re.I)
|
|
81
|
+
# Leading words of a label that can name a commit.
|
|
82
|
+
GUARD_WORDS = 4
|
|
83
|
+
# Controls that only open a sheet of choices (whose committing choice is guarded itself).
|
|
84
|
+
SHEET_OPENERS = frozenset({"share", "share…", "share..."})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
APPROVAL_OPERATIONS = frozenset({"TAP", "SUBMIT", "TYPE_SUBMIT"})
|
|
88
|
+
|
|
89
|
+
# Commits beyond COMMIT_CONTROL's words, for "ask before acting" only; a lookahead spares a control that opens.
|
|
90
|
+
APPROVAL_COMMIT = re.compile(
|
|
91
|
+
r"\b(?:request(?!\s+(?:desktop|mobile)\b)|(?:move\s+to\s+)?trash|"
|
|
92
|
+
r"clear(?!\s+(?:text|search|query|field|filters?|selection)\b)|accept|decline|join|leave|"
|
|
93
|
+
r"block|report|unsubscribe|unfollow|unmatch|archive|mark\s+as\s+(?:junk|spam)|rent|"
|
|
94
|
+
r"cancel\s+(?:ride|trip|order|subscription|booking|reservation|membership|plan|appointment|delivery)|"
|
|
95
|
+
r"end\s+(?:call|trip|ride)|hang\s+up)\b"
|
|
96
|
+
r"|^\s*get\b(?!\s+(?:started|directions|help|info|more|support|details|tickets|the\s+app)\b)",
|
|
97
|
+
re.I)
|
|
98
|
+
# The approval card's act for each of those words ("request", "trash"...).
|
|
99
|
+
APPROVAL_ACT_ALIASES = {"move to trash": "trash", "mark as junk": "junk", "mark as spam": "junk", "hang up": "end call"}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Roles whose label is what they show, not what a tap on them does.
|
|
103
|
+
CONTENT_ROLES = frozenset({"Cell", "StaticText", "TextField", "TextView", "SecureTextField", "SearchField", "Image",
|
|
104
|
+
"Icon"})
|
|
105
|
+
# A tap on a field only focuses it, whatever its label ("Order more coffee filters", "Comment").
|
|
106
|
+
EDITABLE_ROLES = frozenset({"TextField", "TextView", "SecureTextField", "SearchField"})
|
|
107
|
+
# The state iOS appends to a reminder's row ("Buy oat milk, Incomplete"): that label is always content.
|
|
108
|
+
ROW_STATE = re.compile(r",\s*(?:Incomplete|Completed)$", re.I)
|
|
109
|
+
# Acts a row names as often as a control ("Call the dentist"): in content, only in labels of CONTROL_WORDS words.
|
|
110
|
+
TALK_ACTS = frozenset({"call", "dial", "facetime", "reply", "comment", "follow"})
|
|
111
|
+
CONTROL_WORDS = 2
|
|
112
|
+
# The checkout button's own words, which COMMIT_CONTROL's "place order" does not read.
|
|
113
|
+
PLACE_ORDER = re.compile(r"^place\s+(?:(?:your|the|this|my)\s+)?order\b", re.I)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Bare labels that only open a composer or a sheet (a tap on them sends nothing).
|
|
117
|
+
TAP_OPENERS = frozenset({"share", "share…", "share...", "comment", "comments", "reply"})
|
|
118
|
+
UNSEND = re.compile(r"\b(?:unsend|undo send)\b", re.I)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def commit_label(label, role=None):
|
|
122
|
+
"""The commit a control's label names, or None; a spurious ask on a plain row is the safe failure."""
|
|
123
|
+
label = " ".join((label or "").split())
|
|
124
|
+
if ROW_STATE.search(label):
|
|
125
|
+
return None
|
|
126
|
+
folded = label.casefold()
|
|
127
|
+
if role is not None and folded in TAP_OPENERS:
|
|
128
|
+
return None # it opens a composer or a sheet, whose own Send or Post asks
|
|
129
|
+
unsend = UNSEND.search(label)
|
|
130
|
+
if unsend and role not in EDITABLE_ROLES:
|
|
131
|
+
return unsend.group(0)
|
|
132
|
+
match = COMMIT_CONTROL.search(label) or APPROVAL_COMMIT.search(label)
|
|
133
|
+
if role not in CONTENT_ROLES:
|
|
134
|
+
return match.group(0) if match else None
|
|
135
|
+
if role in EDITABLE_ROLES or "," in label:
|
|
136
|
+
return None
|
|
137
|
+
first = PLACE_ORDER.match(label) or COMMIT_CONTROL.match(label) or APPROVAL_COMMIT.match(label)
|
|
138
|
+
if first is None or _act(first.group(0)) in TALK_ACTS and len(label.split()) > CONTROL_WORDS:
|
|
139
|
+
return None
|
|
140
|
+
return first.group(0)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Roles that hold other elements rather than act on a tap: panes, bars, sheets. Text inside one is not its label.
|
|
144
|
+
CONTAINER_ROLES = frozenset({"Application", "Window", "ScrollView", "Table", "CollectionView", "WebView", "Group",
|
|
145
|
+
"NavigationBar", "TabBar", "Toolbar", "StatusBar", "Keyboard", "Alert", "Sheet",
|
|
146
|
+
"Menu", "MenuBar"})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _holds(outer, point):
|
|
150
|
+
x, y, w, h = outer.rect
|
|
151
|
+
return x - 1e-6 <= point[0] <= x + w + 1e-6 and y - 1e-6 <= point[1] <= y + h + 1e-6
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def same_words(a, b):
|
|
155
|
+
return " ".join((a or "").split()).casefold() == " ".join((b or "").split()).casefold()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def approval_subject(elements, target):
|
|
159
|
+
"""The element "ask before acting" reads for a tap on ``target``; a commit is never read away."""
|
|
160
|
+
if target is None or target.role not in CONTENT_ROLES or target.role in EDITABLE_ROLES:
|
|
161
|
+
return target
|
|
162
|
+
point = target.tap_point
|
|
163
|
+
around = [e for e in elements if e is not target and e.id != target.id and _holds(e, point)]
|
|
164
|
+
area = lambda e: e.rect[2] * e.rect[3]
|
|
165
|
+
controls = sorted((e for e in around if e.role not in CONTENT_ROLES and e.role not in CONTAINER_ROLES),
|
|
166
|
+
key=lambda e: (not same_words(e.label, target.label), area(e)))
|
|
167
|
+
if controls:
|
|
168
|
+
control = controls[0]
|
|
169
|
+
if not control.label.strip():
|
|
170
|
+
# An unlabelled button is named by the text on it.
|
|
171
|
+
return replace(control, label=target.label)
|
|
172
|
+
if commit_label(control.label, control.role) or not commit_label(target.label, target.role):
|
|
173
|
+
return control
|
|
174
|
+
return target
|
|
175
|
+
rows = sorted((e for e in around if e.role == "Cell"), key=area)
|
|
176
|
+
if rows and ("," in rows[0].label or ROW_STATE.search(" ".join(rows[0].label.split()))):
|
|
177
|
+
return rows[0]
|
|
178
|
+
return target
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def label_role(operation, role):
|
|
182
|
+
"""The role ``commit_label`` reads a target's label with: its own for a tap, none for a submit."""
|
|
183
|
+
return role if operation == "TAP" else None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def approval_kind(operation, label, *, risk_tier, uncertain=False, role=None):
|
|
187
|
+
"""What "ask before acting" asks: "commit", "unsure" or None; scrolling and typing without Return never commit."""
|
|
188
|
+
if operation in APPROVAL_OPERATIONS and (
|
|
189
|
+
commit_label(label, label_role(operation, role))
|
|
190
|
+
or operation in {"SUBMIT", "TYPE_SUBMIT"} and risk_tier == RiskTier.SIDE_EFFECT.value):
|
|
191
|
+
return "commit"
|
|
192
|
+
return "unsure" if uncertain else None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# The act a commit approval names, from the control's label or else the request, and its title.
|
|
196
|
+
APPROVAL_TITLES = {
|
|
197
|
+
"send": "Send this message?", "reply": "Send this reply?", "comment": "Post this comment?",
|
|
198
|
+
"post": "Post this?", "publish": "Publish this?", "tweet": "Post this?", "share": "Share this?",
|
|
199
|
+
"buy": "Buy this?", "purchase": "Buy this?", "order": "Place this order?", "place order": "Place this order?",
|
|
200
|
+
"checkout": "Check out?", "pay": "Make this payment?", "transfer": "Make this transfer?",
|
|
201
|
+
"donate": "Make this donation?", "tip": "Leave this tip?", "subscribe": "Subscribe?",
|
|
202
|
+
"book": "Make this booking?", "reserve": "Make this reservation?", "delete": "Delete this?",
|
|
203
|
+
"erase": "Erase this?", "remove": "Remove this?", "reset": "Reset this?", "install": "Install this?",
|
|
204
|
+
"upload": "Upload this?", "redeem": "Redeem this?", "call": "Place this call?", "dial": "Place this call?",
|
|
205
|
+
"facetime": "Start this FaceTime call?", "follow": "Follow this account?", "sign up": "Sign up?",
|
|
206
|
+
"submit": "Submit this?", "confirm": "Confirm this?",
|
|
207
|
+
"request": "Send this request?", "trash": "Move this to the trash?", "clear": "Clear these?",
|
|
208
|
+
"accept": "Accept this?", "decline": "Decline this?", "join": "Join this?", "leave": "Leave this?",
|
|
209
|
+
"block": "Block this contact?", "report": "Report this?", "unsubscribe": "Unsubscribe?",
|
|
210
|
+
"unfollow": "Unfollow this account?", "unmatch": "Unmatch?", "archive": "Archive this?",
|
|
211
|
+
"junk": "Mark this as junk?", "rent": "Rent this?", "get": "Get this app?", "end call": "End this call?",
|
|
212
|
+
"react": "Send this reaction?", "unsend": "Unsend this message?",
|
|
213
|
+
}
|
|
214
|
+
MESSAGE_ACTS = frozenset({"send", "reply", "comment", "post", "publish", "tweet", "share"})
|
|
215
|
+
# Acts whose card names who it goes to ("Send this message to Sam?").
|
|
216
|
+
RECIPIENT_TITLES = {"send": "Send this message to {}?", "reply": "Send this reply to {}?"}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _act(match):
|
|
220
|
+
act = re.sub(r"[\s-]+", " ", match.casefold()).strip()
|
|
221
|
+
if PLACE_ORDER.match(act):
|
|
222
|
+
return "place order"
|
|
223
|
+
if act.replace(" ", "") == "signup":
|
|
224
|
+
return "sign up"
|
|
225
|
+
if act in ("unsend", "undo send"):
|
|
226
|
+
return "unsend"
|
|
227
|
+
if act.startswith("cancel ") or act.startswith("end "):
|
|
228
|
+
return "end call" if act == "end call" else act
|
|
229
|
+
return APPROVAL_ACT_ALIASES.get(act, act)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def commit_act(label, request="", role=None, operation="TAP"):
|
|
233
|
+
"""The commit a control performs, as one lowercase verb ("send", "place order"), or "submit"."""
|
|
234
|
+
named = commit_label(label, label_role(operation, role))
|
|
235
|
+
if named:
|
|
236
|
+
return _act(named)
|
|
237
|
+
match = COMMIT_CONTROL.search(asked_part(request))
|
|
238
|
+
return _act(match.group(0)) if match else "submit"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def approval_title(act, *, unsure=False, recipient=None):
|
|
242
|
+
"""The approval card's title: the act as a question ("Send this message to Sam?")."""
|
|
243
|
+
if unsure:
|
|
244
|
+
return "Not sure about this step"
|
|
245
|
+
if recipient and act in RECIPIENT_TITLES:
|
|
246
|
+
return RECIPIENT_TITLES[act].format(recipient)
|
|
247
|
+
if act and act.startswith("cancel "):
|
|
248
|
+
return f"Cancel this {act.split(' ', 1)[1]}?"
|
|
249
|
+
return APPROVAL_TITLES.get(act, "Do this now?")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def action_risk_tier(operation, target_label="", goal=""):
|
|
253
|
+
"""Risk tier for one action. Side-effect parse follows the AUTHORITY_RULES spirit."""
|
|
254
|
+
if operation == "TYPE_SUBMIT":
|
|
255
|
+
# Typing plus Return: a search or address stays TYPE-tier; a send is a side effect.
|
|
256
|
+
if SIDE_EFFECT_CONTROL.search(target_label or "") or SIDE_EFFECT_REQUEST.search(goal or ""):
|
|
257
|
+
return RiskTier.SIDE_EFFECT
|
|
258
|
+
return RiskTier.TYPE
|
|
259
|
+
if operation in {"TYPE", "INCREMENT", "DECREMENT"}:
|
|
260
|
+
return RiskTier.TYPE
|
|
261
|
+
if operation in {"SWIPE_UP", "SWIPE_DOWN"}:
|
|
262
|
+
return RiskTier.SCROLL
|
|
263
|
+
# Horizontal swipes can page media or reveal row actions, so they stay navigation.
|
|
264
|
+
if operation in {"SWIPE_LEFT", "SWIPE_RIGHT", "BACK", "HOME",
|
|
265
|
+
"VOLUME_UP", "VOLUME_DOWN", "LAUNCH_APP"}:
|
|
266
|
+
return RiskTier.NAVIGATION
|
|
267
|
+
if SIDE_EFFECT_CONTROL.search(target_label or "") or SIDE_EFFECT_REQUEST.search(goal or ""):
|
|
268
|
+
return RiskTier.SIDE_EFFECT
|
|
269
|
+
return RiskTier.NAVIGATION
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# The verifier's UNCLEAR ends a run; the request's own words are evidence that the act it names is the right one.
|
|
273
|
+
PROHIBITION_SENTENCE = re.compile(r"[^.!?]*\b(?:do not|don't|never|without|only look)\b[^.!?]*(?:[.!?]|$)", re.I)
|
|
274
|
+
CHANGE_REQUEST = re.compile(
|
|
275
|
+
r"\b(add|log|create|write|compose|draft|send|reply|message|share|post|book|reserve|order|buy|purchase|"
|
|
276
|
+
r"pay|transfer|set|schedule|change|update|edit|rename|move|archive|delete|remove|mark|flag|star|save|"
|
|
277
|
+
r"favorite|like|follow|connect|invite|accept|decline|join|upload|enable|disable|turn on|turn off|apply)\b",
|
|
278
|
+
re.I)
|
|
279
|
+
_TEXT_ASKED = re.compile(r"\b(write|type|enter|message|reply|note|comment|compose|add|search|rename|label|name|"
|
|
280
|
+
r"title|caption|describe|log|fill|draft|email|text)\b", re.I)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def asked_part(request):
|
|
284
|
+
"""The request without its prohibitions ("Do not delete anything" asks for no deletion)."""
|
|
285
|
+
return PROHIBITION_SENTENCE.sub(" ", request or "")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def asks_for_changes(request):
|
|
289
|
+
return bool(CHANGE_REQUEST.search(asked_part(request)))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _inflections(word):
|
|
293
|
+
word = word.casefold()
|
|
294
|
+
stem = word[:-1] if word.endswith("e") else word
|
|
295
|
+
return {word, word + "s", word + "d", word + "ed", stem + "ing", stem + "ed", word + "ment", stem + "ion"}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# Acts that are the same request in other words.
|
|
299
|
+
ACT_SYNONYMS = (
|
|
300
|
+
{"order", "checkout", "purchase", "buy", "pay", "place"},
|
|
301
|
+
{"book", "reserve", "reservation", "confirm"},
|
|
302
|
+
{"send", "message", "post", "reply", "share"},
|
|
303
|
+
{"request", "split", "charge"},
|
|
304
|
+
{"add", "create", "new", "save", "log"},
|
|
305
|
+
{"archive", "file"},
|
|
306
|
+
{"delete", "remove", "trash"},
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def synonyms(act):
|
|
311
|
+
"""Every form of the other words for the same act (see ACT_SYNONYMS)."""
|
|
312
|
+
out = set()
|
|
313
|
+
for group in ACT_SYNONYMS:
|
|
314
|
+
if act in group or act.rstrip("s") in group:
|
|
315
|
+
for word in group:
|
|
316
|
+
out |= _inflections(word)
|
|
317
|
+
return out
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def requested_by(request, operation, role, label):
|
|
321
|
+
"""Whether this action is one the request's own words ask for (never for a look-only request)."""
|
|
322
|
+
if not asks_for_changes(request):
|
|
323
|
+
return False
|
|
324
|
+
asked = asked_part(request).casefold()
|
|
325
|
+
words = set(re.findall(r"[a-z]+", asked))
|
|
326
|
+
if operation in {"TYPE", "TYPE_SUBMIT", "SUBMIT"} and role == "SearchField":
|
|
327
|
+
return True
|
|
328
|
+
if operation in {"TYPE", "TYPE_SUBMIT"}:
|
|
329
|
+
return bool(_TEXT_ASKED.search(asked))
|
|
330
|
+
if operation not in {"TAP", "SUBMIT"} or role not in NAVIGATION_TAP_ROLES:
|
|
331
|
+
return False
|
|
332
|
+
acts = {m.group(0).casefold() for m in re.finditer(r"[A-Za-z]+", label or "")
|
|
333
|
+
if STATE_CHANGING_LABEL.fullmatch(m.group(0)) or SIDE_EFFECT_CONTROL.fullmatch(m.group(0))
|
|
334
|
+
or COMMIT_CONTROL.fullmatch(m.group(0)) or CHANGE_REQUEST.fullmatch(m.group(0))}
|
|
335
|
+
if not acts:
|
|
336
|
+
return True
|
|
337
|
+
return all((_inflections(act) | synonyms(act)) & words for act in acts)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Pro without Xcode, for a paid Apple Developer team (PRO-WITHOUT-XCODE.md, 18 Oct 2026).
|
|
2
|
+
|
|
3
|
+
Mobster signs its helper (the WebDriverAgent runner) on this Mac and installs it with open-source device tooling,
|
|
4
|
+
so a phone-station Mac needs no Xcode:
|
|
5
|
+
|
|
6
|
+
- ``writes``: the three App Store Connect writes (register an iPhone, create a development certificate, create a
|
|
7
|
+
development profile) as a card the person approves, with the ledger (``ledger``) that makes each attempt durable.
|
|
8
|
+
- ``identity``: the certificate signing request and the signing key, kept in a Keychain only Mobster opens.
|
|
9
|
+
- ``resign``: ``codesign`` of the prebuilt runner with the team's profile, and the check that it verifies.
|
|
10
|
+
- ``tooling``: install and launch the signed runner with pymobiledevice3 (go-ios as the fallback), never ``xcodebuild``.
|
|
11
|
+
- ``bundle``: Path C, a signed runner and its profile exported from one Mac and imported on another.
|
|
12
|
+
|
|
13
|
+
Off unless ``MOBSTER_EXPERIMENTAL_NO_XCODE=1``. Free Apple Accounts keep today's Xcode flow. Nothing here uses
|
|
14
|
+
Apple's private sign-in endpoints or changes how the phone looks to an app.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
FLAG = "MOBSTER_EXPERIMENTAL_NO_XCODE"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def enabled(environ=None):
|
|
24
|
+
"""Whether signing without Xcode is on (``MOBSTER_EXPERIMENTAL_NO_XCODE=1``)."""
|
|
25
|
+
environ = os.environ if environ is None else environ
|
|
26
|
+
return str(environ.get(FLAG, "")).strip().casefold() in ("1", "true", "yes", "on")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def state_dir(data_dir=None):
|
|
30
|
+
"""Where this feature keeps its files (the ledger, the certificate, the signed runners): a folder only you can
|
|
31
|
+
open, in Mobster's data folder."""
|
|
32
|
+
from .. import env_file, paths
|
|
33
|
+
|
|
34
|
+
return env_file.private_dir(Path(data_dir) if data_dir is not None else paths.user_data_dir()) / "team-signing"
|