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,249 @@
|
|
|
1
|
+
"""Connect a destination with one click, then pick where its results go (docs/webhooks.mdx)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from ..api_errors import APIError
|
|
9
|
+
from ..connectors import oauth, shared
|
|
10
|
+
from ..connectors import presets as preset_list
|
|
11
|
+
from . import net
|
|
12
|
+
from .kinds import again
|
|
13
|
+
from .webhook import Outcome
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger("mobster.outlets")
|
|
16
|
+
|
|
17
|
+
KEEP_SECONDS = 900.0
|
|
18
|
+
MAX_CONNECTS = 8
|
|
19
|
+
SLACK_OFF = "Mobster's connectors aren't running, and a Slack destination posts with Slack's. Restart Mobster."
|
|
20
|
+
GONE = "That sign-in ran out. Connect again."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Connect:
|
|
24
|
+
def __init__(self, kind, at):
|
|
25
|
+
self.id, self.kind, self.at = uuid.uuid4().hex[:12], kind, at
|
|
26
|
+
self.state, self.message, self.found, self.flow = "waiting", None, None, None
|
|
27
|
+
self.connector = None # Slack: the connector whose sign-in this waits for
|
|
28
|
+
|
|
29
|
+
def public(self):
|
|
30
|
+
found = self.found or {}
|
|
31
|
+
return {"id": self.id, "kind": self.kind.id, "state": self.state, "message": self.message,
|
|
32
|
+
"account": found.get("account"), "targets": found.get("targets")}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Connects:
|
|
36
|
+
"""The connects in flight and finished, for one OutletService."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, *, call, connectors, apps=None, sign_in=oauth.SignIn, clock=time.monotonic):
|
|
39
|
+
self.call, self.connectors, self.clock = call, connectors, clock
|
|
40
|
+
self.apps = apps if apps is not None else shared.Apps()
|
|
41
|
+
self.sign_in_class = sign_in
|
|
42
|
+
self._items = {}
|
|
43
|
+
self._lock = threading.Lock()
|
|
44
|
+
|
|
45
|
+
def offered(self, kind):
|
|
46
|
+
"""Whether Connect can add a destination of ``kind`` now, so the app lists it only then: Slack when a Slack
|
|
47
|
+
connector is signed in already, or when Settings offers the Slack connector (presets.offered: a gated preset
|
|
48
|
+
once Mobster tested it); every other kind always (Connect says why when its service's app isn't ready)."""
|
|
49
|
+
if kind.id != "slack":
|
|
50
|
+
return True
|
|
51
|
+
connectors = self.connectors()
|
|
52
|
+
if connectors is None:
|
|
53
|
+
return False
|
|
54
|
+
if any(row.get("preset") == "slack" and row.get("auth") == "oauth" for row in connectors.list()):
|
|
55
|
+
return True
|
|
56
|
+
return preset_list.offered(preset_list.preset("slack"), connectors.apps)
|
|
57
|
+
|
|
58
|
+
def begin(self, kind):
|
|
59
|
+
"""Start connecting a destination of ``kind``: {"connect", "authorizeUrl"?}."""
|
|
60
|
+
if kind.id == "slack":
|
|
61
|
+
item, url = self._slack(kind)
|
|
62
|
+
return {"connect": item.public(), **({"authorizeUrl": url} if url else {})}
|
|
63
|
+
if kind.provider is None:
|
|
64
|
+
raise ValueError(f"A {kind.label.lower()} destination doesn't connect: give its fields instead")
|
|
65
|
+
app = self.apps.get(kind.provider)
|
|
66
|
+
if isinstance(app, shared.Unavailable):
|
|
67
|
+
raise APIError(f"{shared.why(kind.label, app.reason)}. Try again later.", 503, "connect_unavailable")
|
|
68
|
+
item = self._add(Connect(kind, self.clock()))
|
|
69
|
+
flow = self.sign_in_class(None, lambda ended, tokens, message: self._signed_in(item, tokens, message),
|
|
70
|
+
name=kind.label, app=app, keep=kind.keep)
|
|
71
|
+
try:
|
|
72
|
+
url = flow.begin()
|
|
73
|
+
except oauth.OAuthError as error:
|
|
74
|
+
self._pop(item.id)
|
|
75
|
+
raise APIError(error.message, 502, "sign_in_failed") from None
|
|
76
|
+
item.flow = flow
|
|
77
|
+
return {"connect": item.public(), "authorizeUrl": url}
|
|
78
|
+
|
|
79
|
+
def _signed_in(self, item, tokens, message):
|
|
80
|
+
"""On the sign-in's thread when the browser came back: what the account offers, then done."""
|
|
81
|
+
if tokens is None:
|
|
82
|
+
item.state, item.message = "failed", message
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
found = item.kind.connected(self.call, tokens)
|
|
86
|
+
except ValueError as error:
|
|
87
|
+
item.state, item.message = "failed", str(error)
|
|
88
|
+
return
|
|
89
|
+
except net.NetError as error:
|
|
90
|
+
item.state, item.message = "failed", f"{error.message} Connect again."
|
|
91
|
+
return
|
|
92
|
+
except Exception: # noqa: BLE001 -- a connect that broke says so; the app stays up
|
|
93
|
+
log.exception("outlets: a connect failed unexpectedly")
|
|
94
|
+
item.state, item.message = "failed", f"Mobster couldn't finish connecting {item.kind.label}. Connect again."
|
|
95
|
+
return
|
|
96
|
+
item.found, item.at, item.state = found, self.clock(), "done"
|
|
97
|
+
|
|
98
|
+
def _slack(self, kind):
|
|
99
|
+
"""(connect, authorizeUrl | None): done at once with a signed-in Slack connector; else the Slack connector,
|
|
100
|
+
added when there is none, signs in now, and the connect waits for it (``get``)."""
|
|
101
|
+
connectors = self.connectors()
|
|
102
|
+
if connectors is None:
|
|
103
|
+
raise APIError(SLACK_OFF, 503, "connectors_off")
|
|
104
|
+
rows = [row for row in connectors.list() if row.get("preset") == "slack"]
|
|
105
|
+
item = Connect(kind, self.clock())
|
|
106
|
+
signed = next((row for row in rows if row.get("auth") == "oauth"), None)
|
|
107
|
+
if signed is not None:
|
|
108
|
+
self._slack_found(item, connectors, signed["id"], refuse=True)
|
|
109
|
+
return self._add(item), None
|
|
110
|
+
row = rows[0] if rows else None
|
|
111
|
+
if row is None:
|
|
112
|
+
app = connectors.apps.get("slack")
|
|
113
|
+
if isinstance(app, shared.Unavailable):
|
|
114
|
+
raise APIError(f"{shared.why('Slack', app.reason)}. Connect Slack with your own app under Use in "
|
|
115
|
+
"tasks, then add the destination.", 409, "slack_own_app")
|
|
116
|
+
row = connectors.add({"preset": "slack"})
|
|
117
|
+
try:
|
|
118
|
+
url = connectors.begin_sign_in(row["id"])["authorizeUrl"]
|
|
119
|
+
except ValueError as error:
|
|
120
|
+
# No app to sign in with: Mobster's isn't ready and you have none of your own (connectors/service.py).
|
|
121
|
+
raise APIError(str(error).rstrip(".") + ".", 409, "slack_own_app") from None
|
|
122
|
+
item.connector = row["id"]
|
|
123
|
+
return self._add(item), url
|
|
124
|
+
|
|
125
|
+
def _slack_found(self, item, connectors, connector_id, refuse=False):
|
|
126
|
+
"""Done with the channels the Slack connector's sign-in sees."""
|
|
127
|
+
from ..connectors import client as mcp
|
|
128
|
+
try:
|
|
129
|
+
token = connectors.bearer(connector_id)
|
|
130
|
+
item.found = item.kind.connected(self.call, {"access_token": token, "connector": connector_id})
|
|
131
|
+
item.state, item.at = "done", self.clock()
|
|
132
|
+
except (mcp.AuthNeeded, APIError):
|
|
133
|
+
message = "Slack needs you to sign in again under Use in tasks. Then add the destination."
|
|
134
|
+
if refuse:
|
|
135
|
+
raise APIError(message, 409, "slack_own_app") from None
|
|
136
|
+
item.state, item.message = "failed", message
|
|
137
|
+
except (mcp.NotSent, net.NetError, ValueError) as error:
|
|
138
|
+
message = getattr(error, "message", None) or str(error)
|
|
139
|
+
if refuse:
|
|
140
|
+
raise APIError(message, 502, "slack_unreachable") from None
|
|
141
|
+
item.state, item.message = "failed", message
|
|
142
|
+
|
|
143
|
+
def _follow(self, item):
|
|
144
|
+
"""A Slack connect waiting for the connector's sign-in: done or failed once that is."""
|
|
145
|
+
connectors = self.connectors()
|
|
146
|
+
row = next((row for row in (connectors.list() if connectors is not None else ())
|
|
147
|
+
if row["id"] == item.connector), None)
|
|
148
|
+
sign_in = (row or {}).get("signIn") or {}
|
|
149
|
+
if row is None:
|
|
150
|
+
item.state, item.message = "failed", "Slack was removed from Use in tasks. Connect again."
|
|
151
|
+
elif sign_in.get("state") == "failed":
|
|
152
|
+
item.state, item.message = "failed", sign_in.get("message") or "Slack didn't connect. Connect again."
|
|
153
|
+
elif row.get("auth") == "oauth" and sign_in.get("state") != "waiting":
|
|
154
|
+
self._slack_found(item, connectors, row["id"])
|
|
155
|
+
|
|
156
|
+
def get(self, connect_id):
|
|
157
|
+
item = self._items.get(connect_id) if isinstance(connect_id, str) else None
|
|
158
|
+
if item is None:
|
|
159
|
+
raise APIError(GONE, 404, "not_found")
|
|
160
|
+
if item.connector is not None and item.state == "waiting":
|
|
161
|
+
self._follow(item)
|
|
162
|
+
return item.public()
|
|
163
|
+
|
|
164
|
+
def found(self, connect_id, kind_id):
|
|
165
|
+
"""What a finished connect of ``kind_id`` found, for saving a destination with it. ValueError otherwise."""
|
|
166
|
+
self._sweep()
|
|
167
|
+
item = self._items.get(connect_id) if isinstance(connect_id, str) else None
|
|
168
|
+
if item is None or item.kind.id != kind_id:
|
|
169
|
+
raise ValueError(GONE)
|
|
170
|
+
if item.state != "done":
|
|
171
|
+
raise ValueError(item.message or f"Finish connecting {item.kind.label} in your browser first.")
|
|
172
|
+
return item.found
|
|
173
|
+
|
|
174
|
+
def cancel(self, connect_id):
|
|
175
|
+
item = self._pop(connect_id)
|
|
176
|
+
if item is None:
|
|
177
|
+
raise APIError(GONE, 404, "not_found")
|
|
178
|
+
return connect_id
|
|
179
|
+
|
|
180
|
+
def drop(self, connect_id):
|
|
181
|
+
"""Forget a connect whose tokens are saved now."""
|
|
182
|
+
self._pop(connect_id)
|
|
183
|
+
|
|
184
|
+
def close(self):
|
|
185
|
+
with self._lock:
|
|
186
|
+
items, self._items = list(self._items.values()), {}
|
|
187
|
+
for item in items:
|
|
188
|
+
if item.flow is not None:
|
|
189
|
+
item.flow.cancel()
|
|
190
|
+
|
|
191
|
+
def _add(self, item):
|
|
192
|
+
self._sweep()
|
|
193
|
+
with self._lock:
|
|
194
|
+
while len(self._items) >= MAX_CONNECTS:
|
|
195
|
+
oldest = min(self._items.values(), key=lambda value: value.at)
|
|
196
|
+
self._items.pop(oldest.id)
|
|
197
|
+
if oldest.flow is not None:
|
|
198
|
+
oldest.flow.cancel()
|
|
199
|
+
self._items[item.id] = item
|
|
200
|
+
return item
|
|
201
|
+
|
|
202
|
+
def _pop(self, connect_id):
|
|
203
|
+
with self._lock:
|
|
204
|
+
item = self._items.pop(connect_id, None) if isinstance(connect_id, str) else None
|
|
205
|
+
if item is not None and item.flow is not None:
|
|
206
|
+
item.flow.cancel()
|
|
207
|
+
return item
|
|
208
|
+
|
|
209
|
+
def _sweep(self):
|
|
210
|
+
"""Forget what finished KEEP_SECONDS ago, and a sign-in nobody came back to (its own wait ended it)."""
|
|
211
|
+
now = self.clock()
|
|
212
|
+
with self._lock:
|
|
213
|
+
stale = [key for key, item in self._items.items()
|
|
214
|
+
if now - item.at > (KEEP_SECONDS if item.state != "waiting" else oauth.WAIT_SECONDS + 60)]
|
|
215
|
+
for key in stale:
|
|
216
|
+
self._pop(key)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class NoToken(Exception):
|
|
220
|
+
"""No access token now; ``outcome`` says why. Nothing was sent."""
|
|
221
|
+
|
|
222
|
+
def __init__(self, outcome):
|
|
223
|
+
super().__init__(outcome.message)
|
|
224
|
+
self.outcome = outcome
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def access(ctx, label, refresh=False):
|
|
228
|
+
"""The access token a connected destination sends, renewed through the license API first when it's expiring or
|
|
229
|
+
``refresh`` (after a 401), and saved (``ctx.keep``)."""
|
|
230
|
+
saved = ctx.saved
|
|
231
|
+
# A Notion destination saved before 15 Oct 2026 holds an internal integration's token, which never expires.
|
|
232
|
+
tokens = saved.get("oauth") or ({"access_token": saved["token"]} if saved.get("token") else None)
|
|
233
|
+
if not tokens or not tokens.get("access_token"):
|
|
234
|
+
raise NoToken(Outcome(False, False, None, f"Mobster has no {label} sign-in for this. " + again(label),
|
|
235
|
+
unsent=True))
|
|
236
|
+
if not (refresh or oauth.expiring(tokens, ctx.wall)):
|
|
237
|
+
return tokens["access_token"]
|
|
238
|
+
if not tokens.get("refresh_token"):
|
|
239
|
+
raise NoToken(Outcome(False, False, None, f"{label} signed Mobster out. " + again(label), unsent=True))
|
|
240
|
+
try:
|
|
241
|
+
tokens = ctx.refresh(tokens)
|
|
242
|
+
except oauth.OAuthError as error:
|
|
243
|
+
if error.retry:
|
|
244
|
+
raise NoToken(Outcome(False, True, None, f"Mobster couldn't renew its {label} sign-in. {error.message}",
|
|
245
|
+
unsent=True)) from None
|
|
246
|
+
raise NoToken(Outcome(False, False, None, f"{label} signed Mobster out. " + again(label), unsent=True)) \
|
|
247
|
+
from None
|
|
248
|
+
ctx.keep(tokens)
|
|
249
|
+
return tokens["access_token"]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""A folder destination: each result is appended to one file in a folder you chose, as a CSV row or a JSON line."""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import errno
|
|
5
|
+
import fcntl
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import stat
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .kinds import Kind, cell, flat
|
|
14
|
+
from .webhook import Outcome
|
|
15
|
+
|
|
16
|
+
FILE_NAME = re.compile(r"[^/\\:\x00-\x1f]{1,96}\.(csv|jsonl)", re.IGNORECASE)
|
|
17
|
+
DEFAULT_FILE = "Mobster results.csv"
|
|
18
|
+
HEADER_BYTES = 65_536
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def home():
|
|
22
|
+
"""The home folder (tests replace it)."""
|
|
23
|
+
return Path.home()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def shown(path):
|
|
27
|
+
"""``path`` with the home folder written as ~."""
|
|
28
|
+
try:
|
|
29
|
+
return "~/" + str(Path(path).relative_to(home().resolve())) if Path(path) != home().resolve() else "~"
|
|
30
|
+
except ValueError:
|
|
31
|
+
return str(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def inside_home(value):
|
|
35
|
+
"""The folder ``value`` names (~ allowed), resolved, when it exists inside the home folder; ValueError else."""
|
|
36
|
+
text = value.strip() if isinstance(value, str) else ""
|
|
37
|
+
if not text:
|
|
38
|
+
raise ValueError("Choose a folder.")
|
|
39
|
+
if len(text) > 1024 or "\x00" in text:
|
|
40
|
+
raise ValueError("That isn't a folder's path.")
|
|
41
|
+
base = home().resolve()
|
|
42
|
+
path = Path(text.replace("~", str(base), 1) if text.startswith("~") else text)
|
|
43
|
+
if not path.is_absolute():
|
|
44
|
+
raise ValueError("Choose a folder in your home folder, such as ~/Documents.")
|
|
45
|
+
try:
|
|
46
|
+
real = path.resolve(strict=True)
|
|
47
|
+
except (OSError, RuntimeError):
|
|
48
|
+
raise ValueError("That folder doesn't exist. Choose one that does.") from None
|
|
49
|
+
if real != base and base not in real.parents:
|
|
50
|
+
raise ValueError("Choose a folder in your home folder. Mobster doesn't write anywhere else.")
|
|
51
|
+
if not real.is_dir():
|
|
52
|
+
raise ValueError("That's a file, not a folder.")
|
|
53
|
+
return real
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def file_name(value):
|
|
57
|
+
text = " ".join(value.split()) if isinstance(value, str) else ""
|
|
58
|
+
if not text:
|
|
59
|
+
return DEFAULT_FILE
|
|
60
|
+
if text.startswith(".") or not FILE_NAME.fullmatch(text):
|
|
61
|
+
raise ValueError("Name the file like Results.csv or Results.jsonl.")
|
|
62
|
+
return text
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Folder(Kind):
|
|
66
|
+
id, label = "folder", "Folder"
|
|
67
|
+
example = "Results file"
|
|
68
|
+
fields = ("folder", "file")
|
|
69
|
+
replay_safe = False
|
|
70
|
+
keychain = False
|
|
71
|
+
|
|
72
|
+
def setup(self, body, current=None):
|
|
73
|
+
config = dict(current["row"]["config"]) if current else {}
|
|
74
|
+
if current is None or "folder" in body:
|
|
75
|
+
config["folder"] = shown(inside_home(body.get("folder")))
|
|
76
|
+
if current is None or "file" in body:
|
|
77
|
+
config["file"] = file_name(body.get("file"))
|
|
78
|
+
config["format"] = "jsonl" if config["file"].lower().endswith(".jsonl") else "csv"
|
|
79
|
+
if current is not None and not ({"folder", "file"} & set(body)):
|
|
80
|
+
return {}
|
|
81
|
+
target = config["folder"].rstrip("/") + "/" + config["file"]
|
|
82
|
+
if (inside_home(config["folder"]) / config["file"]).is_symlink():
|
|
83
|
+
raise ValueError("That file is a link. Name another file.")
|
|
84
|
+
return {"hint": target, "config": config, "last_test": None}
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _open(config):
|
|
88
|
+
"""The file, opened to append (created 0644 when new): (fd, path). OSError or ValueError when it can't be."""
|
|
89
|
+
folder = inside_home(config["folder"])
|
|
90
|
+
path = folder / config["file"]
|
|
91
|
+
# Only ever appends: never truncates, renames or replaces the file, and refuses a link or a non-regular file.
|
|
92
|
+
fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_NOFOLLOW | os.O_CLOEXEC, 0o644)
|
|
93
|
+
try:
|
|
94
|
+
if not stat.S_ISREG(os.fstat(fd).st_mode):
|
|
95
|
+
raise ValueError("That isn't a regular file.")
|
|
96
|
+
except BaseException:
|
|
97
|
+
os.close(fd)
|
|
98
|
+
raise
|
|
99
|
+
return fd, path
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _header(path):
|
|
103
|
+
"""The file's first line as CSV column names, and whether it ends with a line break; ([], True) when empty."""
|
|
104
|
+
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
|
|
105
|
+
with os.fdopen(fd, "rb") as handle:
|
|
106
|
+
head = handle.read(HEADER_BYTES)
|
|
107
|
+
size = os.fstat(handle.fileno()).st_size
|
|
108
|
+
ends = True
|
|
109
|
+
if size:
|
|
110
|
+
handle.seek(size - 1)
|
|
111
|
+
ends = handle.read(1) in (b"\n", b"\r")
|
|
112
|
+
if not head:
|
|
113
|
+
return [], True
|
|
114
|
+
first = head.decode("utf-8-sig", errors="replace").splitlines()[0] if head.strip() else ""
|
|
115
|
+
names = next(csv.reader(io.StringIO(first)), []) if first else []
|
|
116
|
+
return [name.strip() for name in names], ends
|
|
117
|
+
|
|
118
|
+
def lines(self, config, body, path):
|
|
119
|
+
"""The bytes to append for ``body``."""
|
|
120
|
+
if config.get("format") == "jsonl":
|
|
121
|
+
document = {key: value for key, value in body.items() if key != "screenshot"}
|
|
122
|
+
return (json.dumps(document, ensure_ascii=False, separators=(",", ":")) + "\n").encode("utf-8")
|
|
123
|
+
row = flat(body)
|
|
124
|
+
header, ends = self._header(path)
|
|
125
|
+
out = io.StringIO()
|
|
126
|
+
writer = csv.writer(out, lineterminator="\n")
|
|
127
|
+
if not ends:
|
|
128
|
+
out.write("\n")
|
|
129
|
+
if not header:
|
|
130
|
+
header = list(row)
|
|
131
|
+
writer.writerow(header)
|
|
132
|
+
lower = {name.casefold(): value for name, value in row.items()}
|
|
133
|
+
writer.writerow([cell(lower.get(name.casefold(), "")) for name in header])
|
|
134
|
+
return out.getvalue().encode("utf-8")
|
|
135
|
+
|
|
136
|
+
def _append(self, ctx, body):
|
|
137
|
+
config = ctx.destination["config"]
|
|
138
|
+
where = config.get("folder", "the folder")
|
|
139
|
+
try:
|
|
140
|
+
fd, path = self._open(config)
|
|
141
|
+
except ValueError as error:
|
|
142
|
+
return Outcome(False, False, None, f"{error} Choose another in Settings › Connectors.")
|
|
143
|
+
except OSError as error:
|
|
144
|
+
if error.errno in (errno.ELOOP, errno.EMLINK):
|
|
145
|
+
return Outcome(False, False, None, f"{config.get('file')} is a link, so Mobster won't write to it. "
|
|
146
|
+
"Name another file in Settings › Connectors.")
|
|
147
|
+
if error.errno in (errno.EACCES, errno.EPERM, errno.EROFS):
|
|
148
|
+
return Outcome(False, False, None, f"macOS didn't let Mobster write in {where}. Allow it in System "
|
|
149
|
+
"Settings › Privacy & Security › Files and Folders, then resend.")
|
|
150
|
+
return Outcome(False, False, None, f"Mobster couldn't open {config.get('file')} in {where}.")
|
|
151
|
+
try:
|
|
152
|
+
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
153
|
+
data = self.lines(config, body, path)
|
|
154
|
+
written = os.write(fd, data)
|
|
155
|
+
if written != len(data):
|
|
156
|
+
return Outcome(False, False, None, f"Only part of the result fit in {config.get('file')}. Check the "
|
|
157
|
+
"file and your disk's free space.")
|
|
158
|
+
except OSError as error:
|
|
159
|
+
if error.errno == errno.ENOSPC:
|
|
160
|
+
return Outcome(False, False, None, "The disk is full. Free some space, then resend.")
|
|
161
|
+
return Outcome(False, False, None, f"Mobster couldn't write to {config.get('file')}.")
|
|
162
|
+
finally:
|
|
163
|
+
os.close(fd)
|
|
164
|
+
return Outcome(True, False, None)
|
|
165
|
+
|
|
166
|
+
def send(self, ctx, body):
|
|
167
|
+
return self._append(ctx, body)
|
|
168
|
+
|
|
169
|
+
def test(self, ctx):
|
|
170
|
+
"""Checks the folder and the file can be written, without adding a row."""
|
|
171
|
+
config = ctx.destination["config"]
|
|
172
|
+
try:
|
|
173
|
+
fd, _path = self._open(config)
|
|
174
|
+
except ValueError as error:
|
|
175
|
+
return Outcome(False, False, None, f"{error} Choose another in Settings › Connectors.")
|
|
176
|
+
except OSError:
|
|
177
|
+
return Outcome(False, False, None, f"Mobster can't write to {config.get('file')} in "
|
|
178
|
+
f"{config.get('folder')}. Choose another folder or file.")
|
|
179
|
+
os.close(fd)
|
|
180
|
+
return Outcome(True, False, None, f"Mobster can write to {config.get('file')}.")
|