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,123 @@
|
|
|
1
|
+
"""A finished result leaving Mobster by a person's click: "Send to" a saved destination, and "File as issue" in a
|
|
2
|
+
connected tracker. Both ride the code that already sends (outlets' deliveries, ConnectorService.call), and both are
|
|
3
|
+
the person's: the routes need the Mac app's session."""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from .api_errors import APIError
|
|
9
|
+
from .connectors import client as mcp
|
|
10
|
+
from .connectors import issue as tracker
|
|
11
|
+
from .connectors import service as connector_service
|
|
12
|
+
from .connectors import store as connector_store
|
|
13
|
+
from .connectors.skill import result_text
|
|
14
|
+
from .journal import JournalError
|
|
15
|
+
from .outlets import service as outlet_service
|
|
16
|
+
from .outlets import webhook
|
|
17
|
+
from .secret_filter import redact
|
|
18
|
+
|
|
19
|
+
ISSUE_KEEP = 50
|
|
20
|
+
TITLE_MAX = 120
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _refuse(code, message, status=409):
|
|
24
|
+
return APIError(message, status, code)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def send(runtime, run, destination):
|
|
28
|
+
"""Queue ``run``'s result for the saved destination ``destination`` names:
|
|
29
|
+
the delivery, as the run view shows it."""
|
|
30
|
+
service = outlet_service.service_for(runtime)
|
|
31
|
+
if service is None:
|
|
32
|
+
raise _refuse("unavailable", "Sending results isn't available right now. Restart Mobster, then try again.",
|
|
33
|
+
503)
|
|
34
|
+
if not service.store.destinations():
|
|
35
|
+
raise _refuse("no_destination", "No destination is saved yet. Add Slack, email or a webhook in Settings › "
|
|
36
|
+
"Connectors, then send the result.")
|
|
37
|
+
try:
|
|
38
|
+
row = service.find(destination)
|
|
39
|
+
except ValueError as error:
|
|
40
|
+
raise APIError(str(error), 404, "destination_not_found") from None
|
|
41
|
+
return {"delivery": service.public_delivery(service.enqueue(run.id, row["id"]))}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _text(run):
|
|
45
|
+
"""The issue's title and text from the run's masked result (the same body a webhook gets)."""
|
|
46
|
+
body = webhook.payload(run)
|
|
47
|
+
title = " ".join(f"{run.goal or 'Mobster task'}".split())[:TITLE_MAX]
|
|
48
|
+
lines = [body["text"], "", f"Task: {redact(run.goal or '')}".strip(), f"Status: {body['status']}"]
|
|
49
|
+
if body.get("reason"):
|
|
50
|
+
lines.append(f"Why: {body['reason']}")
|
|
51
|
+
if run.device_name:
|
|
52
|
+
lines.append(f"Device: {run.device_name}")
|
|
53
|
+
lines += ["", f"Mobster run {run.id}"]
|
|
54
|
+
return title, "\n".join(lines)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _note(runtime, run, change):
|
|
58
|
+
with run.condition:
|
|
59
|
+
updated = change([dict(item) for item in run.issues])
|
|
60
|
+
if run.journal:
|
|
61
|
+
run.journal.annotate(run.id, {"issues": updated}) # JournalError: nothing is sent without this note
|
|
62
|
+
run.issues = updated
|
|
63
|
+
runtime.publish("runs", {"event": "run_issue", "runId": run.id})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def file_issue(runtime, run, body):
|
|
67
|
+
"""File ``run``'s result as one issue: the entry the run keeps ({"id", "tracker", "status", "url", ...}).
|
|
68
|
+
The attempt is noted on the run before the tool is called, and an answer that never came is never asked again."""
|
|
69
|
+
if (not isinstance(body, dict) or set(body) - {"tracker", "target", "again"}
|
|
70
|
+
or type(body.get("again", False)) is not bool):
|
|
71
|
+
raise ValueError('Send {"tracker": "GitHub", "target": {"owner": "...", "repo": "..."}} '
|
|
72
|
+
'(tracker, target optional)')
|
|
73
|
+
target = tracker.clean_target(body.get("target"))
|
|
74
|
+
service = connector_service.service_for(runtime)
|
|
75
|
+
if service is None:
|
|
76
|
+
raise _refuse("unavailable", "Connectors aren't available right now. Restart Mobster, then try again.", 503)
|
|
77
|
+
row, refusal = tracker.pick(service.store.all(), body.get("tracker"))
|
|
78
|
+
if refusal:
|
|
79
|
+
raise _refuse(*refusal)
|
|
80
|
+
name = row["name"]
|
|
81
|
+
mine = [item for item in run.issues if item.get("connector") == row["id"]]
|
|
82
|
+
for item in mine:
|
|
83
|
+
if item["status"] in ("filing", "unknown"):
|
|
84
|
+
raise _refuse("outcome_unknown", f"Mobster doesn't know whether {name} got this issue. Check {name} "
|
|
85
|
+
"before filing it again; Mobster won't send it twice on its own.")
|
|
86
|
+
if mine and not body.get("again"):
|
|
87
|
+
raise _refuse("already_filed", f"This result is already filed in {name}. Send again with \"again\": true "
|
|
88
|
+
"to file another.")
|
|
89
|
+
title, text = _text(run)
|
|
90
|
+
chosen = tracker.tool_for(row, target, title, text)
|
|
91
|
+
if chosen is None:
|
|
92
|
+
raise _refuse("tool_missing", f"{name} has no tool Mobster can file an issue with. Test it in Settings › "
|
|
93
|
+
"Connectors, then try again.")
|
|
94
|
+
tool, arguments = chosen
|
|
95
|
+
if connector_store.mode_of(row, tool) == "off":
|
|
96
|
+
raise _refuse("tool_blocked", f"{name}: {tool} is blocked in Settings › Connectors, so nothing was filed.")
|
|
97
|
+
entry = {"id": uuid.uuid4().hex[:12], "connector": row["id"], "tracker": name, "tool": tool, "status": "filing",
|
|
98
|
+
"url": None, "error": None, "at": int(time.time() * 1000)}
|
|
99
|
+
try:
|
|
100
|
+
_note(runtime, run, lambda items: [entry, *items][:ISSUE_KEEP])
|
|
101
|
+
except JournalError:
|
|
102
|
+
raise _refuse("journal_unavailable", "Mobster couldn't save that. Try again.", 503) from None
|
|
103
|
+
status, url, error = "filed", None, None
|
|
104
|
+
try:
|
|
105
|
+
result = service.call(row, tool, arguments)
|
|
106
|
+
text_back = result_text(result)
|
|
107
|
+
if result.get("isError"):
|
|
108
|
+
status, error = "failed", redact(text_back)[:200]
|
|
109
|
+
else:
|
|
110
|
+
url = tracker.link_of(text_back)
|
|
111
|
+
except mcp.OutcomeUnknown:
|
|
112
|
+
status, error = "unknown", f"{name} didn't answer. The issue may exist, so Mobster won't send it again."
|
|
113
|
+
except mcp.ConnectorError as problem:
|
|
114
|
+
status, error = "failed", redact(problem.message)[:200]
|
|
115
|
+
|
|
116
|
+
def settle(items):
|
|
117
|
+
return [{**item, "status": status, "url": url, "error": error} if item["id"] == entry["id"] else item
|
|
118
|
+
for item in items]
|
|
119
|
+
try:
|
|
120
|
+
_note(runtime, run, settle)
|
|
121
|
+
except JournalError:
|
|
122
|
+
run.issues = settle(run.issues) # the answer stays in memory; the note on disk keeps "filing"
|
|
123
|
+
return {"issue": next(item for item in run.issues if item["id"] == entry["id"])}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""One owner for the threads a run starts: a cancel token and a bounded pool of named workers."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from collections import deque
|
|
7
|
+
from concurrent.futures import Future
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger("mobster.run_executor")
|
|
12
|
+
|
|
13
|
+
KEPT = 32 # finished workers ``workers()`` still names, newest last
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CancelToken:
|
|
17
|
+
"""Set once, never cleared. ``CancelToken(parent)`` is set when its parent is, and can be set alone. ``set``,
|
|
18
|
+
``is_set`` and ``wait`` are threading.Event's, so a run's Stop (``Run.stop``) is one: every ``run.stop.set()``
|
|
19
|
+
reaches the tokens and executors made from it."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, parent=None):
|
|
22
|
+
self._event = threading.Event()
|
|
23
|
+
self._lock = threading.Lock()
|
|
24
|
+
self._callbacks = []
|
|
25
|
+
if parent is not None:
|
|
26
|
+
parent.on_cancel(self.set)
|
|
27
|
+
|
|
28
|
+
def set(self):
|
|
29
|
+
with self._lock:
|
|
30
|
+
if self._event.is_set():
|
|
31
|
+
return
|
|
32
|
+
self._event.set()
|
|
33
|
+
callbacks, self._callbacks = self._callbacks, []
|
|
34
|
+
for fn in callbacks:
|
|
35
|
+
_call(fn)
|
|
36
|
+
|
|
37
|
+
def is_set(self):
|
|
38
|
+
return self._event.is_set()
|
|
39
|
+
|
|
40
|
+
def wait(self, timeout=None):
|
|
41
|
+
return self._event.wait(timeout)
|
|
42
|
+
|
|
43
|
+
def on_cancel(self, fn):
|
|
44
|
+
"""Call ``fn()`` once when the token is set, or now when it already is. It runs on the thread that sets the
|
|
45
|
+
token, inside whatever locks that thread holds (Runtime.stop_run holds the runtime's and the run's), so it
|
|
46
|
+
must be short and never wait."""
|
|
47
|
+
with self._lock:
|
|
48
|
+
if not self._event.is_set():
|
|
49
|
+
self._callbacks.append(fn)
|
|
50
|
+
return
|
|
51
|
+
_call(fn)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _call(fn):
|
|
55
|
+
try:
|
|
56
|
+
fn()
|
|
57
|
+
except Exception: # noqa: BLE001 -- a hook never reaches whoever pressed Stop
|
|
58
|
+
log.exception("a cancel hook failed")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Cancelled(RuntimeError):
|
|
62
|
+
"""``RunExecutor.submit`` after its token was set: the run is stopping, so nothing new starts."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class State(StrEnum):
|
|
66
|
+
QUEUED = "queued"
|
|
67
|
+
RUNNING = "running"
|
|
68
|
+
DONE = "done"
|
|
69
|
+
FAILED = "failed"
|
|
70
|
+
CANCELLED = "cancelled"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class Worker:
|
|
75
|
+
name: str
|
|
76
|
+
state: State
|
|
77
|
+
error: str = "" # a failed worker's exception type
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _Job:
|
|
81
|
+
__slots__ = ("name", "fn", "args", "future", "state", "thread")
|
|
82
|
+
|
|
83
|
+
def __init__(self, name, fn, args):
|
|
84
|
+
self.name, self.fn, self.args = name, fn, args
|
|
85
|
+
self.future, self.state, self.thread = Future(), State.QUEUED, None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RunExecutor:
|
|
89
|
+
"""The threads one run starts. At most ``max_workers`` named daemon workers run at once and the rest wait their
|
|
90
|
+
turn. They all read one ``token``, a child of ``parent`` (the run's Stop): ``cancel()`` sets it, drops the work
|
|
91
|
+
still waiting and refuses more, and ``join()`` waits for the work already running."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, parent=None, *, max_workers=4):
|
|
94
|
+
if max_workers < 1:
|
|
95
|
+
raise ValueError("A run executor needs at least one worker")
|
|
96
|
+
self.token = CancelToken(parent)
|
|
97
|
+
self._max = max_workers
|
|
98
|
+
self._condition = threading.Condition()
|
|
99
|
+
self._live = [] # queued and running jobs, oldest first
|
|
100
|
+
self._finished = deque(maxlen=KEPT)
|
|
101
|
+
self.token.on_cancel(self._drop_queued)
|
|
102
|
+
|
|
103
|
+
def submit(self, name, fn, *args):
|
|
104
|
+
"""Run ``fn(*args)`` on a daemon thread named ``name`` and return its Future. What ``fn`` raises never
|
|
105
|
+
reaches the caller: the Future holds it and ``workers()`` names the worker failed. Raises Cancelled once the
|
|
106
|
+
token is set, and the thread's own error when no thread can be started."""
|
|
107
|
+
job = _Job(name, fn, args)
|
|
108
|
+
with self._condition:
|
|
109
|
+
if self.token.is_set():
|
|
110
|
+
raise Cancelled(f"{name} was not started: this run is stopping")
|
|
111
|
+
self._live.append(job)
|
|
112
|
+
if self._running() < self._max:
|
|
113
|
+
try:
|
|
114
|
+
self._start(job)
|
|
115
|
+
except BaseException:
|
|
116
|
+
self._live.remove(job)
|
|
117
|
+
raise
|
|
118
|
+
return job.future
|
|
119
|
+
|
|
120
|
+
def cancel(self):
|
|
121
|
+
self.token.set()
|
|
122
|
+
|
|
123
|
+
def join(self, timeout=None):
|
|
124
|
+
"""Wait until every worker has finished. False when one still runs after ``timeout`` (``workers()`` names
|
|
125
|
+
it), and at once on one of this executor's own workers, which can't wait for itself."""
|
|
126
|
+
deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
|
|
127
|
+
with self._condition:
|
|
128
|
+
while self._live:
|
|
129
|
+
left = None if deadline is None else deadline - time.monotonic()
|
|
130
|
+
if (left is not None and left <= 0) or self._owns(threading.current_thread()):
|
|
131
|
+
return False
|
|
132
|
+
self._condition.wait(left)
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def in_worker(self):
|
|
136
|
+
"""True on one of this executor's worker threads."""
|
|
137
|
+
with self._condition:
|
|
138
|
+
return self._owns(threading.current_thread())
|
|
139
|
+
|
|
140
|
+
def workers(self):
|
|
141
|
+
"""The last KEPT finished workers, then every queued and running one, oldest first."""
|
|
142
|
+
with self._condition:
|
|
143
|
+
return (*self._finished, *(Worker(job.name, job.state) for job in self._live))
|
|
144
|
+
|
|
145
|
+
def _running(self):
|
|
146
|
+
return sum(job.state is State.RUNNING for job in self._live)
|
|
147
|
+
|
|
148
|
+
def _owns(self, thread):
|
|
149
|
+
return any(job.thread is thread for job in self._live)
|
|
150
|
+
|
|
151
|
+
def _start(self, job):
|
|
152
|
+
job.state = State.RUNNING
|
|
153
|
+
job.thread = threading.Thread(target=self._work, args=(job,), name=job.name, daemon=True)
|
|
154
|
+
try:
|
|
155
|
+
job.thread.start()
|
|
156
|
+
except BaseException:
|
|
157
|
+
job.state, job.thread = State.QUEUED, None
|
|
158
|
+
raise
|
|
159
|
+
|
|
160
|
+
def _work(self, job):
|
|
161
|
+
state, error = State.CANCELLED, ""
|
|
162
|
+
if job.future.set_running_or_notify_cancel():
|
|
163
|
+
try:
|
|
164
|
+
value = job.fn(*job.args)
|
|
165
|
+
except BaseException as failure: # noqa: BLE001 -- kept on its Future, never raised into the submitter
|
|
166
|
+
log.warning("run worker %s failed", job.name, exc_info=True)
|
|
167
|
+
job.future.set_exception(failure)
|
|
168
|
+
state, error = State.FAILED, type(failure).__name__
|
|
169
|
+
else:
|
|
170
|
+
job.future.set_result(value)
|
|
171
|
+
state = State.DONE
|
|
172
|
+
with self._condition:
|
|
173
|
+
self._retire(job, state, error)
|
|
174
|
+
self._start_queued()
|
|
175
|
+
self._condition.notify_all()
|
|
176
|
+
|
|
177
|
+
def _start_queued(self):
|
|
178
|
+
for job in [job for job in self._live if job.state is State.QUEUED]:
|
|
179
|
+
if self.token.is_set() or self._running() >= self._max:
|
|
180
|
+
return
|
|
181
|
+
try:
|
|
182
|
+
self._start(job)
|
|
183
|
+
except Exception as failure: # noqa: BLE001 -- no thread to be had: this job fails, the next still waits
|
|
184
|
+
log.warning("run worker %s could not start", job.name, exc_info=True)
|
|
185
|
+
if job.future.set_running_or_notify_cancel():
|
|
186
|
+
job.future.set_exception(failure)
|
|
187
|
+
self._retire(job, State.FAILED, type(failure).__name__)
|
|
188
|
+
|
|
189
|
+
def _drop_queued(self):
|
|
190
|
+
with self._condition:
|
|
191
|
+
for job in [job for job in self._live if job.state is State.QUEUED]:
|
|
192
|
+
job.future.cancel()
|
|
193
|
+
self._retire(job, State.CANCELLED)
|
|
194
|
+
self._condition.notify_all()
|
|
195
|
+
|
|
196
|
+
def _retire(self, job, state, error=""):
|
|
197
|
+
"""Move ``job`` to the finished list, dropping what it held: a parked worker must not keep a run alive."""
|
|
198
|
+
self._live.remove(job)
|
|
199
|
+
job.fn = job.args = job.thread = None
|
|
200
|
+
self._finished.append(Worker(job.name, state, error))
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""`mobster export ID --gif [--mp4] [--redact] [-o PATH]`: save a finished task as a captioned GIF or MP4, or
|
|
2
|
+
`mobster export ID --trace [--screens]`: as a trace bundle (traces.py)."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sqlite3
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .paths import PACKAGE, source_checkout, user_data_dir
|
|
11
|
+
|
|
12
|
+
FRAME_ID = re.compile(r"f[0-9a-f]{10}")
|
|
13
|
+
RUN_ID = re.compile(r"[0-9a-f]{4,12}")
|
|
14
|
+
ACTIVE = ("queued", "running")
|
|
15
|
+
REDACT_HINT = "It shows every screen of the task. Check it before you post it, or save it again with --redact."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExportError(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def journals():
|
|
23
|
+
folders = []
|
|
24
|
+
if source_checkout():
|
|
25
|
+
folders.append(PACKAGE / ".state")
|
|
26
|
+
folders.append(user_data_dir() / "state")
|
|
27
|
+
paths = [folder / name for folder in folders for name in ("terminal.sqlite3", "mobster.sqlite3")]
|
|
28
|
+
found = [path for path in dict.fromkeys(paths) if path.is_file()]
|
|
29
|
+
return sorted(found, key=lambda path: path.stat().st_mtime, reverse=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _connect(path):
|
|
33
|
+
return sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=2)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _events(connection, run_id):
|
|
37
|
+
events = []
|
|
38
|
+
for (data,) in connection.execute("SELECT data FROM events WHERE run_id=? ORDER BY seq", (run_id,)):
|
|
39
|
+
try:
|
|
40
|
+
event = json.loads(data)
|
|
41
|
+
except ValueError:
|
|
42
|
+
continue
|
|
43
|
+
if isinstance(event, dict):
|
|
44
|
+
events.append(event)
|
|
45
|
+
return events
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_run(identifier, paths=None):
|
|
49
|
+
paths = journals() if paths is None else paths
|
|
50
|
+
identifier = (identifier or "").strip().lower()
|
|
51
|
+
latest = identifier == "latest"
|
|
52
|
+
if not latest and not RUN_ID.fullmatch(identifier):
|
|
53
|
+
raise ExportError(f"“{identifier}” isn’t a task ID. List your tasks with `mobster history`.")
|
|
54
|
+
matches = []
|
|
55
|
+
for path in paths:
|
|
56
|
+
try:
|
|
57
|
+
connection = _connect(path)
|
|
58
|
+
except sqlite3.Error:
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
if latest:
|
|
62
|
+
rows = connection.execute("SELECT id, data FROM runs ORDER BY rowid DESC LIMIT 50").fetchall()
|
|
63
|
+
else:
|
|
64
|
+
rows = connection.execute("SELECT id, data FROM runs WHERE substr(id, 1, ?) = ?",
|
|
65
|
+
(len(identifier), identifier)).fetchall()
|
|
66
|
+
for run_id, data in rows:
|
|
67
|
+
try:
|
|
68
|
+
record = json.loads(data)
|
|
69
|
+
except ValueError:
|
|
70
|
+
continue
|
|
71
|
+
if not isinstance(record, dict) or (latest and record.get("status") in ACTIVE):
|
|
72
|
+
continue
|
|
73
|
+
matches.append((record, path, run_id))
|
|
74
|
+
except sqlite3.Error:
|
|
75
|
+
continue
|
|
76
|
+
finally:
|
|
77
|
+
connection.close()
|
|
78
|
+
if latest:
|
|
79
|
+
matches.sort(key=lambda item: item[0].get("createdAt") or 0, reverse=True)
|
|
80
|
+
matches = matches[:1]
|
|
81
|
+
if not matches:
|
|
82
|
+
what = "No finished task" if latest else f"No task {identifier}"
|
|
83
|
+
raise ExportError(f"{what} in your history. List your tasks with `mobster history`.")
|
|
84
|
+
if len({run_id for _, _, run_id in matches}) > 1:
|
|
85
|
+
raise ExportError(f"Several tasks start with {identifier}. Give more of the ID.")
|
|
86
|
+
record, path, run_id = matches[0]
|
|
87
|
+
connection = _connect(path)
|
|
88
|
+
try:
|
|
89
|
+
record = {**record, "events": _events(connection, run_id)}
|
|
90
|
+
finally:
|
|
91
|
+
connection.close()
|
|
92
|
+
return record, path
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def run_folder(journal, run_id):
|
|
96
|
+
"""Where the run's frames and tape are, beside its journal."""
|
|
97
|
+
return Path(journal).parent / "frames" / run_id
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def frame_reader(journal, run_id):
|
|
101
|
+
folder = run_folder(journal, run_id)
|
|
102
|
+
|
|
103
|
+
def read(frame_id):
|
|
104
|
+
if not FRAME_ID.fullmatch(frame_id or ""):
|
|
105
|
+
return None
|
|
106
|
+
try:
|
|
107
|
+
return (folder / f"{frame_id}.jpg").read_bytes()
|
|
108
|
+
except OSError:
|
|
109
|
+
return None
|
|
110
|
+
return read
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def targets(output, stem, want_gif, want_mp4):
|
|
114
|
+
if output is None or output.is_dir() or str(output).endswith("/"):
|
|
115
|
+
folder = output if output is not None else Path.home() / "Downloads"
|
|
116
|
+
return {ext: (folder, stem, False) for ext, wanted in (("gif", want_gif), ("mp4", want_mp4)) if wanted}
|
|
117
|
+
base = output.with_suffix("") if output.suffix.lower() in (".gif", ".mp4") else output
|
|
118
|
+
return {ext: (base.parent, base.name, True) for ext, wanted in (("gif", want_gif), ("mp4", want_mp4)) if wanted}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _write(folder, stem, extension, data, overwrite):
|
|
122
|
+
from .api_errors import APIError
|
|
123
|
+
from .downloads import save_export_bytes
|
|
124
|
+
if overwrite:
|
|
125
|
+
target = Path(folder) / f"{stem}.{extension}"
|
|
126
|
+
try:
|
|
127
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
target.write_bytes(data)
|
|
129
|
+
except OSError as error:
|
|
130
|
+
raise ExportError(f"Couldn’t write {target}: {error.strerror or error}.") from None
|
|
131
|
+
return {"name": target.name, "path": str(target)}
|
|
132
|
+
try:
|
|
133
|
+
return save_export_bytes(stem, extension, data, folder)
|
|
134
|
+
except APIError as error:
|
|
135
|
+
raise ExportError(str(error)) from None
|
|
136
|
+
except OSError as error:
|
|
137
|
+
raise ExportError(f"Couldn’t write to {folder}: {error.strerror or error}.") from None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _size(count):
|
|
141
|
+
return f"{count / 1_000_000:.1f} MB" if count >= 100_000 else f"{max(1, round(count / 1000))} KB"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _home(path):
|
|
145
|
+
home = str(Path.home())
|
|
146
|
+
return "~" + path[len(home):] if path.startswith(home + "/") else path
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def to_eval(args, stream=None, errors=None):
|
|
150
|
+
"""`mobster history --to-eval ID`: a finished task into the local eval set, beside its journal
|
|
151
|
+
(traces.add_to_evals).
|
|
152
|
+
Exit 0 added (or already there), 1 couldn't."""
|
|
153
|
+
from . import traces
|
|
154
|
+
stream, errors = stream or sys.stdout, errors or sys.stderr
|
|
155
|
+
try:
|
|
156
|
+
record, journal = find_run(args.to_eval)
|
|
157
|
+
try:
|
|
158
|
+
result = traces.add_to_evals(record, run_folder(journal, record.get("id") or ""),
|
|
159
|
+
Path(journal).parent / "evals")
|
|
160
|
+
except traces.EvalError as error:
|
|
161
|
+
raise ExportError(str(error)) from None
|
|
162
|
+
except OSError as error:
|
|
163
|
+
raise ExportError(f"Couldn’t write the eval: {error.strerror or error}.") from None
|
|
164
|
+
except ExportError as error:
|
|
165
|
+
errors.write(f"mobster history: {error}\n")
|
|
166
|
+
return 1
|
|
167
|
+
if args.json:
|
|
168
|
+
stream.write(json.dumps({"ok": True, **result}, ensure_ascii=False) + "\n")
|
|
169
|
+
return 0
|
|
170
|
+
expected = result["expected"]
|
|
171
|
+
stream.write(f"{'Added' if result['added'] else 'Already in your evals:'} {result['id']}\n")
|
|
172
|
+
stream.write("It failed, so a fix is a change in how it acts: `python -m mobile_agent.bench evals` replays it "
|
|
173
|
+
"offline.\n" if expected["replay"] == "changes" else
|
|
174
|
+
"It finished, so a change must keep it acting the same: `python -m mobile_agent.bench evals` "
|
|
175
|
+
"replays it offline.\n")
|
|
176
|
+
stream.write(f"The eval set is {_home(str(Path(result['path']).parent))}\n")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def export_trace(args, stream, errors):
|
|
181
|
+
"""``--trace``: the run's bundle as a zip in -o (a folder, or a file name) or ~/Downloads, never over a file
|
|
182
|
+
that is there. Exit 0 saved, 1 couldn't."""
|
|
183
|
+
from . import traces
|
|
184
|
+
try:
|
|
185
|
+
record, journal = find_run(args.run_id)
|
|
186
|
+
if record.get("status") in ACTIVE:
|
|
187
|
+
raise ExportError("This task is still running. Save it once it ends.")
|
|
188
|
+
run_id = record.get("id") or ""
|
|
189
|
+
data, manifest = traces.bundle(record, run_folder(journal, run_id),
|
|
190
|
+
read_frame=frame_reader(journal, run_id) if args.screens else None)
|
|
191
|
+
output = args.output
|
|
192
|
+
try:
|
|
193
|
+
if output is not None and not output.is_dir() and not str(output).endswith("/"):
|
|
194
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
output.write_bytes(data)
|
|
196
|
+
saved = output
|
|
197
|
+
else:
|
|
198
|
+
saved = traces.save(data, output if output is not None else Path.home() / "Downloads",
|
|
199
|
+
f"mobster-trace-{run_id}")
|
|
200
|
+
except OSError as error:
|
|
201
|
+
raise ExportError(f"Couldn’t write the bundle: {error.strerror or error}.") from None
|
|
202
|
+
except ExportError as error:
|
|
203
|
+
if args.json:
|
|
204
|
+
stream.write(json.dumps({"ok": False, "error": str(error)}) + "\n")
|
|
205
|
+
else:
|
|
206
|
+
errors.write(f"mobster export: {error}\n")
|
|
207
|
+
return 1
|
|
208
|
+
result = {"ok": True, "id": run_id, "trace": {"path": str(saved), "bytes": len(data), "tape": manifest["tape"],
|
|
209
|
+
"screens": manifest["screens"]}}
|
|
210
|
+
if args.json:
|
|
211
|
+
stream.write(json.dumps(result, ensure_ascii=False) + "\n")
|
|
212
|
+
return 0
|
|
213
|
+
tape = "with its tape" if manifest["tape"]["included"] else "without a tape"
|
|
214
|
+
stream.write(f"Saved {saved.name} ({_size(len(data))}, {tape}) to {_home(str(saved.parent))}\n")
|
|
215
|
+
if not manifest["tape"]["included"]:
|
|
216
|
+
stream.write(manifest["tape"]["why"][0].upper() + manifest["tape"]["why"][1:] + ".\n")
|
|
217
|
+
stream.write(("It holds the task's text and the text on its screens, and the screenshots, which can't be masked. "
|
|
218
|
+
if args.screens else "It holds the task's text and the text on its screens. ")
|
|
219
|
+
+ "Keys, codes and passwords are masked. Read it before you share it.\n")
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def export(args, stream=None, errors=None):
|
|
224
|
+
from . import run_gif
|
|
225
|
+
stream, errors = stream or sys.stdout, errors or sys.stderr
|
|
226
|
+
if args.trace:
|
|
227
|
+
if args.gif or args.mp4:
|
|
228
|
+
errors.write("mobster export: --trace saves a bundle; save a GIF or an MP4 in another command.\n")
|
|
229
|
+
return 2
|
|
230
|
+
return export_trace(args, stream, errors)
|
|
231
|
+
if args.screens:
|
|
232
|
+
errors.write("mobster export: --screens goes with --trace.\n")
|
|
233
|
+
return 2
|
|
234
|
+
if not args.gif and not args.mp4:
|
|
235
|
+
errors.write("mobster export: say what to save: --gif, --mp4, --trace, or a GIF and an MP4. Example: "
|
|
236
|
+
"mobster export latest --gif\n")
|
|
237
|
+
return 2
|
|
238
|
+
try:
|
|
239
|
+
record, journal = find_run(args.run_id)
|
|
240
|
+
if record.get("status") in ACTIVE:
|
|
241
|
+
raise ExportError("This task is still running. Save it once it ends.")
|
|
242
|
+
read = frame_reader(journal, record.get("id") or "")
|
|
243
|
+
ffmpeg = run_gif.ffmpeg_path() if args.mp4 else None
|
|
244
|
+
if args.mp4 and ffmpeg is None:
|
|
245
|
+
raise ExportError("--mp4 needs ffmpeg. Install it with `brew install ffmpeg`, or save a GIF with --gif.")
|
|
246
|
+
stem = run_gif.file_stem(record.get("goal"))
|
|
247
|
+
places = targets(args.output, stem, args.gif, args.mp4)
|
|
248
|
+
result = {"ok": True, "id": record.get("id"), "gif": None, "mp4": None}
|
|
249
|
+
if args.gif:
|
|
250
|
+
try:
|
|
251
|
+
clip = run_gif.build_clip(record, read, theme=args.theme, redact=args.redact)
|
|
252
|
+
except ValueError as error:
|
|
253
|
+
raise ExportError(str(error)) from None
|
|
254
|
+
folder, name, overwrite = places["gif"]
|
|
255
|
+
saved = _write(folder, name, "gif", clip.gif, overwrite)
|
|
256
|
+
result["gif"] = {**saved, "bytes": len(clip.gif), "frames": clip.frames, "seconds": round(clip.seconds, 1)}
|
|
257
|
+
stem = Path(saved["name"]).stem
|
|
258
|
+
if args.mp4:
|
|
259
|
+
try:
|
|
260
|
+
data = run_gif.build_mp4(record, read, theme=args.theme, redact=args.redact, ffmpeg=ffmpeg)
|
|
261
|
+
except ValueError as error:
|
|
262
|
+
raise ExportError(str(error)) from None
|
|
263
|
+
except (RuntimeError, OSError) as error:
|
|
264
|
+
raise ExportError(str(error)) from None
|
|
265
|
+
folder, name, overwrite = places["mp4"]
|
|
266
|
+
saved = _write(folder, stem if args.gif and not overwrite else name, "mp4", data, overwrite)
|
|
267
|
+
result["mp4"] = {**saved, "bytes": len(data)}
|
|
268
|
+
except ExportError as error:
|
|
269
|
+
if args.json:
|
|
270
|
+
stream.write(json.dumps({"ok": False, "error": str(error)}) + "\n")
|
|
271
|
+
else:
|
|
272
|
+
errors.write(f"mobster export: {error}\n")
|
|
273
|
+
return 1
|
|
274
|
+
if args.json:
|
|
275
|
+
stream.write(json.dumps(result, ensure_ascii=False) + "\n")
|
|
276
|
+
return 0
|
|
277
|
+
for kind in ("gif", "mp4"):
|
|
278
|
+
saved = result[kind]
|
|
279
|
+
if saved:
|
|
280
|
+
facts = _size(saved["bytes"]) + (f", {saved['seconds']:g} s" if kind == "gif" else "")
|
|
281
|
+
stream.write(f"Saved {saved['name']} ({facts}) to {_home(str(Path(saved['path']).parent))}\n")
|
|
282
|
+
if not args.redact:
|
|
283
|
+
stream.write(REDACT_HINT + "\n")
|
|
284
|
+
return 0
|