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,84 @@
|
|
|
1
|
+
"""Set up checks in a project: `.mobster/` with its `.gitignore` and a starter check for the project's app.
|
|
2
|
+
|
|
3
|
+
The starter check opens the app and expects its first screen to show something with a label. It passes when the app
|
|
4
|
+
launches, stays in front and doesn't crash, and it needs no model and no key. It is a normal check file
|
|
5
|
+
(docs/checks.mdx), checked as `mobster test` checks one before it is written, and Mobster never writes over a file:
|
|
6
|
+
a project with checks already is refused.
|
|
7
|
+
|
|
8
|
+
created = create(root, apps) # [".mobster/.gitignore", ".mobster/checks/app-opens.yaml"]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from ..api_errors import APIError
|
|
14
|
+
from . import folder as folders
|
|
15
|
+
|
|
16
|
+
STARTER = "app-opens.yaml"
|
|
17
|
+
HEADER = ("# A Mobster check: it opens {name} and passes when its first screen shows. No model, no key.\n"
|
|
18
|
+
"# Run checks in Mobster for Mac builds the app, then runs it; `mobster test` runs it on the app the "
|
|
19
|
+
"simulator has.\n")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def starter(app):
|
|
23
|
+
"""The starter check for ``app`` (folder.apps' {kind, name, path, bundle}) as file data."""
|
|
24
|
+
return {"version": 1, "name": f"{app['name']} opens to its first screen"[:120], "app": {"bundle": app["bundle"]},
|
|
25
|
+
"tags": ["smoke"], "expect": [{"visible": {"label": "/.+/"}, "name": "The first screen shows something"}]}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def pick(apps, wanted=None):
|
|
29
|
+
"""The app the starter check is for: the one at ``wanted`` (its path in folder.apps), else the first with a bundle
|
|
30
|
+
id. APIError with what to do when there's none."""
|
|
31
|
+
if wanted is not None:
|
|
32
|
+
app = next((app for app in apps if app["path"] == wanted), None)
|
|
33
|
+
if app is None:
|
|
34
|
+
raise APIError("This project has no app at that path. Choose one of the apps on its page.", 400, "no_app")
|
|
35
|
+
else:
|
|
36
|
+
app = next((app for app in apps if app.get("bundle")), apps[0] if apps else None)
|
|
37
|
+
if app is None:
|
|
38
|
+
raise APIError("Mobster found no Xcode project or Expo app in this folder, so it can't write a check for one. "
|
|
39
|
+
"Add one with: mobster test add smoke --bundle YOUR.BUNDLE.ID", 409, "no_app")
|
|
40
|
+
if not app.get("bundle"):
|
|
41
|
+
raise APIError(f"Mobster couldn't read {app['name']}'s bundle id from its project. Add a check with: "
|
|
42
|
+
"mobster test add smoke --bundle YOUR.BUNDLE.ID", 409, "no_bundle")
|
|
43
|
+
return app
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create(root, apps, *, wanted=None, home=None):
|
|
47
|
+
"""Write `.mobster/.gitignore` (unless there is one) and `.mobster/checks/app-opens.yaml`. Returns the paths it
|
|
48
|
+
wrote, relative to the project. APIError when the project has checks already or the folder can't be written."""
|
|
49
|
+
import yaml
|
|
50
|
+
|
|
51
|
+
from ..verify.assertions import CheckError
|
|
52
|
+
from ..verify.checks import check_from_dict
|
|
53
|
+
from ..verify.runs import ensure_gitignore
|
|
54
|
+
root = Path(root)
|
|
55
|
+
if folders.checks(root):
|
|
56
|
+
raise APIError("This project has checks already, in .mobster/checks. Run them, or add one with mobster test "
|
|
57
|
+
"add.", 409, "has_checks")
|
|
58
|
+
app = pick(apps, wanted)
|
|
59
|
+
data = starter(app)
|
|
60
|
+
try:
|
|
61
|
+
check_from_dict(data, base_dir=root)
|
|
62
|
+
except CheckError as error: # the bundle id the project file holds isn't one a check takes
|
|
63
|
+
raise APIError(f"Mobster couldn't write a check for {app['name']}: {error}.", 409, "no_bundle") from None
|
|
64
|
+
text = HEADER.format(name=app["name"]) + yaml.safe_dump(data, sort_keys=False, allow_unicode=True,
|
|
65
|
+
default_flow_style=False, width=100)
|
|
66
|
+
mobster = root / ".mobster"
|
|
67
|
+
target = mobster / "checks" / STARTER
|
|
68
|
+
created = []
|
|
69
|
+
try:
|
|
70
|
+
had_ignore = (mobster / ".gitignore").exists()
|
|
71
|
+
ensure_gitignore(mobster / "runs")
|
|
72
|
+
if not had_ignore and (mobster / ".gitignore").exists():
|
|
73
|
+
created.append(".mobster/.gitignore")
|
|
74
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
with open(target, "x", encoding="utf-8") as handle:
|
|
76
|
+
handle.write(text)
|
|
77
|
+
except FileExistsError:
|
|
78
|
+
raise APIError(f".mobster/checks/{STARTER} is there already, and Mobster never writes over a file. Run it, or "
|
|
79
|
+
"move it.", 409, "has_checks") from None
|
|
80
|
+
except OSError as error:
|
|
81
|
+
raise APIError(f"Mobster can't write in {folders.shown(mobster, home)} ({error.strerror or 'refused'}). Check "
|
|
82
|
+
"the folder's permissions, then try again.", 403, "unwritable") from None
|
|
83
|
+
created.append(f".mobster/checks/{STARTER}")
|
|
84
|
+
return created
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Projects, approved test revisions and their durable requests in the private run journal.
|
|
2
|
+
|
|
3
|
+
One row per project: its id, its folder (absolute and resolved, one row per folder), its name, how it came in
|
|
4
|
+
(``source``, JSON: {"kind": "folder"} or {"kind": "clone", "url"}) and when it was added and last opened. Nothing
|
|
5
|
+
read from the folder is kept here; folder.py reads it each time.
|
|
6
|
+
|
|
7
|
+
Each project keeps its newest RUN_KEEP check runs. An older finished run is pruned and leaves only its request key
|
|
8
|
+
(hashed) with a summary, so a retry with that key replays the summary, never a second run, until KEY_DAYS after the
|
|
9
|
+
run started. After that the key is new.
|
|
10
|
+
|
|
11
|
+
Writes go through ``Journal.transaction()``, reads hold ``Journal.lock``. Times are seconds here; the API says
|
|
12
|
+
milliseconds.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import math
|
|
18
|
+
import sqlite3
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from ..journal import JournalError
|
|
22
|
+
|
|
23
|
+
SCHEMA = "projects"
|
|
24
|
+
VERSION = 2
|
|
25
|
+
MAX_PROJECTS = 100
|
|
26
|
+
RUN_KEEP = 2_000 # check runs a project keeps; an older finished one leaves only its request key
|
|
27
|
+
KEY_DAYS = 90 # how long a pruned run's request key refuses a retry, from when the run started
|
|
28
|
+
PRUNE_BATCH = 200 # rows one prune removes, so admission never waits on a long one
|
|
29
|
+
MAX_REVISIONS = 100 # approved revisions a project keeps; the oldest older one no kept run names makes room
|
|
30
|
+
RESULT_KEEP = 100
|
|
31
|
+
RETIRED = "id, owner, request_id, mode, project_id, grant_id, revision, state, data" # what _retire reads of a run
|
|
32
|
+
SUMMARY = ("id", "projectId", "mode", "state", "exitCode", "code", "startedAt", "endedAt", "done", "total", "summary",
|
|
33
|
+
"stopRequested", "choice") # what a pruned run's key replays
|
|
34
|
+
UNFINISHED = "('building', 'running', 'stopping')" # a row in one of these states may still change
|
|
35
|
+
COLUMNS = ("id", "path", "name", "source", "added_at", "opened_at")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def migrate(connection, from_version):
|
|
39
|
+
"""Create projects, then add durable check runs and immutable approved revisions."""
|
|
40
|
+
if from_version < 1:
|
|
41
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS projects (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
path TEXT NOT NULL UNIQUE,
|
|
44
|
+
name TEXT NOT NULL,
|
|
45
|
+
source TEXT NOT NULL,
|
|
46
|
+
added_at REAL NOT NULL,
|
|
47
|
+
opened_at REAL NOT NULL)""")
|
|
48
|
+
if from_version < 2:
|
|
49
|
+
connection.execute("""CREATE TABLE project_check_runs (
|
|
50
|
+
id TEXT PRIMARY KEY,
|
|
51
|
+
project_id TEXT NOT NULL,
|
|
52
|
+
owner TEXT NOT NULL,
|
|
53
|
+
grant_id TEXT,
|
|
54
|
+
request_id TEXT NOT NULL,
|
|
55
|
+
mode TEXT NOT NULL,
|
|
56
|
+
revision INTEGER,
|
|
57
|
+
state TEXT NOT NULL,
|
|
58
|
+
data TEXT NOT NULL,
|
|
59
|
+
result_json BLOB,
|
|
60
|
+
result_junit BLOB,
|
|
61
|
+
result_error TEXT,
|
|
62
|
+
UNIQUE(owner, request_id))""")
|
|
63
|
+
connection.execute("""CREATE TABLE project_test_revisions (
|
|
64
|
+
project_id TEXT NOT NULL,
|
|
65
|
+
revision INTEGER NOT NULL,
|
|
66
|
+
fingerprint TEXT NOT NULL,
|
|
67
|
+
manifest TEXT NOT NULL,
|
|
68
|
+
PRIMARY KEY(project_id, revision),
|
|
69
|
+
UNIQUE(project_id, fingerprint))""")
|
|
70
|
+
_v2_objects(connection)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _v2_objects(connection):
|
|
74
|
+
"""The removed runs' request keys and the indexes every admission's queries use. Idempotent: it also repairs a
|
|
75
|
+
journal that an earlier build of this unreleased v2 wrote before they existed (ProjectStore.repair)."""
|
|
76
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS project_request_keys (
|
|
77
|
+
owner TEXT NOT NULL,
|
|
78
|
+
key TEXT NOT NULL,
|
|
79
|
+
project_id TEXT NOT NULL,
|
|
80
|
+
grant_id TEXT,
|
|
81
|
+
mode TEXT NOT NULL,
|
|
82
|
+
revision INTEGER,
|
|
83
|
+
run TEXT NOT NULL,
|
|
84
|
+
at REAL NOT NULL,
|
|
85
|
+
run_id TEXT,
|
|
86
|
+
PRIMARY KEY(owner, key))""")
|
|
87
|
+
if "run_id" not in {row[1] for row in connection.execute("PRAGMA table_info(project_request_keys)")}:
|
|
88
|
+
connection.execute("ALTER TABLE project_request_keys ADD COLUMN run_id TEXT")
|
|
89
|
+
connection.execute("CREATE INDEX IF NOT EXISTS project_check_runs_project ON project_check_runs(project_id)")
|
|
90
|
+
connection.execute("CREATE INDEX IF NOT EXISTS project_request_keys_run ON project_request_keys(owner, run_id)")
|
|
91
|
+
connection.execute("CREATE INDEX IF NOT EXISTS project_request_keys_at ON project_request_keys(at)")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _retire(connection, rows, now):
|
|
95
|
+
"""Delete finished runs, each leaving its request key and summary while KEY_DAYS haven't passed since it started.
|
|
96
|
+
An interrupted run's outcome is unknown, so its key never expires. ``rows``: (id, owner, request_id, mode,
|
|
97
|
+
project_id, grant_id, revision, state, data)."""
|
|
98
|
+
cutoff = now - KEY_DAYS * 86_400
|
|
99
|
+
for run_id, owner, request_id, mode, project_id, grant_id, revision, state, raw in rows:
|
|
100
|
+
try:
|
|
101
|
+
data = json.loads(raw)
|
|
102
|
+
except (ValueError, RecursionError):
|
|
103
|
+
data = None
|
|
104
|
+
data = data if isinstance(data, dict) else {"id": run_id, "projectId": project_id, "mode": mode, "state": state}
|
|
105
|
+
started = data.get("startedAt")
|
|
106
|
+
at = started / 1000 if type(started) in (int, float) else now # unknown: kept the full KEY_DAYS
|
|
107
|
+
if state == "interrupted":
|
|
108
|
+
at = math.inf
|
|
109
|
+
if at >= cutoff:
|
|
110
|
+
connection.execute("INSERT OR REPLACE INTO project_request_keys VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
111
|
+
(owner, _key(request_id), project_id, grant_id, mode, revision,
|
|
112
|
+
json.dumps({key: data.get(key) for key in SUMMARY}, separators=(",", ":")), at,
|
|
113
|
+
run_id))
|
|
114
|
+
connection.execute("DELETE FROM project_check_runs WHERE id=?", (run_id,))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _key(request_id):
|
|
118
|
+
return hashlib.sha256(request_id.encode()).hexdigest()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _kept(connection, owner, now, *, request_id=None, run_id=None):
|
|
122
|
+
"""A removed run's key younger than KEY_DAYS, by its request or its run id under ``owner``: {mode, project_id,
|
|
123
|
+
grant_id, revision, run}, else None. ``owner`` None (a local run by id): any local owner."""
|
|
124
|
+
where = "key=?" if request_id is not None else "run_id=?"
|
|
125
|
+
owners = "owner=?" if owner is not None else "owner LIKE 'local:%'"
|
|
126
|
+
found = _key(request_id) if request_id is not None else run_id
|
|
127
|
+
row = connection.execute(f"SELECT mode, project_id, grant_id, revision, run FROM project_request_keys "
|
|
128
|
+
f"WHERE {owners} AND {where} AND at>=?",
|
|
129
|
+
(*(() if owner is None else (owner,)), found, now - KEY_DAYS * 86_400)).fetchone()
|
|
130
|
+
if row is None:
|
|
131
|
+
return None
|
|
132
|
+
mode, project_id, grant_id, revision, run = row
|
|
133
|
+
return {"mode": mode, "project_id": project_id, "grant_id": grant_id, "revision": revision,
|
|
134
|
+
"run": {**json.loads(run), "requestId": request_id, "pruned": True}}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _project(row):
|
|
138
|
+
value = dict(zip(COLUMNS, row))
|
|
139
|
+
try:
|
|
140
|
+
source = json.loads(value["source"])
|
|
141
|
+
except ValueError:
|
|
142
|
+
source = None
|
|
143
|
+
value["source"] = source if isinstance(source, dict) else {"kind": "folder"}
|
|
144
|
+
return value
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ProjectStore:
|
|
148
|
+
def __init__(self, journal):
|
|
149
|
+
self.journal = journal
|
|
150
|
+
|
|
151
|
+
def _read(self, sql, args=()):
|
|
152
|
+
with self.journal.lock:
|
|
153
|
+
if self.journal.connection is None:
|
|
154
|
+
raise JournalError("The run journal is closed")
|
|
155
|
+
try:
|
|
156
|
+
return self.journal.connection.execute(sql, args).fetchall()
|
|
157
|
+
except sqlite3.Error:
|
|
158
|
+
raise JournalError("Mobster could not read its projects") from None
|
|
159
|
+
|
|
160
|
+
def projects(self):
|
|
161
|
+
"""Every project, the one opened last first."""
|
|
162
|
+
return [_project(row) for row in self._read(
|
|
163
|
+
f"SELECT {', '.join(COLUMNS)} FROM projects ORDER BY opened_at DESC, rowid DESC")]
|
|
164
|
+
|
|
165
|
+
def project(self, project_id):
|
|
166
|
+
rows = self._read(f"SELECT {', '.join(COLUMNS)} FROM projects WHERE id=?", (project_id,))
|
|
167
|
+
return _project(rows[0]) if rows else None
|
|
168
|
+
|
|
169
|
+
def by_path(self, path):
|
|
170
|
+
rows = self._read(f"SELECT {', '.join(COLUMNS)} FROM projects WHERE path=?", (str(path),))
|
|
171
|
+
return _project(rows[0]) if rows else None
|
|
172
|
+
|
|
173
|
+
def add(self, row):
|
|
174
|
+
"""Insert ``row``; the project already at that path when there is one (nothing is written then), else the
|
|
175
|
+
new row. ValueError when MAX_PROJECTS are saved. (A raise inside a transaction reads as a journal failure,
|
|
176
|
+
so the count is decided inside and refused after.)"""
|
|
177
|
+
full = False
|
|
178
|
+
with self.journal.transaction() as connection:
|
|
179
|
+
found = connection.execute("SELECT id FROM projects WHERE path=?", (row["path"],)).fetchone()
|
|
180
|
+
if found is None:
|
|
181
|
+
full = connection.execute("SELECT count(*) FROM projects").fetchone()[0] >= MAX_PROJECTS
|
|
182
|
+
if not full:
|
|
183
|
+
connection.execute(
|
|
184
|
+
f"INSERT INTO projects({', '.join(COLUMNS)}) VALUES ({', '.join('?' * len(COLUMNS))})",
|
|
185
|
+
[row["id"], row["path"], row["name"], json.dumps(row["source"]), row["added_at"],
|
|
186
|
+
row["opened_at"]])
|
|
187
|
+
if full:
|
|
188
|
+
raise ValueError(f"Mobster keeps at most {MAX_PROJECTS} projects. Remove one you don't use, then add "
|
|
189
|
+
"this one.")
|
|
190
|
+
return (self.project(found[0]), False) if found else (self.project(row["id"]), True)
|
|
191
|
+
|
|
192
|
+
def opened(self, project_id, at):
|
|
193
|
+
with self.journal.transaction() as connection:
|
|
194
|
+
connection.execute("UPDATE projects SET opened_at=? WHERE id=?", (at, project_id))
|
|
195
|
+
return self.project(project_id)
|
|
196
|
+
|
|
197
|
+
def delete(self, project_id, now=None):
|
|
198
|
+
"""Forget a project. Its finished runs go now, each leaving its key (_retire); an unfinished one stays."""
|
|
199
|
+
with self.journal.transaction() as connection:
|
|
200
|
+
_retire(connection, connection.execute(
|
|
201
|
+
f"SELECT {RETIRED} FROM project_check_runs WHERE project_id=? AND state NOT IN {UNFINISHED}",
|
|
202
|
+
(project_id,)).fetchall(), time.time() if now is None else now)
|
|
203
|
+
return connection.execute("DELETE FROM projects WHERE id=?", (project_id,)).rowcount > 0
|
|
204
|
+
|
|
205
|
+
def repair(self):
|
|
206
|
+
"""Give a journal an earlier build of this unreleased v2 wrote the v2 objects it lacks (_v2_objects)."""
|
|
207
|
+
with self.journal.transaction() as connection:
|
|
208
|
+
_v2_objects(connection)
|
|
209
|
+
|
|
210
|
+
def check_run(self, run_id, now=None):
|
|
211
|
+
"""A local run by its id, or a removed one's summary (marked pruned); None for an id Mobster doesn't hold."""
|
|
212
|
+
rows = self._read("SELECT data FROM project_check_runs WHERE id=? AND owner='local:' || project_id",
|
|
213
|
+
(run_id,))
|
|
214
|
+
if rows:
|
|
215
|
+
return json.loads(rows[0][0])
|
|
216
|
+
kept = self._kept(None, now, run_id=run_id)
|
|
217
|
+
return kept["run"] if kept else None
|
|
218
|
+
|
|
219
|
+
def latest_run(self, project_id):
|
|
220
|
+
rows = self._read("SELECT data FROM project_check_runs WHERE project_id=? AND owner='local:' || project_id "
|
|
221
|
+
"ORDER BY rowid DESC LIMIT 1",
|
|
222
|
+
(project_id,))
|
|
223
|
+
return json.loads(rows[0][0]) if rows else None
|
|
224
|
+
|
|
225
|
+
def requested_run(self, project_id, request_id, now=None):
|
|
226
|
+
"""(mode, run) for a request, or for a pruned run's key its summary; None for a key Mobster doesn't hold."""
|
|
227
|
+
rows = self._read("SELECT mode, data FROM project_check_runs WHERE owner='local:' || ? AND request_id=?",
|
|
228
|
+
(project_id, request_id))
|
|
229
|
+
if rows:
|
|
230
|
+
return rows[0][0], json.loads(rows[0][1])
|
|
231
|
+
kept = self._kept("local:" + project_id, now, request_id=request_id)
|
|
232
|
+
return (kept["mode"], kept["run"]) if kept else None
|
|
233
|
+
|
|
234
|
+
def _kept(self, owner, now, **lookup):
|
|
235
|
+
with self.journal.lock:
|
|
236
|
+
if self.journal.connection is None:
|
|
237
|
+
raise JournalError("The run journal is closed")
|
|
238
|
+
try:
|
|
239
|
+
return _kept(self.journal.connection, owner, time.time() if now is None else now, **lookup)
|
|
240
|
+
except sqlite3.Error:
|
|
241
|
+
raise JournalError("Mobster could not read its projects. If an earlier build of this branch wrote "
|
|
242
|
+
"this journal, restart Mobster so it can repair it.") from None
|
|
243
|
+
|
|
244
|
+
def claim_run(self, run, *, owner=None, grant_id=None, revision=None, authorize=None):
|
|
245
|
+
"""Persist admission before a worker starts. A request key is never reused for another mode."""
|
|
246
|
+
owner = owner or "local:" + run["projectId"]
|
|
247
|
+
denied = None
|
|
248
|
+
with self.journal.transaction() as connection:
|
|
249
|
+
found = connection.execute("SELECT mode, data FROM project_check_runs WHERE owner=? AND request_id=?",
|
|
250
|
+
(owner, run["requestId"])).fetchone()
|
|
251
|
+
if found is not None:
|
|
252
|
+
return found[0], json.loads(found[1]), False
|
|
253
|
+
kept = _kept(connection, owner, run["startedAt"] / 1000, request_id=run["requestId"])
|
|
254
|
+
if kept is not None: # its run was pruned: the key still refuses a second run
|
|
255
|
+
return kept["mode"], kept["run"], False
|
|
256
|
+
if authorize is not None:
|
|
257
|
+
try:
|
|
258
|
+
authorize(run["projectId"], revision, grant_id, connection)
|
|
259
|
+
except Exception as error: # noqa: BLE001 -- preserve the original denial outside Journal.transaction
|
|
260
|
+
denied = error
|
|
261
|
+
if denied is None:
|
|
262
|
+
connection.execute("INSERT INTO project_check_runs(id, project_id, owner, grant_id, request_id, "
|
|
263
|
+
"mode, revision, state, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
264
|
+
(run["id"], run["projectId"], owner, grant_id, run["requestId"], run["mode"],
|
|
265
|
+
revision, run["state"], json.dumps(run, separators=(",", ":"))))
|
|
266
|
+
if denied is not None:
|
|
267
|
+
raise denied
|
|
268
|
+
return run["mode"], run, True
|
|
269
|
+
|
|
270
|
+
def save_run(self, run):
|
|
271
|
+
"""Write a run's state while its row is unfinished: a late snapshot never overwrites a finished row. Only
|
|
272
|
+
complete_ci ends a CI row, so its verdict and its results are written together."""
|
|
273
|
+
with self.journal.transaction() as connection:
|
|
274
|
+
connection.execute(f"UPDATE project_check_runs SET state=?, data=? WHERE id=? AND state IN {UNFINISHED} "
|
|
275
|
+
f"AND (owner NOT LIKE 'ci:%' OR ? IN {UNFINISHED})",
|
|
276
|
+
(run["state"], json.dumps(run, separators=(",", ":")), run["id"], run["state"]))
|
|
277
|
+
|
|
278
|
+
def prune(self, project_id, now):
|
|
279
|
+
"""Keep the project's newest RUN_KEEP runs: up to PRUNE_BATCH older finished ones go (_retire), and up to
|
|
280
|
+
PRUNE_BATCH expired keys. An unfinished run is never pruned. One transaction, so a failure leaves every row and
|
|
281
|
+
key as it was. Returns how many runs went."""
|
|
282
|
+
with self.journal.transaction() as connection:
|
|
283
|
+
rows = connection.execute(
|
|
284
|
+
f"SELECT {RETIRED} FROM project_check_runs WHERE project_id=? AND state NOT IN {UNFINISHED} "
|
|
285
|
+
"AND rowid < (SELECT min(rowid) FROM (SELECT rowid FROM project_check_runs WHERE project_id=? "
|
|
286
|
+
"ORDER BY rowid DESC LIMIT ?)) ORDER BY rowid LIMIT ?",
|
|
287
|
+
(project_id, project_id, RUN_KEEP, PRUNE_BATCH)).fetchall()
|
|
288
|
+
_retire(connection, rows, now)
|
|
289
|
+
connection.execute("DELETE FROM project_request_keys WHERE rowid IN (SELECT rowid FROM "
|
|
290
|
+
"project_request_keys WHERE at<? LIMIT ?)", (now - KEY_DAYS * 86_400, PRUNE_BATCH))
|
|
291
|
+
return len(rows)
|
|
292
|
+
|
|
293
|
+
def approve_revision(self, project_id, manifest, fingerprint):
|
|
294
|
+
"""An exact repeated local approval gets its original immutable positive revision."""
|
|
295
|
+
with self.journal.transaction() as connection:
|
|
296
|
+
prior = connection.execute("SELECT revision FROM project_test_revisions WHERE project_id=? AND "
|
|
297
|
+
"fingerprint=?", (project_id, fingerprint)).fetchone()
|
|
298
|
+
if prior is not None:
|
|
299
|
+
return prior[0], False
|
|
300
|
+
count = connection.execute("SELECT count(*) FROM project_test_revisions WHERE project_id=?",
|
|
301
|
+
(project_id,)).fetchone()[0]
|
|
302
|
+
if count >= MAX_REVISIONS:
|
|
303
|
+
# The oldest revision no kept run names makes room. The newest stays, so a number is never given twice.
|
|
304
|
+
spare = connection.execute(
|
|
305
|
+
"SELECT revision FROM project_test_revisions AS r WHERE project_id=? AND revision < (SELECT "
|
|
306
|
+
"max(revision) FROM project_test_revisions WHERE project_id=r.project_id) AND NOT EXISTS (SELECT 1 "
|
|
307
|
+
"FROM project_check_runs WHERE project_id=r.project_id AND revision=r.revision) ORDER BY revision "
|
|
308
|
+
"LIMIT 1", (project_id,)).fetchone()
|
|
309
|
+
if spare is None:
|
|
310
|
+
raise JournalError("Every older approved test revision of this project is named by a kept run")
|
|
311
|
+
connection.execute("DELETE FROM project_test_revisions WHERE project_id=? AND revision=?",
|
|
312
|
+
(project_id, spare[0]))
|
|
313
|
+
revision = connection.execute("SELECT coalesce(max(revision), 0) + 1 FROM project_test_revisions "
|
|
314
|
+
"WHERE project_id=?", (project_id,)).fetchone()[0]
|
|
315
|
+
connection.execute("INSERT INTO project_test_revisions VALUES (?, ?, ?, ?)",
|
|
316
|
+
(project_id, revision, fingerprint,
|
|
317
|
+
json.dumps(manifest, sort_keys=True, separators=(",", ":"))))
|
|
318
|
+
return revision, True
|
|
319
|
+
|
|
320
|
+
def revision(self, project_id, revision):
|
|
321
|
+
rows = self._read("SELECT manifest FROM project_test_revisions WHERE project_id=? AND revision=?",
|
|
322
|
+
(project_id, revision))
|
|
323
|
+
return json.loads(rows[0][0]) if rows else None
|
|
324
|
+
|
|
325
|
+
def latest_revisions(self):
|
|
326
|
+
rows = self._read("SELECT project_id, max(revision) FROM project_test_revisions GROUP BY project_id")
|
|
327
|
+
return rows
|
|
328
|
+
|
|
329
|
+
def ci_run(self, run_id=None, *, owner, request_id=None, with_artifacts=False, now=None):
|
|
330
|
+
"""A CI row by its id or its request. A removed run's key answers either with its summary and no results."""
|
|
331
|
+
blobs = "result_json, result_junit" if with_artifacts else "NULL, NULL"
|
|
332
|
+
columns = ("project_id, grant_id, revision, data, " + blobs + ", result_error, "
|
|
333
|
+
"CASE WHEN result_json IS NOT NULL AND result_junit IS NOT NULL THEN 1 ELSE 0 END")
|
|
334
|
+
if run_id is not None:
|
|
335
|
+
rows = self._read(f"SELECT {columns} FROM project_check_runs WHERE id=? AND owner=?", (run_id, owner))
|
|
336
|
+
else:
|
|
337
|
+
rows = self._read(f"SELECT {columns} FROM project_check_runs WHERE request_id=? AND owner=?",
|
|
338
|
+
(request_id, owner))
|
|
339
|
+
if not rows:
|
|
340
|
+
kept = self._kept(owner, now, request_id=request_id) if run_id is None else \
|
|
341
|
+
self._kept(owner, now, run_id=run_id)
|
|
342
|
+
return kept and {"project_id": kept["project_id"], "grant_id": kept["grant_id"],
|
|
343
|
+
"revision": kept["revision"], "run": kept["run"], "artifacts": None, "available": False,
|
|
344
|
+
"result_error": "expired"}
|
|
345
|
+
project_id, grant_id, revision, data, result_json, result_junit, result_error, available = rows[0]
|
|
346
|
+
return {"project_id": project_id, "grant_id": grant_id, "revision": revision, "run": json.loads(data),
|
|
347
|
+
"artifacts": {"json": result_json, "junit": result_junit} if result_json is not None and
|
|
348
|
+
result_junit is not None else None, "available": bool(available), "result_error": result_error}
|
|
349
|
+
|
|
350
|
+
def complete_ci(self, run, artifacts, error=None):
|
|
351
|
+
with self.journal.transaction() as connection:
|
|
352
|
+
connection.execute("UPDATE project_check_runs SET state=?, data=?, result_json=?, result_junit=?, "
|
|
353
|
+
f"result_error=? WHERE id=? AND owner LIKE 'ci:%' AND state IN {UNFINISHED}",
|
|
354
|
+
(run["state"], json.dumps(run, separators=(",", ":")),
|
|
355
|
+
(artifacts or {}).get("json"), (artifacts or {}).get("junit"), error, run["id"]))
|
|
356
|
+
connection.execute("UPDATE project_check_runs SET result_json=NULL, result_junit=NULL, "
|
|
357
|
+
"result_error='expired' WHERE id IN (SELECT id FROM project_check_runs WHERE "
|
|
358
|
+
"owner LIKE 'ci:%' AND result_json IS NOT NULL AND result_junit IS NOT NULL "
|
|
359
|
+
"AND state NOT IN ('building','running') ORDER BY rowid DESC LIMIT -1 OFFSET ?)",
|
|
360
|
+
(RESULT_KEEP,))
|
|
361
|
+
|
|
362
|
+
def interrupt_unfinished(self, at):
|
|
363
|
+
"""A restarted process cannot know an old build or device action's outcome, so it never replays it."""
|
|
364
|
+
with self.journal.transaction() as connection:
|
|
365
|
+
rows = connection.execute("SELECT id, project_id, request_id, mode, data FROM project_check_runs "
|
|
366
|
+
f"WHERE state IN {UNFINISHED}").fetchall()
|
|
367
|
+
for run_id, project_id, request_id, mode, raw in rows:
|
|
368
|
+
try:
|
|
369
|
+
run = json.loads(raw)
|
|
370
|
+
except (ValueError, RecursionError):
|
|
371
|
+
run = None
|
|
372
|
+
if not isinstance(run, dict): # a row that can't be read still ends, from its columns
|
|
373
|
+
run = {"id": run_id, "projectId": project_id, "requestId": request_id, "mode": mode,
|
|
374
|
+
"stopRequested": False, "done": 0, "total": None, "summary": None, "startedAt": None,
|
|
375
|
+
"choice": None, "error": None, "fix": None}
|
|
376
|
+
run.update(state="interrupted", code="interrupted", exitCode=3, endedAt=round(at * 1000),
|
|
377
|
+
line="Interrupted when Mobster restarted")
|
|
378
|
+
connection.execute("UPDATE project_check_runs SET state='interrupted', data=? WHERE id=?",
|
|
379
|
+
(json.dumps(run, separators=(",", ":")), run_id))
|