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,614 @@
1
+ """Detection and the plan card (DESIGN §3.5, §4.1): deterministic and model-free, so nothing is spent before Start."""
2
+
3
+ import hashlib
4
+ import re
5
+ import threading
6
+ import time
7
+ import weakref
8
+
9
+ from . import budget as B
10
+ from . import costs as C
11
+ from .notebook import FEED_KINDS
12
+
13
+ KINDS = ("feed", "survey", "chain", "browse")
14
+ # The collections a survey drafts openers over (inbox.wants_openers): posts, results and rows (READ_EACH reads them
15
+ # as rows, which the extractor reads as openers).
16
+ OPENER_KINDS = ("row", "result", "post")
17
+ PLAN_TTL_S = 15 * 60
18
+ MAX_PLANS = 64
19
+
20
+ FEED_APPS = {"instagram": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
21
+ "ig": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
22
+ "reels": ("com.burbn.instagram", "Instagram", "reel", "Reels"),
23
+ "tiktok": ("com.zhiliaoapp.musically", "TikTok", "tiktok", "TikToks"),
24
+ "tiktoks": ("com.zhiliaoapp.musically", "TikTok", "tiktok", "TikToks"),
25
+ "youtube shorts": ("com.google.ios.youtube", "YouTube", "short", "Shorts"),
26
+ "shorts": ("com.google.ios.youtube", "YouTube", "short", "Shorts"),
27
+ "youtube": ("com.google.ios.youtube", "YouTube", "short", "Shorts")}
28
+ FEED_NOUN = re.compile(r"\b(reels?|tiktoks?|youtube shorts|shorts|my feed|for you page|fyp)\b", re.I)
29
+ FEED_APP = re.compile(r"\b(instagram|ig|tiktok|youtube)\b", re.I)
30
+ WATCH_VERB = re.compile(r"\b(scroll(?:ing)?|doom ?scroll(?:ing)?|watch(?:ing)?|brows(?:e|ing)|go(?:ing)? through|"
31
+ r"look(?:ing)? through|binge)\b", re.I)
32
+ # A survey reads a collection item by item: these verbs always mean that over a collection...
33
+ STRONG_SURVEY = re.compile(r"\b(go(?:ing)? through|look(?:ing)? through|review(?:ing)?|rank(?:ing)?|shortlist|triage|"
34
+ r"sort through|find (?:the |my )?(?:best|most|top)|most promising|which of my)\b", re.I)
35
+ # ...these only with every, all, each or a count ("read all my emails", "check 10 listings").
36
+ WEAK_SURVEY = re.compile(r"\b(read(?:ing)?|check(?:ing)?|look(?:ing)? at|summari[sz]e)\b", re.I)
37
+ EVERY = re.compile(r"\b(all|every|each)\b", re.I)
38
+ # "error messages" or "status messages" name what a screen shows, not a collection to read (found 13 Oct: a build check
39
+ # that looks for "error messages" on each screen was planned as a survey of 40 messages).
40
+ COLLECTION = re.compile(r"(?<!error )(?<!errors )(?<!warning )(?<!status )(?<!system )(?<!validation )(?<!alert )"
41
+ r"\b(matches|profiles|dms|direct messages|messages|chats|conversations|emails|inbox|"
42
+ r"listings|results|reviews|options|restaurants|places)\b", re.I)
43
+ # Verbs that change things: a bulk change is never a long read.
44
+ # A possessive ("the post's link", found 14 Oct) is a noun, never the verb.
45
+ CHANGE_VERB = re.compile(r"\b(delete|archive|remove|unsubscribe|mark|move|like|follow|unfollow|send|reply to all|"
46
+ r"block|report|unmatch|swipe (?:left|right)|post|pay|buy|order|favou?rite|hide|mute|accept|"
47
+ r"decline|approve|skip)\b(?!['’]s\b)", re.I)
48
+ # Words that say the person wants to be told something: with them, a change verb doesn't make a request a loop.
49
+ REPORT_INTENT = re.compile(r"\b(find|which|rank|shortlist|summari[sz]e|summary|draft|tell me|send me|what|best|most|"
50
+ r"promising|learn(?:ed|t)?|write up|list (?:each|every|all))\b", re.I)
51
+ BROWSE_VERB = re.compile(r"\b(scroll(?:ing)?|doom ?scroll(?:ing)?|brows(?:e|ing)|watch(?:ing)?|read(?:ing)?|"
52
+ r"look(?:ing)? (?:at|through)|go(?:ing)? through|explore|check out)\b", re.I)
53
+ RESEARCH = re.compile(r"\b(best|top \d+|compare|comparing|across|options|cheapest|highest[- ]rated|most promising)\b",
54
+ re.I)
55
+ CHAIN_SPLIT = re.compile(r"\s*(?:,?\s*\band then\b|,?\s*\bthen\b|"
56
+ r",?\s*\bafter that\b|;|\n\s*\d+[.)]\s+|^\s*\d+[.)]\s+)\s*",
57
+ re.I)
58
+ VERB = re.compile(r"^(?:and\s+)?(?:please\s+)?([a-z]+)", re.I)
59
+ FOCUS = (re.compile(r"\babout ([a-z0-9][\w' &-]{1,40}?)(?=\s+(?:for|and|then|on|in|from|to|,)\b|[,.;!?]|$)", re.I),
60
+ re.compile(r"\bon ([a-z0-9][\w' &-]{1,30}?) (?:reels|tiktoks|videos|shorts)\b", re.I),
61
+ re.compile(r"\b([a-z][\w'-]{2,24}(?: [a-z][\w'-]{2,24})?) (?:reels|tiktoks|videos|shorts)\b", re.I))
62
+ # One video named on its own ("the Reel that's open", "this TikTok", "a short about cats", "1 reel"): a normal task that
63
+ # watches it, never a feed plan. A plural ("Reels", "my feed") or a time budget is a feed.
64
+ ONE_ITEM = re.compile(r"\b(?:the|this|that|one|a|1)\s+(?:(?:open|current|next|first|last|same|playing)\s+)?"
65
+ r"(?:reel|tiktok|youtube short|short|video)\b", re.I)
66
+ MANY_ITEMS = re.compile(r"\b(?:reels|tiktoks|shorts|videos|my feed|for you page|fyp)\b", re.I)
67
+ NOT_FOCUS = re.compile(r"^(instagram|ig|tiktok|youtube|my|the|some|more|short|all|every|few|watch|scroll|doomscroll|"
68
+ r"go|through|see|ten|\d+.*|the next|next|first|top)$", re.I)
69
+
70
+
71
+ # Apps named with an everyday word (orchestrate._EVERYDAY): after another app's name, the word is that app's noun.
72
+ EVERYDAY_NAMES = frozenset({"news", "photos", "music", "clock", "files", "phone", "mail", "messages", "notes", "maps",
73
+ "books", "health", "wallet", "camera", "weather", "calendar", "contacts", "reminders"})
74
+
75
+
76
+ def _fold(text):
77
+ return " ".join(str(text or "").split())
78
+
79
+
80
+ def chain_parts(goal):
81
+ """The request's parts in order, split on then / and then / after that / ; / a numbered list."""
82
+ parts = [p.strip(" ,.") for p in CHAIN_SPLIT.split(_fold(goal) if "\n" not in str(goal) else str(goal))]
83
+ return [p for p in parts if p and len(p.split()) >= 2]
84
+
85
+
86
+ def _verb(part):
87
+ found = VERB.match(part)
88
+ return found.group(1).casefold() if found else ""
89
+
90
+
91
+ def named_apps(goal, apps):
92
+ """[{bundleId, name}] of ``apps`` the request names (whole words), in the request's order."""
93
+ folded = _fold(goal).casefold()
94
+ hits = []
95
+ for app in apps or ():
96
+ name = str(app.get("name") or "")
97
+ if len(name) > 2 and app.get("bundleId"):
98
+ hit = re.search(rf"\b{re.escape(name.casefold())}\b", folded)
99
+ if hit:
100
+ hits.append((hit.start(), hit.end(), {"bundleId": app["bundleId"], "name": name}))
101
+ found = []
102
+ for start, end, app in hits:
103
+ if app["name"].casefold() in EVERYDAY_NAMES and any(
104
+ other is not app and re.fullmatch(r"(?:['’]s)?\s+", folded[other_end:start])
105
+ for _, other_end, other in hits):
106
+ continue
107
+ found.append((start, app))
108
+ found.sort(key=lambda pair: pair[0])
109
+ out, seen = [], set()
110
+ for _, app in found:
111
+ if app["bundleId"] not in seen:
112
+ seen.add(app["bundleId"])
113
+ out.append(app)
114
+ return out
115
+
116
+
117
+ def one_item(text, parsed=None):
118
+ """Whether the request is about one video ("Watch the Reel that's open"): no feed plan for it (real-device QA,
119
+ 13 Oct: one open Reel got "Watches about 30 Reels")."""
120
+ if parsed is not None and not (parsed.kind == "items" and parsed.items == 1):
121
+ return False
122
+ return bool(ONE_ITEM.search(text)) and not MANY_ITEMS.search(text)
123
+
124
+
125
+ def _single(goal, parsed):
126
+ """feed | survey | browse | None for one part of a request."""
127
+ text = _fold(goal)
128
+ if CHANGE_VERB.search(text) and not REPORT_INTENT.search(text):
129
+ return None
130
+ collection = COLLECTION.search(text)
131
+ counted = parsed is not None and parsed.kind in ("items", "all")
132
+ one = one_item(text, parsed)
133
+ if FEED_NOUN.search(text) and (WATCH_VERB.search(text) or parsed is not None) and not one:
134
+ return "feed"
135
+ if collection and (STRONG_SURVEY.search(text) or WEAK_SURVEY.search(text) and (EVERY.search(text) or counted)):
136
+ return "survey"
137
+ if FEED_APP.search(text) and WATCH_VERB.search(text) and not collection and not one:
138
+ return "feed"
139
+ if parsed is not None and parsed.kind in ("time", "items") and BROWSE_VERB.search(text) and not one:
140
+ return "feed" if FEED_APP.search(text) else "browse"
141
+ return None
142
+
143
+
144
+ def detect(goal, apps=()):
145
+ """"feed" | "survey" | "chain" | "browse" | None."""
146
+ text = _fold(goal)
147
+ if not text:
148
+ return None
149
+ parsed = B.parse_budget(text)
150
+ parts = chain_parts(goal)
151
+ if len(parts) >= 2:
152
+ verbs = {_verb(p) for p in parts}
153
+ long_part = any(_single(p, B.parse_budget(p)) for p in parts)
154
+ if len(verbs) >= 2 and (long_part or RESEARCH.search(text) or len(_part_apps(parts, apps)) >= 2):
155
+ return "chain"
156
+ # A goal that names two or more apps and passes nothing between them ("go through my messages across Messages,
157
+ # WhatsApp and Instagram") is one plan across apps, the orchestrator's (orchestrate.apps_plan), not a long read of
158
+ # one of them.
159
+ from ..catalog import APPS
160
+ from ..orchestrate import named_apps
161
+ if len(named_apps(goal, [a for a in apps or () if a.get("name")] or APPS)) >= 2:
162
+ return None
163
+ return _single(text, parsed)
164
+
165
+
166
+ def _part_app(part, apps):
167
+ """The app one part of a request works in (app_choice.infer_app over the phone's apps, else the catalog), or
168
+ None: "check my calendar for tomorrow" is Calendar, "text the first attendee" is Messages."""
169
+ from ..app_choice import infer_app
170
+ from ..catalog import APPS
171
+ known = [a for a in apps or () if a.get("id") and a.get("name")] or APPS
172
+ try:
173
+ found = infer_app(part, known)
174
+ except Exception: # noqa: BLE001 -- no app: the part names none
175
+ return None
176
+ return found if isinstance(found, dict) else None
177
+
178
+
179
+ def _part_apps(parts, apps):
180
+ """The distinct apps a request's parts work in, in order."""
181
+ out = []
182
+ for part in parts:
183
+ app = _part_app(part, apps)
184
+ if app is not None and app.get("bundleId") not in [a.get("bundleId") for a in out]:
185
+ out.append(app)
186
+ return out
187
+
188
+
189
+ def focus_of(goal):
190
+ """What the feed is about ("basketball"), or None."""
191
+ text = _fold(goal)
192
+ for pattern in FOCUS:
193
+ for found in pattern.finditer(text):
194
+ words = found.group(1).strip(" '-")
195
+ words = re.sub(r"^(?:(?:the|some|my|scroll|scrolling|doomscroll|watch|watching|browse|instagram|ig|tiktok|"
196
+ r"youtube|through|more)\s+)+", "", words, flags=re.I)
197
+ if words and not NOT_FOCUS.match(words) and not FEED_APP.fullmatch(words):
198
+ return words.casefold()
199
+ return None
200
+
201
+
202
+ def _feed_app(goal, apps):
203
+ """(bundleId, name, item kind, noun) of the feed the request means."""
204
+ folded = _fold(goal).casefold()
205
+ for word, value in FEED_APPS.items():
206
+ if re.search(rf"\b{re.escape(word)}\b", folded):
207
+ bundle, name = value[0], value[1]
208
+ installed = next((a for a in apps or () if a.get("bundleId") == bundle), None)
209
+ return bundle, (installed or {}).get("name") or name, value[2], value[3]
210
+ named = named_apps(goal, apps)
211
+ if named:
212
+ return named[0]["bundleId"], named[0]["name"], "reel", "videos"
213
+ return "com.burbn.instagram", "Instagram", "reel", "Reels"
214
+
215
+
216
+ def _survey_app(goal, apps):
217
+ """(bundleId, name) of the app a survey reads in: one the request names among the phone's apps, else the one
218
+ app_choice infers from the request (the catalog's names and words), else (None, None)."""
219
+ named = named_apps(goal, apps)
220
+ if named:
221
+ return named[0]["bundleId"], named[0]["name"]
222
+ inferred = _part_app(goal, apps)
223
+ if inferred is not None and inferred.get("bundleId"):
224
+ return inferred["bundleId"], inferred.get("name")
225
+ return None, None
226
+
227
+
228
+ def _collection(goal):
229
+ found = COLLECTION.search(_fold(goal))
230
+ return found.group(1).casefold() if found else "items"
231
+
232
+
233
+ def _models(nav_model, kind, items):
234
+ from .models import extract_model, synth_model_for
235
+ return {"navigate": nav_model, "extract": extract_model(nav_model), "report": synth_model_for(kind, items,
236
+ nav_model)}
237
+
238
+
239
+ # A collection the person keeps ("my saved posts", "the reels I liked"): the plan goes there, not to the app's feed.
240
+ KEPT = re.compile(r"\b(saved|bookmarked|liked|favou?rited?)\s+(posts|reels|videos|tiktoks|shorts|pins|photos)\b|"
241
+ r"\b(posts|reels|videos|tiktoks|shorts|pins|photos)\s+(?:that\s+)?i(?:'ve|\s+have)?\s+"
242
+ r"(saved|bookmarked|liked)\b", re.I)
243
+
244
+
245
+ def kept_collection(goal):
246
+ """"your saved posts" for a request over a collection the person kept, else None."""
247
+ found = KEPT.search(_fold(goal))
248
+ if not found:
249
+ return None
250
+ how, noun = (found.group(1), found.group(2)) if found.group(1) else (found.group(4), found.group(3))
251
+ return f"your {how.casefold()} {noun.casefold()}"
252
+
253
+
254
+ def _steps(kind, goal, apps, budget, focus):
255
+ if kind == "feed":
256
+ _bundle, name, _, _ = _feed_app(goal, apps)
257
+ kept = kept_collection(goal)
258
+ where = f"find {focus} {budget.item_noun}" if focus else f"go to {kept or budget.item_noun}"
259
+ count = f"about {budget.items}" if budget.items else f"for {B.minutes_label(budget.minutes or 10)}"
260
+ verb = "Go through" if kept else "Watch"
261
+ return [{"id": 1, "title": f"Open {name} and {where}", "app": name},
262
+ {"id": 2, "title": f"{verb} {count} {budget.item_noun}" if budget.items else
263
+ f"{verb} {budget.item_noun} {count}", "app": name},
264
+ {"id": 3, "title": "Write up what it learned", "app": None}]
265
+ if kind == "survey":
266
+ _bundle, name = _survey_app(goal, apps)
267
+ place = name or "the app"
268
+ collection = _collection(goal)
269
+ collection = {"dms": "your DMs", "matches": "your matches", "messages": "your messages",
270
+ "chats": "your chats", "emails": "your inbox", "inbox": "your inbox"}.get(collection,
271
+ f"the {collection}")
272
+ from .inbox import wants_drafts, wants_openers
273
+ sweep = wants_drafts(goal) and budget.item_kind in ("chat", "row")
274
+ openers = wants_openers(goal) and budget.item_kind in OPENER_KINDS
275
+ return [{"id": 1, "title": f"Open {place} and go to {collection}", "app": name},
276
+ {"id": 2, "title": f"Read each of up to {budget.items} {budget.item_noun}", "app": name},
277
+ {"id": 3, "title": "Sort them and draft replies, sending nothing" if sweep else
278
+ "Draft a DM to each person who fits, sending nothing" if openers else
279
+ "Rank them and write it up", "app": None}]
280
+ if kind == "chain":
281
+ out = []
282
+ for index, part in enumerate(chain_parts(goal)[:8], 1):
283
+ named = named_apps(part, apps)
284
+ inferred = None if named else _part_app(part, apps)
285
+ app = named[0]["name"] if named else (inferred or {}).get("name")
286
+ title = part[:1].upper() + part[1:]
287
+ out.append({"id": index, "title": title[:120], "app": app})
288
+ return out
289
+ named = named_apps(goal, apps)
290
+ name = named[0]["name"] if named else None
291
+ return [{"id": 1, "title": f"Browse {name}" if name else "Browse", "app": name},
292
+ {"id": 2, "title": "Write up what it found", "app": None}]
293
+
294
+
295
+ # A commit the request asks for is a verb that opens a clause: "and like the best ones", "then book Friday at 8",
296
+ # "reply to each one", "Text Sam ...". "I'd like to know" or "send me a summary" are not commits.
297
+ CLAUSE = r"(?:^|[,;.:!?]\s*|\b(?:and|then|also|please|to|after that)\s+)"
298
+ NOT_COMMIT = re.compile(r"^(?:send|share|post) (?:me|it to me|them to me|us|what|back)\b", re.I)
299
+ MESSAGE_WORDS = ("text", "message", "dm", "email", "e-mail", "imessage")
300
+ # The act a verb means when several families hold it; else the first act (by name) whose family does.
301
+ PREFERRED = {"reply": "reply", "comment": "comment", "like": "react", "react": "react", "send": "send_message",
302
+ "post": "post", "publish": "post", "tweet": "post", "buy": "order", "purchase": "order",
303
+ "confirm": "other", "submit": "other", "request": "request_money", "share": "share",
304
+ **{word: "send_message" for word in MESSAGE_WORDS}}
305
+ # What a card says it asks before: "Asks before each reply." for a feed, survey or browse; "Asks before it books." for
306
+ # a chain.
307
+ EACH_NOUN = {"reply": "reply", "comment": "comment", "react": "like", "send_message": "message", "post": "post",
308
+ "share": "share", "follow": "follow", "unfollow": "unfollow", "book": "booking", "order": "order",
309
+ "pay": "payment", "transfer": "transfer", "request_money": "money request", "call": "call",
310
+ "delete": "deletion", "cancel": "cancellation", "install": "install", "other": "action"}
311
+ CHAIN_VERB = {"send_message": "send", "react": "like", "other": "confirm"}
312
+
313
+
314
+ def _commit_verbs():
315
+ """[(verb, act)] for every act that asks (contract_asks.ASK_ACTS, "like" among them) and the message verbs, longest
316
+ first."""
317
+ from ..contract_asks import ASK_ACTS
318
+ from ..contract_screen import FAMILIES
319
+ verbs = {}
320
+ for act in sorted(ASK_ACTS):
321
+ for verb in FAMILIES.get(act, ()):
322
+ if verb != "get":
323
+ verbs.setdefault(verb, PREFERRED.get(verb, act))
324
+ for word in MESSAGE_WORDS:
325
+ verbs.setdefault(word, "send_message")
326
+ return sorted(verbs.items(), key=lambda pair: -len(pair[0]))
327
+
328
+
329
+ # A negation that opens a list of acts the request rules out: "don't like, follow, reply to or message anyone",
330
+ # "never post", "without liking or replying". Only the verbs listed right after it are ruled out ("don't forget to send
331
+ # it" rules nothing out, and "if it's not there, book it" still books).
332
+ NEGATION = re.compile(r"\b(?:don['’]t|dont|do not|never|without|no need to|nor)\s+(?:to\s+)?", re.I)
333
+ LIST_GAP = re.compile(r"\s*(?:,\s*(?:or\s+|and\s+|nor\s+)?|\s+or\s+|\s+and\s+|\s+nor\s+|/)\s*")
334
+ PARTICLE = re.compile(r"\s+(?:to|on|at|back|out)\b")
335
+
336
+
337
+ def _ing(verb):
338
+ head, _, rest = verb.partition(" ")
339
+ stem = head[:-1] if head.endswith("e") and not head.endswith("ee") else head
340
+ return (stem + "ing") + (" " + rest if rest else "")
341
+
342
+
343
+ def negated_spans(folded):
344
+ """[(start, end)] of the verbs a negation rules out in ``folded`` (casefolded text)."""
345
+ words = sorted({form for verb, _ in _commit_verbs() for form in (verb, _ing(verb))}, key=len, reverse=True)
346
+ verb = re.compile("(?:" + "|".join(re.escape(w) for w in words) + r")\b")
347
+ spans = []
348
+ for cue in NEGATION.finditer(folded):
349
+ at = cue.end()
350
+ while True:
351
+ hit = verb.match(folded, at)
352
+ if hit is None:
353
+ break
354
+ spans.append((hit.start(), hit.end()))
355
+ at = hit.end()
356
+ particle = PARTICLE.match(folded, at)
357
+ if particle is not None:
358
+ at = particle.end()
359
+ gap = LIST_GAP.match(folded, at)
360
+ if gap is None:
361
+ break
362
+ at = gap.end()
363
+ return spans
364
+
365
+
366
+ def commits_in(text):
367
+ """[(act, verb)] the text asks Mobster to commit, in the text's order (each act once)."""
368
+ folded = _fold(text).casefold()
369
+ ruled_out = negated_spans(folded)
370
+ found = []
371
+ for verb, act in _commit_verbs():
372
+ for hit in re.finditer(CLAUSE + "(" + re.escape(verb) + r")\b", folded):
373
+ if NOT_COMMIT.match(folded[hit.start(1):]):
374
+ continue
375
+ if any(start <= hit.start(1) < end for start, end in ruled_out):
376
+ continue
377
+ found.append((hit.start(1), act, verb))
378
+ break
379
+ out, acts = [], set()
380
+ for _, act, verb in sorted(found):
381
+ if act not in acts:
382
+ acts.add(act)
383
+ out.append((act, verb))
384
+ return out
385
+
386
+
387
+ def bulk_change(goal):
388
+ """A request that changes things and asks to be told nothing ("delete every email")."""
389
+ text = _fold(goal)
390
+ return bool(CHANGE_VERB.search(text) and not REPORT_INTENT.search(text))
391
+
392
+
393
+ def _commits(kind, goal, steps):
394
+ """[{act, app, verb}] the task will commit: every part of a chain (each with its step's app), the whole request
395
+ for a feed, survey or browse."""
396
+ out = []
397
+ if kind == "chain":
398
+ parts = chain_parts(goal)
399
+ for index, part in enumerate(parts):
400
+ step = steps[index] if index < len(steps) else {}
401
+ app = step.get("app") or next((s.get("app") for s in reversed(steps) if s.get("app")), None)
402
+ out += [{"act": act, "app": app, "verb": verb} for act, verb in commits_in(part)
403
+ if act not in [c["act"] for c in out]]
404
+ return out
405
+ app = next((s.get("app") for s in steps if s.get("app")), None)
406
+ return [{"act": act, "app": app, "verb": verb} for act, verb in commits_in(goal)]
407
+
408
+
409
+ def commit_lines(kind, commits):
410
+ """The card's lines for its commits: "Asks before each reply." (a feed, survey or browse repeats the act),
411
+ "Asks before it books." (a chain's step)."""
412
+ lines = []
413
+ for commit in commits[:3]:
414
+ if kind == "chain":
415
+ verb = CHAIN_VERB.get(commit["act"]) or commit["verb"]
416
+ lines.append(f"Asks before it {_third_person(verb)}.")
417
+ else:
418
+ lines.append(f"Asks before each {EACH_NOUN.get(commit['act'], 'action')}.")
419
+ return list(dict.fromkeys(lines))
420
+
421
+
422
+ def _third_person(verb):
423
+ if " " in verb:
424
+ head, _, rest = verb.partition(" ")
425
+ return f"{_third_person(head)} {rest}"
426
+ if re.search(r"[^aeiou]y$", verb):
427
+ return verb[:-1] + "ies"
428
+ if re.search(r"(s|sh|ch|x|z)$", verb):
429
+ return verb + "es"
430
+ return verb + "s"
431
+
432
+
433
+ def _summary(kind, goal, budget, focus, steps):
434
+ if kind == "feed":
435
+ topic = f"{focus} " if focus else ""
436
+ kept = kept_collection(goal)
437
+ if kept and budget.items and not budget.strict:
438
+ return f"Goes through up to {budget.items} of {kept}, then writes up what it found."
439
+ if budget.strict:
440
+ return (f"Watches {topic}{budget.item_noun} for the full {B.minutes_label(budget.minutes)}, "
441
+ "then writes up what it learned.")
442
+ said = (f", the number you'd scroll in {B.minutes_label(budget.minutes)}" if budget.kind == "time" else "")
443
+ return f"Watches about {budget.items} {topic}{budget.item_noun}{said}, then writes up what it learned."
444
+ if kind == "survey":
445
+ place = next((s["app"] for s in steps if s.get("app")), None)
446
+ where = f" in {place}" if place else ""
447
+ from .inbox import wants_drafts, wants_openers
448
+ if wants_drafts(goal) and budget.item_kind in ("chat", "row"):
449
+ # An inbox sweep (longform/inbox.py): drafts for review, none sent.
450
+ return (f"Reads up to {budget.items} {budget.item_noun}{where} and drafts replies to the ones that need "
451
+ "one, for you to review. Nothing is sent.")
452
+ if wants_openers(goal) and budget.item_kind in OPENER_KINDS:
453
+ # Openers (longform/inbox.py): a first message to each person who fits, for review, none sent.
454
+ return (f"Reads up to {budget.items} {budget.item_noun}{where} and drafts a DM to each person who fits, "
455
+ "for you to review. Nothing is sent.")
456
+ return f"Reads up to {budget.items} {budget.item_noun}{where}, then ranks the most promising and says why."
457
+ if kind == "chain":
458
+ return f"Does {len(steps)} steps in order, with a checkpoint after each."
459
+ return f"Browses for about {budget.items or 20} {budget.item_noun}, then writes up what it found."
460
+
461
+
462
+ def plan_id(goal, kind, budget, limit_usd, nav_model):
463
+ raw = f"{_fold(goal)}|{kind}|{budget.to_dict()}|{limit_usd}|{nav_model}"
464
+ return "p_" + hashlib.sha1(raw.encode()).hexdigest()[:10]
465
+
466
+
467
+ def make_plan(goal, *, apps=(), limit_usd=None, nav_model="claude-sonnet-5-5",
468
+ kind=None, change=None, alternative=None, now=None):
469
+ """The plan card (a dict, DESIGN §4.1) for ``goal``, or None if it isn't a long task and ``kind`` isn't forced."""
470
+ goal = _fold(goal)
471
+ kind = kind or detect(goal, apps)
472
+ if kind is None:
473
+ return None
474
+ if kind not in KINDS:
475
+ raise ValueError("kind is feed, survey, chain or browse")
476
+ parsed = B.parse_budget(goal)
477
+ if kind == "feed":
478
+ _bundle, _name, item_kind, noun = _feed_app(goal, apps)
479
+ if parsed is not None and parsed.item_noun not in ("items",) and parsed.item_kind in FEED_KINDS:
480
+ noun = parsed.item_noun if parsed.item_noun != "videos" else noun
481
+ elif kind == "survey":
482
+ item_kind, noun = B.noun_in(goal)
483
+ if item_kind is None:
484
+ item_kind, noun = "row", "items"
485
+ else:
486
+ item_kind, noun = B.noun_in(goal)
487
+ item_kind, noun = (item_kind or "result"), (noun or "results")
488
+ budget = B.for_kind(kind, parsed, item_kind, noun)
489
+ if alternative == "full" and budget.minutes:
490
+ budget = B.time_budget(budget.minutes, budget.item_kind, budget.item_noun, strict=True)
491
+ elif alternative == "more" and budget.kind == "all":
492
+ budget = B.all_budget(budget.item_kind, budget.item_noun, B.ALL_MAX)
493
+ budget = B.override(budget, change)
494
+ focus = focus_of(goal) if kind in ("feed", "browse") else None
495
+ steps = _steps(kind, goal, apps, budget, focus)
496
+ models = _models(nav_model, kind, budget.items or 60)
497
+ estimate = C.estimate(kind, items=budget.items, item_kind=budget.item_kind, steps=len(steps),
498
+ nav_model=nav_model, synth_model=models["report"],
499
+ phone_seconds_cap=budget.phone_seconds_cap)
500
+ alternatives = []
501
+ if kind in ("feed", "browse") and budget.kind == "time" and not budget.strict:
502
+ full = C.estimate(kind, items=None, item_kind=budget.item_kind, steps=len(steps), nav_model=nav_model,
503
+ synth_model=models["report"], phone_seconds_cap=budget.phone_seconds_cap)
504
+ alternatives.append({"id": "full", "label": f"Use the full {B.minutes_label(budget.minutes)}", "items": None,
505
+ "phoneSeconds": round(budget.phone_seconds_cap), "lowUsd": full["lowUsd"],
506
+ "highUsd": full["highUsd"]})
507
+ if budget.kind == "all" and (budget.items or 0) < B.ALL_MAX:
508
+ more = C.estimate(kind, items=B.ALL_MAX, item_kind=budget.item_kind, steps=len(steps), nav_model=nav_model,
509
+ synth_model=models["report"])
510
+ alternatives.append({"id": "more", "label": f"Up to {B.ALL_MAX} {budget.item_noun}", "items": B.ALL_MAX,
511
+ "phoneSeconds": more["phoneSeconds"], "lowUsd": more["lowUsd"],
512
+ "highUsd": more["highUsd"]})
513
+ cap = cap_check(estimate["highUsd"], limit_usd)
514
+ for option in alternatives:
515
+ # Each alternative carries its own cap check: the card proposes the raise for the one the person picks.
516
+ option.update({k: v for k, v in cap_check(option["highUsd"], limit_usd).items() if k != "limitUsd"})
517
+ commits = _commits(kind, goal, steps)
518
+ # A long run asks before every act that asks, with Ask before acting off too (frontier._long_asks), so the card
519
+ # says so whatever the setting.
520
+ policy_lines = {"readOnly": not commits, "lines": commit_lines(kind, commits)}
521
+ now = time.time() if now is None else now
522
+ card = {"id": plan_id(goal, kind, budget, limit_usd, nav_model), "kind": kind, "title": goal[:300],
523
+ "focus": focus, "summary": _summary(kind, goal, budget, focus, steps),
524
+ "apps": list(_plan_apps(kind, goal, apps)), "steps": steps, "budget": budget.public(),
525
+ "alternatives": alternatives,
526
+ "estimate": {k: estimate[k] for k in ("phoneSeconds", "reportSeconds", "usd", "lowUsd", "highUsd")},
527
+ "cap": cap, "policy": policy_lines, "commits": [{"act": c["act"], "app": c["app"]} for c in commits],
528
+ "models": models, "expiresAt": int((now + PLAN_TTL_S) * 1000)}
529
+ card["_budget"] = budget.to_dict()
530
+ return card
531
+
532
+
533
+ def cap_check(high_usd, limit_usd):
534
+ """{"limitUsd", "exceeds", "proposedUsd"}: a plan whose high estimate passes the per-task limit proposes a raise to
535
+ ceil_to_0.50(1.2 x high), which the person switches on before Start."""
536
+ exceeds = limit_usd is not None and high_usd > limit_usd
537
+ return {"limitUsd": limit_usd, "exceeds": bool(exceeds),
538
+ "proposedUsd": C.ceil_half(1.2 * high_usd) if exceeds else None}
539
+
540
+
541
+ def _plan_apps(kind, goal, apps):
542
+ named = named_apps(goal, apps)
543
+ if named:
544
+ return named
545
+ if kind == "feed":
546
+ bundle, name, _, _ = _feed_app(goal, apps)
547
+ return [{"bundleId": bundle, "name": name}]
548
+ if kind == "survey":
549
+ bundle, name = _survey_app(goal, apps)
550
+ return [{"bundleId": bundle, "name": name}] if bundle else []
551
+ return []
552
+
553
+
554
+ def public(card):
555
+ """The card without its private keys (``_budget``)."""
556
+ return {k: v for k, v in (card or {}).items() if not k.startswith("_")}
557
+
558
+
559
+ def budget_of(card):
560
+ return B.Budget.from_dict(card["_budget"]) if card and card.get("_budget") else None
561
+
562
+
563
+ class PlanStore:
564
+ """Plans shown on cards, per Runtime: at most MAX_PLANS, each for PLAN_TTL_S."""
565
+
566
+ def __init__(self):
567
+ self.plans = {}
568
+ self.lock = threading.Lock()
569
+
570
+ def put(self, card, now=None):
571
+ now = time.time() if now is None else now
572
+ with self.lock:
573
+ self.plans = {k: v for k, v in self.plans.items() if v[0] > now}
574
+ self.plans[card["id"]] = (now + PLAN_TTL_S, card)
575
+ while len(self.plans) > MAX_PLANS:
576
+ oldest = min(self.plans, key=lambda key: self.plans[key][0])
577
+ del self.plans[oldest]
578
+ return card
579
+
580
+ def get(self, plan_id, now=None):
581
+ now = time.time() if now is None else now
582
+ with self.lock:
583
+ found = self.plans.get(plan_id)
584
+ if found is None or found[0] <= now:
585
+ self.plans.pop(plan_id, None)
586
+ return None
587
+ return found[1]
588
+
589
+
590
+ _stores = weakref.WeakKeyDictionary()
591
+ _stores_lock = threading.Lock()
592
+
593
+
594
+ def store_for(runtime):
595
+ with _stores_lock:
596
+ store = _stores.get(runtime)
597
+ if store is None:
598
+ store = _stores[runtime] = PlanStore()
599
+ return store
600
+
601
+
602
+ def nav_model():
603
+ from ..engines import smart_model
604
+ try:
605
+ return smart_model()
606
+ except Exception: # noqa: BLE001
607
+ return "claude-sonnet-5-5"
608
+
609
+
610
+ def runtime_apps(runtime):
611
+ try:
612
+ return [a for a in runtime.apps() if a.get("installed") is not False]
613
+ except Exception: # noqa: BLE001
614
+ return []