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,136 @@
|
|
|
1
|
+
"""Drafted replies from Apple's on-device model, through the ``draft-reply`` helper (helper_draft_reply.swift)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .. import native_helpers
|
|
9
|
+
|
|
10
|
+
HELPER = "draft_reply"
|
|
11
|
+
PER_DRAFT_S = 15.0
|
|
12
|
+
TOTAL_S = 75.0
|
|
13
|
+
MAX_DRAFT = 280
|
|
14
|
+
REASONS = {
|
|
15
|
+
"apple_intelligence_off": "Apple Intelligence is off on this Mac, so you'll write your own replies. Turn it on in "
|
|
16
|
+
"System Settings to get drafts.",
|
|
17
|
+
"device_not_eligible": "This Mac can't run Apple's on-device model, so you'll write your own replies.",
|
|
18
|
+
"model_not_ready": "Apple's on-device model is still downloading, so you'll write your own replies this time.",
|
|
19
|
+
"needs_macos_26": "Drafts need macOS 26 or later, so you'll write your own replies.",
|
|
20
|
+
}
|
|
21
|
+
UNAVAILABLE = "Drafts aren't available on this Mac right now, so you'll write your own replies."
|
|
22
|
+
|
|
23
|
+
# A draft that commits the person: a date, a time, money, a day or month, a yes or no to a request.
|
|
24
|
+
_COMMITS = re.compile(
|
|
25
|
+
r"[$€£¥]|\b\d{1,2}[:.]\d{2}\b|\b\d{1,2}\s?(am|pm)\b|\b\d{1,2}/\d{1,2}\b|\b\d+\s?(dollars|bucks|usd|eur|euros)\b"
|
|
26
|
+
r"|\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|tomorrow|tonight|today|january|february|march|april"
|
|
27
|
+
r"|may \d|june|july|august|september|october|november|december)\b"
|
|
28
|
+
r"|^(yes|yeah|yep|sure|no|nope|absolutely|definitely)\b|\bi('ll| will) (be there|come|pay|send|do it|take it)\b"
|
|
29
|
+
r"|\bcount me in\b|\bi('m| am) in\b", re.I)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def reason_text(reason):
|
|
33
|
+
return REASONS.get(reason, UNAVAILABLE)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _run_helper():
|
|
37
|
+
"""The helper's path, or None when this Mac has none and can't build one."""
|
|
38
|
+
path = native_helpers.find(HELPER)
|
|
39
|
+
if path is not None:
|
|
40
|
+
return str(path)
|
|
41
|
+
try:
|
|
42
|
+
return str(native_helpers.build(HELPER))
|
|
43
|
+
except (LookupError, FileNotFoundError, OSError, subprocess.SubprocessError):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def status(runner=None):
|
|
48
|
+
"""{"available": bool, "reason": str}: whether the on-device model can draft. ``runner`` is for tests."""
|
|
49
|
+
if runner is not None:
|
|
50
|
+
return runner.status()
|
|
51
|
+
helper = _run_helper()
|
|
52
|
+
if helper is None:
|
|
53
|
+
return {"available": False, "reason": "no_helper"}
|
|
54
|
+
try:
|
|
55
|
+
done = subprocess.run([helper, "status"], capture_output=True, text=True, timeout=20)
|
|
56
|
+
data = json.loads((done.stdout or "").strip().splitlines()[-1])
|
|
57
|
+
except (OSError, subprocess.SubprocessError, ValueError, IndexError):
|
|
58
|
+
return {"available": False, "reason": "unavailable"}
|
|
59
|
+
return {"available": bool(data.get("available")), "reason": str(data.get("reason") or "")}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def clean(text):
|
|
63
|
+
"""One tidy line of at most 280 characters, or '' when there isn't one."""
|
|
64
|
+
text = " ".join(str(text or "").replace("“", '"').replace("”", '"').split()).strip(' "')
|
|
65
|
+
if len(text) > MAX_DRAFT:
|
|
66
|
+
text = text[:MAX_DRAFT].rsplit(" ", 1)[0].rstrip(" ,;:")
|
|
67
|
+
return text
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def acceptable(text):
|
|
71
|
+
"""A draft that commits the person to nothing (no date, time, money, yes or no on their behalf)."""
|
|
72
|
+
return bool(text) and not _COMMITS.search(text)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def payload(items):
|
|
76
|
+
"""The helper's request for ``items`` (rank.Item): each item's id, situation and last 8 messages."""
|
|
77
|
+
return {"items": [{"id": str(item.chat), "kind": item.bucket,
|
|
78
|
+
"messages": [{"from": "you" if m.mine else "them", "text": m.text} for m in item.context[-8:]]}
|
|
79
|
+
for item in items]}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def draft_all(items, runner=None, per_draft=PER_DRAFT_S, total=TOTAL_S, clock=time.monotonic):
|
|
83
|
+
"""{str(chat id): draft} for the items that got one. Never raises; whatever fails is simply missing."""
|
|
84
|
+
if not items:
|
|
85
|
+
return {}
|
|
86
|
+
request = payload(items)
|
|
87
|
+
if runner is not None:
|
|
88
|
+
lines = runner.run(request, per_draft, total)
|
|
89
|
+
else:
|
|
90
|
+
lines = _stream(request, per_draft, total, clock)
|
|
91
|
+
out = {}
|
|
92
|
+
for line in lines:
|
|
93
|
+
if isinstance(line, dict) and line.get("ok") and isinstance(line.get("id"), str):
|
|
94
|
+
text = clean(line.get("draft"))
|
|
95
|
+
if acceptable(text):
|
|
96
|
+
out[line["id"]] = text
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _stream(request, per_draft, total, clock):
|
|
101
|
+
helper = _run_helper()
|
|
102
|
+
if helper is None:
|
|
103
|
+
return
|
|
104
|
+
deadline = clock() + total
|
|
105
|
+
try:
|
|
106
|
+
process = subprocess.Popen([helper, "run", "--timeout", str(per_draft)], stdin=subprocess.PIPE,
|
|
107
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
|
108
|
+
except OSError:
|
|
109
|
+
return
|
|
110
|
+
try:
|
|
111
|
+
process.stdin.write(json.dumps(request)) # message text goes on stdin, never on a command line or in a log
|
|
112
|
+
process.stdin.close()
|
|
113
|
+
import selectors
|
|
114
|
+
selector = selectors.DefaultSelector()
|
|
115
|
+
selector.register(process.stdout, selectors.EVENT_READ)
|
|
116
|
+
while clock() < deadline:
|
|
117
|
+
if not selector.select(timeout=1.0):
|
|
118
|
+
if process.poll() is not None:
|
|
119
|
+
break
|
|
120
|
+
continue
|
|
121
|
+
line = process.stdout.readline()
|
|
122
|
+
if not line:
|
|
123
|
+
break
|
|
124
|
+
try:
|
|
125
|
+
yield json.loads(line)
|
|
126
|
+
except ValueError:
|
|
127
|
+
continue
|
|
128
|
+
except (OSError, ValueError):
|
|
129
|
+
return
|
|
130
|
+
finally:
|
|
131
|
+
if process.poll() is None:
|
|
132
|
+
process.kill()
|
|
133
|
+
try:
|
|
134
|
+
process.wait(timeout=5)
|
|
135
|
+
except subprocess.SubprocessError:
|
|
136
|
+
pass
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""The "Who's waiting on me" job: Messages read, ranked and drafted on this Mac, never by a cloud model."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sqlite3
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
from .. import result_blocks
|
|
10
|
+
from ..api_errors import APIError
|
|
11
|
+
from . import contacts as contact_book
|
|
12
|
+
from . import drafts, messages, rank
|
|
13
|
+
|
|
14
|
+
BUNDLE = "com.apple.MobileSMS"
|
|
15
|
+
APP = {"id": "messages", "name": "Messages", "bundleId": BUNDLE}
|
|
16
|
+
GOAL = "Who's waiting on me?"
|
|
17
|
+
MAX_LAST = 200
|
|
18
|
+
MISSING = ("Mobster can't find your Messages history on this Mac. Turn on Messages in iCloud in Messages settings, "
|
|
19
|
+
"then try again.")
|
|
20
|
+
DENIED = (
|
|
21
|
+
"Mobster needs Full Disk Access to read your Messages on this Mac. Turn it on in System Settings, then try again.")
|
|
22
|
+
UNREADABLE = (
|
|
23
|
+
"Mobster couldn't read the Messages database on this Mac, so nothing was changed. Update Mobster, then try again.")
|
|
24
|
+
BUSY = "Mobster is already looking through your Messages. Wait for it to finish."
|
|
25
|
+
_lock = threading.Lock()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ago(seconds):
|
|
29
|
+
"""'3 hours ago', '1 day ago', '2 weeks ago'."""
|
|
30
|
+
minutes = max(int(seconds // 60), 0)
|
|
31
|
+
for unit, size in (("week", 7 * 24 * 60), ("day", 24 * 60), ("hour", 60)):
|
|
32
|
+
if minutes >= size:
|
|
33
|
+
count = minutes // size
|
|
34
|
+
return f"{count} {unit}{'' if count == 1 else 's'} ago"
|
|
35
|
+
return "just now"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _short(text, limit=MAX_LAST):
|
|
39
|
+
text = " ".join((text or "").split())
|
|
40
|
+
return text if len(text) <= limit else text[:limit - 1].rsplit(" ", 1)[0].rstrip(" ,;:") + "…"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def block_for(items, names, written, *, title=None):
|
|
44
|
+
"""The review_queue block for ranked ``items``: ``names`` is {chat id: name}, ``written`` {str(chat id): draft}."""
|
|
45
|
+
out = []
|
|
46
|
+
for item in items:
|
|
47
|
+
name = names.get(item.chat)
|
|
48
|
+
when = ago(item.age_s)
|
|
49
|
+
quote = _short(item.last.text)
|
|
50
|
+
if item.bucket == "promised":
|
|
51
|
+
context = f"You said this {when}. They haven't written since."
|
|
52
|
+
quote = f"You wrote: {quote}"
|
|
53
|
+
elif item.bucket == "asked":
|
|
54
|
+
context = f"Asked you {when}"
|
|
55
|
+
else:
|
|
56
|
+
context = f"Wrote {when}"
|
|
57
|
+
row = {"id": f"c{item.chat}", "bucket": item.bucket, "to": name or item.handle,
|
|
58
|
+
"app": APP["name"], "bundleId": BUNDLE, "context": context, "lastMessage": quote,
|
|
59
|
+
"draft": written.get(str(item.chat)) or ""}
|
|
60
|
+
if name:
|
|
61
|
+
row["handle"] = item.handle
|
|
62
|
+
if not row["draft"]:
|
|
63
|
+
# No draft because the on-device model is off or its draft was dropped: the person writes the reply, and
|
|
64
|
+
# Approve takes their typed words (result_blocks.approval_item).
|
|
65
|
+
row["yourWords"] = result_blocks.YOUR_WORDS
|
|
66
|
+
out.append(row)
|
|
67
|
+
buckets = [{"id": bucket, "label": rank.LABELS[bucket]} for bucket in rank.BUCKETS
|
|
68
|
+
if any(item.bucket == bucket for item in items)]
|
|
69
|
+
return result_blocks.review_queue(out, title=title, buckets=buckets, action="send_message", id="waiting")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _plural(count, one, many):
|
|
73
|
+
return f"{count} {one if count == 1 else many}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def answer_for(items, drafted, availability):
|
|
77
|
+
"""The one line the result says: counts only, no names and no message text."""
|
|
78
|
+
if not items:
|
|
79
|
+
return (
|
|
80
|
+
"Nobody is waiting on you in your Messages. Group chats, short codes and conversations older than 30 days "
|
|
81
|
+
"aren't counted.")
|
|
82
|
+
counts = rank.counts(items)
|
|
83
|
+
parts = []
|
|
84
|
+
if counts["asked"]:
|
|
85
|
+
parts.append(f"{_plural(counts['asked'], 'person asked', 'people asked')} you something")
|
|
86
|
+
if counts["waiting"]:
|
|
87
|
+
parts.append(f"{_plural(counts['waiting'], 'person is', 'people are')} waiting on a reply")
|
|
88
|
+
if counts["promised"]:
|
|
89
|
+
parts.append(f"{_plural(counts['promised'], 'promise', 'promises')} to keep")
|
|
90
|
+
line = ", ".join(parts).capitalize() + "."
|
|
91
|
+
if availability.get("available"):
|
|
92
|
+
line += f" {_plural(drafted, 'draft', 'drafts')} to approve. Nothing was sent."
|
|
93
|
+
else:
|
|
94
|
+
line += " " + drafts.reason_text(availability.get("reason")) + " Nothing was sent."
|
|
95
|
+
return line
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def read(now, path=None):
|
|
99
|
+
"""Ranked items from chat.db with the errors the app shows in plain words."""
|
|
100
|
+
target = path or messages.DEFAULT_PATH
|
|
101
|
+
if not os.path.exists(target):
|
|
102
|
+
raise APIError(MISSING, 404, "messages_missing")
|
|
103
|
+
if not messages.columns_ok(target):
|
|
104
|
+
# A file we can see but can't open is Full Disk Access; one we open but that lacks the columns is a newer macOS.
|
|
105
|
+
try:
|
|
106
|
+
messages.connect(target).close()
|
|
107
|
+
except sqlite3.Error:
|
|
108
|
+
raise APIError(DENIED, 403, "full_disk_access") from None
|
|
109
|
+
raise APIError(UNREADABLE, 422, "messages_unreadable")
|
|
110
|
+
try:
|
|
111
|
+
chats = messages.read_chats(target, now)
|
|
112
|
+
except sqlite3.Error:
|
|
113
|
+
raise APIError(DENIED, 403, "full_disk_access") from None
|
|
114
|
+
return rank.rank(chats, now)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def run(runtime, *, now=None, path=None, book=None, runner=None, key=None):
|
|
118
|
+
"""Make the finished task. Returns the Run. ``runner`` and ``book`` are for tests."""
|
|
119
|
+
if key:
|
|
120
|
+
# A retried start (a lost answer) returns the task the first one made, without reading or drafting again.
|
|
121
|
+
prior = runtime.journal.lookup(key[:128])
|
|
122
|
+
if prior and prior[0] in runtime.runs:
|
|
123
|
+
return runtime.runs[prior[0]]
|
|
124
|
+
if not _lock.acquire(blocking=False):
|
|
125
|
+
raise APIError(BUSY, 409, "waiting_busy")
|
|
126
|
+
try:
|
|
127
|
+
now = time.time() if now is None else now
|
|
128
|
+
items = read(now, path)
|
|
129
|
+
availability = drafts.status(runner)
|
|
130
|
+
written = drafts.draft_all(items, runner=runner) if availability.get("available") else {}
|
|
131
|
+
book = book if book is not None else contact_book.ContactBook.load()
|
|
132
|
+
names = {item.chat: book.name(item.handle) for item in items}
|
|
133
|
+
total = len(items)
|
|
134
|
+
title = f"Who's waiting on you: {_plural(total, 'person', 'people')}"
|
|
135
|
+
blocks = [block_for(items, names, written, title=title)] if items else []
|
|
136
|
+
answer = answer_for(items, len(written), availability)
|
|
137
|
+
summary = {"event": "result", "status": "completed", "outcome": "done", "answer": answer, "reason": None,
|
|
138
|
+
"independently_verified": True, "actions": 0, "decisions": 0, "elapsed_ms": 0, "model": None,
|
|
139
|
+
"proof": [{"quote": "Read your Messages on this Mac. Nothing was sent.", "app": APP["name"]}],
|
|
140
|
+
**({"blocks": {"v": 1, "blocks": blocks}} if blocks else {})}
|
|
141
|
+
app = next((dict(one) for one in runtime.apps_for(None) if one.get("bundleId") == BUNDLE), dict(APP))
|
|
142
|
+
return runtime.compose_result(app=app, goal=GOAL, summary=summary,
|
|
143
|
+
idempotency_key=(key or f"waiting-{uuid.uuid4().hex}")[:128], origin="app")
|
|
144
|
+
finally:
|
|
145
|
+
_lock.release()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""A read-only reader of the Messages history on this Mac (``~/Library/Messages/chat.db``)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sqlite3
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import quote
|
|
8
|
+
|
|
9
|
+
from ..notify_triggers.sources import MAC_EPOCH
|
|
10
|
+
|
|
11
|
+
ONE_TO_ONE = 45 # chat.style
|
|
12
|
+
MAX_TEXT = 600 # characters kept of one message
|
|
13
|
+
DEFAULT_PATH = Path.home() / "Library" / "Messages" / "chat.db"
|
|
14
|
+
ATTACHMENT_TEXT = "[attachment]"
|
|
15
|
+
|
|
16
|
+
NEEDED = {
|
|
17
|
+
"message": {"ROWID", "text", "attributedBody", "handle_id", "is_from_me", "date", "item_type",
|
|
18
|
+
"associated_message_type", "cache_has_attachments"},
|
|
19
|
+
"chat": {"ROWID", "style"},
|
|
20
|
+
"chat_message_join": {"chat_id", "message_id"},
|
|
21
|
+
"chat_handle_join": {"chat_id", "handle_id"},
|
|
22
|
+
"handle": {"ROWID", "id"},
|
|
23
|
+
}
|
|
24
|
+
# SMS fallbacks of a tapback from before iOS 10 and from Android: the reaction as a text.
|
|
25
|
+
_REACTION_TEXT = re.compile(r"^(Loved|Liked|Disliked|Laughed at|Emphasized|Questioned|Reacted \S+ to) [“\"]", re.I)
|
|
26
|
+
_OBJECT_REPLACEMENT = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class Message:
|
|
31
|
+
rowid: int
|
|
32
|
+
mine: bool
|
|
33
|
+
when: float # Unix seconds
|
|
34
|
+
text: str
|
|
35
|
+
attachment: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Chat:
|
|
40
|
+
rowid: int
|
|
41
|
+
handle: str # the other person's phone number or email, as Messages has it
|
|
42
|
+
messages: list = field(default_factory=list) # oldest first, real messages only
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def apple_to_unix(value):
|
|
46
|
+
"""Messages' ``date`` as Unix seconds: nanoseconds (macOS 10.13 on) or seconds since 2001-01-01."""
|
|
47
|
+
value = float(value or 0)
|
|
48
|
+
if abs(value) > 1e11:
|
|
49
|
+
value /= 1e9
|
|
50
|
+
return value + MAC_EPOCH
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def unix_to_apple_ns(seconds):
|
|
54
|
+
return int((seconds - MAC_EPOCH) * 1_000_000_000)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def decode_attributed_body(blob):
|
|
58
|
+
"""The message's plain string from an ``attributedBody`` blob, or None when there isn't one to find."""
|
|
59
|
+
if not blob:
|
|
60
|
+
return None
|
|
61
|
+
# typedstream: NSString, class bytes, "+", the UTF-8 length (a byte, or 0x81/0x82/0x83 and 2/4/8 LE bytes), text.
|
|
62
|
+
data = bytes(blob)
|
|
63
|
+
start = 0
|
|
64
|
+
while True:
|
|
65
|
+
at = data.find(b"NSString", start)
|
|
66
|
+
if at < 0:
|
|
67
|
+
return None
|
|
68
|
+
start = at + 1
|
|
69
|
+
plus = data.find(b"+", at + 8, at + 8 + 12)
|
|
70
|
+
if plus < 0:
|
|
71
|
+
continue
|
|
72
|
+
cursor = plus + 1
|
|
73
|
+
if cursor >= len(data):
|
|
74
|
+
continue
|
|
75
|
+
first = data[cursor]
|
|
76
|
+
if first == 0x81:
|
|
77
|
+
width, cursor = 2, cursor + 1
|
|
78
|
+
elif first == 0x82:
|
|
79
|
+
width, cursor = 4, cursor + 1
|
|
80
|
+
elif first == 0x83:
|
|
81
|
+
width, cursor = 8, cursor + 1
|
|
82
|
+
elif first < 0x80:
|
|
83
|
+
width = 0
|
|
84
|
+
else:
|
|
85
|
+
continue
|
|
86
|
+
if width:
|
|
87
|
+
if cursor + width > len(data):
|
|
88
|
+
continue
|
|
89
|
+
length = int.from_bytes(data[cursor:cursor + width], "little")
|
|
90
|
+
cursor += width
|
|
91
|
+
else:
|
|
92
|
+
length, cursor = first, cursor + 1
|
|
93
|
+
if length <= 0 or cursor + length > len(data):
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
text = data[cursor:cursor + length].decode("utf-8")
|
|
97
|
+
except UnicodeDecodeError:
|
|
98
|
+
continue
|
|
99
|
+
return text
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def message_text(text, attributed):
|
|
104
|
+
"""What a message says: ``text``, else the string in ``attributedBody``; attachments' placeholders removed."""
|
|
105
|
+
words = text if isinstance(text, str) and text.strip() else decode_attributed_body(attributed)
|
|
106
|
+
return (words or "").replace(_OBJECT_REPLACEMENT, "").strip()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def connect(path=None):
|
|
110
|
+
"""The database, read-only. ``sqlite3.OperationalError`` when it can't be opened (no file, no access)."""
|
|
111
|
+
target = Path(path) if path else DEFAULT_PATH
|
|
112
|
+
connection = sqlite3.connect(f"file:{quote(str(target))}?mode=ro", uri=True, timeout=5)
|
|
113
|
+
connection.execute("PRAGMA query_only = ON")
|
|
114
|
+
return connection
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def columns_ok(path=None):
|
|
118
|
+
"""Whether chat.db has the tables and columns this reader reads (``PRAGMA table_info`` only)."""
|
|
119
|
+
try:
|
|
120
|
+
connection = connect(path)
|
|
121
|
+
except sqlite3.Error:
|
|
122
|
+
return False
|
|
123
|
+
try:
|
|
124
|
+
for table, needed in NEEDED.items():
|
|
125
|
+
have = {row[1] for row in connection.execute(f"PRAGMA table_info({table})")}
|
|
126
|
+
if not needed <= have:
|
|
127
|
+
return False
|
|
128
|
+
return True
|
|
129
|
+
except sqlite3.Error:
|
|
130
|
+
return False
|
|
131
|
+
finally:
|
|
132
|
+
connection.close()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def read_chats(path, now, days=30, per_chat=40, limit_chats=400):
|
|
136
|
+
"""Every one-to-one chat with a real message in the last ``days`` days"""
|
|
137
|
+
since = unix_to_apple_ns(now - days * 86400)
|
|
138
|
+
connection = connect(path)
|
|
139
|
+
try:
|
|
140
|
+
# Newest activity first; a chat with one handle only (a one-to-one chat has exactly one).
|
|
141
|
+
chats = connection.execute(
|
|
142
|
+
"SELECT c.ROWID, MIN(h.id), COUNT(h.ROWID) "
|
|
143
|
+
"FROM chat c JOIN chat_handle_join j ON j.chat_id = c.ROWID JOIN handle h ON h.ROWID = j.handle_id "
|
|
144
|
+
"WHERE c.style = ? AND EXISTS (SELECT 1 FROM chat_message_join m WHERE m.chat_id = c.ROWID) "
|
|
145
|
+
"GROUP BY c.ROWID HAVING COUNT(h.ROWID) = 1", (ONE_TO_ONE,)).fetchall()
|
|
146
|
+
recent = {row[0]: row[1] for row in connection.execute(
|
|
147
|
+
"SELECT j.chat_id, MAX(m.date) FROM chat_message_join j JOIN message m ON m.ROWID = j.message_id "
|
|
148
|
+
"WHERE m.date >= ? GROUP BY j.chat_id", (since,))}
|
|
149
|
+
wanted = sorted((row for row in chats if row[0] in recent), key=lambda row: -recent[row[0]])[:limit_chats]
|
|
150
|
+
out = []
|
|
151
|
+
for rowid, handle, _count in wanted:
|
|
152
|
+
rows = connection.execute(
|
|
153
|
+
"SELECT m.ROWID, m.text, m.attributedBody, m.is_from_me, m.date, m.cache_has_attachments "
|
|
154
|
+
"FROM chat_message_join j JOIN message m ON m.ROWID = j.message_id "
|
|
155
|
+
"WHERE j.chat_id = ? AND m.date >= ? AND COALESCE(m.item_type, 0) = 0 "
|
|
156
|
+
"AND COALESCE(m.associated_message_type, 0) = 0 "
|
|
157
|
+
"ORDER BY m.date DESC, m.ROWID DESC LIMIT ?", (rowid, since, per_chat * 2)).fetchall()
|
|
158
|
+
messages = []
|
|
159
|
+
for mid, text, body, mine, date, attached in rows:
|
|
160
|
+
words = message_text(text, body)
|
|
161
|
+
if words and _REACTION_TEXT.match(words):
|
|
162
|
+
continue
|
|
163
|
+
if not words:
|
|
164
|
+
if not attached:
|
|
165
|
+
continue
|
|
166
|
+
words = ATTACHMENT_TEXT
|
|
167
|
+
messages.append(Message(mid, bool(mine), apple_to_unix(date), words[:MAX_TEXT], bool(attached)))
|
|
168
|
+
if len(messages) >= per_chat:
|
|
169
|
+
break
|
|
170
|
+
if messages:
|
|
171
|
+
out.append(Chat(rowid, handle, list(reversed(messages))))
|
|
172
|
+
return out
|
|
173
|
+
finally:
|
|
174
|
+
connection.close()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Who is waiting on you: deterministic rules over the chats ``messages.read_chats`` returns."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
|
|
6
|
+
WINDOW_DAYS = 30
|
|
7
|
+
MIN_AGE_S = 2 * 3600
|
|
8
|
+
MAX_ITEMS = 25
|
|
9
|
+
CONTEXT = 8
|
|
10
|
+
BUCKETS = ("asked", "waiting", "promised")
|
|
11
|
+
LABELS = {"asked": "Asked you something", "waiting": "Waiting on you", "promised": "You said you would"}
|
|
12
|
+
|
|
13
|
+
_ASK = re.compile(
|
|
14
|
+
r"\?|\b(can|could|would|will) (you|u)\b|\b(did|do|are|have|were|was|is) (you|u)\b|\blet me know\b|\blmk\b"
|
|
15
|
+
r"|\bcall me\b|\btext me\b|\bget back to me\b|\bany update\b|\bwhen (can|are|will|do) (you|u)\b"
|
|
16
|
+
r"|\bplease (send|call|text|let|reply|confirm|check)\b|\bwhat do you think\b|\bwhat time\b", re.I)
|
|
17
|
+
_PROMISE = re.compile(r"\b(i'll|i will|ill|let me|i can send|i'm going to|im gonna|i am going to)\b|\btomorrow\b", re.I)
|
|
18
|
+
_NOT_A_PROMISE = re.compile(r"\blet me know\b", re.I)
|
|
19
|
+
_CLOSER_WORDS = frozenset((
|
|
20
|
+
"ok okay k kk thanks thank you thx ty tysm np yw lol lmao haha hahaha hehe ha cool nice great awesome perfect "
|
|
21
|
+
"sounds "
|
|
22
|
+
"good got it yep yup yes yeah sure bet will do right same true word love u so much see ya later bye night "
|
|
23
|
+
"goodnight "
|
|
24
|
+
"gn lovely amazing wonderful appreciate appreciated all fine alright sweet noted").split())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clean(text):
|
|
28
|
+
return (text or "").replace("’", "'").replace("‘", "'").strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_closer(text):
|
|
32
|
+
"""Whether a message ends a conversation instead of asking for a reply: "ok", "thanks!", "lol", "👍", "😂😂"."""
|
|
33
|
+
text = clean(text)
|
|
34
|
+
if not text:
|
|
35
|
+
return True
|
|
36
|
+
if "?" in text:
|
|
37
|
+
return False
|
|
38
|
+
if not re.search(r"[^\W_]", text): # no letter or digit: an emoji, "!!!", "..."
|
|
39
|
+
return True
|
|
40
|
+
words = re.findall(r"[^\W_]+(?:'[^\W_]+)?", text.lower())
|
|
41
|
+
return 0 < len(words) <= 5 and all(word in _CLOSER_WORDS for word in words)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def asks(text):
|
|
45
|
+
return bool(_ASK.search(clean(text)))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def promises(text):
|
|
49
|
+
"""Whether the message promises something ("I'll send it tomorrow", "let me check"). "Let me know" doesn't."""
|
|
50
|
+
return bool(_PROMISE.search(_NOT_A_PROMISE.sub(" ", clean(text))))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_person_handle(handle):
|
|
54
|
+
"""A phone number (7 or more digits) or an email address that isn't a no-reply sender."""
|
|
55
|
+
handle = (handle or "").strip()
|
|
56
|
+
if not handle:
|
|
57
|
+
return False
|
|
58
|
+
lowered = handle.lower()
|
|
59
|
+
if re.search(r"no[-_.]?reply|do[-_.]?not[-_.]?reply|donotreply|mailer-daemon|notifications?@", lowered):
|
|
60
|
+
return False
|
|
61
|
+
if "@" in handle:
|
|
62
|
+
return bool(re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", handle))
|
|
63
|
+
if re.fullmatch(r"\+?[\d\s().-]+", handle):
|
|
64
|
+
return len(re.sub(r"\D", "", handle)) >= 7
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Item:
|
|
70
|
+
chat: int
|
|
71
|
+
handle: str
|
|
72
|
+
bucket: str
|
|
73
|
+
last: object # the Message the item is about (theirs; yours for "promised")
|
|
74
|
+
age_s: float
|
|
75
|
+
context: list = field(default_factory=list) # up to 8 newest messages, oldest first
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def classify(chat, now):
|
|
79
|
+
"""The Item for one chat, or None."""
|
|
80
|
+
if not is_person_handle(chat.handle) or not chat.messages:
|
|
81
|
+
return None
|
|
82
|
+
messages = sorted(chat.messages, key=lambda m: (m.when, m.rowid))
|
|
83
|
+
horizon = now - WINDOW_DAYS * 86400
|
|
84
|
+
last = messages[-1]
|
|
85
|
+
if not last.mine:
|
|
86
|
+
age = now - last.when
|
|
87
|
+
if last.when < horizon or age < MIN_AGE_S or is_closer(last.text):
|
|
88
|
+
return None
|
|
89
|
+
bucket = "asked" if asks(last.text) else "waiting"
|
|
90
|
+
return Item(chat.rowid, chat.handle, bucket, last, age, messages[-CONTEXT:])
|
|
91
|
+
# The last message is yours: the newest promise of yours that they haven't answered, unless you sent a file after.
|
|
92
|
+
for index in range(len(messages) - 1, -1, -1):
|
|
93
|
+
message = messages[index]
|
|
94
|
+
if not message.mine:
|
|
95
|
+
return None
|
|
96
|
+
if message.when < horizon:
|
|
97
|
+
return None
|
|
98
|
+
if promises(message.text) and not message.attachment:
|
|
99
|
+
age = now - message.when
|
|
100
|
+
if age < MIN_AGE_S or any(later.attachment for later in messages[index + 1:]):
|
|
101
|
+
return None
|
|
102
|
+
return Item(chat.rowid, chat.handle, "promised", message, age,
|
|
103
|
+
messages[max(0, index - CONTEXT + 1):index + 1])
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def rank(chats, now, limit=MAX_ITEMS):
|
|
108
|
+
"""The items for ``chats``: by bucket, then the longest wait first, at most ``limit``."""
|
|
109
|
+
items = [item for item in (classify(chat, now) for chat in chats) if item is not None]
|
|
110
|
+
items.sort(key=lambda item: (BUCKETS.index(item.bucket), -item.age_s, item.chat))
|
|
111
|
+
return items[:limit]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def counts(items):
|
|
115
|
+
return {bucket: sum(1 for item in items if item.bucket == bucket) for bucket in BUCKETS}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Track ``waiting``'s registrations: no agent or MCP tool, since its result would carry message text to a model."""
|
|
2
|
+
|
|
3
|
+
from ..api_errors import APIError
|
|
4
|
+
from ..api_routes import Response, Route
|
|
5
|
+
from ..api_routes import register as add_route
|
|
6
|
+
from . import job
|
|
7
|
+
|
|
8
|
+
APP_ONLY = "Only the Mobster app can read your Messages. Open Mobster and ask there."
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def start(request):
|
|
12
|
+
"""POST /api/waiting: {"key"?} -> 201 {"runId"}. The Mac app only (its session), like Settings routes."""
|
|
13
|
+
# Fail closed: the result holds message text, so only the Mac app's own session starts it (not an API token alone).
|
|
14
|
+
if not request.app_session:
|
|
15
|
+
raise APIError(APP_ONLY, 403, "app_only")
|
|
16
|
+
body = request.body if isinstance(request.body, dict) else {}
|
|
17
|
+
key = body.get("key")
|
|
18
|
+
if key is not None and (not isinstance(key, str) or not 1 <= len(key) <= 100):
|
|
19
|
+
raise ValueError("key is 1 to 100 characters")
|
|
20
|
+
run = job.run(request.runtime, key=f"waiting-{key}" if key else None)
|
|
21
|
+
return Response.json({"runId": run.id}, 201)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register(api):
|
|
25
|
+
"""``api`` is mobile_agent.harness_api."""
|
|
26
|
+
add_route(Route("waiting", "POST", "/api/waiting", start))
|