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,128 @@
1
+ """A check's conditions on Android: the ones with an Android equivalent are set, the rest make the check couldn't-run
2
+ (it never runs in the wrong state)."""
3
+
4
+ import re
5
+
6
+ from ..verify.conditions import ConditionError
7
+ from .adb import AdbError
8
+
9
+ # iOS privacy service -> Android runtime permissions.
10
+ PERMISSIONS = {
11
+ "calendar": ("android.permission.READ_CALENDAR", "android.permission.WRITE_CALENDAR"),
12
+ "contacts": ("android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"),
13
+ "contacts-limited": ("android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"),
14
+ "location": ("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"),
15
+ "location-always": ("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION",
16
+ "android.permission.ACCESS_BACKGROUND_LOCATION"),
17
+ "microphone": ("android.permission.RECORD_AUDIO",),
18
+ "photos": ("android.permission.READ_MEDIA_IMAGES", "android.permission.READ_MEDIA_VIDEO"),
19
+ }
20
+ NOT_ON_ANDROID = {
21
+ "push": "conditions.push sends an APNs payload, which Android has no equivalent of here",
22
+ "content_size": "conditions.content_size has no Android mapping yet",
23
+ "increase_contrast": "conditions.increase_contrast has no Android mapping yet",
24
+ "route": "conditions.route has no Android mapping yet",
25
+ "media": "conditions.media has no Android mapping yet",
26
+ "status_bar": "conditions.status_bar has no Android mapping yet",
27
+ "network": "conditions.network shapes traffic through an iOS simulator's proxy, which Android has no equivalent of "
28
+ "here",
29
+ }
30
+ SUPPORTED = ("appearance", "locale", "location", "permissions") # what _apply sets; any other key is refused
31
+ FIX = "Remove that condition, or run the check on an iOS simulator with --sim."
32
+
33
+
34
+ class AndroidConditions:
35
+ """Same shape as verify.conditions.Applier: before_launch, after_launch, launch_args, launch_env, restore, report,
36
+ and ``net``, which is always None: network conditions and request stubs are an iOS simulator's (netrun.py)."""
37
+
38
+ net = None
39
+
40
+ def __init__(self, spec, adb, *, bundle, emulator, actions=None):
41
+ self.spec, self.adb, self.bundle, self.emulator = dict(spec), adb, bundle, emulator
42
+ self.actions = actions # the run's action journal (verify/actions.py): every change is journaled first
43
+ self.applied, self.skipped, self._undo = [], [], []
44
+
45
+ def _change(self, operation, call, *args, **kwargs):
46
+ """One device change, journaled before it is sent; an answer that never came stops the run, never retried."""
47
+ if self.actions is None:
48
+ raise ConditionError("Device condition changes need an action journal.", FIX)
49
+ with self.actions.action(operation):
50
+ return call(*args, **kwargs)
51
+
52
+ def launch_args(self):
53
+ return ()
54
+
55
+ def launch_env(self):
56
+ return {}
57
+
58
+ def before_launch(self):
59
+ for key in self.spec: # a key added for iOS later is refused here too, never skipped unseen
60
+ if key not in SUPPORTED:
61
+ why = NOT_ON_ANDROID.get(key, f"conditions.{key} has no Android mapping yet")
62
+ raise ConditionError(f"{why}, so this check can't run on Android.", FIX)
63
+ try:
64
+ self._apply()
65
+ except AdbError as error:
66
+ raise ConditionError(f"Mobster couldn't set a condition on the Android device: {error}",
67
+ "Run `mobster sim doctor`, then try again. Remove the condition to run the check "
68
+ "without it.") from None
69
+
70
+ def _apply(self):
71
+ spec = self.spec
72
+ if "appearance" in spec:
73
+ text = self.adb.shell("cmd", "uimode", "night", timeout=10, check=False).text
74
+ old = re.search(r"Night mode: (yes|no)", text)
75
+ self._change("CONDITION_SET", self.adb.shell, "cmd", "uimode", "night",
76
+ "yes" if spec["appearance"] == "dark" else "no", timeout=10)
77
+ if old:
78
+ self._undo.append(("cmd", "uimode", "night", old.group(1)))
79
+ self.applied.append("appearance")
80
+ if "locale" in spec:
81
+ tag = spec["locale"].replace("_", "-")
82
+ def set_locale():
83
+ result = self.adb.shell("cmd", "locale", "set-app-locales", self.bundle, "--locales", tag, timeout=10,
84
+ check=False)
85
+ if result.code != 0 or b"xception" in result.out + result.err:
86
+ raise AdbError("per-app languages need Android 13 or later")
87
+ self._change("CONDITION_SET", set_locale)
88
+ self._undo.append(("cmd", "locale", "set-app-locales", self.bundle, "--locales", ""))
89
+ self.applied.append("locale")
90
+ if "location" in spec:
91
+ if not self.emulator:
92
+ raise ConditionError("conditions.location sets an emulator's GPS fix, and a USB Android phone has no "
93
+ "such control, so this check can't run on it.", FIX)
94
+ place = spec["location"]
95
+ self._change("CONDITION_SET", self.adb.adb, "emu", "geo", "fix", f"{place['longitude']:g}",
96
+ f"{place['latitude']:g}", timeout=10)
97
+ self.applied.append("location")
98
+ if spec.get("permissions") and not self.emulator:
99
+ # As on an iPhone: a phone's permissions are someone's own, so a check never changes them there.
100
+ self.skipped.append({"key": "permissions", "why": "Mobster never changes an app's permissions on a phone."})
101
+ return
102
+ for service, action in (spec.get("permissions") or {}).items():
103
+ names = PERMISSIONS.get(service)
104
+ if names is None:
105
+ raise ConditionError(f"conditions.permissions.{service} has no Android permission.", FIX)
106
+ for name in names:
107
+ def change(name=name, action=action):
108
+ try:
109
+ self.adb.permission(self.bundle, name, action == "grant")
110
+ except AdbError:
111
+ # A permission the app does not declare, or the OS version lacks, cannot be revoked.
112
+ if action == "grant":
113
+ raise
114
+ self._change("CONDITION_SET", change)
115
+ self.applied.append("permissions")
116
+
117
+ def after_launch(self):
118
+ """Nothing is sent after launch on Android."""
119
+
120
+ def restore(self, actions=None):
121
+ """Undo each change under the device lease, journaled; stop at the first that isn't confirmed, never retried."""
122
+ if actions is not None:
123
+ self.actions = actions
124
+ while self._undo:
125
+ self._change("CONDITION_RESTORE", self.adb.shell, *self._undo.pop(), timeout=10)
126
+
127
+ def report(self):
128
+ return {"applied": list(dict.fromkeys(self.applied)), "skipped": list(self.skipped)}
@@ -0,0 +1,54 @@
1
+ """`mobster sim doctor` rows for Android: SDK, platform-tools, emulator, an AVD, acceleration, devices."""
2
+
3
+ import platform
4
+
5
+ from ..sim.doctor import FAIL, OK, SKIP, WARN, check
6
+ from . import adb as adb_module
7
+
8
+ SDK_FIX = ("Install Android Studio, open it once, and let it install the SDK (about 3 GB with platform-tools and the "
9
+ "emulator)")
10
+ EMULATOR_FIX = "In Android Studio: Settings > SDK Manager > SDK Tools > Android Emulator"
11
+ AVD_FIX = "In Android Studio: Device Manager > Create Virtual Device (an arm64 system image on Apple silicon)"
12
+
13
+
14
+ def run_checks(manager):
15
+ """Rows for the SDK in use. ``manager`` is an EmulatorManager."""
16
+ root = manager.root
17
+ if root is None:
18
+ return [check("android_sdk", "Android SDK", FAIL, "not found (no ANDROID_HOME, no ~/Library/Android/sdk)",
19
+ SDK_FIX)]
20
+ rows = [check("android_sdk", "Android SDK", OK, str(root))]
21
+ adb_path = adb_module.tool("adb", root)
22
+ if not adb_path:
23
+ rows.append(check("android_adb", "adb", FAIL, "platform-tools is missing",
24
+ "SDK Manager > SDK Tools > Platform-Tools"))
25
+ else:
26
+ version = manager._run([adb_path, "version"], 10, None)
27
+ first = version.text.splitlines()[0] if version.code == 0 and version.text else ""
28
+ rows.append(check("android_adb", "adb", OK if first else FAIL, first or "adb did not run",
29
+ "" if first else "Reinstall platform-tools from the SDK Manager"))
30
+ emulator = adb_module.tool("emulator", root)
31
+ rows.append(check("android_emulator", "Android emulator", OK if emulator else WARN,
32
+ emulator or "not installed: USB phones still work", "" if emulator else EMULATOR_FIX))
33
+ if not emulator:
34
+ rows.append(check("android_avd", "Android virtual device", SKIP, "needs the emulator"))
35
+ else:
36
+ avds = manager.avds()
37
+ rows.append(check("android_avd", "Android virtual device", OK if avds else WARN,
38
+ ", ".join(avds) if avds else "none yet", "" if avds else AVD_FIX))
39
+ machine = platform.machine()
40
+ rows.append(check("android_accel", "Hardware acceleration", OK if machine == "arm64" else WARN,
41
+ "Apple silicon (Hypervisor.framework)" if machine == "arm64"
42
+ else f"{machine}: the emulator needs HVF and an x86_64 image",
43
+ "" if machine == "arm64" else "Use an x86_64 system image"))
44
+ try:
45
+ devices = manager.devices()
46
+ except Exception as error:
47
+ return rows + [check("android_devices", "Android devices", FAIL, f"adb failed: {error}",
48
+ "Run `adb kill-server`, then `adb devices -l`")]
49
+ blocked = [row for row in devices if row["state"] != "device"]
50
+ detail = "; ".join(f"{row['name']} ({row['kind']}, {row['state']})" for row in devices) or "none connected"
51
+ fix = ("Unlock the phone and accept the USB debugging prompt" if any(r["state"] == "unauthorized" for r in blocked)
52
+ else "Reconnect the device" if blocked else "")
53
+ rows.append(check("android_devices", "Android devices", WARN if blocked else OK if devices else SKIP, detail, fix))
54
+ return rows
@@ -0,0 +1,137 @@
1
+ """The Android driver: reads the screen with uiautomator, acts with `adb shell input`, over one adb connection.
2
+
3
+ It hands the agent and verify the same Snapshot and AXTree an iPhone does. Nothing here talks to the Appium
4
+ UIAutomator2 server yet: a dump costs about a second where the server's tree costs a few hundred ms, and the server's
5
+ APKs need a pinned download (see docs/android.mdx).
6
+ """
7
+
8
+ import time
9
+
10
+ from ..drivers import Driver, register_driver
11
+ from ..state import HOST_KEY_OPERATIONS, TEXT_OPERATIONS, from_wda_root
12
+ from ..transport import Deadline, TransportError
13
+ from ..wda.gestures import _swipe_points
14
+ from ..wda.guards import DriverRejection, grounded_target
15
+ from . import adb as adb_module
16
+ from .tree import to_wda
17
+
18
+ FRESH_SECONDS = 1.0
19
+ SWIPE_MILLIS = 350
20
+ KEYCODES = {"HOME": 3, "BACK": 4, "VOLUME_UP": 24, "VOLUME_DOWN": 25, "SUBMIT": 66}
21
+ SOURCE = "android"
22
+
23
+
24
+ class AndroidDriver(Driver):
25
+ capabilities = frozenset({"active_app", "capture_preview", "effects", "evidence_source", "launch", "long_press",
26
+ "open_url", "tap_point"})
27
+ # Every method that changes the device: a verify run journals each call (verify/actions.py, guard_driver).
28
+ effects = ("execute", "launch", "open_url", "tap_point", "long_press")
29
+ can_type = True
30
+
31
+ def __init__(self, serial, adb=None):
32
+ self.adb = adb or adb_module.Adb(serial)
33
+ self.serial = serial
34
+ self._screen = None
35
+ self.last_image = None
36
+
37
+ def _guard(self, call, *args, **kwargs):
38
+ try:
39
+ return call(*args, **kwargs)
40
+ except adb_module.AdbError as error:
41
+ raise TransportError(str(error)) from None
42
+
43
+ def screen(self):
44
+ """(width px, height px, dpi), read once: a rotation or a density change needs a new driver."""
45
+ if self._screen is None:
46
+ self._screen = self._guard(self.adb.screen_px)
47
+ return self._screen
48
+
49
+ def evidence_source(self, timeout=15):
50
+ """The tree as WDA-shaped XML, for verify's evidence read."""
51
+ width, height, density = self.screen()
52
+ xml = self._guard(self.adb.dump_ui, timeout)
53
+ try:
54
+ return to_wda(xml, width, height, density)
55
+ except ValueError as error:
56
+ raise TransportError(f"The Android accessibility tree could not be read: {error}") from None
57
+
58
+ def observe(self, timeout=10):
59
+ from ..state import _parse_wda
60
+ snapshot = from_wda_root(_parse_wda(self.evidence_source(timeout)))
61
+ snapshot.source = SOURCE
62
+ return snapshot
63
+
64
+ def active_app(self, timeout=5):
65
+ return self._guard(self.adb.front_package)
66
+
67
+ def capture_preview(self, timeout=3):
68
+ return self._guard(self.adb.screenshot_data_uri, timeout)
69
+
70
+ def launch(self, package, timeout=20):
71
+ self._guard(self.adb.launch, package)
72
+ return self.observe(timeout)
73
+
74
+ def open_url(self, url):
75
+ self._guard(self.adb.open_url, url)
76
+
77
+ def tap_point(self, x, y):
78
+ """Tap a screen point given as fractions of the screen."""
79
+ width, height, _ = self.screen()
80
+ self._guard(self.adb.tap, x * width, y * height)
81
+
82
+ def long_press(self, x, y):
83
+ width, height, _ = self.screen()
84
+ self._guard(self.adb.long_press, x * width, y * height)
85
+
86
+ def _check_current(self, snapshot, deadline):
87
+ if time.monotonic() - snapshot.captured_at <= FRESH_SECONDS:
88
+ return
89
+ if self.observe(timeout=deadline.remaining()).fingerprint != snapshot.fingerprint:
90
+ raise DriverRejection("stale_revision")
91
+
92
+ def execute(self, operation, target, snapshot, text=None, timeout=10):
93
+ deadline = Deadline(timeout)
94
+ width, height, _ = self.screen()
95
+ if operation in HOST_KEY_OPERATIONS or operation == "BACK":
96
+ self._guard(self.adb.key, KEYCODES[operation])
97
+ return {"dispatch_attempted": True}
98
+ if operation == "LAUNCH_APP":
99
+ self._guard(self.adb.launch, target)
100
+ return {}
101
+ if operation == "SUBMIT":
102
+ grounded_target(operation, target, snapshot)
103
+ if "SUBMIT" not in target.actions:
104
+ raise ValueError("SUBMIT needs a keyboard and an editable field")
105
+ self._check_current(snapshot, deadline)
106
+ self._guard(self.adb.key, KEYCODES["SUBMIT"])
107
+ elif operation == "TAP":
108
+ grounded_target(operation, target, snapshot)
109
+ self._check_current(snapshot, deadline)
110
+ x, y = target.tap_point
111
+ self._guard(self.adb.tap, x * width, y * height)
112
+ elif operation in TEXT_OPERATIONS:
113
+ grounded_target(operation, target, snapshot)
114
+ self._check_current(snapshot, deadline)
115
+ if not isinstance(text, str) or not text:
116
+ raise ValueError("TYPE requires text")
117
+ x, y = target.tap_point
118
+ self._guard(self.adb.tap, x * width, y * height)
119
+ self._guard(self.adb.text, text)
120
+ if operation == "TYPE_SUBMIT":
121
+ self._guard(self.adb.key, KEYCODES["SUBMIT"])
122
+ elif operation in {"SWIPE_UP", "SWIPE_DOWN", "SWIPE_LEFT", "SWIPE_RIGHT"}:
123
+ aimed = None
124
+ if target is not None and operation in {"SWIPE_LEFT", "SWIPE_RIGHT"}:
125
+ grounded_target(operation, target, snapshot)
126
+ aimed = target.rect
127
+ x1, y1, x2, y2 = _swipe_points(operation, width, height, aimed)
128
+ self._guard(self.adb.swipe, x1, y1, x2, y2, SWIPE_MILLIS)
129
+ else:
130
+ raise ValueError("Unsupported Android operation")
131
+ return {}
132
+
133
+ def close(self):
134
+ """Nothing is held open: adb runs one command per call."""
135
+
136
+
137
+ register_driver("android", AndroidDriver)
@@ -0,0 +1,216 @@
1
+ """Android emulators Mobster boots: one lease per run, a cap like the iOS simulators', and only its own are stopped."""
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ import time
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ from ..sim.models import SimError, SimLease
12
+ from . import adb as adb_module
13
+ from .adb import Adb, AdbError
14
+
15
+ DEFAULT_MAX = 2
16
+ BOOT_TIMEOUT = 240
17
+ FIRST_PORT, LAST_PORT = 5554, 5584 # emulator console ports are even; adb sees emulator-<port>
18
+ EMULATOR_ARGS = ("-no-window", "-no-audio", "-no-snapshot-save", "-no-boot-anim", "-gpu", "swiftshader_indirect")
19
+ AVD_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._\-]{0,63}")
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class AndroidTarget:
24
+ serial: str
25
+ name: str # "Pixel_8" or the phone's model
26
+ kind: str # "emulator" or "usb"
27
+ avd: str = ""
28
+
29
+
30
+ def _lease(identity):
31
+ from ..journal import Lease
32
+ return Lease.device(identity)
33
+
34
+
35
+ def _spawn(argv):
36
+ return subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
37
+ start_new_session=True)
38
+
39
+
40
+ class EmulatorManager:
41
+ """``adb`` lists devices, ``spawn`` starts the emulator process, ``lease`` takes the per-device lock."""
42
+
43
+ def __init__(self, data_dir, adb=None, run=adb_module.run_process, spawn=_spawn, lease=_lease, root=None,
44
+ sleep=time.sleep, clock=time.monotonic):
45
+ self.data_dir = Path(data_dir).expanduser() / "android"
46
+ self.root = root if root is not None else adb_module.sdk_root()
47
+ self.adb = adb or Adb(run=run, binary=adb_module.tool("adb", self.root) or "adb")
48
+ self._run, self._spawn, self._lease, self.sleep, self.clock = run, spawn, lease, sleep, clock
49
+
50
+ @property
51
+ def max_emulators(self):
52
+ text = str(os.environ.get("MOBSTER_MAX_SIMS") or "").strip()
53
+ if not text:
54
+ return DEFAULT_MAX
55
+ if not text.isdigit() or int(text) < 1:
56
+ raise SimError("environment", f"MOBSTER_MAX_SIMS is {text!r}; it must be a whole number of 1 or more.",
57
+ "Unset it to use 2.")
58
+ return int(text)
59
+
60
+ # -- what exists
61
+
62
+ def avds(self):
63
+ emulator = adb_module.tool("emulator", self.root)
64
+ if not emulator:
65
+ return []
66
+ result = self._run([emulator, "-list-avds"], 20, None)
67
+ return [line.strip() for line in result.text.splitlines() if AVD_NAME.fullmatch(line.strip())]
68
+
69
+ def avd_of(self, serial):
70
+ result = Adb(serial, self._run, self.adb.binary).adb("emu", "avd", "name", timeout=10, check=False)
71
+ lines = [line.strip() for line in result.text.splitlines() if line.strip() and line.strip() != "OK"]
72
+ return lines[0] if lines else ""
73
+
74
+ def devices(self):
75
+ """Android devices as rows: serial, name, kind, state, avd, owned (a Mobster-booted emulator), in_use."""
76
+ rows = []
77
+ for device in self.adb.devices():
78
+ avd = self.avd_of(device.serial) if device.kind == "emulator" and device.ready else ""
79
+ identity = self._identity(device.serial, avd)
80
+ rows.append({"platform": "android", "serial": device.serial, "name": avd or device.model or device.serial,
81
+ "kind": device.kind, "state": device.state, "avd": avd,
82
+ "owned": self._pid_file(avd).exists() if avd else False,
83
+ "in_use": self._in_use(identity)})
84
+ return rows
85
+
86
+ @staticmethod
87
+ def _identity(serial, avd):
88
+ return f"android:avd:{avd}" if avd else f"android:{serial}"
89
+
90
+ def _in_use(self, identity):
91
+ try:
92
+ held = self._lease(identity)
93
+ except Exception:
94
+ return True
95
+ held.close()
96
+ return False
97
+
98
+ def _pid_file(self, avd):
99
+ return self.data_dir / f"{avd}.json"
100
+
101
+ # -- acquire and release
102
+
103
+ def acquire(self, avd=None, serial=None, timeout=BOOT_TIMEOUT):
104
+ """A lease on an idle ready device: ``serial`` (a USB phone or running emulator), else a running emulator of
105
+ ``avd``, else a booted one. SimError("busy") when everything is in use and the cap is reached."""
106
+ rows = self.devices()
107
+ if serial:
108
+ rows = [row for row in rows if row["serial"] == serial]
109
+ if not rows:
110
+ raise SimError("environment", f"No Android device {serial} is connected.", "Run `adb devices -l`.")
111
+ elif avd:
112
+ rows = [row for row in rows if row["avd"] == avd]
113
+ else:
114
+ rows = [row for row in rows if row["kind"] == "emulator"] # a USB phone is used only when named
115
+ for row in rows:
116
+ if row["state"] != "device":
117
+ continue
118
+ lease = self._try_lease(self._identity(row["serial"], row["avd"]))
119
+ if lease is not None:
120
+ target = AndroidTarget(row["serial"], row["name"], row["kind"], row["avd"])
121
+ return SimLease(target, [lease])
122
+ if serial:
123
+ raise SimError("busy", f"{serial} is running a check.", "Wait for it to finish.")
124
+ return self._boot_and_lease(avd, timeout)
125
+
126
+ def _try_lease(self, identity):
127
+ try:
128
+ return self._lease(identity)
129
+ except Exception:
130
+ return None
131
+
132
+ def _boot_and_lease(self, avd, timeout):
133
+ running = [row for row in self.devices() if row["owned"]]
134
+ if len(running) >= self.max_emulators:
135
+ raise SimError("busy", f"All {self.max_emulators} of Mobster's Android emulators are running checks.",
136
+ "Wait for one to finish, or set MOBSTER_MAX_SIMS higher.")
137
+ names = self.avds()
138
+ if not names:
139
+ raise SimError("environment", "There is no Android virtual device (AVD).",
140
+ "Create one in Android Studio's Device Manager, or with avdmanager.")
141
+ if avd and avd not in names:
142
+ raise SimError("environment", f"There is no AVD named {avd}; there are {', '.join(names)}.",
143
+ "Pick one of those, or create it in Android Studio's Device Manager.")
144
+ avd = avd or names[0]
145
+ lease = self._try_lease(self._identity("", avd))
146
+ if lease is None:
147
+ raise SimError("busy", f"{avd} is running a check.", "Wait for it to finish.")
148
+ try:
149
+ serial = self._boot(avd, timeout)
150
+ except BaseException:
151
+ lease.close()
152
+ raise
153
+ return SimLease(AndroidTarget(serial, avd, "emulator", avd), [lease])
154
+
155
+ def _free_port(self):
156
+ taken = {int(row["serial"].split("-")[1]) for row in self.adb.devices() if row.kind == "emulator"}
157
+ for port in range(FIRST_PORT, LAST_PORT + 1, 2):
158
+ if port not in taken:
159
+ return port
160
+ raise SimError("simulator", "Every emulator port from 5554 to 5584 is in use.",
161
+ "Stop an emulator you started by hand.")
162
+
163
+ def _boot(self, avd, timeout):
164
+ emulator = adb_module.tool("emulator", self.root)
165
+ if not emulator:
166
+ raise SimError("environment", "The Android emulator is not installed.",
167
+ "Install it from Android Studio's SDK Manager.")
168
+ port = self._free_port()
169
+ serial = f"emulator-{port}"
170
+ process = self._spawn([emulator, "-avd", avd, "-port", str(port), *EMULATOR_ARGS])
171
+ self.data_dir.mkdir(parents=True, exist_ok=True)
172
+ self._pid_file(avd).write_text(json.dumps({"pid": process.pid, "serial": serial}))
173
+ guest = Adb(serial, self._run, self.adb.binary)
174
+ end = self.clock() + timeout
175
+ while self.clock() < end:
176
+ if process.poll() is not None:
177
+ self._pid_file(avd).unlink(missing_ok=True)
178
+ raise SimError("simulator", f"The emulator for {avd} exited while booting.",
179
+ "Start it once from Android Studio to see why.")
180
+ try:
181
+ if serial in {d.serial for d in self.adb.devices() if d.ready} and guest.booted():
182
+ return serial
183
+ except AdbError:
184
+ pass
185
+ self.sleep(2)
186
+ self._stop(avd, serial, process.pid)
187
+ raise SimError("simulator", f"{avd} did not finish booting in {timeout} s.",
188
+ "Close other heavy jobs and try again.")
189
+
190
+ def _stop(self, avd, serial, pid):
191
+ Adb(serial, self._run, self.adb.binary).adb("emu", "kill", timeout=10, check=False)
192
+ end = self.clock() + 15
193
+ while self.clock() < end:
194
+ try:
195
+ os.kill(pid, 0)
196
+ except OSError:
197
+ break
198
+ self.sleep(.5)
199
+ else:
200
+ try:
201
+ os.kill(pid, 15)
202
+ except OSError:
203
+ pass
204
+ self._pid_file(avd).unlink(missing_ok=True)
205
+
206
+ def shutdown(self, avd=None):
207
+ """Stop the emulators Mobster booted (all, or ``avd``) that are not running a check. Others are never
208
+ touched."""
209
+ stopped = []
210
+ for row in self.devices():
211
+ if not row["owned"] or row["in_use"] or (avd and row["avd"] != avd):
212
+ continue
213
+ info = json.loads(self._pid_file(row["avd"]).read_text())
214
+ self._stop(row["avd"], row["serial"], int(info["pid"]))
215
+ stopped.append(row["avd"])
216
+ return stopped
@@ -0,0 +1,85 @@
1
+ """logcat and crashes. Every line passes the secret filter before it is kept."""
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+
6
+ from .. import secret_filter
7
+ from .adb import Adb, valid_package
8
+
9
+ THREADTIME = re.compile(r"^(\d\d-\d\d \d\d:\d\d:\d\d\.\d+)\s+(\d+)\s+(\d+)\s+([VDIWEF])\s+(.*?)\s*:\s?(.*)$")
10
+ FATAL = re.compile(r"^FATAL EXCEPTION: (.*)$")
11
+ PROCESS = re.compile(r"^Process: ([A-Za-z0-9_.]+), PID: (\d+)")
12
+ ANR = re.compile(r"^ANR in ([A-Za-z0-9_.]+)(?: \(([^)]*)\))?")
13
+ EXCEPTION = re.compile(r"^([A-Za-z0-9_.$]+(?:Exception|Error)): ?(.*)$")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Crash:
18
+ kind: str # "crash" or "anr"
19
+ package: str
20
+ pid: int
21
+ thread: str
22
+ exception: str # the first exception line, e.g. "java.lang.IllegalStateException: boom"
23
+ lines: tuple # the block's lines, filtered
24
+
25
+
26
+ def read_logcat(adb: Adb, since=None, limit=4000):
27
+ """Filtered logcat lines (`adb logcat -d -v threadtime`), newest ``limit`` of them; ``since`` is
28
+ "MM-DD HH:MM:SS.mmm"."""
29
+ args = ["logcat", "-d", "-v", "threadtime", *(["-T", since] if since else [])]
30
+ text = adb.adb(*args, timeout=30).text
31
+ return [secret_filter.redact(line) for line in text.splitlines()[-limit:]]
32
+
33
+
34
+ def parse_crashes(lines, package=None):
35
+ """The crashes and ANRs in threadtime lines. ``package`` keeps only that app's."""
36
+ crashes, block, kind = [], [], None
37
+ last_key = None
38
+
39
+ def close():
40
+ nonlocal block, kind
41
+ if block:
42
+ crash = _crash(kind, block)
43
+ if crash and (package is None or crash.package == package):
44
+ crashes.append(crash)
45
+ block, kind = [], None
46
+
47
+ for raw in lines:
48
+ match = THREADTIME.match(raw)
49
+ if not match:
50
+ continue
51
+ _time, pid, _tid, level, tag, message = match.groups()
52
+ key = (pid, tag)
53
+ if kind and (key != last_key or level not in {"E", "F"}):
54
+ close()
55
+ if not kind:
56
+ if tag == "AndroidRuntime" and FATAL.match(message):
57
+ kind = "crash"
58
+ elif tag == "ActivityManager" and ANR.match(message):
59
+ kind = "anr"
60
+ if kind:
61
+ block.append(message)
62
+ last_key = key
63
+ close()
64
+ return crashes
65
+
66
+
67
+ def _crash(kind, block):
68
+ package, pid, thread, exception = "", 0, "", ""
69
+ for message in block:
70
+ fatal, process, anr = FATAL.match(message), PROCESS.match(message), ANR.match(message)
71
+ if fatal:
72
+ thread = fatal.group(1)
73
+ elif process:
74
+ package, pid = process.group(1), int(process.group(2))
75
+ elif anr:
76
+ package, thread = anr.group(1), anr.group(2) or ""
77
+ elif not exception and EXCEPTION.match(message):
78
+ exception = message
79
+ if not package:
80
+ return None
81
+ return Crash(kind, package, pid, thread, exception, tuple(block))
82
+
83
+
84
+ def crashes_of(adb, package, since=None):
85
+ return parse_crashes(read_logcat(adb, since), valid_package(package))