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,269 @@
|
|
|
1
|
+
"""The Slack connector's tools, answered by Mobster with Slack's Web API and your sign-in (a user token), in place of
|
|
2
|
+
Slack's MCP server, which takes only apps listed in the Slack Marketplace and a workspace's own internal apps."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
|
|
9
|
+
from ..outlets import net, webhook
|
|
10
|
+
from . import client as mcp
|
|
11
|
+
from .api_session import ApiSession, Refusal, count_schema, required_text, short, tool_spec
|
|
12
|
+
|
|
13
|
+
API = "https://slack.com/api"
|
|
14
|
+
NAME = "Slack"
|
|
15
|
+
SEARCH_LIMIT, HISTORY_LIMIT, THREAD_LIMIT = 20, 50, 100
|
|
16
|
+
CHANNEL_PAGES = 5 # users.conversations pages of 200
|
|
17
|
+
TEXT_CHARS = 2000 # what is kept of one message's text
|
|
18
|
+
READ_LIMIT = 4 * 1024 * 1024 # a page of history with its blocks can pass net.READ_LIMIT
|
|
19
|
+
CHANNEL_ID = re.compile(r"[CGD][A-Z0-9]{2,30}")
|
|
20
|
+
CHANNEL_NAME = re.compile(r"#?([a-z0-9][a-z0-9._-]{0,79})")
|
|
21
|
+
TS = re.compile(r"\d{9,11}\.\d{1,6}")
|
|
22
|
+
SIGNED_OUT = frozenset({"invalid_auth", "token_expired", "not_authed", "token_revoked", "account_inactive",
|
|
23
|
+
"user_removed_from_team", "team_disabled"})
|
|
24
|
+
REFUSED = {
|
|
25
|
+
"channel_not_found": "Slack has no channel with that id, or you aren't in it. Use list_channels for the ids",
|
|
26
|
+
"not_in_channel": "You aren't in that channel. Join it in Slack first",
|
|
27
|
+
"is_archived": "That channel was archived",
|
|
28
|
+
"thread_not_found": "Slack has no thread at that ts in that channel. Use the ts of the thread's first message",
|
|
29
|
+
"restricted_action": "A workspace admin turned posting there off",
|
|
30
|
+
"cant_post": "You can't post in that channel",
|
|
31
|
+
"msg_too_long": "The message is too long for Slack. Shorten it",
|
|
32
|
+
"no_text": "The message has no text",
|
|
33
|
+
"feature_not_enabled": "Slack's search API isn't available on this workspace. Read a channel instead",
|
|
34
|
+
"assistant_search_context_disabled": "Slack's search is having trouble. Try again in a minute",
|
|
35
|
+
"query_too_long": "The search words are too long. Use fewer",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
CHANNEL = {"type": "string", "description": "A channel's or DM's id (C…, G… or D…), or a channel's name with #."}
|
|
40
|
+
TOOLS = (
|
|
41
|
+
tool_spec("search_messages", "Search Slack messages",
|
|
42
|
+
"Search your Slack messages in public and private channels, group DMs and DMs, newest first. Slack's "
|
|
43
|
+
"search words work: from:@name, in:#channel, before:2026-10-01, after:2026-10-01, is:thread. Each "
|
|
44
|
+
"message has its author, channel, channel id, ts and link.",
|
|
45
|
+
{"query": {"type": "string", "description": "What to search for, with Slack's search words."},
|
|
46
|
+
"limit": count_schema(SEARCH_LIMIT, 10),
|
|
47
|
+
"sort": {"type": "string", "enum": ["timestamp", "score"],
|
|
48
|
+
"description": "timestamp: newest first (the default); score: best match first."}},
|
|
49
|
+
required=("query",)),
|
|
50
|
+
tool_spec("list_channels", "List my Slack channels",
|
|
51
|
+
"The public and private channels you're in: each one's id and #name.", {}),
|
|
52
|
+
tool_spec("read_channel", "Read a Slack channel",
|
|
53
|
+
"A channel's or DM's latest messages, newest first: each one's author's user id, ts, text and how many "
|
|
54
|
+
"replies its thread has.",
|
|
55
|
+
{"channel": CHANNEL,
|
|
56
|
+
"limit": count_schema(HISTORY_LIMIT, 20)},
|
|
57
|
+
required=("channel",)),
|
|
58
|
+
tool_spec("read_thread", "Read a Slack thread",
|
|
59
|
+
"A thread's messages, oldest first, from the ts of its first message (a search "
|
|
60
|
+
"result's or read_channel's).",
|
|
61
|
+
{"channel": CHANNEL, "ts": {"type": "string", "description": "The ts of the thread's first message."},
|
|
62
|
+
"limit": count_schema(THREAD_LIMIT, 50)},
|
|
63
|
+
required=("channel", "ts")),
|
|
64
|
+
tool_spec("send_message", "Send a Slack message",
|
|
65
|
+
"Post a message as you in a channel or DM, or as a reply in a thread. Plain text: Mobster escapes &, < "
|
|
66
|
+
"and >, so it can't mention @channel or people, or make links.",
|
|
67
|
+
{"channel": CHANNEL, "text": {"type": "string", "description": "The message."},
|
|
68
|
+
"thread_ts": {"type": "string", "description": "Reply in this thread: the ts of its first message."}},
|
|
69
|
+
required=("channel", "text"), read_only=False),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def connect(*, token=None, timeout=mcp.CONNECT_TIMEOUT, name=NAME, call=None):
|
|
74
|
+
"""A ready SlackSession for ``token``. AuthNeeded without one, or when Slack refuses it."""
|
|
75
|
+
session = SlackSession(token, name=name, call=call)
|
|
76
|
+
session.initialize(timeout=timeout)
|
|
77
|
+
return session
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class SlackSession(ApiSession):
|
|
81
|
+
"""The MCP session's surface (client.Session) over Slack's Web API."""
|
|
82
|
+
|
|
83
|
+
NAME, TOOLS = NAME, TOOLS
|
|
84
|
+
|
|
85
|
+
def _api(self, method, params, deadline, write=False):
|
|
86
|
+
"""Slack's answer to ``method`` when it's ``ok``, or a ``Refusal``. Raises AuthNeeded, NotSent and, for a
|
|
87
|
+
write, OutcomeUnknown (a 5xx or no answer: it may have posted, and it's never sent again)."""
|
|
88
|
+
if not self.token:
|
|
89
|
+
raise mcp.AuthNeeded(f"{self.name} isn't signed in.")
|
|
90
|
+
timeout = max(1.0, deadline - time.monotonic())
|
|
91
|
+
body = urlencode({k: v for k, v in params.items() if v is not None}).encode()
|
|
92
|
+
headers = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/x-www-form-urlencoded",
|
|
93
|
+
"User-Agent": webhook.USER_AGENT}
|
|
94
|
+
try:
|
|
95
|
+
reply = self.call("POST", f"{API}/{method}", body, headers, timeout=timeout, read_limit=READ_LIMIT)
|
|
96
|
+
except net.NetError as error:
|
|
97
|
+
if error.unsent:
|
|
98
|
+
raise mcp.NotSent(error.message) from None
|
|
99
|
+
if write:
|
|
100
|
+
raise mcp.OutcomeUnknown(f"{error.message} The message may have posted: check Slack before you send it "
|
|
101
|
+
"again.") from None
|
|
102
|
+
raise mcp.OutcomeUnknown(error.message) from None
|
|
103
|
+
if reply.status == 429:
|
|
104
|
+
wait = reply.retry_after if str(reply.retry_after or "").isdigit() else "a few"
|
|
105
|
+
return Refusal(f"Slack asked Mobster to wait {wait} seconds. Try again then.")
|
|
106
|
+
if reply.status >= 500:
|
|
107
|
+
if write:
|
|
108
|
+
raise mcp.OutcomeUnknown(f"Slack answered {reply.status}. The message may have posted: check Slack "
|
|
109
|
+
"before you send it again.")
|
|
110
|
+
return Refusal(f"Slack answered {reply.status}. Try again in a minute.")
|
|
111
|
+
try:
|
|
112
|
+
answer = json.loads(reply.body.decode("utf-8"))
|
|
113
|
+
except (ValueError, UnicodeDecodeError):
|
|
114
|
+
answer = None
|
|
115
|
+
if not isinstance(answer, dict):
|
|
116
|
+
if write and reply.status == 200:
|
|
117
|
+
raise mcp.OutcomeUnknown("Slack's answer couldn't be read. The message may have posted: check Slack "
|
|
118
|
+
"before you send it again.")
|
|
119
|
+
return Refusal(f"Slack answered {reply.status} without a reply Mobster could read.")
|
|
120
|
+
if answer.get("ok") is True:
|
|
121
|
+
return answer
|
|
122
|
+
error = str(answer.get("error") or "unknown")[:60]
|
|
123
|
+
if error in SIGNED_OUT:
|
|
124
|
+
raise mcp.AuthNeeded(f"Slack signed Mobster out ({error}). Sign in to Slack again in Settings › "
|
|
125
|
+
"Connectors.")
|
|
126
|
+
if error == "missing_scope":
|
|
127
|
+
needed = str(answer.get("needed") or "a scope it wasn't given")[:120]
|
|
128
|
+
return Refusal(f"Your Slack sign-in can't do this: it needs {needed}. Sign in to Slack again in Settings › "
|
|
129
|
+
"Connectors, and allow what Slack asks.")
|
|
130
|
+
if error in ("ratelimited", "rate_limited"):
|
|
131
|
+
return Refusal("Slack asked Mobster to wait. Try again in a minute.")
|
|
132
|
+
return Refusal(f"Slack refused ({error}). {REFUSED[error]}." if error in REFUSED
|
|
133
|
+
else f"Slack refused ({error}).")
|
|
134
|
+
|
|
135
|
+
def initialize(self, timeout=mcp.CONNECT_TIMEOUT):
|
|
136
|
+
"""auth.test: the token works (it needs no scope). AuthNeeded or NotSent when it doesn't."""
|
|
137
|
+
answer = self._api("auth.test", {}, time.monotonic() + timeout)
|
|
138
|
+
if isinstance(answer, Refusal):
|
|
139
|
+
raise mcp.NotSent(answer.message)
|
|
140
|
+
self.server_info = {"name": NAME, "team": str(answer.get("team") or "")[:100]}
|
|
141
|
+
self.ready = True
|
|
142
|
+
return {"serverInfo": self.server_info}
|
|
143
|
+
|
|
144
|
+
def ping(self, timeout=mcp.PING_TIMEOUT):
|
|
145
|
+
if not self.ready:
|
|
146
|
+
raise mcp.NotSent(f"{self.name} isn't connected.")
|
|
147
|
+
answer = self._api("auth.test", {}, time.monotonic() + timeout)
|
|
148
|
+
if isinstance(answer, Refusal):
|
|
149
|
+
raise mcp.NotSent(answer.message)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def _search_messages(self, args, deadline):
|
|
153
|
+
query = required_text(args.get("query"), "query", 500)
|
|
154
|
+
answer = self._api("assistant.search.context", {
|
|
155
|
+
"query": query, "channel_types": "public_channel,private_channel,mpim,im", "content_types": "messages",
|
|
156
|
+
"limit": _limit(args.get("limit"), 10, SEARCH_LIMIT),
|
|
157
|
+
"sort": _choice(args.get("sort"), ("timestamp", "score")),
|
|
158
|
+
"sort_dir": "desc"}, deadline)
|
|
159
|
+
if isinstance(answer, Refusal):
|
|
160
|
+
return answer
|
|
161
|
+
results = answer.get("results") if isinstance(answer.get("results"), dict) else {}
|
|
162
|
+
messages = [{"author": short(m.get("author_name") or m.get("author_user_id"), 100),
|
|
163
|
+
"channel": short(m.get("channel_name"), 100), "channelId": short(m.get("channel_id"), 40),
|
|
164
|
+
"ts": short(m.get("message_ts"), 40), "text": short(m.get("content"), TEXT_CHARS),
|
|
165
|
+
"link": short(m.get("permalink"), 300)}
|
|
166
|
+
for m in results.get("messages") or () if isinstance(m, dict)]
|
|
167
|
+
more = bool((answer.get("response_metadata") or {}).get("next_cursor"))
|
|
168
|
+
return {"messages": messages, "more": more}
|
|
169
|
+
|
|
170
|
+
def _channels(self, deadline):
|
|
171
|
+
found, cursor = [], None
|
|
172
|
+
for _ in range(CHANNEL_PAGES):
|
|
173
|
+
answer = self._api("users.conversations", {"types": "public_channel,private_channel",
|
|
174
|
+
"exclude_archived": "true", "limit": "200", "cursor": cursor},
|
|
175
|
+
deadline)
|
|
176
|
+
if isinstance(answer, Refusal):
|
|
177
|
+
return answer
|
|
178
|
+
for item in answer.get("channels") or ():
|
|
179
|
+
if isinstance(item, dict) and CHANNEL_ID.fullmatch(str(item.get("id"))) \
|
|
180
|
+
and isinstance(item.get("name"), str):
|
|
181
|
+
found.append({"id": item["id"], "name": "#" + item["name"][:80],
|
|
182
|
+
"private": bool(item.get("is_private"))})
|
|
183
|
+
cursor = (answer.get("response_metadata") or {}).get("next_cursor")
|
|
184
|
+
if not cursor:
|
|
185
|
+
break
|
|
186
|
+
return sorted(found, key=lambda item: item["name"])
|
|
187
|
+
|
|
188
|
+
def _list_channels(self, args, deadline):
|
|
189
|
+
found = self._channels(deadline)
|
|
190
|
+
return found if isinstance(found, Refusal) else {"channels": found}
|
|
191
|
+
|
|
192
|
+
def _channel_id(self, value, deadline):
|
|
193
|
+
"""A channel id as given, or the id of the channel you're in by that #name."""
|
|
194
|
+
text = required_text(value, "channel", 90)
|
|
195
|
+
if CHANNEL_ID.fullmatch(text):
|
|
196
|
+
return text
|
|
197
|
+
named = CHANNEL_NAME.fullmatch(text.lower())
|
|
198
|
+
if not named:
|
|
199
|
+
raise ValueError("channel is a channel's id (C…, G… or D…) or its #name")
|
|
200
|
+
found = self._channels(deadline)
|
|
201
|
+
if isinstance(found, Refusal):
|
|
202
|
+
return found
|
|
203
|
+
match = next((c["id"] for c in found if c["name"] == "#" + named.group(1)), None)
|
|
204
|
+
if match is None:
|
|
205
|
+
raise ValueError(f"You aren't in a channel called #{named.group(1)}. Use list_channels for the ones "
|
|
206
|
+
"you're in")
|
|
207
|
+
return match
|
|
208
|
+
|
|
209
|
+
def _read_channel(self, args, deadline):
|
|
210
|
+
channel = self._channel_id(args.get("channel"), deadline)
|
|
211
|
+
if isinstance(channel, Refusal):
|
|
212
|
+
return channel
|
|
213
|
+
answer = self._api("conversations.history", {"channel": channel,
|
|
214
|
+
"limit": _limit(args.get("limit"), 20, HISTORY_LIMIT)}, deadline)
|
|
215
|
+
return answer if isinstance(answer, Refusal) else {"channelId": channel, "messages": _messages(answer),
|
|
216
|
+
"more": bool(answer.get("has_more"))}
|
|
217
|
+
|
|
218
|
+
def _read_thread(self, args, deadline):
|
|
219
|
+
channel = self._channel_id(args.get("channel"), deadline)
|
|
220
|
+
if isinstance(channel, Refusal):
|
|
221
|
+
return channel
|
|
222
|
+
ts = required_text(args.get("ts"), "ts", 20)
|
|
223
|
+
if not TS.fullmatch(ts):
|
|
224
|
+
raise ValueError("ts is a message's ts, such as 1760000000.123456")
|
|
225
|
+
answer = self._api("conversations.replies", {"channel": channel, "ts": ts,
|
|
226
|
+
"limit": _limit(args.get("limit"), 50, THREAD_LIMIT)}, deadline)
|
|
227
|
+
return answer if isinstance(answer, Refusal) else {"channelId": channel, "messages": _messages(answer),
|
|
228
|
+
"more": bool(answer.get("has_more"))}
|
|
229
|
+
|
|
230
|
+
def _send_message(self, args, deadline):
|
|
231
|
+
channel = self._channel_id(args.get("channel"), deadline)
|
|
232
|
+
if isinstance(channel, Refusal):
|
|
233
|
+
return channel
|
|
234
|
+
text = required_text(args.get("text"), "text", 40_000)
|
|
235
|
+
thread = args.get("thread_ts")
|
|
236
|
+
if thread is not None and not TS.fullmatch(str(thread)):
|
|
237
|
+
raise ValueError("thread_ts is the ts of the thread's first message, such as 1760000000.123456")
|
|
238
|
+
# &, < and > are escaped and link_names is off, so text from a screen can't mention @channel or a person, or
|
|
239
|
+
# make a link.
|
|
240
|
+
answer = self._api("chat.postMessage", {"channel": channel, "text": webhook.slack_text(text),
|
|
241
|
+
"thread_ts": thread,
|
|
242
|
+
"link_names": "false", "unfurl_links": "false",
|
|
243
|
+
"unfurl_media": "false"}, deadline, write=True)
|
|
244
|
+
if isinstance(answer, Refusal):
|
|
245
|
+
return answer
|
|
246
|
+
return {"posted": True, "channelId": short(answer.get("channel"), 40), "ts": short(answer.get("ts"), 40)}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _messages(answer):
|
|
250
|
+
return [{"user": short(m.get("user") or m.get("bot_id"), 40), "ts": short(m.get("ts"), 40),
|
|
251
|
+
"text": short(m.get("text"), TEXT_CHARS),
|
|
252
|
+
"replies": m.get("reply_count") if isinstance(m.get("reply_count"), int) else 0}
|
|
253
|
+
for m in answer.get("messages") or () if isinstance(m, dict)]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _limit(value, default, top):
|
|
257
|
+
if value is None:
|
|
258
|
+
return str(default)
|
|
259
|
+
if type(value) is not int or not 1 <= value <= top:
|
|
260
|
+
raise ValueError(f"limit is a whole number from 1 to {top}")
|
|
261
|
+
return str(value)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _choice(value, choices):
|
|
265
|
+
if value is None:
|
|
266
|
+
return choices[0]
|
|
267
|
+
if value not in choices:
|
|
268
|
+
raise ValueError(f"sort is {' or '.join(choices)}")
|
|
269
|
+
return value
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""The workflow builder's "Use a connector" step (batch.register_step, kind ``connector``): one tool of a connected
|
|
2
|
+
service, called by the workflow itself, with no model and without the phone."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from . import client as mcp
|
|
7
|
+
from . import service as connector_service
|
|
8
|
+
from . import store as connector_store
|
|
9
|
+
|
|
10
|
+
ARGS_CHARS = 16_000
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _service(runtime):
|
|
14
|
+
service = connector_service.service_for(runtime)
|
|
15
|
+
if service is None:
|
|
16
|
+
raise ValueError("Connectors aren't available in this Mobster")
|
|
17
|
+
return service
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate(step, runtime):
|
|
21
|
+
"""The step as saved, or ValueError with a sentence."""
|
|
22
|
+
if set(step) - {"id", "kind", "connector", "tool", "arguments"}:
|
|
23
|
+
raise ValueError("A connector step has a connector, a tool and its arguments")
|
|
24
|
+
connector, tool = step.get("connector"), step.get("tool")
|
|
25
|
+
if not isinstance(connector, str) or not connector:
|
|
26
|
+
raise ValueError("Choose a connector")
|
|
27
|
+
if not isinstance(tool, str) or not 0 < len(tool) <= 128:
|
|
28
|
+
raise ValueError("Choose one of the connector's tools")
|
|
29
|
+
arguments = step.get("arguments") if step.get("arguments") is not None else {}
|
|
30
|
+
if not isinstance(arguments, dict):
|
|
31
|
+
raise ValueError("A connector step's arguments are one JSON object")
|
|
32
|
+
if len(json.dumps(arguments)) > ARGS_CHARS:
|
|
33
|
+
raise ValueError("Those arguments are too long")
|
|
34
|
+
if runtime is not None:
|
|
35
|
+
row = _service(runtime).store.get(connector)
|
|
36
|
+
if row is None:
|
|
37
|
+
raise ValueError("That connector was removed. Choose another one.")
|
|
38
|
+
if row["tools"] and not any(t["name"] == tool for t in row["tools"]):
|
|
39
|
+
raise ValueError(f"{row['name']} has no tool called {tool}. Test it in Settings › "
|
|
40
|
+
"Connectors, then choose again.")
|
|
41
|
+
return {"connector": connector, "tool": tool, "arguments": arguments}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _fill(value, values, fill):
|
|
45
|
+
if isinstance(value, str):
|
|
46
|
+
return fill(value, values)
|
|
47
|
+
if isinstance(value, list):
|
|
48
|
+
return [_fill(item, values, fill) for item in value]
|
|
49
|
+
if isinstance(value, dict):
|
|
50
|
+
return {key: _fill(item, values, fill) for key, item in value.items()}
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run(batch, step, values):
|
|
55
|
+
"""One call. {"answer", "line"}, or batch.Stop with a sentence (nothing sent unless the sentence says so)."""
|
|
56
|
+
from ..secret_filter import redact
|
|
57
|
+
from ..workflows.records import Stop
|
|
58
|
+
from .skill import result_text
|
|
59
|
+
service = connector_service.service_for(batch.runtime)
|
|
60
|
+
if service is None:
|
|
61
|
+
raise Stop("dispatch_failed", "Connectors aren't available in this Mobster.")
|
|
62
|
+
row = service.store.get(step["connector"])
|
|
63
|
+
if row is None:
|
|
64
|
+
raise Stop("dispatch_failed", "The connector this step uses was removed. Choose another one.")
|
|
65
|
+
tool, name = step["tool"], row["name"]
|
|
66
|
+
if not row["enabled"]:
|
|
67
|
+
raise Stop("dispatch_failed", f"{name} is turned off in Settings › Connectors, so this step didn't run.")
|
|
68
|
+
if row["tools"] and not any(t["name"] == tool for t in row["tools"]):
|
|
69
|
+
raise Stop("dispatch_failed", f"{name} no longer has a tool called {tool}.")
|
|
70
|
+
mode = connector_store.mode_of(row, tool)
|
|
71
|
+
if mode == "off":
|
|
72
|
+
raise Stop("dispatch_failed", f"{name}: {tool} is blocked in Settings › Connectors, so this step didn't run.")
|
|
73
|
+
if mode != "read":
|
|
74
|
+
raise Stop("dispatch_failed", f"{name}: {tool} asks you first, and a workflow step has nobody to ask, so "
|
|
75
|
+
"nothing was sent. Allow it in Settings › Connectors, or use "
|
|
76
|
+
"a task step that names it.")
|
|
77
|
+
arguments = _fill(step.get("arguments") or {}, values, batch._fill)
|
|
78
|
+
try:
|
|
79
|
+
result = service.call(row, tool, arguments)
|
|
80
|
+
except mcp.OutcomeUnknown as error:
|
|
81
|
+
raise Stop("dispatch_failed", f"{name}: {tool} didn't answer ({error.message}) It may or may not have "
|
|
82
|
+
f"happened, so the workflow stopped and won't send it again.",
|
|
83
|
+
unknown=True) from None
|
|
84
|
+
except mcp.ConnectorError as error:
|
|
85
|
+
raise Stop("dispatch_failed", f"{name}: {tool} wasn't called: {redact(error.message)}",
|
|
86
|
+
retryable=error.sent is False) from None
|
|
87
|
+
text = result_text(result)
|
|
88
|
+
if result.get("isError"):
|
|
89
|
+
raise Stop("dispatch_failed", f"{name}: {tool} returned an error: {text[:200]}")
|
|
90
|
+
structured = result.get("structuredContent")
|
|
91
|
+
return {"answer": text, "line": f"Used {name}: {tool}",
|
|
92
|
+
"structured": structured if isinstance(structured, dict) else None}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Connectors' state in the run journal: schema ``connectors`` v1, in the same private SQLite file (0600).
|
|
2
|
+
|
|
3
|
+
``connectors``: one row per MCP server the person added.
|
|
4
|
+
|
|
5
|
+
| Column | What it holds |
|
|
6
|
+
|---|---|
|
|
7
|
+
| id | 12 hex characters |
|
|
8
|
+
| name | what the person and the model call it ("Notion"); unique, case ignored |
|
|
9
|
+
| transport | "stdio" or "http" |
|
|
10
|
+
| command, args | stdio: the command and its arguments (JSON list). Never an env value |
|
|
11
|
+
| url | http: the server's URL |
|
|
12
|
+
| auth | "none", "token" or "oauth": what the Keychain item holds (vault.py) |
|
|
13
|
+
| enabled | 0 or 1 |
|
|
14
|
+
| preset | the preset's key ("notion"), or NULL |
|
|
15
|
+
| plugin | the plugin that added it, or NULL (plugins track) |
|
|
16
|
+
| tools | the last ``tools/list``, trimmed (JSON list of {name, description, inputSchema, readOnly}) |
|
|
17
|
+
| tools_at | when ``tools`` was read |
|
|
18
|
+
| modes | {tool: "read" \\| "ask" \\| "off"} the person chose (JSON). A tool not in it is "ask" |
|
|
19
|
+
| last_test | {ok, at, message} of the last connect (JSON) |
|
|
20
|
+
| created_at, updated_at | seconds |
|
|
21
|
+
|
|
22
|
+
Writes go through ``Journal.transaction()``, reads hold ``Journal.lock``.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import sqlite3
|
|
27
|
+
|
|
28
|
+
from ..journal import JournalError
|
|
29
|
+
from . import presets
|
|
30
|
+
|
|
31
|
+
SCHEMA = "connectors"
|
|
32
|
+
VERSION = 1
|
|
33
|
+
MAX_CONNECTORS = 50
|
|
34
|
+
MODES = ("read", "ask", "off")
|
|
35
|
+
DEFAULT_MODE = "ask" # unknown means ask: a tool nobody chose for never runs unasked
|
|
36
|
+
COLUMNS = ("id", "name", "transport", "command", "args", "url", "auth", "enabled", "preset", "plugin", "tools",
|
|
37
|
+
"tools_at", "modes", "last_test", "created_at", "updated_at")
|
|
38
|
+
JSON_COLUMNS = ("args", "tools", "modes", "last_test")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def migrate(connection, from_version):
|
|
42
|
+
if from_version < 1:
|
|
43
|
+
connection.execute("""CREATE TABLE IF NOT EXISTS connectors (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
name TEXT NOT NULL,
|
|
46
|
+
transport TEXT NOT NULL CHECK (transport IN ('stdio', 'http')),
|
|
47
|
+
command TEXT,
|
|
48
|
+
args TEXT,
|
|
49
|
+
url TEXT,
|
|
50
|
+
auth TEXT NOT NULL DEFAULT 'none',
|
|
51
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
52
|
+
preset TEXT,
|
|
53
|
+
plugin TEXT,
|
|
54
|
+
tools TEXT,
|
|
55
|
+
tools_at REAL,
|
|
56
|
+
modes TEXT,
|
|
57
|
+
last_test TEXT,
|
|
58
|
+
created_at REAL NOT NULL,
|
|
59
|
+
updated_at REAL NOT NULL)""")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def mode_of(row, tool):
|
|
63
|
+
"""The mode of ``tool`` on a connector row: what the person chose, else "ask"."""
|
|
64
|
+
mode = (row.get("modes") or {}).get(tool)
|
|
65
|
+
mode = mode if mode in MODES else DEFAULT_MODE
|
|
66
|
+
# A tool of its preset's always_ask (a send to other people) never runs unasked: "read" counts as "ask".
|
|
67
|
+
if mode == "read" and tool in (presets.preset(row.get("preset")) or {}).get("always_ask", ()):
|
|
68
|
+
return "ask"
|
|
69
|
+
return mode
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _row(values):
|
|
73
|
+
if values is None:
|
|
74
|
+
return None
|
|
75
|
+
row = dict(zip(COLUMNS, values))
|
|
76
|
+
for column in JSON_COLUMNS:
|
|
77
|
+
try:
|
|
78
|
+
row[column] = json.loads(row[column]) if row[column] else None
|
|
79
|
+
except ValueError:
|
|
80
|
+
row[column] = None
|
|
81
|
+
row["args"] = row["args"] or []
|
|
82
|
+
row["tools"] = row["tools"] or []
|
|
83
|
+
row["modes"] = row["modes"] if isinstance(row["modes"], dict) else {}
|
|
84
|
+
row["enabled"] = bool(row["enabled"])
|
|
85
|
+
return row
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ConnectorStore:
|
|
89
|
+
def __init__(self, journal):
|
|
90
|
+
self.journal = journal
|
|
91
|
+
|
|
92
|
+
def _read(self, sql, args=()):
|
|
93
|
+
with self.journal.lock:
|
|
94
|
+
if self.journal.connection is None:
|
|
95
|
+
raise JournalError("The run journal is closed")
|
|
96
|
+
try:
|
|
97
|
+
return self.journal.connection.execute(sql, args).fetchall()
|
|
98
|
+
except sqlite3.Error:
|
|
99
|
+
raise JournalError("Mobster could not read its connectors") from None
|
|
100
|
+
|
|
101
|
+
def all(self):
|
|
102
|
+
return [_row(r) for r in self._read(f"SELECT {', '.join(COLUMNS)} FROM connectors ORDER BY created_at, rowid")]
|
|
103
|
+
|
|
104
|
+
def get(self, connector_id):
|
|
105
|
+
rows = self._read(f"SELECT {', '.join(COLUMNS)} FROM connectors WHERE id=?", (connector_id,))
|
|
106
|
+
return _row(rows[0]) if rows else None
|
|
107
|
+
|
|
108
|
+
def count(self):
|
|
109
|
+
return self._read("SELECT count(*) FROM connectors")[0][0]
|
|
110
|
+
|
|
111
|
+
def add(self, row):
|
|
112
|
+
values = []
|
|
113
|
+
for column in COLUMNS:
|
|
114
|
+
value = row.get(column)
|
|
115
|
+
if column in JSON_COLUMNS:
|
|
116
|
+
value = json.dumps(value) if value is not None else None
|
|
117
|
+
elif column == "enabled":
|
|
118
|
+
value = int(bool(value if value is not None else True))
|
|
119
|
+
values.append(value)
|
|
120
|
+
with self.journal.transaction() as connection:
|
|
121
|
+
connection.execute(f"INSERT INTO connectors({', '.join(COLUMNS)}) VALUES ({', '.join('?' * len(COLUMNS))})",
|
|
122
|
+
values)
|
|
123
|
+
return self.get(row["id"])
|
|
124
|
+
|
|
125
|
+
def update(self, connector_id, **changes):
|
|
126
|
+
allowed = set(COLUMNS) - {"id", "created_at"}
|
|
127
|
+
if set(changes) - allowed:
|
|
128
|
+
raise ValueError("Unknown connector field")
|
|
129
|
+
values = []
|
|
130
|
+
for key, value in changes.items():
|
|
131
|
+
if key in JSON_COLUMNS:
|
|
132
|
+
value = json.dumps(value) if value is not None else None
|
|
133
|
+
elif key == "enabled":
|
|
134
|
+
value = int(bool(value))
|
|
135
|
+
values.append(value)
|
|
136
|
+
if values:
|
|
137
|
+
with self.journal.transaction() as connection:
|
|
138
|
+
connection.execute(f"UPDATE connectors SET {', '.join(f'{k}=?' for k in changes)} WHERE id=?",
|
|
139
|
+
[*values, connector_id])
|
|
140
|
+
return self.get(connector_id)
|
|
141
|
+
|
|
142
|
+
def delete(self, connector_id):
|
|
143
|
+
with self.journal.transaction() as connection:
|
|
144
|
+
connection.execute("DELETE FROM connectors WHERE id=?", (connector_id,))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Where a connector's secrets live: one generic password per connector in the login Keychain, readable only
|
|
2
|
+
by Mobster's own programs (keychain_acl.py), as outlets/vault.py keeps a destination's address."""
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from . import keystore
|
|
9
|
+
|
|
10
|
+
SERVICE = "app.mobster.desktop.connectors"
|
|
11
|
+
ENV = "MOBSTER_CONNECTORS_KEYCHAIN"
|
|
12
|
+
ACCOUNT = re.compile(r"connector:[0-9a-f]{12}")
|
|
13
|
+
WORD = re.compile(r"[A-Za-z0-9_-]{1,16000}")
|
|
14
|
+
VaultError = keystore.VaultError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def account_for(connector_id):
|
|
18
|
+
account = f"connector:{connector_id}"
|
|
19
|
+
if not ACCOUNT.fullmatch(account):
|
|
20
|
+
raise ValueError("A connector's id is 12 hex characters")
|
|
21
|
+
return account
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encode(value):
|
|
25
|
+
raw = json.dumps(value, separators=(",", ":")).encode()
|
|
26
|
+
return base64.urlsafe_b64encode(raw).decode().rstrip("=")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decode(word):
|
|
30
|
+
try:
|
|
31
|
+
value = json.loads(base64.urlsafe_b64decode(word + "=" * (-len(word) % 4)))
|
|
32
|
+
except (ValueError, TypeError):
|
|
33
|
+
raise VaultError("Mobster couldn't read a connector's sign-in") from None
|
|
34
|
+
if not isinstance(value, dict):
|
|
35
|
+
raise VaultError("Mobster couldn't read a connector's sign-in")
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Vault:
|
|
40
|
+
"""``put(id, secrets)``, ``get(id) -> dict`` ({} when none) and ``delete(id)`` over a raw store."""
|
|
41
|
+
|
|
42
|
+
def put(self, connector_id, secrets):
|
|
43
|
+
secrets = {k: v for k, v in (secrets or {}).items() if v}
|
|
44
|
+
if not secrets:
|
|
45
|
+
self.delete(connector_id)
|
|
46
|
+
return
|
|
47
|
+
self._set(account_for(connector_id), encode(secrets))
|
|
48
|
+
|
|
49
|
+
def get(self, connector_id):
|
|
50
|
+
word = self._get(account_for(connector_id))
|
|
51
|
+
return decode(word) if word else {}
|
|
52
|
+
|
|
53
|
+
def update(self, connector_id, **changes):
|
|
54
|
+
current = self.get(connector_id)
|
|
55
|
+
current.update(changes)
|
|
56
|
+
self.put(connector_id, current)
|
|
57
|
+
return current
|
|
58
|
+
|
|
59
|
+
def delete(self, connector_id):
|
|
60
|
+
self._delete(account_for(connector_id))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FileVault(keystore.FileStore, Vault):
|
|
64
|
+
files = "connectors"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class KeychainVault(keystore.KeychainStore, Vault):
|
|
68
|
+
service, noun, word = SERVICE, "sign-in", WORD
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def backend():
|
|
72
|
+
return keystore.backend(ENV, FileVault, KeychainVault)
|