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,136 @@
1
+ """Drafted replies from Apple's on-device model, through the ``draft-reply`` helper (helper_draft_reply.swift)."""
2
+
3
+ import json
4
+ import re
5
+ import subprocess
6
+ import time
7
+
8
+ from .. import native_helpers
9
+
10
+ HELPER = "draft_reply"
11
+ PER_DRAFT_S = 15.0
12
+ TOTAL_S = 75.0
13
+ MAX_DRAFT = 280
14
+ REASONS = {
15
+ "apple_intelligence_off": "Apple Intelligence is off on this Mac, so you'll write your own replies. Turn it on in "
16
+ "System Settings to get drafts.",
17
+ "device_not_eligible": "This Mac can't run Apple's on-device model, so you'll write your own replies.",
18
+ "model_not_ready": "Apple's on-device model is still downloading, so you'll write your own replies this time.",
19
+ "needs_macos_26": "Drafts need macOS 26 or later, so you'll write your own replies.",
20
+ }
21
+ UNAVAILABLE = "Drafts aren't available on this Mac right now, so you'll write your own replies."
22
+
23
+ # A draft that commits the person: a date, a time, money, a day or month, a yes or no to a request.
24
+ _COMMITS = re.compile(
25
+ r"[$€£¥]|\b\d{1,2}[:.]\d{2}\b|\b\d{1,2}\s?(am|pm)\b|\b\d{1,2}/\d{1,2}\b|\b\d+\s?(dollars|bucks|usd|eur|euros)\b"
26
+ r"|\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|tomorrow|tonight|today|january|february|march|april"
27
+ r"|may \d|june|july|august|september|october|november|december)\b"
28
+ r"|^(yes|yeah|yep|sure|no|nope|absolutely|definitely)\b|\bi('ll| will) (be there|come|pay|send|do it|take it)\b"
29
+ r"|\bcount me in\b|\bi('m| am) in\b", re.I)
30
+
31
+
32
+ def reason_text(reason):
33
+ return REASONS.get(reason, UNAVAILABLE)
34
+
35
+
36
+ def _run_helper():
37
+ """The helper's path, or None when this Mac has none and can't build one."""
38
+ path = native_helpers.find(HELPER)
39
+ if path is not None:
40
+ return str(path)
41
+ try:
42
+ return str(native_helpers.build(HELPER))
43
+ except (LookupError, FileNotFoundError, OSError, subprocess.SubprocessError):
44
+ return None
45
+
46
+
47
+ def status(runner=None):
48
+ """{"available": bool, "reason": str}: whether the on-device model can draft. ``runner`` is for tests."""
49
+ if runner is not None:
50
+ return runner.status()
51
+ helper = _run_helper()
52
+ if helper is None:
53
+ return {"available": False, "reason": "no_helper"}
54
+ try:
55
+ done = subprocess.run([helper, "status"], capture_output=True, text=True, timeout=20)
56
+ data = json.loads((done.stdout or "").strip().splitlines()[-1])
57
+ except (OSError, subprocess.SubprocessError, ValueError, IndexError):
58
+ return {"available": False, "reason": "unavailable"}
59
+ return {"available": bool(data.get("available")), "reason": str(data.get("reason") or "")}
60
+
61
+
62
+ def clean(text):
63
+ """One tidy line of at most 280 characters, or '' when there isn't one."""
64
+ text = " ".join(str(text or "").replace("“", '"').replace("”", '"').split()).strip(' "')
65
+ if len(text) > MAX_DRAFT:
66
+ text = text[:MAX_DRAFT].rsplit(" ", 1)[0].rstrip(" ,;:")
67
+ return text
68
+
69
+
70
+ def acceptable(text):
71
+ """A draft that commits the person to nothing (no date, time, money, yes or no on their behalf)."""
72
+ return bool(text) and not _COMMITS.search(text)
73
+
74
+
75
+ def payload(items):
76
+ """The helper's request for ``items`` (rank.Item): each item's id, situation and last 8 messages."""
77
+ return {"items": [{"id": str(item.chat), "kind": item.bucket,
78
+ "messages": [{"from": "you" if m.mine else "them", "text": m.text} for m in item.context[-8:]]}
79
+ for item in items]}
80
+
81
+
82
+ def draft_all(items, runner=None, per_draft=PER_DRAFT_S, total=TOTAL_S, clock=time.monotonic):
83
+ """{str(chat id): draft} for the items that got one. Never raises; whatever fails is simply missing."""
84
+ if not items:
85
+ return {}
86
+ request = payload(items)
87
+ if runner is not None:
88
+ lines = runner.run(request, per_draft, total)
89
+ else:
90
+ lines = _stream(request, per_draft, total, clock)
91
+ out = {}
92
+ for line in lines:
93
+ if isinstance(line, dict) and line.get("ok") and isinstance(line.get("id"), str):
94
+ text = clean(line.get("draft"))
95
+ if acceptable(text):
96
+ out[line["id"]] = text
97
+ return out
98
+
99
+
100
+ def _stream(request, per_draft, total, clock):
101
+ helper = _run_helper()
102
+ if helper is None:
103
+ return
104
+ deadline = clock() + total
105
+ try:
106
+ process = subprocess.Popen([helper, "run", "--timeout", str(per_draft)], stdin=subprocess.PIPE,
107
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
108
+ except OSError:
109
+ return
110
+ try:
111
+ process.stdin.write(json.dumps(request)) # message text goes on stdin, never on a command line or in a log
112
+ process.stdin.close()
113
+ import selectors
114
+ selector = selectors.DefaultSelector()
115
+ selector.register(process.stdout, selectors.EVENT_READ)
116
+ while clock() < deadline:
117
+ if not selector.select(timeout=1.0):
118
+ if process.poll() is not None:
119
+ break
120
+ continue
121
+ line = process.stdout.readline()
122
+ if not line:
123
+ break
124
+ try:
125
+ yield json.loads(line)
126
+ except ValueError:
127
+ continue
128
+ except (OSError, ValueError):
129
+ return
130
+ finally:
131
+ if process.poll() is None:
132
+ process.kill()
133
+ try:
134
+ process.wait(timeout=5)
135
+ except subprocess.SubprocessError:
136
+ pass
@@ -0,0 +1,145 @@
1
+ """The "Who's waiting on me" job: Messages read, ranked and drafted on this Mac, never by a cloud model."""
2
+
3
+ import os
4
+ import sqlite3
5
+ import threading
6
+ import time
7
+ import uuid
8
+
9
+ from .. import result_blocks
10
+ from ..api_errors import APIError
11
+ from . import contacts as contact_book
12
+ from . import drafts, messages, rank
13
+
14
+ BUNDLE = "com.apple.MobileSMS"
15
+ APP = {"id": "messages", "name": "Messages", "bundleId": BUNDLE}
16
+ GOAL = "Who's waiting on me?"
17
+ MAX_LAST = 200
18
+ MISSING = ("Mobster can't find your Messages history on this Mac. Turn on Messages in iCloud in Messages settings, "
19
+ "then try again.")
20
+ DENIED = (
21
+ "Mobster needs Full Disk Access to read your Messages on this Mac. Turn it on in System Settings, then try again.")
22
+ UNREADABLE = (
23
+ "Mobster couldn't read the Messages database on this Mac, so nothing was changed. Update Mobster, then try again.")
24
+ BUSY = "Mobster is already looking through your Messages. Wait for it to finish."
25
+ _lock = threading.Lock()
26
+
27
+
28
+ def ago(seconds):
29
+ """'3 hours ago', '1 day ago', '2 weeks ago'."""
30
+ minutes = max(int(seconds // 60), 0)
31
+ for unit, size in (("week", 7 * 24 * 60), ("day", 24 * 60), ("hour", 60)):
32
+ if minutes >= size:
33
+ count = minutes // size
34
+ return f"{count} {unit}{'' if count == 1 else 's'} ago"
35
+ return "just now"
36
+
37
+
38
+ def _short(text, limit=MAX_LAST):
39
+ text = " ".join((text or "").split())
40
+ return text if len(text) <= limit else text[:limit - 1].rsplit(" ", 1)[0].rstrip(" ,;:") + "…"
41
+
42
+
43
+ def block_for(items, names, written, *, title=None):
44
+ """The review_queue block for ranked ``items``: ``names`` is {chat id: name}, ``written`` {str(chat id): draft}."""
45
+ out = []
46
+ for item in items:
47
+ name = names.get(item.chat)
48
+ when = ago(item.age_s)
49
+ quote = _short(item.last.text)
50
+ if item.bucket == "promised":
51
+ context = f"You said this {when}. They haven't written since."
52
+ quote = f"You wrote: {quote}"
53
+ elif item.bucket == "asked":
54
+ context = f"Asked you {when}"
55
+ else:
56
+ context = f"Wrote {when}"
57
+ row = {"id": f"c{item.chat}", "bucket": item.bucket, "to": name or item.handle,
58
+ "app": APP["name"], "bundleId": BUNDLE, "context": context, "lastMessage": quote,
59
+ "draft": written.get(str(item.chat)) or ""}
60
+ if name:
61
+ row["handle"] = item.handle
62
+ if not row["draft"]:
63
+ # No draft because the on-device model is off or its draft was dropped: the person writes the reply, and
64
+ # Approve takes their typed words (result_blocks.approval_item).
65
+ row["yourWords"] = result_blocks.YOUR_WORDS
66
+ out.append(row)
67
+ buckets = [{"id": bucket, "label": rank.LABELS[bucket]} for bucket in rank.BUCKETS
68
+ if any(item.bucket == bucket for item in items)]
69
+ return result_blocks.review_queue(out, title=title, buckets=buckets, action="send_message", id="waiting")
70
+
71
+
72
+ def _plural(count, one, many):
73
+ return f"{count} {one if count == 1 else many}"
74
+
75
+
76
+ def answer_for(items, drafted, availability):
77
+ """The one line the result says: counts only, no names and no message text."""
78
+ if not items:
79
+ return (
80
+ "Nobody is waiting on you in your Messages. Group chats, short codes and conversations older than 30 days "
81
+ "aren't counted.")
82
+ counts = rank.counts(items)
83
+ parts = []
84
+ if counts["asked"]:
85
+ parts.append(f"{_plural(counts['asked'], 'person asked', 'people asked')} you something")
86
+ if counts["waiting"]:
87
+ parts.append(f"{_plural(counts['waiting'], 'person is', 'people are')} waiting on a reply")
88
+ if counts["promised"]:
89
+ parts.append(f"{_plural(counts['promised'], 'promise', 'promises')} to keep")
90
+ line = ", ".join(parts).capitalize() + "."
91
+ if availability.get("available"):
92
+ line += f" {_plural(drafted, 'draft', 'drafts')} to approve. Nothing was sent."
93
+ else:
94
+ line += " " + drafts.reason_text(availability.get("reason")) + " Nothing was sent."
95
+ return line
96
+
97
+
98
+ def read(now, path=None):
99
+ """Ranked items from chat.db with the errors the app shows in plain words."""
100
+ target = path or messages.DEFAULT_PATH
101
+ if not os.path.exists(target):
102
+ raise APIError(MISSING, 404, "messages_missing")
103
+ if not messages.columns_ok(target):
104
+ # A file we can see but can't open is Full Disk Access; one we open but that lacks the columns is a newer macOS.
105
+ try:
106
+ messages.connect(target).close()
107
+ except sqlite3.Error:
108
+ raise APIError(DENIED, 403, "full_disk_access") from None
109
+ raise APIError(UNREADABLE, 422, "messages_unreadable")
110
+ try:
111
+ chats = messages.read_chats(target, now)
112
+ except sqlite3.Error:
113
+ raise APIError(DENIED, 403, "full_disk_access") from None
114
+ return rank.rank(chats, now)
115
+
116
+
117
+ def run(runtime, *, now=None, path=None, book=None, runner=None, key=None):
118
+ """Make the finished task. Returns the Run. ``runner`` and ``book`` are for tests."""
119
+ if key:
120
+ # A retried start (a lost answer) returns the task the first one made, without reading or drafting again.
121
+ prior = runtime.journal.lookup(key[:128])
122
+ if prior and prior[0] in runtime.runs:
123
+ return runtime.runs[prior[0]]
124
+ if not _lock.acquire(blocking=False):
125
+ raise APIError(BUSY, 409, "waiting_busy")
126
+ try:
127
+ now = time.time() if now is None else now
128
+ items = read(now, path)
129
+ availability = drafts.status(runner)
130
+ written = drafts.draft_all(items, runner=runner) if availability.get("available") else {}
131
+ book = book if book is not None else contact_book.ContactBook.load()
132
+ names = {item.chat: book.name(item.handle) for item in items}
133
+ total = len(items)
134
+ title = f"Who's waiting on you: {_plural(total, 'person', 'people')}"
135
+ blocks = [block_for(items, names, written, title=title)] if items else []
136
+ answer = answer_for(items, len(written), availability)
137
+ summary = {"event": "result", "status": "completed", "outcome": "done", "answer": answer, "reason": None,
138
+ "independently_verified": True, "actions": 0, "decisions": 0, "elapsed_ms": 0, "model": None,
139
+ "proof": [{"quote": "Read your Messages on this Mac. Nothing was sent.", "app": APP["name"]}],
140
+ **({"blocks": {"v": 1, "blocks": blocks}} if blocks else {})}
141
+ app = next((dict(one) for one in runtime.apps_for(None) if one.get("bundleId") == BUNDLE), dict(APP))
142
+ return runtime.compose_result(app=app, goal=GOAL, summary=summary,
143
+ idempotency_key=(key or f"waiting-{uuid.uuid4().hex}")[:128], origin="app")
144
+ finally:
145
+ _lock.release()
@@ -0,0 +1,174 @@
1
+ """A read-only reader of the Messages history on this Mac (``~/Library/Messages/chat.db``)."""
2
+
3
+ import re
4
+ import sqlite3
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from urllib.parse import quote
8
+
9
+ from ..notify_triggers.sources import MAC_EPOCH
10
+
11
+ ONE_TO_ONE = 45 # chat.style
12
+ MAX_TEXT = 600 # characters kept of one message
13
+ DEFAULT_PATH = Path.home() / "Library" / "Messages" / "chat.db"
14
+ ATTACHMENT_TEXT = "[attachment]"
15
+
16
+ NEEDED = {
17
+ "message": {"ROWID", "text", "attributedBody", "handle_id", "is_from_me", "date", "item_type",
18
+ "associated_message_type", "cache_has_attachments"},
19
+ "chat": {"ROWID", "style"},
20
+ "chat_message_join": {"chat_id", "message_id"},
21
+ "chat_handle_join": {"chat_id", "handle_id"},
22
+ "handle": {"ROWID", "id"},
23
+ }
24
+ # SMS fallbacks of a tapback from before iOS 10 and from Android: the reaction as a text.
25
+ _REACTION_TEXT = re.compile(r"^(Loved|Liked|Disliked|Laughed at|Emphasized|Questioned|Reacted \S+ to) [“\"]", re.I)
26
+ _OBJECT_REPLACEMENT = ""
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Message:
31
+ rowid: int
32
+ mine: bool
33
+ when: float # Unix seconds
34
+ text: str
35
+ attachment: bool = False
36
+
37
+
38
+ @dataclass
39
+ class Chat:
40
+ rowid: int
41
+ handle: str # the other person's phone number or email, as Messages has it
42
+ messages: list = field(default_factory=list) # oldest first, real messages only
43
+
44
+
45
+ def apple_to_unix(value):
46
+ """Messages' ``date`` as Unix seconds: nanoseconds (macOS 10.13 on) or seconds since 2001-01-01."""
47
+ value = float(value or 0)
48
+ if abs(value) > 1e11:
49
+ value /= 1e9
50
+ return value + MAC_EPOCH
51
+
52
+
53
+ def unix_to_apple_ns(seconds):
54
+ return int((seconds - MAC_EPOCH) * 1_000_000_000)
55
+
56
+
57
+ def decode_attributed_body(blob):
58
+ """The message's plain string from an ``attributedBody`` blob, or None when there isn't one to find."""
59
+ if not blob:
60
+ return None
61
+ # typedstream: NSString, class bytes, "+", the UTF-8 length (a byte, or 0x81/0x82/0x83 and 2/4/8 LE bytes), text.
62
+ data = bytes(blob)
63
+ start = 0
64
+ while True:
65
+ at = data.find(b"NSString", start)
66
+ if at < 0:
67
+ return None
68
+ start = at + 1
69
+ plus = data.find(b"+", at + 8, at + 8 + 12)
70
+ if plus < 0:
71
+ continue
72
+ cursor = plus + 1
73
+ if cursor >= len(data):
74
+ continue
75
+ first = data[cursor]
76
+ if first == 0x81:
77
+ width, cursor = 2, cursor + 1
78
+ elif first == 0x82:
79
+ width, cursor = 4, cursor + 1
80
+ elif first == 0x83:
81
+ width, cursor = 8, cursor + 1
82
+ elif first < 0x80:
83
+ width = 0
84
+ else:
85
+ continue
86
+ if width:
87
+ if cursor + width > len(data):
88
+ continue
89
+ length = int.from_bytes(data[cursor:cursor + width], "little")
90
+ cursor += width
91
+ else:
92
+ length, cursor = first, cursor + 1
93
+ if length <= 0 or cursor + length > len(data):
94
+ continue
95
+ try:
96
+ text = data[cursor:cursor + length].decode("utf-8")
97
+ except UnicodeDecodeError:
98
+ continue
99
+ return text
100
+ return None
101
+
102
+
103
+ def message_text(text, attributed):
104
+ """What a message says: ``text``, else the string in ``attributedBody``; attachments' placeholders removed."""
105
+ words = text if isinstance(text, str) and text.strip() else decode_attributed_body(attributed)
106
+ return (words or "").replace(_OBJECT_REPLACEMENT, "").strip()
107
+
108
+
109
+ def connect(path=None):
110
+ """The database, read-only. ``sqlite3.OperationalError`` when it can't be opened (no file, no access)."""
111
+ target = Path(path) if path else DEFAULT_PATH
112
+ connection = sqlite3.connect(f"file:{quote(str(target))}?mode=ro", uri=True, timeout=5)
113
+ connection.execute("PRAGMA query_only = ON")
114
+ return connection
115
+
116
+
117
+ def columns_ok(path=None):
118
+ """Whether chat.db has the tables and columns this reader reads (``PRAGMA table_info`` only)."""
119
+ try:
120
+ connection = connect(path)
121
+ except sqlite3.Error:
122
+ return False
123
+ try:
124
+ for table, needed in NEEDED.items():
125
+ have = {row[1] for row in connection.execute(f"PRAGMA table_info({table})")}
126
+ if not needed <= have:
127
+ return False
128
+ return True
129
+ except sqlite3.Error:
130
+ return False
131
+ finally:
132
+ connection.close()
133
+
134
+
135
+ def read_chats(path, now, days=30, per_chat=40, limit_chats=400):
136
+ """Every one-to-one chat with a real message in the last ``days`` days"""
137
+ since = unix_to_apple_ns(now - days * 86400)
138
+ connection = connect(path)
139
+ try:
140
+ # Newest activity first; a chat with one handle only (a one-to-one chat has exactly one).
141
+ chats = connection.execute(
142
+ "SELECT c.ROWID, MIN(h.id), COUNT(h.ROWID) "
143
+ "FROM chat c JOIN chat_handle_join j ON j.chat_id = c.ROWID JOIN handle h ON h.ROWID = j.handle_id "
144
+ "WHERE c.style = ? AND EXISTS (SELECT 1 FROM chat_message_join m WHERE m.chat_id = c.ROWID) "
145
+ "GROUP BY c.ROWID HAVING COUNT(h.ROWID) = 1", (ONE_TO_ONE,)).fetchall()
146
+ recent = {row[0]: row[1] for row in connection.execute(
147
+ "SELECT j.chat_id, MAX(m.date) FROM chat_message_join j JOIN message m ON m.ROWID = j.message_id "
148
+ "WHERE m.date >= ? GROUP BY j.chat_id", (since,))}
149
+ wanted = sorted((row for row in chats if row[0] in recent), key=lambda row: -recent[row[0]])[:limit_chats]
150
+ out = []
151
+ for rowid, handle, _count in wanted:
152
+ rows = connection.execute(
153
+ "SELECT m.ROWID, m.text, m.attributedBody, m.is_from_me, m.date, m.cache_has_attachments "
154
+ "FROM chat_message_join j JOIN message m ON m.ROWID = j.message_id "
155
+ "WHERE j.chat_id = ? AND m.date >= ? AND COALESCE(m.item_type, 0) = 0 "
156
+ "AND COALESCE(m.associated_message_type, 0) = 0 "
157
+ "ORDER BY m.date DESC, m.ROWID DESC LIMIT ?", (rowid, since, per_chat * 2)).fetchall()
158
+ messages = []
159
+ for mid, text, body, mine, date, attached in rows:
160
+ words = message_text(text, body)
161
+ if words and _REACTION_TEXT.match(words):
162
+ continue
163
+ if not words:
164
+ if not attached:
165
+ continue
166
+ words = ATTACHMENT_TEXT
167
+ messages.append(Message(mid, bool(mine), apple_to_unix(date), words[:MAX_TEXT], bool(attached)))
168
+ if len(messages) >= per_chat:
169
+ break
170
+ if messages:
171
+ out.append(Chat(rowid, handle, list(reversed(messages))))
172
+ return out
173
+ finally:
174
+ connection.close()
@@ -0,0 +1,115 @@
1
+ """Who is waiting on you: deterministic rules over the chats ``messages.read_chats`` returns."""
2
+
3
+ import re
4
+ from dataclasses import dataclass, field
5
+
6
+ WINDOW_DAYS = 30
7
+ MIN_AGE_S = 2 * 3600
8
+ MAX_ITEMS = 25
9
+ CONTEXT = 8
10
+ BUCKETS = ("asked", "waiting", "promised")
11
+ LABELS = {"asked": "Asked you something", "waiting": "Waiting on you", "promised": "You said you would"}
12
+
13
+ _ASK = re.compile(
14
+ r"\?|\b(can|could|would|will) (you|u)\b|\b(did|do|are|have|were|was|is) (you|u)\b|\blet me know\b|\blmk\b"
15
+ r"|\bcall me\b|\btext me\b|\bget back to me\b|\bany update\b|\bwhen (can|are|will|do) (you|u)\b"
16
+ r"|\bplease (send|call|text|let|reply|confirm|check)\b|\bwhat do you think\b|\bwhat time\b", re.I)
17
+ _PROMISE = re.compile(r"\b(i'll|i will|ill|let me|i can send|i'm going to|im gonna|i am going to)\b|\btomorrow\b", re.I)
18
+ _NOT_A_PROMISE = re.compile(r"\blet me know\b", re.I)
19
+ _CLOSER_WORDS = frozenset((
20
+ "ok okay k kk thanks thank you thx ty tysm np yw lol lmao haha hahaha hehe ha cool nice great awesome perfect "
21
+ "sounds "
22
+ "good got it yep yup yes yeah sure bet will do right same true word love u so much see ya later bye night "
23
+ "goodnight "
24
+ "gn lovely amazing wonderful appreciate appreciated all fine alright sweet noted").split())
25
+
26
+
27
+ def clean(text):
28
+ return (text or "").replace("’", "'").replace("‘", "'").strip()
29
+
30
+
31
+ def is_closer(text):
32
+ """Whether a message ends a conversation instead of asking for a reply: "ok", "thanks!", "lol", "👍", "😂😂"."""
33
+ text = clean(text)
34
+ if not text:
35
+ return True
36
+ if "?" in text:
37
+ return False
38
+ if not re.search(r"[^\W_]", text): # no letter or digit: an emoji, "!!!", "..."
39
+ return True
40
+ words = re.findall(r"[^\W_]+(?:'[^\W_]+)?", text.lower())
41
+ return 0 < len(words) <= 5 and all(word in _CLOSER_WORDS for word in words)
42
+
43
+
44
+ def asks(text):
45
+ return bool(_ASK.search(clean(text)))
46
+
47
+
48
+ def promises(text):
49
+ """Whether the message promises something ("I'll send it tomorrow", "let me check"). "Let me know" doesn't."""
50
+ return bool(_PROMISE.search(_NOT_A_PROMISE.sub(" ", clean(text))))
51
+
52
+
53
+ def is_person_handle(handle):
54
+ """A phone number (7 or more digits) or an email address that isn't a no-reply sender."""
55
+ handle = (handle or "").strip()
56
+ if not handle:
57
+ return False
58
+ lowered = handle.lower()
59
+ if re.search(r"no[-_.]?reply|do[-_.]?not[-_.]?reply|donotreply|mailer-daemon|notifications?@", lowered):
60
+ return False
61
+ if "@" in handle:
62
+ return bool(re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", handle))
63
+ if re.fullmatch(r"\+?[\d\s().-]+", handle):
64
+ return len(re.sub(r"\D", "", handle)) >= 7
65
+ return False
66
+
67
+
68
+ @dataclass
69
+ class Item:
70
+ chat: int
71
+ handle: str
72
+ bucket: str
73
+ last: object # the Message the item is about (theirs; yours for "promised")
74
+ age_s: float
75
+ context: list = field(default_factory=list) # up to 8 newest messages, oldest first
76
+
77
+
78
+ def classify(chat, now):
79
+ """The Item for one chat, or None."""
80
+ if not is_person_handle(chat.handle) or not chat.messages:
81
+ return None
82
+ messages = sorted(chat.messages, key=lambda m: (m.when, m.rowid))
83
+ horizon = now - WINDOW_DAYS * 86400
84
+ last = messages[-1]
85
+ if not last.mine:
86
+ age = now - last.when
87
+ if last.when < horizon or age < MIN_AGE_S or is_closer(last.text):
88
+ return None
89
+ bucket = "asked" if asks(last.text) else "waiting"
90
+ return Item(chat.rowid, chat.handle, bucket, last, age, messages[-CONTEXT:])
91
+ # The last message is yours: the newest promise of yours that they haven't answered, unless you sent a file after.
92
+ for index in range(len(messages) - 1, -1, -1):
93
+ message = messages[index]
94
+ if not message.mine:
95
+ return None
96
+ if message.when < horizon:
97
+ return None
98
+ if promises(message.text) and not message.attachment:
99
+ age = now - message.when
100
+ if age < MIN_AGE_S or any(later.attachment for later in messages[index + 1:]):
101
+ return None
102
+ return Item(chat.rowid, chat.handle, "promised", message, age,
103
+ messages[max(0, index - CONTEXT + 1):index + 1])
104
+ return None
105
+
106
+
107
+ def rank(chats, now, limit=MAX_ITEMS):
108
+ """The items for ``chats``: by bucket, then the longest wait first, at most ``limit``."""
109
+ items = [item for item in (classify(chat, now) for chat in chats) if item is not None]
110
+ items.sort(key=lambda item: (BUCKETS.index(item.bucket), -item.age_s, item.chat))
111
+ return items[:limit]
112
+
113
+
114
+ def counts(items):
115
+ return {bucket: sum(1 for item in items if item.bucket == bucket) for bucket in BUCKETS}
@@ -0,0 +1,26 @@
1
+ """Track ``waiting``'s registrations: no agent or MCP tool, since its result would carry message text to a model."""
2
+
3
+ from ..api_errors import APIError
4
+ from ..api_routes import Response, Route
5
+ from ..api_routes import register as add_route
6
+ from . import job
7
+
8
+ APP_ONLY = "Only the Mobster app can read your Messages. Open Mobster and ask there."
9
+
10
+
11
+ def start(request):
12
+ """POST /api/waiting: {"key"?} -> 201 {"runId"}. The Mac app only (its session), like Settings routes."""
13
+ # Fail closed: the result holds message text, so only the Mac app's own session starts it (not an API token alone).
14
+ if not request.app_session:
15
+ raise APIError(APP_ONLY, 403, "app_only")
16
+ body = request.body if isinstance(request.body, dict) else {}
17
+ key = body.get("key")
18
+ if key is not None and (not isinstance(key, str) or not 1 <= len(key) <= 100):
19
+ raise ValueError("key is 1 to 100 characters")
20
+ run = job.run(request.runtime, key=f"waiting-{key}" if key else None)
21
+ return Response.json({"runId": run.id}, 201)
22
+
23
+
24
+ def register(api):
25
+ """``api`` is mobile_agent.harness_api."""
26
+ add_route(Route("waiting", "POST", "/api/waiting", start))