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,789 @@
|
|
|
1
|
+
"""The node types a workflow graph is made of (docs/workflows.mdx › Nodes): each type's fields, its input and output
|
|
2
|
+
ports with their JSON schemas, whether it holds a phone and whether it acts outward, and the check each field gets.
|
|
3
|
+
|
|
4
|
+
``catalog()`` is what ``GET /api/workflows/node-types`` serves, so the canvas builds its palette and inspector from the
|
|
5
|
+
engine; a track adds a type with ``register_node_type``. graph/schema.py checks a whole graph against these.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Callable
|
|
12
|
+
|
|
13
|
+
from ..output_contract import validate_schema
|
|
14
|
+
from ..workflows import starters
|
|
15
|
+
from . import expr, values
|
|
16
|
+
from .infer import schema_kind
|
|
17
|
+
|
|
18
|
+
VERSION = 2
|
|
19
|
+
MAX_NODES = 100
|
|
20
|
+
MAX_EDGES = 300
|
|
21
|
+
MAX_FANOUT = 10
|
|
22
|
+
MAX_NESTING = 2
|
|
23
|
+
MAX_CALL_DEPTH = 3
|
|
24
|
+
MAX_ITERATIONS = 200
|
|
25
|
+
MAX_UNTIL = 50
|
|
26
|
+
MAX_TIMES = 50
|
|
27
|
+
MAX_CONCURRENCY = 10
|
|
28
|
+
MAX_GRAPH_BYTES = 512_000
|
|
29
|
+
MAX_PIN_BYTES = 64_000
|
|
30
|
+
MAX_PINS_BYTES = 1_000_000
|
|
31
|
+
MAX_INPUTS = 30
|
|
32
|
+
MAX_VARS = 50
|
|
33
|
+
MAX_VARS_BYTES = 16_000
|
|
34
|
+
MAX_CASES = 8
|
|
35
|
+
MAX_FIELDS = 50
|
|
36
|
+
MAX_ROWS = 200
|
|
37
|
+
TEXT_CHARS = 2000
|
|
38
|
+
CHECK_CHARS = 300
|
|
39
|
+
TITLE_CHARS = 80
|
|
40
|
+
NOTES_CHARS = 2000
|
|
41
|
+
POSITION = 100_000
|
|
42
|
+
WAIT_RANGE = (5, 86_400)
|
|
43
|
+
APPROVAL_RANGE = (1, 10_080)
|
|
44
|
+
SPEND_RANGE = (0.05, 100.0)
|
|
45
|
+
MINUTES_RANGE = (1, 1440)
|
|
46
|
+
DEFAULT_LIMITS = {"maxSpendUsd": 5.0, "maxMinutes": 240}
|
|
47
|
+
ID = re.compile(r"[A-Za-z0-9_-]{1,24}")
|
|
48
|
+
VAR = re.compile(r"[A-Za-z_][A-Za-z0-9_]{0,39}")
|
|
49
|
+
WORKFLOW_ID = re.compile(r"[0-9a-f]{12}")
|
|
50
|
+
INPUT_TYPES = ("string", "number", "boolean", "list", "object")
|
|
51
|
+
TRIGGERS = ("trigger.manual", "trigger.schedule", "trigger.webhook", "trigger.notification", "trigger.shortcut",
|
|
52
|
+
"trigger.app")
|
|
53
|
+
MODES = ("auto", "replay", "agent")
|
|
54
|
+
TRUSTED_ROOTS = frozenset({"inputs", "vars", "run", "index"})
|
|
55
|
+
TRUSTED_ITEMS = ("times", "rows", "until", "phones") # loops whose $item is the person's own (or a count)
|
|
56
|
+
RESERVED_PORTS = ("in", "out", "error", "else", "items", "item", "index")
|
|
57
|
+
# A node's retry (docs/workflows.mdx › Retries): allowed only on types that may not act outward by their nature; whether
|
|
58
|
+
# an attempt is retried is decided at run time from the permission gate and the effect ledger, never from the type.
|
|
59
|
+
RETRY_TYPES = frozenset({"phone.task", "phone.taught", "phone.check", "app.read", "connector.call", "ai.ask", "wait"})
|
|
60
|
+
RETRY_TRIES = (1, 5)
|
|
61
|
+
RETRY_BACKOFF = (1, 3600)
|
|
62
|
+
APP_EVERY = (5, 1440)
|
|
63
|
+
EXPECT_CHARS = 300
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
ANY = {}
|
|
67
|
+
STRING, NUMBER, INTEGER, BOOLEAN = {"type": "string"}, {"type": "number"}, {"type": "integer"}, {"type": "boolean"}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def obj(**properties):
|
|
71
|
+
return {"type": "object", "properties": properties, "additionalProperties": False}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def nullable(schema):
|
|
75
|
+
kind = schema.get("type")
|
|
76
|
+
return {**schema, "type": [kind, "null"]} if isinstance(kind, str) else ANY
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def array(items=ANY):
|
|
80
|
+
return {"type": "array", "items": items}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
VALUES = {"type": "object", "additionalProperties": STRING}
|
|
84
|
+
ROWS = nullable(array({"type": "object", "additionalProperties": STRING}))
|
|
85
|
+
ERROR_OUT = obj(node=STRING, reason=STRING, status=nullable(STRING), runId=nullable(STRING))
|
|
86
|
+
PHONE_OUT = obj(answer=nullable(STRING), data=ANY, status=STRING, runId=STRING, costUsd=nullable(NUMBER))
|
|
87
|
+
PHONE_DEVICE = obj(deviceId=STRING, name=STRING)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class Field:
|
|
92
|
+
"""One config field; an ``instructs`` field tells a model what to do, so it reads only trusted roots."""
|
|
93
|
+
kind: str
|
|
94
|
+
label: str
|
|
95
|
+
required: bool = False
|
|
96
|
+
default: object = None
|
|
97
|
+
low: float | None = None
|
|
98
|
+
high: float | None = None
|
|
99
|
+
choices: tuple = ()
|
|
100
|
+
expr: str | None = None
|
|
101
|
+
instructs: bool = False
|
|
102
|
+
blanks: bool = False
|
|
103
|
+
wants: str | None = None # the kind a bare expression must give (boolean, number)
|
|
104
|
+
help: str = ""
|
|
105
|
+
|
|
106
|
+
def public(self):
|
|
107
|
+
out = {"kind": self.kind, "label": self.label, "required": self.required}
|
|
108
|
+
for key in ("default", "low", "high", "help", "wants"):
|
|
109
|
+
value = getattr(self, key)
|
|
110
|
+
if value not in (None, ""):
|
|
111
|
+
out[key] = value
|
|
112
|
+
if self.choices:
|
|
113
|
+
out["choices"] = list(self.choices)
|
|
114
|
+
if self.expr:
|
|
115
|
+
out["expressions"] = self.expr
|
|
116
|
+
if self.instructs:
|
|
117
|
+
out["instructs"] = True
|
|
118
|
+
if self.blanks:
|
|
119
|
+
out["blanks"] = True
|
|
120
|
+
return out
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def text(label, high=TEXT_CHARS, *, required=True, default=None, instructs=False, blanks=False, help=""):
|
|
124
|
+
return Field("text", label, required, default, 1, high, expr="text", instructs=instructs, blanks=blanks, help=help)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
DEVICE = Field("device", "Phone", default=None,
|
|
128
|
+
help='null: the workflow\'s phone. "any": any free phone. "loop": the phone of the loop it is in.')
|
|
129
|
+
ACCOUNT = Field("account", "Profile", default=None)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True)
|
|
133
|
+
class NodeType:
|
|
134
|
+
type: str
|
|
135
|
+
label: str
|
|
136
|
+
group: str
|
|
137
|
+
description: str
|
|
138
|
+
fields: dict
|
|
139
|
+
outputs: Callable
|
|
140
|
+
inputs: tuple = ("in",)
|
|
141
|
+
single: frozenset = frozenset()
|
|
142
|
+
phone: bool = False
|
|
143
|
+
outward: bool = False
|
|
144
|
+
check: Callable | None = None
|
|
145
|
+
static: dict = field(default_factory=dict)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def trigger(self):
|
|
149
|
+
return self.group == "trigger"
|
|
150
|
+
|
|
151
|
+
def public(self):
|
|
152
|
+
ports = self.static
|
|
153
|
+
outs = [{"name": name, "schema": schema} for name, schema in ports.items()]
|
|
154
|
+
if not self.trigger:
|
|
155
|
+
outs.append({"name": "error", "schema": ERROR_OUT})
|
|
156
|
+
return {"type": self.type, "label": self.label, "group": self.group, "description": self.description,
|
|
157
|
+
"phone": self.phone, "outward": self.outward,
|
|
158
|
+
"fields": {name: f.public() for name, f in self.fields.items()},
|
|
159
|
+
"inputs": [{"name": name, "single": name in self.single} for name in self.inputs],
|
|
160
|
+
"outputs": outs}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
TYPES = {}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def register_node_type(node_type):
|
|
167
|
+
"""Add a node type (a track's own: a connector call, say). Its name is new: lowercase words joined by dots."""
|
|
168
|
+
if not re.fullmatch(r"[a-z][a-z0-9_]{1,23}(\.[a-z][a-z0-9_]{1,23})?", node_type.type):
|
|
169
|
+
raise ValueError("A node type is a short lowercase name, like connector.call")
|
|
170
|
+
TYPES[node_type.type] = node_type
|
|
171
|
+
return node_type
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def catalog():
|
|
175
|
+
return [TYPES[name].public() for name in TYPES]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Shapes:
|
|
179
|
+
|
|
180
|
+
def __init__(self, validator, node):
|
|
181
|
+
self.validator, self.node = validator, node
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def input(self):
|
|
185
|
+
return self.validator.input_schema(self.node["id"])
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def values(self):
|
|
189
|
+
return self.validator.values_schema
|
|
190
|
+
|
|
191
|
+
def out(self, node_id, port="out"):
|
|
192
|
+
return self.validator.schema_of(node_id, port)
|
|
193
|
+
|
|
194
|
+
def items(self):
|
|
195
|
+
return self.validator.items_schema(self.node["id"])
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _static(schema):
|
|
199
|
+
return lambda node, shapes: {"out": schema}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _trigger(label, description, out, fields=None):
|
|
203
|
+
return NodeType("trigger." + label.lower().split()[0], label, "trigger", description, fields or {},
|
|
204
|
+
lambda node, shapes: {"out": out(shapes) if callable(out) else out}, inputs=(),
|
|
205
|
+
static={"out": None if callable(out) else out})
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _phone_out(node, shapes):
|
|
209
|
+
schema = node["config"].get("outputSchema")
|
|
210
|
+
return {"out": {**PHONE_OUT, "properties": {**PHONE_OUT["properties"],
|
|
211
|
+
"data": nullable(schema) if schema else ANY}}}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _app_read_out(node, shapes):
|
|
215
|
+
config = node["config"]
|
|
216
|
+
columns = (shapes.validator.context.packs or {}).get(config.get("app"), {}).get("readers", {}).get(
|
|
217
|
+
config.get("reader"), [])
|
|
218
|
+
kinds = {"number": NUMBER, "boolean": BOOLEAN}
|
|
219
|
+
row = {"type": "object", "properties": {c["key"]: nullable(kinds.get(c.get("type"), STRING)) for c in columns
|
|
220
|
+
if isinstance(c, dict) and isinstance(c.get("key"), str)}}
|
|
221
|
+
rows = array(row)
|
|
222
|
+
return {"out": obj(rows=rows, columns=array(), title=STRING), "rows": rows}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _loop_out(node, shapes):
|
|
226
|
+
config = node["config"]
|
|
227
|
+
over = config.get("over")
|
|
228
|
+
item = {"times": INTEGER, "until": INTEGER, "phones": PHONE_DEVICE, "rows": {"type": "object",
|
|
229
|
+
"additionalProperties": STRING}}.get(over)
|
|
230
|
+
if over == "items":
|
|
231
|
+
source = shapes.items()
|
|
232
|
+
item = source.get("items", ANY) if schema_kind(source) == "list" else ANY
|
|
233
|
+
collected = shapes.out(config["collect"]) if config.get("collect") else ANY
|
|
234
|
+
return {"item": item or ANY, "index": INTEGER, "out": obj(results=array(nullable(collected) if collected else ANY),
|
|
235
|
+
count=INTEGER)}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _branch_out(node, shapes):
|
|
239
|
+
passed = shapes.input
|
|
240
|
+
ports = {case["id"]: passed for case in node["config"].get("cases") or []}
|
|
241
|
+
ports["else"] = passed
|
|
242
|
+
return ports
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _transform_out(node, shapes):
|
|
246
|
+
types = shapes.validator.field_types.get(node["id"], {})
|
|
247
|
+
return {"out": {"type": "object", "properties": {name: types.get(("fields", name), ANY)
|
|
248
|
+
for name in node["config"].get("fields") or {}},
|
|
249
|
+
"additionalProperties": False}}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _call_out(node, shapes):
|
|
253
|
+
target = (shapes.validator.context.workflows or {}).get(node["config"].get("workflowId")) or {}
|
|
254
|
+
graph = target.get("graph") if isinstance(target.get("graph"), dict) else {}
|
|
255
|
+
output = next((n for n in graph.get("nodes") or [] if n.get("type") == "output"), None)
|
|
256
|
+
if output is None:
|
|
257
|
+
return {"out": obj(status=STRING)}
|
|
258
|
+
return {"out": (output.get("config") or {}).get("schema") or ANY}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _register_core():
|
|
262
|
+
trusted = "The person's own words: {blanks}, and {= } reading $inputs, $vars, $run or a loop's row."
|
|
263
|
+
register_node_type(_trigger("Manual", "Runs when you run it: from Mobster, the CLI, MCP or Remote.",
|
|
264
|
+
lambda s: obj(values=s.values, rows=ROWS, input=ANY)))
|
|
265
|
+
register_node_type(_trigger("Schedule", "Runs by itself at the times you pick.",
|
|
266
|
+
lambda s: obj(values=s.values, rows=ROWS, slot=INTEGER),
|
|
267
|
+
{"cron": Field("cron", "When", required=True),
|
|
268
|
+
"timezone": Field("timezone", "Time zone", required=True),
|
|
269
|
+
"missed": Field("choice", "When the Mac slept through it", default="skip",
|
|
270
|
+
choices=("skip", "run_once")),
|
|
271
|
+
"overlap": Field("choice", "When the last run is still going", default="skip",
|
|
272
|
+
choices=("skip", "queue_one"))}))
|
|
273
|
+
register_node_type(_trigger("Webhook", "Runs when another tool calls the workflow's webhook URL.",
|
|
274
|
+
lambda s: obj(values=s.values, body={"type": "object"})))
|
|
275
|
+
register_node_type(_trigger("Notification", "Runs when a notification arrives on the phone.",
|
|
276
|
+
obj(app=STRING, title=STRING, body=STRING, receivedAt=INTEGER),
|
|
277
|
+
{"app": Field("app", "From app", default=None),
|
|
278
|
+
"contains": Field("short", "Contains", default=None)}))
|
|
279
|
+
register_node_type(_trigger("Shortcut", "Runs from a Shortcut on your Mac or iPhone.",
|
|
280
|
+
lambda s: obj(values=s.values, input=nullable(STRING))))
|
|
281
|
+
register_node_type(NodeType(
|
|
282
|
+
"trigger.app", "New in an app", "trigger", "Reads an app on the phone on a timer and "
|
|
283
|
+
"runs once for each new row.",
|
|
284
|
+
{"app": Field("pack", "App", required=True), "reader": Field("reader", "What to read", required=True),
|
|
285
|
+
"arg": Field("short", "For", default=None), "every": Field("int", "Every (minutes)", default=15,
|
|
286
|
+
low=APP_EVERY[0], high=APP_EVERY[1]),
|
|
287
|
+
"key": Field("short", "The field that names a row", required=True), "device": DEVICE, "account": ACCOUNT},
|
|
288
|
+
lambda node, shapes: {"out": obj(row=_app_read_out(node, shapes)["rows"]["items"],
|
|
289
|
+
rows=_app_read_out(node, shapes)["rows"])},
|
|
290
|
+
inputs=(), static={"out": None}, phone=True))
|
|
291
|
+
register_node_type(NodeType(
|
|
292
|
+
"phone.task", "Phone task", "phone", "Mobster's agent does a task on a phone, in your words.",
|
|
293
|
+
{"text": text("Task", instructs=True, blanks=True, help=trusted),
|
|
294
|
+
"appId": Field("app_id", "Starts in", default=None), "device": DEVICE, "account": ACCOUNT,
|
|
295
|
+
"mode": Field("choice", "How it runs", default="auto", choices=MODES),
|
|
296
|
+
"outputSchema": Field("schema", "Answer shape", default=None),
|
|
297
|
+
"input": Field("input", "Input it reads (as data)", default="{= $input }", expr="text"),
|
|
298
|
+
"expect": text("When it worked, the screen shows", EXPECT_CHARS, required=False, instructs=True, blanks=True,
|
|
299
|
+
help="Mobster looks at the screen after the step; a no fails the step with that screen.")},
|
|
300
|
+
_phone_out, phone=True, outward=True, static={"out": PHONE_OUT}))
|
|
301
|
+
register_node_type(NodeType(
|
|
302
|
+
"phone.taught", "Taught task", "phone", "Repeats a task you taught, with no model where it can.",
|
|
303
|
+
{"workflowId": Field("workflow", "Task", required=True), "device": DEVICE, "account": ACCOUNT},
|
|
304
|
+
_static(PHONE_OUT), phone=True, outward=True, static={"out": PHONE_OUT}))
|
|
305
|
+
open_out = obj(url=STRING, landedApp=STRING, screen=STRING)
|
|
306
|
+
register_node_type(NodeType(
|
|
307
|
+
"phone.open_url", "Open a link", "phone",
|
|
308
|
+
"Opens a link on the phone: a web address or an app's deep link. No model. A link that "
|
|
309
|
+
"writes to someone asks first.",
|
|
310
|
+
{"url": text("Link", 2000, help="A web address or an app link, such as "
|
|
311
|
+
"https://example.com or maps://?q=coffee."),
|
|
312
|
+
"device": DEVICE},
|
|
313
|
+
_static(open_out), phone=True, outward=True, check=_open_url_check, static={"out": open_out}))
|
|
314
|
+
notification_out = obj(title=STRING, body=STRING, app=STRING, at=nullable(STRING))
|
|
315
|
+
waited_out = obj(waitedMinutes=INTEGER)
|
|
316
|
+
|
|
317
|
+
def _notification_ports(node, shapes):
|
|
318
|
+
return {"out": notification_out, "timeout": waited_out}
|
|
319
|
+
register_node_type(NodeType(
|
|
320
|
+
"phone.notification", "Wait for a notification", "phone",
|
|
321
|
+
"Waits for a notification on the phone that holds your words, then opens it. If none comes in time, the "
|
|
322
|
+
"timeout path runs. No model.",
|
|
323
|
+
{"app": Field("app", "From app", default=None),
|
|
324
|
+
"match": Field("short", "Contains", default=None, expr="text", blanks=True),
|
|
325
|
+
"timeoutMinutes": Field("int", "Wait up to (minutes)", default=10, low=1, high=240),
|
|
326
|
+
"tap": Field("bool", "Open it", default=True), "device": DEVICE},
|
|
327
|
+
_notification_ports, phone=True, outward=True,
|
|
328
|
+
static={"out": notification_out, "timeout": waited_out}))
|
|
329
|
+
check_out = obj(observed=BOOLEAN, runId=STRING)
|
|
330
|
+
register_node_type(NodeType(
|
|
331
|
+
"phone.check", "Check the phone", "phone", "Looks at the phone and answers true or false. It only looks.",
|
|
332
|
+
{"question": text("Question", CHECK_CHARS, instructs=True, blanks=True, help=trusted), "device": DEVICE,
|
|
333
|
+
"account": ACCOUNT},
|
|
334
|
+
_static(check_out), phone=True, static={"out": check_out}))
|
|
335
|
+
register_node_type(NodeType(
|
|
336
|
+
"app.read", "Read an app", "phone", "Reads an app's screen into rows with an app pack. No model, read only.",
|
|
337
|
+
{"app": Field("pack", "App", required=True), "reader": Field("reader", "What to read", required=True),
|
|
338
|
+
"n": Field("int", "How many", default=10, low=1, high=50), "arg": Field("short", "For", default=None,
|
|
339
|
+
expr="text"),
|
|
340
|
+
"device": DEVICE, "account": ACCOUNT},
|
|
341
|
+
_app_read_out, phone=True, static={"out": None, "rows": None}))
|
|
342
|
+
register_node_type(NodeType(
|
|
343
|
+
"app.action", "App action", "phone", "Reserved: an app pack's outward action.",
|
|
344
|
+
{"app": Field("pack", "App", required=True), "action": Field("short", "Action", required=True),
|
|
345
|
+
"args": Field("object", "Arguments", default=None, expr="deep"),
|
|
346
|
+
"expect": text("When it worked, the screen shows", EXPECT_CHARS, required=False, instructs=True, blanks=True)},
|
|
347
|
+
_static(obj(done=BOOLEAN, runId=STRING)), phone=True, outward=True, check=_no_actions))
|
|
348
|
+
connector_out = obj(text=STRING, structured=nullable({"type": "object"}), isError=BOOLEAN)
|
|
349
|
+
register_node_type(NodeType(
|
|
350
|
+
"connector.call", "Use a connector", "step", "Calls one tool of a connected service, with no model.",
|
|
351
|
+
{"connector": Field("short", "Connector", required=True), "tool": Field("short", "Tool", required=True),
|
|
352
|
+
"arguments": Field("object", "Arguments", default=None, expr="deep", blanks=True)},
|
|
353
|
+
_static(connector_out), outward=True, check=_connector, static={"out": connector_out}))
|
|
354
|
+
send_out = obj(line=STRING)
|
|
355
|
+
register_node_type(NodeType(
|
|
356
|
+
"send", "Send", "step", "Sends what arrives to a notification, the clipboard, a file or a destination.",
|
|
357
|
+
{"to": Field("delivery", "To", required=True), "destination": Field("short", "Destination", default=None),
|
|
358
|
+
"text": Field("text", "What it sends", default=None, low=1, high=TEXT_CHARS, expr="text", blanks=True),
|
|
359
|
+
"what": Field("choice", "Results", default=None, choices=("last", "all"),
|
|
360
|
+
help="last or all: the run's newest result, or every result so far, instead of what arrives")},
|
|
361
|
+
_static(send_out), outward=True, check=_send, static={"out": send_out}))
|
|
362
|
+
brief_out = obj(answer=nullable(STRING), runId=STRING)
|
|
363
|
+
register_node_type(NodeType(
|
|
364
|
+
"brief", "Brief", "step", "Puts the results of the tasks wired into it together as one result.",
|
|
365
|
+
{"title": Field("text", "Title", default=None, low=1, high=TITLE_CHARS)},
|
|
366
|
+
_static(brief_out), static={"out": brief_out}))
|
|
367
|
+
register_node_type(NodeType(
|
|
368
|
+
"transform", "Set fields", "step", "Works out new fields from what arrived. Instant, no phone or model.",
|
|
369
|
+
{"fields": Field("fields", "Fields", required=True, expr="deep")},
|
|
370
|
+
_transform_out, static={"out": None}))
|
|
371
|
+
ai_out = obj(answer=nullable(STRING), data=ANY)
|
|
372
|
+
register_node_type(NodeType(
|
|
373
|
+
"ai.ask", "Ask the model", "step", "Asks Claude about what arrived, in your words; it reads it as data.",
|
|
374
|
+
{"instruction": text("Instruction", instructs=True, blanks=True, help=trusted),
|
|
375
|
+
"input": Field("input", "Input it reads (as data)", default="{= $input }", expr="text"),
|
|
376
|
+
"outputSchema": Field("schema", "Answer shape", default=None),
|
|
377
|
+
"model": Field("choice", "Model", default="haiku", choices=("haiku", "sonnet"))},
|
|
378
|
+
_phone_out_ai, static={"out": ai_out}))
|
|
379
|
+
register_node_type(NodeType(
|
|
380
|
+
"output", "Result", "step", "The workflow's result: what a run hands back, and what a parent sees.",
|
|
381
|
+
{"value": Field("input", "Value", default="{= $input }", expr="text"),
|
|
382
|
+
"schema": Field("schema", "Shape", default=None)},
|
|
383
|
+
lambda node, shapes: {}, static={}))
|
|
384
|
+
register_node_type(NodeType(
|
|
385
|
+
"branch", "Branch", "flow", "Sends what arrives down the first way that holds, or down Otherwise.",
|
|
386
|
+
{"cases": Field("cases", "Cases", required=True)}, _branch_out, single=frozenset({"in"})))
|
|
387
|
+
register_node_type(NodeType(
|
|
388
|
+
"loop", "Loop", "flow", "Runs the steps inside it for each item, row, phone or round.",
|
|
389
|
+
{"over": Field("choice", "Over", required=True, choices=("times", "rows", "items", "until", "phones")),
|
|
390
|
+
"times": Field("int", "Times", default=None, low=2, high=MAX_TIMES),
|
|
391
|
+
"rows": Field("rows", "Rows", default=None),
|
|
392
|
+
"until": Field("short", "Until", default=None), "path": Field("short", "Reads", default=None),
|
|
393
|
+
"phones": Field("phones", "Phones", default=None),
|
|
394
|
+
"max": Field("int", "At most", default=None, low=1, high=MAX_ITERATIONS),
|
|
395
|
+
"concurrency": Field("int", "At once", default=None, low=1, high=MAX_CONCURRENCY),
|
|
396
|
+
"collect": Field("short", "Collect", default=None)},
|
|
397
|
+
_loop_out, inputs=("in", "items"), single=frozenset({"items"}), check=_loop))
|
|
398
|
+
register_node_type(NodeType(
|
|
399
|
+
"merge", "Merge", "flow", "Joins paths: waits for all of them, or goes on with the first.",
|
|
400
|
+
{"mode": Field("choice", "Waits for", default="all", choices=("all", "first"))},
|
|
401
|
+
_static(obj(items=array(), first=ANY))))
|
|
402
|
+
register_node_type(NodeType(
|
|
403
|
+
"wait", "Wait", "flow", "Waits a while, or until a time, then passes on what arrived.",
|
|
404
|
+
{"seconds": Field("int", "Seconds", default=None, low=WAIT_RANGE[0], high=WAIT_RANGE[1]),
|
|
405
|
+
"until": Field("bare", "Until", default=None, expr="bare", wants="number")},
|
|
406
|
+
lambda node, shapes: {"out": shapes.input}, check=_wait))
|
|
407
|
+
register_node_type(NodeType(
|
|
408
|
+
"approval", "Ask me first", "flow", "Waits for you to approve before the workflow goes on.",
|
|
409
|
+
{"message": text("Message", help="What you'll be asked. It may show values from earlier steps."),
|
|
410
|
+
"timeoutMinutes": Field("int", "Wait at most (minutes)", default=1440, low=APPROVAL_RANGE[0],
|
|
411
|
+
high=APPROVAL_RANGE[1]),
|
|
412
|
+
"showScreen": Field("bool", "Show the phone's screen", default=True),
|
|
413
|
+
"editable": Field("bool", "Let me edit before approving", default=False)},
|
|
414
|
+
lambda node, shapes: {"approved": shapes.input, "declined": obj(reason=STRING, by=nullable(STRING))},
|
|
415
|
+
static={"declined": obj(reason=STRING, by=nullable(STRING))}))
|
|
416
|
+
register_node_type(NodeType(
|
|
417
|
+
"workflow.call", "Run a workflow", "flow", "Runs another workflow and goes on with its result.",
|
|
418
|
+
{"workflowId": Field("workflow", "Workflow", required=True),
|
|
419
|
+
"revision": Field("int", "Version", default=None, low=1, high=10**9),
|
|
420
|
+
"values": Field("values", "Its inputs", default=None, expr="deep", instructs=True)},
|
|
421
|
+
_call_out, outward=True, check=_call))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _phone_out_ai(node, shapes):
|
|
425
|
+
schema = node["config"].get("outputSchema")
|
|
426
|
+
return {"out": obj(answer=nullable(STRING), data=nullable(schema) if schema else ANY)}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _no_actions(config, ctx):
|
|
430
|
+
raise ValueError("No app lists an action Mobster can take yet.")
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _connector(config, ctx):
|
|
434
|
+
if ctx.connector is None:
|
|
435
|
+
return config
|
|
436
|
+
made = ctx.connector({"connector": config["connector"], "tool": config["tool"],
|
|
437
|
+
"arguments": config.get("arguments") or {}})
|
|
438
|
+
return {**config, **{key: made[key] for key in ("connector", "tool", "arguments") if key in made}}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _send(config, ctx):
|
|
442
|
+
if config["to"] == "destination":
|
|
443
|
+
if not isinstance(config.get("destination"), str) or not WORKFLOW_ID.fullmatch(config["destination"]):
|
|
444
|
+
raise ValueError("Choose a saved destination")
|
|
445
|
+
if ctx.destination is None:
|
|
446
|
+
raise ValueError("Sending results isn't available in this Mobster. Choose another place.")
|
|
447
|
+
if not ctx.destination(config["destination"]):
|
|
448
|
+
raise ValueError("That destination was removed. Choose another one.")
|
|
449
|
+
elif config.get("destination") is not None:
|
|
450
|
+
raise ValueError("Only a send to a destination names one")
|
|
451
|
+
return config
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _open_url_check(config, ctx):
|
|
455
|
+
"""A literal link is checked when the workflow is saved: a scheme Mobster never opens is refused there."""
|
|
456
|
+
url = config.get("url")
|
|
457
|
+
if isinstance(url, str) and "{" not in url:
|
|
458
|
+
from .. import deeplinks
|
|
459
|
+
try:
|
|
460
|
+
deeplinks.risk_of(url)
|
|
461
|
+
except deeplinks.LinkError as error:
|
|
462
|
+
raise ValueError(str(error)) from None
|
|
463
|
+
return config
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def app_missing(app_id):
|
|
467
|
+
"""Why a step can't start in ``app_id``: this Mac's list of apps (an iPhone's, or the catalog) doesn't have it."""
|
|
468
|
+
return (f"{str(app_id)[:40]} isn't on this Mac's list of apps yet. Connect the iPhone that has it, or choose Any "
|
|
469
|
+
"app.")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _wait(config, ctx):
|
|
473
|
+
if (config.get("seconds") is None) == (config.get("until") is None):
|
|
474
|
+
raise ValueError("A wait has seconds or until, one of them")
|
|
475
|
+
return config
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _loop(config, ctx):
|
|
479
|
+
over = config["over"]
|
|
480
|
+
allowed = {"times": {"times"}, "rows": {"rows", "max"}, "items": {"max"}, "until": {"until", "path", "max"},
|
|
481
|
+
"phones": {"phones", "max"}}[over] | {"over", "concurrency", "collect"}
|
|
482
|
+
extra = [key for key, value in config.items() if value is not None and key not in allowed]
|
|
483
|
+
if extra:
|
|
484
|
+
raise ValueError(f"A loop over {over} doesn't take {', '.join(sorted(extra))}")
|
|
485
|
+
if over == "times" and config.get("times") is None:
|
|
486
|
+
raise ValueError("Say how many times")
|
|
487
|
+
if over == "until":
|
|
488
|
+
if not config.get("until"):
|
|
489
|
+
raise ValueError("Choose the step whose answer ends the loop")
|
|
490
|
+
if config.get("concurrency") not in (None, 1):
|
|
491
|
+
raise ValueError("A loop until something is seen runs one round at a time")
|
|
492
|
+
config = {**config, "path": config.get("path") or "/observed",
|
|
493
|
+
"max": config.get("max") if config.get("max") is not None else 10}
|
|
494
|
+
if config["max"] > MAX_UNTIL:
|
|
495
|
+
raise ValueError(f"A loop until something is seen runs at most {MAX_UNTIL} rounds")
|
|
496
|
+
_pointer(config["path"])
|
|
497
|
+
if over == "phones" and config.get("phones") is None:
|
|
498
|
+
raise ValueError('Choose the phones: "all", a list, or {"count": n}')
|
|
499
|
+
if over in ("rows", "items") and config.get("max") is None:
|
|
500
|
+
config = {**config, "max": len(config["rows"]) if over == "rows" and config.get("rows") else MAX_ITERATIONS}
|
|
501
|
+
if config.get("concurrency") is None:
|
|
502
|
+
config = {**config, "concurrency": 1}
|
|
503
|
+
return config
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _call(config, ctx):
|
|
507
|
+
target = config["workflowId"]
|
|
508
|
+
if target == ctx.workflow_id:
|
|
509
|
+
raise ValueError("A workflow can't run itself")
|
|
510
|
+
records = ctx.workflows
|
|
511
|
+
if records is not None:
|
|
512
|
+
if target not in records:
|
|
513
|
+
raise ValueError("The workflow it runs was deleted. Choose another one.")
|
|
514
|
+
depth = _call_depth(target, records, {ctx.workflow_id} if ctx.workflow_id else set())
|
|
515
|
+
if depth > 3:
|
|
516
|
+
raise ValueError("Workflows run inside each other at most 3 deep")
|
|
517
|
+
return config
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _call_depth(workflow_id, records, seen):
|
|
521
|
+
if workflow_id in seen:
|
|
522
|
+
raise ValueError("These workflows would run each other in a circle")
|
|
523
|
+
graph = (records.get(workflow_id) or {}).get("graph") or {}
|
|
524
|
+
calls = [n.get("config", {}).get("workflowId") for n in graph.get("nodes") or []
|
|
525
|
+
if n.get("type") == "workflow.call"]
|
|
526
|
+
if any(call in seen for call in calls):
|
|
527
|
+
raise ValueError("These workflows would run each other in a circle")
|
|
528
|
+
return 1 + max((_call_depth(c, records, seen | {workflow_id}) for c in calls if c in records), default=0)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _pointer(path):
|
|
532
|
+
if not isinstance(path, str) or not (path == "" or path.startswith("/")) or len(path) > 200:
|
|
533
|
+
raise ValueError('A path is a JSON Pointer, like "/observed"')
|
|
534
|
+
return path
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def pointer(value, path):
|
|
538
|
+
"""``value`` at the JSON Pointer ``path``, or None."""
|
|
539
|
+
for raw in path.split("/")[1:] if path else []:
|
|
540
|
+
key = raw.replace("~1", "/").replace("~0", "~")
|
|
541
|
+
if isinstance(value, list):
|
|
542
|
+
value = value[int(key)] if key.isdigit() and int(key) < len(value) else None
|
|
543
|
+
elif isinstance(value, dict):
|
|
544
|
+
value = value.get(key)
|
|
545
|
+
else:
|
|
546
|
+
return None
|
|
547
|
+
return value
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _json_size(value):
|
|
551
|
+
return len(json.dumps(value, ensure_ascii=False, separators=(",", ":"), allow_nan=False))
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _short(value, label, high=200):
|
|
555
|
+
if not isinstance(value, str) or not 1 <= len(value.strip()) <= high:
|
|
556
|
+
raise ValueError(f"{label} is 1 to {high} characters")
|
|
557
|
+
return value.strip()
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class _Fields:
|
|
561
|
+
|
|
562
|
+
@staticmethod
|
|
563
|
+
def text(value, f, ctx, v, node):
|
|
564
|
+
cleaned = _short(value, f.label, int(f.high or TEXT_CHARS))
|
|
565
|
+
if f.blanks:
|
|
566
|
+
starters.check_steps(cleaned)
|
|
567
|
+
return cleaned
|
|
568
|
+
|
|
569
|
+
@staticmethod
|
|
570
|
+
def bool(value, f, ctx, v, node):
|
|
571
|
+
if type(value) is not bool:
|
|
572
|
+
raise ValueError(f"{f.label} is true or false")
|
|
573
|
+
return value
|
|
574
|
+
|
|
575
|
+
@staticmethod
|
|
576
|
+
def short(value, f, ctx, v, node):
|
|
577
|
+
return _short(value, f.label)
|
|
578
|
+
|
|
579
|
+
@staticmethod
|
|
580
|
+
def int(value, f, ctx, v, node):
|
|
581
|
+
if type(value) is not int or not f.low <= value <= f.high:
|
|
582
|
+
raise ValueError(f"{f.label} is a whole number from {int(f.low):,} to {int(f.high):,}")
|
|
583
|
+
return value
|
|
584
|
+
|
|
585
|
+
@staticmethod
|
|
586
|
+
def choice(value, f, ctx, v, node):
|
|
587
|
+
if value not in f.choices:
|
|
588
|
+
raise ValueError(f"{f.label} is one of " + ", ".join(f.choices))
|
|
589
|
+
return value
|
|
590
|
+
|
|
591
|
+
@staticmethod
|
|
592
|
+
def bare(value, f, ctx, v, node):
|
|
593
|
+
source = _short(value, f.label, values.MAX_SOURCE + 4)
|
|
594
|
+
shape = expr.template(source) if source.startswith("{=") else None
|
|
595
|
+
if shape is not None:
|
|
596
|
+
if shape.whole is None:
|
|
597
|
+
raise ValueError(f"{f.label} is one expression")
|
|
598
|
+
return shape.whole.source.strip()
|
|
599
|
+
expr.compile(source)
|
|
600
|
+
return source
|
|
601
|
+
|
|
602
|
+
@staticmethod
|
|
603
|
+
def input(value, f, ctx, v, node):
|
|
604
|
+
if value is None:
|
|
605
|
+
return None
|
|
606
|
+
if not isinstance(value, str) or len(value) > TEXT_CHARS:
|
|
607
|
+
raise ValueError(f"{f.label} is an expression, like {{= $input }}")
|
|
608
|
+
return value
|
|
609
|
+
|
|
610
|
+
@staticmethod
|
|
611
|
+
def schema(value, f, ctx, v, node):
|
|
612
|
+
return validate_schema(value)
|
|
613
|
+
|
|
614
|
+
@staticmethod
|
|
615
|
+
def object(value, f, ctx, v, node):
|
|
616
|
+
if not isinstance(value, dict):
|
|
617
|
+
raise ValueError(f"{f.label} is one JSON object")
|
|
618
|
+
if _json_size(value) > 16_000:
|
|
619
|
+
raise ValueError(f"{f.label} is too long")
|
|
620
|
+
return value
|
|
621
|
+
|
|
622
|
+
@staticmethod
|
|
623
|
+
def fields(value, f, ctx, v, node):
|
|
624
|
+
if not isinstance(value, dict) or not 1 <= len(value) <= MAX_FIELDS:
|
|
625
|
+
raise ValueError(f"Set 1 to {MAX_FIELDS} fields")
|
|
626
|
+
for name, item in value.items():
|
|
627
|
+
if not VAR.fullmatch(name):
|
|
628
|
+
raise ValueError(f"A field's name is letters, digits and _, not {name[:40]!r}")
|
|
629
|
+
if not isinstance(item, str) or not item.strip() or len(item) > TEXT_CHARS:
|
|
630
|
+
raise ValueError(f"{name} is a value or an expression, like {{= $input.answer }}")
|
|
631
|
+
return value
|
|
632
|
+
|
|
633
|
+
@staticmethod
|
|
634
|
+
def values(value, f, ctx, v, node):
|
|
635
|
+
if not isinstance(value, dict) or len(value) > starters.MAX_BLANKS:
|
|
636
|
+
raise ValueError(f"Give at most {starters.MAX_BLANKS} values, by name")
|
|
637
|
+
for name, item in value.items():
|
|
638
|
+
_short(name, "An input's name", starters.NAME_CHARS)
|
|
639
|
+
if not isinstance(item, (str, int, float, bool)) or (isinstance(item, str) and len(item) > TEXT_CHARS):
|
|
640
|
+
raise ValueError(f"The value for {name} is short text, a number or an expression")
|
|
641
|
+
return value
|
|
642
|
+
|
|
643
|
+
@staticmethod
|
|
644
|
+
def delivery(value, f, ctx, v, node):
|
|
645
|
+
if value not in ctx.deliveries:
|
|
646
|
+
raise ValueError("Send to a notification, the clipboard, a file or a saved destination")
|
|
647
|
+
return value
|
|
648
|
+
|
|
649
|
+
@staticmethod
|
|
650
|
+
def app_id(value, f, ctx, v, node):
|
|
651
|
+
if value is None:
|
|
652
|
+
return None
|
|
653
|
+
if value == "any":
|
|
654
|
+
return None
|
|
655
|
+
if not isinstance(value, str) or not 1 <= len(value) <= 160:
|
|
656
|
+
raise ValueError("A task's app is an app's id, or null for the Home Screen")
|
|
657
|
+
if ctx.app_ids is not None and value not in ctx.app_ids(value):
|
|
658
|
+
raise ValueError(app_missing(value))
|
|
659
|
+
return value
|
|
660
|
+
|
|
661
|
+
@staticmethod
|
|
662
|
+
def device(value, f, ctx, v, node):
|
|
663
|
+
if value is None or value in ("any", "loop"):
|
|
664
|
+
return value
|
|
665
|
+
if ctx.device is None:
|
|
666
|
+
raise ValueError("Mobster can't run a step on a chosen phone here. Use the workflow's phone.")
|
|
667
|
+
return ctx.device(value)
|
|
668
|
+
|
|
669
|
+
@staticmethod
|
|
670
|
+
def account(value, f, ctx, v, node):
|
|
671
|
+
if value is None:
|
|
672
|
+
return None
|
|
673
|
+
if ctx.account is None:
|
|
674
|
+
raise ValueError("Profiles aren't available right now. Use the workflow's profile.")
|
|
675
|
+
return ctx.account(value)
|
|
676
|
+
|
|
677
|
+
@staticmethod
|
|
678
|
+
def workflow(value, f, ctx, v, node):
|
|
679
|
+
if not isinstance(value, str) or not WORKFLOW_ID.fullmatch(value):
|
|
680
|
+
raise ValueError("Choose a saved workflow")
|
|
681
|
+
if node["type"] == "phone.taught":
|
|
682
|
+
if value == ctx.workflow_id:
|
|
683
|
+
raise ValueError("A workflow can't repeat itself")
|
|
684
|
+
if ctx.workflows is not None:
|
|
685
|
+
found = ctx.workflows.get(value)
|
|
686
|
+
if found is None:
|
|
687
|
+
raise ValueError("The workflow a step runs was deleted. Choose another one.")
|
|
688
|
+
if ctx.plain is not None and not ctx.plain(found):
|
|
689
|
+
raise ValueError(f"“{found.get('name')}” has several steps. A taught step repeats a one-task "
|
|
690
|
+
"workflow.")
|
|
691
|
+
return value
|
|
692
|
+
|
|
693
|
+
@staticmethod
|
|
694
|
+
def pack(value, f, ctx, v, node):
|
|
695
|
+
value = _short(value, "The app", 60)
|
|
696
|
+
if ctx.packs is None:
|
|
697
|
+
raise ValueError("App packs aren't available in this Mobster")
|
|
698
|
+
if value not in ctx.packs:
|
|
699
|
+
raise ValueError(f"No app pack is called {value}")
|
|
700
|
+
return value
|
|
701
|
+
|
|
702
|
+
@staticmethod
|
|
703
|
+
def reader(value, f, ctx, v, node):
|
|
704
|
+
value = _short(value, "What to read", 60)
|
|
705
|
+
pack = (ctx.packs or {}).get(node["config"].get("app")) or {}
|
|
706
|
+
if pack and value not in (pack.get("readers") or {}):
|
|
707
|
+
raise ValueError(f"{pack.get('name') or 'That app'} can't read {value}. It reads "
|
|
708
|
+
+ ", ".join(sorted(pack.get("readers") or {})))
|
|
709
|
+
return value
|
|
710
|
+
|
|
711
|
+
@staticmethod
|
|
712
|
+
def cases(value, f, ctx, v, node):
|
|
713
|
+
if not isinstance(value, list) or not 1 <= len(value) <= MAX_CASES:
|
|
714
|
+
raise ValueError(f"A branch has 1 to {MAX_CASES} cases")
|
|
715
|
+
seen, out = set(), []
|
|
716
|
+
for case in value:
|
|
717
|
+
if not isinstance(case, dict) or set(case) - {"id", "label", "when"}:
|
|
718
|
+
raise ValueError("A case has an id, a label and when")
|
|
719
|
+
ident = case.get("id")
|
|
720
|
+
if not isinstance(ident, str) or not ID.fullmatch(ident) or ident in seen or ident in RESERVED_PORTS:
|
|
721
|
+
raise ValueError("Each case has its own id: 1 to 24 letters, digits, - or _ (not else or error)")
|
|
722
|
+
seen.add(ident)
|
|
723
|
+
label = _short(case.get("label") or ident, "A case's label", TITLE_CHARS)
|
|
724
|
+
when = _Fields.bare(case.get("when"), Field("bare", f"The case {label}"), ctx, v, node)
|
|
725
|
+
out.append({"id": ident, "label": label, "when": when})
|
|
726
|
+
return out
|
|
727
|
+
|
|
728
|
+
@staticmethod
|
|
729
|
+
def rows(value, f, ctx, v, node):
|
|
730
|
+
if value is None:
|
|
731
|
+
return None
|
|
732
|
+
if not isinstance(value, list) or not 1 <= len(value) <= MAX_ROWS:
|
|
733
|
+
raise ValueError(f"Give 1 to {MAX_ROWS} rows")
|
|
734
|
+
out = []
|
|
735
|
+
for index, row in enumerate(value, 1):
|
|
736
|
+
try:
|
|
737
|
+
cleaned = starters.clean_values(row)
|
|
738
|
+
except ValueError as error:
|
|
739
|
+
raise ValueError(f"Row {index}: {error}") from None
|
|
740
|
+
if not cleaned:
|
|
741
|
+
raise ValueError(f"Row {index} is empty")
|
|
742
|
+
out.append({name: item for name, item in row.items() if starters._key(name) in cleaned})
|
|
743
|
+
return out
|
|
744
|
+
|
|
745
|
+
@staticmethod
|
|
746
|
+
def phones(value, f, ctx, v, node):
|
|
747
|
+
if value == "all":
|
|
748
|
+
return value
|
|
749
|
+
if isinstance(value, dict) and set(value) == {"count"}:
|
|
750
|
+
if type(value["count"]) is not int or not 1 <= value["count"] <= MAX_CONCURRENCY:
|
|
751
|
+
raise ValueError(f"A loop takes 1 to {MAX_CONCURRENCY} phones")
|
|
752
|
+
return value
|
|
753
|
+
if isinstance(value, list) and 1 <= len(value) <= MAX_ITERATIONS:
|
|
754
|
+
if ctx.device is None:
|
|
755
|
+
raise ValueError("Mobster can't choose phones here")
|
|
756
|
+
ids = [ctx.device(item) for item in value]
|
|
757
|
+
if len(set(ids)) != len(ids):
|
|
758
|
+
raise ValueError("Each phone once")
|
|
759
|
+
return ids
|
|
760
|
+
raise ValueError('Choose the phones: "all", a list of phones, or {"count": n}')
|
|
761
|
+
|
|
762
|
+
@staticmethod
|
|
763
|
+
def cron(value, f, ctx, v, node):
|
|
764
|
+
value = _short(value, "The schedule", 120)
|
|
765
|
+
return ctx.cron(value, node["config"].get("timezone")) if ctx.cron else value
|
|
766
|
+
|
|
767
|
+
@staticmethod
|
|
768
|
+
def timezone(value, f, ctx, v, node):
|
|
769
|
+
value = _short(value, "The time zone", 64)
|
|
770
|
+
return ctx.timezone(value) if ctx.timezone else value
|
|
771
|
+
|
|
772
|
+
@staticmethod
|
|
773
|
+
def app(value, f, ctx, v, node):
|
|
774
|
+
return None if value is None else _short(value, "The app", 200)
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def public_limits():
|
|
778
|
+
"""The limits a graph is checked against, for the canvas (GET /api/workflows/node-types)."""
|
|
779
|
+
return {"nodes": MAX_NODES, "edges": MAX_EDGES, "fanout": MAX_FANOUT, "nesting": MAX_NESTING,
|
|
780
|
+
"iterations": MAX_ITERATIONS, "untilRounds": MAX_UNTIL, "times": MAX_TIMES, "concurrency": MAX_CONCURRENCY,
|
|
781
|
+
"graphBytes": MAX_GRAPH_BYTES, "pinBytes": MAX_PIN_BYTES, "pinsBytes": MAX_PINS_BYTES, "inputs": MAX_INPUTS,
|
|
782
|
+
"vars": MAX_VARS, "cases": MAX_CASES, "fields": MAX_FIELDS, "rows": MAX_ROWS, "textChars": TEXT_CHARS,
|
|
783
|
+
"waitSeconds": list(WAIT_RANGE), "approvalMinutes": list(APPROVAL_RANGE), "spendUsd": list(SPEND_RANGE),
|
|
784
|
+
"minutes": list(MINUTES_RANGE), "retryTries": list(RETRY_TRIES), "retryBackoffSeconds": list(RETRY_BACKOFF),
|
|
785
|
+
"expression": {"chars": values.MAX_SOURCE, "nodes": values.MAX_NODES, "steps": values.MAX_STEPS,
|
|
786
|
+
"resultBytes": values.MAX_RESULT}}
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
_register_core()
|