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,322 @@
1
+ """`mobster wifi probe`: the owner's probe (SPEC §3.7, owner test plan items 1-4), whose result decides the release."""
2
+
3
+ import os
4
+ import signal
5
+ import socket
6
+ import subprocess
7
+ import time
8
+ from functools import partial
9
+ from pathlib import Path
10
+
11
+ from ..devices import wda_url
12
+ from ..transport import request_once
13
+ from . import devicectl, exposure, network
14
+ from .relay import Relay, check_target
15
+
16
+ OK, WARN, FAIL, INFO = "ok", "warn", "fail", "info"
17
+ START_WAIT = 120.0
18
+
19
+
20
+ def http_status(port, timeout=3.0):
21
+ """WebDriverAgent's /status through 127.0.0.1:``port``: its JSON value, or None."""
22
+ try:
23
+ value = request_once(wda_url(port), "GET", "/status", timeout=timeout).get("value")
24
+ except Exception:
25
+ return None
26
+ return value if isinstance(value, dict) else None
27
+
28
+
29
+ def stream_answers(port, timeout=3.0):
30
+ """Whether the MJPEG port behind 127.0.0.1:``port`` sends anything back to a request."""
31
+ try:
32
+ with socket.create_connection(("127.0.0.1", port), timeout) as sock:
33
+ sock.sendall(b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
34
+ return bool(sock.recv(1))
35
+ except OSError:
36
+ return False
37
+
38
+
39
+ def usbmux_network(udid, runner=None):
40
+ """Whether ``idevice_id -n`` (read-only) lists the phone over the network: True, False, or None (no tool)."""
41
+ from ..mac_tools import tool
42
+ listing = tool("idevice_id")
43
+ if not listing:
44
+ return None
45
+ output = (runner or partial(network.read_output, timeout=5))([listing, "-n"])
46
+ if output is None:
47
+ return None
48
+ return udid.upper() in {line.strip().upper() for line in output.splitlines() if line.strip()}
49
+
50
+
51
+ # It binds only 127.0.0.1, never enters a passcode, and changes nothing on the phone but --start's own helper.
52
+ class Probe:
53
+ """One probe of one phone. ``steps``: [{"key", "state", "title", "detail"}]; ``fields``: what CoreDevice said."""
54
+
55
+ def __init__(self, *, udid, name, slot_port=None, data_dir=None, start=False, manager=None, out=None,
56
+ network_check=None):
57
+ self.udid, self.name, self.slot_port = udid, name, slot_port
58
+ self.network_check = network_check
59
+ self.data_dir, self.start, self.manager = data_dir, start, manager
60
+ self.out = out
61
+ self.steps, self.fields, self.peer, self.address = [], {}, None, None
62
+
63
+ def step(self, key, state, title, detail=""):
64
+ item = {"key": key, "state": state, "title": title, "detail": detail}
65
+ self.steps.append(item)
66
+ if self.out is not None:
67
+ self.out(item)
68
+ return item
69
+
70
+ @property
71
+ def ok(self):
72
+ needed = {"coredevice", "tunnel", "wda", "lan"}
73
+ states = {item["key"]: item["state"] for item in self.steps}
74
+ return needed <= set(states) and all(states[key] == OK for key in needed)
75
+
76
+ def public(self):
77
+ return {"device": {"udid": self.udid, "name": self.name}, "ok": self.ok, "steps": self.steps,
78
+ "fields": self.fields, "tunnelAddress": self.address}
79
+
80
+ # -- the steps --------------------------------------------------------------------------------------------------
81
+
82
+ def run(self, usb_attached):
83
+ usb = usb_attached(self.udid)
84
+ self.step("usb", INFO, "USB", {True: "plugged in: unplug it to probe Wi-Fi", False: "not plugged in",
85
+ None: "couldn't tell"}[usb])
86
+ self._network()
87
+ if not self._coredevice():
88
+ return self
89
+ if not self._tunnel():
90
+ return self
91
+ self._readers()
92
+ wda = self._wda()
93
+ if not wda and self.start:
94
+ if self._launch():
95
+ wda = self._wda()
96
+ self._app_relay()
97
+ self._lan()
98
+ self._usbmux()
99
+ return self
100
+
101
+ def _network(self):
102
+ from .words import NETWORK_PROBLEMS, problem
103
+ try:
104
+ result = (self.network_check or network.peer_check)()
105
+ except Exception: # noqa: BLE001
106
+ result = {"state": "unknown"}
107
+ state = result.get("state")
108
+ code = NETWORK_PROBLEMS.get(state)
109
+ if code:
110
+ found = problem(code, self.name)
111
+ self.step("network", WARN, "This Mac's network", f"{found['message']} {found['fix']}")
112
+ elif state == "ok":
113
+ self.step("network", OK, "This Mac's network", f"devices on it can see each other "
114
+ f"({result.get('interface')})")
115
+ else:
116
+ self.step("network", INFO, "This Mac's network", {"offline": "this Mac isn't on a network"}.get(
117
+ state, "couldn't tell whether devices on it can see each other"))
118
+
119
+ def _coredevice(self):
120
+ try:
121
+ peers = devicectl.list_devices()
122
+ except devicectl.DevicectlError as error:
123
+ self.step("coredevice", FAIL, "CoreDevice", str(error))
124
+ return False
125
+ self.peer = peers.get(self.udid.upper())
126
+ if self.peer is None:
127
+ self.step("coredevice", FAIL, "CoreDevice", "doesn't list this iPhone at all. Pair it with the cable in "
128
+ "Setup, then try again.")
129
+ return False
130
+ self.fields = dict(self.peer.fields)
131
+ seen = ", ".join(f"{key}={value}" for key, value in sorted(self.fields.items())
132
+ if not isinstance(value, list)) or "no connection fields"
133
+ self.step("fields", INFO, "Fields CoreDevice reports", seen)
134
+ if self.peer.wired:
135
+ self.step("coredevice", WARN, "CoreDevice", "sees it on USB (transportType wired). Unplug it and probe "
136
+ "again.")
137
+ return False
138
+ if not self.peer.network:
139
+ self.step("coredevice", FAIL, "CoreDevice", f"doesn't see it over the network (transportType "
140
+ f"{self.peer.transport or 'missing'}). Turn on Wi-Fi with "
141
+ "`mobster wifi on` with the cable, keep the phone unlocked "
142
+ "and on the same Wi-Fi as this Mac.")
143
+ return False
144
+ self.step("coredevice", OK, "CoreDevice", "sees it over the network (transportType localNetwork"
145
+ + (f", iOS {self.peer.os_version}" if self.peer.os_version else "") + ")")
146
+ return True
147
+
148
+ def _tunnel(self):
149
+ peer = self.peer
150
+ if peer.tunnel != "up":
151
+ try:
152
+ found = devicectl.bring_up(self.udid)
153
+ peer = found.get(self.udid.upper()) or peer
154
+ if peer.tunnel != "up":
155
+ peer = devicectl.list_devices().get(self.udid.upper()) or peer
156
+ except devicectl.DevicectlError as error:
157
+ self.step("tunnel", FAIL, "Encrypted tunnel", f"couldn't be brought up: {error}")
158
+ return False
159
+ self.peer = peer
160
+ self.fields = dict(peer.fields) or self.fields
161
+ if peer.tunnel != "up":
162
+ self.step("tunnel", FAIL, "Encrypted tunnel", f"not up (tunnelState {peer.tunnel_state or 'missing'})")
163
+ return False
164
+ try:
165
+ address = check_target(peer.address)
166
+ except ValueError:
167
+ self.step("tunnel", FAIL, "Encrypted tunnel", f"up, but its address ({peer.address or 'missing'}) isn't "
168
+ "an fd00::/8 tunnel address, so Mobster won't use it")
169
+ return False
170
+ interface, problem = network.tunnel_route(address)
171
+ if problem:
172
+ self.step("tunnel", FAIL, "Encrypted tunnel", f"up, but {problem}, so Mobster won't use it")
173
+ return False
174
+ self.address = address
175
+ self.step("tunnel", OK, "Encrypted tunnel", f"up, the phone's end is {address} (through {interface})")
176
+ return True
177
+
178
+ def _readers(self):
179
+ lines, clear = [], True
180
+ for name, understood in (("lockState", self._lock_line), ("apps", self._apps_line)):
181
+ try:
182
+ document = devicectl.run_json(["device", "info", name, "--device", self.udid], devicectl.APPS_TIMEOUT)
183
+ result = document.get("result") if isinstance(document, dict) else None
184
+ keys = ", ".join(sorted(result)) if isinstance(result, dict) else "no result"
185
+ line = understood(document)
186
+ except (devicectl.DevicectlError, RuntimeError) as error:
187
+ line, keys = str(error), "none"
188
+ if line is None:
189
+ clear = False
190
+ line = "unreadable"
191
+ lines.append(f"{name}: {line} (fields: {keys})")
192
+ self.step("readers", INFO if clear else WARN, "Reads through CoreDevice", "; ".join(lines))
193
+
194
+ @staticmethod
195
+ def _lock_line(document):
196
+ locked = devicectl.lock_state(document)
197
+ return None if locked is None else "locked" if locked else "unlocked"
198
+
199
+ @staticmethod
200
+ def _apps_line(document):
201
+ try:
202
+ return (
203
+ f"{len(devicectl.parse_apps(document))} user apps, {len(devicectl.parse_apps(document, 'system'))} "
204
+ "Apple apps")
205
+ except devicectl.DevicectlError:
206
+ return None
207
+
208
+ def _wda(self):
209
+ """WebDriverAgent on the tunnel address, through a relay on 127.0.0.1 (any free port)."""
210
+ relay = Relay(self.address, [(0, 8100), (0, 9100)], name="wifi-probe")
211
+ try:
212
+ relay.start()
213
+ except OSError as error:
214
+ self.step("wda", FAIL, "WebDriverAgent over the tunnel", f"the probe's relay couldn't start: {error}")
215
+ return False
216
+ try:
217
+ api, video = relay.ports
218
+ status = http_status(api)
219
+ if status is None:
220
+ self.step("wda", WARN, "WebDriverAgent over the tunnel",
221
+ "doesn't answer on [tunnel]:8100 (yet). Start Mobster's helper over Wi-Fi: the Mac app "
222
+ "with Wi-Fi for iPhones on, or this probe with --start.")
223
+ return False
224
+ ready = status.get("ready") is True
225
+ streams = stream_answers(video)
226
+ self.step("wda", OK if ready else WARN, "WebDriverAgent over the tunnel",
227
+ f"/status answers through 127.0.0.1 ({'ready' if ready else 'not ready'}); video port "
228
+ f"{'answers' if streams else 'does not answer'}")
229
+ return ready
230
+ finally:
231
+ relay.stop()
232
+
233
+ def _launch(self):
234
+ """--start: launch Mobster's helper over Wi-Fi for this probe, and wait for it."""
235
+ from ..device_manager import wifi_runner_command
236
+ from ..mac_tools import tool
237
+ from . import xctestrun
238
+ manager = self.manager
239
+ if manager is None or not manager.built():
240
+ self.step("runner", FAIL, "Mobster's helper over Wi-Fi", "isn't built for this iPhone. Set it up with "
241
+ "the cable first.")
242
+ return False
243
+ xcodebuild = tool("xcodebuild")
244
+ if not xcodebuild:
245
+ self.step("runner", FAIL, "Mobster's helper over Wi-Fi", "Xcode isn't installed or set up")
246
+ return False
247
+ try:
248
+ plan = xctestrun.write(manager.derived / "Build" / "Products", manager.derived / xctestrun.FOLDER,
249
+ self.address)
250
+ except (LookupError, ValueError) as error:
251
+ self.step("runner", FAIL, "Mobster's helper over Wi-Fi", str(error))
252
+ return False
253
+ log_path = Path(manager.logs) / "wifi-probe.log"
254
+ log_path.parent.mkdir(parents=True, exist_ok=True)
255
+ with open(log_path, "ab") as log:
256
+ process = subprocess.Popen(wifi_runner_command(xcodebuild, plan, self.udid), stdout=log,
257
+ stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, start_new_session=True,
258
+ env=manager.runner_env())
259
+ self._process = process
260
+ relay = Relay(self.address, [(0, 8100)], name="wifi-probe")
261
+ relay.start()
262
+ try:
263
+ deadline = time.monotonic() + START_WAIT
264
+ while time.monotonic() < deadline and process.poll() is None:
265
+ if (http_status(relay.ports[0], timeout=2) or {}).get("ready") is True:
266
+ self.step("runner", OK, "Mobster's helper over Wi-Fi",
267
+ f"started with USE_IP={self.address} (log: {log_path.name})")
268
+ return True
269
+ time.sleep(2)
270
+ finally:
271
+ relay.stop()
272
+ self.step("runner", FAIL, "Mobster's helper over Wi-Fi",
273
+ f"didn't answer within {int(START_WAIT)} s. See {log_path} for xcodebuild's output.")
274
+ return False
275
+
276
+ def stop_launched(self):
277
+ process = getattr(self, "_process", None)
278
+ if process is None or process.poll() is not None:
279
+ return
280
+ for sig in (signal.SIGTERM, signal.SIGKILL):
281
+ try:
282
+ os.killpg(process.pid, sig)
283
+ process.wait(timeout=5)
284
+ return
285
+ except (ProcessLookupError, PermissionError):
286
+ return
287
+ except subprocess.TimeoutExpired:
288
+ continue
289
+
290
+ def _app_relay(self):
291
+ if not self.slot_port:
292
+ return
293
+ status = http_status(self.slot_port, timeout=2)
294
+ self.step("app_relay", INFO, "The Mac app's relay",
295
+ f"127.0.0.1:{self.slot_port} " + ("answers" if status is not None else "doesn't answer (fine when "
296
+ "the app isn't running it)"))
297
+
298
+ def _lan(self):
299
+ names = list(self.peer.hostnames) if self.peer is not None else []
300
+ addresses, hits = exposure.exposed(names)
301
+ if hits:
302
+ self.step("lan", FAIL, "The phone's Wi-Fi address",
303
+ "WebDriverAgent ANSWERS on " + ", ".join(hits) + ". Mobster stops a helper that does this "
304
+ "when it starts it. Turn Wi-Fi off for this phone (`mobster wifi off`) and report this.")
305
+ elif addresses:
306
+ self.step("lan", OK, "The phone's Wi-Fi address",
307
+ f"{', '.join(addresses)}: 8100 and 9100 don't answer (also check from another machine)")
308
+ else:
309
+ self.step("lan", WARN, "The phone's Wi-Fi address",
310
+ "couldn't be found. From another machine on the same Wi-Fi, check that `nc -vz <the phone's "
311
+ "Wi-Fi address> 8100` and 9100 fail.")
312
+
313
+ def _usbmux(self):
314
+ listed = usbmux_network(self.udid)
315
+ self.step("usbmux", INFO, "usbmuxd over the network",
316
+ {True: "lists the phone (evidence only; Mobster never uses it for WebDriverAgent)",
317
+ False: "doesn't list the phone", None: "couldn't tell (idevice_id is missing)"}[listed])
318
+
319
+
320
+ def describe(item):
321
+ glyph = {OK: "✓", WARN: "!", FAIL: "✗", INFO: "·"}[item["state"]]
322
+ return f" {glyph} {item['title']:<32} {item['detail']}"
@@ -0,0 +1,8 @@
1
+ """Wi-Fi's routes and transport monitor, and nothing a task sees: a task runs the same on the cable and on Wi-Fi."""
2
+
3
+
4
+ def register(api):
5
+ """``api`` is mobile_agent.harness_api."""
6
+ from . import api as routes
7
+ routes.register_routes()
8
+ api.register_service(routes.start_service, routes.close_service)
@@ -0,0 +1,179 @@
1
+ """The local relay for a phone on Wi-Fi (SPEC §3.7 item 4): 127.0.0.1-only forwarders to the phone's tunnel."""
2
+
3
+ import ipaddress
4
+ import socket
5
+ import threading
6
+
7
+ from ..devices import LOOPBACK
8
+
9
+ TUNNEL_NETWORK = ipaddress.IPv6Network("fd00::/8")
10
+ NOT_AVAILABLE = "Wi-Fi isn't available for this iPhone yet."
11
+ CONNECT_TIMEOUT = 5.0
12
+ MAX_CONNECTIONS = 64
13
+ BUFFER = 65536
14
+
15
+
16
+ # Only the phone's end of the CoreDevice tunnel: the relay never carries WebDriverAgent over the plain LAN.
17
+ def check_target(address):
18
+ if not isinstance(address, str) or not address.strip() or "%" in address:
19
+ raise ValueError(NOT_AVAILABLE)
20
+ try:
21
+ parsed = ipaddress.ip_address(address.strip().strip("[]"))
22
+ except ValueError:
23
+ raise ValueError(NOT_AVAILABLE) from None
24
+ if not isinstance(parsed, ipaddress.IPv6Address) or parsed.ipv4_mapped is not None or parsed not in TUNNEL_NETWORK:
25
+ raise ValueError(NOT_AVAILABLE)
26
+ return parsed.compressed
27
+
28
+
29
+ def require_tunnel(address, through=None):
30
+ target = check_target(address)
31
+ if through is None:
32
+ from .network import through_tunnel as through
33
+ if not through(target):
34
+ raise ValueError(NOT_AVAILABLE)
35
+ return target
36
+
37
+
38
+ class Relay:
39
+ """Forwards 127.0.0.1:<local> to [target]:<remote> for each ``(local, remote)`` pair, until ``stop``."""
40
+
41
+ def __init__(self, target, pairs, *, connect_timeout=CONNECT_TIMEOUT, name="wifi-relay"):
42
+ self.target = check_target(target)
43
+ self.pairs = []
44
+ for local, remote in pairs:
45
+ if not all(isinstance(port, int) and not isinstance(port, bool) for port in (local, remote)) \
46
+ or not 0 <= local <= 65535 or not 1 <= remote <= 65535:
47
+ raise ValueError("A relay forwards a port number to a port number")
48
+ self.pairs.append((local, remote))
49
+ if not self.pairs:
50
+ raise ValueError("A relay needs at least one port")
51
+ self.connect_timeout = connect_timeout
52
+ self.name = name
53
+ self.listeners = []
54
+ self.connections = set()
55
+ self.slots = threading.BoundedSemaphore(MAX_CONNECTIONS)
56
+ self.lock = threading.Lock()
57
+ self.stopping = threading.Event()
58
+
59
+ @property
60
+ def running(self):
61
+ return bool(self.listeners) and not self.stopping.is_set()
62
+
63
+ @property
64
+ def ports(self):
65
+ """The local ports, in the order of ``pairs`` (after ``start``)."""
66
+ return [listener.getsockname()[1] for listener, _ in self.listeners]
67
+
68
+ def start(self):
69
+ """Listen on every local port, or none: OSError (a port in use) leaves nothing open."""
70
+ if self.running:
71
+ return self
72
+ self.stopping.clear()
73
+ opened = []
74
+ try:
75
+ for local, remote in self.pairs:
76
+ listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
77
+ opened.append((listener, remote))
78
+ listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
79
+ listener.bind((LOOPBACK, local))
80
+ listener.listen(16)
81
+ except OSError:
82
+ for listener, _ in opened:
83
+ listener.close()
84
+ raise
85
+ self.listeners = opened
86
+ for listener, remote in opened:
87
+ threading.Thread(target=self._accept, args=(listener, remote), daemon=True,
88
+ name=f"mobster-{self.name}-{listener.getsockname()[1]}").start()
89
+ return self
90
+
91
+ def stop(self):
92
+ self.stopping.set()
93
+ for listener, _ in self.listeners:
94
+ try:
95
+ listener.close()
96
+ except OSError:
97
+ pass
98
+ with self.lock:
99
+ live = list(self.connections)
100
+ self.connections.clear()
101
+ for sock in live:
102
+ _close(sock)
103
+
104
+ # -- forwarding ------------------------------------------------------------------------------------------------
105
+
106
+ def _accept(self, listener, remote):
107
+ while not self.stopping.is_set():
108
+ try:
109
+ client, _ = listener.accept()
110
+ except OSError:
111
+ return
112
+ if not self.slots.acquire(blocking=False):
113
+ _close(client)
114
+ continue
115
+ with self.lock:
116
+ if self.stopping.is_set():
117
+ self.slots.release()
118
+ _close(client)
119
+ continue
120
+ # Count and track clients before upstream connects: a missing
121
+ # tunnel must not leave unbounded pending threads or sockets.
122
+ self.connections.add(client)
123
+ threading.Thread(target=self._serve, args=(client, remote), daemon=True,
124
+ name=f"mobster-{self.name}-conn").start()
125
+
126
+ def _serve(self, client, remote):
127
+ upstream = None
128
+ try:
129
+ try:
130
+ upstream = socket.create_connection((self.target, remote), timeout=self.connect_timeout)
131
+ except OSError:
132
+ return
133
+ with self.lock:
134
+ if self.stopping.is_set():
135
+ return
136
+ upstream.settimeout(None)
137
+ client.settimeout(None)
138
+ self.connections.add(upstream)
139
+ other = threading.Thread(target=self._pump, args=(upstream, client), daemon=True,
140
+ name=f"mobster-{self.name}-pump")
141
+ other.start()
142
+ self._pump(client, upstream)
143
+ other.join()
144
+ finally:
145
+ with self.lock:
146
+ self.connections.discard(client)
147
+ self.connections.discard(upstream)
148
+ _close(client)
149
+ if upstream is not None:
150
+ _close(upstream)
151
+ self.slots.release()
152
+
153
+ def _pump(self, source, target):
154
+ """Copy ``source`` to ``target`` until ``source`` ends; then end ``target``'s sending side."""
155
+ try:
156
+ while True:
157
+ data = source.recv(BUFFER)
158
+ if not data:
159
+ break
160
+ target.sendall(data)
161
+ except OSError:
162
+ _close(source)
163
+ _close(target)
164
+ return
165
+ try:
166
+ target.shutdown(socket.SHUT_WR)
167
+ except OSError:
168
+ pass
169
+
170
+
171
+ def _close(sock):
172
+ try:
173
+ sock.shutdown(socket.SHUT_RDWR)
174
+ except OSError:
175
+ pass
176
+ try:
177
+ sock.close()
178
+ except OSError:
179
+ pass
@@ -0,0 +1,87 @@
1
+
2
+ import json
3
+ import os
4
+ import tempfile
5
+ import time
6
+ from pathlib import Path
7
+
8
+ from .probe import FAIL, WARN
9
+
10
+ FILE = "wifi-probe.json"
11
+ NOT_RUN = {"state": "not_run", "at": None, "reason": None}
12
+ KEEP = 20
13
+ UNFINISHED = "The check stopped before it could tell. Run it again with the phone unplugged."
14
+
15
+
16
+ def _path(data_dir):
17
+ return Path(data_dir) / FILE
18
+
19
+
20
+ def _read(data_dir):
21
+ try:
22
+ value = json.loads(_path(data_dir).read_text())
23
+ except (OSError, ValueError):
24
+ return {}
25
+ return value if isinstance(value, dict) else {}
26
+
27
+
28
+ def _clean(value):
29
+ if not isinstance(value, dict) or value.get("state") not in ("passed", "failed"):
30
+ return dict(NOT_RUN)
31
+ at, reason = value.get("at"), value.get("reason")
32
+ return {"state": value["state"], "at": at if isinstance(at, int) and not isinstance(at, bool) else None,
33
+ "reason": reason[:300] if isinstance(reason, str) and reason else None}
34
+
35
+
36
+ def reason_of(steps):
37
+ """Why a probe did not pass, in the words of the step that failed (else warned): "<title>: <detail>"."""
38
+ for state in (FAIL, WARN):
39
+ step = next((item for item in steps if item.get("state") == state), None)
40
+ if step is not None:
41
+ detail = str(step.get("detail") or "").strip()
42
+ return f"{step.get('title')}: {detail}" if detail else str(step.get("title"))
43
+ return UNFINISHED
44
+
45
+
46
+ def record(data_dir, udid, public, now=None):
47
+ steps = public.get("steps") or []
48
+ usb = next((item for item in steps if item.get("key") == "usb"), None)
49
+ # a probe made with the cable plugged in says nothing about Wi-Fi, so it isn't kept
50
+ if usb is not None and str(usb.get("detail", "")).startswith("plugged in"):
51
+ return None
52
+ entry = {"state": "passed", "at": None, "reason": None} if public.get("ok") else \
53
+ {"state": "failed", "at": None, "reason": reason_of(steps)}
54
+ entry["at"] = int((time.time() if now is None else now) * 1000)
55
+ kept = _read(data_dir)
56
+ kept[str(udid).upper()] = entry
57
+ for old in sorted(kept, key=lambda key: _clean(kept[key])["at"] or 0)[:-KEEP]:
58
+ del kept[old]
59
+ Path(data_dir).mkdir(parents=True, exist_ok=True)
60
+ handle, temp = tempfile.mkstemp(dir=str(data_dir), prefix=".wifi-probe-")
61
+ try:
62
+ with os.fdopen(handle, "w") as out:
63
+ json.dump(kept, out, separators=(",", ":"))
64
+ os.chmod(temp, 0o600)
65
+ os.replace(temp, _path(data_dir))
66
+ except OSError:
67
+ try:
68
+ os.unlink(temp)
69
+ except OSError:
70
+ pass
71
+ return None
72
+ return entry
73
+
74
+
75
+ def get(data_dir, udid):
76
+ """``udid``'s last probe, or not_run."""
77
+ return _clean(_read(data_dir).get(str(udid).upper()))
78
+
79
+
80
+ def latest(data_dir):
81
+ """This Mac's most recent probe of any phone, with the phone's udid; not_run when none was kept."""
82
+ kept = {udid: _clean(value) for udid, value in _read(data_dir).items()}
83
+ kept = {udid: value for udid, value in kept.items() if value["state"] != "not_run"}
84
+ if not kept:
85
+ return dict(NOT_RUN)
86
+ udid = max(kept, key=lambda key: kept[key]["at"] or 0)
87
+ return {**kept[udid], "udid": udid}
@@ -0,0 +1,85 @@
1
+ """Which phones may be reached over Wi-Fi: ``wifi: {enabled, enabledAt, via}`` per phone (SPEC §3.7). ``via`` says
2
+ who turned on the phone's own switch (Connect via network); with Wi-Fi off it is kept only while that switch is one
3
+ Mobster turned on and couldn't turn off yet, so a later `mobster wifi off` over the cable still can."""
4
+
5
+ import time
6
+ from pathlib import Path
7
+
8
+ VIA = ("mobster", "xcode")
9
+ OFF = {"enabled": False, "enabledAt": None, "via": None}
10
+
11
+
12
+ def _clean(value):
13
+ if not isinstance(value, dict):
14
+ return dict(OFF)
15
+ via = value.get("via") if value.get("via") in VIA else None
16
+ if value.get("enabled") is not True:
17
+ return {**OFF, "via": via}
18
+ at = value.get("enabledAt")
19
+ return {"enabled": True, "enabledAt": at if isinstance(at, int) and not isinstance(at, bool) else None,
20
+ "via": via}
21
+
22
+
23
+ def _primary(data_dir):
24
+ from ..cli_constants import DEFAULT_WDA_URL
25
+ from ..device_manager import DeviceManager
26
+ return DeviceManager(Path(data_dir), DEFAULT_WDA_URL)
27
+
28
+
29
+ def _same(a, b):
30
+ return isinstance(a, str) and isinstance(b, str) and a.upper() == b.upper()
31
+
32
+
33
+ def get(data_dir, udid):
34
+ """{"enabled", "enabledAt", "via"} for ``udid``; off when it isn't set up here or never was turned on."""
35
+ from ..devices import DeviceStore
36
+ entry = next((item for item in DeviceStore(data_dir).phones() if _same(item["udid"], udid)), None)
37
+ if entry is not None:
38
+ return _clean(entry.get("wifi"))
39
+ settings = _primary(data_dir).settings() if Path(data_dir, "device.json").exists() else {}
40
+ wifi = settings.get("wifi")
41
+ # the primary's entry keeps the UDID it belongs to, so choosing another primary phone never carries it over
42
+ if _same(settings.get("udid"), udid) and isinstance(wifi, dict) and _same(wifi.get("udid"), udid):
43
+ return _clean(wifi)
44
+ return dict(OFF)
45
+
46
+
47
+ def enabled(data_dir, udid):
48
+ return get(data_dir, udid)["enabled"]
49
+
50
+
51
+ def is_set_up(data_dir, udid):
52
+ """True when ``udid`` is the phone chosen in Setup or one added besides it."""
53
+ from ..devices import DeviceStore
54
+ if any(_same(item["udid"], udid) for item in DeviceStore(data_dir).phones()):
55
+ return True
56
+ return _same(_primary(data_dir).settings().get("udid"), udid)
57
+
58
+
59
+ def put(data_dir, udid, on, via=None):
60
+ from ..devices import DeviceStore
61
+ if via is not None and via not in VIA:
62
+ raise ValueError("via is mobster or xcode")
63
+ value = ({"enabled": True, "enabledAt": round(time.time() * 1000), "via": via} if on else {**OFF, "via": via})
64
+ kept = on or via is not None
65
+ store = DeviceStore(data_dir)
66
+ if any(_same(item["udid"], udid) for item in store.phones()):
67
+ store.set_wifi(udid, value if kept else None)
68
+ return _clean(value)
69
+ manager = _primary(data_dir)
70
+ if not _same(manager.settings().get("udid"), udid):
71
+ raise LookupError("not_set_up")
72
+ manager.save_settings(wifi={**value, "udid": udid} if kept else None)
73
+ return _clean(value)
74
+
75
+
76
+ def enabled_udids(data_dir):
77
+ """The UDIDs (upper case) with Wi-Fi on, of every phone set up here."""
78
+ from ..devices import DeviceStore
79
+ found = {item["udid"].upper() for item in DeviceStore(data_dir).phones() if _clean(item.get("wifi"))["enabled"]}
80
+ if Path(data_dir, "device.json").exists():
81
+ settings = _primary(data_dir).settings()
82
+ wifi = settings.get("wifi")
83
+ if isinstance(wifi, dict) and _same(settings.get("udid"), wifi.get("udid")) and _clean(wifi)["enabled"]:
84
+ found.add(settings["udid"].upper())
85
+ return found