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,316 @@
|
|
|
1
|
+
"""The permission gate: one per run; applies the run's mode to an action and says allow or ask."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
|
|
8
|
+
from ..state import icon_glyph, pixel_read
|
|
9
|
+
from ..task_policy import approval_title
|
|
10
|
+
from . import consequence, project_policy
|
|
11
|
+
from . import policy as P
|
|
12
|
+
from .rules import RULE_OP, Action, changes, code_entry, norm, outside_act, rememberable, rule_words, screen_context
|
|
13
|
+
|
|
14
|
+
TITLES = {"code": "Enter this code?", "setting": "Change this setting?", "save": "Save this?",
|
|
15
|
+
"submit": "Submit this?", "change": "Make this change?", "react": "Add this reaction?",
|
|
16
|
+
"follow": "Follow this account?", "unfollow": "Unfollow this account?", "post": "Repost this?",
|
|
17
|
+
"block": "Block this?", "report": "Report this?"}
|
|
18
|
+
# The card's line for an act ``rules.outside_act`` found (at most 12 words).
|
|
19
|
+
OUTSIDE_REASONS = {"react": "Others see this reaction", "follow": "Others see this follow",
|
|
20
|
+
"unfollow": "Others see this unfollow", "post": "Others see this repost",
|
|
21
|
+
"block": "Changes who can reach you", "report": "Sends a report about someone"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Decision:
|
|
26
|
+
decision: str # allow | ask
|
|
27
|
+
kind: str # navigate | input | change | commit | floor
|
|
28
|
+
act: str | None # the card's verb
|
|
29
|
+
reason: str # for the card, at most 12 words
|
|
30
|
+
ms: float # time this decision added
|
|
31
|
+
title: str | None = None
|
|
32
|
+
# The approval card may offer "Don't ask again" (an allow rule for ``rule`` in this app).
|
|
33
|
+
rememberable: bool = False
|
|
34
|
+
rule: str | None = None
|
|
35
|
+
# The ask is the project policy's (project_policy.py): the mode and the guards would have let it go ahead.
|
|
36
|
+
by_policy: bool = False
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def asks(self):
|
|
40
|
+
return self.decision == "ask"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def decision_for(kind, mode, existing=False):
|
|
44
|
+
"""allow | ask for an action of ``kind`` under ``mode``. ``existing``: the agent's own rules ask."""
|
|
45
|
+
if kind == "floor":
|
|
46
|
+
return "ask"
|
|
47
|
+
if mode == P.DONT_ASK:
|
|
48
|
+
return "allow"
|
|
49
|
+
if existing or kind == "commit":
|
|
50
|
+
return "ask"
|
|
51
|
+
if kind == "change" and mode == P.ASK_ALL:
|
|
52
|
+
return "ask"
|
|
53
|
+
return "allow"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_COMPOSE = re.compile(r"\b(message|imessage|text message|reply|comment|write|chat|say something|add a caption)\b")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def composer_text(elements):
|
|
60
|
+
"""The text waiting in the screen's composer, "" for an empty one, None when the screen has no composer."""
|
|
61
|
+
found = None
|
|
62
|
+
for element in elements or ():
|
|
63
|
+
label, placeholder = getattr(element, "label", "") or "", getattr(element, "placeholder", "") or ""
|
|
64
|
+
if (getattr(element, "role", None) not in ("TextView", "TextField")
|
|
65
|
+
or not _COMPOSE.search(norm(label or placeholder))):
|
|
66
|
+
continue
|
|
67
|
+
value = getattr(element, "value", "") or ""
|
|
68
|
+
if placeholder and value == placeholder or value == label:
|
|
69
|
+
value = ""
|
|
70
|
+
if value.strip():
|
|
71
|
+
return value
|
|
72
|
+
found = ""
|
|
73
|
+
return found
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def action_from(op, element, snapshot, *, text=None, typed=None, app_name="", goal="", title=None, origins=None):
|
|
77
|
+
"""The gate's Action for ``op`` on ``element`` (a state.Element, or None) in ``snapshot``."""
|
|
78
|
+
elements = getattr(snapshot, "elements", ()) or ()
|
|
79
|
+
if title is None:
|
|
80
|
+
try:
|
|
81
|
+
from ..contract_screen import screen_title
|
|
82
|
+
title = screen_title(snapshot) or ""
|
|
83
|
+
except Exception:
|
|
84
|
+
title = ""
|
|
85
|
+
return Action(op=op, label=getattr(element, "label", "") or "", role=getattr(element, "role", None),
|
|
86
|
+
value=getattr(element, "value", None) or None,
|
|
87
|
+
app=getattr(snapshot, "bundle_id", "") or "", app_name=app_name or "", text=text,
|
|
88
|
+
typed=typed if typed is not None else composer_text(elements), screen=title or "",
|
|
89
|
+
context=screen_context(elements), goal=goal or "",
|
|
90
|
+
hint=" ".join(str(getattr(element, key, "") or "") for key in ("placeholder", "identifier")),
|
|
91
|
+
origins=tuple(origins) if origins is not None else None,
|
|
92
|
+
rect=tuple(getattr(element, "rect", ()) or ()), around=tuple(elements),
|
|
93
|
+
pixels=pixel_read(snapshot), glyph=icon_glyph(getattr(element, "locator", "")))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PermissionGate:
|
|
97
|
+
def __init__(self, mode=P.DEFAULT_MODE, *, apps=None, emit=None, goal="", origin=None, exceptions=None,
|
|
98
|
+
policy=None, profile=None):
|
|
99
|
+
self.mode = mode if mode in P.MODES else P.DEFAULT_MODE
|
|
100
|
+
self.apps = apps if apps is not None else P.AppPolicy()
|
|
101
|
+
self.emit = emit
|
|
102
|
+
self.goal = goal
|
|
103
|
+
self.origin = origin
|
|
104
|
+
self.exceptions = exceptions
|
|
105
|
+
# The project's policy and the profile (accounts' ``{id, handle...}``) the task acts as: they only add asks.
|
|
106
|
+
self.policy = policy
|
|
107
|
+
self.profile = profile
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def narrowed(self):
|
|
111
|
+
"""Whether the project policy can ask here, so a run asks (or refuses, with nobody to answer) in Full access."""
|
|
112
|
+
return self.policy is not None and self.policy.narrows
|
|
113
|
+
|
|
114
|
+
def reading(self, action, existing_act=None):
|
|
115
|
+
"""(kind, act, reason) for ``action`` by today's rules."""
|
|
116
|
+
if code_entry(action):
|
|
117
|
+
return "floor", "code", "Types a one-time code"
|
|
118
|
+
if existing_act:
|
|
119
|
+
return "commit", existing_act, ""
|
|
120
|
+
found = consequence.read(action if action.goal or not self.goal else replace(action, goal=self.goal),
|
|
121
|
+
action.around)
|
|
122
|
+
if found is not None:
|
|
123
|
+
return "commit", found.act, found.reason
|
|
124
|
+
act = outside_act(action)
|
|
125
|
+
if act is not None:
|
|
126
|
+
return "commit", act, OUTSIDE_REASONS[act]
|
|
127
|
+
act = changes(action)
|
|
128
|
+
if act is not None:
|
|
129
|
+
return "change", act, ""
|
|
130
|
+
op = (action.op or "").upper()
|
|
131
|
+
return ("input" if op in ("TYPE", "SET_TEXT") else "navigate"), None, ""
|
|
132
|
+
|
|
133
|
+
def decide(self, action, existing_act=None, forced=False):
|
|
134
|
+
"""The answer for ``action``; ``forced`` asks whatever the mode says and offers or uses no allow rule. The
|
|
135
|
+
project policy, when there is one, can turn an allow into an ask and nothing else."""
|
|
136
|
+
decision = self._decide(action, existing_act, forced)
|
|
137
|
+
if self.policy is not None and decision.kind in ("commit", "change"):
|
|
138
|
+
decision = self._narrow(decision, action)
|
|
139
|
+
return self._record(decision)
|
|
140
|
+
|
|
141
|
+
def _narrow(self, decision, action):
|
|
142
|
+
"""``decision`` or, when the project policy asks where the mode and the guards would not, an ask. An ask the
|
|
143
|
+
mode already made stays as it is, without an offer of "Don't ask again" if the policy asks too."""
|
|
144
|
+
kinds = project_policy.kinds_of(decision.kind, decision.act)
|
|
145
|
+
why = self.policy.asks(kinds, action.app, self.profile)
|
|
146
|
+
if decision.asks:
|
|
147
|
+
return replace(decision, rememberable=False, rule=None) if why and decision.rememberable else decision
|
|
148
|
+
why = why or self.policy.take(kinds, action.app, self.profile)
|
|
149
|
+
if why is None:
|
|
150
|
+
return decision
|
|
151
|
+
act = decision.act
|
|
152
|
+
title = consequence.TITLES.get(act or "") or TITLES.get(act or "") or approval_title(act)
|
|
153
|
+
return Decision("ask", decision.kind, act, why, decision.ms, title, by_policy=True)
|
|
154
|
+
|
|
155
|
+
def _decide(self, action, existing_act, forced):
|
|
156
|
+
started = time.perf_counter()
|
|
157
|
+
kind, act, reason = self.reading(action, existing_act)
|
|
158
|
+
verdict = decision_for(kind, self.mode, existing=bool(existing_act))
|
|
159
|
+
if forced and verdict == "allow":
|
|
160
|
+
verdict = "ask"
|
|
161
|
+
outside = self.origin in P.OUTSIDE_ORIGINS
|
|
162
|
+
rule = (rememberable(action, act) if verdict == "ask" and kind in ("commit", "change") and not forced
|
|
163
|
+
else None)
|
|
164
|
+
if rule is not None and not outside and self.exceptions is not None:
|
|
165
|
+
try:
|
|
166
|
+
remembered = self.exceptions.contains(action.app, rule, RULE_OP)
|
|
167
|
+
except Exception: # noqa: BLE001 -- an unreadable store asks
|
|
168
|
+
remembered = False
|
|
169
|
+
if remembered:
|
|
170
|
+
return Decision("allow", kind, act, "You said not to ask again for this",
|
|
171
|
+
(time.perf_counter() - started) * 1000, rule=rule)
|
|
172
|
+
title = None
|
|
173
|
+
if verdict == "ask":
|
|
174
|
+
title = (consequence.REASON_TITLES.get(reason) or TITLES.get(act or "")
|
|
175
|
+
or consequence.TITLES.get(act or "")) if kind in ("floor", "change") or (
|
|
176
|
+
kind == "commit" and not existing_act) else None
|
|
177
|
+
return Decision(verdict, kind, act, reason if verdict != "allow" else "",
|
|
178
|
+
(time.perf_counter() - started) * 1000, title,
|
|
179
|
+
rememberable=rule is not None and not outside, rule=rule)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def remember(decision, app, app_name=""):
|
|
183
|
+
"""The approval request's ``remember`` for ``decision`` (run.Run keeps it until the answer)."""
|
|
184
|
+
if decision is None or not decision.rememberable or not decision.rule or not app:
|
|
185
|
+
return None
|
|
186
|
+
return {"app": app, "label": decision.rule, "op": RULE_OP, "app_name": app_name or "",
|
|
187
|
+
"words": rule_words(decision.rule, app_name)}
|
|
188
|
+
|
|
189
|
+
def _record(self, decision):
|
|
190
|
+
if self.emit is not None and decision.decision != "allow":
|
|
191
|
+
try:
|
|
192
|
+
# No label, no text: what the gate said, and why.
|
|
193
|
+
self.emit({"event": "permission_decision", "decision": decision.decision, "kind": decision.kind,
|
|
194
|
+
"act": decision.act, "ms": round(decision.ms, 2), "mode": self.mode,
|
|
195
|
+
**({"policy": True} if decision.by_policy else {})})
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
return decision
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class PermissionGuard:
|
|
202
|
+
"""A phone guard for the person's app choices; the rest is the inner guard's. Fails closed: no answer, no app."""
|
|
203
|
+
|
|
204
|
+
def __init__(self, inner, apps, names=None, outside=False, ask=None, grants=None, conversation=None):
|
|
205
|
+
self.inner, self.names, self.outside = inner, dict(names or {}), outside
|
|
206
|
+
self._apps = apps
|
|
207
|
+
self.ask, self.grants = ask, grants
|
|
208
|
+
self.declined = set() # apps the person said Not now to in this run: not asked again in it
|
|
209
|
+
# Allow covers this run, or its conversation's runs; never an app that is refused or switched off.
|
|
210
|
+
self._conversation = conversation is not None
|
|
211
|
+
self._granted = conversation if conversation is not None else set()
|
|
212
|
+
self.instruction = None
|
|
213
|
+
self._lock = threading.Lock()
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def apps(self):
|
|
217
|
+
apps = self._apps
|
|
218
|
+
try:
|
|
219
|
+
return apps() if callable(apps) else apps
|
|
220
|
+
except Exception: # noqa: BLE001 -- unreadable settings: nothing counts as approved, the never-open list holds
|
|
221
|
+
return P.AppPolicy(access="ask")
|
|
222
|
+
|
|
223
|
+
@apps.setter
|
|
224
|
+
def apps(self, value):
|
|
225
|
+
self._apps = value
|
|
226
|
+
|
|
227
|
+
def _approved(self, apps, bundle):
|
|
228
|
+
return bundle in self._granted or apps.approved(bundle, self.grants)
|
|
229
|
+
|
|
230
|
+
def take_instruction(self):
|
|
231
|
+
"""What the person said to do instead on the app card, once; None when they didn't."""
|
|
232
|
+
with self._lock:
|
|
233
|
+
instruction, self.instruction = self.instruction, None
|
|
234
|
+
return instruction
|
|
235
|
+
|
|
236
|
+
def __getattr__(self, name):
|
|
237
|
+
if self.inner is None:
|
|
238
|
+
raise AttributeError(name)
|
|
239
|
+
return getattr(self.inner, name)
|
|
240
|
+
|
|
241
|
+
def check(self, driver, *, cause):
|
|
242
|
+
if self.inner is None:
|
|
243
|
+
from ..agent_hooks import READY
|
|
244
|
+
return READY
|
|
245
|
+
return self.inner.check(driver, cause=cause)
|
|
246
|
+
|
|
247
|
+
def attached(self):
|
|
248
|
+
return self.inner.attached() if self.inner is not None else None
|
|
249
|
+
|
|
250
|
+
def finish(self, driver):
|
|
251
|
+
if self.inner is not None:
|
|
252
|
+
self.inner.finish(driver)
|
|
253
|
+
|
|
254
|
+
def _name(self, bundle):
|
|
255
|
+
return self.names.get(bundle) or bundle
|
|
256
|
+
|
|
257
|
+
def request(self, bundles):
|
|
258
|
+
"""Whether every app in ``bundles`` may be used now: False if one is refused, declined, or nobody answers."""
|
|
259
|
+
apps = self.apps
|
|
260
|
+
wanted = [b for b in dict.fromkeys(bundles) if b and b != P.SPRINGBOARD]
|
|
261
|
+
if any(not apps.allows(b, outside=self.outside) for b in wanted):
|
|
262
|
+
return False
|
|
263
|
+
with self._lock:
|
|
264
|
+
missing = [b for b in wanted if not self._approved(apps, b)]
|
|
265
|
+
if not missing:
|
|
266
|
+
return True
|
|
267
|
+
if any(b in self.declined for b in missing) or self.ask is None:
|
|
268
|
+
return False
|
|
269
|
+
names = [self._name(b) for b in missing]
|
|
270
|
+
listed = names[0] if len(names) == 1 else ", ".join(names[:-1]) + " and " + names[-1]
|
|
271
|
+
card = {"kind": "commit", "operation": "OPEN_APP", "label": listed[:200], "role": "App", "text": None,
|
|
272
|
+
"title": f"Allow Mobster to use {listed}?", "act": "use app", "app_name": names[0], "apps": names,
|
|
273
|
+
"bundles": missing, "reason": P.task_card_reason(len(names), self._conversation)}
|
|
274
|
+
if self.grants is not None:
|
|
275
|
+
card["remember"] = {"app": missing[0], "label": P.APP_LABEL, "op": P.APP_OP, "app_name": names[0],
|
|
276
|
+
"words": P.ALWAYS_ALLOW,
|
|
277
|
+
"apps": [{"app": b, "app_name": n} for b, n in zip(missing, names)]}
|
|
278
|
+
try:
|
|
279
|
+
answer = self.ask(card)
|
|
280
|
+
except Exception: # noqa: BLE001 -- no answer: the app stays shut
|
|
281
|
+
answer = None
|
|
282
|
+
if answer != "approved":
|
|
283
|
+
self.declined.update(missing)
|
|
284
|
+
if isinstance(answer, str) and answer.startswith("redirected:"):
|
|
285
|
+
self.instruction = " ".join(answer.split(":", 1)[1].split())[:500] or None
|
|
286
|
+
return False
|
|
287
|
+
# Allow: this run only; Always allow is read from the store at each check, so it can be off mid-run.
|
|
288
|
+
self._granted.update(b for b in missing if not apps.approved(b, self.grants))
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
def allows_app(self, bundle_id):
|
|
292
|
+
try:
|
|
293
|
+
if bundle_id and bundle_id != P.SPRINGBOARD and not self.request([bundle_id]):
|
|
294
|
+
return False
|
|
295
|
+
inner = getattr(self.inner, "allows_app", None)
|
|
296
|
+
return bool(inner(bundle_id)) if callable(inner) else True
|
|
297
|
+
except Exception: # noqa: BLE001 -- fails closed
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
def refusal(self, bundles):
|
|
301
|
+
"""Why ``request(bundles)`` said no, without asking again."""
|
|
302
|
+
apps = self.apps
|
|
303
|
+
wanted = [b for b in dict.fromkeys(bundles) if b and b != P.SPRINGBOARD]
|
|
304
|
+
refused = [b for b in wanted if not apps.allows(b, outside=self.outside)]
|
|
305
|
+
waiting = [b for b in wanted if not self._approved(apps, b)]
|
|
306
|
+
return next((self.blocked_message(b) for b in refused + waiting), None)
|
|
307
|
+
|
|
308
|
+
def blocked_message(self, bundle_id, name=None):
|
|
309
|
+
name = name or self._name(bundle_id)
|
|
310
|
+
apps = self.apps
|
|
311
|
+
if not apps.allows(bundle_id, outside=self.outside):
|
|
312
|
+
return apps.blocked_message(name, outside=self.outside)
|
|
313
|
+
if not self._approved(apps, bundle_id):
|
|
314
|
+
return apps.unapproved_message(name)
|
|
315
|
+
inner = getattr(self.inner, "blocked_message", None)
|
|
316
|
+
return inner(bundle_id, name) if callable(inner) else None
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Approvals for outside agents over MCP: the same gate applies, and only the Mac app's session can answer one."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import asdict
|
|
9
|
+
|
|
10
|
+
from . import consequence
|
|
11
|
+
from . import policy as P
|
|
12
|
+
from .gate import PermissionGate
|
|
13
|
+
|
|
14
|
+
EXPIRES_S = 600
|
|
15
|
+
WAIT_S = 38.0
|
|
16
|
+
KEEP_S = 120 # a decided approval stays readable this long, for the agent that asked
|
|
17
|
+
MAX_PENDING = 20
|
|
18
|
+
POLL_S = 20.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Refused(Exception):
|
|
22
|
+
"""The action may not go ahead now; the message says why and what the agent can do."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message="", state=None, minutes=None):
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.state, self.minutes = state, minutes
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _clean(value, limit):
|
|
30
|
+
return " ".join(str(value or "").split())[:limit] or None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Registry:
|
|
34
|
+
def __init__(self, clock=time.time):
|
|
35
|
+
self.clock = clock
|
|
36
|
+
self._items = {}
|
|
37
|
+
self._version = 0
|
|
38
|
+
self._cond = threading.Condition()
|
|
39
|
+
|
|
40
|
+
def _expire(self, now):
|
|
41
|
+
for item in list(self._items.values()):
|
|
42
|
+
if item["state"] == "pending" and now * 1000 >= item["expiresAt"]:
|
|
43
|
+
item["state"], item["decidedAt"] = "timeout", now * 1000
|
|
44
|
+
self._version += 1
|
|
45
|
+
if item["state"] != "pending" and now * 1000 - (item.get("decidedAt") or 0) > KEEP_S * 1000:
|
|
46
|
+
del self._items[item["id"]]
|
|
47
|
+
|
|
48
|
+
def create(self, body):
|
|
49
|
+
if not isinstance(body, dict) or set(body) - {"key", "request", "device", "source"}:
|
|
50
|
+
raise ValueError("An approval takes key, request, device and source")
|
|
51
|
+
request = body.get("request")
|
|
52
|
+
if not isinstance(request, dict) or not isinstance(request.get("label"), str):
|
|
53
|
+
raise ValueError("request needs at least the control's label")
|
|
54
|
+
key = _clean(body.get("key"), 128)
|
|
55
|
+
source = body.get("source") if isinstance(body.get("source"), dict) else {}
|
|
56
|
+
fingerprint = hashlib.sha256(json.dumps((body.get("device"), source, request), sort_keys=True,
|
|
57
|
+
ensure_ascii=False, allow_nan=False).encode()).hexdigest()
|
|
58
|
+
now = self.clock()
|
|
59
|
+
with self._cond:
|
|
60
|
+
self._expire(now)
|
|
61
|
+
for item in self._items.values():
|
|
62
|
+
if key and item["key"] == key and item["state"] == "pending":
|
|
63
|
+
if item["fingerprint"] != fingerprint:
|
|
64
|
+
raise ValueError("This approval key already names a different waiting action")
|
|
65
|
+
return dict(item)
|
|
66
|
+
if sum(item["state"] == "pending" for item in self._items.values()) >= MAX_PENDING:
|
|
67
|
+
raise ValueError("Too many approvals are waiting")
|
|
68
|
+
item = {"id": uuid.uuid4().hex[:12], "key": key, "fingerprint": fingerprint, "state": "pending",
|
|
69
|
+
"kind": "commit", "outside": True,
|
|
70
|
+
"source": {"kind": "mcp", "client": _clean(source.get("client"), 60)},
|
|
71
|
+
"device": _clean(body.get("device"), 128),
|
|
72
|
+
"operation": _clean(request.get("operation"), 20) or "TAP",
|
|
73
|
+
"label": _clean(request.get("label"), 200) or "", "role": _clean(request.get("role"), 40) or "",
|
|
74
|
+
"text": str(request["text"])[:4000] if isinstance(request.get("text"), str) else None,
|
|
75
|
+
"title": _clean(request.get("title"), 160), "act": _clean(request.get("act"), 40),
|
|
76
|
+
"reason": _clean(request.get("reason"), 120), "app": _clean(request.get("app_name"), 80) or "",
|
|
77
|
+
"target": None, "requestedAt": now * 1000, "expiresAt": (now + EXPIRES_S) * 1000,
|
|
78
|
+
"instruction": None}
|
|
79
|
+
if isinstance(request.get("apps"), list):
|
|
80
|
+
item["apps"] = [name for name in (_clean(n, 80) for n in request["apps"][:6]) if name]
|
|
81
|
+
self._items[item["id"]] = item
|
|
82
|
+
self._version += 1
|
|
83
|
+
self._cond.notify_all()
|
|
84
|
+
return dict(item)
|
|
85
|
+
|
|
86
|
+
def get(self, approval_id, wait=0.0):
|
|
87
|
+
"""The approval, once decided or after ``wait`` seconds; None when unknown."""
|
|
88
|
+
deadline = time.monotonic() + max(0.0, min(wait, POLL_S))
|
|
89
|
+
with self._cond:
|
|
90
|
+
while True:
|
|
91
|
+
self._expire(self.clock())
|
|
92
|
+
item = self._items.get(approval_id)
|
|
93
|
+
if item is None or item["state"] != "pending":
|
|
94
|
+
return dict(item) if item else None
|
|
95
|
+
left = deadline - time.monotonic()
|
|
96
|
+
if left <= 0:
|
|
97
|
+
return dict(item)
|
|
98
|
+
self._cond.wait(min(left, 1.0))
|
|
99
|
+
|
|
100
|
+
def pending(self, since=None, wait=0.0):
|
|
101
|
+
"""(version, the approvals waiting): returns at once, or when the list changes after ``since``."""
|
|
102
|
+
deadline = time.monotonic() + max(0.0, min(wait, POLL_S))
|
|
103
|
+
with self._cond:
|
|
104
|
+
while True:
|
|
105
|
+
self._expire(self.clock())
|
|
106
|
+
if since is None or since != self._version or time.monotonic() >= deadline:
|
|
107
|
+
items = [dict(i) for i in self._items.values() if i["state"] == "pending"]
|
|
108
|
+
return self._version, sorted(items, key=lambda i: i["requestedAt"])
|
|
109
|
+
self._cond.wait(min(deadline - time.monotonic(), 1.0))
|
|
110
|
+
|
|
111
|
+
def answer(self, approval_id, approve, instruction=None):
|
|
112
|
+
if type(approve) is not bool:
|
|
113
|
+
raise ValueError("approve must be true or false")
|
|
114
|
+
if instruction is not None:
|
|
115
|
+
instruction = " ".join(str(instruction).split())
|
|
116
|
+
if approve or not 1 <= len(instruction) <= 500:
|
|
117
|
+
raise ValueError("A redirect declines the action and says in 1–500 characters what to do instead")
|
|
118
|
+
with self._cond:
|
|
119
|
+
self._expire(self.clock())
|
|
120
|
+
item = self._items.get(approval_id)
|
|
121
|
+
if item is None or item["state"] != "pending":
|
|
122
|
+
return False
|
|
123
|
+
item["state"] = "approved" if approve else "redirected" if instruction else "denied"
|
|
124
|
+
item["instruction"], item["decidedAt"] = instruction, self.clock() * 1000
|
|
125
|
+
self._version += 1
|
|
126
|
+
self._cond.notify_all()
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
def cancel(self, approval_id):
|
|
130
|
+
with self._cond:
|
|
131
|
+
item = self._items.get(approval_id)
|
|
132
|
+
if item is None or item["state"] != "pending":
|
|
133
|
+
return False
|
|
134
|
+
item["state"], item["decidedAt"] = "cancelled", self.clock() * 1000
|
|
135
|
+
self._version += 1
|
|
136
|
+
self._cond.notify_all()
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# In memory only: an approval can hold the exact text to be sent, which is never journaled.
|
|
141
|
+
REGISTRY = Registry()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def public(item):
|
|
145
|
+
"""An approval as the Mac app reads it (the exact text included: it never leaves this Mac's memory)."""
|
|
146
|
+
return {k: v for k, v in item.items() if k not in {"key", "fingerprint"}}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def key_for(device, action):
|
|
150
|
+
raw = json.dumps((device, asdict(action)), sort_keys=True, ensure_ascii=False, separators=(",", ":"))
|
|
151
|
+
return hashlib.sha256(raw.encode()).hexdigest()[:32]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def app_name(bundle, device=None, folder=None):
|
|
155
|
+
"""The name the person knows the app by ("Chrome")."""
|
|
156
|
+
try:
|
|
157
|
+
from ..catalog import app_name as catalog_name
|
|
158
|
+
from ..installed_apps import InventoryCache
|
|
159
|
+
from ..paths import user_data_dir
|
|
160
|
+
inventory = InventoryCache(folder or user_data_dir() / "installed-apps").load(device) if device else None
|
|
161
|
+
for app in (inventory.user if inventory is not None else ()):
|
|
162
|
+
if app.get("bundleId") == bundle and app.get("name") and app["name"] != bundle:
|
|
163
|
+
return app["name"]
|
|
164
|
+
return catalog_name(bundle) or bundle
|
|
165
|
+
except Exception: # noqa: BLE001 -- a name is for reading: the bundle id stands in
|
|
166
|
+
return bundle
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def check_app(bundle, name=None, *, device, client_name=None, settings=None, grants=None, connect=None,
|
|
170
|
+
wait_s=WAIT_S):
|
|
171
|
+
"""An outside agent opening or acting in ``bundle``: returns once approved, else Refused."""
|
|
172
|
+
settings = settings or P.from_file()
|
|
173
|
+
name = name or app_name(bundle, device)
|
|
174
|
+
if not settings.apps.allows(bundle, outside=True):
|
|
175
|
+
raise Refused(settings.apps.blocked_message(name, outside=settings.apps.allows(bundle)) +
|
|
176
|
+
" Nothing was done.")
|
|
177
|
+
if grants is None:
|
|
178
|
+
from .exceptions import Exceptions, default_path
|
|
179
|
+
grants = Exceptions(default_path())
|
|
180
|
+
if settings.apps.approved(bundle, grants):
|
|
181
|
+
return
|
|
182
|
+
request = {"operation": "OPEN_APP", "label": name, "role": "App", "text": None,
|
|
183
|
+
"title": f"Allow Mobster to use {name}?", "act": "use app", "app_name": name, "apps": [name],
|
|
184
|
+
"reason": P.app_card_reason()}
|
|
185
|
+
answer = ask_app(request, key=hashlib.sha256(f"{device}␟app␟{bundle}".encode()).hexdigest()[:32],
|
|
186
|
+
device=device, client_name=client_name, connect=connect, wait_s=wait_s)
|
|
187
|
+
if answer.get("state") == "approved":
|
|
188
|
+
try:
|
|
189
|
+
grants.add(bundle, P.APP_LABEL, P.APP_OP, name)
|
|
190
|
+
except Exception: # noqa: BLE001 -- approved now even if the store can't keep it
|
|
191
|
+
pass
|
|
192
|
+
return
|
|
193
|
+
_refuse(answer, f"Allow Mobster to use {name}")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _refuse(answer, what):
|
|
197
|
+
"""Refused with what the person's answer (not approved) means for the agent."""
|
|
198
|
+
state = answer.get("state")
|
|
199
|
+
if state == "redirected":
|
|
200
|
+
raise Refused(f"The user declined this in the Mobster app and said: {answer.get('instruction')!r}. Nothing "
|
|
201
|
+
"was done.", state)
|
|
202
|
+
if state == "denied":
|
|
203
|
+
raise Refused("The user declined this in the Mobster app. Nothing was done.", state)
|
|
204
|
+
if state == "timeout":
|
|
205
|
+
raise Refused("Nobody answered the approval in the Mobster app in 10 minutes, so nothing was done.", state)
|
|
206
|
+
if state == "pending":
|
|
207
|
+
left = max(0, int((answer.get("expiresAt", 0) / 1000 - time.time()) // 60))
|
|
208
|
+
raise Refused(f"This needs the user's OK in the Mobster app ({what}). They haven't answered yet, so nothing "
|
|
209
|
+
f"was done. Tell the user, then make the same call again to keep waiting (the request stays "
|
|
210
|
+
f"open about {left} more minutes).", state, left)
|
|
211
|
+
raise Refused("The approval was withdrawn, so nothing was done.", "withdrawn")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _project_policy():
|
|
215
|
+
"""The policy of the project this server runs in (its working folder), when MOBSTER_POLICY is on."""
|
|
216
|
+
from ..paths import default_history_db
|
|
217
|
+
from . import project_policy
|
|
218
|
+
folder = project_policy.project_folder() if project_policy.enabled() else None
|
|
219
|
+
return project_policy.load(folder, project_policy.counts_path(default_history_db())) if folder else None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def check_action(action, *, device, client_name=None, settings=None, connect=None, wait_s=WAIT_S, forced=False):
|
|
223
|
+
"""An outside agent's action: whether it asked (True), or Refused; never below ask_important."""
|
|
224
|
+
settings = settings or P.from_file()
|
|
225
|
+
if action.app:
|
|
226
|
+
check_app(action.app, action.app_name, device=device, client_name=client_name, settings=settings,
|
|
227
|
+
connect=connect, wait_s=wait_s)
|
|
228
|
+
from ..harness_api import commit_act
|
|
229
|
+
gate = PermissionGate(P.effective_mode(settings.mode, None, "mcp"), apps=settings.apps, origin="mcp",
|
|
230
|
+
policy=_project_policy())
|
|
231
|
+
op = (action.op or "").upper()
|
|
232
|
+
typing = op in ("TYPE", "SET_TEXT")
|
|
233
|
+
# A row's swipe is read by the gate's own signals (permissions.consequence): its words are the row's content.
|
|
234
|
+
existing = None if typing or op.startswith("SWIPE_") else commit_act(action.op, action.label, None,
|
|
235
|
+
action.app or None)
|
|
236
|
+
if existing == "other" and op in consequence.RETURN_OPS and consequence.query_return(action) and commit_act(
|
|
237
|
+
"TYPE", action.label, None, action.app or None) is None:
|
|
238
|
+
# commit_act calls every Return a commit; only a field told for certain to be a search or address bar drops it.
|
|
239
|
+
existing = None
|
|
240
|
+
decision = gate.decide(action, existing_act=existing, forced=forced)
|
|
241
|
+
if decision.decision == "allow":
|
|
242
|
+
return False
|
|
243
|
+
title = decision.title or approval_title(decision.act)
|
|
244
|
+
text = action.text
|
|
245
|
+
if text is None and decision.act in {"send_message", "reply", "comment", "post", "share"}:
|
|
246
|
+
text = action.typed
|
|
247
|
+
request = {"operation": action.op, "label": action.label, "role": action.role or "", "text": text,
|
|
248
|
+
"title": title, "act": decision.act, "reason": decision.reason or None,
|
|
249
|
+
"app_name": action.app_name or action.app}
|
|
250
|
+
answer = ask_app(request, key=key_for(device, action), device=device, client_name=client_name, connect=connect,
|
|
251
|
+
wait_s=wait_s)
|
|
252
|
+
if answer.get("state") == "approved":
|
|
253
|
+
return True
|
|
254
|
+
_refuse(answer, title)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def check_code(bundle, field_name, *, device, client_name=None, connect=None, wait_s=WAIT_S):
|
|
258
|
+
"""An outside agent entering a one-time code (use_code): it asks in the Mac app in every mode, never remembered."""
|
|
259
|
+
request = {"operation": "USE_CODE", "label": str(field_name or "the code field")[:200], "role": "TextField",
|
|
260
|
+
"text": None, "title": "Enter this code?", "act": "code", "reason": "Types a one-time code",
|
|
261
|
+
"app_name": bundle or ""}
|
|
262
|
+
answer = ask_app(request, key=hashlib.sha256(f"{device}␟code␟{bundle}␟{field_name}".encode()).hexdigest()[:32],
|
|
263
|
+
device=device, client_name=client_name, connect=connect, wait_s=wait_s)
|
|
264
|
+
if answer.get("state") == "approved":
|
|
265
|
+
return
|
|
266
|
+
_refuse(answer, "Enter this code?")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def check_link(url, title, app, app_name, *, device, client_name=None, connect=None, wait_s=WAIT_S):
|
|
270
|
+
"""An outside agent opening a link that asks (deeplinks.decide): the Mac app's card every time, never remembered."""
|
|
271
|
+
request = {"operation": "OPEN_URL", "label": f"Open {str(url)[:180]}", "role": "Link", "text": str(url)[:500],
|
|
272
|
+
"title": title or "Open this link?", "act": "open link", "reason": None,
|
|
273
|
+
"app_name": app_name or app or ""}
|
|
274
|
+
answer = ask_app(request, key=hashlib.sha256(f"{device}␟link␟{url}".encode()).hexdigest()[:32], device=device,
|
|
275
|
+
client_name=client_name, connect=connect, wait_s=wait_s)
|
|
276
|
+
if answer.get("state") == "approved":
|
|
277
|
+
return
|
|
278
|
+
_refuse(answer, title or "Open this link?")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
_TITLES = {"send_message": "Send this message?", "reply": "Send this reply?", "comment": "Post this comment?",
|
|
282
|
+
"post": "Post this?", "react": "Send this reaction?", "pay": "Make this payment?",
|
|
283
|
+
"request_money": "Send this request?", "transfer": "Make this transfer?", "order": "Place this order?",
|
|
284
|
+
"book": "Make this booking?", "follow": "Follow this account?", "unfollow": "Unfollow this account?",
|
|
285
|
+
"share": "Share this?", "call": "Place this call?", "delete": "Delete this?", "cancel": "Cancel this?",
|
|
286
|
+
"install": "Get this app?"}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def approval_title(act):
|
|
290
|
+
"""The card's question for an act of today's commit test ("Send this message?")."""
|
|
291
|
+
return _TITLES.get(act or "", "Do this now?")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def ask_app(request, *, key, device=None, client_name=None, connect=None, wait_s=WAIT_S):
|
|
295
|
+
"""Raise the approval in the Mac app and wait up to ``wait_s`` for its answer: the approval as the app has it."""
|
|
296
|
+
from ..threads.client import Client, NoService, ServiceError
|
|
297
|
+
try:
|
|
298
|
+
client = (connect or (lambda: Client.connect(origin="mcp")))()
|
|
299
|
+
made = client.call("POST", "/api/approvals", {"key": key, "request": request, "device": device,
|
|
300
|
+
"source": {"client": client_name}})
|
|
301
|
+
except (NoService, ServiceError, OSError):
|
|
302
|
+
raise Refused("This needs the user's OK, and the Mobster app isn't open to ask them. Ask the user to open "
|
|
303
|
+
"Mobster, then try again. Nothing was done.") from None
|
|
304
|
+
approval = made.get("approval") or made
|
|
305
|
+
deadline = time.monotonic() + wait_s
|
|
306
|
+
while approval.get("state") == "pending":
|
|
307
|
+
left = deadline - time.monotonic()
|
|
308
|
+
if left <= 0:
|
|
309
|
+
break
|
|
310
|
+
try:
|
|
311
|
+
got = client.call("GET", f"/api/approvals/{approval['id']}?wait={min(POLL_S, max(1, int(left)))}",
|
|
312
|
+
timeout=min(POLL_S, left) + 5)
|
|
313
|
+
except (ServiceError, OSError):
|
|
314
|
+
break
|
|
315
|
+
approval = got.get("approval") or got
|
|
316
|
+
return approval
|