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,243 @@
1
+ """A watchdog for a wedged WebDriverAgent runner or relay, and the record of actions in flight.
2
+
3
+ ``DeviceManager.probe`` feeds it one result per probe (``GET /wda/locked``, bounded at 3 s). After
4
+ FAILURES_TO_WEDGE failures in a row the phone is ``wedged``; once it has stayed wedged for WEDGED_RESTART_SECONDS the
5
+ watchdog restarts the runner, subject to the rules below. They are the safety case, and tests/test_wda_watchdog.py
6
+ has one test for each:
7
+
8
+ 1. **An action in flight is never re-sent, and never restarted under.** ``ActionTracker`` records every WDA action
9
+ (tap, swipe, key, button, element value, app activate) from just before it is sent until it answers or fails.
10
+ A restart waits while one is in flight (``admit_restart`` checks that, the unknown outcomes and the task, and blocks,
11
+ in one step). When one ends without an answer its outcome is *unknown*: it is
12
+ recorded, the transport never replays it (transport.HTTP), the effect ledger keeps blocking the same effect
13
+ (effect_ledger.py), and the run ends or goes on from a fresh look. The watchdog sends nothing.
14
+ 2. **While a restart runs, no new action leaves.** ``admit_restart``, ``run_started`` and ``begin`` share one lock: an
15
+ action that starts
16
+ after the restart began is refused before it is sent ("nothing sent"), a known outcome.
17
+ 3. **A restart with a task running happens only between actions** (nothing in flight, no unknown outcome waiting to
18
+ be settled), never on a dead runner: a runner that is not running ends the run with a plain sentence instead
19
+ (``on_dead_runner``), because there is nothing to restart into a task safely.
20
+ 4. **Backoff, no storm.** The gap between restarts doubles for each restart in a row (MIN_RESTART_GAP, 2x, 4x...
21
+ up to MAX_RESTART_GAP), and resets after HEALTHY_SECONDS of passing probes.
22
+ """
23
+
24
+ import threading
25
+ import time
26
+
27
+ from ..transport import NotSent
28
+ from .timeouts import is_action
29
+
30
+ FAILURES_TO_WEDGE = 3
31
+ WEDGED_RESTART_SECONDS = 90.0
32
+ MIN_RESTART_GAP = 180.0
33
+ MAX_RESTART_GAP = 1800.0
34
+ HEALTHY_SECONDS = 120.0
35
+
36
+ DEAD_RUNNER = ("Mobster's helper on your iPhone stopped, so this task ended. No further action was taken. "
37
+ "Check that your iPhone is connected and unlocked, then run the task again.")
38
+
39
+
40
+ class ActionBlocked(NotSent):
41
+ """An action refused before it was sent, because the runner is restarting. Nothing happened on the phone."""
42
+
43
+
44
+ class ActionTracker:
45
+ UNKNOWN_KEPT = 20
46
+
47
+ def __init__(self, clock=time.time):
48
+ self.clock = clock
49
+ self.lock = threading.Lock()
50
+ self.flying = {} # token -> (url, method, path, started, run_id)
51
+ self.unknown = {} # url -> [(method, path, at, run_id)]
52
+ self.blocked = set()
53
+ self.runs = {} # url -> run id of the task holding the phone
54
+ self.last_action = {} # url -> when the last action was sent (wall clock)
55
+ self.next_token = 0
56
+
57
+ @staticmethod
58
+ def _key(url):
59
+ return (url or "").rstrip("/")
60
+
61
+ def run_started(self, url, run_id):
62
+ """A task takes the phone. Raises ActionBlocked while a restart runs: the start is refused, nothing sent."""
63
+ url = self._key(url)
64
+ with self.lock:
65
+ if url in self.blocked:
66
+ raise ActionBlocked("Mobster's helper is restarting; nothing sent")
67
+ self.runs[url] = run_id
68
+
69
+ def run_ended(self, url, run_id):
70
+ """The task is over: its journal holds what it left unknown, so the tracker forgets those outcomes."""
71
+ url = self._key(url)
72
+ with self.lock:
73
+ if self.runs.get(url) != run_id:
74
+ return # a waiting or failed task cannot retire the actual holder's unknown outcome
75
+ self.runs.pop(url, None)
76
+ self.unknown.pop(url, None)
77
+
78
+ def has_run(self, url):
79
+ with self.lock:
80
+ return self._key(url) in self.runs
81
+
82
+ def begin(self, url, method, path):
83
+ """Record an action about to be sent; None for a read. Raises ActionBlocked while a restart runs."""
84
+ if not is_action(method, path):
85
+ return None
86
+ url = self._key(url)
87
+ with self.lock:
88
+ if url in self.blocked:
89
+ raise ActionBlocked("Mobster's helper is restarting; nothing sent")
90
+ self.next_token += 1
91
+ self.flying[self.next_token] = (url, method, path, self.clock(), self.runs.get(url))
92
+ self.last_action[url] = self.clock()
93
+ return self.next_token
94
+
95
+ def end(self, token, outcome="done"):
96
+ """outcome: "done" (answered), "not_sent" (refused before it left) or "unknown" (it left; no answer)."""
97
+ if token is None:
98
+ return
99
+ with self.lock:
100
+ entry = self.flying.pop(token, None)
101
+ if outcome == "unknown" and entry is not None:
102
+ url, method, path, _, run_id = entry
103
+ self.unknown.setdefault(url, []).append((method, path, self.clock(), run_id))
104
+ del self.unknown[url][:-self.UNKNOWN_KEPT]
105
+
106
+ def last_action_at(self, url):
107
+ with self.lock:
108
+ return self.last_action.get(self._key(url))
109
+
110
+ def in_flight(self, url):
111
+ url = self._key(url)
112
+ with self.lock:
113
+ return [(m, p) for u, m, p, *_ in self.flying.values() if u == url]
114
+
115
+ def unknown_outcomes(self, url):
116
+ with self.lock:
117
+ return [(m, p, at) for m, p, at, _ in self.unknown.get(self._key(url), ())]
118
+
119
+ def admit_restart(self, url):
120
+ """Decide and block in one step: None when new actions are now blocked, else why the restart may not go."""
121
+ url = self._key(url)
122
+ with self.lock:
123
+ if url in self.blocked:
124
+ return "restarting"
125
+ if any(u == url for u, *_ in self.flying.values()):
126
+ return "action_in_flight"
127
+ if url in self.runs and self.unknown.get(url):
128
+ return "unknown_outcome"
129
+ self.blocked.add(url)
130
+ return None
131
+
132
+ def unblock(self, url):
133
+ with self.lock:
134
+ self.blocked.discard(self._key(url))
135
+
136
+
137
+ tracker = ActionTracker()
138
+
139
+
140
+ def send(url, method, path, send_request):
141
+ """Run ``send_request()`` for a WDA request, recording it when it is an action. Never retries."""
142
+ token = tracker.begin(url, method, path)
143
+ try:
144
+ result = send_request()
145
+ except NotSent:
146
+ tracker.end(token, "not_sent")
147
+ raise
148
+ except Exception:
149
+ tracker.end(token, "unknown")
150
+ raise
151
+ tracker.end(token, "done")
152
+ return result
153
+
154
+
155
+ class Watchdog:
156
+ def __init__(self, restart, *, url=lambda: None, busy=lambda: False, alive=lambda: True, on_dead_runner=None,
157
+ actions=tracker, wall=time.time):
158
+ """``restart`` stops and starts the runner and relay; ``on_dead_runner(message)`` ends the running task."""
159
+ self.restart, self.url, self.busy, self.alive = restart, url, busy, alive
160
+ self.on_dead_runner, self.actions, self.wall = on_dead_runner, actions, wall
161
+ self.state, self.restarts, self.last_restart = "ok", 0, None
162
+ self.failures, self.wedged_since = 0, None
163
+ self.last_restart_at = None
164
+ self.streak = 0 # restarts in a row, for the backoff
165
+ self.healthy_since = None
166
+ self.deferred = None # why the last restart did not happen: a short id
167
+
168
+ def probe_result(self, ok, now):
169
+ """One probe's result at monotonic time ``now``. Returns True when it restarted the runner."""
170
+ if ok:
171
+ self.failures, self.wedged_since, self.deferred = 0, None, None
172
+ if self.state != "restarting":
173
+ self.state = "ok"
174
+ if self.healthy_since is None:
175
+ self.healthy_since = now
176
+ elif now - self.healthy_since >= HEALTHY_SECONDS:
177
+ self.streak = 0
178
+ return False
179
+ self.healthy_since = None
180
+ self.failures += 1
181
+ if self.wedged_since is None:
182
+ self.wedged_since = now
183
+ if self.failures >= FAILURES_TO_WEDGE:
184
+ self.state = "wedged"
185
+ else:
186
+ return False
187
+ if now - self.wedged_since < WEDGED_RESTART_SECONDS:
188
+ return False
189
+ return self._try_restart(now)
190
+
191
+ def reset_failures(self):
192
+ """The runner is stopped on purpose or still starting: missed checks count for nothing."""
193
+ self.failures, self.wedged_since = 0, None
194
+ if self.state == "wedged":
195
+ self.state = "ok"
196
+
197
+ def gap(self):
198
+ return min(MAX_RESTART_GAP, MIN_RESTART_GAP * 2 ** max(0, self.streak - 1))
199
+
200
+ def admit(self, now):
201
+ """Every automatic restart asks here: None to go (actions blocked until ``done``), else why it must not."""
202
+ url = self.url()
203
+ if self.last_restart_at is not None and now - self.last_restart_at < self.gap():
204
+ return "backoff"
205
+ reason = self.actions.admit_restart(url)
206
+ if reason is not None:
207
+ return reason
208
+ self.state = "restarting"
209
+ self.last_restart_at = now
210
+ self.streak += 1
211
+ self.restarts += 1
212
+ self.last_restart = int(self.wall() * 1000)
213
+ self.failures, self.wedged_since = 0, None
214
+ return None
215
+
216
+ def done(self, ok=True):
217
+ self.actions.unblock(self.url())
218
+ self.state = "ok" if ok else "wedged"
219
+
220
+ def _try_restart(self, now):
221
+ url = self.url()
222
+ if not self.alive():
223
+ # Nothing to restart into: with a task running it ends the task with a plain sentence. Without one the
224
+ # supervisor's gated relaunch and autostart bring it back.
225
+ if (self.actions.has_run(url) or self.busy()) and callable(self.on_dead_runner):
226
+ self.deferred = "dead_runner_with_task"
227
+ self.on_dead_runner(DEAD_RUNNER)
228
+ return False
229
+ reason = self.admit(now)
230
+ if reason is not None:
231
+ self.deferred = reason
232
+ return False
233
+ self.deferred = None
234
+ try:
235
+ self.restart()
236
+ except Exception: # noqa: BLE001 -- the next probes decide; backoff holds the next try
237
+ self.done(False)
238
+ return False
239
+ self.done(True)
240
+ return True
241
+
242
+ def snapshot(self):
243
+ return {"state": self.state, "restarts": self.restarts, "last_restart": self.last_restart}
@@ -0,0 +1,15 @@
1
+ """Track `wireless`: Wi-Fi after one cable pairing, through Apple's encrypted CoreDevice tunnel (SPEC §3.7)."""
2
+
3
+ import os
4
+
5
+ # What an unset MOBSTER_WIFI_TRANSPORT means. This is the one line to flip, and only after `mobster wifi probe`
6
+ # passes on a phone with its cable out (read_screen, launch_app, install_app and unlock_status over Wi-Fi too).
7
+ DEFAULT_ON = False
8
+ GATE = "MOBSTER_WIFI_TRANSPORT"
9
+ ON_WORDS = ("1", "true", "yes", "on")
10
+ OFF_WORDS = ("0", "false", "no", "off")
11
+
12
+
13
+ def transport_on(env=None):
14
+ value = str((os.environ if env is None else env).get(GATE, "")).strip().lower()
15
+ return True if value in ON_WORDS else False if value in OFF_WORDS else DEFAULT_ON
@@ -0,0 +1,354 @@
1
+ """Wi-Fi's HTTP routes (seam S3) and the monitor service (SPEC §3.7, API)."""
2
+
3
+ import os
4
+ import threading
5
+ import time
6
+ from urllib.parse import unquote
7
+
8
+ from .. import api_routes
9
+ from ..api_routes import Response, Route
10
+ from . import GATE, result, store, switch, transport, transport_on, words
11
+ from .monitor import Monitor, task_on
12
+
13
+ # id(runtime) -> (runtime, Monitor): only for runtimes that manage USB phones, made when Wi-Fi for iPhones is on.
14
+ _monitors = {}
15
+ _monitors_lock = threading.Lock()
16
+ # GET /api/wireless/network's last answer: (monotonic time, result). One check at a time; reused for NETWORK_FRESH s.
17
+ NETWORK_FRESH = 15.0
18
+ _network = {"at": None, "result": None}
19
+ _network_lock = threading.Lock()
20
+ # The Wi-Fi checks Add a device starts (POST /api/wireless/probe): UDID -> the running or last one. A check still
21
+ # running after PROBE_SECONDS reads as timed out, and its late end changes nothing.
22
+ PROBE_SECONDS = 180.0
23
+ _probes = {}
24
+ _probes_lock = threading.Lock()
25
+
26
+
27
+ def _managed(runtime):
28
+ return getattr(runtime, "manager", None) is not None and getattr(runtime, "fleet", None) is not None
29
+
30
+
31
+ # -- the service ----------------------------------------------------------------------------------------------------
32
+
33
+ def start_service(runtime):
34
+ if _managed(runtime) and transport_on():
35
+ monitor_for(runtime, create=True).start()
36
+
37
+
38
+ def close_service(runtime):
39
+ with _monitors_lock:
40
+ entry = _monitors.pop(id(runtime), None)
41
+ if entry is not None and entry[0] is runtime:
42
+ entry[1].stop()
43
+
44
+
45
+ def monitor_for(runtime, create=False):
46
+ """The runtime's monitor; with ``create``, made when missing (a runtime that manages USB phones only)."""
47
+ with _monitors_lock:
48
+ entry = _monitors.get(id(runtime))
49
+ if entry is not None and entry[0] is runtime:
50
+ return entry[1]
51
+ if not create or not _managed(runtime):
52
+ return None
53
+ monitor = Monitor(runtime)
54
+ _monitors[id(runtime)] = (runtime, monitor)
55
+ return monitor
56
+
57
+
58
+ # -- what a phone's Wi-Fi looks like ------------------------------------------------------------------------------
59
+
60
+ def _phones(runtime):
61
+ return runtime.fleet.usb_managers() if _managed(runtime) else []
62
+
63
+
64
+ def _name(phone, manager):
65
+ name = None
66
+ try:
67
+ name = manager.settings().get("device_name") if phone.primary else None
68
+ except Exception:
69
+ name = None
70
+ return phone.name or (name if isinstance(name, str) and name else None)
71
+
72
+
73
+ def row(runtime, phone, udid, manager):
74
+ """One phone's Wi-Fi, as GET /api/wireless lists it."""
75
+ data_dir = runtime.fleet.data_dir
76
+ settings = store.get(data_dir, udid)
77
+ link = transport.link(udid) if transport_on() else None
78
+ name = _name(phone, manager)
79
+ running = manager.runner is not None and manager.runner.running
80
+ now = (link.transport if link is not None else
81
+ getattr(manager, "transport", None) if running else None)
82
+ problem = link.problem if link is not None and settings["enabled"] else None
83
+ return {"id": udid, "udid": udid, "name": name, "primary": bool(getattr(phone, "primary", False)),
84
+ "enabled": settings["enabled"], "transport": now,
85
+ "tunnel": link.tunnel if link is not None else "unknown",
86
+ "reachable": bool(link is not None and link.reachable),
87
+ "lastSeenAt": link.last_seen_at if link is not None else None,
88
+ "probe": _probe_view(udid, result.get(data_dir, udid)),
89
+ "lastCheck": _check_view(udid),
90
+ "problem": words.problem(problem, name) if problem in words.PROBLEMS else None}
91
+
92
+
93
+ def _probe_view(udid, kept):
94
+ """The kept probe, or while a check runs: ``{state: "running", step, at}`` (the step it is on)."""
95
+ check = _current(udid)
96
+ if check is not None and check["state"] == "running":
97
+ return {"state": "running", "at": kept["at"], "reason": kept["reason"], "step": check["step"]}
98
+ return kept
99
+
100
+
101
+ def _check_view(udid):
102
+ """The check Add a device started on this phone and how it ended, or None."""
103
+ check = _current(udid)
104
+ return None if check is None else {key: check[key] for key in ("state", "step", "startedAt", "reason", "next")}
105
+
106
+
107
+ def _current(udid, clock=time.monotonic):
108
+ with _probes_lock:
109
+ check = _probes.get(str(udid).upper())
110
+ if check is not None and check["state"] == "running" and clock() - check["began"] >= PROBE_SECONDS:
111
+ check.update(state="timeout", reason=None, next=words.problem("probe_timeout", check["name"]))
112
+ return check
113
+
114
+
115
+ # The step that failed -> what to tell the person (words.PROBLEMS).
116
+ STEP_PROBLEMS = {"coredevice": "not_seen", "tunnel": "tunnel_down", "wda": "runner", "lan": "not_seen"}
117
+
118
+
119
+ def _next_step(probe, name):
120
+ """The problem the check found, with its one fix: from the step that failed (else warned), else None."""
121
+ usb = next((item for item in probe.steps if item["key"] == "usb"), None)
122
+ if usb is not None and usb["detail"].startswith("plugged in"):
123
+ return words.problem("plugged_in", name)
124
+ for state in ("fail", "warn"):
125
+ step = next((item for item in probe.steps if item["state"] == state), None)
126
+ if step is not None:
127
+ code = STEP_PROBLEMS.get(step["key"])
128
+ return words.problem(code, name) if code else None
129
+ return None
130
+
131
+
132
+ def network_view(result):
133
+ """``{state, problem}`` for a network.peer_check result (problem: null or {code, message, fix}); None for None."""
134
+ if not isinstance(result, dict):
135
+ return None
136
+ code = words.NETWORK_PROBLEMS.get(result.get("state"))
137
+ return {"state": result.get("state") or "unknown", "problem": words.problem(code) if code else None}
138
+
139
+
140
+ def overview(runtime):
141
+ monitor = monitor_for(runtime) if transport_on() else None
142
+ with _network_lock:
143
+ checked = _network["result"]
144
+ data_dir = getattr(getattr(runtime, "fleet", None), "data_dir", None)
145
+ return {"wifiTransport": transport_on(),
146
+ "probe": result.latest(data_dir) if data_dir else dict(result.NOT_RUN),
147
+ "network": network_view(monitor.network if monitor is not None and monitor.network else checked),
148
+ "devices": [row(runtime, phone, udid, manager) for phone, udid, manager in _phones(runtime)]}
149
+
150
+
151
+ def _find(runtime, ident):
152
+ """(phone, UDID, manager) for a USB phone set up here, or raises a Response-shaped APIError."""
153
+ from ..api_errors import APIError
154
+ from ..devices import AmbiguousDevice
155
+ if not _managed(runtime):
156
+ raise APIError("This Mobster doesn't manage USB iPhones.", 409, "not_managed")
157
+ fleet = runtime.fleet
158
+ try:
159
+ phone = fleet.lookup(unquote(ident))
160
+ except AmbiguousDevice as error:
161
+ raise APIError(str(error), 400, "device_ambiguous") from None
162
+ except LookupError as error:
163
+ raise APIError(str(error), 404, "device_not_found") from None
164
+ for candidate, udid, manager in fleet.usb_managers():
165
+ if candidate is phone or (phone.udid and phone.udid.upper() == udid.upper()):
166
+ return candidate, udid, manager
167
+ problem = words.problem("not_set_up", phone.name)
168
+ raise APIError(problem["message"], 409, "not_set_up", fix=problem["fix"])
169
+
170
+
171
+ def _refuse(code, name, status=409, **details):
172
+ problem = words.problem(code, name)
173
+ return Response.error(problem["message"], status, code, fix=problem["fix"], **details)
174
+
175
+
176
+ # -- handlers -------------------------------------------------------------------------------------------------------
177
+
178
+ def get_wireless(request):
179
+ return Response.json(overview(request.runtime))
180
+
181
+
182
+ def check_network(request, *, check=None, clock=time.monotonic):
183
+ from . import network
184
+ with _network_lock:
185
+ now = clock()
186
+ if _network["at"] is None or now - _network["at"] >= NETWORK_FRESH or _network["result"] is None:
187
+ try:
188
+ _network["result"] = (check or network.peer_check)()
189
+ except Exception: # noqa: BLE001 -- a check that fails says nothing
190
+ _network["result"] = {"state": "unknown", "interface": None, "peers": 0}
191
+ _network["at"] = now
192
+ result = _network["result"]
193
+ return Response.json({"network": network_view(result)})
194
+
195
+
196
+ def set_transport(request):
197
+ body = request.body if isinstance(request.body, dict) else {}
198
+ if set(body) != {"wifiTransport"} or type(body["wifiTransport"]) is not bool:
199
+ raise ValueError("Send {\"wifiTransport\": true} or false")
200
+ on = body["wifiTransport"]
201
+ runtime = request.runtime
202
+ setup = getattr(runtime, "setup", None)
203
+ env_file = getattr(setup, "env_file", None) or getattr(runtime.config, "env_file", None)
204
+ if env_file:
205
+ from ..env_secrets import update_env_file
206
+ update_env_file(env_file, {GATE: "1" if on else "0"})
207
+ os.environ[GATE] = "1" if on else "0"
208
+ monitor = monitor_for(runtime, create=on)
209
+ if monitor is not None:
210
+ if on:
211
+ monitor.start()
212
+ monitor.kick()
213
+ else:
214
+ monitor.stop()
215
+ monitor.retire()
216
+ return Response.json(overview(runtime))
217
+
218
+
219
+ def set_device_wifi(request):
220
+ body = request.body if isinstance(request.body, dict) else {}
221
+ if set(body) != {"enabled"} or type(body["enabled"]) is not bool:
222
+ raise ValueError("Send {\"enabled\": true} or false")
223
+ runtime = request.runtime
224
+ phone, udid, manager = _find(runtime, request.params["device"])
225
+ name = _name(phone, manager)
226
+ data_dir = runtime.fleet.data_dir
227
+ listing = next((item for item in _listing(manager) if item.get("udid", "").upper() == udid.upper()), None)
228
+ following = None
229
+ if not body["enabled"]:
230
+ link = transport.link(udid)
231
+ running_on_wifi = ((link is not None and link.transport == "wifi")
232
+ or getattr(manager, "transport", None) == "wifi")
233
+ if running_on_wifi and task_on(runtime, phone, udid):
234
+ return _refuse("busy", name)
235
+ try:
236
+ if body["enabled"]:
237
+ if switch.turn_on(data_dir, udid, listing) == "xcode":
238
+ following = words.problem("needs_xcode", name)
239
+ elif left_on := switch.turn_off(data_dir, udid, listing):
240
+ following = words.problem(left_on, name) # off in Mobster; the phone's own switch is still on
241
+ except switch.SwitchRefused as error:
242
+ return _refuse(error.code, name)
243
+ except RuntimeError:
244
+ return Response.error("Devices are off in this Mobster (MOBSTER_NO_DEVICES).", 503, "devices_off")
245
+ monitor = monitor_for(runtime)
246
+ if monitor is not None:
247
+ monitor.kick(udid)
248
+ answer = {"device": row(runtime, phone, udid, manager)}
249
+ if following is not None:
250
+ answer["next"] = following
251
+ return Response.json(answer)
252
+
253
+
254
+ def _listing(manager):
255
+ try:
256
+ return manager.devices()
257
+ except Exception:
258
+ return []
259
+
260
+
261
+ def reconnect(request):
262
+ body = request.body if isinstance(request.body, dict) else {}
263
+ if set(body) != {"device"} or not isinstance(body["device"], str):
264
+ raise ValueError("Send {\"device\": \"<id, UDID or name>\"}")
265
+ runtime = request.runtime
266
+ phone, udid, manager = _find(runtime, body["device"])
267
+ if not transport_on():
268
+ return _refuse("off", _name(phone, manager))
269
+ monitor = monitor_for(runtime)
270
+ if monitor is not None:
271
+ monitor.kick(udid)
272
+ return Response.json({"device": udid, "accepted": True}, 202)
273
+
274
+
275
+ def _run_probe(runtime, check, probe, manager_udid):
276
+ from ..mac_tools import usb_attached
277
+ try:
278
+ probe.run(usb_attached)
279
+ except Exception as error: # noqa: BLE001 -- the check ends with its words, never a trace
280
+ probe.step("probe", "fail", "Wi-Fi check", f"stopped: {type(error).__name__}")
281
+ finally:
282
+ try:
283
+ probe.stop_launched()
284
+ except Exception: # noqa: BLE001
285
+ pass
286
+ with _probes_lock:
287
+ if check["state"] != "running":
288
+ return # it timed out: a late end changes nothing
289
+ usb = next((item for item in probe.steps if item["key"] == "usb"), None)
290
+ if usb is not None and usb["detail"].startswith("plugged in"):
291
+ state = "plugged_in"
292
+ else:
293
+ state = "passed" if probe.ok else "failed"
294
+ result.record(runtime.fleet.data_dir, manager_udid, probe.public())
295
+ check.update(state=state, reason=None if probe.ok else result.reason_of(probe.steps),
296
+ next=None if probe.ok else _next_step(probe, check["name"]))
297
+
298
+
299
+ def probe_wifi(request):
300
+ """Check one phone's Wi-Fi in the background, the way ``mobster wifi probe`` does (wireless/probe.py)."""
301
+ body = request.body if isinstance(request.body, dict) else {}
302
+ if set(body) - {"device", "start"} or not isinstance(body.get("device", ""), str) \
303
+ or type(body.get("start", False)) is not bool:
304
+ raise ValueError("Send {\"device\": \"<id, UDID or name>\"} (optional) and {\"start\": true} (optional)")
305
+ runtime = request.runtime
306
+ from .. import native_helpers
307
+ if not native_helpers.devices_allowed():
308
+ return Response.error("Devices are off in this Mobster (MOBSTER_NO_DEVICES).", 503, "devices_off")
309
+ if body.get("device"):
310
+ phone, udid, manager = _find(runtime, body["device"])
311
+ else:
312
+ phones = _phones(runtime)
313
+ if len(phones) != 1:
314
+ from ..api_errors import APIError
315
+ raise APIError("Name the iPhone to check." if phones else "Mobster sees no iPhone set up here.",
316
+ 400 if phones else 409, "device_ambiguous" if phones else "no_device")
317
+ phone, udid, manager = phones[0]
318
+ name = _name(phone, manager)
319
+ if task_on(runtime, phone, udid):
320
+ return _refuse("busy", name)
321
+ from .. import devices
322
+ from .probe import Probe
323
+ key = udid.upper()
324
+ with _probes_lock:
325
+ others = list(_probes)
326
+ if any(check["state"] == "running" for check in map(_current, others) if check):
327
+ return _refuse("probe_running", name)
328
+ check = {"state": "running", "step": None, "startedAt": int(time.time() * 1000), "began": time.monotonic(),
329
+ "reason": None, "next": None, "name": name or "your iPhone", "udid": udid}
330
+ probe = Probe(
331
+ udid=udid, name=name or "your iPhone", slot_port=devices.port_of(getattr(phone, "wda_url", None)),
332
+ data_dir=runtime.fleet.data_dir, start=bool(body.get("start")), manager=manager,
333
+ out=lambda step: check.update(step=step["title"]))
334
+ with _probes_lock:
335
+ _probes[key] = check
336
+ threading.Thread(target=_run_probe, args=(runtime, check, probe, udid), name="mobster-wifi-probe",
337
+ daemon=True).start()
338
+ return Response.json({"device": udid, "accepted": True, "check": _check_view(udid)}, 202)
339
+
340
+
341
+ # Every route needs the API token, like every other, and none grants anything an approval guards.
342
+ ROUTES = (
343
+ Route("wireless", "GET", "/api/wireless", get_wireless, body="none"),
344
+ Route("wireless", "GET", "/api/wireless/network", check_network, body="none"),
345
+ Route("wireless", "POST", "/api/wireless", set_transport, max_bytes=1024),
346
+ Route("wireless", "POST", "/api/devices/{device}/wifi", set_device_wifi, max_bytes=1024),
347
+ Route("wireless", "POST", "/api/wireless/reconnect", reconnect, max_bytes=1024),
348
+ Route("wireless", "POST", "/api/wireless/probe", probe_wifi, max_bytes=1024),
349
+ )
350
+
351
+
352
+ def register_routes():
353
+ for route in ROUTES:
354
+ api_routes.register(route)