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,171 @@
|
|
|
1
|
+
"""Track `learned`'s registrations (tracks.load calls ``register(api)`` once per process, staged).
|
|
2
|
+
|
|
3
|
+
- The run listener that learns a saved workflow's path, and the pre-run hook that replays it (workflows.py).
|
|
4
|
+
- Saved programs (app_path.py): the listener that keeps a done Smart run whole, and the pre-run hook that hands the
|
|
5
|
+
live loop a program for a request with the same words and other values (``MOBSTER_PROGRAMS``: on by default for
|
|
6
|
+
Mobster for Mac, ``mobster serve``, ``phone_task`` through ``mobster mcp`` and Mobster Remote, ``off`` turns it off;
|
|
7
|
+
the store and matching are programs.py).
|
|
8
|
+
- ``GET /api/replay/workflows`` → ``{"learned": {workflowId: {...}}}``: which saved workflows run without the model,
|
|
9
|
+
for the Mac app's Workflows rows.
|
|
10
|
+
- ``POST /api/replay/workflows/{id}/forget`` → ``{"forgotten": n}``: the next run of that workflow uses the model
|
|
11
|
+
and learns again.
|
|
12
|
+
- ``GET /api/replay/programs`` → ``{"programs": [...]}``: the saved and taught programs (programs.public), newest
|
|
13
|
+
first, for the Programs list under Workflows. A taught program saved as a workflow names it in ``workflowIds``: a
|
|
14
|
+
workflow's "repeat the task you taught" step references that workflow.
|
|
15
|
+
- Run field ``practice`` (a taught program's id): Try it now. The run replays that program and asks about each step
|
|
16
|
+
first (approval kind ``practice``); the person's corrections change the program after the run.
|
|
17
|
+
- ``POST /api/replay/programs/{program}/rename`` ``{"name"}`` and ``POST /api/replay/programs/{program}/forget``
|
|
18
|
+
(``{program}``: the id ``GET`` gives, 16 hex, a dash, 8 hex). Forgetting a taught program also forgets what its
|
|
19
|
+
workflow learned, so its next run uses the model.
|
|
20
|
+
|
|
21
|
+
The check front end (replay_core) needs no registration: `mobster test` and its MCP tool call it through
|
|
22
|
+
verify/runner.py.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from ..replay_core.store import PROGRAM_ID
|
|
26
|
+
|
|
27
|
+
# After harness pick-up (0) and the packs and long-form hooks (1 to 5): a run being picked up goes first. A saved
|
|
28
|
+
# workflow's own learned path goes before a saved program.
|
|
29
|
+
PRE_RUN_ORDER = 20
|
|
30
|
+
PROGRAM_ORDER = 24
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def register(api):
|
|
34
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
35
|
+
from ..api_routes import Route
|
|
36
|
+
from ..api_routes import register as add_route
|
|
37
|
+
from .workflows import LearnListener, pre_run
|
|
38
|
+
api.register_run_listener(LearnListener())
|
|
39
|
+
api.register_pre_run(pre_run, order=PRE_RUN_ORDER)
|
|
40
|
+
from .app_path import ProgramListener
|
|
41
|
+
from .app_path import pre_run as program_pre_run
|
|
42
|
+
api.register_run_listener(ProgramListener())
|
|
43
|
+
api.register_pre_run(program_pre_run, order=PROGRAM_ORDER)
|
|
44
|
+
add_route(Route("learned", "GET", "/api/replay/workflows", learned_workflows, body="none"))
|
|
45
|
+
add_route(Route("learned", "POST", "/api/replay/workflows/{id}/forget", forget_workflow, body="none"))
|
|
46
|
+
api.register_run_field("practice", validate_practice, public=True)
|
|
47
|
+
add_route(Route("learned", "GET", "/api/replay/programs", list_programs, body="none"))
|
|
48
|
+
add_route(Route("learned", "POST", "/api/replay/programs/" + PROGRAM + "/rename", rename_program))
|
|
49
|
+
add_route(Route("learned", "POST", "/api/replay/programs/" + PROGRAM + "/forget", forget_program, body="none"))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
PROGRAM = f"(?P<program>{PROGRAM_ID.pattern})"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_practice(value, runtime):
|
|
56
|
+
"""``practice`` on POST /api/runs: a taught program's id."""
|
|
57
|
+
if not isinstance(value, str) or not PROGRAM_ID.fullmatch(value):
|
|
58
|
+
raise ValueError("practice is a taught program's id")
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _context(runtime):
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
from ..catalog import APPS
|
|
66
|
+
state_db = getattr(getattr(runtime, "config", None), "state_db", None)
|
|
67
|
+
data_dir = Path(state_db).parent if state_db else None
|
|
68
|
+
bundles = {app["id"]: app.get("bundleId") for app in APPS}
|
|
69
|
+
apps = getattr(runtime, "apps", None)
|
|
70
|
+
if callable(apps):
|
|
71
|
+
try:
|
|
72
|
+
bundles.update({app["id"]: app.get("bundleId") for app in apps()})
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
return data_dir, bundles
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _workflows(runtime):
|
|
79
|
+
try:
|
|
80
|
+
return runtime.workflows.list() if getattr(runtime, "workflows", None) is not None else []
|
|
81
|
+
except Exception: # noqa: BLE001 -- no saved workflows to link: the programs still list
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def learned_workflows(request):
|
|
86
|
+
from ..api_routes import Response
|
|
87
|
+
from .workflows import learned_for
|
|
88
|
+
runtime = request.runtime
|
|
89
|
+
data_dir, bundles = _context(runtime)
|
|
90
|
+
workflows = _workflows(runtime)
|
|
91
|
+
learned = learned_for(workflows, data_dir, bundles)
|
|
92
|
+
# A workflow saved from a lesson runs its taught program before it learns anything of its own.
|
|
93
|
+
for program in _programs(data_dir, workflows):
|
|
94
|
+
for workflow_id in program["workflowIds"]:
|
|
95
|
+
if workflow_id not in learned and program["taught"]:
|
|
96
|
+
learned[workflow_id] = {"steps": program["steps"], "learnedAt": (program["createdAt"] or 0) / 1000,
|
|
97
|
+
"relearnedAt": None, "replays": program["replays"],
|
|
98
|
+
"needsModelToAnswer": False, "liveSteps": 0, "approvals": program["asks"],
|
|
99
|
+
"last": None, "device": program["device"], "taught": True,
|
|
100
|
+
"programId": program["id"]}
|
|
101
|
+
return Response.json({"learned": learned})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _programs(data_dir, workflows):
|
|
105
|
+
from . import programs
|
|
106
|
+
if data_dir is None:
|
|
107
|
+
return []
|
|
108
|
+
store = programs.Programs(programs.programs_dir(data_dir))
|
|
109
|
+
out = []
|
|
110
|
+
for path in store.all():
|
|
111
|
+
listed = programs.public(path, workflows)
|
|
112
|
+
stored = path.get("workflowId")
|
|
113
|
+
if stored and stored not in listed["workflowIds"] and any(w.get("id") == stored for w in workflows):
|
|
114
|
+
listed["workflowIds"].insert(0, stored)
|
|
115
|
+
out.append(listed)
|
|
116
|
+
return out
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def list_programs(request):
|
|
120
|
+
from ..api_routes import Response
|
|
121
|
+
data_dir, _ = _context(request.runtime)
|
|
122
|
+
return Response.json({"programs": _programs(data_dir, _workflows(request.runtime))})
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _program(request):
|
|
126
|
+
from ..api_errors import APIError
|
|
127
|
+
from . import programs
|
|
128
|
+
data_dir, _ = _context(request.runtime)
|
|
129
|
+
if data_dir is None:
|
|
130
|
+
raise APIError("Mobster has no data folder with programs", 404, "program_not_found")
|
|
131
|
+
store = programs.Programs(programs.programs_dir(data_dir))
|
|
132
|
+
path = store.by_id(request.params["program"])
|
|
133
|
+
if path is None:
|
|
134
|
+
raise APIError("No saved program has that id. It may have been forgotten.", 404, "program_not_found")
|
|
135
|
+
return store, path, data_dir
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def rename_program(request):
|
|
139
|
+
from ..api_routes import Response
|
|
140
|
+
from . import programs
|
|
141
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
142
|
+
if set(body) != {"name"} or not isinstance(body["name"], str):
|
|
143
|
+
raise ValueError("Rename takes a name")
|
|
144
|
+
store, path, data_dir = _program(request)
|
|
145
|
+
renamed = store.rename(request.params["program"], body["name"])
|
|
146
|
+
return Response.json({"program": programs.public(renamed, _workflows(request.runtime))})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def forget_program(request):
|
|
150
|
+
from ..api_routes import Response
|
|
151
|
+
from . import programs
|
|
152
|
+
from .workflows import forget_for
|
|
153
|
+
store, path, data_dir = _program(request)
|
|
154
|
+
workflows = _workflows(request.runtime)
|
|
155
|
+
_, bundles = _context(request.runtime)
|
|
156
|
+
linked = set(programs.public(path, workflows)["workflowIds"]) | (
|
|
157
|
+
{path["workflowId"]} if path.get("workflowId") else set())
|
|
158
|
+
forgotten = bool(store.forget(path))
|
|
159
|
+
for workflow in workflows:
|
|
160
|
+
if workflow.get("id") in linked:
|
|
161
|
+
forget_for(workflow, data_dir, bundles)
|
|
162
|
+
return Response.json({"forgotten": forgotten})
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def forget_workflow(request):
|
|
166
|
+
from ..api_routes import Response
|
|
167
|
+
from .workflows import forget_for
|
|
168
|
+
runtime = request.runtime
|
|
169
|
+
data_dir, bundles = _context(runtime)
|
|
170
|
+
workflow = runtime.workflows.get(request.params["id"])
|
|
171
|
+
return Response.json({"forgotten": forget_for(workflow, data_dir, bundles) if data_dir else 0})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""How iOS's own moves work on each phone, kept in the learned store: which route opened an app (Spotlight, the App
|
|
2
|
+
Library, the Home Screen's pages), and whether Settings' search finds pages or the path has to be walked.
|
|
3
|
+
|
|
4
|
+
``ios.system`` asks a ``RouteMemory`` before it opens an app or a Settings page and tells it what worked, so the next
|
|
5
|
+
time goes straight to the route that worked on that phone (an app hidden from Spotlight goes straight to the App
|
|
6
|
+
Library). A route that stops working is forgotten at its first miss.
|
|
7
|
+
|
|
8
|
+
Each fact is one replay_core path (``kind`` "route", one live step naming the route) in the data folder's
|
|
9
|
+
``replays/routes/`` (0700, files 0600), keyed by the phone and the app: the same store as saved programs and learned
|
|
10
|
+
workflows, no second memory. No screen text is kept.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from ..replay_core.path import new_path
|
|
16
|
+
from ..replay_core.store import Cache, env_of
|
|
17
|
+
|
|
18
|
+
ROUTE_OP = "SYSTEM_ROUTE"
|
|
19
|
+
KNOWN = frozenset({"bundle", "spotlight", "app_library", "home_pages", "search", "path"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def routes_dir(data_dir):
|
|
23
|
+
return Path(data_dir) / "replays" / "routes" if data_dir else None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RouteMemory:
|
|
27
|
+
"""``get(phone, what)`` -> the route that last worked, or None; ``put`` and ``miss`` record what happened."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, directory):
|
|
30
|
+
self.cache = Cache(directory)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _keys(phone, what):
|
|
34
|
+
identity = {"kind": "route", "device": phone or None, "what": str(what)[:255]}
|
|
35
|
+
return identity, env_of(None, None, phone)
|
|
36
|
+
|
|
37
|
+
def get(self, phone, what):
|
|
38
|
+
identity, env = self._keys(phone, what)
|
|
39
|
+
path = self.cache.load(identity, env)
|
|
40
|
+
if not path or path.get("kind") != "route":
|
|
41
|
+
return None
|
|
42
|
+
route = (path["steps"][0] or {}).get("param")
|
|
43
|
+
return route if route in KNOWN else None
|
|
44
|
+
|
|
45
|
+
def put(self, phone, what, route):
|
|
46
|
+
if route not in KNOWN or self.get(phone, what) == route:
|
|
47
|
+
return
|
|
48
|
+
identity, env = self._keys(phone, what)
|
|
49
|
+
self.cache.save(new_path([{"op": ROUTE_OP, "live": True, "param": route}], kind="route", identity=identity,
|
|
50
|
+
env=env))
|
|
51
|
+
|
|
52
|
+
def miss(self, phone, what, route):
|
|
53
|
+
if self.get(phone, what) == route:
|
|
54
|
+
identity, env = self._keys(phone, what)
|
|
55
|
+
self.cache.forget(identity, env)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def for_data_dir(data_dir):
|
|
59
|
+
"""The data folder's RouteMemory, or None without one (tests, a bare driver)."""
|
|
60
|
+
directory = routes_dir(data_dir)
|
|
61
|
+
return RouteMemory(directory) if directory is not None else None
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""What a stopped lesson becomes (learned/teach.py records it live): a taught program saved in the shape a done run's
|
|
2
|
+
program has, a lesson merged into one already taught, and a practice run's corrections kept in it."""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from ..replay_core.path import POINT_OPS, TEXT_OPS, new_path
|
|
8
|
+
from ..replay_core.recorder import typed_plainly
|
|
9
|
+
from ..replay_core.store import env_of
|
|
10
|
+
from .programs import (
|
|
11
|
+
BLANK,
|
|
12
|
+
MAX_NAME,
|
|
13
|
+
MAX_TAUGHT,
|
|
14
|
+
MIN_LITERAL,
|
|
15
|
+
PROVISIONAL,
|
|
16
|
+
TRUSTED,
|
|
17
|
+
blank_for,
|
|
18
|
+
code_fills,
|
|
19
|
+
folded,
|
|
20
|
+
holds_secret,
|
|
21
|
+
normalized,
|
|
22
|
+
shown,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
OPENS = re.compile(r"^(?:please\s+)?(?:open|go to|show me|take me to|navigate to|launch)\b", re.I)
|
|
26
|
+
ASKS = re.compile(r"^(?:please\s+)?(?:what|which|how|when|where|who|is|are|does|do|tell me|read|check|find|get)\b",
|
|
27
|
+
re.I)
|
|
28
|
+
LOOKING = frozenset({"HOME", "LAUNCH_APP", "TAP", "SCROLL_TO", "SWIPE_UP", "SWIPE_DOWN"})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def final_title(final):
|
|
32
|
+
"""The title (navigation bar) of the screen a lesson ended on, from its recorded role/label pairs, else None."""
|
|
33
|
+
pairs = (final or {}).get("pairs")
|
|
34
|
+
for role, label in pairs or ():
|
|
35
|
+
if role == "NavigationBar" and normalized(label) and not str(label).startswith(("_", "UI")):
|
|
36
|
+
return normalized(label)[:160]
|
|
37
|
+
return normalized((final or {}).get("title")) or None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def lesson_items(request, steps, final=None):
|
|
41
|
+
"""A taught lesson's contract, by code; None for a point, a step chosen each run or a commit the request lacks."""
|
|
42
|
+
from ..catalog import app_name
|
|
43
|
+
from ..contract import FAMILIES
|
|
44
|
+
request = normalized(request)
|
|
45
|
+
if not request or BLANK.search(request) or not steps:
|
|
46
|
+
return None
|
|
47
|
+
if any(step.get("decide") or step.get("op") == "TAP_XY" for step in steps):
|
|
48
|
+
return None
|
|
49
|
+
words = re.findall(r"\w+", request.casefold())
|
|
50
|
+
commits = [step for step in steps if step.get("commit")]
|
|
51
|
+
app = next((app_name(step.get("app")) for step in reversed(steps) if step.get("app")), None)
|
|
52
|
+
if not app:
|
|
53
|
+
return None
|
|
54
|
+
if commits:
|
|
55
|
+
items = []
|
|
56
|
+
for step in commits:
|
|
57
|
+
act = step["commit"]
|
|
58
|
+
verb = next((word for word in words if word in FAMILIES.get(act, ())), None)
|
|
59
|
+
if act not in FAMILIES or act in ("none", "other") or verb is None:
|
|
60
|
+
return None
|
|
61
|
+
items.append({"kind": "COMMIT", "app": app_name(step.get("app")) or app, "what": request[:160],
|
|
62
|
+
"payload": "", "act": act, "count": "", "condition": "", "quote": verb})
|
|
63
|
+
return items
|
|
64
|
+
if any(step.get("op") not in LOOKING for step in steps):
|
|
65
|
+
return None
|
|
66
|
+
last = next((step for step in reversed(steps) if step.get("op") in ("TAP", "SCROLL_TO") and step.get("app")), None)
|
|
67
|
+
if OPENS.match(request):
|
|
68
|
+
label = final_title(final) or normalized((((last or {}).get("target") or {}).get("label")) or "")
|
|
69
|
+
if not label or not (final_title(final) or set(re.findall(r"\w+", label.casefold())) <= set(words)):
|
|
70
|
+
return None
|
|
71
|
+
return [{"kind": "READ", "app": app, "what": label[:160], "payload": "", "act": "none", "count": "",
|
|
72
|
+
"condition": "", "quote": label[:160]}]
|
|
73
|
+
if ASKS.match(request):
|
|
74
|
+
return [{"kind": "REPORT", "app": app, "what": request[:160], "payload": "", "act": "none", "count": "",
|
|
75
|
+
"condition": "", "quote": ""}]
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def save_from_demo(store, steps, request, blanks, *, device, name=None, final=None, typed=None, purposes=None,
|
|
80
|
+
rules=None, decide=None, report=None):
|
|
81
|
+
"""Keep a lesson as a provisional saved program; a secret in the request, a blank or a kept text saves nothing."""
|
|
82
|
+
from ..replay_core.path import screen_shape, signature_hash, validate
|
|
83
|
+
from ..secret_filter import is_secret
|
|
84
|
+
request = normalized(request)
|
|
85
|
+
if not request:
|
|
86
|
+
raise ValueError("Say in a few words what this does, such as Log coffee in CalTrack")
|
|
87
|
+
if len(request) > 500:
|
|
88
|
+
raise ValueError("Keep the words under 500 characters")
|
|
89
|
+
if is_secret(request) or holds_secret(request):
|
|
90
|
+
raise ValueError("Leave passwords and codes out of the words: Mobster never keeps them")
|
|
91
|
+
name = " ".join(str(name or "").split()) or request
|
|
92
|
+
if len(name) > MAX_NAME:
|
|
93
|
+
raise ValueError(f"A program's name is 1 to {MAX_NAME} characters")
|
|
94
|
+
typed = dict(typed or {})
|
|
95
|
+
if not isinstance(blanks, (list, tuple)) or len(set(blanks)) != len(blanks) \
|
|
96
|
+
or not all(type(index) is int and index in typed for index in blanks):
|
|
97
|
+
raise ValueError("Each blank must be a step where you typed something")
|
|
98
|
+
if not steps:
|
|
99
|
+
raise ValueError("This lesson has no step Mobster can repeat. Teach it again, tapping named controls")
|
|
100
|
+
purposes, rules, decide = dict(purposes or {}), dict(rules or {}), set(decide or ())
|
|
101
|
+
for index, text in list(purposes.items()) + [(i, r) for i, given in rules.items() for r in given]:
|
|
102
|
+
if type(index) is not int or not 0 <= index < len(steps) or not isinstance(text, str) \
|
|
103
|
+
or not 1 <= len(normalized(text)) <= 160 or holds_secret(text):
|
|
104
|
+
raise ValueError("A step's purpose or rule is 1 to 160 characters, with no password or code in it")
|
|
105
|
+
if not all(type(index) is int and 0 <= index < len(steps) for index in decide):
|
|
106
|
+
raise ValueError("A step Mobster chooses each run must be one of the lesson's steps")
|
|
107
|
+
into = _taught_into(store, request, typed, device)
|
|
108
|
+
if into is not None:
|
|
109
|
+
# The same program, other values: its blanks are the typed values that fill them now.
|
|
110
|
+
blanks = [index for index, text in typed.items() if isinstance(text, str)
|
|
111
|
+
and any(folded(text) == folded(value) for value in into[1].values())]
|
|
112
|
+
template, values, out = request, {}, []
|
|
113
|
+
for index, step in enumerate(steps):
|
|
114
|
+
step = {key: value for key, value in step.items() if key != "pairs"}
|
|
115
|
+
if index in purposes:
|
|
116
|
+
step["purpose"] = normalized(purposes[index])
|
|
117
|
+
if rules.get(index):
|
|
118
|
+
step["rules"] = [normalized(rule) for rule in rules[index]][:3]
|
|
119
|
+
if index in decide:
|
|
120
|
+
step["decide"] = True
|
|
121
|
+
if step.get("purpose") and holds_secret(step["purpose"]):
|
|
122
|
+
step["purpose"] = None # a drafted purpose never keeps what the filter would mask
|
|
123
|
+
if step.get("op") in TEXT_OPS:
|
|
124
|
+
text = typed.get(index)
|
|
125
|
+
if not (isinstance(text, str) and typed_plainly(text)) or holds_secret(text):
|
|
126
|
+
raise ValueError("A typed value can't be kept. Teach it again without it")
|
|
127
|
+
if index in blanks and into is not None:
|
|
128
|
+
# Named as the saved program names it: the blank this value fills in its words.
|
|
129
|
+
blank = next(name for name, value in into[1].items() if folded(value) == folded(text))
|
|
130
|
+
values[blank] = into[1][blank]
|
|
131
|
+
step["param"], step["text"] = blank, None
|
|
132
|
+
elif index in blanks:
|
|
133
|
+
template, blank = blank_for(template, values, text)
|
|
134
|
+
if blank is None:
|
|
135
|
+
raise ValueError(f'Put "{normalized(text)[:60]}" in the words, so a later task can change it')
|
|
136
|
+
step["param"], step["text"] = blank, None
|
|
137
|
+
if step.get("purpose"):
|
|
138
|
+
# "Search for “coffee”" becomes "Search for “{p1}”": each run reads its own value there.
|
|
139
|
+
step["purpose"] = re.sub(re.escape(normalized(text)), "{" + blank + "}", step["purpose"],
|
|
140
|
+
flags=re.IGNORECASE)
|
|
141
|
+
else:
|
|
142
|
+
step["text"] = text
|
|
143
|
+
out.append(step)
|
|
144
|
+
if len(BLANK.sub("", template).strip()) < MIN_LITERAL:
|
|
145
|
+
raise ValueError("Say a little more than the values: a few words that name the task")
|
|
146
|
+
for kept, step in zip(out, steps):
|
|
147
|
+
kept["shows"] = shown(values, step.get("pairs"))
|
|
148
|
+
if report is not None:
|
|
149
|
+
report.clear()
|
|
150
|
+
if into is None:
|
|
151
|
+
try:
|
|
152
|
+
same = store.cache.load(store.identity(None, template, device), env_of(None, None, device))
|
|
153
|
+
except (OSError, ValueError):
|
|
154
|
+
same = None
|
|
155
|
+
into = (same, None) if isinstance(same, dict) and same.get("taught") else None
|
|
156
|
+
if into is not None:
|
|
157
|
+
merged, added, changed = merge_lesson(into[0], out, purposes=set(purposes), answered=set(rules) | decide)
|
|
158
|
+
validate(merged)
|
|
159
|
+
store.cache.save(merged)
|
|
160
|
+
if report is not None:
|
|
161
|
+
report.update(into=merged.get("name") or template, added=added, changed=changed)
|
|
162
|
+
return merged
|
|
163
|
+
if sum(1 for path in store.all() if path.get("taught")) >= MAX_TAUGHT:
|
|
164
|
+
raise ValueError(f"Mobster keeps {MAX_TAUGHT} taught programs. Forget one in Workflows, then save this")
|
|
165
|
+
identity = store.identity(None, template, device)
|
|
166
|
+
path = new_path(out, kind="program", identity=identity, env=env_of(None, None, device), source_run=None)
|
|
167
|
+
path["contract"] = lesson_items(request, out, final)
|
|
168
|
+
path["blanks"] = sorted(values)
|
|
169
|
+
path["name"] = name
|
|
170
|
+
path["example"] = dict(values) # the person's own typed words (never a secret), for Run in the Programs list
|
|
171
|
+
path["taught"] = {"at": round(time.time(), 3), "points": sum(1 for s in out if s.get("op") == "TAP_XY"),
|
|
172
|
+
"asks": sum(1 for s in out if s.get("commit"))}
|
|
173
|
+
pairs = (final or {}).get("pairs")
|
|
174
|
+
if pairs:
|
|
175
|
+
path["final"] = {"hash": signature_hash(pairs), "shape": screen_shape(pairs), "shows": shown(values, pairs),
|
|
176
|
+
"title": final_title(final)}
|
|
177
|
+
# A lesson that only opens and looks, with a recorded final screen, has nothing a write could fail to land: it is
|
|
178
|
+
# trusted on save and finishes in code (frontier._screen_goal_finish).
|
|
179
|
+
# Anything else keeps its supervised first replay.
|
|
180
|
+
opens_only = bool(path["contract"]) and all(item["kind"] == "READ" for item in path["contract"]) \
|
|
181
|
+
and bool(path.get("final"))
|
|
182
|
+
path["status"], path["misses"], path["used_at"] = TRUSTED if opens_only else PROVISIONAL, 0, round(time.time(), 3)
|
|
183
|
+
validate(path)
|
|
184
|
+
store.cache.save(path)
|
|
185
|
+
return path
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _taught_into(store, request, typed, device):
|
|
189
|
+
found = []
|
|
190
|
+
for path in store.all():
|
|
191
|
+
identity = path.get("identity") or {}
|
|
192
|
+
if not path.get("taught") or identity.get("device") != (device or None) \
|
|
193
|
+
or not BLANK.search(identity.get("template") or ""):
|
|
194
|
+
continue
|
|
195
|
+
fills = code_fills(identity.get("template") or "", request)
|
|
196
|
+
if len(fills) == 1:
|
|
197
|
+
found.append((path, fills[0]))
|
|
198
|
+
if len(found) != 1:
|
|
199
|
+
return None
|
|
200
|
+
path, values = found[0]
|
|
201
|
+
typed_values = {folded(text) for text in (typed or {}).values() if isinstance(text, str)}
|
|
202
|
+
if not all(folded(value) in typed_values for value in values.values()):
|
|
203
|
+
return None
|
|
204
|
+
return path, values
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _merge_key(step):
|
|
208
|
+
op = step.get("op")
|
|
209
|
+
if op in ("TAP", "TAP_XY", "LONG_PRESS"):
|
|
210
|
+
return ("tap",)
|
|
211
|
+
if op == "LAUNCH_APP":
|
|
212
|
+
return ("open", step.get("app"))
|
|
213
|
+
if op in TEXT_OPS:
|
|
214
|
+
return (op, step.get("param") or ("text", step.get("text")))
|
|
215
|
+
return (op,)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _screen_key(step):
|
|
219
|
+
screen = step.get("screen") or {}
|
|
220
|
+
field = "fingerprint" if step.get("op") in POINT_OPS else "hash"
|
|
221
|
+
return field, screen.get(field)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _likeness(one, other):
|
|
225
|
+
"""Points require exact geometry; element steps compare their structural screen hashes and pairs."""
|
|
226
|
+
if one.get("op") in POINT_OPS or other.get("op") in POINT_OPS:
|
|
227
|
+
key = _screen_key(one)
|
|
228
|
+
return 1.0 if key[1] and key == _screen_key(other) else 0.0
|
|
229
|
+
one, other = one.get("screen") or {}, other.get("screen") or {}
|
|
230
|
+
if one.get("hash") and one.get("hash") == other.get("hash"):
|
|
231
|
+
return 1.0
|
|
232
|
+
ours, theirs = set(one.get("shape") or ()), set(other.get("shape") or ())
|
|
233
|
+
union = ours | theirs
|
|
234
|
+
return len(ours & theirs) / len(union) if union else 0.0
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _same_action(one, other):
|
|
238
|
+
if one.get("op") != other.get("op"):
|
|
239
|
+
return False
|
|
240
|
+
a, b = one.get("target") or {}, other.get("target") or {}
|
|
241
|
+
if (a.get("label"), a.get("role")) != (b.get("label"), b.get("role")):
|
|
242
|
+
return False
|
|
243
|
+
p, q = one.get("point"), other.get("point")
|
|
244
|
+
return p == q or (isinstance(p, list) and isinstance(q, list) and max(abs(p[0] - q[0]), abs(p[1] - q[1])) < .02)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def merge_lesson(path, steps, *, purposes=(), answered=()):
|
|
248
|
+
from ..replay_core.engine import STRONG
|
|
249
|
+
from ..replay_core.path import MAX_ALTS
|
|
250
|
+
old = [dict(step) for step in path.get("steps") or ()]
|
|
251
|
+
lead_old = next((i for i, step in enumerate(old) if step.get("op") != "HOME"), len(old))
|
|
252
|
+
lead_new = next((i for i, step in enumerate(steps) if step.get("op") != "HOME"), len(steps))
|
|
253
|
+
if [_merge_key(step) for step in old[lead_old:]] != [_merge_key(step) for step in steps[lead_new:]]:
|
|
254
|
+
name = path.get("name") or (path.get("identity") or {}).get("template") or "this task"
|
|
255
|
+
raise ValueError(f"These steps differ from the ones saved for “{name}”, not only on another screen. Use "
|
|
256
|
+
f"other words to keep both, or forget “{name}” in Programs first")
|
|
257
|
+
added, changed = [], []
|
|
258
|
+
for offset, new in enumerate(steps[lead_new:]):
|
|
259
|
+
step, lesson_index = old[lead_old + offset], lead_new + offset
|
|
260
|
+
if new.get("rules"):
|
|
261
|
+
joined = [rule for rule in step.get("rules") or () if rule not in new["rules"]] + list(new["rules"])
|
|
262
|
+
step["rules"] = joined[-3:]
|
|
263
|
+
if lesson_index in purposes and new.get("purpose"):
|
|
264
|
+
step["purpose"] = new["purpose"]
|
|
265
|
+
if lesson_index in answered:
|
|
266
|
+
step["decide"] = bool(new.get("decide"))
|
|
267
|
+
if not step["decide"]:
|
|
268
|
+
step.pop("decide")
|
|
269
|
+
if step.get("op") in ("LAUNCH_APP", "HOME") or not (new.get("screen") or {}).get("hash"):
|
|
270
|
+
continue
|
|
271
|
+
version = {"op": new["op"], "target": new.get("target"), "point": new.get("point"), "screen": new["screen"]}
|
|
272
|
+
if _likeness(step, new) >= STRONG:
|
|
273
|
+
if not _same_action(step, new):
|
|
274
|
+
step.update(version)
|
|
275
|
+
changed.append(lesson_index)
|
|
276
|
+
continue
|
|
277
|
+
alts = [dict(alt) for alt in step.get("alts") or ()]
|
|
278
|
+
like = next((i for i, alt in enumerate(alts) if _likeness(alt, new) >= STRONG), None)
|
|
279
|
+
if like is not None and _same_action(alts[like], new):
|
|
280
|
+
continue
|
|
281
|
+
if like is not None:
|
|
282
|
+
alts.pop(like)
|
|
283
|
+
step["alts"] = (alts + [version])[-MAX_ALTS:]
|
|
284
|
+
added.append(lesson_index)
|
|
285
|
+
merged = {**path, "steps": old, "status": PROVISIONAL, "misses": 0, "used_at": round(time.time(), 3),
|
|
286
|
+
"merged_at": round(time.time(), 3)}
|
|
287
|
+
merged["taught"] = {**(path.get("taught") or {}), "points": sum(1 for s in old if s.get("op") == "TAP_XY"),
|
|
288
|
+
"asks": sum(1 for s in old if s.get("commit"))}
|
|
289
|
+
return merged, added, changed
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def apply_corrections(store, matched):
|
|
293
|
+
"""Keep a practice run's corrections and make the program provisional again, so its next run is checked."""
|
|
294
|
+
from ..replay_core.path import MAX_ALTS
|
|
295
|
+
corrections = list(getattr(matched, "corrections", None) or ())
|
|
296
|
+
if not corrections or not getattr(matched, "taught", False):
|
|
297
|
+
return None
|
|
298
|
+
path = store.cache.load(matched.path.get("identity") or {}, matched.path.get("env") or {})
|
|
299
|
+
if path is None:
|
|
300
|
+
return None
|
|
301
|
+
steps, skipped = [dict(step) for step in path.get("steps") or ()], set()
|
|
302
|
+
for correction in corrections:
|
|
303
|
+
index = correction.get("index")
|
|
304
|
+
if not isinstance(index, int) or not 0 <= index < len(steps):
|
|
305
|
+
continue
|
|
306
|
+
if correction.get("kind") == "skip":
|
|
307
|
+
skipped.add(index)
|
|
308
|
+
continue
|
|
309
|
+
change = {"op": correction["op"], "target": correction.get("target"), "point": correction.get("point"),
|
|
310
|
+
"screen": correction.get("screen")}
|
|
311
|
+
step = steps[index]
|
|
312
|
+
if correction.get("same"):
|
|
313
|
+
step.update(change)
|
|
314
|
+
step.pop("label_param", None), step.pop("label_prefix", None)
|
|
315
|
+
else:
|
|
316
|
+
alts = [alt for alt in step.get("alts") or () if _screen_key(alt) != _screen_key(change)]
|
|
317
|
+
step["alts"] = (alts + [change])[-MAX_ALTS:]
|
|
318
|
+
kept = [step for index, step in enumerate(steps) if index not in skipped]
|
|
319
|
+
if not kept:
|
|
320
|
+
store.forget(path)
|
|
321
|
+
return None
|
|
322
|
+
path = {**path, "steps": kept, "status": PROVISIONAL, "misses": 0, "corrected_at": round(time.time(), 3)}
|
|
323
|
+
path["taught"] = {**(path.get("taught") or {}), "points": sum(1 for s in kept if s.get("op") == "TAP_XY"),
|
|
324
|
+
"asks": sum(1 for s in kept if s.get("commit"))}
|
|
325
|
+
try:
|
|
326
|
+
store.cache.save(path)
|
|
327
|
+
except (OSError, ValueError):
|
|
328
|
+
return None
|
|
329
|
+
return path
|