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,267 @@
|
|
|
1
|
+
"""`mobster completion zsh|bash|fish`: tab completion, generated from the command line's own parser.
|
|
2
|
+
|
|
3
|
+
The script is written from ``build_parser()`` when you ask for it, so it always matches this build's commands and
|
|
4
|
+
options, and nothing here costs another command any start-up time. Device names, past task IDs and conversation
|
|
5
|
+
IDs complete from `mobster completion --values devices|tasks|threads`, which reads the same lists `mobster devices`
|
|
6
|
+
and `mobster history` do.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
VALUE_SOURCES = {"device": "devices", "resume": "tasks", "thread": "threads"}
|
|
14
|
+
PATH_DESTS = {"env_file", "out", "output", "junit", "html", "app", "check", "state_db", "data_dir"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run(args):
|
|
18
|
+
if getattr(args, "values", None):
|
|
19
|
+
return print_values(args.values)
|
|
20
|
+
if not args.shell:
|
|
21
|
+
print("mobster completion: name a shell: zsh, bash or fish. Example: mobster completion zsh",
|
|
22
|
+
file=sys.stderr)
|
|
23
|
+
return 2
|
|
24
|
+
from .cli_parser import build_parser
|
|
25
|
+
tree = describe(build_parser())
|
|
26
|
+
text = {"zsh": zsh, "bash": bash, "fish": fish}[args.shell](tree)
|
|
27
|
+
try:
|
|
28
|
+
sys.stdout.write(text)
|
|
29
|
+
sys.stdout.flush()
|
|
30
|
+
except BrokenPipeError:
|
|
31
|
+
pass
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def print_values(kind):
|
|
36
|
+
"""One value per line, for the scripts' dynamic completion. Never fails loudly: a shell is waiting."""
|
|
37
|
+
try:
|
|
38
|
+
if kind == "devices":
|
|
39
|
+
from . import devices
|
|
40
|
+
for item in devices.discover(probe=False):
|
|
41
|
+
print(item["name"])
|
|
42
|
+
elif kind == "tasks":
|
|
43
|
+
from .history import read_history
|
|
44
|
+
from .paths import default_history_db
|
|
45
|
+
for record in read_history(default_history_db(), 30):
|
|
46
|
+
if record.get("id"):
|
|
47
|
+
print(record["id"])
|
|
48
|
+
elif kind == "threads":
|
|
49
|
+
import json
|
|
50
|
+
|
|
51
|
+
from .threads.recent import path
|
|
52
|
+
try:
|
|
53
|
+
for value in json.loads(path().read_text()).values():
|
|
54
|
+
print(value)
|
|
55
|
+
except (OSError, ValueError):
|
|
56
|
+
pass
|
|
57
|
+
except Exception: # noqa: BLE001
|
|
58
|
+
pass
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _visible(action):
|
|
63
|
+
return action.help != argparse.SUPPRESS
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def describe(parser):
|
|
67
|
+
"""{"options": [...], "commands": {name: {"help", "options", "commands", "positionals"}}} from a parser."""
|
|
68
|
+
from .help_topics import visible
|
|
69
|
+
|
|
70
|
+
def options(target):
|
|
71
|
+
out = []
|
|
72
|
+
for action in target._actions:
|
|
73
|
+
if not action.option_strings or not _visible(action):
|
|
74
|
+
continue
|
|
75
|
+
takes = action.nargs != 0 and not isinstance(action, (argparse._StoreTrueAction,
|
|
76
|
+
argparse._StoreFalseAction,
|
|
77
|
+
argparse._HelpAction, argparse._VersionAction,
|
|
78
|
+
argparse._CountAction))
|
|
79
|
+
source = VALUE_SOURCES.get(action.dest)
|
|
80
|
+
if takes and action.choices:
|
|
81
|
+
source = "choices:" + " ".join(str(c) for c in action.choices)
|
|
82
|
+
elif takes and source is None and (action.dest in PATH_DESTS or action.type is not None and
|
|
83
|
+
getattr(action.type, "__name__", "") == "path_type"):
|
|
84
|
+
source = "files"
|
|
85
|
+
out.append({"flags": list(action.option_strings), "help": action.help or "", "takes": takes,
|
|
86
|
+
"metavar": (action.metavar if isinstance(action.metavar, str) else None)
|
|
87
|
+
or action.dest.upper(), "source": source})
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
def node(target, top=False):
|
|
91
|
+
subs = next((a for a in target._actions if isinstance(a, argparse._SubParsersAction)), None)
|
|
92
|
+
commands = {}
|
|
93
|
+
if subs is not None:
|
|
94
|
+
helps = {a.dest: a.help for a in subs._choices_actions}
|
|
95
|
+
for name, child in subs.choices.items():
|
|
96
|
+
if top and not visible(name):
|
|
97
|
+
continue
|
|
98
|
+
if name not in helps and not top:
|
|
99
|
+
continue
|
|
100
|
+
entry = node(child)
|
|
101
|
+
entry["help"] = helps.get(name) or ""
|
|
102
|
+
commands[name] = entry
|
|
103
|
+
positionals = [a for a in target._actions if not a.option_strings and _visible(a)
|
|
104
|
+
and not isinstance(a, argparse._SubParsersAction)]
|
|
105
|
+
choices = []
|
|
106
|
+
for action in positionals:
|
|
107
|
+
if action.choices:
|
|
108
|
+
choices += [str(c) for c in action.choices]
|
|
109
|
+
return {"options": options(target), "commands": commands, "choices": choices}
|
|
110
|
+
tree = node(parser, top=True)
|
|
111
|
+
# The commands in the order `mobster --help` lists them, then any a private build adds.
|
|
112
|
+
from .help_topics import GROUPS, MORE
|
|
113
|
+
order = [name for _, names in GROUPS for name in names] + list(MORE)
|
|
114
|
+
tree["commands"] = dict(sorted(tree["commands"].items(),
|
|
115
|
+
key=lambda item: order.index(item[0]) if item[0] in order else len(order)))
|
|
116
|
+
return tree
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _zsh_text(text):
|
|
120
|
+
# Inside zsh's single quotes nothing escapes a quote, so an apostrophe is written as ’.
|
|
121
|
+
return re.sub(r"([\[\]:\\])", r"\\\1", text.replace("`", "").replace("'", "’")).replace("\n", " ")[:120]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _zsh_option(option):
|
|
125
|
+
flags = option["flags"]
|
|
126
|
+
help_text = _zsh_text(option["help"])
|
|
127
|
+
action = ""
|
|
128
|
+
if option["takes"]:
|
|
129
|
+
source = option["source"]
|
|
130
|
+
if source == "files":
|
|
131
|
+
completer = "_files"
|
|
132
|
+
elif source and source.startswith("choices:"):
|
|
133
|
+
completer = "(" + source.split(":", 1)[1] + ")"
|
|
134
|
+
elif source in ("devices", "tasks", "threads"):
|
|
135
|
+
completer = f"_mobster_values {source}"
|
|
136
|
+
else:
|
|
137
|
+
completer = " "
|
|
138
|
+
action = f":{_zsh_text(option['metavar'].lower())}:{completer}"
|
|
139
|
+
if len(flags) > 1:
|
|
140
|
+
exclusive = " ".join(flags)
|
|
141
|
+
return f"'({exclusive})'{{{','.join(flags)}}}'[{help_text}]{action}'"
|
|
142
|
+
return f"'{flags[0]}[{help_text}]{action}'"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def zsh(tree):
|
|
146
|
+
lines = ["#compdef mobster", "# mobster completion zsh: generated from this build's parser.", "",
|
|
147
|
+
"_mobster_values() {", " local -a values",
|
|
148
|
+
' values=("${(@f)$(mobster completion --values $1 2>/dev/null)}")',
|
|
149
|
+
" compadd -a values", "}", ""]
|
|
150
|
+
|
|
151
|
+
def function(name, entry):
|
|
152
|
+
commands = entry["commands"]
|
|
153
|
+
body = [f"{name}() {{", " local curcontext=\"$curcontext\" state line", " typeset -A opt_args"]
|
|
154
|
+
specs = [_zsh_option(option) for option in entry["options"]]
|
|
155
|
+
if commands:
|
|
156
|
+
body.append(" local -a commands")
|
|
157
|
+
body.append(" commands=(")
|
|
158
|
+
for sub, child in commands.items():
|
|
159
|
+
body.append(f" '{sub}:{_zsh_text(child.get('help') or '')}'")
|
|
160
|
+
body.append(" )")
|
|
161
|
+
specs += ["'1: :->command'", "'*:: :->args'"]
|
|
162
|
+
elif entry["choices"]:
|
|
163
|
+
specs.append(f"'1: :({' '.join(entry['choices'])})'")
|
|
164
|
+
specs.append("'*:argument:_default'")
|
|
165
|
+
else:
|
|
166
|
+
specs.append("'*:argument:_default'")
|
|
167
|
+
body.append(" _arguments -C -s \\")
|
|
168
|
+
body += [f" {spec} \\" for spec in specs[:-1]] + [f" {specs[-1]}"]
|
|
169
|
+
if commands:
|
|
170
|
+
body.append(" case $state in")
|
|
171
|
+
body.append(" command) _describe -t commands 'mobster command' commands ;;")
|
|
172
|
+
body.append(" args)")
|
|
173
|
+
body.append(" case $line[1] in")
|
|
174
|
+
for sub in commands:
|
|
175
|
+
body.append(f" {sub}) {name}_{sub.replace('-', '_')} ;;")
|
|
176
|
+
body.append(" esac ;;")
|
|
177
|
+
body.append(" esac")
|
|
178
|
+
body.append("}")
|
|
179
|
+
body.append("")
|
|
180
|
+
out = body
|
|
181
|
+
for sub, child in commands.items():
|
|
182
|
+
out += function(f"{name}_{sub.replace('-', '_')}", child)
|
|
183
|
+
return out
|
|
184
|
+
lines += function("_mobster", tree)
|
|
185
|
+
lines += ['if [ "$funcstack[1]" = "_mobster" ]; then', ' _mobster "$@"', "else",
|
|
186
|
+
" compdef _mobster mobster", "fi", ""]
|
|
187
|
+
return "\n".join(lines)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def bash(tree):
|
|
191
|
+
lines = ["# mobster completion bash: generated from this build's parser.", "_mobster() {",
|
|
192
|
+
" local cur prev words cword", " cur=\"${COMP_WORDS[COMP_CWORD]}\"",
|
|
193
|
+
" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"",
|
|
194
|
+
" local command=\"\" sub=\"\"", " local i",
|
|
195
|
+
" for ((i = 1; i < COMP_CWORD; i++)); do",
|
|
196
|
+
" case \"${COMP_WORDS[i]}\" in -*) ;; *) if [ -z \"$command\" ]; then command=\"${COMP_WORDS[i]}\"; "
|
|
197
|
+
"elif [ -z \"$sub\" ]; then sub=\"${COMP_WORDS[i]}\"; fi ;; esac", " done",
|
|
198
|
+
" case \"$prev\" in",
|
|
199
|
+
" --device) COMPREPLY=($(compgen -W \"$(mobster completion --values devices 2>/dev/null)\" -- "
|
|
200
|
+
"\"$cur\")); return ;;",
|
|
201
|
+
" --resume) COMPREPLY=($(compgen -W \"$(mobster completion --values tasks 2>/dev/null)\" -- \"$cur\"));"
|
|
202
|
+
" return ;;",
|
|
203
|
+
" --thread) COMPREPLY=($(compgen -W \"$(mobster completion --values threads 2>/dev/null)\" -- "
|
|
204
|
+
"\"$cur\")); return ;;",
|
|
205
|
+
" --env-file|--out|--output|--junit|--html) COMPREPLY=($(compgen -f -- \"$cur\")); return ;;",
|
|
206
|
+
" esac", " local words_for=\"\""]
|
|
207
|
+
top = " ".join(list(tree["commands"]) + [f for o in tree["options"] for f in o["flags"]])
|
|
208
|
+
lines.append(" if [ -z \"$command\" ]; then words_for=\"" + top + "\"")
|
|
209
|
+
lines.append(" else")
|
|
210
|
+
lines.append(" case \"$command\" in")
|
|
211
|
+
for name, entry in tree["commands"].items():
|
|
212
|
+
flags = " ".join(f for o in entry["options"] for f in o["flags"])
|
|
213
|
+
subs = " ".join(list(entry["commands"]) + entry["choices"])
|
|
214
|
+
if entry["commands"]:
|
|
215
|
+
lines.append(f" {name}) if [ -z \"$sub\" ]; then words_for=\"{subs} {flags}\"; else case \"$sub\" in")
|
|
216
|
+
for sub, child in entry["commands"].items():
|
|
217
|
+
child_flags = " ".join(f for o in child["options"] for f in o["flags"])
|
|
218
|
+
lines.append(f" {sub}) words_for=\"{' '.join(child['choices'])} {child_flags}\" ;;")
|
|
219
|
+
lines.append(" esac; fi ;;")
|
|
220
|
+
else:
|
|
221
|
+
lines.append(f" {name}) words_for=\"{subs} {flags}\" ;;")
|
|
222
|
+
lines += [" esac", " fi", " COMPREPLY=($(compgen -W \"$words_for\" -- \"$cur\"))", "}",
|
|
223
|
+
"complete -o default -F _mobster mobster", ""]
|
|
224
|
+
return "\n".join(lines)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _fish_text(text):
|
|
228
|
+
return text.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ")[:120]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def fish(tree):
|
|
232
|
+
lines = ["# mobster completion fish: generated from this build's parser.", "complete -c mobster -f"]
|
|
233
|
+
names = " ".join(tree["commands"])
|
|
234
|
+
for name, entry in tree["commands"].items():
|
|
235
|
+
lines.append(f"complete -c mobster -n 'not __fish_seen_subcommand_from {names}' -a {name} "
|
|
236
|
+
f"-d '{_fish_text(entry.get('help') or '')}'")
|
|
237
|
+
|
|
238
|
+
def option_lines(condition, options):
|
|
239
|
+
out = []
|
|
240
|
+
for option in options:
|
|
241
|
+
parts = []
|
|
242
|
+
for flag in option["flags"]:
|
|
243
|
+
parts.append(f"-l {flag[2:]}" if flag.startswith("--") else f"-s {flag[1:]}")
|
|
244
|
+
extra = ""
|
|
245
|
+
if option["takes"]:
|
|
246
|
+
source = option["source"]
|
|
247
|
+
if source == "files":
|
|
248
|
+
extra = " -r -F"
|
|
249
|
+
elif source and source.startswith("choices:"):
|
|
250
|
+
extra = f" -x -a '{source.split(':', 1)[1]}'"
|
|
251
|
+
elif source in ("devices", "tasks", "threads"):
|
|
252
|
+
extra = f" -x -a '(mobster completion --values {source} 2>/dev/null)'"
|
|
253
|
+
else:
|
|
254
|
+
extra = " -x"
|
|
255
|
+
out.append(f"complete -c mobster -n '{condition}' {' '.join(parts)}{extra} "
|
|
256
|
+
f"-d '{_fish_text(option['help'])}'")
|
|
257
|
+
return out
|
|
258
|
+
lines += option_lines(f"not __fish_seen_subcommand_from {names}", tree["options"])
|
|
259
|
+
for name, entry in tree["commands"].items():
|
|
260
|
+
condition = f"__fish_seen_subcommand_from {name}"
|
|
261
|
+
lines += option_lines(condition, entry["options"])
|
|
262
|
+
for sub, child in entry["commands"].items():
|
|
263
|
+
lines.append(f"complete -c mobster -n '{condition}' -a {sub} -d '{_fish_text(child.get('help') or '')}'")
|
|
264
|
+
lines += option_lines(f"{condition}; and __fish_seen_subcommand_from {sub}", child["options"])
|
|
265
|
+
if entry["choices"]:
|
|
266
|
+
lines.append(f"complete -c mobster -n '{condition}' -a '{' '.join(entry['choices'])}'")
|
|
267
|
+
return "\n".join(lines) + "\n"
|
mobile_agent/compose.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Composition root: the driver for a run target, and best-effort shutdown."""
|
|
2
|
+
|
|
3
|
+
from . import harness_api
|
|
4
|
+
from .drivers import build_driver
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_target_driver(*, wda_url=None, session=None, expected_bundle=None, connection=None, app_names=None,
|
|
8
|
+
labeller=None, icon_memory=None, **options):
|
|
9
|
+
if connection == "mirroring":
|
|
10
|
+
from . import mirroring
|
|
11
|
+
if not mirroring.enabled():
|
|
12
|
+
raise ValueError(f"The iPhone Mirroring connection is experimental: set {mirroring.ENV_FLAG}=1 to use it")
|
|
13
|
+
return build_driver("mirroring", app_names=app_names, labeller=labeller, icon_memory=icon_memory)
|
|
14
|
+
if connection == "cable_bt":
|
|
15
|
+
from . import cable_bt
|
|
16
|
+
if not cable_bt.enabled():
|
|
17
|
+
raise ValueError(f"The Cable + Bluetooth connection is experimental: set {cable_bt.ENV_FLAG}=1 to use it")
|
|
18
|
+
return build_driver("cable_bt", app_names=app_names, labeller=labeller, icon_memory=icon_memory, **options)
|
|
19
|
+
if connection not in (None, "wda"):
|
|
20
|
+
raise ValueError(f"Unknown connection {connection!r}")
|
|
21
|
+
if wda_url:
|
|
22
|
+
driver = build_driver("wda", url=wda_url, session=session)
|
|
23
|
+
driver.configure()
|
|
24
|
+
return driver
|
|
25
|
+
options = {key: value for key, value in options.items() if value is not None}
|
|
26
|
+
target_driver = harness_api.hooks().target_driver
|
|
27
|
+
if options and target_driver is not None:
|
|
28
|
+
return target_driver(expected_bundle=expected_bundle, **options)
|
|
29
|
+
raise ValueError("A run needs --wda-url (and optionally --session)")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _closer(client, name):
|
|
33
|
+
try:
|
|
34
|
+
return getattr(client, name, None)
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def close_all(*clients):
|
|
40
|
+
for client in clients:
|
|
41
|
+
if client is None:
|
|
42
|
+
continue
|
|
43
|
+
for closer in (_closer(client, "close"), _closer(_closer(client, "http"), "close")):
|
|
44
|
+
if callable(closer):
|
|
45
|
+
try:
|
|
46
|
+
closer()
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
mobile_agent/config.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Typed run budgets."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnvFile(NamedTuple):
|
|
8
|
+
"""What ``load_env_file`` did."""
|
|
9
|
+
|
|
10
|
+
loaded: list
|
|
11
|
+
warnings: list
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def missing_env_file(path):
|
|
15
|
+
"""The warning for an env file that doesn't exist: one sentence naming it (~ for the home folder)."""
|
|
16
|
+
shown = str(path)
|
|
17
|
+
home = str(Path.home())
|
|
18
|
+
if shown == home or shown.startswith(home + "/"):
|
|
19
|
+
shown = "~" + shown[len(home):]
|
|
20
|
+
return f"{shown} does not exist; no keys or settings were loaded from it"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_env_file(path, missing_ok=False):
|
|
24
|
+
"""Load KEY=VALUE lines into the environment. Variables already set win; a bad line is a warning, never a stop."""
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
from . import env_secrets
|
|
28
|
+
values, warnings = _parse_env_file(path, missing_ok)
|
|
29
|
+
loaded = []
|
|
30
|
+
# Settings can choose the secret store (MOBSTER_VAULT=file). App secrets come only from its locked read,
|
|
31
|
+
# never this plaintext snapshot, which may precede a key replacement or logout.
|
|
32
|
+
for key, value in values.items():
|
|
33
|
+
if key not in env_secrets.SECRET_NAMES and key not in os.environ:
|
|
34
|
+
os.environ[key] = value
|
|
35
|
+
loaded.append(key)
|
|
36
|
+
secrets, problems = env_secrets.secrets_for(path, values)
|
|
37
|
+
warnings += problems
|
|
38
|
+
if not env_secrets.applies(path):
|
|
39
|
+
secrets = {key: value for key, value in values.items() if key in env_secrets.SECRET_NAMES}
|
|
40
|
+
for key, value in secrets.items():
|
|
41
|
+
if key not in os.environ:
|
|
42
|
+
os.environ[key] = value
|
|
43
|
+
loaded.append(key)
|
|
44
|
+
return EnvFile(loaded, warnings)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_env_values(path):
|
|
48
|
+
"""{NAME: value} for the file's valid lines, without touching the environment ({} when it can't be read)."""
|
|
49
|
+
return _parse_env_file(path, missing_ok=True)[0]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_env_file(path, missing_ok):
|
|
53
|
+
import re
|
|
54
|
+
values, warnings = {}, []
|
|
55
|
+
name = Path(path).name
|
|
56
|
+
try:
|
|
57
|
+
data = Path(path).read_bytes()
|
|
58
|
+
except FileNotFoundError:
|
|
59
|
+
if not missing_ok:
|
|
60
|
+
warnings.append(missing_env_file(path))
|
|
61
|
+
return values, warnings
|
|
62
|
+
except OSError as error:
|
|
63
|
+
warnings.append(f"{name} could not be read ({type(error).__name__}); no settings were loaded from it")
|
|
64
|
+
return values, warnings
|
|
65
|
+
for number, raw in enumerate(data.removeprefix(b"\xef\xbb\xbf").splitlines(), 1):
|
|
66
|
+
try:
|
|
67
|
+
line = raw.decode("utf-8").strip()
|
|
68
|
+
except UnicodeDecodeError:
|
|
69
|
+
warnings.append(f"{name} line {number} is not UTF-8 text; skipped it")
|
|
70
|
+
continue
|
|
71
|
+
if not line or line.startswith("#"):
|
|
72
|
+
continue
|
|
73
|
+
if "=" not in line:
|
|
74
|
+
warnings.append(f"{name} line {number} is not NAME=value; skipped it")
|
|
75
|
+
continue
|
|
76
|
+
key, value = (part.strip() for part in line.split("=", 1))
|
|
77
|
+
key = key.removeprefix("export ").strip()
|
|
78
|
+
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", key):
|
|
79
|
+
warnings.append(f"{name} line {number} has a name with characters other than letters, digits "
|
|
80
|
+
"and _; skipped it")
|
|
81
|
+
continue
|
|
82
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
|
|
83
|
+
value = value[1:-1]
|
|
84
|
+
if "\x00" in value:
|
|
85
|
+
warnings.append(f"{name} line {number} contains a null character; skipped it")
|
|
86
|
+
continue
|
|
87
|
+
values.setdefault(key, value) # the first line for a name wins, as it did when each line set the environment
|
|
88
|
+
return values, warnings
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Where each key and setting this process loaded came from: {name: "env-file" | "mac-app"}. A name set in the shell
|
|
92
|
+
# is not here. doctor and `mobster login status` read it to say "from Mobster for Mac".
|
|
93
|
+
SOURCES = {}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def app_env_file():
|
|
97
|
+
"""The file Mobster for Mac (and `mobster login`) keep keys and settings in: agent.env in the data folder, 0600."""
|
|
98
|
+
from .paths import user_data_dir
|
|
99
|
+
return user_data_dir() / "agent.env"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def load_env_layers(env_file=None):
|
|
103
|
+
"""Load an explicit --env-file, then the keys Mobster for Mac saved, never over a variable already set."""
|
|
104
|
+
warnings = []
|
|
105
|
+
if env_file:
|
|
106
|
+
loaded = load_env_file(env_file)
|
|
107
|
+
SOURCES.update({name: "env-file" for name in loaded.loaded})
|
|
108
|
+
warnings += loaded.warnings
|
|
109
|
+
path = app_env_file()
|
|
110
|
+
if env_file is None or Path(env_file).expanduser().resolve() != path.resolve():
|
|
111
|
+
loaded = load_env_file(path, missing_ok=True)
|
|
112
|
+
SOURCES.update({name: "mac-app" for name in loaded.loaded})
|
|
113
|
+
# The app's own file is never the user's to fix line by line: its bad lines are skipped quietly.
|
|
114
|
+
return warnings
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Connectors: the agent uses tools of other services (MCP servers) during a task."""
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""What the connectors that answer MCP calls with a service's own API share (graph_api.py, slack_api.py): the tool
|
|
2
|
+
descriptor, the argument checks, ``Refusal``, and the session surface the connector service drives (client.Session's:
|
|
3
|
+
initialize, ping, list_tools, call_tool, close)."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from ..outlets import net
|
|
9
|
+
from . import client as mcp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Refusal:
|
|
13
|
+
"""The service (or the arguments) said no before anything happened: the tool's error result."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message):
|
|
16
|
+
self.message = message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def tool_spec(name, title, description, properties, required=(), read_only=True):
|
|
20
|
+
return {"name": name, "title": title, "description": description,
|
|
21
|
+
"inputSchema": {"type": "object", "properties": properties, "required": list(required),
|
|
22
|
+
"additionalProperties": False},
|
|
23
|
+
"annotations": {"title": title, "readOnlyHint": read_only, "destructiveHint": False,
|
|
24
|
+
"openWorldHint": not read_only}}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def count_schema(top, default):
|
|
28
|
+
return {"type": "integer", "minimum": 1, "maximum": top, "description": f"At most {top}; {default} if left out."}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def short(value, limit):
|
|
32
|
+
return str(value)[:limit] if value is not None else None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def required_text(value, field, limit):
|
|
36
|
+
if not isinstance(value, str) or not value.strip():
|
|
37
|
+
raise ValueError(f"{field} is required")
|
|
38
|
+
if len(value) > limit:
|
|
39
|
+
raise ValueError(f"{field} is longer than {limit} characters")
|
|
40
|
+
return value.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ApiSession:
|
|
44
|
+
"""A session over a service's API."""
|
|
45
|
+
|
|
46
|
+
NAME = TOOLS = None
|
|
47
|
+
|
|
48
|
+
def __init__(self, token, name=None, call=None):
|
|
49
|
+
self.token, self.name = token, name or self.NAME
|
|
50
|
+
self.call = call or net.call
|
|
51
|
+
self.ready, self.tools_changed = False, False
|
|
52
|
+
self.server_info, self.instructions = {}, ""
|
|
53
|
+
|
|
54
|
+
def list_tools(self, timeout=mcp.LIST_TIMEOUT):
|
|
55
|
+
return [json.loads(json.dumps(spec)) for spec in self.TOOLS]
|
|
56
|
+
|
|
57
|
+
def close(self):
|
|
58
|
+
self.ready = False
|
|
59
|
+
|
|
60
|
+
def call_tool(self, name, arguments, timeout=mcp.CALL_TIMEOUT):
|
|
61
|
+
"""The tool's result ({"content", "structuredContent"?, "isError"?}), as client.Session.call_tool."""
|
|
62
|
+
if not self.ready:
|
|
63
|
+
raise mcp.NotSent(f"{self.name} isn't connected.")
|
|
64
|
+
method = getattr(self, f"_{name}", None) if name in {t["name"] for t in self.TOOLS} else None
|
|
65
|
+
if method is None:
|
|
66
|
+
raise mcp.ServerError(f"{self.name} has no tool called {name}.")
|
|
67
|
+
arguments = arguments if isinstance(arguments, dict) else {}
|
|
68
|
+
deadline = time.monotonic() + timeout
|
|
69
|
+
try:
|
|
70
|
+
found = method(arguments, deadline)
|
|
71
|
+
except ValueError as error:
|
|
72
|
+
found = Refusal(str(error))
|
|
73
|
+
if isinstance(found, Refusal):
|
|
74
|
+
return {"content": [{"type": "text", "text": found.message}], "isError": True}
|
|
75
|
+
return {"content": [{"type": "text", "text": json.dumps(found, ensure_ascii=False)}],
|
|
76
|
+
"structuredContent": found}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""The page your browser shows when a sign-in comes back to this Mac (oauth.SignIn's listener): Mobster's app icon and
|
|
2
|
+
the service's own mark, joined by a dotted line, then one line that says what happened."""
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import html
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from .. import brand
|
|
10
|
+
from ..brand_fonts import FONT_FACES
|
|
11
|
+
from .marks import DARK, LIGHT, NAMES
|
|
12
|
+
|
|
13
|
+
APP_LINK = "mobster://connectors"
|
|
14
|
+
BACK = "Mobster is back in front. You can close this tab."
|
|
15
|
+
# The success page waits for its line to finish drawing (about 600 ms) before Mobster comes forward.
|
|
16
|
+
HAND_BACK = (f'setTimeout(function () {{ location.href = "{APP_LINK}" }}, '
|
|
17
|
+
f'matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 900)')
|
|
18
|
+
|
|
19
|
+
# Mobster's app icon, drawn from the mark (brand.MARK): the Night tile, the felt slabs and the violet row between them.
|
|
20
|
+
_SLABS = re.findall(r"M[^M]*", re.search(r' d="([^"]+)"', brand.MARK["light"]).group(1))
|
|
21
|
+
_TILE = ('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" fill="{night}"/>'
|
|
22
|
+
'<g transform="translate(256 256) scale(.7) translate(-256 -256)">{paths}</g></svg>').format(
|
|
23
|
+
night=brand.DARK["paper"],
|
|
24
|
+
paths="".join(f'<path d="{d}" fill="{brand.LIGHT["violet-solid"] if index == 1 else brand.LIGHT["sunken"]}"/>'
|
|
25
|
+
for index, d in enumerate(_SLABS)))
|
|
26
|
+
_SANS = "".join(face for face in re.findall(r"@font-face \{.*?\}", FONT_FACES) if '"Mobster Sans"' in face)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _data(svg):
|
|
30
|
+
return "data:image/svg+xml;base64," + base64.b64encode(svg.encode()).decode()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def mark_of(name):
|
|
34
|
+
"""The service's mark, by any name it goes by ("Google Drive", "monday.com"), as the tile's picture: the mark on the
|
|
35
|
+
tile's square (docs/brand/marks, scripts/gen_agent_marks.py), with its version for a dark page where it has one."""
|
|
36
|
+
key = NAMES.get(re.sub(r"[^a-z0-9]+", "-", (name or "").lower()).strip("-"))
|
|
37
|
+
if key is None:
|
|
38
|
+
return None
|
|
39
|
+
img = f'<img src="{_data(LIGHT[key])}" alt="">'
|
|
40
|
+
if key not in DARK:
|
|
41
|
+
return img
|
|
42
|
+
return f'<picture><source srcset="{_data(DARK[key])}" media="(prefers-color-scheme: dark)">{img}</picture>'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
STYLE = """
|
|
46
|
+
/* A desktop page at desktop widths, as Remote's: Mobster at the gutter (32, 16 on a phone), then one centred 640 px
|
|
47
|
+
column with a 32 px title; a phone gets the same column at its own width. */
|
|
48
|
+
body { margin: 0; min-height: 100vh; display: grid; grid-template-rows: auto 1fr; background: var(--paper);
|
|
49
|
+
color: var(--ink); font: 400 16px/24px var(--font-sans); -webkit-font-smoothing: antialiased; }
|
|
50
|
+
.top { display: flex; align-items: center; min-height: 72px; padding: 16px 32px; }
|
|
51
|
+
.brand { display: inline-flex; align-items: center; gap: 8px; font-size: 17px; font-weight: 500; letter-spacing: -.01em; }
|
|
52
|
+
.brand img { display: block; width: 28px; height: 28px; border-radius: 23%; }
|
|
53
|
+
main { place-self: center; display: grid; justify-items: center; width: min(640px, calc(100vw - 32px)); padding: 48px 0;
|
|
54
|
+
text-align: center; }
|
|
55
|
+
.link { display: flex; align-items: center; gap: 12px; margin-bottom: 32px; }
|
|
56
|
+
.tile { position: relative; flex: none; display: grid; place-items: center; width: 80px; height: 80px;
|
|
57
|
+
border-radius: 23%; overflow: hidden; background: var(--surface); color: var(--ink-2); font-size: 32px; font-weight: 500; }
|
|
58
|
+
.tile img, .tile picture { display: block; width: 100%; height: 100%; }
|
|
59
|
+
.tile::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px rgb(24 24 24 / .08); }
|
|
60
|
+
.run { display: flex; gap: 8px; }
|
|
61
|
+
.run i { width: 4px; height: 4px; border-radius: 50%; background: var(--ink-4); }
|
|
62
|
+
.knot { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; }
|
|
63
|
+
.knot svg { display: block; }
|
|
64
|
+
[data-state="ok"] .run i { background: var(--green); }
|
|
65
|
+
[data-state="ok"] .knot { background: var(--green); color: var(--paper); }
|
|
66
|
+
[data-state="error"] .knot { background: var(--coral); color: var(--paper); }
|
|
67
|
+
/* Not allowed: the line breaks where the check would be, and its last dots fade into the gap. */
|
|
68
|
+
[data-state="denied"] .knot, [data-state="pick"] .knot { visibility: hidden; }
|
|
69
|
+
[data-state="denied"] .run:first-of-type i:last-child, [data-state="denied"] .run:last-of-type i:first-child { opacity: .35; }
|
|
70
|
+
h1 { margin: 0; font-size: 32px; line-height: 40px; font-weight: 500; letter-spacing: -.025em; text-wrap: balance; }
|
|
71
|
+
p { margin: 8px auto 0; max-width: 60ch; color: var(--ink-2); text-wrap: pretty; }
|
|
72
|
+
/* The title and the line take the column's width, centred inside it, so a short one doesn't shrink the page. */
|
|
73
|
+
main > h1, main > p { justify-self: stretch; }
|
|
74
|
+
a { display: inline-flex; align-items: center; height: 40px; margin-top: 32px; padding: 0 16px; border-radius: 999px;
|
|
75
|
+
background: var(--surface); box-shadow: var(--shadow-button-secondary); color: var(--ink); font-weight: 500;
|
|
76
|
+
letter-spacing: -.011em; text-decoration: none; transition: background-color 150ms var(--ease-standard); }
|
|
77
|
+
a:hover { background: var(--card-hover); }
|
|
78
|
+
a:focus-visible { outline: 2px solid var(--violet-solid); outline-offset: 2px; }
|
|
79
|
+
@media (prefers-color-scheme: dark) { .tile::after { box-shadow: inset 0 0 0 1px rgb(255 255 255 / .1); } }
|
|
80
|
+
@media (max-width: 639px) { .top { padding: 16px; } h1 { font-size: 24px; line-height: 32px; letter-spacing: -.022em; }
|
|
81
|
+
.tile { width: 64px; height: 64px; font-size: 28px; } .link { margin-bottom: 24px; } }
|
|
82
|
+
/* Connected: the dots arrive from each icon toward the middle, 50 ms apart, then the check grows in and draws. */
|
|
83
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
84
|
+
[data-state="ok"] .run i { animation: dot 160ms var(--ease-out) both; }
|
|
85
|
+
[data-state="ok"] .run:first-of-type i:nth-child(2), [data-state="ok"] .run:last-of-type i:nth-child(3) { animation-delay: 50ms; }
|
|
86
|
+
[data-state="ok"] .run:first-of-type i:nth-child(3), [data-state="ok"] .run:last-of-type i:nth-child(2) { animation-delay: 100ms; }
|
|
87
|
+
[data-state="ok"] .run:first-of-type i:nth-child(4), [data-state="ok"] .run:last-of-type i:nth-child(1) { animation-delay: 150ms; }
|
|
88
|
+
[data-state="ok"] .knot { animation: knot 240ms var(--ease-out) 260ms both; }
|
|
89
|
+
[data-state="ok"] .knot path { stroke-dasharray: 16; animation: tick 200ms var(--ease-out) 400ms both; }
|
|
90
|
+
}
|
|
91
|
+
@keyframes dot { from { opacity: 0; transform: scale(.4); background: var(--ink-4); } }
|
|
92
|
+
@keyframes knot { from { opacity: 0; transform: scale(.6); } }
|
|
93
|
+
@keyframes tick { from { stroke-dashoffset: 16; } }
|
|
94
|
+
""" # noqa: E501
|
|
95
|
+
|
|
96
|
+
GLYPHS = {
|
|
97
|
+
"ok": '<path d="m7.5 12.5 3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" '
|
|
98
|
+
'stroke-linejoin="round"/>',
|
|
99
|
+
"error": '<path d="M12 7.5v5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'
|
|
100
|
+
'<circle cx="12" cy="16.25" r="1.25" fill="currentColor"/>',
|
|
101
|
+
"denied": "",
|
|
102
|
+
"pick": "",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render(state, name, message):
|
|
107
|
+
"""The page for ``state`` ("ok", "denied" or "error") about the connector called ``name``, with its mark when the
|
|
108
|
+
name is a service's (a preset keeps its service's name). ``name`` and ``message`` are never markup."""
|
|
109
|
+
if state == "ok":
|
|
110
|
+
message = BACK
|
|
111
|
+
title = {"ok": f"Connected to {name}", "denied": f"{name} isn’t connected"}.get(state,
|
|
112
|
+
f"Mobster couldn’t connect {name}")
|
|
113
|
+
return document(state, name, title, message, script=HAND_BACK if state == "ok" else None)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def document(state, name, title, message, *, script=None, nonce=None, actions="", style="", referrer="no-referrer"):
|
|
117
|
+
"""The page: Mobster's icon and ``name``'s mark joined in ``state``, then ``title`` and ``message`` (text, never
|
|
118
|
+
markup), ``actions`` (markup) beside Open Mobster, and ``script`` inline, with ``nonce`` when the page's CSP names
|
|
119
|
+
one."""
|
|
120
|
+
# The app's typography: a curly apostrophe, also in a message the service or the listener wrote.
|
|
121
|
+
title, message = (text.replace("'", "’") for text in (title, message))
|
|
122
|
+
theirs = mark_of(name) or html.escape((name or "?").strip()[:1].upper())
|
|
123
|
+
dots = '<span class="run">' + "<i></i>" * 4 + "</span>"
|
|
124
|
+
link = (f'<div class="link" aria-hidden="true"><span class="tile"><img src="{_data(_TILE)}" alt=""></span>{dots}'
|
|
125
|
+
'<span class="knot"><svg width="24" height="24" viewBox="0 0 24 '
|
|
126
|
+
f'24">{GLYPHS.get(state, GLYPHS["error"])}</svg></span>{dots}<span class="tile">{theirs}</span></div>')
|
|
127
|
+
attribute = f' nonce="{nonce}"' if nonce else ""
|
|
128
|
+
script = f"<script{attribute}>{script}</script>" if script else ""
|
|
129
|
+
back = f'<a href="{APP_LINK}">Open Mobster</a>'
|
|
130
|
+
return ('<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,'
|
|
131
|
+
'initial-scale=1"><meta name="color-scheme" content="light dark"><meta '
|
|
132
|
+
f'name="referrer" content="{referrer}">'
|
|
133
|
+
f"<title>{html.escape(title)}</title><style>{_SANS}{brand.STYLESHEET}{STYLE}{style}</style>"
|
|
134
|
+
f'<header class="top"><span class="brand"><img src="{_data(_TILE)}" alt="">Mobster</span></header>'
|
|
135
|
+
f'<main data-state="{state if state in GLYPHS else "error"}" '
|
|
136
|
+
f'aria-live="polite">{link}<h1>{html.escape(title)}</h1><p>{html.escape(message)}</p>'
|
|
137
|
+
f'{f"<div class=actions>{actions}{back}</div>" if actions else back}</main>'
|
|
138
|
+
f"{script}</html>")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def csp():
|
|
142
|
+
"""The page's Content-Security-Policy: nothing from outside, inline styles and data: pictures and fonts, and the
|
|
143
|
+
hand-back script by its hash."""
|
|
144
|
+
digest = base64.b64encode(hashlib.sha256(HAND_BACK.encode()).digest()).decode()
|
|
145
|
+
return ("default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src data:; script-src "
|
|
146
|
+
f"'sha256-{digest}'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'")
|