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,130 @@
1
+ """Audit each XML source read a verification driver actually makes, without another WDA request."""
2
+
3
+ import threading
4
+ from urllib.parse import parse_qsl, urlencode, urlsplit
5
+
6
+ from .tree import parse_tree
7
+
8
+
9
+ def _xml_source(path):
10
+ if not isinstance(path, str):
11
+ return None
12
+ parts = urlsplit(path)
13
+ if parts.scheme or parts.netloc or parts.path != "/source":
14
+ return None
15
+ query = parse_qsl(parts.query, keep_blank_values=True)
16
+ if [value for key, value in query if key == "format"] != ["xml"]:
17
+ return None
18
+ # Keep the caller's other source options; only attributes omitted for fast grounding are restored.
19
+ query = [(key, value) for key, value in query if key != "excluded_attributes"]
20
+ query.append(("excluded_attributes", "accessible,index"))
21
+ return parts.path + "?" + urlencode(query, safe=",")
22
+
23
+
24
+ class SourceObservation:
25
+ """A private, run-owned adapter around a driver's bound call/observe methods."""
26
+
27
+ def __init__(self, run):
28
+ self.run = run
29
+ self.driver = None
30
+ self._originals = {}
31
+ self._wrappers = {}
32
+ self._local = threading.local()
33
+ self._active = False
34
+
35
+ @property
36
+ def active(self):
37
+ return self._active
38
+
39
+ def attach(self, driver):
40
+ if self.driver is driver and self._active:
41
+ return True
42
+ if getattr(driver, "_verify_source_owner", None) is not None or not callable(getattr(driver, "call", None)):
43
+ self.run._incomplete_screen("The driver could not expose its accessibility source reads")
44
+ return False
45
+ try:
46
+ owned = vars(driver)
47
+ except TypeError:
48
+ # Restoring inherited methods requires knowing which attributes the instance owned before wrapping.
49
+ self.run._incomplete_screen("The driver could not expose its accessibility source reads")
50
+ return False
51
+ originals = {"call": ("call" in owned, driver.call)}
52
+ observe = getattr(driver, "observe", None)
53
+ if callable(observe):
54
+ originals["observe"] = ("observe" in owned, observe)
55
+
56
+ def call(method, path, *args, **kwargs):
57
+ source = _xml_source(path) if isinstance(method, str) and method.upper() == "GET" else None
58
+ if source is None:
59
+ return originals["call"][1](method, path, *args, **kwargs)
60
+ release = self.run._reserve_source_read()
61
+ try:
62
+ xml = originals["call"][1](method, source, *args, **kwargs)
63
+ if release is not None and self._active:
64
+ try:
65
+ tree = parse_tree(xml)
66
+ self.run._observe_screen(None, tree)
67
+ self._local.sources = getattr(self._local, "sources", 0) + 1
68
+ except Exception as error:
69
+ self.run.log(f"source audit skipped: {type(error).__name__}")
70
+ self.run._incomplete_screen("An accessibility source could not be audited")
71
+ return xml
72
+ except BaseException:
73
+ if release is not None and self._active:
74
+ self.run._incomplete_screen("An accessibility source request failed")
75
+ raise
76
+ finally:
77
+ if release is not None:
78
+ release()
79
+
80
+ wrappers = {"call": call}
81
+ if "observe" in originals:
82
+ def observed(*args, **kwargs):
83
+ before = getattr(self._local, "sources", 0)
84
+ release = self.run._reserve_source_read()
85
+ try:
86
+ snapshot = originals["observe"][1](*args, **kwargs)
87
+ if release is not None and self._active:
88
+ from ..state import PIXEL_SOURCES
89
+ if (getattr(snapshot, "source", None) in PIXEL_SOURCES
90
+ or getattr(self._local, "sources", 0) == before):
91
+ self.run._incomplete_screen("A screen read had no complete accessibility source")
92
+ return snapshot
93
+ except BaseException:
94
+ if release is not None and self._active:
95
+ self.run._incomplete_screen("A screen observation failed")
96
+ raise
97
+ finally:
98
+ if release is not None:
99
+ release()
100
+
101
+ wrappers["observe"] = observed
102
+ try:
103
+ for name, wrapper in wrappers.items():
104
+ setattr(driver, name, wrapper)
105
+ driver._verify_source_owner = self
106
+ except (AttributeError, TypeError):
107
+ for name, (own, original) in originals.items():
108
+ if getattr(driver, name, None) is wrappers.get(name):
109
+ if own:
110
+ setattr(driver, name, original)
111
+ else:
112
+ delattr(driver, name)
113
+ self.run._incomplete_screen("The driver could not expose its accessibility source reads")
114
+ return False
115
+ self.driver, self._originals, self._wrappers, self._active = driver, originals, wrappers, True
116
+ return True
117
+
118
+ def detach(self, driver):
119
+ if self.driver is not driver:
120
+ return
121
+ self._active = False
122
+ if getattr(driver, "_verify_source_owner", None) is self:
123
+ for name, (own, original) in self._originals.items():
124
+ if getattr(driver, name, None) is self._wrappers[name]:
125
+ if own:
126
+ setattr(driver, name, original)
127
+ else:
128
+ delattr(driver, name)
129
+ delattr(driver, "_verify_source_owner")
130
+ self.driver = None
@@ -0,0 +1,7 @@
1
+ """Check steps for the system surfaces an app lives on outside its own window, driven and read on a simulator:
2
+ App Intents, Home Screen widgets, Live Activities, Control Center, App Clips, Spotlight and the share sheet.
3
+
4
+ Off unless ``MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES=1`` (docs/checks.mdx). A surface is driven from outside the app with
5
+ WebDriverAgent and read from the accessibility tree like any screen, so the assertions are the check language's own.
6
+ ``spec`` is the check file's block, ``play`` runs it after the flow. They are imported by name because the check
7
+ loader needs the first before the run machinery the second leans on exists."""
@@ -0,0 +1,106 @@
1
+ """Run a check's surfaces on the simulator the run holds, after its flow."""
2
+
3
+ from ..smart import is_real_device
4
+ from ..verdict import CouldntRun
5
+ from . import spec
6
+ from .stage import Stage, SurfaceMiss
7
+ from .surfaces import DRIVERS
8
+
9
+ NOT_ON_SIMULATOR = "Not on a simulator: "
10
+
11
+
12
+ class Context:
13
+ """What a driver needs from the simulator manager, with its errors as a check failing rather than the run."""
14
+
15
+ def __init__(self, run):
16
+ self.run = run
17
+
18
+ def _try(self, what, *args):
19
+ try:
20
+ return what(self.run.target, *args)
21
+ except Exception as error:
22
+ raise SurfaceMiss(str(error) or type(error).__name__) from None
23
+
24
+ def open_url(self, url):
25
+ self._try(self.run.manager.open_url, url)
26
+
27
+ def launch(self, bundle, env):
28
+ self._try(self.run.manager.launch, bundle, (), env)
29
+
30
+ def install(self, path):
31
+ self._try(self.run.manager.install, path)
32
+
33
+
34
+ def refuse_unsupported(run):
35
+ """The run can't drive these, so it says so before it touches the simulator."""
36
+ if is_real_device(run.manager):
37
+ raise CouldntRun("environment", NOT_ON_SIMULATOR + "system surfaces are driven on a simulator. They press, "
38
+ "swipe and lock the screen, which Mobster never does on your iPhone.",
39
+ "Run the check with --sim.")
40
+ for surface in run.check.surfaces:
41
+ via = surface.params.get("via", "url")
42
+ if surface.kind == "app_clip" and via != "url":
43
+ raise CouldntRun("environment", NOT_ON_SIMULATOR + f"an App Clip card opened by {via} has no simulator "
44
+ "path. Only its invocation URL runs.",
45
+ "Use via: url, or test the card on a phone by hand.")
46
+
47
+
48
+ def play(run, stage, ctx, surface):
49
+ """One surface: drive it, tap its ``pick``, and read: (assertion results, the sentence that failed, its frame)."""
50
+ results, miss, frames = [], None, []
51
+ stage.shot = lambda: frames.append(run.frame(f"surface-{surface.kind}-{len(frames) + 1}"))
52
+ try:
53
+ stage.follow_the_front()
54
+ results, tree = DRIVERS[surface.kind](stage, surface, ctx)
55
+ if surface.pick is not None:
56
+ stage.tap_node(stage.wait_for(None, f"{surface.pick.render()} to pick", selector=surface.pick), "the pick")
57
+ after, tree = stage.settle(surface.then)
58
+ results = [*results, *after]
59
+ elif surface.kind == "share_sheet":
60
+ stage.tap(tree.size[0] / 2, tree.size[1] * .08, "outside the sheet to close it")
61
+ except SurfaceMiss as error:
62
+ miss = str(error)
63
+ # A CouldntRun (an action with no answer) skips this: nothing more is sent to a simulator in an unknown state.
64
+ stage.activate(run.app.bundle_id)
65
+ return results, miss, next((frame for frame in reversed(frames) if frame), None)
66
+
67
+
68
+ def finding(surface, text, frame, run):
69
+ frame = run._relative(frame)
70
+ return {"id": f"{surface.label()}", "guideline": None, "level": "fail", "frame": frame,
71
+ "message": f"{surface.label()}: {text}", "evidence": frame or "the surface"}
72
+
73
+
74
+ def drive(run):
75
+ """Every surface in order. A surface that fails adds a fail finding and the next one still runs; a refused
76
+ environment or an action with no answer ends the run."""
77
+ surfaces = run.check.surfaces
78
+ if not surfaces:
79
+ return
80
+ if not spec.enabled():
81
+ raise CouldntRun("usage", f"The check has surfaces, which are off. Set {spec.FLAG}=1.")
82
+ refuse_unsupported(run)
83
+ from ..runner import _now # the run's own clock, so a test that sets it sets this one too
84
+ stage = Stage(run.driver, log=run.log, sleep=run._sleep, clock=_now)
85
+ ctx = Context(run)
86
+ run.surface_reports = []
87
+ with run.lock:
88
+ for surface in surfaces:
89
+ run._check_stop()
90
+ results, miss, frame = play(run, stage, ctx, surface)
91
+ failed = [item for item in results if not item.ok]
92
+ run.event({"event": "surface", "surface": surface.kind, "name": surface.label(),
93
+ "held": len(results) - len(failed), "failed": len(failed), "miss": miss})
94
+ run.log(f"surface {surface.label()}: {len(results) - len(failed)} of {len(results)} held"
95
+ + (f"; {miss}" if miss else ""))
96
+ if miss:
97
+ run.findings.append(finding(surface, miss, frame, run))
98
+ for item in failed:
99
+ run.findings.append(finding(surface, f"{item.text}: {item.observed}", frame, run))
100
+ run.surface_reports.append({"surface": surface.kind, "name": surface.label(), "ok": not (miss or failed)})
101
+
102
+
103
+ def note(run):
104
+ """The verdict's tail for a run that drove surfaces: how many held."""
105
+ reports = getattr(run, "surface_reports", ())
106
+ return f"; {sum(item['ok'] for item in reports)} of {len(reports)} system surfaces held" if reports else ""
@@ -0,0 +1,139 @@
1
+ """The ``surfaces`` block of a check: which system surfaces to drive and what must show on each."""
2
+
3
+ import difflib
4
+ import os
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from ..assertions import CheckError, expand_assertion, parse_selector, reject_unknown_keys
9
+
10
+ FLAG = "MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES"
11
+ LIMIT = 10
12
+ WHERE = ("lock_screen", "dynamic_island")
13
+ # App Clip invocations that need a phone, a tag or a place; only the URL has a simulator path.
14
+ VIA_UNSUPPORTED = ("nfc", "qr", "banner", "maps")
15
+ VIA = ("url", *VIA_UNSUPPORTED)
16
+
17
+ COMMON = ("surface", "name", "expect")
18
+ # kind -> (keys it takes besides COMMON, keys it needs)
19
+ KINDS = {
20
+ "intent": (("shortcut", "siri", "input"), ()),
21
+ "widget": (("add",), ()),
22
+ "live_activity": (("where", "expanded"), ()),
23
+ "control_center": (("pick", "then"), ()),
24
+ "app_clip": (("bundle", "url", "via", "path"), ("bundle", "url")),
25
+ "spotlight": (("query", "pick", "then"), ("query",)),
26
+ "share_sheet": (("open", "pick", "then"), ("open",)),
27
+ }
28
+
29
+
30
+ def enabled():
31
+ """Whether system surfaces are switched on (``MOBSTER_EXPERIMENTAL_SYSTEM_SURFACES=1``)."""
32
+ return os.environ.get(FLAG, "").strip().casefold() in ("1", "true", "yes", "on")
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class Surface:
37
+ kind: str
38
+ name: str
39
+ params: dict # the validated keys as written, minus expect and then
40
+ expect: tuple = () # of assertions: what shows once the surface is up
41
+ then: tuple = () # of assertions: what shows after `pick`
42
+ pick: Optional[object] = None # assertions.Selector: the element to tap
43
+ open: Optional[object] = None # assertions.Selector: the in-app control that presents the surface
44
+
45
+ def label(self):
46
+ return self.name or self.kind.replace("_", " ")
47
+
48
+
49
+ def _text(value, where):
50
+ if isinstance(value, bool) or not isinstance(value, str) or not value.strip():
51
+ raise CheckError(f"{where} must be text")
52
+ if len(value) > 500:
53
+ raise CheckError(f"{where} is longer than 500 characters")
54
+ return value.strip()
55
+
56
+
57
+ def _selector(value, where):
58
+ """A label as text, or a selector object."""
59
+ return parse_selector({"label": value} if isinstance(value, str) else value, where)
60
+
61
+
62
+ def _assertions(value, where):
63
+ if value is None:
64
+ return ()
65
+ if not isinstance(value, list):
66
+ raise CheckError(f"{where} must be a list of assertions, such as [{{text: 12 day streak}}]")
67
+ out = []
68
+ for index, item in enumerate(value):
69
+ out.extend(expand_assertion(item, f"{where}[{index}]"))
70
+ return tuple(out)
71
+
72
+
73
+ def _one(index, item, base_dir):
74
+ where = f"surfaces[{index}]"
75
+ if not isinstance(item, dict):
76
+ raise CheckError(f"{where} must be an object such as {{surface: widget, expect: [{{text: Streak}}]}}")
77
+ kind = item.get("surface")
78
+ if kind not in KINDS:
79
+ close = difflib.get_close_matches(str(kind), list(KINDS), n=1)
80
+ raise CheckError(f"{where}.surface {kind!r} isn't a system surface; "
81
+ + (f"did you mean {close[0]}? " if close else "") + f"Use {', '.join(KINDS)}.")
82
+ keys, required = KINDS[kind]
83
+ reject_unknown_keys(item, (*COMMON, *keys), where)
84
+ for key in required:
85
+ if item.get(key) is None:
86
+ raise CheckError(f"{where}.{key} is required for a {kind.replace('_', ' ')}")
87
+ params = {key: item[key] for key in keys if item.get(key) is not None}
88
+ if kind == "intent" and ("shortcut" in params) == ("siri" in params):
89
+ raise CheckError(f"{where} needs exactly one of shortcut (a shortcut's name) and siri (a phrase to say)")
90
+ if "input" in params and "shortcut" not in params:
91
+ raise CheckError(f"{where}.input goes with shortcut")
92
+ if "where" in params and params["where"] not in WHERE:
93
+ raise CheckError(f"{where}.where must be one of {', '.join(WHERE)}")
94
+ if "expanded" in params and not isinstance(params["expanded"], bool):
95
+ raise CheckError(f"{where}.expanded must be true or false")
96
+ if "via" in params and params["via"] not in VIA:
97
+ raise CheckError(f"{where}.via must be one of {', '.join(VIA)}")
98
+ for key in ("shortcut", "siri", "input", "add", "query", "bundle", "url", "path"):
99
+ if key in params:
100
+ params[key] = _text(params[key], f"{where}.{key}")
101
+ if "path" in params and base_dir is not None and not os.path.isabs(params["path"]):
102
+ params["path"] = os.path.normpath(os.path.join(base_dir, params["path"]))
103
+ pick = _selector(params["pick"], f"{where}.pick") if "pick" in params else None
104
+ opens = _selector(params["open"], f"{where}.open") if "open" in params else None
105
+ then = _assertions(item.get("then"), f"{where}.then")
106
+ if then and pick is None:
107
+ raise CheckError(f"{where}.then is read after pick, and there is no pick")
108
+ expect = _assertions(item.get("expect"), f"{where}.expect")
109
+ if not (expect or then):
110
+ raise CheckError(f"{where} checks nothing. Add expect: what must show on the {kind.replace('_', ' ')}")
111
+ name = item.get("name")
112
+ return Surface(kind, _text(name, f"{where}.name") if name is not None else "", params,
113
+ expect, then, pick, opens)
114
+
115
+
116
+ def parse(value, base_dir=None):
117
+ """The ``surfaces`` block as a tuple of Surface: () when absent. Raises CheckError; off unless the flag is on."""
118
+ if value is None:
119
+ return ()
120
+ if not enabled():
121
+ raise CheckError(f"surfaces is experimental and off. Set {FLAG}=1 to use it.")
122
+ if not isinstance(value, list) or not value:
123
+ raise CheckError("surfaces must list at least one surface, such as [{surface: widget, expect: [...]}]")
124
+ if len(value) > LIMIT:
125
+ raise CheckError(f"surfaces has more than {LIMIT} entries")
126
+ return tuple(_one(index, item, base_dir) for index, item in enumerate(value))
127
+
128
+
129
+ def to_dict(surfaces):
130
+ """The file shape of ``surfaces``, as ``parse`` reads it."""
131
+ out = []
132
+ for surface in surfaces:
133
+ item = {"surface": surface.kind, **({"name": surface.name} if surface.name else {}), **surface.params}
134
+ if surface.expect:
135
+ item["expect"] = [assertion.to_dict() for assertion in surface.expect]
136
+ if surface.then:
137
+ item["then"] = [assertion.to_dict() for assertion in surface.then]
138
+ out.append(item)
139
+ return out
@@ -0,0 +1,138 @@
1
+ """The device side of a surface: WebDriverAgent gestures and reads on a simulator, with nothing retried after an
2
+ unknown outcome."""
3
+
4
+ import time
5
+
6
+ from ..assertions import evaluate_on, folded
7
+ from ..tree import read_tree
8
+ from ..verdict import CouldntRun
9
+
10
+ # WDA reads the app it was told is active while that app runs, even under Control Center or the Lock Screen, and the
11
+ # run names the app under test (drivers.py). A name no app has makes WDA read the app at its detection point, a
12
+ # system overlay included (measured 26 Oct 2026, iOS 26.4 simulator, WDA 16.12.10).
13
+ IN_FRONT = "auto"
14
+ READ_POLL = .5
15
+ TAP_MS = 40
16
+ HOLD_MS = 1200
17
+ DRAG_MS = 350
18
+
19
+
20
+ class SurfaceMiss(Exception):
21
+ """The surface couldn't be reached or didn't show; the check fails with this sentence and goes on."""
22
+
23
+
24
+ class Stage:
25
+ """One simulator's screen, driven from outside the app under test. Every call is logged before it is sent."""
26
+
27
+ def __init__(self, driver, *, log, sleep=time.sleep, clock=time.monotonic):
28
+ self.driver, self._log, self.sleep, self.clock = driver, log, sleep, clock
29
+ self.size = (0.0, 0.0)
30
+ self.shot = None # called with no arguments when a settle ends: the frame of the surface as it stood
31
+
32
+ def act(self, what, method, path, body=None, timeout=20):
33
+ """One action. An answer that never came is an unknown outcome: the run stops and nothing is sent again."""
34
+ self._log(f"surface: {what}")
35
+ try:
36
+ return self.driver.call(method, path, body, timeout)
37
+ except Exception as error:
38
+ raise CouldntRun("unknown_outcome",
39
+ f"WebDriverAgent didn't confirm whether this happened: {what} ({type(error).__name__}).",
40
+ "Look at the simulator before starting another run; Mobster didn't send it again.") \
41
+ from None
42
+
43
+ def follow_the_front(self):
44
+ """Reads come from whatever is in front until ``activate`` names an app again."""
45
+ self.act("read whatever is in front", "POST", "/appium/settings",
46
+ {"settings": {"defaultActiveApplication": IN_FRONT}})
47
+
48
+ def read(self):
49
+ """The accessibility tree of whatever is in front, the Home Screen and Lock Screen included."""
50
+ try:
51
+ tree = read_tree(self.driver, pause=self.sleep)
52
+ except Exception as error:
53
+ raise CouldntRun("wda", f"WebDriverAgent stopped answering while a surface was read "
54
+ f"({type(error).__name__}).", "Run `mobster sim doctor --fix`, then try again.") \
55
+ from None
56
+ self.size = tree.size
57
+ return tree
58
+
59
+ def settle(self, assertions, timeout=8):
60
+ """Read until every assertion holds or ``timeout`` passes: (results, tree) from the last read."""
61
+ deadline = self.clock() + timeout
62
+ while True:
63
+ tree = self.read()
64
+ results = evaluate_on(assertions, tree)
65
+ if all(result.ok for result in results) or self.clock() >= deadline:
66
+ if self.shot is not None:
67
+ self.shot()
68
+ return results, tree
69
+ self.sleep(READ_POLL)
70
+
71
+ def find(self, tree, *needles):
72
+ """The first shown node whose label or identifier contains one of ``needles``, ignoring case."""
73
+ wanted = [folded(needle) for needle in needles]
74
+ for node in tree.shown():
75
+ text = (folded(node.label), folded(node.identifier))
76
+ if any(needle and needle in part for needle in wanted for part in text):
77
+ return node
78
+ return None
79
+
80
+ def wait_for(self, needles, what, timeout=6, selector=None):
81
+ """The node for ``needles`` (or ``selector``) once it shows; SurfaceMiss when it never does."""
82
+ deadline = self.clock() + timeout
83
+ while True:
84
+ tree = self.read()
85
+ node = (next(iter(tree.select(selector)), None) if selector is not None
86
+ else self.find(tree, *([needles] if isinstance(needles, str) else needles)))
87
+ if node is not None:
88
+ return node
89
+ if self.clock() >= deadline:
90
+ raise SurfaceMiss(f"{what} didn't show; the screen had {self._seen(tree)}")
91
+ self.sleep(READ_POLL)
92
+
93
+ @staticmethod
94
+ def _seen(tree):
95
+ labels = [node.label for node in tree.shown() if node.label.strip()][:6]
96
+ return ", ".join(f'"{label[:40]}"' for label in labels) or "no labelled elements"
97
+
98
+ def tap_node(self, node, what):
99
+ x, y, w, h = node.rect
100
+ self.tap(x + w / 2, y + h / 2, what)
101
+
102
+ def tap(self, x, y, what):
103
+ self._pointer(f"tap {what}", [{"type": "pointerMove", "duration": 0, "x": round(x), "y": round(y)},
104
+ {"type": "pointerDown"}, {"type": "pause", "duration": TAP_MS},
105
+ {"type": "pointerUp"}])
106
+
107
+ def hold(self, x, y, what):
108
+ self._pointer(f"press and hold {what}", [{"type": "pointerMove", "duration": 0, "x": round(x), "y": round(y)},
109
+ {"type": "pointerDown"}, {"type": "pause", "duration": HOLD_MS},
110
+ {"type": "pointerUp"}])
111
+
112
+ def drag(self, start, end, what):
113
+ self._pointer(f"swipe {what}", [{"type": "pointerMove", "duration": 0, "x": round(start[0]),
114
+ "y": round(start[1])}, {"type": "pointerDown"},
115
+ {"type": "pointerMove", "duration": DRAG_MS, "x": round(end[0]),
116
+ "y": round(end[1])}, {"type": "pointerUp"}])
117
+
118
+ def _pointer(self, what, steps):
119
+ self.act(what, "POST", "/actions", {"actions": [{"type": "pointer", "id": "finger",
120
+ "parameters": {"pointerType": "touch"}, "actions": steps}]})
121
+
122
+ def home(self):
123
+ self.act("press Home", "POST", "/wda/pressButton", {"name": "home"})
124
+
125
+ def lock(self):
126
+ self.act("lock the simulator", "POST", "/wda/lock")
127
+
128
+ def unlock(self):
129
+ self.act("unlock the simulator", "POST", "/wda/unlock")
130
+
131
+ def type(self, text):
132
+ self.act("type into the search field", "POST", "/wda/keys", {"value": list(text)})
133
+
134
+ def siri(self, phrase):
135
+ self.act("ask Siri", "POST", "/wda/siri/activate", {"text": phrase})
136
+
137
+ def activate(self, bundle):
138
+ self.act(f"bring {bundle} forward", "POST", "/wda/apps/activate", {"bundleId": bundle})
@@ -0,0 +1,121 @@
1
+ """One driver per system surface. Each leaves the surface up, reads it, and returns (results, tree); ``pick`` and
2
+ ``then`` are handled once, by ``play``.
3
+
4
+ The labels these drivers look for (``Edit``, ``Add Widget``, ``Done``) are the strings iOS 26 shows in English; a
5
+ simulator in another language needs the same strings in its own."""
6
+
7
+ from urllib.parse import quote, urlencode
8
+
9
+ from ..assertions import parse_selector
10
+ from .stage import SurfaceMiss
11
+
12
+ ICON_ROLES = frozenset({"Icon", "Button", "Cell"})
13
+ SEARCH_FIELD = parse_selector({"role": "field"})
14
+ # A press and hold on the Home Screen opens edit mode with an Edit button (iOS 26.4) or a menu with Edit Home Screen;
15
+ # Edit's menu and the widget's page both say Add Widget. Selectors, so an icon named like them is never tapped.
16
+ EDIT_HOME = parse_selector({"label": "/^Edit( Home Screen)?$/"})
17
+ ADD_WIDGET = parse_selector({"label": "/^\\W*Add Widget$/", "role": "button"})
18
+ # iPhone Control Center opens from the top right; Spotlight from a swipe down on the Home Screen's middle.
19
+ CONTROL_CENTER_X = 25
20
+ HOME_ROWS = (.25, .75, .03)
21
+
22
+
23
+ def empty_point(tree):
24
+ """A point on the Home Screen that holds no icon or button: where a press and hold opens its edit menu."""
25
+ width, height = tree.size
26
+ covered = [node.rect for node in tree.shown() if node.role in ICON_ROLES]
27
+ step = HOME_ROWS[2]
28
+ row = HOME_ROWS[0]
29
+ while row <= HOME_ROWS[1]:
30
+ for column in (.5, .15, .85):
31
+ x, y = width * column, height * row
32
+ if not any(rx <= x <= rx + rw and ry <= y <= ry + rh for rx, ry, rw, rh in covered):
33
+ return x, y
34
+ row += step
35
+ return None
36
+
37
+
38
+ def intent(stage, surface, ctx):
39
+ """A shortcut that runs the app's App Intent (``shortcuts://run-shortcut``), or a phrase said to Siri."""
40
+ params = surface.params
41
+ if "siri" in params:
42
+ stage.siri(params["siri"])
43
+ else:
44
+ given = {"input": "text", "text": params["input"]} if "input" in params else {}
45
+ query = {"name": params["shortcut"], **given}
46
+ # %20, not +: Shortcuts reads a + in the name as a +, and finds no shortcut (iOS 26.4 simulator, 26 Oct 2026)
47
+ ctx.open_url("shortcuts://run-shortcut?" + urlencode(query, quote_via=quote))
48
+ return stage.settle(surface.expect)
49
+
50
+
51
+ def widget(stage, surface, ctx):
52
+ """The Home Screen, after adding the app's widget from the gallery when ``add`` names the app."""
53
+ stage.home()
54
+ if "add" in surface.params:
55
+ _add_widget(stage, surface.params["add"])
56
+ return stage.settle(surface.expect)
57
+
58
+
59
+ def _add_widget(stage, app):
60
+ point = empty_point(stage.read())
61
+ if point is None:
62
+ raise SurfaceMiss("the Home Screen has no empty spot to press and hold; clear a row of icons")
63
+ stage.hold(*point, "an empty spot on the Home Screen")
64
+ stage.tap_node(stage.wait_for(None, "the Home Screen's Edit button", selector=EDIT_HOME), "Edit")
65
+ stage.tap_node(stage.wait_for(None, "Add Widget in the Edit menu", selector=ADD_WIDGET), "Add Widget")
66
+ stage.tap_node(stage.wait_for(None, "the widget search field", selector=SEARCH_FIELD), "the search field")
67
+ stage.type(app)
68
+ listed = parse_selector({"label": app, "role": "cell"}) # not the app's own icon behind the gallery
69
+ stage.tap_node(stage.wait_for(None, f"{app} in the widget gallery", selector=listed), app)
70
+ stage.tap_node(stage.wait_for(None, f"the Add Widget button for {app}", selector=ADD_WIDGET), "Add Widget")
71
+ stage.tap_node(stage.wait_for("Done", "the Done button"), "Done")
72
+
73
+
74
+ def live_activity(stage, surface, ctx):
75
+ """A Live Activity the app started, on the Lock Screen or in the Dynamic Island."""
76
+ if surface.params.get("where", "dynamic_island") == "lock_screen":
77
+ stage.lock()
78
+ try:
79
+ return stage.settle(surface.expect)
80
+ finally:
81
+ stage.unlock()
82
+ stage.home()
83
+ if surface.params.get("expanded"):
84
+ stage.hold(stage.read().size[0] / 2, 20, "the Dynamic Island")
85
+ return stage.settle(surface.expect)
86
+
87
+
88
+ def control_center(stage, surface, ctx):
89
+ width, height = stage.read().size
90
+ stage.drag((width - CONTROL_CENTER_X, 3), (width - CONTROL_CENTER_X, height * .6), "down into Control Center")
91
+ return stage.settle(surface.expect)
92
+
93
+
94
+ def app_clip(stage, surface, ctx):
95
+ """The App Clip, launched the way Xcode's scheme does: with its invocation URL in ``_XCAppClipURL``."""
96
+ params = surface.params
97
+ if "path" in params:
98
+ ctx.install(params["path"])
99
+ ctx.launch(params["bundle"], {"_XCAppClipURL": params["url"]})
100
+ stage.activate(params["bundle"])
101
+ return stage.settle(surface.expect)
102
+
103
+
104
+ def spotlight(stage, surface, ctx):
105
+ stage.home()
106
+ width, height = stage.read().size
107
+ stage.drag((width / 2, height * .35), (width / 2, height * .75), "down on the Home Screen")
108
+ stage.tap_node(stage.wait_for(None, "the Spotlight search field", selector=SEARCH_FIELD), "the search field")
109
+ stage.type(surface.params["query"])
110
+ return stage.settle(surface.expect)
111
+
112
+
113
+ def share_sheet(stage, surface, ctx):
114
+ """The app's own control opens the share sheet; ``expect`` reads the sheet."""
115
+ stage.tap_node(stage.wait_for(None, f"the control that opens the share sheet ({surface.open.render()})",
116
+ selector=surface.open), "the share control")
117
+ return stage.settle(surface.expect)
118
+
119
+
120
+ DRIVERS = {"intent": intent, "widget": widget, "live_activity": live_activity, "control_center": control_center,
121
+ "app_clip": app_clip, "spotlight": spotlight, "share_sheet": share_sheet}