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,207 @@
1
+ """`mobster memory`: see and edit what Mobster remembers (devtools.py creates its parser with fixed help).
2
+
3
+ It reads and writes the same file as the Mac app (user_data_dir()/memory/memory.sqlite3), so a change here shows in
4
+ Settings › Memory the next time it's opened. ``run(args)`` returns 0 done, 1 refused or not found, 2 usage, 3 memory
5
+ can't be opened here (no home folder, or saved by a newer Mobster), 130 ctrl+c. It never raises.
6
+ """
7
+
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from ..api_errors import APIError
13
+ from .store import CONFIRM
14
+
15
+
16
+ def add_arguments(parser, helpers):
17
+ subs = parser.add_subparsers(dest="memory_command", metavar="<subcommand>")
18
+
19
+ def as_json(sub):
20
+ sub.add_argument("--json", action="store_true", help="print JSON")
21
+
22
+ listing = subs.add_parser("list", help="list what Mobster remembers (the default)",
23
+ description="List what Mobster remembers: pinned things first, then the newest.")
24
+ # dest "bundle": `--app` before a command's name is the terminal UI's (__main__.check_placement reads args.app).
25
+ listing.add_argument("--app", dest="bundle", metavar="BUNDLE",
26
+ help="only what Mobster remembers for this app (its bundle ID)")
27
+ listing.add_argument("--scope", metavar="SCOPE", help="only this scope: global, app:<bundle>, project:<id>, "
28
+ "profile:<id>, workflow:<id>, "
29
+ "profile:<id>/workflow:<id> or phone:<id>")
30
+ listing.add_argument("--kind", metavar="KIND", help="only this kind (path, place, value, preference, rule, "
31
+ "structure, device, outcome, cost or repo)")
32
+ listing.add_argument("--new", action="store_true", help="only what Mobster learned since you last looked")
33
+ as_json(listing)
34
+
35
+ add = subs.add_parser("add", help="tell Mobster something to remember",
36
+ description="Tell Mobster something to remember, in your words (up to 300 characters). "
37
+ "Pinned things go with eligible tasks in their scope; "
38
+ "the rest go with tasks that fit. "
39
+ "Mobster never remembers passwords, codes or card numbers.",
40
+ epilog='examples:\n mobster memory add "My gym is the one on 5th Street"\n'
41
+ ' mobster memory add "Sign my texts with – Sam" --app com.apple.MobileSMS --pin')
42
+ add.add_argument("text", metavar="TEXT", help="what to remember")
43
+ add.add_argument("--app", dest="bundle", metavar="BUNDLE", help="only for tasks in this app (its bundle ID)")
44
+ add.add_argument("--scope", metavar="SCOPE", help="where it applies: app:<bundle>, project:<id>, profile:<id>, "
45
+ "workflow:<id>, profile:<id>/workflow:<id> or phone:<id> "
46
+ "(default: everywhere)")
47
+ add.add_argument("--kind", metavar="KIND", help="what it is (default: value, or preference when pinned)")
48
+ add.add_argument("--pin", action="store_true",
49
+ help="send it with every eligible task in its scope (up to 10 pinned)")
50
+ as_json(add)
51
+
52
+ remove = subs.add_parser("rm", aliases=["forget"], help="forget one thing",
53
+ description="Forget one thing, by the ID `list` shows. Mobster won't learn it again "
54
+ "for 90 days.")
55
+ remove.add_argument("id", metavar="ID", help="the 12-character ID from `mobster memory list`")
56
+ keep = subs.add_parser("keep", help="keep something Mobster learned",
57
+ description="Keep one thing Mobster learned: it stops showing as new and is never "
58
+ "offered for removal.")
59
+ keep.add_argument("id", metavar="ID", help="the 12-character ID from `mobster memory list`")
60
+
61
+ pin = subs.add_parser("pin", help="send one thing with every eligible task in its scope",
62
+ description="Pin one thing for every eligible task in its scope (up to 10 pinned).")
63
+ pin.add_argument("id", metavar="ID", help="the 12-character ID from `mobster memory list`")
64
+ unpin = subs.add_parser("unpin", help="send one thing only with eligible tasks that fit",
65
+ description="Unpin one thing: it goes only with eligible tasks that fit.")
66
+ unpin.add_argument("id", metavar="ID", help="the 12-character ID from `mobster memory list`")
67
+
68
+ clear = subs.add_parser("clear", help="delete everything Mobster remembers",
69
+ description="Delete everything Mobster remembers, and its suggestions. Asks you to "
70
+ "type \"delete everything\" first, unless you add --yes.")
71
+ clear.add_argument("--yes", action="store_true", help="don't ask")
72
+
73
+ export = subs.add_parser("export", help="print everything Mobster remembers as Markdown",
74
+ description="Print everything Mobster remembers as Markdown, or save it with --output.")
75
+ export.add_argument("--output", metavar="FILE", help="save it to this file instead")
76
+ export.add_argument("--json", action="store_true", help="export JSON with every fact's kind, source and scope")
77
+
78
+
79
+ def _print(text=""):
80
+ try:
81
+ print(text, flush=True)
82
+ except BrokenPipeError:
83
+ pass
84
+
85
+
86
+ def _fail(message, code=1):
87
+ print(f"mobster memory: {message}", file=sys.stderr, flush=True)
88
+ return code
89
+
90
+
91
+ def _scope(args):
92
+ scope = getattr(args, "scope", None)
93
+ bundle = getattr(args, "bundle", None)
94
+ if scope and bundle:
95
+ raise APIError("Use --scope or --app, not both.", 400, "invalid_scope")
96
+ return scope or (f"app:{bundle}" if bundle else None)
97
+
98
+
99
+ def _line(fact):
100
+ where = (f"In {fact['app']['name']}: " if fact.get("app")
101
+ else f"[{fact['scopeKind']} {fact['scopeId']}] " if fact["scopeKind"] != "global" else "")
102
+ marks = [mark for mark, on in (("pinned", fact["pinned"]), ("new", fact["new"]),
103
+ (f"from {fact['source']}", fact["source"] != "user")) if on]
104
+ kind = f"{fact['kind']:<10} " if fact["source"] != "user" else ""
105
+ return f"{fact['id']} {kind}{where}{fact['text']}{' (' + ', '.join(marks) + ')' if marks else ''}"
106
+
107
+
108
+ def run(args):
109
+ from .store import default_store
110
+ command = getattr(args, "memory_command", None) or "list"
111
+ store = default_store()
112
+ if store is None:
113
+ return _fail("Mobster can't keep memory here: there's no home folder (HOME isn't set).", 3)
114
+ try:
115
+ return COMMANDS[command](store, args)
116
+ except APIError as error:
117
+ if error.status == 503:
118
+ return _fail(str(error), 3)
119
+ return _fail(str(error), 1)
120
+ except KeyboardInterrupt:
121
+ return 130
122
+
123
+
124
+ def _list(store, args):
125
+ facts = store.list_facts(scope=_scope(args), kind=getattr(args, "kind", None), new=getattr(args, "new", False))
126
+ if getattr(args, "json", False):
127
+ _print(json.dumps({"facts": facts}, ensure_ascii=False))
128
+ return 0
129
+ if not facts:
130
+ _print("Mobster doesn't remember anything yet. Tell it something:\n"
131
+ ' mobster memory add "My gym is the one on 5th Street"')
132
+ return 0
133
+ for fact in facts:
134
+ _print(_line(fact))
135
+ count = len(facts)
136
+ _print(f"\n{count} {'thing' if count == 1 else 'things'}. Pinned ones go with eligible tasks in their scope; "
137
+ "the rest go with tasks that fit. Everything stays on this Mac.")
138
+ return 0
139
+
140
+
141
+ def _add(store, args):
142
+ fact = store.add_fact(args.text, _scope(args) or "global", pinned=args.pin, kind=args.kind)
143
+ if args.json:
144
+ _print(json.dumps({"fact": fact}, ensure_ascii=False))
145
+ else:
146
+ _print(f"Mobster will remember: {fact['text']} ({fact['id']})")
147
+ return 0
148
+
149
+
150
+ def _remove(store, args):
151
+ fact = store.forget(args.id)
152
+ _print(f"Forgot: {fact['text']}")
153
+ return 0
154
+
155
+
156
+ def _keep(store, args):
157
+ _print(f"Kept: {store.keep(args.id)['text']}")
158
+ return 0
159
+
160
+
161
+ def _pin(pinned):
162
+ def change(store, args):
163
+ fact = store.update_fact(args.id, pinned=pinned)
164
+ _print(f"{'Pinned' if pinned else 'Unpinned'}: {fact['text']}")
165
+ return 0
166
+ return change
167
+
168
+
169
+ def _clear(store, args):
170
+ count = store.count()
171
+ if not args.yes:
172
+ if not sys.stdin.isatty():
173
+ return _fail("Add --yes to delete everything Mobster remembers without being asked.", 2)
174
+ try:
175
+ typed = input(f'Delete all {count} {"thing" if count == 1 else "things"} Mobster remembers? '
176
+ f'Type "{CONFIRM}" to confirm: ')
177
+ except EOFError:
178
+ typed = ""
179
+ if typed.strip().lower() != CONFIRM:
180
+ _print("Nothing was deleted.")
181
+ return 1
182
+ deleted = store.clear()
183
+ _print(f"Deleted everything Mobster remembered ({deleted['facts']} "
184
+ f"{'thing' if deleted['facts'] == 1 else 'things'}).")
185
+ return 0
186
+
187
+
188
+ def _export(store, args):
189
+ text = (json.dumps(store.export_json(), ensure_ascii=False, indent=2) + "\n"
190
+ if args.json else store.export_markdown())
191
+ if args.output:
192
+ try:
193
+ Path(args.output).expanduser().write_text(text, encoding="utf-8")
194
+ except OSError as error:
195
+ return _fail(f"Couldn't write {args.output}: {error.strerror or error}.", 1)
196
+ _print(f"Saved to {args.output}")
197
+ return 0
198
+ try:
199
+ sys.stdout.write(text)
200
+ sys.stdout.flush()
201
+ except BrokenPipeError:
202
+ pass
203
+ return 0
204
+
205
+
206
+ COMMANDS = {"list": _list, "add": _add, "rm": _remove, "forget": _remove, "keep": _keep, "pin": _pin(True),
207
+ "unpin": _pin(False), "clear": _clear, "export": _export}
@@ -0,0 +1,191 @@
1
+ """Suggestions to remember something, read out of what the person wrote (SPEC §3.3 F3).
2
+
3
+ Deterministic: no model call, so no prompt changes and no cost. It looks for the few ways people say something worth
4
+ keeping, sentence by sentence:
5
+
6
+ - "remember (that) …", "don't forget (that) …", "keep in mind …" -> the rest, as they said it
7
+ - "my X is Y" -> "My X is Y" (X up to four words; never a passing state such as "my battery
8
+ is low", never inside a question, never inside a task such as "text Sam that
9
+ my train is late")
10
+ - "I always / never / usually / prefer …", "I'd rather …" -> as said (a preference, so it's suggested pinned)
11
+ - "call me …" -> "Call me …" (suggested pinned)
12
+
13
+ A suggestion is only that: nothing is stored until the person accepts it (store.MemoryStore.resolve_proposal).
14
+ """
15
+
16
+ import re
17
+ from dataclasses import dataclass
18
+
19
+ from ..secret_filter import is_secret
20
+
21
+ MAX_PER_TEXT = 2
22
+ MIN_CHARS = 8
23
+
24
+ _BREAK = re.compile(r"[.!?;]+\s+|\n+")
25
+ # The word right before a full stop. Searched in a short window before the break (the lookbehind still sees the text
26
+ # before it), so a message full of "Dr. Dr. Dr." stays linear: a 4,000-character one took half a second before.
27
+ _LAST_WORD = re.compile(r"(?<![\w.])([\w.]+)[.]$")
28
+ _WORD_WINDOW = 24
29
+ _ABBREVIATIONS = {"dr", "mr", "mrs", "ms", "mx", "prof", "st", "ave", "rd", "blvd", "apt", "no", "vs", "etc", "jr",
30
+ "sr", "mt", "ft", "e.g", "i.e", "approx", "dept", "inc", "ltd", "co"}
31
+ _REMEMBER = re.compile(r"^(?:(?:please|pls|and|also|oh|ok|okay|so|hey)[,\s]+)*"
32
+ r"(?:(?:can|could|would|will) you\s+)?(?:please\s+)?"
33
+ r"(?:remember|don'?t forget|don’t forget|do not forget|keep in mind)\b[,:]?\s*"
34
+ r"(?:that\s+)?(?P<rest>.+)$", re.I)
35
+ _MY_IS = re.compile(r"\bmy\s+(?P<thing>(?:[\w'’-]+\s+){0,3}?[\w'’-]+)\s+(?P<verb>is|are)\s+(?P<value>.+)$", re.I)
36
+ _PREFERENCE = re.compile(r"\b(?P<rest>I(?:\s+(?:always|never|usually|prefer|would rather)|['’]d rather)\s+"
37
+ r"(?P<next>[\w'’-]+).*)$", re.I)
38
+ _CALL_ME = re.compile(r"\b(?i:call me)\s+(?P<name>[A-Z][\w'’-]*(?:\s+[A-Z][\w'’-]*)?)")
39
+
40
+ _PASSING = {"battery", "phone", "iphone", "screen", "order", "package", "delivery", "flight", "train", "bus",
41
+ "ride", "uber", "car", "wifi", "wi-fi", "internet", "connection", "data", "storage", "volume",
42
+ "alarm", "timer", "meeting", "call", "inbox", "cart", "code", "password", "passcode", "pin", "otp",
43
+ "account", "app", "message", "email", "text", "reply", "question", "answer", "task", "request",
44
+ "food", "coffee", "lunch", "dinner", "breakfast", "day", "week", "morning", "night", "plan", "plans",
45
+ "schedule", "calendar", "location", "card", "balance", "bill", "payment", "verification", "turn",
46
+ "time", "guess", "point", "bad", "fault", "pleasure", "problem", "issue", "mistake", "head", "back"}
47
+ _PASSING_VALUE = re.compile(r"^(?:low|dead|dying|charging|broken|late|early|running|on|off|full|empty|down|up|"
48
+ r"out|here|there|ready|done|gone|lost|stuck|frozen|slow|fast|open|closed|busy|free|"
49
+ r"almost|nearly|about|now|today|tomorrow|tonight|still|already|not|being|getting|"
50
+ r"going|coming|at\s+\d|\d+\s*%|\d+\s*percent)\b", re.I)
51
+ _PRONOUN_VALUE = re.compile(r"^(?:it|this|that|these|those|them|him|her)\b", re.I)
52
+ _QUESTION_START = re.compile(r"^(?:what|when|where|who|whom|which|why|how|is|are|does|do|did|can|could|would|"
53
+ r"should|will|whats|what's)\b", re.I)
54
+ _COMMAND = re.compile(r"^(?:text|send|message|call|email|reply|tell|ask|book|buy|order|post|delete|open|find|check|"
55
+ r"show|look|search|set|turn|add|remind|schedule|play|go|get|make|write|create|share|pay|move|"
56
+ r"copy|read|say|let)\b", re.I)
57
+ _PAST = re.compile(r"^(?:\w+ed|got|saw|heard|went|did|had|was|were|said|sent|made|knew|thought|told|took|came|"
58
+ r"found|gave|left|read|ran|bought|paid|met|forgot|lost|won|ate|drank|wrote|spoke)$", re.I)
59
+ _WHEN_WORDS = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "tomorrow", "today",
60
+ "tonight", "later", "back", "asap", "when", "if", "after", "before", "at", "on", "in", "around"}
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class Candidate:
65
+ text: str
66
+ pin: bool = False # a preference or a name: it helps in every task, so it's suggested pinned
67
+
68
+
69
+ def _tidy(text):
70
+ text = " ".join(text.split()).strip(" ,;:-–—\"'“”‘’")
71
+ text = re.sub(r"[.!]+$", "", text).strip()
72
+ if text and text[0].islower():
73
+ text = text[0].upper() + text[1:]
74
+ return text
75
+
76
+
77
+ def _inside_task(before):
78
+ """The words before a match make it part of a task or a quote ("Text Sam that …", "Tell her I always …")."""
79
+ before = before.strip(" ,")
80
+ if not before:
81
+ return False
82
+ words = re.sub(r"^(?:(?:please|pls|and|also|oh|ok|okay|so|hey|then|now)[,\s]+)*", "", before, flags=re.I)
83
+ return bool(_COMMAND.match(words) or re.search(r"\b(?:that|say|says|said|saying|tell|telling|asks?)$", before,
84
+ re.I))
85
+
86
+
87
+ def _sentences(text):
88
+ start = 0
89
+ for found in _BREAK.finditer(text):
90
+ word = _LAST_WORD.search(text, max(start, found.start() - _WORD_WINDOW), found.start() + 1)
91
+ if found.group().startswith(".") and word and (word.group(1).lower() in _ABBREVIATIONS
92
+ or re.fullmatch(r"[A-Z]", word.group(1))):
93
+ continue
94
+ part = text[start:found.start() + (0 if found.group()[0] == "\n" else 1)].strip()
95
+ start = found.end()
96
+ if part:
97
+ yield part
98
+ part = text[start:].strip()
99
+ if part:
100
+ yield part
101
+
102
+
103
+ def _remember(sentence):
104
+ found = _REMEMBER.match(sentence)
105
+ if not found:
106
+ return None
107
+ rest = found.group("rest")
108
+ # "remember to buy milk" is a reminder, not something about the person; a question asks, it doesn't tell.
109
+ if re.match(r"^to\s", rest, re.I) or sentence.rstrip().endswith("?"):
110
+ return None
111
+ return Candidate(_tidy(rest), pin=bool(_PREFERENCE.match(rest.strip()) or _CALL_ME.match(rest.strip())))
112
+
113
+
114
+ def _call_me(sentence):
115
+ found = _CALL_ME.search(sentence)
116
+ if not found or _inside_task(sentence[:found.start()]):
117
+ return None
118
+ name = found.group("name")
119
+ if name.split()[0].lower() in _WHEN_WORDS:
120
+ return None
121
+ return Candidate(f"Call me {name}", pin=True)
122
+
123
+
124
+ def _preference(sentence):
125
+ found = _PREFERENCE.search(sentence)
126
+ if not found or _inside_task(sentence[:found.start()]) or _PAST.match(found.group("next")):
127
+ return None
128
+ return Candidate(_tidy(found.group("rest")), pin=True)
129
+
130
+
131
+ def _my_is(sentence):
132
+ found = _MY_IS.search(sentence)
133
+ if not found or _inside_task(sentence[:found.start()]):
134
+ return None
135
+ thing, verb, value = found.group("thing"), found.group("verb").lower(), found.group("value").strip()
136
+ words = [re.sub(r"['’]s$", "", word) for word in thing.lower().split()]
137
+ lasting = any(word in ("favorite", "favourite", "usual", "go-to", "regular", "preferred") for word in words)
138
+ if not lasting and any(word in _PASSING for word in words):
139
+ return None
140
+ if _PASSING_VALUE.match(value) or _PRONOUN_VALUE.match(value) or len(value) < 2:
141
+ return None
142
+ return Candidate(_tidy(f"My {thing} {verb} {value}"))
143
+
144
+
145
+ def _from_sentence(sentence):
146
+ """At most one candidate per sentence: the clearest of the patterns."""
147
+ remembered = _remember(sentence)
148
+ if remembered is not None or _REMEMBER.match(sentence):
149
+ return remembered
150
+ if sentence.rstrip().endswith("?") or _QUESTION_START.match(sentence):
151
+ return None
152
+ return _call_me(sentence) or _preference(sentence) or _my_is(sentence)
153
+
154
+
155
+ def candidates(text):
156
+ """Up to two suggestions from ``text`` (a goal, or a message sent while a task worked); never a secret."""
157
+ if not isinstance(text, str) or not text.strip():
158
+ return []
159
+ out, seen = [], set()
160
+ for sentence in _sentences(text[:4000]):
161
+ candidate = _from_sentence(sentence)
162
+ if candidate is None:
163
+ continue
164
+ if not MIN_CHARS <= len(candidate.text) <= 300 or is_secret(candidate.text):
165
+ continue
166
+ key = candidate.text.lower()
167
+ if key in seen:
168
+ continue
169
+ seen.add(key)
170
+ out.append(candidate)
171
+ if len(out) >= MAX_PER_TEXT:
172
+ break
173
+ return out
174
+
175
+
176
+ _JOINED_TASK = re.compile(r"(?:,|;|\band\b|\bthen\b|\balso\b)\s+(?:please\s+)?" + _COMMAND.pattern[1:], re.I)
177
+
178
+
179
+ def remember_only(text):
180
+ """What to remember when ``text`` asks only that, else None; a secret comes back too, so the caller refuses it."""
181
+ if not isinstance(text, str) or not text.strip() or len(text) > 600:
182
+ return None
183
+ found = []
184
+ for sentence in _sentences(text):
185
+ candidate = _remember(sentence)
186
+ if candidate is None or _JOINED_TASK.search(sentence[_REMEMBER.match(sentence).start("rest"):]):
187
+ return None
188
+ if len(candidate.text) < 3:
189
+ return None
190
+ found.append(candidate)
191
+ return found or None
@@ -0,0 +1,100 @@
1
+ """Suggestions to remember something, from what a person types (SPEC §3.3 F3).
2
+
3
+ A run listener (harness_api.register_run_listener): it reads the goal of a task a person started (the Mac app, the
4
+ terminal UI, `mobster chat`) and the messages they send while it works, never text from another agent (MCP, the HTTP
5
+ API) or from a screen. Each suggestion goes to the conversation as a ``memory_proposal`` item when the task belongs
6
+ to one, and on the bus topic ``memory``. Nothing is remembered until the person accepts it.
7
+
8
+ It runs on the listeners' own thread, so the store's file never holds up a task.
9
+ """
10
+
11
+ import logging
12
+ from collections.abc import Mapping
13
+
14
+ from .. import harness_api
15
+ from . import extract
16
+ from .model import SCOPE_ID, WORKFLOW_ID
17
+ from .store import default_store
18
+
19
+ log = logging.getLogger("mobster.memory")
20
+ ITEM_KIND = "memory_proposal"
21
+ MESSAGE_SOURCES = ("app", "tui", "cli", "voice")
22
+
23
+
24
+ def proposal_scope(run):
25
+ """Only immutable, admitted identities may narrow an automatic suggestion."""
26
+ account = getattr(run, "account_context", None)
27
+ if account is not None and not isinstance(account, Mapping):
28
+ return None
29
+ profile = account.get("id") if account is not None else None
30
+ workflow = getattr(run, "workflow_id", None)
31
+ if account is not None and (not isinstance(profile, str) or not SCOPE_ID.fullmatch(profile)):
32
+ return None
33
+ if workflow is not None and (not isinstance(workflow, str) or not WORKFLOW_ID.fullmatch(workflow)):
34
+ return None
35
+ if account is None and (getattr(run, "extras", None) or {}).get("account") is not None:
36
+ return None
37
+ if profile and workflow:
38
+ return f"profile:{profile}/workflow:{workflow}"
39
+ if profile:
40
+ return f"profile:{profile}"
41
+ if workflow:
42
+ return f"workflow:{workflow}"
43
+ return "global"
44
+
45
+
46
+ def publish(event):
47
+ """On the bus topic ``memory`` (never raises)."""
48
+ try:
49
+ from .. import eventbus
50
+ eventbus.bus.publish("memory", event)
51
+ except Exception: # noqa: BLE001
52
+ log.exception("memory: bus publish failed")
53
+
54
+
55
+ def thread_item(proposal):
56
+ """The thread item that shows a suggestion (conversations' sink; chat-ui renders it through the registry)."""
57
+ return {"kind": ITEM_KIND, "proposalId": proposal["id"], "text": proposal["text"], "scope": proposal["scope"],
58
+ "app": proposal["app"], "pin": proposal["pin"], "status": proposal["status"], "runId": proposal["runId"],
59
+ "expiresAt": proposal["expiresAt"]}
60
+
61
+
62
+ class ProposalListener:
63
+ """on_created reads the goal; on_event reads ``user_message`` (a message sent while the task works)."""
64
+
65
+ def __init__(self, store_factory=default_store):
66
+ self.store_factory = store_factory
67
+
68
+ def on_created(self, run, runtime):
69
+ self.consider(run, getattr(run, "goal", None))
70
+
71
+ def on_event(self, run, event):
72
+ if event.get("event") == "user_message" and event.get("source") in MESSAGE_SOURCES:
73
+ self.consider(run, event.get("text"))
74
+
75
+ def consider(self, run, text):
76
+ if getattr(run, "origin", "api") not in harness_api.INTERACTIVE:
77
+ return []
78
+ found = extract.candidates(text)
79
+ if not found:
80
+ return []
81
+ scope = proposal_scope(run)
82
+ if scope is None:
83
+ return []
84
+ store = self.store_factory()
85
+ if store is None or not store.settings().get("suggest", True):
86
+ return []
87
+ thread_id = (getattr(run, "extras", None) or {}).get("threadId")
88
+ made = []
89
+ for candidate in found:
90
+ proposal = store.propose(candidate.text, scope, thread_id=thread_id, run_id=getattr(run, "id", None),
91
+ pin=candidate.pin)
92
+ if proposal is None:
93
+ continue
94
+ made.append(proposal)
95
+ if thread_id:
96
+ item = harness_api.post_thread_item(thread_id, thread_item(proposal))
97
+ if isinstance(item, dict) and item.get("id"):
98
+ store.set_proposal_item(proposal["id"], item["id"])
99
+ publish({"event": "proposal_created", "proposal": proposal})
100
+ return made
@@ -0,0 +1,144 @@
1
+ """What a fact is: its kinds, sources and scopes, the checks every text passes, and the fingerprint that dedupes.
2
+
3
+ A fact has a kind (what it is), a source (who or what it came from) and a scope (where it applies). Scopes are
4
+ "global" (everywhere), "app:<bundle id>", "phone:<id>", "project:<id>", "profile:<id>", "workflow:<id>"
5
+ and "profile:<id>/workflow:<id>"; the API also takes "everywhere" for "global". A stored unknown scope
6
+ never matches a task.
7
+
8
+ Never secrets (``secret_filter.is_secret``), whoever writes. Text read from a screen is data, never an instruction, so
9
+ only the person (source ``user``) or their profile (``profile``) may write a rule or a preference; what a run, a
10
+ check, a lesson or the repo teaches is a path, a place, a structure, a value, an outcome, a device or a cost.
11
+ """
12
+
13
+ import hashlib
14
+ import re
15
+
16
+ from ..api_errors import APIError
17
+ from ..secret_filter import is_secret
18
+
19
+ FACT_CHARS = 300
20
+
21
+ KINDS = ("path", "place", "value", "preference", "rule", "structure", "device", "outcome", "cost", "repo")
22
+ SOURCES = ("run", "lesson", "check", "repo", "profile", "user")
23
+ LEARNED_SOURCES = tuple(source for source in SOURCES if source != "user")
24
+ INSTRUCTIONAL = frozenset({"preference", "rule"}) # these read as orders, so only a person's words may be them
25
+ INSTRUCTORS = frozenset({"user", "profile"})
26
+ SCOPE_KINDS = ("profile_workflow", "profile", "workflow", "project", "app", "phone")
27
+ UNPROMPTED = frozenset({"cost"}) # kept for estimates and the page; never sent to the model
28
+
29
+ BUNDLE = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]*(?:\.[A-Za-z0-9-]+)+")
30
+ SCOPE_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._@~+-]{0,119}")
31
+ WORKFLOW_ID = re.compile(r"[0-9a-f]{12}")
32
+ REF_KEYS = ("runId", "lessonId", "checkPath", "repo", "profileId")
33
+
34
+ SECRET = "Mobster doesn't remember passwords, codes or card numbers."
35
+
36
+
37
+ def normalize(text):
38
+ """For spotting the same thing said twice: lowercase, single spaces, no closing punctuation."""
39
+ return " ".join(str(text).lower().split()).rstrip(" .!;,")
40
+
41
+
42
+ def digest(text):
43
+ return hashlib.sha256(normalize(text).encode()).hexdigest()[:32]
44
+
45
+
46
+ def clean_text(text):
47
+ """Whitespace collapsed; APIError 400 for an empty, long or secret text."""
48
+ if not isinstance(text, str):
49
+ raise APIError("Write what Mobster should remember.", 400, "invalid_text")
50
+ text = " ".join(text.split())
51
+ if not text:
52
+ raise APIError("Write what Mobster should remember.", 400, "invalid_text")
53
+ if len(text) > FACT_CHARS:
54
+ raise APIError(f"Keep it to {FACT_CHARS} characters.", 400, "too_long", limit=FACT_CHARS)
55
+ if is_secret(text):
56
+ raise APIError(SECRET, 400, "secret")
57
+ return text
58
+
59
+
60
+ def clean_scope(scope):
61
+ """Canonical scope for a fact. Profile/workflow conjunctions require both admitted identities."""
62
+ if scope in (None, "", "global", "everywhere"):
63
+ return "global"
64
+ if isinstance(scope, str) and len(scope) <= 160 and ":" in scope:
65
+ kind, _, ident = scope.partition(":")
66
+ if kind == "profile" and "/workflow:" in ident:
67
+ profile, _, workflow = ident.partition("/workflow:")
68
+ if SCOPE_ID.fullmatch(profile) and WORKFLOW_ID.fullmatch(workflow):
69
+ return scope
70
+ elif kind == "workflow" and WORKFLOW_ID.fullmatch(ident):
71
+ return scope
72
+ elif kind in ("profile", "project", "app", "phone"):
73
+ if (BUNDLE if kind == "app" else SCOPE_ID).fullmatch(ident):
74
+ return scope
75
+ raise APIError("Choose everywhere, or one app, phone, project, profile or workflow.", 400, "invalid_scope")
76
+
77
+
78
+ def scope_parts(scope):
79
+ """Parse stored scopes without turning unknown or damaged data into global memory."""
80
+ if scope == "global":
81
+ return "global", None
82
+ if not isinstance(scope, str) or not scope:
83
+ return "unknown", str(scope) if scope is not None else None
84
+ try:
85
+ if clean_scope(scope) != scope:
86
+ return "unknown", scope
87
+ except APIError:
88
+ return "unknown", str(scope) if scope is not None else None
89
+ kind, _, ident = scope.partition(":")
90
+ return ("profile_workflow" if kind == "profile" and "/workflow:" in ident else kind), ident
91
+
92
+
93
+ def clean_kind(kind, default="value"):
94
+ kind = default if kind in (None, "") else kind
95
+ if kind not in KINDS:
96
+ raise APIError(f"A fact's kind is one of: {', '.join(KINDS)}.", 400, "invalid_kind")
97
+ return kind
98
+
99
+
100
+ def clean_source(source, kind):
101
+ """The source, checked against the kind: only a person's words may be a rule or a preference."""
102
+ if source not in SOURCES:
103
+ raise APIError(f"A fact's source is one of: {', '.join(SOURCES)}.", 400, "invalid_source")
104
+ if kind in INSTRUCTIONAL and source not in INSTRUCTORS:
105
+ raise APIError("Only you or a profile can set a rule or a preference. Mobster doesn't take orders from what it "
106
+ "reads on a screen.", 400, "not_an_instruction")
107
+ return source
108
+
109
+
110
+ def clean_ref(ref):
111
+ """Where a fact came from: runId, lessonId, checkPath, repo or profileId, each a short string."""
112
+ if not ref:
113
+ return {}
114
+ if not isinstance(ref, dict):
115
+ raise APIError("A source reference is an object.", 400, "invalid_ref")
116
+ out = {}
117
+ for key, value in ref.items():
118
+ if key not in REF_KEYS or not isinstance(value, str) or not value or len(value) > 300:
119
+ raise APIError(f"A source reference is one of: {', '.join(REF_KEYS)}.", 400, "invalid_ref")
120
+ out[key] = value
121
+ return out
122
+
123
+
124
+ def clean_confidence(value, default=1.0):
125
+ if value is None:
126
+ return default
127
+ if isinstance(value, bool) or not isinstance(value, (int, float)) or not 0 <= value <= 1:
128
+ raise APIError("Confidence is a number from 0 to 1.", 400, "invalid_confidence")
129
+ return float(value)
130
+
131
+
132
+ def clean_key(key):
133
+ if key is None:
134
+ return None
135
+ if not isinstance(key, str) or not key.strip() or len(key) > 200:
136
+ raise APIError("A fact's key is a short string.", 400, "invalid_key")
137
+ return " ".join(key.split()).lower()
138
+
139
+
140
+ def fingerprint(scope, text, key=None):
141
+ """The same fact told twice is one fact. With a ``key`` (the thing a fact is about: "cost:com.x:read reels") a
142
+ changed value updates the fact in place, and forgetting it stops the new value too."""
143
+ basis = f"key\0{key}" if key else f"text\0{normalize(text)}"
144
+ return hashlib.sha256(f"{scope}\0{basis}".encode()).hexdigest()[:32]