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,223 @@
1
+ """Finding the Mac's own tools, and asking the Mac short questions."""
2
+
3
+ import functools
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import threading
9
+ import time
10
+ from pathlib import Path
11
+
12
+ from .devices import LOOPBACK, UDID_PATTERN
13
+
14
+ TOOL_DIRS = ("/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/Applications/Xcode.app/Contents/Developer/usr/bin")
15
+ # The iPhone connection tools: libimobiledevice's and libusbmuxd's. Mobster for Mac carries its own build of them
16
+ # (desktop/scripts/build-iphone-tools.sh), found before any Homebrew copy, so nobody needs Homebrew or Terminal.
17
+ IPHONE_TOOLS = ("idevice_id", "ideviceinfo", "ideviceinstaller", "iproxy", "afcclient", "idevicesyslog",
18
+ "idevicecrashreport")
19
+ # /usr/bin/xcodebuild and /usr/bin/git exist on every Mac: they are stubs that forward to the active
20
+ # developer directory (and fail, or open an install dialog, without one). They never count as Xcode or
21
+ # git: both come from the Xcode app itself, git also from the Command Line Tools or Homebrew.
22
+ XCODE_SHIMS = frozenset({"xcodebuild", "git"})
23
+ CLT_DIR = "/Library/Developer/CommandLineTools"
24
+ XCODE_APP_DIRS = ("/Applications", str(Path.home() / "Applications"))
25
+ GIT_DIRS = ("/opt/homebrew/bin", "/usr/local/bin", CLT_DIR + "/usr/bin")
26
+ # Xcode's state takes a few short xcodebuild calls: a working Xcode is re-checked rarely, one that
27
+ # needs fixing often enough that the fix shows up on its own.
28
+ XCODE_OK_TTL, XCODE_RETRY_TTL = 300.0, 10.0
29
+ WDA_PORT, VIDEO_PORT = 8100, 9100
30
+
31
+
32
+ def executable(directory, name):
33
+ path = Path(directory) / name
34
+ return str(path) if path.is_file() and os.access(path, os.X_OK) else None
35
+
36
+
37
+ def bundled_tools_dir():
38
+ override = os.environ.get("MOBSTER_IPHONE_TOOLS")
39
+ if override:
40
+ return Path(override)
41
+ frozen = getattr(sys, "_MEIPASS", None)
42
+ if not frozen:
43
+ return None
44
+ for resources in (Path(frozen).parent / "Resources", Path(sys.executable).resolve().parent.parent / "Resources"):
45
+ if (resources / "iphone-tools" / "bin").is_dir():
46
+ return resources / "iphone-tools" / "bin"
47
+ return None
48
+
49
+
50
+ def bundled_tool(name):
51
+ """The app's own copy of an iPhone connection tool, or None."""
52
+ directory = bundled_tools_dir() if name in IPHONE_TOOLS else None
53
+ return executable(directory, name) if directory else None
54
+
55
+
56
+ def tool(name):
57
+ if name in XCODE_SHIMS:
58
+ return xcode_tool(name)
59
+ if path := bundled_tool(name):
60
+ return path
61
+ for directory in TOOL_DIRS:
62
+ if path := executable(directory, name):
63
+ return path
64
+ return shutil.which(name)
65
+
66
+
67
+ def is_xcode(path):
68
+ """A developer directory inside an Xcode app (not the Command Line Tools)."""
69
+ path = Path(path)
70
+ return (path.name == "Developer" and path.parent.name == "Contents" and path.parent.parent.suffix == ".app"
71
+ and executable(path / "usr" / "bin", "xcodebuild") is not None)
72
+
73
+
74
+ def developer_dir():
75
+ selected = (run(["/usr/bin/xcode-select", "-p"], timeout=5) or "").strip().rstrip("/") or None
76
+ if selected and is_xcode(selected):
77
+ return selected, selected
78
+ for root in XCODE_APP_DIRS:
79
+ try:
80
+ apps = sorted(Path(root).glob("Xcode*.app"), key=lambda app: (app.name != "Xcode.app", app.name))
81
+ except OSError:
82
+ continue
83
+ for app in apps:
84
+ if is_xcode(app / "Contents" / "Developer"):
85
+ return str(app / "Contents" / "Developer"), selected
86
+ return None, selected
87
+
88
+
89
+ # A shell's compiler settings that clang and xcodebuild read from the environment. Meant for building for this Mac
90
+ # (Homebrew, conda), they point an iPhone or simulator build at the Mac's headers and SDK: on 17 Oct a shell with
91
+ # CPATH=/usr/local/include failed the helper's first build ("include location '/usr/local/include' is unsafe for
92
+ # cross-compilation", -Werror). Xcode's builds never take them.
93
+ HOST_BUILD_ENV = ("CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH", "OBJCPLUS_INCLUDE_PATH",
94
+ "LIBRARY_PATH", "SDKROOT", "CFLAGS", "CXXFLAGS", "OBJCFLAGS", "CPPFLAGS", "LDFLAGS")
95
+
96
+
97
+ def xcode_env(developer=None, base=None):
98
+ env = {key: value for key, value in (os.environ if base is None else base).items() if key not in HOST_BUILD_ENV}
99
+ if developer:
100
+ env["DEVELOPER_DIR"] = developer
101
+ return env
102
+
103
+
104
+ # In place of asking for `sudo xcode-select -s`: only this process's environment changes, never the Mac's setting.
105
+ def adopt_xcode(environ=None):
106
+ environ = os.environ if environ is None else environ
107
+ if environ.get("DEVELOPER_DIR"):
108
+ return environ["DEVELOPER_DIR"]
109
+ xcode, selected = developer_dir()
110
+ if xcode and selected != xcode:
111
+ environ["DEVELOPER_DIR"] = xcode
112
+ return xcode
113
+
114
+
115
+ def xcode_tool(name, developer=...):
116
+ developer = developer_dir()[0] if developer is ... else developer
117
+ directories = (([str(Path(developer) / "usr" / "bin")] if developer else [])
118
+ + (list(GIT_DIRS) if name == "git" else []))
119
+ return next((path for directory in directories if (path := executable(directory, name))), None)
120
+
121
+
122
+ def execute(command, env=None, timeout=20):
123
+ """(exit code, stdout and stderr) of a short command; (None, "") if it could not run."""
124
+ try:
125
+ completed = subprocess.run(command, capture_output=True, text=True, timeout=timeout, env=env,
126
+ stdin=subprocess.DEVNULL)
127
+ except (OSError, subprocess.SubprocessError):
128
+ return None, ""
129
+ return completed.returncode, (completed.stdout or "") + (completed.stderr or "")
130
+
131
+
132
+ # The Mac's xcode-select choice is never a problem: Mobster runs Xcode's tools with DEVELOPER_DIR (adopt_xcode).
133
+ def xcode_status():
134
+ xcode, selected = developer_dir()
135
+ status = {"state": "ok", "app": str(Path(xcode).parent.parent) if xcode else None, "developer_dir": xcode,
136
+ "selected": selected, "version": None, "problems": [], "fixes": []}
137
+ problems, fixes = status["problems"], status["fixes"]
138
+ if not xcode:
139
+ problems.append("missing")
140
+ else:
141
+ xcodebuild, env = str(Path(xcode) / "usr" / "bin" / "xcodebuild"), {**os.environ, "DEVELOPER_DIR": xcode}
142
+ code, output = execute([xcodebuild, "-version"], env)
143
+ if code == 0:
144
+ status["version"] = (output.strip().splitlines() or [None])[0]
145
+ if execute([xcodebuild, "-checkFirstLaunchStatus"], env)[0] not in (0, None):
146
+ problems.append("first_launch")
147
+ fixes.append("sudo xcodebuild -runFirstLaunch")
148
+ else:
149
+ code, sdks = execute([xcodebuild, "-showsdks"], env, timeout=30)
150
+ if code == 0 and "iphoneos" not in sdks:
151
+ problems.append("no_ios")
152
+ fixes.append("xcodebuild -downloadPlatform iOS")
153
+ elif "license" in output.lower():
154
+ problems.append("license")
155
+ fixes.append("sudo xcodebuild -license accept")
156
+ else:
157
+ problems.append("broken")
158
+ status["state"] = problems[0] if problems else "ok"
159
+ return status
160
+
161
+
162
+ def count_iphones(items, simulated=False):
163
+ """iPhones in a system_profiler USB tree; a virtual one on a simulated bus doesn't count."""
164
+ total = 0
165
+ for item in items if isinstance(items, list) else ():
166
+ if not isinstance(item, dict):
167
+ continue
168
+ on_simulated = simulated or item.get("USBKeyHardwareType") == "Simulated"
169
+ name = str(item.get("_name") or "")
170
+ vendor = str(item.get("USBDeviceKeyVendorID") or item.get("vendor_id") or "").lower()
171
+ if name.startswith("iPhone") and "virtual" not in name.lower() and "0x05ac" in vendor and not on_simulated:
172
+ total += 1
173
+ total += count_iphones(item.get("_items"), on_simulated)
174
+ return total
175
+
176
+
177
+ def run(command, timeout=10):
178
+ """stdout of a short command, or None if it failed."""
179
+ try:
180
+ completed = subprocess.run(command, capture_output=True, text=True, timeout=timeout)
181
+ except (OSError, subprocess.SubprocessError):
182
+ return None
183
+ return completed.stdout if completed.returncode == 0 else None
184
+
185
+
186
+ # How long one USB listing answers ``usb_attached``: a pulled cable is known within this many seconds.
187
+ ATTACHED_TTL = 2.0
188
+ _attached_cache = {"at": None, "udids": None}
189
+ _attached_lock = threading.Lock()
190
+
191
+
192
+ def usb_attached(udid, now=None, ttl=ATTACHED_TTL):
193
+ if not isinstance(udid, str) or not UDID_PATTERN.fullmatch(udid):
194
+ return None
195
+ now = time.monotonic() if now is None else now
196
+ with _attached_lock:
197
+ at, udids = _attached_cache["at"], _attached_cache["udids"]
198
+ if at is None or now - at >= ttl:
199
+ listing = tool("idevice_id")
200
+ output = run([listing, "-l"], timeout=3) if listing else None
201
+ udids = None if output is None else {line.strip().upper() for line in output.splitlines() if line.strip()}
202
+ _attached_cache.update(at=now, udids=udids)
203
+ return None if udids is None else udid.upper() in udids
204
+
205
+
206
+ @functools.lru_cache(maxsize=4)
207
+ def iproxy_binds_loopback(path):
208
+ """Whether this iproxy takes -s; an older one cannot be kept off the LAN."""
209
+ try:
210
+ completed = subprocess.run([path, "-h"], capture_output=True, text=True, timeout=5)
211
+ except (OSError, subprocess.SubprocessError):
212
+ return False
213
+ return "--source" in completed.stdout + completed.stderr
214
+
215
+
216
+ def relay_command(udid, wda_port=WDA_PORT, video_port=VIDEO_PORT):
217
+ iproxy = tool("iproxy")
218
+ if not iproxy:
219
+ raise LookupError("The iPhone connection tools aren't installed. Open Setup › Get your Mac ready.")
220
+ if not iproxy_binds_loopback(iproxy):
221
+ raise LookupError("This iproxy can't be limited to this Mac. Update it: "
222
+ "brew upgrade libimobiledevice libusbmuxd")
223
+ return [iproxy, "-s", LOOPBACK, "-u", udid, f"{wda_port}:{WDA_PORT}", f"{video_port}:{VIDEO_PORT}"]
@@ -0,0 +1,143 @@
1
+ """Direct control of the phone from the dashboard's live view; the server refuses it while a task holds the phone."""
2
+
3
+ import math
4
+ import threading
5
+ import time
6
+
7
+ from .drivers import WDA
8
+ from .state import validate_input_text
9
+ from .wda.gestures import WDA_TAP_HOLD_MS
10
+
11
+ BUTTONS = {"home": "home", "volume_up": "volumeUp", "volume_down": "volumeDown"}
12
+ MAX_HOLD_MS = 3000
13
+ SWIPE_MS = (40, 3000)
14
+ SIZE_TTL_SECONDS = 30
15
+
16
+
17
+ def _unit(value, name):
18
+ if type(value) not in (int, float) or not 0 <= value <= 1:
19
+ raise ValueError(f"{name} must be a number from 0 to 1")
20
+ return float(value)
21
+
22
+
23
+ def parse_command(body):
24
+ """A validated gesture, or ValueError. Unknown fields are refused, never ignored."""
25
+ if not isinstance(body, dict):
26
+ raise ValueError("A control command must be an object")
27
+ kind = body.get("action")
28
+ allowed = {"tap": {"x", "y", "holdMs"}, "swipe": {"x1", "y1", "x2", "y2", "durationMs"},
29
+ "type": {"text", "submit"}, "button": {"name"}}
30
+ if kind not in allowed:
31
+ raise ValueError("action must be tap, swipe, type or button")
32
+ extra = set(body) - allowed[kind] - {"action"}
33
+ if extra:
34
+ raise ValueError(f"Unsupported field: {sorted(extra)[0]}")
35
+ if kind == "tap":
36
+ hold = body.get("holdMs", WDA_TAP_HOLD_MS)
37
+ if type(hold) is not int or not 0 <= hold <= MAX_HOLD_MS:
38
+ raise ValueError(f"holdMs must be 0 to {MAX_HOLD_MS}")
39
+ return {"action": "tap", "x": _unit(body.get("x"), "x"), "y": _unit(body.get("y"), "y"), "holdMs": hold}
40
+ if kind == "swipe":
41
+ duration = body.get("durationMs", 250)
42
+ if type(duration) is not int or not SWIPE_MS[0] <= duration <= SWIPE_MS[1]:
43
+ raise ValueError(f"durationMs must be {SWIPE_MS[0]} to {SWIPE_MS[1]}")
44
+ return {"action": "swipe", **{k: _unit(body.get(k), k) for k in ("x1", "y1", "x2", "y2")},
45
+ "durationMs": duration}
46
+ if kind == "type":
47
+ submit = body.get("submit", False)
48
+ if type(submit) is not bool:
49
+ raise ValueError("submit must be true or false")
50
+ return {"action": "type", "text": validate_input_text(body.get("text")), "submit": submit}
51
+ if body.get("name") not in BUTTONS:
52
+ raise ValueError("name must be home, volume_up or volume_down")
53
+ return {"action": "button", "name": body["name"]}
54
+
55
+
56
+ class ManualControl:
57
+ def __init__(self, wda_url, session_provider):
58
+ self.wda_url, self.session_provider = wda_url, session_provider
59
+ self.lock = threading.Lock()
60
+ self.driver = None
61
+ self.session = None
62
+ self.size = None # (width, height, read_at)
63
+
64
+ def _driver(self):
65
+ session = self.session_provider()
66
+ if not session:
67
+ raise ConnectionError("Your iPhone is not connected")
68
+ if self.driver is None or session != self.session:
69
+ self.close()
70
+ self.driver, self.session, self.size = WDA(self.wda_url, session), session, None
71
+ return self.driver
72
+
73
+ def _points(self, driver):
74
+ """The screen in points. /wda/screen measures through SpringBoard; /window/size
75
+ goes through the "active" app, which can be a transient system overlay (measured
76
+ on iOS 26: an 8-10 s stall, then a stale-element error), so it is only a fallback."""
77
+ if self.size is None or time.monotonic() - self.size[2] > SIZE_TTL_SECONDS:
78
+ size = None
79
+ try:
80
+ screen = WDA.response_value(driver.http.request("GET", "/wda/screen", None, 3))
81
+ size = screen.get("screenSize") if isinstance(screen, dict) else None
82
+ except Exception:
83
+ size = None
84
+ if not self._valid(size):
85
+ size = driver.call("GET", "/window/size", timeout=3)
86
+ if not self._valid(size):
87
+ raise ConnectionError("Your iPhone did not report its screen size")
88
+ self.size = (float(size["width"]), float(size["height"]), time.monotonic())
89
+ return self.size[:2]
90
+
91
+ @staticmethod
92
+ def _valid(size):
93
+ return isinstance(size, dict) and all(
94
+ type(size.get(k)) in (int, float) and math.isfinite(size[k]) and size[k] > 0 for k in ("width", "height"))
95
+
96
+ def perform(self, body, teach=None):
97
+ """Send one gesture; with ``teach``, the screen is read first and nothing is sent when it can't be."""
98
+ command = parse_command(body)
99
+ with self.lock:
100
+ driver = self._driver()
101
+ before = teach.read(driver) if teach is not None else None
102
+ try:
103
+ self._dispatch(driver, command)
104
+ except Exception:
105
+ # A dropped session is re-resolved on the next gesture.
106
+ self.close()
107
+ raise
108
+ if teach is not None:
109
+ teach.record(command, before)
110
+ return command["action"]
111
+
112
+ def observe(self, timeout=8):
113
+ """The screen now (a teach session's end screen)."""
114
+ with self.lock:
115
+ return self._driver().observe(timeout=timeout)
116
+
117
+ def _dispatch(self, driver, command):
118
+ kind = command["action"]
119
+ if kind == "button":
120
+ driver.call("POST", "/wda/pressButton", {"name": BUTTONS[command["name"]]}, timeout=5)
121
+ return
122
+ if kind == "type":
123
+ keys = [command["text"], "\n"] if command["submit"] else [command["text"]]
124
+ driver.call("POST", "/wda/keys", {"value": keys}, timeout=10)
125
+ return
126
+ width, height = self._points(driver)
127
+ if kind == "tap":
128
+ x, y = round(command["x"] * width), round(command["y"] * height)
129
+ steps = [{"type": "pointerMove", "duration": 0, "x": x, "y": y}, {"type": "pointerDown"},
130
+ {"type": "pause", "duration": command["holdMs"]}, {"type": "pointerUp"}]
131
+ else:
132
+ steps = [{"type": "pointerMove", "duration": 0, "x": round(command["x1"] * width),
133
+ "y": round(command["y1"] * height)}, {"type": "pointerDown"},
134
+ {"type": "pointerMove", "duration": command["durationMs"], "x": round(command["x2"] * width),
135
+ "y": round(command["y2"] * height)}, {"type": "pointerUp"}]
136
+ driver.call("POST", "/actions", {"actions": [{"type": "pointer", "id": "finger",
137
+ "parameters": {"pointerType": "touch"}, "actions": steps}]},
138
+ timeout=10 + (command.get("holdMs", 0) + command.get("durationMs", 0)) / 1000)
139
+
140
+ def close(self):
141
+ driver, self.driver, self.session = self.driver, None, None
142
+ if driver is not None:
143
+ driver.close()
mobile_agent/maps.py ADDED
@@ -0,0 +1,149 @@
1
+ """Places on a map, for a result's ``map`` block and a table's Map view (docs/results.mdx).
2
+
3
+ ``snapshot(places, ...)`` draws them on an Apple Maps snapshot made on this Mac by the ``map`` helper
4
+ (helper_map.swift: MapKit off screen, no window): {"image": PNG bytes at 2x, "width", "height" (points), "places":
5
+ [{"id", "lat", "lon", "x", "y"}], "missing": [ids]}. A place that has no coordinates is found by its address, then
6
+ by its name near ``near``, with Apple's geocoder. The page draws its own numbered pins over the picture (x and y are
7
+ where each one goes), so a pin is a button that names its place and says where it is.
8
+
9
+ Snapshots are kept on disk by what they show (the places, the appearance and the size), so a result opened again
10
+ draws at once and asks Apple's geocoder nothing. What reaches the helper is a place's name and address or
11
+ coordinates: the same words the result already shows.
12
+
13
+ ``places_of(block)``: a ``map`` block's places, or a table's rows as places (a Name column, an Address or Location
14
+ column, or Latitude and Longitude columns).
15
+ """
16
+
17
+ import hashlib
18
+ import json
19
+ import re
20
+ import subprocess
21
+ import tempfile
22
+ import threading
23
+ from pathlib import Path
24
+
25
+ SECONDS = 45
26
+ KEEP = 200
27
+ MAX_PLACES = 30
28
+ _lock = threading.BoundedSemaphore(2)
29
+
30
+ NAME = re.compile(r"^(name|place|title|restaurant|store|shop|venue|business|cafe|café|spot)$", re.I)
31
+ ADDRESS = re.compile(r"^(address|location|where|street|addr\.?)$", re.I)
32
+ LAT = re.compile(r"^(lat|latitude)$", re.I)
33
+ LON = re.compile(r"^(lon|lng|long|longitude)$", re.I)
34
+
35
+
36
+ class MapError(RuntimeError):
37
+ """The map couldn't be drawn; the message says why, in plain words."""
38
+
39
+
40
+ def _column(columns, pattern):
41
+ return next((c["key"] for c in columns if pattern.match(str(c.get("label") or "").strip())), None)
42
+
43
+
44
+ def places_of(block):
45
+ """[{"id", "name", "address"?, "lat"?, "lon"?}] for a map block or a table with places, else []."""
46
+ if not isinstance(block, dict):
47
+ return []
48
+ if block.get("type") == "map":
49
+ return [{key: place[key] for key in ("id", "name", "address", "lat", "lon") if key in place}
50
+ for place in block.get("places") or ()][:MAX_PLACES]
51
+ if block.get("type") != "table":
52
+ return []
53
+ columns = block.get("columns") or []
54
+ address, lat, lon = _column(columns, ADDRESS), _column(columns, LAT), _column(columns, LON)
55
+ if address is None and (lat is None or lon is None):
56
+ return []
57
+ name = _column(columns, NAME) or next((c["key"] for c in columns if c["key"] not in (address, lat, lon)), None)
58
+ places = []
59
+ for index, row in enumerate(block.get("rows") or ()):
60
+ cells = row.get("cells") or {}
61
+ place = {"id": f"r{index}", "name": str(cells.get(name) or f"Place {index + 1}")[:120]}
62
+ if address is not None and isinstance(cells.get(address), str) and cells[address].strip():
63
+ place["address"] = cells[address].strip()[:200]
64
+ if lat is not None and lon is not None:
65
+ try:
66
+ y, x = float(cells.get(lat)), float(cells.get(lon))
67
+ if -90 <= y <= 90 and -180 <= x <= 180:
68
+ place["lat"], place["lon"] = y, x
69
+ except (TypeError, ValueError):
70
+ pass
71
+ if "address" in place or "lat" in place:
72
+ places.append(place)
73
+ if len(places) >= MAX_PLACES:
74
+ break
75
+ return places
76
+
77
+
78
+ def _cache_dir():
79
+ from .paths import user_data_dir
80
+ folder = Path(user_data_dir()) / "cache" / "maps"
81
+ folder.mkdir(parents=True, exist_ok=True, mode=0o700)
82
+ return folder
83
+
84
+
85
+ def _prune(folder):
86
+ files = sorted(folder.glob("*.png"), key=lambda path: path.stat().st_mtime)
87
+ for old in files[:-KEEP]:
88
+ for path in (old, old.with_suffix(".json")):
89
+ path.unlink(missing_ok=True)
90
+
91
+
92
+ def snapshot(places, *, near=None, dark=False, width=720, height=360, helper=None, cache_dir=None,
93
+ timeout=SECONDS):
94
+ """The places on a map (see the module docstring). MapError when there's nothing to draw or no helper."""
95
+ places = [p for p in places or () if isinstance(p, dict) and p.get("name")][:MAX_PLACES]
96
+ if not places:
97
+ raise MapError("There are no places to show on a map.")
98
+ width, height = max(200, min(1200, int(width))), max(120, min(900, int(height)))
99
+ request = {"places": places, "near": near, "width": width, "height": height, "dark": bool(dark)}
100
+ key = hashlib.sha256(json.dumps(request, sort_keys=True).encode()).hexdigest()[:24]
101
+ folder = Path(cache_dir) if cache_dir is not None else _cache_dir()
102
+ image, meta = folder / f"{key}.png", folder / f"{key}.json"
103
+ if image.is_file() and meta.is_file():
104
+ try:
105
+ return {**json.loads(meta.read_text()), "image": image.read_bytes()}
106
+ except (OSError, ValueError):
107
+ pass
108
+ if helper is None:
109
+ from . import native_helpers
110
+ helper = native_helpers.find("map")
111
+ if helper is None:
112
+ try:
113
+ helper = native_helpers.build("map")
114
+ except (LookupError, OSError, subprocess.SubprocessError) as error:
115
+ raise MapError("Mobster can't draw maps on this Mac: " + str(error)) from None
116
+ if not _lock.acquire(timeout=timeout):
117
+ raise MapError("Mobster is drawing other maps. Try again in a moment.")
118
+ try:
119
+ with tempfile.TemporaryDirectory(prefix="mobster-map-") as work:
120
+ source, target = Path(work) / "places.json", Path(work) / "map.png"
121
+ source.write_text(json.dumps(request), encoding="utf-8")
122
+ try:
123
+ done = subprocess.run([str(helper), str(source), str(target)], capture_output=True, text=True,
124
+ timeout=timeout)
125
+ except subprocess.TimeoutExpired:
126
+ raise MapError("Mobster took too long to draw the map. Try again.") from None
127
+ except OSError as error:
128
+ raise MapError(f"Mobster couldn't draw the map: {error}") from None
129
+ if done.returncode != 0 or not target.is_file():
130
+ lines = (done.stderr or "").strip().splitlines()
131
+ said = next((l for l in reversed(lines) if l.startswith("Mobster")), "Mobster couldn't draw the map")
132
+ raise MapError(said if said.endswith(".") else said + ".")
133
+ try:
134
+ answer = json.loads((done.stdout or "").strip().splitlines()[-1])
135
+ except (ValueError, IndexError):
136
+ raise MapError("Mobster couldn't read where the places are.") from None
137
+ data = target.read_bytes()
138
+ finally:
139
+ _lock.release()
140
+ out = {"width": answer.get("width", width), "height": answer.get("height", height),
141
+ "places": [p for p in answer.get("places") or () if isinstance(p, dict)],
142
+ "missing": [m for m in answer.get("missing") or () if isinstance(m, str)]}
143
+ try:
144
+ image.write_bytes(data)
145
+ meta.write_text(json.dumps(out))
146
+ _prune(folder)
147
+ except OSError:
148
+ pass
149
+ return {**out, "image": data}
mobile_agent/marks.py ADDED
@@ -0,0 +1,92 @@
1
+ """Set-of-Mark: numbered boxes on the screenshot, one per element a decision model may pick.
2
+
3
+ A decision model answers "which mark" and "which gesture"; code turns the mark back into the exact element (its
4
+ AX frame's centre when it taps), so the model never writes a coordinate.
5
+
6
+ ``Mark`` keeps the element; ``mark_lines`` is the same list as text, for a text-only decider (Jev) and so both
7
+ deciders get identical options.
8
+ """
9
+
10
+ import io
11
+ from dataclasses import dataclass
12
+
13
+ TARGET_ROLES = frozenset({"Button", "Cell", "Link", "TextField", "SearchField", "SecureTextField", "TextView",
14
+ "Switch", "Toggle", "Tab", "TabBar", "MenuItem", "Image", "Icon", "SegmentedControl",
15
+ "Key", "PickerWheel", "Slider", "Stepper", "Other", "StaticText"})
16
+ MAX_MARKS = 120
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Mark:
21
+ id: str
22
+ element: object
23
+ rect: tuple
24
+ label: str
25
+ role: str
26
+
27
+ @property
28
+ def center(self):
29
+ x, y, w, h = self.rect
30
+ return x + w / 2, y + h / 2
31
+
32
+
33
+ def element_marks(elements, limit=MAX_MARKS):
34
+ """Marks for the targetable elements on screen, top to bottom, ids "1", "2"...; at most ``limit``."""
35
+ out = []
36
+ for element in elements:
37
+ if len(out) >= limit:
38
+ break
39
+ x, y, w, h = element.rect
40
+ if w <= 0 or h <= 0 or y >= 1 or y + h <= 0:
41
+ continue
42
+ if element.role not in TARGET_ROLES and not getattr(element, "editable", False):
43
+ continue
44
+ out.append(Mark(str(len(out) + 1), element, tuple(element.rect), (element.label or "")[:120], element.role))
45
+ return out
46
+
47
+
48
+ def mark_lines(marks):
49
+ """The marks as text: ``7 Button "Search" @4,14,92,5`` (frame in percent)."""
50
+ lines = []
51
+ for mark in marks:
52
+ x, y, w, h = (round(v * 100) for v in mark.rect)
53
+ label = f'"{mark.label}"' if mark.label else "(no label)"
54
+ value = getattr(mark.element, "value", "")
55
+ value = f" value={str(value)[:40]!r}" if value and value != mark.label else ""
56
+ lines.append(f"{mark.id} {mark.role} {label}{value} @{x},{y},{w},{h}")
57
+ return lines
58
+
59
+
60
+ def options(marks, *, abstain=True):
61
+ """A choice question's options for these marks (value = mark id), plus "none"."""
62
+ out = {mark.id: line for mark, line in zip(marks, mark_lines(marks))}
63
+ if abstain:
64
+ out["none"] = "None of the marked elements is right"
65
+ return out
66
+
67
+
68
+ def draw(jpeg, marks, side=None):
69
+ """The screenshot (JPEG bytes) with each mark's box and number drawn; JPEG bytes back."""
70
+ from PIL import Image, ImageDraw, ImageFont
71
+ image = Image.open(io.BytesIO(jpeg)).convert("RGB")
72
+ if side and image.width > side:
73
+ image = image.resize((side, round(image.height * side / image.width)))
74
+ canvas = ImageDraw.Draw(image)
75
+ try:
76
+ font = ImageFont.load_default(size=max(10, image.width // 36))
77
+ except TypeError:
78
+ font = ImageFont.load_default()
79
+ W, H = image.size
80
+ for mark in marks:
81
+ x, y, w, h = mark.rect
82
+ box = (int(x * W), int(y * H), int((x + w) * W), int((y + h) * H))
83
+ colour = (255, 40, 120)
84
+ canvas.rectangle(box, outline=colour, width=2)
85
+ text = mark.id
86
+ tw, th = canvas.textbbox((0, 0), text, font=font)[2:]
87
+ tag = (box[0], max(0, box[1] - th - 2), box[0] + tw + 4, max(th + 2, box[1]))
88
+ canvas.rectangle(tag, fill=colour)
89
+ canvas.text((tag[0] + 2, tag[1]), text, fill=(255, 255, 255), font=font)
90
+ out = io.BytesIO()
91
+ image.save(out, "JPEG", quality=70)
92
+ return out.getvalue()
@@ -0,0 +1,3 @@
1
+ """`mobster mcp`: the MCP server that coding agents use to check iOS screens."""
2
+
3
+ __all__ = ["cli", "protocol", "session", "tools"]
@@ -0,0 +1,29 @@
1
+ """`python -m mobile_agent.mcp_server`: the same server as `mobster mcp`, with the same flags."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ DESCRIPTION = (
7
+ "Serve Mobster's tools to a coding agent over MCP (stdio). The agent starts a check\n"
8
+ "with verify_start, drives the app on a headless simulator, and gets the verdict\n"
9
+ "from verify_finish. With an OpenAI or Anthropic key, smart runs the steps itself\n"
10
+ "(Smart), on the model Smart picks for that key or the one MOBSTER_SMART_MODEL names.")
11
+
12
+
13
+ def build_parser():
14
+ from pathlib import Path
15
+
16
+ from .cli import add_arguments
17
+ parser = argparse.ArgumentParser(prog="python -m mobile_agent.mcp_server", description=DESCRIPTION,
18
+ formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
19
+ add_arguments(parser, {"path_type": lambda value: Path(value).expanduser()})
20
+ return parser
21
+
22
+
23
+ def main(argv=None):
24
+ from .cli import run
25
+ return run(build_parser().parse_args(argv))
26
+
27
+
28
+ if __name__ == "__main__":
29
+ sys.exit(main())
@@ -0,0 +1,24 @@
1
+ """Durable intent and outcome for direct MCP mutations, including key-less checks."""
2
+
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+ from uuid import uuid4
6
+
7
+ from ..journal import append_jsonl
8
+
9
+
10
+ @contextmanager
11
+ def record_action(run_dir, operation):
12
+ # Arguments can contain passwords, codes and private URLs. Record only the
13
+ # operation here; the existing step records own their redacted descriptions.
14
+ path = Path(run_dir) / "actions.jsonl"
15
+ action = {"id": uuid4().hex, "operation": operation}
16
+ append_jsonl(path, {"event": "action_started", **action})
17
+ try:
18
+ yield
19
+ except BaseException:
20
+ # A timeout or failed screen read cannot prove that nothing happened.
21
+ append_jsonl(path, {"event": "action_finished", **action, "outcome": "unknown"})
22
+ raise
23
+ else:
24
+ append_jsonl(path, {"event": "action_finished", **action, "outcome": "acknowledged"})