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,67 @@
1
+ """A throwaway certificate authority for one run: the proxy answers a stubbed https host with a certificate this signs.
2
+
3
+ The private key lives in a 0700 folder and is deleted when the run ends, so the public certificate that stays in the
4
+ simulator's trust store can sign nothing."""
5
+
6
+ import shutil
7
+ import ssl
8
+ import subprocess
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ OPENSSL = "/usr/bin/openssl" # LibreSSL ships with macOS; a Homebrew openssl on PATH is not assumed
13
+ DAYS = "2"
14
+ TIMEOUT = 30
15
+
16
+
17
+ class CertError(Exception):
18
+ pass
19
+
20
+
21
+ class Authority:
22
+ def __init__(self, run=subprocess.run):
23
+ self._run = run
24
+ self.dir = Path(tempfile.mkdtemp(prefix="mobster-ca-"))
25
+ self.dir.chmod(0o700)
26
+ self.cert = self.dir / "ca.pem"
27
+ self._key = self.dir / "ca.key"
28
+ self._contexts = {}
29
+ # LibreSSL writes explicit curve parameters unless it is handed a named one.
30
+ self._curve = self.dir / "curve.pem"
31
+ self._openssl("ecparam", "-name", "prime256v1", "-out", self._curve)
32
+ self._openssl("req", "-x509", "-newkey", f"ec:{self._curve}", "-nodes",
33
+ "-keyout", self._key, "-out", self.cert, "-sha256", "-days", DAYS,
34
+ "-subj", "/CN=Mobster check CA", "-addext", "basicConstraints=critical,CA:TRUE",
35
+ "-addext", "keyUsage=critical,keyCertSign", "-addext", "subjectKeyIdentifier=hash")
36
+
37
+ def _openssl(self, *args):
38
+ try:
39
+ done = self._run([OPENSSL, *map(str, args)], capture_output=True, text=True, timeout=TIMEOUT,
40
+ stdin=subprocess.DEVNULL, cwd=self.dir)
41
+ except (OSError, subprocess.TimeoutExpired) as error:
42
+ raise CertError(f"openssl didn't run ({type(error).__name__}).") from None
43
+ if done.returncode != 0:
44
+ last = ((done.stderr or "").strip().splitlines() or ["failed"])[-1]
45
+ raise CertError("openssl couldn't make a certificate: " + last)
46
+
47
+ def context(self, host):
48
+ """A server-side SSL context for ``host``, with a certificate this authority signed."""
49
+ if host not in self._contexts:
50
+ stem = self.dir / f"leaf-{len(self._contexts)}"
51
+ extensions = stem.with_suffix(".ext")
52
+ # The key identifiers: strict verification (Python 3.13 and later by default) refuses a leaf without them.
53
+ extensions.write_text(f"subjectAltName=DNS:{host}\nextendedKeyUsage=serverAuth\nbasicConstraints=CA:FALSE\n"
54
+ "subjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always\n")
55
+ self._openssl("req", "-new", "-newkey", f"ec:{self._curve}", "-nodes",
56
+ "-keyout", stem.with_suffix(".key"), "-out", stem.with_suffix(".csr"), "-subj", f"/CN={host}")
57
+ self._openssl("x509", "-req", "-in", stem.with_suffix(".csr"), "-CA", self.cert, "-CAkey", self._key,
58
+ "-CAcreateserial", "-out", stem.with_suffix(".pem"), "-sha256", "-days", DAYS,
59
+ "-extfile", extensions)
60
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
61
+ context.load_cert_chain(stem.with_suffix(".pem"), stem.with_suffix(".key"))
62
+ context.set_alpn_protocols(["http/1.1"])
63
+ self._contexts[host] = context
64
+ return self._contexts[host]
65
+
66
+ def close(self):
67
+ shutil.rmtree(self.dir, ignore_errors=True)
@@ -0,0 +1,356 @@
1
+ """The run's local proxy: it shapes one simulator's traffic with a network profile and answers ``stubs:``.
2
+
3
+ It listens on 127.0.0.1 only, serves one request per connection, and holds no state outside its own sockets: stopping
4
+ it, or the process dying, ends every effect. Plain http requests and https requests to a stubbed host are read and
5
+ answered or forwarded; every other https connection is a tunnel the proxy relays without opening."""
6
+
7
+ import asyncio
8
+ import random
9
+ import socket
10
+ import ssl
11
+ import struct
12
+ import threading
13
+ import time
14
+ from dataclasses import dataclass
15
+ from urllib.parse import urlsplit
16
+
17
+ from . import netstubs
18
+
19
+ HEAD_LIMIT = 1 << 20
20
+ BODY_LIMIT = 8 << 20
21
+ HEAD_TIMEOUT = 30
22
+ STALL_SECONDS = 1.0 # what a lost packet costs: about one retransmission timeout
23
+ CHUNK = 16384
24
+ ROW_LIMIT = 500
25
+ CAPTURE_LIMIT = 200 # requests kept whole for wire assertions (verify/wire.py)
26
+ CAPTURE_BYTES = 65536 # of each request body and each response, as sent
27
+ SKIPPED_HEADERS = ("proxy-connection", "connection", "keep-alive", "proxy-authorization", "te", "upgrade")
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class Profile:
32
+ """A link: round-trip time, bandwidth each way, the share of chunks that stall, the share of connections that
33
+ reset."""
34
+ rtt_ms: int = 0
35
+ down_kbps: int = 0 # 0 is unlimited
36
+ up_kbps: int = 0
37
+ stall_pct: float = 0
38
+ reset_pct: float = 0
39
+ offline: bool = False
40
+
41
+
42
+ PROFILES = {
43
+ "offline": Profile(offline=True),
44
+ "slow-3g": Profile(rtt_ms=400, down_kbps=400, up_kbps=400, stall_pct=2),
45
+ "lte": Profile(rtt_ms=50, down_kbps=12000, up_kbps=5000),
46
+ "flaky": Profile(rtt_ms=150, down_kbps=1500, up_kbps=750, stall_pct=5, reset_pct=10),
47
+ }
48
+
49
+
50
+ class ProxyError(Exception):
51
+ pass
52
+
53
+
54
+ def _reset(writer):
55
+ """Close with a TCP reset, which is what a dropped link looks like to the app."""
56
+ sock = writer.get_extra_info("socket")
57
+ try:
58
+ if sock is not None:
59
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
60
+ except OSError:
61
+ pass
62
+ writer.transport.abort()
63
+
64
+
65
+ def _parse_head(raw):
66
+ lines = raw.decode("latin-1").split("\r\n")
67
+ parts = lines[0].split(" ", 2)
68
+ if len(parts) != 3:
69
+ raise ValueError("bad request line")
70
+ headers = []
71
+ for line in lines[1:]:
72
+ if line:
73
+ name, _, value = line.partition(":")
74
+ headers.append((name.strip(), value.strip()))
75
+ return parts[0], parts[1], headers
76
+
77
+
78
+ def _header(headers, name):
79
+ return next((value for key, value in headers if key.lower() == name), None)
80
+
81
+
82
+ class _Link:
83
+ """One connection's clock: which way data last went, so a change of direction costs half a round trip."""
84
+
85
+ def __init__(self):
86
+ self.last = None
87
+ self.started = time.monotonic()
88
+
89
+
90
+ class Proxy:
91
+ def __init__(self, profile=None, stubs=(), authority=None, *, resolve=None, upstream_ssl=None, seed=0,
92
+ watch=None):
93
+ self.profile = profile or Profile()
94
+ self.stubs = tuple(stubs)
95
+ # Host patterns whose https the proxy opens so wire assertions see paths and bodies; None keeps no captures.
96
+ self.watch = tuple(watch) if watch is not None else None
97
+ self._captures = [] if watch is not None else None
98
+ self.authority = authority
99
+ self._resolve = dict(resolve or {}) # host -> (address, port), for tests and local origins
100
+ self._upstream_ssl = upstream_ssl
101
+ self._rng = random.Random(seed)
102
+ self._rows, self.total = [], 0
103
+ self._started = time.monotonic()
104
+ self.port = None
105
+ self._loop = self._thread = self._stop = self._server = None
106
+ self._error = None
107
+ self._ready = threading.Event()
108
+ self._tasks = set()
109
+
110
+ # lifecycle
111
+
112
+ def start(self):
113
+ self._thread = threading.Thread(target=self._main, name="mobster-net-proxy", daemon=True)
114
+ self._thread.start()
115
+ self._ready.wait(15)
116
+ if self._error is not None or self.port is None:
117
+ why = type(self._error).__name__ if self._error else "timeout"
118
+ raise ProxyError(f"The network proxy didn't start ({why}).")
119
+ return self.port
120
+
121
+ def stop(self):
122
+ loop, stop = self._loop, self._stop
123
+ if loop is not None and stop is not None and not loop.is_closed():
124
+ try:
125
+ loop.call_soon_threadsafe(stop.set)
126
+ except RuntimeError:
127
+ pass
128
+ if self._thread is not None:
129
+ self._thread.join(10)
130
+
131
+ def rows(self):
132
+ return [dict(row) for row in self._rows]
133
+
134
+ def captures(self):
135
+ """The requests seen whole (wire assertions only): method, host, path, headers, body and response prefix."""
136
+ return [dict(item) for item in self._captures or ()]
137
+
138
+ def _main(self):
139
+ loop = self._loop = asyncio.new_event_loop()
140
+ try:
141
+ loop.run_until_complete(self._serve())
142
+ except BaseException as error: # reported by start()
143
+ self._error = error
144
+ self._ready.set()
145
+ finally:
146
+ try:
147
+ loop.close()
148
+ except Exception:
149
+ pass
150
+
151
+ async def _serve(self):
152
+ self._stop = asyncio.Event()
153
+ self._server = await asyncio.start_server(self._client, "127.0.0.1", 0, limit=HEAD_LIMIT)
154
+ self.port = self._server.sockets[0].getsockname()[1]
155
+ self._ready.set()
156
+ await self._stop.wait()
157
+ self._server.close()
158
+ for task in list(self._tasks):
159
+ task.cancel()
160
+ await asyncio.gather(*self._tasks, return_exceptions=True)
161
+
162
+ # shaping
163
+
164
+ async def _shape(self, link, way, size):
165
+ profile = self.profile
166
+ if link.last != way and profile.rtt_ms:
167
+ await asyncio.sleep(profile.rtt_ms / 2000)
168
+ link.last = way
169
+ if profile.stall_pct and self._rng.random() * 100 < profile.stall_pct:
170
+ await asyncio.sleep(STALL_SECONDS)
171
+ kbps = profile.down_kbps if way == "down" else profile.up_kbps
172
+ if kbps:
173
+ await asyncio.sleep(size * 8 / (kbps * 1000))
174
+
175
+ async def _send(self, writer, data, link, way):
176
+ for start in range(0, len(data), CHUNK):
177
+ piece = data[start:start + CHUNK]
178
+ await self._shape(link, way, len(piece))
179
+ writer.write(piece)
180
+ await writer.drain()
181
+
182
+ # rows
183
+
184
+ def _row(self, **row):
185
+ self.total += 1
186
+ row = {"atMs": round((time.monotonic() - self._started) * 1000), "via": "", "status": None, "bytes": 0,
187
+ "ms": None, **row}
188
+ if len(self._rows) < ROW_LIMIT:
189
+ self._rows.append(row)
190
+ return row
191
+
192
+ @staticmethod
193
+ def _finish(row, link, **fields):
194
+ row.update(fields, ms=round((time.monotonic() - link.started) * 1000))
195
+
196
+ # connections
197
+
198
+ async def _client(self, reader, writer):
199
+ task = asyncio.current_task()
200
+ self._tasks.add(task)
201
+ link = _Link()
202
+ try:
203
+ await self._handle(reader, writer, link)
204
+ except (asyncio.CancelledError, ConnectionError, asyncio.IncompleteReadError, asyncio.TimeoutError,
205
+ ssl.SSLError, OSError, ValueError, asyncio.LimitOverrunError):
206
+ pass
207
+ finally:
208
+ self._tasks.discard(task)
209
+ try:
210
+ writer.close()
211
+ except Exception:
212
+ pass
213
+
214
+ async def _read_head(self, reader):
215
+ return await asyncio.wait_for(reader.readuntil(b"\r\n\r\n"), HEAD_TIMEOUT)
216
+
217
+ async def _handle(self, reader, writer, link):
218
+ raw = await self._read_head(reader)
219
+ method, target, headers = _parse_head(raw)
220
+ host = urlsplit(target).hostname if "://" in target else None
221
+ if not host:
222
+ host = target.rsplit(":", 1)[0] if method == "CONNECT" else (_header(headers, "host") or "").split(":")[0]
223
+ row = self._row(method=method, host=host or "", path="" if method == "CONNECT" else urlsplit(target).path)
224
+ if self.profile.offline or (self.profile.reset_pct and self._rng.random() * 100 < self.profile.reset_pct):
225
+ self._finish(row, link, via="refused")
226
+ _reset(writer)
227
+ return
228
+ if self.profile.rtt_ms:
229
+ await asyncio.sleep(self.profile.rtt_ms / 1000) # the connection's own handshake
230
+ if method == "CONNECT":
231
+ name, _, port = target.rpartition(":")
232
+ opened = [stub["host"] for stub in self.stubs] + list(self.watch or ())
233
+ if any(netstubs.host_matches(pattern, name) for pattern in opened) and self.authority is not None:
234
+ writer.write(b"HTTP/1.1 200 Connection established\r\n\r\n")
235
+ await writer.drain()
236
+ await writer.start_tls(self.authority.context(name))
237
+ raw = await self._read_head(reader)
238
+ method, target, headers = _parse_head(raw)
239
+ row.update(method=method, path=urlsplit(target).path)
240
+ await self._request(reader, writer, link, row, method, target, headers, "https", name, int(port or 443))
241
+ else:
242
+ await self._tunnel(reader, writer, link, row, name, int(port or 443))
243
+ else:
244
+ split = urlsplit(target)
245
+ await self._request(reader, writer, link, row, method, target, headers, split.scheme or "http",
246
+ host, split.port or 80)
247
+
248
+ async def _connect(self, host, port, tls):
249
+ address, number = self._resolve.get(host, (host, port))
250
+ if not tls:
251
+ return await asyncio.open_connection(address, number, limit=HEAD_LIMIT)
252
+ context = self._upstream_ssl or ssl.create_default_context()
253
+ return await asyncio.open_connection(address, number, ssl=context, server_hostname=host, limit=HEAD_LIMIT)
254
+
255
+ async def _tunnel(self, reader, writer, link, row, host, port):
256
+ row["via"] = "tunnel" # while it's open too: a no_request judged mid-run must see this connection
257
+ try:
258
+ upstream_reader, upstream_writer = await self._connect(host, port, False)
259
+ except OSError:
260
+ self._finish(row, link, via="tunnel", status=502)
261
+ writer.write(b"HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n")
262
+ return
263
+ writer.write(b"HTTP/1.1 200 Connection established\r\n\r\n")
264
+ await writer.drain()
265
+ sizes = {"up": 0, "down": 0}
266
+
267
+ async def pipe(source, sink, way):
268
+ try:
269
+ while data := await source.read(CHUNK):
270
+ sizes[way] += len(data)
271
+ await self._send(sink, data, link, way)
272
+ except (ConnectionError, OSError):
273
+ pass
274
+ finally:
275
+ sink.close()
276
+ try:
277
+ await asyncio.gather(pipe(reader, upstream_writer, "up"), pipe(upstream_reader, writer, "down"))
278
+ finally:
279
+ self._finish(row, link, via="tunnel", status=200, bytes=sizes["down"])
280
+ upstream_writer.close()
281
+
282
+ async def _body(self, reader, headers):
283
+ """The request's body as sent (chunked framing kept), or b"" when it has none."""
284
+ if "chunked" in (_header(headers, "transfer-encoding") or "").lower():
285
+ body = b""
286
+ while True:
287
+ size_line = await reader.readuntil(b"\r\n")
288
+ body += size_line
289
+ size = int(size_line.split(b";")[0].strip() or b"0", 16)
290
+ body += await reader.readexactly(size + 2) if size else await reader.readuntil(b"\r\n")
291
+ if not size:
292
+ return body
293
+ if len(body) > BODY_LIMIT:
294
+ raise ValueError("body too large")
295
+ length = int(_header(headers, "content-length") or 0)
296
+ if length > BODY_LIMIT:
297
+ raise ValueError("body too large")
298
+ return await reader.readexactly(length) if length else b""
299
+
300
+ async def _request(self, reader, writer, link, row, method, target, headers, scheme, host, port):
301
+ body = await self._body(reader, headers)
302
+ split = urlsplit(target)
303
+ path = split.path or "/"
304
+ origin = path + (f"?{split.query}" if split.query else "")
305
+ await self._shape(link, "up", len(body))
306
+ seen = None
307
+ if self._captures is not None and len(self._captures) < CAPTURE_LIMIT:
308
+ seen = {"atMs": row["atMs"], "method": method, "host": host, "path": origin, "headers": list(headers),
309
+ "body": body[:CAPTURE_BYTES], "status": None, "response": b""}
310
+ self._captures.append(seen)
311
+ stub = next((s for s in self.stubs if netstubs.matches(s, host, method, path)), None)
312
+ if stub is not None:
313
+ await self._stub(writer, link, row, stub, seen)
314
+ return
315
+ kept = "".join(f"{name}: {value}\r\n" for name, value in headers if name.lower() not in SKIPPED_HEADERS)
316
+ head = f"{method} {origin} HTTP/1.1\r\n" + kept + "Connection: close\r\n\r\n"
317
+ try:
318
+ upstream_reader, upstream_writer = await self._connect(host, port, scheme == "https")
319
+ except (OSError, ssl.SSLError):
320
+ self._finish(row, link, via="pass", status=502)
321
+ writer.write(b"HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
322
+ return
323
+ try:
324
+ upstream_writer.write(head.encode("latin-1") + body)
325
+ await upstream_writer.drain()
326
+ first, size = True, 0
327
+ while data := await upstream_reader.read(CHUNK):
328
+ if seen is not None and len(seen["response"]) < CAPTURE_BYTES:
329
+ seen["response"] += data[:CAPTURE_BYTES - len(seen["response"])]
330
+ if first:
331
+ first = False
332
+ try:
333
+ row["status"] = int(data.split(b" ", 2)[1])
334
+ if seen is not None:
335
+ seen["status"] = row["status"]
336
+ except (IndexError, ValueError):
337
+ pass
338
+ size += len(data)
339
+ await self._send(writer, data, link, "down")
340
+ self._finish(row, link, via="pass", bytes=size)
341
+ finally:
342
+ upstream_writer.close()
343
+
344
+ async def _stub(self, writer, link, row, stub, seen=None):
345
+ status, kind, body = netstubs.answer(stub)
346
+ if stub["delay_ms"]:
347
+ await asyncio.sleep(stub["delay_ms"] / 1000)
348
+ reason = {200: "OK", 201: "Created", 204: "No Content", 400: "Bad Request", 401: "Unauthorized",
349
+ 403: "Forbidden", 404: "Not Found", 429: "Too Many Requests", 500: "Internal Server Error",
350
+ 503: "Service Unavailable"}.get(status, "Stub")
351
+ head = (f"HTTP/1.1 {status} {reason}\r\nContent-Type: {kind}\r\nContent-Length: {len(body)}\r\n"
352
+ "Connection: close\r\n\r\n").encode()
353
+ if seen is not None:
354
+ seen.update(status=status, response=(head + body)[:CAPTURE_BYTES])
355
+ await self._send(writer, head + body, link, "down")
356
+ self._finish(row, link, via="stub", status=status, bytes=len(body))
@@ -0,0 +1,81 @@
1
+ """One run's network conditions on a simulator: the proxy, the shim that points the app at it, and the cleanup.
2
+
3
+ ``start`` returns the environment to launch the app with; ``stop`` ends the proxy and deletes the throwaway
4
+ certificate authority, and is safe to call twice. Nothing else on the Mac changes."""
5
+
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from . import netcerts, netshim
10
+ from .netproxy import PROFILES, Proxy, ProxyError
11
+
12
+ NOT_SEEN = ("No request from the app reached the proxy. The app may use a network stack Mobster doesn't reach "
13
+ "(see docs/testing.mdx).")
14
+ SUMMARY_ROWS = 200
15
+
16
+
17
+ class NetError(Exception):
18
+ """What went wrong, then what to do."""
19
+
20
+ def __init__(self, message, fix):
21
+ super().__init__(message)
22
+ self.message, self.fix = message, fix
23
+
24
+
25
+ class NetRun:
26
+ def __init__(self, profile, stubs, *, udid, run, build=netshim.ensure_built, authority=netcerts.Authority,
27
+ proxy=Proxy, watch=None):
28
+ self.name, self.stubs, self.udid, self.run = profile, tuple(stubs), udid, run
29
+ self._build, self._authority, self._proxy_class = build, authority, proxy
30
+ self.proxy = self.ca = None
31
+ self._rows, self._total, self._captures = [], 0, []
32
+ self.watch = None if watch is None else tuple(watch) # hosts wire assertions need opened (wire.py)
33
+
34
+ def start(self):
35
+ try:
36
+ library = self._build()
37
+ if self.stubs or self.watch: # only a stub or a watched host needs https opened; a profile tunnels it
38
+ self.ca = self._authority()
39
+ code, output = self.run(["keychain", self.udid, "add-root-cert", str(self.ca.cert)])
40
+ if code != 0:
41
+ raise NetError("Mobster couldn't make the simulator trust its stub certificate"
42
+ + (f": {output.splitlines()[-1][:160]}" if output else "") + ".",
43
+ "Run `mobster sim doctor`, then try again, or remove stubs from the check.")
44
+ extra = {} if self.watch is None else {"watch": self.watch}
45
+ self.proxy = self._proxy_class(PROFILES.get(self.name), self.stubs, self.ca, **extra)
46
+ port = self.proxy.start()
47
+ except netshim.ShimError as error:
48
+ self.stop()
49
+ raise NetError(error.message, error.fix) from None
50
+ except (netcerts.CertError, ProxyError) as error:
51
+ self.stop()
52
+ raise NetError(str(error), "Check that /usr/bin/openssl runs, then try again.") from None
53
+ except NetError:
54
+ self.stop()
55
+ raise
56
+ return {"DYLD_INSERT_LIBRARIES": library, "MOBSTER_PROXY_HOST": "127.0.0.1", "MOBSTER_PROXY_PORT": str(port)}
57
+
58
+ def stop(self):
59
+ proxy, ca, self.proxy, self.ca = self.proxy, self.ca, None, None
60
+ if proxy is not None:
61
+ proxy.stop()
62
+ self._rows, self._total = proxy.rows(), proxy.total
63
+ self._captures = proxy.captures() if self.watch is not None else []
64
+ if ca is not None:
65
+ ca.close()
66
+
67
+ def seen(self):
68
+ """(whole requests, rows) the proxy saw so far, for wire assertions; still readable after ``stop``."""
69
+ proxy = self.proxy
70
+ return (proxy.captures(), proxy.rows()) if proxy is not None else (list(self._captures), list(self._rows))
71
+
72
+ def summary(self):
73
+ """What the proxy saw, for the result: the profile, the request count and the first rows."""
74
+ out = {"profile": self.name, "requests": self._total, "rows": self._rows[:SUMMARY_ROWS]}
75
+ if not out["requests"]:
76
+ out["note"] = NOT_SEEN
77
+ return out
78
+
79
+ def write(self, directory):
80
+ Path(directory, "network.json").write_text(json.dumps(self.summary(), indent=1), encoding="utf-8")
81
+
@@ -0,0 +1,28 @@
1
+ // Mobster's network shim. Mobster launches an app in the iOS Simulator with this library in DYLD_INSERT_LIBRARIES and
2
+ // MOBSTER_PROXY_HOST / MOBSTER_PROXY_PORT set; every NSURLSession that sets no proxy of its own then goes through the
3
+ // run's local proxy. It reads nothing else, writes nothing, and exists only in that process.
4
+ #import <Foundation/Foundation.h>
5
+ #import <objc/runtime.h>
6
+
7
+ static NSDictionary *proxies(void) {
8
+ const char *host = getenv("MOBSTER_PROXY_HOST"), *port = getenv("MOBSTER_PROXY_PORT");
9
+ if (!host || !port) return nil;
10
+ NSString *name = @(host);
11
+ NSNumber *number = @(atoi(port));
12
+ return @{@"HTTPEnable": @1, @"HTTPProxy": name, @"HTTPPort": number,
13
+ @"HTTPSEnable": @1, @"HTTPSProxy": name, @"HTTPSPort": number};
14
+ }
15
+
16
+ static NSDictionary *(*original)(id, SEL);
17
+ static NSDictionary *replacement(id self, SEL command) {
18
+ NSDictionary *own = original(self, command);
19
+ return own ?: proxies();
20
+ }
21
+
22
+ __attribute__((constructor)) static void install(void) {
23
+ if (!proxies()) return;
24
+ Method method = class_getInstanceMethod([NSURLSessionConfiguration class], @selector(connectionProxyDictionary));
25
+ if (!method) return;
26
+ original = (NSDictionary *(*)(id, SEL))method_getImplementation(method);
27
+ method_setImplementation(method, (IMP)replacement);
28
+ }
@@ -0,0 +1,48 @@
1
+ """Builds Mobster's network shim (netshim.m) for the iOS Simulator with Xcode's clang, once per source version."""
2
+
3
+ import hashlib
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ SOURCE = Path(__file__).with_name("netshim.m")
9
+ MIN_IOS = "15.0"
10
+ TIMEOUT = 120
11
+
12
+
13
+ class ShimError(Exception):
14
+ """The shim couldn't be built: what happened, then what to do."""
15
+
16
+ def __init__(self, message, fix="Install Xcode's command-line tools (`xcode-select --install`), then try again."):
17
+ super().__init__(message)
18
+ self.message, self.fix = message, fix
19
+
20
+
21
+ def cache_dir():
22
+ return Path(os.environ.get("MOBSTER_CACHE_DIR") or Path.home() / "Library" / "Caches" / "Mobster")
23
+
24
+
25
+ def ensure_built(*, cache=None, run=subprocess.run):
26
+ """The path of the compiled, ad-hoc signed shim; built on first use and kept by the hash of its source."""
27
+ source = SOURCE.read_bytes()
28
+ folder = Path(cache or cache_dir()) / "netshim" / hashlib.sha256(source).hexdigest()[:12]
29
+ library = folder / "netshim.dylib"
30
+ if library.is_file():
31
+ return str(library)
32
+ folder.mkdir(parents=True, exist_ok=True)
33
+ partial = folder / f"netshim.{os.getpid()}.tmp"
34
+ try:
35
+ for command in (["xcrun", "--sdk", "iphonesimulator", "clang", "-dynamiclib", "-arch", "arm64",
36
+ "-arch", "x86_64", f"-mios-simulator-version-min={MIN_IOS}", "-fobjc-arc",
37
+ "-framework", "Foundation", "-o", str(partial), str(SOURCE)],
38
+ ["codesign", "-s", "-", "--force", str(partial)]):
39
+ done = run(command, capture_output=True, text=True, timeout=TIMEOUT, stdin=subprocess.DEVNULL)
40
+ if done.returncode != 0:
41
+ last = next((line for line in reversed((done.stderr or "").splitlines()) if line.strip()), "")
42
+ raise ShimError(f"Mobster couldn't build its network shim: {last[:200] or command[0] + ' failed'}.")
43
+ partial.replace(library)
44
+ except (OSError, subprocess.TimeoutExpired) as error:
45
+ raise ShimError(f"Mobster couldn't build its network shim ({type(error).__name__}).") from None
46
+ finally:
47
+ partial.unlink(missing_ok=True)
48
+ return str(library)