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,396 @@
1
+ """The Microsoft 365 connector's tools, answered by Mobster with Microsoft Graph and your sign-in (a delegated token),
2
+ because Microsoft runs no MCP server for Outlook, Calendar, OneDrive, Contacts and Teams chats that a Mobster app can
3
+ sign in to."""
4
+
5
+ import json
6
+ import re
7
+ import time
8
+ from datetime import datetime, timedelta, timezone
9
+ from urllib.parse import quote, urlencode
10
+
11
+ from ..outlets import net, webhook
12
+ from . import client as mcp
13
+ from .api_session import ApiSession, Refusal, count_schema, required_text, short, tool_spec
14
+
15
+ API = "https://graph.microsoft.com/v1.0"
16
+ NAME = "Microsoft 365"
17
+ LIMIT_TOP = 50
18
+ BODY_CHARS = 8000 # what is kept of one message's text
19
+ TEXT_CHARS = 2000 # what is kept of one chat message's text
20
+ SEND_CHARS = 28_000 # Teams takes about 28 KB of message
21
+ READ_LIMIT = 4 * 1024 * 1024
22
+ ID = re.compile(r"[A-Za-z0-9_=:.@!%+\-]{1,512}")
23
+ EMAIL = re.compile(r"[^@\s<>,;\"']{1,64}@[A-Za-z0-9.-]{1,200}\.[A-Za-z]{2,24}")
24
+ WHEN = re.compile(r"\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?(Z|[+-]\d{2}:\d{2})?")
25
+ ZONE = re.compile(r"[A-Za-z0-9_/ +.\-]{1,60}")
26
+ FOLDERS = ("inbox", "sentitems", "drafts", "archive", "deleteditems")
27
+ DENIED = frozenset({"ErrorAccessDenied", "Authorization_RequestDenied", "AccessDenied",
28
+ "ErrorInsufficientPermissionsToDelegate", "Forbidden"})
29
+ REFUSED = {
30
+ "ErrorItemNotFound": "Microsoft has no item with that id. Use an id from a list or a search",
31
+ "ResourceNotFound": "Microsoft has nothing at that id. Use an id from a list or a search",
32
+ "ErrorInvalidIdMalformed": "That id isn't one Microsoft gave. Use an id from a list or a search",
33
+ "ErrorQuotaExceeded": "Your mailbox is full",
34
+ "ErrorMessageSizeExceeded": "The message is too big to send. Shorten it",
35
+ }
36
+
37
+
38
+ ADDRESSES = {"type": "array", "items": {"type": "string"}, "minItems": 1, "maxItems": 20,
39
+ "description": "Email addresses."}
40
+ WHEN_FIELD = {"type": "string", "description": "A date or a date and time, such as 2026-10-20 or 2026-10-20T14:00:00Z."}
41
+ TOOLS = (
42
+ tool_spec("list_mail", "List Outlook mail",
43
+ "Your Outlook messages, newest first: each one's id, subject, sender, time, read state and a preview. "
44
+ "Give query to search instead (Outlook's search words work: from:sam, subject:invoice); a search is "
45
+ "ordered by match.",
46
+ {"folder": {"type": "string", "enum": list(FOLDERS), "description": "inbox if left out. Not used by a "
47
+ "search, which covers all folders."},
48
+ "query": {"type": "string", "description": "Words to search for."},
49
+ "limit": count_schema(LIMIT_TOP, 10)}),
50
+ tool_spec("read_message", "Read an Outlook message",
51
+ "One message's sender, recipients, subject, time and text, from an id that list_mail gave.",
52
+ {"id": {"type": "string", "description": "The message's id."}}, required=("id",)),
53
+ tool_spec("list_events", "List my calendar events",
54
+ "Your calendar between two times, in time order: each event's id, subject, start, end (UTC), place, "
55
+ "organizer and attendees.",
56
+ {"start": WHEN_FIELD | {"description": "From when. Now if left out."},
57
+ "end": WHEN_FIELD | {"description": "Until when. 7 days after start if left out."},
58
+ "limit": count_schema(LIMIT_TOP, 25)}),
59
+ tool_spec("create_event", "Create a calendar event",
60
+ "Add an event to your calendar. With attendees, Outlook emails them an invitation as you.",
61
+ {"subject": {"type": "string"}, "start": {"type": "string",
62
+ "description": "Local time, 2026-10-20T14:00:00."},
63
+ "end": {"type": "string", "description": "Local time, 2026-10-20T15:00:00."},
64
+ "timezone": {"type": "string", "description": "A time zone name such as "
65
+ "Europe/London; UTC if left out."},
66
+ "attendees": ADDRESSES, "location": {"type": "string"}, "body": {"type": "string"}},
67
+ required=("subject", "start", "end"), read_only=False),
68
+ tool_spec("search_files", "Search OneDrive files",
69
+ "Your OneDrive files and folders matching words in their name or content: each one's id, name, link, "
70
+ "size and time changed.",
71
+ {"query": {"type": "string", "description": "What to search for."}, "limit": count_schema(LIMIT_TOP, 10)},
72
+ required=("query",)),
73
+ tool_spec("list_contacts", "List my contacts",
74
+ "Your Outlook contacts: each one's name, email addresses, company and phone. Give query to search them.",
75
+ {"query": {"type": "string", "description": "A name or an address to search for."},
76
+ "limit": count_schema(LIMIT_TOP, 25)}),
77
+ tool_spec("list_chats", "List my Teams chats",
78
+ "Your Teams chats, most recently active first: each one's id, topic, kind and last activity.",
79
+ {"limit": count_schema(LIMIT_TOP, 20)}),
80
+ tool_spec("read_chat", "Read a Teams chat",
81
+ "A Teams chat's latest messages, newest first: each one's sender, time and text.",
82
+ {"chat_id": {"type": "string", "description": "A chat's id from list_chats."},
83
+ "limit": count_schema(LIMIT_TOP, 20)},
84
+ required=("chat_id",)),
85
+ tool_spec("send_mail", "Send an email",
86
+ "Send an email as you from Outlook, in plain text. A copy goes in Sent Items.",
87
+ {"to": ADDRESSES, "cc": ADDRESSES, "subject": {"type": "string"}, "body": {"type": "string"}},
88
+ required=("to", "subject", "body"), read_only=False),
89
+ tool_spec("send_chat_message", "Send a Teams chat message",
90
+ "Post a message as you in a Teams chat, in plain text.",
91
+ {"chat_id": {"type": "string", "description": "A chat's id from list_chats."},
92
+ "text": {"type": "string"}},
93
+ required=("chat_id", "text"), read_only=False),
94
+ )
95
+ WRITES = frozenset(t["name"] for t in TOOLS if not t["annotations"]["readOnlyHint"])
96
+
97
+
98
+ def connect(*, token=None, timeout=mcp.CONNECT_TIMEOUT, name=NAME, call=None):
99
+ """A ready GraphSession for ``token``. AuthNeeded without one, or when Microsoft refuses it."""
100
+ session = GraphSession(token, name=name, call=call)
101
+ session.initialize(timeout=timeout)
102
+ return session
103
+
104
+
105
+ class GraphSession(ApiSession):
106
+ """The MCP session's surface (client.Session) over Microsoft Graph."""
107
+
108
+ NAME, TOOLS = NAME, TOOLS
109
+
110
+ def _api(self, method, path, deadline, query=None, body=None, headers=None):
111
+ """Graph's JSON answer ({} for a 2xx with none) or a ``Refusal``. Raises AuthNeeded, NotSent and, for a write,
112
+ OutcomeUnknown (a 5xx or no answer: it may have happened, and it's never sent again)."""
113
+ if not self.token:
114
+ raise mcp.AuthNeeded(f"{self.name} isn't signed in.")
115
+ write = method != "GET"
116
+ url = f"{API}{path}" + (f"?{urlencode(query, quote_via=quote)}" if query else "")
117
+ send = {"Authorization": f"Bearer {self.token}", "Accept": "application/json", "User-Agent": webhook.USER_AGENT,
118
+ **(headers or {})}
119
+ data = None
120
+ if body is not None:
121
+ data = json.dumps(body, ensure_ascii=False).encode()
122
+ send["Content-Type"] = "application/json"
123
+ try:
124
+ reply = self.call(method, url, data, send, timeout=max(1.0, deadline - time.monotonic()),
125
+ read_limit=READ_LIMIT)
126
+ except net.NetError as error:
127
+ if error.unsent:
128
+ raise mcp.NotSent(error.message) from None
129
+ if write:
130
+ raise mcp.OutcomeUnknown(f"{error.message} It may have gone through: check Microsoft 365 before you do "
131
+ "it again.") from None
132
+ raise mcp.OutcomeUnknown(error.message) from None
133
+ if reply.status == 429:
134
+ wait = reply.retry_after if str(reply.retry_after or "").isdigit() else "a few"
135
+ return Refusal(f"Microsoft asked Mobster to wait {wait} seconds. Try again then.")
136
+ if reply.status >= 500:
137
+ if write:
138
+ raise mcp.OutcomeUnknown(f"Microsoft answered {reply.status}. It may have gone through: check "
139
+ "Microsoft 365 before you do it again.")
140
+ return Refusal(f"Microsoft answered {reply.status}. Try again in a minute.")
141
+ try:
142
+ answer = json.loads(reply.body.decode("utf-8")) if reply.body else {}
143
+ except (ValueError, UnicodeDecodeError):
144
+ answer = None
145
+ if 200 <= reply.status < 300:
146
+ if isinstance(answer, dict):
147
+ return answer
148
+ # A write Microsoft answered 2xx happened, whatever its body says; a read has nothing to show.
149
+ return {} if write else Refusal("Microsoft's answer couldn't be read. Try again in a minute.")
150
+ error = answer.get("error") if isinstance(answer, dict) else None
151
+ code = str(error.get("code") if isinstance(error, dict) else "")[:60]
152
+ if reply.status == 401:
153
+ raise mcp.AuthNeeded(f"Microsoft signed Mobster out ({code or 401}). Sign in to {self.name} again in "
154
+ "Settings › Connectors.")
155
+ if reply.status == 403 or code in DENIED:
156
+ return Refusal(f"Microsoft refused ({code or 403}). Your {self.name} sign-in can't do this: sign in again "
157
+ f"in Settings › Connectors and allow what Microsoft asks, or ask your admin.")
158
+ if code in REFUSED:
159
+ return Refusal(f"Microsoft refused ({code}). {REFUSED[code]}.")
160
+ said = re.sub(r"\s+", " ", str(error.get("message") or "") if isinstance(error, dict) else "")[:200]
161
+ return Refusal(f"Microsoft refused ({code or reply.status})." + (f" {said}" if said else ""))
162
+
163
+ def initialize(self, timeout=mcp.CONNECT_TIMEOUT):
164
+ """GET /me: the token works (it needs only User.Read). AuthNeeded or NotSent when it doesn't."""
165
+ answer = self._api("GET", "/me", time.monotonic() + timeout, {"$select": "displayName,userPrincipalName"})
166
+ if isinstance(answer, Refusal):
167
+ raise mcp.NotSent(answer.message)
168
+ self.server_info = {"name": NAME, "user": str(answer.get("userPrincipalName") or "")[:100]}
169
+ self.ready = True
170
+ return {"serverInfo": self.server_info}
171
+
172
+ def ping(self, timeout=mcp.PING_TIMEOUT):
173
+ if not self.ready:
174
+ raise mcp.NotSent(f"{self.name} isn't connected.")
175
+ answer = self._api("GET", "/me", time.monotonic() + timeout, {"$select": "id"})
176
+ if isinstance(answer, Refusal):
177
+ raise mcp.NotSent(answer.message)
178
+ return True
179
+
180
+ def _list_mail(self, args, deadline):
181
+ top = _limit(args.get("limit"), 10)
182
+ select = "id,subject,from,receivedDateTime,isRead,bodyPreview,webLink"
183
+ query = args.get("query")
184
+ if query is not None:
185
+ words = " ".join(required_text(query, "query", 300).replace('"', " ").split())
186
+ answer = self._api("GET", "/me/messages", deadline,
187
+ {"$search": f'"{words}"', "$top": top, "$select": select})
188
+ else:
189
+ folder = _choice(args.get("folder"), FOLDERS, "folder")
190
+ answer = self._api("GET", f"/me/mailFolders/{folder}/messages", deadline,
191
+ {"$top": top, "$orderby": "receivedDateTime desc", "$select": select})
192
+ if isinstance(answer, Refusal):
193
+ return answer
194
+ return {"messages": [{"id": short(m.get("id"), 512), "subject": short(m.get("subject"), 300),
195
+ "from": _address(m.get("from")), "received": short(m.get("receivedDateTime"), 40),
196
+ "read": bool(m.get("isRead")), "preview": short(m.get("bodyPreview"), 300),
197
+ "link": short(m.get("webLink"), 500)}
198
+ for m in _items(answer)],
199
+ "more": "@odata.nextLink" in answer}
200
+
201
+ def _read_message(self, args, deadline):
202
+ ident = _id(args.get("id"), "id")
203
+ answer = self._api("GET", f"/me/messages/{quote(ident, safe='')}", deadline,
204
+ {"$select": "id,subject,from,toRecipients,ccRecipients,receivedDateTime,body,webLink"},
205
+ headers={"Prefer": 'outlook.body-content-type="text"'})
206
+ if isinstance(answer, Refusal):
207
+ return answer
208
+ body = answer.get("body") if isinstance(answer.get("body"), dict) else {}
209
+ return {"id": short(answer.get("id"), 512), "subject": short(answer.get("subject"), 300),
210
+ "from": _address(answer.get("from")), "to": [_address(r) for r in answer.get("toRecipients") or ()],
211
+ "cc": [_address(r) for r in answer.get("ccRecipients") or ()],
212
+ "received": short(answer.get("receivedDateTime"), 40), "text": short(body.get("content"), BODY_CHARS),
213
+ "link": short(answer.get("webLink"), 500)}
214
+
215
+ def _list_events(self, args, deadline):
216
+ start = _when(args.get("start"), "start") or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
217
+ end = _when(args.get("end"), "end")
218
+ if end is None:
219
+ first = datetime.fromisoformat(_utc(start).replace("Z", "+00:00"))
220
+ end = (first + timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")
221
+ answer = self._api("GET", "/me/calendarView", deadline,
222
+ {"startDateTime": _utc(start), "endDateTime": _utc(end),
223
+ "$top": _limit(args.get("limit"), 25), "$orderby": "start/dateTime",
224
+ "$select": "id,subject,start,end,location,organizer,attendees,webLink,isCancelled"},
225
+ headers={"Prefer": 'outlook.timezone="UTC"'})
226
+ if isinstance(answer, Refusal):
227
+ return answer
228
+ return {"events": [{"id": short(e.get("id"), 512), "subject": short(e.get("subject"), 300),
229
+ "start": short((e.get("start") or {}).get("dateTime"), 40),
230
+ "end": short((e.get("end") or {}).get("dateTime"), 40),
231
+ "place": short((e.get("location") or {}).get("displayName"), 200),
232
+ "organizer": _address(e.get("organizer")),
233
+ "attendees": [_address(a) for a in (e.get("attendees") or ())[:20]],
234
+ "cancelled": bool(e.get("isCancelled")), "link": short(e.get("webLink"), 500)}
235
+ for e in _items(answer)],
236
+ "more": "@odata.nextLink" in answer}
237
+
238
+ def _search_files(self, args, deadline):
239
+ words = required_text(args.get("query"), "query", 300).replace("'", "''")
240
+ answer = self._api("GET", f"/me/drive/root/search(q='{quote(words, safe='')}')", deadline,
241
+ {"$top": _limit(args.get("limit"), 10),
242
+ "$select": "id,name,webUrl,size,lastModifiedDateTime,file,folder"})
243
+ if isinstance(answer, Refusal):
244
+ return answer
245
+ return {"files": [{"id": short(f.get("id"), 512), "name": short(f.get("name"), 300),
246
+ "kind": "folder" if f.get("folder") else "file", "size": f.get("size")
247
+ if isinstance(f.get("size"), int) else None,
248
+ "modified": short(f.get("lastModifiedDateTime"), 40), "link": short(f.get("webUrl"), 500)}
249
+ for f in _items(answer)],
250
+ "more": "@odata.nextLink" in answer}
251
+
252
+ def _list_contacts(self, args, deadline):
253
+ query = {"$top": _limit(args.get("limit"), 25),
254
+ "$select": "id,displayName,emailAddresses,companyName,mobilePhone"}
255
+ if args.get("query") is not None:
256
+ query["$search"] = '"' + " ".join(
257
+ required_text(args["query"], "query", 200).replace('"', " ").split()) + '"'
258
+ answer = self._api("GET", "/me/contacts", deadline, query)
259
+ if isinstance(answer, Refusal):
260
+ return answer
261
+ return {"contacts": [{"id": short(c.get("id"), 512), "name": short(c.get("displayName"), 200),
262
+ "emails": [short(a.get("address"), 200) for a in (c.get("emailAddresses") or ())[:5]
263
+ if isinstance(a, dict)],
264
+ "company": short(c.get("companyName"), 200), "phone": short(c.get("mobilePhone"), 40)}
265
+ for c in _items(answer)],
266
+ "more": "@odata.nextLink" in answer}
267
+
268
+ def _list_chats(self, args, deadline):
269
+ answer = self._api("GET", "/me/chats", deadline, {"$top": _limit(args.get("limit"), 20),
270
+ "$orderby": "lastMessagePreview/createdDateTime desc",
271
+ "$select": "id,topic,chatType,lastUpdatedDateTime"})
272
+ if isinstance(answer, Refusal):
273
+ return answer
274
+ return {"chats": [{"id": short(c.get("id"), 512), "topic": short(c.get("topic"), 200),
275
+ "kind": short(c.get("chatType"), 20), "updated": short(c.get("lastUpdatedDateTime"), 40)}
276
+ for c in _items(answer)],
277
+ "more": "@odata.nextLink" in answer}
278
+
279
+ def _read_chat(self, args, deadline):
280
+ chat = _id(args.get("chat_id"), "chat_id")
281
+ answer = self._api("GET", f"/me/chats/{quote(chat, safe='')}/messages", deadline,
282
+ {"$top": _limit(args.get("limit"), 20)})
283
+ if isinstance(answer, Refusal):
284
+ return answer
285
+ found = []
286
+ for m in _items(answer):
287
+ sender = (((m.get("from") or {}).get("user") or {}).get("displayName") if isinstance(m.get("from"), dict)
288
+ else None)
289
+ body = m.get("body") if isinstance(m.get("body"), dict) else {}
290
+ found.append({"id": short(m.get("id"), 80), "from": short(sender, 200),
291
+ "time": short(m.get("createdDateTime"), 40),
292
+ "text": short(re.sub(r"<[^>]+>", "", str(body.get("content") or ""))
293
+ if body.get("contentType") == "html"
294
+ else body.get("content"), TEXT_CHARS)})
295
+ return {"chatId": chat, "messages": found, "more": "@odata.nextLink" in answer}
296
+
297
+ def _send_mail(self, args, deadline):
298
+ message = {"subject": required_text(args.get("subject"), "subject", 300),
299
+ "body": {"contentType": "Text", "content": required_text(args.get("body"), "body", 100_000)},
300
+ "toRecipients": _recipients(args.get("to"), "to", required=True)}
301
+ copied = _recipients(args.get("cc"), "cc")
302
+ if copied:
303
+ message["ccRecipients"] = copied
304
+ answer = self._api("POST", "/me/sendMail", deadline, body={"message": message, "saveToSentItems": True})
305
+ return answer if isinstance(answer, Refusal) else {"sent": True}
306
+
307
+ def _create_event(self, args, deadline):
308
+ zone = args.get("timezone", "UTC")
309
+ if not isinstance(zone, str) or not ZONE.fullmatch(zone):
310
+ raise ValueError("timezone is a time zone name such as Europe/London")
311
+ event = {"subject": required_text(args.get("subject"), "subject", 300),
312
+ "start": {"dateTime": _local(args.get("start"), "start"), "timeZone": zone},
313
+ "end": {"dateTime": _local(args.get("end"), "end"), "timeZone": zone}}
314
+ if args.get("location") is not None:
315
+ event["location"] = {"displayName": required_text(args["location"], "location", 200)}
316
+ if args.get("body") is not None:
317
+ event["body"] = {"contentType": "Text", "content": required_text(args["body"], "body", 20_000)}
318
+ guests = _recipients(args.get("attendees"), "attendees")
319
+ if guests:
320
+ event["attendees"] = [{**g, "type": "required"} for g in guests]
321
+ answer = self._api("POST", "/me/events", deadline, body=event)
322
+ return answer if isinstance(answer, Refusal) else {"created": True, "id": short(answer.get("id"), 512),
323
+ "link": short(answer.get("webLink"), 500)}
324
+
325
+ def _send_chat_message(self, args, deadline):
326
+ chat = _id(args.get("chat_id"), "chat_id")
327
+ text = required_text(args.get("text"), "text", SEND_CHARS)
328
+ answer = self._api("POST", f"/me/chats/{quote(chat, safe='')}/messages", deadline,
329
+ body={"body": {"contentType": "text", "content": text}})
330
+ return (answer if isinstance(answer, Refusal)
331
+ else {"posted": True, "chatId": chat, "id": short(answer.get("id"), 80)})
332
+
333
+
334
+ def _items(answer):
335
+ return [item for item in answer.get("value") or () if isinstance(item, dict)]
336
+
337
+
338
+ def _address(value):
339
+ box = value.get("emailAddress") if isinstance(value, dict) else None
340
+ if not isinstance(box, dict):
341
+ return None
342
+ return {"name": short(box.get("name"), 200), "address": short(box.get("address"), 200)}
343
+
344
+
345
+ def _id(value, field):
346
+ text = required_text(value, field, 512)
347
+ if not ID.fullmatch(text):
348
+ raise ValueError(f"{field} is an id that a list or a search gave")
349
+ return text
350
+
351
+
352
+ def _limit(value, default):
353
+ if value is None:
354
+ return default
355
+ if type(value) is not int or not 1 <= value <= LIMIT_TOP:
356
+ raise ValueError(f"limit is a whole number from 1 to {LIMIT_TOP}")
357
+ return value
358
+
359
+
360
+ def _choice(value, choices, field):
361
+ if value is None:
362
+ return choices[0]
363
+ if value not in choices:
364
+ raise ValueError(f"{field} is {' or '.join(choices)}")
365
+ return value
366
+
367
+
368
+ def _recipients(value, field, required=False):
369
+ if value is None and not required:
370
+ return []
371
+ if not isinstance(value, list) or not value or len(value) > 20 or not all(isinstance(a, str) for a in value):
372
+ raise ValueError(f"{field} is a list of 1 to 20 email addresses")
373
+ found = [a.strip() for a in value]
374
+ if not all(EMAIL.fullmatch(a) for a in found):
375
+ raise ValueError(f"{field} has an address that isn't an email address")
376
+ return [{"emailAddress": {"address": a}} for a in found]
377
+
378
+
379
+ def _when(value, field):
380
+ if value is None:
381
+ return None
382
+ if not isinstance(value, str) or not WHEN.fullmatch(value.strip()):
383
+ raise ValueError(f"{field} is a date or a date and time, such as 2026-10-20T14:00:00Z")
384
+ return value.strip()
385
+
386
+
387
+ def _utc(value):
388
+ """A date or time as Graph's calendarView wants it: with a zone (UTC when it has none)."""
389
+ text = value if len(value) > 10 else value + "T00:00:00"
390
+ return text if re.search(r"(Z|[+-]\d{2}:\d{2})$", text) else text + "Z"
391
+
392
+
393
+ def _local(value, field):
394
+ if not isinstance(value, str) or not re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?", value.strip()):
395
+ raise ValueError(f"{field} is a local time such as 2026-10-20T14:00:00")
396
+ return value.strip()
@@ -0,0 +1,170 @@
1
+ """Import the MCP servers you already set up in Claude Desktop, Claude Code, Cursor or Codex: Settings ›
2
+ Connectors lists each one with an Add button (GET /api/connectors/imports), and Add makes it a connector (POST
3
+ /api/connectors/imports) without retyping its command, URL or keys."""
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+
9
+ from ..integrations import clients as agent_clients
10
+ from ..integrations import jsonc, textedit
11
+ from ..secret_filter import redact
12
+ from . import client as mcp
13
+
14
+ SOURCES = (("claude-desktop", agent_clients.ClaudeDesktop), ("claude-code", agent_clients.ClaudeCode),
15
+ ("cursor", agent_clients.Cursor), ("codex", agent_clients.Codex))
16
+ MAX_SERVERS = 60 # per file
17
+ MAX_BYTES = 4_000_000 # a config larger than this is skipped (~/.claude.json can grow; it holds history too)
18
+ SELF = ("mobster", "mobster-agent")
19
+
20
+
21
+ def _read(path):
22
+ try:
23
+ if not path.is_file() or path.stat().st_size > MAX_BYTES:
24
+ return None
25
+ return path.read_text(encoding="utf-8")
26
+ except (OSError, UnicodeDecodeError):
27
+ return None
28
+
29
+
30
+ def _servers(client, path):
31
+ """{name: entry} from one app's file, or {} when it has none or can't be read."""
32
+ text = _read(path)
33
+ if text is None:
34
+ return {}
35
+ try:
36
+ if isinstance(client, agent_clients.Codex):
37
+ servers = textedit.toml_loads(text).get("mcp_servers")
38
+ else:
39
+ try:
40
+ data = json.loads(text) # fast for a large ~/.claude.json
41
+ except ValueError:
42
+ data = jsonc.loads(text) # comments and trailing commas (Cursor allows them)
43
+ servers = data
44
+ for key in client.container:
45
+ servers = servers.get(key) if isinstance(servers, dict) else None
46
+ except (ValueError, jsonc.JsoncError, textedit.EditError):
47
+ return {}
48
+ if not isinstance(servers, dict):
49
+ return {}
50
+ return {str(k): v for k, v in list(servers.items())[:MAX_SERVERS] if isinstance(v, dict)}
51
+
52
+
53
+ def _is_mobster(name, entry):
54
+ if name.casefold() in SELF:
55
+ return True
56
+ command = entry.get("command")
57
+ if isinstance(command, str) and os.path.basename(command) in SELF:
58
+ return True
59
+ args = entry.get("args") if isinstance(entry.get("args"), list) else []
60
+ return "mobile_agent" in args and "mcp" in args
61
+
62
+
63
+ def _token_of(entry):
64
+ """A bearer token from an entry's headers (Authorization: Bearer …), else None; and whether other headers were left
65
+ out."""
66
+ headers = entry.get("headers") if isinstance(entry.get("headers"), dict) else {}
67
+ token, others = None, False
68
+ for key, value in headers.items():
69
+ if str(key).casefold() == "authorization" and isinstance(value, str) and value.startswith("Bearer "):
70
+ token = value[7:].strip() or None
71
+ else:
72
+ others = True
73
+ if isinstance(entry.get("bearer_token"), str): # Codex's inline form
74
+ token = entry["bearer_token"].strip() or token
75
+ return token, others
76
+
77
+
78
+ def describe(entry):
79
+ """What Add would make of ``entry``: {"transport", "command", "args", "url", "envNames", "token", "headers",
80
+ "problem"}."""
81
+ command, url = entry.get("command"), entry.get("url") or entry.get("serverUrl")
82
+ kind = str(entry.get("type") or entry.get("transport") or "").casefold()
83
+ out = {"transport": None, "command": None, "args": [], "url": None, "envNames": [], "token": False,
84
+ "headers": False, "problem": None}
85
+ if isinstance(command, str) and command.strip():
86
+ args = entry.get("args") if isinstance(entry.get("args"), list) else []
87
+ env = entry.get("env") if isinstance(entry.get("env"), dict) else {}
88
+ out.update(transport="stdio", command=command.strip(), args=[str(a) for a in args],
89
+ envNames=sorted(str(k) for k in env))
90
+ return out
91
+ if isinstance(url, str) and url.strip():
92
+ out["url"] = url.strip()
93
+ if kind == "sse" or url.rstrip("/").endswith("/sse"):
94
+ out["problem"] = "It uses SSE, an older MCP transport Mobster doesn't speak. Use the server's /mcp URL."
95
+ return out
96
+ try:
97
+ mcp.validate_url(url)
98
+ except ValueError as error:
99
+ out["problem"] = f"{error}."
100
+ return out
101
+ token, others = _token_of(entry)
102
+ out.update(transport="http", token=bool(token), headers=others)
103
+ return out
104
+ out["problem"] = "It has no command or URL Mobster can use."
105
+ return out
106
+
107
+
108
+ def found(env=None):
109
+ """Every server the other apps have, as (source id, source name, name, entry) tuples, Mobster left out."""
110
+ env = env or agent_clients.Env.current()
111
+ out = []
112
+ for source, cls in SOURCES:
113
+ client = cls()
114
+ for path in client.paths(env):
115
+ for name, entry in _servers(client, Path(path)).items():
116
+ if not _is_mobster(name, entry):
117
+ out.append((source, client.name, name, entry))
118
+ return out
119
+
120
+
121
+ def public(found_servers, connectors):
122
+ """The list for GET /api/connectors/imports. ``connectors``: the existing rows (to say which are added). It never
123
+ carries a value: env values and tokens stay on this Mac and go straight to the Keychain on Add."""
124
+ rows = []
125
+ for source, source_name, name, entry in found_servers:
126
+ info = describe(entry)
127
+ added = next((c for c in connectors if (info["transport"] == "stdio" and c["transport"] == "stdio" and
128
+ os.path.basename(c.get("command") or "") ==
129
+ os.path.basename(info["command"] or "") and
130
+ list(c.get("args") or []) == info["args"])
131
+ or (info["transport"] == "http" and c.get("url") and info["url"] and
132
+ mcp.hint(c["url"]) == mcp.hint(info["url"]))), None)
133
+ rows.append({
134
+ "source": source, "sourceName": source_name, "key": name[:80], "name": suggested_name(name),
135
+ "transport": info["transport"],
136
+ "command": os.path.basename(info["command"]) if info["command"] else None,
137
+ "args": [redact(a)[:200] for a in info["args"][:40]],
138
+ "url": mcp.hint(info["url"]) if info["url"] and not info["problem"] else None,
139
+ "envNames": info["envNames"][:20], "token": info["token"], "headers": info["headers"],
140
+ "problem": info["problem"], "added": added["id"] if added else None})
141
+ return rows
142
+
143
+
144
+ def suggested_name(key):
145
+ """A connector name from a config's key: "brave-search" stays, cut to 40 characters."""
146
+ text = " ".join(str(key).split()) or "Server"
147
+ return text[:40]
148
+
149
+
150
+ def body_for(found_servers, source, key, name=None):
151
+ """The POST /api/connectors body for one found server, with its secrets (env values, a header's bearer token) read
152
+ now from the app's own file."""
153
+ match = next((entry for src, _, k, entry in found_servers if src == source and k == key), None)
154
+ if match is None:
155
+ raise ValueError("That server isn't in the app's settings any more. Look again")
156
+ info = describe(match)
157
+ if info["problem"]:
158
+ raise ValueError(info["problem"].rstrip("."))
159
+ body = {"name": name or suggested_name(key)}
160
+ if info["transport"] == "stdio":
161
+ env = match.get("env") if isinstance(match.get("env"), dict) else {}
162
+ body.update(command=info["command"], args=info["args"])
163
+ if env:
164
+ body["env"] = {str(k): str(v) for k, v in env.items()}
165
+ else:
166
+ body["url"] = info["url"]
167
+ token, _ = _token_of(match)
168
+ if token:
169
+ body["token"] = token
170
+ return body
@@ -0,0 +1,66 @@
1
+ """File a finished result as an issue through a connected tracker (GitHub, Linear or Jira): one tool call of the
2
+ connector the person already connected, through the same ConnectorService.call a workflow's connector step uses."""
3
+
4
+ import re
5
+
6
+ # preset -> [(tool, the arguments for it)], the first tool the connector offers wins. ``target`` is what only the
7
+ # person knows (GitHub's owner and repo, Linear's team, Jira's cloudId and projectKey); the title and text are ours.
8
+ TRACKERS = {
9
+ "github": (("issue_write", lambda t, title, text: {"method": "create", **t, "title": title, "body": text}),
10
+ ("create_issue", lambda t, title, text: {**t, "title": title, "body": text})),
11
+ "linear": (("save_issue", lambda t, title, text: {**t, "title": title, "description": text}),
12
+ ("create_issue", lambda t, title, text: {**t, "title": title, "description": text})),
13
+ "jira": (("createJiraIssue", lambda t, title, text: {"issueTypeName": "Bug", **t, "summary": title,
14
+ "description": text}),),
15
+ }
16
+ TARGET_KEYS = {"owner", "repo", "team", "teamId", "project", "projectId", "cloudId", "projectKey", "issueTypeName",
17
+ "labels"}
18
+ URL = re.compile(r"https://[^\s\"'<>)]+")
19
+
20
+
21
+ def trackers(rows):
22
+ """The enabled connectors that are one of the trackers, in the order they were added."""
23
+ return [row for row in rows if row.get("enabled") and row.get("preset") in TRACKERS]
24
+
25
+
26
+ def pick(rows, wanted):
27
+ """The tracker connector ``wanted`` names (its id, name or preset), else the only one connected.
28
+ Returns (row, None) or (None, (code, message)) when no suitable one is connected."""
29
+ found = trackers(rows)
30
+ if not found:
31
+ return None, ("no_tracker", "No issue tracker is connected. Connect GitHub, Linear or Jira in Settings › "
32
+ "Connectors, then file the issue.")
33
+ if wanted:
34
+ wanted = str(wanted).lower()
35
+ found = [row for row in found if wanted in (row["id"].lower(), row["name"].lower(), row["preset"])]
36
+ if not found:
37
+ return None, ("no_tracker", f"{wanted} isn't a connected issue tracker. Pick one that is connected.")
38
+ if len(found) > 1:
39
+ return None, ("tracker_ambiguous", "Several issue trackers are connected: " +
40
+ ", ".join(row["name"] for row in found) + ". Name the one to use.")
41
+ return found[0], None
42
+
43
+
44
+ def tool_for(row, target, title, text):
45
+ """(tool, arguments) for the first create-issue tool the connector offers, else None."""
46
+ offered = {tool["name"] for tool in row.get("tools") or ()}
47
+ for tool, build in TRACKERS[row["preset"]]:
48
+ if tool in offered:
49
+ return tool, build(target, title, text)
50
+ return None
51
+
52
+
53
+ def clean_target(value):
54
+ if value is None:
55
+ return {}
56
+ if not isinstance(value, dict) or set(value) - TARGET_KEYS:
57
+ raise ValueError("target holds only " + ", ".join(sorted(TARGET_KEYS)))
58
+ if not all(isinstance(item, (str, list)) for item in value.values()):
59
+ raise ValueError("Every target value is text (labels is a list)")
60
+ return dict(value)
61
+
62
+
63
+ def link_of(text):
64
+ """The first https link in the tracker's answer (the issue's address), or None."""
65
+ found = URL.search(text or "")
66
+ return found.group(0).rstrip(".,") if found else None