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,308 @@
|
|
|
1
|
+
"""The terminal UI's engine: the same ``server.Runtime`` that `mobster serve` and the Mac app run."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .. import serve, server
|
|
7
|
+
from ..api_errors import APIError
|
|
8
|
+
from ..cli_constants import DEFAULT_WDA_URL
|
|
9
|
+
from ..journal import JournalError, LeaseHeld
|
|
10
|
+
from ..paths import default_history_db, user_data_dir
|
|
11
|
+
from ..transport import wda_screenshot
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def serve_config(**overrides):
|
|
15
|
+
"""A config with exactly the attributes `mobster serve` would parse, then ``overrides``."""
|
|
16
|
+
from ..cli_parser import build_parser
|
|
17
|
+
config = build_parser().parse_args(["serve"])
|
|
18
|
+
for name, value in overrides.items():
|
|
19
|
+
setattr(config, name, value)
|
|
20
|
+
return config
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TerminalRuntime(server.Runtime):
|
|
24
|
+
"""``server.Runtime`` plus the USB iPhone's installed apps when no device manager runs."""
|
|
25
|
+
|
|
26
|
+
one_target = True # its phone is --wda-url / --device; it never reads the device list to choose one
|
|
27
|
+
|
|
28
|
+
def __init__(self, config):
|
|
29
|
+
super().__init__(config)
|
|
30
|
+
if self.installed_apps is None and config.wda_url and _is_usb_relay(config.wda_url):
|
|
31
|
+
from ..device_manager import DeviceManager
|
|
32
|
+
from ..installed_apps import InstalledApps
|
|
33
|
+
data_dir = getattr(config, "data_dir", None) or user_data_dir()
|
|
34
|
+
manager = _phone_manager(data_dir, config.wda_url) or DeviceManager(data_dir, config.wda_url)
|
|
35
|
+
self.installed_apps = InstalledApps(manager.device)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_usb_relay(url):
|
|
39
|
+
"""True for the USB relay's own address (8100), or another iPhone's (8101-8199, devices.py)."""
|
|
40
|
+
from ..devices import FIRST_SLOT, LAST_SLOT, WDA_BASE, port_of
|
|
41
|
+
url = url.rstrip("/")
|
|
42
|
+
port = port_of(url)
|
|
43
|
+
return (url.startswith(("http://127.0.0.1:", "http://localhost:")) and port is not None
|
|
44
|
+
and WDA_BASE <= port <= WDA_BASE + LAST_SLOT and (port == WDA_BASE or port - WDA_BASE >= FIRST_SLOT))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _phone_manager(data_dir, url):
|
|
48
|
+
"""The device manager of the iPhone set up besides the first one whose relay is ``url`` (8101-8199)."""
|
|
49
|
+
from ..devices import DeviceStore, manager_for, port_of
|
|
50
|
+
port = port_of(url)
|
|
51
|
+
entry = next((item for item in DeviceStore(data_dir).phones() if item["wdaPort"] == port), None)
|
|
52
|
+
return manager_for(entry["udid"], data_dir) if entry else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DemoRuntime(server.Runtime):
|
|
56
|
+
"""The real runtime and Mobster's agent over a scripted phone and model (``demo_phone``). No network."""
|
|
57
|
+
|
|
58
|
+
one_target = True
|
|
59
|
+
needs_plan = False # it never acts on a phone (plan_gate)
|
|
60
|
+
pace = 1.0
|
|
61
|
+
|
|
62
|
+
def target_status(self):
|
|
63
|
+
return {"device": True, "ready": True, "health": None, "can_act": True, "driver": "demo",
|
|
64
|
+
"screen_reading": False}
|
|
65
|
+
|
|
66
|
+
def status(self):
|
|
67
|
+
value = super().status()
|
|
68
|
+
value.update(live_enabled=not self.closing, device="Scripted demo iPhone", limitations=[], demo=True)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
def check_app(self, app):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def smart_client(self):
|
|
75
|
+
from .demo_phone import DemoChat
|
|
76
|
+
return DemoChat(self.pace)
|
|
77
|
+
|
|
78
|
+
def open_driver(self, run, trace, setup):
|
|
79
|
+
from .demo_phone import DemoPhone
|
|
80
|
+
driver = setup.hold(DemoPhone(run.app["bundleId"], run.goal, self.pace))
|
|
81
|
+
if run.stop.is_set():
|
|
82
|
+
return None
|
|
83
|
+
run.emit({"event": "app_launch_started", "app": run.app["name"]})
|
|
84
|
+
setup.phase("launching")
|
|
85
|
+
driver._wait(.3)
|
|
86
|
+
run.emit({"event": "app_launch_acknowledged", "app": run.app["name"]})
|
|
87
|
+
setup.phase("reading_app")
|
|
88
|
+
return driver
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Session:
|
|
92
|
+
"""One terminal session: a runtime, its running task and settings. Methods may block,
|
|
93
|
+
so the UI calls them off its event loop."""
|
|
94
|
+
|
|
95
|
+
def __init__(self, *, wda_url=None, env_file=None, demo=False, history_db=None, spend_cap_usd=None, pace=1.0,
|
|
96
|
+
origin="tui"):
|
|
97
|
+
"""``history_db``: its journal (default: the terminal's; a demo keeps tasks in memory). ``origin``:
|
|
98
|
+
"tui" or "cli"."""
|
|
99
|
+
self.demo = demo
|
|
100
|
+
self.origin = origin
|
|
101
|
+
if env_file:
|
|
102
|
+
from ..config import load_env_file
|
|
103
|
+
# "~/keys.env" as typed (`--env-file=~/…` reaches us unexpanded): read and save in the home folder.
|
|
104
|
+
env_file = Path(env_file).expanduser()
|
|
105
|
+
load_env_file(env_file)
|
|
106
|
+
if demo:
|
|
107
|
+
# Its own lease key and no journal: a demo never waits for, or blocks, another Mobster.
|
|
108
|
+
import uuid
|
|
109
|
+
config = serve_config(wda_url=f"demo://phone/{uuid.uuid4().hex[:8]}", enable_live=True,
|
|
110
|
+
state_db=None, env_file=None, spend_cap_usd=spend_cap_usd)
|
|
111
|
+
runtime_class = type("DemoRuntime", (DemoRuntime,), {"pace": pace})
|
|
112
|
+
else:
|
|
113
|
+
import os
|
|
114
|
+
config = serve_config(wda_url=wda_url or os.environ.get("MOBSTER_WDA_URL") or DEFAULT_WDA_URL,
|
|
115
|
+
enable_live=os.environ.get("MOBSTER_ENABLE_LIVE", "1") != "0",
|
|
116
|
+
state_db=history_db if history_db is not None else default_history_db(),
|
|
117
|
+
env_file=env_file, spend_cap_usd=spend_cap_usd)
|
|
118
|
+
runtime_class = serve.runtime_class_for(config, TerminalRuntime)
|
|
119
|
+
self.config = config
|
|
120
|
+
try:
|
|
121
|
+
self.runtime = runtime_class(config)
|
|
122
|
+
except LeaseHeld:
|
|
123
|
+
# The terminal UI and `mobster run` share one task history, and one process at a time may hold it.
|
|
124
|
+
raise LeaseHeld("Another Mobster has this task history open, such as the terminal UI or a `mobster run` "
|
|
125
|
+
"in another terminal. Use that one or wait for it to finish, then try again.") from None
|
|
126
|
+
self.run = None
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def wda_url(self):
|
|
130
|
+
return None if self.demo else self.config.wda_url
|
|
131
|
+
|
|
132
|
+
def status(self):
|
|
133
|
+
return self.runtime.status()
|
|
134
|
+
|
|
135
|
+
def apps(self, fresh=False):
|
|
136
|
+
"""The apps a task can open: the catalog, plus the phone's own when they can be read."""
|
|
137
|
+
installed = getattr(self.runtime, "installed_apps", None)
|
|
138
|
+
if fresh and installed is not None:
|
|
139
|
+
installed.refresh()
|
|
140
|
+
return self.runtime.apps()
|
|
141
|
+
|
|
142
|
+
def settings(self):
|
|
143
|
+
return self.runtime.settings()
|
|
144
|
+
|
|
145
|
+
def set_setting(self, name, value):
|
|
146
|
+
"""askBeforeActing. Saved to the env file when there is one, like the Mac app."""
|
|
147
|
+
return self.runtime.update_settings({name: bool(value)})
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def spend_cap_usd(self):
|
|
151
|
+
return getattr(self.config, "spend_cap_usd", None)
|
|
152
|
+
|
|
153
|
+
def set_spend_cap(self, usd):
|
|
154
|
+
"""A cap in dollars for the next task, or None for no cap."""
|
|
155
|
+
if usd is not None:
|
|
156
|
+
from ..costs import usd_to_nanodollars
|
|
157
|
+
usd_to_nanodollars(usd) # validates
|
|
158
|
+
self.config.spend_cap_usd = usd
|
|
159
|
+
|
|
160
|
+
def _await_phone_release(self):
|
|
161
|
+
"""The last task has published its result; its worker releases the phone a moment later. Wait up to 3 s."""
|
|
162
|
+
previous = self.run
|
|
163
|
+
if previous is None or previous.finished_at is None:
|
|
164
|
+
return
|
|
165
|
+
deadline = time.monotonic() + 3
|
|
166
|
+
while self.runtime.capacity() == 0 and time.monotonic() < deadline:
|
|
167
|
+
with self.runtime.lock:
|
|
168
|
+
if previous.id not in self.runtime.active_runs:
|
|
169
|
+
break
|
|
170
|
+
time.sleep(.02)
|
|
171
|
+
|
|
172
|
+
def start(self, app_id, goal, *, output_format="auto"):
|
|
173
|
+
"""Start a task on the phone. Raises ValueError or APIError with a message for the user."""
|
|
174
|
+
self._await_phone_release()
|
|
175
|
+
run = self.runtime.create(app_id, goal, "live", output_format=output_format, origin=self.origin)
|
|
176
|
+
self.run = run
|
|
177
|
+
return run
|
|
178
|
+
|
|
179
|
+
def threads(self):
|
|
180
|
+
"""The runtime's ThreadService, or None when conversations are not running."""
|
|
181
|
+
from ..threads.service import service_for
|
|
182
|
+
return service_for(self.runtime)
|
|
183
|
+
|
|
184
|
+
def recent_threads(self, limit=30):
|
|
185
|
+
live = self.threads()
|
|
186
|
+
if live is None:
|
|
187
|
+
return []
|
|
188
|
+
return live.store.list(limit=limit)[0]
|
|
189
|
+
|
|
190
|
+
def new_thread(self):
|
|
191
|
+
live = self.threads()
|
|
192
|
+
return live.create_thread() if live is not None else None
|
|
193
|
+
|
|
194
|
+
def send(self, thread_id, text, *, app_id=None, attachments=None, output_format="auto"):
|
|
195
|
+
"""A message to a conversation."""
|
|
196
|
+
live = self.threads()
|
|
197
|
+
body = {"text": text, "outputFormat": output_format, "mode": "auto"}
|
|
198
|
+
if app_id:
|
|
199
|
+
body["appId"] = app_id
|
|
200
|
+
if attachments:
|
|
201
|
+
body["attachmentIds"] = list(attachments)
|
|
202
|
+
self._await_phone_release()
|
|
203
|
+
status, response = live.message(thread_id, body, origin=self.origin)
|
|
204
|
+
routed = response.get("routed") or ("new_run" if response.get("run") else "steer")
|
|
205
|
+
run = None
|
|
206
|
+
if response.get("run"):
|
|
207
|
+
run = self.runtime.runs.get(response["run"]["id"])
|
|
208
|
+
self.run = run
|
|
209
|
+
return routed, run, response
|
|
210
|
+
|
|
211
|
+
def attach(self, path, thread_id=None):
|
|
212
|
+
"""Attach a file for the next task: (attachment id, its name and size in words). Raises ValueError."""
|
|
213
|
+
path = Path(path).expanduser()
|
|
214
|
+
if not path.is_file():
|
|
215
|
+
raise ValueError(f"There's no file at {path}.")
|
|
216
|
+
from ..attachments.service import store_for
|
|
217
|
+
from ..attachments.store import FILE_MAX
|
|
218
|
+
store = store_for(self.runtime)
|
|
219
|
+
if store is None:
|
|
220
|
+
raise ValueError("Attaching files needs Mobster's task history, which this window doesn't keep.")
|
|
221
|
+
if path.stat().st_size > FILE_MAX:
|
|
222
|
+
raise ValueError("Attach files of 25 MB or less.")
|
|
223
|
+
attachment = store.create(path.read_bytes(), path.name, thread_id=thread_id)
|
|
224
|
+
return attachment.public()
|
|
225
|
+
|
|
226
|
+
def stop(self, run=None):
|
|
227
|
+
run = run or self.run
|
|
228
|
+
if run is not None and run.finished_at is None:
|
|
229
|
+
self.runtime.stop_run(run.id)
|
|
230
|
+
|
|
231
|
+
def answer(self, approval_id, approve, choice=None, run=None, remember=False):
|
|
232
|
+
"""Answer the run's pending approval; ``remember``: its "Don't ask again" (Always allow on an app card)."""
|
|
233
|
+
run = run or self.run
|
|
234
|
+
return bool(run and run.answer_approval(approval_id, approve, choice, remember=remember))
|
|
235
|
+
|
|
236
|
+
def pending_approval(self, run=None):
|
|
237
|
+
"""The live approval request (with the exact text to be typed), or None."""
|
|
238
|
+
run = run or self.run
|
|
239
|
+
if run is None:
|
|
240
|
+
return None
|
|
241
|
+
with run.condition:
|
|
242
|
+
return dict(run.approval) if run.approval else None
|
|
243
|
+
|
|
244
|
+
def follow(self, run, on_event, *, stop=None):
|
|
245
|
+
"""Call ``on_event(event)`` for each of the run's events, in order, until it finishes."""
|
|
246
|
+
index = 0
|
|
247
|
+
while stop is None or not stop.is_set():
|
|
248
|
+
with run.condition:
|
|
249
|
+
while index >= len(run.events) and run.finished_at is None and not (stop and stop.is_set()):
|
|
250
|
+
run.condition.wait(timeout=.5)
|
|
251
|
+
batch = run.events[index:]
|
|
252
|
+
finished = run.finished_at is not None
|
|
253
|
+
for event in batch:
|
|
254
|
+
on_event(event)
|
|
255
|
+
index += len(batch)
|
|
256
|
+
if finished and index >= len(run.events):
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
def preview_image(self, run=None):
|
|
260
|
+
"""The phone's latest screen as PNG bytes, or None. Captured the way the Mac app's live view is."""
|
|
261
|
+
import base64
|
|
262
|
+
run = run or self.run
|
|
263
|
+
image = None
|
|
264
|
+
if run is not None and run.finished_at is None:
|
|
265
|
+
image = self.runtime.preview(run).get("image")
|
|
266
|
+
elif run is not None and run.image:
|
|
267
|
+
image = run.image
|
|
268
|
+
if image is None and not self.demo and self.config.wda_url:
|
|
269
|
+
image = wda_screenshot(self.config.wda_url)
|
|
270
|
+
if image is None and self.demo and run is None:
|
|
271
|
+
from .demo_phone import SETTINGS, render_screen
|
|
272
|
+
return render_screen(SETTINGS, "top")
|
|
273
|
+
if isinstance(image, str) and image.startswith("data:image"):
|
|
274
|
+
try:
|
|
275
|
+
return base64.b64decode(image.split(",", 1)[1])
|
|
276
|
+
except (ValueError, IndexError):
|
|
277
|
+
return None
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
def history(self):
|
|
281
|
+
"""Runs in the journal, newest first."""
|
|
282
|
+
with self.runtime.lock:
|
|
283
|
+
runs = list(self.runtime.runs.values())
|
|
284
|
+
return sorted(runs, key=lambda run: run.created_at, reverse=True)
|
|
285
|
+
|
|
286
|
+
def find(self, prefix):
|
|
287
|
+
prefix = (prefix or "").strip().lower()
|
|
288
|
+
matches = [run for run in self.history() if run.id.startswith(prefix)] if prefix else []
|
|
289
|
+
return matches[0] if len(matches) == 1 else None
|
|
290
|
+
|
|
291
|
+
def close(self, timeout=10):
|
|
292
|
+
try:
|
|
293
|
+
if self.run is not None and self.run.finished_at is None:
|
|
294
|
+
self.runtime.stop_run(self.run.id)
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
return self.runtime.close(timeout)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def error_text(error):
|
|
301
|
+
"""A one-line message for an exception from the runtime, for the user."""
|
|
302
|
+
if isinstance(error, (APIError, ValueError, JournalError)):
|
|
303
|
+
text = str(error)
|
|
304
|
+
if getattr(error, "code", None) == "engine_unavailable" and "_API_KEY" in text:
|
|
305
|
+
# The runtime's reason names the env file the Mac app's sidecar reads; here `mobster login` adds a key.
|
|
306
|
+
return "Mobster's agent needs your Claude or OpenAI key first: mobster login adds it."
|
|
307
|
+
return text
|
|
308
|
+
return f"{type(error).__name__}: {str(error)[:200]}"
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""The terminal UI's tasks: starting one, steering it, answering its approvals."""
|
|
2
|
+
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
from textual import work
|
|
5
|
+
from textual.containers import VerticalScroll
|
|
6
|
+
from textual.css.query import NoMatches
|
|
7
|
+
from textual.widgets import Input, Static
|
|
8
|
+
|
|
9
|
+
from ..narrate import status_tone
|
|
10
|
+
from .render import ACCENT, FAINT, GREEN, MUTED, RED, TEXT
|
|
11
|
+
from .screens import AskScreen
|
|
12
|
+
from .widgets import NEXT_PLACEHOLDER, PLACEHOLDER, STEER_PLACEHOLDER, ApprovalCard, Composer, RunView
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TaskMixin:
|
|
16
|
+
"""Running a task for the app: submit it on a worker, show its events, steer it, answer its approvals."""
|
|
17
|
+
|
|
18
|
+
def start_task(self, app, goal):
|
|
19
|
+
if app is not None:
|
|
20
|
+
self.last_app = app
|
|
21
|
+
self.app_choice = None
|
|
22
|
+
self.query_one("#composer", Composer).add_class("busy")
|
|
23
|
+
attachments = [item["id"] for item in self.attachments]
|
|
24
|
+
self.attachments = []
|
|
25
|
+
self.refresh_notice()
|
|
26
|
+
self.submit_task(app, goal, self.output_format, attachments)
|
|
27
|
+
|
|
28
|
+
def default_app(self):
|
|
29
|
+
"""Where a task that names no app starts: the Home Screen; Settings for the demo, which needs an app."""
|
|
30
|
+
if self.demo:
|
|
31
|
+
return self.last_app or next(a for a in self.apps if a["id"] == "settings")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
@work(thread=True, group="run")
|
|
35
|
+
def submit_task(self, app, goal, output_format, attachments=()):
|
|
36
|
+
from .session import error_text
|
|
37
|
+
app = app or self.default_app()
|
|
38
|
+
app_id = app["id"] if app else None
|
|
39
|
+
try:
|
|
40
|
+
threads = self.session.threads()
|
|
41
|
+
if threads is not None:
|
|
42
|
+
thread_id = self.thread_id or self.session.new_thread()["id"]
|
|
43
|
+
if thread_id != self.thread_id:
|
|
44
|
+
self.thread_id = thread_id
|
|
45
|
+
self.remember_thread(thread_id)
|
|
46
|
+
routed, run, response = self.session.send(thread_id, goal, app_id=app_id, attachments=attachments,
|
|
47
|
+
output_format=output_format)
|
|
48
|
+
if routed == "remember":
|
|
49
|
+
self.call_from_thread(self.offer_to_remember, goal, response.get("proposals") or [])
|
|
50
|
+
return
|
|
51
|
+
if run is None:
|
|
52
|
+
self.call_from_thread(self.task_idle)
|
|
53
|
+
return
|
|
54
|
+
else:
|
|
55
|
+
if attachments:
|
|
56
|
+
raise ValueError("Attaching files needs Mobster's task history, which this window doesn't keep.")
|
|
57
|
+
offered = self.remember_here(goal)
|
|
58
|
+
if offered is not None:
|
|
59
|
+
self.call_from_thread(self.offer_to_remember, goal, offered)
|
|
60
|
+
return
|
|
61
|
+
run = self.session.start(app_id, goal, output_format=output_format)
|
|
62
|
+
except Exception as error:
|
|
63
|
+
self.call_from_thread(self.task_refused, app, goal, error_text(error))
|
|
64
|
+
return
|
|
65
|
+
self.call_from_thread(self.task_started, run)
|
|
66
|
+
self.session.follow(run, lambda event: self.call_from_thread(self.task_event, run, event),
|
|
67
|
+
stop=self.follow_stop)
|
|
68
|
+
|
|
69
|
+
def remember_here(self, goal):
|
|
70
|
+
"""Without conversations (the demo): a "Remember that …" message becomes proposals,
|
|
71
|
+
else None to run it as a task."""
|
|
72
|
+
from ..memory import extract
|
|
73
|
+
from ..memory.store import default_store
|
|
74
|
+
found = extract.remember_only(goal)
|
|
75
|
+
if not found:
|
|
76
|
+
return None
|
|
77
|
+
if self.demo:
|
|
78
|
+
return [{"id": None, "text": candidate.text, "pin": candidate.pin} for candidate in found]
|
|
79
|
+
store = default_store()
|
|
80
|
+
if store is None:
|
|
81
|
+
return None
|
|
82
|
+
made = []
|
|
83
|
+
for candidate in found:
|
|
84
|
+
proposal = store.propose(candidate.text, "global", pin=candidate.pin)
|
|
85
|
+
if proposal is not None:
|
|
86
|
+
made.append({"id": proposal["id"], "text": proposal["text"], "pin": proposal["pin"]})
|
|
87
|
+
return made
|
|
88
|
+
|
|
89
|
+
def task_idle(self):
|
|
90
|
+
self.query_one("#composer", Composer).remove_class("busy")
|
|
91
|
+
|
|
92
|
+
def offer_to_remember(self, goal, proposals):
|
|
93
|
+
"""A message that only asked Mobster to remember something: one card per suggestion, Remember or Not now."""
|
|
94
|
+
self.task_idle()
|
|
95
|
+
self.show_welcome(False)
|
|
96
|
+
transcript = self.query_one("#transcript", VerticalScroll)
|
|
97
|
+
line = Text()
|
|
98
|
+
line.append("❯ ", style=f"bold {ACCENT}")
|
|
99
|
+
line.append(goal, style=f"bold {TEXT}")
|
|
100
|
+
box = Static(line, classes="run")
|
|
101
|
+
transcript.mount(box)
|
|
102
|
+
if not proposals:
|
|
103
|
+
self.note("Mobster already has this.")
|
|
104
|
+
return
|
|
105
|
+
pending = list(proposals)
|
|
106
|
+
|
|
107
|
+
def ask_next():
|
|
108
|
+
if not pending:
|
|
109
|
+
return
|
|
110
|
+
proposal = pending.pop(0)
|
|
111
|
+
body = (f"“{proposal['text']}”\n\n" + ("It goes with every task (pinned). " if proposal.get("pin") else
|
|
112
|
+
"Mobster will use it in tasks it fits. ")
|
|
113
|
+
+ "It stays on this Mac.")
|
|
114
|
+
|
|
115
|
+
def answered(yes):
|
|
116
|
+
self.answer_proposal(proposal, bool(yes))
|
|
117
|
+
ask_next()
|
|
118
|
+
self.push_screen(AskScreen("Remember this?", body, "Remember", "Not now"), answered)
|
|
119
|
+
ask_next()
|
|
120
|
+
|
|
121
|
+
@work(thread=True, group="memory")
|
|
122
|
+
def answer_proposal(self, proposal, yes):
|
|
123
|
+
message = "Mobster will remember this." if yes else "Not remembered."
|
|
124
|
+
try:
|
|
125
|
+
from ..memory.store import default_store
|
|
126
|
+
store = default_store()
|
|
127
|
+
if proposal.get("id"):
|
|
128
|
+
store.resolve_proposal(proposal["id"], yes)
|
|
129
|
+
elif yes and not self.demo:
|
|
130
|
+
store.add_fact(proposal["text"], pinned=bool(proposal.get("pin")))
|
|
131
|
+
elif yes and self.demo:
|
|
132
|
+
message = "The demo doesn't save anything; `mobster` would remember this."
|
|
133
|
+
except Exception as error: # noqa: BLE001 -- the store's own sentence
|
|
134
|
+
message = str(error)
|
|
135
|
+
self.call_from_thread(self.proposal_answered, yes, message)
|
|
136
|
+
|
|
137
|
+
def proposal_answered(self, yes, message):
|
|
138
|
+
text = Text()
|
|
139
|
+
text.append(" ✓ " if yes else " – ", style=GREEN if yes else FAINT)
|
|
140
|
+
text.append(message, style=MUTED)
|
|
141
|
+
line = Static(text, classes="item")
|
|
142
|
+
self.query_one("#transcript", VerticalScroll).mount(line)
|
|
143
|
+
self.query_one("#transcript", VerticalScroll).scroll_end(animate=False)
|
|
144
|
+
|
|
145
|
+
def steer(self, text):
|
|
146
|
+
"""A message to the running task: it reads it before its next step (Mobster's agent only)."""
|
|
147
|
+
run = self.running
|
|
148
|
+
if run is None or self.session is None:
|
|
149
|
+
return
|
|
150
|
+
if self.run_view is not None:
|
|
151
|
+
self.run_view.note(f"While Mobster worked, you said “{text}”", MUTED)
|
|
152
|
+
self.send_steer(run, text)
|
|
153
|
+
|
|
154
|
+
@work(thread=True, group="steer")
|
|
155
|
+
def send_steer(self, run, text):
|
|
156
|
+
from .session import error_text
|
|
157
|
+
try:
|
|
158
|
+
if self.thread_id and self.session.threads() is not None:
|
|
159
|
+
self.session.send(self.thread_id, text)
|
|
160
|
+
else:
|
|
161
|
+
run.steer(text, source="tui")
|
|
162
|
+
except Exception as error:
|
|
163
|
+
self.call_from_thread(self.note, error_text(error), "warning")
|
|
164
|
+
|
|
165
|
+
def task_refused(self, app, goal, message):
|
|
166
|
+
self.task_idle()
|
|
167
|
+
transcript = self.query_one("#transcript", VerticalScroll)
|
|
168
|
+
view = Static(classes="run")
|
|
169
|
+
text = Text()
|
|
170
|
+
text.append("❯ ", style=f"bold {ACCENT}")
|
|
171
|
+
text.append(goal, style=f"bold {TEXT}")
|
|
172
|
+
if app:
|
|
173
|
+
text.append(f" {app['name']}", style=FAINT)
|
|
174
|
+
text.append("\n\n")
|
|
175
|
+
text.append("✗ Couldn't start ", style=f"bold {RED}")
|
|
176
|
+
text.append(message + "\n", style=TEXT)
|
|
177
|
+
if self.needs_setup():
|
|
178
|
+
text.append("\n")
|
|
179
|
+
text.append_text(self.setup_hints())
|
|
180
|
+
prompt = self.query_one("#prompt", Input)
|
|
181
|
+
if not prompt.value.strip():
|
|
182
|
+
# The task waits in the box, so enter runs it once what was missing is there.
|
|
183
|
+
prompt.value = goal
|
|
184
|
+
prompt.cursor_position = len(goal)
|
|
185
|
+
text.append("\nYour task is back in the box: press enter when you're set.\n", style=FAINT)
|
|
186
|
+
view.update(text)
|
|
187
|
+
self.show_welcome(False)
|
|
188
|
+
transcript.mount(view)
|
|
189
|
+
transcript.scroll_end(animate=False)
|
|
190
|
+
self.recall_index = None
|
|
191
|
+
|
|
192
|
+
def task_started(self, run):
|
|
193
|
+
self.show_welcome(False)
|
|
194
|
+
self.running = run
|
|
195
|
+
self.run_view = RunView(run, self.apps)
|
|
196
|
+
self.finished_views.append(self.run_view)
|
|
197
|
+
transcript = self.query_one("#transcript", VerticalScroll)
|
|
198
|
+
transcript.mount(self.run_view)
|
|
199
|
+
transcript.scroll_end(animate=False)
|
|
200
|
+
prompt = self.query_one("#prompt", Input)
|
|
201
|
+
prompt.placeholder = STEER_PLACEHOLDER if run.steerable else "Mobster is working… esc stops it"
|
|
202
|
+
self.refresh_chip()
|
|
203
|
+
self.refresh_status()
|
|
204
|
+
self.poll_phone()
|
|
205
|
+
|
|
206
|
+
def task_event(self, run, event):
|
|
207
|
+
view = self.run_view
|
|
208
|
+
if view is None or view.run is not run:
|
|
209
|
+
return
|
|
210
|
+
changed = view.feed(event)
|
|
211
|
+
kind = event.get("event")
|
|
212
|
+
if kind == "approval_requested":
|
|
213
|
+
request = self.session.pending_approval(run)
|
|
214
|
+
if request:
|
|
215
|
+
self.show_approval(request)
|
|
216
|
+
elif kind == "approval_resolved":
|
|
217
|
+
self.hide_approval()
|
|
218
|
+
elif kind == "run_finished":
|
|
219
|
+
self.task_finished(run)
|
|
220
|
+
if changed:
|
|
221
|
+
transcript = self.query_one("#transcript", VerticalScroll)
|
|
222
|
+
if transcript.max_scroll_y - transcript.scroll_y < 6 or kind == "run_finished":
|
|
223
|
+
transcript.scroll_end(animate=False)
|
|
224
|
+
if kind in {"observation", "observation_after_action", "run_finished"} or (
|
|
225
|
+
not self.image_class and kind in {"decision", "action_started", "action_acknowledged"}):
|
|
226
|
+
self.poll_phone()
|
|
227
|
+
self.refresh_status()
|
|
228
|
+
|
|
229
|
+
def task_finished(self, run):
|
|
230
|
+
self.running = None
|
|
231
|
+
self.tasks_done += 1
|
|
232
|
+
self.hide_approval()
|
|
233
|
+
self.task_idle()
|
|
234
|
+
status = run.status
|
|
235
|
+
if self.bell_enabled and status_tone(status) in {"error", "warning"}:
|
|
236
|
+
self.bell()
|
|
237
|
+
self.refresh_status()
|
|
238
|
+
self.refresh_chip()
|
|
239
|
+
self.query_one("#prompt", Input).focus()
|
|
240
|
+
if status == "approval_denied":
|
|
241
|
+
self.query_one("#prompt", Input).placeholder = "Tell Mobster what to do instead… (↑ for the last task)"
|
|
242
|
+
else:
|
|
243
|
+
self.query_one("#prompt", Input).placeholder = NEXT_PLACEHOLDER if self.thread_id else PLACEHOLDER
|
|
244
|
+
|
|
245
|
+
def approval_open(self):
|
|
246
|
+
try:
|
|
247
|
+
return self.query_one(ApprovalCard).has_class("open")
|
|
248
|
+
except NoMatches:
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def show_approval(self, request):
|
|
252
|
+
card = self.query_one(ApprovalCard)
|
|
253
|
+
card.ask(request)
|
|
254
|
+
card.add_class("open")
|
|
255
|
+
card.focus()
|
|
256
|
+
self.refresh_status()
|
|
257
|
+
if self.bell_enabled:
|
|
258
|
+
self.bell()
|
|
259
|
+
|
|
260
|
+
def hide_approval(self):
|
|
261
|
+
card = self.query_one(ApprovalCard)
|
|
262
|
+
if card.has_class("open"):
|
|
263
|
+
card.remove_class("open")
|
|
264
|
+
card.request = None
|
|
265
|
+
self.query_one("#prompt", Input).focus()
|
|
266
|
+
self.refresh_status()
|
|
267
|
+
|
|
268
|
+
def answer_approval(self, request, choice):
|
|
269
|
+
if not request or self.session is None:
|
|
270
|
+
return
|
|
271
|
+
run = self.running
|
|
272
|
+
approve = choice != "__no"
|
|
273
|
+
picked = None if choice in {"__yes", "__always", "__no"} else choice
|
|
274
|
+
self.hide_approval()
|
|
275
|
+
self.send_answer(run, request["id"], approve, picked, remember=choice == "__always")
|
|
276
|
+
|
|
277
|
+
@work(thread=True, group="answer")
|
|
278
|
+
def send_answer(self, run, approval_id, approve, choice, remember=False):
|
|
279
|
+
try:
|
|
280
|
+
self.session.answer(approval_id, approve, choice, run=run, remember=remember)
|
|
281
|
+
except Exception as error:
|
|
282
|
+
from .session import error_text
|
|
283
|
+
self.call_from_thread(self.note, error_text(error), "error")
|
|
284
|
+
|
|
285
|
+
def note(self, message, tone="neutral"):
|
|
286
|
+
self.notify(message, severity={"warning": "warning", "error": "error"}.get(tone, "information"),
|
|
287
|
+
timeout=4)
|