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,394 @@
|
|
|
1
|
+
"""accounts.json: the profiles a person runs (Settings › Profiles), each on one app and (optionally) one phone."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import errno
|
|
5
|
+
import fcntl
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import tempfile
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .. import secret_filter
|
|
16
|
+
|
|
17
|
+
STORE = "accounts.json"
|
|
18
|
+
STORE_LOCK = "accounts.lock"
|
|
19
|
+
LOCK_TIMEOUT = 10.0
|
|
20
|
+
MAX_ACCOUNTS = 200
|
|
21
|
+
MAX_LINKS = 10
|
|
22
|
+
MAX_VARIABLES = 20
|
|
23
|
+
NOTES_CHARS = 2000
|
|
24
|
+
SIGNATURE_CHARS = 300
|
|
25
|
+
VALUE_CHARS = 1000
|
|
26
|
+
LINK_LABEL_CHARS = 40
|
|
27
|
+
URL_CHARS = 500
|
|
28
|
+
|
|
29
|
+
ID = re.compile(r"[a-f0-9]{12}")
|
|
30
|
+
# Instagram, TikTok, X, Threads and LinkedIn vanity names: letters, digits, dots, underscores and hyphens.
|
|
31
|
+
HANDLE = re.compile(r"[A-Za-z0-9._-]{1,64}")
|
|
32
|
+
BUNDLE = re.compile(r"[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+")
|
|
33
|
+
VARIABLE = re.compile(r"[a-z][a-z0-9_]{0,31}")
|
|
34
|
+
# Names a task's placeholders already mean (context.fill): a variable can't take one.
|
|
35
|
+
RESERVED = frozenset({"handle", "account", "app", "signature", "link", "links", "notes"})
|
|
36
|
+
FIELDS = frozenset({"handle", "app", "device", "notes", "signature", "links", "variables", "region", "language",
|
|
37
|
+
"timezone"})
|
|
38
|
+
# What the phone should be set to for this profile (fleet_health.py checks it before a task): optional.
|
|
39
|
+
REGION = re.compile(r"[A-Za-z]{2}")
|
|
40
|
+
LANGUAGE = re.compile(r"[A-Za-z]{2,3}")
|
|
41
|
+
TIMEZONE = re.compile(r"[A-Za-z][A-Za-z0-9_+-]*(?:/[A-Za-z0-9_+-]+){0,2}")
|
|
42
|
+
|
|
43
|
+
NOT_FOUND = "No profile has that name. Add it in Settings › Profiles."
|
|
44
|
+
SECRET = "Profiles don't keep passwords, card numbers or API keys. Leave that value out."
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AccountNotFound(LookupError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AmbiguousAccount(LookupError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def handle_of(value):
|
|
56
|
+
"""``value`` as a stored handle: no ``@``, no surrounding space. ValueError when it isn't one."""
|
|
57
|
+
if not isinstance(value, str):
|
|
58
|
+
raise ValueError("handle is the profile's name on the app, such as @acme")
|
|
59
|
+
handle = value.strip().lstrip("@").strip()
|
|
60
|
+
if not HANDLE.fullmatch(handle):
|
|
61
|
+
raise ValueError("A handle is 1–64 letters, digits, dots, underscores or hyphens, such as @acme")
|
|
62
|
+
return handle
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def display(account):
|
|
66
|
+
""""@acme on Instagram"."""
|
|
67
|
+
return f"@{account['handle']} on {account['app']['name']}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _text(value, name, limit):
|
|
71
|
+
"""``name`` as a person reads it ("A signature"): the profile form shows the message as it is (P05-18)."""
|
|
72
|
+
if value is None:
|
|
73
|
+
return ""
|
|
74
|
+
if not isinstance(value, str):
|
|
75
|
+
raise ValueError(f"{name} is text of at most {limit:,} characters")
|
|
76
|
+
if len(value) > limit:
|
|
77
|
+
raise ValueError(f"{name} can be at most {limit:,} characters. Shorten it and try again.")
|
|
78
|
+
value = value.strip()
|
|
79
|
+
if value and looks_secret(value):
|
|
80
|
+
raise ValueError(SECRET)
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def looks_secret(text):
|
|
85
|
+
"""secret_filter's password words, API keys, card numbers, SSNs, IBANs and an email with a password beside it."""
|
|
86
|
+
f = secret_filter
|
|
87
|
+
return (f.MASK in text or bool(f.KEYWORD.search(text)) or any(p.search(text) for p in (f.API_KEY, f.SSN, f.IBAN))
|
|
88
|
+
or bool(f._cards(text)) or bool(f.EMAIL_PASSWORD.search(text)))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _app(value):
|
|
92
|
+
if not isinstance(value, dict) or set(value) - {"bundleId", "name"}:
|
|
93
|
+
raise ValueError("app is {bundleId, name}: the app this profile is on")
|
|
94
|
+
bundle, name = value.get("bundleId"), value.get("name")
|
|
95
|
+
if not isinstance(bundle, str) or not BUNDLE.fullmatch(bundle) or len(bundle) > 155:
|
|
96
|
+
raise ValueError("app.bundleId is the app's bundle id, such as com.burbn.instagram")
|
|
97
|
+
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
|
|
98
|
+
raise ValueError("app.name is the app's name, 1–80 characters")
|
|
99
|
+
return {"bundleId": bundle, "name": name.strip()}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _expectation(value, name, pattern, hint, fold):
|
|
103
|
+
"""An expected region, language or time zone: None to clear, else the value in its usual case."""
|
|
104
|
+
if value is None or value == "":
|
|
105
|
+
return None
|
|
106
|
+
if not isinstance(value, str) or not pattern.fullmatch(value.strip()) or len(value.strip()) > 64:
|
|
107
|
+
raise ValueError(f"{value!r:.40} isn't a {name} Mobster can use. Use {hint}, or leave it empty.")
|
|
108
|
+
return fold(value.strip())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _timezone(value):
|
|
112
|
+
zone = _expectation(value, "timezone", TIMEZONE, "a time zone name such as America/Chicago", lambda v: v)
|
|
113
|
+
if zone is not None:
|
|
114
|
+
import zoneinfo
|
|
115
|
+
known = zoneinfo.available_timezones()
|
|
116
|
+
if known and zone not in known:
|
|
117
|
+
raise ValueError(f"{zone[:40]!r} isn't a time zone Mobster knows. Use a name such as America/Chicago, "
|
|
118
|
+
"or leave it empty.")
|
|
119
|
+
return zone
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _links(value):
|
|
123
|
+
if value is None:
|
|
124
|
+
return []
|
|
125
|
+
if not isinstance(value, list) or len(value) > MAX_LINKS:
|
|
126
|
+
raise ValueError(f"links is a list of at most {MAX_LINKS} {{label, url}}")
|
|
127
|
+
out, labels = [], set()
|
|
128
|
+
for link in value:
|
|
129
|
+
if not isinstance(link, dict) or set(link) - {"label", "url"}:
|
|
130
|
+
raise ValueError("Each link is {label, url}")
|
|
131
|
+
label = _text(link.get("label"), "A link's label", LINK_LABEL_CHARS)
|
|
132
|
+
url = _text(link.get("url"), "A link's url", URL_CHARS)
|
|
133
|
+
if not re.fullmatch(r"https?://[^\s]{1,490}", url):
|
|
134
|
+
raise ValueError("A link's url starts with https:// or http://")
|
|
135
|
+
if not label:
|
|
136
|
+
raise ValueError("Give each link a label, such as Shop")
|
|
137
|
+
if label.casefold() in labels:
|
|
138
|
+
raise ValueError(f"Two links are called {label}")
|
|
139
|
+
labels.add(label.casefold())
|
|
140
|
+
out.append({"label": label, "url": url})
|
|
141
|
+
return out
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _variables(value):
|
|
145
|
+
if value is None:
|
|
146
|
+
return {}
|
|
147
|
+
if not isinstance(value, dict) or len(value) > MAX_VARIABLES:
|
|
148
|
+
raise ValueError(f"variables is an object of at most {MAX_VARIABLES} names and values")
|
|
149
|
+
out = {}
|
|
150
|
+
for name, text in value.items():
|
|
151
|
+
if not isinstance(name, str) or not VARIABLE.fullmatch(name):
|
|
152
|
+
raise ValueError("A variable's name is lowercase letters, digits and underscores, such as caption")
|
|
153
|
+
if name in RESERVED:
|
|
154
|
+
raise ValueError(f"{name} is already a profile value. Choose another name.")
|
|
155
|
+
out[name] = _text(text, f"The variable {name}", VALUE_CHARS)
|
|
156
|
+
return out
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def clean(body, partial=False):
|
|
160
|
+
"""The validated fields of ``body`` (every field for a new profile; only those given for a change)."""
|
|
161
|
+
if not isinstance(body, dict) or set(body) - FIELDS:
|
|
162
|
+
raise ValueError("A profile has handle, app, device, notes, signature, links, variables, "
|
|
163
|
+
"region, language and timezone")
|
|
164
|
+
if not partial and ("handle" not in body or "app" not in body):
|
|
165
|
+
raise ValueError("Name the profile's handle and its app")
|
|
166
|
+
out = {}
|
|
167
|
+
if "handle" in body:
|
|
168
|
+
out["handle"] = handle_of(body["handle"])
|
|
169
|
+
if "app" in body:
|
|
170
|
+
out["app"] = _app(body["app"])
|
|
171
|
+
if "device" in body:
|
|
172
|
+
device = body["device"]
|
|
173
|
+
if device is not None and (not isinstance(device, str) or not 1 <= len(device.strip()) <= 128):
|
|
174
|
+
raise ValueError("device is the phone's id or name, or null for the default phone")
|
|
175
|
+
out["device"] = device.strip() if isinstance(device, str) else None
|
|
176
|
+
if "notes" in body or not partial:
|
|
177
|
+
out["notes"] = _text(body.get("notes"), "Notes", NOTES_CHARS)
|
|
178
|
+
if "signature" in body or not partial:
|
|
179
|
+
out["signature"] = _text(body.get("signature"), "A signature", SIGNATURE_CHARS)
|
|
180
|
+
if "links" in body or not partial:
|
|
181
|
+
out["links"] = _links(body.get("links"))
|
|
182
|
+
if "variables" in body or not partial:
|
|
183
|
+
out["variables"] = _variables(body.get("variables"))
|
|
184
|
+
if "region" in body:
|
|
185
|
+
out["region"] = _expectation(body["region"], "region", REGION, "a two-letter country code such as US",
|
|
186
|
+
str.upper)
|
|
187
|
+
if "language" in body:
|
|
188
|
+
out["language"] = _expectation(body["language"], "language", LANGUAGE, "a language code such as en",
|
|
189
|
+
str.lower)
|
|
190
|
+
if "timezone" in body:
|
|
191
|
+
out["timezone"] = _timezone(body["timezone"])
|
|
192
|
+
return out
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _valid(entry):
|
|
196
|
+
try:
|
|
197
|
+
return (isinstance(entry, dict) and ID.fullmatch(entry.get("id") or "") is not None
|
|
198
|
+
and HANDLE.fullmatch(entry.get("handle") or "") is not None and isinstance(entry.get("app"), dict)
|
|
199
|
+
and isinstance(entry["app"].get("bundleId"), str) and isinstance(entry["app"].get("name"), str))
|
|
200
|
+
except TypeError:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class AccountStore:
|
|
205
|
+
"""accounts.json in ``data_dir``."""
|
|
206
|
+
|
|
207
|
+
def __init__(self, data_dir):
|
|
208
|
+
self.data_dir = Path(data_dir)
|
|
209
|
+
self.path = self.data_dir / STORE
|
|
210
|
+
self.lock_path = self.data_dir / STORE_LOCK
|
|
211
|
+
|
|
212
|
+
def read(self):
|
|
213
|
+
try:
|
|
214
|
+
data = json.loads(self.path.read_text())
|
|
215
|
+
except (OSError, ValueError):
|
|
216
|
+
return {"version": 1, "accounts": []}
|
|
217
|
+
items = data.get("accounts") if isinstance(data, dict) else None
|
|
218
|
+
accounts = []
|
|
219
|
+
for entry in items if isinstance(items, list) else ():
|
|
220
|
+
if not _valid(entry):
|
|
221
|
+
continue
|
|
222
|
+
links = entry.get("links") if isinstance(entry.get("links"), list) else []
|
|
223
|
+
accounts.append({"id": entry["id"], "handle": entry["handle"],
|
|
224
|
+
"app": {"bundleId": entry["app"]["bundleId"], "name": entry["app"]["name"]},
|
|
225
|
+
"device": entry.get("device") if isinstance(entry.get("device"), str) else None,
|
|
226
|
+
"deviceName": (entry.get("deviceName") if isinstance(entry.get("deviceName"), str)
|
|
227
|
+
else None),
|
|
228
|
+
"notes": entry.get("notes") if isinstance(entry.get("notes"), str) else "",
|
|
229
|
+
"signature": entry.get("signature") if isinstance(entry.get("signature"), str) else "",
|
|
230
|
+
"links": [link for link in links if isinstance(link, dict)
|
|
231
|
+
and isinstance(link.get("label"), str) and isinstance(link.get("url"), str)],
|
|
232
|
+
"variables": {k: v for k, v in (entry.get("variables") or {}).items()
|
|
233
|
+
if isinstance(k, str) and isinstance(v, str)}
|
|
234
|
+
if isinstance(entry.get("variables"), dict) else {},
|
|
235
|
+
**{key: entry[key] for key in ("region", "language", "timezone")
|
|
236
|
+
if isinstance(entry.get(key), str) and entry[key]},
|
|
237
|
+
"createdAt": entry.get("createdAt") if isinstance(entry.get("createdAt"), int) else 0,
|
|
238
|
+
"updatedAt": entry.get("updatedAt") if isinstance(entry.get("updatedAt"), int) else 0})
|
|
239
|
+
return {"version": 1, "accounts": accounts}
|
|
240
|
+
|
|
241
|
+
def all(self):
|
|
242
|
+
return self.read()["accounts"]
|
|
243
|
+
|
|
244
|
+
def get(self, account_id):
|
|
245
|
+
"""The stored account with this id, else None."""
|
|
246
|
+
return next((a for a in self.all() if a["id"] == account_id), None)
|
|
247
|
+
|
|
248
|
+
@contextlib.contextmanager
|
|
249
|
+
def edit(self):
|
|
250
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
fd = os.open(self.lock_path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
|
|
252
|
+
try:
|
|
253
|
+
end = time.monotonic() + LOCK_TIMEOUT
|
|
254
|
+
while True:
|
|
255
|
+
try:
|
|
256
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
257
|
+
break
|
|
258
|
+
except OSError as exc:
|
|
259
|
+
if exc.errno not in (errno.EWOULDBLOCK, errno.EAGAIN) or time.monotonic() >= end:
|
|
260
|
+
raise OSError("Another Mobster process is holding the profile list") from None
|
|
261
|
+
time.sleep(.05)
|
|
262
|
+
data = self.read()
|
|
263
|
+
before = json.dumps(data, sort_keys=True)
|
|
264
|
+
yield data
|
|
265
|
+
if json.dumps(data, sort_keys=True) != before:
|
|
266
|
+
self._write(data)
|
|
267
|
+
finally:
|
|
268
|
+
os.close(fd)
|
|
269
|
+
|
|
270
|
+
def _write(self, data):
|
|
271
|
+
fd, temporary = tempfile.mkstemp(prefix=".accounts.", suffix=".json", dir=self.data_dir)
|
|
272
|
+
try:
|
|
273
|
+
with os.fdopen(fd, "w", encoding="utf-8") as stream:
|
|
274
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
275
|
+
stream.write(json.dumps(data, indent=1) + "\n")
|
|
276
|
+
os.replace(temporary, self.path)
|
|
277
|
+
except BaseException:
|
|
278
|
+
with contextlib.suppress(OSError):
|
|
279
|
+
os.unlink(temporary)
|
|
280
|
+
raise
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def _taken(accounts, handle, bundle, other=None):
|
|
284
|
+
return any(a["handle"].casefold() == handle.casefold() and a["app"]["bundleId"] == bundle
|
|
285
|
+
and a["id"] != other for a in accounts)
|
|
286
|
+
|
|
287
|
+
def add(self, body, device=None):
|
|
288
|
+
"""A new account from ``body`` (store.clean)."""
|
|
289
|
+
fields = clean(body)
|
|
290
|
+
now = round(time.time() * 1000)
|
|
291
|
+
with self.edit() as data:
|
|
292
|
+
if len(data["accounts"]) >= MAX_ACCOUNTS:
|
|
293
|
+
raise ValueError(f"Mobster keeps up to {MAX_ACCOUNTS} profiles. Remove one you no longer run.")
|
|
294
|
+
if self._taken(data["accounts"], fields["handle"], fields["app"]["bundleId"]):
|
|
295
|
+
raise ValueError(f"@{fields['handle']} on {fields['app']['name']} is already in your profiles")
|
|
296
|
+
account = {"id": uuid.uuid4().hex[:12], **fields, "device": device[0] if device else None,
|
|
297
|
+
"deviceName": device[1] if device else None, "createdAt": now, "updatedAt": now}
|
|
298
|
+
data["accounts"].append(account)
|
|
299
|
+
return dict(account)
|
|
300
|
+
|
|
301
|
+
def update(self, account_id, body, device=None):
|
|
302
|
+
"""The account with ``body``'s fields changed."""
|
|
303
|
+
fields = clean(body, partial=True)
|
|
304
|
+
with self.edit() as data:
|
|
305
|
+
account = next((a for a in data["accounts"] if a["id"] == account_id), None)
|
|
306
|
+
if account is None:
|
|
307
|
+
raise AccountNotFound(NOT_FOUND)
|
|
308
|
+
merged = {**account, **fields}
|
|
309
|
+
if self._taken(data["accounts"], merged["handle"], merged["app"]["bundleId"], other=account_id):
|
|
310
|
+
raise ValueError(f"@{merged['handle']} on {merged['app']['name']} is already in your profiles")
|
|
311
|
+
if "device" in fields:
|
|
312
|
+
merged["device"], merged["deviceName"] = (device if device else (None, None))
|
|
313
|
+
merged["updatedAt"] = round(time.time() * 1000)
|
|
314
|
+
account.clear()
|
|
315
|
+
account.update(merged)
|
|
316
|
+
return dict(account)
|
|
317
|
+
|
|
318
|
+
def remove(self, account_id):
|
|
319
|
+
with self.edit() as data:
|
|
320
|
+
before = len(data["accounts"])
|
|
321
|
+
data["accounts"] = [a for a in data["accounts"] if a["id"] != account_id]
|
|
322
|
+
if len(data["accounts"]) == before:
|
|
323
|
+
raise AccountNotFound(NOT_FOUND)
|
|
324
|
+
|
|
325
|
+
def forget_device(self, device_id):
|
|
326
|
+
"""Phones Mobster forgets leave their accounts on the default phone."""
|
|
327
|
+
with self.edit() as data:
|
|
328
|
+
for account in data["accounts"]:
|
|
329
|
+
if account.get("device") == device_id:
|
|
330
|
+
account["device"], account["deviceName"] = None, None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
ON = re.compile(r"^\s*@?(?P<handle>[A-Za-z0-9._-]{1,64})\s+on\s+(?P<app>.{1,80}?)\s*$", re.I)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def resolve(accounts, value, bundle=None):
|
|
337
|
+
"""The account ``value`` names: its id, "@acme", "acme", or "@acme on Instagram"."""
|
|
338
|
+
if not isinstance(value, str) or not 1 <= len(value.strip()) <= 160:
|
|
339
|
+
raise ValueError("account is a profile's id or its handle, such as @acme")
|
|
340
|
+
text = value.strip()
|
|
341
|
+
if ID.fullmatch(text):
|
|
342
|
+
found = next((a for a in accounts if a["id"] == text), None)
|
|
343
|
+
if found is not None:
|
|
344
|
+
return found
|
|
345
|
+
on = ON.match(text)
|
|
346
|
+
app_name = None
|
|
347
|
+
if on:
|
|
348
|
+
text, app_name = on.group("handle"), on.group("app").casefold()
|
|
349
|
+
handle = text.lstrip("@").casefold()
|
|
350
|
+
matches = [a for a in accounts if a["handle"].casefold() == handle]
|
|
351
|
+
if app_name is not None:
|
|
352
|
+
matches = [a for a in matches if a["app"]["name"].casefold() == app_name]
|
|
353
|
+
if len(matches) > 1 and bundle:
|
|
354
|
+
matches = [a for a in matches if a["app"]["bundleId"] == bundle] or matches
|
|
355
|
+
if not matches:
|
|
356
|
+
raise AccountNotFound(NOT_FOUND)
|
|
357
|
+
if len(matches) > 1:
|
|
358
|
+
apps = " and ".join(sorted(a["app"]["name"] for a in matches))
|
|
359
|
+
raise AmbiguousAccount(f"@{matches[0]['handle']} is on {apps}. Say which one, such as "
|
|
360
|
+
f"\"@{matches[0]['handle']} on {matches[0]['app']['name']}\".")
|
|
361
|
+
return matches[0]
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
MENTION = re.compile(r"(?<![\w.@/])@([A-Za-z0-9._-]{1,64})")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def mentioned(accounts, text, bundle=None):
|
|
368
|
+
"""The one account a message names with an @handle ("Reply to my comments as @acme"), or None when it names
|
|
369
|
+
none of them, or more than one (then the person picks it in the composer)."""
|
|
370
|
+
found = {}
|
|
371
|
+
for match in MENTION.finditer(text or ""):
|
|
372
|
+
handle = match.group(1).rstrip(".").casefold()
|
|
373
|
+
for account in accounts:
|
|
374
|
+
if account["handle"].casefold() == handle and (bundle is None or account["app"]["bundleId"] == bundle):
|
|
375
|
+
found[account["id"]] = account
|
|
376
|
+
return next(iter(found.values())) if len(found) == 1 else None
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
_stores = {}
|
|
380
|
+
_stores_lock = threading.Lock()
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def store_for(runtime):
|
|
384
|
+
"""The AccountStore beside ``runtime``'s journal (as its replays, loops and frames), else in its data folder
|
|
385
|
+
(one store per folder)."""
|
|
386
|
+
from ..paths import user_data_dir
|
|
387
|
+
config = getattr(runtime, "config", None)
|
|
388
|
+
state_db = getattr(config, "state_db", None)
|
|
389
|
+
folder = Path(state_db).parent if state_db else Path(getattr(config, "data_dir", None) or user_data_dir())
|
|
390
|
+
with _stores_lock:
|
|
391
|
+
store = _stores.get(folder)
|
|
392
|
+
if store is None:
|
|
393
|
+
store = _stores[folder] = AccountStore(folder)
|
|
394
|
+
return store
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Seams between the agent loop (frontier), the phone guard (lockscreen) and skills."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Callable, Optional, Protocol
|
|
4
|
+
|
|
5
|
+
SKILL_IMAGES = 2
|
|
6
|
+
MASK = "••••••"
|
|
7
|
+
# A wait for the user this long (approvals, hand-offs) or longer is followed by check(cause="resume").
|
|
8
|
+
RESUME_AFTER_SECONDS = 20
|
|
9
|
+
# The history line the model sees after the guard unlocked the phone (never a frame of the keypad).
|
|
10
|
+
UNLOCKED_LINE = "Mobster unlocked the iPhone"
|
|
11
|
+
|
|
12
|
+
# Why the phone guard stopped a run; none carries a passcode, a code or a digit count.
|
|
13
|
+
STOP_CODES = ("phone_locked", "unlock_declined", "passcode_failed", "face_id", "app_locked", "apple_confirmation",
|
|
14
|
+
"unplugged", "keychain_locked", "blocked_app", "system_dialog")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class GuardVerdict:
|
|
19
|
+
state: str # "ready" | "unlocked" | "stop"
|
|
20
|
+
code: str = "" # when stop: one of STOP_CODES
|
|
21
|
+
message: str = "" # one plain sentence for the user; never a passcode, a code, or a digit count
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PhoneGuard(Protocol):
|
|
25
|
+
def check(self, driver, *, cause: str) -> GuardVerdict: ...
|
|
26
|
+
# cause: "preflight" | "launch_failed" | "lock_suspected" | "resume" | "sheet_suspected"
|
|
27
|
+
def attached(self) -> Optional[bool]: ... # False = USB detached (fail fast); None = unknown
|
|
28
|
+
def allows_app(self, bundle_id: str) -> bool: ... # C7 stretch; implementations without it return True
|
|
29
|
+
def finish(self, driver) -> None: ... # run ended: relock if this guard unlocked and relock is on
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Prepared:
|
|
34
|
+
title: Optional[str] # approval question, or None when no approval is needed; never contains a secret
|
|
35
|
+
state: object = None # skill-private (e.g. the code, held in memory only)
|
|
36
|
+
remember: Optional[dict] = None # the approval's "Don't ask again" (run.Run.request_approval), or None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class SkillResult:
|
|
41
|
+
feedback: str # what the model is told next turn (no secrets)
|
|
42
|
+
changed: bool = False
|
|
43
|
+
secrets: tuple = () # strings frontier masks ("••••••") in every later row, note, history line and answer
|
|
44
|
+
secret_rects: tuple = () # (x, y, w, h) screen fractions frontier blurs in screenshots until that field changes
|
|
45
|
+
stop: Optional[GuardVerdict] = None
|
|
46
|
+
# Seconds the skill waited for the person, which never count against the task's time.
|
|
47
|
+
waited: float = 0.0
|
|
48
|
+
status: Optional[str] = None
|
|
49
|
+
# Shown to the model once, on its next turn, before the screen; only a run that sends screenshots sends them.
|
|
50
|
+
images: tuple = ()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class SkillContext:
|
|
55
|
+
driver: object
|
|
56
|
+
request: str
|
|
57
|
+
app_bundle: Optional[str]
|
|
58
|
+
emit: Callable[[dict], None]
|
|
59
|
+
guard: Optional[PhoneGuard] = None
|
|
60
|
+
approve: Optional[Callable[[dict], str]] = None # the frontier's approver, or None; replayed commit steps only
|
|
61
|
+
clarify: Optional[Callable[[dict], str]] = None # run.request_approval limited to kind "clarify"; others raise
|
|
62
|
+
run: Optional[object] = None # the run's harness_api.RunContext, or None (benchmarks, tests)
|
|
63
|
+
# A skill that needs the user's OK returns Prepared from prepare(); with no approver it never runs.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Skill(Protocol):
|
|
67
|
+
op: str # e.g. "USE_CODE", "READ_NOTIFICATIONS"; must not clash with frontier.OPERATIONS
|
|
68
|
+
prompt: str # one "- OP ...: ..." line appended to frontier.SYSTEM's action list
|
|
69
|
+
needs_target: bool # True: the first action targets an element id (USE_CODE: the code field)
|
|
70
|
+
# interactive_only offers the tool only in a run a person watches.
|
|
71
|
+
def available(self, ctx: SkillContext) -> bool: ...
|
|
72
|
+
def prepare(self, ctx: SkillContext, act: dict, snapshot) -> Prepared: ... # may navigate; no writes
|
|
73
|
+
def perform(self, ctx: SkillContext, prepared: Prepared, act: dict, snapshot) -> SkillResult: ...
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
READY = GuardVerdict("ready")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def mask(text, secrets):
|
|
80
|
+
"""``text`` with every secret replaced by MASK (longest first, so a code inside a longer one goes too)."""
|
|
81
|
+
if not text or not secrets or not isinstance(text, str):
|
|
82
|
+
return text
|
|
83
|
+
for secret in sorted(secrets, key=len, reverse=True):
|
|
84
|
+
if secret:
|
|
85
|
+
text = text.replace(secret, MASK)
|
|
86
|
+
return text
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def safe_check(guard, driver, cause):
|
|
90
|
+
"""``guard.check(driver, cause=cause)``, or None without a guard; a guard that raises is a stop, never a go."""
|
|
91
|
+
if guard is None:
|
|
92
|
+
return None
|
|
93
|
+
try:
|
|
94
|
+
verdict = guard.check(driver, cause=cause)
|
|
95
|
+
if isinstance(verdict, GuardVerdict) and verdict.state in ("ready", "unlocked", "stop"):
|
|
96
|
+
return verdict
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
return GuardVerdict("stop", "phone_locked", "Mobster couldn't check whether your iPhone is unlocked, so it "
|
|
100
|
+
"stopped.")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def safe_attached(guard):
|
|
104
|
+
"""``guard.attached()``: False only when the guard knows the phone was unplugged."""
|
|
105
|
+
if guard is None:
|
|
106
|
+
return None
|
|
107
|
+
try:
|
|
108
|
+
return guard.attached()
|
|
109
|
+
except Exception:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def safe_allows(guard, bundle_id):
|
|
114
|
+
"""``guard.allows_app(bundle_id)``; True without a guard or for a guard without the C7 list."""
|
|
115
|
+
if guard is None or not bundle_id:
|
|
116
|
+
return True
|
|
117
|
+
allows = getattr(guard, "allows_app", None)
|
|
118
|
+
if not callable(allows):
|
|
119
|
+
return True
|
|
120
|
+
try:
|
|
121
|
+
return allows(bundle_id) is True
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""The coding agents Mobster can run a task on, one instance each per process (so their status is cached)."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
from .claude_code import ClaudeCode
|
|
6
|
+
from .codex import Codex
|
|
7
|
+
from .gemini import Gemini
|
|
8
|
+
|
|
9
|
+
CLASSES = {"codex": Codex, "claude_code": ClaudeCode, "gemini": Gemini}
|
|
10
|
+
_instances = {}
|
|
11
|
+
_lock = threading.Lock()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ids():
|
|
15
|
+
return tuple(CLASSES)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get(agent_id):
|
|
19
|
+
"""The agent for ``agent_id`` (codex, claude_code ...). KeyError for another."""
|
|
20
|
+
cls = CLASSES[agent_id]
|
|
21
|
+
with _lock:
|
|
22
|
+
if agent_id not in _instances:
|
|
23
|
+
_instances[agent_id] = cls()
|
|
24
|
+
return _instances[agent_id]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def statuses(max_age=60.0):
|
|
28
|
+
"""{agent id: status} for every agent (cli.CliAgent.status), read in parallel."""
|
|
29
|
+
out, threads = {}, []
|
|
30
|
+
|
|
31
|
+
def read(agent_id):
|
|
32
|
+
out[agent_id] = get(agent_id).status(max_age=max_age)
|
|
33
|
+
for agent_id in CLASSES:
|
|
34
|
+
thread = threading.Thread(target=read, args=(agent_id,), daemon=True)
|
|
35
|
+
thread.start()
|
|
36
|
+
threads.append(thread)
|
|
37
|
+
for thread in threads:
|
|
38
|
+
thread.join(15)
|
|
39
|
+
return {agent_id: out.get(agent_id) or {"id": agent_id, "ready": False, "installed": False}
|
|
40
|
+
for agent_id in CLASSES}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ready(env=None):
|
|
44
|
+
"""The id of the coding agent a task that needs a model runs on when Mobster's own agent has no key."""
|
|
45
|
+
import os
|
|
46
|
+
names = {name for name, state in snapshot(max_age=60, wait=True).items() if state.get("ready")}
|
|
47
|
+
preferred = str((os.environ if env is None else env).get("MOBSTER_PREFERRED_AGENT", "")).strip()
|
|
48
|
+
return next((name for name in (preferred, *CLASSES) if name in names and name in CLASSES), None)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def reset_for_tests():
|
|
52
|
+
with _lock:
|
|
53
|
+
_instances.clear()
|
|
54
|
+
with _snapshot_lock:
|
|
55
|
+
_snapshot.update(value=None, at=0.0, refreshing=False, generation=_snapshot["generation"] + 1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
_snapshot = {"value": None, "at": 0.0, "refreshing": False, "generation": 0}
|
|
59
|
+
_snapshot_lock = threading.Lock()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def snapshot(max_age=60.0, wait=False):
|
|
63
|
+
"""The agents' statuses from the last check, for GET /api/status (polled often)."""
|
|
64
|
+
import time
|
|
65
|
+
with _snapshot_lock:
|
|
66
|
+
value, at, busy = _snapshot["value"], _snapshot["at"], _snapshot["refreshing"]
|
|
67
|
+
generation = _snapshot["generation"]
|
|
68
|
+
stale = value is None or time.monotonic() - at >= max_age
|
|
69
|
+
if stale and not busy and not wait:
|
|
70
|
+
_snapshot["refreshing"] = True
|
|
71
|
+
if stale and wait:
|
|
72
|
+
fresh = statuses(max_age=max_age)
|
|
73
|
+
with _snapshot_lock:
|
|
74
|
+
_snapshot.update(value=fresh, at=time.monotonic())
|
|
75
|
+
return public(fresh)
|
|
76
|
+
if stale and not busy:
|
|
77
|
+
def refresh():
|
|
78
|
+
try:
|
|
79
|
+
fresh = statuses(max_age=max_age)
|
|
80
|
+
with _snapshot_lock:
|
|
81
|
+
# A check begun before a reset (the agents' paths changed) is dropped, not kept.
|
|
82
|
+
if _snapshot["generation"] == generation:
|
|
83
|
+
_snapshot.update(value=fresh, at=time.monotonic())
|
|
84
|
+
finally:
|
|
85
|
+
with _snapshot_lock:
|
|
86
|
+
if _snapshot["generation"] == generation:
|
|
87
|
+
_snapshot["refreshing"] = False
|
|
88
|
+
threading.Thread(target=refresh, daemon=True, name="mobster-agents-status").start()
|
|
89
|
+
return public(value or {})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def public(states):
|
|
93
|
+
"""What the app shows of each agent's status: never its path's home folder beyond the file name."""
|
|
94
|
+
keep = ("id", "name", "plan", "vendor", "installed", "signedIn", "ready", "version", "account", "billing", "hint",
|
|
95
|
+
"installUrl", "installCommand", "signInCommand")
|
|
96
|
+
return {agent_id: {key: state.get(key) for key in keep} for agent_id, state in (states or {}).items()}
|