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
mobile_agent/usage.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Durable workspace usage independent of the bounded task history."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from .costs import NANODOLLARS
|
|
8
|
+
|
|
9
|
+
# Who priced a call: Jev, Gemini on Vertex, an OpenAI-compatible helper, OpenAI or Anthropic (the Smart engine), or
|
|
10
|
+
# a plan or provider from Settings › Models (harness_api.EngineHooks.model_routes; a plan call costs 0).
|
|
11
|
+
# The coding agents a task can run on (ai_plans.AGENT_ENGINES) record their runs under their own names.
|
|
12
|
+
PROVIDERS = frozenset({"typesafe", "google", "helper", "openai", "anthropic", "claude_code", "codex", "gemini"})
|
|
13
|
+
BILLINGS = frozenset({"plan", "api_key"})
|
|
14
|
+
# Columns added after the first ledger: added in place to a ledger that predates them (NULL in old rows: unknown).
|
|
15
|
+
ADDED_COLUMNS = (("cache_read_tokens", "INTEGER"), ("cache_write_tokens", "INTEGER"), ("billing", "TEXT"),
|
|
16
|
+
("api_equivalent_nanodollars", "INTEGER"))
|
|
17
|
+
MAX_COUNT = 9_007_199_254_740_991
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def known_provider(provider):
|
|
21
|
+
from .harness_api import hooks
|
|
22
|
+
return provider in PROVIDERS or hooks().model_routes().get(provider) is not None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def initialize(connection):
|
|
26
|
+
connection.executescript("""
|
|
27
|
+
CREATE TABLE IF NOT EXISTS usage_tracking (
|
|
28
|
+
id INTEGER PRIMARY KEY CHECK(id=1), since REAL NOT NULL);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS usage_calls (
|
|
30
|
+
run_id TEXT NOT NULL, call_id TEXT NOT NULL,
|
|
31
|
+
provider TEXT NOT NULL, model TEXT NOT NULL,
|
|
32
|
+
started_at REAL NOT NULL, finished_at REAL,
|
|
33
|
+
input_tokens INTEGER, output_tokens INTEGER, total_tokens INTEGER,
|
|
34
|
+
cost_nanodollars INTEGER, PRIMARY KEY(run_id,call_id));
|
|
35
|
+
CREATE TABLE IF NOT EXISTS usage_limits (
|
|
36
|
+
provider TEXT PRIMARY KEY, data TEXT NOT NULL, at REAL NOT NULL);
|
|
37
|
+
""")
|
|
38
|
+
present = {row[1] for row in connection.execute("PRAGMA table_info(usage_calls)")}
|
|
39
|
+
for name, kind in ADDED_COLUMNS:
|
|
40
|
+
if name not in present:
|
|
41
|
+
connection.execute(f"ALTER TABLE usage_calls ADD COLUMN {name} {kind}")
|
|
42
|
+
connection.execute("INSERT OR IGNORE INTO usage_tracking VALUES (1,?)", (time.time() * 1000,))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _count(value):
|
|
46
|
+
return value is None or (type(value) is int and 0 <= value <= MAX_COUNT)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def record_call(connection, run_id, event):
|
|
50
|
+
"""Called in the same transaction as the run event it records."""
|
|
51
|
+
kind = event.get("event")
|
|
52
|
+
if kind == "agent_limits":
|
|
53
|
+
record_limits(connection, event)
|
|
54
|
+
return
|
|
55
|
+
if kind not in {"inference_started", "inference_finished"}:
|
|
56
|
+
return
|
|
57
|
+
call_id, provider, model = (event.get(key) for key in ("call_id", "provider", "model"))
|
|
58
|
+
if (not isinstance(call_id, str) or not 1 <= len(call_id) <= 128
|
|
59
|
+
or not known_provider(provider)
|
|
60
|
+
or not isinstance(model, str) or not 1 <= len(model) <= 200):
|
|
61
|
+
raise ValueError("Invalid inference usage identity")
|
|
62
|
+
if kind == "inference_started":
|
|
63
|
+
connection.execute("""INSERT INTO usage_calls
|
|
64
|
+
(run_id,call_id,provider,model,started_at) VALUES (?,?,?,?,?)""",
|
|
65
|
+
(run_id, call_id, provider, model, event["timestamp"]))
|
|
66
|
+
return
|
|
67
|
+
usage = event.get("usage", {})
|
|
68
|
+
counts = [usage.get(key) for key in ("input_tokens", "output_tokens", "total_tokens")]
|
|
69
|
+
# Smart's calls say cached_input_tokens; the coding agents' say cache_read_tokens.
|
|
70
|
+
cache = [usage.get("cache_read_tokens", usage.get("cached_input_tokens")), usage.get("cache_write_tokens")]
|
|
71
|
+
cost, equivalent = event.get("cost_nanodollars"), event.get("api_equivalent_nanodollars")
|
|
72
|
+
if not all(_count(n) for n in [*counts, *cache, cost, equivalent]):
|
|
73
|
+
raise ValueError("Invalid inference usage counts")
|
|
74
|
+
billing = event.get("billing")
|
|
75
|
+
if billing is None and event.get("cost_basis") == "plan":
|
|
76
|
+
billing = "plan"
|
|
77
|
+
if billing is not None and billing not in BILLINGS:
|
|
78
|
+
raise ValueError("Invalid inference billing")
|
|
79
|
+
changed = connection.execute("""UPDATE usage_calls SET model=?,finished_at=?,
|
|
80
|
+
input_tokens=?,output_tokens=?,total_tokens=?,cost_nanodollars=?,
|
|
81
|
+
cache_read_tokens=?,cache_write_tokens=?,billing=?,api_equivalent_nanodollars=?
|
|
82
|
+
WHERE run_id=? AND call_id=? AND provider=? AND finished_at IS NULL""",
|
|
83
|
+
(model, event["timestamp"], *counts, cost, *cache, billing, equivalent, run_id, call_id, provider)).rowcount
|
|
84
|
+
if changed != 1:
|
|
85
|
+
raise ValueError("Inference completion must match one pending call")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# What a coding agent says of its plan's usage limits (Claude Code's rate_limit_event): kept per agent, newest only.
|
|
89
|
+
LIMIT_STATUSES = frozenset({"allowed", "allowed_warning", "rejected"})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def record_limits(connection, event):
|
|
93
|
+
"""Keep the newest limit signal an agent's run reported (ai_plans.cli.limit_signal's shape). Anything malformed is
|
|
94
|
+
dropped: a limit reading is a hint, never a reason to stop a run."""
|
|
95
|
+
provider, limits = event.get("agent"), event.get("limits")
|
|
96
|
+
if provider not in PROVIDERS or not isinstance(limits, dict) or limits.get("status") not in LIMIT_STATUSES:
|
|
97
|
+
return
|
|
98
|
+
data = json.dumps(limits, sort_keys=True)
|
|
99
|
+
if len(data) > 2000:
|
|
100
|
+
return
|
|
101
|
+
connection.execute("INSERT INTO usage_limits VALUES (?,?,?) ON CONFLICT(provider) DO UPDATE SET data=excluded.data,"
|
|
102
|
+
"at=excluded.at", (provider, data, event["timestamp"]))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def limits(connection):
|
|
106
|
+
"""{agent: {...the limit signal, "at": ms}} for every agent that reported one."""
|
|
107
|
+
out = {}
|
|
108
|
+
for provider, data, at in connection.execute("SELECT provider,data,at FROM usage_limits ORDER BY provider"):
|
|
109
|
+
try:
|
|
110
|
+
out[provider] = {**json.loads(data), "at": at}
|
|
111
|
+
except ValueError:
|
|
112
|
+
continue
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def summary(connection, run_id=None):
|
|
117
|
+
where, params = ("WHERE run_id=?", (run_id,)) if run_id else ("", ())
|
|
118
|
+
rows = connection.execute(f"""SELECT provider,model,billing,count(*),
|
|
119
|
+
count(total_tokens),sum(input_tokens),sum(output_tokens),sum(total_tokens),
|
|
120
|
+
sum(cost_nanodollars),count(cost_nanodollars),
|
|
121
|
+
sum(CASE WHEN finished_at IS NULL THEN 1 ELSE 0 END),
|
|
122
|
+
sum(cache_read_tokens),sum(cache_write_tokens),sum(api_equivalent_nanodollars)
|
|
123
|
+
FROM usage_calls {where} GROUP BY provider,model,billing ORDER BY provider,model,billing""", params).fetchall()
|
|
124
|
+
providers = []
|
|
125
|
+
for (provider, model, billing, calls, reported, incoming, outgoing, total, cost, priced, pending, cache_read,
|
|
126
|
+
cache_write, equivalent) in rows:
|
|
127
|
+
providers.append({"provider": provider, "model": model, "billing": billing, "calls": calls,
|
|
128
|
+
"reported_calls": reported, "input_tokens": incoming, "output_tokens": outgoing,
|
|
129
|
+
"total_tokens": total, "estimated_usd": cost / NANODOLLARS if cost is not None else None,
|
|
130
|
+
"priced_calls": priced, "unreported_calls": calls - reported,
|
|
131
|
+
"unpriced_calls": calls - priced, "in_flight_calls": pending,
|
|
132
|
+
"cache_read_tokens": cache_read, "cache_write_tokens": cache_write,
|
|
133
|
+
"api_equivalent_usd": equivalent / NANODOLLARS if equivalent is not None else None})
|
|
134
|
+
totals = {key: sum(p[key] for p in providers) for key in (
|
|
135
|
+
"calls", "reported_calls", "priced_calls", "unreported_calls", "unpriced_calls", "in_flight_calls")}
|
|
136
|
+
for key in ("input_tokens", "output_tokens", "total_tokens", "estimated_usd", "cache_read_tokens",
|
|
137
|
+
"cache_write_tokens", "api_equivalent_usd"):
|
|
138
|
+
known = [p[key] for p in providers if p[key] is not None]
|
|
139
|
+
totals[key] = sum(known) if known else None
|
|
140
|
+
plan = [p for p in providers if p["billing"] == "plan" and p["total_tokens"] is not None]
|
|
141
|
+
totals["plan_tokens"] = sum(p["total_tokens"] for p in plan) if plan else None
|
|
142
|
+
return {"tracked_since": connection.execute("SELECT since FROM usage_tracking WHERE id=1").fetchone()[0],
|
|
143
|
+
"updated_at": time.time() * 1000, "scope": "mobster_workspace",
|
|
144
|
+
"cost_basis": "published_rate", "totals": totals, "providers": providers,
|
|
145
|
+
"limits": limits(connection) if not run_id else {}}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def close_pending(connection, run_id, timestamp):
|
|
149
|
+
# Terminal/recovered runs cannot leave a perpetual 'in flight' counter.
|
|
150
|
+
# Unknown usage remains NULL, including interrupted requests that may bill.
|
|
151
|
+
connection.execute("UPDATE usage_calls SET finished_at=? WHERE run_id=? AND finished_at IS NULL",
|
|
152
|
+
(timestamp, run_id))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def month_start(now=None):
|
|
156
|
+
"""Milliseconds at the start of this calendar month in the Mac's local time zone."""
|
|
157
|
+
# Keep this local and naive so timestamp() uses the offset at the month's start,
|
|
158
|
+
# which can differ from today's offset across a daylight-saving transition.
|
|
159
|
+
now = datetime.fromtimestamp(time.time() if now is None else now)
|
|
160
|
+
return now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def week_start(now=None):
|
|
164
|
+
"""Milliseconds at the start of this week (Monday, 00:00) in the Mac's local time zone."""
|
|
165
|
+
from datetime import timedelta
|
|
166
|
+
local = datetime.fromtimestamp(time.time() if now is None else now) # naive local time: DST-safe below
|
|
167
|
+
monday = (local - timedelta(days=local.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
168
|
+
return monday.timestamp() * 1000
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def spend_since(connection, since_ms):
|
|
172
|
+
"""Published-rate spend of the calls started at or after ``since_ms`` (unpriced calls add nothing)."""
|
|
173
|
+
cost = connection.execute("SELECT sum(cost_nanodollars) FROM usage_calls WHERE started_at>=?",
|
|
174
|
+
(since_ms,)).fetchone()[0]
|
|
175
|
+
return (cost or 0) / NANODOLLARS
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def plan_since(connection, since_ms):
|
|
179
|
+
"""Tokens on the person's plans since ``since_ms`` and what they'd cost at list price: {"tokens",
|
|
180
|
+
"apiEquivalentUsd"}
|
|
181
|
+
(None where nothing was reported)."""
|
|
182
|
+
tokens, equivalent = connection.execute("""SELECT sum(total_tokens),sum(api_equivalent_nanodollars)
|
|
183
|
+
FROM usage_calls WHERE started_at>=? AND billing='plan'""", (since_ms,)).fetchone()
|
|
184
|
+
return {"tokens": tokens, "apiEquivalentUsd": equivalent / NANODOLLARS if equivalent is not None else None}
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""`python -m mobile_agent.verify …`: the same command as `mobster verify`, with the same flags and exit codes."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_parser():
|
|
10
|
+
from .. import cli_parser as mobster
|
|
11
|
+
from ..devtools import TEXT
|
|
12
|
+
from . import cli
|
|
13
|
+
description, epilog = TEXT["verify"]
|
|
14
|
+
parser = argparse.ArgumentParser(prog="mobster verify", description=description, epilog=epilog,
|
|
15
|
+
formatter_class=mobster.Formatter)
|
|
16
|
+
cli.add_arguments(parser, {"env_option": mobster._env_option, "path_type": mobster.path_type,
|
|
17
|
+
"bounded": mobster._bounded})
|
|
18
|
+
mobster._tidy(parser) # "show this help and exit", as under `mobster`
|
|
19
|
+
return parser
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(argv=None):
|
|
23
|
+
from .. import tls
|
|
24
|
+
from ..config import load_env_file
|
|
25
|
+
from . import cli
|
|
26
|
+
args = build_parser().parse_args(argv)
|
|
27
|
+
env_file = getattr(args, "env_file", None)
|
|
28
|
+
if env_file is None and os.environ.get("MOBSTER_ENV_FILE"):
|
|
29
|
+
env_file = Path(os.environ["MOBSTER_ENV_FILE"]).expanduser()
|
|
30
|
+
if env_file:
|
|
31
|
+
for warning in load_env_file(env_file).warnings:
|
|
32
|
+
print(f"mobster: {warning}", file=sys.stderr, flush=True)
|
|
33
|
+
tls.ensure_ca_bundle()
|
|
34
|
+
try:
|
|
35
|
+
return cli.run(args)
|
|
36
|
+
except KeyboardInterrupt:
|
|
37
|
+
return 130
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
sys.exit(main())
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""The accessibility audit (docs/accessibility.mdx): rules on the trees a run reads, with no model or pixels."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from .conditions import CONTENT_SIZES
|
|
7
|
+
from .tree import SCROLL_CONTAINERS, STRUCTURAL
|
|
8
|
+
|
|
9
|
+
NAME = "a11y"
|
|
10
|
+
MIN_TARGET = 44.0
|
|
11
|
+
EDGE = 1.0 # points of slack for rounding in frames
|
|
12
|
+
CONTROLS = frozenset({"Button", "Switch", "Toggle", "Link", "Slider", "Stepper", "TextField", "SecureTextField",
|
|
13
|
+
"SearchField", "TextView"})
|
|
14
|
+
# Controls whose own value is not a name ("0", "1", "50%"), so only a label names them.
|
|
15
|
+
ENTRY = frozenset({"TextField", "SecureTextField", "SearchField", "TextView"})
|
|
16
|
+
TARGETS = frozenset({"Button", "Link", "Switch", "Toggle"})
|
|
17
|
+
TEXT = frozenset({"StaticText", "Button", "Link", "TextField", "SecureTextField", "SearchField", "TextView"})
|
|
18
|
+
LARGE_SIZES = CONTENT_SIZES[CONTENT_SIZES.index("extra-large"):]
|
|
19
|
+
GUIDELINES = {"control-no-label": "WCAG 4.1.2", "target-too-small": "HIG 44 pt", "image-no-label": "WCAG 1.1.1",
|
|
20
|
+
"text-clipped": "WCAG 1.4.4", "duplicate-label": "WCAG 2.4.6", "label-unhelpful": "WCAG 2.4.6",
|
|
21
|
+
"unreachable-element": "WCAG 2.1.1"}
|
|
22
|
+
LEVELS = ("warn", "strict")
|
|
23
|
+
GENERIC = frozenset({"button", "image", "icon", "img", "picture", "photo", "untitled", "label", "view", "item"})
|
|
24
|
+
FILE_LABEL = re.compile(r".+\.(png|jpe?g|pdf|svg|gif|heic|webp)", re.I)
|
|
25
|
+
CODE_LABEL = re.compile(r"[a-z][a-z0-9]*(?:[_-][a-z0-9]+)+")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def large_text(content_size):
|
|
29
|
+
return content_size in LARGE_SIZES
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _describe(node):
|
|
33
|
+
name = f"{node.role} \"{node.label}\"" if node.label.strip() else (
|
|
34
|
+
f"{node.role} (id {node.identifier})" if node.identifier else node.role)
|
|
35
|
+
x, y, w, h = node.rect
|
|
36
|
+
return name, f"at {x:g}, {y:g}, {w:g} x {h:g} pt"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _finding(rule, node, message, frame):
|
|
40
|
+
name, where = _describe(node)
|
|
41
|
+
x, y, w, h = node.rect
|
|
42
|
+
return {"id": rule, "audit": NAME, "guideline": GUIDELINES[rule], "level": "fail", "frame": frame,
|
|
43
|
+
"message": f"{name} {where}: {message}", "evidence": f"{name} {where}",
|
|
44
|
+
"element": {"role": node.role, "label": node.label, "identifier": node.identifier, "path": node.path,
|
|
45
|
+
"rect": [x, y, w, h]}}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _ancestors(path):
|
|
49
|
+
while "/" in path.strip("/"):
|
|
50
|
+
path = path.rsplit("/", 1)[0]
|
|
51
|
+
yield path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def audit_tree(tree, *, content_size=None, frame=None):
|
|
55
|
+
"""The findings of one screen's tree."""
|
|
56
|
+
shown = tree.shown()
|
|
57
|
+
by_path = {node.path: node for node in tree.nodes}
|
|
58
|
+
labelled_controls = {node.path for node in shown if node.role in CONTROLS and node.label.strip()}
|
|
59
|
+
width = tree.size[0]
|
|
60
|
+
out = []
|
|
61
|
+
for node in shown:
|
|
62
|
+
x, y, w, h = node.rect
|
|
63
|
+
if node.role in CONTROLS and not node.label.strip() and not (node.role in ENTRY and node.placeholder.strip()):
|
|
64
|
+
out.append(_finding("control-no-label", node,
|
|
65
|
+
"it has no accessibility label, so VoiceOver can't say what it does. Give it one "
|
|
66
|
+
"(SwiftUI: .accessibilityLabel; UIKit: accessibilityLabel).", frame))
|
|
67
|
+
if node.role in TARGETS and w > 0 and h > 0 and (w < MIN_TARGET or h < MIN_TARGET):
|
|
68
|
+
out.append(_finding("target-too-small", node,
|
|
69
|
+
f"the tap target is {w:g} x {h:g} pt, under {MIN_TARGET:g} x {MIN_TARGET:g}. "
|
|
70
|
+
"Make the control or its tappable area at least 44 pt each way (SwiftUI: "
|
|
71
|
+
".frame(minWidth: 44, minHeight: 44) or .contentShape).", frame))
|
|
72
|
+
if node.role == "Image" and not node.label.strip() and w > 1 and h > 1 \
|
|
73
|
+
and not any(path in labelled_controls for path in _ancestors(node.path)):
|
|
74
|
+
out.append(_finding("image-no-label", node,
|
|
75
|
+
"the image has no label. Label it, or hide it from accessibility if it is "
|
|
76
|
+
"decoration (SwiftUI: .accessibilityHidden(true)).", frame))
|
|
77
|
+
if (node.role in CONTROLS or node.role == "Image") and node.label.strip() and unhelpful(node.label) \
|
|
78
|
+
and not (node.role in ENTRY):
|
|
79
|
+
out.append(_finding("label-unhelpful", node,
|
|
80
|
+
f"the label \"{node.label.strip()}\" doesn't say what it is for. Name the action or "
|
|
81
|
+
"the picture (SwiftUI: .accessibilityLabel).", frame))
|
|
82
|
+
if large_text(content_size) and node.role in TEXT and node.label.strip() and w > 0 and h > 0:
|
|
83
|
+
clipped = _clipped(node, by_path, width)
|
|
84
|
+
if clipped:
|
|
85
|
+
out.append(_finding("text-clipped", node,
|
|
86
|
+
f"at the {content_size} text size it is {clipped}. Let the text wrap or the "
|
|
87
|
+
"container grow (SwiftUI: remove fixed frames and lineLimit).", frame))
|
|
88
|
+
out.extend(_duplicates(shown, frame))
|
|
89
|
+
out.extend(_unreachable(tree, by_path, frame))
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def unhelpful(label):
|
|
94
|
+
text = label.strip()
|
|
95
|
+
return text.lower() in GENERIC or bool(FILE_LABEL.fullmatch(text)) or bool(CODE_LABEL.fullmatch(text))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _duplicates(shown, frame):
|
|
99
|
+
groups = {}
|
|
100
|
+
for node in shown:
|
|
101
|
+
if node.role in TARGETS and node.label.strip() and node.rect[2] > 0 and node.rect[3] > 0:
|
|
102
|
+
groups.setdefault((node.role, " ".join(node.label.split()).casefold()), []).append(node)
|
|
103
|
+
return [_finding("duplicate-label", nodes[0],
|
|
104
|
+
f"{len(nodes)} {nodes[0].role.lower()}s on this screen are all labelled "
|
|
105
|
+
f"\"{nodes[0].label.strip()}\", "
|
|
106
|
+
"so VoiceOver reads them alike. Say what each one acts on (SwiftUI: .accessibilityLabel).", frame)
|
|
107
|
+
for nodes in groups.values() if len(nodes) > 1 and len({tuple(n.rect) for n in nodes}) > 1]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _unreachable(tree, by_path, frame):
|
|
111
|
+
width, height = tree.size
|
|
112
|
+
out = []
|
|
113
|
+
for node in tree.nodes:
|
|
114
|
+
x, y, w, h = node.rect
|
|
115
|
+
if node.role not in CONTROLS or not node.label.strip() or not node.enabled or w <= 0 or h <= 0:
|
|
116
|
+
continue
|
|
117
|
+
if not (x + w <= 0 or x >= width or y + h <= 0 or y >= height):
|
|
118
|
+
continue
|
|
119
|
+
if any(by_path[path].role in SCROLL_CONTAINERS for path in _ancestors(node.path) if path in by_path):
|
|
120
|
+
continue
|
|
121
|
+
out.append(_finding("unreachable-element", node,
|
|
122
|
+
"its frame is wholly off the screen and it is in no scroll view, so nothing can reach it. "
|
|
123
|
+
"Move it on screen, or remove it from the layout when it is meant to be hidden.", frame))
|
|
124
|
+
return out
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _clipped(node, by_path, screen_width):
|
|
128
|
+
x, y, w, h = node.rect
|
|
129
|
+
ancestors = [by_path[path] for path in _ancestors(node.path) if path in by_path]
|
|
130
|
+
in_scroll = any(item.role in SCROLL_CONTAINERS for item in ancestors)
|
|
131
|
+
if x < -EDGE or x + w > screen_width + EDGE:
|
|
132
|
+
if not in_scroll:
|
|
133
|
+
return "wider than the screen"
|
|
134
|
+
parent = next((item for item in ancestors if item.role not in STRUCTURAL and item.rect[2] * item.rect[3] > 0), None)
|
|
135
|
+
if parent is not None and parent.role not in SCROLL_CONTAINERS:
|
|
136
|
+
px, py, pw, ph = parent.rect
|
|
137
|
+
if x < px - EDGE or x + w > px + pw + EDGE or y < py - EDGE or y + h > py + ph + EDGE:
|
|
138
|
+
return f"outside its {parent.role}"
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def baseline_key(scope, finding):
|
|
143
|
+
"""A finding's baseline key: the check's file, the rule, the role, label and identifier. Not the frame, so a layout
|
|
144
|
+
shift doesn't make an old finding new."""
|
|
145
|
+
element = finding["element"]
|
|
146
|
+
return json.dumps((scope, finding["id"], element["role"], element["label"], element["identifier"]),
|
|
147
|
+
ensure_ascii=False, separators=(",", ":"))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Collector:
|
|
151
|
+
LIMIT = 60
|
|
152
|
+
|
|
153
|
+
def __init__(self, content_size=None, *, level="strict", baseline=frozenset(), scope=""):
|
|
154
|
+
self.content_size, self.level, self.baseline, self.scope = content_size, level, baseline, scope
|
|
155
|
+
self._seen, self._found = {}, []
|
|
156
|
+
self._keys = set()
|
|
157
|
+
|
|
158
|
+
def add(self, tree, frame=None):
|
|
159
|
+
fingerprint = tree.audit_fingerprint()
|
|
160
|
+
if fingerprint in self._seen:
|
|
161
|
+
if frame is not None:
|
|
162
|
+
start, end = self._seen[fingerprint]
|
|
163
|
+
for finding in self._found[start:end]:
|
|
164
|
+
if finding["frame"] is None:
|
|
165
|
+
finding["frame"] = frame
|
|
166
|
+
return True
|
|
167
|
+
if len(self._seen) >= self.LIMIT:
|
|
168
|
+
return False
|
|
169
|
+
start = len(self._found)
|
|
170
|
+
for finding in audit_tree(tree, content_size=self.content_size, frame=frame):
|
|
171
|
+
element = finding["element"]
|
|
172
|
+
key = (finding["id"], element["role"], element["label"], element["identifier"],
|
|
173
|
+
tuple(round(n) for n in element["rect"]))
|
|
174
|
+
if key not in self._keys:
|
|
175
|
+
self._keys.add(key)
|
|
176
|
+
finding["key"] = baseline_key(self.scope, finding)
|
|
177
|
+
known = finding["key"] in self.baseline
|
|
178
|
+
finding["level"] = "known" if known else ("fail" if self.level == "strict" else "warn")
|
|
179
|
+
finding["baseline"] = known
|
|
180
|
+
self._found.append(finding)
|
|
181
|
+
self._seen[fingerprint] = (start, len(self._found))
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def screens(self):
|
|
186
|
+
return len(self._seen)
|
|
187
|
+
|
|
188
|
+
def findings(self):
|
|
189
|
+
return list(self._found)
|