mobster-cli 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""The terminal UI (`mobster`, `mobster tui`). Textual loads only when it opens, so `serve` never imports it."""
|
|
2
|
+
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main(args):
|
|
8
|
+
"""Open the terminal UI for parsed command-line ``args``. Returns an exit code."""
|
|
9
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
10
|
+
print("mobster: the terminal UI needs an interactive terminal. In a script, run the task with "
|
|
11
|
+
"`mobster run \"TASK\" --execute` (JSON lines when piped).", file=sys.stderr)
|
|
12
|
+
return 2
|
|
13
|
+
try:
|
|
14
|
+
from .app import MobsterApp
|
|
15
|
+
except ImportError as error:
|
|
16
|
+
print(f"mobster: the terminal UI is not available in this build ({error.name} is missing). "
|
|
17
|
+
"Give the task in Mobster for Mac (https://mobster.dev/download), or use `mobster run`.",
|
|
18
|
+
file=sys.stderr)
|
|
19
|
+
return 1
|
|
20
|
+
from .images import image_widget
|
|
21
|
+
image_class = image_widget() # before the UI starts: it may ask the terminal what it can draw
|
|
22
|
+
demo = bool(getattr(args, "demo", False))
|
|
23
|
+
wda_url = getattr(args, "wda_url", None)
|
|
24
|
+
env_file = getattr(args, "env_file", None)
|
|
25
|
+
|
|
26
|
+
def open_session():
|
|
27
|
+
# Runs on a worker thread after the first frame: the agent's modules load behind the UI.
|
|
28
|
+
from .session import Session
|
|
29
|
+
return Session(wda_url=wda_url, env_file=env_file, demo=demo)
|
|
30
|
+
|
|
31
|
+
resume = getattr(args, "resume", None)
|
|
32
|
+
app = MobsterApp(open_session, demo=demo, resume=resume,
|
|
33
|
+
app_id=getattr(args, "app", None),
|
|
34
|
+
bell=not getattr(args, "no_bell", False), image_class=image_class,
|
|
35
|
+
task=getattr(args, "task", None), continue_=bool(getattr(args, "continue_", False)),
|
|
36
|
+
thread_id=getattr(args, "thread_id", None))
|
|
37
|
+
with _exit_on_signals(app) as caught:
|
|
38
|
+
app.run()
|
|
39
|
+
from ..style import color_enabled
|
|
40
|
+
summary = app.transcript_summary(color=color_enabled(sys.stdout))
|
|
41
|
+
session = app.session
|
|
42
|
+
if session is not None:
|
|
43
|
+
if session.run is not None and session.run.finished_at is None:
|
|
44
|
+
_say("Stopping the running task at its next safe point…", sys.stderr)
|
|
45
|
+
session.close()
|
|
46
|
+
if summary:
|
|
47
|
+
_say(summary, sys.stdout)
|
|
48
|
+
return 128 + caught[0] if caught else 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _exit_on_signals:
|
|
52
|
+
"""SIGTERM and SIGHUP (a closed terminal window, `kill`) end the UI the way ctrl+d does,
|
|
53
|
+
so Textual restores the terminal."""
|
|
54
|
+
|
|
55
|
+
NAMES = ("SIGTERM", "SIGHUP")
|
|
56
|
+
|
|
57
|
+
def __init__(self, app):
|
|
58
|
+
self.app, self.caught, self.previous = app, [], {}
|
|
59
|
+
|
|
60
|
+
def handler(self, signum, frame):
|
|
61
|
+
self.caught.append(signum)
|
|
62
|
+
loop = getattr(self.app, "_loop", None)
|
|
63
|
+
if len(self.caught) == 1 and loop is not None and not loop.is_closed():
|
|
64
|
+
try:
|
|
65
|
+
loop.call_soon_threadsafe(self.app.exit) # safe from a signal handler: it wakes the loop
|
|
66
|
+
return
|
|
67
|
+
except RuntimeError:
|
|
68
|
+
pass
|
|
69
|
+
raise KeyboardInterrupt
|
|
70
|
+
|
|
71
|
+
def __enter__(self):
|
|
72
|
+
for name in self.NAMES:
|
|
73
|
+
number = getattr(signal, name, None)
|
|
74
|
+
if number is not None:
|
|
75
|
+
self.previous[number] = signal.signal(number, self.handler)
|
|
76
|
+
return self.caught
|
|
77
|
+
|
|
78
|
+
def __exit__(self, *exc):
|
|
79
|
+
for number, old in self.previous.items():
|
|
80
|
+
signal.signal(number, old)
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _say(text, stream):
|
|
85
|
+
"""Print after the UI closed; the terminal may be gone (SIGHUP), and that is no error."""
|
|
86
|
+
try:
|
|
87
|
+
print(text, file=stream, flush=True)
|
|
88
|
+
except (OSError, ValueError):
|
|
89
|
+
pass
|
mobile_agent/tui/app.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"""The Mobster terminal UI: a Textual app over ``session.Session``."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from textual import on, work
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
11
|
+
from textual.css.query import NoMatches
|
|
12
|
+
from textual.widgets import Input, OptionList, Static
|
|
13
|
+
|
|
14
|
+
from ..app_choice import find_app
|
|
15
|
+
from ..catalog import APPS
|
|
16
|
+
from ..narrate import Approval, Launch, Outcome, Step, facts, status_glyph
|
|
17
|
+
from .chrome import PLUG_IN, ChromeMixin
|
|
18
|
+
from .commands import CommandMixin
|
|
19
|
+
from .prompt import PromptMixin
|
|
20
|
+
from .render import APPROVALS
|
|
21
|
+
from .tasks import TaskMixin
|
|
22
|
+
from .theme import APP_CSS, THEME
|
|
23
|
+
from .widgets import ApprovalCard, Composer, PhonePanel, RunView
|
|
24
|
+
|
|
25
|
+
PHONE_MIN_WIDTH = 110 # the phone panel shows by default from this terminal width
|
|
26
|
+
TALL = 30 # from this many rows the header sits one row under the top edge
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MobsterApp(ChromeMixin, PromptMixin, TaskMixin, CommandMixin, App):
|
|
30
|
+
TITLE = "Mobster"
|
|
31
|
+
ENABLE_COMMAND_PALETTE = False
|
|
32
|
+
CSS = APP_CSS
|
|
33
|
+
|
|
34
|
+
BINDINGS = [
|
|
35
|
+
Binding("ctrl+c", "interrupt", show=False, priority=True),
|
|
36
|
+
Binding("ctrl+d", "quit_now", show=False, priority=True),
|
|
37
|
+
Binding("escape", "escape", show=False),
|
|
38
|
+
Binding("shift+tab", "toggle_ask", show=False, priority=True),
|
|
39
|
+
Binding("ctrl+o", "toggle_details", show=False, priority=True),
|
|
40
|
+
Binding("ctrl+s", "toggle_screen", show=False, priority=True),
|
|
41
|
+
Binding("ctrl+r", "history", show=False, priority=True),
|
|
42
|
+
Binding("ctrl+l", "clear", show=False, priority=True),
|
|
43
|
+
Binding("ctrl+n", "new_thread", show=False, priority=True),
|
|
44
|
+
Binding("ctrl+t", "threads", show=False, priority=True),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def __init__(self, session_factory, *, demo=False, resume=None, app_id=None, bell=True,
|
|
48
|
+
image_class=None, task=None, continue_=False, thread_id=None):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.session_factory = session_factory
|
|
51
|
+
# A textual-image widget class when this terminal shows images (tui.images), else None: text.
|
|
52
|
+
self.image_class = image_class
|
|
53
|
+
self.session = None
|
|
54
|
+
self.demo = demo
|
|
55
|
+
self.resume_id = resume
|
|
56
|
+
self.bell_enabled = bell
|
|
57
|
+
self.apps = [dict(app) for app in APPS]
|
|
58
|
+
self.app_choice = find_app(app_id, self.apps) if app_id else None
|
|
59
|
+
# An --app the catalog does not know (an app installed on the phone, or a typo) waits for the phone's
|
|
60
|
+
# own list (apps_ready), and is reported if nothing there matches: never dropped without a word. A list
|
|
61
|
+
# without the phone's apps (WDA still starting) rules nothing out: it is read again once the phone is ready.
|
|
62
|
+
self.requested_app = app_id if app_id and self.app_choice is None else None
|
|
63
|
+
self.requested_app_waiting = self.requested_app_reread = False
|
|
64
|
+
self.last_app = self.app_choice
|
|
65
|
+
self.status = {}
|
|
66
|
+
from ..permissions import from_env as permission_settings
|
|
67
|
+
mode = permission_settings().mode
|
|
68
|
+
self.settings = {"askBeforeActing": mode != "dont_ask", "permissionMode": mode}
|
|
69
|
+
self.settings_writer = asyncio.Lock()
|
|
70
|
+
self.setting_requests = {}
|
|
71
|
+
self.output_format = "auto"
|
|
72
|
+
self.expanded = False
|
|
73
|
+
self.frame = 0
|
|
74
|
+
self.run_view = None
|
|
75
|
+
self.finished_views = [] # this session's tasks, for the summary printed after the UI closes
|
|
76
|
+
self.running = None
|
|
77
|
+
self.follow_stop = threading.Event()
|
|
78
|
+
self.recalls = []
|
|
79
|
+
self.recall_index = None
|
|
80
|
+
self.quit_armed = 0.0
|
|
81
|
+
self.phone_visible = None
|
|
82
|
+
self.phone_busy = False
|
|
83
|
+
self.connect_error = None
|
|
84
|
+
# Conversations: the one tasks go to (None: a new one starts with the next task), -c's request, and the
|
|
85
|
+
# files attached for the next task.
|
|
86
|
+
self.thread_id = thread_id
|
|
87
|
+
self.continue_requested = bool(continue_)
|
|
88
|
+
self.attachments = []
|
|
89
|
+
self.initial_task = task
|
|
90
|
+
self.phone = None # (state, words, name) from the device list: the checklist's iPhone row
|
|
91
|
+
self.tasks_done = 0
|
|
92
|
+
self.update_line = ""
|
|
93
|
+
|
|
94
|
+
def call_from_thread(self, callback, *args, **kwargs):
|
|
95
|
+
"""Discard worker UI updates after shutdown, including callbacks queued before widgets were removed."""
|
|
96
|
+
if not self.is_running:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
def deliver():
|
|
100
|
+
if self.is_running:
|
|
101
|
+
return callback(*args, **kwargs)
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return super().call_from_thread(deliver)
|
|
106
|
+
except RuntimeError:
|
|
107
|
+
if self.is_running:
|
|
108
|
+
raise
|
|
109
|
+
return None # the event loop stopped between the check and dispatch
|
|
110
|
+
|
|
111
|
+
def compose(self) -> ComposeResult:
|
|
112
|
+
yield Static(id="header")
|
|
113
|
+
with Horizontal(id="body"):
|
|
114
|
+
with VerticalScroll(id="transcript", classes="idle"):
|
|
115
|
+
yield Static(self.welcome(), id="welcome")
|
|
116
|
+
yield PhonePanel(self.image_class, id="phone")
|
|
117
|
+
with Vertical(id="bottom"):
|
|
118
|
+
yield OptionList(id="suggest")
|
|
119
|
+
yield Static(id="suggest-hint")
|
|
120
|
+
yield ApprovalCard()
|
|
121
|
+
yield Static(id="notice")
|
|
122
|
+
yield Composer(id="composer")
|
|
123
|
+
yield Static(id="status")
|
|
124
|
+
|
|
125
|
+
def on_mount(self):
|
|
126
|
+
self.register_theme(THEME)
|
|
127
|
+
self.theme = "mobster"
|
|
128
|
+
prompt = self.query_one("#prompt", Input)
|
|
129
|
+
prompt.focus()
|
|
130
|
+
if self.initial_task:
|
|
131
|
+
prompt.value = self.initial_task
|
|
132
|
+
prompt.cursor_position = len(prompt.value)
|
|
133
|
+
self.refresh_chrome()
|
|
134
|
+
self.set_interval(.1, self.tick)
|
|
135
|
+
self.set_interval(2, self.poll_status)
|
|
136
|
+
self.set_interval(1.5, self.poll_phone)
|
|
137
|
+
self.open_session()
|
|
138
|
+
if not self.demo:
|
|
139
|
+
self.check_for_update()
|
|
140
|
+
|
|
141
|
+
def on_resize(self, event):
|
|
142
|
+
self.screen.set_class(event.size.height >= TALL, "tall")
|
|
143
|
+
if self.phone_visible is None:
|
|
144
|
+
self.query_one("#phone").set_class(event.size.width < PHONE_MIN_WIDTH, "hidden")
|
|
145
|
+
self.call_after_refresh(self.poll_phone)
|
|
146
|
+
self.call_after_refresh(self.refresh_layout) # after the resize lands, so self.size is the new one
|
|
147
|
+
|
|
148
|
+
def refresh_layout(self):
|
|
149
|
+
"""The parts that fit themselves to the terminal: the header and the welcome screen."""
|
|
150
|
+
try:
|
|
151
|
+
self.refresh_header()
|
|
152
|
+
self.refresh_welcome()
|
|
153
|
+
except NoMatches:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
@work(thread=True, exclusive=True, group="session")
|
|
157
|
+
def open_session(self):
|
|
158
|
+
try:
|
|
159
|
+
session = self.session_factory()
|
|
160
|
+
except Exception as error:
|
|
161
|
+
from .session import error_text
|
|
162
|
+
self.call_from_thread(self.session_failed, error_text(error))
|
|
163
|
+
return
|
|
164
|
+
self.session = session
|
|
165
|
+
status = settings = None
|
|
166
|
+
try:
|
|
167
|
+
settings = session.settings()
|
|
168
|
+
status = session.status()
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
phone = self.read_phone()
|
|
172
|
+
self.call_from_thread(self.session_ready, status, settings, phone)
|
|
173
|
+
try:
|
|
174
|
+
apps = session.apps()
|
|
175
|
+
except Exception:
|
|
176
|
+
apps = None
|
|
177
|
+
self.call_from_thread(self.apps_ready, apps or self.apps)
|
|
178
|
+
|
|
179
|
+
def session_failed(self, message):
|
|
180
|
+
self.connect_error = message
|
|
181
|
+
self.refresh_chrome()
|
|
182
|
+
self.refresh_welcome()
|
|
183
|
+
|
|
184
|
+
def session_ready(self, status, settings, phone=None):
|
|
185
|
+
if status:
|
|
186
|
+
self.status = status
|
|
187
|
+
if settings:
|
|
188
|
+
self.settings.update(settings)
|
|
189
|
+
self.phone = phone
|
|
190
|
+
history = self.session.history()
|
|
191
|
+
self.recalls = [run.goal for run in reversed(history)][-200:]
|
|
192
|
+
self.tasks_done = sum(1 for run in history if run.finished_at is not None)
|
|
193
|
+
self.refresh_chrome()
|
|
194
|
+
self.refresh_welcome()
|
|
195
|
+
self.poll_phone()
|
|
196
|
+
if self.continue_requested or self.thread_id:
|
|
197
|
+
self.continue_thread(self.thread_id)
|
|
198
|
+
if self.resume_id:
|
|
199
|
+
self.resume(self.resume_id)
|
|
200
|
+
if self.initial_task:
|
|
201
|
+
self.start_initial_task()
|
|
202
|
+
|
|
203
|
+
def start_initial_task(self):
|
|
204
|
+
"""`mobster "TASK"`: run it now when Mobster can."""
|
|
205
|
+
prompt = self.query_one("#prompt", Input)
|
|
206
|
+
if prompt.value.strip() != (self.initial_task or "").strip():
|
|
207
|
+
return # changed meanwhile: it's the person's now
|
|
208
|
+
if self.demo or not self.needs_setup():
|
|
209
|
+
self.initial_task = None
|
|
210
|
+
self.submit_value(prompt.value)
|
|
211
|
+
else:
|
|
212
|
+
self.note("Your task is in the box. It runs when the checklist above is done: press enter then.")
|
|
213
|
+
self.initial_task = None
|
|
214
|
+
|
|
215
|
+
@work(thread=True, exclusive=True, group="apps")
|
|
216
|
+
def fetch_apps(self):
|
|
217
|
+
"""The phone's apps, read now rather than from the cache: the phone has just become ready."""
|
|
218
|
+
try:
|
|
219
|
+
apps = self.session.apps(fresh=True)
|
|
220
|
+
except Exception:
|
|
221
|
+
apps = None
|
|
222
|
+
self.call_from_thread(self.apps_ready, apps or self.apps)
|
|
223
|
+
|
|
224
|
+
def apps_ready(self, apps):
|
|
225
|
+
self.apps = apps
|
|
226
|
+
if self.app_choice:
|
|
227
|
+
self.app_choice = next((a for a in apps if a["id"] == self.app_choice["id"]), self.app_choice)
|
|
228
|
+
if self.requested_app:
|
|
229
|
+
self.resolve_requested_app()
|
|
230
|
+
|
|
231
|
+
def phone_listed_apps(self):
|
|
232
|
+
"""Whether ``self.apps`` holds the phone's own list (its installed apps are marked), not the catalog alone."""
|
|
233
|
+
return self.demo or any(app.get("foundOnPhone") or app.get("installed") is not None for app in self.apps)
|
|
234
|
+
|
|
235
|
+
def resolve_requested_app(self):
|
|
236
|
+
"""Choose the app --app named, or say why not, once the phone's own list can tell."""
|
|
237
|
+
wanted = self.requested_app
|
|
238
|
+
if self.app_choice is not None:
|
|
239
|
+
self.requested_app = None # chosen since, with /app or @
|
|
240
|
+
return
|
|
241
|
+
found = find_app(wanted, self.apps)
|
|
242
|
+
if found is None and not self.phone_listed_apps():
|
|
243
|
+
if not self.status.get("device_ready"):
|
|
244
|
+
if not self.requested_app_waiting:
|
|
245
|
+
self.requested_app_waiting = True
|
|
246
|
+
self.note(f"Couldn't read the phone's apps yet. Mobster looks for {wanted} when the phone "
|
|
247
|
+
"answers.")
|
|
248
|
+
return # status_ready reads them again when the phone is ready
|
|
249
|
+
if not self.requested_app_reread:
|
|
250
|
+
self.requested_app_reread = True
|
|
251
|
+
self.fetch_apps()
|
|
252
|
+
return
|
|
253
|
+
self.requested_app = None
|
|
254
|
+
self.note(f"Couldn't read the phone's apps to find {wanted}. Mobster picks the app from each task; "
|
|
255
|
+
"/app chooses one.", "warning")
|
|
256
|
+
self.refresh_chip()
|
|
257
|
+
return
|
|
258
|
+
self.requested_app = None
|
|
259
|
+
self.app_choice = found
|
|
260
|
+
if found is None:
|
|
261
|
+
self.note(f"No app called {wanted} on this phone. Mobster picks the app from each task; "
|
|
262
|
+
"/app chooses one.", "warning")
|
|
263
|
+
self.refresh_chip()
|
|
264
|
+
|
|
265
|
+
def poll_status(self):
|
|
266
|
+
if self.session is not None and not self.status_polling:
|
|
267
|
+
self.fetch_status()
|
|
268
|
+
|
|
269
|
+
status_polling = False
|
|
270
|
+
|
|
271
|
+
def read_phone(self):
|
|
272
|
+
"""(state, words, name) for the checklist's iPhone row, from the device list; None in the demo."""
|
|
273
|
+
if self.demo or self.session is None:
|
|
274
|
+
return None
|
|
275
|
+
from ..native_helpers import devices_allowed
|
|
276
|
+
if not devices_allowed(): # MOBSTER_NO_DEVICES=1, as in local CI: never list a phone that's plugged in
|
|
277
|
+
return ("unplugged", PLUG_IN, None)
|
|
278
|
+
try:
|
|
279
|
+
from .. import devices
|
|
280
|
+
found = devices.discover(probe=False)
|
|
281
|
+
except Exception:
|
|
282
|
+
return None
|
|
283
|
+
url = (self.session.wda_url or "").rstrip("/")
|
|
284
|
+
mine = next((item for item in found if (item.get("wdaUrl") or "").rstrip("/") == url), None)
|
|
285
|
+
usb = [item for item in found if item.get("kind") == "usb"]
|
|
286
|
+
item = mine or (usb[0] if usb else None)
|
|
287
|
+
if item is None:
|
|
288
|
+
return ("unplugged", PLUG_IN, None)
|
|
289
|
+
name = item.get("name")
|
|
290
|
+
state = item.get("state")
|
|
291
|
+
reason = (item.get("reason") or "").lower()
|
|
292
|
+
if state == "needs_setup" and "trust" in reason:
|
|
293
|
+
return ("trust", "tap Trust on it", name)
|
|
294
|
+
if state == "needs_setup":
|
|
295
|
+
return ("setup", "run mobster setup, or set it up in Mobster for Mac", name)
|
|
296
|
+
if state == "disconnected":
|
|
297
|
+
return ("unplugged", PLUG_IN, name)
|
|
298
|
+
if "lock" in reason:
|
|
299
|
+
return ("locked", "unlock it", name)
|
|
300
|
+
return ("starting", "starting Mobster's helper on it", name)
|
|
301
|
+
|
|
302
|
+
@work(thread=True, group="status")
|
|
303
|
+
def fetch_status(self):
|
|
304
|
+
self.status_polling = True
|
|
305
|
+
try:
|
|
306
|
+
status = self.session.status()
|
|
307
|
+
phone = None if status.get("device_ready") else self.read_phone()
|
|
308
|
+
except Exception:
|
|
309
|
+
status, phone = None, None
|
|
310
|
+
finally:
|
|
311
|
+
self.status_polling = False
|
|
312
|
+
if status:
|
|
313
|
+
self.call_from_thread(self.status_ready, status, phone)
|
|
314
|
+
|
|
315
|
+
def status_ready(self, status, phone=None):
|
|
316
|
+
was_ready = self.status.get("device_ready")
|
|
317
|
+
was_setup = self.needs_setup()
|
|
318
|
+
self.status = status
|
|
319
|
+
self.phone = phone
|
|
320
|
+
if not self.query("#header"):
|
|
321
|
+
return # the UI is closing: a status fetch still in flight lands after its widgets are gone
|
|
322
|
+
self.refresh_chrome()
|
|
323
|
+
if self.requested_app and status.get("device_ready") and not was_ready:
|
|
324
|
+
# The phone has just answered: its apps can be read now, for the app --app named.
|
|
325
|
+
self.requested_app_reread = True
|
|
326
|
+
self.fetch_apps()
|
|
327
|
+
if self.run_view is None:
|
|
328
|
+
self.refresh_welcome()
|
|
329
|
+
if was_setup and not self.needs_setup():
|
|
330
|
+
prompt = self.query_one("#prompt", Input)
|
|
331
|
+
if prompt.value.strip() and self.running is None:
|
|
332
|
+
self.note("Everything's ready. Press enter to run your task.")
|
|
333
|
+
|
|
334
|
+
def tick(self):
|
|
335
|
+
if not self.is_running or self.running is None:
|
|
336
|
+
return
|
|
337
|
+
self.frame += 1
|
|
338
|
+
if self.run_view is not None:
|
|
339
|
+
for view in self.run_view.views.values():
|
|
340
|
+
if view.live:
|
|
341
|
+
view.redraw(self.frame)
|
|
342
|
+
if self.frame % 5 == 0:
|
|
343
|
+
self.refresh_status()
|
|
344
|
+
try:
|
|
345
|
+
card = self.query_one(ApprovalCard)
|
|
346
|
+
except NoMatches: # the timer fires once more while the app closes mid-task, its widgets gone
|
|
347
|
+
return
|
|
348
|
+
if card.has_class("open") and self.frame % 10 == 0:
|
|
349
|
+
card.redraw()
|
|
350
|
+
|
|
351
|
+
@on(Input.Changed, "#prompt")
|
|
352
|
+
def prompt_changed(self, event):
|
|
353
|
+
self.refresh_chip()
|
|
354
|
+
self.suggest(event.value)
|
|
355
|
+
|
|
356
|
+
@on(OptionList.OptionSelected, "#suggest")
|
|
357
|
+
def suggestion_clicked(self):
|
|
358
|
+
self.accept_suggestion()
|
|
359
|
+
self.query_one("#prompt", Input).focus()
|
|
360
|
+
|
|
361
|
+
@on(Input.Submitted, "#prompt")
|
|
362
|
+
def prompt_submitted(self, event):
|
|
363
|
+
value = event.value.strip()
|
|
364
|
+
if not value:
|
|
365
|
+
return
|
|
366
|
+
if value.startswith("/"):
|
|
367
|
+
name, _, argument = value[1:].partition(" ")
|
|
368
|
+
event.input.value = ""
|
|
369
|
+
self.run_command(name.lower(), argument.strip())
|
|
370
|
+
return
|
|
371
|
+
self.submit_value(value)
|
|
372
|
+
|
|
373
|
+
def submit_value(self, value):
|
|
374
|
+
prompt = self.query_one("#prompt", Input)
|
|
375
|
+
if self.running is not None:
|
|
376
|
+
prompt.value = ""
|
|
377
|
+
self.steer(value)
|
|
378
|
+
return
|
|
379
|
+
if self.session is None:
|
|
380
|
+
self.note(self.connect_error or "Still starting. Try again in a moment.", "warning")
|
|
381
|
+
return
|
|
382
|
+
if self.awaiting_requested_app(value):
|
|
383
|
+
# Never run it in an inferred app while the app --app named may still be on the phone.
|
|
384
|
+
self.note(f"Still looking for {self.requested_app} on the phone. Try again when it is ready, or "
|
|
385
|
+
"choose an app with /app.", "warning")
|
|
386
|
+
return
|
|
387
|
+
app = self.target_app(value)
|
|
388
|
+
goal = self.split_mentions(value)[0] or value
|
|
389
|
+
prompt.value = ""
|
|
390
|
+
self.recalls.append(value)
|
|
391
|
+
self.recall_index = None
|
|
392
|
+
self.start_task(app, goal)
|
|
393
|
+
|
|
394
|
+
def action_interrupt(self):
|
|
395
|
+
prompt = self.query_one("#prompt", Input)
|
|
396
|
+
if self.running is not None:
|
|
397
|
+
self.stop_task()
|
|
398
|
+
return
|
|
399
|
+
if prompt.value:
|
|
400
|
+
prompt.value = ""
|
|
401
|
+
return
|
|
402
|
+
now = time.monotonic()
|
|
403
|
+
if now - self.quit_armed < 2:
|
|
404
|
+
self.exit()
|
|
405
|
+
return
|
|
406
|
+
self.quit_armed = now
|
|
407
|
+
self.notify("Press ctrl+c again to quit", timeout=2)
|
|
408
|
+
|
|
409
|
+
def action_quit_now(self):
|
|
410
|
+
self.exit()
|
|
411
|
+
|
|
412
|
+
def action_escape(self):
|
|
413
|
+
if self.suggestions_open():
|
|
414
|
+
self.close_suggestions()
|
|
415
|
+
elif self.running is not None:
|
|
416
|
+
self.stop_task()
|
|
417
|
+
else:
|
|
418
|
+
self.query_one("#prompt", Input).value = ""
|
|
419
|
+
|
|
420
|
+
def stop_task(self):
|
|
421
|
+
run = self.running
|
|
422
|
+
if run is None or self.session is None:
|
|
423
|
+
return
|
|
424
|
+
self.notify("Stopping at the next safe point…", timeout=3)
|
|
425
|
+
threading.Thread(target=self.session.stop, args=(run,), daemon=True).start()
|
|
426
|
+
|
|
427
|
+
def action_toggle_ask(self):
|
|
428
|
+
self.change_setting("askBeforeActing", not self.settings.get("askBeforeActing", True))
|
|
429
|
+
|
|
430
|
+
def change_setting(self, name, value):
|
|
431
|
+
self.settings[name] = value
|
|
432
|
+
if name == "askBeforeActing":
|
|
433
|
+
# The older switch sets the mode (the server keeps a stricter one when it goes back on).
|
|
434
|
+
from ..permissions.policy import mode_for_ask
|
|
435
|
+
self.settings["permissionMode"] = mode_for_ask(value, self.settings.get("permissionMode"))
|
|
436
|
+
self.refresh_status()
|
|
437
|
+
if self.session is not None:
|
|
438
|
+
request = self.setting_requests[name] = object()
|
|
439
|
+
self.save_setting(name, value, request)
|
|
440
|
+
if self.running is not None:
|
|
441
|
+
self.notify("Applies from the next task.", timeout=3)
|
|
442
|
+
|
|
443
|
+
@work(group="settings")
|
|
444
|
+
async def save_setting(self, name, value, request):
|
|
445
|
+
async with self.settings_writer:
|
|
446
|
+
if self.setting_requests.get(name) is not request:
|
|
447
|
+
return # A newer choice already replaced this queued save.
|
|
448
|
+
try:
|
|
449
|
+
settings = await asyncio.to_thread(self.session.set_setting, name, value)
|
|
450
|
+
except Exception as error:
|
|
451
|
+
from .session import error_text
|
|
452
|
+
self.note(error_text(error), "error")
|
|
453
|
+
try:
|
|
454
|
+
settings = await asyncio.to_thread(self.session.settings)
|
|
455
|
+
except Exception:
|
|
456
|
+
return # The save error is already shown; a closed service cannot provide a rollback.
|
|
457
|
+
if self.setting_requests.get(name) is request:
|
|
458
|
+
self.settings[name] = settings[name]
|
|
459
|
+
if name == "askBeforeActing":
|
|
460
|
+
self.settings["permissionMode"] = settings["permissionMode"]
|
|
461
|
+
self.refresh_status()
|
|
462
|
+
|
|
463
|
+
def action_toggle_details(self):
|
|
464
|
+
self.expanded = not self.expanded
|
|
465
|
+
for view in self.query(RunView):
|
|
466
|
+
view.redraw()
|
|
467
|
+
|
|
468
|
+
def action_toggle_screen(self):
|
|
469
|
+
phone = self.query_one("#phone")
|
|
470
|
+
self.phone_visible = phone.has_class("hidden")
|
|
471
|
+
phone.set_class(not self.phone_visible, "hidden")
|
|
472
|
+
if self.phone_visible:
|
|
473
|
+
self.poll_phone()
|
|
474
|
+
|
|
475
|
+
def action_history(self):
|
|
476
|
+
self.run_command("history", "")
|
|
477
|
+
|
|
478
|
+
def action_clear(self):
|
|
479
|
+
for view in self.query(".run"):
|
|
480
|
+
if view is not self.run_view or self.running is None:
|
|
481
|
+
view.remove()
|
|
482
|
+
if self.running is None:
|
|
483
|
+
self.run_view = None
|
|
484
|
+
self.refresh_welcome()
|
|
485
|
+
self.show_welcome(True)
|
|
486
|
+
self.refresh_status()
|
|
487
|
+
|
|
488
|
+
def poll_phone(self):
|
|
489
|
+
"""Refresh the phone pane: the outline from the latest observation, or a new screenshot."""
|
|
490
|
+
try:
|
|
491
|
+
phone = self.query_one("#phone")
|
|
492
|
+
if phone.has_class("hidden") or self.session is None:
|
|
493
|
+
return
|
|
494
|
+
if not self.image_class:
|
|
495
|
+
self.show_outline()
|
|
496
|
+
return
|
|
497
|
+
except NoMatches:
|
|
498
|
+
return # the UI is closing: its interval can fire after the widgets are gone
|
|
499
|
+
if self.phone_busy:
|
|
500
|
+
return
|
|
501
|
+
if self.running is None and self.run_view is not None and self.frame and not self.demo and \
|
|
502
|
+
not self.status.get("device_ready"):
|
|
503
|
+
return
|
|
504
|
+
self.phone_busy = True
|
|
505
|
+
self.fetch_phone()
|
|
506
|
+
|
|
507
|
+
def phone_caption(self):
|
|
508
|
+
view = self.run_view
|
|
509
|
+
if view is not None and view.narrator.screen is not None:
|
|
510
|
+
screen = view.narrator.screen
|
|
511
|
+
return f"{screen.get('app') or view.run.app['name']}, {len(screen['elements'])} elements read"
|
|
512
|
+
return "Scripted screens, drawn locally" if self.demo and self.image_class else ""
|
|
513
|
+
|
|
514
|
+
def show_outline(self):
|
|
515
|
+
view = self.run_view
|
|
516
|
+
screen = view.narrator.screen if view is not None else None
|
|
517
|
+
step = view.narrator.step if view is not None else None
|
|
518
|
+
target = step.target_id if step is not None and step.act in {None, "started"} and \
|
|
519
|
+
step.state in {"thinking", "acting"} else None
|
|
520
|
+
self.query_one(PhonePanel).show_outline(screen, target, self.phone_caption())
|
|
521
|
+
|
|
522
|
+
@work(thread=True, group="phone")
|
|
523
|
+
def fetch_phone(self):
|
|
524
|
+
try:
|
|
525
|
+
png = self.session.preview_image(self.running or (self.run_view.run if self.run_view else None))
|
|
526
|
+
except Exception:
|
|
527
|
+
png = None
|
|
528
|
+
finally:
|
|
529
|
+
self.phone_busy = False
|
|
530
|
+
self.call_from_thread(self.phone_ready, png)
|
|
531
|
+
|
|
532
|
+
def phone_ready(self, png):
|
|
533
|
+
self.query_one(PhonePanel).show_image(png, self.phone_caption())
|
|
534
|
+
|
|
535
|
+
def transcript_summary(self, color=False):
|
|
536
|
+
"""The session, printed to the terminal after the UI closes so it stays in scrollback."""
|
|
537
|
+
from ..style import Palette
|
|
538
|
+
paint = Palette(color)
|
|
539
|
+
roles = {"success": "green", "error": "coral", "warning": "amber", "review": "accent", "neutral": "secondary"}
|
|
540
|
+
lines = []
|
|
541
|
+
for view in self.finished_views:
|
|
542
|
+
narrator = view.narrator
|
|
543
|
+
app = view.run.app.get("name") if view.run.app.get("bundleId") else None
|
|
544
|
+
lines.append(paint("❯ ", "accent", "bold") + paint(view.run.goal, "bold")
|
|
545
|
+
+ (paint(f" {app}", "tertiary") if app else ""))
|
|
546
|
+
outcome = None
|
|
547
|
+
for item in narrator.items:
|
|
548
|
+
if isinstance(item, Launch) and item.done:
|
|
549
|
+
lines.append(" " + paint("●", "green") + f" Opened {item.app}")
|
|
550
|
+
elif isinstance(item, Step) and item.operation and item.operation not in {"DONE", "WAIT", "BLOCKED"}:
|
|
551
|
+
glyph = paint("●", "green" if item.state == "done" else "amber" if item.state == "unchanged"
|
|
552
|
+
else "tertiary")
|
|
553
|
+
lines.append(f" {glyph} {item.title}")
|
|
554
|
+
elif isinstance(item, Approval) and item.decision:
|
|
555
|
+
words = APPROVALS.get(item.decision, (None, "You chose"))[1]
|
|
556
|
+
lines.append(" " + paint("◆", "accent") + f" {words}: {item.title}")
|
|
557
|
+
elif isinstance(item, Outcome):
|
|
558
|
+
outcome = item
|
|
559
|
+
elapsed = narrator.elapsed_ms(time.time() * 1000)
|
|
560
|
+
line = facts(narrator.actions, elapsed, narrator.cost_nanodollars, bool(narrator.priced_calls),
|
|
561
|
+
plan=bool(narrator.plan_calls))
|
|
562
|
+
if outcome is not None:
|
|
563
|
+
glyph = status_glyph(outcome.head_tone)
|
|
564
|
+
lines.append(paint(f"{glyph} {outcome.head}", roles.get(outcome.head_tone, "secondary"), "bold")
|
|
565
|
+
+ " " + paint(line, "tertiary"))
|
|
566
|
+
if outcome.hero:
|
|
567
|
+
lines.append(" " + outcome.hero)
|
|
568
|
+
detail = outcome.proof or outcome.detail
|
|
569
|
+
if detail:
|
|
570
|
+
lines.append(" " + paint(detail, "tertiary"))
|
|
571
|
+
elif narrator.finished_at is None:
|
|
572
|
+
# Still running when the UI closed: `tui.main` stops it at its next safe point.
|
|
573
|
+
lines.append(paint("■ Stopped when you quit", "secondary", "bold") + " " + paint(line, "tertiary"))
|
|
574
|
+
if not self.demo:
|
|
575
|
+
lines.append(" " + paint(f"mobster --resume {view.run.id}", "tertiary"))
|
|
576
|
+
lines.append("")
|
|
577
|
+
while lines and not lines[-1]:
|
|
578
|
+
lines.pop()
|
|
579
|
+
return "\n".join(lines)
|
|
580
|
+
|
|
581
|
+
def on_unmount(self):
|
|
582
|
+
self.follow_stop.set()
|