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,475 @@
|
|
|
1
|
+
"""The queue at work: due posts run on their profiles' phones, one phone per worker, phones in parallel."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .. import phone_ready
|
|
10
|
+
from ..api_errors import APIError
|
|
11
|
+
from . import client, prompt
|
|
12
|
+
from .media_check import POST_ACTS
|
|
13
|
+
from .store import (
|
|
14
|
+
ADD_PHOTOS,
|
|
15
|
+
LATE_LIMIT_MS,
|
|
16
|
+
PACED_REFRESH_MS,
|
|
17
|
+
STAGGER_S,
|
|
18
|
+
QueueError,
|
|
19
|
+
armed_for,
|
|
20
|
+
caption_text,
|
|
21
|
+
fingerprint,
|
|
22
|
+
now_ms,
|
|
23
|
+
store_for,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger("mobster.queue")
|
|
27
|
+
|
|
28
|
+
TICK_S = 3.0
|
|
29
|
+
WATCH_SETTLE_S = 10.0
|
|
30
|
+
RUN_POLL_S = 1.0
|
|
31
|
+
LINKS = {
|
|
32
|
+
"com.burbn.instagram": re.compile(
|
|
33
|
+
r"^https://(?:www\.)?instagram\.com/(?:[\w.]+/)?(?:p|reel|reels|tv|stories/[\w.]+)/"
|
|
34
|
+
r"[\w-]+/?(?:\?\S*)?$"),
|
|
35
|
+
"com.zhiliaoapp.musically": re.compile(r"^https://(?:www\.|vm\.|vt\.)?tiktok\.com/\S+$"),
|
|
36
|
+
}
|
|
37
|
+
ANY_LINK = re.compile(r"^https://\S{8,300}$")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def preapproval(item, profile):
|
|
41
|
+
"""The run's one batch-approved Post (Run.preapproved): this profile, this app, a post, once."""
|
|
42
|
+
return {"kind": "queue_post", "via": "content_queue", "acting_as": f"@{profile['handle']} "
|
|
43
|
+
f"on {profile['app']['name']}",
|
|
44
|
+
"handle": profile["handle"], "app": profile["app"]["name"], "item": item["id"],
|
|
45
|
+
"source": {"queue": True, "item": item["id"]},
|
|
46
|
+
# With the queue checks on, the Post card is answered only after the Mac saw the post's media in the
|
|
47
|
+
# composer (media_check.py); otherwise it waits for the person.
|
|
48
|
+
"preview": phone_ready.enabled()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def preapproval_matches(spec, request):
|
|
52
|
+
"""Whether ``request`` is the one Post ``spec`` covers (with the queue checks on, its media seen on the Mac);
|
|
53
|
+
anything else asks as usual (fail closed)."""
|
|
54
|
+
if not isinstance(spec, dict) or spec.get("kind") != "queue_post" or spec.get("used"):
|
|
55
|
+
return False
|
|
56
|
+
if not isinstance(request, dict) or (request.get("kind") or "action") != "commit":
|
|
57
|
+
return False
|
|
58
|
+
if request.get("act") not in POST_ACTS or request.get("operation") not in ("TAP", "SUBMIT", "TYPE_SUBMIT"):
|
|
59
|
+
return False
|
|
60
|
+
if spec.get("preview") and request.get("media_checked") is not True:
|
|
61
|
+
return False
|
|
62
|
+
acting = " ".join(str(request.get("acting_as") or "").split()).casefold()
|
|
63
|
+
if not acting or acting != " ".join(str(spec.get("acting_as") or "").split()).casefold():
|
|
64
|
+
return False
|
|
65
|
+
return " ".join(str(request.get("app_name") or "").split()).casefold() == str(spec.get("app") or "").casefold()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def outcome(run):
|
|
69
|
+
"""(state, note) for a finished queue run, from its approval events and its result."""
|
|
70
|
+
asked, decided = {}, {}
|
|
71
|
+
for event in list(getattr(run, "events", ()) or ()):
|
|
72
|
+
if event.get("event") == "approval_requested":
|
|
73
|
+
asked[event.get("approval_id")] = event
|
|
74
|
+
elif event.get("event") == "approval_resolved":
|
|
75
|
+
decided[event.get("approval_id")] = event.get("decision")
|
|
76
|
+
posts = [key for key, event in asked.items() if (event.get("kind") or "action") == "commit"
|
|
77
|
+
and event.get("act") in POST_ACTS]
|
|
78
|
+
approved = [key for key in posts if decided.get(key) == "approved"]
|
|
79
|
+
summary = getattr(run, "summary", None) or {}
|
|
80
|
+
data = summary.get("data") if isinstance(summary.get("data"), dict) else {}
|
|
81
|
+
note = (data.get("note") or summary.get("reason") or summary.get("answer") or "")[:300] or None
|
|
82
|
+
if approved:
|
|
83
|
+
if summary.get("status") == "completed" and data.get("posted") is True and data.get("challenge") is not True:
|
|
84
|
+
return "posted", None
|
|
85
|
+
return "unknown", ("The Post was approved, and the task couldn't confirm it went out. Look at the profile, "
|
|
86
|
+
"then mark it.")
|
|
87
|
+
if posts:
|
|
88
|
+
return "declined", "The Post wasn't approved, so nothing went out."
|
|
89
|
+
if data.get("challenge") is True:
|
|
90
|
+
return "failed", "The app showed a check (a captcha or a verification), so Mobster stopped and paused this " \
|
|
91
|
+
"profile. Nothing went out."
|
|
92
|
+
return "failed", note or "The task stopped before it reached Post. Nothing went out."
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class QueueRunner:
|
|
96
|
+
"""One per runtime: the scheduler thread and the per-phone workers."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, runtime, *, transfer_run=None, link_reader=None, sleep=time.sleep, clock=time.time):
|
|
99
|
+
from ..phone_io.transfer import transfers_for
|
|
100
|
+
self.runtime = runtime
|
|
101
|
+
self.store = store_for(runtime)
|
|
102
|
+
self.transfers = transfers_for(runtime)
|
|
103
|
+
self._transfer_run = transfer_run
|
|
104
|
+
self._link_reader = link_reader or read_link
|
|
105
|
+
self.sleep, self.clock = sleep, clock
|
|
106
|
+
self._stop = threading.Event()
|
|
107
|
+
self._workers = {}
|
|
108
|
+
self._lock = threading.Lock()
|
|
109
|
+
self._thread = None
|
|
110
|
+
self.store.recover()
|
|
111
|
+
|
|
112
|
+
def start(self):
|
|
113
|
+
"""The scheduler thread, only while some queue is started: none at all for a person who never uses the queue."""
|
|
114
|
+
with self._lock:
|
|
115
|
+
if self._thread is not None and self._thread.is_alive() or self._stop.is_set():
|
|
116
|
+
return
|
|
117
|
+
if not any(queue.get("armed") for queue in self.store.all()):
|
|
118
|
+
return
|
|
119
|
+
self._thread = threading.Thread(target=self._loop, name="mobster-queue", daemon=True)
|
|
120
|
+
self._thread.start()
|
|
121
|
+
|
|
122
|
+
def close(self):
|
|
123
|
+
self._stop.set()
|
|
124
|
+
|
|
125
|
+
def _loop(self):
|
|
126
|
+
while not self._stop.wait(TICK_S):
|
|
127
|
+
try:
|
|
128
|
+
self.tick()
|
|
129
|
+
except Exception: # noqa: BLE001 - one bad tick never stops the queue
|
|
130
|
+
log.exception("queue tick failed")
|
|
131
|
+
if not any(queue.get("armed") for queue in self.store.all()) and not any(
|
|
132
|
+
worker.is_alive() for worker in self._workers.values()):
|
|
133
|
+
with self._lock:
|
|
134
|
+
self._thread = None
|
|
135
|
+
return # every queue stopped: the thread ends, and the next Start begins it again
|
|
136
|
+
|
|
137
|
+
def tick(self):
|
|
138
|
+
"""Start a worker for each phone with a due item and no worker. Returns the phones started."""
|
|
139
|
+
from ..accounts.store import store_for as profiles_for
|
|
140
|
+
profiles = {p["id"]: p for p in profiles_for(self.runtime).all()}
|
|
141
|
+
due = {}
|
|
142
|
+
stagger = {}
|
|
143
|
+
stamp = now_ms()
|
|
144
|
+
pacing = self._pacing()
|
|
145
|
+
for queue in self.store.all():
|
|
146
|
+
if not queue.get("armed"):
|
|
147
|
+
continue
|
|
148
|
+
if queue.get("watch"):
|
|
149
|
+
self.take_new_files(queue, profiles)
|
|
150
|
+
queue = self.store.get(queue["id"])
|
|
151
|
+
for item in queue["items"]:
|
|
152
|
+
if item["state"] != "ready" or not armed_for(queue, item):
|
|
153
|
+
continue # a draft waits for its photos; a profile whose posts weren't started keeps its own
|
|
154
|
+
if not item.get("media"):
|
|
155
|
+
self.store.set_state(queue["id"], item["id"], "draft", note=ADD_PHOTOS)
|
|
156
|
+
continue
|
|
157
|
+
profile = profiles.get(item.get("profile"))
|
|
158
|
+
if not self._due(queue["id"], item, profile, pacing, stamp):
|
|
159
|
+
continue
|
|
160
|
+
phone = profile.get("device") or "default"
|
|
161
|
+
due.setdefault(phone, []).append((queue["id"], item["id"]))
|
|
162
|
+
stagger.setdefault(phone, queue.get("staggerSeconds", STAGGER_S))
|
|
163
|
+
started = []
|
|
164
|
+
with self._lock:
|
|
165
|
+
for phone, items in due.items():
|
|
166
|
+
worker = self._workers.get(phone)
|
|
167
|
+
if worker is not None and worker.is_alive():
|
|
168
|
+
continue
|
|
169
|
+
# Phones starting in the same tick start apart (the queue's stagger), never at the same second.
|
|
170
|
+
delay = len(started) * float(stagger.get(phone) or 0)
|
|
171
|
+
worker = threading.Thread(target=self._work, args=(items, profiles, delay),
|
|
172
|
+
name=f"mobster-queue-{phone}", daemon=True)
|
|
173
|
+
self._workers[phone] = worker
|
|
174
|
+
worker.start()
|
|
175
|
+
started.append(phone)
|
|
176
|
+
return started
|
|
177
|
+
|
|
178
|
+
def _due(self, queue_id, item, profile, pacing, stamp):
|
|
179
|
+
"""Check timing and holds both when selecting work and when its worker reaches dispatch."""
|
|
180
|
+
when = item.get("scheduledAt")
|
|
181
|
+
if when is not None and when > stamp:
|
|
182
|
+
return False
|
|
183
|
+
note, hold = self._paced(pacing, profile, item) if pacing is not None and profile else (None, None)
|
|
184
|
+
# Late because Mobster held it for its profile's pace or posting window (the person's own settings) is
|
|
185
|
+
# no miss while its day lasts. Late while Mobster wasn't running, while its profile was paused or
|
|
186
|
+
# while it waited for the client is, and its note says which.
|
|
187
|
+
held = item.get("pacedAt") is not None and stamp - item["pacedAt"] <= LATE_LIMIT_MS
|
|
188
|
+
if when is not None and stamp - when > LATE_LIMIT_MS and not held:
|
|
189
|
+
if client.holds(item):
|
|
190
|
+
missed = "Its time passed while it waited for your client. Set a new time."
|
|
191
|
+
elif hold == "paused":
|
|
192
|
+
missed = (f"Your post misses its time because of a pause on @{profile['handle']}. Set a new "
|
|
193
|
+
"time or run it.")
|
|
194
|
+
else:
|
|
195
|
+
missed = "Its time passed while Mobster wasn't running. Set a new time or run it."
|
|
196
|
+
self.store.set_state(queue_id, item["id"], "missed", note=missed)
|
|
197
|
+
return False
|
|
198
|
+
if client.holds(item):
|
|
199
|
+
return False # the row shows what the client said (client.public)
|
|
200
|
+
if profile is None:
|
|
201
|
+
self.store.set_state(queue_id, item["id"], "failed",
|
|
202
|
+
note="Choose a profile for this post (Settings › Profiles).")
|
|
203
|
+
return False
|
|
204
|
+
if hold == "day":
|
|
205
|
+
self.store.set_state(queue_id, item["id"], "missed", note=note)
|
|
206
|
+
return False
|
|
207
|
+
if note is not None:
|
|
208
|
+
paused = hold == "paused"
|
|
209
|
+
# Each pace hold says so again now and then: the proof Mobster held it, so it isn't a miss.
|
|
210
|
+
if note != item.get("note"):
|
|
211
|
+
self.store.set_state(queue_id, item["id"], "ready", note=note,
|
|
212
|
+
**({} if paused else {"pacedAt": stamp}))
|
|
213
|
+
elif not paused and stamp - (item.get("pacedAt") or 0) > PACED_REFRESH_MS:
|
|
214
|
+
# Only the stamp is due: no journal line, and never on a post that has moved on since the read.
|
|
215
|
+
self.store.change_item(queue_id, item["id"], lambda held_item, at=stamp: held_item.update(
|
|
216
|
+
pacedAt=at) if held_item["state"] == "ready" else None)
|
|
217
|
+
return False
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
def wait(self, timeout=10):
|
|
221
|
+
"""Tests: until every worker ended."""
|
|
222
|
+
end = time.monotonic() + timeout
|
|
223
|
+
for worker in list(self._workers.values()):
|
|
224
|
+
worker.join(max(0, end - time.monotonic()))
|
|
225
|
+
|
|
226
|
+
def _pacing(self):
|
|
227
|
+
try:
|
|
228
|
+
from ..accounts.pacing import pacing_for
|
|
229
|
+
return pacing_for(self.runtime)
|
|
230
|
+
except Exception: # noqa: BLE001 - no profiles folder: nothing to pace by
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _paced(pacing, profile, item):
|
|
235
|
+
"""What the profile's pacing says of a due post: (None, None) when it may go now; else its note and why:
|
|
236
|
+
"paused", "pace" (it waits for its pace or posting window) or "day" (that wait runs past its day: a miss)."""
|
|
237
|
+
settings = pacing.settings(profile["id"])
|
|
238
|
+
if settings.get("paused"):
|
|
239
|
+
return (f"@{profile['handle']} is paused: {settings['paused']['reason']} Resume it "
|
|
240
|
+
"here once it's dealt with."), "paused"
|
|
241
|
+
when = item.get("scheduledAt")
|
|
242
|
+
at = pacing.next_post_time(profile["id"], (when or 0) / 1000 or None)
|
|
243
|
+
if at is None:
|
|
244
|
+
return None, None
|
|
245
|
+
waits = at > pacing.clock() + 1
|
|
246
|
+
# A timed post waits for its pace or window only within its own day, in the profile's time zone: Monday
|
|
247
|
+
# evening's post never goes out on Tuesday unasked. That counts once the pace held it, too, if its day ended
|
|
248
|
+
# before it could go. Run again drops the time (store.mark): the person's OK to post it late.
|
|
249
|
+
if (when is not None and (waits or (item.get("pacedAt") or 0) >= when)
|
|
250
|
+
and not pacing.same_day(profile["id"], when / 1000, at)):
|
|
251
|
+
return (f"@{profile['handle']}'s pace or posting window would post it after its day. Set a new time or "
|
|
252
|
+
"run it."), "day"
|
|
253
|
+
if not waits:
|
|
254
|
+
return None, None
|
|
255
|
+
import datetime
|
|
256
|
+
local = datetime.datetime.fromtimestamp(at).astimezone()
|
|
257
|
+
return f"Waits for @{profile['handle']}'s pace: next post at {local.strftime('%a %H:%M')}.", "pace"
|
|
258
|
+
|
|
259
|
+
def take_new_files(self, queue, profiles):
|
|
260
|
+
"""New photos and videos in the queue's watched folder join it, one post each, for the folder's profile."""
|
|
261
|
+
from .importing import MEDIA_SUFFIXES, body_for
|
|
262
|
+
watch = queue["watch"]
|
|
263
|
+
folder = Path(watch["folder"])
|
|
264
|
+
seen = set(watch.get("seen") or ())
|
|
265
|
+
try:
|
|
266
|
+
files = sorted(p for p in folder.iterdir() if p.is_file() and p.suffix.lower() in MEDIA_SUFFIXES
|
|
267
|
+
and not p.name.startswith(".") and str(p) not in seen)
|
|
268
|
+
except OSError:
|
|
269
|
+
return []
|
|
270
|
+
settled = [p for p in files if time.time() - p.stat().st_mtime >= WATCH_SETTLE_S]
|
|
271
|
+
if not settled:
|
|
272
|
+
return []
|
|
273
|
+
bodies = [body_for(file, watch.get("profile")) for file in settled]
|
|
274
|
+
try:
|
|
275
|
+
added = self.store.add_items(queue["id"], bodies, list(profiles.values()))
|
|
276
|
+
except QueueError as error:
|
|
277
|
+
log.warning("watched folder: %s", error)
|
|
278
|
+
added = []
|
|
279
|
+
self.store.note_seen(queue["id"], [str(p) for p in settled])
|
|
280
|
+
if added:
|
|
281
|
+
self.store.journal(queue["id"], "folder_added", items=[i["id"] for i in added])
|
|
282
|
+
return added
|
|
283
|
+
|
|
284
|
+
def _work(self, items, profiles, delay=0.0):
|
|
285
|
+
if delay and self._stop.wait(delay):
|
|
286
|
+
return
|
|
287
|
+
for queue_id, item_id in items:
|
|
288
|
+
if self._stop.is_set():
|
|
289
|
+
return
|
|
290
|
+
try:
|
|
291
|
+
queue = self.store.get(queue_id)
|
|
292
|
+
except QueueError:
|
|
293
|
+
continue
|
|
294
|
+
item = next((i for i in queue["items"] if i["id"] == item_id), None)
|
|
295
|
+
if (
|
|
296
|
+
item is None
|
|
297
|
+
or not armed_for(queue, item)
|
|
298
|
+
or item["state"] != "ready"
|
|
299
|
+
or not item.get("media")
|
|
300
|
+
or client.holds(item)
|
|
301
|
+
):
|
|
302
|
+
continue # read again now: a revoke or an edit since the tick holds the post back
|
|
303
|
+
if self.held(queue_id, item, profiles[item["profile"]].get("device")):
|
|
304
|
+
break # the phone is locked or out of reach: its other posts wait too, and the next tick looks again
|
|
305
|
+
try:
|
|
306
|
+
# A stagger or an earlier post can carry this item past its day or into a new pace hold.
|
|
307
|
+
if not self._due(queue_id, item, profiles[item["profile"]], self._pacing(), now_ms()):
|
|
308
|
+
continue
|
|
309
|
+
self.post(queue, item, profiles[item["profile"]])
|
|
310
|
+
except Exception as error: # noqa: BLE001 - one item never stops the phone's others
|
|
311
|
+
log.exception("queue item failed")
|
|
312
|
+
current = self._state(queue_id, item_id)
|
|
313
|
+
if current in ("sending",):
|
|
314
|
+
self.store.set_state(queue_id, item_id, "failed",
|
|
315
|
+
note=f"Mobster hit an error ({type(error).__name__}).")
|
|
316
|
+
elif current == "posting":
|
|
317
|
+
self.store.set_state(queue_id, item_id, "unknown", note=f"Mobster hit an error "
|
|
318
|
+
f"({type(error).__name__}) while it posted. Look at "
|
|
319
|
+
"the profile, then mark it.")
|
|
320
|
+
|
|
321
|
+
def _phone(self, device):
|
|
322
|
+
"""(the helper's address, the phone's name) for the profile's ``device``; APIError for no phone here."""
|
|
323
|
+
phone = self.runtime.resolve_device(device)
|
|
324
|
+
return phone.wda_url, phone.name or phone.id
|
|
325
|
+
|
|
326
|
+
def held(self, queue_id, item, device):
|
|
327
|
+
"""True while the phone is locked or its lock can't be read (the check is off: never). The item stays ``ready``
|
|
328
|
+
with the reason as its note, written once, and nothing is sent. Locked and unknown both hold: a phone Mobster
|
|
329
|
+
can't read is never treated as unlocked."""
|
|
330
|
+
if not phone_ready.enabled():
|
|
331
|
+
return False
|
|
332
|
+
try:
|
|
333
|
+
why = phone_ready.hold(self._phone(device)[0])
|
|
334
|
+
except APIError:
|
|
335
|
+
why = phone_ready.HOLD_UNREADABLE
|
|
336
|
+
if why is not None and item.get("note") != why:
|
|
337
|
+
self.store.set_state(queue_id, item["id"], "ready", note=why)
|
|
338
|
+
return why is not None
|
|
339
|
+
|
|
340
|
+
def preflight(self, queue):
|
|
341
|
+
"""Before a queue starts, for each phone its waiting posts use: QueueError when Photos access is off or
|
|
342
|
+
unanswered there (no file would ever arrive), else the Low Power Mode warnings, [{code, device, message}]."""
|
|
343
|
+
from ..accounts.store import store_for as profiles_for
|
|
344
|
+
profiles = {p["id"]: p for p in profiles_for(self.runtime).all()}
|
|
345
|
+
wanted = [item.get("profile") for item in queue["items"] if item["state"] == "ready"]
|
|
346
|
+
wanted.append((queue.get("watch") or {}).get("profile"))
|
|
347
|
+
warnings = []
|
|
348
|
+
for device in dict.fromkeys(profiles[w].get("device") for w in wanted if w in profiles):
|
|
349
|
+
try:
|
|
350
|
+
url, name = self._phone(device)
|
|
351
|
+
except APIError as error:
|
|
352
|
+
raise QueueError(str(error), "phone_unreachable", 409) from None
|
|
353
|
+
refused = phone_ready.photos_refusal(url, name)
|
|
354
|
+
if refused is not None:
|
|
355
|
+
raise QueueError(str(refused), refused.code, 409)
|
|
356
|
+
warning = phone_ready.low_power_warning(url, name, "the first post")
|
|
357
|
+
if warning:
|
|
358
|
+
warnings.append({"code": "low_power_mode", "device": name, "message": warning})
|
|
359
|
+
return warnings
|
|
360
|
+
|
|
361
|
+
def _state(self, queue_id, item_id):
|
|
362
|
+
try:
|
|
363
|
+
return next((i["state"] for i in self.store.get(queue_id)["items"] if i["id"] == item_id), None)
|
|
364
|
+
except QueueError:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def post(self, queue, item, profile):
|
|
368
|
+
queue_id, item_id = queue["id"], item["id"]
|
|
369
|
+
device = profile.get("device")
|
|
370
|
+
sent = self.send_media(queue_id, item, device)
|
|
371
|
+
if sent is None:
|
|
372
|
+
return
|
|
373
|
+
from .. import permissions
|
|
374
|
+
from ..api_errors import APIError
|
|
375
|
+
saved = permissions.from_env().mode
|
|
376
|
+
mode = saved if saved in (permissions.ASK_ALL, permissions.ASK_IMPORTANT) else permissions.ASK_IMPORTANT
|
|
377
|
+
self.store.set_state(queue_id, item_id, "posting", transfer=sent, note=None)
|
|
378
|
+
# queueItem: the run's permission gate (runtime_limits.py) skips every "Don't ask again" rule for it.
|
|
379
|
+
try:
|
|
380
|
+
run = self.runtime.create(None, prompt.goal(item, profile), "live", engine="smart",
|
|
381
|
+
extras={"account": profile["id"], "permissionMode": mode,
|
|
382
|
+
"queueItem": {"queue": queue_id, "item": item_id}},
|
|
383
|
+
origin="app", output_schema=prompt.ANSWER, output_format="json")
|
|
384
|
+
except APIError as error:
|
|
385
|
+
if error.code == "run_active":
|
|
386
|
+
# The phone is busy with another task: nothing started. The next tick tries again.
|
|
387
|
+
self.store.set_state(queue_id, item_id, "ready", transfer=sent,
|
|
388
|
+
note="Waiting for the phone: a task is running on it.")
|
|
389
|
+
return
|
|
390
|
+
self.store.set_state(queue_id, item_id, "failed", transfer=sent, note=str(error))
|
|
391
|
+
return
|
|
392
|
+
except ValueError as error:
|
|
393
|
+
self.store.set_state(queue_id, item_id, "failed", transfer=sent, note=str(error))
|
|
394
|
+
return
|
|
395
|
+
batch = queue.get("batch") or {}
|
|
396
|
+
if queue.get("approval") == "batch" and (batch.get("items") or {}).get(item_id) == fingerprint(item):
|
|
397
|
+
with run.condition:
|
|
398
|
+
run.preapproved.append(preapproval(item, profile))
|
|
399
|
+
self.store.set_state(queue_id, item_id, "posting", runId=run.id, transfer=sent)
|
|
400
|
+
while run.finished_at is None and not self._stop.is_set():
|
|
401
|
+
with run.condition:
|
|
402
|
+
run.condition.wait(RUN_POLL_S)
|
|
403
|
+
if run.finished_at is None:
|
|
404
|
+
return # Mobster is closing: the run ends as interrupted; recover() makes the item unknown next start
|
|
405
|
+
state, note = outcome(run)
|
|
406
|
+
result = {"runId": run.id, "finishedAt": now_ms(), "caption": caption_text(item)[:200]}
|
|
407
|
+
if state == "posted":
|
|
408
|
+
link = None
|
|
409
|
+
data = (run.summary or {}).get("data") if isinstance((run.summary or {}).get("data"), dict) else {}
|
|
410
|
+
if data.get("link_copied") is not False:
|
|
411
|
+
link = self._link_reader(self.runtime, device, profile["app"]["bundleId"])
|
|
412
|
+
result.update(link=link, postedAt=now_ms())
|
|
413
|
+
if not link:
|
|
414
|
+
note = "Posted. Mobster couldn't read the post's link."
|
|
415
|
+
self.store.set_state(queue_id, item_id, state, runId=run.id, result=result, note=note)
|
|
416
|
+
|
|
417
|
+
def send_media(self, queue_id, item, device):
|
|
418
|
+
"""The transfer's public record when every file arrived; None (and the item failed) otherwise."""
|
|
419
|
+
from ..phone_io import PhoneIOError
|
|
420
|
+
from ..phone_io.transfer import Transfer, phone_from_runtime
|
|
421
|
+
done = item.get("transfer") or {}
|
|
422
|
+
if (done.get("state") == "done" and done.get("device") == device
|
|
423
|
+
and [i.get("sha256") for i in done.get("items") or ()] == [m["sha256"] for m in item["media"]]):
|
|
424
|
+
return done # sent before (a declined or failed post run again): the same files are on the phone
|
|
425
|
+
if phone_ready.enabled():
|
|
426
|
+
try:
|
|
427
|
+
url, name = self._phone(device)
|
|
428
|
+
refused = phone_ready.photos_refusal(url, name)
|
|
429
|
+
except APIError as error:
|
|
430
|
+
refused = PhoneIOError(str(error), "no_device")
|
|
431
|
+
if refused is not None:
|
|
432
|
+
self.store.set_state(queue_id, item["id"], "failed", note=f"{refused} Nothing was posted.")
|
|
433
|
+
return None
|
|
434
|
+
self.store.set_state(queue_id, item["id"], "sending", note=None)
|
|
435
|
+
try:
|
|
436
|
+
phone = phone_from_runtime(self.runtime, device)
|
|
437
|
+
transfer = Transfer("to_phone", "photos", phone, paths=[m["path"] for m in item["media"]],
|
|
438
|
+
album="Mobster", date_now=True, queue_item=item["id"])
|
|
439
|
+
if self._transfer_run is None:
|
|
440
|
+
self.transfers.start(transfer, wait=True)
|
|
441
|
+
else:
|
|
442
|
+
self._transfer_run(transfer)
|
|
443
|
+
except PhoneIOError as error:
|
|
444
|
+
self.store.set_state(queue_id, item["id"], "failed", note=str(error))
|
|
445
|
+
return None
|
|
446
|
+
except Exception as error: # noqa: BLE001 - a phone that can't be named, a transfer that broke
|
|
447
|
+
self.store.set_state(queue_id, item["id"], "failed", note=str(error)[:300] or type(error).__name__)
|
|
448
|
+
return None
|
|
449
|
+
public = transfer.public()
|
|
450
|
+
public = {key: public[key] for key in ("id", "state", "route", "device", "before", "after", "verified")} | {
|
|
451
|
+
"items": [{"name": i["name"], "state": i["state"], "sha256": i["sha256"]} for i in public["items"]]}
|
|
452
|
+
if transfer.state != "done":
|
|
453
|
+
reason = (transfer.error or {}).get("message") or next(
|
|
454
|
+
(i.error for i in transfer.items if i.error), "The media didn't all arrive.")
|
|
455
|
+
self.store.set_state(queue_id, item["id"], "failed", transfer=public,
|
|
456
|
+
note=f"{reason} Nothing was posted.")
|
|
457
|
+
return None
|
|
458
|
+
return public
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def read_link(runtime, device, bundle):
|
|
462
|
+
"""The post's link from the phone's clipboard, when it is one for this app's posts; else None."""
|
|
463
|
+
from ..phone_io import PhoneIOError
|
|
464
|
+
from ..phone_io.clipboard import get_text
|
|
465
|
+
from ..phone_io.transfer import phone_from_runtime
|
|
466
|
+
try:
|
|
467
|
+
phone = phone_from_runtime(runtime, device)
|
|
468
|
+
if phone.runner is None:
|
|
469
|
+
return None
|
|
470
|
+
with phone.runner() as request:
|
|
471
|
+
text = (get_text(request) or "").strip()
|
|
472
|
+
except (PhoneIOError, OSError, ValueError):
|
|
473
|
+
return None
|
|
474
|
+
pattern = LINKS.get(bundle, ANY_LINK)
|
|
475
|
+
return text if pattern.match(text) else None
|