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.
Files changed (1009) hide show
  1. mobile_agent/__init__.py +4 -0
  2. mobile_agent/__main__.py +598 -0
  3. mobile_agent/accounts/__init__.py +1 -0
  4. mobile_agent/accounts/context.py +80 -0
  5. mobile_agent/accounts/mcp.py +55 -0
  6. mobile_agent/accounts/pacing.py +318 -0
  7. mobile_agent/accounts/register.py +78 -0
  8. mobile_agent/accounts/routes.py +212 -0
  9. mobile_agent/accounts/routing.py +69 -0
  10. mobile_agent/accounts/screen.py +273 -0
  11. mobile_agent/accounts/stats.py +54 -0
  12. mobile_agent/accounts/store.py +394 -0
  13. mobile_agent/agent_hooks.py +123 -0
  14. mobile_agent/ai_plans/__init__.py +7 -0
  15. mobile_agent/ai_plans/agents.py +96 -0
  16. mobile_agent/ai_plans/claude_code.py +208 -0
  17. mobile_agent/ai_plans/cli.py +400 -0
  18. mobile_agent/ai_plans/codex.py +258 -0
  19. mobile_agent/ai_plans/compat_chat.py +307 -0
  20. mobile_agent/ai_plans/device_login.py +164 -0
  21. mobile_agent/ai_plans/draft_client.py +68 -0
  22. mobile_agent/ai_plans/gemini.py +137 -0
  23. mobile_agent/ai_plans/instructions.py +65 -0
  24. mobile_agent/ai_plans/mcp_http.py +160 -0
  25. mobile_agent/ai_plans/phone_tools.py +892 -0
  26. mobile_agent/ai_plans/providers.py +235 -0
  27. mobile_agent/ai_plans/routes.py +160 -0
  28. mobile_agent/ai_plans/runner.py +400 -0
  29. mobile_agent/ai_plans/vault.py +114 -0
  30. mobile_agent/alerts.py +337 -0
  31. mobile_agent/android/__init__.py +18 -0
  32. mobile_agent/android/adb.py +268 -0
  33. mobile_agent/android/conditions.py +128 -0
  34. mobile_agent/android/doctor.py +54 -0
  35. mobile_agent/android/driver.py +137 -0
  36. mobile_agent/android/emulator.py +216 -0
  37. mobile_agent/android/logs.py +85 -0
  38. mobile_agent/android/manager.py +225 -0
  39. mobile_agent/android/records.py +31 -0
  40. mobile_agent/android/tree.py +96 -0
  41. mobile_agent/answer_shapes.py +230 -0
  42. mobile_agent/api_devices.py +333 -0
  43. mobile_agent/api_errors.py +17 -0
  44. mobile_agent/api_get.py +249 -0
  45. mobile_agent/api_http.py +467 -0
  46. mobile_agent/api_post.py +453 -0
  47. mobile_agent/api_routes.py +173 -0
  48. mobile_agent/app_api/__init__.py +4 -0
  49. mobile_agent/app_api/cli.py +151 -0
  50. mobile_agent/app_api/endpoints.py +193 -0
  51. mobile_agent/app_api/mcp.py +54 -0
  52. mobile_agent/app_api/openapi.py +146 -0
  53. mobile_agent/app_api/reading.py +172 -0
  54. mobile_agent/app_api/register.py +10 -0
  55. mobile_agent/app_api/screen.py +216 -0
  56. mobile_agent/app_api/service.py +382 -0
  57. mobile_agent/app_api/sessions.py +404 -0
  58. mobile_agent/app_api/tokens.py +167 -0
  59. mobile_agent/app_choice.py +131 -0
  60. mobile_agent/app_icons.py +183 -0
  61. mobile_agent/app_killer.py +69 -0
  62. mobile_agent/artifacts.py +606 -0
  63. mobile_agent/attach/__init__.py +13 -0
  64. mobile_agent/attach/cli.py +46 -0
  65. mobile_agent/attach/flutter.py +28 -0
  66. mobile_agent/attach/score.py +119 -0
  67. mobile_agent/attach/web.py +13 -0
  68. mobile_agent/attachments/__init__.py +1 -0
  69. mobile_agent/attachments/agent.py +418 -0
  70. mobile_agent/attachments/extract.py +217 -0
  71. mobile_agent/attachments/mcp.py +104 -0
  72. mobile_agent/attachments/register.py +73 -0
  73. mobile_agent/attachments/routes.py +326 -0
  74. mobile_agent/attachments/service.py +170 -0
  75. mobile_agent/attachments/sniff.py +152 -0
  76. mobile_agent/attachments/store.py +441 -0
  77. mobile_agent/audit_verify.py +436 -0
  78. mobile_agent/bench/README.md +410 -0
  79. mobile_agent/bench/__init__.py +0 -0
  80. mobile_agent/bench/__main__.py +394 -0
  81. mobile_agent/bench/ab.py +168 -0
  82. mobile_agent/bench/actuation.py +368 -0
  83. mobile_agent/bench/agents/__init__.py +9 -0
  84. mobile_agent/bench/agents/base.py +105 -0
  85. mobile_agent/bench/agents/plan_clients.py +245 -0
  86. mobile_agent/bench/agents/smart.py +533 -0
  87. mobile_agent/bench/checks.py +358 -0
  88. mobile_agent/bench/decide_gate.py +214 -0
  89. mobile_agent/bench/diagnose.py +181 -0
  90. mobile_agent/bench/evals/__init__.py +14 -0
  91. mobile_agent/bench/evals/build.py +565 -0
  92. mobile_agent/bench/evals/checkpoint.py +214 -0
  93. mobile_agent/bench/evals/irt.py +219 -0
  94. mobile_agent/bench/evals/l0.py +647 -0
  95. mobile_agent/bench/evals/l1.py +866 -0
  96. mobile_agent/bench/evals/models.py +124 -0
  97. mobile_agent/bench/evals/pairwise.py +189 -0
  98. mobile_agent/bench/evals/proxy.py +90 -0
  99. mobile_agent/bench/evals/sets.py +149 -0
  100. mobile_agent/bench/evals/traces.py +446 -0
  101. mobile_agent/bench/fixtures.py +176 -0
  102. mobile_agent/bench/guard_replay.py +129 -0
  103. mobile_agent/bench/h2-manifest.json +20 -0
  104. mobile_agent/bench/h3-manifest.json +20 -0
  105. mobile_agent/bench/heldout-manifest.json +32 -0
  106. mobile_agent/bench/heldout.py +375 -0
  107. mobile_agent/bench/iosworld.py +901 -0
  108. mobile_agent/bench/iosworld_stats.py +510 -0
  109. mobile_agent/bench/llm_units.py +116 -0
  110. mobile_agent/bench/manifest.json +93 -0
  111. mobile_agent/bench/metrics.py +66 -0
  112. mobile_agent/bench/phone.py +44 -0
  113. mobile_agent/bench/replay.py +172 -0
  114. mobile_agent/bench/report.py +207 -0
  115. mobile_agent/bench/runner.py +456 -0
  116. mobile_agent/bench/safety.py +310 -0
  117. mobile_agent/bench/stream_timing.py +213 -0
  118. mobile_agent/bench/suite.py +444 -0
  119. mobile_agent/bench/sweep.py +176 -0
  120. mobile_agent/bench/tape.py +798 -0
  121. mobile_agent/bench/tape_replay.py +88 -0
  122. mobile_agent/bench/trace_evals.py +128 -0
  123. mobile_agent/bench/trace_view.py +81 -0
  124. mobile_agent/bench/truth/iphone15pro.json +138 -0
  125. mobile_agent/bench/truth.py +340 -0
  126. mobile_agent/bench/tuning.py +543 -0
  127. mobile_agent/bench/tuning_analysis.py +656 -0
  128. mobile_agent/bench/vague-heldout-manifest.json +12 -0
  129. mobile_agent/bench/vague_rubric.py +135 -0
  130. mobile_agent/bench/vague_run.py +243 -0
  131. mobile_agent/brand.py +454 -0
  132. mobile_agent/brand_fonts.py +756 -0
  133. mobile_agent/build_identity.py +21 -0
  134. mobile_agent/cable_bt.py +623 -0
  135. mobile_agent/cable_bt_runtime.py +128 -0
  136. mobile_agent/catalog.py +69 -0
  137. mobile_agent/chart_svg.py +415 -0
  138. mobile_agent/chat_here.py +189 -0
  139. mobile_agent/cli_constants.py +5 -0
  140. mobile_agent/cli_parser.py +611 -0
  141. mobile_agent/cli_run.py +115 -0
  142. mobile_agent/compiled_read/__init__.py +21 -0
  143. mobile_agent/compiled_read/delta.py +89 -0
  144. mobile_agent/compiled_read/extractors.py +202 -0
  145. mobile_agent/compiled_read/routes.py +56 -0
  146. mobile_agent/completion.py +267 -0
  147. mobile_agent/compose.py +48 -0
  148. mobile_agent/config.py +114 -0
  149. mobile_agent/connectors/__init__.py +1 -0
  150. mobile_agent/connectors/api_session.py +76 -0
  151. mobile_agent/connectors/callback_page.py +146 -0
  152. mobile_agent/connectors/client.py +582 -0
  153. mobile_agent/connectors/github_app.py +55 -0
  154. mobile_agent/connectors/graph_api.py +396 -0
  155. mobile_agent/connectors/imports.py +170 -0
  156. mobile_agent/connectors/issue.py +66 -0
  157. mobile_agent/connectors/keystore.py +85 -0
  158. mobile_agent/connectors/marks.py +222 -0
  159. mobile_agent/connectors/oauth.py +476 -0
  160. mobile_agent/connectors/picker.py +228 -0
  161. mobile_agent/connectors/presets.py +591 -0
  162. mobile_agent/connectors/register.py +48 -0
  163. mobile_agent/connectors/routes.py +153 -0
  164. mobile_agent/connectors/service.py +966 -0
  165. mobile_agent/connectors/shared.py +136 -0
  166. mobile_agent/connectors/skill.py +446 -0
  167. mobile_agent/connectors/slack_api.py +269 -0
  168. mobile_agent/connectors/steps.py +92 -0
  169. mobile_agent/connectors/store.py +144 -0
  170. mobile_agent/connectors/vault.py +72 -0
  171. mobile_agent/console.py +178 -0
  172. mobile_agent/content_queue/__init__.py +1 -0
  173. mobile_agent/content_queue/client.py +263 -0
  174. mobile_agent/content_queue/importing.py +153 -0
  175. mobile_agent/content_queue/media_check.py +224 -0
  176. mobile_agent/content_queue/prompt.py +64 -0
  177. mobile_agent/content_queue/register.py +91 -0
  178. mobile_agent/content_queue/routes.py +370 -0
  179. mobile_agent/content_queue/runner.py +475 -0
  180. mobile_agent/content_queue/store.py +521 -0
  181. mobile_agent/contract.py +996 -0
  182. mobile_agent/contract_asks.py +248 -0
  183. mobile_agent/contract_prompt.py +45 -0
  184. mobile_agent/contract_screen.py +981 -0
  185. mobile_agent/contract_view.py +84 -0
  186. mobile_agent/corpus/__init__.py +26 -0
  187. mobile_agent/corpus/cli.py +63 -0
  188. mobile_agent/corpus/consent.py +55 -0
  189. mobile_agent/corpus/listener.py +76 -0
  190. mobile_agent/corpus/register.py +9 -0
  191. mobile_agent/corpus/skeleton.py +72 -0
  192. mobile_agent/corpus/store.py +143 -0
  193. mobile_agent/costs.py +161 -0
  194. mobile_agent/decider_scope.py +40 -0
  195. mobile_agent/decision_provider.py +189 -0
  196. mobile_agent/deeplinks.py +352 -0
  197. mobile_agent/device_info.py +116 -0
  198. mobile_agent/device_manager.py +978 -0
  199. mobile_agent/device_status.py +195 -0
  200. mobile_agent/device_targets.py +217 -0
  201. mobile_agent/devices.py +676 -0
  202. mobile_agent/devkit/__init__.py +64 -0
  203. mobile_agent/devkit/account_cli.py +90 -0
  204. mobile_agent/devkit/asc.py +562 -0
  205. mobile_agent/devkit/asc_cli.py +214 -0
  206. mobile_agent/devkit/ci.py +737 -0
  207. mobile_agent/devkit/ci_cli.py +196 -0
  208. mobile_agent/devkit/ci_hosts.py +194 -0
  209. mobile_agent/devkit/es256.py +197 -0
  210. mobile_agent/devkit/expo.py +440 -0
  211. mobile_agent/devkit/expo_cli.py +405 -0
  212. mobile_agent/devkit/expo_dev.py +203 -0
  213. mobile_agent/devkit/expo_routes.py +90 -0
  214. mobile_agent/devkit/fixtures_cli.py +133 -0
  215. mobile_agent/devkit/fixtures_routes.py +82 -0
  216. mobile_agent/devkit/links.py +375 -0
  217. mobile_agent/devkit/links_cli.py +123 -0
  218. mobile_agent/devkit/links_routes.py +83 -0
  219. mobile_agent/devkit/nightly.py +152 -0
  220. mobile_agent/devkit/shots.py +203 -0
  221. mobile_agent/devkit/testflight.py +155 -0
  222. mobile_agent/devkit/testflight_cli.py +127 -0
  223. mobile_agent/devkit/testflight_routes.py +111 -0
  224. mobile_agent/devkit/xcode.py +236 -0
  225. mobile_agent/devkit/xcode_cli.py +101 -0
  226. mobile_agent/devtools.py +419 -0
  227. mobile_agent/doctor.py +509 -0
  228. mobile_agent/doctor_fix.py +467 -0
  229. mobile_agent/downloads.py +55 -0
  230. mobile_agent/drivers.py +793 -0
  231. mobile_agent/dry_run.py +102 -0
  232. mobile_agent/effect_ledger.py +83 -0
  233. mobile_agent/engine_health.py +378 -0
  234. mobile_agent/engine_hooks.py +96 -0
  235. mobile_agent/engines.py +408 -0
  236. mobile_agent/env_file.py +63 -0
  237. mobile_agent/env_secrets.py +149 -0
  238. mobile_agent/errors.py +15 -0
  239. mobile_agent/evals/__init__.py +3 -0
  240. mobile_agent/evals/app_survey.py +149 -0
  241. mobile_agent/evals/development/__init__.py +1 -0
  242. mobile_agent/evals/development/score.py +276 -0
  243. mobile_agent/evals/latency_report.py +312 -0
  244. mobile_agent/evals/offline_report.py +374 -0
  245. mobile_agent/evals/oracles.py +298 -0
  246. mobile_agent/evals/redteam/__init__.py +15 -0
  247. mobile_agent/evals/redteam/__main__.py +72 -0
  248. mobile_agent/evals/redteam/cases.py +730 -0
  249. mobile_agent/evals/redteam/harness.py +604 -0
  250. mobile_agent/evals/redteam/live.py +139 -0
  251. mobile_agent/evals/redteam/plain.py +85 -0
  252. mobile_agent/evals/vision/__init__.py +0 -0
  253. mobile_agent/evals/vision/images/dog_001.jpg +0 -0
  254. mobile_agent/evals/vision/images/dog_002.jpg +0 -0
  255. mobile_agent/evals/vision/images/dog_003.jpg +0 -0
  256. mobile_agent/evals/vision/images/dog_004.jpg +0 -0
  257. mobile_agent/evals/vision/images/dog_005.jpg +0 -0
  258. mobile_agent/evals/vision/images/dog_006.jpg +0 -0
  259. mobile_agent/evals/vision/images/dog_007.jpg +0 -0
  260. mobile_agent/evals/vision/images/dog_010.jpg +0 -0
  261. mobile_agent/evals/vision/images/dog_011.jpg +0 -0
  262. mobile_agent/evals/vision/images/dog_013.jpg +0 -0
  263. mobile_agent/evals/vision/images/dog_014.jpg +0 -0
  264. mobile_agent/evals/vision/images/dog_015.jpg +0 -0
  265. mobile_agent/evals/vision/images/dog_016.jpg +0 -0
  266. mobile_agent/evals/vision/images/dog_017.jpg +0 -0
  267. mobile_agent/evals/vision/images/dog_019.jpg +0 -0
  268. mobile_agent/evals/vision/images/dog_020.jpg +0 -0
  269. mobile_agent/evals/vision/images/dog_021.jpg +0 -0
  270. mobile_agent/evals/vision/images/dog_022.jpg +0 -0
  271. mobile_agent/evals/vision/images/dog_023.jpg +0 -0
  272. mobile_agent/evals/vision/images/dog_024.jpg +0 -0
  273. mobile_agent/evals/vision/images/dog_026.jpg +0 -0
  274. mobile_agent/evals/vision/images/dog_027.jpg +0 -0
  275. mobile_agent/evals/vision/images/dog_034.jpg +0 -0
  276. mobile_agent/evals/vision/images/dog_035.jpg +0 -0
  277. mobile_agent/evals/vision/images/dog_038.jpg +0 -0
  278. mobile_agent/evals/vision/images/dog_042.jpg +0 -0
  279. mobile_agent/evals/vision/images/dog_047.jpg +0 -0
  280. mobile_agent/evals/vision/images/dog_052.jpg +0 -0
  281. mobile_agent/evals/vision/images/dog_058.jpg +0 -0
  282. mobile_agent/evals/vision/images/dog_062.jpg +0 -0
  283. mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
  284. mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
  285. mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
  286. mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
  287. mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
  288. mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
  289. mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
  290. mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
  291. mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
  292. mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
  293. mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
  294. mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
  295. mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
  296. mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
  297. mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
  298. mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
  299. mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
  300. mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
  301. mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
  302. mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
  303. mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
  304. mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
  305. mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
  306. mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
  307. mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
  308. mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
  309. mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
  310. mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
  311. mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
  312. mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
  313. mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
  314. mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
  315. mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
  316. mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
  317. mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
  318. mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
  319. mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
  320. mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
  321. mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
  322. mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
  323. mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
  324. mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
  325. mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
  326. mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
  327. mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
  328. mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
  329. mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
  330. mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
  331. mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
  332. mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
  333. mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
  334. mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
  335. mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
  336. mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
  337. mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
  338. mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
  339. mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
  340. mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
  341. mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
  342. mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
  343. mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
  344. mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
  345. mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
  346. mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
  347. mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
  348. mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
  349. mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
  350. mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
  351. mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
  352. mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
  353. mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
  354. mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
  355. mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
  356. mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
  357. mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
  358. mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
  359. mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
  360. mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
  361. mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
  362. mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
  363. mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
  364. mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
  365. mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
  366. mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
  367. mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
  368. mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
  369. mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
  370. mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
  371. mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
  372. mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
  373. mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
  374. mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
  375. mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
  376. mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
  377. mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
  378. mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
  379. mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
  380. mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
  381. mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
  382. mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
  383. mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
  384. mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
  385. mobile_agent/evals/vision/manifest.json +2355 -0
  386. mobile_agent/eventbus.py +110 -0
  387. mobile_agent/explain.py +34 -0
  388. mobile_agent/failure_line.py +197 -0
  389. mobile_agent/fanout.py +663 -0
  390. mobile_agent/feedback/__init__.py +2 -0
  391. mobile_agent/feedback/bundle.py +204 -0
  392. mobile_agent/feedback/outbox.py +196 -0
  393. mobile_agent/feedback/register.py +9 -0
  394. mobile_agent/feedback/routes.py +177 -0
  395. mobile_agent/fleet.py +841 -0
  396. mobile_agent/fleet_health.py +729 -0
  397. mobile_agent/fleet_phone.py +186 -0
  398. mobile_agent/frontier/__init__.py +79 -0
  399. mobile_agent/frontier/act.py +380 -0
  400. mobile_agent/frontier/actions.py +70 -0
  401. mobile_agent/frontier/agent.py +256 -0
  402. mobile_agent/frontier/approvals.py +366 -0
  403. mobile_agent/frontier/hooks.py +199 -0
  404. mobile_agent/frontier/interrupts.py +125 -0
  405. mobile_agent/frontier/observe.py +331 -0
  406. mobile_agent/frontier/phases.py +381 -0
  407. mobile_agent/frontier/pipeline.py +61 -0
  408. mobile_agent/frontier/prompt.py +384 -0
  409. mobile_agent/frontier/protocol.py +91 -0
  410. mobile_agent/frontier/safety.py +467 -0
  411. mobile_agent/frontier/saved.py +375 -0
  412. mobile_agent/frontier/screen.py +419 -0
  413. mobile_agent/frontier/skills.py +147 -0
  414. mobile_agent/frontier/stages/__init__.py +1 -0
  415. mobile_agent/frontier/stages/after.py +192 -0
  416. mobile_agent/frontier/stages/approve.py +138 -0
  417. mobile_agent/frontier/stages/controls.py +115 -0
  418. mobile_agent/frontier/stages/ends.py +102 -0
  419. mobile_agent/frontier/stages/gate.py +92 -0
  420. mobile_agent/frontier/stages/guards.py +132 -0
  421. mobile_agent/frontier/stages/points.py +155 -0
  422. mobile_agent/frontier/stages/send.py +177 -0
  423. mobile_agent/frontier/turn.py +151 -0
  424. mobile_agent/full_disk_access.py +83 -0
  425. mobile_agent/gemini.py +69 -0
  426. mobile_agent/genuine.py +120 -0
  427. mobile_agent/graph/__init__.py +4 -0
  428. mobile_agent/graph/catalog.py +789 -0
  429. mobile_agent/graph/chain.py +306 -0
  430. mobile_agent/graph/dispatch.py +98 -0
  431. mobile_agent/graph/draft.py +373 -0
  432. mobile_agent/graph/edit.py +76 -0
  433. mobile_agent/graph/estimate.py +189 -0
  434. mobile_agent/graph/expr.py +562 -0
  435. mobile_agent/graph/functions.py +271 -0
  436. mobile_agent/graph/infer.py +144 -0
  437. mobile_agent/graph/nodes.py +936 -0
  438. mobile_agent/graph/phone_steps.py +178 -0
  439. mobile_agent/graph/rerun.py +327 -0
  440. mobile_agent/graph/run.py +899 -0
  441. mobile_agent/graph/schema.py +842 -0
  442. mobile_agent/graph/state.py +161 -0
  443. mobile_agent/graph/values.py +198 -0
  444. mobile_agent/harness/__init__.py +2 -0
  445. mobile_agent/harness/checkpoints.py +468 -0
  446. mobile_agent/harness/control.py +101 -0
  447. mobile_agent/harness/prewarm.py +213 -0
  448. mobile_agent/harness/register.py +43 -0
  449. mobile_agent/harness/routes.py +114 -0
  450. mobile_agent/harness/settings.py +66 -0
  451. mobile_agent/harness/tools/__init__.py +67 -0
  452. mobile_agent/harness/tools/ask_user.py +102 -0
  453. mobile_agent/harness/tools/watch.py +120 -0
  454. mobile_agent/harness_api.py +815 -0
  455. mobile_agent/hedge.py +203 -0
  456. mobile_agent/help_topics.py +305 -0
  457. mobile_agent/hints.py +248 -0
  458. mobile_agent/history.py +69 -0
  459. mobile_agent/home.py +214 -0
  460. mobile_agent/http_pool.py +175 -0
  461. mobile_agent/image_policy.py +139 -0
  462. mobile_agent/inbox.py +148 -0
  463. mobile_agent/installed_apps.py +344 -0
  464. mobile_agent/integrations/__init__.py +1 -0
  465. mobile_agent/integrations/cli.py +472 -0
  466. mobile_agent/integrations/client_files.py +65 -0
  467. mobile_agent/integrations/clients.py +998 -0
  468. mobile_agent/integrations/doctor.py +185 -0
  469. mobile_agent/integrations/jsonc.py +383 -0
  470. mobile_agent/integrations/routes.py +135 -0
  471. mobile_agent/integrations/rules.py +112 -0
  472. mobile_agent/integrations/server_options.py +46 -0
  473. mobile_agent/integrations/skill.py +152 -0
  474. mobile_agent/integrations/skill_files.py +10 -0
  475. mobile_agent/integrations/textedit.py +309 -0
  476. mobile_agent/intents/__init__.py +17 -0
  477. mobile_agent/intents/atlas.py +216 -0
  478. mobile_agent/intents/callback.py +44 -0
  479. mobile_agent/intents/cli.py +55 -0
  480. mobile_agent/intents/harvest.py +91 -0
  481. mobile_agent/intents/plan.py +93 -0
  482. mobile_agent/ios/__init__.py +1 -0
  483. mobile_agent/ios/guide.py +16 -0
  484. mobile_agent/ios/kb.py +212 -0
  485. mobile_agent/ios/kb_data.py +1588 -0
  486. mobile_agent/ios/mcp.py +44 -0
  487. mobile_agent/ios/register.py +8 -0
  488. mobile_agent/ios/system.py +369 -0
  489. mobile_agent/journal.py +531 -0
  490. mobile_agent/keychain_acl.py +374 -0
  491. mobile_agent/keys.py +395 -0
  492. mobile_agent/latency_overlap.py +36 -0
  493. mobile_agent/latency_trace.py +170 -0
  494. mobile_agent/learned/__init__.py +15 -0
  495. mobile_agent/learned/app_path.py +160 -0
  496. mobile_agent/learned/code_read.py +38 -0
  497. mobile_agent/learned/parts.py +546 -0
  498. mobile_agent/learned/programs.py +691 -0
  499. mobile_agent/learned/register.py +171 -0
  500. mobile_agent/learned/routes.py +61 -0
  501. mobile_agent/learned/taught.py +329 -0
  502. mobile_agent/learned/teach.py +538 -0
  503. mobile_agent/learned/workflows.py +647 -0
  504. mobile_agent/learned/zero.py +41 -0
  505. mobile_agent/links.py +37 -0
  506. mobile_agent/listen.py +607 -0
  507. mobile_agent/lock_spike.py +278 -0
  508. mobile_agent/lockscreen.py +526 -0
  509. mobile_agent/login.py +354 -0
  510. mobile_agent/longform/__init__.py +15 -0
  511. mobile_agent/longform/ads.py +191 -0
  512. mobile_agent/longform/blocks.py +117 -0
  513. mobile_agent/longform/budget.py +265 -0
  514. mobile_agent/longform/context.py +285 -0
  515. mobile_agent/longform/costs.py +93 -0
  516. mobile_agent/longform/digest.py +145 -0
  517. mobile_agent/longform/extract.py +508 -0
  518. mobile_agent/longform/feed.py +505 -0
  519. mobile_agent/longform/followup.py +168 -0
  520. mobile_agent/longform/frames.py +82 -0
  521. mobile_agent/longform/identity.py +87 -0
  522. mobile_agent/longform/inbox.py +221 -0
  523. mobile_agent/longform/items.py +425 -0
  524. mobile_agent/longform/models.py +115 -0
  525. mobile_agent/longform/notebook.py +329 -0
  526. mobile_agent/longform/plan.py +614 -0
  527. mobile_agent/longform/recall.py +195 -0
  528. mobile_agent/longform/receipts.py +81 -0
  529. mobile_agent/longform/register.py +124 -0
  530. mobile_agent/longform/report.py +343 -0
  531. mobile_agent/longform/routes.py +102 -0
  532. mobile_agent/longform/session.py +644 -0
  533. mobile_agent/longform/store.py +146 -0
  534. mobile_agent/longform/synthesis.py +347 -0
  535. mobile_agent/mac_tools.py +223 -0
  536. mobile_agent/manual_control.py +143 -0
  537. mobile_agent/maps.py +149 -0
  538. mobile_agent/marks.py +92 -0
  539. mobile_agent/mcp_server/__init__.py +3 -0
  540. mobile_agent/mcp_server/__main__.py +29 -0
  541. mobile_agent/mcp_server/action_journal.py +24 -0
  542. mobile_agent/mcp_server/action_tools.py +638 -0
  543. mobile_agent/mcp_server/approvals.py +219 -0
  544. mobile_agent/mcp_server/catalog.py +92 -0
  545. mobile_agent/mcp_server/cli.py +194 -0
  546. mobile_agent/mcp_server/codes.py +147 -0
  547. mobile_agent/mcp_server/device_tools.py +306 -0
  548. mobile_agent/mcp_server/direct.py +272 -0
  549. mobile_agent/mcp_server/expo_tools.py +105 -0
  550. mobile_agent/mcp_server/phone_tools.py +510 -0
  551. mobile_agent/mcp_server/protocol.py +364 -0
  552. mobile_agent/mcp_server/registry.py +55 -0
  553. mobile_agent/mcp_server/run_tools.py +531 -0
  554. mobile_agent/mcp_server/schemas.py +425 -0
  555. mobile_agent/mcp_server/screen_tools.py +168 -0
  556. mobile_agent/mcp_server/session.py +469 -0
  557. mobile_agent/mcp_server/support.py +145 -0
  558. mobile_agent/mcp_server/tools.py +493 -0
  559. mobile_agent/memory/__init__.py +9 -0
  560. mobile_agent/memory/cli.py +207 -0
  561. mobile_agent/memory/extract.py +191 -0
  562. mobile_agent/memory/listener.py +100 -0
  563. mobile_agent/memory/model.py +144 -0
  564. mobile_agent/memory/project.py +96 -0
  565. mobile_agent/memory/provider.py +127 -0
  566. mobile_agent/memory/register.py +21 -0
  567. mobile_agent/memory/retrieval.py +172 -0
  568. mobile_agent/memory/routes.py +216 -0
  569. mobile_agent/memory/runs.py +124 -0
  570. mobile_agent/memory/store.py +806 -0
  571. mobile_agent/memory/writer.py +278 -0
  572. mobile_agent/mirroring.py +585 -0
  573. mobile_agent/mirroring_runtime.py +221 -0
  574. mobile_agent/model_clients.py +604 -0
  575. mobile_agent/narrate.py +813 -0
  576. mobile_agent/native_helpers.py +84 -0
  577. mobile_agent/netxray/__init__.py +1 -0
  578. mobile_agent/netxray/analyze.py +322 -0
  579. mobile_agent/netxray/attribution.py +62 -0
  580. mobile_agent/netxray/capture.py +88 -0
  581. mobile_agent/netxray/cli.py +142 -0
  582. mobile_agent/netxray/job.py +219 -0
  583. mobile_agent/netxray/mobster-pcap.c +560 -0
  584. mobile_agent/netxray/packets.py +410 -0
  585. mobile_agent/netxray/pcapng.py +223 -0
  586. mobile_agent/netxray/register.py +93 -0
  587. mobile_agent/netxray/report.py +214 -0
  588. mobile_agent/netxray/store.py +48 -0
  589. mobile_agent/netxray/summary.py +157 -0
  590. mobile_agent/netxray/target.py +67 -0
  591. mobile_agent/netxray/tool.py +66 -0
  592. mobile_agent/netxray/trackers.py +194 -0
  593. mobile_agent/notify_triggers/__init__.py +1 -0
  594. mobile_agent/notify_triggers/rules.py +39 -0
  595. mobile_agent/notify_triggers/sources.py +315 -0
  596. mobile_agent/notify_triggers/watch.py +135 -0
  597. mobile_agent/offers.py +86 -0
  598. mobile_agent/orchestrate.py +215 -0
  599. mobile_agent/outlets/__init__.py +1 -0
  600. mobile_agent/outlets/connect.py +249 -0
  601. mobile_agent/outlets/folder.py +180 -0
  602. mobile_agent/outlets/hooks.py +453 -0
  603. mobile_agent/outlets/inbound.py +472 -0
  604. mobile_agent/outlets/inbound_reply.py +57 -0
  605. mobile_agent/outlets/inbound_routes.py +81 -0
  606. mobile_agent/outlets/kinds.py +308 -0
  607. mobile_agent/outlets/mail.py +110 -0
  608. mobile_agent/outlets/net.py +86 -0
  609. mobile_agent/outlets/notion.py +244 -0
  610. mobile_agent/outlets/register.py +56 -0
  611. mobile_agent/outlets/routes.py +135 -0
  612. mobile_agent/outlets/service.py +580 -0
  613. mobile_agent/outlets/sheets.py +169 -0
  614. mobile_agent/outlets/slack.py +177 -0
  615. mobile_agent/outlets/store.py +194 -0
  616. mobile_agent/outlets/vault.py +80 -0
  617. mobile_agent/outlets/webhook.py +316 -0
  618. mobile_agent/output_contract.py +141 -0
  619. mobile_agent/owner_presence.py +211 -0
  620. mobile_agent/ownership.py +219 -0
  621. mobile_agent/packs/__init__.py +1 -0
  622. mobile_agent/packs/calendar.py +243 -0
  623. mobile_agent/packs/context.py +68 -0
  624. mobile_agent/packs/core.py +757 -0
  625. mobile_agent/packs/front_door.py +316 -0
  626. mobile_agent/packs/heal.py +214 -0
  627. mobile_agent/packs/health.py +122 -0
  628. mobile_agent/packs/insights.py +76 -0
  629. mobile_agent/packs/instagram.py +640 -0
  630. mobile_agent/packs/linkedin.py +106 -0
  631. mobile_agent/packs/maps.py +196 -0
  632. mobile_agent/packs/mcp.py +149 -0
  633. mobile_agent/packs/messages.py +270 -0
  634. mobile_agent/packs/notes.py +226 -0
  635. mobile_agent/packs/photos.py +166 -0
  636. mobile_agent/packs/pins.py +47 -0
  637. mobile_agent/packs/reddit.py +110 -0
  638. mobile_agent/packs/register.py +276 -0
  639. mobile_agent/packs/rows.py +108 -0
  640. mobile_agent/packs/safari.py +143 -0
  641. mobile_agent/packs/settings.py +253 -0
  642. mobile_agent/packs/social.py +300 -0
  643. mobile_agent/packs/threads.py +97 -0
  644. mobile_agent/packs/tiktok.py +455 -0
  645. mobile_agent/packs/tools.py +290 -0
  646. mobile_agent/packs/x.py +127 -0
  647. mobile_agent/packs/youtube.py +199 -0
  648. mobile_agent/parity.py +270 -0
  649. mobile_agent/paths.py +99 -0
  650. mobile_agent/perception.py +470 -0
  651. mobile_agent/permissions/__init__.py +32 -0
  652. mobile_agent/permissions/consequence.py +535 -0
  653. mobile_agent/permissions/exceptions.py +99 -0
  654. mobile_agent/permissions/gate.py +316 -0
  655. mobile_agent/permissions/outside.py +316 -0
  656. mobile_agent/permissions/policy.py +288 -0
  657. mobile_agent/permissions/project_policy.py +411 -0
  658. mobile_agent/permissions/register.py +148 -0
  659. mobile_agent/permissions/rules.py +291 -0
  660. mobile_agent/phone_io/__init__.py +146 -0
  661. mobile_agent/phone_io/cli.py +427 -0
  662. mobile_agent/phone_io/clipboard.py +78 -0
  663. mobile_agent/phone_io/controls.py +78 -0
  664. mobile_agent/phone_io/files.py +289 -0
  665. mobile_agent/phone_io/handoff.py +123 -0
  666. mobile_agent/phone_io/icloud.py +118 -0
  667. mobile_agent/phone_io/install.py +188 -0
  668. mobile_agent/phone_io/network_capture.py +188 -0
  669. mobile_agent/phone_io/network_cli.py +138 -0
  670. mobile_agent/phone_io/network_profiles.py +138 -0
  671. mobile_agent/phone_io/photos.py +512 -0
  672. mobile_agent/phone_io/recording.py +115 -0
  673. mobile_agent/phone_io/secrets.py +146 -0
  674. mobile_agent/phone_io/transfer.py +895 -0
  675. mobile_agent/phone_io/tunnel.py +129 -0
  676. mobile_agent/phone_ready.py +91 -0
  677. mobile_agent/pinning.py +158 -0
  678. mobile_agent/plan_gate.py +807 -0
  679. mobile_agent/plugins/__init__.py +3 -0
  680. mobile_agent/plugins/install.py +163 -0
  681. mobile_agent/plugins/manifest.py +242 -0
  682. mobile_agent/plugins/register.py +15 -0
  683. mobile_agent/plugins/routes.py +82 -0
  684. mobile_agent/plugins/service.py +322 -0
  685. mobile_agent/plugins/skills.py +76 -0
  686. mobile_agent/plugins/store.py +79 -0
  687. mobile_agent/pool.py +285 -0
  688. mobile_agent/power_watch.py +275 -0
  689. mobile_agent/premium_model.py +22 -0
  690. mobile_agent/project_env.py +291 -0
  691. mobile_agent/project_env_cli.py +74 -0
  692. mobile_agent/project_env_routes.py +55 -0
  693. mobile_agent/projects/__init__.py +27 -0
  694. mobile_agent/projects/ci.py +332 -0
  695. mobile_agent/projects/clone.py +339 -0
  696. mobile_agent/projects/drafts.py +158 -0
  697. mobile_agent/projects/folder.py +436 -0
  698. mobile_agent/projects/github.py +131 -0
  699. mobile_agent/projects/register.py +31 -0
  700. mobile_agent/projects/revisions.py +253 -0
  701. mobile_agent/projects/routes.py +234 -0
  702. mobile_agent/projects/runs.py +297 -0
  703. mobile_agent/projects/service.py +695 -0
  704. mobile_agent/projects/setup.py +84 -0
  705. mobile_agent/projects/store.py +379 -0
  706. mobile_agent/projects/sync.py +380 -0
  707. mobile_agent/projects/workflow_files.py +270 -0
  708. mobile_agent/replay_core/__init__.py +26 -0
  709. mobile_agent/replay_core/checks.py +594 -0
  710. mobile_agent/replay_core/engine.py +596 -0
  711. mobile_agent/replay_core/heal.py +364 -0
  712. mobile_agent/replay_core/path.py +240 -0
  713. mobile_agent/replay_core/recorder.py +206 -0
  714. mobile_agent/replay_core/store.py +171 -0
  715. mobile_agent/result_blocks.py +941 -0
  716. mobile_agent/run.py +642 -0
  717. mobile_agent/run_delivery.py +123 -0
  718. mobile_agent/run_executor.py +200 -0
  719. mobile_agent/run_export.py +284 -0
  720. mobile_agent/run_gif.py +891 -0
  721. mobile_agent/runner_hints.py +86 -0
  722. mobile_agent/runner_renewal.py +205 -0
  723. mobile_agent/runtime_create.py +549 -0
  724. mobile_agent/runtime_driver.py +458 -0
  725. mobile_agent/runtime_limits.py +319 -0
  726. mobile_agent/runtime_queue.py +394 -0
  727. mobile_agent/runtime_runs.py +383 -0
  728. mobile_agent/runtime_status.py +181 -0
  729. mobile_agent/runtime_teach.py +295 -0
  730. mobile_agent/runtime_work.py +599 -0
  731. mobile_agent/screen_text.py +101 -0
  732. mobile_agent/secret_filter.py +113 -0
  733. mobile_agent/serve.py +160 -0
  734. mobile_agent/server.py +363 -0
  735. mobile_agent/service_support.py +42 -0
  736. mobile_agent/settle/__init__.py +1 -0
  737. mobile_agent/settle/predict.py +102 -0
  738. mobile_agent/settle/replay.py +92 -0
  739. mobile_agent/settle/store.py +93 -0
  740. mobile_agent/settle/stream.py +43 -0
  741. mobile_agent/setup_cli.py +225 -0
  742. mobile_agent/setup_errors.py +87 -0
  743. mobile_agent/setup_service.py +527 -0
  744. mobile_agent/share.py +104 -0
  745. mobile_agent/signing.py +184 -0
  746. mobile_agent/sim/__init__.py +6 -0
  747. mobile_agent/sim/__main__.py +26 -0
  748. mobile_agent/sim/api.py +952 -0
  749. mobile_agent/sim/apps.py +172 -0
  750. mobile_agent/sim/biometrics.py +84 -0
  751. mobile_agent/sim/cli.py +271 -0
  752. mobile_agent/sim/doctor.py +293 -0
  753. mobile_agent/sim/governor.py +90 -0
  754. mobile_agent/sim/holders.py +88 -0
  755. mobile_agent/sim/models.py +62 -0
  756. mobile_agent/sim/registry.py +161 -0
  757. mobile_agent/sim/simctl.py +269 -0
  758. mobile_agent/sim/wda.py +618 -0
  759. mobile_agent/sim/xcode.py +74 -0
  760. mobile_agent/skills/__init__.py +12 -0
  761. mobile_agent/skills/codes.py +597 -0
  762. mobile_agent/skills/notifications.py +281 -0
  763. mobile_agent/skills/open_url.py +127 -0
  764. mobile_agent/smart_events.py +588 -0
  765. mobile_agent/smart_result.py +490 -0
  766. mobile_agent/smart_run.py +413 -0
  767. mobile_agent/state.py +662 -0
  768. mobile_agent/states.py +217 -0
  769. mobile_agent/steering.py +70 -0
  770. mobile_agent/style.py +178 -0
  771. mobile_agent/supervised.py +234 -0
  772. mobile_agent/swift/helper_bt_hid.swift +534 -0
  773. mobile_agent/swift/helper_doc_text.swift +184 -0
  774. mobile_agent/swift/helper_draft_reply.swift +170 -0
  775. mobile_agent/swift/helper_map.swift +110 -0
  776. mobile_agent/swift/helper_mirroring.swift +1099 -0
  777. mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
  778. mobile_agent/swift/helper_pdf.swift +100 -0
  779. mobile_agent/swift/helper_phone_audio.swift +993 -0
  780. mobile_agent/swift/helper_phone_screen.swift +416 -0
  781. mobile_agent/swift/helper_screen_text.swift +24 -0
  782. mobile_agent/swift/helper_transfer.swift +246 -0
  783. mobile_agent/swift/ocr.swift +22 -0
  784. mobile_agent/task_policy.py +337 -0
  785. mobile_agent/team_signing/__init__.py +34 -0
  786. mobile_agent/team_signing/apple_ca.py +37 -0
  787. mobile_agent/team_signing/bundle.py +136 -0
  788. mobile_agent/team_signing/cli.py +208 -0
  789. mobile_agent/team_signing/identity.py +248 -0
  790. mobile_agent/team_signing/ledger.py +105 -0
  791. mobile_agent/team_signing/prebuilt.py +61 -0
  792. mobile_agent/team_signing/resign.py +256 -0
  793. mobile_agent/team_signing/service.py +382 -0
  794. mobile_agent/team_signing/tooling.py +243 -0
  795. mobile_agent/team_signing/writes.py +268 -0
  796. mobile_agent/terminal_image.py +81 -0
  797. mobile_agent/testkit/__init__.py +1 -0
  798. mobile_agent/testkit/a11y_baseline.py +132 -0
  799. mobile_agent/testkit/api.py +403 -0
  800. mobile_agent/testkit/capsule.py +400 -0
  801. mobile_agent/testkit/capsule_cli.py +57 -0
  802. mobile_agent/testkit/cli.py +948 -0
  803. mobile_agent/testkit/cli_arguments.py +193 -0
  804. mobile_agent/testkit/comparison.py +288 -0
  805. mobile_agent/testkit/comparison_cli.py +41 -0
  806. mobile_agent/testkit/discover.py +257 -0
  807. mobile_agent/testkit/editor.py +243 -0
  808. mobile_agent/testkit/execute.py +515 -0
  809. mobile_agent/testkit/github_checks.py +248 -0
  810. mobile_agent/testkit/gitref.py +66 -0
  811. mobile_agent/testkit/heal_review.py +176 -0
  812. mobile_agent/testkit/health.py +480 -0
  813. mobile_agent/testkit/history.py +255 -0
  814. mobile_agent/testkit/hosted_results.py +321 -0
  815. mobile_agent/testkit/input_files.py +173 -0
  816. mobile_agent/testkit/jobs.py +285 -0
  817. mobile_agent/testkit/junit_verdict.py +33 -0
  818. mobile_agent/testkit/maestro.py +802 -0
  819. mobile_agent/testkit/mcp.py +562 -0
  820. mobile_agent/testkit/mcp_comparison.py +88 -0
  821. mobile_agent/testkit/minimize.py +471 -0
  822. mobile_agent/testkit/minimizer_cli.py +48 -0
  823. mobile_agent/testkit/notify.py +212 -0
  824. mobile_agent/testkit/phone_video.py +191 -0
  825. mobile_agent/testkit/pr.py +491 -0
  826. mobile_agent/testkit/pr_report.py +562 -0
  827. mobile_agent/testkit/prbuild.py +148 -0
  828. mobile_agent/testkit/proof_report.py +55 -0
  829. mobile_agent/testkit/provenance.py +119 -0
  830. mobile_agent/testkit/prrun.py +244 -0
  831. mobile_agent/testkit/recent.py +166 -0
  832. mobile_agent/testkit/recipes.py +146 -0
  833. mobile_agent/testkit/record.py +204 -0
  834. mobile_agent/testkit/register.py +288 -0
  835. mobile_agent/testkit/reports.py +998 -0
  836. mobile_agent/testkit/repro.py +432 -0
  837. mobile_agent/testkit/scenario.py +440 -0
  838. mobile_agent/testkit/scenario_cli.py +37 -0
  839. mobile_agent/testkit/snapshots_cli.py +60 -0
  840. mobile_agent/testkit/suite.py +670 -0
  841. mobile_agent/testkit/targets.py +497 -0
  842. mobile_agent/testkit/xctest.py +378 -0
  843. mobile_agent/threads/__init__.py +1 -0
  844. mobile_agent/threads/chat_once.py +264 -0
  845. mobile_agent/threads/cli.py +740 -0
  846. mobile_agent/threads/client.py +326 -0
  847. mobile_agent/threads/context.py +246 -0
  848. mobile_agent/threads/mcp.py +739 -0
  849. mobile_agent/threads/recent.py +34 -0
  850. mobile_agent/threads/register.py +54 -0
  851. mobile_agent/threads/routes.py +188 -0
  852. mobile_agent/threads/service.py +975 -0
  853. mobile_agent/threads/store.py +361 -0
  854. mobile_agent/threads/summary.py +117 -0
  855. mobile_agent/threads/term.py +101 -0
  856. mobile_agent/threads/workflow_cli.py +104 -0
  857. mobile_agent/tls.py +36 -0
  858. mobile_agent/traces.py +271 -0
  859. mobile_agent/tracks.py +235 -0
  860. mobile_agent/transport.py +271 -0
  861. mobile_agent/triggers/__init__.py +1 -0
  862. mobile_agent/triggers/actions.py +204 -0
  863. mobile_agent/triggers/model.py +146 -0
  864. mobile_agent/triggers/register.py +29 -0
  865. mobile_agent/triggers/routes.py +101 -0
  866. mobile_agent/triggers/service.py +387 -0
  867. mobile_agent/triggers/store.py +142 -0
  868. mobile_agent/tui/__init__.py +89 -0
  869. mobile_agent/tui/app.py +582 -0
  870. mobile_agent/tui/chrome.py +321 -0
  871. mobile_agent/tui/commands.py +375 -0
  872. mobile_agent/tui/demo_phone.py +369 -0
  873. mobile_agent/tui/images.py +29 -0
  874. mobile_agent/tui/keys.py +64 -0
  875. mobile_agent/tui/prompt.py +195 -0
  876. mobile_agent/tui/render.py +395 -0
  877. mobile_agent/tui/screens.py +180 -0
  878. mobile_agent/tui/session.py +308 -0
  879. mobile_agent/tui/tasks.py +287 -0
  880. mobile_agent/tui/theme.py +118 -0
  881. mobile_agent/tui/widgets.py +272 -0
  882. mobile_agent/update_check.py +213 -0
  883. mobile_agent/usage.py +184 -0
  884. mobile_agent/verify/__init__.py +0 -0
  885. mobile_agent/verify/__main__.py +41 -0
  886. mobile_agent/verify/a11y.py +189 -0
  887. mobile_agent/verify/actions.py +290 -0
  888. mobile_agent/verify/assertions.py +570 -0
  889. mobile_agent/verify/audit.py +312 -0
  890. mobile_agent/verify/business.py +221 -0
  891. mobile_agent/verify/business_run.py +131 -0
  892. mobile_agent/verify/business_state.py +168 -0
  893. mobile_agent/verify/check_schema.py +398 -0
  894. mobile_agent/verify/checks.py +614 -0
  895. mobile_agent/verify/cli.py +423 -0
  896. mobile_agent/verify/conditions.py +483 -0
  897. mobile_agent/verify/crashes.py +321 -0
  898. mobile_agent/verify/device_audit.py +246 -0
  899. mobile_agent/verify/dialogs.py +287 -0
  900. mobile_agent/verify/evidence.py +705 -0
  901. mobile_agent/verify/fixtures.py +305 -0
  902. mobile_agent/verify/flow.py +213 -0
  903. mobile_agent/verify/l10n.py +141 -0
  904. mobile_agent/verify/netcerts.py +67 -0
  905. mobile_agent/verify/netproxy.py +356 -0
  906. mobile_agent/verify/netrun.py +81 -0
  907. mobile_agent/verify/netshim.m +28 -0
  908. mobile_agent/verify/netshim.py +48 -0
  909. mobile_agent/verify/netstubs.py +132 -0
  910. mobile_agent/verify/overlay.py +174 -0
  911. mobile_agent/verify/perf.py +239 -0
  912. mobile_agent/verify/proof.py +192 -0
  913. mobile_agent/verify/replay_admission.py +48 -0
  914. mobile_agent/verify/report.py +952 -0
  915. mobile_agent/verify/run_cleanup.py +67 -0
  916. mobile_agent/verify/run_links.py +48 -0
  917. mobile_agent/verify/run_perf.py +44 -0
  918. mobile_agent/verify/run_secrets.py +39 -0
  919. mobile_agent/verify/runner.py +976 -0
  920. mobile_agent/verify/runs.py +57 -0
  921. mobile_agent/verify/screen_coverage.py +181 -0
  922. mobile_agent/verify/smart.py +106 -0
  923. mobile_agent/verify/snapshot_run.py +114 -0
  924. mobile_agent/verify/snapshots.py +306 -0
  925. mobile_agent/verify/sound.py +335 -0
  926. mobile_agent/verify/source_observation.py +130 -0
  927. mobile_agent/verify/system_surfaces/__init__.py +7 -0
  928. mobile_agent/verify/system_surfaces/play.py +106 -0
  929. mobile_agent/verify/system_surfaces/spec.py +139 -0
  930. mobile_agent/verify/system_surfaces/stage.py +138 -0
  931. mobile_agent/verify/system_surfaces/surfaces.py +121 -0
  932. mobile_agent/verify/tree.py +223 -0
  933. mobile_agent/verify/verdict.py +377 -0
  934. mobile_agent/verify/wire.py +392 -0
  935. mobile_agent/verify/wirestore.py +176 -0
  936. mobile_agent/waiting/__init__.py +1 -0
  937. mobile_agent/waiting/contacts.py +73 -0
  938. mobile_agent/waiting/drafts.py +136 -0
  939. mobile_agent/waiting/job.py +145 -0
  940. mobile_agent/waiting/messages.py +174 -0
  941. mobile_agent/waiting/rank.py +115 -0
  942. mobile_agent/waiting/register.py +26 -0
  943. mobile_agent/watch.py +502 -0
  944. mobile_agent/wda/__init__.py +2 -0
  945. mobile_agent/wda/frame_clock.py +646 -0
  946. mobile_agent/wda/frame_source.py +325 -0
  947. mobile_agent/wda/frame_store.py +115 -0
  948. mobile_agent/wda/gate.py +163 -0
  949. mobile_agent/wda/gestures.py +291 -0
  950. mobile_agent/wda/guards.py +94 -0
  951. mobile_agent/wda/identity.py +55 -0
  952. mobile_agent/wda/observe.py +884 -0
  953. mobile_agent/wda/patches.py +856 -0
  954. mobile_agent/wda/point_gestures.py +150 -0
  955. mobile_agent/wda/source.py +32 -0
  956. mobile_agent/wda/system_dialogs.py +571 -0
  957. mobile_agent/wda/text.py +694 -0
  958. mobile_agent/wda/timeouts.py +77 -0
  959. mobile_agent/wda/video.py +505 -0
  960. mobile_agent/wda/watchdog.py +243 -0
  961. mobile_agent/wireless/__init__.py +15 -0
  962. mobile_agent/wireless/api.py +354 -0
  963. mobile_agent/wireless/cli.py +287 -0
  964. mobile_agent/wireless/devicectl.py +229 -0
  965. mobile_agent/wireless/exposure.py +33 -0
  966. mobile_agent/wireless/mobster-wifi-pair.c +122 -0
  967. mobile_agent/wireless/monitor.py +332 -0
  968. mobile_agent/wireless/network.py +334 -0
  969. mobile_agent/wireless/onboarding.py +293 -0
  970. mobile_agent/wireless/pair.py +124 -0
  971. mobile_agent/wireless/probe.py +322 -0
  972. mobile_agent/wireless/register.py +8 -0
  973. mobile_agent/wireless/relay.py +179 -0
  974. mobile_agent/wireless/result.py +87 -0
  975. mobile_agent/wireless/store.py +85 -0
  976. mobile_agent/wireless/switch.py +77 -0
  977. mobile_agent/wireless/transport.py +118 -0
  978. mobile_agent/wireless/words.py +81 -0
  979. mobile_agent/wireless/xctestrun.py +83 -0
  980. mobile_agent/workflow_tools.py +59 -0
  981. mobile_agent/workflows/__init__.py +2 -0
  982. mobile_agent/workflows/brief.py +201 -0
  983. mobile_agent/workflows/cron.py +110 -0
  984. mobile_agent/workflows/health.py +257 -0
  985. mobile_agent/workflows/history.py +126 -0
  986. mobile_agent/workflows/records.py +442 -0
  987. mobile_agent/workflows/runs.py +1000 -0
  988. mobile_agent/workflows/scheduler.py +134 -0
  989. mobile_agent/workflows/service.py +973 -0
  990. mobile_agent/workflows/starters.py +285 -0
  991. mobile_agent/workflows/triggers.py +248 -0
  992. mobile_agent/xray/__init__.py +1 -0
  993. mobile_agent/xray/app_cli.py +116 -0
  994. mobile_agent/xray/cli.py +21 -0
  995. mobile_agent/xray/crawl.py +754 -0
  996. mobile_agent/xray/diff.py +171 -0
  997. mobile_agent/xray/diff_cli.py +43 -0
  998. mobile_agent/xray/job.py +184 -0
  999. mobile_agent/xray/paywall.py +188 -0
  1000. mobile_agent/xray/policy.py +305 -0
  1001. mobile_agent/xray/register.py +43 -0
  1002. mobile_agent/xray/report.py +283 -0
  1003. mobile_agent/xray/screens.py +117 -0
  1004. mobster_cli-0.4.3.dist-info/METADATA +93 -0
  1005. mobster_cli-0.4.3.dist-info/RECORD +1009 -0
  1006. mobster_cli-0.4.3.dist-info/WHEEL +5 -0
  1007. mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
  1008. mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
  1009. 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()