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,306 @@
1
+ """Finding the devices the tools may drive, who holds them, and the direct session on one."""
2
+
3
+ import time
4
+
5
+ from ..android.records import is_phone
6
+ from .protocol import ToolResult
7
+ from .session import Session
8
+ from .support import SHUTTING_DOWN, ToolError, cap_text, jsonable, sentence
9
+
10
+ DEVICE_LIST_TTL = 30.0
11
+
12
+
13
+ class DeviceTools:
14
+ def _device_records(self, fresh=False, probe=None):
15
+ at, records = self._devices_cache
16
+ if records is None or fresh or time.monotonic() - at > DEVICE_LIST_TTL:
17
+ if self._devices_source is not None:
18
+ records = self._devices_source()
19
+ else:
20
+ from ..android.records import records as android_records
21
+ from ..devices import discover
22
+ records = discover(probe=fresh if probe is None else probe) + android_records()
23
+ self._devices_cache = (time.monotonic(), records)
24
+ return records
25
+
26
+ def _app_ownership(self):
27
+ if self._ownership_source is not None:
28
+ return self._ownership_source()
29
+ if self._devices_source is not None:
30
+ return {}
31
+ at, found = self._ownership_cache
32
+ if time.monotonic() - at < 3.0:
33
+ return found
34
+ found = {}
35
+ try:
36
+ from ..threads.client import Client
37
+ listing = Client.connect(origin="mcp").call("GET", "/api/devices", timeout=2.0)
38
+ for item in listing.get("devices") or ():
39
+ if isinstance(item, dict) and isinstance(item.get("id"), str):
40
+ found[item["id"]] = {"holder": item.get("holder"), "queue": item.get("queue") or []}
41
+ except Exception: # noqa: BLE001 -- no app, an older app, a refused token
42
+ found = {}
43
+ self._ownership_cache = (time.monotonic(), found)
44
+ return found
45
+
46
+ def _holder_of(self, record):
47
+ from .. import ownership
48
+ for session in self._all_sessions() + self._direct_sessions():
49
+ run = session.run
50
+ if session.open and getattr(run, "device_id", None) in (record.get("id"), record.get("udid")):
51
+ since = time.time() - max(0.0, self.clock() - session.started)
52
+ label = ownership.clean_client(self.client_name()) or "this MCP client"
53
+ return {"kind": "mcp", "label": label, "runId": session.run_id, "since": int(since * 1000),
54
+ "stopping": False}, []
55
+ if record.get("kind") == "simulator":
56
+ other = self._simulator_holder(record)
57
+ if other is not None:
58
+ return other, []
59
+ known = self._app_ownership().get(record.get("id")) or {}
60
+ return known.get("holder"), known.get("queue") or []
61
+
62
+ def _simulator_holder(self, record):
63
+ """The holder of a Mobster simulator in another process (an agent's session, a test run), from its record."""
64
+ from ..sim import holders
65
+ data_dir = getattr(self._manager_instance, "data_dir", None)
66
+ if data_dir is None:
67
+ from ..devices import simulator_folder
68
+ data_dir = simulator_folder()
69
+ found = holders.read(data_dir, record.get("udid") or record.get("id"))
70
+ if found is None:
71
+ return None
72
+ return {"kind": "mcp", "label": f"{found['client']} ({found['session']})", "runId": None,
73
+ "since": int(found["since"] * 1000), "stopping": False}
74
+
75
+ @staticmethod
76
+ def _holder_words(holder, queue):
77
+ from .. import ownership
78
+ if not holder:
79
+ return ""
80
+ minutes = max(0, int((time.time() * 1000 - holder["since"]) // 60000))
81
+ words = f"{ownership.holding_sentence(holder)}, {minutes} min" + (", stopping after this step"
82
+ if holder.get("stopping") else "")
83
+ return words + (f"; {len(queue)} waiting" if queue else "")
84
+
85
+ def _named_device(self, name, required=False):
86
+ from ..devices import AmbiguousDevice, DeviceNotFound, resolve
87
+ if not name:
88
+ return None
89
+ for attempt in range(2):
90
+ try:
91
+ return resolve(self._device_records(fresh=attempt > 0, probe=False), name)
92
+ except AmbiguousDevice as error:
93
+ raise ToolError(str(error)) from None
94
+ except DeviceNotFound as error:
95
+ if attempt == 0 and time.monotonic() - self._devices_cache[0] > 1.0:
96
+ continue
97
+ if required:
98
+ raise ToolError(f"{error} Call list_devices to see them.") from None
99
+ return None
100
+ return None
101
+
102
+ def _open_direct(self, name):
103
+ folded = str(name or "").strip().casefold()
104
+ if not folded:
105
+ return None
106
+ sessions = self._direct_sessions()
107
+ for session in sessions:
108
+ record = getattr(session.run, "record", None) or {}
109
+ if folded in {str(record.get("id") or "").casefold(), str(record.get("udid") or "").casefold()}:
110
+ return session
111
+ named = [session for session in sessions
112
+ if str((getattr(session.run, "record", None) or {}).get("name") or "").casefold() == folded]
113
+ return named[0] if len(named) == 1 else None
114
+
115
+ def _direct_sessions(self, open_only=True):
116
+ with self._lock:
117
+ return [s for s in self.device_sessions.values() if s.open or not open_only]
118
+
119
+ def _allowed(self, record):
120
+ if not is_phone(record) or self.allow_devices is None:
121
+ return True
122
+ names = {str(record.get(key) or "").strip().casefold() for key in ("id", "udid", "name")}
123
+ return bool(names & self.allow_devices)
124
+
125
+ def _refuse_unless_allowed(self, record):
126
+ if not self._allowed(record):
127
+ raise ToolError(f"“{record['name']}” is a real device, and this server may drive only the ones "
128
+ "`mobster mcp --allow-device` names. Ask the user to add it to the server's command.")
129
+
130
+ def _device_busy_with_check(self, record):
131
+ for session in self._all_sessions():
132
+ run = session.run
133
+ if (session.open
134
+ and getattr(getattr(run, "target", None), "udid", None) in (record.get("udid"), record["id"])):
135
+ return session
136
+ if session.open and getattr(run, "device_id", None) == record["id"]:
137
+ return session
138
+ return None
139
+
140
+ def _device_session(self, name):
141
+ session = self._open_direct(name)
142
+ if session is not None and session.open:
143
+ session.touch()
144
+ return session
145
+ record = self._named_device(name, required=True)
146
+ with self._lock:
147
+ session = self.device_sessions.get(record["id"])
148
+ if session is not None and session.open:
149
+ session.touch()
150
+ return session
151
+ if self._closing.is_set():
152
+ raise ToolError(SHUTTING_DOWN)
153
+ check = self._device_busy_with_check(record)
154
+ if check is not None:
155
+ raise ToolError(f"Run {check.run_id} is checking an app on {record['name']}. Pass run_id "
156
+ f"{check.run_id}, or stop that run first.")
157
+ self._refuse_unless_allowed(record)
158
+ if record.get("platform") == "android":
159
+ raise ToolError(f"“{record['name']}” is an Android device: use verify_start with device, or run_tests. "
160
+ "Driving it directly with tap and screen isn't available yet.")
161
+ if record.get("state") in ("needs_setup", "disconnected") or not record.get("wdaUrl"):
162
+ raise ToolError(f"“{record['name']}” isn't ready: {record.get('reason') or record['state']}")
163
+ from .direct import DeviceRun
164
+ factory = self._device_run or DeviceRun
165
+ folder = self.runs_dir if record.get("kind") == "simulator" else self.device_runs_dir
166
+ run = factory(record, runs_dir=folder)
167
+ session = self._open_direct_run(run, record)
168
+ self.log(f"Run {session.run_id}: driving {record['name']} ({record['kind']}) directly.")
169
+ return session
170
+
171
+ def _open_direct_run(self, run, record, *, own=False):
172
+ """Prepare ``run`` and file it as the direct session on ``record``'s device. Call it under self._lock."""
173
+ try:
174
+ run.prepare()
175
+ except Exception as error:
176
+ run.close("It couldn't be opened.")
177
+ fix = getattr(error, "fix", "")
178
+ raise ToolError(sentence(error) + (" " + sentence(fix) if fix else "")) from None
179
+ if own:
180
+ # the simulator is known only once its lease is taken
181
+ udid, name = run.target.udid, run.target.name
182
+ record.update(id=udid, udid=udid, name=name)
183
+ run.device_id, run.device_name = udid, name
184
+ self._own_sim = udid
185
+ session = Session(run, "direct", clock=self.clock, image=True)
186
+ self.codes.seed(session, record["id"])
187
+ session.message = f"Driving {record['name']} directly."
188
+ session.set_state("ready")
189
+ self.device_sessions[record["id"]] = session
190
+ return session
191
+
192
+ def _yield_session_simulator(self, args):
193
+ """A check that names no device takes a free simulator itself: hand back the one this session leased, so
194
+ one session never holds two."""
195
+ session = self.device_sessions.get(self._own_sim) if self._own_sim else None
196
+ if args.get("device") or self.device or session is None or not session.open or session.busy:
197
+ return
198
+ self._close_direct(session, "A check is starting, so Mobster handed this session's simulator to it.",
199
+ timeout=0)
200
+
201
+ def _session_simulator(self):
202
+ """This session's own simulator: leased on the first call that names no device, kept until the session
203
+ ends or sits idle for 30 minutes, and never given to another session while held."""
204
+ with self._lock:
205
+ session = self.device_sessions.get(self._own_sim) if self._own_sim else None
206
+ if session is not None and session.open:
207
+ session.touch()
208
+ return session
209
+ self._own_sim = None
210
+ if self._closing.is_set():
211
+ raise ToolError(SHUTTING_DOWN)
212
+ from .direct import DeviceRun
213
+ record = {"id": "", "udid": "", "kind": "simulator", "name": "a Mobster simulator"}
214
+ # no queueing: when every simulator is held, the answer names the holders at once
215
+ run = (self._device_run or DeviceRun)(record, runs_dir=self.runs_dir, manager=self._manager(),
216
+ acquire_timeout=0)
217
+ session = self._open_direct_run(run, record, own=True)
218
+ self.log(f"Run {session.run_id}: leased {record['name']} for this session.")
219
+ return session
220
+
221
+ def _close_direct(self, session, reason, timeout=None):
222
+ if not session.open:
223
+ return True
224
+ locked = session.run.lock.acquire(timeout=self.lock_wait if timeout is None else max(0.0, timeout))
225
+ if not locked:
226
+ return False
227
+ try:
228
+ if session.open:
229
+ if self._own_sim and session.run.device_id == self._own_sim:
230
+ self._own_sim = None
231
+ session.set_state("finished", jsonable(session.run.close(reason)))
232
+ self.log(f"Run {session.run_id}: {reason}")
233
+ finally:
234
+ session.run.lock.release()
235
+ return True
236
+
237
+ def _target_session(self, args):
238
+ run_id, device = args.get("run_id"), args.get("device")
239
+ if run_id:
240
+ session = self._ready_session(run_id)
241
+ if device:
242
+ record = self._named_device(device, required=True)
243
+ run = session.run
244
+ on = getattr(run, "device_id", None) or getattr(getattr(run, "target", None), "udid", None)
245
+ if on not in (record["id"], record.get("udid")):
246
+ raise ToolError(f"Run {run_id} isn't on {record['name']}. Leave out device, or pass the run_id "
247
+ "of a run on it.")
248
+ return session
249
+ if device:
250
+ name = device
251
+ elif self.device and self._open_direct(self.device) is not None:
252
+ name = self.device
253
+ else:
254
+ name = self.device if self._named_device(self.device) is not None else None
255
+ if not name:
256
+ return self._session_simulator()
257
+ return self._device_session(name)
258
+
259
+ def tool_list_devices(self, args, call):
260
+ from ..devices import describe
261
+ records = self._device_records(fresh=True)
262
+ open_runs = {s.run.device_id: s.run_id for s in self._all_sessions() + self._direct_sessions()
263
+ if s.open and getattr(s.run, "device_id", None)}
264
+ rows = []
265
+ for item in records:
266
+ row = {key: item.get(key) for key in ("id", "kind", "name", "model", "modelName", "ios", "state", "reason")}
267
+ row["platform"] = item.get("platform") or "ios"
268
+ row["run_id"] = open_runs.get(item["id"])
269
+ row["holder"], row["queue"] = self._holder_of(item)
270
+ row["yours"] = bool(self._own_sim) and item["id"] == self._own_sim
271
+ if not self._allowed(item):
272
+ row["state"] = "not_allowed"
273
+ row["reason"] = "this server may drive only the devices `mobster mcp --allow-device` names"
274
+ rows.append(row)
275
+ if not rows:
276
+ return ToolResult("No devices. Plug in an iPhone set up in the Mobster app, or prepare a simulator "
277
+ "(`mobster sim prepare`).", {"devices": []})
278
+ lines = [describe(item) + f" (id {item['id']})" + (f", open in run {row['run_id']}" if row["run_id"] else "")
279
+ + (", not allowed: not named by --allow-device" if row["state"] == "not_allowed" else "")
280
+ + (", yours" if row["yours"] else "")
281
+ + (f". {self._holder_words(row['holder'], row['queue'])}" if row["holder"] and not row["yours"]
282
+ else "")
283
+ for item, row in zip(records, rows)]
284
+ return ToolResult(cap_text("\n".join(lines)), {"devices": rows})
285
+
286
+ def _phone_record(self, session):
287
+ record = getattr(session.run, "record", None)
288
+ if isinstance(record, dict):
289
+ return record
290
+ device_id = getattr(session.run, "device_id", None)
291
+ try:
292
+ return self._named_device(device_id) if device_id else None
293
+ except ToolError:
294
+ return None
295
+
296
+ def _on_phone(self, session):
297
+ record = self._phone_record(session)
298
+ return bool(record) and is_phone(record)
299
+
300
+ def _record_for(self, args):
301
+ name = args.get("device") or self.device
302
+ if not name:
303
+ raise ToolError("Pass device (from list_devices).")
304
+ record = self._named_device(name, required=True)
305
+ self._refuse_unless_allowed(record)
306
+ return record
@@ -0,0 +1,272 @@
1
+ """A device the phone tools drive directly, outside a check (SPEC §3: MCP drives real USB iPhones too)."""
2
+
3
+ import re
4
+ import threading
5
+ import time
6
+ from pathlib import Path
7
+ from urllib.parse import urlsplit
8
+
9
+ FRAME_QUALITY = 75
10
+ EVALUATE_PAUSE = .5
11
+
12
+
13
+ def url_app(url):
14
+ # An ordinary web link stays in Safari, so the person is asked about Safari before the page opens.
15
+ from ..deeplinks import app_for
16
+ return app_for(url) or ("com.apple.mobilesafari" if urlsplit(url).scheme.casefold() in ("http", "https") else "")
17
+
18
+
19
+ class PhoneStop(Exception):
20
+ def __init__(self, verdict):
21
+ super().__init__(getattr(verdict, "message", "") or "Your iPhone isn't ready. No action was taken.")
22
+ self.code = getattr(verdict, "code", "") or "phone_locked"
23
+ self.verdict = verdict
24
+
25
+
26
+ CHECK_FAILED = "Mobster couldn't check whether your iPhone is unlocked, so it stopped."
27
+ VERDICTS = ("ready", "unlocked", "stop")
28
+
29
+
30
+ def strict_check(guard, driver, cause):
31
+ from ..agent_hooks import GuardVerdict
32
+ try:
33
+ verdict = guard.check(driver, cause=cause)
34
+ except Exception:
35
+ verdict = None
36
+ if getattr(verdict, "state", None) not in VERDICTS:
37
+ return GuardVerdict("stop", "phone_locked", CHECK_FAILED)
38
+ return verdict
39
+
40
+
41
+ def _decline(*_args, **_kwargs):
42
+ # MCP has nobody to ask: an approval the guard would want is declined.
43
+ return "denied"
44
+
45
+
46
+ def default_guard(record, emit=None):
47
+ if record.get("kind") == "simulator":
48
+ return None
49
+ from ..lockscreen import guard_for
50
+ return guard_for(device_id=record["id"], wda_url=record.get("wdaUrl"), mode="scripts", approve=_decline,
51
+ emit=emit or (lambda event: None))
52
+
53
+
54
+ def lockscreen_available():
55
+ try:
56
+ from ..lockscreen import CAN_UNLOCK
57
+ except ImportError:
58
+ return False
59
+ return CAN_UNLOCK is True
60
+
61
+
62
+ def unlock_settings(record):
63
+ keys = {"enabled": False, "askBeforeUnlocking": True, "scripts": False, "relockAfter": True, "needsCheck": False}
64
+ settings = None
65
+ try:
66
+ from .. import lockscreen
67
+ reader = getattr(lockscreen, "unlock_settings", None)
68
+ if callable(reader):
69
+ settings = reader(record["id"])
70
+ except ImportError:
71
+ pass
72
+ except Exception:
73
+ settings = None
74
+ if not isinstance(settings, dict):
75
+ try:
76
+ from ..devices import DeviceStore
77
+ from ..paths import user_data_dir
78
+ entry = next((item for item in DeviceStore(user_data_dir()).phones()
79
+ if item.get("udid") in (record.get("udid"), record.get("id"))), None)
80
+ settings = (entry or {}).get("unlock")
81
+ except Exception:
82
+ settings = None
83
+ settings = settings if isinstance(settings, dict) else {}
84
+ return {key: settings.get(key) is True if not default else settings.get(key) is not False
85
+ for key, default in keys.items()}
86
+
87
+
88
+ def passcode_saved(record):
89
+ try:
90
+ from .. import passcode_vault
91
+ except ImportError:
92
+ return False
93
+ try:
94
+ return bool(passcode_vault.exists(record.get("udid") or record["id"]))
95
+ except Exception:
96
+ return False
97
+
98
+
99
+ class DeviceRun:
100
+ mode = "direct"
101
+
102
+ def __init__(self, record, *, runs_dir, manager=None, driver_factory=None, guard_factory=None, emit=None,
103
+ acquire_timeout=None):
104
+ from ..device_targets import WdaDevice
105
+ from ..verify.runs import make_run_dir
106
+ self.record = record
107
+ self.device_id = record["id"]
108
+ self.device_name = record.get("name") or record["id"]
109
+ self.manager = manager or WdaDevice(record)
110
+ self._driver_factory = driver_factory
111
+ self._acquire_timeout = acquire_timeout # None: the manager's own wait
112
+ self.runs_dir = Path(runs_dir).expanduser().resolve()
113
+ # Ignored in git and readable by this user only: the frames show the phone's screen.
114
+ self.run_id, self.run_dir = make_run_dir(self.runs_dir)
115
+ self.lock = threading.RLock()
116
+ self.steps, self.frames = [], []
117
+ self.state = "new"
118
+ self.driver = self.target = self.lease = self.app = None
119
+ self.result = None
120
+ self._guard_factory = guard_factory or default_guard
121
+ self._emit = emit or (lambda event: None)
122
+ self.guard = self.preflight = None
123
+ self._frame_n = 0
124
+ self._frame_width = None
125
+ self._t0 = time.monotonic()
126
+ self._release_lock = threading.Lock()
127
+ self.log(f"device {self.device_name} ({self.device_id}), driven directly")
128
+
129
+ def log(self, line):
130
+ try:
131
+ with open(self.run_dir / "run.log", "a", encoding="utf-8") as handle:
132
+ handle.write(f"{time.strftime('%H:%M:%S')} {line}\n")
133
+ except OSError:
134
+ pass
135
+
136
+ def prepare(self):
137
+ self.lease = (self.manager.acquire() if self._acquire_timeout is None
138
+ else self.manager.acquire(timeout=self._acquire_timeout))
139
+ self.target = self.lease.target
140
+ try:
141
+ if self._driver_factory is not None:
142
+ self.driver = self._driver_factory(self.target)
143
+ else:
144
+ from ..verify.runner import build_driver
145
+ self.driver = build_driver(self.target, self.manager, self.run_dir, "keyless")
146
+ self._check_phone("preflight")
147
+ except BaseException:
148
+ self._release()
149
+ raise
150
+ self.state = "ready"
151
+
152
+ def _check_phone(self, cause):
153
+ if self.guard is None and self.preflight is None:
154
+ self.guard = self._guard_factory(self.record, self._emit)
155
+ if self.guard is None:
156
+ return None
157
+ verdict = strict_check(self.guard, self.driver, cause)
158
+ if cause == "preflight":
159
+ self.preflight = verdict
160
+ self.log(f"phone check ({cause}): {getattr(verdict, 'state', '?')} {getattr(verdict, 'code', '')}".rstrip())
161
+ if getattr(verdict, "state", "") == "stop":
162
+ raise PhoneStop(verdict)
163
+ return verdict
164
+
165
+ def check_phone(self, cause="resume"):
166
+ with self.lock:
167
+ return self._check_phone(cause)
168
+
169
+ def read_tree(self):
170
+ from ..verify.tree import read_tree, tree_from_pixels
171
+ pixels = getattr(self.driver, "pixel_snapshot", None)
172
+ if not callable(pixels):
173
+ return read_tree(self.driver, pause=time.sleep)
174
+ snapshot = pixels()
175
+ if snapshot is not None:
176
+ return tree_from_pixels(snapshot)
177
+ from ..wda.observe import WDA_TREE_BUDGET_SECONDS
178
+ started = time.monotonic()
179
+ try:
180
+ return read_tree(self.driver, timeout=WDA_TREE_BUDGET_SECONDS, pause=time.sleep, retry_slow=False)
181
+ except Exception:
182
+ if time.monotonic() - started < WDA_TREE_BUDGET_SECONDS - .1:
183
+ raise
184
+ snapshot = pixels(mark=True)
185
+ if snapshot is None:
186
+ raise
187
+ return tree_from_pixels(snapshot)
188
+
189
+ def frame(self, label):
190
+ self._frame_n += 1
191
+ label = re.sub(r"[^a-z0-9-]+", "-", str(label).lower()).strip("-") or "frame"
192
+ path = self.run_dir / "frames" / f"{self._frame_n:02d}-{label}.jpg"
193
+ try:
194
+ self.manager.screenshot(self.target, path, max_width=self._frame_width, quality=FRAME_QUALITY)
195
+ if self._frame_width is None:
196
+ from PIL import Image
197
+ with Image.open(path) as image:
198
+ self._frame_width = max(1, image.width // 2)
199
+ except Exception as error:
200
+ self.log(f"no {label} frame: {type(error).__name__}")
201
+ return None
202
+ self.frames.append(path.relative_to(self.run_dir).as_posix())
203
+ return path
204
+
205
+ def record_step(self, *, op, text, target=None, typed=None, changed=None, frame=None):
206
+ from ..journal import append_jsonl
207
+ step = {"index": len(self.steps) + 1, "op": op, "text": text,
208
+ "target": ({"id": target.get("id"), "label": target.get("label"), "role": target.get("role")}
209
+ if target else None),
210
+ "typed": typed, "changed": changed,
211
+ "frame": Path(frame).relative_to(self.run_dir).as_posix() if frame else None,
212
+ "at_ms": round((time.monotonic() - self._t0) * 1000)}
213
+ append_jsonl(self.run_dir / "events.jsonl", {"event": "step", **step})
214
+ self.steps.append(step)
215
+ return step
216
+
217
+ def open_url(self, url):
218
+ from ..deeplinks import open_link
219
+ from ..verify.checks import URL_SCHEME, CheckError
220
+ if not isinstance(url, str) or not URL_SCHEME.match(url) or len(url) > 2000:
221
+ raise CheckError("the link needs a scheme, such as https://example.com or myapp://home")
222
+ with self.lock:
223
+ return open_link(self.driver, url, bundle=url_app(url) or None)
224
+
225
+ def launch_app(self, bundle_id):
226
+ with self.lock:
227
+ self.driver.call("POST", "/wda/apps/activate", {"bundleId": bundle_id}, timeout=30)
228
+
229
+ def home(self):
230
+ with self.lock:
231
+ self.driver.call("POST", "/wda/pressButton", {"name": "home"}, timeout=10)
232
+
233
+ def evaluate(self, assertions, *, timeout=5):
234
+ from ..verify.assertions import evaluate_on
235
+ deadline = time.monotonic() + max(0.0, timeout)
236
+ with self.lock:
237
+ while True:
238
+ results = evaluate_on(assertions, self.read_tree())
239
+ if all(result.ok for result in results) or time.monotonic() >= deadline:
240
+ return results
241
+ time.sleep(EVALUATE_PAUSE)
242
+
243
+ def _release(self):
244
+ with self._release_lock:
245
+ driver, self.driver = self.driver, None
246
+ lease, self.lease = self.lease, None
247
+ guard, self.guard = self.guard, None
248
+ if guard is not None and driver is not None:
249
+ try:
250
+ guard.finish(driver) # locks the phone again if this guard unlocked it and relock is on
251
+ except Exception as error:
252
+ self.log(f"the phone guard's finish failed ({type(error).__name__})")
253
+ if driver is not None:
254
+ try:
255
+ driver.close()
256
+ except Exception:
257
+ pass
258
+ if lease is not None:
259
+ try:
260
+ lease.release()
261
+ except Exception:
262
+ pass
263
+
264
+ def close(self, reason):
265
+ if self.result is None:
266
+ self._release()
267
+ self.state = "finished"
268
+ self.result = {"schema": "mobster.device/1", "run_id": self.run_id, "device": self.device_id,
269
+ "device_name": self.device_name, "status": "closed", "summary": reason,
270
+ "steps": list(self.steps), "frames": list(self.frames), "run_dir": str(self.run_dir)}
271
+ self.log(f"closed: {reason}")
272
+ return self.result
@@ -0,0 +1,105 @@
1
+ """The Expo dev loop as MCP tools: ``expo_preview`` opens the app from Metro, ``expo_reload`` reloads it."""
2
+
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from .. import harness_api
7
+ from ..devkit import DevError, expo, expo_dev
8
+ from .protocol import ToolResult
9
+ from .support import ToolError, cap_text
10
+
11
+ LOG_TAIL = 600
12
+ START_WAIT = 30
13
+ RELOAD_SETTLE = 2.0
14
+ MAX_EXPECT_WAIT = 30
15
+ BUNDLE_WAIT = 20
16
+
17
+
18
+ def quoted(text, limit=LOG_TAIL):
19
+ # What Metro or a dev server said is data from the user's project, never instructions.
20
+ flat = " ".join(str(text or "").split())
21
+ return "“" + (flat if len(flat) <= limit else "…" + flat[-(limit - 1):]) + "”"
22
+
23
+
24
+ def metro_tail(app, limit=LOG_TAIL):
25
+ try:
26
+ text = (Path(app.root) / expo.STATE_DIR / "metro.log").read_text(encoding="utf-8", errors="replace")
27
+ except OSError:
28
+ return ""
29
+ text = text[-4000:].strip()
30
+ return harness_api.fence("Metro's log (the end of it)", text[-limit:]) if text else ""
31
+
32
+
33
+ def ensure_metro(toolset, app, port, wait=START_WAIT):
34
+ toolset.log(f"Checking Metro on port {port} for {app.name}")
35
+ try:
36
+ server, started = expo_dev.ensure(app, port, wait=wait)
37
+ return server["pid"] if started else None
38
+ except DevError as error:
39
+ tail = metro_tail(app)
40
+ raise DevError(error.message, (error.fix + (" " + tail if tail else "")).strip()) from None
41
+
42
+
43
+ def _fail(error):
44
+ return ToolError(cap_text(error.line()))
45
+
46
+
47
+ def preview(toolset, args, call, sleep=time.sleep):
48
+ try:
49
+ app = expo.resolve_project(args.get("project"))
50
+ if not app.expo:
51
+ raise DevError("expo_preview opens Expo apps, and this React Native app has no Expo.",
52
+ "Run it from Xcode, or install a build with install_app.")
53
+ record = toolset._record_for(args)
54
+ port = int(args.get("port") or expo.DEFAULT_PORT)
55
+ path = args.get("path")
56
+ link = expo.app_link(app, path) if path and app.client == "dev-client" else None
57
+ if path and app.client == "dev-client" and link is None:
58
+ raise DevError("path needs the app's own URL scheme, and its Expo config has no scheme.",
59
+ "Add \"scheme\" to app.json, rebuild the dev client, then try again.")
60
+ started = ensure_metro(toolset, app, port)
61
+ broken = expo_dev.bundle_error(app, port, wait=BUNDLE_WAIT)
62
+ if broken:
63
+ where = f" in {broken['file']}:{broken['line']}" if broken.get("file") and broken.get("line") else ""
64
+ raise DevError(f"Metro can't bundle {app.name}{where}: {quoted(broken['message'])}",
65
+ "Fix the error, then call expo_preview again.")
66
+ url = expo.preview_url(app, expo.host_for(record), port, path)
67
+ except DevError as error:
68
+ raise _fail(error) from None
69
+ target = {"device": record["id"]}
70
+ opened = toolset.tool_open_url({**target, "url": url}, call)
71
+ lines = [f"Opened {app.name} from Metro on port {port} in "
72
+ f"{'its dev client' if app.client == 'dev-client' else 'Expo Go'} on {record['name']}."]
73
+ structured = {**(opened.structured or {}), "app": app.name, "client": app.client, "url": url, "port": port,
74
+ "metro_started": started is not None}
75
+ if link:
76
+ sleep(3) # the dev client loads the bundle first; its own link needs the app up
77
+ opened = toolset.tool_open_url({**target, "url": link}, call)
78
+ lines.append(f"Then opened its screen {quoted(link, 120)}.")
79
+ structured["link"] = link
80
+ if started is not None:
81
+ lines.append(f"Metro started in the background (log {expo.STATE_DIR}/metro.log). Call expo_reload after an "
82
+ "edit.")
83
+ expect = args.get("expect") or []
84
+ if expect:
85
+ waited = toolset.tool_wait_for({**target, "timeout_s": min(float(args.get("wait_s") or 20), MAX_EXPECT_WAIT),
86
+ "expect": [{"text": text} for text in expect]}, call)
87
+ structured["held"] = (waited.structured or {}).get("held")
88
+ return ToolResult(cap_text("\n".join(lines) + "\n" + waited.text), {**structured, **(waited.structured or {})})
89
+ return ToolResult(cap_text("\n".join(lines) + "\n" + opened.text), structured)
90
+
91
+
92
+ def reload(toolset, args, call, sleep=time.sleep):
93
+ port = int(args.get("port") or expo.DEFAULT_PORT)
94
+ try:
95
+ app = expo.resolve_project(args.get("project"))
96
+ record = toolset._record_for(args)
97
+ expo.reload(port)
98
+ except DevError as error:
99
+ raise _fail(error) from None
100
+ sleep(RELOAD_SETTLE)
101
+ screen = toolset.tool_screen({"device": record["id"], "image": bool(args.get("image", False))}, call)
102
+ head = f"Reloaded the JavaScript in every app connected to Metro on port {port}."
103
+ return ToolResult(cap_text(head + "\n" + screen.text),
104
+ {"app": app.name, "port": port, "reloaded": True,
105
+ "screen": (screen.structured or {}).get("screen")}, screen.images)