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
mobile_agent/listen.py ADDED
@@ -0,0 +1,607 @@
1
+ """Hearing what an iPhone plays (MOBSTER_LISTEN, on unless set to off): a voice note, a video, a reel."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ import threading
11
+ import time
12
+ from pathlib import Path
13
+
14
+ from . import native_helpers
15
+
16
+ log = logging.getLogger("mobster.listen")
17
+ SETTING = "MOBSTER_LISTEN"
18
+ MAX_SECONDS = 60
19
+ DEFAULT_SECONDS = 10
20
+ # A recording whose loudest moment is quieter than this heard nothing: the phone played no sound.
21
+ SILENT_DB = -50.0
22
+ # A recording that never left digital zero (the helper reports -160 dB) got no audio at all. On an iPhone that is
23
+ # playing, its sound is going somewhere else: Bluetooth or AirPlay, which a USB capture never hears.
24
+ DIGITAL_SILENCE_DB = -120.0
25
+ SILENCE = ("Mobster heard silence. If your iPhone's sound is going to headphones, a car or a speaker over Bluetooth, "
26
+ "Mobster can't hear it; play it on the iPhone's speaker.")
27
+ QUIET = "Mobster heard almost nothing: check that the media is playing and the iPhone isn't muted."
28
+ # A WATCH hears for at most this long (the helper takes up to 120 s).
29
+ LIVE_MAX_SECONDS = 120
30
+ OFF = (f"Hearing the iPhone is off. Turn it on in Settings, or with {SETTING}=on in the env file, then try again.")
31
+ # macOS doesn't let Mobster capture the iPhone (the helper's "permission", or an older helper's "capture_denied").
32
+ # The helper's own words come first; this is for an answer without them.
33
+ PERMISSION = ("Mobster needs permission to hear your iPhone. Allow it in System Settings › Privacy & Security › "
34
+ "Microphone/Camera.")
35
+ PERMISSION_CODES = ("permission", "capture_denied")
36
+ # Failures that come after the helper opened the phone's capture device, which resets the phone's USB network link
37
+ # (drivers.WDA_LINK_BLIP_SECONDS): the caller still brings WebDriverAgent back. Earlier failures opened nothing.
38
+ AFTER_CAPTURE = ("capture_failed", "no_engine", "speech_denied", "transcribe_failed")
39
+
40
+
41
+ class ListenError(RuntimeError):
42
+ """``code`` names what failed; the message says what happened and what to do."""
43
+
44
+ def __init__(self, code, message):
45
+ super().__init__(message)
46
+ self.code = code
47
+
48
+
49
+ def enabled():
50
+ """On unless MOBSTER_LISTEN is off (0, off, false or no): the Mac app's Settings › Hear the iPhone."""
51
+ return os.environ.get(SETTING, "").strip().lower() not in ("0", "off", "false", "no")
52
+
53
+
54
+ def helper_path():
55
+ """The phone-audio helper: the Mac app's, else one built from this checkout (needs Xcode's tools)."""
56
+ found = native_helpers.find("phone-audio")
57
+ if found is not None:
58
+ return found
59
+ try:
60
+ return native_helpers.build("phone_audio")
61
+ except (LookupError, OSError, subprocess.SubprocessError) as error:
62
+ raise ListenError("no_helper", "Mobster's audio helper isn't built, and this Mac can't build it: "
63
+ f"{error}. Install Xcode's command line tools, then try again.") from None
64
+
65
+
66
+ def listen(seconds=DEFAULT_SECONDS, *, udid=None, locale=None, keep=None, run=subprocess.run, helper=None,
67
+ phones=None):
68
+ """What the USB iPhone ``udid`` (else the only one) plays for ``seconds`` (1 to 60)"""
69
+ if not enabled():
70
+ raise ListenError("off", OFF)
71
+ if not native_helpers.devices_allowed():
72
+ raise ListenError("devices_off", "Devices are off in this process (MOBSTER_NO_DEVICES=1).")
73
+ seconds = max(1, min(MAX_SECONDS, int(seconds)))
74
+ if udid is None and mirroring_on():
75
+ return listen_mirroring(seconds, locale=locale, keep=keep, run=run)
76
+ executable = helper or helper_path()
77
+ work = Path(tempfile.mkdtemp(prefix="mobster-listen-"))
78
+ os.chmod(work, 0o700)
79
+ try:
80
+ kept = work / "audio.wav" if keep else None
81
+ argv = [str(executable), "listen", "--seconds", str(seconds), "--work", str(work)]
82
+ argv += ["--udid", udid] + capture_hints(udid, phones) if udid else []
83
+ argv += ["--locale", locale] if locale else []
84
+ argv += ["--keep", str(kept)] if kept else []
85
+ try:
86
+ completed = run(argv, capture_output=True, text=True, timeout=seconds + 120)
87
+ except subprocess.TimeoutExpired:
88
+ raise ListenError("helper_failed", "Mobster's audio helper didn't finish. Try again.") from None
89
+ return _finish(_answer(completed.stdout), seconds, keep=keep)
90
+ finally:
91
+ shutil.rmtree(work, ignore_errors=True)
92
+
93
+
94
+ def mirroring_on():
95
+ """Whether this process hears the phone through iPhone Mirroring (its experimental connection is on)."""
96
+ from . import mirroring
97
+ return mirroring.enabled()
98
+
99
+
100
+ def mirroring_helper():
101
+ from . import mirroring
102
+ try:
103
+ return mirroring.helper_path()
104
+ except (LookupError, OSError, subprocess.SubprocessError, RuntimeError) as error:
105
+ raise ListenError("no_helper",
106
+ f"Mobster's Mirroring helper isn't built, and this Mac can't build it: {error}.")\
107
+ from None
108
+
109
+
110
+ def understood(recorded, *, locale=None, run=subprocess.run):
111
+ """phone-audio's answer for a Mirroring recording"""
112
+ if not recorded.get("ok"):
113
+ return recorded
114
+ audio = str(recorded.get("file") or "")
115
+ executable = str(helper_path())
116
+ answer = {"ok": True, "recordedSeconds": recorded.get("recordedSeconds"), "peakDb": recorded.get("peakDb"),
117
+ "kept": audio}
118
+ try:
119
+ words = _answer(run([executable, "transcribe", audio] + (["--locale", locale] if locale else []),
120
+ capture_output=True, text=True, timeout=180).stdout)
121
+ sounds = _answer(run([executable, "classify", audio], capture_output=True, text=True, timeout=120).stdout)
122
+ except subprocess.TimeoutExpired:
123
+ raise ListenError("helper_failed", "Mobster's audio helper didn't finish. Try again.") from None
124
+ if not words.get("ok"):
125
+ return words
126
+ answer.update({key: words.get(key) for key in ("text", "segments", "engine", "locale", "transcribeMs")})
127
+ if sounds.get("ok"):
128
+ answer.update({key: sounds.get(key) for key in ("soundWindows", "classifyMs")})
129
+ else:
130
+ answer["soundsError"] = sounds.get("message") or "the sounds weren't told apart"
131
+ return answer
132
+
133
+
134
+ def listen_mirroring(seconds, *, locale=None, keep=None, run=subprocess.run):
135
+ """listen() over iPhone Mirroring: its sound recorded on this Mac (``mirroring audio``), then understood."""
136
+ work = Path(tempfile.mkdtemp(prefix="mobster-listen-"))
137
+ os.chmod(work, 0o700)
138
+ try:
139
+ argv = [str(mirroring_helper()), "audio", "--seconds", str(seconds), "--out", str(work / "audio.caf")]
140
+ try:
141
+ recorded = _answer(run(argv, capture_output=True, text=True, timeout=seconds + 30).stdout)
142
+ except subprocess.TimeoutExpired:
143
+ raise ListenError("helper_failed", "Mobster's Mirroring helper didn't finish. Try again.") from None
144
+ return _finish(understood(recorded, locale=locale, run=run), seconds, keep=keep)
145
+ finally:
146
+ shutil.rmtree(work, ignore_errors=True)
147
+
148
+
149
+ def _finish(answer, seconds, *, keep=None):
150
+ """The helper's answer as listen()'s result; raises ListenError for a failure it reports."""
151
+ if not answer.get("ok"):
152
+ code = str(answer.get("error") or "helper_failed")
153
+ if code in PERMISSION_CODES:
154
+ raise ListenError("permission", str(answer.get("message") or PERMISSION))
155
+ raise ListenError(code, str(answer.get("message") or "Mobster couldn't hear the iPhone. Try again."))
156
+ text = " ".join(str(answer.get("text") or "").split())
157
+ peak = answer.get("peakDb")
158
+ result = {"text": text, "words": len(text.split()), "engine": answer.get("engine"),
159
+ "locale": answer.get("locale"), "seconds": seconds, "findMs": answer.get("findMs"),
160
+ "recordMs": answer.get("recordMs"), "transcribeMs": answer.get("transcribeMs"),
161
+ "segments": segments_of(answer.get("segments")), "sounds": sounds_of(answer.get("soundWindows")),
162
+ "classifyMs": answer.get("classifyMs"),
163
+ "silent": isinstance(peak, (int, float)) and peak < SILENT_DB}
164
+ if answer.get("soundsError"):
165
+ log.warning("the sounds weren't told apart: %s", answer["soundsError"])
166
+ if answer.get("match") in MATCHES:
167
+ result["match"] = answer["match"]
168
+ log.info("heard the iPhone through the capture device found by %s", MATCHES[answer["match"]])
169
+ if isinstance(answer.get("recordedSeconds"), (int, float)):
170
+ result["recordedSeconds"] = round(float(answer["recordedSeconds"]), 1)
171
+ if isinstance(peak, (int, float)):
172
+ result["peakDb"] = peak
173
+ note = silence_note(result)
174
+ if note:
175
+ result["note"] = note
176
+ audio = Path(answer["kept"]) if isinstance(answer.get("kept"), str) else None
177
+ if keep and audio and audio.is_file():
178
+ target = Path(keep).expanduser()
179
+ target.parent.mkdir(parents=True, exist_ok=True)
180
+ shutil.move(str(audio), target)
181
+ result["kept"] = str(target)
182
+ return result
183
+
184
+
185
+ def silence_note(result):
186
+ """What to say about a recording with no words in it that was silent, else None"""
187
+ if not result or not result.get("silent") or (result.get("text") or result.get("transcript")):
188
+ return None
189
+ peak = result.get("peakDb")
190
+ return SILENCE if isinstance(peak, (int, float)) and peak <= DIGITAL_SILENCE_DB else QUIET
191
+
192
+
193
+ def segments_of(value):
194
+ """The helper's timed pieces of speech as [{start, end, text}] (seconds, one decimal), dropping any it garbled."""
195
+ out = []
196
+ for piece in value if isinstance(value, list) else ():
197
+ if not isinstance(piece, dict):
198
+ continue
199
+ text = " ".join(str(piece.get("text") or "").split())
200
+ start, end = piece.get("start"), piece.get("end")
201
+ if text and isinstance(start, (int, float)) and isinstance(end, (int, float)):
202
+ out.append({"start": round(float(start), 1), "end": round(float(max(start, end)), 1), "text": text})
203
+ return out
204
+
205
+
206
+ # A sound starts when the classifier is this sure of it in a window, and carries on through the next windows while it
207
+ # stays at least SOUND_STAY sure. The helper reports nothing below 0.3.
208
+ SOUND_ENTER = 0.5
209
+ SOUND_STAY = 0.35
210
+ # An instrument is named with the music when it was this sure on average over the music's windows; at most 3 are.
211
+ INSTRUMENT_MEAN = 0.4
212
+ INSTRUMENTS_NAMED = 3
213
+ # Quiet music under speech: labelled "background music" when this share of the windows hear it (_background_music).
214
+ BACKGROUND_SHARE = 0.6
215
+ # At most this many sounds for a recording (120 s at most): the surest and longest are kept.
216
+ MAX_SOUNDS = 24
217
+ # Apple's labels that read as one sound: laughter, not giggling and a snicker besides. A label not here reads as
218
+ # itself, its underscores as spaces. Silence isn't a sound.
219
+ FAMILIES = {
220
+ "laughter": "laughter baby_laughter giggling snicker belly_laugh chuckle_chortle",
221
+ "shouting": "shout yell battle_cry children_shouting screaming",
222
+ "crying": "crying_sobbing baby_crying",
223
+ "singing": "singing choir_singing yodeling humming",
224
+ "applause": "clapping applause",
225
+ "crowd": "chatter crowd babble",
226
+ "a dog": "dog dog_bark dog_howl dog_bow_wow dog_growl dog_whimper",
227
+ "a cat": "cat cat_purr cat_meow",
228
+ "birds": "bird bird_vocalization bird_chirp_tweet bird_squawk pigeon_dove_coo crow_caw owl_hoot bird_flapping",
229
+ "a siren": "siren police_siren ambulance_siren fire_engine_siren civil_defense_siren emergency_vehicle",
230
+ "a phone ringing": "telephone telephone_bell_ringing ringtone",
231
+ "an alarm": "alarm_clock smoke_detector",
232
+ "a bell": "bell church_bell bicycle_bell cowbell chime wind_chime tuning_fork singing_bowl",
233
+ "rain": "rain raindrop thunderstorm thunder",
234
+ "water": "water stream_burbling waterfall ocean sea_waves gurgling",
235
+ "wind": "wind wind_rustling_leaves wind_noise_microphone",
236
+ "traffic": "car_passing_by traffic_noise vehicle_skidding race_car truck bus motorcycle engine engine_starting "
237
+ "engine_idling engine_accelerating_revving engine_knocking car_horn air_horn reverse_beeps",
238
+ "gunshots": "gunshot_gunfire artillery_fire",
239
+ "fireworks": "fireworks firecracker",
240
+ "typing": "typing typewriter typing_computer_keyboard",
241
+ "footsteps": "person_running person_shuffling person_walking",
242
+ "a door": "door door_slam door_sliding knock door_bell drawer_open_close",
243
+ }
244
+ # Instruments are music; which ones is said with it: "music with piano and drums".
245
+ INSTRUMENTS = {
246
+ "guitar": "plucked_string_instrument guitar electric_guitar bass_guitar acoustic_guitar steel_guitar_slide_guitar "
247
+ "guitar_tapping guitar_strum banjo sitar mandolin zither ukulele",
248
+ "piano": "keyboard_musical piano electric_piano harpsichord",
249
+ "organ": "organ electronic_organ hammond_organ",
250
+ "synthesizer": "synthesizer theremin",
251
+ "drums": "percussion drum_kit drum snare_drum bass_drum timpani tabla cymbal hi_hat tambourine rattle_instrument "
252
+ "gong disc_scratching",
253
+ "mallets": "mallet_percussion marimba_xylophone glockenspiel vibraphone steelpan",
254
+ "strings": "orchestra bowed_string_instrument violin_fiddle cello double_bass harp",
255
+ "brass": "brass_instrument french_horn trumpet trombone shofar didgeridoo",
256
+ "woodwinds": "wind_instrument flute saxophone clarinet oboe bassoon harmonica accordion bagpipes",
257
+ }
258
+ _FAMILY = {label: name for name, labels in FAMILIES.items() for label in labels.split()}
259
+ _INSTRUMENT = {label: name for name, labels in INSTRUMENTS.items() for label in labels.split()}
260
+ _LABEL = re.compile(r"[a-z][a-z_]{0,39}")
261
+
262
+
263
+ def _sound_of(label):
264
+ """(sound, instrument) for one of Apple's labels: ("music", "piano") for piano; (None, None) for silence."""
265
+ if label == "silence":
266
+ return None, None
267
+ if label in _INSTRUMENT:
268
+ return "music", _INSTRUMENT[label]
269
+ return _FAMILY.get(label, label.replace("_", " ")), None
270
+
271
+
272
+ def sounds_of(value):
273
+ """The helper's classified windows"""
274
+ windows = []
275
+ for window in value if isinstance(value, list) else ():
276
+ if not isinstance(window, dict) or not isinstance(window.get("labels"), dict):
277
+ continue
278
+ start, length = window.get("t"), window.get("d")
279
+ if not isinstance(start, (int, float)) or not isinstance(length, (int, float)) or length <= 0:
280
+ continue
281
+ sounds, instruments = {}, {}
282
+ for label, confidence in window["labels"].items():
283
+ if not isinstance(label, str) or not _LABEL.fullmatch(label) or not isinstance(confidence, (int, float)):
284
+ continue
285
+ sound, instrument = _sound_of(label)
286
+ if sound is None:
287
+ continue
288
+ sounds[sound] = max(sounds.get(sound, 0.0), float(confidence))
289
+ if instrument:
290
+ instruments[instrument] = max(instruments.get(instrument, 0.0), float(confidence))
291
+ windows.append((float(start), float(start) + float(length), sounds, instruments))
292
+ windows.sort(key=lambda window: window[0])
293
+ found = []
294
+ for sound in sorted({sound for window in windows for sound in window[2]}):
295
+ run = []
296
+ for window in windows + [None]:
297
+ confidence = window[2].get(sound, 0.0) if window else 0.0
298
+ if run and confidence >= SOUND_STAY:
299
+ run.append(window)
300
+ continue
301
+ if run:
302
+ found.append(_sound(sound, run))
303
+ run = [window] if confidence >= SOUND_ENTER else []
304
+ if windows and not any(item["label"] == "music" for item in found):
305
+ background = _background_music(windows)
306
+ if background:
307
+ found.append(background)
308
+ found.sort(key=lambda item: -item["confidence"] * (item["end"] - item["start"]))
309
+ return sorted(found[:MAX_SOUNDS], key=lambda item: (item["start"], -item["confidence"]))
310
+
311
+
312
+ def _background_music(windows):
313
+ """Music too quiet to be sure of in any one window (under SOUND_ENTER), but there through the recording"""
314
+ heard = [window for window in windows if window[2].get("music", 0.0) >= SOUND_STAY]
315
+ if len(windows) < 2 or len(heard) < BACKGROUND_SHARE * len(windows):
316
+ return None
317
+ item = _sound("music", heard)
318
+ item["label"] = "background music"
319
+ return item
320
+
321
+
322
+ def _sound(sound, run):
323
+ item = {"start": round(run[0][0], 1), "end": round(max(window[1] for window in run), 1), "label": sound,
324
+ "confidence": round(sum(window[2][sound] for window in run) / len(run), 2)}
325
+ if sound == "music":
326
+ means = {name: sum(window[3].get(name, 0.0) for window in run) / len(run)
327
+ for name in {name for window in run for name in window[3]}}
328
+ named = sorted((name for name, mean in means.items() if mean >= INSTRUMENT_MEAN), key=lambda n: -means[n])
329
+ if named[:INSTRUMENTS_NAMED]:
330
+ item["instruments"] = named[:INSTRUMENTS_NAMED]
331
+ return item
332
+
333
+
334
+ def sound_text(sound):
335
+ """One sound as words: "music with piano and synthesizer"."""
336
+ instruments = sound.get("instruments") or []
337
+ if not instruments:
338
+ return sound["label"]
339
+ listed = instruments[0] if len(instruments) == 1 else ", ".join(instruments[:-1]) + " and " + instruments[-1]
340
+ return f"{sound['label']} with {listed}"
341
+
342
+
343
+ def sound_lines(sounds, *, clock):
344
+ """The sounds as the lines the model reads: "0:00-0:12 sound: music with piano (confidence 0.85)"."""
345
+ return [f"{clock(sound['start'])}-{clock(sound['end'])} sound: {sound_text(sound)} "
346
+ f"(confidence {sound['confidence']:.2f})" for sound in sounds]
347
+
348
+
349
+ class Session:
350
+ """A recording of the iPhone's audio running in the background while WATCH samples the screen."""
351
+
352
+ QUIET_DB = -45.0
353
+ # It opens the USB iPhone's capture device: iOS shows a 9:41 status bar until it closes, and closing resets the
354
+ # phone's link (watch.py).
355
+ opens_capture = True
356
+ helper_name, margin = "audio", 120
357
+ no_answer = "Mobster's audio helper gave no answer. Try again."
358
+
359
+ def __init__(self, seconds, *, udid=None, locale=None, helper=None, popen=subprocess.Popen, phones=None,
360
+ frames=False, clock=None):
361
+ if not enabled():
362
+ raise ListenError("off", OFF)
363
+ if not native_helpers.devices_allowed():
364
+ raise ListenError("devices_off", "Devices are off in this process (MOBSTER_NO_DEVICES=1).")
365
+ self.seconds = max(1, min(LIVE_MAX_SECONDS, int(seconds)))
366
+ executable = helper or helper_path()
367
+ self.work = Path(tempfile.mkdtemp(prefix="mobster-watch-"))
368
+ os.chmod(self.work, 0o700)
369
+ argv = [str(executable), "listen", "--seconds", str(self.seconds), "--work", str(self.work), "--live"]
370
+ argv += ["--udid", udid] + capture_hints(udid, phones) if udid else []
371
+ argv += ["--locale", locale] if locale else []
372
+ argv += ["--frames"] if frames else []
373
+ self._clock = clock or time.monotonic
374
+ self._frame = None # (seq, JPEG bytes, clock() when it came): the phone's picture from the capture
375
+ self._started = threading.Event()
376
+ self._lock = threading.Lock()
377
+ self._lines = []
378
+ self._asking = False # macOS is asking for the camera or the microphone
379
+ self._loud_at = None # recording time of the last level above QUIET_DB
380
+ self._level_at = None # recording time of the newest level
381
+ try:
382
+ self.process = popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
383
+ text=True, bufsize=1)
384
+ except OSError as error:
385
+ shutil.rmtree(self.work, ignore_errors=True)
386
+ raise ListenError("helper_failed", f"Mobster's audio helper couldn't start ({error}).") from None
387
+ self._reader = threading.Thread(target=self._read, name="mobster-watch-audio", daemon=True)
388
+ self._reader.start()
389
+
390
+ def _read(self):
391
+ for line in self.process.stdout:
392
+ try:
393
+ value = json.loads(line)
394
+ except ValueError:
395
+ continue
396
+ if not isinstance(value, dict):
397
+ continue
398
+ with self._lock:
399
+ if value.get("event") == "asking":
400
+ self._asking = True
401
+ elif value.get("event") == "recording":
402
+ self._asking = False
403
+ self._started.set()
404
+ elif value.get("event") == "frame":
405
+ self._take_frame(value)
406
+ elif value.get("event") == "level":
407
+ t, db = value.get("t"), value.get("db")
408
+ if isinstance(t, (int, float)) and isinstance(db, (int, float)):
409
+ self._level_at = float(t)
410
+ if db > self.QUIET_DB:
411
+ self._loud_at = float(t)
412
+ else:
413
+ self._lines.append(value)
414
+ self._started.set() # the helper ended (a failure before recording, too): nobody waits on it any longer
415
+
416
+ def _take_frame(self, value):
417
+ """Keep the helper's newest picture (only in this session's folder), read at once. Called with the lock held."""
418
+ path, seq = value.get("path"), value.get("seq")
419
+ if not isinstance(path, str) or not isinstance(seq, int):
420
+ return
421
+ try:
422
+ target = Path(path).resolve()
423
+ if self.work.resolve() not in target.parents:
424
+ return
425
+ data = target.read_bytes()
426
+ except OSError:
427
+ return
428
+ if data:
429
+ self._frame = (seq, data, self._clock())
430
+
431
+ def latest_frame(self):
432
+ """(seq, JPEG bytes, when) of the capture's newest picture, else None; WDA is unreliable during a capture."""
433
+ with self._lock:
434
+ return self._frame
435
+
436
+ def wait_started(self, timeout):
437
+ """True once the recording runs; False when the helper ended first or ``timeout`` passed."""
438
+ self._started.wait(timeout)
439
+ with self._lock:
440
+ return self._started.is_set() and not self._lines and self.process.poll() is None
441
+
442
+ def asking(self):
443
+ """True while macOS asks the person whether Mobster may capture the iPhone, and the helper waits for them."""
444
+ with self._lock:
445
+ return self._asking and not self._started.is_set()
446
+
447
+ def quiet_for(self):
448
+ """Seconds since the phone last made a sound above QUIET_DB; None before it made any."""
449
+ with self._lock:
450
+ if self._loud_at is None or self._level_at is None:
451
+ return None
452
+ return max(0.0, self._level_at - self._loud_at)
453
+
454
+ def stop(self):
455
+ """End the recording now; what it has is transcribed."""
456
+ try:
457
+ self.process.stdin.write("stop\n")
458
+ self.process.stdin.flush()
459
+ self.process.stdin.close()
460
+ except (OSError, ValueError, AttributeError):
461
+ pass
462
+
463
+ def result(self, timeout=None):
464
+ """listen()'s result for what was recorded (``seconds`` is how long it ran); raises ListenError."""
465
+ self.stop()
466
+ try:
467
+ self.process.wait(timeout=timeout if timeout is not None else self.seconds + self.margin)
468
+ except subprocess.TimeoutExpired:
469
+ self.close()
470
+ raise ListenError("helper_failed",
471
+ f"Mobster's {self.helper_name} helper didn't finish. Try again.") from None
472
+ self._reader.join(timeout=5)
473
+ with self._lock:
474
+ answer = self._lines[-1] if self._lines else {
475
+ "ok": False, "error": "helper_failed", "message": self.no_answer}
476
+ heard = answer.get("recordedSeconds") if isinstance(answer.get("recordedSeconds"), (int, float)) else None
477
+ return _finish(self._understand(answer), round(heard) if heard else self.seconds)
478
+
479
+ def _understand(self, answer):
480
+ return answer
481
+
482
+ def close(self):
483
+ try:
484
+ if self.process.poll() is None:
485
+ self.process.kill()
486
+ self.process.wait(timeout=5)
487
+ except (OSError, subprocess.SubprocessError):
488
+ pass
489
+ shutil.rmtree(self.work, ignore_errors=True)
490
+
491
+
492
+ class MirroringSession(Session):
493
+ """Session over iPhone Mirroring: the Mirroring app's sound, recorded on this Mac"""
494
+
495
+ opens_capture = False # the phone's own status bar shows, and nothing on the phone resets
496
+ helper_name, margin = "Mirroring", 30
497
+ no_answer = "Mobster's Mirroring helper gave no answer."
498
+
499
+ def __init__(self, seconds, *, locale=None, popen=subprocess.Popen, run=subprocess.run):
500
+ self.locale, self._run = locale, run
501
+ super().__init__(seconds, locale=locale, helper="mirroring", popen=self._popen(popen))
502
+
503
+ def _popen(self, popen):
504
+ def start(argv, **kwargs):
505
+ work = argv[argv.index("--work") + 1]
506
+ mirrored = [str(mirroring_helper()), "audio", "--seconds", argv[argv.index("--seconds") + 1],
507
+ "--out", str(Path(work) / "audio.caf"), "--live"]
508
+ return popen(mirrored, **kwargs)
509
+ return start
510
+
511
+ def _understand(self, recorded):
512
+ return understood(recorded, locale=self.locale, run=self._run)
513
+
514
+
515
+ def session_for(seconds, *, udid=None, locale=None, frames=False, clock=None):
516
+ """The recording WATCH starts: over iPhone Mirroring when that connection is on and no USB iPhone is named"""
517
+ if udid is None and mirroring_on():
518
+ return MirroringSession(seconds, locale=locale)
519
+ return Session(seconds, udid=udid, locale=locale, frames=frames, clock=clock)
520
+
521
+
522
+ # How the helper told the iPhone's capture device, as the log says it: never the phone's name or UDID.
523
+ MATCHES = {"udid": "its UDID", "name": "the phone's name", "only": "being the only iPhone"}
524
+ # How long a phone's name, once read, is used without reading it again.
525
+ NAME_TTL = 600.0
526
+ _names = {}
527
+ _names_lock = threading.Lock()
528
+
529
+
530
+ def usb_phones(*, tool=None, run=None, clock=None):
531
+ """The iPhones on this Mac's USB as the device layer reads them (libimobiledevice)"""
532
+ from . import mac_tools
533
+ from .devices import UDID_PATTERN
534
+ tool, run, clock = tool or mac_tools.tool, run or mac_tools.run, clock or time.monotonic
535
+ listing = tool("idevice_id")
536
+ output = run([listing, "-l"], timeout=3) if listing else None
537
+ if output is None:
538
+ return None
539
+ udids = []
540
+ for line in output.splitlines():
541
+ udid = line.strip()
542
+ # 0000FE01-… entries are not physical USB phones (device_manager.devices).
543
+ if UDID_PATTERN.fullmatch(udid) and not udid.startswith("0000FE01") and udid not in udids:
544
+ udids.append(udid)
545
+ info = tool("ideviceinfo")
546
+ phones = []
547
+ for udid in udids:
548
+ with _names_lock:
549
+ cached = _names.get(udid)
550
+ if cached is None or clock() - cached[0] >= NAME_TTL:
551
+ value = run([info, "-u", udid, "-k", "DeviceName"], timeout=5) if info else None
552
+ cached = (clock(), " ".join((value or "").split()) or None)
553
+ if cached[1]:
554
+ with _names_lock:
555
+ _names[udid] = cached
556
+ phones.append({"udid": udid, "name": cached[1]})
557
+ return phones
558
+
559
+
560
+ def capture_hints(udid, phones=None):
561
+ """``--name`` and ``--phones``: how the helper finds ``udid`` when its capture device has an id of its own."""
562
+ from .devices import UDID_PATTERN
563
+ if not isinstance(udid, str) or not UDID_PATTERN.fullmatch(udid):
564
+ return []
565
+ try:
566
+ attached = (phones or usb_phones)()
567
+ except Exception: # noqa: BLE001 -- without the hints the UDID still works
568
+ attached = None
569
+ if not attached:
570
+ return []
571
+ this = next((phone for phone in attached if str(phone.get("udid") or "").upper() == udid.upper()), None)
572
+ if this is None:
573
+ return []
574
+ hints = []
575
+ name = " ".join(str(this.get("name") or "").split())
576
+ if name and sum(" ".join(str(phone.get("name") or "").split()).casefold() == name.casefold()
577
+ for phone in attached) == 1:
578
+ hints += ["--name", name]
579
+ return hints + ["--phones", str(len(attached))]
580
+
581
+
582
+ def _answer(stdout):
583
+ for line in reversed((stdout or "").strip().splitlines()):
584
+ try:
585
+ value = json.loads(line)
586
+ except ValueError:
587
+ continue
588
+ if isinstance(value, dict):
589
+ return value
590
+ return {"ok": False, "error": "helper_failed", "message": "Mobster's audio helper gave no answer. Try again."}
591
+
592
+
593
+ def phone_udid(run_context):
594
+ """The UDID of a run's USB iPhone, or None (a simulator, an address, or one Mobster can't name)."""
595
+ if run_context is None:
596
+ return None
597
+ kind = getattr(run_context, "device_kind", None)
598
+ if kind not in (None, "usb"):
599
+ return None
600
+ device = getattr(run_context, "device_id", None)
601
+ if device and kind == "usb":
602
+ return device
603
+ manager = getattr(getattr(run_context, "runtime", None), "manager", None)
604
+ try:
605
+ return (getattr(manager, "pinned", None) or manager.settings().get("udid")) if manager is not None else None
606
+ except Exception: # noqa: BLE001
607
+ return None