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,47 @@
1
+ """Pack pins: a team can pin each app pack to the app version it was checked on (``Verified.version``, the version
2
+ ``GET /api/packs`` shows), or turn one off. A Mac with a pin uses a pack only when this build's pack matches it; any
3
+ other pack steps aside, and the general agent does the step, as it does for a pack that is stale. A pin never makes a
4
+ pack do more: packs only move around and read, so a pin is about every Mac in a team doing the same thing, not about
5
+ safety. The team track (teams/control_plane.py) sets the pins it read; with none set, every pack works as shipped.
6
+ """
7
+
8
+ OFF = "off"
9
+ _pins = {} # pack key -> OFF, or the app version the team pinned the pack to
10
+
11
+
12
+ def set_pins(pins):
13
+ """Replace the pins with ``pins`` ({pack key: OFF or a version}); {} clears them."""
14
+ _pins.clear()
15
+ _pins.update(
16
+ {key: value for key, value in dict(pins or {}).items() if isinstance(key, str) and isinstance(value, str)}
17
+ )
18
+
19
+
20
+ def current():
21
+ return dict(_pins)
22
+
23
+
24
+ def aside(pack):
25
+ """Why this Mac's ``pack`` steps aside under the team's pin, in a sentence, or None when it may be used."""
26
+ pin = _pins.get(pack.key)
27
+ if pin is None:
28
+ return None
29
+ if pin == OFF:
30
+ return f"Your team turned off the {pack.name} pack."
31
+ built = pack.verified.version if pack.verified else None
32
+ if built == pin:
33
+ return None
34
+ on = f"the pack checked on {pack.name} {built}" if built else "a pack with no checked version"
35
+ return (
36
+ f"Your team pinned the {pack.name} pack to {pack.name} {pin}, and this Mobster has {on}. Update Mobster, "
37
+ "or ask an Admin to change the pin."
38
+ )
39
+
40
+
41
+ def state(pack):
42
+ """What this Mac reports about ``pack`` to its team: the version it has and whether it is stepping aside."""
43
+ return {
44
+ "key": pack.key,
45
+ "version": pack.verified.version if pack.verified else None,
46
+ "aside": aside(pack) is not None,
47
+ }
@@ -0,0 +1,110 @@
1
+ """The Reddit pack: your home feed, a post and its comments, communities, chat, your inbox and search."""
2
+
3
+ import re
4
+
5
+ from . import core, rows, social
6
+ from .core import PickRow, Reader, Route, ScreenSpec, Tap, TypeIn, Verified, sig
7
+
8
+ BUNDLE = "com.reddit.Reddit"
9
+ TAB = (.86, 1.0)
10
+ TOP = (0.0, .2)
11
+ TAB_LABEL = r"^(Home|Communities|Answers|Create|Chat|Inbox)\b"
12
+ COLUMNS = (("place", "Community", "text"), ("handle", "Author", "text"), ("text", "Post", "text"),
13
+ ("when", "When", "text"), ("score", "Score", "number"), ("comments", "Comments", "number"),
14
+ ("ad", "Ad", "text"))
15
+ COMMENT_COLUMNS = (("handle", "Author", "text"), ("text", "Comment", "text"), ("when", "When", "text"),
16
+ ("score", "Score", "number"))
17
+
18
+
19
+ tab = rows.tab_tap(TAB)
20
+
21
+
22
+ class Reddit(core.Pack):
23
+ name, key, prefix, bundle = "Reddit", "reddit", "RD", BUNDLE
24
+ verified = Verified("2025.44.0", "2026-10-14", "inferred from Reddit's usual labels; no recorded tree yet")
25
+ tabs = sig(TAB_LABEL, "Button", region=TAB)
26
+ never = re.compile(r"^(upvote|downvote|join|leave|award|give award|share|crosspost|save|hide|report|block|"
27
+ r"reply|post|send|follow)\b", re.I)
28
+ mentions = re.compile(r"\b(reddit|subreddits?|r/[A-Za-z0-9_]+|upvotes?)\b", re.I)
29
+ sites = ("www.reddit.com", "reddit.com", "old.reddit.com", "redd.it")
30
+
31
+ screens = (
32
+ ScreenSpec("home", "your Reddit home feed", (
33
+ sig(r"^Home$", "Button", region=TAB, selected=True, must=True, weight=2),
34
+ sig(r"\br/[A-Za-z0-9_]{2,}", "Cell", weight=2), sig(r"\b\d[\d,.]*[KM]? (upvotes?|comments?|votes?)\b")),
35
+ ocr=(sig(r"\br/[A-Za-z0-9_]{2,}", weight=2),
36
+ sig(r"\b\d[\d,.]*[KM]?\s*(upvotes?|comments?|votes?)\b|^\d[\d.,]*[KM]?$"),
37
+ sig(r"^(Home|Popular|Latest)$", region=TOP), sig(r"^(Join|Joined)$", never=True))),
38
+ ScreenSpec("community", "a community", (
39
+ sig(r"^r/[A-Za-z0-9_]{2,}$", region=TOP, must=True, weight=2), sig(r"^(Join|Joined)$", "Button"),
40
+ sig(r"\b(members|online)\b")),
41
+ ocr=(sig(r"^r/[A-Za-z0-9_]{2,}$", region=TOP, must=True, weight=2), sig(r"^(Join|Joined)$"),
42
+ sig(r"\b(members|online)\b"))),
43
+ ScreenSpec("post", "a post and its comments", (
44
+ sig(r"^(Add a comment|Join the conversation)", must=True, weight=2), sig(r"^Back$", "Button", region=TOP),
45
+ sig(r"^Sort by|^(Best|Top|New) comments")),
46
+ ocr=(sig(r"^(Add a comment|Join the conversation)", must=True, weight=2),
47
+ sig(r"^Back$", "Button", region=TOP), sig(r"^Sort by|^(Best|Top|New) comments"))),
48
+ ScreenSpec("chat", "your chats", (
49
+ sig(r"^Chat$", "Button", region=TAB, selected=True, must=True, weight=2),
50
+ sig(r"^(Threads|Requests|Start chat)\b")),
51
+ ocr=(sig(r"^Chat$", region=TOP, must=True, weight=2), sig(r"^(Threads|Requests|Start chat)\b"))),
52
+ ScreenSpec("inbox", "your inbox", (
53
+ sig(r"^Inbox$", "Button", region=TAB, selected=True, must=True, weight=2),
54
+ sig(r"^(Notifications|Messages)$", region=TOP)),
55
+ ocr=(sig(r"^Inbox$", region=TOP, must=True, weight=2),
56
+ sig(r"^(Notifications|Messages)$", region=(0, .25)))),
57
+ ScreenSpec("search", "search results", (
58
+ sig(r"^Search", "SearchField", must=True),
59
+ sig(r"^(Posts|Communities|Comments|Media|People)$", region=(0, .3), weight=2)),
60
+ ocr=(sig(r"^Search", "SearchField", must=True),
61
+ sig(r"^(Posts|Communities|Comments|Media|People)$", region=(0, .3), weight=2))),
62
+ )
63
+ routes = {
64
+ "home": Route("home", "your Reddit home feed", ("home",), (tab(r"^Home$", "the Home tab"),)),
65
+ "chat": Route("chat", "your chats", ("chat",), (tab(r"^Chat$", "the Chat tab"),)),
66
+ "inbox": Route("inbox", "your inbox", ("inbox",), (tab(r"^Inbox$", "the Inbox tab"),)),
67
+ "thread": Route("thread", "a chat", ("chat",), (tab(r"^Chat$", "the Chat tab"),
68
+ PickRow("chat", roles=("Cell", "Button"))), arg="name"),
69
+ "community": Route("community", "a community", ("community",), (),
70
+ link="https://www.reddit.com/r/{arg}", arg="name", from_tabs=False),
71
+ "user": Route("user", "a profile", ("community", "home"), (), link="https://www.reddit.com/user/{arg}",
72
+ arg="user", from_tabs=False),
73
+ "search": Route("search", "search results", ("search",),
74
+ (tab(r"^Home$", "the Home tab"), Tap(r"^Search", "Search", region=TOP),
75
+ TypeIn(r"^Search", "the search field", submit=True)), arg="query"),
76
+ "post": Route("post", "the post", ("post",), (), link="{arg}", arg="url", from_tabs=False),
77
+ "comments": Route("comments", "the comments", ("post",), (), from_tabs=False),
78
+ }
79
+ hidden_routes = ("user", "comments")
80
+ open_aliases = {"feed": "home", "subreddit": "community", "r": "community", "dms": "chat", "messages": "inbox",
81
+ "link": "post", "chats": "chat"}
82
+ read_aliases = {"feed": "home", "posts": "home", "subreddit": "community", "comment": "comments",
83
+ "dms": "chat", "chats": "chat"}
84
+ read_default = {"home": 10, "community": 10, "comments": 20, "search": 10, "chat": 15}
85
+ read_titles = {"home": "posts", "community": "posts", "comments": "comments", "search": "results",
86
+ "chat": "chats"}
87
+ nouns = {"home": "post", "community": "post", "comments": "comment", "search": "result", "chat": "chat"}
88
+ prompts = {
89
+ "OPEN": ("- RD_OPEN where: go to a Reddit screen by code, no taps from you: home | community <name> | chat "
90
+ "| inbox | thread <name> | search <words> | post <link>. If it can't, go on the usual way. It never "
91
+ "votes, joins, comments or posts: those are your taps, and the person approves them."),
92
+ "READ": ("- RD_READ what [n]: read Reddit by code from the screen's tree: home n | community n (the one "
93
+ "open) | comments n (on the post open) | search n | chat n. Gives community, author, title, time, "
94
+ "score and comments. Opens the screen it needs."),
95
+ }
96
+ mcp_open_hint = "your home feed, a community, chats, your inbox, search, a post's link"
97
+ mcp_read_hint = "your home feed, a community, a post's comments, search results or your chats"
98
+
99
+ @property
100
+ def readers(self):
101
+ not_a_chat = re.compile(r"^(threads|requests|start chat|new chat|search)\b", re.I)
102
+ return {"home": Reader(social.feed_reader("home", "posts", COLUMNS), ("home",), "home"),
103
+ "community": Reader(social.feed_reader("community", "posts", COLUMNS), ("community",), None),
104
+ "comments": Reader(social.feed_reader("comments", "comments", COMMENT_COLUMNS, roles=("Cell", "Other")),
105
+ ("post",), None),
106
+ "search": Reader(social.feed_reader("search", "results", COLUMNS), ("search",), None),
107
+ "chat": Reader(social.dm_reader(not_a_chat), ("chat",), "chat")}
108
+
109
+
110
+ PACK = Reddit()
@@ -0,0 +1,276 @@
1
+ """Track ``packs``'s registrations (tracks.load calls ``register(api)`` once per process, staged)."""
2
+
3
+ import functools
4
+ import time as _time
5
+
6
+ from . import pins
7
+
8
+ TOOL_ORDER = 4
9
+ CONTEXT_ORDER = 40
10
+ SLEEP = _time.sleep # the health run's pause after a tap (tests set it to nothing)
11
+ # The packs Mobster ships, by module: each module has PACK, an instance of core.Pack.
12
+ MODULES = ("instagram", "tiktok", "messages", "settings", "calendar", "notes", "maps", "photos", "safari",
13
+ "x", "youtube", "threads", "linkedin", "reddit")
14
+ _extra = []
15
+
16
+
17
+ def all_packs():
18
+ """Every pack this build has, in MODULES order, then any a test added (``add_for_tests``)."""
19
+ import importlib
20
+ out = []
21
+ for name in MODULES:
22
+ try:
23
+ module = importlib.import_module(f"{__package__}.{name}")
24
+ except ModuleNotFoundError as error:
25
+ if error.name != f"{__package__}.{name}":
26
+ raise
27
+ continue
28
+ out.append(module.PACK)
29
+ return out + list(_extra)
30
+
31
+
32
+ def add_for_tests(pack):
33
+ _extra.append(pack)
34
+ return lambda: _extra.remove(pack)
35
+
36
+
37
+ def status_route(request):
38
+ """GET /api/packs: {packs: [health.status ...]}."""
39
+ from pathlib import Path
40
+
41
+ from ..api_routes import Response
42
+ from . import core, health
43
+ runtime = request.runtime
44
+ state_db = getattr(getattr(runtime, "config", None), "state_db", None)
45
+ entries = health.recent(Path(state_db).parent if state_db else None)
46
+ return Response.json({"packs": [health.status(pack, core.app_version(runtime, pack.bundle), entries)
47
+ for pack in all_packs()]})
48
+
49
+
50
+ class Confirmer:
51
+ def on_finished(self, run, summary, private):
52
+ if (summary or {}).get("status") == "completed":
53
+ from . import heal
54
+ heal.confirm_run(getattr(run, "id", None))
55
+
56
+
57
+ def register(api):
58
+ """``api`` is mobile_agent.harness_api."""
59
+ from .. import api_routes
60
+ from ..mcp_server import registry as mcp_registry
61
+ from . import context, mcp, tools
62
+ packs = all_packs()
63
+ if packs:
64
+ # What a pack read stays in front of the model on later turns (context.py).
65
+ api.register_context_provider(context.factory(packs), order=CONTEXT_ORDER)
66
+ for pack in packs:
67
+ api.register_tool(tools.factory(pack, "OPEN"), order=TOOL_ORDER)
68
+ api.register_tool(tools.factory(pack, "READ"), order=TOOL_ORDER)
69
+ if packs:
70
+ mcp_registry.register_provider(mcp.PacksProvider(packs))
71
+ # A run that finished completed confirms the variants proposed from screens a pack missed in it (heal.py).
72
+ api.register_run_listener(Confirmer())
73
+ api_routes.register(api_routes.Route("packs", "GET", "/api/packs", status_route, body="none"))
74
+ if packs:
75
+ # The watch: a read-only health run per app, as a task with packCheck (a schedule can run it).
76
+ api.register_run_field("packCheck", functools.partial(validate_pack_key, "packCheck"))
77
+ api.register_pre_run(pack_check, order=3)
78
+ # The numbers: a read-only run per app that stores today's profile and Insights numbers (accounts/stats.py).
79
+ api.register_run_field("packStats", functools.partial(validate_pack_key, "packStats"))
80
+ api.register_pre_run(pack_stats, order=3)
81
+ # A workflow's "Read an app" step (graph app.read): one reader's rows, by code, no model.
82
+ api.register_run_field("packRead", validate_pack_read)
83
+ api.register_pre_run(pack_read, order=3)
84
+ # The front door: a request that is exactly a pack reader's question or a Settings page is answered by code,
85
+ # before any model call (front_door.py).
86
+ from . import front_door
87
+ api.register_pre_run(front_door.hook, order=front_door.ORDER)
88
+
89
+
90
+ def validate_pack_key(field, value, runtime):
91
+ """A pack run field (``packCheck``, ``packStats``): a pack's key; the run is that pack's read-only run."""
92
+ if not isinstance(value, str) or value not in {pack.key for pack in all_packs()}:
93
+ raise ValueError(f"{field} is the key of an app pack (GET /api/packs)")
94
+ return value
95
+
96
+
97
+ def validate_pack_read(value, runtime):
98
+ """``packRead``: {"pack", "reader", "n", "arg"}, one of a pack's readers; the run is that read, with no model."""
99
+ if not isinstance(value, dict) or set(value) - {"pack", "reader", "n", "arg"}:
100
+ raise ValueError("packRead is {pack, reader, n, arg}")
101
+ pack = next((p for p in all_packs() if p.key == value.get("pack")), None)
102
+ if pack is None or value.get("reader") not in pack.readers:
103
+ raise ValueError("packRead names an app pack and one of its readers (GET /api/packs)")
104
+ n = value.get("n", 10)
105
+ if type(n) is not int or not 1 <= n <= 50:
106
+ raise ValueError("packRead reads 1 to 50 rows")
107
+ arg = value.get("arg")
108
+ if arg is not None and (not isinstance(arg, str) or not 1 <= len(arg.strip()) <= 200):
109
+ raise ValueError("packRead's arg is 1 to 200 characters")
110
+ return {"pack": pack.key, "reader": value["reader"], "n": n, "arg": arg.strip() if arg else None}
111
+
112
+
113
+ def stepping_aside(hook):
114
+ """A pack pre-run hook for a run that names its pack (packRead, packCheck, packStats): when the team's pin has the
115
+ pack step aside, the run ends saying so, and never goes on to a model that nobody asked for."""
116
+ @functools.wraps(hook)
117
+ def wrapped(ctx, driver, approve):
118
+ from .. import harness_api
119
+ from . import core
120
+ try:
121
+ return hook(ctx, driver, approve)
122
+ except core.PinnedAside as aside:
123
+ return harness_api.PreRun(result={
124
+ "event": "result", "status": "completion_not_confirmed", "engine": "smart", "outcome": "check",
125
+ "answer": None, "reason": str(aside), "actions": 0, "decisions": 0, "costUsd": 0.0, "model": None,
126
+ "elapsed_ms": 0, "independently_verified": False, "proof": []})
127
+ return wrapped
128
+
129
+
130
+ @stepping_aside
131
+ def pack_read(ctx, driver, approve):
132
+ """Pre-run hook: a run with ``packRead`` reads that reader's rows on the phone by code and ends with them as its
133
+ data and a table."""
134
+ spec = (ctx.extras or {}).get("packRead")
135
+ if not spec or driver is None:
136
+ return None
137
+ import time
138
+
139
+ from .. import harness_api
140
+ from .. import result_blocks as rb
141
+ from . import core
142
+ pack = next(p for p in all_packs() if p.key == spec["pack"])
143
+ io = _io(ctx, driver, pack) # a pack the team's pin has step aside ends the run here, before anything acts
144
+ # An acting event, journaled before the first tap: a crash mid-run is never read as a run that didn't act.
145
+ ctx.emit({"event": "skill_started", "op": f"{pack.prefix}_READ"})
146
+ started = time.monotonic()
147
+
148
+ def ended(status, outcome, answer, reason=None, data=None, blocks=()):
149
+ return harness_api.PreRun(result={
150
+ "event": "result", "status": status, "engine": "smart", "outcome": outcome, "answer": answer,
151
+ "reason": reason, "data": data, "actions": 1, "decisions": 0, "costUsd": 0.0, "model": None,
152
+ "elapsed_ms": round((time.monotonic() - started) * 1000), "blocks": rb.envelope(list(blocks)),
153
+ "independently_verified": outcome == "done", "proof": []})
154
+ try:
155
+ result = pack.read(io, spec["reader"], spec["n"], spec.get("arg"))
156
+ except core.Lost as lost:
157
+ said = str(lost).rstrip(".")
158
+ return ended("completion_not_confirmed", "check", None,
159
+ f"Mobster couldn't read {pack.name}: {said}. Check the phone shows {pack.name}, "
160
+ "then run it again.")
161
+ scalars = (str, int, float, bool, type(None))
162
+ rows = [{k: v for k, v in row.items() if isinstance(v, scalars)} for row in result.rows]
163
+ columns = [{"key": k, "label": label, "type": kind} for k, label, kind in result.columns]
164
+ data = {"rows": rows, "columns": columns, "title": result.title, "stopped": result.stopped or None}
165
+ table = rb.table(columns, rows, title=result.title, id=f"{pack.key}-{spec['reader']}") if rows else None
166
+ return ended("completed", "done", f"{result.title}: {len(rows)} {'row' if len(rows) == 1 else 'rows'}.",
167
+ data=data, blocks=[table] if table else ())
168
+
169
+
170
+ def _io(ctx, driver, pack):
171
+ from . import core
172
+ pinned = pins.aside(pack)
173
+ if pinned:
174
+ raise core.PinnedAside(pinned)
175
+ return core.PackIO(driver, pack, cancelled=ctx.cancelled, sleep=SLEEP,
176
+ allows_app=lambda bundle: ctx.runtime is None or getattr(ctx.runtime, "allows_app",
177
+ lambda *a: True)(ctx.run_id, bundle))
178
+
179
+
180
+ def pack_run(ctx, driver, field, op):
181
+ """What both pack pre-run hooks start from: ``(pack, io, started)`` for the run's ``field``, or None when the run
182
+ doesn't carry it or has no phone."""
183
+ key = (ctx.extras or {}).get(field)
184
+ if not key or driver is None:
185
+ return None
186
+ import time
187
+ pack = next(p for p in all_packs() if p.key == key)
188
+ io = _io(ctx, driver, pack) # a pack the team's pin has step aside ends the run here, before anything acts
189
+ # An acting event, journaled before the first tap: a crash mid-run is never read as a run that didn't act.
190
+ ctx.emit({"event": "skill_started", "op": f"{pack.prefix}_{op}"})
191
+ return pack, io, time.monotonic()
192
+
193
+
194
+ @stepping_aside
195
+ def pack_check(ctx, driver, approve):
196
+ """Pre-run hook: a run with ``packCheck`` visits the pack's tab screens on the phone, records each in the health
197
+ log,
198
+ and ends with a checklist."""
199
+ run = pack_run(ctx, driver, "packCheck", "CHECK")
200
+ if run is None:
201
+ return None
202
+ import time
203
+
204
+ from . import core, health
205
+ pack, io, started = run
206
+ rows = health.check(pack, io)
207
+ version = core.app_version(ctx.runtime, pack.bundle)
208
+ for row in rows:
209
+ health.record(ctx.data_dir, {"pack": pack.key, "op": "CHECK", "step": row["route"], "ok": row["landed"],
210
+ "screen": row["screen"], "confidence": row["confidence"], "version": version,
211
+ **({"unsure_screen": pack.routes[row["route"]].lands[0]}
212
+ if not row["landed"] and not row["screen"] else {})})
213
+ ctx.emit({"event": "app_pack_check", "pack": pack.key, "routes": len(rows),
214
+ "landed": sum(1 for row in rows if row["landed"]), "version": version})
215
+ from .. import harness_api
216
+ return harness_api.PreRun(result=health.check_result(pack, rows, version, time.monotonic() - started))
217
+
218
+
219
+ @stepping_aside
220
+ def pack_stats(ctx, driver, approve):
221
+ """Pre-run hook: a run with ``packStats`` reads the signed-in profile's numbers and its Insights overview and stores
222
+ them as today's row (accounts/stats.py), no model."""
223
+ run = pack_run(ctx, driver, "packStats", "STATS")
224
+ if run is None:
225
+ return None
226
+ import time
227
+
228
+ from .. import harness_api
229
+ from .. import result_blocks as rb
230
+ from ..accounts import stats
231
+ from . import core
232
+ pack, io, started = run
233
+ account = (ctx.extras or {}).get("account")
234
+ wanted = account.get("handle") if isinstance(account, dict) else None
235
+
236
+ def done(answer, rows=(), ok=True):
237
+ blocks = [rb.table([{"label": label, "key": k, "type": kind} for k, label, kind in pack_columns],
238
+ list(rows), title=f"{pack.name} numbers", id=f"{pack.key}-stats")] if rows else []
239
+ return harness_api.PreRun(result={
240
+ "event": "result", "status": "completed", "engine": "smart", "outcome": "done" if ok else "check",
241
+ "answer": answer, "actions": 1, "decisions": 0, "elapsed_ms": round((time.monotonic() - started) * 1000),
242
+ "costUsd": 0.0, "model": None, "blocks": rb.envelope(blocks)})
243
+
244
+ pack_columns = (("handle", "Profile", "text"), ("followers", "Followers", "number"),
245
+ ("following", "Following", "number"), ("posts", "Posts", "number"), ("likes", "Likes", "number"),
246
+ ("period", "Period", "text"), ("views", "Views", "number"), ("reach", "Reached", "number"),
247
+ ("profile_visits", "Profile visits", "number"), ("follower_change", "Follower change", "number"))
248
+ try:
249
+ profile = pack.read(io, "profile", 1, None)
250
+ mine = profile.rows[0] if profile.rows else {}
251
+ handle = mine.get("handle")
252
+ if mine.get("state") != "yours" or not handle:
253
+ return done(f"Mobster didn't store {pack.name} numbers: the Profile tab doesn't show your own profile. "
254
+ "Open your profile in the app, then run this again.", ok=False)
255
+ if wanted and wanted.lstrip("@").casefold() != handle.lstrip("@").casefold():
256
+ return done(f"Mobster didn't store {pack.name} numbers: the app shows @{handle}, "
257
+ f"not @{wanted.lstrip('@')}. "
258
+ "Switch to that account, then run this again.", ok=False)
259
+ values = {k: mine.get(k) for k in ("followers", "following", "posts", "likes")}
260
+ try:
261
+ overview = pack.read(io, "insights", 1, None).rows[0]
262
+ values.update({k: overview.get(k) for k in ("period", "views", "reach", "profile_visits",
263
+ "follower_change")})
264
+ note = ""
265
+ except core.Lost as lost:
266
+ note = f" Insights weren't read: {str(lost).rstrip('.')}."
267
+ folder = ctx.data_dir
268
+ if ctx.runtime is not None:
269
+ from ..accounts.store import store_for
270
+ folder = store_for(ctx.runtime).data_dir
271
+ stats.record(folder, pack.key, handle, values)
272
+ except core.Lost as lost:
273
+ return done(f"Mobster couldn't read {pack.name}: {str(lost).rstrip('.')}. Check the phone shows "
274
+ f"{pack.name}, then run this again.", ok=False)
275
+ ctx.emit({"event": "app_pack_stats", "pack": pack.key})
276
+ return done(f"Stored @{handle}'s {pack.name} numbers for today.{note}", [{"handle": handle, **values}])
@@ -0,0 +1,108 @@
1
+ """Rows read from list screens, the same way on an accessibility tree and on a picture (image-only connections)."""
2
+
3
+ import re
4
+
5
+ from . import core
6
+
7
+ # A time or a date as list rows show them: "9:55 PM", "Yesterday", "Monday", "10/1/26", "Oct 3", "2h", "3 days ago".
8
+ WHEN = re.compile(r"^(\d{1,2}:\d{2}\s?([AaPp]\.?[Mm]\.?)?|yesterday|today|now|just now|"
9
+ r"(mon|tues|wednes|thurs|fri|satur|sun)day|\d{1,2}/\d{1,2}(/\d{2,4})?|"
10
+ r"(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.? \d{1,2}(, \d{4})?|"
11
+ r"\d{1,2} (jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*|"
12
+ r"\d+\s?(s|m|min|h|hr|d|w|mo|y)|\d+ (seconds?|minutes?|hours?|days?|weeks?|months?|years?) ago)$",
13
+ re.I)
14
+ FLAGS = ("unread", "pinned", "muted", "time sensitive", "draft", "starred", "important", "verified", "new")
15
+
16
+
17
+ def labelled(snapshot, roles=("Cell",), region=(.1, .92), minimum=2):
18
+ """The elements in ``region`` with ``roles`` and a label of at least ``minimum`` characters, each label once
19
+ (largest
20
+ element first when two share a label at the same place)."""
21
+ out, seen = [], set()
22
+ for element in sorted(core.find_all(snapshot, r".", roles=roles, region=region),
23
+ key=lambda e: (round(core.centre_y(e), 2), -e.rect[2] * e.rect[3])):
24
+ label = core.plain(element.label)
25
+ key = (label, round(core.centre_y(element), 2))
26
+ if len(label) < minimum or key in seen:
27
+ continue
28
+ seen.add(key)
29
+ out.append(element)
30
+ return out
31
+
32
+
33
+ def parts(label):
34
+ """(parts, flags): a row label's comma parts with iOS's flags ("Unread", "Pinned"...) taken out, lowercased."""
35
+ tokens = [t.strip() for t in core.plain(label).split(",")]
36
+ flags = {t.casefold() for t in tokens if t.casefold() in FLAGS}
37
+ return [t for t in tokens if t and t.casefold() not in FLAGS], flags
38
+
39
+
40
+ def lines(snapshot, region=(0.0, 1.0)):
41
+ """A picture's text lines in ``region``, top to bottom then left to right."""
42
+ return sorted((e for e in getattr(snapshot, "elements", ()) or ()
43
+ if e.label and region[0] <= core.centre_y(e) <= region[1]),
44
+ key=lambda e: (round(core.centre_y(e), 3), e.rect[0]))
45
+
46
+
47
+ def anchored(snapshot, anchor=WHEN, region=(.1, .92), right=.55, same_line=.012):
48
+ """On a picture: rows found by ``anchor`` (a regex on a whole line) at the right of their first line."""
49
+ texts = lines(snapshot, region)
50
+ marks = [e for e in texts if e.rect[0] >= right and anchor.match(core.plain(e.label))]
51
+ rows = []
52
+ for index, mark in enumerate(marks):
53
+ top = core.centre_y(mark)
54
+ bottom = core.centre_y(marks[index + 1]) - same_line if index + 1 < len(marks) else region[1]
55
+ head = [e for e in texts if e is not mark and abs(core.centre_y(e) - top) <= same_line
56
+ and e.rect[0] < mark.rect[0]]
57
+ body = [e for e in texts if e is not mark and e not in head and top + same_line < core.centre_y(e) < bottom]
58
+ rows.append({"anchor": core.plain(mark.label), "head": [core.plain(e.label) for e in head],
59
+ "body": [core.plain(e.label) for e in body], "top": round(top, 3)})
60
+ return rows
61
+
62
+
63
+ def pairs(snapshot, region=(.1, .95), roles=("Cell", "Button", "StaticText", "Other")):
64
+ """{label: value} of a settings-style list: "iOS Version, 26.0.1" on a tree; on a picture, a line on the left and a
65
+ line on the right at the same height."""
66
+ out = {}
67
+ if core.image_only(snapshot):
68
+ texts = lines(snapshot, region)
69
+ for left in (e for e in texts if e.rect[0] < .45):
70
+ right = [e for e in texts if e.rect[0] >= .45 and abs(core.centre_y(e) - core.centre_y(left)) < .012]
71
+ value = re.sub(r"\s*[>›]$", "", core.plain(right[-1].label)) if right else ""
72
+ if value and not re.fullmatch(r"[>›<‹]", value):
73
+ out.setdefault(re.sub(r"\s*[>›]$", "", core.plain(left.label)), value)
74
+ return out
75
+ for element in labelled(snapshot, roles, region):
76
+ label, value = core.plain(element.label), core.plain(element.value)
77
+ if value and value not in ("0", "1") and label:
78
+ out.setdefault(label, value)
79
+ elif ", " in label:
80
+ key, _, rest = label.partition(", ")
81
+ if key and rest:
82
+ out.setdefault(key, re.sub(r"(\d), (\d{3})\b", r"\1,\2", rest))
83
+ return out
84
+
85
+
86
+ def tabbar(names, region=(.86, 1.0)):
87
+ """A tab bar's signal: any of ``names`` (whole labels) at the bottom, a button on a tree, a line on a picture."""
88
+ return core.sig(rf"^({'|'.join(names)})$", region=region)
89
+
90
+
91
+ def top_left_back(snapshot, top=(.04, .14), never=core.NEVER):
92
+ """A navigation bar's Back button: a button at the top left whose label names the screen before ("Settings",
93
+ "General", "Search", "Oct 15"), never one whose words name an act."""
94
+ if core.image_only(snapshot):
95
+ return None
96
+ for element in sorted(core.find_all(snapshot, r"\S", role="Button", region=top), key=lambda e: e.rect[0]):
97
+ if element.rect[0] < .2 and element.rect[2] < .4 and not never.search(element.label or "") \
98
+ and not core.ACT_LABEL.match(core.plain(element.label)):
99
+ return element
100
+ return None
101
+
102
+
103
+ def back_unless_act(pack, snapshot):
104
+ return top_left_back(snapshot, never=pack.never)
105
+
106
+
107
+ def tab_tap(region):
108
+ return lambda label, what: core.Tap(label, what, role="Button", region=region)