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,212 @@
|
|
|
1
|
+
"""The profiles API (Settings › Profiles), registered under /api/accounts (api_routes, seam S3)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from .. import harness_api
|
|
6
|
+
from ..api_errors import APIError
|
|
7
|
+
from ..api_routes import Request, Response, Route, register
|
|
8
|
+
from .store import AccountNotFound, AmbiguousAccount, display, resolve, store_for
|
|
9
|
+
|
|
10
|
+
KEY = re.compile(r"[A-Za-z0-9_.:-]{1,100}")
|
|
11
|
+
MAX_RUN_ACCOUNTS = 20
|
|
12
|
+
RUN_FIELDS = frozenset({"accounts", "goal", "appId", "engine", "outputFormat"})
|
|
13
|
+
NOT_HERE = "Change your profiles in Mobster on your Mac, in Settings › Profiles."
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _writable(request):
|
|
17
|
+
if request.origin in ("remote", "mcp"):
|
|
18
|
+
raise APIError(NOT_HERE, 403, "not_here")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _device(runtime, value):
|
|
22
|
+
"""(id, name) of the phone ``value`` names, or None for null. ValueError when no phone has that name."""
|
|
23
|
+
if value is None:
|
|
24
|
+
return None
|
|
25
|
+
fleet = getattr(runtime, "fleet", None)
|
|
26
|
+
if fleet is None:
|
|
27
|
+
raise ValueError("This Mobster runs every task on one phone. Leave the profile's phone out.")
|
|
28
|
+
try:
|
|
29
|
+
phone = fleet.find(value)
|
|
30
|
+
except LookupError as error:
|
|
31
|
+
raise ValueError(str(error)) from None
|
|
32
|
+
return phone.id, phone.name
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def public(account):
|
|
36
|
+
return {**account, "display": display(account)}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def list_accounts(request: Request):
|
|
40
|
+
return Response.json({"accounts": [public(a) for a in store_for(request.runtime).all()]})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _remember(account):
|
|
44
|
+
"""A saved profile's voice and signature are memory facts, edited where the profile is (memory/writer.py)."""
|
|
45
|
+
from ..memory import writer
|
|
46
|
+
writer.from_profile(account)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def add_account(request: Request):
|
|
50
|
+
_writable(request)
|
|
51
|
+
body = request.body or {}
|
|
52
|
+
device = _device(request.runtime, body.get("device")) if isinstance(body, dict) else None
|
|
53
|
+
account = store_for(request.runtime).add(body, device)
|
|
54
|
+
_remember(account)
|
|
55
|
+
return Response.json({"account": public(account)}, 201)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def edit_account(request: Request):
|
|
59
|
+
_writable(request)
|
|
60
|
+
body = request.body or {}
|
|
61
|
+
device = _device(request.runtime, body.get("device")) if isinstance(body, dict) and "device" in body else None
|
|
62
|
+
try:
|
|
63
|
+
account = store_for(request.runtime).update(request.params["id"], body, device)
|
|
64
|
+
except AccountNotFound as error:
|
|
65
|
+
raise APIError(str(error), 404, "account_not_found") from None
|
|
66
|
+
_remember(account)
|
|
67
|
+
return Response.json({"account": public(account)})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def delete_account(request: Request):
|
|
71
|
+
_writable(request)
|
|
72
|
+
try:
|
|
73
|
+
store_for(request.runtime).remove(request.params["id"])
|
|
74
|
+
except AccountNotFound as error:
|
|
75
|
+
raise APIError(str(error), 404, "account_not_found") from None
|
|
76
|
+
from ..memory import writer
|
|
77
|
+
writer.profile_removed(request.params["id"])
|
|
78
|
+
return Response.json({"deleted": True})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _key(request):
|
|
82
|
+
headers = request.headers
|
|
83
|
+
values = headers.get_all("Idempotency-Key", []) if hasattr(headers, "get_all") else (
|
|
84
|
+
[headers["Idempotency-Key"]] if headers.get("Idempotency-Key") else [])
|
|
85
|
+
if not values:
|
|
86
|
+
return None
|
|
87
|
+
if len(values) > 1 or not KEY.fullmatch(values[0]):
|
|
88
|
+
raise ValueError("Invalid Idempotency-Key")
|
|
89
|
+
return values[0]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run_accounts(request: Request):
|
|
93
|
+
"""One task per account."""
|
|
94
|
+
runtime, body = request.runtime, request.body or {}
|
|
95
|
+
if not isinstance(body, dict) or set(body) - RUN_FIELDS:
|
|
96
|
+
raise ValueError("A task for several profiles has accounts, goal, and optionally appId, engine and "
|
|
97
|
+
"outputFormat")
|
|
98
|
+
names = body.get("accounts")
|
|
99
|
+
if not isinstance(names, list) or not 1 <= len(names) <= MAX_RUN_ACCOUNTS:
|
|
100
|
+
raise ValueError(f"accounts is a list of 1 to {MAX_RUN_ACCOUNTS} profiles")
|
|
101
|
+
stored = store_for(runtime).all()
|
|
102
|
+
chosen = []
|
|
103
|
+
for name in names:
|
|
104
|
+
try:
|
|
105
|
+
account = resolve(stored, name)
|
|
106
|
+
except (AccountNotFound, AmbiguousAccount) as error:
|
|
107
|
+
raise APIError(str(error), 404 if isinstance(error, AccountNotFound) else 400,
|
|
108
|
+
"account_not_found" if isinstance(error, AccountNotFound) else "account_ambiguous") from None
|
|
109
|
+
if all(account["id"] != other["id"] for other in chosen):
|
|
110
|
+
chosen.append(account)
|
|
111
|
+
by_phone = {}
|
|
112
|
+
for account in chosen:
|
|
113
|
+
by_phone.setdefault(account.get("device") or "", []).append(account)
|
|
114
|
+
for together in by_phone.values():
|
|
115
|
+
if len(together) > 1:
|
|
116
|
+
where = together[0].get("deviceName") or "your default phone"
|
|
117
|
+
listed = " and ".join(display(a) for a in together)
|
|
118
|
+
raise APIError(f"{listed} are both on {where}, and a phone runs one task at a time. Run them one after "
|
|
119
|
+
"the other, or move one profile to another phone.", 409, "same_phone")
|
|
120
|
+
key = _key(request)
|
|
121
|
+
origin = request.origin if request.origin in harness_api.ORIGINS else "api"
|
|
122
|
+
rows = []
|
|
123
|
+
for account in chosen:
|
|
124
|
+
try:
|
|
125
|
+
created = runtime.create(body.get("appId"), body.get("goal"), "live",
|
|
126
|
+
f"{key}:{account['id']}" if key else None,
|
|
127
|
+
output_format=body.get("outputFormat", "auto"), engine=body.get("engine"),
|
|
128
|
+
extras={"account": account["id"]}, origin=origin)
|
|
129
|
+
except APIError as error:
|
|
130
|
+
rows.append({"account": account["id"], "display": display(account), "error": str(error),
|
|
131
|
+
"code": error.code})
|
|
132
|
+
continue
|
|
133
|
+
except ValueError as error:
|
|
134
|
+
if len(chosen) == 1:
|
|
135
|
+
raise
|
|
136
|
+
rows.append({"account": account["id"], "display": display(account), "error": str(error),
|
|
137
|
+
"code": "invalid"})
|
|
138
|
+
continue
|
|
139
|
+
run = created[0] if isinstance(created, tuple) else created
|
|
140
|
+
rows.append({"account": account["id"], "display": display(account), "run": run.public(include_events=False)})
|
|
141
|
+
started = any("run" in row for row in rows)
|
|
142
|
+
return Response.json({"runs": rows}, 201 if started else 409)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _pacing_answer(request, profile):
|
|
146
|
+
from .pacing import pacing_for
|
|
147
|
+
pacing = pacing_for(request.runtime)
|
|
148
|
+
return {**pacing.settings(profile), "today": pacing.today(profile)}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _known(request):
|
|
152
|
+
profile = request.params["id"]
|
|
153
|
+
if store_for(request.runtime).get(profile) is None:
|
|
154
|
+
raise APIError("No profile has that id. Add it in Settings › Profiles.", 404, "account_not_found")
|
|
155
|
+
return profile
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_pacing(request):
|
|
159
|
+
return Response.json({"pacing": _pacing_answer(request, _known(request))})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def save_pacing(request):
|
|
163
|
+
from .pacing import PacingError, pacing_for
|
|
164
|
+
_writable(request)
|
|
165
|
+
profile = _known(request)
|
|
166
|
+
try:
|
|
167
|
+
pacing_for(request.runtime).save(profile, request.body or {})
|
|
168
|
+
except PacingError as error:
|
|
169
|
+
raise APIError(str(error), 400, "invalid") from None
|
|
170
|
+
return Response.json({"pacing": _pacing_answer(request, profile)})
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def resume_profile(request):
|
|
174
|
+
from .pacing import pacing_for
|
|
175
|
+
_writable(request)
|
|
176
|
+
profile = _known(request)
|
|
177
|
+
pacing_for(request.runtime).resume(profile)
|
|
178
|
+
return Response.json({"pacing": _pacing_answer(request, profile)})
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_stats(request):
|
|
182
|
+
from ..packs.register import all_packs
|
|
183
|
+
from . import stats
|
|
184
|
+
app = request.params["app"]
|
|
185
|
+
if app not in {pack.key for pack in all_packs()}:
|
|
186
|
+
raise APIError(f"There is no {app} pack. Use one of: " + ", ".join(p.key for p in all_packs()) + ".", 404,
|
|
187
|
+
"stats_unknown_app")
|
|
188
|
+
try:
|
|
189
|
+
days = int((request.query.get("days") or ["30"])[0])
|
|
190
|
+
except ValueError:
|
|
191
|
+
days = 0
|
|
192
|
+
if not 1 <= days <= 365:
|
|
193
|
+
raise APIError("days is a number from 1 to 365.", 400, "invalid")
|
|
194
|
+
return Response.json(stats.series(store_for(request.runtime).data_dir, app, request.params["handle"], days))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
ROUTES = (
|
|
198
|
+
Route("accounts", "GET", "/api/accounts", list_accounts, body="none"),
|
|
199
|
+
Route("accounts", "POST", "/api/accounts", add_account),
|
|
200
|
+
Route("accounts", "POST", "/api/accounts/runs", run_accounts, idempotency=True),
|
|
201
|
+
Route("accounts", "POST", "/api/accounts/{id}", edit_account),
|
|
202
|
+
Route("accounts", "GET", "/api/accounts/{id}/pacing", get_pacing, body="none"),
|
|
203
|
+
Route("accounts", "POST", "/api/accounts/{id}/pacing", save_pacing),
|
|
204
|
+
Route("accounts", "POST", "/api/accounts/{id}/resume", resume_profile, body="none"),
|
|
205
|
+
Route("accounts", "GET", "/api/accounts/{app}/{handle}/stats", get_stats, body="none"),
|
|
206
|
+
Route("accounts", "DELETE", "/api/accounts/{id}", delete_account, body="none"),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def register_routes():
|
|
211
|
+
for route in ROUTES:
|
|
212
|
+
register(route)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""A task that names a profile (an account in the code): its run field, and where it runs."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
|
|
7
|
+
from .context import fill
|
|
8
|
+
from .store import AccountNotFound, AmbiguousAccount, display, resolve, store_for
|
|
9
|
+
|
|
10
|
+
SMART_ONLY = ("A task for a profile runs on Mobster's agent, which confirms the profile on screen before it posts. "
|
|
11
|
+
"Choose Mobster, or leave the profile out.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate(value, runtime):
|
|
15
|
+
if isinstance(value, dict):
|
|
16
|
+
value = value.get("id")
|
|
17
|
+
try:
|
|
18
|
+
account = resolve(store_for(runtime).all(), value)
|
|
19
|
+
except (AccountNotFound, AmbiguousAccount) as error:
|
|
20
|
+
raise ValueError(str(error)) from None
|
|
21
|
+
return {"id": account["id"], "handle": account["handle"], "app": dict(account["app"]),
|
|
22
|
+
"device": account.get("device"), "deviceName": account.get("deviceName")}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def capture(runtime, account):
|
|
26
|
+
"""Detach the selected writing context from the editable profile at admission."""
|
|
27
|
+
stored = store_for(runtime).get(account["id"])
|
|
28
|
+
if stored is None:
|
|
29
|
+
raise ValueError("This profile was removed. Choose a saved profile again.")
|
|
30
|
+
identity = ("id", "handle", "app", "device", "deviceName")
|
|
31
|
+
if any(stored.get(key) != account.get(key) for key in identity):
|
|
32
|
+
raise ValueError("This profile changed while the task was starting. Choose it again.")
|
|
33
|
+
selected = {key: stored.get(key) for key in identity}
|
|
34
|
+
selected.update(notes=stored.get("notes") or "", signature=stored.get("signature") or "",
|
|
35
|
+
links=stored.get("links") or [], variables=stored.get("variables") or {})
|
|
36
|
+
fingerprint = digest(selected)
|
|
37
|
+
frozen = MappingProxyType({**selected, "app": MappingProxyType(dict(selected["app"])),
|
|
38
|
+
"links": tuple(MappingProxyType(dict(link)) for link in selected["links"]),
|
|
39
|
+
"variables": MappingProxyType(dict(selected["variables"]))})
|
|
40
|
+
return frozen, fingerprint
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def digest(account):
|
|
44
|
+
plain = {**account, "app": dict(account["app"]), "links": [dict(link) for link in account["links"]],
|
|
45
|
+
"variables": dict(account["variables"])}
|
|
46
|
+
return hashlib.sha256(json.dumps(plain, sort_keys=True, ensure_ascii=True,
|
|
47
|
+
allow_nan=False).encode()).hexdigest()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def route(runtime, account, goal, device):
|
|
51
|
+
"""(goal, device) for a task as ``account``. ValueError when the task names another phone."""
|
|
52
|
+
stored = account
|
|
53
|
+
wanted = stored.get("device")
|
|
54
|
+
if wanted and device is not None:
|
|
55
|
+
fleet = getattr(runtime, "fleet", None)
|
|
56
|
+
named = None
|
|
57
|
+
if fleet is not None:
|
|
58
|
+
try:
|
|
59
|
+
named = fleet.find(device).id
|
|
60
|
+
except LookupError:
|
|
61
|
+
named = None
|
|
62
|
+
if named != wanted:
|
|
63
|
+
where = stored.get("deviceName") or "another phone"
|
|
64
|
+
raise ValueError(f"{display(stored)} is on {where}. Leave the phone out and Mobster uses that one, or "
|
|
65
|
+
"change the profile's phone in Settings › Profiles.")
|
|
66
|
+
if (wanted and device is None and getattr(runtime, "fleet", None) is not None
|
|
67
|
+
and getattr(runtime, "pool", 1) is None):
|
|
68
|
+
device = wanted
|
|
69
|
+
return fill(goal, stored), device
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Which profile is signed in, read off the screen, and the check before every commit as that profile."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from .store import display
|
|
8
|
+
|
|
9
|
+
TOP = .45 # the top part of the screen a profile's handle sits in (screen fractions)
|
|
10
|
+
MAX_REFUSALS = 3
|
|
11
|
+
# Own-profile controls in the apps' own words (English UI): "Edit profile", "Edit Profile", "Share profile".
|
|
12
|
+
OWN_PROFILE = re.compile(r"^\s*(?:edit|share)\s+profile\b", re.I)
|
|
13
|
+
# Controls only someone else's profile shows.
|
|
14
|
+
OTHER_PROFILE = re.compile(r"^\s*(?:follow|following|follow back|requested|message|subscribe)\s*$", re.I)
|
|
15
|
+
HANDLE_TOKEN = re.compile(r"(?<![A-Za-z0-9_.@-])@([A-Za-z0-9._-]{1,64}?)(?=[^A-Za-z0-9_.-]|\.(?![A-Za-z0-9_-])|$)")
|
|
16
|
+
TITLE_TOKEN = re.compile(r"[A-Za-z0-9._-]{1,64}")
|
|
17
|
+
|
|
18
|
+
VERBS = {"post": ("post", "posting"), "comment": ("comment", "commenting"), "reply": ("reply", "replying"),
|
|
19
|
+
"send_message": ("send", "sending"), "react": ("react", "reacting"), "follow": ("follow", "following"),
|
|
20
|
+
"unfollow": ("unfollow", "unfollowing"), "share": ("share", "sharing"), "delete": ("delete", "deleting")}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _texts(element):
|
|
24
|
+
return [t for t in (getattr(element, "label", "") or "", getattr(element, "value", "") or "") if t]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _top(element):
|
|
28
|
+
x, y, w, h = element.rect
|
|
29
|
+
return y + h / 2 <= TOP
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def shows(text, handle):
|
|
33
|
+
"""Whether ``text`` names ``handle`` as a whole token, with or without its @ ("acme", "@acme", "acme ✓")."""
|
|
34
|
+
pattern = r"(?<![A-Za-z0-9_.@-])@?" + re.escape(handle) + r"(?![A-Za-z0-9_-]|\.[A-Za-z0-9_-])"
|
|
35
|
+
return re.search(pattern, text or "", re.I) is not None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def own_profile(snapshot):
|
|
39
|
+
"""Whether ``snapshot`` is the signed-in profile's own page: an own-profile control, and no Follow or Message."""
|
|
40
|
+
elements = list(getattr(snapshot, "elements", ()) or ())
|
|
41
|
+
labels = [(e.label or "").strip() for e in elements]
|
|
42
|
+
return any(OWN_PROFILE.search(l) for l in labels) and not any(OTHER_PROFILE.fullmatch(l) for l in labels if l)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def read(snapshot, handle, known=()):
|
|
46
|
+
"""("confirmed", "@acme") | ("mismatch", "@other" or None) | ("unknown", None) for ``handle`` on ``snapshot``."""
|
|
47
|
+
if not own_profile(snapshot):
|
|
48
|
+
return "unknown", None
|
|
49
|
+
top = [e for e in getattr(snapshot, "elements", ()) or () if _top(e)]
|
|
50
|
+
if any(shows(text, handle) for e in top for text in _texts(e)):
|
|
51
|
+
return "confirmed", "@" + handle
|
|
52
|
+
for other in known:
|
|
53
|
+
if other.casefold() != handle.casefold() and any(shows(t, other) for e in top for t in _texts(e)):
|
|
54
|
+
return "mismatch", "@" + other
|
|
55
|
+
for e in top:
|
|
56
|
+
for text in _texts(e):
|
|
57
|
+
found = HANDLE_TOKEN.search(text)
|
|
58
|
+
if found and found.group(1).casefold() != handle.casefold():
|
|
59
|
+
return "mismatch", "@" + found.group(1)
|
|
60
|
+
# The navigation bar's title on an own profile is the username (Instagram, Threads).
|
|
61
|
+
bar = next((e for e in top if e.role == "NavigationBar" and (e.label or "").strip()), None)
|
|
62
|
+
if bar is not None:
|
|
63
|
+
title = " ".join(bar.label.split()).split(",")[0].strip()
|
|
64
|
+
if TITLE_TOKEN.fullmatch(title):
|
|
65
|
+
return "mismatch", "@" + title
|
|
66
|
+
return "mismatch", None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AccountCheck:
|
|
70
|
+
"""One run's profile: what was read in its app, and the commit check."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, account, known=(), device_name=None, emit=None):
|
|
73
|
+
self.account = account
|
|
74
|
+
self.handle = account["handle"]
|
|
75
|
+
self.bundle = account["app"]["bundleId"]
|
|
76
|
+
self.app_name = account["app"]["name"]
|
|
77
|
+
self.known = tuple(known)
|
|
78
|
+
self.device_name = device_name
|
|
79
|
+
self.emit = emit or (lambda event: None)
|
|
80
|
+
self.state, self.shown = "unknown", None
|
|
81
|
+
# Pacing (accounts/pacing.py) and the run's stop, set by check_for; None in a run without them.
|
|
82
|
+
self.pacing = None
|
|
83
|
+
self.cancelled = lambda: False
|
|
84
|
+
self.on_signal = None # called with "challenge" or "signed_out" when the screen shows one
|
|
85
|
+
self.media = None # content_queue.media_check.MediaCheck for a queued post's task (the queue checks on)
|
|
86
|
+
self.refusals = 0
|
|
87
|
+
self.lock = threading.Lock()
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def acting_as(self):
|
|
91
|
+
return display(self.account)
|
|
92
|
+
|
|
93
|
+
def look(self, snapshot):
|
|
94
|
+
"""Read ``snapshot`` (in the profile's app only) and keep what it says. Returns (state, shown)."""
|
|
95
|
+
if getattr(snapshot, "bundle_id", None) != self.bundle:
|
|
96
|
+
return "unknown", None
|
|
97
|
+
state, shown = read(snapshot, self.handle, self.known)
|
|
98
|
+
if state != "confirmed":
|
|
99
|
+
# A sign-in page or a security check in the profile's app, on a screen that doesn't show the profile: kept
|
|
100
|
+
# at once for the Phones page and the pre-run check (fleet_health.py), and as an event. The screen's text is
|
|
101
|
+
# only matched, never followed.
|
|
102
|
+
from ..fleet_health import classify_screen, snapshot_labels
|
|
103
|
+
signal = classify_screen(snapshot_labels(snapshot))
|
|
104
|
+
if signal is not None:
|
|
105
|
+
self.emit({"event": "account_signal", "account": self.account["id"], "signal": signal})
|
|
106
|
+
if self.on_signal is not None:
|
|
107
|
+
try:
|
|
108
|
+
self.on_signal(signal)
|
|
109
|
+
except Exception: # noqa: BLE001 -- keeping a sighting is never part of the run's outcome
|
|
110
|
+
pass
|
|
111
|
+
with self.lock:
|
|
112
|
+
if state != "unknown":
|
|
113
|
+
self.state, self.shown = state, shown
|
|
114
|
+
self.emit({"event": "account_checked", "account": self.account["id"], "state": state})
|
|
115
|
+
return state, shown
|
|
116
|
+
|
|
117
|
+
def commit(self, act, snapshot):
|
|
118
|
+
"""The verdict before a commit ``act`` on ``snapshot``: None (not in the profile's app), {"acting_as"},
|
|
119
|
+
{"refuse": feedback} or {"stop": reason, "code"}."""
|
|
120
|
+
if getattr(snapshot, "bundle_id", None) != self.bundle:
|
|
121
|
+
return None
|
|
122
|
+
# A later own-profile screen may show a different account. Other screens retain the last check.
|
|
123
|
+
self.look(snapshot)
|
|
124
|
+
with self.lock:
|
|
125
|
+
state, shown = self.state, self.shown
|
|
126
|
+
if state == "mismatch":
|
|
127
|
+
return {"stop": self.mismatch_line(act, shown), "code": "account_mismatch"}
|
|
128
|
+
if state == "confirmed":
|
|
129
|
+
paced = self.paced(act)
|
|
130
|
+
if paced is not None:
|
|
131
|
+
return paced
|
|
132
|
+
if self.media is not None and act in self.media.POST_ACTS:
|
|
133
|
+
# A queued post: the app's limited-photos notice ends the task here; otherwise the frontier hands
|
|
134
|
+
# the screen to ``preview``, and the batch approval answers this Post only when it holds the media.
|
|
135
|
+
return self.media.stop(snapshot) or {"acting_as": self.acting_as, "preview": self.media.preview}
|
|
136
|
+
return {"acting_as": self.acting_as}
|
|
137
|
+
with self.lock:
|
|
138
|
+
self.refusals += 1
|
|
139
|
+
if self.refusals >= MAX_REFUSALS:
|
|
140
|
+
return {"stop": self.unconfirmed_line(act), "code": "account_unconfirmed"}
|
|
141
|
+
return {"refuse": (f"Refused: confirm the profile first. Open {self.app_name}'s own profile screen (the one "
|
|
142
|
+
f"with Edit profile), use CHECK_PROFILE there, and once it confirms @{self.handle}, come "
|
|
143
|
+
"back and do this.")}
|
|
144
|
+
|
|
145
|
+
def paced(self, act):
|
|
146
|
+
"""The profile's pacing before ``act`` (accounts/pacing.py): None to go on."""
|
|
147
|
+
if self.pacing is None:
|
|
148
|
+
return None
|
|
149
|
+
from .pacing import WAIT_MAX_S
|
|
150
|
+
try:
|
|
151
|
+
verdict = self.pacing.check(self.account["id"], act)
|
|
152
|
+
except Exception: # noqa: BLE001
|
|
153
|
+
return {"stop": "Mobster couldn't read this profile's pacing, so it stopped before acting.",
|
|
154
|
+
"code": "pacing_unreadable"}
|
|
155
|
+
if verdict is None:
|
|
156
|
+
return None
|
|
157
|
+
if "wait" in verdict and verdict["wait"] <= WAIT_MAX_S:
|
|
158
|
+
self.emit({"event": "pacing_wait", "account": self.account["id"], "seconds": round(verdict["wait"])})
|
|
159
|
+
end = time.monotonic() + verdict["wait"]
|
|
160
|
+
while time.monotonic() < end:
|
|
161
|
+
if self.cancelled():
|
|
162
|
+
return {"stop": "Stopped while waiting for this profile's pace.", "code": "pacing_wait"}
|
|
163
|
+
time.sleep(min(1.0, end - time.monotonic()))
|
|
164
|
+
return None
|
|
165
|
+
if "wait" in verdict:
|
|
166
|
+
minutes = round(verdict["wait"] / 60)
|
|
167
|
+
stop = {"stop": f"@{self.handle} {verdict['reason']}; the next can go in {minutes} minutes. Mobster "
|
|
168
|
+
"stopped before acting.", "code": "pacing_gap"}
|
|
169
|
+
else:
|
|
170
|
+
stop = {"stop": f"@{self.handle}: {verdict['stop']}", "code": verdict.get("code") or "pacing"}
|
|
171
|
+
if isinstance(verdict.get("until"), (int, float)):
|
|
172
|
+
stop["until"] = verdict["until"]
|
|
173
|
+
# The task's record of why it stopped and when the profile may act again (a workflow waits for it).
|
|
174
|
+
self.emit({"event": "pacing_stop", "account": self.account["id"], "code": stop["code"],
|
|
175
|
+
**({"until": stop["until"]} if "until" in stop else {})})
|
|
176
|
+
return stop
|
|
177
|
+
|
|
178
|
+
def mismatch_line(self, act, shown):
|
|
179
|
+
verb = VERBS.get(act, ("do this", "acting"))
|
|
180
|
+
who = shown or "another profile"
|
|
181
|
+
return f"This would {verb[0]} as {who}, not @{self.handle}. Mobster stopped before {verb[1]}."
|
|
182
|
+
|
|
183
|
+
def unconfirmed_line(self, act):
|
|
184
|
+
verb = VERBS.get(act, ("do this", "acting"))[1]
|
|
185
|
+
where = f" on {self.device_name}" if self.device_name else ""
|
|
186
|
+
return (f"Mobster couldn't confirm on screen that @{self.handle} is signed in to {self.app_name}, so it "
|
|
187
|
+
f"stopped before {verb}. Open {self.app_name}{where}, make sure @{self.handle} is the profile signed "
|
|
188
|
+
f"in, and run the task again.")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class CheckAccount:
|
|
192
|
+
"""The CHECK_PROFILE tool (agent_hooks.Skill): reads the screen at hand and says what it shows."""
|
|
193
|
+
|
|
194
|
+
op = "CHECK_PROFILE"
|
|
195
|
+
needs_target = False
|
|
196
|
+
|
|
197
|
+
def __init__(self, check):
|
|
198
|
+
self.check = check
|
|
199
|
+
self.prompt = (f"- CHECK_PROFILE: confirm the signed-in profile is @{check.handle} in {check.app_name}. Use it "
|
|
200
|
+
"on the app's own profile screen (the one with Edit profile) before you post, reply, comment "
|
|
201
|
+
"or send a message.")
|
|
202
|
+
|
|
203
|
+
def available(self, context):
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def prepare(self, context, act, snapshot):
|
|
207
|
+
from ..agent_hooks import Prepared
|
|
208
|
+
return Prepared(title=None)
|
|
209
|
+
|
|
210
|
+
def perform(self, context, prepared, act, snapshot):
|
|
211
|
+
from ..agent_hooks import GuardVerdict, SkillResult
|
|
212
|
+
if getattr(snapshot, "bundle_id", None) != self.check.bundle:
|
|
213
|
+
return SkillResult(f"This screen isn't {self.check.app_name}. Open {self.check.app_name}'s own profile "
|
|
214
|
+
"screen, then use CHECK_PROFILE.")
|
|
215
|
+
state, shown = self.check.look(snapshot)
|
|
216
|
+
if state == "confirmed":
|
|
217
|
+
return SkillResult(f"Confirmed: @{self.check.handle} is the profile signed in to {self.check.app_name}. "
|
|
218
|
+
"Go on with the task.")
|
|
219
|
+
if state == "mismatch":
|
|
220
|
+
line = (f"{shown or 'Another profile'} is signed in to {self.check.app_name}, not @{self.check.handle}. "
|
|
221
|
+
"Mobster stopped before acting as the wrong profile. Switch to "
|
|
222
|
+
f"@{self.check.handle} in {self.check.app_name}, then run the task again.")
|
|
223
|
+
return SkillResult(line, stop=GuardVerdict("stop", "account_mismatch", line))
|
|
224
|
+
return SkillResult(f"This isn't {self.check.app_name}'s own profile screen (it has no Edit profile, or it "
|
|
225
|
+
f"shows Follow or Message). Open the profile tab, then use CHECK_PROFILE.")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
_checks = {}
|
|
229
|
+
_checks_lock = threading.Lock()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def check_for(run_context):
|
|
233
|
+
"""The run's AccountCheck, made once per run from its ``account`` field; None for a run with no profile."""
|
|
234
|
+
extras = getattr(run_context, "extras", None) or {}
|
|
235
|
+
account = extras.get("account")
|
|
236
|
+
if not isinstance(account, dict) or not account.get("id"):
|
|
237
|
+
return None
|
|
238
|
+
with _checks_lock:
|
|
239
|
+
check = _checks.get(run_context.run_id)
|
|
240
|
+
if check is None:
|
|
241
|
+
from .store import store_for
|
|
242
|
+
known = ()
|
|
243
|
+
try:
|
|
244
|
+
known = tuple(a["handle"] for a in store_for(run_context.runtime).all()
|
|
245
|
+
if a["app"]["bundleId"] == account["app"]["bundleId"] and a["id"] != account["id"])
|
|
246
|
+
except Exception: # noqa: BLE001 -- the other handles only name a mismatch better
|
|
247
|
+
pass
|
|
248
|
+
check = _checks[run_context.run_id] = AccountCheck(account, known, account.get("deviceName"),
|
|
249
|
+
emit=run_context.emit)
|
|
250
|
+
try:
|
|
251
|
+
from .pacing import pacing_for
|
|
252
|
+
check.pacing = pacing_for(run_context.runtime)
|
|
253
|
+
except Exception: # noqa: BLE001 - a runtime with no data folder (tests): no pacing to keep
|
|
254
|
+
check.pacing = None
|
|
255
|
+
check.cancelled = getattr(run_context, "cancelled", None) or (lambda: False)
|
|
256
|
+
from ..content_queue.media_check import for_run
|
|
257
|
+
check.media = for_run(run_context)
|
|
258
|
+
health = getattr(getattr(run_context.runtime, "fleet", None), "health", None)
|
|
259
|
+
if health is not None:
|
|
260
|
+
device = run_context.device_id
|
|
261
|
+
check.on_signal = lambda signal: health.note_screen(account, device, signal)
|
|
262
|
+
return check
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def forget(run_id):
|
|
266
|
+
with _checks_lock:
|
|
267
|
+
_checks.pop(run_id, None)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def commit_check_for(run_context):
|
|
271
|
+
"""The frontier's ``commit_check`` for this run, or None (no profile: the frontier is as before)."""
|
|
272
|
+
check = check_for(run_context)
|
|
273
|
+
return check.commit if check is not None else None
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""An account's numbers over time: one row a day per app and handle, in ``account-stats.json`` beside accounts.json."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import threading
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
FILE = "account-stats.json"
|
|
11
|
+
KEEP_DAYS = 400
|
|
12
|
+
FIELDS = ("followers", "following", "posts", "likes", "period", "views", "reach", "profile_visits", "follower_change")
|
|
13
|
+
_lock = threading.Lock()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load(folder):
|
|
17
|
+
try:
|
|
18
|
+
data = json.loads((Path(folder) / FILE).read_text())
|
|
19
|
+
except (OSError, ValueError):
|
|
20
|
+
return {}
|
|
21
|
+
return data if isinstance(data, dict) else {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def record(folder, app, handle, values, now=None):
|
|
25
|
+
"""Merge ``values`` (any of FIELDS) into today's row for ``app`` and ``handle``;
|
|
26
|
+
a value of None keeps what is there."""
|
|
27
|
+
now = now or datetime.now(timezone.utc)
|
|
28
|
+
key, today = f"{app}/{handle.lstrip('@').casefold()}", now.date().isoformat()
|
|
29
|
+
with _lock:
|
|
30
|
+
data = _load(folder)
|
|
31
|
+
rows = data.setdefault(key, {})
|
|
32
|
+
row = rows.setdefault(today, {})
|
|
33
|
+
row.update({k: v for k, v in values.items() if k in FIELDS and v is not None})
|
|
34
|
+
row["at"] = now.isoformat(timespec="seconds")
|
|
35
|
+
for old in sorted(rows)[:-KEEP_DAYS]:
|
|
36
|
+
del rows[old]
|
|
37
|
+
Path(folder).mkdir(parents=True, exist_ok=True)
|
|
38
|
+
handle_, temporary = tempfile.mkstemp(prefix=".stats.", suffix=".json", dir=folder)
|
|
39
|
+
with os.fdopen(handle_, "w") as stream:
|
|
40
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
41
|
+
stream.write(json.dumps(data, indent=1) + "\n")
|
|
42
|
+
os.replace(temporary, Path(folder) / FILE)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def series(folder, app, handle, days=30, now=None):
|
|
46
|
+
"""{app, handle, days, snapshots: [{date, at, ...FIELDS}] oldest first, latest: newest snapshot or None}."""
|
|
47
|
+
now = now or datetime.now(timezone.utc)
|
|
48
|
+
since = (now - timedelta(days=days)).date().isoformat()
|
|
49
|
+
handle = handle.lstrip("@").casefold()
|
|
50
|
+
rows = _load(folder).get(f"{app}/{handle}", {})
|
|
51
|
+
snapshots = [{"date": day, **{k: row.get(k) for k in ("at",) + FIELDS}} for day, row in sorted(rows.items())
|
|
52
|
+
if day >= since]
|
|
53
|
+
return {"app": app, "handle": handle, "days": days, "snapshots": snapshots,
|
|
54
|
+
"latest": snapshots[-1] if snapshots else None}
|