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,12 @@
1
+ """Skills: operations the agent loop hands to code instead of to the model (agent_hooks.Skill)."""
2
+
3
+ from .codes import UseCode
4
+ from .notifications import ReadNotifications
5
+ from .open_url import OpenUrl
6
+
7
+ __all__ = ["UseCode", "ReadNotifications", "OpenUrl", "default_skills"]
8
+
9
+
10
+ def default_skills():
11
+ """The skills every agent run offers, in prompt order."""
12
+ return (UseCode(), ReadNotifications(), OpenUrl())
@@ -0,0 +1,597 @@
1
+ """USE_CODE: a verification code from the phone, typed into the code field by code, never seen by the model."""
2
+
3
+ import datetime
4
+ import re
5
+ import time
6
+ from dataclasses import dataclass
7
+ from dataclasses import field as dataclass_field
8
+ from typing import Optional
9
+
10
+ from ..agent_hooks import MASK, Prepared, SkillResult
11
+
12
+ MAX_AGE_S = 600
13
+ SPRINGBOARD = "com.apple.springboard"
14
+ MESSAGES = "com.apple.MobileSMS"
15
+ # Where a code is read, never typed: a code typed into a message, a mail or a note would send or keep it.
16
+ REFUSED_APPS = {MESSAGES: "Messages", "com.apple.mobilemail": "Mail", "com.apple.mobilenotes": "Notes",
17
+ SPRINGBOARD: "the Home Screen"}
18
+
19
+ # Words that make a message a verification code's. "passcode" here is a bank's one-time passcode in a text;
20
+ # a *field* named passcode is the device's or the account's, and is refused (SECRET_FIELD).
21
+ STRONG = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|passcode)\b", re.I)
22
+ # Masking reaches further: any number in a sign-in or security message is hidden from the model.
23
+ BROAD = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|passcode|log[- ]?in|"
24
+ r"sign[- ]?in|security|authenticat\w*)\b", re.I)
25
+ # A digit group that can be a code: 4 to 8 digits, or two halves (482 913, 482-913), alone or after a one- to
26
+ # three-letter prefix (G-482913). Not money ($1234), a phone number (+1 555…), a time (10:42), a decimal, a
27
+ # path or part of a word.
28
+ CANDIDATE = re.compile(r"(?:(?<=\b[A-Z]-)|(?<=\b[A-Z]{2}-)|(?<=\b[A-Z]{3}-)|(?<![\w$€£¥#+./:-]))"
29
+ r"(\d{3,4}[ -]\d{3,4}|\d{4,8})(?![\w%]|[.:,]\d)")
30
+ PHONE_BEFORE = re.compile(r"(?:\(\d{3}\)\s?|\d[-. ])$")
31
+
32
+ CODE_FIELD = re.compile(r"\b(?:codes?|verification|verify|one[- ]?time|otp|2fa|two[- ]factor|digits?|"
33
+ r"confirmation|authenticat\w*)\b|onetimecode|one_time|otc", re.I)
34
+ SECRET_FIELD = re.compile(r"password|passcode|\bpin\b|card|cvv|cvc|csc|expir|ssn|social security|account number|"
35
+ r"routing", re.I)
36
+ NOT_VERIFICATION = re.compile(r"promo|discount|coupon|gift|referral|invit|zip|postal|country|area code|voucher|"
37
+ r"redeem|qr|bar ?code", re.I)
38
+ COMPOSER = re.compile(r"\b(?:message|imessage|text message|reply|comment|compose|post|tweet|caption|note|"
39
+ r"subject|to:|search|ask)\b", re.I)
40
+ PAYMENT_SCREEN = re.compile(r"card number|expiration|expiry|\bcvv\b|\bcvc\b|billing address", re.I)
41
+ QUICKTYPE = re.compile(r"\bfrom (messages|mail)\b", re.I)
42
+ EDITABLE = {"TextField", "SecureTextField"}
43
+
44
+
45
+ class Clock:
46
+ """Time as the skills read it; tests pass their own."""
47
+
48
+ monotonic = staticmethod(time.monotonic)
49
+ sleep = staticmethod(time.sleep)
50
+ time = staticmethod(time.time)
51
+
52
+
53
+ CLOCK = Clock()
54
+
55
+
56
+ class CodeRefused(Exception):
57
+ """One plain sentence: why no code was typed. It never holds a code."""
58
+
59
+
60
+ class NoCodeYet(CodeRefused):
61
+ """No code at all in any source (none stale, none from another sender): it may still be on its way."""
62
+
63
+
64
+ # A sign-in sends its code as the person asks for it, so USE_CODE often looks before it arrives: with no code anywhere
65
+ # it watches the keyboard's suggestion (QuickType: "From Messages 482913", read from the screen it is on, nothing
66
+ # opened) every CODE_POLL_S seconds for up to CODE_WAIT_S, then looks everywhere once more.
67
+ CODE_WAIT_S = 60
68
+ CODE_POLL_S = 2.0
69
+
70
+
71
+ def _candidates(text):
72
+ found = []
73
+ for match in CANDIDATE.finditer(text or ""):
74
+ start = match.start(1)
75
+ if PHONE_BEFORE.search(text[max(0, start - 6):start]):
76
+ continue
77
+ digits = re.sub(r"\D", "", match.group(1))
78
+ if 4 <= len(digits) <= 8:
79
+ found.append((start, match.end(1), digits))
80
+ if len(found) > 1: # a year beside a real code is not the code
81
+ found = [item for item in found if not re.fullmatch(r"(?:19|20)\d\d", item[2])] or found
82
+ return found
83
+
84
+
85
+ def extract_code(text, require_words=True):
86
+ """The one code a message carries, or None: the digit group nearest a code word, a 6-digit one first."""
87
+ text = text or ""
88
+ words = [m.start() for m in STRONG.finditer(text)]
89
+ if require_words and not words:
90
+ return None
91
+ found = _candidates(text)
92
+ if not found:
93
+ return None
94
+
95
+ def rank(item):
96
+ distance = min(abs(item[0] - at) for at in words) if words else 0
97
+ return (distance // 40, len(item[2]) != 6, distance, item[0])
98
+ return min(found, key=rank)[2]
99
+
100
+
101
+ def mask_codes(text, mask=MASK, *, context=None):
102
+ """Mask possible codes when ``text`` (or its full message, ``context``) has sign-in or security wording."""
103
+ if not text or not BROAD.search(text if context is None else context):
104
+ return text
105
+ out = text
106
+ for start, end, _digits in sorted(_candidates(text), reverse=True):
107
+ out = out[:start] + mask + out[end:]
108
+ return out
109
+
110
+
111
+ def parse_age(text, now=None):
112
+ """Seconds since a notification's or a message row's time (now, 2m ago, 10:42 AM, Yesterday)."""
113
+ lowered = (text or "").lower()
114
+ if re.search(r"\b(?:just now|now)\b", lowered):
115
+ return 0
116
+ for pattern, unit in ((r"\b(\d{1,3})\s*(?:s|sec|secs|seconds?)\b\.?\s*ago", 1),
117
+ (r"\b(\d{1,3})\s*(?:m|min|mins|minutes?)\b\.?\s*ago", 60),
118
+ (r"\b(\d{1,2})\s*(?:h|hr|hrs|hours?)\b\.?\s*ago", 3600)):
119
+ match = re.search(pattern, lowered)
120
+ if match:
121
+ return int(match.group(1)) * unit
122
+ if re.search(r"\byesterday\b|\b(?:mon|tues|wednes|thurs|fri|satur|sun)day\b", lowered):
123
+ return 86400
124
+ now = time.time() if now is None else now
125
+ local = datetime.datetime.fromtimestamp(now)
126
+ match = re.search(r"\b(\d{1,2}):([0-5]\d)\s*([ap])\.?\s*m\b\.?", lowered)
127
+ if match:
128
+ hour = int(match.group(1)) % 12 + (12 if match.group(3) == "p" else 0)
129
+ else:
130
+ match = re.search(r"\b([01]?\d|2[0-3]):([0-5]\d)\b", lowered)
131
+ if not match:
132
+ return None
133
+ hour = int(match.group(1))
134
+ then = local.replace(hour=hour, minute=int(match.group(2)), second=0, microsecond=0)
135
+ seconds = (local - then).total_seconds()
136
+ if seconds < -60:
137
+ seconds += 86400
138
+ return max(0, int(seconds))
139
+
140
+
141
+ def row_age(parts, now=None):
142
+ """The age a row's own time part gives (the last part that parses), else its whole text's."""
143
+ for part in reversed(parts):
144
+ age = parse_age(part, now) if len(part) <= 24 else None
145
+ if age is not None:
146
+ return age
147
+ return None
148
+
149
+
150
+ @dataclass(frozen=True)
151
+ class Field:
152
+ """The field a code goes into: what the checks read, and where it is (screen fractions)."""
153
+ label: str
154
+ role: str
155
+ rect: tuple
156
+ identifier: str = ""
157
+ placeholder: str = ""
158
+ locator: str = ""
159
+
160
+ @classmethod
161
+ def of(cls, element):
162
+ role = (getattr(element, "role", "") or "").removeprefix("XCUIElementType")
163
+ return cls(label=getattr(element, "label", "") or "", role=role, rect=tuple(element.rect),
164
+ identifier=str(getattr(element, "identifier", "") or ""),
165
+ placeholder=getattr(element, "placeholder", "") or "", locator=getattr(element, "locator", "") or "")
166
+
167
+ @property
168
+ def words(self):
169
+ return " ".join(part for part in (self.label, self.placeholder, self.identifier) if part)
170
+
171
+ @property
172
+ def center(self):
173
+ x, y, w, h = self.rect
174
+ return x + w / 2, y + h / 2
175
+
176
+
177
+ def _role(element):
178
+ return (getattr(element, "role", "") or "").removeprefix("XCUIElementType")
179
+
180
+
181
+ def code_boxes(target, elements):
182
+ """The row of 4 to 8 one-character boxes ``target`` is one of, left to right; () when it isn't."""
183
+ tx, ty, tw, th = target.rect
184
+ if tw >= .2:
185
+ return ()
186
+ row = [e for e in elements if _role(e) in EDITABLE and e.rect[2] < .2
187
+ and abs((e.rect[1] + e.rect[3] / 2) - (ty + th / 2)) < .03 and abs(e.rect[3] - th) < .03]
188
+ if not any(Field.of(e).rect == target.rect for e in row):
189
+ row.append(target)
190
+ return tuple(sorted(row, key=lambda e: e.rect[0])) if 4 <= len(row) <= 8 else ()
191
+
192
+
193
+ def check_app(bundle):
194
+ if not bundle:
195
+ # Without the app's name none of the checks below can hold: it might be Messages.
196
+ raise CodeRefused("Mobster couldn't tell which app is in front, so it typed no code.")
197
+ if bundle in REFUSED_APPS:
198
+ raise CodeRefused(f"Mobster never types a code into {REFUSED_APPS[bundle]}: it only goes into the code "
199
+ "field of the app you're signing in to.")
200
+
201
+
202
+ def check_field(target, snapshot):
203
+ """Raise CodeRefused unless ``target`` looks like a verification-code field of the screen ``snapshot``."""
204
+ words = target.words
205
+ if target.role == "SearchField":
206
+ raise CodeRefused("That is a search field, not a code field, so Mobster typed nothing.")
207
+ if target.role == "TextView":
208
+ raise CodeRefused("That is a message or text box, not a code field, so Mobster typed nothing.")
209
+ if target.role not in EDITABLE:
210
+ raise CodeRefused("The target isn't a text field. Point USE_CODE at the code field.")
211
+ if SECRET_FIELD.search(words):
212
+ raise CodeRefused("That looks like a password, PIN or card field, not a code field, so Mobster typed "
213
+ "nothing.")
214
+ if PAYMENT_SCREEN.search(getattr(snapshot, "text", "") or ""):
215
+ raise CodeRefused("This looks like a payment form, so Mobster typed no code.")
216
+ if NOT_VERIFICATION.search(words):
217
+ raise CodeRefused("That is a promo, gift, postal or other code field, not a sign-in code field, so Mobster "
218
+ "typed nothing.")
219
+ if CODE_FIELD.search(words):
220
+ return ()
221
+ if COMPOSER.search(words):
222
+ raise CodeRefused("That is a message, comment or search box, not a code field, so Mobster typed nothing.")
223
+ boxes = code_boxes(target, getattr(snapshot, "elements", ()))
224
+ if boxes:
225
+ return boxes
226
+ raise CodeRefused("That field doesn't look like a verification-code field, so Mobster typed nothing.")
227
+
228
+
229
+ def app_name(bundle):
230
+ from ..catalog import APPS
231
+ for app in APPS:
232
+ if app.get("bundleId") == bundle:
233
+ return app["name"]
234
+ return "this app"
235
+
236
+
237
+ @dataclass(frozen=True)
238
+ class Candidate:
239
+ code: str
240
+ sender: str
241
+ age_s: Optional[int]
242
+ source: str # quicktype | notification | messages
243
+ text: str = dataclass_field(default="", repr=False)
244
+
245
+
246
+ @dataclass(frozen=True)
247
+ class Found:
248
+ """A code, found for one field of one app. Held in memory only (Prepared.state)."""
249
+ code: str = dataclass_field(repr=False)
250
+ sender: str
251
+ age_s: Optional[int]
252
+ source: str
253
+ bundle: str
254
+ field: Field
255
+ found_at: float
256
+ boxes: tuple = ()
257
+
258
+ def __repr__(self): # never the code, even in a traceback's locals
259
+ return f"Found(source={self.source!r}, sender={self.sender!r}, age_s={self.age_s!r})"
260
+
261
+ @property
262
+ def frame(self):
263
+ """Where the code shows once typed (screen fractions): the row of boxes, else the field."""
264
+ return _union(self.boxes) if self.boxes else tuple(self.field.rect)
265
+
266
+
267
+ def _union(elements):
268
+ rects = [tuple(element.rect) for element in elements]
269
+ left, top = min(r[0] for r in rects), min(r[1] for r in rects)
270
+ right, bottom = max(r[0] + r[2] for r in rects), max(r[1] + r[3] for r in rects)
271
+ return (left, top, right - left, bottom - top)
272
+
273
+
274
+ def _front(driver, snapshot=None):
275
+ active = getattr(driver, "active_app", None)
276
+ if callable(active):
277
+ try:
278
+ return active(timeout=5)
279
+ except Exception:
280
+ pass
281
+ snapshot = snapshot if snapshot is not None else driver.observe(timeout=5)
282
+ return getattr(snapshot, "bundle_id", "") or ""
283
+
284
+
285
+ def _activate(driver, bundle):
286
+ if bundle and bundle != SPRINGBOARD:
287
+ driver.call("POST", "/wda/apps/activate", {"bundleId": bundle}, timeout=15)
288
+
289
+
290
+ def _texts(snapshot):
291
+ seen, out = set(), []
292
+ for element in getattr(snapshot, "elements", ()):
293
+ for text in (" ".join(p for p in (element.label, getattr(element, "value", "")) if p).strip(),):
294
+ if text and text not in seen:
295
+ seen.add(text)
296
+ out.append(text)
297
+ for line in (getattr(snapshot, "text", "") or "").splitlines():
298
+ if line.strip() and line.strip() not in seen:
299
+ seen.add(line.strip())
300
+ out.append(line.strip())
301
+ return out
302
+
303
+
304
+ def _clean_sender(sender, code):
305
+ sender = " ".join((sender or "").split())[:60]
306
+ if not sender or (code and code in re.sub(r"\D", "", sender)):
307
+ return "Messages"
308
+ return sender
309
+
310
+
311
+ def quicktype_candidates(snapshot):
312
+ """Codes the keyboard offers (QuickType: "From Messages 482913")."""
313
+ out = []
314
+ for text in _texts(snapshot):
315
+ match = QUICKTYPE.search(text)
316
+ if not match:
317
+ continue
318
+ code = extract_code(QUICKTYPE.sub(" ", text), require_words=False)
319
+ if code:
320
+ out.append(Candidate(code, match.group(1).title(), None, "quicktype", text))
321
+ return out
322
+
323
+
324
+ def row_candidates(rows, source):
325
+ """Codes in notification or message rows: (code, sender, age) per row with code wording."""
326
+ out = []
327
+ for row in rows:
328
+ code = extract_code(row.text)
329
+ if code:
330
+ out.append(Candidate(code, _clean_sender(row.sender, code), row.age_s, source, row.text))
331
+ return out
332
+
333
+
334
+ def _focus(driver, target, snapshot, clock, seconds=2.0):
335
+ """Tap the field (only when no keyboard is up) and wait for the keyboard; the latest screen."""
336
+ if getattr(snapshot, "keyboard", "") == "visible":
337
+ return snapshot
338
+ x, y = target.center
339
+ driver.tap_point(x, y, snapshot, timeout=10)
340
+ end = clock.monotonic() + seconds
341
+ while True:
342
+ clock.sleep(.25)
343
+ current = driver.observe(timeout=5)
344
+ if getattr(current, "keyboard", "") or clock.monotonic() >= end:
345
+ return current
346
+
347
+
348
+ def find_code(driver, *, field, bundle, snapshot, hint=None, clock=None,
349
+ sources=("quicktype", "notification", "messages")):
350
+ """The one fresh code for ``field`` of ``bundle`` (Found), or CodeRefused."""
351
+ from .notifications import NotificationCenter, message_rows
352
+ clock = clock or CLOCK
353
+ check_app(bundle)
354
+ boxes = check_field(field, snapshot)
355
+ hint = " ".join(str(hint or "").split()).casefold() or None
356
+ stale = hinted_out = False
357
+ for source in sources:
358
+ try:
359
+ if source == "quicktype":
360
+ snapshot = _focus(driver, field, snapshot, clock)
361
+ found = quicktype_candidates(snapshot)
362
+ elif source == "notification":
363
+ found = row_candidates(NotificationCenter(driver, clock).read(origin=bundle, size=snapshot),
364
+ "notification")
365
+ else:
366
+ found = row_candidates(message_rows(driver, origin=bundle, clock=clock), "messages")
367
+ finally:
368
+ if source != "quicktype" and _front(driver) != bundle:
369
+ _activate(driver, bundle)
370
+ fresh = [c for c in found if c.source == "quicktype" or (c.age_s is not None and c.age_s <= MAX_AGE_S)]
371
+ if found and not fresh:
372
+ stale = True
373
+ continue
374
+ if hint:
375
+ matching = [c for c in fresh if hint in (c.sender + " " + c.text).casefold()]
376
+ hinted_out = hinted_out or (bool(fresh) and not matching)
377
+ fresh = matching
378
+ codes = {c.code for c in fresh}
379
+ if len(codes) > 1:
380
+ raise CodeRefused("Two different codes arrived in the last 10 minutes"
381
+ + (" from that sender" if hint else "")
382
+ + ", so Mobster typed neither. Name the sender in USE_CODE's text, or ask for a new "
383
+ "code.")
384
+ if codes:
385
+ best = min(fresh, key=lambda c: c.age_s if c.age_s is not None else 0)
386
+ return Found(code=best.code, sender=best.sender, age_s=best.age_s, source=best.source, bundle=bundle,
387
+ field=field, found_at=clock.monotonic(), boxes=boxes)
388
+ if hinted_out:
389
+ raise CodeRefused("No code from that sender arrived in the last 10 minutes. Ask for a new code, then try "
390
+ "again.")
391
+ if stale:
392
+ raise CodeRefused("The newest code is older than 10 minutes, so Mobster won't use it. Ask for a new code, "
393
+ "then try again.")
394
+ raise NoCodeYet("No verification code arrived in the last 10 minutes (keyboard suggestions, Notification "
395
+ "Center and Messages). Ask for a new code, then try again.")
396
+
397
+
398
+ def await_code(driver, *, field, bundle, snapshot, hint=None, clock=None, seconds=CODE_WAIT_S, cancelled=None,
399
+ emit=None):
400
+ """find_code, and when nothing has arrived yet (NoCodeYet), wait for it."""
401
+ clock = clock or CLOCK
402
+ try:
403
+ return find_code(driver, field=field, bundle=bundle, snapshot=snapshot, hint=hint, clock=clock)
404
+ except NoCodeYet as first:
405
+ refusal = first
406
+ hint = " ".join(str(hint or "").split()).casefold() or None
407
+ _say(emit, {"event": "code_waiting", "seconds": int(seconds)})
408
+ started = clock.monotonic()
409
+ found = None
410
+ try:
411
+ # The other sources brought the app back to the front without the keyboard: focus the field again, as the
412
+ # first look did, so the suggestion can show.
413
+ _focus(driver, field, driver.observe(timeout=5), clock)
414
+ except Exception: # noqa: BLE001 - the polls below read whatever is there
415
+ pass
416
+ while found is None and clock.monotonic() - started < seconds and not _stopped(cancelled):
417
+ clock.sleep(CODE_POLL_S)
418
+ try:
419
+ current = driver.observe(timeout=5)
420
+ except Exception: # noqa: BLE001 - a read that failed: look again next time
421
+ continue
422
+ if getattr(current, "bundle_id", bundle) not in (bundle, "", None):
423
+ break # the person moved to another app: stop waiting, never read another app's keyboard
424
+ offered = [c for c in quicktype_candidates(current)
425
+ if not hint or hint in (c.sender + " " + c.text).casefold()]
426
+ if len({c.code for c in offered}) == 1:
427
+ found = offered[0]
428
+ try:
429
+ if found is not None:
430
+ return Found(code=found.code, sender=found.sender, age_s=found.age_s, source=found.source, bundle=bundle,
431
+ field=field, found_at=clock.monotonic(), boxes=check_field(field, snapshot))
432
+ if _stopped(cancelled):
433
+ raise refusal
434
+ return find_code(driver, field=field, bundle=bundle, snapshot=driver.observe(timeout=5), hint=hint,
435
+ clock=clock)
436
+ finally:
437
+ _say(emit, {"event": "code_wait_finished", "ok": found is not None,
438
+ "waited_s": round(max(0.0, clock.monotonic() - started), 1)})
439
+
440
+
441
+ def _stopped(cancelled):
442
+ try:
443
+ return bool(cancelled()) if callable(cancelled) else False
444
+ except Exception: # noqa: BLE001 - can't tell whether Stop was pressed: stop waiting
445
+ return True
446
+
447
+
448
+ def _say(emit, event):
449
+ if callable(emit):
450
+ try:
451
+ emit(event)
452
+ except Exception: # noqa: BLE001
453
+ pass
454
+
455
+
456
+ def say_age(seconds):
457
+ """A code's age as people say it: "just now", "40 seconds ago", "2 minutes ago"."""
458
+ seconds = max(0, int(seconds))
459
+ if seconds < 5:
460
+ return "just now"
461
+ if seconds < 60:
462
+ return f"{seconds} seconds ago"
463
+ minutes = seconds // 60
464
+ return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
465
+
466
+
467
+ def approval_title(found, app):
468
+ """The approval question: the sender, the age, the app; never the code."""
469
+ if found.age_s is None:
470
+ return f"Use the code the keyboard suggests from {found.sender} to sign in to {app}?"
471
+ return f"Use the code {found.sender} sent {say_age(found.age_s)} to sign in to {app}?"
472
+
473
+
474
+ def _refind(snapshot, target):
475
+ elements = list(getattr(snapshot, "elements", ()))
476
+ if target.locator:
477
+ for element in elements:
478
+ if getattr(element, "locator", "") == target.locator:
479
+ return element
480
+ for element in elements:
481
+ if _role(element) == target.role and all(abs(a - b) < .02 for a, b in zip(element.rect, target.rect)):
482
+ return element
483
+ return None
484
+
485
+
486
+ def enter_code(driver, found, *, clock=None):
487
+ """Type ``found``'s code into its field, after checking the app in front and the code's age again."""
488
+ clock = clock or CLOCK
489
+ fresh = driver.observe(timeout=5)
490
+ if _front(driver, fresh) != found.bundle:
491
+ raise CodeRefused("The app in front changed since Mobster found the code, so it typed nothing.")
492
+ if found.age_s is not None and found.age_s + (clock.monotonic() - found.found_at) > MAX_AGE_S:
493
+ raise CodeRefused("The code is older than 10 minutes now, so Mobster typed nothing. Ask for a new code, "
494
+ "then try again.")
495
+ element = _refind(fresh, found.field)
496
+ if element is None:
497
+ raise CodeRefused("The code field is no longer on the screen, so Mobster typed nothing.")
498
+ target = Field.of(element)
499
+ boxes = check_field(target, fresh)
500
+ if boxes:
501
+ target = Field.of(boxes[0])
502
+ # Always the code field's own tap: a keyboard that is up may belong to another field (the email above it),
503
+ # and keys go to whichever field has the focus.
504
+ keyboard = getattr(fresh, "keyboard", "") == "visible"
505
+ x, y = target.center
506
+ driver.tap_point(x, y, fresh, timeout=10)
507
+ if keyboard:
508
+ clock.sleep(.15)
509
+ else:
510
+ end = clock.monotonic() + 2.0
511
+ while clock.monotonic() < end:
512
+ clock.sleep(.2)
513
+ if getattr(driver.observe(timeout=5), "keyboard", ""):
514
+ break
515
+ driver.call("POST", "/wda/keys", {"value": [found.code]}, timeout=10)
516
+ return _union(boxes) if boxes else tuple(element.rect)
517
+
518
+
519
+ @dataclass(frozen=True)
520
+ class _Pending:
521
+ found: Optional[Found] = None
522
+ refusal: str = ""
523
+
524
+
525
+ def _target(act, snapshot):
526
+ element = act.get("element") if isinstance(act, dict) else None
527
+ if element is not None:
528
+ return element
529
+ elements = list(getattr(snapshot, "elements", ()))
530
+ wanted = act.get("target") if isinstance(act, dict) else None
531
+ if wanted:
532
+ for element in elements:
533
+ if element.id == wanted:
534
+ return element
535
+ label = (act.get("target_label") if isinstance(act, dict) else None) or None
536
+ if label:
537
+ named = [e for e in elements if e.label == label and _role(e) in EDITABLE | {"SearchField", "TextView"}]
538
+ if len(named) == 1:
539
+ return named[0]
540
+ return None
541
+
542
+
543
+ class UseCode:
544
+ """agent_hooks.Skill: USE_CODE."""
545
+
546
+ op = "USE_CODE"
547
+ prompt = ("- USE_CODE target [text]: a sign-in asks for a verification code sent by text or email: Mobster finds "
548
+ "it itself (keyboard suggestion, Notification Center, Messages), asks the user, and types it into "
549
+ "target, the code field; text may name the sender (\"Chase\"). You never see the code, so never open "
550
+ "Messages or Mail to read one.")
551
+ needs_target = True
552
+
553
+ def __init__(self, clock=None):
554
+ self.clock = clock or CLOCK
555
+
556
+ def available(self, ctx):
557
+ driver = ctx.driver
558
+ return callable(getattr(driver, "call", None)) and callable(getattr(driver, "observe", None))
559
+
560
+ def prepare(self, ctx, act, snapshot):
561
+ element = _target(act, snapshot)
562
+ if element is None:
563
+ return Prepared(None, _Pending(refusal="USE_CODE needs the code field as its target."))
564
+ bundle = getattr(snapshot, "bundle_id", "") or ctx.app_bundle or ""
565
+ hint = act.get("text") if isinstance(act, dict) else None
566
+ run = getattr(ctx, "run", None)
567
+ try:
568
+ found = await_code(ctx.driver, field=Field.of(element), bundle=bundle, snapshot=snapshot, hint=hint,
569
+ clock=self.clock, cancelled=getattr(run, "cancelled", None), emit=ctx.emit)
570
+ except CodeRefused as refusal:
571
+ return Prepared(None, _Pending(refusal=str(refusal)))
572
+ except Exception as error:
573
+ return Prepared(None, _Pending(refusal=f"Mobster couldn't look for the code ({type(error).__name__})."))
574
+ return Prepared(approval_title(found, app_name(bundle)), _Pending(found=found))
575
+
576
+ def perform(self, ctx, prepared, act, snapshot):
577
+ pending = (prepared.state
578
+ if isinstance(prepared.state, _Pending) else _Pending(refusal="USE_CODE found no code."))
579
+ if pending.found is None:
580
+ return SkillResult(feedback=f"USE_CODE typed nothing: {pending.refusal}")
581
+ found = pending.found
582
+ try:
583
+ rect = enter_code(ctx.driver, found, clock=self.clock)
584
+ except CodeRefused as refusal:
585
+ return SkillResult(feedback=f"USE_CODE typed nothing: {refusal}")
586
+ except Exception as error:
587
+ # Only the error's kind: a driver's message is never echoed next to a code. The code may be in the
588
+ # field already (a timeout after the keys landed), so it is masked and its field blurred all the same.
589
+ return SkillResult(feedback=f"USE_CODE couldn't type the code ({type(error).__name__}). Look at the "
590
+ "field before trying again.", changed=True, secrets=(found.code,),
591
+ secret_rects=(found.frame,))
592
+ try:
593
+ ctx.emit({"event": "code_used", "source": found.source, "sender": found.sender, "age_s": found.age_s})
594
+ except Exception:
595
+ pass
596
+ return SkillResult(feedback=f"Entered the code from {found.sender}.", changed=True,
597
+ secrets=(found.code,), secret_rects=(tuple(rect),))