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,80 @@
|
|
|
1
|
+
"""A profile's notes for the agent, and its values in a task's placeholders (the feature is Profiles; see screen.py)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from .. import harness_api
|
|
6
|
+
from .store import display
|
|
7
|
+
|
|
8
|
+
ORDER = 15
|
|
9
|
+
MAX_CHARS = 4000
|
|
10
|
+
PLACEHOLDER = re.compile(r"(?<!\{)\{\s*([A-Za-z][A-Za-z0-9_ -]{0,39}?)\s*\}(?!\})")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def values(account):
|
|
14
|
+
"""{lowercase name: value} a placeholder may name."""
|
|
15
|
+
out = {"handle": "@" + account["handle"]}
|
|
16
|
+
if account.get("signature"):
|
|
17
|
+
out["signature"] = account["signature"]
|
|
18
|
+
links = account.get("links") or []
|
|
19
|
+
if links:
|
|
20
|
+
out["link"] = links[0]["url"]
|
|
21
|
+
for link in links:
|
|
22
|
+
out.setdefault(re.sub(r"\s+", "_", link["label"].strip().casefold()), link["url"])
|
|
23
|
+
out.setdefault(link["label"].strip().casefold(), link["url"])
|
|
24
|
+
for name, value in (account.get("variables") or {}).items():
|
|
25
|
+
if value:
|
|
26
|
+
out.setdefault(name, value)
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def fill(goal, account):
|
|
31
|
+
"""``goal`` with the profile's values in its placeholders: {handle}, {signature}, {link}, link labels, variables."""
|
|
32
|
+
known = values(account)
|
|
33
|
+
|
|
34
|
+
def put(match):
|
|
35
|
+
name = " ".join(match.group(1).split()).casefold()
|
|
36
|
+
value = known.get(name, known.get(name.replace(" ", "_")))
|
|
37
|
+
return value if value is not None else match.group(0)
|
|
38
|
+
|
|
39
|
+
return PLACEHOLDER.sub(put, goal or "")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def text(account):
|
|
43
|
+
lines = [f"You act as {display(account)} in this task. Everything you post, reply, comment or send there goes out "
|
|
44
|
+
f"as @{account['handle']}.",
|
|
45
|
+
f"Before the first post, reply, comment or message in {account['app']['name']}, open its own profile "
|
|
46
|
+
"screen (the one with Edit profile) and use CHECK_PROFILE. If another profile is signed in, stop: "
|
|
47
|
+
"Mobster never switches profiles."]
|
|
48
|
+
if account.get("notes"):
|
|
49
|
+
lines.append("How this profile sounds (the person's notes): " + account["notes"])
|
|
50
|
+
if account.get("signature"):
|
|
51
|
+
lines.append("Signature: " + account["signature"])
|
|
52
|
+
for link in account.get("links") or ():
|
|
53
|
+
lines.append(f"Link, {link['label']}: {link['url']}")
|
|
54
|
+
for name, value in (account.get("variables") or {}).items():
|
|
55
|
+
if value:
|
|
56
|
+
lines.append(f"{name}: {value}")
|
|
57
|
+
return "\n".join(lines)[:MAX_CHARS]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AccountProvider:
|
|
61
|
+
key = "account"
|
|
62
|
+
max_chars = MAX_CHARS
|
|
63
|
+
|
|
64
|
+
def __init__(self, account):
|
|
65
|
+
self.account = account
|
|
66
|
+
|
|
67
|
+
def blocks(self, ctx):
|
|
68
|
+
return (harness_api.ContextBlock(key=self.key, title="The profile", text=text(self.account), stable=True),)
|
|
69
|
+
|
|
70
|
+
def turn_blocks(self, ctx, turn):
|
|
71
|
+
return ()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def provider(ctx):
|
|
75
|
+
"""The profile context captured when this run was admitted."""
|
|
76
|
+
account = (ctx.extras or {}).get("account")
|
|
77
|
+
if not isinstance(account, dict) or not account.get("id"):
|
|
78
|
+
return None
|
|
79
|
+
snapshot = getattr(ctx, "account_context", None)
|
|
80
|
+
return AccountProvider(snapshot) if snapshot is not None else None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""The ``list_profiles`` MCP tool, and ``list_accounts``, its deprecated alias (provider ``accounts``, seam S10)."""
|
|
2
|
+
|
|
3
|
+
from ..threads.client import Client, NoService, ServiceError
|
|
4
|
+
|
|
5
|
+
NO_SERVICE = "Open Mobster or run `mobster serve` so your agent can read your profiles."
|
|
6
|
+
EMPTY = "The user hasn't mapped any profiles yet. They add them in Mobster, Settings › Profiles."
|
|
7
|
+
SCHEMA = {"type": "object", "properties": {}, "required": [], "additionalProperties": False}
|
|
8
|
+
TOOL = "list_profiles"
|
|
9
|
+
ALIAS = "list_accounts"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AccountsProvider:
|
|
13
|
+
name = "accounts"
|
|
14
|
+
|
|
15
|
+
def __init__(self, connect=None):
|
|
16
|
+
self._connect = connect or (lambda: Client.connect(origin="mcp"))
|
|
17
|
+
|
|
18
|
+
def definitions(self, toolset):
|
|
19
|
+
return [{"name": TOOL, "title": "List profiles",
|
|
20
|
+
"description": "The user's social media profiles mapped in Mobster (handle, app and the iPhone each "
|
|
21
|
+
"is signed in on). Pass one to phone_task as profile, such as "
|
|
22
|
+
"\"@getmobster on Instagram\".",
|
|
23
|
+
"inputSchema": dict(SCHEMA)},
|
|
24
|
+
{"name": ALIAS, "title": "List profiles (old name)",
|
|
25
|
+
"description": "Deprecated: use list_profiles, which answers the same list. Kept so clients that "
|
|
26
|
+
"learned this name keep working.",
|
|
27
|
+
"inputSchema": dict(SCHEMA)}]
|
|
28
|
+
|
|
29
|
+
def annotations(self):
|
|
30
|
+
from ..mcp_server.tools import READ_ONLY
|
|
31
|
+
return {TOOL: READ_ONLY, ALIAS: READ_ONLY}
|
|
32
|
+
|
|
33
|
+
def instructions(self):
|
|
34
|
+
return ("phone_task takes profile (from list_profiles): the task runs on that profile's iPhone, and Mobster "
|
|
35
|
+
"confirms the profile on screen before it posts or sends.")
|
|
36
|
+
|
|
37
|
+
def call(self, name, arguments, call, toolset):
|
|
38
|
+
from ..mcp_server.protocol import ToolResult
|
|
39
|
+
from ..mcp_server.tools import ToolError
|
|
40
|
+
try:
|
|
41
|
+
client = self._connect()
|
|
42
|
+
answer = client.call("GET", "/api/accounts")
|
|
43
|
+
except (NoService, ServiceError) as error:
|
|
44
|
+
raise ToolError(NO_SERVICE if isinstance(error, NoService) else str(error)) from None
|
|
45
|
+
key, field = ("accounts", "account") if name == ALIAS else ("profiles", "profile")
|
|
46
|
+
rows = [{"id": a.get("id"), field: a.get("display"), "handle": "@" + str(a.get("handle")),
|
|
47
|
+
"app": (a.get("app") or {}).get("name"), "phone": a.get("deviceName") or "the default phone"}
|
|
48
|
+
for a in (answer or {}).get("accounts") or () if isinstance(a, dict)]
|
|
49
|
+
allow = getattr(toolset, "allow_devices", None)
|
|
50
|
+
if allow is not None:
|
|
51
|
+
rows = [row for row in rows if str(row["phone"]).strip().casefold() in allow]
|
|
52
|
+
if not rows:
|
|
53
|
+
return ToolResult(EMPTY, structured={key: []})
|
|
54
|
+
lines = "\n".join(f"- {row[field]}, on {row['phone']}" for row in rows)
|
|
55
|
+
return ToolResult("The user's profiles:\n" + lines, structured={key: rows})
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Pacing per profile: how often and when a profile may act, and a stop when the app challenges it."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import datetime
|
|
5
|
+
import fcntl
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
14
|
+
|
|
15
|
+
SETTINGS = "pacing.json"
|
|
16
|
+
LEDGER = "pacing.jsonl"
|
|
17
|
+
WAIT_MAX_S = 15 * 60
|
|
18
|
+
KEEP_DAYS = 3
|
|
19
|
+
COMPACT_BYTES = 128 * 1024 # a ledger bigger than this drops the entries older than KEEP_DAYS on the next record
|
|
20
|
+
KINDS = ("post", "message", "comment", "follow", "react")
|
|
21
|
+
ACT_KIND = {"post": "post", "publish": "post", "share": "post", "tweet": "post",
|
|
22
|
+
"send_message": "message", "reply": "message", "send": "message",
|
|
23
|
+
"comment": "comment",
|
|
24
|
+
"follow": "follow", "unfollow": "follow", "subscribe": "follow",
|
|
25
|
+
"react": "react", "like": "react"}
|
|
26
|
+
DEFAULTS = {"post": {"daily": 4, "gapMinutes": 60}, "message": {"daily": 20, "gapMinutes": 4},
|
|
27
|
+
"comment": {"daily": 30, "gapMinutes": 2}, "follow": {"daily": 30, "gapMinutes": 2},
|
|
28
|
+
"react": {"daily": 100, "gapMinutes": 0.5}}
|
|
29
|
+
CAPS = {"daily": (0, 1000), "gapMinutes": (0, 24 * 60)}
|
|
30
|
+
CHALLENGE_WORDS = ("captcha", "suspect", "automated", "unusual activity", "confirm it's you", "confirm it’s you",
|
|
31
|
+
"verify your account", "verify it's you", "verify it’s you", "selfie", "upload an id",
|
|
32
|
+
"temporarily blocked", "try again later", "action blocked", "we restrict", "challenge")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PacingError(ValueError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def kind_of(act):
|
|
40
|
+
return ACT_KIND.get(act or "")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _zone(name):
|
|
44
|
+
if not name:
|
|
45
|
+
return None
|
|
46
|
+
try:
|
|
47
|
+
return ZoneInfo(name)
|
|
48
|
+
except (ZoneInfoNotFoundError, ValueError):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _local(settings, at):
|
|
53
|
+
"""``at`` (epoch seconds) in the profile's time zone, or the Mac's when it names none."""
|
|
54
|
+
zone = _zone(settings.get("timeZone"))
|
|
55
|
+
return datetime.datetime.fromtimestamp(at, zone) if zone else datetime.datetime.fromtimestamp(at).astimezone()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _minutes(text):
|
|
59
|
+
hours, minutes = text.split(":")
|
|
60
|
+
value = int(hours) * 60 + int(minutes)
|
|
61
|
+
if not 0 <= value <= 24 * 60:
|
|
62
|
+
raise ValueError
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def clean(body):
|
|
67
|
+
"""A profile's pacing settings from ``body``: {timeZone, window: {start, end} | None, limits: {kind: {daily,
|
|
68
|
+
gapMinutes}}}."""
|
|
69
|
+
if not isinstance(body, dict) or set(body) - {"timeZone", "window", "limits"}:
|
|
70
|
+
raise PacingError("Pacing takes timeZone, window and limits.")
|
|
71
|
+
out = {"timeZone": None, "window": None, "limits": {}}
|
|
72
|
+
zone = body.get("timeZone")
|
|
73
|
+
if zone not in (None, ""):
|
|
74
|
+
if not isinstance(zone, str) or _zone(zone) is None:
|
|
75
|
+
raise PacingError("timeZone is a time zone such as America/New_York.")
|
|
76
|
+
out["timeZone"] = zone
|
|
77
|
+
window = body.get("window")
|
|
78
|
+
if window is not None:
|
|
79
|
+
try:
|
|
80
|
+
start, end = _minutes(window["start"]), _minutes(window["end"])
|
|
81
|
+
except (KeyError, TypeError, ValueError, AttributeError):
|
|
82
|
+
raise PacingError("window is {start, end} as 24-hour times, such as 09:00 and 21:00.") from None
|
|
83
|
+
if start == end:
|
|
84
|
+
raise PacingError("A posting window needs a start and an end that differ.")
|
|
85
|
+
out["window"] = {"start": window["start"], "end": window["end"]}
|
|
86
|
+
limits = body.get("limits", {})
|
|
87
|
+
if not isinstance(limits, dict):
|
|
88
|
+
raise PacingError("limits is an object with daily and gapMinutes values per kind.")
|
|
89
|
+
for kind, limit in limits.items():
|
|
90
|
+
if kind not in KINDS or not isinstance(limit, dict) or set(limit) - set(CAPS):
|
|
91
|
+
raise PacingError(f"limits are per kind: {', '.join(KINDS)}, each with daily and gapMinutes.")
|
|
92
|
+
cleaned = {}
|
|
93
|
+
for key, value in limit.items():
|
|
94
|
+
low, high = CAPS[key]
|
|
95
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not low <= value <= high:
|
|
96
|
+
raise PacingError(f"{kind} {key} is a number from {low} to {high}.")
|
|
97
|
+
cleaned[key] = value
|
|
98
|
+
out["limits"][kind] = cleaned
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Pacing:
|
|
103
|
+
"""Settings and ledger in ``data_dir``. ``clock`` replaces time.time in tests."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, data_dir, clock=time.time):
|
|
106
|
+
self.data_dir = Path(data_dir)
|
|
107
|
+
self.settings_path = self.data_dir / SETTINGS
|
|
108
|
+
self.ledger_path = self.data_dir / LEDGER
|
|
109
|
+
self.clock = clock
|
|
110
|
+
self._lock = threading.RLock()
|
|
111
|
+
|
|
112
|
+
def _read(self):
|
|
113
|
+
try:
|
|
114
|
+
data = json.loads(self.settings_path.read_text())
|
|
115
|
+
except FileNotFoundError:
|
|
116
|
+
return {"version": 1, "profiles": {}}
|
|
117
|
+
if not isinstance(data, dict) or not isinstance(data.get("profiles"), dict):
|
|
118
|
+
raise PacingError("The saved profile pacing settings aren't readable.")
|
|
119
|
+
for stored in data["profiles"].values():
|
|
120
|
+
if not isinstance(stored, dict):
|
|
121
|
+
raise PacingError("The saved profile pacing settings aren't readable.")
|
|
122
|
+
clean({key: value for key, value in stored.items() if key != "paused"})
|
|
123
|
+
paused = stored.get("paused")
|
|
124
|
+
if paused is not None and (not isinstance(paused, dict) or not isinstance(paused.get("reason"), str)):
|
|
125
|
+
raise PacingError("The saved profile pause isn't readable.")
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
@contextlib.contextmanager
|
|
129
|
+
def _exclusive(self):
|
|
130
|
+
"""This process's lock and the folder's file lock: the Mac app and the CLI share the folder."""
|
|
131
|
+
with self._lock:
|
|
132
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
fd = os.open(self.data_dir / "pacing.lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
|
|
134
|
+
try:
|
|
135
|
+
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
os.close(fd)
|
|
139
|
+
|
|
140
|
+
def _replace(self, path, text, prefix):
|
|
141
|
+
handle, temporary = tempfile.mkstemp(prefix=prefix, suffix=path.suffix, dir=self.data_dir)
|
|
142
|
+
try:
|
|
143
|
+
with os.fdopen(handle, "w") as stream:
|
|
144
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
145
|
+
stream.write(text)
|
|
146
|
+
os.replace(temporary, path)
|
|
147
|
+
except BaseException:
|
|
148
|
+
Path(temporary).unlink(missing_ok=True)
|
|
149
|
+
raise
|
|
150
|
+
|
|
151
|
+
@contextlib.contextmanager
|
|
152
|
+
def _edit(self):
|
|
153
|
+
with self._exclusive():
|
|
154
|
+
data = self._read()
|
|
155
|
+
yield data
|
|
156
|
+
self._replace(self.settings_path, json.dumps(data, indent=1) + "\n", ".pacing.")
|
|
157
|
+
|
|
158
|
+
def settings(self, profile):
|
|
159
|
+
"""The profile's settings with every kind's limit filled in, and whether it is paused."""
|
|
160
|
+
stored = self._read()["profiles"].get(profile) or {}
|
|
161
|
+
limits = {kind: {**DEFAULTS[kind], **((stored.get("limits") or {}).get(kind) or {})} for kind in KINDS}
|
|
162
|
+
return {"timeZone": stored.get("timeZone"), "window": stored.get("window"), "limits": limits,
|
|
163
|
+
"paused": stored.get("paused")}
|
|
164
|
+
|
|
165
|
+
def save(self, profile, body):
|
|
166
|
+
cleaned = clean(body)
|
|
167
|
+
with self._edit() as data:
|
|
168
|
+
current = data["profiles"].get(profile) or {}
|
|
169
|
+
data["profiles"][profile] = {**cleaned, "paused": current.get("paused")}
|
|
170
|
+
return self.settings(profile)
|
|
171
|
+
|
|
172
|
+
def pause(self, profile, reason):
|
|
173
|
+
with self._edit() as data:
|
|
174
|
+
current = data["profiles"].setdefault(profile, {})
|
|
175
|
+
current["paused"] = {"at": round(self.clock() * 1000), "reason": str(reason)[:300]}
|
|
176
|
+
|
|
177
|
+
def resume(self, profile):
|
|
178
|
+
with self._edit() as data:
|
|
179
|
+
if profile in data["profiles"]:
|
|
180
|
+
data["profiles"][profile]["paused"] = None
|
|
181
|
+
return self.settings(profile)
|
|
182
|
+
|
|
183
|
+
def record(self, profile, act, run_id=None):
|
|
184
|
+
kind = kind_of(act)
|
|
185
|
+
if kind is None:
|
|
186
|
+
return
|
|
187
|
+
line = json.dumps({"at": self.clock(), "profile": profile, "kind": kind, "run": run_id}) + "\n"
|
|
188
|
+
with self._exclusive():
|
|
189
|
+
fd = os.open(self.ledger_path, os.O_CREAT | os.O_WRONLY | os.O_APPEND | os.O_NOFOLLOW, 0o600)
|
|
190
|
+
try:
|
|
191
|
+
os.write(fd, line.encode())
|
|
192
|
+
finally:
|
|
193
|
+
os.close(fd)
|
|
194
|
+
# Preserve the ledger when maintenance cannot read or replace it.
|
|
195
|
+
with contextlib.suppress(OSError, ValueError):
|
|
196
|
+
self._compact()
|
|
197
|
+
|
|
198
|
+
def _compact(self):
|
|
199
|
+
"""Drop the entries older than KEEP_DAYS once the ledger is big (called under ``_exclusive``)."""
|
|
200
|
+
if self.ledger_path.stat().st_size <= COMPACT_BYTES:
|
|
201
|
+
return
|
|
202
|
+
cutoff = self.clock() - KEEP_DAYS * 86400
|
|
203
|
+
kept = [json.dumps(entry) + "\n" for entry in self._ledger() if entry["at"] >= cutoff]
|
|
204
|
+
self._replace(self.ledger_path, "".join(kept), ".pacing.")
|
|
205
|
+
|
|
206
|
+
def _ledger(self):
|
|
207
|
+
try:
|
|
208
|
+
lines = self.ledger_path.read_text().splitlines()
|
|
209
|
+
except FileNotFoundError:
|
|
210
|
+
return []
|
|
211
|
+
entries = []
|
|
212
|
+
for line in lines:
|
|
213
|
+
entry = json.loads(line)
|
|
214
|
+
if (not isinstance(entry, dict) or not isinstance(entry.get("profile"), str)
|
|
215
|
+
or entry.get("kind") not in KINDS
|
|
216
|
+
or type(entry.get("at")) not in (int, float)):
|
|
217
|
+
raise PacingError("The saved profile pacing history isn't readable.")
|
|
218
|
+
try:
|
|
219
|
+
entry["at"] = float(entry["at"])
|
|
220
|
+
except OverflowError:
|
|
221
|
+
raise PacingError("The saved profile pacing history isn't readable.") from None
|
|
222
|
+
if not math.isfinite(entry["at"]):
|
|
223
|
+
raise PacingError("The saved profile pacing history isn't readable.")
|
|
224
|
+
entries.append(entry)
|
|
225
|
+
return entries
|
|
226
|
+
|
|
227
|
+
def _entries(self, profile, kind):
|
|
228
|
+
cutoff = self.clock() - KEEP_DAYS * 86400
|
|
229
|
+
return sorted(entry["at"] for entry in self._ledger()
|
|
230
|
+
if entry["profile"] == profile and entry["kind"] == kind and entry["at"] >= cutoff)
|
|
231
|
+
|
|
232
|
+
def _day_start(self, settings, now):
|
|
233
|
+
return _local(settings, now).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
234
|
+
|
|
235
|
+
def today(self, profile):
|
|
236
|
+
"""{kind: how many ran today} in the profile's time zone."""
|
|
237
|
+
settings = self.settings(profile)
|
|
238
|
+
start = self._day_start(settings, self.clock()).timestamp()
|
|
239
|
+
return {kind: sum(1 for at in self._entries(profile, kind) if at >= start) for kind in KINDS}
|
|
240
|
+
|
|
241
|
+
def check(self, profile, act):
|
|
242
|
+
"""None when the profile may do ``act`` now; else {"wait": seconds, "reason"} or {"stop": reason}."""
|
|
243
|
+
kind = kind_of(act)
|
|
244
|
+
settings = self.settings(profile)
|
|
245
|
+
if settings.get("paused"):
|
|
246
|
+
return {"stop": "This profile is paused: " + settings["paused"]["reason"] + " Resume it in the Queue "
|
|
247
|
+
"after you've dealt with it in the app.", "code": "profile_paused"}
|
|
248
|
+
if kind is None:
|
|
249
|
+
return None
|
|
250
|
+
limit = settings["limits"][kind]
|
|
251
|
+
now = self.clock()
|
|
252
|
+
done = self._entries(profile, kind)
|
|
253
|
+
start = self._day_start(settings, now)
|
|
254
|
+
today = [at for at in done if at >= start.timestamp()]
|
|
255
|
+
if len(today) >= limit["daily"]:
|
|
256
|
+
tomorrow = (start + datetime.timedelta(days=1)).timestamp()
|
|
257
|
+
return {"stop": f"This profile has reached today's limit for {kind}s ({limit['daily']}). The next can go "
|
|
258
|
+
"after midnight in its time zone.", "code": "pacing_daily", "until": tomorrow}
|
|
259
|
+
if done and limit["gapMinutes"] > 0:
|
|
260
|
+
ready = done[-1] + limit["gapMinutes"] * 60
|
|
261
|
+
if ready > now:
|
|
262
|
+
return {"wait": ready - now, "reason": f"keeps at least {limit['gapMinutes']:g} minutes between "
|
|
263
|
+
f"{kind}s", "until": ready}
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
def next_post_time(self, profile, wanted=None):
|
|
267
|
+
"""The earliest time (epoch seconds) at or after ``wanted`` (default now) the profile may post."""
|
|
268
|
+
settings = self.settings(profile)
|
|
269
|
+
if settings.get("paused"):
|
|
270
|
+
return None
|
|
271
|
+
at = max(wanted or self.clock(), self.clock())
|
|
272
|
+
verdict = self.check(profile, "post")
|
|
273
|
+
if verdict and verdict.get("until"):
|
|
274
|
+
at = max(at, verdict["until"])
|
|
275
|
+
return self._in_window(settings, at)
|
|
276
|
+
|
|
277
|
+
def same_day(self, profile, first, second):
|
|
278
|
+
"""Whether two times (epoch seconds) fall on one calendar day in the profile's time zone (the Mac's when it
|
|
279
|
+
names none). Settings it can't read raise, so it never guesses a day."""
|
|
280
|
+
settings = self.settings(profile)
|
|
281
|
+
return _local(settings, first).date() == _local(settings, second).date()
|
|
282
|
+
|
|
283
|
+
def _in_window(self, settings, at):
|
|
284
|
+
window = settings.get("window")
|
|
285
|
+
if not window:
|
|
286
|
+
return at
|
|
287
|
+
local = _local(settings, at)
|
|
288
|
+
start, end = _minutes(window["start"]), _minutes(window["end"])
|
|
289
|
+
minute = local.hour * 60 + local.minute
|
|
290
|
+
inside = start <= minute < end if start < end else (minute >= start or minute < end)
|
|
291
|
+
if inside:
|
|
292
|
+
return at
|
|
293
|
+
day = local.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
294
|
+
opens = day + datetime.timedelta(minutes=start)
|
|
295
|
+
if opens.timestamp() <= at:
|
|
296
|
+
opens += datetime.timedelta(days=1)
|
|
297
|
+
return opens.timestamp()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def looks_challenged(*texts):
|
|
301
|
+
"""Whether a task's words say an app challenged the profile (fail closed: any of the words)."""
|
|
302
|
+
joined = " ".join(str(t or "") for t in texts).casefold()
|
|
303
|
+
return any(word in joined for word in CHALLENGE_WORDS)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
_instances = {}
|
|
307
|
+
_instances_lock = threading.Lock()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def pacing_for(runtime):
|
|
311
|
+
"""The Pacing beside ``runtime``'s profiles (accounts.json's folder)."""
|
|
312
|
+
from .store import store_for
|
|
313
|
+
folder = store_for(runtime).data_dir
|
|
314
|
+
with _instances_lock:
|
|
315
|
+
found = _instances.get(folder)
|
|
316
|
+
if found is None:
|
|
317
|
+
found = _instances[folder] = Pacing(folder)
|
|
318
|
+
return found
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Track `accounts`'s registrations (tracks.load calls ``register(api)`` once per process, staged)."""
|
|
2
|
+
|
|
3
|
+
from .. import alerts
|
|
4
|
+
from . import context, routing, screen
|
|
5
|
+
|
|
6
|
+
TOOL_ORDER = 5
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def tool(ctx):
|
|
10
|
+
check = screen.check_for(ctx)
|
|
11
|
+
return screen.CheckAccount(check) if check is not None else None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Listener:
|
|
15
|
+
"""Forgets a finished run's profile check; counts each commit a run as a profile carried out (pacing's ledger);
|
|
16
|
+
and pauses a profile whose run ended on an app's challenge (a captcha, "we suspect automated behavior", an ID
|
|
17
|
+
check), with an alert, so nothing else runs as it until the person resumes it."""
|
|
18
|
+
|
|
19
|
+
def on_step(self, run, record):
|
|
20
|
+
account = (getattr(run, "extras", None) or {}).get("account")
|
|
21
|
+
if not isinstance(account, dict) or not getattr(record, "commit", None):
|
|
22
|
+
return
|
|
23
|
+
pacing = _pacing(run)
|
|
24
|
+
if pacing is not None:
|
|
25
|
+
pacing.record(account["id"], record.commit, getattr(run, "id", None))
|
|
26
|
+
|
|
27
|
+
def on_finished(self, run, summary, private):
|
|
28
|
+
screen.forget(run.id)
|
|
29
|
+
account = (getattr(run, "extras", None) or {}).get("account")
|
|
30
|
+
if not isinstance(account, dict) or not isinstance(summary, dict):
|
|
31
|
+
return
|
|
32
|
+
from .pacing import looks_challenged
|
|
33
|
+
data = summary.get("data") if isinstance(summary.get("data"), dict) else {}
|
|
34
|
+
if (data.get("challenge") is True or summary.get("code") == "challenge" or (
|
|
35
|
+
summary.get("status") != "completed"
|
|
36
|
+
and looks_challenged(summary.get("reason"), summary.get("answer")))):
|
|
37
|
+
reason = (f"{account.get('app', {}).get('name', 'The app')} showed @{account.get('handle')} a check "
|
|
38
|
+
"(a captcha, a verification or a warning about automated behavior).")
|
|
39
|
+
pacing = _pacing(run)
|
|
40
|
+
if pacing is not None:
|
|
41
|
+
pacing.pause(account["id"], reason)
|
|
42
|
+
alerts.Alerts().challenge(f"@{account.get('handle')} paused", reason, getattr(run, "id", None))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _pacing(run):
|
|
46
|
+
from .pacing import pacing_for
|
|
47
|
+
runtime = _RUNTIME[0]
|
|
48
|
+
if runtime is None:
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
return pacing_for(runtime)
|
|
52
|
+
except Exception: # noqa: BLE001
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_RUNTIME = [None]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def started(runtime):
|
|
60
|
+
_RUNTIME[0] = runtime
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def stopped(runtime):
|
|
64
|
+
if _RUNTIME[0] is runtime:
|
|
65
|
+
_RUNTIME[0] = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def register(api):
|
|
69
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
70
|
+
from ..mcp_server import registry as mcp_registry
|
|
71
|
+
from . import mcp, routes
|
|
72
|
+
api.register_run_field("account", routing.validate, public=True)
|
|
73
|
+
api.register_context_provider(context.provider, order=context.ORDER)
|
|
74
|
+
api.register_tool(tool, order=TOOL_ORDER)
|
|
75
|
+
api.register_run_listener(Listener())
|
|
76
|
+
api.register_service(started, stopped)
|
|
77
|
+
routes.register_routes()
|
|
78
|
+
mcp_registry.register_provider(mcp.AccountsProvider())
|