mobster-cli 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""The kinds of destination, and what each one takes and does with a result."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
from . import net, webhook
|
|
8
|
+
from .webhook import Outcome
|
|
9
|
+
|
|
10
|
+
FIELDS = ("name", "kind", "events", "scope")
|
|
11
|
+
# The kinds that take events besides a result (run.started, needs_you): the ones that post a message or JSON. A row in
|
|
12
|
+
# a sheet, a page, a file line or an email per result stays a result.
|
|
13
|
+
EVENT_KINDS = frozenset({"webhook", "slack", "discord"})
|
|
14
|
+
EVENTS = ("run.finished", "run.started", "needs_you")
|
|
15
|
+
SCOPES = ("named", "all") # named: runs that choose it (sendTo); all: every run
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def subscription(body, kind_id, current=None):
|
|
19
|
+
"""{"events", "scope"} from a body's ``events`` and ``scope`` (each optional), over ``current``'s."""
|
|
20
|
+
current = current or {}
|
|
21
|
+
events = body.get("events", current.get("events") or ["run.finished"])
|
|
22
|
+
scope = body.get("scope", current.get("scope") or "named")
|
|
23
|
+
if not isinstance(events, list) or not events or not all(isinstance(e, str) and e in EVENTS for e in events):
|
|
24
|
+
raise ValueError("events is a list of run.finished, run.started and needs_you, at least one")
|
|
25
|
+
events = [e for e in EVENTS if e in events]
|
|
26
|
+
if kind_id not in EVENT_KINDS and events != ["run.finished"]:
|
|
27
|
+
raise ValueError("This kind of destination takes results only. Use a webhook, Slack or Discord for the other "
|
|
28
|
+
"events")
|
|
29
|
+
if scope not in SCOPES:
|
|
30
|
+
raise ValueError("scope is named (tasks that choose it) or all (every task)")
|
|
31
|
+
return {"events": events, "scope": scope}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Kind:
|
|
35
|
+
id = "webhook"
|
|
36
|
+
label = "Webhook"
|
|
37
|
+
fields = () # what the add and update bodies may hold besides name and kind
|
|
38
|
+
example = "My server" # a name for one, in the empty-name error (the app's kindCopy names the same)
|
|
39
|
+
replay_safe = True # False: marked in flight before each try, so a crash mid-try never sends it twice
|
|
40
|
+
keychain = True # keeps an item in the Keychain (vault.py)
|
|
41
|
+
provider = None # the license API's sign-in Connect uses (connect.py), or None: it doesn't connect
|
|
42
|
+
keep = () # fields of that sign-in's token answer ``connected`` reads (Discord's ``webhook``)
|
|
43
|
+
|
|
44
|
+
def setup(self, body, current=None):
|
|
45
|
+
"""{"hint", "config", "has_secret", "screenshot", "vault", "last_test"}, the parts that change."""
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
def send(self, ctx, body):
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
|
|
51
|
+
def test(self, ctx):
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
def connected(self, call, tokens):
|
|
55
|
+
"""What a finished sign-in found: {"credentials", "account", "targets"} (connect.py)."""
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def connection(body, kind):
|
|
60
|
+
"""What a Connect found (connect.py), as the service put it in ``body["connect"]``; ValueError without one."""
|
|
61
|
+
found = body.get("connect")
|
|
62
|
+
if not isinstance(found, dict):
|
|
63
|
+
raise ValueError(f"Connect {kind.label} first.")
|
|
64
|
+
return found
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def target(found, value, what):
|
|
68
|
+
"""The one of a Connect's ``targets`` that ``value`` names: {"id", "name"}. ValueError when none does."""
|
|
69
|
+
for item in found.get("targets") or ():
|
|
70
|
+
if item["id"] == value:
|
|
71
|
+
return item
|
|
72
|
+
raise ValueError(f"Pick {what} from the list.")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def again(label):
|
|
76
|
+
return f"Connect {label} again in Settings › Connectors."
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def boolean(body, key, default=False):
|
|
80
|
+
value = body.get(key, default)
|
|
81
|
+
if type(value) is not bool:
|
|
82
|
+
raise ValueError(f"{key} is true or false")
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
COLUMNS = ("Finished", "Status", "Answer", "Reason", "Workflow", "Data", "Run")
|
|
87
|
+
EXTRA_COLUMNS = 30
|
|
88
|
+
RISKY = ("=", "+", "-", "@", "\t", "\r")
|
|
89
|
+
NUMBER = re.compile(r"-?\d+(?:\.\d+)?")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def cell(value):
|
|
93
|
+
"""A spreadsheet-safe cell: text that a spreadsheet would read as a formula (=, +, -, @) starts with an apostrophe,
|
|
94
|
+
so text from a screen can never run as one."""
|
|
95
|
+
if value is None:
|
|
96
|
+
return ""
|
|
97
|
+
if isinstance(value, bool) or isinstance(value, (int, float)):
|
|
98
|
+
return value
|
|
99
|
+
if not isinstance(value, str):
|
|
100
|
+
value = json.dumps(value, ensure_ascii=False)
|
|
101
|
+
if value.startswith(RISKY) and not NUMBER.fullmatch(value):
|
|
102
|
+
return "'" + value
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def flat(body):
|
|
107
|
+
"""The result as one row by column name: COLUMNS, then each blank of its workflow, then each top-level field of its
|
|
108
|
+
data that is text, a number or true/false (at most EXTRA_COLUMNS)."""
|
|
109
|
+
flow = body.get("workflow") or {}
|
|
110
|
+
data = body.get("data")
|
|
111
|
+
row = {"Finished": body.get("finishedAt") or "", "Status": body.get("status") or "",
|
|
112
|
+
"Answer": body.get("answer") or "", "Reason": body.get("reason") or "", "Workflow": flow.get("name") or "",
|
|
113
|
+
"Data": json.dumps(data, ensure_ascii=False) if data is not None else "", "Run": body.get("runId") or ""}
|
|
114
|
+
extra = list((flow.get("blanks") or {}).items())
|
|
115
|
+
if isinstance(data, dict):
|
|
116
|
+
extra += [(k, v) for k, v in data.items() if isinstance(v, (str, int, float, bool)) or v is None]
|
|
117
|
+
known = {name.casefold() for name in row}
|
|
118
|
+
for key, value in extra[:EXTRA_COLUMNS]:
|
|
119
|
+
name = " ".join(str(key).split())[:60]
|
|
120
|
+
if name and name.casefold() not in known:
|
|
121
|
+
known.add(name.casefold())
|
|
122
|
+
row[name] = value if value is not None else ""
|
|
123
|
+
return row
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def title_of(body, limit=200):
|
|
127
|
+
"""One line for a title or a subject: the workflow's name, then the answer, or how it ended."""
|
|
128
|
+
flow = (body.get("workflow") or {}).get("name")
|
|
129
|
+
text = body.get("text") or ""
|
|
130
|
+
line = text.split(": ", 1)[1] if ": " in text else text
|
|
131
|
+
line = f"{flow}: {line}" if flow else line
|
|
132
|
+
return line if len(line) <= limit else line[:limit - 1] + "…"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def careful(attempt, where):
|
|
136
|
+
"""One try at a destination that can't take the same result twice: ``attempt()`` -> Reply."""
|
|
137
|
+
try:
|
|
138
|
+
return attempt(), None
|
|
139
|
+
except net.NetError as error:
|
|
140
|
+
if error.unsent:
|
|
141
|
+
return None, Outcome(False, error.retry, None, error.message, unsent=True)
|
|
142
|
+
return None, Outcome(False, False, None, f"{error.message} It may have arrived: check {where}, then resend "
|
|
143
|
+
"if it's missing.")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
PRESETS = {
|
|
147
|
+
"zapier": (re.compile(r"hooks\.zapier\.com"),
|
|
148
|
+
"Use the address of Zapier's Catch Hook trigger. It starts with https://hooks.zapier.com/hooks/catch/."),
|
|
149
|
+
"make": (re.compile(r"hook\.(?:[a-z0-9-]+\.)?make\.com|hook\.(?:[a-z0-9-]+\.)?integromat\.com"),
|
|
150
|
+
"Use the address of Make's Custom webhook module. It starts with https://hook. and ends in make.com/…."),
|
|
151
|
+
"n8n": (None, None), # any host: n8n runs in the cloud or on your own server
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def preset_url(preset, url):
|
|
156
|
+
"""``url`` when it is where ``preset``'s webhooks live; AddressError otherwise."""
|
|
157
|
+
pattern, words = PRESETS[preset]
|
|
158
|
+
if pattern is not None:
|
|
159
|
+
parts = urlsplit(url)
|
|
160
|
+
if parts.scheme != "https" or not pattern.fullmatch((parts.hostname or "").lower()):
|
|
161
|
+
raise webhook.AddressError(words)
|
|
162
|
+
return url
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Webhook(Kind):
|
|
166
|
+
id, label = "webhook", "Webhook"
|
|
167
|
+
fields = ("url", "secret", "screenshot", "preset")
|
|
168
|
+
|
|
169
|
+
def setup(self, body, current=None):
|
|
170
|
+
if current is None:
|
|
171
|
+
preset = body.get("preset")
|
|
172
|
+
if preset is not None and preset not in PRESETS:
|
|
173
|
+
raise ValueError("A webhook's preset is zapier, make or n8n")
|
|
174
|
+
url = webhook.validate_url(body.get("url"))
|
|
175
|
+
if preset:
|
|
176
|
+
preset_url(preset, url)
|
|
177
|
+
secret = webhook.validate_secret(body.get("secret"))
|
|
178
|
+
return {"hint": webhook.hint(url), "has_secret": secret is not None,
|
|
179
|
+
"config": {"preset": preset} if preset else {},
|
|
180
|
+
"screenshot": boolean(body, "screenshot"), "vault": {"url": url, "secret": secret}}
|
|
181
|
+
if "preset" in body and body["preset"] != current["row"].get("config", {}).get("preset"):
|
|
182
|
+
raise ValueError("A webhook keeps its preset. Add a new one instead.")
|
|
183
|
+
changes = {}
|
|
184
|
+
if "screenshot" in body:
|
|
185
|
+
changes["screenshot"] = boolean(body, "screenshot")
|
|
186
|
+
if "url" in body or "secret" in body:
|
|
187
|
+
url = webhook.validate_url(body["url"]) if "url" in body else None
|
|
188
|
+
preset = current["row"].get("config", {}).get("preset")
|
|
189
|
+
if url and preset in PRESETS:
|
|
190
|
+
preset_url(preset, url)
|
|
191
|
+
secret = webhook.validate_secret(body["secret"]) if "secret" in body else None
|
|
192
|
+
saved = current["saved"]()
|
|
193
|
+
if saved is None and url is None:
|
|
194
|
+
raise ValueError("Paste the address again: Mobster no longer has it.")
|
|
195
|
+
url = url or saved["url"]
|
|
196
|
+
secret = secret if "secret" in body else (saved or {}).get("secret")
|
|
197
|
+
changes.update(vault={"url": url, "secret": secret}, hint=webhook.hint(url), has_secret=secret is not None,
|
|
198
|
+
last_test=None)
|
|
199
|
+
return changes
|
|
200
|
+
|
|
201
|
+
def send(self, ctx, body):
|
|
202
|
+
data = webhook.encode_body(ctx.saved["url"], body)
|
|
203
|
+
return ctx.post(ctx.saved["url"], data, webhook.headers(ctx.event, ctx.delivery_id, data, ctx.saved["secret"],
|
|
204
|
+
ctx.now))
|
|
205
|
+
|
|
206
|
+
def test(self, ctx):
|
|
207
|
+
return self.send(ctx, webhook.test_payload(ctx.now))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
DISCORD_HOSTS = ("discord.com", "discordapp.com", "ptb.discord.com", "canary.discord.com")
|
|
211
|
+
DISCORD_MAX = 2000
|
|
212
|
+
SNOWFLAKE = re.compile(r"\d{15,25}")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def discord_url(value):
|
|
216
|
+
"""The webhook address Discord answered, checked: https, one of Discord's hosts, under /api/webhooks/."""
|
|
217
|
+
refused = ValueError("Discord answered with an address Mobster doesn't post to. " + again("Discord"))
|
|
218
|
+
try:
|
|
219
|
+
text = webhook.validate_url(value)
|
|
220
|
+
except webhook.AddressError:
|
|
221
|
+
raise refused from None
|
|
222
|
+
parts = urlsplit(text)
|
|
223
|
+
if parts.scheme != "https" or (parts.hostname or "").lower() not in DISCORD_HOSTS \
|
|
224
|
+
or not parts.path.startswith("/api/webhooks/"):
|
|
225
|
+
raise refused
|
|
226
|
+
return text
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class Discord(Kind):
|
|
230
|
+
"""Connect asks Discord for ``webhook.incoming``: its consent page picks the server and the channel, and its token
|
|
231
|
+
answer holds the webhook it made there."""
|
|
232
|
+
id, label = "discord", "Discord"
|
|
233
|
+
example = "Team channel"
|
|
234
|
+
fields = ("connect",)
|
|
235
|
+
replay_safe = False
|
|
236
|
+
provider, keep = "discord", ("webhook",)
|
|
237
|
+
|
|
238
|
+
def connected(self, call, tokens):
|
|
239
|
+
"""Mobster keeps the webhook's address (Keychain) and the channel's ids, never the access token."""
|
|
240
|
+
hook = (tokens.get("kept") or {}).get("webhook")
|
|
241
|
+
if not isinstance(hook, dict):
|
|
242
|
+
raise ValueError("Discord didn't say which channel to post in. " + again("Discord"))
|
|
243
|
+
url = discord_url(hook.get("url"))
|
|
244
|
+
server, channel = hook.get("guild_id"), hook.get("channel_id")
|
|
245
|
+
if not all(isinstance(value, str) and SNOWFLAKE.fullmatch(value) for value in (server, channel)):
|
|
246
|
+
raise ValueError("Discord didn't say which channel to post in. " + again("Discord"))
|
|
247
|
+
return {"credentials": {"url": url, "server": server, "channel": channel}, "account": None, "targets": None}
|
|
248
|
+
|
|
249
|
+
def setup(self, body, current=None):
|
|
250
|
+
if current is not None and "connect" not in body:
|
|
251
|
+
return {}
|
|
252
|
+
found = connection(body, self)["credentials"]
|
|
253
|
+
return {"hint": "A Discord channel",
|
|
254
|
+
"config": {"server": found["server"], "channel": found["channel"]},
|
|
255
|
+
"vault": {"url": found["url"], "secret": None}, "last_test": None}
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def message(text):
|
|
259
|
+
"""Discord's body: the line as plain content, with every mention turned off, so text from a screen can't ping
|
|
260
|
+
@everyone."""
|
|
261
|
+
text = text if len(text) <= DISCORD_MAX else text[:DISCORD_MAX - 1] + "…"
|
|
262
|
+
return json.dumps({"content": text, "username": "Mobster", "allowed_mentions": {"parse": []}},
|
|
263
|
+
ensure_ascii=False).encode()
|
|
264
|
+
|
|
265
|
+
def _post(self, ctx, text):
|
|
266
|
+
url = ctx.saved["url"]
|
|
267
|
+
reply, failed = careful(lambda: ctx.call("POST", url, self.message(text),
|
|
268
|
+
{"Content-Type": "application/json",
|
|
269
|
+
"User-Agent": webhook.USER_AGENT}), "the channel")
|
|
270
|
+
if failed:
|
|
271
|
+
return failed
|
|
272
|
+
status = reply.status
|
|
273
|
+
if 200 <= status < 300:
|
|
274
|
+
return Outcome(True, False, status)
|
|
275
|
+
if status == 429:
|
|
276
|
+
return Outcome(False, True, status, "Discord asked Mobster to wait.", unsent=True)
|
|
277
|
+
if status in (401, 403, 404):
|
|
278
|
+
return Outcome(False, False, status, f"Discord answered {status}: the webhook was deleted. "
|
|
279
|
+
+ again("Discord"))
|
|
280
|
+
if status >= 500:
|
|
281
|
+
return Outcome(False, False, status, f"Discord answered {status}. Check the channel, then resend if the "
|
|
282
|
+
"message is missing.")
|
|
283
|
+
return Outcome(False, False, status, f"Discord answered {status}. " + again("Discord"))
|
|
284
|
+
|
|
285
|
+
def send(self, ctx, body):
|
|
286
|
+
return self._post(ctx, body.get("text") or "Mobster")
|
|
287
|
+
|
|
288
|
+
def test(self, ctx):
|
|
289
|
+
return self._post(ctx, "Mobster: this is a test. Results from your tasks will arrive here.")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def all_kinds():
|
|
293
|
+
from . import folder, mail, notion, sheets, slack
|
|
294
|
+
return {kind.id: kind for kind in (Webhook(), slack.Slack(), Discord(), sheets.Sheets(), notion.Notion(),
|
|
295
|
+
folder.Folder(), mail.Email())}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
_KINDS = None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get(kind_id):
|
|
302
|
+
global _KINDS
|
|
303
|
+
if _KINDS is None:
|
|
304
|
+
_KINDS = all_kinds()
|
|
305
|
+
found = _KINDS.get(kind_id or "webhook")
|
|
306
|
+
if found is None:
|
|
307
|
+
raise ValueError("A destination's kind is " + ", ".join(_KINDS))
|
|
308
|
+
return found
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""An email destination: each result is an email from the Mail app on this Mac to the one address saved with the
|
|
2
|
+
destination."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
from .kinds import Kind, title_of
|
|
9
|
+
from .webhook import Outcome
|
|
10
|
+
|
|
11
|
+
OSASCRIPT = "/usr/bin/osascript"
|
|
12
|
+
TIMEOUT = 60
|
|
13
|
+
LABEL = r"[A-Za-z0-9](?:[A-Za-z0-9\-]{0,62}[A-Za-z0-9])?"
|
|
14
|
+
ADDRESS = re.compile(r"[^@\s<>\"',;]{1,64}@" + LABEL + r"(?:\." + LABEL + ")+")
|
|
15
|
+
BODY_MAX = 50_000
|
|
16
|
+
|
|
17
|
+
# Reads {"to", "subject", "content"} from stdin and sends it from Mail's default account. Prints "sent", or
|
|
18
|
+
# "no_account" when Mail has none.
|
|
19
|
+
SCRIPT = """
|
|
20
|
+
ObjC.import('Foundation');
|
|
21
|
+
function run() {
|
|
22
|
+
const input = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
|
|
23
|
+
const message = JSON.parse($.NSString.alloc.initWithDataEncoding(input, $.NSUTF8StringEncoding).js);
|
|
24
|
+
const Mail = Application('Mail');
|
|
25
|
+
if (Mail.accounts.length === 0) return 'no_account';
|
|
26
|
+
const outgoing = Mail.OutgoingMessage({ subject: message.subject, content: message.content, visible: false });
|
|
27
|
+
Mail.outgoingMessages.push(outgoing);
|
|
28
|
+
outgoing.toRecipients.push(Mail.Recipient({ address: message.to }));
|
|
29
|
+
outgoing.send();
|
|
30
|
+
return 'sent';
|
|
31
|
+
}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run(argv, data):
|
|
36
|
+
"""One osascript (tests replace it): (returncode, stdout, stderr)."""
|
|
37
|
+
done = subprocess.run(argv, input=data, capture_output=True, timeout=TIMEOUT)
|
|
38
|
+
return done.returncode, done.stdout.decode(errors="replace"), done.stderr.decode(errors="replace")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def address(value):
|
|
42
|
+
text = value.strip() if isinstance(value, str) else ""
|
|
43
|
+
if len(text) > 254 or not ADDRESS.fullmatch(text):
|
|
44
|
+
raise ValueError("Type your email address, like you@example.com.")
|
|
45
|
+
return text
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def content(body):
|
|
49
|
+
"""The email's text: the answer, why it stopped when it did, the workflow's blanks and the data."""
|
|
50
|
+
parts = [body.get("answer") or ("Done." if not body.get("reason") else "The task couldn't finish.")]
|
|
51
|
+
if body.get("reason"):
|
|
52
|
+
parts.append(f"Why it stopped: {body['reason']}")
|
|
53
|
+
flow = body.get("workflow") or {}
|
|
54
|
+
if flow.get("blanks"):
|
|
55
|
+
parts.append("\n".join(f"{name}: {value}" for name, value in flow["blanks"].items()))
|
|
56
|
+
if body.get("data") is not None:
|
|
57
|
+
parts.append(json.dumps(body["data"], ensure_ascii=False, indent=2))
|
|
58
|
+
facts = [f"Status: {body.get('status')}"]
|
|
59
|
+
if body.get("finishedAt"):
|
|
60
|
+
facts.append(f"Finished: {body['finishedAt']}")
|
|
61
|
+
if body.get("runId"):
|
|
62
|
+
facts.append(f"Task: {body['runId']}")
|
|
63
|
+
parts.append("\n".join(facts))
|
|
64
|
+
parts.append("Sent by Mobster from this Mac. Change where results go in Settings › Connectors.")
|
|
65
|
+
text = "\n\n".join(parts)
|
|
66
|
+
return text if len(text) <= BODY_MAX else text[:BODY_MAX - 1] + "…"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Email(Kind):
|
|
70
|
+
id, label = "email", "Email"
|
|
71
|
+
example = "My email"
|
|
72
|
+
fields = ("to",)
|
|
73
|
+
replay_safe = False
|
|
74
|
+
|
|
75
|
+
def setup(self, body, current=None):
|
|
76
|
+
if current is not None and "to" not in body:
|
|
77
|
+
return {}
|
|
78
|
+
to = address(body.get("to"))
|
|
79
|
+
return {"hint": to, "vault": {"url": None, "secret": None, "to": to}, "last_test": None}
|
|
80
|
+
|
|
81
|
+
def _send(self, to, subject, text):
|
|
82
|
+
# The address, subject and body go to osascript on stdin as JSON, never in argv.
|
|
83
|
+
data = json.dumps({"to": to, "subject": subject, "content": text}, ensure_ascii=False).encode("utf-8")
|
|
84
|
+
try:
|
|
85
|
+
code, out, err = run([OSASCRIPT, "-l", "JavaScript", "-e", SCRIPT], data)
|
|
86
|
+
except subprocess.TimeoutExpired:
|
|
87
|
+
return Outcome(False, False, None, "Mail didn't answer within a minute. Check Mail's Outbox, then resend "
|
|
88
|
+
"if it isn't there.")
|
|
89
|
+
except OSError:
|
|
90
|
+
return Outcome(False, False, None, "Mobster couldn't start osascript to reach Mail.")
|
|
91
|
+
said = out.strip()
|
|
92
|
+
if code == 0 and said == "sent":
|
|
93
|
+
return Outcome(True, False, None)
|
|
94
|
+
if code == 0 and said == "no_account":
|
|
95
|
+
return Outcome(False, False, None, "Mail has no account to send from. Add one in Mail, then resend.",
|
|
96
|
+
unsent=True)
|
|
97
|
+
if "-1743" in err or "Not authorized" in err or "not allowed" in err.lower():
|
|
98
|
+
return Outcome(False, False, None, "macOS didn't let Mobster use Mail. Allow it in System Settings › "
|
|
99
|
+
"Privacy & Security › Automation › Mobster, then resend.", unsent=True)
|
|
100
|
+
if "-600" in err or "-609" in err or "isn't running" in err:
|
|
101
|
+
return Outcome(False, False, None, "Mail couldn't be opened. Open Mail once, then resend.", unsent=True)
|
|
102
|
+
return Outcome(False, False, None, "Mail didn't send it. Check Mail's Outbox, then resend if it isn't there.")
|
|
103
|
+
|
|
104
|
+
def send(self, ctx, body):
|
|
105
|
+
return self._send(ctx.saved["to"], "Mobster: " + title_of(body, 120), content(body))
|
|
106
|
+
|
|
107
|
+
def test(self, ctx):
|
|
108
|
+
return self._send(ctx.saved["to"], "Mobster: a test",
|
|
109
|
+
"This is a test from Mobster. When a task that sends here ends, its result arrives like "
|
|
110
|
+
"this.\n\nSent by Mobster from this Mac. Change where results go in Settings › Connectors.")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""One HTTP request for the destinations that talk to a web service (webhook, Discord, Sheets, Notion), with the
|
|
2
|
+
standard library only, as the frozen app ships it."""
|
|
3
|
+
|
|
4
|
+
import http.client
|
|
5
|
+
import json
|
|
6
|
+
import socket
|
|
7
|
+
import ssl
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
TIMEOUT = 10.0
|
|
11
|
+
READ_LIMIT = 65_536
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Reply:
|
|
15
|
+
def __init__(self, status, location=None, body=b"", retry_after=None):
|
|
16
|
+
self.status, self.location, self.body, self.retry_after = status, location, body, retry_after
|
|
17
|
+
|
|
18
|
+
def json_object(self):
|
|
19
|
+
"""The body as a JSON object, or None when it isn't one."""
|
|
20
|
+
try:
|
|
21
|
+
value = json.loads(self.body.decode("utf-8"))
|
|
22
|
+
except (ValueError, UnicodeDecodeError):
|
|
23
|
+
return None
|
|
24
|
+
return value if isinstance(value, dict) else None
|
|
25
|
+
|
|
26
|
+
def __repr__(self):
|
|
27
|
+
return f"Reply({self.status}, location={self.location!r}, {len(self.body)} bytes)"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NetError(Exception):
|
|
31
|
+
"""No answer. ``unsent``: the request never left this Mac. ``message`` names the host, never the address."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message, unsent, retry=True):
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
self.message, self.unsent, self.retry = message, unsent, retry
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def host_of(url):
|
|
39
|
+
return (urlsplit(url).hostname or "").lower()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def connection(url, timeout):
|
|
43
|
+
parts = urlsplit(url)
|
|
44
|
+
if parts.scheme.lower() == "https":
|
|
45
|
+
return http.client.HTTPSConnection(parts.hostname, parts.port or 443, timeout=timeout,
|
|
46
|
+
context=ssl.create_default_context())
|
|
47
|
+
return http.client.HTTPConnection(parts.hostname, parts.port or 80, timeout=timeout)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def call(method, url, data=None, headers=None, timeout=TIMEOUT, connect=connection, read_limit=READ_LIMIT):
|
|
51
|
+
"""``method`` ``url`` once: a Reply, or NetError. Redirects are answers, never followed. NetError says whether the
|
|
52
|
+
request surely never left this Mac (``unsent``) or may have arrived."""
|
|
53
|
+
host = host_of(url)
|
|
54
|
+
parts = urlsplit(url)
|
|
55
|
+
path = (parts.path or "/") + (f"?{parts.query}" if parts.query else "")
|
|
56
|
+
try:
|
|
57
|
+
link = connect(url, timeout)
|
|
58
|
+
except Exception: # noqa: BLE001
|
|
59
|
+
raise NetError("Mobster couldn't open that address.", unsent=True, retry=False) from None
|
|
60
|
+
try:
|
|
61
|
+
# Connecting first, on its own, tells "never sent" from "sent, then no answer".
|
|
62
|
+
try:
|
|
63
|
+
link.connect()
|
|
64
|
+
except ssl.SSLCertVerificationError:
|
|
65
|
+
raise NetError(f"{host}'s certificate couldn't be verified.", unsent=True, retry=False) from None
|
|
66
|
+
except (socket.timeout, TimeoutError):
|
|
67
|
+
raise NetError(f"{host} didn't answer within {int(timeout)} seconds.", unsent=True) from None
|
|
68
|
+
except (OSError, http.client.HTTPException):
|
|
69
|
+
raise NetError(f"{host} couldn't be reached.", unsent=True) from None
|
|
70
|
+
try:
|
|
71
|
+
link.request(method, path, body=data, headers=headers or {})
|
|
72
|
+
response = link.getresponse()
|
|
73
|
+
body = response.read(read_limit)
|
|
74
|
+
status = int(response.status)
|
|
75
|
+
location = response.getheader("Location")
|
|
76
|
+
retry_after = response.getheader("Retry-After")
|
|
77
|
+
except (socket.timeout, TimeoutError):
|
|
78
|
+
raise NetError(f"{host} didn't answer within {int(timeout)} seconds.", unsent=False) from None
|
|
79
|
+
except (OSError, http.client.HTTPException):
|
|
80
|
+
raise NetError(f"{host} couldn't be reached.", unsent=False) from None
|
|
81
|
+
finally:
|
|
82
|
+
try:
|
|
83
|
+
link.close()
|
|
84
|
+
except Exception: # noqa: BLE001
|
|
85
|
+
pass
|
|
86
|
+
return Reply(status, location, body, retry_after)
|