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,339 @@
|
|
|
1
|
+
"""Cloning a repository by URL into a project folder.
|
|
2
|
+
|
|
3
|
+
``parse(text)`` reads what the person pasted: an https or SSH URL, `github.com/owner/name`, or `owner/name` (a
|
|
4
|
+
GitHub repository). ``Clone`` runs one `git clone` in the background and says how far it got.
|
|
5
|
+
|
|
6
|
+
The rules: git runs with no shell and with ``--`` before the URL, with no terminal prompt, no Git Credential Manager
|
|
7
|
+
window and, unless your git config names its own SSH command, SSH in batch mode, so a clone never waits on a
|
|
8
|
+
question nobody sees. Your Mac's git sign-in (the Keychain, an SSH agent) is used as it is; a password or token in the
|
|
9
|
+
URL is refused, so it is never kept or shown. A clone that fails is never retried: it says why and what to do, and
|
|
10
|
+
the folder it made is removed. A clone has 30 minutes.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import signal
|
|
17
|
+
import subprocess
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from ..api_errors import APIError
|
|
23
|
+
from . import folder as folders
|
|
24
|
+
|
|
25
|
+
MAX_URL = 2048
|
|
26
|
+
CLONE_TIMEOUT = 30 * 60
|
|
27
|
+
HOST = r"[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?"
|
|
28
|
+
REPO_PATH = r"[A-Za-z0-9._~-]+(?:/[A-Za-z0-9._~-]+)*/?"
|
|
29
|
+
HTTPS = re.compile(rf"https://(?P<host>{HOST})(?::\d{{1,5}})?/(?P<path>{REPO_PATH})")
|
|
30
|
+
SSH = re.compile(rf"ssh://[A-Za-z0-9._-]{{1,64}}@(?P<host>{HOST})(?::\d{{1,5}})?/(?P<path>{REPO_PATH})")
|
|
31
|
+
SCP = re.compile(rf"[A-Za-z0-9._-]{{1,64}}@(?P<host>{HOST}):(?P<path>{REPO_PATH})")
|
|
32
|
+
SHORT = re.compile(r"(?:github\.com/)?(?P<owner>[A-Za-z0-9-]{1,39})/(?P<name>[A-Za-z0-9._-]{1,100}?)(?:\.git)?/?")
|
|
33
|
+
CREDENTIALS = re.compile(r"^https?://[^/@]+@")
|
|
34
|
+
NAME = re.compile(r"[A-Za-z0-9._-]{1,100}")
|
|
35
|
+
PROGRESS = re.compile(r"(Counting objects|Compressing objects|Receiving objects|Resolving deltas|Updating files):\s+"
|
|
36
|
+
r"(\d{1,3})%")
|
|
37
|
+
# The share of the bar each phase takes (Receiving is most of a clone's time).
|
|
38
|
+
PHASES = {"Counting objects": (0, 5), "Compressing objects": (5, 10), "Receiving objects": (10, 85),
|
|
39
|
+
"Resolving deltas": (85, 95), "Updating files": (95, 100)}
|
|
40
|
+
TAIL = 20
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse(text):
|
|
44
|
+
"""(url, host, folder name) for what the person pasted. APIError with what to write otherwise."""
|
|
45
|
+
if not isinstance(text, str) or not text.strip() or len(text) > MAX_URL or any(c.isspace() for c in text.strip()):
|
|
46
|
+
raise APIError("Paste the repository's URL, such as https://github.com/owner/name, or owner/name.", 400,
|
|
47
|
+
"invalid_url")
|
|
48
|
+
text = text.strip()
|
|
49
|
+
if CREDENTIALS.match(text):
|
|
50
|
+
raise APIError("Leave the password or token out of the URL. Mobster clones with your Mac's git sign-in (the "
|
|
51
|
+
"Keychain or your SSH key).", 400, "credentials_in_url")
|
|
52
|
+
short = SHORT.fullmatch(text)
|
|
53
|
+
if short and not text.startswith(("http", "ssh", "git@")) and "@" not in text and ":" not in text:
|
|
54
|
+
text = f"https://github.com/{short.group('owner')}/{short.group('name')}.git"
|
|
55
|
+
for pattern in (HTTPS, SSH, SCP):
|
|
56
|
+
match = pattern.fullmatch(text)
|
|
57
|
+
if match:
|
|
58
|
+
parts = [part for part in match.group("path").split("/") if part]
|
|
59
|
+
if not parts or any(part in (".", "..") or part.startswith("-") for part in parts):
|
|
60
|
+
break
|
|
61
|
+
name = parts[-1][:-4] if parts[-1].endswith(".git") else parts[-1]
|
|
62
|
+
if not NAME.fullmatch(name) or name in (".", ".."):
|
|
63
|
+
break
|
|
64
|
+
return text, match.group("host").lower(), name
|
|
65
|
+
raise APIError("Mobster clones from https and SSH URLs, such as https://github.com/owner/name or "
|
|
66
|
+
"git@github.com:owner/name.git.", 400, "invalid_url")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def destination(parent, name, *, default_parent, home=None):
|
|
70
|
+
"""The folder a clone makes: ``name`` in ``parent`` (the default folder when None, made when missing). APIError
|
|
71
|
+
when the name or the folder won't do, or something is already there."""
|
|
72
|
+
if name is None or not isinstance(name, str) or not NAME.fullmatch(name) or name in (".", "..") \
|
|
73
|
+
or name.startswith("-"):
|
|
74
|
+
raise APIError("Name the folder with letters, numbers, dots, dashes and underscores.", 400, "invalid_name")
|
|
75
|
+
if parent is None:
|
|
76
|
+
folder = Path(default_parent)
|
|
77
|
+
try:
|
|
78
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
except OSError:
|
|
80
|
+
raise APIError(f"Mobster couldn't make {folders.shown(folder, home)}. Choose another folder to clone "
|
|
81
|
+
"into.", 403, "unwritable") from None
|
|
82
|
+
else:
|
|
83
|
+
if not isinstance(parent, str) or not Path(parent).expanduser().is_absolute() or "\0" in parent:
|
|
84
|
+
raise APIError("Give the folder to clone into as a full path.", 400, "invalid_path")
|
|
85
|
+
folder = Path(parent).expanduser()
|
|
86
|
+
if not folder.is_dir():
|
|
87
|
+
raise APIError(f"There's no folder at {folders.shown(folder, home)}. Choose the folder again.", 404,
|
|
88
|
+
"missing")
|
|
89
|
+
folder = folder.resolve()
|
|
90
|
+
if not os.access(folder, os.W_OK | os.X_OK):
|
|
91
|
+
raise APIError(f"Mobster can't write in {folders.shown(folder, home)}. Choose another folder to clone into.",
|
|
92
|
+
403, "unwritable")
|
|
93
|
+
target = folder / name
|
|
94
|
+
if os.path.lexists(target):
|
|
95
|
+
raise APIError(f"{folders.shown(target, home)} is already there. Add that folder, or clone under another "
|
|
96
|
+
"name.", 409, "exists", details={"path": str(target)})
|
|
97
|
+
return target
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def classify(lines, url, host):
|
|
101
|
+
"""(code, sentence) for a clone that failed, from git's last lines."""
|
|
102
|
+
text = "\n".join(lines).lower()
|
|
103
|
+
if "could not create work tree dir" in text or "could not create leading directories" in text:
|
|
104
|
+
return "unwritable", "git couldn't make the folder to clone into. Choose another folder, then clone again."
|
|
105
|
+
if "could not resolve host" in text or "couldn't connect" in text or "could not connect" in text \
|
|
106
|
+
or "network is unreachable" in text or "operation timed out" in text or "connection timed out" in text:
|
|
107
|
+
return "offline", f"Mobster couldn't reach {host}. Check your connection, then clone again."
|
|
108
|
+
if "host key verification failed" in text:
|
|
109
|
+
return "host_key", (f"SSH doesn't know {host} yet. Run ssh -T git@{host} in Terminal once, or clone with "
|
|
110
|
+
"the https URL.")
|
|
111
|
+
if any(words in text for words in ("repository not found", "authentication failed", "could not read username",
|
|
112
|
+
"could not read password", "terminal prompts disabled", "permission denied",
|
|
113
|
+
"access denied", "error: 401", "error: 403", "error: 404", "not found",
|
|
114
|
+
"could not read from remote repository")):
|
|
115
|
+
return "no_access", (f"{host} didn't let Mobster clone this repository. Check the URL, and that git on this "
|
|
116
|
+
"Mac is signed in to an account that can read it.")
|
|
117
|
+
if "no space left" in text:
|
|
118
|
+
return "disk_full", "This Mac ran out of disk space during the clone. Free some space, then clone again."
|
|
119
|
+
last = next((line for line in reversed(lines) if line.strip()), "")
|
|
120
|
+
last = re.sub(r"https?://[^/@\s]+@", "https://", last.strip())[:200]
|
|
121
|
+
return "failed", f"git couldn't clone {url}" + (f": {last}" if last else ".")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Clone:
|
|
125
|
+
"""One `git clone` in the background. ``on_change(clone)`` is told as it goes (at most 4 times a second) and
|
|
126
|
+
once when it ends; ``on_done(clone)`` runs on the clone's thread after git succeeded, before the end is told,
|
|
127
|
+
and may set ``error``. ``popen`` is subprocess.Popen (tests pass a fake)."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, identifier, url, host, target, *, on_change=None, on_done=None, popen=subprocess.Popen,
|
|
130
|
+
clock=time.time, timeout=CLONE_TIMEOUT, home=None, env=None, signed_in=None):
|
|
131
|
+
self.id, self.url, self.host, self.target = identifier, url, host, Path(target)
|
|
132
|
+
self.state, self.phase, self.percent = "cloning", None, 0
|
|
133
|
+
self.code = self.error = self.project_id = None
|
|
134
|
+
self.started_at, self.ended_at = clock(), None
|
|
135
|
+
self._on_change, self._on_done, self._popen, self._clock = on_change, on_done, popen, clock
|
|
136
|
+
self._timeout, self._home = timeout, home
|
|
137
|
+
# git's own environment for a sign-in (github.clone_env: never argv, never the URL), and whose sign-in it is.
|
|
138
|
+
self._extra_env, self._signed_in = dict(env or {}), signed_in
|
|
139
|
+
self._process = None
|
|
140
|
+
self._made = None # identity of the directory this job made; cleanup never owns a replacement
|
|
141
|
+
self._canceled = False
|
|
142
|
+
self._told = 0.0
|
|
143
|
+
self._lock = threading.Lock()
|
|
144
|
+
self._thread = threading.Thread(target=self._run, name=f"mobster-clone-{identifier}", daemon=True)
|
|
145
|
+
|
|
146
|
+
def public(self):
|
|
147
|
+
return {"id": self.id, "url": self.url, "path": str(self.target),
|
|
148
|
+
"shownPath": folders.shown(self.target, self._home),
|
|
149
|
+
"name": self.target.name, "state": self.state, "phase": self.phase, "percent": self.percent,
|
|
150
|
+
"code": self.code, "error": self.error, "projectId": self.project_id,
|
|
151
|
+
"startedAt": round(self.started_at * 1000),
|
|
152
|
+
"endedAt": round(self.ended_at * 1000) if self.ended_at else None}
|
|
153
|
+
|
|
154
|
+
def start(self):
|
|
155
|
+
self._thread.start()
|
|
156
|
+
|
|
157
|
+
def join(self, timeout=None):
|
|
158
|
+
self._thread.join(timeout)
|
|
159
|
+
|
|
160
|
+
def cancel(self):
|
|
161
|
+
"""Stop the clone; the folder it made is removed. False when it had already ended."""
|
|
162
|
+
with self._lock:
|
|
163
|
+
if self.state != "cloning":
|
|
164
|
+
return False
|
|
165
|
+
self._canceled = True
|
|
166
|
+
process = self._process
|
|
167
|
+
if process is not None:
|
|
168
|
+
_stop(process)
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
def _env(self):
|
|
172
|
+
env = {**os.environ, "GIT_TERMINAL_PROMPT": "0", "GCM_INTERACTIVE": "never", "GIT_OPTIONAL_LOCKS": "0",
|
|
173
|
+
**self._extra_env}
|
|
174
|
+
if not _git_config("core.sshCommand") and "GIT_SSH_COMMAND" not in os.environ:
|
|
175
|
+
env["GIT_SSH_COMMAND"] = "ssh -o BatchMode=yes -o ConnectTimeout=20"
|
|
176
|
+
return env
|
|
177
|
+
|
|
178
|
+
def _tell(self, force=False):
|
|
179
|
+
now = self._clock()
|
|
180
|
+
if self._on_change is not None and (force or now - self._told >= 0.25):
|
|
181
|
+
self._told = now
|
|
182
|
+
try:
|
|
183
|
+
self._on_change(self)
|
|
184
|
+
except Exception: # noqa: BLE001 -- telling never stops a clone
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
def _run(self):
|
|
188
|
+
lines = []
|
|
189
|
+
try:
|
|
190
|
+
if not folders.git_ready():
|
|
191
|
+
raise FileNotFoundError("git")
|
|
192
|
+
env = self._env()
|
|
193
|
+
with self._lock:
|
|
194
|
+
if self._canceled:
|
|
195
|
+
raise _Canceled
|
|
196
|
+
self.target.mkdir()
|
|
197
|
+
made = self.target.stat()
|
|
198
|
+
self._made = (made.st_dev, made.st_ino)
|
|
199
|
+
self._process = self._popen(
|
|
200
|
+
["git", "-c", "core.fsmonitor=false", "clone", "--progress", "--", self.url, str(self.target)],
|
|
201
|
+
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, env=env,
|
|
202
|
+
start_new_session=True)
|
|
203
|
+
deadline = threading.Timer(self._timeout, self._expire)
|
|
204
|
+
deadline.daemon = True
|
|
205
|
+
deadline.start()
|
|
206
|
+
try:
|
|
207
|
+
for line in _lines(self._process.stderr):
|
|
208
|
+
self._progress(line)
|
|
209
|
+
lines.append(line)
|
|
210
|
+
del lines[:-TAIL]
|
|
211
|
+
code = self._process.wait()
|
|
212
|
+
finally:
|
|
213
|
+
deadline.cancel()
|
|
214
|
+
self._process.stderr.close()
|
|
215
|
+
if self._canceled:
|
|
216
|
+
raise _Canceled
|
|
217
|
+
if code != 0:
|
|
218
|
+
if self._expired:
|
|
219
|
+
self._fail("timeout", "The clone took longer than 30 minutes, so Mobster stopped it. Clone it in "
|
|
220
|
+
"Terminal, then add the folder.")
|
|
221
|
+
else:
|
|
222
|
+
code, sentence = classify(lines, self.url, self.host)
|
|
223
|
+
if code == "no_access" and self._signed_in:
|
|
224
|
+
sentence = (f"{self.host} didn't let Mobster clone this repository with your {self._signed_in} "
|
|
225
|
+
"sign-in. Check the repository, or sign in in Connectors with an account that can "
|
|
226
|
+
"read it.")
|
|
227
|
+
self._fail(code, sentence)
|
|
228
|
+
return
|
|
229
|
+
self.phase, self.percent = None, 100
|
|
230
|
+
if self._on_done is not None:
|
|
231
|
+
try:
|
|
232
|
+
self._on_done(self)
|
|
233
|
+
except Exception: # noqa: BLE001 -- git succeeded: the folder stays whatever happens next
|
|
234
|
+
self.code = self.code or "refused"
|
|
235
|
+
where = folders.shown(self.target, self._home)
|
|
236
|
+
self.error = self.error or (f"The repository is cloned at {where}, but Mobster couldn't add it. "
|
|
237
|
+
"Add the folder from Add project.")
|
|
238
|
+
self._end("failed" if self.error else "done")
|
|
239
|
+
except _Canceled:
|
|
240
|
+
self._remove()
|
|
241
|
+
self._end("canceled")
|
|
242
|
+
except FileExistsError:
|
|
243
|
+
self._fail("exists", f"{folders.shown(self.target, self._home)} is already there. Add that folder, or "
|
|
244
|
+
"clone under another name.")
|
|
245
|
+
except FileNotFoundError:
|
|
246
|
+
self._fail("no_git", "Mobster needs git to clone. Install the Xcode command line tools "
|
|
247
|
+
"(xcode-select --install), then clone again.")
|
|
248
|
+
except Exception: # noqa: BLE001 -- a clone that broke says so, never retries
|
|
249
|
+
self._fail("failed", f"Mobster couldn't clone {self.url}. Try again, or clone it in Terminal and add "
|
|
250
|
+
"the folder.")
|
|
251
|
+
|
|
252
|
+
_expired = False
|
|
253
|
+
|
|
254
|
+
def _expire(self):
|
|
255
|
+
self._expired = True
|
|
256
|
+
if self._process is not None:
|
|
257
|
+
_stop(self._process)
|
|
258
|
+
|
|
259
|
+
def _progress(self, line):
|
|
260
|
+
match = PROGRESS.search(line)
|
|
261
|
+
if not match:
|
|
262
|
+
return
|
|
263
|
+
phase, share = match.group(1), min(int(match.group(2)), 100)
|
|
264
|
+
start, end = PHASES[phase]
|
|
265
|
+
self.phase = phase
|
|
266
|
+
self.percent = max(self.percent, start + (end - start) * share // 100)
|
|
267
|
+
self._tell()
|
|
268
|
+
|
|
269
|
+
def _remove(self):
|
|
270
|
+
if not self._made:
|
|
271
|
+
return
|
|
272
|
+
try:
|
|
273
|
+
current = self.target.lstat()
|
|
274
|
+
if (current.st_dev, current.st_ino) == self._made and self.target.is_dir() and not self.target.is_symlink():
|
|
275
|
+
shutil.rmtree(self.target)
|
|
276
|
+
except OSError:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
def _fail(self, code, sentence):
|
|
280
|
+
self._remove()
|
|
281
|
+
self.code, self.error = code, sentence
|
|
282
|
+
self._end("failed")
|
|
283
|
+
|
|
284
|
+
def _end(self, state):
|
|
285
|
+
with self._lock:
|
|
286
|
+
self.state, self.ended_at = state, self._clock()
|
|
287
|
+
if state != "cloning":
|
|
288
|
+
self._process = None
|
|
289
|
+
self._tell(force=True)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class _Canceled(Exception):
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _lines(stream):
|
|
297
|
+
"""git's progress lines: it ends them with \\r while a phase goes on and \\n when it is done."""
|
|
298
|
+
buffer = b""
|
|
299
|
+
while True:
|
|
300
|
+
chunk = stream.read1(4096) if hasattr(stream, "read1") else stream.read(4096)
|
|
301
|
+
if not chunk:
|
|
302
|
+
break
|
|
303
|
+
buffer += chunk
|
|
304
|
+
*done, buffer = re.split(rb"[\r\n]", buffer)
|
|
305
|
+
for line in done:
|
|
306
|
+
if line:
|
|
307
|
+
yield line.decode("utf-8", errors="replace")
|
|
308
|
+
buffer = buffer[-8192:]
|
|
309
|
+
if buffer:
|
|
310
|
+
yield buffer.decode("utf-8", errors="replace")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _stop(process):
|
|
314
|
+
"""End git and what it started (git-remote-https, ssh): TERM to its process group, KILL after 2 seconds."""
|
|
315
|
+
try:
|
|
316
|
+
os.killpg(process.pid, signal.SIGTERM)
|
|
317
|
+
except (OSError, AttributeError):
|
|
318
|
+
try:
|
|
319
|
+
process.terminate()
|
|
320
|
+
except OSError:
|
|
321
|
+
return
|
|
322
|
+
try:
|
|
323
|
+
process.wait(2)
|
|
324
|
+
except subprocess.TimeoutExpired:
|
|
325
|
+
try:
|
|
326
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
327
|
+
except OSError:
|
|
328
|
+
pass
|
|
329
|
+
except Exception: # noqa: BLE001 -- a fake process in tests
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _git_config(key):
|
|
334
|
+
try:
|
|
335
|
+
result = subprocess.run(["git", "config", "--global", "--get", key], capture_output=True, timeout=3,
|
|
336
|
+
stdin=subprocess.DEVNULL)
|
|
337
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
338
|
+
return None
|
|
339
|
+
return result.stdout.decode("utf-8", errors="replace").strip() or None
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Reviewed local check files in one already-saved project. Saving never executes a check."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import stat
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from ..api_errors import APIError
|
|
11
|
+
from ..testkit.record import RUN_ID, RecordError, check_data
|
|
12
|
+
from ..verify.checks import CheckError, check_from_dict
|
|
13
|
+
|
|
14
|
+
MAX_DRAFT_BYTES = 60_000
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _StrictLoader(yaml.SafeLoader):
|
|
18
|
+
def construct_mapping(self, node, deep=False):
|
|
19
|
+
self.flatten_mapping(node)
|
|
20
|
+
out = {}
|
|
21
|
+
for key_node, value_node in node.value:
|
|
22
|
+
key = self.construct_object(key_node, deep=deep)
|
|
23
|
+
if key in out:
|
|
24
|
+
raise yaml.constructor.ConstructorError(None, None, f"duplicate key {key!r}", key_node.start_mark)
|
|
25
|
+
out[key] = self.construct_object(value_node, deep=deep)
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _child(parent, name):
|
|
30
|
+
try:
|
|
31
|
+
os.mkdir(name, 0o700, dir_fd=parent)
|
|
32
|
+
except FileExistsError:
|
|
33
|
+
pass
|
|
34
|
+
return os.open(name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _root_fd(root):
|
|
38
|
+
fd = os.open("/", os.O_RDONLY | os.O_DIRECTORY)
|
|
39
|
+
try:
|
|
40
|
+
for part in Path(root).parts[1:]:
|
|
41
|
+
child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
|
|
42
|
+
os.close(fd)
|
|
43
|
+
fd = child
|
|
44
|
+
return fd
|
|
45
|
+
except BaseException:
|
|
46
|
+
os.close(fd)
|
|
47
|
+
raise
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _save(root, name, raw):
|
|
51
|
+
root_fd = _root_fd(root)
|
|
52
|
+
try:
|
|
53
|
+
mobster_fd = _child(root_fd, ".mobster")
|
|
54
|
+
try:
|
|
55
|
+
checks_fd = _child(mobster_fd, "checks")
|
|
56
|
+
try:
|
|
57
|
+
try:
|
|
58
|
+
fd = os.open(name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600,
|
|
59
|
+
dir_fd=checks_fd)
|
|
60
|
+
except FileExistsError:
|
|
61
|
+
fd = os.open(name, os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW, dir_fd=checks_fd)
|
|
62
|
+
try:
|
|
63
|
+
info = os.fstat(fd)
|
|
64
|
+
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size != len(raw) or \
|
|
65
|
+
os.read(fd, len(raw) + 1) != raw:
|
|
66
|
+
raise APIError("That reviewed check has changed. Review and save it again.", 409,
|
|
67
|
+
"check_changed")
|
|
68
|
+
finally:
|
|
69
|
+
os.close(fd)
|
|
70
|
+
return True
|
|
71
|
+
try:
|
|
72
|
+
with os.fdopen(fd, "wb") as stream:
|
|
73
|
+
stream.write(raw)
|
|
74
|
+
stream.flush()
|
|
75
|
+
os.fsync(stream.fileno())
|
|
76
|
+
os.fsync(checks_fd)
|
|
77
|
+
except BaseException:
|
|
78
|
+
os.unlink(name, dir_fd=checks_fd)
|
|
79
|
+
raise
|
|
80
|
+
return False
|
|
81
|
+
finally:
|
|
82
|
+
os.close(checks_fd)
|
|
83
|
+
finally:
|
|
84
|
+
os.close(mobster_fd)
|
|
85
|
+
finally:
|
|
86
|
+
os.close(root_fd)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _reviewed(root, check, raw, name):
|
|
90
|
+
try:
|
|
91
|
+
replayed = _save(root, name, raw)
|
|
92
|
+
except OSError:
|
|
93
|
+
raise APIError("Mobster cannot write that project's checks folder.", 403, "unwritable") from None
|
|
94
|
+
return {"check": {"path": f".mobster/checks/{name}", "name": check.name,
|
|
95
|
+
"bundle": check.bundle_id, "steps": len(check.steps), "expect": len(check.expect)},
|
|
96
|
+
"verified": False, "replayed": replayed}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def author(root, body, bundles):
|
|
100
|
+
if not isinstance(body, dict) or set(body) - {"name", "app", "steps", "expect", "reviewed"} or \
|
|
101
|
+
not {"app", "steps", "expect", "reviewed"} <= set(body) or body["reviewed"] is not True:
|
|
102
|
+
raise ValueError("Review the app, steps and expected final text before saving the check")
|
|
103
|
+
app, steps, expect = body["app"], body["steps"], body["expect"]
|
|
104
|
+
if not isinstance(app, dict) or set(app) != {"bundle"} or \
|
|
105
|
+
not isinstance(steps, list) or not steps or not isinstance(expect, list) or not expect or \
|
|
106
|
+
any(not isinstance(item, dict) or set(item) != {"text"} or
|
|
107
|
+
not isinstance(item["text"], str) or not item["text"].strip() for item in expect):
|
|
108
|
+
raise ValueError("Name one app bundle, plain-English steps and expected final text")
|
|
109
|
+
data = {"version": 1, "app": app, "reset": "none", "steps": steps, "expect": expect}
|
|
110
|
+
if "name" in body:
|
|
111
|
+
data["name"] = body["name"]
|
|
112
|
+
try:
|
|
113
|
+
check = check_from_dict(data, base_dir=root)
|
|
114
|
+
except CheckError as error:
|
|
115
|
+
raise ValueError(f"Review a valid check: {error}") from None
|
|
116
|
+
if not check.bundle_id or check.app_path or not check.steps or not check.expect or check.bundle_id not in bundles:
|
|
117
|
+
raise ValueError("Choose an app bundle this project builds, steps and expected final text")
|
|
118
|
+
try:
|
|
119
|
+
raw = yaml.safe_dump(data, sort_keys=False, allow_unicode=True, default_flow_style=False, width=100).encode()
|
|
120
|
+
except (yaml.YAMLError, UnicodeError):
|
|
121
|
+
raise ValueError("The reviewed check contains invalid text") from None
|
|
122
|
+
if len(raw) > MAX_DRAFT_BYTES:
|
|
123
|
+
raise ValueError("The reviewed check is longer than 60 KB")
|
|
124
|
+
return _reviewed(root, check, raw, f"authored-{hashlib.sha256(raw).hexdigest()}.yaml")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def save_finished(runtime, root, body, bundles):
|
|
128
|
+
if not isinstance(body, dict) or set(body) != {"runId", "yaml", "reviewed"} or body["reviewed"] is not True:
|
|
129
|
+
raise ValueError("Send a finished run, reviewed YAML and reviewed: true")
|
|
130
|
+
run_id, text = body["runId"], body["yaml"]
|
|
131
|
+
if not isinstance(run_id, str) or not RUN_ID.fullmatch(run_id):
|
|
132
|
+
raise ValueError("runId must name one finished task")
|
|
133
|
+
if not isinstance(text, str) or not text.strip():
|
|
134
|
+
raise ValueError("yaml must be a reviewed check of at most 60 KB")
|
|
135
|
+
try:
|
|
136
|
+
raw = text.encode("utf-8")
|
|
137
|
+
except UnicodeError:
|
|
138
|
+
raise ValueError("The reviewed check contains invalid text") from None
|
|
139
|
+
if len(raw) > MAX_DRAFT_BYTES:
|
|
140
|
+
raise ValueError("yaml must be a reviewed check of at most 60 KB")
|
|
141
|
+
with runtime.lock:
|
|
142
|
+
run = runtime.runs.get(run_id)
|
|
143
|
+
source = {**run.public(), "app": dict(run.app or {})} if run is not None else None
|
|
144
|
+
if source is None:
|
|
145
|
+
raise APIError("That task is not in Mobster's history.", 404, "run_not_found")
|
|
146
|
+
try:
|
|
147
|
+
recorded = check_data(source)
|
|
148
|
+
check = check_from_dict(yaml.load(text, Loader=_StrictLoader), base_dir=root)
|
|
149
|
+
except RecordError as error:
|
|
150
|
+
raise APIError(error.message, error.status, error.code) from None
|
|
151
|
+
except (yaml.YAMLError, CheckError, RecursionError, TypeError) as error:
|
|
152
|
+
raise ValueError(f"Review a valid check: {error}") from None
|
|
153
|
+
if check.bundle_id != recorded["app"]["bundle"] or check.bundle_id not in bundles or check.app_path:
|
|
154
|
+
raise ValueError("The reviewed check must keep this project's finished task app bundle")
|
|
155
|
+
if not check.expect:
|
|
156
|
+
raise ValueError("Add an expectation before saving the reviewed check")
|
|
157
|
+
name = f"recorded-{run_id}-{hashlib.sha256(raw).hexdigest()}.yaml"
|
|
158
|
+
return _reviewed(root, check, raw, name)
|