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,638 @@
1
+ """The tools that act on the screen: tap, type, swipe, gestures, alerts, links, launch and wait_for."""
2
+
3
+ import os
4
+ import threading
5
+ import time
6
+ import traceback
7
+ from types import SimpleNamespace
8
+
9
+ from ..verify.assertions import normalize
10
+ from .action_journal import record_action
11
+ from .codes import SECURE_MASK
12
+ from .protocol import ToolResult
13
+ from .session import Outline, collapse, node_name, node_summary, node_target, role_name
14
+ from .support import (
15
+ Phases,
16
+ ToolError,
17
+ cap_text,
18
+ changed_words,
19
+ describe_assertion,
20
+ describe_selector,
21
+ element_at,
22
+ hit_point,
23
+ is_check_error,
24
+ jsonable,
25
+ navbar_hint,
26
+ quote,
27
+ screen_changed,
28
+ screen_fingerprint,
29
+ sentence,
30
+ )
31
+
32
+ # Swipes with `until` stop before this, so the call stays inside CALL_BUDGET.
33
+ SWIPE_SECONDS = 30.0
34
+
35
+ LOGGED_TEXT = 40
36
+
37
+
38
+ class ActionTools:
39
+ def tool_launch_app(self, args, call):
40
+ bundle = args.get("bundle_id")
41
+ if bool(bundle) == bool(args.get("restart")):
42
+ raise ToolError("Pass bundle_id to open an app, or restart to quit the check's app and launch it again, "
43
+ "not both.")
44
+ if args.get("restart"):
45
+ return self._relaunch(args, call)
46
+
47
+ def work(session, phases):
48
+ run, driver = session.run, session.run.driver
49
+ snapshot = driver.observe(timeout=10)
50
+ self._permit_app(session, bundle)
51
+ self._check_action_cancelled(session, call)
52
+ if session.mode == "direct":
53
+ run.launch_app(bundle)
54
+ else:
55
+ driver.call("POST", "/wda/apps/activate", {"bundleId": bundle}, timeout=30)
56
+ after = self._settle(driver, snapshot, timeout=4, wait_seconds=1.0)
57
+ changed = screen_changed(snapshot, after)
58
+ text = f"Opened {bundle}"
59
+ return {"op": "LAUNCH", "text": text, "changed": changed, "line": text + changed_words(changed),
60
+ "after": screen_fingerprint(after)}
61
+ return self._act(args, call, work, "LAUNCH")
62
+
63
+ def tool_home(self, args, call):
64
+ def work(session, phases):
65
+ driver = session.run.driver
66
+ snapshot = driver.observe(timeout=10)
67
+ driver.call("POST", "/wda/pressButton", {"name": "home"}, timeout=10)
68
+ after = self._settle(driver, snapshot)
69
+ changed = screen_changed(snapshot, after)
70
+ return {"op": "HOME", "text": "Pressed Home", "changed": changed,
71
+ "line": "Pressed Home" + changed_words(changed), "after": screen_fingerprint(after)}
72
+ return self._act(args, call, work, "HOME", check=False)
73
+
74
+ def _act(self, args, call, work, operation, check=True):
75
+ session = self._target_session(args)
76
+ run = session.run
77
+ started = time.monotonic()
78
+ with self._locked(session), record_action(run.run_dir, operation):
79
+ phases = Phases()
80
+ if check:
81
+ self._front(session)
82
+ self._check_action_cancelled(session, call)
83
+ # The code fields blacked out when the step began: the after frame is taken while the outline is read,
84
+ # so it can still show a code the outline (read a moment later) no longer has.
85
+ rects_before = list(session.secret_rects)
86
+ try:
87
+ done = work(session, phases)
88
+ except ToolError:
89
+ raise
90
+ except Exception as error:
91
+ self.log(f"Run {session.run_id}: the action failed after {phases.spent}: "
92
+ + "".join(traceback.format_exception(type(error), error, error.__traceback__, limit=6)))
93
+ raise ToolError(self._action_error(error)) from None
94
+ phases("act")
95
+ step, shot = None, {}
96
+ shooter = None
97
+ try:
98
+ if check:
99
+ # A link or tap may have switched apps: check before any frame of it is captured.
100
+ try:
101
+ self._front(session)
102
+ except ToolError as error:
103
+ raise ToolError(done["text"] + ". " + str(error)) from None
104
+ if done.get("record", True):
105
+ release_frame = (run.reserve_step_frame() if session.mode != "direct" else lambda: None)
106
+ if release_frame is None:
107
+ raise ToolError("This run is finishing, so it cannot capture the action's screen.")
108
+
109
+ def take():
110
+ try:
111
+ shot["frame"] = run.frame("step")
112
+ except Exception as error:
113
+ self.log(f"Run {session.run_id}: the step frame failed ({type(error).__name__}).")
114
+ finally:
115
+ release_frame()
116
+ shooter = threading.Thread(target=take, daemon=True, name=f"mobster-mcp-frame-{session.run_id}")
117
+ try:
118
+ shooter.start()
119
+ except BaseException:
120
+ release_frame()
121
+ raise
122
+ outline, alert = self._outline(session, phases, before=done.get("after"))
123
+ except ToolError as error:
124
+ # The action happened; what comes back says why the screen isn't shown (an app that isn't allowed
125
+ # came up), in the tool's own words.
126
+ raise ToolError(done["text"] + ". " + str(error)) from None
127
+ except Exception as error:
128
+ raise ToolError(done["text"] + ". " + self._action_error(error)) from None
129
+ finally:
130
+ if shooter is not None:
131
+ shooter.join(20)
132
+ phases("frame")
133
+ frame = shot.get("frame")
134
+ rects = rects_before + [rect for rect in session.secret_rects if rect not in rects_before]
135
+ if frame and rects:
136
+ if not self.codes.redact_file(session, str(frame), rects) or not os.path.isfile(str(frame)):
137
+ frame = None # it couldn't be redacted, so it was deleted
138
+ if shooter is not None:
139
+ step = run.record_step(op=done["op"], text=done["text"], target=done.get("target"),
140
+ typed=done.get("typed"), changed=done.get("changed"), frame=frame)
141
+ seconds = round(time.monotonic() - started, 2)
142
+ timing = dict(phases.spent)
143
+ self.log(f"Run {session.run_id}: {done.get('log', done['text'])} ({seconds:.2f} s: "
144
+ + ", ".join(f"{name} {value:.2f}" for name, value in timing.items()) + ")")
145
+ picture = self.imager(path=str(frame)) if args.get("image") and frame else None
146
+ structured = {"ok": bool(done.get("ok", True)), "changed": done.get("changed"),
147
+ "step": jsonable(step) if step is not None else None, "screen": outline.structured(alert),
148
+ "seconds": seconds}
149
+ structured.update(done.get("extra") or {})
150
+ return ToolResult(cap_text(done["line"] + "\n" + outline.render(alert)), structured,
151
+ [picture] if picture else [])
152
+
153
+ @staticmethod
154
+ def _action_error(error):
155
+ name = type(error).__name__
156
+ if is_check_error(error):
157
+ return sentence(error)
158
+ if hasattr(error, "klass") or hasattr(error, "kind"):
159
+ return f"The simulator couldn't do that: {error}. Call screen to see the current screen."
160
+ if name == "DriverRejection":
161
+ if getattr(error, "code", None) in getattr(error, "PRE_DISPATCH", ()):
162
+ return "The screen changed before the action. Call screen for current refs."
163
+ return "The action may have reached the device. Call screen to inspect it before another action."
164
+ if name in ("TransportError", "TimeoutError", "URLError", "ConnectionError"):
165
+ return "WebDriverAgent didn't answer in time. Call screen to inspect the device, or stop the run."
166
+ return f"The action failed ({name}: {str(error)[:160]}). Call screen to see the current screen."
167
+
168
+ def _resolve(self, session, tree, args, required=True):
169
+ ref, target = args.get("ref"), args.get("target")
170
+ if ref and target:
171
+ raise ToolError("Pass ref or target, not both.")
172
+ if not ref and not target:
173
+ if required:
174
+ raise ToolError("Pass ref (from the latest outline) or target (a selector) to say which element.")
175
+ return None
176
+ shown = {node.path: node for node in tree.shown()}
177
+ if ref:
178
+ # Refs are numbered across the run, so a ref from an earlier outline never names a node of this one.
179
+ outline = session.outline
180
+ if outline is None or ref not in outline.refs:
181
+ if ref in session.book.issued:
182
+ raise ToolError(f"{ref} is from an earlier screen. Call screen for current refs.")
183
+ raise ToolError(f"{ref} isn't on the latest outline. Call screen for current refs.")
184
+ if tree.fingerprint() != outline.fingerprint or outline.refs[ref] not in shown:
185
+ raise ToolError(f"{ref} is from an earlier screen. Call screen for current refs.")
186
+ return shown[outline.refs[ref]]
187
+ try:
188
+ selector = self.api.parse_selector(target)
189
+ except Exception as error:
190
+ if is_check_error(error):
191
+ raise ToolError(sentence(error)) from None
192
+ raise
193
+ matches = collapse([node for node in tree.select(selector) if node.path in shown])
194
+ if len(matches) == 1:
195
+ return matches[0]
196
+ same_tree = session.outline is not None and tree is session.outline.tree
197
+ session.outline = Outline(tree, session.book)
198
+ if not same_tree:
199
+ session.anchor = None
200
+ outline = session.outline
201
+ described = describe_selector(target)
202
+ if matches:
203
+ lines, named = outline.match_lines(matches)
204
+ if not lines:
205
+ raise ToolError(f"{len(matches)} elements match {described}, and none has a line in the outline. "
206
+ "Add id, label or role to the target to pick one.")
207
+ more = len(matches) - named
208
+ raise ToolError(f"{len(matches)} elements match {described}: " + "; ".join(lines)
209
+ + (f"; and {more} more" if more else "")
210
+ + ". Pass one of these refs, or add id or role to the target.")
211
+ lines = outline.candidates(near=[target.get("label"), target.get("id"), target.get("value")])
212
+ if not lines and target.get("role"):
213
+ lines = outline.candidates([node for node in outline.nodes
214
+ if role_name(node) == str(target["role"]).lower()])
215
+ raise ToolError(f"No shown element matches {described}."
216
+ + (" Closest: " + "; ".join(lines) + "." if lines else "")
217
+ + " Call screen to see what is shown.")
218
+
219
+ def tool_tap(self, args, call):
220
+ def work(session, phases):
221
+ driver = session.run.driver
222
+ tree, snapshot = self._current(session, phases)
223
+ node = self._resolve(session, tree, args)
224
+ element = element_at(snapshot, node.path)
225
+ if self._permit(session, "TAP", node, element, snapshot, call):
226
+ element, snapshot = self._rebind(session, element, snapshot, call)
227
+ if element is not None:
228
+ driver.execute("TAP", element, snapshot, timeout=10)
229
+ else:
230
+ x, y = hit_point(node, tree)
231
+ driver.tap_point(x, y, snapshot, timeout=10)
232
+ phases("dispatch")
233
+ after = self._settle(driver, snapshot)
234
+ phases("settle")
235
+ changed = screen_changed(snapshot, after)
236
+ text = f"Tapped {quote(node_name(node))}"
237
+ return {"op": "TAP", "text": text, "target": node_target(node), "changed": changed,
238
+ "line": text + changed_words(changed), "after": screen_fingerprint(after),
239
+ "extra": {"target": node_summary(node)}}
240
+ return self._act(args, call, work, "TAP")
241
+
242
+ @staticmethod
243
+ def _check_gesture_cancelled(session, call):
244
+ if session.cancelled.is_set() or call.cancelled.is_set() or call.stop.is_set():
245
+ raise ToolError("This gesture was cancelled before dispatch. Nothing was sent.")
246
+
247
+ def _gesture_screen(self, session, grounded, gesture, call):
248
+ from ..wda.point_gestures import same_point_image
249
+ stale = "The gesture's screen changed. Call screen with image=true and choose its points again."
250
+ driver = session.run.driver
251
+
252
+ def read():
253
+ self._check_gesture_cancelled(session, call)
254
+ snapshot = driver.observe(timeout=5)
255
+ if snapshot.bundle_id != grounded["bundle"] or screen_fingerprint(snapshot) != grounded["fingerprint"]:
256
+ raise ToolError(stale)
257
+ return snapshot
258
+
259
+ read()
260
+ self._check_gesture_cancelled(session, call) # a cancel during the first read stops before the capture
261
+ picture = self._screenshot(session)
262
+ after = read()
263
+ self._check_gesture_cancelled(session, call)
264
+ parts = [{"image_url": {"url": "data:image/jpeg;base64," + value}}
265
+ for value in (grounded["picture"], picture or "")]
266
+ if not picture or not all(same_point_image(*parts, x * 100, y * 100) for x, y in gesture.points):
267
+ raise ToolError(stale)
268
+ return after
269
+
270
+ def tool_drag_point(self, args, call):
271
+ return self._point_gesture(args, call, "drag")
272
+
273
+ def tool_hold_point(self, args, call):
274
+ return self._point_gesture(args, call, "hold")
275
+
276
+ def tool_flick_point(self, args, call):
277
+ return self._point_gesture(args, call, "flick")
278
+
279
+ def _point_gesture(self, args, call, kind):
280
+ # Sent once: a failure after dispatch is never repeated.
281
+ from ..contract_asks import point_spot
282
+ from ..wda.point_gestures import GESTURE_SCHEMAS, check_gesture_path, validate_gesture_request
283
+ schema = GESTURE_SCHEMAS[kind]
284
+ try:
285
+ gesture = validate_gesture_request(kind, {key: args[key] for key in schema["properties"] if key in args})
286
+ except ValueError as error:
287
+ raise ToolError(str(error)) from None
288
+ operation = kind.upper() + "_POINT"
289
+
290
+ def work(session, phases):
291
+ if session.mode != "direct":
292
+ raise ToolError(f"{kind}_point needs a direct device session. Finish this app check, then pass device.")
293
+ grounded, session.point_screen = session.point_screen, None
294
+ if grounded is None:
295
+ raise ToolError(f"Call screen with image=true before each {kind}, then name its visible points.")
296
+ snapshot = self._gesture_screen(session, grounded, gesture, call)
297
+ check_gesture_path(snapshot, gesture)
298
+ endpoints = [(gesture.start, gesture.start_label)]
299
+ if gesture.end is not None:
300
+ endpoints.append((gesture.end, gesture.end_label))
301
+ node = None
302
+ for (x, y), label in endpoints:
303
+ _, element = point_spot(snapshot, x, y)
304
+ node = SimpleNamespace(label=label, role="Point", placeholder="", identifier="", value="")
305
+ self._permit(session, "TAP_XY", node, element, snapshot, call)
306
+ node.label = gesture.label
307
+ self._permit(session, operation, node, None, snapshot, call, forced=True)
308
+ self._front(session)
309
+ snapshot = self._gesture_screen(session, grounded, gesture, call)
310
+ check_gesture_path(snapshot, gesture)
311
+ self._check_gesture_cancelled(session, call)
312
+ positions = (*gesture.start, *(gesture.end or ()))
313
+ options = {"duration": gesture.duration, "timeout": 15 if kind == "hold" else 10}
314
+ if kind == "drag":
315
+ options["hold_seconds"] = gesture.hold_seconds
316
+ send = getattr(session.run.driver, kind + "_point", None)
317
+ if not callable(send):
318
+ raise ToolError(f"This device can't {kind} by point.")
319
+ try:
320
+ send(*positions, snapshot, **options)
321
+ except Exception as error:
322
+ if type(error).__name__ == "DriverRejection" and getattr(error, "code", None) in error.PRE_DISPATCH:
323
+ raise
324
+ raise ToolError(f"The {kind} may have reached the device. It was not repeated. "
325
+ "Call screen to inspect the result before another gesture.") from None
326
+ phases("dispatch")
327
+ verb = {"drag": "Dragged", "hold": "Held", "flick": "Flicked"}[kind]
328
+ text = f"{verb} {quote(gesture.start_label)}"
329
+ if gesture.end is not None:
330
+ text += f" to {quote(gesture.end_label)}"
331
+ return {"op": operation, "text": text, "line": text + ".", "changed": None}
332
+ return self._act(args, call, work, operation)
333
+
334
+ def tool_type_text(self, args, call):
335
+ text, append, submit = args["text"], bool(args.get("append")), bool(args.get("submit"))
336
+
337
+ def work(session, phases):
338
+ driver = session.run.driver
339
+ tree, snapshot = self._current(session, phases)
340
+ node = self._resolve(session, tree, args)
341
+ role = (node.role or "").removeprefix("XCUIElementType")
342
+ element = element_at(snapshot, node.path)
343
+ if role == "SecureTextField":
344
+ from ..state import validate_input_text
345
+ validate_input_text(text)
346
+ # Typing a one-time code asks in the Mac app, with or without Return (permissions.rules.code_entry);
347
+ # Return that commits asks too.
348
+ if self._permit(session, "TYPE_SUBMIT" if submit else "TYPE", node, element, snapshot, call, text=text):
349
+ element, snapshot = self._rebind(session, element, snapshot, call)
350
+ receipt = None
351
+ if role == "SecureTextField":
352
+ # The fast read leaves secure fields out, so there is no Element: tap, then type through WDA.
353
+ x, y = hit_point(node, tree)
354
+ driver.tap_point(x, y, snapshot, timeout=10)
355
+ self._await_focus(driver)
356
+ self._check_action_cancelled(session, call)
357
+ try:
358
+ driver.call("POST", "/wda/keys", {"value": [text]}, timeout=30)
359
+ if submit:
360
+ self._check_action_cancelled(session, call)
361
+ driver.call("POST", "/wda/keys", {"value": ["\n"]}, timeout=5)
362
+ except Exception as error:
363
+ # A driver's error can echo the password it tried to send.
364
+ raise ToolError(f"Mobster couldn't finish typing into the secure field ({type(error).__name__}). "
365
+ "Call screen before another action.") from None
366
+ typed, shown = SECURE_MASK, SECURE_MASK
367
+ elif element is not None and element.editable:
368
+ receipt = driver.write_text(element, snapshot, text, append=append, submit=submit,
369
+ timeout=min(36.0, 16 + len(text) / 20))
370
+ typed, shown = text, quote(text)
371
+ else:
372
+ raise ToolError(f"{args.get('ref') or 'The target'} is a {role_name(node)}, not a text field. "
373
+ "Pass a ref or target for a field.")
374
+ phases("type")
375
+ after = None
376
+ if submit:
377
+ try:
378
+ after = driver.observe_ready(timeout=5)
379
+ except Exception:
380
+ pass
381
+ phases("settle")
382
+ ok = receipt is None or receipt.get("value") is None or bool(receipt.get("matches"))
383
+ into = quote(node_name(node))
384
+ line = f"Typed {shown} into {into}" + (" and pressed Return" if submit else "")
385
+ if not ok:
386
+ line += f"; the field reads {quote(receipt.get('value'), 80)}, not the text typed"
387
+ logged = SECURE_MASK if typed == SECURE_MASK else quote(text, LOGGED_TEXT)
388
+ return {"op": "TYPE_SUBMIT" if submit else "TYPE", "text": f"Typed {shown} into {into}",
389
+ "target": node_target(node), "typed": typed, "changed": True, "ok": ok, "line": line + ".",
390
+ "after": screen_fingerprint(after),
391
+ "log": f"Typed {logged} into {into}",
392
+ "extra": {"field_reads": None if not receipt or receipt.get("value") is None
393
+ else str(receipt["value"])[:200]}}
394
+ return self._act(args, call, work, "TYPE_SUBMIT" if submit else "TYPE")
395
+
396
+ @staticmethod
397
+ def _await_focus(driver, seconds=2.0):
398
+ end = time.monotonic() + seconds
399
+ while time.monotonic() < end:
400
+ try:
401
+ if getattr(driver.observe(timeout=5), "keyboard", "") in ("visible", "parked"):
402
+ return
403
+ except Exception:
404
+ break
405
+ time.sleep(.2)
406
+ raise ToolError("Mobster couldn't confirm the secure field's keyboard, so it didn't type. Call screen "
407
+ "before another action.")
408
+
409
+ def tool_swipe(self, args, call):
410
+ direction = args["direction"]
411
+ until = self._parse_assertion(args["until"]) if args.get("until") is not None else None
412
+ limit = int(args.get("max_swipes") or (8 if until is not None else 1))
413
+
414
+ def holds(tree):
415
+ return bool(self.api.evaluate_on([until], tree)[0].ok)
416
+
417
+ def work(session, phases):
418
+ driver = session.run.driver
419
+ tree, _snapshot = self._current(session, phases)
420
+ node = self._resolve(session, tree, args, required=False)
421
+ # A row's swipe archives or deletes in some apps (Gmail, Outlook): the same rules as a tap, for every
422
+ # swipe. After the first, the next row may have slid under the same spot, and nobody approved that one.
423
+ row_swipe = node is not None and direction in ("left", "right")
424
+ if row_swipe:
425
+ if self._permit(session, "SWIPE_" + direction.upper(), node, element_at(_snapshot, node.path),
426
+ _snapshot, call):
427
+ self._rebind(session, None, _snapshot, call)
428
+ held = holds(tree) if until is not None else None
429
+ swipes, moved, stuck, after, refused = 0, False, False, None, None
430
+ aim = node
431
+ deadline = time.monotonic() + SWIPE_SECONDS
432
+ while not held and swipes < limit and time.monotonic() < deadline:
433
+ snapshot = driver.observe(timeout=10)
434
+ self._check_action_cancelled(session, call)
435
+ if swipes and row_swipe and self._real_phone(session) is not None:
436
+ aim = {n.path: n for n in self.codes.read_tree(session).shown()}.get(aim.path)
437
+ if aim is None:
438
+ break # the element is gone: nothing approved is left to swipe
439
+ try:
440
+ if self._permit(session, "SWIPE_" + direction.upper(), aim, element_at(snapshot, aim.path),
441
+ snapshot, call):
442
+ self._rebind(session, None, snapshot, call)
443
+ except ToolError as error:
444
+ refused = str(error)
445
+ break
446
+ if aim is None:
447
+ driver.execute("SWIPE_" + direction.upper(), None, snapshot, timeout=10)
448
+ else:
449
+ self._swipe_in(driver, aim, tree, direction)
450
+ swipes += 1
451
+ after = self._settle(driver, snapshot)
452
+ if screen_changed(snapshot, after) is False:
453
+ stuck = True
454
+ break
455
+ moved = True
456
+ if until is not None:
457
+ tree = self.codes.read_tree(session)
458
+ held = holds(tree)
459
+ where = f" on {quote(node_name(node))}" if node is not None else ""
460
+ count = "once" if swipes == 1 else f"{swipes} times"
461
+ text = f"Swiped {direction}{where} {count}" if swipes else f"No swipe{where}"
462
+ if until is not None:
463
+ line = (f"{describe_assertion(args['until'])} already held; no swipe needed." if held and not swipes
464
+ else f"{text}; the expectation holds now." if held
465
+ else f"{text}; the expectation still doesn't hold"
466
+ + (" (the content stopped moving)." if stuck else "."))
467
+ else:
468
+ line = text + ("; the screen didn't move." if stuck and not moved else ".")
469
+ if refused:
470
+ line += f" It stopped before the next swipe: {refused}"
471
+ return {"op": "SWIPE_" + direction.upper(), "text": text,
472
+ "target": node_target(node) if node is not None else None, "changed": moved,
473
+ "ok": held if until is not None else moved, "line": line, "record": swipes > 0,
474
+ "after": screen_fingerprint(after),
475
+ "extra": {"swipes": swipes, "held": held}}
476
+ return self._act(args, call, work, "SWIPE_" + direction.upper())
477
+
478
+ @staticmethod
479
+ def _swipe_in(driver, node, tree, direction):
480
+ width, height = tree.size
481
+ x, y, w, h = node.rect
482
+ cx, cy = x + w / 2, y + h / 2
483
+ dx, dy = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0)}[direction]
484
+ x1, y1 = cx - dx * w * .3, cy - dy * h * .3
485
+ x2, y2 = cx + dx * w * .3, cy + dy * h * .3
486
+ clamp = lambda value, top: round(min(top - 1, max(1, value))) # noqa: E731
487
+ driver.call("POST", "/actions", {"actions": [{
488
+ "type": "pointer", "id": "finger", "parameters": {"pointerType": "touch"},
489
+ "actions": [{"type": "pointerMove", "duration": 0, "x": clamp(x1, width), "y": clamp(y1, height)},
490
+ {"type": "pointerDown"},
491
+ {"type": "pointerMove", "duration": 250, "x": clamp(x2, width), "y": clamp(y2, height)},
492
+ {"type": "pointerUp"}]}]}, timeout=10)
493
+
494
+ @staticmethod
495
+ def _alert_button(requested, buttons):
496
+ # iOS 26.4 names the notification prompt's button "Don’t Allow", with U+2019.
497
+ wanted = normalize(requested)
498
+ exact = [b for b in buttons if normalize(b) == wanted]
499
+ if exact:
500
+ return exact[0]
501
+ loose = [b for b in buttons if normalize(b).casefold() == wanted.casefold()]
502
+ return loose[0] if len(loose) == 1 else None
503
+
504
+ def tool_alert(self, args, call):
505
+ action, requested = args["action"], args.get("button")
506
+
507
+ def work(session, phases):
508
+ driver = session.run.driver
509
+ alert = self._alert(driver)
510
+ if alert is None:
511
+ raise ToolError("No alert is showing. Call screen to see the current screen.")
512
+ button = requested
513
+ if requested and alert["buttons"]:
514
+ button = self._alert_button(requested, alert["buttons"])
515
+ if button is None:
516
+ raise ToolError(f"The alert has no button {quote(requested)}. Its buttons: "
517
+ + ", ".join(quote(b) for b in alert["buttons"]) + ".")
518
+ self._refuse_grant(session, alert, action, button)
519
+ self._permit_alert(session, alert, action, button, call)
520
+ snapshot = driver.observe(timeout=10)
521
+ self._check_action_cancelled(session, call)
522
+ # WebDriverAgent finds the button by its real name, curly apostrophe and all.
523
+ driver.call("POST", f"/alert/{action}", {"name": button} if button else {}, timeout=10)
524
+ after = self._settle(driver, snapshot)
525
+ changed = screen_changed(snapshot, after)
526
+ verb = "Accepted" if action == "accept" else "Dismissed"
527
+ text = f"{verb} the alert {quote(alert['text'], 80)}" + (f" with {quote(button)}" if button else "")
528
+ return {"op": "ALERT", "text": text, "changed": changed, "line": text + ".",
529
+ "after": screen_fingerprint(after),
530
+ "target": {"id": "", "label": button or alert["text"][:120], "role": "Alert"}}
531
+ return self._act(args, call, work, "ALERT")
532
+
533
+ def tool_open_url(self, args, call):
534
+ from ..deeplinks import LinkError, decide
535
+ from .direct import url_app
536
+ url = args.get("url")
537
+ if not url:
538
+ return self._deep_links(args)
539
+ try:
540
+ # The one rule for links (deeplinks.decide). An outside agent never uses "Don't ask again": a link that
541
+ # asks asks every time, on the card in the Mac app.
542
+ decision = decide(url, None)
543
+ except LinkError as error:
544
+ raise ToolError(str(error)) from None
545
+
546
+ def work(session, phases):
547
+ run, driver = session.run, session.run.driver
548
+ record = self._real_phone(session) if self._on_phone(session) else None
549
+ if record is not None:
550
+ target = url_app(url)
551
+ if target:
552
+ self._permit_app(session, target)
553
+ if decision.action == "ask":
554
+ from ..permissions import outside
555
+ try:
556
+ outside.check_link(url, decision.title, decision.app, decision.app_name,
557
+ device=record.get("id"), client_name=self.client_name())
558
+ except outside.Refused as refused:
559
+ raise ToolError(str(refused).replace("Nothing was done.", "Nothing was opened.")) from None
560
+ self._check_action_cancelled(session, call)
561
+ if target:
562
+ self._permit_app(session, target)
563
+ elif decision.action == "ask" and self._on_phone(session):
564
+ raise ToolError(f"Mobster asks before opening {quote(url, 80)}, and nobody here can answer, so it "
565
+ "didn't open it on the iPhone. Ask the user to open it.")
566
+ snapshot = driver.observe(timeout=10)
567
+ self._check_action_cancelled(session, call)
568
+ opened = run.open_url(url)
569
+ after = self._settle(driver, snapshot, timeout=4, wait_seconds=1.5)
570
+ changed = screen_changed(snapshot, after)
571
+ text = f"Opened {quote(url, 120)}"
572
+ app_link = opened.get("url") if isinstance(opened, dict) else None
573
+ if isinstance(app_link, str) and app_link != url:
574
+ text += (f" as {quote(app_link, 120)}, the app link for that screen (the web address opens the "
575
+ "home feed)")
576
+ return {"op": "OPEN_URL", "text": text, "changed": changed, "line": text + changed_words(changed),
577
+ "after": screen_fingerprint(after)}
578
+ return self._act(args, call, work, "OPEN_URL")
579
+
580
+ def tool_expo_preview(self, args, call):
581
+ from . import expo_tools
582
+ return expo_tools.preview(self, args, call)
583
+
584
+ def tool_expo_reload(self, args, call):
585
+ from . import expo_tools
586
+ return expo_tools.reload(self, args, call)
587
+
588
+ def _relaunch(self, args, call):
589
+ if not args.get("run_id") and args.get("device"):
590
+ raise ToolError("restart applies to a check's app. On a device you drive directly, pass bundle_id.")
591
+
592
+ def work(session, phases):
593
+ run, driver = session.run, session.run.driver
594
+ run.relaunch()
595
+ try:
596
+ after = driver.observe_ready(timeout=10)
597
+ except Exception:
598
+ after = None
599
+ name = getattr(getattr(run, "app", None), "name", "") or "the app"
600
+ text = f"Relaunched {name}, keeping its data"
601
+ return {"op": "RELAUNCH", "text": text, "changed": True, "line": text + ".",
602
+ "after": screen_fingerprint(after)}
603
+ return self._act(args, call, work, "RELAUNCH")
604
+
605
+ def tool_wait_for(self, args, call):
606
+ assertions = [self._parse_assertion(item) for item in args["expect"]]
607
+ asked = float(args.get("timeout_s", 10))
608
+ session = self._target_session(args)
609
+ started = time.monotonic()
610
+ with self._locked(session):
611
+ self._front(session)
612
+ # evaluate reads until its deadline, then its last read and the outline take their own time (3 to 4 s
613
+ # per read under load): the wait keeps EVALUATE_RESERVE of the call's budget for them.
614
+ timeout = min(asked, self._left(call, self.call_budget - self.evaluate_reserve))
615
+ try:
616
+ results = session.run.evaluate(assertions, timeout=timeout)
617
+ outline, alert = self._outline(session)
618
+ except ToolError:
619
+ raise
620
+ except Exception as error:
621
+ raise ToolError(self._action_error(error)) from None
622
+ seconds = time.monotonic() - started
623
+ held = all(result.ok for result in results)
624
+ head = (f"All {len(results)} hold ({seconds:.1f} s)." if held else
625
+ f"{sum(r.ok for r in results)} of {len(results)} hold after {seconds:.1f} s.")
626
+ shortened = timeout < asked
627
+ if shortened and not held: # when all hold, the cut changed nothing
628
+ head += (f" The wait was cut to {timeout:.1f} s of the {asked:g} s asked, so this call answers within "
629
+ f"{self.call_budget:.0f} s.")
630
+ lines = [head] + [f"{'✓' if r.ok else '✗'} {r.text}: {str(r.observed)[:200]}" for r in results]
631
+ lines += list(dict.fromkeys(hint for hint in (navbar_hint(item) for item, r in zip(args["expect"], results)
632
+ if not r.ok) if hint))
633
+ lines.append("This never changes the verdict: verify_finish judges the expectations from verify_start.")
634
+ structured = {"held": held, "seconds": round(seconds, 2), "timeout_s": round(timeout, 2),
635
+ "shortened": shortened,
636
+ "results": [{"ok": bool(r.ok), "text": r.text, "observed": r.observed} for r in results],
637
+ "screen": outline.structured(alert)}
638
+ return ToolResult(cap_text("\n".join(lines) + "\n" + outline.render(alert)), jsonable(structured))