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/pool.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Many phones behind one runtime: one task per phone, first come first served, unhealthy phones withdrawn."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
from .devices import UDID_PATTERN
|
|
11
|
+
from .errors import MobsterError
|
|
12
|
+
from .journal import JournalError, Lease
|
|
13
|
+
from .wda.video import default_mjpeg_url
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NoDeviceAvailable(MobsterError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _local_http_url(value, what):
|
|
21
|
+
parts = urlsplit(value or "")
|
|
22
|
+
if (parts.scheme not in {"http", "https"} or not parts.hostname or parts.username or parts.password
|
|
23
|
+
or parts.query or parts.fragment):
|
|
24
|
+
raise ValueError(f"{what} must be an http(s) URL without credentials")
|
|
25
|
+
return value.rstrip("/")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class WdaDeviceSpec:
|
|
30
|
+
id: str
|
|
31
|
+
wda_url: str
|
|
32
|
+
mjpeg_url: str = ""
|
|
33
|
+
udid: str = ""
|
|
34
|
+
label: str = ""
|
|
35
|
+
truth: str = ""
|
|
36
|
+
|
|
37
|
+
kind = "wda"
|
|
38
|
+
|
|
39
|
+
def __post_init__(self):
|
|
40
|
+
if not isinstance(self.id, str) or not self.id or len(self.id) > 64:
|
|
41
|
+
raise ValueError("A device needs a short id")
|
|
42
|
+
object.__setattr__(self, "wda_url", _local_http_url(self.wda_url, "wda_url"))
|
|
43
|
+
mjpeg = (_local_http_url(self.mjpeg_url, "mjpeg_url") if self.mjpeg_url
|
|
44
|
+
else default_mjpeg_url(self.wda_url) or "")
|
|
45
|
+
object.__setattr__(self, "mjpeg_url", mjpeg)
|
|
46
|
+
if self.udid and not UDID_PATTERN.fullmatch(self.udid):
|
|
47
|
+
raise ValueError("Invalid device UDID")
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def identity(self):
|
|
51
|
+
# The string direct runs lease a WDA phone by, so pooled and direct runs exclude each other.
|
|
52
|
+
return self.wda_url
|
|
53
|
+
|
|
54
|
+
def public(self):
|
|
55
|
+
# No URL or UDID: the dashboard reads this.
|
|
56
|
+
return {"id": self.id, "label": self.label or self.id, "kind": "wda", "truth": self.truth or None}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class DeviceState:
|
|
61
|
+
spec: object
|
|
62
|
+
healthy: bool = True
|
|
63
|
+
reason: str = ""
|
|
64
|
+
owner: object = None
|
|
65
|
+
lease: object = None
|
|
66
|
+
# -inf: the first allocation probes even when the host uptime is shorter than the health interval.
|
|
67
|
+
checked_at: float = float("-inf")
|
|
68
|
+
used_at: float = 0
|
|
69
|
+
failures: int = 0
|
|
70
|
+
completed: int = 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def probe_wda(url, timeout=3):
|
|
74
|
+
from .transport import HTTP
|
|
75
|
+
client = HTTP(url)
|
|
76
|
+
try:
|
|
77
|
+
status = client.request("GET", "/status", timeout=timeout)
|
|
78
|
+
finally:
|
|
79
|
+
client.close()
|
|
80
|
+
value = status.get("value") if isinstance(status, dict) else None
|
|
81
|
+
if not isinstance(value, dict) or value.get("ready") is False:
|
|
82
|
+
raise RuntimeError("WebDriverAgent is not ready")
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Probes for an extension's device kinds.
|
|
87
|
+
PROBES = {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DevicePool:
|
|
91
|
+
def __init__(self, specs, health_interval=20, probe=None, on_event=None):
|
|
92
|
+
specs = list(specs)
|
|
93
|
+
if not specs:
|
|
94
|
+
raise ValueError("A device pool needs at least one device")
|
|
95
|
+
if len({spec.id for spec in specs}) != len(specs):
|
|
96
|
+
raise ValueError("Device ids must be unique")
|
|
97
|
+
if len({spec.identity for spec in specs}) != len(specs):
|
|
98
|
+
raise ValueError("Two devices cannot share one route")
|
|
99
|
+
udids = [spec.udid for spec in specs if getattr(spec, "udid", "")]
|
|
100
|
+
if len(set(udids)) != len(udids):
|
|
101
|
+
raise ValueError("Two devices cannot share one UDID")
|
|
102
|
+
self.states = {spec.id: DeviceState(spec) for spec in specs}
|
|
103
|
+
self.health_interval = health_interval
|
|
104
|
+
self.probe = probe
|
|
105
|
+
self.on_event = on_event or (lambda event: None)
|
|
106
|
+
self.condition = threading.Condition(threading.RLock())
|
|
107
|
+
self.closed = False
|
|
108
|
+
self._tickets = itertools.count()
|
|
109
|
+
self._waiting = []
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def size(self):
|
|
113
|
+
return len(self.states)
|
|
114
|
+
|
|
115
|
+
def _emit(self, event, **fields):
|
|
116
|
+
try:
|
|
117
|
+
self.on_event({"event": event, **fields})
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
def _refresh(self, state, force=False):
|
|
122
|
+
if state.owner is not None:
|
|
123
|
+
return state.healthy
|
|
124
|
+
if not force and time.monotonic() - state.checked_at < self.health_interval:
|
|
125
|
+
return state.healthy
|
|
126
|
+
identity = state.spec.identity
|
|
127
|
+
probe = self.probe or (probe_wda if state.spec.kind == "wda" else PROBES[state.spec.kind])
|
|
128
|
+
self.condition.release()
|
|
129
|
+
try:
|
|
130
|
+
probe(identity)
|
|
131
|
+
except Exception as error:
|
|
132
|
+
healthy, reason = False, str(error)[:120]
|
|
133
|
+
else:
|
|
134
|
+
healthy, reason = True, ""
|
|
135
|
+
finally:
|
|
136
|
+
self.condition.acquire()
|
|
137
|
+
if state.owner is not None:
|
|
138
|
+
# Taken while the probe was in flight: a stale result must not rewrite the owner's health.
|
|
139
|
+
return state.healthy
|
|
140
|
+
state.checked_at = time.monotonic()
|
|
141
|
+
if healthy != state.healthy:
|
|
142
|
+
self._emit("device_health", device=state.spec.id, healthy=healthy, reason=reason)
|
|
143
|
+
state.healthy, state.reason = healthy, reason
|
|
144
|
+
return healthy
|
|
145
|
+
|
|
146
|
+
def _take(self, owner):
|
|
147
|
+
candidates = [state for state in self.states.values()
|
|
148
|
+
if state.owner is None and self._refresh(state)]
|
|
149
|
+
# _refresh releases the lock while probing: filter again now that it is held until ownership is assigned.
|
|
150
|
+
if self.closed:
|
|
151
|
+
raise NoDeviceAvailable("The device pool is shutting down")
|
|
152
|
+
if any(state.owner == owner for state in self.states.values()):
|
|
153
|
+
raise ValueError("This owner already holds a device")
|
|
154
|
+
candidates = [state for state in candidates if state.owner is None and state.healthy]
|
|
155
|
+
for state in sorted(candidates, key=lambda item: (item.used_at, item.spec.id)):
|
|
156
|
+
try:
|
|
157
|
+
# Another Mobster process may own this phone.
|
|
158
|
+
state.lease = Lease.device(state.spec.identity)
|
|
159
|
+
except JournalError:
|
|
160
|
+
state.healthy = False
|
|
161
|
+
state.reason = "held by another Mobster process"
|
|
162
|
+
state.checked_at = time.monotonic()
|
|
163
|
+
self._emit("device_health", device=state.spec.id, healthy=False, reason=state.reason)
|
|
164
|
+
continue
|
|
165
|
+
state.owner = owner
|
|
166
|
+
state.used_at = time.monotonic()
|
|
167
|
+
self._emit("device_acquired", device=state.spec.id, owner=str(owner))
|
|
168
|
+
return state.spec
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def acquire(self, owner, timeout=0, cancelled=None):
|
|
172
|
+
if owner is None:
|
|
173
|
+
raise ValueError("A device lease needs an owner")
|
|
174
|
+
deadline = time.monotonic() + max(0, timeout)
|
|
175
|
+
ticket = next(self._tickets)
|
|
176
|
+
with self.condition:
|
|
177
|
+
if self.closed:
|
|
178
|
+
raise NoDeviceAvailable("The device pool is shutting down")
|
|
179
|
+
if any(state.owner == owner for state in self.states.values()):
|
|
180
|
+
raise ValueError("This owner already holds a device")
|
|
181
|
+
self._waiting.append(ticket)
|
|
182
|
+
try:
|
|
183
|
+
while True:
|
|
184
|
+
if cancelled and cancelled():
|
|
185
|
+
raise NoDeviceAvailable("Cancelled while waiting for a device")
|
|
186
|
+
if self.closed:
|
|
187
|
+
raise NoDeviceAvailable("The device pool is shutting down")
|
|
188
|
+
# Strict FIFO.
|
|
189
|
+
if self._waiting[0] == ticket:
|
|
190
|
+
spec = self._take(owner)
|
|
191
|
+
if spec is not None:
|
|
192
|
+
return spec
|
|
193
|
+
remaining = deadline - time.monotonic()
|
|
194
|
+
if remaining <= 0:
|
|
195
|
+
raise NoDeviceAvailable(
|
|
196
|
+
"Every device is busy" if any(s.owner is not None for s in self.states.values())
|
|
197
|
+
else "No device is currently reachable")
|
|
198
|
+
self.condition.wait(min(remaining, 1.0))
|
|
199
|
+
finally:
|
|
200
|
+
self._waiting.remove(ticket)
|
|
201
|
+
self.condition.notify_all()
|
|
202
|
+
|
|
203
|
+
def release(self, owner, failed=False):
|
|
204
|
+
with self.condition:
|
|
205
|
+
for state in self.states.values():
|
|
206
|
+
if state.owner != owner:
|
|
207
|
+
continue
|
|
208
|
+
if state.lease is not None:
|
|
209
|
+
try:
|
|
210
|
+
state.lease.close()
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
state.lease = None
|
|
214
|
+
state.owner = None
|
|
215
|
+
state.used_at = time.monotonic()
|
|
216
|
+
if failed:
|
|
217
|
+
state.failures += 1
|
|
218
|
+
# Re-probe before this device is offered again.
|
|
219
|
+
state.checked_at = float("-inf")
|
|
220
|
+
else:
|
|
221
|
+
state.completed += 1
|
|
222
|
+
self._emit("device_released", device=state.spec.id, owner=str(owner), failed=bool(failed))
|
|
223
|
+
self.condition.notify_all()
|
|
224
|
+
return state.spec
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def position(self):
|
|
228
|
+
with self.condition:
|
|
229
|
+
return {"waiting": len(self._waiting),
|
|
230
|
+
"busy": sum(1 for state in self.states.values() if state.owner is not None),
|
|
231
|
+
"healthy": sum(1 for state in self.states.values() if state.healthy),
|
|
232
|
+
"total": len(self.states)}
|
|
233
|
+
|
|
234
|
+
def public(self):
|
|
235
|
+
with self.condition:
|
|
236
|
+
return [{**state.spec.public(), "healthy": state.healthy,
|
|
237
|
+
"status": "busy" if state.owner is not None else
|
|
238
|
+
"ready" if state.healthy else "unavailable",
|
|
239
|
+
"reason": state.reason, "completed": state.completed,
|
|
240
|
+
"failures": state.failures}
|
|
241
|
+
for state in sorted(self.states.values(), key=lambda item: item.spec.id)]
|
|
242
|
+
|
|
243
|
+
def close(self):
|
|
244
|
+
with self.condition:
|
|
245
|
+
self.closed = True
|
|
246
|
+
for state in self.states.values():
|
|
247
|
+
if state.lease is not None:
|
|
248
|
+
try:
|
|
249
|
+
state.lease.close()
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
state.lease = None
|
|
253
|
+
self.condition.notify_all()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def wda_specs_from_env(value=None):
|
|
257
|
+
value = os.environ.get("MOBSTER_WDA_DEVICES", "") if value is None else value
|
|
258
|
+
specs = []
|
|
259
|
+
for index, entry in enumerate(part for part in value.split(";") if part.strip()):
|
|
260
|
+
fields = {}
|
|
261
|
+
for pair in entry.split(","):
|
|
262
|
+
if not pair.strip():
|
|
263
|
+
continue
|
|
264
|
+
key, sep, item = pair.partition("=")
|
|
265
|
+
key = key.strip().lower()
|
|
266
|
+
if not sep or key not in {"id", "udid", "wda", "mjpeg", "label", "truth"} or key in fields:
|
|
267
|
+
raise ValueError(f"MOBSTER_WDA_DEVICES entry {index + 1}: expected unique key=value pairs "
|
|
268
|
+
"(id, udid, wda, mjpeg, label, truth)")
|
|
269
|
+
fields[key] = item.strip()
|
|
270
|
+
if not fields.get("wda"):
|
|
271
|
+
raise ValueError(f"MOBSTER_WDA_DEVICES entry {index + 1} needs wda=<url>")
|
|
272
|
+
specs.append(WdaDeviceSpec(id=fields.get("id") or f"phone-{index + 1}", wda_url=fields["wda"],
|
|
273
|
+
mjpeg_url=fields.get("mjpeg", ""), udid=fields.get("udid", ""),
|
|
274
|
+
label=fields.get("label", ""), truth=fields.get("truth", "")))
|
|
275
|
+
return specs
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def wda_specs_from_config(config):
|
|
279
|
+
items = [item for item in getattr(config, "devices", None) or () if isinstance(item, dict) and item.get("wdaUrl")]
|
|
280
|
+
if items:
|
|
281
|
+
return [WdaDeviceSpec(id=item.get("id") or f"phone-{index + 1}", wda_url=item["wdaUrl"],
|
|
282
|
+
mjpeg_url=item.get("mjpegUrl", ""), udid=item.get("udid", ""),
|
|
283
|
+
label=item.get("label", ""), truth=item.get("truth", ""))
|
|
284
|
+
for index, item in enumerate(items)]
|
|
285
|
+
return wda_specs_from_env()
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Power while tasks run (Runtime.power): the Mac kept awake, its battery watched, a phone's battery watched."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from . import device_status, engines, phone_ready
|
|
8
|
+
from .harness import control
|
|
9
|
+
|
|
10
|
+
INTERVAL = 30.0
|
|
11
|
+
MAC_WARN_DEFAULT = 20
|
|
12
|
+
PHONE_PAUSE_DEFAULT = 10
|
|
13
|
+
PAUSE_RANGE = (1, 50)
|
|
14
|
+
# A battery pause longer than this ends the task (status "stopped"): nobody came to plug the phone in.
|
|
15
|
+
BATTERY_PAUSE_LIMIT = 30 * 60
|
|
16
|
+
PHONE_BATTERY = "phone_battery"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _percent(name, default, low=1, high=99):
|
|
20
|
+
"""MOBSTER_* percent: the default when unset or unreadable, None when ``off`` (or 0)."""
|
|
21
|
+
raw = os.environ.get(name, "").strip().lower()
|
|
22
|
+
if not raw:
|
|
23
|
+
return default
|
|
24
|
+
if raw in ("off", "0", "false", "no"):
|
|
25
|
+
return None
|
|
26
|
+
try:
|
|
27
|
+
value = int(float(raw))
|
|
28
|
+
except (ValueError, OverflowError):
|
|
29
|
+
return default
|
|
30
|
+
return value if low <= value <= high else default
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def mac_warn_threshold():
|
|
34
|
+
return _percent("MOBSTER_MAC_BATTERY_WARN", MAC_WARN_DEFAULT)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def phone_pause_threshold():
|
|
38
|
+
return _percent("MOBSTER_PHONE_BATTERY_PAUSE", PHONE_PAUSE_DEFAULT, *PAUSE_RANGE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AwakeHolds:
|
|
42
|
+
"""One keep-awake (workflow_scheduler.KeepAwake) shared by owners, held while any owner holds it."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, keep_awake=None):
|
|
45
|
+
self.keep_awake = keep_awake
|
|
46
|
+
self.owners = set()
|
|
47
|
+
self.lock = threading.Lock()
|
|
48
|
+
|
|
49
|
+
def attach(self, keep_awake):
|
|
50
|
+
with self.lock:
|
|
51
|
+
self.keep_awake = keep_awake
|
|
52
|
+
if self.owners and keep_awake is not None:
|
|
53
|
+
keep_awake.hold()
|
|
54
|
+
|
|
55
|
+
def hold(self, owner):
|
|
56
|
+
with self.lock:
|
|
57
|
+
self.owners.add(owner)
|
|
58
|
+
if self.keep_awake is not None:
|
|
59
|
+
self.keep_awake.hold()
|
|
60
|
+
|
|
61
|
+
def release(self, owner):
|
|
62
|
+
with self.lock:
|
|
63
|
+
self.owners.discard(owner)
|
|
64
|
+
if not self.owners and self.keep_awake is not None:
|
|
65
|
+
self.keep_awake.release()
|
|
66
|
+
|
|
67
|
+
def close(self):
|
|
68
|
+
with self.lock:
|
|
69
|
+
self.owners.clear()
|
|
70
|
+
if self.keep_awake is not None:
|
|
71
|
+
self.keep_awake.release()
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def held(self):
|
|
75
|
+
return bool(self.keep_awake is not None and self.keep_awake.held)
|
|
76
|
+
|
|
77
|
+
def handle(self, owner):
|
|
78
|
+
"""The scheduler's keep-awake: hold() and release() as ``owner``."""
|
|
79
|
+
return _Handle(self, owner)
|
|
80
|
+
|
|
81
|
+
def status(self):
|
|
82
|
+
with self.lock:
|
|
83
|
+
owners = sorted("schedule" if owner == "schedule" else "task" for owner in self.owners)
|
|
84
|
+
enabled = bool(self.keep_awake is not None and getattr(self.keep_awake, "enabled", False))
|
|
85
|
+
return {"keptAwake": self.held, "keepAwake": enabled, "for": sorted(set(owners))}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _Handle:
|
|
89
|
+
def __init__(self, holds, owner):
|
|
90
|
+
self.holds, self.owner = holds, owner
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def held(self):
|
|
94
|
+
return self.owner in self.holds.owners and self.holds.held
|
|
95
|
+
|
|
96
|
+
def hold(self):
|
|
97
|
+
self.holds.hold(self.owner)
|
|
98
|
+
|
|
99
|
+
def release(self):
|
|
100
|
+
self.holds.release(self.owner)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class PowerWatch:
|
|
104
|
+
def __init__(self, runtime, *, read_mac=None, read_phone=None, clock=time.monotonic, interval=INTERVAL):
|
|
105
|
+
self.runtime = runtime
|
|
106
|
+
self.awake = AwakeHolds()
|
|
107
|
+
self.read_mac = read_mac or device_status.read_mac
|
|
108
|
+
self.read_phone = read_phone or phone_reader
|
|
109
|
+
self.clock, self.interval = clock, interval
|
|
110
|
+
self.lock = threading.Lock()
|
|
111
|
+
self._mac = (None, None) # (read at, value)
|
|
112
|
+
self._phones = {} # phone key -> (read at, value)
|
|
113
|
+
self.mac_low = None # the percent while the Mac's battery warning stands, else None
|
|
114
|
+
self.warned = set() # runs told the Mac's battery is low
|
|
115
|
+
self.paused = {} # run id -> the level it paused at (a battery pause this watch made)
|
|
116
|
+
self.kept_going = set() # runs a person continued from a battery pause
|
|
117
|
+
self.stop = threading.Event()
|
|
118
|
+
self.thread = None
|
|
119
|
+
|
|
120
|
+
# -- runs ------------------------------------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def run_started(self, run):
|
|
123
|
+
self.awake.hold(run.id)
|
|
124
|
+
self._ensure_thread()
|
|
125
|
+
if getattr(run, "origin", None) == "schedule" and phone_ready.enabled():
|
|
126
|
+
threading.Thread(target=self._low_power, args=(run,), name="mobster-low-power", daemon=True).start()
|
|
127
|
+
|
|
128
|
+
def _low_power(self, run):
|
|
129
|
+
"""A scheduled task's one ``phone_low_power`` warning, as it starts: Low Power Mode makes its phone lock after
|
|
130
|
+
30 seconds. A read that fails says nothing (phone_ready.py); it never touches the task."""
|
|
131
|
+
try:
|
|
132
|
+
phone = self.runtime.phone_for(run)
|
|
133
|
+
warning = phone_ready.low_power_warning(phone.wda_url, phone.name or phone.id, "the next scheduled task")
|
|
134
|
+
if warning:
|
|
135
|
+
run.emit({"event": "phone_low_power", "message": warning})
|
|
136
|
+
except Exception: # noqa: BLE001 -- a warning never stops a task
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
def run_finished(self, run):
|
|
140
|
+
self.awake.release(run.id)
|
|
141
|
+
with self.lock:
|
|
142
|
+
self.warned.discard(run.id)
|
|
143
|
+
self.paused.pop(run.id, None)
|
|
144
|
+
self.kept_going.discard(run.id)
|
|
145
|
+
|
|
146
|
+
def close(self):
|
|
147
|
+
self.stop.set()
|
|
148
|
+
self.awake.close()
|
|
149
|
+
|
|
150
|
+
def _ensure_thread(self):
|
|
151
|
+
with self.lock:
|
|
152
|
+
if self.thread is not None or self.stop.is_set():
|
|
153
|
+
return
|
|
154
|
+
thread = threading.Thread(target=self._work, name="mobster-power", daemon=True)
|
|
155
|
+
thread.start()
|
|
156
|
+
self.thread = thread
|
|
157
|
+
|
|
158
|
+
def _work(self):
|
|
159
|
+
while not self.stop.wait(self.interval):
|
|
160
|
+
try:
|
|
161
|
+
self.tick()
|
|
162
|
+
except Exception: # noqa: BLE001 -- the watch never stops a task
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# -- readings --------------------------------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def mac(self, fresh=False):
|
|
168
|
+
at, value = self._mac
|
|
169
|
+
if fresh or at is None or self.clock() - at > device_status.MAC_TTL:
|
|
170
|
+
try:
|
|
171
|
+
value = self.read_mac()
|
|
172
|
+
except Exception: # noqa: BLE001
|
|
173
|
+
value = {"power": None}
|
|
174
|
+
self._mac = (self.clock(), value)
|
|
175
|
+
return dict(value)
|
|
176
|
+
|
|
177
|
+
def phone(self, phone, fresh=False):
|
|
178
|
+
key = getattr(phone, "key", None)
|
|
179
|
+
at, value = self._phones.get(key, (None, None))
|
|
180
|
+
if fresh or at is None or self.clock() - at > device_status.PHONE_TTL:
|
|
181
|
+
try:
|
|
182
|
+
value = self.read_phone(phone)
|
|
183
|
+
except Exception: # noqa: BLE001
|
|
184
|
+
value = {}
|
|
185
|
+
self._phones[key] = (self.clock(), value)
|
|
186
|
+
return dict(value)
|
|
187
|
+
|
|
188
|
+
def warnings(self):
|
|
189
|
+
if self.mac_low is None or not self.runtime.active_runs:
|
|
190
|
+
return []
|
|
191
|
+
return [{"code": "mac_battery_low", "percent": self.mac_low,
|
|
192
|
+
"message": f"This Mac is on battery at {self.mac_low}%. Plug it in: a Mac that runs out of battery "
|
|
193
|
+
"sleeps and stops its tasks."}]
|
|
194
|
+
|
|
195
|
+
# -- one look --------------------------------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
def tick(self):
|
|
198
|
+
runs = self._running()
|
|
199
|
+
if not runs:
|
|
200
|
+
return
|
|
201
|
+
self._mac_battery(runs)
|
|
202
|
+
threshold = phone_pause_threshold()
|
|
203
|
+
for run in runs:
|
|
204
|
+
try:
|
|
205
|
+
self._phone_battery(run, threshold)
|
|
206
|
+
except Exception: # noqa: BLE001 -- one phone's reading never stops the others'
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
def _running(self):
|
|
210
|
+
runtime = self.runtime
|
|
211
|
+
with runtime.lock:
|
|
212
|
+
ids = list(runtime.active_runs)
|
|
213
|
+
return [run for run in (runtime.runs.get(i) for i in ids) if run is not None and run.finished_at is None]
|
|
214
|
+
|
|
215
|
+
def _mac_battery(self, runs):
|
|
216
|
+
threshold = mac_warn_threshold()
|
|
217
|
+
mac = self.mac(fresh=True)
|
|
218
|
+
level = mac.get("battery")
|
|
219
|
+
low = (threshold is not None and mac.get("power") == "battery" and type(level) is int and level <= threshold)
|
|
220
|
+
if not low:
|
|
221
|
+
self.mac_low = None
|
|
222
|
+
self.warned.clear()
|
|
223
|
+
return
|
|
224
|
+
first = self.mac_low is None
|
|
225
|
+
self.mac_low = level
|
|
226
|
+
event = {"event": "mac_battery_low", "percent": level, "threshold": threshold}
|
|
227
|
+
for run in runs:
|
|
228
|
+
if run.id not in self.warned:
|
|
229
|
+
self.warned.add(run.id)
|
|
230
|
+
run.emit(dict(event))
|
|
231
|
+
if first:
|
|
232
|
+
self.runtime.publish("power", {**event, "runs": len(runs)})
|
|
233
|
+
|
|
234
|
+
def _phone_battery(self, run, threshold):
|
|
235
|
+
queue = run.steering
|
|
236
|
+
if run.id in self.paused:
|
|
237
|
+
why = control.reason(queue)
|
|
238
|
+
if not control.paused(queue) or not why or why.get("code") != PHONE_BATTERY:
|
|
239
|
+
# A person continued it (or paused it themselves): theirs from now on, and its battery doesn't pause
|
|
240
|
+
# this task again.
|
|
241
|
+
self.paused.pop(run.id, None)
|
|
242
|
+
self.kept_going.add(run.id)
|
|
243
|
+
return
|
|
244
|
+
battery = self.phone(self.runtime.phone_for(run), fresh=True).get("battery") or {}
|
|
245
|
+
if threshold is None or battery.get("state") in device_status.CHARGING:
|
|
246
|
+
self.paused.pop(run.id, None)
|
|
247
|
+
control.set_paused(queue, False)
|
|
248
|
+
return
|
|
249
|
+
if threshold is None or run.id in self.kept_going or not eligible(run) or control.paused(queue):
|
|
250
|
+
return # off, kept going by a person, not a long or scheduled task, or a person's own pause stands
|
|
251
|
+
battery = self.phone(self.runtime.phone_for(run), fresh=True).get("battery") or {}
|
|
252
|
+
level, state = battery.get("level"), battery.get("state")
|
|
253
|
+
if type(level) is int and level <= threshold and state in device_status.DRAINING:
|
|
254
|
+
self.paused[run.id] = level
|
|
255
|
+
control.set_paused(queue, True, reason={"code": PHONE_BATTERY, "level": level,
|
|
256
|
+
"limit": BATTERY_PAUSE_LIMIT})
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def eligible(run):
|
|
260
|
+
"""A long task or a scheduled task on Mobster's agent, live (the only ones the battery may pause)."""
|
|
261
|
+
return (getattr(run, "engine", None) == engines.SMART and getattr(run, "mode", None) == "live"
|
|
262
|
+
and (getattr(run, "longform", None) is not None or getattr(run, "origin", None) == "schedule"))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def phone_reader(phone):
|
|
266
|
+
"""device_status.read_phone for a fleet.Phone (the primary's UDID is its device manager's)."""
|
|
267
|
+
kind = getattr(phone, "kind", None)
|
|
268
|
+
udid = getattr(phone, "udid", None)
|
|
269
|
+
manager = getattr(phone, "manager", None)
|
|
270
|
+
if kind == "usb" and not udid and manager is not None:
|
|
271
|
+
try:
|
|
272
|
+
udid = getattr(manager, "pinned", None) or manager.settings().get("udid")
|
|
273
|
+
except Exception: # noqa: BLE001
|
|
274
|
+
udid = None
|
|
275
|
+
return device_status.read_phone(kind, udid=udid, wda_url=getattr(phone, "wda_url", None))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""``premiumModel``: Mobster is free, and Mobster's model is what you pay for. Off until the owner flips it.
|
|
2
|
+
|
|
3
|
+
Off, nothing changes. On, the plan gate (plan_gate.py) lets every task through, and approvals, the journal, the device
|
|
4
|
+
lock and the secret filter go on as they do on any run: they never read this flag."""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# The flip, together with ON in desktop/src-tauri/src/premium_model.rs (test_premium_model pins that they agree).
|
|
10
|
+
ON = False
|
|
11
|
+
DEV_ENV = "MOBSTER_PREMIUM_MODEL"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def enabled(environ=None):
|
|
15
|
+
"""``ON``, or ``MOBSTER_PREMIUM_MODEL=1`` in a source checkout. The frozen agent ignores the variable, because there
|
|
16
|
+
it would open the plan gate."""
|
|
17
|
+
if ON:
|
|
18
|
+
return True
|
|
19
|
+
if getattr(sys, "frozen", False):
|
|
20
|
+
return False
|
|
21
|
+
environ = os.environ if environ is None else environ
|
|
22
|
+
return environ.get(DEV_ENV) == "1"
|