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
mobile_agent/api_post.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""DELETE and POST routes of the local API."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from urllib.parse import urlsplit
|
|
5
|
+
|
|
6
|
+
from . import ai_plans, engines, env_secrets, harness_api, ownership
|
|
7
|
+
from .api_devices import runtime_teach
|
|
8
|
+
from .api_errors import APIError
|
|
9
|
+
from .api_http import (
|
|
10
|
+
ALREADY_ANSWERED,
|
|
11
|
+
APP_ONLY,
|
|
12
|
+
APPROVE_IN_APP,
|
|
13
|
+
APPROVE_IN_APP_ORIGINS,
|
|
14
|
+
DEVICE_PATH,
|
|
15
|
+
FILE_TROUBLE,
|
|
16
|
+
TOOK_TOO_LONG,
|
|
17
|
+
WORKFLOW_TOOL,
|
|
18
|
+
api_path,
|
|
19
|
+
)
|
|
20
|
+
from .downloads import EXPORT_MAX_BYTES, save_export
|
|
21
|
+
from .graph.schema import GraphError
|
|
22
|
+
from .journal import JournalError
|
|
23
|
+
from .output_contract import FORMATS as OUTPUT_FORMATS
|
|
24
|
+
from .output_contract import validate_schema
|
|
25
|
+
from .transport import decode_json
|
|
26
|
+
from .workflows.cron import preview as schedule_preview
|
|
27
|
+
from .workflows.records import WORKFLOW_ID
|
|
28
|
+
from .workflows.runs import WorkflowDispatchUncertain
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WriteRoutesMixin:
|
|
32
|
+
def do_DELETE(self):
|
|
33
|
+
self.close_connection = True
|
|
34
|
+
if self.mcp_run():
|
|
35
|
+
return
|
|
36
|
+
if self.refused():
|
|
37
|
+
return
|
|
38
|
+
path = api_path(urlsplit(self.path).path)
|
|
39
|
+
if self.extension("DELETE", path):
|
|
40
|
+
return
|
|
41
|
+
link_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/"
|
|
42
|
+
r"([A-Za-z0-9_@.-]{1,64})/link", path)
|
|
43
|
+
if link_match:
|
|
44
|
+
if not self.has_app_session():
|
|
45
|
+
return self.send_json({"error": "Stop a link in the Mobster app.", "code": "app_only"}, 403)
|
|
46
|
+
try:
|
|
47
|
+
return self.send_json(self.runtime.workflows.approval_links.stop(*link_match.groups()))
|
|
48
|
+
except APIError as exc:
|
|
49
|
+
return self.send_api_error(exc)
|
|
50
|
+
workflow_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})", path)
|
|
51
|
+
if workflow_match:
|
|
52
|
+
try:
|
|
53
|
+
return self.send_json({"deleted": self.runtime.workflows.delete(workflow_match[1])})
|
|
54
|
+
except APIError as exc:
|
|
55
|
+
return self.send_api_error(exc)
|
|
56
|
+
except JournalError:
|
|
57
|
+
return self.send_json({"error": "Workflows could not be saved. Try again.",
|
|
58
|
+
"code": "persistence_unavailable"}, 503)
|
|
59
|
+
device_match = DEVICE_PATH.fullmatch(path)
|
|
60
|
+
if device_match and device_match[2] is None and device_match[3] is None:
|
|
61
|
+
phone = self.device_phone(device_match[1])
|
|
62
|
+
if phone is None:
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
return self.send_json({"forgotten": self.runtime.fleet.forget(phone.id)})
|
|
66
|
+
except LookupError as error:
|
|
67
|
+
return self.send_json({"error": str(error), "code": "device_refused"}, 409)
|
|
68
|
+
match = re.fullmatch(r"/api/runs/([a-f0-9]{12})", path)
|
|
69
|
+
if not match:
|
|
70
|
+
return self.send_json({"error": "Not found"}, 404)
|
|
71
|
+
try:
|
|
72
|
+
self.runtime.delete_run(match[1])
|
|
73
|
+
except APIError as exc:
|
|
74
|
+
return self.send_api_error(exc)
|
|
75
|
+
except JournalError:
|
|
76
|
+
return self.send_json({"error": "Task history could not be saved. Try again.",
|
|
77
|
+
"code": "persistence_unavailable"}, 503)
|
|
78
|
+
return self.send_json({"deleted": match[1]})
|
|
79
|
+
|
|
80
|
+
def do_POST(self):
|
|
81
|
+
# Close after POST so unread malformed bodies cannot become a second request.
|
|
82
|
+
self.close_connection = True
|
|
83
|
+
ownership.set_client(self.headers.get("X-Mobster-Client")) # a label for who holds the phone, nothing more
|
|
84
|
+
if self.mcp_run():
|
|
85
|
+
return
|
|
86
|
+
if self.refused():
|
|
87
|
+
return
|
|
88
|
+
if self.extension("POST", api_path(urlsplit(self.path).path)):
|
|
89
|
+
return
|
|
90
|
+
if self.headers.get_content_type() != "application/json":
|
|
91
|
+
return self.send_json({"error": "Use application/json"}, 415)
|
|
92
|
+
try:
|
|
93
|
+
if self.headers.get("Transfer-Encoding") or len(self.headers.get_all("Content-Length", [])) != 1:
|
|
94
|
+
raise ValueError("Supply one Content-Length and no Transfer-Encoding")
|
|
95
|
+
size = int(self.headers.get("Content-Length", "0"))
|
|
96
|
+
# Exports carry a whole result; every other body is a small command.
|
|
97
|
+
limit = EXPORT_MAX_BYTES + 4096 if api_path(self.path) == "/api/export" else 64000
|
|
98
|
+
if not 0 < size <= limit:
|
|
99
|
+
return self.send_json({"error": "Invalid body length"}, 400)
|
|
100
|
+
raw = self.rfile.read(size)
|
|
101
|
+
if len(raw) != size:
|
|
102
|
+
raise ValueError("Incomplete request body")
|
|
103
|
+
body = decode_json(raw)
|
|
104
|
+
if not isinstance(body, dict):
|
|
105
|
+
raise ValueError("JSON body must be an object")
|
|
106
|
+
path = api_path(self.path)
|
|
107
|
+
if path.startswith("/api/setup/"):
|
|
108
|
+
return self.setup_action(path, body)
|
|
109
|
+
if path == "/api/devices/chosen":
|
|
110
|
+
return self.choose_devices(body)
|
|
111
|
+
if path == "/api/fleet/health/acknowledge":
|
|
112
|
+
# "Mark as handled" lifts a profile's pre-run refusal, so it needs the Mac app's own session.
|
|
113
|
+
if self.needs_app():
|
|
114
|
+
return
|
|
115
|
+
fleet = getattr(self.runtime, "fleet", None)
|
|
116
|
+
profile = body.get("profile")
|
|
117
|
+
if fleet is None or set(body) - {"profile", "code"} or not isinstance(profile, str) or not profile:
|
|
118
|
+
raise ValueError("Name the profile to mark as handled, and optionally the code")
|
|
119
|
+
try:
|
|
120
|
+
cleared = fleet.health.acknowledge(profile, body.get("code"))
|
|
121
|
+
except OSError:
|
|
122
|
+
raise APIError("Mobster could not save that: another Mobster process is holding the phone "
|
|
123
|
+
"health file. Choose Mark as handled again in a moment.", 503,
|
|
124
|
+
"health_busy") from None
|
|
125
|
+
return self.send_json({"cleared": cleared})
|
|
126
|
+
device_match = DEVICE_PATH.fullmatch(path)
|
|
127
|
+
if device_match and device_match[2] in ("take-back", "resume-queue"):
|
|
128
|
+
return self.owner_action(device_match[1], device_match[2], body)
|
|
129
|
+
if device_match and (device_match[3] or device_match[2] in ("control", "teach")):
|
|
130
|
+
return self.device_action(device_match[1], device_match[2], device_match[3], body)
|
|
131
|
+
provider_match = re.fullmatch(r"/api/ai-plans/providers/([a-z_]{2,30})/([a-z-]{2,20})", path)
|
|
132
|
+
if provider_match:
|
|
133
|
+
if self.needs_app():
|
|
134
|
+
return
|
|
135
|
+
from .ai_plans import routes as plan_routes
|
|
136
|
+
try:
|
|
137
|
+
return self.send_json(plan_routes.handle(self.runtime.keys, provider_match[1], provider_match[2],
|
|
138
|
+
body))
|
|
139
|
+
except plan_routes.RouteError as error:
|
|
140
|
+
return self.send_json({"error": str(error), "code": error.code}, error.status)
|
|
141
|
+
if device_match and device_match[2] in ("disconnect", "connect", None) and not device_match[3]:
|
|
142
|
+
return self.manage_device(device_match[1], device_match[2], body)
|
|
143
|
+
if path.startswith("/api/groups/"):
|
|
144
|
+
if self.group_request("POST", path, body) is False:
|
|
145
|
+
return self.send_json({"error": "Not found"}, 404)
|
|
146
|
+
return
|
|
147
|
+
if path == "/api/workflows/preview":
|
|
148
|
+
return self.send_json(schedule_preview(body, self.runtime.workflows.now()))
|
|
149
|
+
if path == "/api/workflows":
|
|
150
|
+
return self.send_json({"workflow": self.runtime.workflows.create(body)}, 201)
|
|
151
|
+
if path == "/api/workflows/draft":
|
|
152
|
+
return self.send_json(self.runtime.workflows.draft(body))
|
|
153
|
+
if path == "/api/workflows/validate":
|
|
154
|
+
if not isinstance(body, dict) or set(body) - {"graph", "workflowId"}:
|
|
155
|
+
raise ValueError("Send the graph, and the workflowId when it is a saved workflow's")
|
|
156
|
+
return self.send_json({"graph": self.runtime.workflows.check_graph(body.get("graph"),
|
|
157
|
+
body.get("workflowId"))})
|
|
158
|
+
if path == "/api/workflows/evaluate":
|
|
159
|
+
from .graph import expr as graph_expr
|
|
160
|
+
if not isinstance(body, dict) or set(body) - {"expression", "scope", "workflowId", "batchId", "node"} \
|
|
161
|
+
or not isinstance(body.get("scope", {}), dict):
|
|
162
|
+
raise ValueError("Send the expression, and a scope of values it reads or the run and step "
|
|
163
|
+
"(workflowId, batchId, node) whose values it reads")
|
|
164
|
+
scope = body.get("scope") or {}
|
|
165
|
+
if body.get("batchId"):
|
|
166
|
+
try:
|
|
167
|
+
scope = {**self.runtime.workflows.recorded_scope(body.get("workflowId"), body["batchId"],
|
|
168
|
+
body.get("node")), **scope}
|
|
169
|
+
except APIError as exc:
|
|
170
|
+
return self.send_api_error(exc)
|
|
171
|
+
try:
|
|
172
|
+
value = graph_expr.render(body.get("expression"), scope)
|
|
173
|
+
except graph_expr.ExprError as error:
|
|
174
|
+
return self.send_json({"error": str(error), "code": "invalid_expression", "at": error.at}, 400)
|
|
175
|
+
return self.send_json({"value": value, "type": graph_expr.kind_of(value)})
|
|
176
|
+
template = re.fullmatch(r"/api/workflows/templates/([a-z-]{1,40})", path)
|
|
177
|
+
if template:
|
|
178
|
+
from .workflows import brief
|
|
179
|
+
return self.send_json({"workflow": brief.create_from_template(self.runtime.workflows, template[1],
|
|
180
|
+
body)}, 201)
|
|
181
|
+
link_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/"
|
|
182
|
+
r"([A-Za-z0-9_@.-]{1,64})/link", path)
|
|
183
|
+
if link_match:
|
|
184
|
+
# Only the person asks for a link: MCP, Remote and hooks never make one.
|
|
185
|
+
if not self.has_app_session():
|
|
186
|
+
return self.send_json({"error": "Ask by link in the Mobster app.", "code": "app_only"}, 403)
|
|
187
|
+
if not isinstance(body, dict) or set(body) - {"label"}:
|
|
188
|
+
raise ValueError("A link takes a label, and nothing else")
|
|
189
|
+
return self.send_json(self.runtime.workflows.approval_links.make(*link_match.groups(),
|
|
190
|
+
label=body.get("label")))
|
|
191
|
+
approval_match = re.fullmatch(
|
|
192
|
+
r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/([A-Za-z0-9_@.-]{1,64})", path)
|
|
193
|
+
if approval_match:
|
|
194
|
+
# Only the person answers: MCP, Remote, hooks and typed messages never approve a step.
|
|
195
|
+
if not self.has_app_session():
|
|
196
|
+
return self.send_json({"error": "Answer this in the Mobster app.", "code": "app_only"}, 403)
|
|
197
|
+
return self.send_json({"approval": self.runtime.workflows.answer_approval(*approval_match.groups(),
|
|
198
|
+
body)})
|
|
199
|
+
workflow_match = re.fullmatch(
|
|
200
|
+
r"/api/workflows/([a-f0-9]{12})(/run|/stop|/publish|/rollback|/prove|/resume)?", path)
|
|
201
|
+
if workflow_match:
|
|
202
|
+
if workflow_match[2] in ("/prove", "/resume"):
|
|
203
|
+
# A prove and a bulk resume start from Mobster's editor, like a test run and a Resume.
|
|
204
|
+
if not self.has_app_session():
|
|
205
|
+
return self.send_json({"error": "A test run or a re-run starts from Mobster's editor.",
|
|
206
|
+
"code": "test_from_editor"}, 403)
|
|
207
|
+
if len(self.headers.get_all("Idempotency-Key", [])) != 1:
|
|
208
|
+
raise ValueError("Send one Idempotency-Key")
|
|
209
|
+
key = self.headers.get("Idempotency-Key")
|
|
210
|
+
if workflow_match[2] == "/prove":
|
|
211
|
+
return self.send_json(self.runtime.workflows.prove(workflow_match[1], body, key,
|
|
212
|
+
origin=self.origin_label()), 201)
|
|
213
|
+
if not isinstance(body, dict) or set(body) != {"batchIds"}:
|
|
214
|
+
raise ValueError("Send batchIds: the runs to resume")
|
|
215
|
+
return self.send_json(self.runtime.workflows.resume_all(workflow_match[1], body["batchIds"], key,
|
|
216
|
+
origin=self.origin_label()), 202)
|
|
217
|
+
if not workflow_match[2]:
|
|
218
|
+
return self.send_json({"workflow": self.runtime.workflows.update(workflow_match[1], body)})
|
|
219
|
+
if workflow_match[2] == "/publish":
|
|
220
|
+
return self.send_json({"workflow": self.runtime.workflows.history.publish(workflow_match[1], body)})
|
|
221
|
+
if workflow_match[2] == "/rollback":
|
|
222
|
+
return self.send_json(
|
|
223
|
+
{"workflow": self.runtime.workflows.history.rollback(workflow_match[1], body)})
|
|
224
|
+
if workflow_match[2] == "/stop":
|
|
225
|
+
return self.send_json({"batch": self.runtime.workflows.stop_batch(workflow_match[1])})
|
|
226
|
+
if set(body) - {"values", "threadId", "newConversation", "rows", "test", "from", "only", "resume",
|
|
227
|
+
"trigger", "input"} \
|
|
228
|
+
or len(self.headers.get_all("Idempotency-Key", [])) != 1:
|
|
229
|
+
raise ValueError("A run takes values, threadId, newConversation, rows, test, from, only, "
|
|
230
|
+
"resume, trigger or input, and one Idempotency-Key")
|
|
231
|
+
rerun = {key: body[key] for key in ("from", "only", "resume") if key in body} or None
|
|
232
|
+
if rerun is not None and len(rerun) > 1:
|
|
233
|
+
raise ValueError("A re-run is one of from, only or resume")
|
|
234
|
+
if (body.get("test") or rerun) and not self.has_app_session():
|
|
235
|
+
return self.send_json({"error": "A test run or a re-run starts from Mobster's editor.",
|
|
236
|
+
"code": "test_from_editor"}, 403)
|
|
237
|
+
from .threads.routes import grant_header
|
|
238
|
+
started = self.runtime.workflows.start(workflow_match[1], self.headers.get("Idempotency-Key"),
|
|
239
|
+
values=body.get("values"), thread_id=body.get("threadId"),
|
|
240
|
+
new_conversation=body.get("newConversation", False),
|
|
241
|
+
origin=self.origin_label(), rows=body.get("rows"),
|
|
242
|
+
remote_grant=grant_header(self.origin_label(), self.headers),
|
|
243
|
+
test=body.get("test", False), rerun=rerun,
|
|
244
|
+
trigger=body.get("trigger"), input=body.get("input"),
|
|
245
|
+
project_secrets=self.has_app_session())
|
|
246
|
+
return self.send_json(started, 200 if started["replayed"] else 201)
|
|
247
|
+
tool_match = WORKFLOW_TOOL.fullmatch(path)
|
|
248
|
+
if tool_match and not WORKFLOW_ID.fullmatch(tool_match[1]):
|
|
249
|
+
# A workflow made a tool, by its slug (POST /v1/workflows/<slug>): the same schema as its MCP tool.
|
|
250
|
+
if len(self.headers.get_all("Idempotency-Key", [])) != 1:
|
|
251
|
+
raise ValueError("Send one Idempotency-Key with a workflow run")
|
|
252
|
+
answer, ended = self.runtime.workflows.run_tool(tool_match[1], body,
|
|
253
|
+
self.headers.get("Idempotency-Key"),
|
|
254
|
+
origin=self.origin_label(),
|
|
255
|
+
project_secrets=self.has_app_session())
|
|
256
|
+
return self.send_json(answer, 200 if ended else 202)
|
|
257
|
+
if path == "/api/usage/estimate":
|
|
258
|
+
engine = engines.read_engine(body.get("engine"))
|
|
259
|
+
if (set(body) - {"goal", "outputSchema", "outputFormat", "engine"}
|
|
260
|
+
or not isinstance(body.get("goal"), str) or len(body["goal"].strip()) > 4000
|
|
261
|
+
or engine is not None and engine not in engines.ENGINES and not ai_plans.is_agent(engine)
|
|
262
|
+
or body.get("outputFormat") not in (None, "auto", *OUTPUT_FORMATS)):
|
|
263
|
+
raise ValueError("Supply a draft of at most 4000 characters and an optional output schema, "
|
|
264
|
+
"output format and engine")
|
|
265
|
+
schema = validate_schema(body["outputSchema"]) if body.get("outputSchema") is not None else None
|
|
266
|
+
estimates = self.runtime.engine_estimates(body["goal"].strip(), schema,
|
|
267
|
+
output_format=body.get("outputFormat"))
|
|
268
|
+
return self.send_json({"engine": engine or engines.SMART, "engines": estimates})
|
|
269
|
+
if path == "/api/runs":
|
|
270
|
+
if body.get("mode", "live") != "live":
|
|
271
|
+
raise ValueError("Dashboard tasks run on your iPhone. Synthetic replays are not supported here.")
|
|
272
|
+
if len(self.headers.get_all("Idempotency-Key", [])) > 1:
|
|
273
|
+
raise ValueError("Supply only one Idempotency-Key")
|
|
274
|
+
key = self.headers.get("Idempotency-Key")
|
|
275
|
+
core = {"appId", "goal", "mode", "outputSchema", "outputFormat", "allowedBundles", "engine", "device",
|
|
276
|
+
"phones", "profiles", "pinnedApps", "queue", "teamGroup", "dryRun"}
|
|
277
|
+
fields = harness_api.run_fields()
|
|
278
|
+
if body.get("dryRun") is True:
|
|
279
|
+
return self.send_json({"dryRun": self.runtime.dry_run(body)})
|
|
280
|
+
if "dryRun" in body:
|
|
281
|
+
raise ValueError("dryRun is true, or leave it out")
|
|
282
|
+
if set(body) - core - set(fields):
|
|
283
|
+
raise ValueError("Unsupported task field")
|
|
284
|
+
if body.get("device") is not None and not isinstance(body["device"], str):
|
|
285
|
+
raise ValueError("device must be a device's id, UDID or name")
|
|
286
|
+
if "queue" in body and type(body["queue"]) is not bool:
|
|
287
|
+
raise ValueError("queue is true (wait for a held phone, the default) or false (refuse)")
|
|
288
|
+
if set(body) & harness_api.claimed_fields():
|
|
289
|
+
raise ValueError("consoleMember and consoleSession are set by Mobster, never by a request")
|
|
290
|
+
extras = {name: body[name] for name in body if name in fields and name not in core}
|
|
291
|
+
if extras.get("projectEnv") is not None and not self.has_app_session():
|
|
292
|
+
# Resolving local secrets needs the private app session, never an origin label.
|
|
293
|
+
return self.send_json({"error": "Start a task with project secrets in the Mobster app on this Mac.",
|
|
294
|
+
"code": "app_only"}, 403)
|
|
295
|
+
longform = extras.get("longform")
|
|
296
|
+
if isinstance(longform, dict) and longform.get("capUsd") is not None and not self.has_app_session():
|
|
297
|
+
# Raising a task's spending limit is the Mac app's: X-Mobster-Origin grants nothing.
|
|
298
|
+
return self.send_json({"error": APP_ONLY, "code": "app_only"}, 403)
|
|
299
|
+
if self.loosening(body):
|
|
300
|
+
return
|
|
301
|
+
origin = self.origin_label()
|
|
302
|
+
extras.update(self.origin_claim())
|
|
303
|
+
if body.get("teamGroup") is not None and body.get("phones") is None:
|
|
304
|
+
raise ValueError("teamGroup is set by the team's console, for phones only")
|
|
305
|
+
if body.get("phones") is not None or body.get("profiles") is not None:
|
|
306
|
+
return self.start_group(body, key, extras, origin)
|
|
307
|
+
created = self.runtime.create(body.get("appId"), body.get("goal"), "live", key,
|
|
308
|
+
output_schema=body.get("outputSchema"),
|
|
309
|
+
output_format=body.get("outputFormat", "auto"),
|
|
310
|
+
allowed_bundles=body.get("allowedBundles"), engine=body.get("engine"),
|
|
311
|
+
**({"device": body["device"]} if body.get("device") is not None else {}),
|
|
312
|
+
**({"pinned_apps": body["pinnedApps"]}
|
|
313
|
+
if body.get("pinnedApps") is not None else {}),
|
|
314
|
+
**({"extras": extras} if extras else {}),
|
|
315
|
+
**({"origin": origin} if origin != "api" else {}),
|
|
316
|
+
queue=body.get("queue", True))
|
|
317
|
+
run, replayed = created if key else (created, False)
|
|
318
|
+
return self.send_json({"run": run.public(), "replayed": replayed}, 200 if replayed else 201)
|
|
319
|
+
if path == "/api/settings":
|
|
320
|
+
# Asking less and spending more are the person's, in the Mac app (S6.6), as a task's capUsd is.
|
|
321
|
+
if {"askBeforeActing", "taskCostLimitUsd", "monthlyLimitUsd"} & set(body) and self.needs_app():
|
|
322
|
+
return
|
|
323
|
+
return self.send_json(self.runtime.update_settings(body))
|
|
324
|
+
if path == "/api/keys":
|
|
325
|
+
return self.send_json(self.runtime.save_keys(body))
|
|
326
|
+
if path == "/api/keys/test":
|
|
327
|
+
if set(body) != {"target"}:
|
|
328
|
+
raise ValueError("Send the target to test: anthropic, openai or helper")
|
|
329
|
+
return self.send_json(self.runtime.check_key(body["target"]))
|
|
330
|
+
if path == "/api/export":
|
|
331
|
+
return self.send_json(save_export(body))
|
|
332
|
+
if path in ("/api/device/control", "/api/device/teach") and self.needs_app():
|
|
333
|
+
return # as /api/devices/{id}/control and teach (device_action)
|
|
334
|
+
if path == "/api/device/control":
|
|
335
|
+
return self.send_json({"ok": True, "action": self.runtime.control_device(body)})
|
|
336
|
+
if path == "/api/device/teach":
|
|
337
|
+
return self.send_json(runtime_teach(self.runtime, body, None))
|
|
338
|
+
shared = re.fullmatch(r"/api/runs/([a-f0-9]{12})/share(?:/(preview)|/([A-Za-z0-9_-]{22})/revoke)?", path)
|
|
339
|
+
if shared:
|
|
340
|
+
token = body.pop("accessToken", None) if isinstance(body, dict) else None
|
|
341
|
+
if shared[2] == "preview":
|
|
342
|
+
if set(body) - {"people", "screens", "title"}:
|
|
343
|
+
raise ValueError("A share preview takes people, screens and title")
|
|
344
|
+
return self.send_json(self.runtime.share_preview(shared[1], body.get("people", "mask"),
|
|
345
|
+
body.get("screens", False), body.get("title")))
|
|
346
|
+
# Making or turning off a link is the Mac app's: it holds the account (as Remote's turn-on).
|
|
347
|
+
if self.needs_app():
|
|
348
|
+
return
|
|
349
|
+
if shared[3]:
|
|
350
|
+
if set(body):
|
|
351
|
+
raise ValueError("Turning a link off takes only the account's accessToken")
|
|
352
|
+
return self.send_json(self.runtime.unshare_run(shared[1], shared[3], token))
|
|
353
|
+
if set(body) - {"people", "screens", "title", "expiresDays"}:
|
|
354
|
+
raise ValueError("A share takes people, screens, title, expiresDays and the accessToken")
|
|
355
|
+
return self.send_json(self.runtime.share_run(shared[1], token, body.get("people", "mask"),
|
|
356
|
+
body.get("screens", False), body.get("title"),
|
|
357
|
+
body.get("expiresDays", 30)), 201)
|
|
358
|
+
artifact = re.fullmatch(r"/api/runs/([a-f0-9]{12})/artifact", path)
|
|
359
|
+
if artifact:
|
|
360
|
+
if set(body) - {"blocks"} != {"format"} or body["format"] not in ("pdf", "png", "md"):
|
|
361
|
+
raise ValueError("An artifact takes format (pdf, png or md) and optionally blocks")
|
|
362
|
+
return self.send_json(self.runtime.save_artifact(artifact[1], body["format"], body.get("blocks")))
|
|
363
|
+
blocked = re.fullmatch(r"/api/runs/([a-f0-9]{12})/blocks/([A-Za-z0-9_.:-]{1,64})/items/"
|
|
364
|
+
r"([A-Za-z0-9_.:-]{1,64})", path)
|
|
365
|
+
if blocked:
|
|
366
|
+
if (set(body) - {"text"} != {"decision"} or body["decision"] not in ("approve", "skip", "reopen")
|
|
367
|
+
or "text" in body and not isinstance(body["text"], str)):
|
|
368
|
+
raise ValueError("A decision on a result is approve, skip or reopen (and optionally the "
|
|
369
|
+
"edited text with approve)")
|
|
370
|
+
# Approving is the Mac app's, as an approval card's yes is (S6.6); skipping stays token-only.
|
|
371
|
+
if body["decision"] == "approve" and self.needs_app(APPROVE_IN_APP):
|
|
372
|
+
return
|
|
373
|
+
return self.send_json(self.runtime.decide_block_item(blocked[1], blocked[2], blocked[3],
|
|
374
|
+
body["decision"], body.get("text")))
|
|
375
|
+
match = re.fullmatch(r"/api/runs/([a-f0-9]{12})/(stop|approval|review|gif|eval)", path)
|
|
376
|
+
run = self.runtime.runs.get(match[1]) if match else None
|
|
377
|
+
if run and match[2] == "eval":
|
|
378
|
+
if body:
|
|
379
|
+
raise ValueError("Adding a task to your evals takes no body")
|
|
380
|
+
return self.send_json(self.runtime.add_to_evals(match[1]))
|
|
381
|
+
if run and match[2] == "gif":
|
|
382
|
+
theme, redact, mp4 = body.get("theme", "paper"), body.get("redact", False), body.get("mp4", False)
|
|
383
|
+
if (set(body) - {"theme", "redact", "mp4"} or theme not in ("paper", "night")
|
|
384
|
+
or type(redact) is not bool or type(mp4) is not bool):
|
|
385
|
+
raise ValueError("A GIF takes an optional theme (paper or night), redact and mp4 "
|
|
386
|
+
"(true or false)")
|
|
387
|
+
return self.send_json(self.runtime.save_run_gif(match[1], theme, redact, mp4))
|
|
388
|
+
if run and match[2] == "approval":
|
|
389
|
+
if (set(body) - {"choice", "instruction", "answer", "remember", "items", "text", "allowCost"}
|
|
390
|
+
!= {"id", "approve"}
|
|
391
|
+
or "remember" in body and type(body["remember"]) is not bool
|
|
392
|
+
or "allowCost" in body and type(body["allowCost"]) is not bool
|
|
393
|
+
or not isinstance(body["id"], str)
|
|
394
|
+
or type(body["approve"]) is not bool
|
|
395
|
+
or "choice" in body and not isinstance(body["choice"], str)
|
|
396
|
+
or "instruction" in body and not isinstance(body["instruction"], str)
|
|
397
|
+
or "answer" in body and not isinstance(body["answer"], str)
|
|
398
|
+
or "items" in body and not isinstance(body["items"], list)
|
|
399
|
+
or "text" in body and not isinstance(body["text"], str)):
|
|
400
|
+
raise ValueError("An approval needs its id and approve: true or false (and optionally a "
|
|
401
|
+
"choice, an instruction with approve false, an answer to a question, or "
|
|
402
|
+
"the items it decides and an edited text)")
|
|
403
|
+
pending = run.approval
|
|
404
|
+
question = (pending is not None and pending.get("id") == body["id"]
|
|
405
|
+
and pending.get("kind") == "clarify")
|
|
406
|
+
# Approving or choosing needs the Mac app; a decline, a redirect or a clarifying answer is token-only.
|
|
407
|
+
if (body["approve"] or "choice" in body) and not question:
|
|
408
|
+
if run.origin in APPROVE_IN_APP_ORIGINS and not self.has_app_session():
|
|
409
|
+
return self.send_json({"error": APP_ONLY, "code": "app_only"}, 403)
|
|
410
|
+
if self.needs_app(APPROVE_IN_APP):
|
|
411
|
+
return
|
|
412
|
+
if "items" in body or "text" in body:
|
|
413
|
+
answered = run.decide_proposals(body["id"], body["approve"], body.get("items"),
|
|
414
|
+
body.get("text"))
|
|
415
|
+
else:
|
|
416
|
+
answered = run.answer_approval(body["id"], body["approve"], body.get("choice"),
|
|
417
|
+
body.get("instruction"),
|
|
418
|
+
**({"answer": body["answer"]} if "answer" in body else {}),
|
|
419
|
+
**({"remember": True} if body.get("remember") else {}),
|
|
420
|
+
**({"allow_cost": True} if body.get("allowCost") else {}))
|
|
421
|
+
if not answered:
|
|
422
|
+
decision = run.approval_decision(body["id"])
|
|
423
|
+
raise APIError(ALREADY_ANSWERED.get(decision, "This approval request is no longer waiting"),
|
|
424
|
+
409, "approval_not_pending", **({"decision": decision} if decision else {}))
|
|
425
|
+
return self.send_json({"run": run.public()})
|
|
426
|
+
if run and match[2] == "review":
|
|
427
|
+
if set(body) != {"ok"} or type(body["ok"]) is not bool:
|
|
428
|
+
raise ValueError("A review needs ok: true or false")
|
|
429
|
+
return self.send_json({"run": self.runtime.review_run(run.id, body["ok"]).public()})
|
|
430
|
+
if run:
|
|
431
|
+
self.runtime.stop_run(run.id)
|
|
432
|
+
return self.send_json({"run": run.public()})
|
|
433
|
+
return self.send_json({"error": "Not found"}, 404)
|
|
434
|
+
except GraphError as exc:
|
|
435
|
+
return self.send_json({"error": str(exc), "code": exc.code, "errors": exc.errors}, 400)
|
|
436
|
+
except (ValueError, TypeError) as exc:
|
|
437
|
+
return self.send_json({"error": str(exc)}, 400)
|
|
438
|
+
except APIError as exc:
|
|
439
|
+
return self.send_api_error(exc)
|
|
440
|
+
except WorkflowDispatchUncertain as exc:
|
|
441
|
+
return self.send_json({"error": str(exc), "code": "workflow_dispatch_uncertain", "activeRunId": exc.run_id},
|
|
442
|
+
503)
|
|
443
|
+
except JournalError:
|
|
444
|
+
return self.send_json({"error": "Mobster couldn't save this to its history on this Mac, and a task may "
|
|
445
|
+
"have started. Check its status before you try again.",
|
|
446
|
+
"code": "persistence_unavailable"}, 503)
|
|
447
|
+
except TimeoutError: # the request's socket (5 s, ``setup``)
|
|
448
|
+
return self.send_json({"error": TOOK_TOO_LONG, "code": "request_timeout"}, 408)
|
|
449
|
+
except env_secrets.KeychainError: # a key or the alert address, which never fall back to the file
|
|
450
|
+
return self.send_json({"error": env_secrets.REFUSED, "code": "keychain"}, 503)
|
|
451
|
+
except OSError:
|
|
452
|
+
return self.send_json({"error": FILE_TROUBLE,
|
|
453
|
+
"code": "io_error"}, 500)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""HTTP routes the tracks register, each under its owner's PREFIXES so it can never shadow a core route."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Callable, Iterable, Mapping, Optional
|
|
6
|
+
|
|
7
|
+
ID = r"(?P<id>[a-f0-9]{12})"
|
|
8
|
+
DEVICE = r"(?P<device>[^/]{1,128})"
|
|
9
|
+
HEADER_ORIGINS = ("app", "tui", "cli", "mcp", "remote", "console")
|
|
10
|
+
|
|
11
|
+
PREFIXES = {
|
|
12
|
+
"harness": ("/api/runs/{id}/messages", "/api/runs/{id}/pause", "/api/runs/{id}/pickup", "/api/prewarm",
|
|
13
|
+
"/api/harness"),
|
|
14
|
+
"integrations": ("/api/integrations",),
|
|
15
|
+
"threads": ("/api/threads",),
|
|
16
|
+
"memory": ("/api/memory",),
|
|
17
|
+
"files": ("/api/attachments", "/api/phone/files"),
|
|
18
|
+
"wireless": ("/api/wireless", "/api/devices/{device}/wifi"),
|
|
19
|
+
"testkit": ("/api/tests",),
|
|
20
|
+
"remote": ("/api/remote",),
|
|
21
|
+
"accounts": ("/api/accounts",),
|
|
22
|
+
"learned": ("/api/replay",),
|
|
23
|
+
"longform": ("/api/longform", "/api/runs/{id}/longform"),
|
|
24
|
+
"outlets": ("/api/outlets", "/api/runs/{id}/send"),
|
|
25
|
+
"connectors": ("/api/connectors", "/api/runs/{id}/issue"),
|
|
26
|
+
"plugins": ("/api/plugins",),
|
|
27
|
+
"permissions": ("/api/permissions", "/api/approvals"),
|
|
28
|
+
"packs": ("/api/packs",),
|
|
29
|
+
"waiting": ("/api/waiting",),
|
|
30
|
+
"netxray": ("/api/xray/network",),
|
|
31
|
+
# In no track of its own, so it never answers 503.
|
|
32
|
+
"access": ("/api/access",),
|
|
33
|
+
"triggers": ("/api/triggers",),
|
|
34
|
+
"queue": ("/api/queue", "/api/transfers"),
|
|
35
|
+
"feedback": ("/api/feedback",),
|
|
36
|
+
"teams": ("/api/teams",),
|
|
37
|
+
"xray": ("/api/xray/app",),
|
|
38
|
+
"projectenv": ("/api/project/env",),
|
|
39
|
+
"projects": ("/api/projects",),
|
|
40
|
+
}
|
|
41
|
+
# A route answers 503 while its owner's track is off.
|
|
42
|
+
TRACK = {"harness": "harness", "integrations": "harness",
|
|
43
|
+
"threads": "threads", "memory": "memory", "files": "attachments",
|
|
44
|
+
"wireless": "wireless", "testkit": "testkit", "remote": "remote", "accounts": "accounts",
|
|
45
|
+
"learned": "learned", "longform": "longform", "outlets": "outlets",
|
|
46
|
+
"connectors": "connectors", "plugins": "plugins",
|
|
47
|
+
"permissions": "permissions", "packs": "packs", "waiting": "waiting", "netxray": "netxray",
|
|
48
|
+
"triggers": "triggers", "queue": "content_queue", "feedback": "feedback", "xray": "xray", "teams": "teams",
|
|
49
|
+
"projects": "projects"}
|
|
50
|
+
BODIES = ("json", "raw", "none")
|
|
51
|
+
JSON_MAX = 64_000
|
|
52
|
+
NEWER_DATA = "This was saved by a newer Mobster. Update Mobster to use it."
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def expand(pattern):
|
|
56
|
+
return pattern.replace("{id}", ID).replace("{device}", DEVICE)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class Route:
|
|
61
|
+
owner: str
|
|
62
|
+
method: str
|
|
63
|
+
pattern: str
|
|
64
|
+
handler: Callable[["Request"], "Response"]
|
|
65
|
+
body: str = "json"
|
|
66
|
+
max_bytes: int = JSON_MAX
|
|
67
|
+
stream: bool = False
|
|
68
|
+
idempotency: bool = False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Request:
|
|
73
|
+
runtime: Any
|
|
74
|
+
method: str
|
|
75
|
+
path: str
|
|
76
|
+
params: dict
|
|
77
|
+
query: dict
|
|
78
|
+
headers: Mapping[str, str]
|
|
79
|
+
body: Any
|
|
80
|
+
content_type: Optional[str]
|
|
81
|
+
origin: str # a UI label only: it grants nothing
|
|
82
|
+
app_session: bool
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Response:
|
|
86
|
+
def __init__(self, status=200, *, value=None, data=None, content_type=None, events=None, keepalive_s=5.0,
|
|
87
|
+
filename=None):
|
|
88
|
+
self.status, self.value, self.data, self.content_type = status, value, data, content_type
|
|
89
|
+
self.events, self.keepalive_s = events, keepalive_s
|
|
90
|
+
self.filename = filename
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def json(value, status: int = 200) -> "Response":
|
|
94
|
+
return Response(status, value=value)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def bytes(data: bytes, content_type: str, status: int = 200, filename: Optional[str] = None) -> "Response":
|
|
98
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
99
|
+
raise TypeError("Response.bytes takes bytes")
|
|
100
|
+
return Response(status, data=bytes(data), content_type=content_type, filename=filename)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def sse(events: Iterable[Optional[dict]], *, keepalive_s: float = 5.0) -> "Response":
|
|
104
|
+
return Response(200, events=events, keepalive_s=keepalive_s)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def error(message: str, status: int, code: str, **details) -> "Response":
|
|
108
|
+
return Response(status, value={"error": message, "code": code, **details})
|
|
109
|
+
|
|
110
|
+
def is_stream(self):
|
|
111
|
+
return self.events is not None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_routes: list = []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def register(route: Route) -> None:
|
|
120
|
+
if route.owner not in PREFIXES:
|
|
121
|
+
raise ValueError(f"Unknown route owner {route.owner!r}")
|
|
122
|
+
if route.method not in ("GET", "POST", "DELETE"):
|
|
123
|
+
raise ValueError("A route's method is GET, POST or DELETE")
|
|
124
|
+
if route.body not in BODIES:
|
|
125
|
+
raise ValueError("A route's body is json, raw or none")
|
|
126
|
+
if route.body == "json" and not 0 < route.max_bytes <= JSON_MAX:
|
|
127
|
+
raise ValueError("A JSON body is at most 64 KB")
|
|
128
|
+
if route.body == "raw" and not 0 < route.max_bytes <= 64 * 1024 * 1024:
|
|
129
|
+
raise ValueError("A raw body states its cap, at most 64 MB")
|
|
130
|
+
if not callable(route.handler):
|
|
131
|
+
raise ValueError("A route needs a handler")
|
|
132
|
+
pattern = expand(route.pattern)
|
|
133
|
+
if not any(_inside(pattern, expand(prefix)) for prefix in PREFIXES[route.owner]):
|
|
134
|
+
raise ValueError(f"{route.pattern} is outside {route.owner}'s prefixes")
|
|
135
|
+
try:
|
|
136
|
+
re.compile(pattern)
|
|
137
|
+
except re.error as error:
|
|
138
|
+
raise ValueError(f"Invalid route pattern: {error}") from None
|
|
139
|
+
if any(r.method == route.method and expand(r.pattern) == pattern for r in _routes):
|
|
140
|
+
raise ValueError(f"{route.method} {route.pattern} is already registered")
|
|
141
|
+
_routes.append(route)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _inside(pattern, prefix):
|
|
145
|
+
if not pattern.startswith(prefix):
|
|
146
|
+
return False
|
|
147
|
+
rest = pattern[len(prefix):]
|
|
148
|
+
return rest == "" or rest[0] in "/(?$"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _owned(path, owner):
|
|
152
|
+
return any(re.fullmatch(expand(prefix) + r"(?:/.*)?", path) for prefix in PREFIXES[owner])
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def match(method: str, path: str) -> Optional[tuple]:
|
|
156
|
+
for route in _routes:
|
|
157
|
+
if route.method != method:
|
|
158
|
+
continue
|
|
159
|
+
found = re.fullmatch(expand(route.pattern), path)
|
|
160
|
+
if found and _owned(path, route.owner):
|
|
161
|
+
return route, {k: v for k, v in found.groupdict().items() if v is not None}
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def track_off(route) -> bool:
|
|
166
|
+
from . import harness_api
|
|
167
|
+
return harness_api.disabled(TRACK.get(route.owner))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def reset_for_tests() -> None:
|
|
173
|
+
_routes.clear()
|