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,21 @@
1
+ """Distinguish the standalone CLI's entry point from the Mac app's frozen agent."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ # Only packaging/cli/entry.py selects this, before importing the command. A missing or damaged selection
7
+ # keeps the app's checks. Neither an environment variable nor renaming the app's agent selects the CLI.
8
+ STANDALONE_CLI = False
9
+
10
+
11
+ def app_bundle():
12
+ """Whether the frozen process must enforce the Mac app's plan and signature checks."""
13
+ if not getattr(sys, "frozen", False):
14
+ return False
15
+ if STANDALONE_CLI is not True:
16
+ return True
17
+ try:
18
+ # The CLI entry point cannot opt an executable inside an app out of the app's checks.
19
+ return any(parent.suffix == ".app" for parent in Path(sys.executable).resolve().parents)
20
+ except (OSError, RuntimeError, TypeError, ValueError):
21
+ return True
@@ -0,0 +1,623 @@
1
+ """Cable + Bluetooth: the iPhone's screen video over USB, and this Mac as its Bluetooth keyboard and mouse."""
2
+
3
+ import json
4
+ import os
5
+ import queue
6
+ import subprocess
7
+ import threading
8
+ import time
9
+ from dataclasses import dataclass
10
+
11
+ from . import perception
12
+ from .drivers import grounded_target, register_driver
13
+ from .mirroring import HELPER_TIMEOUT, MirroringDriver, helper_path, typable
14
+ from .state import validate_input_text
15
+ from .transport import TransportError
16
+
17
+ SOURCE = "cable_bt"
18
+ ENV_FLAG = "MOBSTER_EXPERIMENTAL_CABLE_BT"
19
+
20
+ # While the phone is captured, iOS draws a demonstration status bar; this top share of the picture is blacked out and
21
+ # its text left unread (59 of 852 pt on an iPhone 15 Pro: the status bar and the Dynamic Island).
22
+ STATUS_BAR = .069
23
+ # The helper's video width in pixels (scaled in hardware): about Mirroring's capture and WDA's screenshots.
24
+ VIDEO_WIDTH = 590
25
+
26
+ # The keys MirroringDriver sends for Home and Spotlight (iPhone Mirroring's own); Phone turns them into the iPhone's.
27
+ MIRRORING_HOME, MIRRORING_SPOTLIGHT = "cmd+1", "cmd+3"
28
+
29
+ # The pointer. Homing: big steps into the top-left corner. Moving: a constant pace, so one gain holds.
30
+ HOME_COUNTS = 6000
31
+ HOME_STEP = 1500
32
+ HOME_INTERVAL_MS = 8
33
+ MOVE_STEP = 12
34
+ MOVE_INTERVAL_MS = 12
35
+ CALIBRATE_COUNTS = (240, 480)
36
+ # A click lands only within this distance of the target (points of a 393-point-wide screen).
37
+ TOLERANCE_PT = 9.0
38
+ MAX_CORRECTIONS = 2
39
+ # How long the video is watched for the pointer after a move, and how often.
40
+ FIND_SECONDS = .6
41
+ FIND_POLL_SECONDS = .04
42
+ # A pointer-sized change in the 148 x 320 difference grid (AssistiveTouch's pointer is about 7 cells across).
43
+ POINTER_AREA = (6, 220)
44
+ SETTLE_SECONDS = .06
45
+ # With a hardware keyboard the software keyboard doesn't appear: a tapped field gets this long to take focus.
46
+ FOCUS_SECONDS = .45
47
+ CLICK_MS = 50
48
+ LONG_PRESS_MS = 1500
49
+ # While no paired phone answers, the Mac shows itself for pairing this long.
50
+ ADVERTISE_SECONDS = 600
51
+ DRAG_STEP = 10
52
+ DRAG_INTERVAL_MS = 10
53
+
54
+ PHONE_WIDTH_PT = perception.PHONE_WIDTH_PT
55
+
56
+ # Lock and sleep, read from the picture: the phone must stay unlocked, and Mobster never unlocks it.
57
+ # The lock screen's clock is about 8% of the screen tall on iOS 26; app times are under 3%.
58
+ LOCK_CLOCK_HEIGHT = .05
59
+ LOCK_LINES = ("swipe up to open", "swipe up to unlock", "enter passcode", "touch id or enter passcode",
60
+ "face id", "press home to open", "press home to unlock")
61
+
62
+
63
+ def usb_locked(udid, *, tool=None, run=None):
64
+ if not udid:
65
+ return None
66
+ from . import mac_tools
67
+ tool, run = tool or mac_tools.tool, run or mac_tools.run
68
+ info = tool("ideviceinfo")
69
+ value = run([info, "-u", udid, "-k", "PasswordProtected"], timeout=3) if info else None
70
+ answer = (value or "").strip().casefold()
71
+ return {"true": True, "false": False}.get(answer)
72
+
73
+
74
+ def _lock_clock(row):
75
+ """The lock screen's big clock: a time alone on its line, drawn tall in the top third (an app's times are small)."""
76
+ import re
77
+ rect = row.get("rect") or (0, 1, 0, 0)
78
+ return (bool(re.fullmatch(r"\d{1,2}[:.]\d{2}", " ".join(str(row.get("text") or "").split())))
79
+ and float(rect[3]) >= LOCK_CLOCK_HEIGHT and float(rect[1]) < .35)
80
+
81
+
82
+ def enabled():
83
+ """Whether the experimental Cable + Bluetooth connection is on (``MOBSTER_EXPERIMENTAL_CABLE_BT=1``)."""
84
+ return os.environ.get(ENV_FLAG, "").strip().casefold() in ("1", "true", "yes", "on")
85
+
86
+
87
+ class CableBtError(TransportError):
88
+ """A helper refused or failed."""
89
+
90
+ def __init__(self, message, code="capture"):
91
+ super().__init__(message)
92
+ self.code = code
93
+
94
+
95
+ FIXES = {
96
+ "no_device": "Plug the iPhone into this Mac with a cable, unlock it and tap Trust if it asks, then try again.",
97
+ "ambiguous_device": "Mobster can't tell the plugged-in iPhones apart. Unplug the others, then try again.",
98
+ "permission": "Allow Camera for Mobster in System Settings > Privacy & Security, then try again.",
99
+ "no_bluetooth": "Turn Bluetooth on on this Mac, then try again.",
100
+ "bt_permission": "Allow Bluetooth for Mobster in System Settings > Privacy & Security, then try again.",
101
+ "no_host": "Pair the iPhone with this Mac: on the iPhone, open Settings > Bluetooth and tap Mobster Keyboard. "
102
+ "Then try again.",
103
+ "pointer": "Turn on AssistiveTouch on the iPhone (Settings > Accessibility > Touch), turn off Automatically Hide "
104
+ "Pointer (Accessibility > Pointer Control), then try again.",
105
+ "locked": "Unlock your iPhone, then try again. Mobster never unlocks it.",
106
+ }
107
+
108
+
109
+ def fix_for(error):
110
+ """The sentence that says what to do about ``error`` (a CableBtError), or ""."""
111
+ return FIXES.get(getattr(error, "code", ""), "")
112
+
113
+
114
+ class Link:
115
+ """One helper process, spoken to in JSON lines."""
116
+
117
+ def __init__(self, command, label, process=None):
118
+ self.label = label
119
+ self._process = process or subprocess.Popen([str(part) for part in command], stdin=subprocess.PIPE,
120
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True,
121
+ bufsize=1)
122
+ self._lock = threading.Lock()
123
+ self._waiting = {}
124
+ self._next = 0
125
+ self.closed = False
126
+ self._reader = threading.Thread(target=self._read, name=f"{label}-helper", daemon=True)
127
+ self._reader.start()
128
+
129
+ def _read(self):
130
+ for line in self._process.stdout:
131
+ try:
132
+ answer = json.loads(line)
133
+ except ValueError:
134
+ continue
135
+ ident = answer.get("id") if isinstance(answer, dict) else None
136
+ with self._lock:
137
+ box = self._waiting.pop(ident, None) if ident is not None else None
138
+ if box is not None:
139
+ box.put(answer)
140
+ with self._lock:
141
+ self.closed = True
142
+ waiting, self._waiting = self._waiting, {}
143
+ for box in waiting.values():
144
+ box.put(None)
145
+
146
+ def request(self, op, timeout=HELPER_TIMEOUT, **fields):
147
+ """The helper's answer to ``op``; CableBtError when it refuses, fails, stops or doesn't answer in time."""
148
+ box = queue.Queue(maxsize=1)
149
+ with self._lock:
150
+ if self.closed:
151
+ raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped")
152
+ self._next += 1
153
+ ident = self._next
154
+ self._waiting[ident] = box
155
+ try:
156
+ self._process.stdin.write(json.dumps({"op": op, "id": ident, **fields}) + "\n")
157
+ self._process.stdin.flush()
158
+ except (OSError, ValueError):
159
+ self._waiting.pop(ident, None)
160
+ raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped") from None
161
+ try:
162
+ answer = box.get(timeout=timeout)
163
+ except queue.Empty:
164
+ with self._lock:
165
+ self._waiting.pop(ident, None)
166
+ raise CableBtError(f"Mobster's {self.label} helper didn't answer {op} in {timeout:g} s", "write") from None
167
+ if answer is None:
168
+ raise CableBtError(f"Mobster's {self.label} helper stopped", "stopped")
169
+ if answer.get("ok") is not True:
170
+ raise CableBtError(str(answer.get("error") or f"The {self.label} helper refused"),
171
+ str(answer.get("code") or "capture"))
172
+ return answer
173
+
174
+ def close(self):
175
+ process, self._process = self._process, None
176
+ if process is None:
177
+ return
178
+ for step in (lambda: process.stdin.close(), lambda: process.wait(timeout=2)):
179
+ try:
180
+ step()
181
+ except Exception: # noqa: BLE001 -- closing is best effort; kill below
182
+ process.kill()
183
+ break
184
+ try:
185
+ process.stdout.close()
186
+ except Exception: # noqa: BLE001
187
+ pass
188
+
189
+
190
+ _HID = None
191
+ _HID_LOCK = threading.Lock()
192
+
193
+
194
+ class _SocketProcess:
195
+ """A connection to the shared Bluetooth keyboard helper, shaped like the Popen that Link talks to."""
196
+
197
+ def __init__(self, connection):
198
+ self._connection = connection
199
+ self.stdout = connection.makefile("r", encoding="utf-8", newline="\n")
200
+ self.stdin = connection.makefile("w", encoding="utf-8", newline="\n")
201
+
202
+ def wait(self, timeout=None):
203
+ self.kill()
204
+ return 0
205
+
206
+ def kill(self):
207
+ import socket
208
+ try:
209
+ self._connection.shutdown(socket.SHUT_RDWR) # the reader sees the end before its file is closed
210
+ except OSError:
211
+ pass
212
+ self._connection.close()
213
+
214
+
215
+ def socket_path():
216
+ """Where the shared Bluetooth keyboard helper listens: this user's private temporary folder."""
217
+ import tempfile
218
+ return os.path.join(tempfile.gettempdir(), f"mobster-{os.getuid()}-bt-hid.sock")
219
+
220
+
221
+ def _connect(path):
222
+ import socket
223
+ connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
224
+ try:
225
+ connection.connect(path)
226
+ except OSError:
227
+ connection.close()
228
+ return None
229
+ return Link(None, "Bluetooth keyboard", process=_SocketProcess(connection))
230
+
231
+
232
+ def _start_shared(path, spawn=subprocess.Popen):
233
+ """Start the shared helper (it outlives this process, and ends 2 minutes after its last client leaves)."""
234
+ process = spawn([str(helper_path("bt_hid")), "--socket", path], stdin=subprocess.DEVNULL,
235
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, start_new_session=True)
236
+ line = process.stdout.readline()
237
+ process.stdout.close()
238
+ try:
239
+ ready = json.loads(line or "{}")
240
+ except ValueError:
241
+ ready = {}
242
+ if not ready.get("ok"):
243
+ raise CableBtError("Mobster's Bluetooth keyboard helper didn't start: "
244
+ + str(ready.get("error") or "no answer"),
245
+ "no_bluetooth")
246
+
247
+
248
+ # One helper for every Mobster process on this Mac: one HID service is what the iPhones see.
249
+ def hid_link(spawn=subprocess.Popen, path=None):
250
+ global _HID
251
+ with _HID_LOCK:
252
+ if _HID is not None and not _HID.closed:
253
+ return _HID
254
+ path = path or socket_path()
255
+ link = _connect(path)
256
+ if link is None:
257
+ _start_shared(path, spawn=spawn)
258
+ link = _connect(path)
259
+ if link is None:
260
+ raise CableBtError("Mobster couldn't reach its Bluetooth keyboard helper", "no_bluetooth")
261
+ deadline = time.monotonic() + 5
262
+ while True:
263
+ state = link.request("status", timeout=3).get("state")
264
+ if state == "on":
265
+ break
266
+ if state == "unauthorized" or time.monotonic() >= deadline:
267
+ link.close()
268
+ code = "bt_permission" if state == "unauthorized" else "no_bluetooth"
269
+ raise CableBtError("macOS doesn't let Mobster use Bluetooth" if code == "bt_permission"
270
+ else "Bluetooth isn't on on this Mac", code)
271
+ time.sleep(.1)
272
+ _HID = link
273
+ return link
274
+
275
+
276
+ def open_screen(udid=None, name=None, phones=None):
277
+ # a capture device may not carry the phone's UDID: its name and the phone count go along, never a guess between two
278
+ if udid and name is None and phones is None:
279
+ from .listen import capture_hints
280
+ hints = capture_hints(udid)
281
+ hinted = dict(zip(hints[::2], hints[1::2]))
282
+ name = hinted.get("--name")
283
+ phones = int(hinted["--phones"]) if hinted.get("--phones", "").isdigit() else None
284
+ link = Link([helper_path("phone_screen")], "iPhone screen")
285
+ try:
286
+ link.request("open", timeout=25, udid=udid, name=name, phones=phones, width=VIDEO_WIDTH)
287
+ except Exception:
288
+ link.close()
289
+ raise
290
+ return link
291
+
292
+
293
+ def calibration_path():
294
+ from .paths import user_data_dir
295
+ return user_data_dir() / "cable_bt.json"
296
+
297
+
298
+ @dataclass
299
+ class Calibration:
300
+ """One phone's pairing and pointer: its Bluetooth host on this Mac and the pointer's gain per count."""
301
+ host: str = ""
302
+ gain: tuple = ()
303
+ home: str = "cmd+h"
304
+ spotlight: str = "cmd+space"
305
+
306
+ @classmethod
307
+ def load(cls, udid, path=None):
308
+ try:
309
+ record = json.loads((path or calibration_path()).read_text()).get("phones", {}).get(udid or "", {})
310
+ except (OSError, ValueError, AttributeError):
311
+ record = {}
312
+ gain = record.get("gain")
313
+ ok = isinstance(gain, (list, tuple)) and len(gain) == 2 and all(isinstance(v, (int, float)) and 0 < v < 1
314
+ for v in gain)
315
+ return cls(host=str(record.get("host") or ""), gain=tuple(gain) if ok else (),
316
+ home=str(record.get("home") or "cmd+h"), spotlight=str(record.get("spotlight") or "cmd+space"))
317
+
318
+ def save(self, udid, path=None):
319
+ path = path or calibration_path()
320
+ try:
321
+ data = json.loads(path.read_text())
322
+ except (OSError, ValueError):
323
+ data = {}
324
+ phones = data.setdefault("phones", {})
325
+ phones[udid or ""] = {"host": self.host, "gain": list(self.gain), "home": self.home,
326
+ "spotlight": self.spotlight}
327
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
328
+ partial = path.with_suffix(".partial")
329
+ partial.write_text(json.dumps(data, indent=2))
330
+ os.chmod(partial, 0o600)
331
+ os.replace(partial, path)
332
+
333
+
334
+ class Pointer:
335
+ """AssistiveTouch's pointer on one phone: put where a tap should land, and seen there in the video first."""
336
+
337
+ def __init__(self, screen, hid, calibration, *, aspect=2556 / 1179, save=None, sleep=time.sleep,
338
+ clock=time.monotonic):
339
+ self.screen, self.hid, self.cal = screen, hid, calibration
340
+ self.aspect = aspect # height / width
341
+ self._save = save or (lambda cal: None)
342
+ self._sleep, self._clock = sleep, clock
343
+ self.corrections = 0
344
+ self.taps = 0
345
+
346
+ def tolerance(self):
347
+ return TOLERANCE_PT / PHONE_WIDTH_PT, TOLERANCE_PT / (PHONE_WIDTH_PT * self.aspect)
348
+
349
+ def _move(self, dx, dy, *, step=MOVE_STEP, interval=MOVE_INTERVAL_MS, mask=0):
350
+ dx, dy = int(round(dx)), int(round(dy))
351
+ if not dx and not dy:
352
+ return
353
+ reports = max(abs(dx), abs(dy)) / max(step, 1)
354
+ self.hid.request("move", host=self.cal.host, dx=dx, dy=dy, step=step, interval_ms=interval, mask=mask,
355
+ timeout=5 + reports * (interval / 1000 + .02))
356
+
357
+ def home(self):
358
+ """Into the top-left corner: big steps, far more than the screen."""
359
+ self._move(-HOME_COUNTS, -HOME_COUNTS, step=HOME_STEP, interval=HOME_INTERVAL_MS)
360
+ self._sleep(SETTLE_SECONDS)
361
+
362
+ def find(self, near, avoid=(0.0, 0.0)):
363
+ deadline = self._clock() + FIND_SECONDS
364
+ tx, ty = self.tolerance()
365
+ while True:
366
+ blobs = self.screen.request("blobs", timeout=3).get("blobs") or []
367
+ candidates = [b for b in blobs if POINTER_AREA[0] <= int(b.get("area") or 0) <= POINTER_AREA[1]
368
+ and not (abs(b["x"] - avoid[0]) < tx * 1.5 and abs(b["y"] - avoid[1]) < ty * 1.5)]
369
+ if candidates:
370
+ return min(candidates, key=lambda b: (b["x"] - near[0]) ** 2 + ((b["y"] - near[1]) * self.aspect) ** 2)
371
+ if self._clock() >= deadline:
372
+ return None
373
+ self._sleep(FIND_POLL_SECONDS)
374
+
375
+ def calibrate(self):
376
+ """Measure the pointer's gain: from the corner, a known move, and where the video shows it."""
377
+ self.home()
378
+ self.screen.request("mark")
379
+ cx, cy = CALIBRATE_COUNTS
380
+ self._move(cx, cy)
381
+ seen = self.find(near=(.5, .5))
382
+ if seen is None:
383
+ raise CableBtError("Mobster couldn't see AssistiveTouch's pointer move on the iPhone; nothing was tapped",
384
+ "pointer")
385
+ if not (.03 < seen["x"] < .97 and .03 < seen["y"] < .97):
386
+ raise CableBtError("The pointer ran to the screen's edge while Mobster measured it; nothing was tapped. "
387
+ "Set Tracking Speed lower in AssistiveTouch, then try again.", "pointer")
388
+ self.cal.gain = (seen["x"] / cx, seen["y"] / cy)
389
+ self._save(self.cal)
390
+ return self.cal.gain
391
+
392
+ def aim(self, fx, fy):
393
+ if not (0 <= fx <= 1 and 0 <= fy <= 1):
394
+ raise ValueError("The point is outside the screen")
395
+ if not self.cal.gain:
396
+ self.calibrate()
397
+ gx, gy = self.cal.gain
398
+ tx, ty = self.tolerance()
399
+ self.home()
400
+ self.screen.request("mark")
401
+ self._move(fx / gx, fy / gy)
402
+ avoid, total = (0.0, 0.0), (fx / gx, fy / gy)
403
+ for attempt in range(MAX_CORRECTIONS + 1):
404
+ seen = self.find(near=(fx, fy), avoid=avoid)
405
+ if seen is None:
406
+ raise CableBtError("Mobster couldn't see the pointer after moving it; nothing was tapped", "pointer")
407
+ ex, ey = fx - seen["x"], fy - seen["y"]
408
+ if attempt == 0 and .05 < seen["x"] < .95 and .05 < seen["y"] < .95:
409
+ # Learn from a clean move out of the corner (no correction mixed in), slowly.
410
+ gx = .7 * gx + .3 * (seen["x"] / total[0]) if total[0] > 0 else gx
411
+ gy = .7 * gy + .3 * (seen["y"] / total[1]) if total[1] > 0 else gy
412
+ self.cal.gain = (gx, gy)
413
+ if abs(ex) <= tx and abs(ey) <= ty:
414
+ return seen
415
+ if attempt == MAX_CORRECTIONS:
416
+ break
417
+ self.corrections += 1
418
+ avoid = (seen["x"], seen["y"])
419
+ self.screen.request("mark")
420
+ self._move(ex / gx, ey / gy)
421
+ raise CableBtError("The pointer didn't land on the target; nothing was tapped", "pointer")
422
+
423
+ def tap(self, fx, fy, hold_ms=None):
424
+ self.aim(fx, fy)
425
+ self.hid.request("click", host=self.cal.host, ms=hold_ms or CLICK_MS, timeout=5 + (hold_ms or 0) / 1000)
426
+ self.taps += 1
427
+ if self.taps % 20 == 1:
428
+ self._save(self.cal)
429
+
430
+ def drag(self, x1, y1, x2, y2):
431
+ """A touch drag: the pointer seen at the start, the button held through a constant-pace move, then let go."""
432
+ self.aim(x1, y1)
433
+ gx, gy = self.cal.gain
434
+ self.hid.request("buttons", host=self.cal.host, mask=1)
435
+ try:
436
+ self._move((x2 - x1) / gx, (y2 - y1) / gy, step=DRAG_STEP, interval=DRAG_INTERVAL_MS, mask=1)
437
+ finally:
438
+ self.hid.request("buttons", host=self.cal.host, mask=0)
439
+
440
+
441
+ class Phone:
442
+ """One phone on this connection, speaking the protocol MirroringDriver's helper speaks."""
443
+
444
+ def __init__(self, screen, hid, calibration, *, udid="", sleep=time.sleep, clock=time.monotonic,
445
+ calibration_file=None, locked=None):
446
+ self.screen, self.hid, self.udid = screen, hid, udid
447
+ self.cal = calibration
448
+ self._file = calibration_file
449
+ self.pointer = Pointer(screen, hid, calibration, save=self.save, sleep=sleep, clock=clock)
450
+ self.locked = locked or usb_locked
451
+ self.window = None
452
+
453
+ def save(self, cal):
454
+ try:
455
+ cal.save(self.udid, self._file)
456
+ except OSError:
457
+ pass # a calibration not kept is measured again next time
458
+
459
+ def _capture(self, fields):
460
+ fields = dict(fields)
461
+ fields.setdefault("mask", STATUS_BAR)
462
+ answer = self.screen.request("capture", timeout=HELPER_TIMEOUT, **fields)
463
+ window = answer.get("window") or {}
464
+ if window.get("w") and window.get("h"):
465
+ self.window = window
466
+ self.pointer.aspect = float(window["h"]) / float(window["w"])
467
+ return answer
468
+
469
+ def state(self):
470
+ try:
471
+ seen = self._capture({"ocr": True, "fast": True})
472
+ except CableBtError as error:
473
+ return {"state": "no-video", "code": error.code, "said": [str(error)]}
474
+ if (seen.get("age") or 0) > 3000:
475
+ return {"state": "no-video", "code": "capture", "said": ["The iPhone's screen video stopped"]}
476
+ if float(seen.get("dark") or 0) > .97:
477
+ return {"state": "asleep", "code": "locked", "said": ["The iPhone's screen is off"]}
478
+ if self.locked(self.udid):
479
+ return {"state": "locked", "code": "locked", "said": ["The iPhone is locked"]}
480
+ rows = seen.get("rows") or ()
481
+ lines = [" ".join(str(row.get("text") or "").split()).casefold() for row in rows]
482
+ if any(line.startswith(lock) for line in lines for lock in LOCK_LINES) or any(map(_lock_clock, rows)):
483
+ return {"state": "locked", "code": "locked", "said": ["The iPhone is locked"]}
484
+ try:
485
+ hosts = self.hid.request("status", timeout=3).get("hosts") or []
486
+ except CableBtError as error:
487
+ return {"state": "no-keyboard", "code": error.code, "said": [str(error)]}
488
+ if not any(h.get("host") == self.cal.host and h.get("ready") for h in hosts):
489
+ return {"state": "no-keyboard", "code": "no_host",
490
+ "said": ["The iPhone isn't using this Mac as a keyboard"]}
491
+ return {"state": "ready"}
492
+
493
+ def _key(self, combo):
494
+ self.hid.request("key", host=self.cal.host, combo=combo)
495
+
496
+ def request(self, op, timeout=HELPER_TIMEOUT, **fields):
497
+ if op == "capture":
498
+ return self._capture(fields)
499
+ if op == "state":
500
+ return {"ok": True, **self.state()}
501
+ if op == "tap":
502
+ self.pointer.tap(fields["x"], fields["y"])
503
+ elif op == "press":
504
+ self.pointer.tap(fields["x"], fields["y"], hold_ms=fields.get("ms") or LONG_PRESS_MS)
505
+ elif op == "drag":
506
+ self.pointer.drag(fields["x1"], fields["y1"], fields["x2"], fields["y2"])
507
+ elif op == "scroll":
508
+ # MirroringDriver's scroll: dy window points, positive reveals what's below (the finger moves up).
509
+ height = float((self.window or {}).get("h") or 1)
510
+ span = max(min(abs(float(fields.get("dy") or 0)) / height, .6), .1)
511
+ x, y = float(fields.get("x", .5)), float(fields.get("y", .5))
512
+ top, bottom = y - span / 2, y + span / 2
513
+ if float(fields.get("dy") or 0) > 0:
514
+ self.pointer.drag(x, bottom, x, top)
515
+ else:
516
+ self.pointer.drag(x, top, x, bottom)
517
+ elif op == "key":
518
+ combo = fields.get("combo") or ""
519
+ # MirroringDriver sends iPhone Mirroring's own keys for Home (cmd+1) and Spotlight (cmd+3): the iPhone's are
520
+ # this phone's calibrated ones (cmd+H and cmd+space).
521
+ self._key({MIRRORING_HOME: self.cal.home, MIRRORING_SPOTLIGHT: self.cal.spotlight}.get(combo, combo))
522
+ elif op == "type":
523
+ if fields.get("mode") != "keys" or not typable(fields.get("text") or ""):
524
+ raise CableBtError("Cable + Bluetooth types US keyboard characters only (at most 80); nothing was "
525
+ "typed", "refused")
526
+ text = fields["text"]
527
+ self.hid.request("type", host=self.cal.host, text=text, timeout=5 + len(text) * .1)
528
+ elif op == "restore":
529
+ self.hid.request("release", host=self.cal.host, timeout=3)
530
+ else:
531
+ raise CableBtError(f"Cable + Bluetooth can't {op}", "usage")
532
+ return {"ok": True}
533
+
534
+ def close(self):
535
+ self.screen.close()
536
+
537
+
538
+ def assign_host(screen, hid, sleep=time.sleep):
539
+ hosts = [h["host"] for h in hid.request("status", timeout=3).get("hosts") or () if h.get("ready")]
540
+ # moving a pointer touches nothing (only a click does), so nudging every free host is safe
541
+ for host in hosts:
542
+ hid.request("move", host=host, dx=-HOME_COUNTS, dy=-HOME_COUNTS, step=HOME_STEP, interval_ms=HOME_INTERVAL_MS)
543
+ sleep(SETTLE_SECONDS)
544
+ screen.request("mark")
545
+ hid.request("move", host=host, dx=200, dy=400, step=MOVE_STEP, interval_ms=MOVE_INTERVAL_MS, timeout=10)
546
+ sleep(.25)
547
+ if any(POINTER_AREA[0] <= int(b.get("area") or 0) <= POINTER_AREA[1]
548
+ for b in screen.request("blobs").get("blobs") or ()):
549
+ return host
550
+ return None
551
+
552
+
553
+ def open_phone(udid=None, name=None, phones=None, *, calibration_file=None):
554
+ hid = hid_link()
555
+ screen = open_screen(udid=udid, name=name, phones=phones)
556
+ try:
557
+ cal = Calibration.load(udid, calibration_file)
558
+ ready = [h["host"] for h in hid.request("status", timeout=3).get("hosts") or () if h.get("ready")]
559
+ if cal.host not in ready:
560
+ cal.host = assign_host(screen, hid) or ""
561
+ if not cal.host:
562
+ # Show the Mac as "Mobster Keyboard" in the iPhone's Bluetooth settings for a while, for pairing.
563
+ hid.request("advertise", seconds=ADVERTISE_SECONDS)
564
+ raise CableBtError("This iPhone isn't using this Mac as a Bluetooth keyboard and mouse", "no_host")
565
+ cal.gain = ()
566
+ phone = Phone(screen, hid, cal, udid=udid or "", calibration_file=calibration_file)
567
+ phone.save(cal)
568
+ return phone
569
+ except Exception:
570
+ screen.close()
571
+ raise
572
+
573
+
574
+ def _shows(snapshot, text):
575
+ """Whether ``text`` (typed) is on the screen: its words in order, in one element's text, any case."""
576
+ wanted = " ".join(text.split()).casefold()
577
+ if not wanted:
578
+ return True
579
+ return any(wanted in " ".join(e.label.split()).casefold() for e in snapshot.elements)
580
+
581
+
582
+ class CableBtDriver(MirroringDriver):
583
+ """The device contract over Cable + Bluetooth: MirroringDriver's, over a ``Phone`` (tests pass a fake)."""
584
+
585
+ source = SOURCE
586
+
587
+ def __init__(self, helper=None, *, udid=None, name=None, phones=None, app_names=None, frames_dir=None,
588
+ sleep=time.sleep, clock=time.monotonic, labeller=None, icon_memory=None):
589
+ if helper is None:
590
+ helper = open_phone(udid=udid, name=name, phones=phones)
591
+ super().__init__(helper, app_names=app_names, frames_dir=frames_dir, sleep=sleep, clock=clock,
592
+ labeller=labeller, icon_memory=icon_memory)
593
+
594
+ def observe(self, timeout=10):
595
+ result, started = self._capture(True, timeout)
596
+ return perception.snapshot_from(result, source=SOURCE, bundle=self.current_app, read_started=started,
597
+ icons=self._icons(result))
598
+
599
+ def _await_keyboard(self):
600
+ # A Bluetooth keyboard keeps the software keyboard away: wait for focus, then read the text back after typing.
601
+ self._sleep(FOCUS_SECONDS)
602
+ return True
603
+
604
+ def execute(self, operation, target, snapshot, text=None, timeout=10):
605
+ if operation not in ("TYPE", "TYPE_SUBMIT"):
606
+ return super().execute(operation, target, snapshot, text=text, timeout=timeout)
607
+ grounded_target(operation, target, snapshot)
608
+ validate_input_text(text, multiline=False)
609
+ if not typable(text):
610
+ raise ValueError("Cable + Bluetooth types US keyboard characters only (at most 80); nothing was typed")
611
+ self._check_current(snapshot, target=target)
612
+ x, y = target.tap_point
613
+ self._tap(x, y)
614
+ self._await_keyboard()
615
+ self._type(text)
616
+ if not _shows(self.observe(timeout=5), text):
617
+ raise TransportError("The typed text didn't show on the screen, so Return wasn't pressed")
618
+ if operation == "TYPE_SUBMIT":
619
+ self.helper.request("key", combo="return")
620
+ return None
621
+
622
+
623
+ register_driver(SOURCE, CableBtDriver)