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,321 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .. import secret_filter
|
|
9
|
+
|
|
10
|
+
DIAGNOSTIC_REPORTS = Path.home() / "Library" / "Logs" / "DiagnosticReports"
|
|
11
|
+
CRASH_BUG_TYPE = "309"
|
|
12
|
+
TOP_FRAMES = 8
|
|
13
|
+
MAX_REPORT_BYTES = 8 * 1024 * 1024
|
|
14
|
+
MAX_DSYMS = 24
|
|
15
|
+
TOOL_TIMEOUT = 15
|
|
16
|
+
MESSAGE_LIMIT = 400
|
|
17
|
+
TIME_FORMATS = ("%Y-%m-%d %H:%M:%S.%f %z", "%Y-%m-%d %H:%M:%S %z")
|
|
18
|
+
ATOS_LINE = re.compile(r"^(?P<symbol>.+?) \(in (?P<image>[^)]+)\)(?: \((?P<file>[^():]+):(?P<line>\d+)\))?\s*$")
|
|
19
|
+
SYSTEM_PREFIXES = ("/System/", "/usr/lib/", "/Library/Developer/CoreSimulator/Volumes/", "/Applications/Xcode")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Report:
|
|
24
|
+
path: Path
|
|
25
|
+
header: dict
|
|
26
|
+
body: dict
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def bundle_id(self):
|
|
30
|
+
return self.header.get("bundleID") or (self.body.get("bundleInfo") or {}).get("CFBundleIdentifier")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def process(self):
|
|
34
|
+
return self.body.get("procName") or self.header.get("name") or self.header.get("app_name")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def pid(self):
|
|
38
|
+
pid = self.body.get("pid")
|
|
39
|
+
return pid if isinstance(pid, int) and not isinstance(pid, bool) else None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def wall(self):
|
|
43
|
+
"""The crash time as seconds since the epoch: the body's captureTime, else the header's timestamp."""
|
|
44
|
+
return parse_time(self.body.get("captureTime")) or parse_time(self.header.get("timestamp"))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_time(text):
|
|
48
|
+
if not isinstance(text, str):
|
|
49
|
+
return None
|
|
50
|
+
for form in TIME_FORMATS:
|
|
51
|
+
try:
|
|
52
|
+
return datetime.strptime(text.strip(), form).timestamp()
|
|
53
|
+
except ValueError:
|
|
54
|
+
continue
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def read(path):
|
|
59
|
+
"""The crash report at ``path``, or None when it isn't one an app crash wrote (or can't be read)."""
|
|
60
|
+
path = Path(path)
|
|
61
|
+
try:
|
|
62
|
+
if path.stat().st_size > MAX_REPORT_BYTES:
|
|
63
|
+
return None
|
|
64
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
65
|
+
except OSError:
|
|
66
|
+
return None
|
|
67
|
+
first, _, rest = text.partition("\n")
|
|
68
|
+
try:
|
|
69
|
+
header, body = json.loads(first), json.loads(rest)
|
|
70
|
+
except ValueError:
|
|
71
|
+
return None
|
|
72
|
+
if not isinstance(header, dict) or not isinstance(body, dict):
|
|
73
|
+
return None
|
|
74
|
+
if str(header.get("bug_type")
|
|
75
|
+
or body.get("bug_type")) != CRASH_BUG_TYPE or not isinstance(body.get("threads"), list):
|
|
76
|
+
return None
|
|
77
|
+
return Report(path, header, body)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def candidates(folder, executable, since):
|
|
81
|
+
"""The .ips files in ``folder`` (and its Retired folder) named for ``executable`` and changed since ``since`` (epoch
|
|
82
|
+
seconds), newest first."""
|
|
83
|
+
out = []
|
|
84
|
+
for directory in (Path(folder), Path(folder) / "Retired"):
|
|
85
|
+
try:
|
|
86
|
+
entries = list(directory.iterdir())
|
|
87
|
+
except OSError:
|
|
88
|
+
continue
|
|
89
|
+
for path in entries:
|
|
90
|
+
if not path.name.startswith(f"{executable}-") or not path.name.endswith(".ips"):
|
|
91
|
+
continue
|
|
92
|
+
try:
|
|
93
|
+
changed = path.stat().st_mtime
|
|
94
|
+
except OSError:
|
|
95
|
+
continue
|
|
96
|
+
if changed >= since:
|
|
97
|
+
out.append((changed, path))
|
|
98
|
+
return [path for _, path in sorted(out, reverse=True)]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def belongs(report, *, bundle_id, executable, since, until=None, pids=(), udid=None):
|
|
102
|
+
# Two simulators running the same app never take each other's crash, nor a second crash on the same one.
|
|
103
|
+
named = report.bundle_id == bundle_id if report.bundle_id else (executable and report.process == executable)
|
|
104
|
+
if not named:
|
|
105
|
+
return False
|
|
106
|
+
wall = report.wall
|
|
107
|
+
if wall is None or wall < since or (until is not None and wall > until):
|
|
108
|
+
return False
|
|
109
|
+
if pids:
|
|
110
|
+
return report.pid is not None and report.pid in set(pids)
|
|
111
|
+
if udid:
|
|
112
|
+
where = f"{report.body.get('procPath') or ''} {report.body.get('coalitionName') or ''}"
|
|
113
|
+
return udid in where
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _thread(body):
|
|
118
|
+
threads = body.get("threads") or []
|
|
119
|
+
index = body.get("faultingThread")
|
|
120
|
+
if isinstance(index, int) and 0 <= index < len(threads) and isinstance(threads[index], dict):
|
|
121
|
+
return threads[index]
|
|
122
|
+
return next((thread for thread in threads if isinstance(thread, dict) and thread.get("triggered")), None)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def message(body):
|
|
126
|
+
asi = body.get("asi")
|
|
127
|
+
if isinstance(asi, dict):
|
|
128
|
+
for lines in asi.values():
|
|
129
|
+
for line in lines if isinstance(lines, list) else [lines]:
|
|
130
|
+
if isinstance(line, str) and line.strip():
|
|
131
|
+
return _clean(line)
|
|
132
|
+
termination = body.get("termination") or {}
|
|
133
|
+
for key in ("details", "reasons"):
|
|
134
|
+
value = termination.get(key)
|
|
135
|
+
if isinstance(value, list) and value and isinstance(value[0], str):
|
|
136
|
+
return _clean(value[0])
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _clean(text):
|
|
141
|
+
text = " ".join(str(text).split())
|
|
142
|
+
return text[:MESSAGE_LIMIT]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def frames(report, top=TOP_FRAMES, executable=None):
|
|
146
|
+
body = report.body
|
|
147
|
+
thread = _thread(body) or {}
|
|
148
|
+
images = body.get("usedImages") or []
|
|
149
|
+
out = []
|
|
150
|
+
for index, frame in enumerate((thread.get("frames") or [])[:top]):
|
|
151
|
+
if not isinstance(frame, dict):
|
|
152
|
+
continue
|
|
153
|
+
image_index = frame.get("imageIndex")
|
|
154
|
+
image = images[image_index] if isinstance(image_index, int) and 0 <= image_index < len(images) else {}
|
|
155
|
+
base, offset = image.get("base"), frame.get("imageOffset")
|
|
156
|
+
out.append({"index": index, "image": image.get("name") or "???", "own": _own_image(image, executable),
|
|
157
|
+
"symbol": frame.get("symbol"),
|
|
158
|
+
"offset": frame.get("symbolLocation"), "file": frame.get("sourceFile"),
|
|
159
|
+
"line": frame.get("sourceLine"), "imageIndex": image_index,
|
|
160
|
+
"address": base + offset if isinstance(base, int) and isinstance(offset, int) else None})
|
|
161
|
+
return out
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def summary(report, *, executable=None, dsyms=(), run=subprocess.run, top=TOP_FRAMES):
|
|
165
|
+
"""{type, signal, subtype, message, thread, frames, symbolicated}: what a person needs to see first."""
|
|
166
|
+
body = report.body
|
|
167
|
+
exception = body.get("exception") or {}
|
|
168
|
+
thread = _thread(body) or {}
|
|
169
|
+
stack = frames(report, top, executable or report.process)
|
|
170
|
+
symbolicated = symbolicate(stack, body.get("usedImages") or [], dsyms, executable=executable, run=run)
|
|
171
|
+
demangle(stack, run=run)
|
|
172
|
+
for frame in stack:
|
|
173
|
+
frame.pop("imageIndex", None)
|
|
174
|
+
frame.pop("address", None)
|
|
175
|
+
return {"type": exception.get("type"), "signal": exception.get("signal"), "subtype": exception.get("subtype"),
|
|
176
|
+
"message": message(body), "thread": thread.get("queue") or thread.get("name"),
|
|
177
|
+
"process": report.process, "pid": report.pid, "frames": stack, "symbolicated": symbolicated}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def headline(summary_, app_name):
|
|
181
|
+
"""One sentence for the verdict: what crashed, how, and where in the app's own code."""
|
|
182
|
+
what = summary_.get("message") or " ".join(filter(None, [summary_.get("type"), f"({summary_['signal']})"
|
|
183
|
+
if summary_.get("type") and summary_.get("signal")
|
|
184
|
+
else summary_.get("signal")]))
|
|
185
|
+
where = first_own_frame(summary_)
|
|
186
|
+
text = f"{app_name} crashed" + (f": {what}" if what else "")
|
|
187
|
+
if where:
|
|
188
|
+
text += f", in {where}"
|
|
189
|
+
return text.rstrip(".") + "."
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def first_own_frame(summary_):
|
|
193
|
+
"""'CrashTrigger.crashNow() (ContentView.swift:23)': the first frame in the app's own image, else None."""
|
|
194
|
+
for frame in summary_.get("frames") or ():
|
|
195
|
+
if frame.get("own") and frame.get("symbol"):
|
|
196
|
+
return frame_text(frame, image=False)
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def frame_text(frame, image=True):
|
|
201
|
+
symbol = frame.get("symbol") or (f"0x{frame['address']:x}" if frame.get("address") else "???")
|
|
202
|
+
where = f" ({frame['file']}:{frame['line']})" if frame.get("file") and frame.get("line") else ""
|
|
203
|
+
return (f"{frame.get('image')} " if image else "") + symbol + where
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def find_dsyms(app_path=None, root=None):
|
|
207
|
+
found = []
|
|
208
|
+
if app_path:
|
|
209
|
+
app = Path(app_path)
|
|
210
|
+
found.append(app.parent / f"{app.name}.dSYM")
|
|
211
|
+
if root:
|
|
212
|
+
products = Path(root) / ".mobster" / "build" / "Build" / "Products"
|
|
213
|
+
try:
|
|
214
|
+
for config in sorted(products.iterdir()):
|
|
215
|
+
found.extend(sorted(config.glob("*.dSYM")) if config.is_dir() else [])
|
|
216
|
+
except OSError:
|
|
217
|
+
pass
|
|
218
|
+
unique = []
|
|
219
|
+
for path in found:
|
|
220
|
+
if path.is_dir() and path not in unique:
|
|
221
|
+
unique.append(path)
|
|
222
|
+
return unique[:MAX_DSYMS]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _uuid(text):
|
|
226
|
+
return re.sub(r"[^0-9A-F]", "", str(text or "").upper())
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def dsym_uuids(dsym, run=subprocess.run):
|
|
230
|
+
"""{UUID (hex, upper case, no dashes): the DWARF file inside the dSYM} from ``dwarfdump --uuid``."""
|
|
231
|
+
try:
|
|
232
|
+
done = run(["/usr/bin/xcrun", "dwarfdump", "--uuid", str(dsym)], capture_output=True, text=True,
|
|
233
|
+
timeout=TOOL_TIMEOUT, stdin=subprocess.DEVNULL)
|
|
234
|
+
except (OSError, subprocess.SubprocessError):
|
|
235
|
+
return {}
|
|
236
|
+
out = {}
|
|
237
|
+
for line in (done.stdout or "").splitlines():
|
|
238
|
+
match = re.match(r"UUID: ([0-9A-Fa-f-]+) \(\S+\) (.+)$", line.strip())
|
|
239
|
+
if match:
|
|
240
|
+
out[_uuid(match.group(1))] = match.group(2)
|
|
241
|
+
return out
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _own_image(image, executable):
|
|
245
|
+
path = str(image.get("path") or "")
|
|
246
|
+
if executable and image.get("name") == executable:
|
|
247
|
+
return True
|
|
248
|
+
return ".app/" in path and not path.startswith(SYSTEM_PREFIXES)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def symbolicate(stack, images, dsyms, *, executable=None, run=subprocess.run):
|
|
252
|
+
"""Name the app's own frames with ``atos`` and a dSYM whose UUID matches their image."""
|
|
253
|
+
wanted = {}
|
|
254
|
+
for frame in stack:
|
|
255
|
+
index = frame.get("imageIndex")
|
|
256
|
+
if not isinstance(index, int) or not 0 <= index < len(images) or frame.get("address") is None:
|
|
257
|
+
continue
|
|
258
|
+
if frame.get("own") and not (frame.get("symbol") and frame.get("file")):
|
|
259
|
+
wanted.setdefault(index, []).append(frame)
|
|
260
|
+
if not wanted or not dsyms:
|
|
261
|
+
return False
|
|
262
|
+
files = {}
|
|
263
|
+
for dsym in dsyms:
|
|
264
|
+
files.update(dsym_uuids(dsym, run=run))
|
|
265
|
+
named = False
|
|
266
|
+
for index, todo in wanted.items():
|
|
267
|
+
image = images[index]
|
|
268
|
+
dwarf = files.get(_uuid(image.get("uuid")))
|
|
269
|
+
base = image.get("base")
|
|
270
|
+
if not dwarf or not isinstance(base, int):
|
|
271
|
+
continue
|
|
272
|
+
argv = ["/usr/bin/xcrun", "atos", "-arch", str(image.get("arch") or "arm64"), "-o", dwarf, "-l", hex(base)]
|
|
273
|
+
argv += [hex(frame["address"]) for frame in todo]
|
|
274
|
+
try:
|
|
275
|
+
done = run(argv, capture_output=True, text=True, timeout=TOOL_TIMEOUT, stdin=subprocess.DEVNULL)
|
|
276
|
+
except (OSError, subprocess.SubprocessError):
|
|
277
|
+
continue
|
|
278
|
+
for frame, line in zip(todo, (done.stdout or "").splitlines()):
|
|
279
|
+
match = ATOS_LINE.match(line.strip())
|
|
280
|
+
if not match:
|
|
281
|
+
continue
|
|
282
|
+
frame["symbol"] = match.group("symbol")
|
|
283
|
+
if match.group("file"):
|
|
284
|
+
frame["file"], frame["line"] = match.group("file"), int(match.group("line"))
|
|
285
|
+
frame["offset"] = None
|
|
286
|
+
named = True
|
|
287
|
+
return named
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def demangle(stack, run=subprocess.run):
|
|
291
|
+
"""Swift's mangled names ($s…) made readable with ``swift-demangle``, in place; the rest stay."""
|
|
292
|
+
mangled = [frame for frame in stack if str(frame.get("symbol") or "").lstrip("_").startswith("$s")]
|
|
293
|
+
if not mangled:
|
|
294
|
+
return
|
|
295
|
+
try:
|
|
296
|
+
done = run(["/usr/bin/xcrun", "swift-demangle", "--compact", "--simplified"],
|
|
297
|
+
input="\n".join(frame["symbol"] for frame in mangled), capture_output=True, text=True,
|
|
298
|
+
timeout=TOOL_TIMEOUT)
|
|
299
|
+
except (OSError, subprocess.SubprocessError):
|
|
300
|
+
return
|
|
301
|
+
lines = (done.stdout or "").splitlines()
|
|
302
|
+
if len(lines) == len(mangled):
|
|
303
|
+
for frame, line in zip(mangled, lines):
|
|
304
|
+
if line.strip():
|
|
305
|
+
frame["symbol"] = line.strip()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def write_copy(report, folder):
|
|
309
|
+
# Every string goes through the secret filter; numbers, such as frame offsets, are kept as they are.
|
|
310
|
+
folder = Path(folder)
|
|
311
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
312
|
+
target = folder / report.path.name
|
|
313
|
+
header = secret_filter.redact_deep(report.header)
|
|
314
|
+
body = secret_filter.redact_deep(report.body)
|
|
315
|
+
target.write_text(json.dumps(header, ensure_ascii=False) + "\n" + json.dumps(body, indent=2, ensure_ascii=False)
|
|
316
|
+
+ "\n", encoding="utf-8")
|
|
317
|
+
return target
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
__all__ = ["Report", "read", "candidates", "belongs", "summary", "headline", "frame_text", "find_dsyms",
|
|
321
|
+
"symbolicate", "write_copy", "DIAGNOSTIC_REPORTS"]
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Apple's own accessibility audit, run on the iPhone (`audit: ax-device`, experimental).
|
|
2
|
+
|
|
3
|
+
The phone's accessibility audit daemon (``com.apple.accessibility.axAuditDaemon.remoteserver``) checks contrast, hit
|
|
4
|
+
regions, element descriptions, clipped text and Dynamic Type on the screen that is up, with no model and no pixels sent
|
|
5
|
+
anywhere. Mobster asks for it through ``pymobiledevice3 developer accessibility``, a separate program you install
|
|
6
|
+
yourself (``pipx install pymobiledevice3``), over the tunnel macOS keeps to an iPhone paired for development
|
|
7
|
+
(``--native``, iOS 17 or later). Mobster never imports pymobiledevice3: it reads the JSON the program prints. A
|
|
8
|
+
simulator has no such service, so on a simulator the check reports one note and the tree audit (a11y.py) is what covers
|
|
9
|
+
the screen.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
|
|
17
|
+
from ..phone_io import tunnel
|
|
18
|
+
|
|
19
|
+
__all__ = ["NAME", "SETTING", "enabled", "findings_from_issues", "Collector"]
|
|
20
|
+
|
|
21
|
+
NAME = "ax-device"
|
|
22
|
+
SETTING = "MOBSTER_EXPERIMENTAL_AUDIT"
|
|
23
|
+
TIMEOUT = 45.0 # seconds for one command: each opens macOS's tunnel to the phone again (about 7 s, phone_io/tunnel.py)
|
|
24
|
+
LIMIT = 30 # screens per run: an audit takes seconds on the phone
|
|
25
|
+
UDID = re.compile(r"[0-9A-Fa-f-]{24,40}")
|
|
26
|
+
AUDIT_TYPE = re.compile(r"[A-Za-z0-9_]{1,64}")
|
|
27
|
+
NO_TOOL = (
|
|
28
|
+
"The on-device audit runs pymobiledevice3, which isn't on this Mac. Install it with "
|
|
29
|
+
"`pipx install pymobiledevice3`, then run again."
|
|
30
|
+
)
|
|
31
|
+
# issue_classification (pymobiledevice3 names the classification it knows) -> (rule id, guideline, what to do)
|
|
32
|
+
RULES = {
|
|
33
|
+
"testTypeContrast": (
|
|
34
|
+
"device-contrast",
|
|
35
|
+
"WCAG 1.4.3",
|
|
36
|
+
"its text contrast is too low. Darken the text or lighten the background.",
|
|
37
|
+
),
|
|
38
|
+
"testTypeHitRegion": (
|
|
39
|
+
"device-hit-region",
|
|
40
|
+
"HIG 44 pt",
|
|
41
|
+
"its tap area is too small. Make the control or its tappable area at least 44 pt.",
|
|
42
|
+
),
|
|
43
|
+
"testTypeSufficientElementDescription": (
|
|
44
|
+
"device-description",
|
|
45
|
+
"WCAG 4.1.2",
|
|
46
|
+
"VoiceOver has no good description for it. Give it an accessibility label.",
|
|
47
|
+
),
|
|
48
|
+
"testTypeElementDetection": (
|
|
49
|
+
"device-element-detection",
|
|
50
|
+
"WCAG 4.1.2",
|
|
51
|
+
"it looks like a control the app doesn't expose. Make it an accessibility element.",
|
|
52
|
+
),
|
|
53
|
+
"testTypeTextClipped": (
|
|
54
|
+
"device-text-clipped",
|
|
55
|
+
"WCAG 1.4.4",
|
|
56
|
+
"its text is clipped. Let the text wrap or the container grow.",
|
|
57
|
+
),
|
|
58
|
+
"testTypeDynamicText": (
|
|
59
|
+
"device-dynamic-text",
|
|
60
|
+
"WCAG 1.4.4",
|
|
61
|
+
"its text doesn't follow the Dynamic Type size. Use a text style or scaled font.",
|
|
62
|
+
),
|
|
63
|
+
}
|
|
64
|
+
RECT = re.compile(r"-?\d+(?:\.\d+)?")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def enabled():
|
|
68
|
+
return os.environ.get(SETTING, "").strip().lower() in ("1", "on", "true", "yes")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _rect(value):
|
|
72
|
+
numbers = [float(n) for n in RECT.findall(value)] if isinstance(value, str) else []
|
|
73
|
+
return numbers if len(numbers) == 4 else [0.0, 0.0, 0.0, 0.0]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _note(rule, message, evidence, frame):
|
|
77
|
+
return {
|
|
78
|
+
"id": rule,
|
|
79
|
+
"audit": NAME,
|
|
80
|
+
"guideline": None,
|
|
81
|
+
"level": "review",
|
|
82
|
+
"frame": frame,
|
|
83
|
+
"message": message,
|
|
84
|
+
"evidence": evidence,
|
|
85
|
+
"element": None,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def findings_from_issues(issues, frame=None):
|
|
90
|
+
"""Run findings from the audit's issues, as ``run-audit`` prints them. An issue of a kind this table doesn't know is
|
|
91
|
+
kept, as a review."""
|
|
92
|
+
out = []
|
|
93
|
+
for issue in issues:
|
|
94
|
+
kind = str(issue.get("issue_classification"))
|
|
95
|
+
rule, guideline, advice = RULES.get(kind, (f"device-{kind}", None, "Apple's audit flagged it. Check it."))
|
|
96
|
+
rect = _rect(issue.get("element_rect_value"))
|
|
97
|
+
where = f"at {rect[0]:g}, {rect[1]:g}, {rect[2]:g} x {rect[3]:g} pt"
|
|
98
|
+
detail = issue.get("ml_generated_description") or issue.get("long_description_extra_info")
|
|
99
|
+
name = f" ({detail})" if isinstance(detail, str) and detail.strip() else ""
|
|
100
|
+
out.append(
|
|
101
|
+
{
|
|
102
|
+
"id": rule,
|
|
103
|
+
"audit": NAME,
|
|
104
|
+
"guideline": guideline,
|
|
105
|
+
"level": "review",
|
|
106
|
+
"frame": frame,
|
|
107
|
+
"message": f"The element{name} {where}: {advice}",
|
|
108
|
+
"evidence": f"{kind} {where}",
|
|
109
|
+
"element": {"role": "", "label": "", "identifier": "", "path": "", "rect": rect},
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ToolMissing(RuntimeError):
|
|
116
|
+
"""pymobiledevice3 isn't on this Mac."""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Unavailable(RuntimeError):
|
|
120
|
+
"""The audit gave no answer Mobster can read. The message names the reason only, never the phone's or the app's
|
|
121
|
+
text."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def run_tool(argv, timeout):
|
|
125
|
+
"""(exit code, standard output) of one pymobiledevice3 command, run as a separate program for at most ``timeout``
|
|
126
|
+
seconds. Its standard error is dropped: a finding never carries the program's own text."""
|
|
127
|
+
from ..native_helpers import require_devices
|
|
128
|
+
|
|
129
|
+
require_devices()
|
|
130
|
+
env = dict(os.environ, NO_COLOR="1", TERM="dumb", COLUMNS="200")
|
|
131
|
+
try:
|
|
132
|
+
done = subprocess.run(argv, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
|
|
133
|
+
text=True, errors="replace", timeout=timeout, env=env)
|
|
134
|
+
except subprocess.TimeoutExpired:
|
|
135
|
+
raise Unavailable(f"pymobiledevice3 didn't finish in {timeout:g} seconds") from None
|
|
136
|
+
except OSError as error:
|
|
137
|
+
raise Unavailable(f"pymobiledevice3 couldn't start ({type(error).__name__})") from None
|
|
138
|
+
return done.returncode, done.stdout
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def command(program, udid, *args):
|
|
142
|
+
"""``pymobiledevice3 developer accessibility <args>`` for the iPhone ``udid``, over macOS's own tunnel."""
|
|
143
|
+
if not isinstance(udid, str) or not UDID.fullmatch(udid):
|
|
144
|
+
raise Unavailable("the run's device has no iPhone identifier")
|
|
145
|
+
return [program, "developer", "accessibility", *args, "--native", "--udid", udid]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def audit_types(value):
|
|
149
|
+
"""The audit types the phone supports, as ``supported-audit-types`` prints them: JSON strings only. None is no
|
|
150
|
+
answer, never "audit nothing": an empty list, or numbers ``run-audit`` would get as text, could pass every screen
|
|
151
|
+
unchecked."""
|
|
152
|
+
types = value if isinstance(value, list) else []
|
|
153
|
+
if not types or not all(isinstance(item, str) and AUDIT_TYPE.fullmatch(item) for item in types):
|
|
154
|
+
raise Unavailable("pymobiledevice3 listed no audit types")
|
|
155
|
+
return types
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def audit_issues(value):
|
|
159
|
+
"""The issues ``run-audit`` printed: a list of objects, or no answer."""
|
|
160
|
+
if not isinstance(value, list) or not all(isinstance(item, dict) for item in value):
|
|
161
|
+
raise Unavailable("pymobiledevice3 printed no list of issues")
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Collector:
|
|
166
|
+
"""The audit's findings for a run; ``add`` audits the screen that is up on the phone, once per distinct screen."""
|
|
167
|
+
|
|
168
|
+
def __init__(self, udid_of, *, program=tunnel.tool, run=run_tool):
|
|
169
|
+
self._udid_of = udid_of # () -> the real iPhone's UDID, or None on a simulator
|
|
170
|
+
self._program, self._run = program, run # () -> pymobiledevice3's path or None; (argv, timeout) -> (code, out)
|
|
171
|
+
self._types = None # the phone's audit types, read once a run
|
|
172
|
+
self._seen, self._found, self._noted = set(), [], False
|
|
173
|
+
|
|
174
|
+
def _note_once(self, rule, message, evidence):
|
|
175
|
+
if not self._noted:
|
|
176
|
+
self._noted = True
|
|
177
|
+
self._found.append(_note(rule, message, evidence, None))
|
|
178
|
+
|
|
179
|
+
def _read(self, argv):
|
|
180
|
+
code, output = self._run(argv, TIMEOUT)
|
|
181
|
+
if code != 0:
|
|
182
|
+
raise Unavailable(f"pymobiledevice3 exited {code}")
|
|
183
|
+
try:
|
|
184
|
+
return json.loads(output)
|
|
185
|
+
except ValueError:
|
|
186
|
+
raise Unavailable("pymobiledevice3 printed no JSON") from None
|
|
187
|
+
|
|
188
|
+
def _audit(self, udid):
|
|
189
|
+
program = self._program()
|
|
190
|
+
if not program:
|
|
191
|
+
raise ToolMissing()
|
|
192
|
+
if self._types is None:
|
|
193
|
+
self._types = audit_types(self._read(command(program, udid, "supported-audit-types")))
|
|
194
|
+
return audit_issues(self._read(command(program, udid, "run-audit", *self._types)))
|
|
195
|
+
|
|
196
|
+
def add(self, tree, frame=None):
|
|
197
|
+
if not enabled():
|
|
198
|
+
return self._note_once(
|
|
199
|
+
"device-audit-off",
|
|
200
|
+
f"The on-device audit is experimental. Set {SETTING}=1 to run it.",
|
|
201
|
+
f"{SETTING} is not set",
|
|
202
|
+
)
|
|
203
|
+
udid = self._udid_of()
|
|
204
|
+
if udid is None:
|
|
205
|
+
return self._note_once(
|
|
206
|
+
"device-audit-simulator",
|
|
207
|
+
"The on-device audit needs a real iPhone: a simulator "
|
|
208
|
+
"has no audit service. The accessibility tree audit (`audit: a11y`) covers "
|
|
209
|
+
"this screen.",
|
|
210
|
+
"the run's device is a simulator",
|
|
211
|
+
)
|
|
212
|
+
fingerprint = tree.fingerprint()
|
|
213
|
+
if fingerprint in self._seen or len(self._seen) >= LIMIT:
|
|
214
|
+
return None
|
|
215
|
+
self._seen.add(fingerprint)
|
|
216
|
+
try:
|
|
217
|
+
issues = self._audit(udid)
|
|
218
|
+
except ToolMissing:
|
|
219
|
+
return self._note_once("device-audit-no-tool", NO_TOOL, "pymobiledevice3 is not installed")
|
|
220
|
+
except Exception as error:
|
|
221
|
+
reason = str(error) if isinstance(error, Unavailable) else type(error).__name__
|
|
222
|
+
return self._note_once(
|
|
223
|
+
"device-audit-unavailable",
|
|
224
|
+
f"The iPhone's audit service didn't answer ({reason}). It needs iOS 17 or later, paired with this Mac "
|
|
225
|
+
"for development, unlocked, in Developer Mode and with the developer disk image mounted. Fix what's "
|
|
226
|
+
"missing, then run again.",
|
|
227
|
+
f"{reason} from the audit",
|
|
228
|
+
)
|
|
229
|
+
self._found.extend(findings_from_issues(issues, frame))
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def screens(self):
|
|
234
|
+
return len(self._seen)
|
|
235
|
+
|
|
236
|
+
def findings(self):
|
|
237
|
+
return list(self._found)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def for_run(run, audit):
|
|
241
|
+
"""The collector for ``run`` when its check audits ``ax_device``, else None. It asks for the iPhone's UDID when it
|
|
242
|
+
reads: None on a simulator or before a device is held."""
|
|
243
|
+
from .smart import is_real_device
|
|
244
|
+
if NAME not in audit:
|
|
245
|
+
return None
|
|
246
|
+
return Collector(lambda: getattr(run.target, "udid", None) if is_real_device(run._manager()) else None)
|