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,411 @@
|
|
|
1
|
+
"""A project's policy, ``.mobster/policy.yaml``: where Mobster asks first in this project. It only narrows.
|
|
2
|
+
|
|
3
|
+
The policy adds questions. It never answers one: a rule that says ``allow`` removes no question that the permission
|
|
4
|
+
mode, a guard, a "never open" app or a floor (a one-time code, an unknown outcome) asks. The stricter of the policy and
|
|
5
|
+
the person's mode wins, and a policy that can't be read asks before every act (``Policy.broken``).
|
|
6
|
+
|
|
7
|
+
version: 1
|
|
8
|
+
default: allow # ask: an act no rule lists asks
|
|
9
|
+
rules:
|
|
10
|
+
- name: Work hours # optional, shown on the approval
|
|
11
|
+
profile: acme # optional: a profile's handle or id; left out, every task
|
|
12
|
+
app: com.burbn.instagram # optional: a bundle id; left out or *, every app
|
|
13
|
+
kind: [post, comment] # one kind or a list; any (the default) covers all of them
|
|
14
|
+
action: allow # allow (default ask)
|
|
15
|
+
window: mon-fri 09:00-18:00 # allow only then; outside it the policy asks
|
|
16
|
+
timezone: America/New_York # the window's and the day's clock; default this Mac's
|
|
17
|
+
max_per_day: 5 # after this many a day, the policy asks
|
|
18
|
+
|
|
19
|
+
Off unless ``MOBSTER_POLICY=on``. The count of what ran unasked today is kept in ``policy-counts.json`` beside the
|
|
20
|
+
task journal, one file lock for every process on the Mac.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import contextlib
|
|
24
|
+
import datetime
|
|
25
|
+
import fcntl
|
|
26
|
+
import hashlib
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
import tempfile
|
|
31
|
+
import threading
|
|
32
|
+
import time
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
ENV = "MOBSTER_POLICY"
|
|
37
|
+
FILE = Path(".mobster") / "policy.yaml"
|
|
38
|
+
MAX_BYTES = 64 * 1024
|
|
39
|
+
MAX_RULES = 200
|
|
40
|
+
ACTIONS = ("allow", "ask")
|
|
41
|
+
TOP_KEYS = frozenset({"version", "default", "rules"})
|
|
42
|
+
RULE_KEYS = frozenset({"name", "profile", "app", "kind", "action", "window", "timezone", "max_per_day"})
|
|
43
|
+
DAYS = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
|
|
44
|
+
_BUNDLE = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_.-]*[A-Za-z0-9_]")
|
|
45
|
+
_WINDOW = re.compile(r"(?:(?P<days>[a-z]{3}(?:-[a-z]{3})?(?:,[a-z]{3}(?:-[a-z]{3})?)*)\s+)?"
|
|
46
|
+
r"(?P<start>\d{1,2}:\d{2})\s*-\s*(?P<end>\d{1,2}:\d{2})", re.I)
|
|
47
|
+
|
|
48
|
+
# What a rule's ``kind`` can name, and the gate acts each stands for (permissions.consequence, rules.RULE_KINDS and the
|
|
49
|
+
# contract's acts). An act is in every kind listed for it, so a rule on any one of them reaches it. An outward act no
|
|
50
|
+
# kind lists is "other"; a change that stays on the phone and no kind lists is "change".
|
|
51
|
+
ACT_KINDS = {
|
|
52
|
+
"send": ("message",), "send_message": ("message",), "reply": ("message",), "unsend": ("message", "delete"),
|
|
53
|
+
"comment": ("comment",),
|
|
54
|
+
"post": ("post",), "publish": ("post",), "tweet": ("post",), "share": ("post",),
|
|
55
|
+
"react": ("react",), "like": ("react",), "unlike": ("react",),
|
|
56
|
+
"follow": ("follow",), "unfollow": ("follow",), "subscribe": ("follow", "buy"), "unsubscribe": ("follow", "buy"),
|
|
57
|
+
"call": ("call",), "dial": ("call",), "facetime": ("call",), "end call": ("call",),
|
|
58
|
+
"buy": ("buy",), "purchase": ("buy",), "pay": ("buy",), "checkout": ("buy",), "order": ("buy",),
|
|
59
|
+
"place order": ("buy",), "transfer": ("buy",), "donate": ("buy",), "tip": ("buy",), "redeem": ("buy",),
|
|
60
|
+
"rent": ("buy",), "get": ("buy",), "install": ("buy",),
|
|
61
|
+
"book": ("book",), "reserve": ("book",),
|
|
62
|
+
"delete": ("delete",), "erase": ("delete",), "remove": ("delete",), "reset": ("delete",), "clear": ("delete",),
|
|
63
|
+
"trash": ("delete",), "wipe": ("delete",), "unmatch": ("delete",),
|
|
64
|
+
"setting": ("setting",), "turn off": ("setting",),
|
|
65
|
+
"change": ("change",), "save": ("change",), "submit": ("change",), "archive": ("change",), "junk": ("change",),
|
|
66
|
+
}
|
|
67
|
+
KINDS = tuple(sorted({kind for kinds in ACT_KINDS.values() for kind in kinds} | {"other"})) + ("any",)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def enabled(env=None):
|
|
71
|
+
"""Whether project policies are read (``MOBSTER_POLICY=on``); off by default."""
|
|
72
|
+
return ((env if env is not None else os.environ).get(ENV) or "").strip().casefold() == "on"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def kinds_of(gate_kind, act):
|
|
76
|
+
"""The policy kinds of one gate decision: ``gate_kind`` is "commit" or "change", ``act`` its verb."""
|
|
77
|
+
act = " ".join(str(act or "").casefold().split())
|
|
78
|
+
found = ACT_KINDS.get(act) or ACT_KINDS.get(act.replace("_", " "))
|
|
79
|
+
return frozenset(found or (("change",) if gate_kind == "change" else ("other",)))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PolicyError(ValueError):
|
|
83
|
+
"""The file can't be used; the message says what to change."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _text(value, name, limit):
|
|
87
|
+
if not isinstance(value, str) or not value.strip() or len(value) > limit:
|
|
88
|
+
raise PolicyError(f"{name} is text of at most {limit} characters")
|
|
89
|
+
return " ".join(value.split())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _minutes(clock):
|
|
93
|
+
hours, minutes = clock.split(":")
|
|
94
|
+
value = int(hours) * 60 + int(minutes)
|
|
95
|
+
if not 0 <= value <= 24 * 60 or int(minutes) > 59:
|
|
96
|
+
raise PolicyError("a window's times are 24-hour, such as 09:00-18:00")
|
|
97
|
+
return value
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _days(text):
|
|
101
|
+
out = set()
|
|
102
|
+
for part in text.casefold().split(","):
|
|
103
|
+
first, _, last = part.partition("-")
|
|
104
|
+
if first not in DAYS or last and last not in DAYS:
|
|
105
|
+
raise PolicyError("a window's days are mon to sun, such as mon-fri or sat,sun")
|
|
106
|
+
low, high = DAYS.index(first), DAYS.index(last or first)
|
|
107
|
+
out |= set(range(low, high + 1)) if low <= high else set(range(low, 7)) | set(range(high + 1))
|
|
108
|
+
return frozenset(out)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _zone(name):
|
|
112
|
+
if not name:
|
|
113
|
+
return None
|
|
114
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
115
|
+
try:
|
|
116
|
+
return ZoneInfo(name)
|
|
117
|
+
except (ZoneInfoNotFoundError, ValueError, OSError):
|
|
118
|
+
raise PolicyError("timezone is a time zone such as America/New_York") from None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True)
|
|
122
|
+
class Rule:
|
|
123
|
+
index: int
|
|
124
|
+
name: str
|
|
125
|
+
profile: str | None
|
|
126
|
+
app: str | None
|
|
127
|
+
kinds: frozenset
|
|
128
|
+
action: str
|
|
129
|
+
window: str | None = None
|
|
130
|
+
days: frozenset | None = None
|
|
131
|
+
start: int = 0
|
|
132
|
+
end: int = 0
|
|
133
|
+
timezone: str | None = None
|
|
134
|
+
max_per_day: int | None = None
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def key(self):
|
|
138
|
+
"""What the day's count is kept under: the rule's scope, so raising its cap keeps today's count."""
|
|
139
|
+
scope = json.dumps([self.name, self.profile, self.app, sorted(self.kinds)])
|
|
140
|
+
return hashlib.sha256(scope.encode()).hexdigest()[:16]
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def label(self):
|
|
144
|
+
return f"“{self.name}”" if self.name else f"rule {self.index + 1}"
|
|
145
|
+
|
|
146
|
+
def matches(self, kinds, app, profile):
|
|
147
|
+
return ((self.app is None or self.app == app) and (self.profile is None or _profile_is(self.profile, profile))
|
|
148
|
+
and ("any" in self.kinds or bool(self.kinds & kinds)))
|
|
149
|
+
|
|
150
|
+
def local(self, now):
|
|
151
|
+
zone = _zone(self.timezone)
|
|
152
|
+
if zone:
|
|
153
|
+
return datetime.datetime.fromtimestamp(now, zone)
|
|
154
|
+
return datetime.datetime.fromtimestamp(now).astimezone()
|
|
155
|
+
|
|
156
|
+
def day(self, now):
|
|
157
|
+
return self.local(now).date().isoformat()
|
|
158
|
+
|
|
159
|
+
def open(self, now):
|
|
160
|
+
"""Whether ``now`` is inside the rule's window (always, when it has none)."""
|
|
161
|
+
if self.window is None:
|
|
162
|
+
return True
|
|
163
|
+
local = self.local(now)
|
|
164
|
+
minute, weekday = local.hour * 60 + local.minute, local.weekday()
|
|
165
|
+
if self.start < self.end:
|
|
166
|
+
return weekday in self.days and self.start <= minute < self.end
|
|
167
|
+
# A window past midnight belongs to the day it started.
|
|
168
|
+
return (weekday in self.days and minute >= self.start) or ((weekday - 1) % 7 in self.days and minute < self.end)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _profile_is(wanted, profile):
|
|
172
|
+
if not profile:
|
|
173
|
+
return False
|
|
174
|
+
names = {str(profile.get(key) or "").casefold().lstrip("@") for key in ("id", "handle", "name")} - {""}
|
|
175
|
+
return wanted.casefold().lstrip("@") in names
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Counts:
|
|
179
|
+
"""How many acts the policy let through unasked today, per rule. ``path`` None keeps them in memory (tests, dry
|
|
180
|
+
runs). Unreadable counts read as a reached cap: the policy then asks (fail closed)."""
|
|
181
|
+
|
|
182
|
+
def __init__(self, path=None):
|
|
183
|
+
self.path = Path(path) if path else None
|
|
184
|
+
self._memory = {}
|
|
185
|
+
self._lock = threading.Lock()
|
|
186
|
+
|
|
187
|
+
@contextlib.contextmanager
|
|
188
|
+
def _exclusive(self):
|
|
189
|
+
with self._lock:
|
|
190
|
+
if self.path is None:
|
|
191
|
+
yield
|
|
192
|
+
return
|
|
193
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
fd = os.open(str(self.path) + ".lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
|
|
195
|
+
try:
|
|
196
|
+
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
197
|
+
yield
|
|
198
|
+
finally:
|
|
199
|
+
os.close(fd)
|
|
200
|
+
|
|
201
|
+
def _read(self):
|
|
202
|
+
if self.path is None:
|
|
203
|
+
return self._memory
|
|
204
|
+
try:
|
|
205
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
206
|
+
except FileNotFoundError:
|
|
207
|
+
return {}
|
|
208
|
+
if not isinstance(data, dict) or not all(isinstance(v, dict) and isinstance(v.get("day"), str)
|
|
209
|
+
and type(v.get("n")) is int for v in data.values()):
|
|
210
|
+
raise ValueError("unreadable")
|
|
211
|
+
return data
|
|
212
|
+
|
|
213
|
+
def _write(self, data):
|
|
214
|
+
if self.path is None:
|
|
215
|
+
self._memory = data
|
|
216
|
+
return
|
|
217
|
+
handle, temporary = tempfile.mkstemp(dir=self.path.parent, prefix=".policy-counts-")
|
|
218
|
+
try:
|
|
219
|
+
with os.fdopen(handle, "w", encoding="utf-8") as stream:
|
|
220
|
+
json.dump(data, stream)
|
|
221
|
+
os.chmod(temporary, 0o600)
|
|
222
|
+
os.replace(temporary, self.path)
|
|
223
|
+
except BaseException:
|
|
224
|
+
Path(temporary).unlink(missing_ok=True)
|
|
225
|
+
raise
|
|
226
|
+
|
|
227
|
+
def today(self, key, day):
|
|
228
|
+
"""How many ran today under ``key``; None when the counts can't be read."""
|
|
229
|
+
try:
|
|
230
|
+
with self._exclusive():
|
|
231
|
+
entry = self._read().get(key)
|
|
232
|
+
except (OSError, ValueError):
|
|
233
|
+
return None
|
|
234
|
+
return entry["n"] if entry and entry["day"] == day else 0
|
|
235
|
+
|
|
236
|
+
def take(self, wanted):
|
|
237
|
+
"""Count one more under each (key, day, cap) of ``wanted``, unless one is at its cap or unreadable: then
|
|
238
|
+
nothing is counted and that item is returned. All or nothing, under one lock."""
|
|
239
|
+
try:
|
|
240
|
+
with self._exclusive():
|
|
241
|
+
data = self._read()
|
|
242
|
+
for key, day, cap in wanted:
|
|
243
|
+
entry = data.get(key)
|
|
244
|
+
if (entry["n"] if entry and entry["day"] == day else 0) >= cap:
|
|
245
|
+
return (key, day, cap)
|
|
246
|
+
for key, day, _ in wanted:
|
|
247
|
+
entry = data.get(key)
|
|
248
|
+
data[key] = {"day": day, "n": entry["n"] + 1 if entry and entry["day"] == day else 1}
|
|
249
|
+
if len(data) > 400:
|
|
250
|
+
data = dict(sorted(data.items(), key=lambda item: item[1]["day"])[-200:])
|
|
251
|
+
self._write(data)
|
|
252
|
+
except (OSError, ValueError):
|
|
253
|
+
return ("unreadable", "", 0)
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass(frozen=True)
|
|
258
|
+
class Policy:
|
|
259
|
+
rules: tuple = ()
|
|
260
|
+
default: str = "allow"
|
|
261
|
+
path: Path | None = None
|
|
262
|
+
error: str | None = None
|
|
263
|
+
counts: Counts = field(default_factory=Counts, compare=False)
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def broken(cls, path, error, counts=None):
|
|
267
|
+
return cls(default="ask", path=path, error=error, counts=counts or Counts())
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def narrows(self):
|
|
271
|
+
"""Whether the policy can ask at all: any rule but a plain ``allow``, a default of ask, or a broken file."""
|
|
272
|
+
return self.error is not None or self.default == "ask" or any(
|
|
273
|
+
r.action == "ask" or r.window is not None or r.max_per_day is not None for r in self.rules)
|
|
274
|
+
|
|
275
|
+
def _matching(self, kinds, app, profile):
|
|
276
|
+
return [rule for rule in self.rules if rule.matches(kinds, app, profile)]
|
|
277
|
+
|
|
278
|
+
def asks(self, kinds, app, profile=None, now=None):
|
|
279
|
+
"""The sentence for why the policy asks about an act of ``kinds`` in ``app`` as ``profile`` now, or None.
|
|
280
|
+
Reads the counts; counts nothing."""
|
|
281
|
+
now = time.time() if now is None else now
|
|
282
|
+
if self.error is not None:
|
|
283
|
+
return f"Your project policy can't be read: {self.error}"
|
|
284
|
+
matched = self._matching(kinds, app, profile)
|
|
285
|
+
for rule in matched:
|
|
286
|
+
if rule.action == "ask":
|
|
287
|
+
return f"Your project policy {rule.label} asks before this" if rule.name else \
|
|
288
|
+
"Your project policy asks before this"
|
|
289
|
+
if not rule.open(now):
|
|
290
|
+
return f"Your project policy allows this only {rule.window}"
|
|
291
|
+
if rule.max_per_day is not None:
|
|
292
|
+
done = self.counts.today(rule.key, rule.day(now))
|
|
293
|
+
if done is None or done >= rule.max_per_day:
|
|
294
|
+
return f"You reached today's limit of {rule.max_per_day} in your project policy"
|
|
295
|
+
if not matched and self.default == "ask":
|
|
296
|
+
return "Your project policy asks before anything it doesn't list"
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
def take(self, kinds, app, profile=None, now=None):
|
|
300
|
+
"""An act the engine lets through unasked: count it under every capped rule it matches. None when counted,
|
|
301
|
+
else the sentence for why the policy asks (a cap was reached between ``asks`` and now, or the counts are
|
|
302
|
+
unreadable)."""
|
|
303
|
+
now = time.time() if now is None else now
|
|
304
|
+
capped = [(r.key, r.day(now), r.max_per_day) for r in self._matching(kinds, app, profile)
|
|
305
|
+
if r.max_per_day is not None]
|
|
306
|
+
if not capped or self.counts.take(capped) is None:
|
|
307
|
+
return None
|
|
308
|
+
return "You reached today's limit in your project policy"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def parse(text, *, path=None, counts=None):
|
|
312
|
+
"""The ``Policy`` in ``text``. Raises ``PolicyError`` for a key it doesn't know (a typo must not pass), a value
|
|
313
|
+
out of range, or a window or cap on a rule that asks."""
|
|
314
|
+
import yaml
|
|
315
|
+
try:
|
|
316
|
+
data = yaml.safe_load(text)
|
|
317
|
+
except yaml.YAMLError as error:
|
|
318
|
+
line = getattr(getattr(error, "problem_mark", None), "line", None)
|
|
319
|
+
raise PolicyError("the file isn't valid YAML" + (f" (line {line + 1})" if line is not None else "")) from None
|
|
320
|
+
if not isinstance(data, dict) or set(data) - TOP_KEYS:
|
|
321
|
+
raise PolicyError("the file takes version, default and rules")
|
|
322
|
+
if data.get("version", 1) != 1:
|
|
323
|
+
raise PolicyError("version is 1")
|
|
324
|
+
default = data.get("default", "allow")
|
|
325
|
+
if default not in ACTIONS:
|
|
326
|
+
raise PolicyError("default is allow or ask")
|
|
327
|
+
listed = [] if data.get("rules") is None else data["rules"]
|
|
328
|
+
if not isinstance(listed, list) or len(listed) > MAX_RULES:
|
|
329
|
+
raise PolicyError(f"rules is a list of at most {MAX_RULES}")
|
|
330
|
+
rules = []
|
|
331
|
+
for index, raw in enumerate(listed):
|
|
332
|
+
try:
|
|
333
|
+
rules.append(_rule(index, raw))
|
|
334
|
+
except PolicyError as error:
|
|
335
|
+
raise PolicyError(f"rule {index + 1}: {error}") from None
|
|
336
|
+
return Policy(tuple(rules), default, path, None, counts or Counts())
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _rule(index, raw):
|
|
340
|
+
if not isinstance(raw, dict) or set(raw) - RULE_KEYS:
|
|
341
|
+
raise PolicyError("a rule takes " + ", ".join(sorted(RULE_KEYS)))
|
|
342
|
+
action = raw.get("action", "ask")
|
|
343
|
+
if action not in ACTIONS:
|
|
344
|
+
raise PolicyError("action is allow or ask")
|
|
345
|
+
kinds = raw.get("kind", "any")
|
|
346
|
+
kinds = [kinds] if isinstance(kinds, str) else kinds
|
|
347
|
+
if not isinstance(kinds, list) or not kinds or not all(isinstance(k, str) and k.casefold() in KINDS for k in kinds):
|
|
348
|
+
raise PolicyError("kind is one of " + ", ".join(KINDS) + ", or a list of them")
|
|
349
|
+
app = raw.get("app")
|
|
350
|
+
if app is not None and app != "*" and (not isinstance(app, str) or len(app) > 255 or "." not in app
|
|
351
|
+
or not _BUNDLE.fullmatch(app)):
|
|
352
|
+
raise PolicyError("app is a bundle id such as com.burbn.instagram, or *")
|
|
353
|
+
window, days, start, end = raw.get("window"), None, 0, 0
|
|
354
|
+
if action == "ask" and (window is not None or raw.get("max_per_day") is not None):
|
|
355
|
+
raise PolicyError("a rule that asks always asks: put window and max_per_day on an allow rule")
|
|
356
|
+
if window is not None:
|
|
357
|
+
found = _WINDOW.fullmatch(_text(window, "window", 40))
|
|
358
|
+
if found is None:
|
|
359
|
+
raise PolicyError("window is days and hours, such as mon-fri 09:00-18:00")
|
|
360
|
+
days = _days(found["days"]) if found["days"] else frozenset(range(7))
|
|
361
|
+
start, end = _minutes(found["start"]), _minutes(found["end"])
|
|
362
|
+
if start == end:
|
|
363
|
+
raise PolicyError("a window needs a start and an end that differ")
|
|
364
|
+
window = " ".join(window.split())
|
|
365
|
+
cap = raw.get("max_per_day")
|
|
366
|
+
if cap is not None and (type(cap) is not int or not 1 <= cap <= 10_000):
|
|
367
|
+
raise PolicyError("max_per_day is a whole number from 1 to 10000")
|
|
368
|
+
zone = raw.get("timezone")
|
|
369
|
+
if zone is not None:
|
|
370
|
+
_zone(_text(zone, "timezone", 64))
|
|
371
|
+
return Rule(index, _text(raw["name"], "name", 60) if "name" in raw else "",
|
|
372
|
+
_text(raw["profile"], "profile", 80) if raw.get("profile") is not None else None,
|
|
373
|
+
None if app == "*" else app, frozenset(k.casefold() for k in kinds), action, window, days, start, end,
|
|
374
|
+
zone, cap)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def load(folder, counts_path=None):
|
|
378
|
+
"""The ``Policy`` in ``folder``/.mobster/policy.yaml; None when there is no file. A file that can't be read or
|
|
379
|
+
checked, a link, or one too big gives a broken policy, which asks before every act."""
|
|
380
|
+
path = Path(folder) / FILE
|
|
381
|
+
counts = Counts(counts_path)
|
|
382
|
+
try:
|
|
383
|
+
info = path.lstat()
|
|
384
|
+
except FileNotFoundError:
|
|
385
|
+
return None
|
|
386
|
+
except OSError:
|
|
387
|
+
return Policy.broken(path, "the file can't be read", counts)
|
|
388
|
+
if not path.is_file() or path.is_symlink() or path.parent.is_symlink() or info.st_size > MAX_BYTES:
|
|
389
|
+
return Policy.broken(path, "it must be a plain file of at most 64 KB, not a link", counts)
|
|
390
|
+
try:
|
|
391
|
+
return parse(path.read_text(encoding="utf-8"), path=path, counts=counts)
|
|
392
|
+
except (OSError, UnicodeDecodeError):
|
|
393
|
+
return Policy.broken(path, "the file can't be read as text", counts)
|
|
394
|
+
except PolicyError as error:
|
|
395
|
+
return Policy.broken(path, str(error), counts)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def project_folder(start=None):
|
|
399
|
+
"""The folder that holds ``.mobster/``, walking up from ``start`` (default the working directory); None without
|
|
400
|
+
one."""
|
|
401
|
+
start = Path(start or Path.cwd()).resolve()
|
|
402
|
+
start = start if start.is_dir() else start.parent
|
|
403
|
+
return next((folder for folder in (start, *start.parents) if (folder / ".mobster").is_dir()), None)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def counts_path(state_db=None):
|
|
407
|
+
"""Where today's counts live: beside the task journal. None for no journal (a scripted demo): they stay in
|
|
408
|
+
memory."""
|
|
409
|
+
if state_db:
|
|
410
|
+
return Path(state_db).parent / "policy-counts.json"
|
|
411
|
+
return None
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Track `permissions`' registrations; the gate never depends on them, so a failed one only takes the screens away."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from ..api_errors import APIError
|
|
6
|
+
from ..api_routes import Response, Route
|
|
7
|
+
from . import outside
|
|
8
|
+
from . import policy as P
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _mode(value, runtime):
|
|
12
|
+
return P.validate_mode(value)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _store(runtime):
|
|
16
|
+
store = getattr(runtime, "permission_exceptions", None)
|
|
17
|
+
try:
|
|
18
|
+
return store() if callable(store) else None
|
|
19
|
+
except Exception:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def state(runtime):
|
|
24
|
+
"""Settings › Safety's state: the saved mode, each known app's standing, the approved apps and the allow rules."""
|
|
25
|
+
settings = P.from_env()
|
|
26
|
+
store = _store(runtime)
|
|
27
|
+
apps = []
|
|
28
|
+
try:
|
|
29
|
+
listed = runtime.apps() if runtime is not None else []
|
|
30
|
+
except Exception:
|
|
31
|
+
listed = []
|
|
32
|
+
seen = set()
|
|
33
|
+
for app in listed:
|
|
34
|
+
bundle = app.get("bundleId")
|
|
35
|
+
if not isinstance(bundle, str) or bundle in seen:
|
|
36
|
+
continue
|
|
37
|
+
seen.add(bundle)
|
|
38
|
+
apps.append({"bundleId": bundle, "name": app.get("name") or bundle, "installed": app.get("installed"),
|
|
39
|
+
**settings.apps.status(bundle, store)})
|
|
40
|
+
try:
|
|
41
|
+
remembered = store.public() if store is not None else []
|
|
42
|
+
except Exception:
|
|
43
|
+
remembered = []
|
|
44
|
+
approved = [{"bundleId": item["app"], "name": item.get("appName") or item["app"], "addedAt": item["addedAt"]}
|
|
45
|
+
for item in remembered if item.get("op") == P.APP_OP]
|
|
46
|
+
return {**settings.public(), "modes": [{"id": m, "label": P.MODE_LABELS[m]} for m in P.MODES],
|
|
47
|
+
"apps": apps, "approvedApps": approved,
|
|
48
|
+
"exceptions": [_rule(item) for item in remembered if item.get("op") != P.APP_OP]}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _rule(item):
|
|
52
|
+
"""A "Don't ask again" entry as Settings › Safety lists it: an allow rule names its kind of action in words."""
|
|
53
|
+
from .rules import RULE_OP, RULE_WORDS
|
|
54
|
+
if item.get("op") == RULE_OP:
|
|
55
|
+
words = RULE_WORDS.get(item.get("label") or "", item.get("label") or "")
|
|
56
|
+
return {**item, "words": words[:1].upper() + words[1:]}
|
|
57
|
+
return item
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_permissions(request):
|
|
61
|
+
return Response.json(state(request.runtime))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def post_permissions(request):
|
|
65
|
+
# api_http.APP_ONLY_ROUTES: with the Mac app running, only its session reaches here.
|
|
66
|
+
runtime = request.runtime
|
|
67
|
+
body = request.body or {}
|
|
68
|
+
if isinstance(body, dict) and set(body) == {"forget"}:
|
|
69
|
+
# A "Don't ask again" exception the person takes back.
|
|
70
|
+
if not isinstance(body["forget"], str) or not callable(getattr(runtime, "permission_exceptions", None)):
|
|
71
|
+
raise ValueError("forget takes an exception's key")
|
|
72
|
+
runtime.permission_exceptions().remove(body["forget"])
|
|
73
|
+
return Response.json(state(runtime))
|
|
74
|
+
if isinstance(body, dict) and len(body) == 1 and set(body) <= {"allow", "revoke"}:
|
|
75
|
+
# An app approved ahead of its first use, or switched off (it asks again the next time a task needs it).
|
|
76
|
+
(key, value), = body.items()
|
|
77
|
+
bundle = P.validate_ids([value], key)[0]
|
|
78
|
+
store = _store(runtime)
|
|
79
|
+
if store is None:
|
|
80
|
+
raise ValueError("Approving apps needs the Mobster service")
|
|
81
|
+
if key == "allow":
|
|
82
|
+
names = {a.get("bundleId"): a.get("name") for a in (runtime.apps() or []) if isinstance(a, dict)}
|
|
83
|
+
store.add(bundle, P.APP_LABEL, P.APP_OP, names.get(bundle) or bundle)
|
|
84
|
+
else:
|
|
85
|
+
from .exceptions import key_of
|
|
86
|
+
store.remove(key_of(bundle, P.APP_LABEL, P.APP_OP))
|
|
87
|
+
return Response.json(state(runtime))
|
|
88
|
+
changes = P.changes_for(body, P.from_env())
|
|
89
|
+
env_file = (runtime.setup.env_file if getattr(runtime, "setup", None) else
|
|
90
|
+
getattr(getattr(runtime, "config", None), "env_file", None))
|
|
91
|
+
if env_file:
|
|
92
|
+
from ..env_secrets import update_env_file
|
|
93
|
+
update_env_file(env_file, changes)
|
|
94
|
+
os.environ.update(changes)
|
|
95
|
+
return Response.json(state(runtime))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def create_approval(request):
|
|
99
|
+
item = outside.REGISTRY.create(request.body or {})
|
|
100
|
+
return Response.json({"approval": outside.public(item)}, 201)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def list_approvals(request):
|
|
104
|
+
since = (request.query.get("since") or [None])[0]
|
|
105
|
+
wait = float((request.query.get("wait") or ["0"])[0] or 0)
|
|
106
|
+
version, items = outside.REGISTRY.pending(int(since) if since and since.isdigit() else None, wait=wait)
|
|
107
|
+
return Response.json({"version": version, "approvals": [outside.public(i) for i in items]})
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_approval(request):
|
|
111
|
+
wait = float((request.query.get("wait") or ["0"])[0] or 0)
|
|
112
|
+
item = outside.REGISTRY.get(request.params["id"], wait=wait)
|
|
113
|
+
if item is None:
|
|
114
|
+
raise APIError("No such approval", 404, "approval_not_found")
|
|
115
|
+
return Response.json({"approval": outside.public(item)})
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def answer_approval(request):
|
|
119
|
+
# api_http.APP_ONLY_PATTERNS: an outside agent can raise the card; only the person in the Mac app answers it.
|
|
120
|
+
body = request.body or {}
|
|
121
|
+
if set(body) - {"approve", "instruction"} or "approve" not in body:
|
|
122
|
+
raise ValueError("An answer is approve: true or false (and optionally an instruction with false)")
|
|
123
|
+
if not outside.REGISTRY.answer(request.params["id"], body["approve"], body.get("instruction")):
|
|
124
|
+
raise APIError("This approval request is no longer waiting", 409, "approval_not_pending")
|
|
125
|
+
return Response.json({"ok": True})
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cancel_approval(request):
|
|
129
|
+
return Response.json({"ok": outside.REGISTRY.cancel(request.params["id"])})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
ROUTES = (
|
|
133
|
+
Route("permissions", "GET", r"/api/permissions", get_permissions, body="none"),
|
|
134
|
+
Route("permissions", "POST", r"/api/permissions", post_permissions),
|
|
135
|
+
Route("permissions", "GET", r"/api/approvals", list_approvals, body="none"),
|
|
136
|
+
Route("permissions", "POST", r"/api/approvals", create_approval),
|
|
137
|
+
Route("permissions", "GET", r"/api/approvals/{id}", get_approval, body="none"),
|
|
138
|
+
Route("permissions", "POST", r"/api/approvals/{id}", answer_approval),
|
|
139
|
+
Route("permissions", "DELETE", r"/api/approvals/{id}", cancel_approval, body="none"),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def register(api):
|
|
144
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
145
|
+
from .. import api_routes
|
|
146
|
+
api.register_run_field("permissionMode", _mode, public=True)
|
|
147
|
+
for route in ROUTES:
|
|
148
|
+
api_routes.register(route)
|