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,148 @@
1
+ import json
2
+ import plistlib
3
+ import subprocess
4
+ from pathlib import Path
5
+
6
+ BUILD_DIR = "build" # .mobster/build: the derived data folder
7
+ LOG_NAME = "pr-build.log"
8
+ BUILD_TIMEOUT = 45 * 60
9
+ LIST_TIMEOUT = 120
10
+ SDKS = {"sim": "iphonesimulator", "device": "iphoneos"}
11
+ DESTINATIONS = {"sim": "generic/platform=iOS Simulator", "device": "generic/platform=iOS"}
12
+
13
+
14
+ class BuildError(Exception):
15
+ """The build couldn't be made: one plain sentence, and the fix."""
16
+
17
+ def __init__(self, message, fix=""):
18
+ super().__init__(message)
19
+ self.message, self.fix = message, fix
20
+
21
+
22
+ def find_project(root):
23
+ """The workspace or project to build: one at the project root, else one a folder below it."""
24
+ root = Path(root)
25
+ folders = [root]
26
+ if root.is_dir():
27
+ folders += sorted(p for p in root.iterdir() if p.is_dir() and not p.name.startswith(".")
28
+ and p.suffix not in (".xcodeproj", ".xcworkspace"))
29
+ for folder in folders:
30
+ workspaces = [p for p in folder.glob("*.xcworkspace") if p.is_dir()]
31
+ projects = [p for p in folder.glob("*.xcodeproj") if p.is_dir()]
32
+ if len(workspaces) > 1 or (not workspaces and len(projects) > 1):
33
+ names = ", ".join(p.name for p in (workspaces or projects))
34
+ raise BuildError(f"{folder} has more than one Xcode project ({names}).",
35
+ "Pass the one to build with --project.")
36
+ if workspaces:
37
+ return "workspace", workspaces[0]
38
+ if projects:
39
+ return "project", projects[0]
40
+ return None
41
+
42
+
43
+ def project_arg(path):
44
+ """("workspace" | "project", path) for a path given with --project."""
45
+ path = Path(path)
46
+ if path.suffix == ".xcworkspace":
47
+ return "workspace", path
48
+ if path.suffix == ".xcodeproj":
49
+ return "project", path
50
+ raise BuildError(f"{path} isn't an .xcodeproj or an .xcworkspace.", "Pass --project YourApp.xcodeproj.")
51
+
52
+
53
+ def run_command(argv, cwd, log=None, timeout=BUILD_TIMEOUT):
54
+ """(exit code, output); output goes to ``log`` when given."""
55
+ try:
56
+ if log is not None:
57
+ Path(log).parent.mkdir(parents=True, exist_ok=True)
58
+ with open(log, "wb") as handle:
59
+ result = subprocess.run(argv, cwd=str(cwd), stdout=handle, stderr=subprocess.STDOUT,
60
+ stdin=subprocess.DEVNULL, timeout=timeout)
61
+ return result.returncode, Path(log).read_text(encoding="utf-8", errors="replace")[-200_000:]
62
+ result = subprocess.run(argv, cwd=str(cwd), capture_output=True, stdin=subprocess.DEVNULL, timeout=timeout)
63
+ return result.returncode, (result.stdout + result.stderr).decode("utf-8", errors="replace")
64
+ except FileNotFoundError:
65
+ return 127, "xcodebuild isn't installed."
66
+ except subprocess.TimeoutExpired:
67
+ return 124, f"xcodebuild didn't finish in {timeout // 60} minutes."
68
+
69
+
70
+ def schemes(found, *, runner=None, cwd=None):
71
+ kind, path = found
72
+ code, output = (runner or run_command)(
73
+ ["xcodebuild", "-list", "-json", f"-{kind}", str(path)], cwd or path.parent)
74
+ # stderr follows the JSON (xcodebuild's warnings, CocoaPods' notes), so read one object.
75
+ start = output.find("{")
76
+ try:
77
+ data = json.JSONDecoder().raw_decode(output[start:])[0] if start >= 0 else {}
78
+ except ValueError:
79
+ data = {}
80
+ block = data.get(kind) or data.get("project") or data.get("workspace") or {}
81
+ return [name for name in block.get("schemes") or () if isinstance(name, str)]
82
+
83
+
84
+ def pick_scheme(found, *, runner=None, cwd=None):
85
+ """The only scheme, or the one named like the project; else BuildError (pass --scheme)."""
86
+ names = schemes(found, runner=runner, cwd=cwd)
87
+ stem = Path(found[1]).stem
88
+ if len(names) == 1:
89
+ return names[0]
90
+ if stem in names:
91
+ return stem
92
+ listed = ", ".join(names[:8]) or "none"
93
+ raise BuildError(f"Mobster can't tell which scheme to build (schemes: {listed}).", "Pass --scheme NAME.")
94
+
95
+
96
+ def build(root, *, scheme, project, platform="sim", configuration="Debug", runner=None, progress=None,
97
+ bundle=None, for_testing=False, test_plan=None):
98
+ """Build ``scheme`` for ``platform`` ("sim" or "device") into .mobster/build. With ``for_testing`` the same build
99
+ also makes the test bundles and the .xctestrun file that ``xctest.run`` runs (build-for-testing)."""
100
+ root = Path(root)
101
+ kind, path = project
102
+ derived = root / ".mobster" / BUILD_DIR
103
+ log = derived / LOG_NAME
104
+ argv = ["xcodebuild", f"-{kind}", str(path), "-scheme", scheme, "-configuration", configuration,
105
+ "-destination", DESTINATIONS[platform], "-derivedDataPath", str(derived)]
106
+ if platform == "sim":
107
+ argv.append("CODE_SIGNING_ALLOWED=NO")
108
+ else:
109
+ argv.append("-allowProvisioningUpdates")
110
+ if for_testing and test_plan:
111
+ argv += ["-testPlan", test_plan]
112
+ argv.append("build-for-testing" if for_testing else "build")
113
+ (progress or (lambda line: None))(f"Building {scheme} for the {'simulator' if platform == 'sim' else 'iPhone'}")
114
+ code, output = (runner or run_command)(argv, root, log)
115
+ if code != 0:
116
+ errors = [line.strip() for line in output.splitlines() if "error:" in line][:3]
117
+ detail = ("; ".join(errors))[:600] if errors else f"xcodebuild exited {code}"
118
+ fix = "Fix the build, or pass --no-build to test the builds the checks already name."
119
+ if platform == "device" and "signing" in output.lower():
120
+ fix = "Open the project in Xcode and pick a team under Signing & Capabilities, then run it again."
121
+ raise BuildError(f"The build failed: {detail}.", fix)
122
+ built = {**product(derived, configuration, platform, scheme=scheme, bundle=bundle), "log": str(log)}
123
+ from ..memory import writer
124
+ writer.from_build(root, scheme, built.get("bundle")) # the project's scheme and bundle become memory
125
+ return built
126
+
127
+
128
+ def product(derived, configuration, platform, *, scheme=None, bundle=None):
129
+ """The .app the build made: the one with the checks' bundle ID, else the scheme's name, else the only one."""
130
+ folder = Path(derived) / "Build" / "Products" / f"{configuration}-{SDKS[platform]}"
131
+ apps = sorted(p for p in folder.glob("*.app") if p.is_dir())
132
+ found = [(app, _bundle(app)) for app in apps]
133
+ choice = next((app for app, bid in found if bundle and bid == bundle), None) or \
134
+ next((app for app, _ in found if scheme and app.stem == scheme), None) or \
135
+ (apps[0] if len(apps) == 1 else None)
136
+ if choice is None:
137
+ raise BuildError(f"The build finished, but Mobster couldn't tell which app in {folder} to test.",
138
+ "Pass --scheme with the app's scheme.")
139
+ return {"bundle": _bundle(choice), "path": str(choice), "platform": platform}
140
+
141
+
142
+ def _bundle(app):
143
+ try:
144
+ with open(Path(app) / "Info.plist", "rb") as handle:
145
+ value = plistlib.load(handle).get("CFBundleIdentifier")
146
+ return value if isinstance(value, str) else None
147
+ except (OSError, plistlib.InvalidFileException, ValueError, AttributeError):
148
+ return None
@@ -0,0 +1,55 @@
1
+ """The step proof in `mobster test`'s reports: the cause line and the failing step's requests and log for JUnit,
2
+ and each step's clip, log lines and requests for the HTML report (verify/proof.py builds the proof)."""
3
+
4
+
5
+ def cause_html(proof):
6
+ """The cause line for the HTML report (verify/report.py draws it)."""
7
+ from ..verify.report import cause_line
8
+ return cause_line(proof)
9
+
10
+
11
+ def cause_lines(proof):
12
+ cause = (proof or {}).get("cause")
13
+ return [f"Cause ({cause.get('side')}): {cause.get('line')}"] if isinstance(cause, dict) else []
14
+
15
+
16
+ def step_lines(proof, evidence):
17
+ """The failing step's span for <system-out>: its text, the requests the log shows and the lines the app logged."""
18
+ from ..verify.evidence import log_text
19
+ from ..verify.proof import failing_entry
20
+ entry = failing_entry(proof, (evidence or {}).get("step")) if proof else None
21
+ if entry is None:
22
+ return []
23
+ out = [f"Step {entry.get('index')}: {entry.get('text')} ({entry.get('fromMs', 0) / 1000:.3f} to "
24
+ f"{entry.get('toMs', 0) / 1000:.3f} s)"]
25
+ rows = (entry.get("network") or {}).get("rows") or []
26
+ for row in rows:
27
+ out.append("Request: " + " ".join(str(row[key]) for key in ("method", "host", "status", "durationMs")
28
+ if row.get(key) is not None))
29
+ lines = (entry.get("log") or {}).get("lines") or []
30
+ if lines:
31
+ out += [f"The app's log in this step ({len(lines)} of {(entry.get('log') or {}).get('count')} lines):",
32
+ log_text(lines)]
33
+ return out
34
+
35
+
36
+ def proof_steps(result):
37
+ """Each step's clip, log lines and requests, the failing step open."""
38
+ from ..verify.proof import clip, failing_entry
39
+ from ..verify.report import proof_pane
40
+ proof = result.get("stepProof") or {}
41
+ entries = proof.get("steps") or []
42
+ if not entries:
43
+ return ""
44
+ failing = failing_entry(proof, (result.get("evidence") or {}).get("step"))
45
+ video, offset = result.get("video"), result.get("videoOffsetMs")
46
+ panes = [proof_pane(entry, clip=clip(entry, offset), video=video,
47
+ title=f"Step {entry.get('index')}: {_short(entry.get('text'))}",
48
+ opened=entry is failing) for entry in entries]
49
+ panes = [pane for pane in panes if pane]
50
+ return '<div class="proof-steps">' + "".join(panes) + "</div>" if panes else ""
51
+
52
+
53
+ def _short(text, limit=80):
54
+ text = " ".join(str(text or "").split())
55
+ return text if len(text) <= limit else text[:limit - 1] + "…"
@@ -0,0 +1,119 @@
1
+ """Frozen local check inputs for comparing completed suite evidence."""
2
+
3
+ import hashlib
4
+ import json
5
+ from dataclasses import replace
6
+ from pathlib import Path
7
+
8
+ from ..devkit import links
9
+ from ..replay_core.heal import HealError, InputChanged, proof_assets
10
+ from ..verify.checks import FILE_LIMIT, check_to_dict
11
+ from ..verify.fixtures import FixtureError, with_fixtures
12
+
13
+ FIELDS = ("checkSpecDigest", "baselineDigest", "fixtureDigest", "visualBaselineSha256",
14
+ "businessBindingSha256", "appDigest")
15
+
16
+
17
+ def _hash(value):
18
+ return hashlib.sha256(json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False,
19
+ separators=(",", ":")).encode("utf-8")).hexdigest()
20
+
21
+
22
+ def _source_digest(path):
23
+ from .input_files import digest
24
+ return digest(Path(path), FILE_LIMIT)
25
+
26
+
27
+ def _app_digest(path, cache):
28
+ if path is None:
29
+ return None
30
+ path = Path(path)
31
+ if cache is not None and path in cache:
32
+ return cache[path]
33
+ # Project revisions use the same bounded, streaming file/tree digest for immutable prebuilt apps.
34
+ from .input_files import MAX_APP_BYTES, digest
35
+ try:
36
+ value = digest(path, MAX_APP_BYTES)
37
+ except (OSError, ValueError):
38
+ value = None
39
+ if cache is not None:
40
+ cache[path] = value
41
+ return value
42
+
43
+
44
+ def app_digest(path):
45
+ """Bounded fingerprint of a locally preserved app artifact, or unknown."""
46
+ return _app_digest(path, None)
47
+
48
+
49
+ def case_provenance(entry, root, *, asset_cache=None):
50
+ """Capture the effective parsed check and external proof at this admission boundary.
51
+
52
+ `app.path` bytes are an intentional comparison variable; launch and assertion semantics remain in the spec.
53
+ Each input freezes on its own: one that can't be read is null rather than an invented comparable identity, and the
54
+ others stay pinned. `inputChanged` says an input differs from what the check was admitted with, which refuses it.
55
+ """
56
+ unknown = {**dict.fromkeys(FIELDS), "sourceDigest": None, "inputChanged": False}
57
+ check = entry.check
58
+ if check is None:
59
+ return unknown
60
+ try:
61
+ source_digest = _source_digest(entry.path)
62
+ except (OSError, ValueError):
63
+ source_digest = None
64
+ frozen = {**unknown, "sourceDigest": source_digest, "appDigest": _app_digest(check.app_path, asset_cache)}
65
+ try:
66
+ effective = with_fixtures(check, root)
67
+ if check.fixture_origin is not None:
68
+ # A prepared Check retains its source. Re-resolve only fixture-owned fields here: PR builds may
69
+ # intentionally replace app.path, while fixture YAML and link-registry edits must still refuse.
70
+ try:
71
+ current = with_fixtures(check.fixture_origin, root)
72
+ if any(links.OPEN_STEP.match(step) for step in current.steps):
73
+ current = replace(current,
74
+ steps=links.expand_steps(current.steps, links.discover(root, deep=False)))
75
+ except (FixtureError, links.LinksError) as error:
76
+ raise InputChanged(str(error)) from None # it resolved when the check was prepared
77
+ if (current.steps, current.reset, current.launch_args, current.launch_env, current.seed) != (
78
+ check.steps, check.reset, check.launch_args, check.launch_env, check.seed):
79
+ raise InputChanged("A fixture or linked step changed during this suite")
80
+ spec = check_to_dict(replace(effective, fixture_origin=None), project_root=root)
81
+ spec["app"].pop("path", None)
82
+ seed = effective.seed
83
+ spec["effectiveSeed"] = {"files": [(str(Path(source).relative_to(root)) if Path(source).is_relative_to(root)
84
+ else str(source), destination)
85
+ for source, destination in seed.files],
86
+ "keychain": seed.keychain, "pasteboard": seed.pasteboard,
87
+ "risky": list(seed.risky)}
88
+ spec["l10n"] = effective.l10n
89
+ spec["a11yScope"] = effective.a11y_scope
90
+ proof = proof_assets(effective)
91
+ baseline = {key: proof[key] for key in ("visualSha256", "mediaSha256", "pushSha256", "a11ySha256")}
92
+ fixture = {key: proof[key] for key in ("businessSha256", "seedSha256")}
93
+ baseline_digest = _hash(baseline) if any(value is not None for value in baseline.values()) else "none"
94
+ fixture_digest = _hash(fixture) if effective.business is not None or effective.seed else "none"
95
+ return {**frozen, "checkSpecDigest": _hash(spec), "baselineDigest": baseline_digest,
96
+ "visualBaselineSha256": proof["visualSha256"],
97
+ "fixtureDigest": fixture_digest, "businessBindingSha256": proof["businessSha256"]}
98
+ except InputChanged:
99
+ return {**frozen, "inputChanged": True}
100
+ except (OSError, ValueError, TypeError, FixtureError, HealError, links.LinksError):
101
+ return frozen
102
+
103
+
104
+ def unpinned(frozen, check):
105
+ """What of a check was never frozen, as a note names it, so its report can't claim that identity."""
106
+ return [name for name, missing in (("check file", frozen["sourceDigest"] is None),
107
+ ("fixtures or assets", frozen["checkSpecDigest"] is None),
108
+ ("app", bool(check.app_path) and frozen["appDigest"] is None)) if missing]
109
+
110
+
111
+ def require_case_provenance(entry, root, frozen, *, asset_cache=None):
112
+ """Refuse an input known to have changed, before another physical effect. A field that could not be read pins
113
+ nothing: the check runs, and its null keeps the report and `mobster test compare` from claiming an identity."""
114
+ from .targets import Refused
115
+ current = case_provenance(entry, root, asset_cache=asset_cache)
116
+ if frozen["inputChanged"] or any(pinned is not None and current[field] != pinned
117
+ for field, pinned in frozen.items()):
118
+ raise Refused(f"{entry.file}, its app, or a fixture or assertion asset it uses changed during this suite. "
119
+ "Start a new suite.", "refused")
@@ -0,0 +1,244 @@
1
+ from dataclasses import dataclass, field, replace
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from . import pr as prmod
6
+ from . import pr_report
7
+ from .prbuild import BuildError
8
+
9
+ SMOKE = "smoke"
10
+ DRAFT_MODES = ("run", "write", "off")
11
+
12
+
13
+ @dataclass
14
+ class PrOptions:
15
+ base: Optional[str] = None # default: the branch the PR merges into (pr.default_base)
16
+ build: bool = True
17
+ scheme: Optional[str] = None
18
+ project: Optional[str] = None # an .xcodeproj or .xcworkspace; default the one in the project
19
+ configuration: str = "Debug"
20
+ drafts: str = "run" # run | write | off
21
+ gifs: bool = True
22
+ tested_with: bool = False # end the PR comment with "Tested with Mobster" (opt-in)
23
+ github: str = "check" # check | comment | off: Mobster Checks on the PR (github_checks.py)
24
+
25
+
26
+ @dataclass
27
+ class PrResult:
28
+ pr: dict
29
+ doc: dict
30
+ paths: dict
31
+ exit_code: int
32
+ notes: list = field(default_factory=list)
33
+
34
+
35
+ def with_build(entries, built, testflight):
36
+ """Point each check of the built app at the new build; a TestFlight app's checks use the build on the phone."""
37
+ out = []
38
+ for entry in entries:
39
+ check = entry.check
40
+ if check is None:
41
+ out.append(entry)
42
+ continue
43
+ if check.bundle_id and check.bundle_id in testflight:
44
+ entry = replace(entry, check=replace(check, app_path=None))
45
+ elif built and built.get("path") and (
46
+ (check.bundle_id and check.bundle_id == built.get("bundle")) or
47
+ (not check.bundle_id and check.app_path and Path(check.app_path).name == Path(built["path"]).name)):
48
+ entry = replace(entry, check=replace(check, app_path=built["path"]))
49
+ out.append(entry)
50
+ return out
51
+
52
+
53
+ def _all_checks(root):
54
+ from .discover import DiscoveryError, discover
55
+ if not (Path(root) / ".mobster" / "checks").is_dir():
56
+ return []
57
+ try:
58
+ return discover((), root)
59
+ except DiscoveryError:
60
+ return []
61
+
62
+
63
+ def plan_pr(root, pr_options, *, git=prmod.run_git, entries=None, env=None):
64
+ """(PrPlan without drafts, every saved check). Raises pr.PrError."""
65
+ changes = prmod.read_changes(root, pr_options.base, git=git, env=env)
66
+ screens = prmod.screens_in(changes)
67
+ entries = _all_checks(root) if entries is None else entries
68
+ picked = prmod.pick(entries, screens, changes)
69
+ fallback = None
70
+ if not picked and not screens:
71
+ smoke = [entry for entry in entries if SMOKE in {tag.casefold() for tag in entry.tags}]
72
+ picked = [prmod.Pick(entry, ["tagged smoke"], []) for entry in smoke]
73
+ fallback = SMOKE if smoke else None
74
+ return prmod.PrPlan(changes=changes, screens=screens, picked=picked, fallback=fallback), entries
75
+
76
+
77
+ def build_app(root, pr_options, options, *, runner=None, progress=None, bundle=None):
78
+ from . import prbuild
79
+ if not pr_options.build:
80
+ return None
81
+ platform = "device" if any(True for _ in options.devices) else "sim"
82
+ if not pr_options.project and not options.xctest:
83
+ built = _build_react_native(root, pr_options, platform, runner=runner, progress=progress, bundle=bundle)
84
+ if built is not None:
85
+ return built
86
+ found = prbuild.project_arg(Path(root) / pr_options.project if not Path(pr_options.project).is_absolute()
87
+ else pr_options.project) if pr_options.project else prbuild.find_project(root)
88
+ if found is None:
89
+ return None
90
+ scheme = pr_options.scheme or prbuild.pick_scheme(found, runner=runner, cwd=root)
91
+ if options.xctest: # one build-for-testing makes the app the checks drive and the tests
92
+ from . import xctest
93
+ return xctest.build(root, scheme=scheme, project=found, test_plan=options.xctest_plan,
94
+ configuration=pr_options.configuration, runner=runner, progress=progress, bundle=bundle)
95
+ return prbuild.build(root, scheme=scheme, project=found, platform=platform,
96
+ configuration=pr_options.configuration, runner=runner, progress=progress, bundle=bundle)
97
+
98
+
99
+ def _build_react_native(root, pr_options, platform, *, runner=None, progress=None, bundle=None):
100
+ """An Expo or React Native app's build, or None for any other project."""
101
+ # A Debug React Native build loads its JavaScript from Metro, which a test run doesn't start: Debug becomes Release.
102
+ from ..devkit import DevError, expo
103
+ app = expo.detect(root)
104
+ if app is None or not (app.expo or app.native):
105
+ return None
106
+ configuration = "Release" if pr_options.configuration == "Debug" else pr_options.configuration
107
+ (progress or (lambda line: None))(f"{app.name} is a{'n Expo' if app.expo else ' React Native'} app: building "
108
+ f"{configuration}, with the JavaScript inside")
109
+ try:
110
+ return expo.build_sim(app, configuration=configuration, platform=platform, runner=runner, progress=progress,
111
+ bundle=bundle)
112
+ except DevError as error:
113
+ raise BuildError(error.message, error.fix) from None
114
+
115
+
116
+ def test_pr(pr_options, options, *, root, progress=None, git=prmod.run_git, env=None, github=None, **kwargs):
117
+ """Run the PR's checks."""
118
+ from .github_checks import GITHUB_MODES_ERROR, MODES, Poster
119
+ if pr_options.github not in MODES:
120
+ from .api import UsageError
121
+ raise UsageError(GITHUB_MODES_ERROR)
122
+ poster = github or Poster(Path(root), pr_options.github, progress, environ=env, git=git)
123
+ try:
124
+ result = _test_pr(pr_options, options, root=root, progress=progress, git=git, env=env, on_plan=poster.start,
125
+ **kwargs)
126
+ except BaseException:
127
+ poster.abandon()
128
+ raise
129
+ poster.finish(result)
130
+ return result
131
+
132
+
133
+ def _test_pr(pr_options, options, *, root, progress=None, stop=None, key=None, suite_id=None, on_suite=None,
134
+ git=prmod.run_git, build_runner=None, execute=None, gif=None, env=None, named_device=None,
135
+ attempts=None, gate=None, on_plan=None):
136
+ from .api import Planned, UsageError, check_options, resolve_targets
137
+ from .discover import DiscoveryError, apply_quarantine, discover, load_quarantine
138
+ from .reports import make_suite_dir
139
+ progress = progress or (lambda line: None)
140
+ if pr_options.drafts not in DRAFT_MODES:
141
+ raise UsageError(f"--drafts is one of {', '.join(DRAFT_MODES)}")
142
+ if options.testflight and not options.devices:
143
+ raise UsageError("--testflight goes with --device: a TestFlight build is on an iPhone, not a simulator.")
144
+ check_options(options)
145
+ root = Path(root)
146
+ # before on_plan starts the PR's check run, so a usage error leaves none behind marked cancelled
147
+ targets = resolve_targets(options, root=root, named_device=named_device)
148
+ plan, entries = plan_pr(root, pr_options, git=git, env=env)
149
+ if on_plan is not None:
150
+ on_plan(plan.changes)
151
+ notes = []
152
+ if plan.changes.truncated:
153
+ notes.append(f"This PR changes more than {prmod.MAX_FILES} files; Mobster read the first {prmod.MAX_FILES}.")
154
+ progress(_plan_line(plan))
155
+ preferred = next((p.entry.check.bundle_id for p in plan.picked if p.entry.check is not None
156
+ and p.entry.check.bundle_id), None)
157
+ tested_on_phone = set(options.testflight or ())
158
+ every_testflight = bool(options.devices) and plan.picked and all(
159
+ p.entry.check is not None and p.entry.check.bundle_id in tested_on_phone for p in plan.picked)
160
+ built = None
161
+ if not every_testflight and (plan.picked or plan.screens or options.xctest):
162
+ try:
163
+ built = build_app(root, pr_options, options, runner=build_runner, progress=progress, bundle=preferred)
164
+ except BuildError as error:
165
+ return _failed_build(plan, error, root, options, suite_id, pr_options, make_suite_dir)
166
+ picked_entries = with_build([p.entry for p in plan.picked], built, tested_on_phone)
167
+ listed = load_quarantine(root / ".mobster" / "quarantine.yaml")
168
+ apply_quarantine(picked_entries, listed)
169
+ if pr_options.drafts != "off":
170
+ app = prmod.app_of(entries, built)
171
+ plan.drafts = prmod.draft_checks(plan.screens, plan.picked, app=app, root=root)
172
+ draft_entries = []
173
+ if plan.drafts and pr_options.drafts == "run":
174
+ try:
175
+ draft_entries = discover([str(path) for _, path in plan.drafts], root)
176
+ except DiscoveryError:
177
+ draft_entries = []
178
+ for entry in draft_entries:
179
+ entry.quarantined = pr_report.DRAFT_REASON
180
+ draft_entries = with_build(draft_entries, built, tested_on_phone)
181
+ run_entries = picked_entries + draft_entries
182
+ plan_public = plan.public(root)
183
+ head = plan.changes.head
184
+ if not run_entries and not options.xctest:
185
+ suite_id, folder = make_suite_dir(root / ".mobster" / "test-results", suite_id)
186
+ doc = {"suite": suite_id, "checks": [], "exitCode": 0, "summary": {}}
187
+ result = pr_report.document(plan_public, doc, folder=folder, frames={}, gifs={}, built=_public_build(built),
188
+ head=head, tested_with=pr_options.tested_with, notes=notes)
189
+ paths = {"folder": folder, **pr_report.write(result, folder)}
190
+ return PrResult(result, doc, paths, 0, notes)
191
+ planned = Planned(root=root, entries=run_entries, targets=targets, options=options, notes=list(notes),
192
+ xctest_build=built if options.xctest else None,
193
+ changed=tuple(change.path for change in plan.changes.files) if options.xctest else None)
194
+ if execute is None:
195
+ from .api import execute
196
+ kwargs = {"progress": progress, "stop": stop, "key": key, "suite_id": suite_id, "on_suite": on_suite}
197
+ if named_device is not None:
198
+ kwargs["named_device"] = named_device
199
+ if attempts is not None:
200
+ kwargs["attempts"] = attempts
201
+ if gate is not None:
202
+ kwargs["gate"] = gate
203
+ doc, paths = execute(planned, **kwargs)
204
+ folder = Path(paths["folder"])
205
+ frames = pr_report.copy_frames(doc, folder)
206
+ gifs = pr_report.make_gifs(doc, folder, **({"build": gif} if gif else {})) if pr_options.gifs else {}
207
+ result = pr_report.document(plan_public, doc, folder=folder, frames=frames, gifs=gifs,
208
+ built=_public_build(built), head=head, tested_with=pr_options.tested_with,
209
+ notes=list(dict.fromkeys(notes + list(doc.get("notes") or ()))))
210
+ paths.update(pr_report.write(result, folder))
211
+ return PrResult(result, doc, paths, int(doc.get("exitCode", 3)), notes)
212
+
213
+
214
+ def _plan_line(plan):
215
+ checks = len(plan.picked)
216
+ noun = "check" if checks == 1 else "checks"
217
+ if plan.screens:
218
+ screens = len(plan.screens)
219
+ return (f"mobster test pr: {screens} changed screen{'' if screens == 1 else 's'}, {checks} saved {noun} "
220
+ "cover them")
221
+ if plan.fallback == SMOKE:
222
+ return f"mobster test pr: no screen changed, so the {checks} {noun} tagged smoke run"
223
+ return "mobster test pr: no screen changed and no check is tagged smoke"
224
+
225
+
226
+ def _public_build(built):
227
+ if not built:
228
+ return None
229
+ return {"bundle": built.get("bundle"), "app": Path(built["path"]).name if built.get("path") else None,
230
+ "platform": built.get("platform")}
231
+
232
+
233
+ def _failed_build(plan, error, root, options, suite_id, pr_options, make_suite_dir):
234
+ suite_id, folder = make_suite_dir(Path(root) / ".mobster" / "test-results", suite_id)
235
+ doc = {"suite": suite_id, "checks": [], "exitCode": 3, "summary": {}}
236
+ note = f"{error.message} {error.fix}".strip()
237
+ result = pr_report.document(plan.public(root), doc, folder=folder, frames={}, gifs={}, built=None,
238
+ head=plan.changes.head, tested_with=pr_options.tested_with, notes=[note])
239
+ result["headline"] = "Mobster checks: the build failed"
240
+ paths = {"folder": folder, **pr_report.write(result, folder)}
241
+ return PrResult(result, doc, paths, 3, [note])
242
+
243
+
244
+ test_pr.__test__ = False # a function named test_* that unittest must not collect