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,527 @@
|
|
|
1
|
+
"""The setup checklist the dashboard's onboarding flow renders (docs/setup-api.md)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
|
|
10
|
+
from . import mac_tools, phone_ready, signing
|
|
11
|
+
from .env_secrets import update_env_file
|
|
12
|
+
from .keys import anthropic_key, openai_key
|
|
13
|
+
from .runner_renewal import RunnerRenewal
|
|
14
|
+
|
|
15
|
+
# The slow reads (Xcode's state, the USB phones and System Information) run side by side.
|
|
16
|
+
READS = ThreadPoolExecutor(max_workers=3, thread_name_prefix="mobster-setup-read")
|
|
17
|
+
|
|
18
|
+
INSTALL_TOOLS = "brew install libimobiledevice"
|
|
19
|
+
# What Install runs in the background when this build doesn't carry the tools itself (a source checkout with Homebrew):
|
|
20
|
+
# libimobiledevice, and ideviceinstaller for the Apps page. The shipped app never needs it (build-iphone-tools.sh).
|
|
21
|
+
INSTALL_FORMULAE = ("libimobiledevice", "ideviceinstaller")
|
|
22
|
+
# brew.sh's own install command, shown only when Homebrew is missing.
|
|
23
|
+
INSTALL_HOMEBREW = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
|
24
|
+
BREW_DIRS = ("/opt/homebrew/bin", "/usr/local/bin")
|
|
25
|
+
# What is wrong with Xcode, in the user's words (mac_tools.xcode_status states). The step lists the
|
|
26
|
+
# Terminal commands that fix it, so these say what happened and any way to fix it without Terminal.
|
|
27
|
+
XCODE_DETAIL = {
|
|
28
|
+
"missing": "Install Xcode from the Mac App Store. It's a large download and can take an hour, so start it "
|
|
29
|
+
"now, then open Xcode once when it finishes.",
|
|
30
|
+
"license": "Accept the Xcode license: open Xcode and agree, or use the command below.",
|
|
31
|
+
"first_launch": "Xcode hasn't finished installing. Open it once and let it install its components, or use "
|
|
32
|
+
"the command below.",
|
|
33
|
+
"no_ios": "Xcode doesn't have the iOS platform yet. Open Xcode › Settings › Components and get iOS, or use "
|
|
34
|
+
"the command below.",
|
|
35
|
+
"broken": "Xcode isn't responding. Open it once to finish installing it, then check again.",
|
|
36
|
+
}
|
|
37
|
+
EXPIRED_DETAIL = ("Mobster's helper needs a refresh: a free Apple Account signs it for 7 days at a time. Refresh it to "
|
|
38
|
+
"keep using your iPhone.")
|
|
39
|
+
# Measured 17 Oct 2026 on this repository's M5 Max and an iPhone 15 Pro: 115 s to build, sign and install (a cold
|
|
40
|
+
# clone of the pinned release is 5 s and its unsigned build 52 s). docs/requirements.mdx says the same.
|
|
41
|
+
FIRST_BUILD = "The first install takes about 2 minutes. Keep your iPhone unlocked and plugged in."
|
|
42
|
+
REBUILD = "Installing again is quicker than the first time. Keep your iPhone unlocked and plugged in."
|
|
43
|
+
DEVELOPER_MODE_TTL = (60.0, 5.0) # re-read after this long when on, and when off or unknown
|
|
44
|
+
# The preflight's one-line reading of each Xcode state (mac_tools.xcode_status).
|
|
45
|
+
XCODE_WORD = {"ok": "Ready", "missing": "Not installed", "license": "License",
|
|
46
|
+
"first_launch": "Not finished", "no_ios": "No iOS platform", "broken": "Not responding"}
|
|
47
|
+
XCODE_PREFLIGHT = {
|
|
48
|
+
"missing": "Free from the Mac App Store. It's a large download, so start it now.",
|
|
49
|
+
"license": "Open Xcode once and accept its license.",
|
|
50
|
+
"first_launch": "Open Xcode once so it can finish installing.",
|
|
51
|
+
"no_ios": "Open Xcode › Settings › Components and get iOS.",
|
|
52
|
+
"broken": "Open Xcode once so it can finish installing.",
|
|
53
|
+
}
|
|
54
|
+
def plan_ready():
|
|
55
|
+
"""Whether Mobster's agent runs a task typed in Mobster without an OpenAI or Anthropic key."""
|
|
56
|
+
from . import engines
|
|
57
|
+
try:
|
|
58
|
+
return (engines.model_provider(engines.smart_model()) not in ("openai", "anthropic")
|
|
59
|
+
and bool(engines.smart_key()))
|
|
60
|
+
except Exception: # noqa: BLE001 -- a check that fails leaves the step to the keys
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def agents_ready():
|
|
65
|
+
"""The coding agents ready on this Mac (installed and signed in: Codex, Claude Code, Gemini CLI), by name."""
|
|
66
|
+
from .ai_plans import agents
|
|
67
|
+
try:
|
|
68
|
+
return [state.get("name") or agent_id for agent_id,
|
|
69
|
+
state in agents.snapshot(max_age=300).items() if state.get("ready")]
|
|
70
|
+
except Exception: # noqa: BLE001 -- a check that fails leaves the step to the keys
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def agent_detail(names):
|
|
75
|
+
"""The AI step's line when a coding agent is what typed tasks run on."""
|
|
76
|
+
listed = names[0] if len(names) == 1 else f"{', '.join(names[:-1])} or {names[-1]}"
|
|
77
|
+
return f"Tasks you type run on {listed}, signed in on this Mac."
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# The AI account step (MESSAGING §11, Setup): whose account Mobster's agent uses, in the user's words.
|
|
81
|
+
KEY_DETAIL = {
|
|
82
|
+
"plan": "Mobster runs on a plan you already pay for. Settings › Models shows which.",
|
|
83
|
+
"openai": "Mobster's agent uses your OpenAI account. Your key is saved privately on this Mac.",
|
|
84
|
+
"anthropic": "Mobster's agent uses your Claude account. Your key is saved privately on this Mac.",
|
|
85
|
+
"none": "Mobster's agent runs on a plan you already pay for or on your own key. "
|
|
86
|
+
"Choose one to run tasks you type here.",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# The Photos step's line for each answer the helper gives (MOBSTER_QUEUE_PREFLIGHT, phone_ready.py). The person answers
|
|
91
|
+
# iOS's question on the iPhone; Mobster never presses it.
|
|
92
|
+
PHOTOS_DETAIL = {
|
|
93
|
+
"authorized": "Mobster can add the photos and videos you queue to Photos.",
|
|
94
|
+
"limited": "Mobster can add the photos and videos you queue to Photos. Limit Access is enough.",
|
|
95
|
+
"not_determined": "Press Allow, then answer your iPhone's question. Limit Access is enough.",
|
|
96
|
+
"denied": "Photos access is off for Mobster on your iPhone. Turn it on in Settings › Privacy & Security › Photos › "
|
|
97
|
+
"WebDriverAgentRunner-Runner. Limit Access is enough.",
|
|
98
|
+
"restricted": "Photos is restricted on your iPhone (Screen Time or a profile), so Mobster can't add to it.",
|
|
99
|
+
None: "Mobster couldn't read Photos access on your iPhone yet. Check again in a moment.",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ToolsInstall:
|
|
104
|
+
"""Installs the iPhone connection tools with Homebrew in the background, one line of progress at a time."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, popen=subprocess.Popen):
|
|
107
|
+
self.popen = popen
|
|
108
|
+
self.lock = threading.Lock()
|
|
109
|
+
self.current = {"state": "idle", "line": None, "error": None}
|
|
110
|
+
|
|
111
|
+
def status(self):
|
|
112
|
+
with self.lock:
|
|
113
|
+
return dict(self.current)
|
|
114
|
+
|
|
115
|
+
def start(self, brew, on_done=None):
|
|
116
|
+
with self.lock:
|
|
117
|
+
if self.current["state"] == "running":
|
|
118
|
+
return
|
|
119
|
+
self.current = {"state": "running", "line": "Getting ready", "error": None}
|
|
120
|
+
threading.Thread(target=self.run, args=(brew, on_done), daemon=True, name="mobster-tools-install").start()
|
|
121
|
+
|
|
122
|
+
def update(self, **changes):
|
|
123
|
+
with self.lock:
|
|
124
|
+
self.current = {**self.current, **changes}
|
|
125
|
+
|
|
126
|
+
def run(self, brew, on_done=None):
|
|
127
|
+
env = {**os.environ, "HOMEBREW_NO_AUTO_UPDATE": "1", "HOMEBREW_NO_ENV_HINTS": "1", "NONINTERACTIVE": "1"}
|
|
128
|
+
tail = []
|
|
129
|
+
try:
|
|
130
|
+
process = self.popen([brew, "install", *INSTALL_FORMULAE], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
131
|
+
stdin=subprocess.DEVNULL, text=True, env=env)
|
|
132
|
+
for raw in process.stdout:
|
|
133
|
+
line = raw.strip()
|
|
134
|
+
if line:
|
|
135
|
+
tail = (tail + [line])[-12:]
|
|
136
|
+
words = install_words(line)
|
|
137
|
+
if words:
|
|
138
|
+
self.update(line=words)
|
|
139
|
+
code = process.wait()
|
|
140
|
+
except OSError as error:
|
|
141
|
+
self.update(state="failed", line=None, error=f"Homebrew didn't start ({error.strerror or error}).")
|
|
142
|
+
return
|
|
143
|
+
if code == 0:
|
|
144
|
+
self.update(state="succeeded", line=None, error=None)
|
|
145
|
+
else:
|
|
146
|
+
last = next((line for line in reversed(tail) if line.lower().startswith("error")), tail[-1] if tail else "")
|
|
147
|
+
self.update(state="failed", line=None,
|
|
148
|
+
error="The install didn't finish. Check your internet connection, then try again." +
|
|
149
|
+
(f" Homebrew said: {last[:200]}" if last else ""))
|
|
150
|
+
if on_done is not None:
|
|
151
|
+
on_done()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def install_words(line):
|
|
155
|
+
"""Homebrew's progress line in a person's words ("Downloading libplist"),
|
|
156
|
+
or None for a line that says nothing new."""
|
|
157
|
+
match = re.match(r"==> (Fetching|Pouring|Installing|Upgrading)\s+(?:dependencies for\s+)?([\w@.+-]+)", line)
|
|
158
|
+
if not match:
|
|
159
|
+
return None
|
|
160
|
+
verb = {"Fetching": "Downloading", "Pouring": "Installing", "Installing": "Installing",
|
|
161
|
+
"Upgrading": "Updating"}[match[1]]
|
|
162
|
+
name = re.sub(r"(--|\.)[\w.]*bottle.*$", "", match[2]).rstrip(":")
|
|
163
|
+
return f"{verb} {name}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def task_running(runtime):
|
|
167
|
+
"""Whether a task is running now on the runtime's primary phone."""
|
|
168
|
+
try:
|
|
169
|
+
fleet = getattr(runtime, "fleet", None)
|
|
170
|
+
if fleet is not None and callable(getattr(runtime, "run_on", None)):
|
|
171
|
+
return runtime.run_on(fleet.primary) is not None
|
|
172
|
+
return getattr(runtime, "active", None) is not None
|
|
173
|
+
except Exception:
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class SetupService:
|
|
178
|
+
"""``video`` and ``busy`` are a phone's own when it is not the runtime's primary phone (fleet.py)."""
|
|
179
|
+
|
|
180
|
+
def __init__(self, runtime, manager, env_file, video=None, busy=None, stop=None, idle=None):
|
|
181
|
+
self.runtime, self.manager, self.env_file = runtime, manager, env_file
|
|
182
|
+
# What the manager's watchdog and presence need from the runtime: whether a task runs on this phone, how to end
|
|
183
|
+
# it with a plain sentence when its helper is dead (wda/watchdog.py), and what to let go of when the phone has
|
|
184
|
+
# been idle (owner_presence.py: live view, prewarmed screen, awake hold).
|
|
185
|
+
if hasattr(manager, "watchdog"):
|
|
186
|
+
manager.busy = busy or (lambda: task_running(runtime))
|
|
187
|
+
manager.end_task = stop or (lambda message: runtime.stop_phone_run(runtime.fleet.primary, message))
|
|
188
|
+
if idle is not None:
|
|
189
|
+
manager.idle_hooks.append(idle)
|
|
190
|
+
elif callable(getattr(runtime, "release_idle", None)):
|
|
191
|
+
manager.idle_hooks.append(lambda: runtime.release_idle(runtime.fleet.primary))
|
|
192
|
+
self.video = video
|
|
193
|
+
self.cache = {}
|
|
194
|
+
self._team_signing = None
|
|
195
|
+
self.tools_install = ToolsInstall()
|
|
196
|
+
# The watch loop renews the runner before its signature runs out (runner_renewal.py).
|
|
197
|
+
self.renewal = RunnerRenewal(manager, busy=busy or (lambda: task_running(runtime))) \
|
|
198
|
+
if hasattr(manager, "start_build") else None
|
|
199
|
+
if self.renewal is not None:
|
|
200
|
+
manager.renewal = self.renewal
|
|
201
|
+
|
|
202
|
+
def team_signing(self):
|
|
203
|
+
"""Signing without Xcode for a paid team (team_signing/), or None while MOBSTER_EXPERIMENTAL_NO_XCODE is off."""
|
|
204
|
+
from . import team_signing
|
|
205
|
+
if not team_signing.enabled():
|
|
206
|
+
return None
|
|
207
|
+
if self._team_signing is None:
|
|
208
|
+
from .team_signing.service import TeamSigning
|
|
209
|
+
self._team_signing = TeamSigning(self.manager)
|
|
210
|
+
return self._team_signing
|
|
211
|
+
|
|
212
|
+
def _signing(self):
|
|
213
|
+
signing_service = self.team_signing()
|
|
214
|
+
if signing_service is None:
|
|
215
|
+
raise LookupError("Without Xcode is off in this Mobster")
|
|
216
|
+
return signing_service
|
|
217
|
+
|
|
218
|
+
def signing_card(self):
|
|
219
|
+
self._signing().card()
|
|
220
|
+
|
|
221
|
+
def signing_approve(self, digest):
|
|
222
|
+
if not isinstance(digest, str) or not digest:
|
|
223
|
+
raise ValueError("Approve the card by its digest")
|
|
224
|
+
self._signing().approve(digest)
|
|
225
|
+
|
|
226
|
+
def signing_resolve(self, digest, made):
|
|
227
|
+
if not isinstance(digest, str) or type(made) is not bool:
|
|
228
|
+
raise ValueError("Say which change, and whether it was made")
|
|
229
|
+
self._signing().resolve(digest, made)
|
|
230
|
+
|
|
231
|
+
def signing_reveal_developer_mode(self):
|
|
232
|
+
"""Makes the Developer Mode switch appear in Settings on the chosen iPhone. The person still turns it on."""
|
|
233
|
+
from .team_signing import tooling
|
|
234
|
+
device = self.manager.device()
|
|
235
|
+
program = tooling.pmd3()
|
|
236
|
+
if device is None or not device.get("trusted"):
|
|
237
|
+
raise LookupError("Connect your iPhone and tap Trust first")
|
|
238
|
+
if program is None:
|
|
239
|
+
raise LookupError("pymobiledevice3 isn't installed: pipx install pymobiledevice3")
|
|
240
|
+
tooling.run_short(tooling.reveal_developer_mode_command(program, device["udid"]), timeout=60)
|
|
241
|
+
self.cache.pop(("developer_mode", device["udid"]), None)
|
|
242
|
+
|
|
243
|
+
def developer_mode(self, device):
|
|
244
|
+
"""Whether Developer Mode is on (True or False), or None when iOS or libimobiledevice can't say."""
|
|
245
|
+
if not device or not device.get("trusted") or not device.get("udid"):
|
|
246
|
+
return None
|
|
247
|
+
cached = self.cache.get(("developer_mode", device["udid"]))
|
|
248
|
+
if cached and time.monotonic() - cached[0] < DEVELOPER_MODE_TTL[cached[1] is not True]:
|
|
249
|
+
return cached[1]
|
|
250
|
+
info = mac_tools.tool("ideviceinfo")
|
|
251
|
+
output = mac_tools.run([info, "-u", device["udid"], "-q", "com.apple.security.mac.amfi", "-k",
|
|
252
|
+
"DeveloperModeStatus"], timeout=5) if info else None
|
|
253
|
+
value = {"true": True, "false": False}.get((output or "").strip().lower())
|
|
254
|
+
self.cache[("developer_mode", device["udid"])] = (time.monotonic(), value)
|
|
255
|
+
return value
|
|
256
|
+
|
|
257
|
+
def tools_step(self, tools):
|
|
258
|
+
"""The tools step's detail and commands: Xcode first (it takes longest), then the iPhone connection tools."""
|
|
259
|
+
xcode = tools["xcode"]
|
|
260
|
+
usb_ok = bool(tools["iproxy"] and tools["idevice_id"])
|
|
261
|
+
bundled = (usb_ok and mac_tools.bundled_tool("iproxy") == tools["iproxy"]
|
|
262
|
+
and mac_tools.bundled_tool("idevice_id") == tools["idevice_id"])
|
|
263
|
+
homebrew = any(mac_tools.executable(directory, "brew") for directory in BREW_DIRS)
|
|
264
|
+
commands = [] if usb_ok else ([] if homebrew else [INSTALL_HOMEBREW]) + [INSTALL_TOOLS]
|
|
265
|
+
# Homebrew's installer can switch the Mac to the Command Line Tools, so Xcode's fixes come after it.
|
|
266
|
+
commands += xcode["fixes"]
|
|
267
|
+
if xcode["state"] == "ok" and usb_ok:
|
|
268
|
+
detail = f"{xcode['version'] or 'Xcode'} and the iPhone connection tools are installed."
|
|
269
|
+
elif xcode["state"] != "ok":
|
|
270
|
+
detail = XCODE_DETAIL.get(xcode["state"], XCODE_DETAIL["broken"])
|
|
271
|
+
elif homebrew:
|
|
272
|
+
detail = "Install the iPhone connection tools, which let this Mac talk to your iPhone over the cable."
|
|
273
|
+
else:
|
|
274
|
+
detail = ("Install Homebrew, then the iPhone connection tools, which let this Mac talk to your iPhone over "
|
|
275
|
+
"the cable.")
|
|
276
|
+
return detail, commands, {"xcode": {key: xcode[key] for key in ("state", "version", "app")},
|
|
277
|
+
"libimobiledevice": usb_ok, "homebrew": homebrew, "bundled": bundled,
|
|
278
|
+
"install": self.tools_install.status()}
|
|
279
|
+
|
|
280
|
+
def preflight(self, tools, devices, device, usb, without_xcode=False):
|
|
281
|
+
"""Setup's first screen: Xcode, the cable, trust and Developer Mode, each with a word."""
|
|
282
|
+
xcode = tools["xcode"]
|
|
283
|
+
usb_tools = bool(tools["idevice_id"])
|
|
284
|
+
connected = bool(devices) or bool(usb)
|
|
285
|
+
|
|
286
|
+
def check(id_, title, state, word, detail):
|
|
287
|
+
return {"id": id_, "title": title, "state": state, "word": word, "detail": detail}
|
|
288
|
+
|
|
289
|
+
xcode_detail = (xcode["version"] or "Xcode is installed.") if xcode["state"] == "ok" else \
|
|
290
|
+
XCODE_PREFLIGHT.get(xcode["state"], XCODE_PREFLIGHT["broken"])
|
|
291
|
+
checks = [check("xcode", "Xcode", "ok" if xcode["state"] == "ok" else "todo",
|
|
292
|
+
XCODE_WORD.get(xcode["state"], "Not responding"),
|
|
293
|
+
xcode_detail)]
|
|
294
|
+
if without_xcode:
|
|
295
|
+
detail = "Mobster signs its helper on this Mac and installs it without Xcode (a paid Apple Developer team)."
|
|
296
|
+
checks = [check("xcode", "Xcode", "ok", "Not needed", detail)]
|
|
297
|
+
if connected:
|
|
298
|
+
count = len(devices) or usb or 1
|
|
299
|
+
name = device["name"] if device and device.get("trusted") else None
|
|
300
|
+
checks.append(check("cable", "iPhone cable", "ok", "Connected",
|
|
301
|
+
f"“{name}” is connected." if name else
|
|
302
|
+
f"{count} iPhones are connected." if count > 1 else "Your iPhone is connected."))
|
|
303
|
+
elif usb_tools or usb is not None:
|
|
304
|
+
checks.append(check("cable", "iPhone cable", "todo", "Not connected",
|
|
305
|
+
"Plug your iPhone into this Mac with a cable that carries data."))
|
|
306
|
+
else:
|
|
307
|
+
checks.append(check("cable", "iPhone cable", "unknown", "Can't tell yet",
|
|
308
|
+
"Mobster sees the cable once the iPhone tools are installed."))
|
|
309
|
+
trusted = bool(device and device.get("trusted")) or any(item.get("trusted") for item in devices)
|
|
310
|
+
if trusted:
|
|
311
|
+
checks.append(check("trust", "Trust this Mac", "ok", "Trusted", "Your iPhone trusts this Mac."))
|
|
312
|
+
elif devices:
|
|
313
|
+
checks.append(check("trust", "Trust this Mac", "todo", "Tap Trust",
|
|
314
|
+
"Unlock your iPhone, tap Trust and enter your passcode."))
|
|
315
|
+
elif connected and not usb_tools:
|
|
316
|
+
checks.append(check("trust", "Trust this Mac", "unknown", "Can't tell yet",
|
|
317
|
+
"Mobster checks this once the iPhone tools are installed."))
|
|
318
|
+
else:
|
|
319
|
+
checks.append(check("trust", "Trust this Mac", "waiting", "Waiting", "Connect your iPhone first."))
|
|
320
|
+
mode = device.get("developerMode") if device else None
|
|
321
|
+
if not trusted:
|
|
322
|
+
checks.append(check("developer_mode", "Developer Mode", "waiting", "Waiting",
|
|
323
|
+
"Mobster reads it once your iPhone trusts this Mac."))
|
|
324
|
+
elif mode is True:
|
|
325
|
+
checks.append(check("developer_mode", "Developer Mode", "ok", "On", "Developer Mode is on."))
|
|
326
|
+
elif mode is False:
|
|
327
|
+
checks.append(check("developer_mode", "Developer Mode", "todo", "Off",
|
|
328
|
+
"On your iPhone, turn it on in Settings › Privacy & Security › Developer Mode."))
|
|
329
|
+
elif without_xcode:
|
|
330
|
+
checks.append(check("developer_mode", "Developer Mode", "unknown", "Can't tell yet",
|
|
331
|
+
"Mobster reads it over the cable. If Settings › Privacy & Security has no Developer "
|
|
332
|
+
"Mode switch, Setup can show it."))
|
|
333
|
+
else:
|
|
334
|
+
checks.append(check("developer_mode", "Developer Mode", "unknown", "After the first build",
|
|
335
|
+
"The switch appears on your iPhone once Xcode has seen it. Setup asks when it's time."))
|
|
336
|
+
return checks
|
|
337
|
+
|
|
338
|
+
def state(self):
|
|
339
|
+
manager, config = self.manager, self.runtime.config
|
|
340
|
+
# Xcode's checks and the USB phones are independent: read them together. System Information
|
|
341
|
+
# (a second of system_profiler) is asked only when libimobiledevice can't see a phone:
|
|
342
|
+
# it isn't installed, or it lists nothing.
|
|
343
|
+
tools_read = READS.submit(manager.tools)
|
|
344
|
+
devices_read = READS.submit(manager.devices)
|
|
345
|
+
tools = tools_read.result()
|
|
346
|
+
usb_read = None
|
|
347
|
+
if not tools["idevice_id"] and hasattr(manager, "usb_iphones"):
|
|
348
|
+
usb_read = READS.submit(manager.usb_iphones)
|
|
349
|
+
devices = devices_read.result() if tools["idevice_id"] else []
|
|
350
|
+
if usb_read is None and not devices and hasattr(manager, "usb_iphones"):
|
|
351
|
+
usb_read = READS.submit(manager.usb_iphones)
|
|
352
|
+
try:
|
|
353
|
+
usb = usb_read.result() if usb_read is not None else None
|
|
354
|
+
except Exception:
|
|
355
|
+
usb = None
|
|
356
|
+
device = manager.device(devices)
|
|
357
|
+
if device is not None:
|
|
358
|
+
device = {**device, "developerMode": self.developer_mode(device)}
|
|
359
|
+
settings = manager.settings()
|
|
360
|
+
chosen = settings.get("udid")
|
|
361
|
+
chosen_name = f"“{settings['device_name']}”" if settings.get("device_name") else "Your iPhone"
|
|
362
|
+
chosen_in_sentence = chosen_name if settings.get("device_name") else "your iPhone"
|
|
363
|
+
built_for = settings.get("built_for", chosen)
|
|
364
|
+
build = dict(manager.build)
|
|
365
|
+
runner = dict(manager.runner_state())
|
|
366
|
+
stream = self.video() if self.video is not None else self.runtime.video
|
|
367
|
+
video = stream.status() if hasattr(stream, "status") else {}
|
|
368
|
+
has_openai = bool(openai_key()[0])
|
|
369
|
+
has_anthropic = bool(anthropic_key()[0]) # Smart runs on Claude with it (engines.smart_model)
|
|
370
|
+
has_plan = plan_ready() # a plan or provider from Settings › Models, with its sign-in or key
|
|
371
|
+
# Else a coding agent signed in on this Mac: typed tasks go to it (the composer's default, lib/agents).
|
|
372
|
+
agents_on = [] if has_openai or has_anthropic or has_plan else agents_ready()
|
|
373
|
+
# The account Smart plans on (engines.smart_model): with both keys, Claude's unless OpenAI was chosen.
|
|
374
|
+
from . import engines
|
|
375
|
+
claude = has_anthropic and (engines.model_provider(engines.smart_model()) == "anthropic" or not has_openai)
|
|
376
|
+
team_signing = self.team_signing()
|
|
377
|
+
# Without Xcode, the helper is signed here and installed by pymobiledevice3: Xcode and git aren't needed.
|
|
378
|
+
tools_ok = all(tools[name] for name in (("iproxy", "idevice_id") if team_signing else
|
|
379
|
+
("xcodebuild", "iproxy", "idevice_id", "git")))
|
|
380
|
+
tools_detail, tools_commands, tool_states = self.tools_step(tools)
|
|
381
|
+
phone_ok = bool(device and device["trusted"])
|
|
382
|
+
built = manager.built()
|
|
383
|
+
expired = manager.expired()
|
|
384
|
+
expires_at = manager.signature_expiry()
|
|
385
|
+
running = runner["state"] == "running"
|
|
386
|
+
photos = phone_ready.photos_status(manager.wda_url) if running and phone_ready.enabled() else None
|
|
387
|
+
building = build["state"] == "running"
|
|
388
|
+
first_build = build.get("first", not built and not expires_at)
|
|
389
|
+
build.update(expires_at=round(expires_at * 1000) if expires_at else None, expired=expired,
|
|
390
|
+
started_at=round(build["started_at"] * 1000) if build.get("started_at") else None,
|
|
391
|
+
first=bool(first_build), renewal=bool(build.get("renewal")), problem=build.get("problem"),
|
|
392
|
+
team=build.get("team") or settings.get("team"))
|
|
393
|
+
# The runner's signature and its automatic renewal (runner_renewal.py), for Settings › iPhone.
|
|
394
|
+
runner["expires_at"] = build["expires_at"]
|
|
395
|
+
runner["renewal"] = self.renewal.status() if self.renewal is not None else None
|
|
396
|
+
if runner.get("error") and hasattr(manager, "runner_problem"):
|
|
397
|
+
runner["problem"] = manager.runner_problem()
|
|
398
|
+
|
|
399
|
+
def step(id_, title, done, detail, action=None, blocked_by=None, working=False, **extra):
|
|
400
|
+
# blocked_by names the step this one waits on; None when it can start now.
|
|
401
|
+
waiting = None if done or working else blocked_by
|
|
402
|
+
state = "done" if done else "working" if working else "blocked" if waiting else "todo"
|
|
403
|
+
return {"id": id_, "title": title, "state": state, "detail": detail, "action": action,
|
|
404
|
+
"blocked_by": waiting, **extra}
|
|
405
|
+
|
|
406
|
+
# Tools come first: the Xcode download takes longest, and the key can be added while it runs.
|
|
407
|
+
steps = [
|
|
408
|
+
step("tools", "Get your Mac ready", tools_ok, tools_detail, commands=tools_commands),
|
|
409
|
+
step("api_key", "Connect your AI account", has_openai or has_anthropic or has_plan or bool(agents_on),
|
|
410
|
+
agent_detail(agents_on) if agents_on else
|
|
411
|
+
KEY_DETAIL["anthropic" if claude else "openai" if has_openai else "plan" if has_plan else "none"],
|
|
412
|
+
"save_key"),
|
|
413
|
+
step("phone", "Connect your iPhone", phone_ok,
|
|
414
|
+
f"“{device['name']}” is connected and trusts this Mac." if phone_ok else
|
|
415
|
+
"Unlock your iPhone and tap Trust on it." if device else
|
|
416
|
+
f"{chosen_name} isn't connected. Plug it in with a cable and unlock it." if chosen else
|
|
417
|
+
"Choose which iPhone Mobster should use." if len(devices) > 1 else
|
|
418
|
+
"Plug your iPhone into this Mac with a cable and unlock it.",
|
|
419
|
+
"choose_phone" if len(devices) > 1 or chosen and not device else None,
|
|
420
|
+
blocked_by=None if tools_ok else "tools"),
|
|
421
|
+
step("wda_build", "Install Mobster's helper on your iPhone", built,
|
|
422
|
+
"Mobster's helper is installed on your iPhone." if built else
|
|
423
|
+
(FIRST_BUILD if first_build else REBUILD) if building else
|
|
424
|
+
build["error"] or (EXPIRED_DETAIL if expired else None) or
|
|
425
|
+
(f"Mobster's helper was installed on another iPhone. Install it again on {chosen_in_sentence}."
|
|
426
|
+
if built_for and chosen and built_for != chosen else None) or
|
|
427
|
+
"Xcode signs Mobster's helper with your Apple Account and installs it on your iPhone. "
|
|
428
|
+
"The first install takes about 2 minutes.",
|
|
429
|
+
"build", blocked_by=None if phone_ok else "phone", working=building,
|
|
430
|
+
problem=build.get("problem") if build["state"] == "failed" and not building else None),
|
|
431
|
+
step("wda_running", "Start Mobster's helper", running,
|
|
432
|
+
(runner["error"] or "Mobster is connected to your iPhone.") if running else
|
|
433
|
+
runner["error"] or "Mobster's helper keeps the connection to your iPhone open while Mobster runs.",
|
|
434
|
+
"start", blocked_by="phone" if not phone_ok else None if built else "wda_build",
|
|
435
|
+
working=runner["state"] == "starting",
|
|
436
|
+
problem=(runner.get("problem") if runner["state"] in ("failed", "starting") and runner.get("error")
|
|
437
|
+
else None)),
|
|
438
|
+
step("live", "Allow Mobster to tap and type", bool(config.enable_live),
|
|
439
|
+
"Tasks can tap, scroll and type on your iPhone." if config.enable_live else
|
|
440
|
+
"Turn this on so tasks can tap, scroll and type.", "enable_live"),
|
|
441
|
+
*([step("photos", "Let Mobster add to Photos", photos in phone_ready.GRANTED, PHOTOS_DETAIL[photos],
|
|
442
|
+
"allow_photos" if photos == "not_determined" else None,
|
|
443
|
+
blocked_by=None if running else "wda_running", working=running and photos is None)]
|
|
444
|
+
if phone_ready.enabled() else []),
|
|
445
|
+
step("video", "See your iPhone live", video.get("capturedAt") is not None,
|
|
446
|
+
"Your iPhone's screen is streaming." if video.get("capturedAt") else
|
|
447
|
+
"A live picture of your screen confirms everything works.",
|
|
448
|
+
blocked_by=None if running else "wda_running"),
|
|
449
|
+
]
|
|
450
|
+
teams = [{**team, "label": signing.team_label(team)} for team in manager.teams()]
|
|
451
|
+
# Photos is for the content queue only: the rest of setup is complete without it.
|
|
452
|
+
return {"complete": all(item["state"] == "done" for item in steps if item["id"] != "photos"), "steps": steps,
|
|
453
|
+
"device": device, "devices": devices, "chosen": chosen, "teams": teams,
|
|
454
|
+
"tools": tool_states, "build": build, "runner": runner,
|
|
455
|
+
"keys": {"openai": has_openai, "anthropic": has_anthropic},
|
|
456
|
+
"preflight": self.preflight(tools, devices, device, usb, without_xcode=team_signing is not None),
|
|
457
|
+
**({"teamSigning": team_signing.status()} if team_signing is not None else {}),
|
|
458
|
+
"wifi": self.wifi_step(device, built, running)}
|
|
459
|
+
|
|
460
|
+
def wifi_step(self, device, built, running):
|
|
461
|
+
"""The Wi-Fi step of this phone's setup (wireless/onboarding.py): off unless Wi-Fi for iPhones is on."""
|
|
462
|
+
from .wireless import onboarding
|
|
463
|
+
facts = onboarding.Facts(attached=device is not None, trusted=bool(device and device.get("trusted")),
|
|
464
|
+
built=built, running=running, ios=(device or {}).get("ios"))
|
|
465
|
+
return onboarding.step(self._wifi_udid(device), getattr(self.manager, "data_dir", None), facts)
|
|
466
|
+
|
|
467
|
+
def answer_wifi(self, device, yes):
|
|
468
|
+
"""`mobster setup`'s answer to "use this iPhone over Wi-Fi?" (state()'s ``wifi`` is ``asking``). LookupError
|
|
469
|
+
when Mobster isn't asking about it now."""
|
|
470
|
+
from .wireless import onboarding
|
|
471
|
+
onboarding.answer(self._wifi_udid(device), yes)
|
|
472
|
+
|
|
473
|
+
def _wifi_udid(self, device):
|
|
474
|
+
return (getattr(self.manager, "pinned", None) or (device or {}).get("udid")
|
|
475
|
+
or self.manager.settings().get("udid"))
|
|
476
|
+
|
|
477
|
+
def refresh(self):
|
|
478
|
+
"""Check the tools, teams and Developer Mode again now ("Check again")."""
|
|
479
|
+
self.cache.clear()
|
|
480
|
+
if self._team_signing is not None:
|
|
481
|
+
self._team_signing.refresh()
|
|
482
|
+
if hasattr(self.manager, "refresh"):
|
|
483
|
+
self.manager.refresh()
|
|
484
|
+
|
|
485
|
+
def install_tools(self):
|
|
486
|
+
"""Setup's Install: the iPhone connection tools, with Homebrew, in the background. Status reads its progress."""
|
|
487
|
+
tools = self.manager.tools()
|
|
488
|
+
if tools["iproxy"] and tools["idevice_id"]:
|
|
489
|
+
return
|
|
490
|
+
brew = next((path for directory in BREW_DIRS if (path := mac_tools.executable(directory, "brew"))), None)
|
|
491
|
+
if not brew:
|
|
492
|
+
raise LookupError("Mobster needs Homebrew to install these on this Mac. Help › For developers shows how.")
|
|
493
|
+
self.tools_install.start(brew, on_done=self.refresh)
|
|
494
|
+
|
|
495
|
+
def open_xcode(self, opener=subprocess.Popen):
|
|
496
|
+
"""Setup's Open Xcode: Xcode finishes installing its components the first time it opens."""
|
|
497
|
+
app = (self.manager.tools().get("xcode") or {}).get("app")
|
|
498
|
+
if not app:
|
|
499
|
+
raise LookupError("Xcode isn't installed yet. Get it from the App Store first.")
|
|
500
|
+
opener(["/usr/bin/open", app], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
501
|
+
self.refresh()
|
|
502
|
+
|
|
503
|
+
def allow_photos(self, device=None):
|
|
504
|
+
"""Setup's Allow for Photos on ``device`` (None: the first phone): iOS shows its Photos question on the
|
|
505
|
+
iPhone and the person answers there, for up to phone_io.photos.AUTHORIZE_WAIT_S seconds. Nothing is saved or
|
|
506
|
+
pressed.
|
|
507
|
+
LookupError (its sentence is the answer) when the check is off, a task has the phone, or the helper can't."""
|
|
508
|
+
if not phone_ready.enabled():
|
|
509
|
+
raise LookupError("Setup's Photos step is off in this Mobster (MOBSTER_QUEUE_PREFLIGHT).")
|
|
510
|
+
from .phone_io import PhoneIOError
|
|
511
|
+
from .phone_io.photos import authorize
|
|
512
|
+
from .phone_io.transfer import phone_from_runtime
|
|
513
|
+
try:
|
|
514
|
+
phone = phone_from_runtime(self.runtime, device)
|
|
515
|
+
if phone.runner is None:
|
|
516
|
+
raise PhoneIOError("Mobster's helper isn't set up on this iPhone yet.", "no_device")
|
|
517
|
+
with phone.runner() as request:
|
|
518
|
+
authorize(request, name=phone.name, activate=lambda bundle: request(
|
|
519
|
+
"POST", "/wda/apps/activate", {"bundleId": bundle}, 15))
|
|
520
|
+
except PhoneIOError as error:
|
|
521
|
+
raise LookupError(str(error)) from None
|
|
522
|
+
|
|
523
|
+
def set_live(self, enabled):
|
|
524
|
+
if type(enabled) is not bool:
|
|
525
|
+
raise ValueError("enabled must be true or false")
|
|
526
|
+
update_env_file(self.env_file, {"MOBSTER_ENABLE_LIVE": "1" if enabled else "0"})
|
|
527
|
+
self.runtime.config.enable_live = enabled
|
mobile_agent/share.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Share links (docs/results.mdx › Share a result): one task's result as a page anyone with the link can read, at
|
|
2
|
+
mobster.dev/s/<id>, until its owner turns it off or it expires."""
|
|
3
|
+
|
|
4
|
+
import http.client
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import socket
|
|
8
|
+
import ssl
|
|
9
|
+
from urllib.parse import urlsplit
|
|
10
|
+
|
|
11
|
+
from . import artifacts, pinning
|
|
12
|
+
from .transport import user_agent
|
|
13
|
+
|
|
14
|
+
ACCOUNT_API = "https://api.mobster.dev"
|
|
15
|
+
TIMEOUT = 20.0
|
|
16
|
+
EXPIRY_DAYS = (1, 7, 30, 90)
|
|
17
|
+
MAX_PAGE_BYTES = 4_000_000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ShareError(Exception):
|
|
21
|
+
"""The account service refused or couldn't be reached."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, code, status, message):
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
self.code, self.status = code, status
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def api_base():
|
|
29
|
+
"""The account service: MOBSTER_ACCOUNT_API (development: http on this Mac) or https://api.mobster.dev."""
|
|
30
|
+
value = (os.environ.get("MOBSTER_ACCOUNT_API") or ACCOUNT_API).rstrip("/")
|
|
31
|
+
parts = urlsplit(value)
|
|
32
|
+
if parts.scheme == "https" or parts.scheme == "http" and parts.hostname in ("127.0.0.1", "localhost"):
|
|
33
|
+
return value
|
|
34
|
+
return ACCOUNT_API
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def page(run, *, frames=None, people="mask", screens=False, title=None):
|
|
38
|
+
"""The page as it's shared: see the module docstring."""
|
|
39
|
+
return artifacts.document(run, frames=frames, people=people, screens=screens, share=True, title=title)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def call(method, path, token, body=None, timeout=TIMEOUT, limit=256_000):
|
|
43
|
+
"""(HTTP status, the JSON object answered or {}) of one request to the account service, ``token`` its Bearer, with
|
|
44
|
+
``body`` as JSON (bytes go as they are)."""
|
|
45
|
+
base = api_base()
|
|
46
|
+
parts = urlsplit(base)
|
|
47
|
+
connection = (pinning.PinnedHTTPSConnection(parts.hostname, parts.port or 443, timeout=timeout)
|
|
48
|
+
if parts.scheme == "https"
|
|
49
|
+
else http.client.HTTPConnection(parts.hostname, parts.port or 80, timeout=timeout))
|
|
50
|
+
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json", "User-Agent": user_agent()}
|
|
51
|
+
payload = None
|
|
52
|
+
if body is not None:
|
|
53
|
+
payload = body if isinstance(body, bytes) else json.dumps(body).encode()
|
|
54
|
+
headers["Content-Type"] = "application/json"
|
|
55
|
+
try:
|
|
56
|
+
connection.request(method, f"{parts.path.rstrip('/')}{path}", body=payload, headers=headers)
|
|
57
|
+
response = connection.getresponse()
|
|
58
|
+
data = response.read(limit)
|
|
59
|
+
except pinning.PinMismatch:
|
|
60
|
+
raise ShareError("untrusted_server", 503, "Mobster couldn't verify its server, so it didn't connect.") from None
|
|
61
|
+
except (OSError, socket.timeout, http.client.HTTPException, ssl.SSLError):
|
|
62
|
+
raise ShareError("unreachable", 503, "Mobster couldn't reach its account service. Check your connection and "
|
|
63
|
+
"try again.") from None
|
|
64
|
+
finally:
|
|
65
|
+
headers = None # noqa: F841 -- the token's only other reference goes with this frame
|
|
66
|
+
connection.close()
|
|
67
|
+
try:
|
|
68
|
+
value = json.loads(data) if data else {}
|
|
69
|
+
except ValueError:
|
|
70
|
+
value = {}
|
|
71
|
+
return response.status, value if isinstance(value, dict) else {}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _request(method, path, token, body=None, timeout=TIMEOUT):
|
|
75
|
+
status, value = call(method, path, token, body, timeout)
|
|
76
|
+
if 200 <= status < 300:
|
|
77
|
+
return value
|
|
78
|
+
message = value.get("message") if isinstance(value.get("message"), str) else None
|
|
79
|
+
code = value.get("error") if isinstance(value.get("error"), str) else "error"
|
|
80
|
+
if status == 401:
|
|
81
|
+
raise ShareError("session", 401, "Sign in to Mobster again to share.")
|
|
82
|
+
if status in (400, 403, 404, 409, 413):
|
|
83
|
+
raise ShareError(code, status, message or "Mobster's account service refused that link.")
|
|
84
|
+
raise ShareError("error", 502,
|
|
85
|
+
message or "Mobster's account service couldn't make the link. Try again in a minute.")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create(token, *, title, html, expires_days=30):
|
|
89
|
+
"""The new link: {id, url, title, createdAt, expiresAt, live, bytes}."""
|
|
90
|
+
if expires_days is not None and expires_days not in EXPIRY_DAYS:
|
|
91
|
+
raise ValueError("expiresDays is 1, 7, 30, 90 or null")
|
|
92
|
+
if len(html.encode("utf-8")) > MAX_PAGE_BYTES:
|
|
93
|
+
raise ShareError("too_large", 413, "That page is over 4 MB. Leave out the screens, or share fewer cards.")
|
|
94
|
+
value = _request("POST", "/api/shares", token, {"title": title, "html": html, "expiresDays": expires_days})
|
|
95
|
+
share = value.get("share")
|
|
96
|
+
if not isinstance(share, dict) or not isinstance(share.get("id"), str) or not isinstance(share.get("url"), str):
|
|
97
|
+
raise ShareError("error", 502, "Mobster's account service answered without a link. Try again.")
|
|
98
|
+
return share
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def revoke(token, share_id):
|
|
102
|
+
"""Turns a link off; its page is deleted at once. The link as it is now."""
|
|
103
|
+
value = _request("DELETE", f"/api/shares/{share_id}", token)
|
|
104
|
+
return value.get("share") if isinstance(value.get("share"), dict) else {"id": share_id, "live": False}
|