@_vrsen/openswarm 0.1.0

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 (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/bin/openswarm.js +38 -0
  4. package/config.py +34 -0
  5. package/data_analyst_agent/.cursor/rules/data_analyst.mdc +43 -0
  6. package/data_analyst_agent/__init__.py +3 -0
  7. package/data_analyst_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  8. package/data_analyst_agent/__pycache__/data_analyst_agent.cpython-312.pyc +0 -0
  9. package/data_analyst_agent/data_analyst_agent.py +45 -0
  10. package/data_analyst_agent/instructions.md +173 -0
  11. package/data_analyst_agent/test_files/test_file.csv +21 -0
  12. package/data_analyst_agent/tools/__init__.py +6 -0
  13. package/deep_research/__init__.py +1 -0
  14. package/deep_research/__pycache__/__init__.cpython-312.pyc +0 -0
  15. package/deep_research/__pycache__/deep_research.cpython-312.pyc +0 -0
  16. package/deep_research/deep_research.py +27 -0
  17. package/deep_research/instructions.md +104 -0
  18. package/deep_research/tools/__init__.py +1 -0
  19. package/docs_agent/__init__.py +3 -0
  20. package/docs_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/docs_agent/__pycache__/docs_agent.cpython-312.pyc +0 -0
  22. package/docs_agent/docs_agent.py +61 -0
  23. package/docs_agent/instructions.md +418 -0
  24. package/docs_agent/tools/ConvertDocument.py +323 -0
  25. package/docs_agent/tools/CreateDocument.py +287 -0
  26. package/docs_agent/tools/ListDocuments.py +134 -0
  27. package/docs_agent/tools/ModifyDocument.py +247 -0
  28. package/docs_agent/tools/RestoreDocument.py +79 -0
  29. package/docs_agent/tools/ViewDocument.py +153 -0
  30. package/docs_agent/tools/__init__.py +1 -0
  31. package/docs_agent/tools/__pycache__/ConvertDocument.cpython-312.pyc +0 -0
  32. package/docs_agent/tools/__pycache__/CreateDocument.cpython-312.pyc +0 -0
  33. package/docs_agent/tools/__pycache__/ListDocuments.cpython-312.pyc +0 -0
  34. package/docs_agent/tools/__pycache__/ModifyDocument.cpython-312.pyc +0 -0
  35. package/docs_agent/tools/__pycache__/RestoreDocument.cpython-312.pyc +0 -0
  36. package/docs_agent/tools/__pycache__/ViewDocument.cpython-312.pyc +0 -0
  37. package/docs_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  38. package/docs_agent/tools/utils/__init__.py +1 -0
  39. package/docs_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  40. package/docs_agent/tools/utils/__pycache__/doc_file_utils.cpython-312.pyc +0 -0
  41. package/docs_agent/tools/utils/__pycache__/html_docx_blocks.cpython-312.pyc +0 -0
  42. package/docs_agent/tools/utils/__pycache__/html_docx_constants.cpython-312.pyc +0 -0
  43. package/docs_agent/tools/utils/__pycache__/html_docx_core.cpython-312.pyc +0 -0
  44. package/docs_agent/tools/utils/__pycache__/html_docx_css.cpython-312.pyc +0 -0
  45. package/docs_agent/tools/utils/__pycache__/html_docx_images.cpython-312.pyc +0 -0
  46. package/docs_agent/tools/utils/__pycache__/html_docx_page.cpython-312.pyc +0 -0
  47. package/docs_agent/tools/utils/__pycache__/html_docx_paragraphs.cpython-312.pyc +0 -0
  48. package/docs_agent/tools/utils/__pycache__/html_docx_playwright.cpython-312.pyc +0 -0
  49. package/docs_agent/tools/utils/__pycache__/html_docx_selectors.cpython-312.pyc +0 -0
  50. package/docs_agent/tools/utils/__pycache__/html_docx_shared.cpython-312.pyc +0 -0
  51. package/docs_agent/tools/utils/__pycache__/html_validation.cpython-312.pyc +0 -0
  52. package/docs_agent/tools/utils/doc_file_utils.py +29 -0
  53. package/docs_agent/tools/utils/html_docx_blocks.py +262 -0
  54. package/docs_agent/tools/utils/html_docx_constants.py +78 -0
  55. package/docs_agent/tools/utils/html_docx_core.py +138 -0
  56. package/docs_agent/tools/utils/html_docx_css.py +262 -0
  57. package/docs_agent/tools/utils/html_docx_images.py +293 -0
  58. package/docs_agent/tools/utils/html_docx_page.py +185 -0
  59. package/docs_agent/tools/utils/html_docx_paragraphs.py +342 -0
  60. package/docs_agent/tools/utils/html_docx_playwright.py +184 -0
  61. package/docs_agent/tools/utils/html_docx_selectors.py +196 -0
  62. package/docs_agent/tools/utils/html_docx_shared.py +23 -0
  63. package/docs_agent/tools/utils/html_docx_tables.py +942 -0
  64. package/docs_agent/tools/utils/html_validation.py +102 -0
  65. package/helpers.py +59 -0
  66. package/image_generation_agent/__init__.py +1 -0
  67. package/image_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  68. package/image_generation_agent/__pycache__/image_generation_agent.cpython-312.pyc +0 -0
  69. package/image_generation_agent/image_generation_agent.py +31 -0
  70. package/image_generation_agent/instructions.md +80 -0
  71. package/image_generation_agent/tools/CombineImages.py +211 -0
  72. package/image_generation_agent/tools/EditImages.py +200 -0
  73. package/image_generation_agent/tools/GenerateImages.py +184 -0
  74. package/image_generation_agent/tools/RemoveBackground.py +108 -0
  75. package/image_generation_agent/tools/__init__.py +2 -0
  76. package/image_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  77. package/image_generation_agent/tools/__pycache__/EditImages.cpython-312.pyc +0 -0
  78. package/image_generation_agent/tools/__pycache__/GenerateImages.cpython-312.pyc +0 -0
  79. package/image_generation_agent/tools/__pycache__/RemoveBackground.cpython-312.pyc +0 -0
  80. package/image_generation_agent/tools/utils/__init__.py +2 -0
  81. package/image_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/image_generation_agent/tools/utils/__pycache__/image_io.cpython-312.pyc +0 -0
  83. package/image_generation_agent/tools/utils/image_io.py +308 -0
  84. package/onboard.py +325 -0
  85. package/orchestrator/__init__.py +3 -0
  86. package/orchestrator/__pycache__/__init__.cpython-312.pyc +0 -0
  87. package/orchestrator/__pycache__/orchestrator.cpython-312.pyc +0 -0
  88. package/orchestrator/instructions.md +90 -0
  89. package/orchestrator/orchestrator.py +33 -0
  90. package/package.json +49 -0
  91. package/patches/__init__.py +1 -0
  92. package/patches/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/patches/__pycache__/patch_agency_swarm_dual_comms.cpython-312.pyc +0 -0
  94. package/patches/__pycache__/patch_file_attachment_refs.cpython-312.pyc +0 -0
  95. package/patches/__pycache__/patch_ipython_interpreter_composio.cpython-312.pyc +0 -0
  96. package/patches/dom-to-pptx+1.1.5.patch +133440 -0
  97. package/patches/patch_agency_swarm_dual_comms.py +199 -0
  98. package/patches/patch_file_attachment_refs.py +74 -0
  99. package/patches/patch_ipython_interpreter_composio.py +54 -0
  100. package/pyproject.toml +67 -0
  101. package/run.py +343 -0
  102. package/server.py +26 -0
  103. package/shared_instructions.md +119 -0
  104. package/shared_tools/CopyFile.py +68 -0
  105. package/shared_tools/ExecuteTool.py +184 -0
  106. package/shared_tools/FindTools.py +101 -0
  107. package/shared_tools/ManageConnections.py +43 -0
  108. package/shared_tools/SearchTools.py +44 -0
  109. package/shared_tools/__init__.py +7 -0
  110. package/shared_tools/__pycache__/CopyFile.cpython-312.pyc +0 -0
  111. package/shared_tools/__pycache__/ExecuteTool.cpython-312.pyc +0 -0
  112. package/shared_tools/__pycache__/FindTools.cpython-312.pyc +0 -0
  113. package/shared_tools/__pycache__/ManageConnections.cpython-312.pyc +0 -0
  114. package/shared_tools/__pycache__/SearchTools.cpython-312.pyc +0 -0
  115. package/shared_tools/__pycache__/__init__.cpython-312.pyc +0 -0
  116. package/slides_agent/.cursor/rules/slides-agent-workflow.mdc +9 -0
  117. package/slides_agent/__init__.py +1 -0
  118. package/slides_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/slides_agent/__pycache__/slides_agent.cpython-312.pyc +0 -0
  120. package/slides_agent/instructions.md +298 -0
  121. package/slides_agent/pptx/SKILL.md +528 -0
  122. package/slides_agent/pptx/html2pptx.md +625 -0
  123. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  124. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  125. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  126. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  127. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  128. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  129. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  130. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  131. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  132. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  133. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  134. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  135. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  136. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  137. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  138. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  139. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  140. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  141. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  142. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  143. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  144. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  145. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  146. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  147. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  148. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  149. package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  150. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  151. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  152. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  153. package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  154. package/slides_agent/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  155. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  156. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  157. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  158. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  159. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  160. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  161. package/slides_agent/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  162. package/slides_agent/pptx/ooxml/scripts/pack.py +169 -0
  163. package/slides_agent/pptx/ooxml/scripts/unpack.py +29 -0
  164. package/slides_agent/pptx/ooxml/scripts/validate.py +69 -0
  165. package/slides_agent/pptx/ooxml/scripts/validation/__init__.py +15 -0
  166. package/slides_agent/pptx/ooxml/scripts/validation/base.py +951 -0
  167. package/slides_agent/pptx/ooxml/scripts/validation/docx.py +274 -0
  168. package/slides_agent/pptx/ooxml/scripts/validation/pptx.py +315 -0
  169. package/slides_agent/pptx/ooxml/scripts/validation/redlining.py +279 -0
  170. package/slides_agent/pptx/ooxml.md +427 -0
  171. package/slides_agent/pptx/scripts/html2pptx.js +1092 -0
  172. package/slides_agent/pptx/scripts/inventory.py +1020 -0
  173. package/slides_agent/pptx/scripts/rearrange.py +231 -0
  174. package/slides_agent/pptx/scripts/replace.py +385 -0
  175. package/slides_agent/pptx/scripts/thumbnail.py +451 -0
  176. package/slides_agent/slides_agent.py +109 -0
  177. package/slides_agent/test_deck/_theme.css +92 -0
  178. package/slides_agent/test_deck/assets/placeholder.svg +11 -0
  179. package/slides_agent/test_deck/slide_01_title.html +10 -0
  180. package/slides_agent/test_deck/slide_02_image_split.html +23 -0
  181. package/slides_agent/test_deck/slide_03_kpi.html +21 -0
  182. package/slides_agent/tools/ApplyPptxTextReplacements.py +91 -0
  183. package/slides_agent/tools/BuildPptxFromHtmlSlides.py +221 -0
  184. package/slides_agent/tools/CheckSlide.py +218 -0
  185. package/slides_agent/tools/CheckSlideCanvasOverflow.py +221 -0
  186. package/slides_agent/tools/CreateImageMontage.py +261 -0
  187. package/slides_agent/tools/CreatePptxThumbnailGrid.py +168 -0
  188. package/slides_agent/tools/DeleteSlide.py +78 -0
  189. package/slides_agent/tools/DownloadImage.py +79 -0
  190. package/slides_agent/tools/EnsureRasterImage.py +157 -0
  191. package/slides_agent/tools/ExtractPptxTextInventory.py +104 -0
  192. package/slides_agent/tools/GenerateImage.py +189 -0
  193. package/slides_agent/tools/ImageSearch.py +127 -0
  194. package/slides_agent/tools/InsertNewSlides.py +393 -0
  195. package/slides_agent/tools/ManageTheme.py +112 -0
  196. package/slides_agent/tools/ModifySlide.py +563 -0
  197. package/slides_agent/tools/ReadSlide.py +26 -0
  198. package/slides_agent/tools/RearrangePptxSlidesFromTemplate.py +114 -0
  199. package/slides_agent/tools/RestoreSnapshot.py +89 -0
  200. package/slides_agent/tools/SlideScreenshot.py +66 -0
  201. package/slides_agent/tools/__init__.py +54 -0
  202. package/slides_agent/tools/__pycache__/ApplyPptxTextReplacements.cpython-312.pyc +0 -0
  203. package/slides_agent/tools/__pycache__/BuildPptxFromHtmlSlides.cpython-312.pyc +0 -0
  204. package/slides_agent/tools/__pycache__/CheckSlide.cpython-312.pyc +0 -0
  205. package/slides_agent/tools/__pycache__/CheckSlideCanvasOverflow.cpython-312.pyc +0 -0
  206. package/slides_agent/tools/__pycache__/CreateImageMontage.cpython-312.pyc +0 -0
  207. package/slides_agent/tools/__pycache__/CreatePptxThumbnailGrid.cpython-312.pyc +0 -0
  208. package/slides_agent/tools/__pycache__/DeleteSlide.cpython-312.pyc +0 -0
  209. package/slides_agent/tools/__pycache__/DownloadImage.cpython-312.pyc +0 -0
  210. package/slides_agent/tools/__pycache__/EnsureRasterImage.cpython-312.pyc +0 -0
  211. package/slides_agent/tools/__pycache__/ExtractPptxTextInventory.cpython-312.pyc +0 -0
  212. package/slides_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  213. package/slides_agent/tools/__pycache__/ImageSearch.cpython-312.pyc +0 -0
  214. package/slides_agent/tools/__pycache__/InsertNewSlides.cpython-312.pyc +0 -0
  215. package/slides_agent/tools/__pycache__/ManageTheme.cpython-312.pyc +0 -0
  216. package/slides_agent/tools/__pycache__/ModifySlide.cpython-312.pyc +0 -0
  217. package/slides_agent/tools/__pycache__/ReadSlide.cpython-312.pyc +0 -0
  218. package/slides_agent/tools/__pycache__/RearrangePptxSlidesFromTemplate.cpython-312.pyc +0 -0
  219. package/slides_agent/tools/__pycache__/RestoreSnapshot.cpython-312.pyc +0 -0
  220. package/slides_agent/tools/__pycache__/SlideScreenshot.cpython-312.pyc +0 -0
  221. package/slides_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  222. package/slides_agent/tools/__pycache__/slide_file_utils.cpython-312.pyc +0 -0
  223. package/slides_agent/tools/__pycache__/slide_html_utils.cpython-312.pyc +0 -0
  224. package/slides_agent/tools/__pycache__/template_registry.cpython-312.pyc +0 -0
  225. package/slides_agent/tools/deck_utils.py +31 -0
  226. package/slides_agent/tools/html2pptx_runner.js +1183 -0
  227. package/slides_agent/tools/html_writer_instructions.md +149 -0
  228. package/slides_agent/tools/slide_file_utils.py +108 -0
  229. package/slides_agent/tools/slide_html_utils.py +354 -0
  230. package/slides_agent/tools/template_registry.py +55 -0
  231. package/swarm.py +82 -0
  232. package/video_generation_agent/__init__.py +1 -0
  233. package/video_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  234. package/video_generation_agent/__pycache__/video_generation_agent.cpython-312.pyc +0 -0
  235. package/video_generation_agent/instructions.md +178 -0
  236. package/video_generation_agent/tools/AddSubtitles.py +425 -0
  237. package/video_generation_agent/tools/CombineImages.py +166 -0
  238. package/video_generation_agent/tools/CombineVideos.py +113 -0
  239. package/video_generation_agent/tools/EditAudio.py +297 -0
  240. package/video_generation_agent/tools/EditImage.py +144 -0
  241. package/video_generation_agent/tools/EditVideoContent.py +369 -0
  242. package/video_generation_agent/tools/GenerateImage.py +133 -0
  243. package/video_generation_agent/tools/GenerateVideo.py +556 -0
  244. package/video_generation_agent/tools/TrimVideo.py +233 -0
  245. package/video_generation_agent/tools/__init__.py +1 -0
  246. package/video_generation_agent/tools/__pycache__/AddSubtitles.cpython-312.pyc +0 -0
  247. package/video_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
  248. package/video_generation_agent/tools/__pycache__/CombineVideos.cpython-312.pyc +0 -0
  249. package/video_generation_agent/tools/__pycache__/EditAudio.cpython-312.pyc +0 -0
  250. package/video_generation_agent/tools/__pycache__/EditImage.cpython-312.pyc +0 -0
  251. package/video_generation_agent/tools/__pycache__/EditVideoContent.cpython-312.pyc +0 -0
  252. package/video_generation_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
  253. package/video_generation_agent/tools/__pycache__/GenerateVideo.cpython-312.pyc +0 -0
  254. package/video_generation_agent/tools/__pycache__/TrimVideo.cpython-312.pyc +0 -0
  255. package/video_generation_agent/tools/utils/__init__.py +1 -0
  256. package/video_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  257. package/video_generation_agent/tools/utils/__pycache__/image_utils.cpython-312.pyc +0 -0
  258. package/video_generation_agent/tools/utils/__pycache__/video_utils.cpython-312.pyc +0 -0
  259. package/video_generation_agent/tools/utils/image_utils.py +174 -0
  260. package/video_generation_agent/tools/utils/video_utils.py +522 -0
  261. package/video_generation_agent/video_generation_agent.py +26 -0
  262. package/virtual_assistant/__init__.py +1 -0
  263. package/virtual_assistant/__pycache__/__init__.cpython-312.pyc +0 -0
  264. package/virtual_assistant/__pycache__/virtual_assistant.cpython-312.pyc +0 -0
  265. package/virtual_assistant/instructions.md +206 -0
  266. package/virtual_assistant/tools/AddLabelToEmail.py +154 -0
  267. package/virtual_assistant/tools/CheckEventsForDate.py +218 -0
  268. package/virtual_assistant/tools/CheckUnreadSlackMessages.py +216 -0
  269. package/virtual_assistant/tools/CreateCalendarEvent.py +261 -0
  270. package/virtual_assistant/tools/DeleteCalendarEvent.py +137 -0
  271. package/virtual_assistant/tools/DeleteDraft.py +95 -0
  272. package/virtual_assistant/tools/DraftEmail.py +239 -0
  273. package/virtual_assistant/tools/EditFile.py +113 -0
  274. package/virtual_assistant/tools/FindEmails.py +330 -0
  275. package/virtual_assistant/tools/GetCurrentTime.py +69 -0
  276. package/virtual_assistant/tools/GetSlackUserInfo.py +117 -0
  277. package/virtual_assistant/tools/ListDirectory.py +113 -0
  278. package/virtual_assistant/tools/ListSkills.py +94 -0
  279. package/virtual_assistant/tools/ManageLabels.py +295 -0
  280. package/virtual_assistant/tools/ProductSearch.py +254 -0
  281. package/virtual_assistant/tools/ReadEmail.py +251 -0
  282. package/virtual_assistant/tools/ReadFile.py +108 -0
  283. package/virtual_assistant/tools/ReadSlackMessages.py +191 -0
  284. package/virtual_assistant/tools/RemoveLabelFromEmail.py +137 -0
  285. package/virtual_assistant/tools/RescheduleCalendarEvent.py +227 -0
  286. package/virtual_assistant/tools/ScholarSearch.py +216 -0
  287. package/virtual_assistant/tools/SendDraft.py +101 -0
  288. package/virtual_assistant/tools/SendSlackMessage.py +148 -0
  289. package/virtual_assistant/tools/WriteFile.py +95 -0
  290. package/virtual_assistant/tools/__init__.py +1 -0
  291. package/virtual_assistant/tools/__pycache__/AddLabelToEmail.cpython-312.pyc +0 -0
  292. package/virtual_assistant/tools/__pycache__/CheckEventsForDate.cpython-312.pyc +0 -0
  293. package/virtual_assistant/tools/__pycache__/CheckUnreadSlackMessages.cpython-312.pyc +0 -0
  294. package/virtual_assistant/tools/__pycache__/CreateCalendarEvent.cpython-312.pyc +0 -0
  295. package/virtual_assistant/tools/__pycache__/DeleteCalendarEvent.cpython-312.pyc +0 -0
  296. package/virtual_assistant/tools/__pycache__/DeleteDraft.cpython-312.pyc +0 -0
  297. package/virtual_assistant/tools/__pycache__/DraftEmail.cpython-312.pyc +0 -0
  298. package/virtual_assistant/tools/__pycache__/EditFile.cpython-312.pyc +0 -0
  299. package/virtual_assistant/tools/__pycache__/FindEmails.cpython-312.pyc +0 -0
  300. package/virtual_assistant/tools/__pycache__/GetCurrentTime.cpython-312.pyc +0 -0
  301. package/virtual_assistant/tools/__pycache__/GetSlackUserInfo.cpython-312.pyc +0 -0
  302. package/virtual_assistant/tools/__pycache__/ListDirectory.cpython-312.pyc +0 -0
  303. package/virtual_assistant/tools/__pycache__/ListSkills.cpython-312.pyc +0 -0
  304. package/virtual_assistant/tools/__pycache__/ManageLabels.cpython-312.pyc +0 -0
  305. package/virtual_assistant/tools/__pycache__/ProductSearch.cpython-312.pyc +0 -0
  306. package/virtual_assistant/tools/__pycache__/ReadEmail.cpython-312.pyc +0 -0
  307. package/virtual_assistant/tools/__pycache__/ReadFile.cpython-312.pyc +0 -0
  308. package/virtual_assistant/tools/__pycache__/ReadSlackMessages.cpython-312.pyc +0 -0
  309. package/virtual_assistant/tools/__pycache__/RemoveLabelFromEmail.cpython-312.pyc +0 -0
  310. package/virtual_assistant/tools/__pycache__/RescheduleCalendarEvent.cpython-312.pyc +0 -0
  311. package/virtual_assistant/tools/__pycache__/ScholarSearch.cpython-312.pyc +0 -0
  312. package/virtual_assistant/tools/__pycache__/SendDraft.cpython-312.pyc +0 -0
  313. package/virtual_assistant/tools/__pycache__/SendSlackMessage.cpython-312.pyc +0 -0
  314. package/virtual_assistant/tools/__pycache__/WriteFile.cpython-312.pyc +0 -0
  315. package/virtual_assistant/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  316. package/virtual_assistant/virtual_assistant.py +52 -0
@@ -0,0 +1,216 @@
1
+ from typing import Literal
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+ from datetime import datetime
6
+
7
+ from helpers import execute_composio_tool
8
+
9
+ class CheckUnreadSlackMessages(BaseTool):
10
+ """
11
+ Retrieves unread Slack messages using the search API.
12
+
13
+ Efficiently finds unread messages with minimal API calls:
14
+ 1. Searches for recent messages (1 call)
15
+ 2. Checks unread status for top conversations (max 10 calls)
16
+
17
+ Returns sender, message preview, timestamp, permalink, and attachment info.
18
+ """
19
+
20
+ conversation_types: Literal["dm", "channels", "all"] = Field(
21
+ default="dm",
22
+ description=(
23
+ "'dm' for direct messages only (default), "
24
+ "'channels' for public/private channels only, "
25
+ "'all' for both"
26
+ )
27
+ )
28
+
29
+ max_messages: int = Field(
30
+ default=20,
31
+ ge=1,
32
+ le=50,
33
+ description="Maximum number of unread messages to return (1-50). Default is 20."
34
+ )
35
+
36
+ days_back: int = Field(
37
+ default=7,
38
+ ge=1,
39
+ le=30,
40
+ description="How many days back to search for messages (1-30). Default is 7."
41
+ )
42
+
43
+ include_bots: bool = Field(
44
+ default=True,
45
+ description="Include messages from bots. Default is True."
46
+ )
47
+
48
+ def run(self):
49
+ try:
50
+ search_results = self._search_recent_messages(execute_composio_tool)
51
+
52
+ if not search_results:
53
+ return json.dumps({
54
+ "total_unread": 0,
55
+ "messages": [],
56
+ "summary": "No recent messages found."
57
+ }, indent=2)
58
+
59
+ unread_messages = self._filter_unread(execute_composio_tool, search_results)
60
+
61
+ # Sort by timestamp (newest first) and limit
62
+ unread_messages.sort(key=lambda x: x.get("_ts", "0"), reverse=True)
63
+ unread_messages = unread_messages[:self.max_messages]
64
+
65
+ # Clean up internal fields
66
+ for msg in unread_messages:
67
+ msg.pop("_ts", None)
68
+
69
+ # Build summary
70
+ if not unread_messages:
71
+ summary = "No unread messages."
72
+ else:
73
+ conv_ids = set(msg["conversation_id"] for msg in unread_messages)
74
+ summary = f"{len(unread_messages)} unread message{'s' if len(unread_messages) > 1 else ''} from {len(conv_ids)} conversation{'s' if len(conv_ids) > 1 else ''}."
75
+
76
+ return json.dumps({
77
+ "total_unread": len(unread_messages),
78
+ "messages": unread_messages,
79
+ "summary": summary
80
+ }, indent=2)
81
+
82
+ except Exception as e:
83
+ return f"Error fetching unread Slack messages: {str(e)}"
84
+
85
+ def _search_recent_messages(self, execute_tool) -> list:
86
+ """Searches for recent messages (1 API call)."""
87
+ from datetime import timedelta
88
+
89
+ start_date = (datetime.now() - timedelta(days=self.days_back)).strftime("%Y-%m-%d")
90
+
91
+ if self.conversation_types == "dm":
92
+ query = f"after:{start_date} is:dm"
93
+ elif self.conversation_types == "channels":
94
+ query = f"after:{start_date} -is:dm"
95
+ else:
96
+ query = f"after:{start_date}"
97
+
98
+ result = execute_tool(
99
+ tool_name="SLACK_SEARCH_MESSAGES",
100
+ arguments={
101
+ "query": query,
102
+ "sort": "timestamp",
103
+ "sort_dir": "desc",
104
+ "count": min(self.max_messages * 2, 100)
105
+ },
106
+ )
107
+
108
+ if isinstance(result, dict) and result.get("error"):
109
+ return []
110
+
111
+ return result.get("data", {}).get("messages", {}).get("matches", [])
112
+
113
+ def _filter_unread(self, execute_tool, messages: list) -> list:
114
+ """Filters to unread messages only (max 2 API calls for last_read checks)."""
115
+ # Group messages by channel
116
+ by_channel = {}
117
+ for msg in messages:
118
+ channel = msg.get("channel", {})
119
+ channel_id = channel.get("id")
120
+ if not channel_id:
121
+ continue
122
+
123
+ # Filter bots if needed
124
+ if not self.include_bots and (msg.get("bot_id") or msg.get("subtype")):
125
+ continue
126
+
127
+ if channel_id not in by_channel:
128
+ by_channel[channel_id] = {
129
+ "name": self._get_display_name(channel),
130
+ "messages": []
131
+ }
132
+ by_channel[channel_id]["messages"].append(msg)
133
+
134
+ # Check last_read for top 10 channels (to limit API calls)
135
+ unread_messages = []
136
+ channels_checked = 0
137
+
138
+ for channel_id, conv_data in by_channel.items():
139
+ if channels_checked >= 10:
140
+ break
141
+
142
+ channels_checked += 1
143
+
144
+ # Get last_read timestamp
145
+ info_result = execute_tool(
146
+ tool_name="SLACK_RETRIEVE_CONVERSATION_INFORMATION",
147
+ arguments={"channel": channel_id},
148
+ )
149
+
150
+ if isinstance(info_result, dict) and info_result.get("error"):
151
+ continue
152
+
153
+ last_read = info_result.get("data", {}).get("channel", {}).get("last_read", "0")
154
+
155
+ # Filter to messages after last_read
156
+ for msg in conv_data["messages"]:
157
+ ts = msg.get("ts", "0")
158
+ if ts > last_read:
159
+ unread_messages.append(self._format_message(
160
+ msg, conv_data["name"], channel_id
161
+ ))
162
+
163
+ return unread_messages
164
+
165
+ def _get_display_name(self, channel: dict) -> str:
166
+ """Gets display name for channel."""
167
+ if channel.get("is_im"):
168
+ return channel.get("name", "Direct Message")
169
+ return f"#{channel.get('name', 'Unknown')}"
170
+
171
+ def _format_message(self, msg: dict, conv_name: str, channel_id: str) -> dict:
172
+ """Formats a message for output."""
173
+ files = msg.get("files", [])
174
+ attachments = [f.get("name") or f.get("title") or "file" for f in files] if files else None
175
+
176
+ text = msg.get("text", "")
177
+ if len(text) > 200:
178
+ text = text[:197] + "..."
179
+
180
+ return {
181
+ "conversation": conv_name,
182
+ "conversation_id": channel_id,
183
+ "sender": msg.get("username", "Unknown"),
184
+ "text": text,
185
+ "timestamp": self._format_timestamp(msg.get("ts")),
186
+ "permalink": msg.get("permalink", ""),
187
+ "has_attachment": len(files) > 0,
188
+ "attachments": attachments,
189
+ "_ts": msg.get("ts")
190
+ }
191
+
192
+ def _format_timestamp(self, ts: str) -> str:
193
+ """Formats Slack timestamp."""
194
+ if not ts:
195
+ return ""
196
+ try:
197
+ unix_ts = float(ts.split(".")[0])
198
+ return datetime.fromtimestamp(unix_ts).strftime("%Y-%m-%d %H:%M")
199
+ except (ValueError, TypeError):
200
+ return ts
201
+
202
+
203
+ if __name__ == "__main__":
204
+ import sys
205
+ import os
206
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
207
+
208
+ print("CheckUnreadSlackMessages Test")
209
+ print("-" * 40)
210
+
211
+ tool = CheckUnreadSlackMessages(
212
+ conversation_types="all",
213
+ max_messages=10,
214
+ days_back=7
215
+ )
216
+ print(tool.run())
@@ -0,0 +1,261 @@
1
+ from typing import Literal, Optional, List
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+
6
+ from helpers import execute_composio_tool
7
+
8
+
9
+ class CreateCalendarEvent(BaseTool):
10
+ """
11
+ Creates a calendar event in Google Calendar or Outlook.
12
+
13
+ The event is automatically accepted for the user after creation.
14
+ Returns the event ID and a link to view the event.
15
+ """
16
+
17
+ provider: Literal["google", "outlook"] = Field(
18
+ ...,
19
+ description="Calendar provider: 'google' (Google Calendar) or 'outlook'"
20
+ )
21
+
22
+ title: str = Field(
23
+ ...,
24
+ description="Title/subject of the event"
25
+ )
26
+
27
+ start_datetime: str = Field(
28
+ ...,
29
+ description="Start date and time in ISO 8601 format (e.g., '2026-01-15T14:00:00')"
30
+ )
31
+
32
+ duration_hours: int = Field(
33
+ default=1,
34
+ ge=0,
35
+ description="Duration in hours (default 1). Use with duration_minutes for partial hours."
36
+ )
37
+
38
+ duration_minutes: int = Field(
39
+ default=0,
40
+ ge=0,
41
+ le=59,
42
+ description="Duration in minutes (0-59). Combined with duration_hours."
43
+ )
44
+
45
+ timezone: str = Field(
46
+ default="UTC",
47
+ description="Timezone in IANA format (e.g., 'America/New_York', 'Europe/London', 'Asia/Dubai')"
48
+ )
49
+
50
+ description: Optional[str] = Field(
51
+ default=None,
52
+ description="Description/notes for the event"
53
+ )
54
+
55
+ location: Optional[str] = Field(
56
+ default=None,
57
+ description="Location of the event (address or meeting room)"
58
+ )
59
+
60
+ attendees: Optional[List[str]] = Field(
61
+ default=None,
62
+ description="List of attendee email addresses to invite"
63
+ )
64
+
65
+ create_meeting_link: bool = Field(
66
+ default=False,
67
+ description="If True, creates a video meeting link (Google Meet or Teams)"
68
+ )
69
+
70
+ def run(self):
71
+ try:
72
+ if self.provider == "google":
73
+ return self._create_google_event(execute_composio_tool)
74
+ else:
75
+ return self._create_outlook_event(execute_composio_tool)
76
+
77
+ except Exception as e:
78
+ return f"Error creating calendar event: {str(e)}"
79
+
80
+ def _create_google_event(self, execute_tool) -> str:
81
+ """Creates a Google Calendar event and accepts it."""
82
+ arguments = {
83
+ "calendar_id": "primary",
84
+ "start_datetime": self.start_datetime,
85
+ "event_duration_hour": self.duration_hours,
86
+ "event_duration_minutes": self.duration_minutes,
87
+ "timezone": self.timezone,
88
+ "summary": self.title,
89
+ "create_meeting_room": self.create_meeting_link,
90
+ "exclude_organizer": False, # Include organizer as attendee
91
+ "send_updates": True
92
+ }
93
+
94
+ if self.description:
95
+ arguments["description"] = self.description
96
+
97
+ if self.location:
98
+ arguments["location"] = self.location
99
+
100
+ if self.attendees:
101
+ arguments["attendees"] = self.attendees
102
+
103
+ result = execute_tool(
104
+ tool_name="GOOGLECALENDAR_CREATE_EVENT",
105
+ arguments=arguments,
106
+ )
107
+
108
+ if isinstance(result, dict) and result.get("error"):
109
+ return f"Error creating Google Calendar event: {result.get('error')}"
110
+
111
+ data = result.get("data", {})
112
+ if "response_data" in data:
113
+ data = data["response_data"]
114
+ event_id = data.get("id")
115
+
116
+ if event_id:
117
+ accept_result = execute_tool(
118
+ tool_name="GOOGLECALENDAR_PATCH_EVENT",
119
+ arguments={
120
+ "calendar_id": "primary",
121
+ "event_id": event_id,
122
+ "rsvp_response": "accepted",
123
+ "send_updates": "none"
124
+ },
125
+ )
126
+
127
+ accepted = not (isinstance(accept_result, dict) and accept_result.get("error"))
128
+ else:
129
+ accepted = False
130
+
131
+ meeting_link = None
132
+ conference_data = data.get("conferenceData", {})
133
+ entry_points = conference_data.get("entryPoints", [])
134
+ for entry in entry_points:
135
+ if entry.get("entryPointType") == "video":
136
+ meeting_link = entry.get("uri")
137
+ break
138
+
139
+ return json.dumps({
140
+ "provider": "google",
141
+ "success": True,
142
+ "event_id": event_id,
143
+ "title": self.title,
144
+ "start": data.get("start", {}).get("dateTime"),
145
+ "end": data.get("end", {}).get("dateTime"),
146
+ "accepted": accepted,
147
+ "meeting_link": meeting_link,
148
+ "html_link": data.get("htmlLink", "")
149
+ }, indent=2)
150
+
151
+ def _create_outlook_event(self, execute_tool) -> str:
152
+ """Creates an Outlook calendar event."""
153
+ from datetime import datetime, timedelta
154
+
155
+ try:
156
+ start_dt = datetime.fromisoformat(self.start_datetime.replace("Z", "+00:00"))
157
+ except ValueError:
158
+ start_dt = datetime.fromisoformat(self.start_datetime)
159
+
160
+ duration = timedelta(hours=self.duration_hours, minutes=self.duration_minutes)
161
+ end_dt = start_dt + duration
162
+ end_datetime = end_dt.strftime("%Y-%m-%dT%H:%M:%S")
163
+
164
+ arguments = {
165
+ "user_id": "me",
166
+ "subject": self.title,
167
+ "start_datetime": self.start_datetime,
168
+ "end_datetime": end_datetime,
169
+ "time_zone": self.timezone,
170
+ "is_html": False
171
+ }
172
+
173
+ if self.description:
174
+ arguments["body"] = self.description
175
+
176
+ if self.location:
177
+ arguments["location"] = self.location
178
+
179
+ if self.attendees:
180
+ arguments["attendees_info"] = [{"email": email} for email in self.attendees]
181
+
182
+ if self.create_meeting_link:
183
+ arguments["is_online_meeting"] = True
184
+ arguments["online_meeting_provider"] = "teamsForBusiness"
185
+
186
+ result = execute_tool(
187
+ tool_name="OUTLOOK_CALENDAR_CREATE_EVENT",
188
+ arguments=arguments,
189
+ )
190
+
191
+ if isinstance(result, dict) and result.get("error"):
192
+ return f"Error creating Outlook event: {result.get('error')}"
193
+
194
+ data = result.get("data", {})
195
+
196
+ # Extract meeting link if created
197
+ meeting_link = None
198
+ online_meeting = data.get("onlineMeeting", {})
199
+ if online_meeting:
200
+ meeting_link = online_meeting.get("joinUrl")
201
+
202
+ return json.dumps({
203
+ "provider": "outlook",
204
+ "success": True,
205
+ "event_id": data.get("id"),
206
+ "title": self.title,
207
+ "start": data.get("start", {}).get("dateTime"),
208
+ "end": data.get("end", {}).get("dateTime"),
209
+ "accepted": True, # Organizer is automatically accepted
210
+ "meeting_link": meeting_link,
211
+ "web_link": data.get("webLink", "")
212
+ }, indent=2)
213
+
214
+
215
+ if __name__ == "__main__":
216
+ import sys
217
+ import os
218
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
219
+
220
+ print("=" * 60)
221
+ print("CreateCalendarEvent Test Suite")
222
+ print("=" * 60)
223
+ print()
224
+
225
+ # Test 1: Create Google Calendar event
226
+ print("Test 1: Create Google Calendar event")
227
+ print("-" * 60)
228
+ tool = CreateCalendarEvent(
229
+ provider="google",
230
+ title="Test Event from Virtual Assistant",
231
+ start_datetime="2026-01-08T15:00:00",
232
+ duration_hours=0,
233
+ duration_minutes=30,
234
+ timezone="Asia/Dubai",
235
+ description="This is a test event created by the Virtual Assistant tool.",
236
+ create_meeting_link=False
237
+ )
238
+ result = tool.run()
239
+ print(result)
240
+ print()
241
+
242
+ # Test 2: Create Outlook event
243
+ print("Test 2: Create Outlook Calendar event")
244
+ print("-" * 60)
245
+ tool = CreateCalendarEvent(
246
+ provider="outlook",
247
+ title="Test Outlook Event",
248
+ start_datetime="2026-01-08T16:00:00",
249
+ duration_hours=1,
250
+ duration_minutes=0,
251
+ timezone="UTC",
252
+ description="This is a test Outlook event."
253
+ )
254
+ result = tool.run()
255
+ print(result)
256
+ print()
257
+
258
+ print("=" * 60)
259
+ print("Tests completed! Remember to delete test events from calendars.")
260
+ print("=" * 60)
261
+
@@ -0,0 +1,137 @@
1
+ from typing import Literal
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+
5
+ from helpers import execute_composio_tool
6
+ import json
7
+
8
+
9
+ class DeleteCalendarEvent(BaseTool):
10
+ """
11
+ Deletes a calendar event.
12
+
13
+ Supports both Google Calendar and Outlook.
14
+ Use CheckEventsForDate first to get the event_id of the event to delete.
15
+
16
+ WARNING: This action is irreversible. The event will be permanently deleted.
17
+ """
18
+
19
+ provider: Literal["google", "outlook"] = Field(
20
+ ...,
21
+ description="Calendar provider: 'google' or 'outlook'"
22
+ )
23
+
24
+ event_id: str = Field(
25
+ ...,
26
+ description="The unique ID of the event to delete (from CheckEventsForDate)"
27
+ )
28
+
29
+ send_notifications: bool = Field(
30
+ default=True,
31
+ description="Whether to send cancellation notifications to attendees"
32
+ )
33
+
34
+ def run(self):
35
+ try:
36
+ if self.provider == "google":
37
+ return self._delete_google_event(execute_composio_tool)
38
+ else:
39
+ return self._delete_outlook_event(execute_composio_tool)
40
+
41
+ except Exception as e:
42
+ return f"Error deleting event: {str(e)}"
43
+
44
+ def _delete_google_event(self, execute_tool) -> str:
45
+ """Deletes a Google Calendar event."""
46
+ result = execute_tool(
47
+ tool_name="GOOGLECALENDAR_DELETE_EVENT",
48
+ arguments={
49
+ "calendar_id": "primary",
50
+ "event_id": self.event_id
51
+ },
52
+ )
53
+
54
+ if isinstance(result, dict) and result.get("error"):
55
+ return f"Error deleting Google Calendar event: {result.get('error')}"
56
+
57
+ return json.dumps({
58
+ "provider": "google",
59
+ "success": True,
60
+ "event_id": self.event_id,
61
+ "message": "Event deleted successfully"
62
+ }, indent=2)
63
+
64
+ def _delete_outlook_event(self, execute_tool) -> str:
65
+ """Deletes an Outlook calendar event."""
66
+ result = execute_tool(
67
+ tool_name="OUTLOOK_DELETE_EVENT",
68
+ arguments={
69
+ "user_id": "me",
70
+ "event_id": self.event_id,
71
+ "send_notifications": self.send_notifications
72
+ },
73
+ )
74
+
75
+ if isinstance(result, dict) and result.get("error"):
76
+ return f"Error deleting Outlook event: {result.get('error')}"
77
+
78
+ return json.dumps({
79
+ "provider": "outlook",
80
+ "success": True,
81
+ "event_id": self.event_id,
82
+ "message": "Event deleted successfully",
83
+ "notifications_sent": self.send_notifications
84
+ }, indent=2)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ import sys
89
+ import os
90
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
91
+
92
+ print("=" * 60)
93
+ print("DeleteCalendarEvent Test Suite")
94
+ print("=" * 60)
95
+ print()
96
+
97
+ # First, list events to find a test event
98
+ from virtual_assistant.tools.CheckEventsForDate import CheckEventsForDate
99
+
100
+ print("=== Getting events for tomorrow ===")
101
+ from datetime import datetime, timedelta
102
+ tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
103
+
104
+ check_tool = CheckEventsForDate(provider="google", date=tomorrow)
105
+ result = check_tool.run()
106
+
107
+ import json
108
+ data = json.loads(result)
109
+
110
+ # Find a test event to delete
111
+ test_events = [e for e in data.get("events", []) if "Test" in e.get("title", "")]
112
+
113
+ if test_events:
114
+ event = test_events[0]
115
+ print(f"Found test event: {event['title']} (ID: {event['event_id']})")
116
+ print()
117
+
118
+ # Delete it
119
+ print("=== Deleting test event ===")
120
+ tool = DeleteCalendarEvent(
121
+ provider="google",
122
+ event_id=event["event_id"],
123
+ send_notifications=False
124
+ )
125
+ result = tool.run()
126
+ print(result)
127
+ else:
128
+ print("No test events found to delete")
129
+ print("Available events:")
130
+ for e in data.get("events", [])[:5]:
131
+ print(f" - {e['title']}")
132
+
133
+ print()
134
+ print("=" * 60)
135
+ print("Test completed!")
136
+ print("=" * 60)
137
+