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,332 @@
1
+ """The transport monitor: the service that keeps each phone on the cable or on Wi-Fi (SPEC §3.7 items 2, 5-7)."""
2
+
3
+ import logging
4
+ import threading
5
+ import time
6
+
7
+ from . import devicectl, store, transport
8
+ from .relay import check_target
9
+
10
+ log = logging.getLogger("mobster.wireless")
11
+
12
+ TICK = 10.0
13
+ SLOW = 60.0 # devicectl at most this often while every phone with Wi-Fi on is on the cable
14
+ START_TIMEOUT = 120.0 # a runner started over Wi-Fi answers within this, or it is stopped and retried
15
+ LOST_AFTER = 30.0 # a Wi-Fi runner that stops answering for this long (and no task) is restarted
16
+ NETWORK_EVERY = 60.0 # the network check, at most this often while a phone with Wi-Fi on can't be found
17
+
18
+
19
+ def task_on(runtime, phone, udid):
20
+ # when it can't be told, it counts as busy
21
+ try:
22
+ keys = dict.fromkeys(key for key in (udid, getattr(phone, "key", None)) if key)
23
+ if any(runtime.device_busy(key) for key in keys):
24
+ return True
25
+ except Exception: # noqa: BLE001
26
+ return True
27
+ in_use = getattr(getattr(runtime, "fleet", None), "in_use", None)
28
+ if not callable(in_use):
29
+ return False
30
+ try:
31
+ return bool(in_use(phone))
32
+ except Exception: # noqa: BLE001
33
+ return True
34
+
35
+
36
+ class Monitor:
37
+ def __init__(self, runtime, *, lister=None, bring_up=None, attached=None, nwi=None, clock=None, publish=None,
38
+ through=None, network_check=None):
39
+ from ..mac_tools import usb_attached
40
+ from . import network
41
+ self.runtime = runtime
42
+ self.through = through or network.through_tunnel
43
+ self.network_check = network_check or network.peer_check
44
+ self.network = None # the last network.peer_check result, or None before one ran
45
+ self.network_at = None
46
+ self.lister = lister or devicectl.list_devices
47
+ self.bring_up = bring_up or devicectl.bring_up
48
+ self.attached = attached or usb_attached
49
+ self.nwi = nwi or (lambda: network.signature(network.nwi()))
50
+ self.clock = clock or time.monotonic
51
+ self.publish = publish or getattr(runtime, "publish", None) or (lambda topic, event: None)
52
+ self.backoffs = {} # UDID -> transport.Backoff
53
+ self.starting = {} # UDID -> when a Wi-Fi start began
54
+ self.silent = {} # UDID -> since when a Wi-Fi runner stopped answering
55
+ self.peers = {} # UDID -> devicectl.Peer
56
+ self.peers_at = None
57
+ self.devicectl_problem = None
58
+ self.net = None
59
+ self.known = set()
60
+ self.lock = threading.Lock()
61
+ self.stopping = threading.Event()
62
+ self.kicked = threading.Event()
63
+ self.thread = None
64
+
65
+ # -- the service ----------------------------------------------------------------------------------------------
66
+
67
+ @property
68
+ def running(self):
69
+ return self.thread is not None and self.thread.is_alive()
70
+
71
+ def start(self):
72
+ if self.running:
73
+ return
74
+ self.stopping.clear()
75
+ self.thread = threading.Thread(target=self._loop, name="mobster-wifi-monitor", daemon=True)
76
+ self.thread.start()
77
+
78
+ def stop(self, timeout=2.0):
79
+ self.stopping.set()
80
+ self.kicked.set()
81
+ if self.thread is not None:
82
+ self.thread.join(timeout)
83
+
84
+ def kick(self, udid=None):
85
+ """Look again now; with ``udid``, forget its backoff first (Try again, Wi-Fi just turned on)."""
86
+ if udid:
87
+ self.backoff(udid).reset()
88
+ self.peers_at = None
89
+ self.kicked.set()
90
+
91
+ def _loop(self):
92
+ while not self.stopping.is_set() and not getattr(self.runtime, "closing", False):
93
+ try:
94
+ self.tick()
95
+ except Exception: # noqa: BLE001 -- the monitor never stops Mobster
96
+ log.exception("the Wi-Fi monitor's check failed")
97
+ self.kicked.wait(self.pause())
98
+ self.kicked.clear()
99
+
100
+ def pause(self):
101
+ now = self.clock()
102
+ due = [backoff.next_at - now for backoff in list(self.backoffs.values()) if backoff.failures]
103
+ return max(0.5, min([TICK, *due]))
104
+
105
+ def backoff(self, udid):
106
+ return self.backoffs.setdefault(str(udid).upper(), transport.Backoff())
107
+
108
+ # -- one look at every phone ----------------------------------------------------------------------------------
109
+
110
+ def tick(self):
111
+ with self.lock:
112
+ if getattr(self.runtime, "closing", False):
113
+ return
114
+ fleet = getattr(self.runtime, "fleet", None)
115
+ if fleet is None or fleet.data_dir is None:
116
+ return
117
+ now = self.clock()
118
+ phones = fleet.usb_managers()
119
+ enabled = store.enabled_udids(fleet.data_dir)
120
+ signature = self.nwi()
121
+ if signature is not None and self.net is not None and signature != self.net:
122
+ # Another Wi-Fi, a new address, a VPN: the tunnel is set up again, so look again at once.
123
+ for backoff in list(self.backoffs.values()):
124
+ backoff.reset()
125
+ self.peers_at = None
126
+ self.network_at = None
127
+ if signature is not None:
128
+ self.net = signature
129
+ usb = {udid.upper(): self._usb(udid) for _, udid, _ in phones}
130
+ off_cable = [udid for _, udid, _ in phones if udid.upper() in enabled and usb[udid.upper()] is not True]
131
+ due = SLOW if not off_cable else TICK * 0.9
132
+ if enabled and (self.peers_at is None or now - self.peers_at >= due):
133
+ self._refresh(now)
134
+ away = [udid for udid in off_cable if not self.peers.get(udid.upper()) or
135
+ not self.peers[udid.upper()].network]
136
+ if away and (self.network_at is None or now - self.network_at >= NETWORK_EVERY):
137
+ self._check_network(now)
138
+ managed = set()
139
+ for phone, udid, manager in phones:
140
+ managed.add(udid.upper())
141
+ try:
142
+ self._step(phone, udid, manager, udid.upper() in enabled, usb[udid.upper()], now)
143
+ except Exception: # noqa: BLE001 -- one phone never stops the others
144
+ log.exception("the Wi-Fi monitor's check of one phone failed")
145
+ for gone in self.known - managed:
146
+ transport.forget(gone)
147
+ self.known = managed
148
+
149
+ def busy(self, phone, udid):
150
+ return task_on(self.runtime, phone, udid)
151
+
152
+ def _usb(self, udid):
153
+ try:
154
+ return self.attached(udid)
155
+ except Exception:
156
+ return None
157
+
158
+ def _refresh(self, now):
159
+ self.peers_at = now
160
+ try:
161
+ self.peers = self.lister()
162
+ self.devicectl_problem = None
163
+ except devicectl.DevicectlError as error:
164
+ self.peers = {}
165
+ if error.code == "hang":
166
+ self.devicectl_problem = "devicectl_hang"
167
+ else:
168
+ self.devicectl_problem = "no_xcode" if error.code == "no_xcode" else "not_seen"
169
+ except RuntimeError:
170
+ self.peers, self.devicectl_problem = {}, "not_seen" # MOBSTER_NO_DEVICES=1
171
+
172
+ def _check_network(self, now):
173
+ self.network_at = now
174
+ try:
175
+ self.network = self.network_check()
176
+ except Exception: # noqa: BLE001 -- a check that fails says nothing
177
+ log.exception("the Wi-Fi monitor's network check failed")
178
+ self.network = None
179
+
180
+ def network_problem(self):
181
+ from .words import NETWORK_PROBLEMS
182
+ return NETWORK_PROBLEMS.get((self.network or {}).get("state"))
183
+
184
+ def _tunnel(self, udid):
185
+ peer = self.peers.get(udid.upper())
186
+ if peer is None or not peer.network:
187
+ return ("unknown" if peer is None else "down"), None, False
188
+ address = None
189
+ if peer.tunnel == "up" and peer.address:
190
+ try:
191
+ address = check_target(peer.address)
192
+ except ValueError:
193
+ return "up", None, True
194
+ # only an address this Mac reaches through its CoreDevice tunnel, never through Wi-Fi, Ethernet or a VPN
195
+ if not self.through(address):
196
+ return "up", None, True
197
+ return peer.tunnel, address, True
198
+
199
+ @staticmethod
200
+ def _now(manager):
201
+ """(what the phone's runner uses now, or None when it isn't running)."""
202
+ running = manager.runner is not None and manager.runner.running
203
+ return getattr(manager, "transport", None) if running else None
204
+
205
+ def _step(self, phone, udid, manager, enabled, usb, now):
206
+ key = udid.upper()
207
+ current = self._now(manager)
208
+ tunnel, address, seen = self._tunnel(udid) if enabled else ("unknown", None, False)
209
+ reachable = bool(current == "wifi" and address is None and manager.wda_ready(timeout=1.5))
210
+ route_lost = False
211
+ if current == "wifi" and enabled and usb is not True and reachable and address is None:
212
+ # Answering over Wi-Fi: devicectl not answering (or not listing it this time) doesn't end a working link,
213
+ # while this Mac still reaches its address through the tunnel.
214
+ route_lost = not self.through(manager.wifi_address)
215
+ if not route_lost:
216
+ address, tunnel = manager.wifi_address, "up"
217
+ desired = transport.choose(usb is True, enabled, address is not None)
218
+ busy = self.busy(phone, udid)
219
+ step = transport.next_step(current, desired, busy)
220
+ if step == "stay" and current == "wifi" and address and manager.wifi_address != address and not busy:
221
+ step = "switch" # the tunnel came back with another address: start again on it
222
+ backoff = self.backoff(udid)
223
+ problem, waiting = None, step == "wait"
224
+
225
+ if step == "switch" and desired == "wifi":
226
+ problem, _started = self._start_wifi(udid, manager, address, backoff, now)
227
+ elif step == "switch" and current == "wifi":
228
+ manager.stop_runner()
229
+ self.starting.pop(key, None)
230
+ self.silent.pop(key, None)
231
+ if desired == "usb":
232
+ manager.autostart() # the cable's runner, as at launch
233
+ else:
234
+ backoff.fail(now)
235
+ problem = "route" if route_lost else "tunnel_down" if seen else (self.devicectl_problem or "not_seen")
236
+ elif desired is None and enabled and usb is not True and current != "wifi" and not busy:
237
+ problem = self._bring_up(udid, tunnel, seen, backoff, now)
238
+
239
+ current = self._now(manager)
240
+ reachable = bool(current is not None and manager.wda_ready(timeout=1.5))
241
+ if current == "wifi":
242
+ problem = self._watch_wifi(udid, manager, reachable, busy, backoff, now) or problem
243
+ current = self._now(manager)
244
+ reachable = reachable and current == "wifi"
245
+ # Switching: a change waits for a task, or a start over Wi-Fi hasn't answered yet.
246
+ switching = waiting or (current == "wifi" and key in self.starting and not reachable)
247
+ if not enabled:
248
+ problem = None
249
+ elif problem == "not_seen":
250
+ problem = self.network_problem() or problem
251
+ before, after = transport.update(udid, transport=current, tunnel=tunnel if enabled else "unknown",
252
+ reachable=reachable, problem=problem, switching=bool(switching))
253
+ if before is None or (before.transport, before.reachable) != (after.transport, after.reachable):
254
+ self.publish("devices", {"event": "transport_changed", "device": udid, "transport": after.transport,
255
+ "reachable": after.reachable})
256
+
257
+ def _start_wifi(self, udid, manager, address, backoff, now):
258
+ if not manager.settings().get("autostart"):
259
+ return None, False # stopped in Setup: stays stopped on Wi-Fi too
260
+ if not backoff.ready(now):
261
+ return ("runner" if backoff.failures else None), False
262
+ try:
263
+ manager.start_wifi_runner(address, self.peers[udid.upper()].udid)
264
+ except LookupError as error:
265
+ backoff.fail(now)
266
+ text = str(error)
267
+ return ("identity" if "different iPhone" in text else "address" if "isn't available" in text else
268
+ "port_busy" if "port" in text else "not_set_up" if "Build" in text or "Setup" in text else
269
+ "runner"), False
270
+ self.starting[udid.upper()] = now
271
+ self.silent.pop(udid.upper(), None)
272
+ return None, True
273
+
274
+ def _watch_wifi(self, udid, manager, reachable, busy, backoff, now):
275
+ key = udid.upper()
276
+ if reachable:
277
+ self.starting.pop(key, None)
278
+ self.silent.pop(key, None)
279
+ backoff.reset()
280
+ return None
281
+ if busy:
282
+ return None
283
+ started = self.starting.get(key)
284
+ if started is not None:
285
+ if now - started < START_TIMEOUT:
286
+ return None
287
+ else:
288
+ since = self.silent.setdefault(key, now)
289
+ if now - since < LOST_AFTER:
290
+ return None
291
+ manager.stop_runner()
292
+ self.starting.pop(key, None)
293
+ self.silent.pop(key, None)
294
+ backoff.fail(now)
295
+ return "runner"
296
+
297
+ def _bring_up(self, udid, tunnel, seen, backoff, now):
298
+ """Unplugged with Wi-Fi on and no usable tunnel: ask CoreDevice to bring it up, at the backoff's pace."""
299
+ if self.devicectl_problem in ("no_xcode", "devicectl_hang"):
300
+ return self.devicectl_problem
301
+ if not seen:
302
+ return "not_seen"
303
+ if tunnel == "up":
304
+ return "address" # up, but its address isn't a tunnel address: never used
305
+ if not backoff.ready(now):
306
+ return "tunnel_down"
307
+ try:
308
+ self.peers.update(self.bring_up(udid))
309
+ self.peers_at = None # read the list again next time
310
+ except devicectl.DevicectlError as error:
311
+ backoff.fail(now)
312
+ if error.code == "hang":
313
+ return "devicectl_hang"
314
+ return "tunnel_down"
315
+ except (RuntimeError, ValueError):
316
+ backoff.fail(now)
317
+ return "tunnel_down"
318
+ self.kicked.set() # look again soon: the tunnel may be up now
319
+ return "tunnel_down"
320
+
321
+ def retire(self):
322
+ with self.lock:
323
+ fleet = getattr(self.runtime, "fleet", None)
324
+ for phone, udid, manager in (fleet.usb_managers() if fleet is not None else ()):
325
+ if getattr(manager, "transport", None) == "wifi" and not self.busy(phone, udid):
326
+ try:
327
+ manager.stop_runner()
328
+ manager.autostart()
329
+ except Exception: # noqa: BLE001
330
+ log.exception("couldn't stop a runner on Wi-Fi")
331
+ transport.forget(udid)
332
+ self.known = set()
@@ -0,0 +1,334 @@
1
+ """This Mac's network, read without changing it: changes, a VPN, Local Network permission, peers and routes."""
2
+
3
+ import errno
4
+ import hashlib
5
+ import ipaddress
6
+ import os
7
+ import re
8
+ import select
9
+ import socket
10
+ import subprocess
11
+ import threading
12
+ import time
13
+
14
+ SCUTIL = "/usr/sbin/scutil"
15
+ ROUTE = "/sbin/route"
16
+ IFCONFIG = "/sbin/ifconfig"
17
+ DNS_SD = "/usr/bin/dns-sd"
18
+ VPN_INTERFACE = re.compile(r"^(utun|ipsec|ppp|tun|tap|wg)\d*$")
19
+ # A network this Mac can share with an iPhone: Wi-Fi or Ethernet (en0, en7, ...), or a bridge.
20
+ LAN_INTERFACE = re.compile(r"^(en|bridge)\d+$")
21
+ # Apple's CoreDevice tunnel to a phone is a utun interface on this Mac that holds this Mac's end of the link: an
22
+ # address on the phone's own /64 (or a narrower network), or a point-to-point address whose peer is the phone. A VPN
23
+ # is a utun too, so the route must also be more than a default route, and the utun not one macOS marks as a VPN.
24
+ TUNNEL_INTERFACE = re.compile(r"^utun\d+$")
25
+ TUNNEL_PREFIX = 64
26
+ # `ifconfig -v`'s marks of a VPN: the IS_VPN interface flag, or a NetworkExtension VPN agent.
27
+ VPN_MARK = re.compile(r"\bIS_VPN\b|agent domain:NetworkExtension type:VPN\b")
28
+ INET6 = re.compile(r"^\s*inet6\s+([0-9a-fA-F:.]+)(?:%\S+)?(?:\s+-->\s+([0-9a-fA-F:.]+))?\s+prefixlen\s+(\d+)", re.M)
29
+ # What phones, TVs, speakers, printers and computers advertise. Info.plist's NSBonjourServices lists the same.
30
+ PEER_TYPES = ("_companion-link._tcp", "_airplay._tcp", "_raop._tcp", "_apple-mobdev2._tcp", "_remotepairing._tcp",
31
+ "_googlecast._tcp", "_hap._tcp", "_ipp._tcp", "_sleep-proxy._udp", "_spotify-connect._tcp")
32
+ BROWSE_SECONDS = 2.5
33
+ # mDNSResponder's "policy denied": macOS's Local Network privacy refused the browse.
34
+ POLICY_DENIED = "-65570"
35
+ LOOPBACK_INDEX = 1
36
+
37
+
38
+ def read_output(argv, timeout=3):
39
+ try:
40
+ completed = subprocess.run(argv, capture_output=True, text=True, timeout=timeout, stdin=subprocess.DEVNULL)
41
+ except (OSError, subprocess.SubprocessError):
42
+ return None
43
+ return completed.stdout if completed.returncode == 0 else None
44
+
45
+
46
+ def nwi(reader=None):
47
+ """`scutil --nwi`: the network state macOS routes by (interfaces, addresses, reachability), or None."""
48
+ return (reader or read_output)([SCUTIL, "--nwi"])
49
+
50
+
51
+ def signature(text):
52
+ if text is None:
53
+ return None
54
+ lines = [" ".join(line.split()) for line in text.splitlines() if line.strip()]
55
+ return hashlib.sha256("\n".join(lines).encode()).hexdigest()[:16]
56
+
57
+
58
+ def ipv4_interfaces(text):
59
+ """The interfaces `scutil --nwi` lists under IPv4, in its order (the primary first)."""
60
+ interfaces, section = [], None
61
+ for line in (text or "").splitlines():
62
+ stripped = line.strip()
63
+ if stripped.startswith("IPv4 network interface information"):
64
+ section = "v4"
65
+ continue
66
+ if stripped.startswith("IPv6 network interface information") or stripped.startswith("Network interfaces:"):
67
+ section = None
68
+ continue
69
+ if section == "v4":
70
+ match = re.match(r"^([a-z]+\d+)\s*:\s*flags", stripped)
71
+ if match:
72
+ interfaces.append(match.group(1))
73
+ return interfaces
74
+
75
+
76
+ def vpn_interface(text):
77
+ """The IPv4 interface of a VPN when one carries this Mac's traffic (utun, ipsec, ppp, ...), else None."""
78
+ return next((name for name in ipv4_interfaces(text) if VPN_INTERFACE.match(name)), None)
79
+
80
+
81
+ def default_gateway(reader=None):
82
+ """The IPv4 default gateway (`route -n get default`), or None."""
83
+ text = (reader or read_output)([ROUTE, "-n", "get", "default"])
84
+ match = re.search(r"^\s*gateway:\s*(\S+)\s*$", text or "", re.M)
85
+ if not match:
86
+ return None
87
+ try:
88
+ return str(ipaddress.IPv4Address(match.group(1)))
89
+ except ValueError:
90
+ return None
91
+
92
+
93
+ # macOS's Local Network privacy refuses with EHOSTUNREACH to a gateway that is there; any other answer means allowed.
94
+ def local_network_allowed(gateway, timeout=0.6, connect=None):
95
+ if not gateway:
96
+ return None
97
+ attempt = connect or socket.create_connection
98
+ try:
99
+ attempt((gateway, 9), timeout).close()
100
+ return True
101
+ except ConnectionRefusedError:
102
+ return True
103
+ except (socket.timeout, TimeoutError):
104
+ return True
105
+ except OSError as error:
106
+ if error.errno in (errno.EHOSTUNREACH, errno.EPERM, errno.EACCES):
107
+ return False
108
+ return None
109
+
110
+
111
+ def resolve(hostnames, timeout=4.0, lookup=None):
112
+ lookup = lookup or socket.getaddrinfo
113
+ found = {"ipv4": [], "ipv6": []}
114
+ for name in hostnames:
115
+ result = []
116
+
117
+ def ask(name=name, result=result):
118
+ try:
119
+ result.extend(lookup(name, None, 0, socket.SOCK_STREAM))
120
+ except (OSError, UnicodeError):
121
+ pass
122
+
123
+ worker = threading.Thread(target=ask, name="mobster-wifi-resolve", daemon=True)
124
+ worker.start()
125
+ worker.join(timeout)
126
+ for family, *_rest, sockaddr in list(result):
127
+ address = sockaddr[0].split("%", 1)[0]
128
+ try:
129
+ parsed = ipaddress.ip_address(address)
130
+ except ValueError:
131
+ continue
132
+ # the tunnel's fd00::/8 end is not an address on the Wi-Fi network
133
+ if parsed.version == 6 and parsed in ipaddress.IPv6Network("fd00::/8"):
134
+ continue
135
+ key = "ipv4" if parsed.version == 4 else "ipv6"
136
+ if sockaddr[0] not in found[key]:
137
+ found[key].append(sockaddr[0])
138
+ return found
139
+
140
+
141
+ def port_open(host, port, timeout=2.0, connect=None):
142
+ attempt = connect or socket.create_connection
143
+ try:
144
+ attempt((host, port), timeout).close()
145
+ return True
146
+ except OSError:
147
+ return False
148
+
149
+
150
+ def primary_interface(reader=None):
151
+ text = (reader or read_output)([ROUTE, "-n", "get", "default"])
152
+ match = re.search(r"^\s*interface:\s*(\S+)\s*$", text or "", re.M)
153
+ return match.group(1) if match else None
154
+
155
+
156
+ def _field(text, key):
157
+ """One ``key: value`` line of `route -n get`'s answer, or None."""
158
+ match = re.search(rf"^\s*{key}:\s*(\S+)\s*$", text or "", re.M)
159
+ return match.group(1) if match else None
160
+
161
+
162
+ def tunnel_route(address, reader=None):
163
+ """(interface, None) when this Mac reaches ``address``, the phone's end of its CoreDevice tunnel, through that
164
+ tunnel; else (the interface or None, why not). Whatever it can't tell apart from a VPN is refused: no route, a
165
+ route through Wi-Fi or Ethernet, only a default route, a utun macOS marks as a VPN, or a utun that holds no
166
+ address on a link to ``address``."""
167
+ read = reader or read_output
168
+ try:
169
+ target = ipaddress.IPv6Address(str(address).strip("[]"))
170
+ except ValueError:
171
+ return None, f"{address} isn't an IPv6 address"
172
+ shown = target.compressed
173
+ route = read([ROUTE, "-n", "get", "-inet6", shown])
174
+ name = _field(route, "interface")
175
+ if not (name and TUNNEL_INTERFACE.match(name)):
176
+ return name, f"this Mac reaches {shown} through {name or 'no interface'}, not a tunnel (utun)"
177
+ if _field(route, "destination") == "default":
178
+ return name, f"this Mac reaches {shown} only through {name}'s default route, which a VPN sets, not a tunnel"
179
+ facts = read([IFCONFIG, "-v", name])
180
+ if facts is None:
181
+ return name, f"this Mac couldn't read {name}'s addresses"
182
+ if VPN_MARK.search(facts):
183
+ return name, f"this Mac reaches {shown} through {name}, which is a VPN, not the tunnel"
184
+ if not link_end(facts, target):
185
+ held = ", ".join(f"{own}/{prefix}" + (f" to {peer}" if peer else "")
186
+ for own, peer, prefix in INET6.findall(facts)
187
+ if not own.lower().startswith("fe80:")) or "only link-local addresses"
188
+ return name, f"{name} holds no address on a link to {shown} ({held}), so it isn't the tunnel to the phone"
189
+ return name, None
190
+
191
+
192
+ def link_end(facts, target):
193
+ """Whether an address `ifconfig` lists in ``facts`` is this Mac's end of a link to ``target``: one whose
194
+ point-to-point peer is ``target``, or whose network, /64 or narrower, holds it."""
195
+ for own, peer, prefix in INET6.findall(facts or ""):
196
+ try:
197
+ own, peer = ipaddress.IPv6Address(own), ipaddress.IPv6Address(peer) if peer else None
198
+ except ValueError:
199
+ continue
200
+ if own.is_link_local or own == target:
201
+ continue
202
+ if peer == target or (TUNNEL_PREFIX <= int(prefix) < 128
203
+ and target in ipaddress.IPv6Network(f"{own}/{prefix}", strict=False)):
204
+ return True
205
+ return False
206
+
207
+
208
+ def through_tunnel(address, reader=None):
209
+ return tunnel_route(address, reader)[1] is None
210
+
211
+
212
+ def parse_browse(text):
213
+ found, denied = [], POLICY_DENIED in (text or "")
214
+ for line in (text or "").splitlines():
215
+ parts = line.split(None, 6)
216
+ if len(parts) == 7 and parts[1] == "Add" and parts[3].isdigit():
217
+ found.append((int(parts[3]), parts[6].strip()))
218
+ return found, denied
219
+
220
+
221
+ def _browse_one(service, seconds, stop, sink):
222
+ # dns-sd buffers its output unless it writes to a terminal, so it gets a pseudo-terminal
223
+ import pty
224
+ try:
225
+ master, slave = pty.openpty()
226
+ except OSError:
227
+ return False
228
+ try:
229
+ process = subprocess.Popen([DNS_SD, "-B", service, "local."], stdin=subprocess.DEVNULL, stdout=slave,
230
+ stderr=slave, start_new_session=True)
231
+ except OSError:
232
+ os.close(master)
233
+ os.close(slave)
234
+ return False
235
+ os.close(slave)
236
+ deadline = time.monotonic() + seconds
237
+ try:
238
+ while time.monotonic() < deadline and not stop.is_set():
239
+ ready, _, _ = select.select([master], [], [], 0.1)
240
+ if not ready:
241
+ continue
242
+ try:
243
+ data = os.read(master, 4096)
244
+ except OSError:
245
+ break
246
+ if not data:
247
+ break
248
+ sink.append(data)
249
+ finally:
250
+ try:
251
+ os.killpg(process.pid, 9)
252
+ except (ProcessLookupError, PermissionError):
253
+ pass
254
+ try:
255
+ process.wait(timeout=2)
256
+ except subprocess.TimeoutExpired:
257
+ pass
258
+ os.close(master)
259
+ return True
260
+
261
+
262
+ def browse(interface_index, types=PEER_TYPES, seconds=BROWSE_SECONDS, settle=1.0):
263
+ if not os.access(DNS_SD, os.X_OK):
264
+ return {service: None for service in types}
265
+ stop = threading.Event()
266
+ sinks = {service: [] for service in types}
267
+ ran = {}
268
+
269
+ def run(service):
270
+ ran[service] = _browse_one(service, seconds, stop, sinks[service])
271
+
272
+ def outputs():
273
+ return {service: b"".join(list(sinks[service])).decode("utf-8", "replace") if ran.get(service, True)
274
+ else None for service in types}
275
+
276
+ workers = [threading.Thread(target=run, args=(service,), name="mobster-wifi-bonjour", daemon=True)
277
+ for service in types]
278
+ began = time.monotonic()
279
+ for worker in workers:
280
+ worker.start()
281
+ while any(worker.is_alive() for worker in workers) and time.monotonic() - began < seconds + 3:
282
+ time.sleep(0.1)
283
+ # after `settle`, this Mac's own services have shown on the loopback interface too
284
+ if time.monotonic() - began >= settle and peers_heard(outputs(), interface_index)[0]:
285
+ stop.set()
286
+ stop.set()
287
+ for worker in workers:
288
+ worker.join(3)
289
+ return outputs()
290
+
291
+
292
+ def peers_heard(outputs, interface_index):
293
+ own, others, denied, ran = set(), [], False, False
294
+ for text in outputs.values():
295
+ if text is None:
296
+ continue
297
+ ran = True
298
+ found, refused = parse_browse(text)
299
+ denied = denied or refused
300
+ # macOS also lists this Mac's own services on the loopback interface: they aren't other devices
301
+ own.update(name for index, name in found if index == LOOPBACK_INDEX)
302
+ others += [name for index, name in found if index == interface_index]
303
+ return sorted({name for name in others if name not in own}), denied, ran
304
+
305
+
306
+ def peer_check(*, interface=None, gateway=None, allowed=None, outputs=None, index_of=None):
307
+ from .. import native_helpers
308
+ if outputs is None and not native_helpers.devices_allowed():
309
+ return {"state": "unknown", "interface": interface, "peers": 0}
310
+ interface = primary_interface() if interface is None else interface
311
+ if not interface:
312
+ return {"state": "offline", "interface": None, "peers": 0}
313
+ if VPN_INTERFACE.match(interface):
314
+ return {"state": "vpn", "interface": interface, "peers": 0}
315
+ if not LAN_INTERFACE.match(interface):
316
+ return {"state": "unknown", "interface": interface, "peers": 0}
317
+ gateway = default_gateway() if gateway is None else gateway
318
+ allowed = local_network_allowed(gateway) if allowed is None else allowed
319
+ if allowed is False:
320
+ return {"state": "no_permission", "interface": interface, "peers": 0}
321
+ try:
322
+ index = (index_of or socket.if_nametoindex)(interface)
323
+ except OSError:
324
+ return {"state": "unknown", "interface": interface, "peers": 0}
325
+ if outputs is None:
326
+ outputs = browse(index)
327
+ names, denied, ran = peers_heard(outputs, index)
328
+ if names:
329
+ return {"state": "ok", "interface": interface, "peers": len(names)}
330
+ if denied:
331
+ return {"state": "no_permission", "interface": interface, "peers": 0}
332
+ if not ran:
333
+ return {"state": "unknown", "interface": interface, "peers": 0}
334
+ return {"state": "blocked", "interface": interface, "peers": 0}