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,188 @@
1
+ """Install a local .app or .ipa on a device with devicectl or simctl, run without a shell."""
2
+
3
+ import json
4
+ import os
5
+ import plistlib
6
+ import re
7
+ import subprocess
8
+ import tempfile
9
+ import threading
10
+ import time
11
+ import zipfile
12
+ from pathlib import Path
13
+
14
+ from . import PhoneIOError
15
+
16
+ TIMEOUT = 600
17
+ UNPACK_LIMIT = 8 * 1024 ** 3
18
+
19
+ PROBLEMS = (
20
+ ("developer_mode", re.compile(r"developer ?mode|0xe8000094|DeveloperModeDisabled", re.I),
21
+ "Developer Mode is off on the iPhone.",
22
+ "Turn it on in Settings › Privacy & Security › Developer Mode, restart the iPhone, then try again."),
23
+ ("not_signed", re.compile(r"0xe80080(?:15|16|18|1c)|ApplicationVerificationFailed|provisioning profile|"
24
+ r"could not be verified|not been signed|code ?sign|signature|unsigned|"
25
+ r"not (?:authorized|trusted)|integrity|untrusted developer", re.I),
26
+ "The build isn't signed for this iPhone.",
27
+ "Sign it with a development profile that includes this iPhone (Xcode › Signing & Capabilities), build it "
28
+ "for a device, then try again."),
29
+ ("locked", re.compile(r"device is locked|while (?:the device is )?locked|0xe80000e2", re.I),
30
+ "The iPhone is locked.", "Unlock it, then try again."),
31
+ ("no_device", re.compile(r"no devices? (?:matched|are booted|found)|unable to (?:find|locate)|not connected|"
32
+ r"is not available|disconnected|invalid device|state: shutdown", re.I),
33
+ "The device isn't reachable.", "Plug it in (or boot the simulator) and try again."),
34
+ )
35
+
36
+
37
+ def check_path(path):
38
+ """``path`` as a local .app folder or .ipa file that exists; PhoneIOError (usage) otherwise."""
39
+ text = str(path or "").strip()
40
+ if not text or "://" in text:
41
+ raise PhoneIOError("Pass the path of a local .app or .ipa, not a link.", "usage")
42
+ candidate = Path(text).expanduser()
43
+ if not candidate.is_absolute():
44
+ candidate = Path.cwd() / candidate
45
+ candidate = candidate.resolve()
46
+ if candidate.suffix.lower() == ".ipa":
47
+ if not candidate.is_file() or not zipfile.is_zipfile(candidate):
48
+ raise PhoneIOError(f"{candidate} isn't an .ipa file.", "usage")
49
+ return candidate
50
+ if candidate.suffix.lower() == ".app":
51
+ if not (candidate.is_dir() and (candidate / "Info.plist").is_file()):
52
+ raise PhoneIOError(f"{candidate} isn't an app bundle (no Info.plist).", "usage")
53
+ return candidate
54
+ raise PhoneIOError(f"{candidate.name} isn't an .app or an .ipa.", "usage")
55
+
56
+
57
+ def unpack(ipa, folder):
58
+ """The .app inside ``ipa`` (Payload/X.app), unpacked into ``folder``; no entry may leave it."""
59
+ root = Path(folder).resolve()
60
+ with zipfile.ZipFile(ipa) as archive:
61
+ total = 0
62
+ for info in archive.infolist():
63
+ target = (root / info.filename).resolve()
64
+ if root not in target.parents and target != root:
65
+ raise PhoneIOError(f"{Path(ipa).name} has an entry outside its folder; Mobster won't unpack it.",
66
+ "usage")
67
+ total += info.file_size
68
+ if total > UNPACK_LIMIT:
69
+ raise PhoneIOError(f"{Path(ipa).name} unpacks to more than 8 GB.", "usage")
70
+ archive.extractall(root)
71
+ apps = sorted((root / "Payload").glob("*.app"))
72
+ if len(apps) != 1:
73
+ raise PhoneIOError(f"{Path(ipa).name} has no single app in its Payload folder.", "usage")
74
+ return apps[0]
75
+
76
+
77
+ def bundle_id(app):
78
+ try:
79
+ with open(Path(app) / "Info.plist", "rb") as handle:
80
+ value = plistlib.load(handle).get("CFBundleIdentifier")
81
+ return value if isinstance(value, str) else None
82
+ except (OSError, plistlib.InvalidFileException, ValueError, AttributeError):
83
+ return None
84
+
85
+
86
+ def install_argv(record, app, json_path=None):
87
+ """The command that installs ``app`` (an .app) on ``record``'s device. No shell: a list of arguments."""
88
+ kind, udid = record.get("kind"), record.get("udid") or record.get("id")
89
+ if kind == "usb":
90
+ argv = ["xcrun", "devicectl", "device", "install", "app", "--device", str(udid), str(app)]
91
+ if json_path:
92
+ argv += ["--json-output", str(json_path)]
93
+ return argv
94
+ if kind == "simulator":
95
+ return ["xcrun", "simctl", "install", str(udid), str(app)]
96
+ raise PhoneIOError(f"Mobster can't install a build on “{record.get('name')}”: it's a WebDriverAgent address, "
97
+ "not an iPhone or simulator it knows.", "usage", "Install with Xcode instead.")
98
+
99
+
100
+ def explain(output, returncode):
101
+ """PhoneIOError for a failed install, from the tool's output."""
102
+ for code, pattern, message, fix in PROBLEMS:
103
+ if pattern.search(output or ""):
104
+ return PhoneIOError(message, code, fix)
105
+ lines = [line.strip() for line in (output or "").splitlines() if line.strip()]
106
+ last = next((line for line in reversed(lines) if line.lower().startswith(("error", "an error"))),
107
+ lines[-1] if lines else "")
108
+ return PhoneIOError(f"The install failed (exit {returncode}){': ' + last[:300] if last else ''}.", "failed")
109
+
110
+
111
+ def run_streaming(argv, progress=None, timeout=TIMEOUT):
112
+ """(exit code, output) of ``argv``, each line handed to ``progress`` as it comes."""
113
+ try:
114
+ process = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL,
115
+ text=True, errors="replace")
116
+ except FileNotFoundError:
117
+ raise PhoneIOError("Xcode's command-line tools aren't installed.", "tools",
118
+ "Install Xcode, open it once, then try again.") from None
119
+ lines = []
120
+
121
+ def pump():
122
+ # Its own thread: a tool that hangs without printing must still meet the time limit.
123
+ for line in process.stdout:
124
+ lines.append(line)
125
+ if progress is not None and line.strip():
126
+ try:
127
+ progress(line.rstrip())
128
+ except Exception:
129
+ pass
130
+ reader = threading.Thread(target=pump, daemon=True, name="mobster-install-output")
131
+ reader.start()
132
+ try:
133
+ code = process.wait(timeout=timeout)
134
+ except subprocess.TimeoutExpired:
135
+ limit = f"{timeout / 60:g} minutes" if timeout >= 60 else f"{timeout:g} seconds"
136
+ raise PhoneIOError(f"The install took over {limit}; Mobster stopped it.", "failed") from None
137
+ finally:
138
+ if process.poll() is None:
139
+ process.kill()
140
+ try:
141
+ process.wait(timeout=5)
142
+ except subprocess.TimeoutExpired:
143
+ pass
144
+ reader.join(5)
145
+ if not reader.is_alive():
146
+ process.stdout.close()
147
+ return code, "".join(lines)
148
+
149
+
150
+ def installed_bundle(json_path):
151
+ try:
152
+ data = json.loads(Path(json_path).read_text())
153
+ except (OSError, ValueError):
154
+ return None
155
+ apps = ((data.get("result") or {}).get("installedApplications") or []) if isinstance(data, dict) else []
156
+ for app in apps:
157
+ if isinstance(app, dict) and isinstance(app.get("bundleID"), str):
158
+ return app["bundleID"]
159
+ return None
160
+
161
+
162
+ def install(record, path, *, runner=None, progress=None):
163
+ """Install the .app or .ipa at ``path`` on ``record``'s device."""
164
+ source = check_path(path)
165
+ if runner is None:
166
+ from ..native_helpers import require_devices
167
+ require_devices() # a test or local CI never reaches a phone
168
+ runner = runner or (lambda argv: run_streaming(argv, progress))
169
+ started = time.monotonic()
170
+ with tempfile.TemporaryDirectory(prefix="mobster-install-") as folder:
171
+ app = unpack(source, folder) if source.suffix.lower() == ".ipa" else source
172
+ json_path = Path(folder) / "devicectl.json"
173
+ argv = install_argv(record, app, json_path)
174
+ if progress is not None:
175
+ progress(f"Installing {app.name} on {record.get('name') or record.get('id')}…")
176
+ code, output = runner(argv)
177
+ if code != 0:
178
+ raise explain(output, code)
179
+ bundle = installed_bundle(json_path) or bundle_id(app)
180
+ return {"ok": True, "device": record.get("id"), "name": record.get("name"), "path": str(source),
181
+ "bundle_id": bundle, "seconds": round(time.monotonic() - started, 1)}
182
+
183
+
184
+ def describe_path(path):
185
+ """A path for messages: the user's home as ~."""
186
+ home = os.path.expanduser("~")
187
+ text = str(path)
188
+ return "~" + text[len(home):] if text.startswith(home + os.sep) else text
@@ -0,0 +1,188 @@
1
+ """Bounded USB packet traces through Apple's Remote Virtual Interface, without TLS interception."""
2
+
3
+ import fcntl
4
+ import hashlib
5
+ import json
6
+ import os
7
+ import re
8
+ import shutil
9
+ import signal
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ from . import PhoneIOError, capture_cancellation
15
+
16
+ # tcpdump holds the phone lock, so it must end by itself even when Mobster is SIGKILLed. Mobster stops it at
17
+ # `seconds`; the kernel's SIGALRM, which survives exec, ends it this long after that.
18
+ HARD_STOP_GRACE = 15
19
+
20
+
21
+ def die_after(seconds):
22
+ """A preexec_fn that arms SIGALRM in the child; the pending alarm survives exec into tcpdump."""
23
+ return lambda: signal.alarm(seconds)
24
+
25
+
26
+ def binary(name):
27
+ path = shutil.which(name)
28
+ if not path and Path("/Library/Apple/usr/bin", name).is_file():
29
+ path = str(Path("/Library/Apple/usr/bin", name))
30
+ if not path:
31
+ raise PhoneIOError(f"{name} isn't installed. Install Xcode and its device support tools.", "tools")
32
+ return path
33
+
34
+
35
+ def rvi_for(text, udid):
36
+ for line in text.splitlines():
37
+ if re.search(r"(?<![A-Za-z0-9-])" + re.escape(udid) + r"(?![A-Za-z0-9-])", line, re.I):
38
+ found = re.search(r"\b(rvi\d+)\b", line)
39
+ if found:
40
+ return found.group(1)
41
+ return None
42
+
43
+
44
+ def rvi_command(tool, *arguments):
45
+ try:
46
+ result = subprocess.run([tool, *arguments], capture_output=True, text=True, timeout=15,
47
+ stdin=subprocess.DEVNULL)
48
+ except subprocess.TimeoutExpired:
49
+ raise PhoneIOError("rvictl didn't answer. Its outcome is unknown; "
50
+ "inspect `rvictl -l` before trying again.") from None
51
+ except KeyboardInterrupt:
52
+ raise PhoneIOError("rvictl was interrupted. Its outcome is unknown; "
53
+ "inspect `rvictl -l` before trying again.") from None
54
+ except OSError:
55
+ raise PhoneIOError("Mobster couldn't start rvictl. Check Xcode's device support tools.", "tools") from None
56
+ if result.returncode:
57
+ raise PhoneIOError("rvictl refused the request. Unlock the USB iPhone and check this Mac's device permissions.")
58
+ return result.stdout + result.stderr
59
+
60
+
61
+ def clear_stale(rvictl, udid, state):
62
+ """With the phone lock held, no capture is alive, so a state file is a dead capture's leftover."""
63
+ if not state.exists():
64
+ return
65
+ if rvi_for(rvi_command(rvictl, "-l"), udid):
66
+ rvi_command(rvictl, "-x", udid)
67
+ state.unlink(missing_ok=True)
68
+
69
+
70
+ def stop(process):
71
+ try:
72
+ if process.poll() is None:
73
+ try:
74
+ process.send_signal(signal.SIGINT)
75
+ except ProcessLookupError:
76
+ pass # The process can exit between poll and signal; wait still reaps it.
77
+ try:
78
+ process.wait(timeout=5)
79
+ except subprocess.TimeoutExpired:
80
+ process.kill()
81
+ process.wait(timeout=5)
82
+ except (OSError, subprocess.TimeoutExpired):
83
+ raise PhoneIOError("Mobster couldn't stop tcpdump. Inspect running capture processes "
84
+ "before trying again.") from None
85
+
86
+
87
+ def capture(record, output, *, seconds=30, packets=10000, snaplen=128):
88
+ """Capture beside a running task. A separate per-phone lock owns the RVI lifecycle, not the UI lease."""
89
+ from ..env_file import open_private, private_dir
90
+ from ..mac_tools import usb_attached
91
+ from ..native_helpers import require_devices
92
+ from ..paths import user_data_dir
93
+ if (isinstance(seconds, bool) or not isinstance(seconds, int) or not 1 <= seconds <= 300 or
94
+ isinstance(packets, bool) or not isinstance(packets, int) or not 1 <= packets <= 100000 or
95
+ isinstance(snaplen, bool) or not isinstance(snaplen, int) or not 64 <= snaplen <= 65535):
96
+ raise PhoneIOError("Use 1–300 seconds, 1–100000 packets and a snap length of 64–65535 bytes.", "usage")
97
+ require_devices()
98
+ udid = record.get("udid")
99
+ if record.get("kind") != "usb" or not udid or not usb_attached(udid):
100
+ raise PhoneIOError("Packet capture needs a paired iPhone connected by USB.", "needs_cable")
101
+ rvictl, tcpdump = binary("rvictl"), binary("tcpdump")
102
+ folder = user_data_dir() / "network-capture"
103
+ private_dir(folder)
104
+ key = hashlib.sha256(udid.upper().encode()).hexdigest()[:24]
105
+ with open_private(folder / (key + ".lock"), "a+") as lock:
106
+ try:
107
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
108
+ except BlockingIOError:
109
+ raise PhoneIOError(f"Mobster is already capturing this iPhone. Stop that capture first; an orphaned "
110
+ f"one ends by itself within {HARD_STOP_GRACE} seconds of its time limit.") from None
111
+ state = folder / (key + ".json")
112
+ with capture_cancellation():
113
+ clear_stale(rvictl, udid, state)
114
+ return _capture(rvictl, tcpdump, udid, output, seconds, packets, snaplen, lock.fileno(), state)
115
+
116
+
117
+ def _capture(rvictl, tcpdump, udid, output, seconds, packets, snaplen, lock_fd, state):
118
+ from ..env_file import open_private
119
+ target = Path(output).expanduser().absolute()
120
+ try:
121
+ stream = open(target, "xb", opener=lambda p, flags: os.open(p, flags, 0o600))
122
+ except FileExistsError:
123
+ raise PhoneIOError("The capture file already exists. Choose another --output path.", "usage") from None
124
+ except OSError:
125
+ raise PhoneIOError("Mobster couldn't create the capture file. Check the output folder.") from None
126
+ owned, process, interface = False, None, None
127
+ cleanup_error = None
128
+ try:
129
+ listing = rvi_command(rvictl, "-l")
130
+ interface = rvi_for(listing, udid)
131
+ if interface is None:
132
+ # Journal the intent first: if Mobster dies before `-x`, the next start removes this interface.
133
+ with open_private(state, "w") as intent:
134
+ json.dump({"owned": True}, intent)
135
+ result = rvi_command(rvictl, "-s", udid)
136
+ interface = rvi_for(result, udid)
137
+ if not interface or "[SUCCEEDED]" not in result:
138
+ raise PhoneIOError("rvictl didn't identify a created interface. Inspect `rvictl -l` before retrying.")
139
+ owned = True
140
+ try:
141
+ # A surviving tcpdump must keep the phone locked if Mobster is killed without cleanup.
142
+ with capture_cancellation(defer=True):
143
+ process = subprocess.Popen([tcpdump, "-i", interface, "-n", "-U", "-s", str(snaplen),
144
+ "-c", str(packets), "-w", "-"], stdin=subprocess.DEVNULL,
145
+ stdout=stream, stderr=subprocess.DEVNULL, pass_fds=(lock_fd,),
146
+ preexec_fn=die_after(seconds + HARD_STOP_GRACE))
147
+ try:
148
+ process.wait(timeout=seconds)
149
+ except subprocess.TimeoutExpired:
150
+ stop(process)
151
+ if process.returncode not in (0, -signal.SIGINT):
152
+ raise PhoneIOError("tcpdump couldn't capture packets. Check BPF capture permissions on this Mac; "
153
+ "Apple's packet-capture instructions require administrator access.")
154
+ except OSError:
155
+ raise PhoneIOError("Mobster couldn't start tcpdump. Check this Mac's capture permissions.",
156
+ "tools") from None
157
+ finally:
158
+ try:
159
+ if process is not None:
160
+ stop(process)
161
+ finally:
162
+ try:
163
+ stream.close()
164
+ finally:
165
+ if owned:
166
+ try:
167
+ rvi_command(rvictl, "-x", udid)
168
+ state.unlink(missing_ok=True)
169
+ except PhoneIOError:
170
+ cleanup_error = "The capture's RVI could not be removed. Inspect `rvictl -l`."
171
+ try:
172
+ if target.stat().st_size == 0:
173
+ target.unlink()
174
+ except FileNotFoundError:
175
+ pass # Removing the output during capture must not hide its original error or cleanup warning.
176
+ failure = sys.exception()
177
+ if cleanup_error and failure is not None:
178
+ message = str(failure) if isinstance(failure, PhoneIOError) else "Packet capture was interrupted."
179
+ raise PhoneIOError(f"{message} {cleanup_error}", getattr(failure, "code", "failed")) from None
180
+ try:
181
+ size = target.stat().st_size
182
+ except FileNotFoundError:
183
+ size = 0
184
+ if size < 24:
185
+ message = "No complete capture file remains. Check capture permissions and the output path before trying again."
186
+ raise PhoneIOError(f"{message} {cleanup_error}" if cleanup_error else message)
187
+ return {"ok": True, "path": str(target), "bytes": size, "interface": interface,
188
+ "snaplen": snaplen, "packetLimit": packets, "secondsLimit": seconds, "warning": cleanup_error}
@@ -0,0 +1,138 @@
1
+ """Network profile generation and diagnostics for `mobster phone network`."""
2
+
3
+ from . import PhoneIOError, choose_device
4
+
5
+
6
+ def add_arguments(subs, helpers):
7
+ path_type = helpers.get("path_type")
8
+ parser = subs.add_parser("network", help="create network profiles, diagnose this Mac, or capture USB traffic",
9
+ description="Create removable iPhone network profiles, inspect this Mac's connectivity, "
10
+ "or capture a USB iPhone's packets. Profiles need installation in Settings.")
11
+ verbs = parser.add_subparsers(dest="network_command", metavar="<subcommand>", required=True)
12
+
13
+ proxy = verbs.add_parser("proxy-profile", help="write a Wi-Fi HTTP proxy or PAC profile",
14
+ description="Write a Wi-Fi profile for one SSID. Apps may ignore HTTP proxies; this "
15
+ "does not route cellular traffic or decrypt TLS.")
16
+ proxy.add_argument("--ssid", required=True, help="the Wi-Fi network name")
17
+ mode = proxy.add_mutually_exclusive_group(required=True)
18
+ mode.add_argument("--server", help="the HTTP proxy hostname or IP address")
19
+ mode.add_argument("--pac", metavar="URL", help="the HTTP or HTTPS proxy auto-configuration URL")
20
+ proxy.add_argument("--port", type=int, default=8080, help="the HTTP proxy port (default: 8080)")
21
+ proxy.add_argument("--username", help="the HTTP proxy username")
22
+ proxy.add_argument("--password-file", type=path_type, metavar="FILE",
23
+ help="read the proxy password from this UTF-8 file")
24
+ proxy.add_argument("--wifi-password-file", type=path_type, metavar="FILE",
25
+ help="read the Wi-Fi password from this UTF-8 file")
26
+ proxy.add_argument("--encryption", choices=("Any", "WPA2", "WPA3", "None"), default="Any",
27
+ help="the Wi-Fi encryption type (default: Any)")
28
+
29
+ vpn = verbs.add_parser("vpn-profile", help="write an IKEv2 VPN profile with username authentication",
30
+ description="Write an IKEv2 EAP VPN profile for your provider. This does not create a VPN "
31
+ "server. Install the profile and connect in Settings.")
32
+ vpn.add_argument("--server", required=True, help="the VPN server hostname or IP address")
33
+ vpn.add_argument("--remote-id", required=True, help="the server identifier from your VPN provider")
34
+ vpn.add_argument("--local-id", required=True, help="the client identifier from your VPN provider")
35
+ vpn.add_argument("--username", required=True, help="the VPN account username")
36
+ vpn.add_argument("--password-file", type=path_type, metavar="FILE",
37
+ help="read the VPN password from this UTF-8 file")
38
+
39
+ relay = verbs.add_parser("relay-profile", help="write an Apple HTTP/2 network relay profile",
40
+ description="Write an Apple network relay profile for an existing compatible HTTP/2 "
41
+ "relay service. This is not an ordinary HTTP proxy or a new relay server.")
42
+ relay.add_argument("--url", required=True, help="the HTTPS relay endpoint")
43
+ relay.add_argument("--token-file", type=path_type, metavar="FILE",
44
+ help="read the bearer token from this UTF-8 file")
45
+
46
+ for command, kind, name in ((proxy, "proxy", "Mobster proxy"), (vpn, "vpn", "Mobster VPN"),
47
+ (relay, "relay", "Mobster relay")):
48
+ command.add_argument("--output", required=True, type=path_type, metavar="FILE",
49
+ help="write a new private .mobileconfig file; existing files are kept")
50
+ command.add_argument("--name", default=name, help=f"the profile's display name (default: {name})")
51
+ command.add_argument("--identifier", default=f"app.mobster.network.{kind}",
52
+ help=f"the stable profile identifier (default: app.mobster.network.{kind})")
53
+ command.add_argument("--json", action="store_true", help="print one JSON object")
54
+
55
+ diagnose = verbs.add_parser("diagnose", help="inspect this Mac's route and optional TCP endpoint reachability",
56
+ description="Check this Mac's default route and optional TCP endpoint. With --device, "
57
+ "also report USB attachment. This does not test the iPhone's internet "
58
+ "egress, proxy authentication, or installed profiles.")
59
+ diagnose.add_argument("--device", metavar="NAME", help="also inspect USB attachment for this device")
60
+ diagnose.add_argument("--host", help="the hostname or IP address to test from this Mac")
61
+ diagnose.add_argument("--port", type=int, default=8080, help="the TCP endpoint port (default: 8080)")
62
+ diagnose.add_argument("--json", action="store_true", help="print one JSON object")
63
+
64
+ capture = verbs.add_parser("capture", help="save a bounded USB iPhone packet trace",
65
+ description="Capture packets through Apple's Remote Virtual Interface. Requires a "
66
+ "paired USB iPhone, Xcode tools and BPF permissions on this Mac. TLS "
67
+ "remains encrypted; packet data may contain private information.")
68
+ capture.add_argument("--device", required=True, metavar="NAME", help="the paired USB iPhone's id, UDID or name")
69
+ capture.add_argument("--output", required=True, type=path_type, metavar="FILE",
70
+ help="write a new private pcap file")
71
+ capture.add_argument("--seconds", type=int, default=30, help="stop after 1 to 300 seconds (default: 30)")
72
+ capture.add_argument("--packets", type=int, default=10000, help="stop after 1 to 100000 packets (default: 10000)")
73
+ capture.add_argument("--snaplen", type=int, default=128, help="capture 64 to 65535 bytes per packet (default: 128)")
74
+ capture.add_argument("--json", action="store_true", help="print one JSON object")
75
+
76
+
77
+ def run(args):
78
+ from . import network_capture
79
+ from . import network_profiles as profiles
80
+ from .cli import _print
81
+
82
+ command = args.network_command
83
+ if command == "diagnose":
84
+ result, lines = diagnose(args)
85
+ elif command == "capture":
86
+ # Validate before device discovery so bad input cannot initiate device work.
87
+ if not (1 <= args.seconds <= 300 and 1 <= args.packets <= 100000 and 64 <= args.snaplen <= 65535):
88
+ raise PhoneIOError("Use 1–300 seconds, 1–100000 packets and a snap length of 64–65535 bytes.", "usage")
89
+ record = choose_device(args.device)
90
+ result = network_capture.capture(record, args.output, seconds=args.seconds,
91
+ packets=args.packets, snaplen=args.snaplen)
92
+ lines = [f"Saved {result['bytes']:,} bytes to {result['path']}."]
93
+ if result.get("warning"):
94
+ lines.append(result["warning"])
95
+ else:
96
+ if command == "proxy-profile":
97
+ payload = profiles.proxy(ssid=args.ssid, server=args.server, port=args.port, pac=args.pac,
98
+ username=args.username, password=profiles.secret(args.password_file),
99
+ wifi_password=profiles.secret(args.wifi_password_file), encryption=args.encryption)
100
+ elif command == "vpn-profile":
101
+ payload = profiles.vpn(server=args.server, remote_id=args.remote_id, local_id=args.local_id,
102
+ username=args.username, password=profiles.secret(args.password_file))
103
+ else:
104
+ payload = profiles.relay(endpoint=args.url, token=profiles.secret(args.token_file))
105
+ result = profiles.write_profile(payload, args.output, name=args.name, identifier=args.identifier)
106
+ lines = [f"Saved {result['path']}. The profile is not installed.", result["next"]]
107
+ _print(result, args.json, "\n".join(lines))
108
+ return 0 if result["ok"] else 1
109
+
110
+
111
+ def diagnose(args):
112
+ from .. import mac_tools
113
+ from ..wireless import network
114
+ from .network_profiles import host
115
+
116
+ if not 1 <= args.port <= 65535:
117
+ raise PhoneIOError("The endpoint port must be between 1 and 65535.", "usage")
118
+ endpoint_host = host(args.host) if args.host is not None else None
119
+ record = choose_device(args.device) if args.device else None
120
+ interface = network.primary_interface()
121
+ state = network.nwi()
122
+ result = {"ok": True, "scope": "mac", "mac": {"defaultInterface": interface,
123
+ "gateway": network.default_gateway(), "vpnInterface": network.vpn_interface(state)},
124
+ "phoneEgressTested": False}
125
+ lines = [f"Mac default interface: {interface or 'unknown'}.",
126
+ f"Mac VPN interface: {result['mac']['vpnInterface'] or 'none detected'}."]
127
+ if endpoint_host:
128
+ reachable = network.port_open(endpoint_host, args.port)
129
+ result["endpoint"] = {"host": endpoint_host, "port": args.port, "reachableFromMac": reachable}
130
+ result["ok"] = reachable
131
+ lines.append(f"TCP endpoint from this Mac: {'reachable' if reachable else 'unreachable'}.")
132
+ if record:
133
+ attached = mac_tools.usb_attached(record.get("udid")) if record.get("kind") == "usb" else None
134
+ result["device"] = {"id": record["id"], "kind": record.get("kind"), "usbAttached": attached}
135
+ lines.append(
136
+ f"Device USB attachment: { {True: 'connected', False: 'disconnected', None: 'unknown'}[attached]}.")
137
+ lines.append("The iPhone's internet egress, proxy authentication and installed profiles were not tested.")
138
+ return result, lines
@@ -0,0 +1,138 @@
1
+ """Removable Apple network profiles. Generating one never changes a device's routing.
2
+
3
+ Keys follow apple/device-management's WiFi, VPN.IKEv2 and Relay payload schemas.
4
+ Credentials come from files, never command-line arguments or result messages.
5
+ """
6
+
7
+ import ipaddress
8
+ import os
9
+ import plistlib
10
+ import re
11
+ import tempfile
12
+ import uuid
13
+ from pathlib import Path
14
+ from urllib.parse import urlsplit
15
+
16
+ from . import PhoneIOError
17
+
18
+
19
+ def host(value):
20
+ value = str(value or "").strip()
21
+ try:
22
+ return str(ipaddress.ip_address(value))
23
+ except ValueError:
24
+ if (len(value) > 253 or not value or any(not re.fullmatch(r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?", p)
25
+ for p in value.rstrip(".").split("."))):
26
+ raise PhoneIOError("Use a hostname or IP address without a scheme, path or credentials.", "usage") from None
27
+ return value
28
+
29
+
30
+ def url(value, *, https=False):
31
+ try:
32
+ parsed = urlsplit(value)
33
+ valid = (parsed.scheme in (("https",) if https else ("http", "https")) and parsed.hostname and
34
+ parsed.username is None and parsed.password is None and not parsed.fragment and
35
+ not any(c.isspace() or ord(c) < 32 for c in value))
36
+ host(parsed.hostname)
37
+ if parsed.port is not None and not 1 <= parsed.port <= 65535:
38
+ valid = False
39
+ except (ValueError, TypeError, PhoneIOError):
40
+ valid = False
41
+ if not valid:
42
+ raise PhoneIOError("Use an HTTPS URL without embedded credentials." if https else
43
+ "Use an HTTP or HTTPS URL without embedded credentials.", "usage")
44
+ return value
45
+
46
+
47
+ def secret(path):
48
+ if not path:
49
+ return None
50
+ try:
51
+ with Path(path).expanduser().open("rb") as stream:
52
+ # Include the optional CRLF plus one extra byte so oversized files cannot be truncated into valid secrets.
53
+ value = stream.read(4099).decode("utf-8").removesuffix("\n").removesuffix("\r")
54
+ except (OSError, UnicodeError):
55
+ raise PhoneIOError("Mobster couldn't read the credential file as UTF-8.", "usage") from None
56
+ if not value or len(value.encode()) > 4096 or any(ord(c) < 32 for c in value):
57
+ raise PhoneIOError("The credential file must contain one nonempty line of at most 4 KB.", "usage")
58
+ return value
59
+
60
+
61
+ def proxy(*, ssid, server=None, port=8080, pac=None, username=None, password=None,
62
+ wifi_password=None, encryption="Any"):
63
+ if not isinstance(ssid, str) or not 1 <= len(ssid.encode()) <= 32 or any(ord(c) < 32 for c in ssid):
64
+ raise PhoneIOError("The Wi-Fi name must contain 1 to 32 UTF-8 bytes.", "usage")
65
+ if bool(server) == bool(pac):
66
+ raise PhoneIOError("Choose a proxy server or a PAC URL.", "usage")
67
+ if encryption not in ("Any", "WPA2", "WPA3", "None") or encryption == "None" and wifi_password:
68
+ raise PhoneIOError("Choose Any, WPA2, WPA3 or None; an open network takes no Wi-Fi password.", "usage")
69
+ payload = {"PayloadType": "com.apple.wifi.managed", "SSID_STR": ssid, "EncryptionType": encryption,
70
+ "AutoJoin": True, "ProxyType": "Auto" if pac else "Manual"}
71
+ if wifi_password:
72
+ payload["Password"] = wifi_password
73
+ if pac:
74
+ if username or password:
75
+ raise PhoneIOError("PAC profiles do not take proxy credentials.", "usage")
76
+ payload.update(ProxyPACURL=url(pac), ProxyPACFallbackAllowed=False)
77
+ else:
78
+ if isinstance(port, bool) or not isinstance(port, int) or not 1 <= port <= 65535:
79
+ raise PhoneIOError("The proxy port must be between 1 and 65535.", "usage")
80
+ payload.update(ProxyServer=host(server), ProxyServerPort=port)
81
+ if password and not username:
82
+ raise PhoneIOError("A proxy password needs --username.", "usage")
83
+ if username:
84
+ payload["ProxyUsername"] = username
85
+ if password:
86
+ payload["ProxyPassword"] = password
87
+ return payload
88
+
89
+
90
+ def vpn(*, server, remote_id, local_id, username, password=None):
91
+ if not all(isinstance(v, str) and v.strip() for v in (remote_id, local_id, username)):
92
+ raise PhoneIOError("IKEv2 needs a remote identifier, local identifier and username from your VPN provider.",
93
+ "usage")
94
+ settings = {"RemoteAddress": host(server), "RemoteIdentifier": remote_id, "LocalIdentifier": local_id,
95
+ "AuthenticationMethod": "None", "ExtendedAuthEnabled": 1, "AuthName": username,
96
+ "IncludeAllNetworks": 1, "OnDemandEnabled": 0}
97
+ if password:
98
+ settings["AuthPassword"] = password
99
+ return {"PayloadType": "com.apple.vpn.managed", "VPNType": "IKEv2", "UserDefinedName": "Mobster VPN",
100
+ "IKEv2": settings}
101
+
102
+
103
+ def relay(*, endpoint, token=None):
104
+ server = {"HTTP2RelayURL": url(endpoint, https=True)}
105
+ if token:
106
+ if any(ord(c) < 32 for c in token):
107
+ raise PhoneIOError("The relay token must be one line.", "usage")
108
+ server["AdditionalHTTPHeaderFields"] = {"Authorization": "Bearer " + token}
109
+ return {"PayloadType": "com.apple.relay.managed", "Relays": [server]}
110
+
111
+
112
+ def write_profile(payload, output, *, name="Mobster network", identifier="app.mobster.network"):
113
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9.-]{1,199}", identifier):
114
+ raise PhoneIOError("Use a profile identifier of 2 to 200 letters, numbers, dots or hyphens.", "usage")
115
+ def envelope(kind, identity):
116
+ return {"PayloadType": kind, "PayloadVersion": 1, "PayloadIdentifier": identity,
117
+ "PayloadUUID": str(uuid.uuid5(uuid.NAMESPACE_URL, identity)), "PayloadDisplayName": name}
118
+ content = {**payload, **envelope(payload["PayloadType"], identifier + ".settings")}
119
+ profile = {**envelope("Configuration", identifier), "PayloadContent": [content], "PayloadRemovalDisallowed": False}
120
+ try:
121
+ data = plistlib.dumps(profile)
122
+ except (TypeError, ValueError):
123
+ raise PhoneIOError("The profile contains text Apple can't encode. Remove control characters.",
124
+ "usage") from None
125
+ target = Path(output).expanduser().absolute()
126
+ try:
127
+ # Publish only the complete private plist; link keeps a concurrent writer's output intact.
128
+ with tempfile.NamedTemporaryFile(dir=target.parent, prefix=".mobster-profile-") as stream:
129
+ stream.write(data)
130
+ stream.flush()
131
+ os.link(stream.name, target)
132
+ except FileExistsError:
133
+ raise PhoneIOError("The output file already exists. Choose another --output path.", "usage") from None
134
+ except OSError:
135
+ raise PhoneIOError("Mobster couldn't write the profile. Check the output folder.") from None
136
+ return {"ok": True, "path": str(target), "identifier": identifier, "installed": False,
137
+ "next": "Transfer this profile to your iPhone, then review and install it in Settings > General > "
138
+ "VPN & Device Management. Remove that profile there to remove this configuration."}