@_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,94 @@
1
+ import os
2
+
3
+ from agency_swarm.tools import BaseTool
4
+
5
+
6
+ class ListSkills(BaseTool):
7
+ """
8
+ Lists all skills currently available to you.
9
+ """
10
+
11
+ def run(self):
12
+ try:
13
+ skills_path = os.path.join(os.getcwd(), "mnt/skills")
14
+
15
+ if not os.path.exists(skills_path):
16
+ return f"Error: Skills folder does not exist: {skills_path}"
17
+
18
+ if not os.path.isdir(skills_path):
19
+ return f"Error: Skills path is not a directory: {skills_path}"
20
+
21
+ skills = []
22
+ try:
23
+ entries = os.listdir(skills_path)
24
+ except PermissionError:
25
+ return f"Error: Permission denied accessing skills folder: {skills_path}"
26
+
27
+ for entry in sorted(entries):
28
+ entry_path = os.path.join(skills_path, entry)
29
+
30
+ if not os.path.isdir(entry_path):
31
+ continue
32
+
33
+ skill_file = None
34
+ if os.path.exists(os.path.join(entry_path, "SKILL.md")):
35
+ skill_file = os.path.join(entry_path, "SKILL.md")
36
+ elif os.path.exists(os.path.join(entry_path, "skill.md")):
37
+ skill_file = os.path.join(entry_path, "skill.md")
38
+
39
+ if not skill_file:
40
+ continue
41
+
42
+ try:
43
+ with open(skill_file, "r", encoding="utf-8") as f:
44
+ lines = f.readlines()
45
+
46
+ name = None
47
+ description = None
48
+
49
+ # Parse frontmatter: lines 2 and 3 carry "name:" and "description:"
50
+ if len(lines) > 1:
51
+ line2 = lines[1].strip()
52
+ if line2.startswith("name:"):
53
+ name = line2.split("name:", 1)[1].strip()
54
+
55
+ if len(lines) > 2:
56
+ line3 = lines[2].strip()
57
+ if line3.startswith("description:"):
58
+ description = line3.split("description:", 1)[1].strip()
59
+
60
+ skills.append({
61
+ "name": name or entry,
62
+ "description": description or "No description available",
63
+ "relative_path": os.path.relpath(skill_file, os.getcwd()),
64
+ })
65
+
66
+ except Exception as e:
67
+ skills.append({
68
+ "name": entry,
69
+ "description": f"Error reading skill file: {str(e)}",
70
+ "relative_path": os.path.relpath(skill_file, os.getcwd()),
71
+ })
72
+
73
+ if not skills:
74
+ return f"No skills found in {skills_path}"
75
+
76
+ output = [f"Found {len(skills)} skill(s) in {skills_path}:\n"]
77
+
78
+ for i, skill in enumerate(skills, 1):
79
+ output.append(f"\n{i}. {skill['name']}")
80
+ output.append(f" Description: {skill['description']}")
81
+ output.append(f" Path: {skill['relative_path']}")
82
+
83
+ return "\n".join(output)
84
+
85
+ except Exception as e:
86
+ return f"Error listing skills: {str(e)}"
87
+
88
+
89
+ if __name__ == "__main__":
90
+ # Test the tool
91
+ tool = ListSkills()
92
+ result = tool.run()
93
+ print(result)
94
+
@@ -0,0 +1,295 @@
1
+ from typing import Literal, Optional
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 ManageLabels(BaseTool):
10
+ """
11
+ Manages email labels (Gmail) or categories (Outlook).
12
+
13
+ Actions:
14
+ - list: List all labels/categories
15
+ - create: Create a new label/category
16
+ - update: Rename or update a label (Gmail only)
17
+ - delete: Delete a label/category
18
+ """
19
+
20
+ provider: Literal["gmail", "outlook"] = Field(
21
+ ...,
22
+ description="Email provider: 'gmail' or 'outlook'"
23
+ )
24
+
25
+ action: Literal["list", "create", "update", "delete"] = Field(
26
+ ...,
27
+ description="Action to perform: 'list', 'create', 'update', or 'delete'"
28
+ )
29
+
30
+ label_name: Optional[str] = Field(
31
+ default=None,
32
+ description="Name of the label/category (required for create, update)"
33
+ )
34
+
35
+ label_id: Optional[str] = Field(
36
+ default=None,
37
+ description="ID of the label/category (required for update, delete). For Gmail, use format 'Label_123'"
38
+ )
39
+
40
+ new_name: Optional[str] = Field(
41
+ default=None,
42
+ description="New name when updating a label (Gmail only)"
43
+ )
44
+
45
+ color: Optional[str] = Field(
46
+ default=None,
47
+ description="Color for the label. Gmail: hex color like '#fb4c2f'. Outlook: 'preset0' through 'preset24'"
48
+ )
49
+
50
+ def run(self):
51
+ try:
52
+ if self.provider == "gmail":
53
+ return self._manage_gmail_labels(execute_composio_tool)
54
+ else:
55
+ return self._manage_outlook_categories(execute_composio_tool)
56
+
57
+ except Exception as e:
58
+ return f"Error managing labels: {str(e)}"
59
+
60
+ def _manage_gmail_labels(self, execute_tool) -> str:
61
+ """Manages Gmail labels."""
62
+ if self.action == "list":
63
+ return self._list_gmail_labels(execute_tool)
64
+ elif self.action == "create":
65
+ return self._create_gmail_label(execute_tool)
66
+ elif self.action == "update":
67
+ return self._update_gmail_label(execute_tool)
68
+ elif self.action == "delete":
69
+ return self._delete_gmail_label(execute_tool)
70
+ else:
71
+ return f"Unknown action: {self.action}"
72
+
73
+ def _list_gmail_labels(self, execute_tool) -> str:
74
+ """Lists all Gmail labels."""
75
+ result = execute_tool(
76
+ tool_name="GMAIL_LIST_LABELS",
77
+ arguments={"user_id": "me"},
78
+ )
79
+
80
+ if isinstance(result, dict) and result.get("error"):
81
+ return f"Error listing Gmail labels: {result.get('error')}"
82
+
83
+ labels = result.get("data", {}).get("labels", [])
84
+
85
+ formatted_labels = []
86
+ for label in labels:
87
+ formatted_labels.append({
88
+ "id": label.get("id"),
89
+ "name": label.get("name"),
90
+ "type": label.get("type"), # system or user
91
+ })
92
+
93
+ return json.dumps({
94
+ "provider": "gmail",
95
+ "count": len(formatted_labels),
96
+ "labels": formatted_labels
97
+ }, indent=2)
98
+
99
+ def _create_gmail_label(self, execute_tool) -> str:
100
+ """Creates a Gmail label."""
101
+ if not self.label_name:
102
+ return "Error: label_name is required for create action"
103
+
104
+ arguments = {
105
+ "user_id": "me",
106
+ "label_name": self.label_name
107
+ }
108
+
109
+ if self.color:
110
+ arguments["background_color"] = self.color
111
+
112
+ result = execute_tool(
113
+ tool_name="GMAIL_CREATE_LABEL",
114
+ arguments=arguments,
115
+ )
116
+
117
+ if isinstance(result, dict) and result.get("error"):
118
+ return f"Error creating Gmail label: {result.get('error')}"
119
+
120
+ data = result.get("data", {})
121
+
122
+ return json.dumps({
123
+ "provider": "gmail",
124
+ "success": True,
125
+ "action": "create",
126
+ "label_id": data.get("id"),
127
+ "label_name": data.get("name")
128
+ }, indent=2)
129
+
130
+ def _update_gmail_label(self, execute_tool) -> str:
131
+ """Updates a Gmail label."""
132
+ if not self.label_id:
133
+ return "Error: label_id is required for update action"
134
+
135
+ arguments = {
136
+ "userId": "me",
137
+ "id": self.label_id
138
+ }
139
+
140
+ if self.new_name:
141
+ arguments["name"] = self.new_name
142
+
143
+ if self.color:
144
+ arguments["color"] = {
145
+ "backgroundColor": self.color,
146
+ "textColor": "#ffffff"
147
+ }
148
+
149
+ if not self.new_name and not self.color:
150
+ return "Error: new_name or color is required for update action"
151
+
152
+ result = execute_tool(
153
+ tool_name="GMAIL_PATCH_LABEL",
154
+ arguments=arguments,
155
+ )
156
+
157
+ if isinstance(result, dict) and result.get("error"):
158
+ return f"Error updating Gmail label: {result.get('error')}"
159
+
160
+ data = result.get("data", {})
161
+
162
+ return json.dumps({
163
+ "provider": "gmail",
164
+ "success": True,
165
+ "action": "update",
166
+ "label_id": data.get("id"),
167
+ "label_name": data.get("name")
168
+ }, indent=2)
169
+
170
+ def _delete_gmail_label(self, execute_tool) -> str:
171
+ """Deletes a Gmail label."""
172
+ if not self.label_id:
173
+ return "Error: label_id is required for delete action"
174
+
175
+ result = execute_tool(
176
+ tool_name="GMAIL_DELETE_LABEL",
177
+ arguments={
178
+ "user_id": "me",
179
+ "label_id": self.label_id
180
+ },
181
+ )
182
+
183
+ if isinstance(result, dict) and result.get("error"):
184
+ return f"Error deleting Gmail label: {result.get('error')}"
185
+
186
+ return json.dumps({
187
+ "provider": "gmail",
188
+ "success": True,
189
+ "action": "delete",
190
+ "label_id": self.label_id
191
+ }, indent=2)
192
+
193
+ def _manage_outlook_categories(self, execute_tool) -> str:
194
+ """Manages Outlook categories."""
195
+ if self.action == "list":
196
+ return self._list_outlook_categories(execute_tool)
197
+ elif self.action == "create":
198
+ return self._create_outlook_category(execute_tool)
199
+ elif self.action == "update":
200
+ return "Error: Outlook categories cannot be renamed via API. Delete and recreate instead."
201
+ elif self.action == "delete":
202
+ return "Error: Outlook category deletion not available. Categories can only be managed in Outlook settings."
203
+ else:
204
+ return f"Unknown action: {self.action}"
205
+
206
+ def _list_outlook_categories(self, execute_tool) -> str:
207
+ """Lists all Outlook categories."""
208
+ result = execute_tool(
209
+ tool_name="OUTLOOK_GET_MASTER_CATEGORIES",
210
+ arguments={"user_id": "me"},
211
+ )
212
+
213
+ if isinstance(result, dict) and result.get("error"):
214
+ return f"Error listing Outlook categories: {result.get('error')}"
215
+
216
+ categories = result.get("data", {}).get("value", [])
217
+
218
+ formatted_categories = []
219
+ for cat in categories:
220
+ formatted_categories.append({
221
+ "id": cat.get("id"),
222
+ "name": cat.get("displayName"),
223
+ "color": cat.get("color")
224
+ })
225
+
226
+ return json.dumps({
227
+ "provider": "outlook",
228
+ "count": len(formatted_categories),
229
+ "categories": formatted_categories
230
+ }, indent=2)
231
+
232
+ def _create_outlook_category(self, execute_tool) -> str:
233
+ """Creates an Outlook category."""
234
+ if not self.label_name:
235
+ return "Error: label_name is required for create action"
236
+
237
+ arguments = {"displayName": self.label_name}
238
+
239
+ if self.color:
240
+ arguments["color"] = self.color
241
+
242
+ result = execute_tool(
243
+ tool_name="OUTLOOK_CREATE_MASTER_CATEGORY",
244
+ arguments=arguments,
245
+ )
246
+
247
+ if isinstance(result, dict) and result.get("error"):
248
+ return f"Error creating Outlook category: {result.get('error')}"
249
+
250
+ data = result.get("data", {})
251
+
252
+ return json.dumps({
253
+ "provider": "outlook",
254
+ "success": True,
255
+ "action": "create",
256
+ "category_id": data.get("id"),
257
+ "category_name": data.get("displayName"),
258
+ "color": data.get("color")
259
+ }, indent=2)
260
+
261
+
262
+ if __name__ == "__main__":
263
+ import sys
264
+ import os
265
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
266
+
267
+ print("=" * 60)
268
+ print("ManageLabels Test Suite")
269
+ print("=" * 60)
270
+ print()
271
+
272
+ # Test 1: List Gmail labels
273
+ print("Test 1: List Gmail labels")
274
+ print("-" * 60)
275
+ tool = ManageLabels(provider="gmail", action="list")
276
+ result = tool.run()
277
+ # Just show first few labels
278
+ import json
279
+ data = json.loads(result)
280
+ data["labels"] = data["labels"][:5]
281
+ print(json.dumps(data, indent=2))
282
+ print()
283
+
284
+ # Test 2: List Outlook categories
285
+ print("Test 2: List Outlook categories")
286
+ print("-" * 60)
287
+ tool = ManageLabels(provider="outlook", action="list")
288
+ result = tool.run()
289
+ print(result)
290
+ print()
291
+
292
+ print("=" * 60)
293
+ print("Tests completed!")
294
+ print("=" * 60)
295
+
@@ -0,0 +1,254 @@
1
+ from typing import Literal, Optional
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+
6
+ import os
7
+
8
+
9
+ class ProductSearch(BaseTool):
10
+ """
11
+ Searches for products on Google Shopping.
12
+
13
+ Returns product listings with prices, ratings, sellers, and availability.
14
+ Useful for price comparisons, finding deals, and product research.
15
+
16
+ RATE LIMIT: This tool can only be called ONCE per each user request (message) to save API costs.
17
+ Make sure to request enough results in a single call.
18
+ """
19
+
20
+ query: str = Field(
21
+ ...,
22
+ description="Search query for products (e.g., 'iPhone 15 Pro', 'running shoes', 'wireless headphones')"
23
+ )
24
+
25
+ location: Optional[str] = Field(
26
+ default=None,
27
+ description="Location for the search (e.g., 'United States', 'New York', 'London'). Affects pricing and availability."
28
+ )
29
+
30
+ country: Optional[str] = Field(
31
+ default="us",
32
+ description="Country code (e.g., 'us', 'gb', 'de', 'fr', 'jp')"
33
+ )
34
+
35
+ language: Optional[str] = Field(
36
+ default="en",
37
+ description="Interface language (e.g., 'en', 'es', 'fr', 'de')"
38
+ )
39
+
40
+ sort_by: Optional[Literal["relevance", "review_score", "price_low_to_high", "price_high_to_low"]] = Field(
41
+ default="relevance",
42
+ description="Sort results by: 'relevance', 'review_score', 'price_low_to_high', or 'price_high_to_low'"
43
+ )
44
+
45
+ price_min: Optional[float] = Field(
46
+ default=None,
47
+ description="Minimum price filter"
48
+ )
49
+
50
+ price_max: Optional[float] = Field(
51
+ default=None,
52
+ description="Maximum price filter"
53
+ )
54
+
55
+ condition: Optional[Literal["new", "used"]] = Field(
56
+ default=None,
57
+ description="Filter by product condition: 'new' or 'used'"
58
+ )
59
+
60
+ num_results: int = Field(
61
+ default=10,
62
+ ge=1,
63
+ le=60,
64
+ description="Number of results to return (1-60)"
65
+ )
66
+
67
+ page: int = Field(
68
+ default=1,
69
+ ge=1,
70
+ description="Page number for pagination"
71
+ )
72
+
73
+ def run(self):
74
+ try:
75
+ import requests
76
+
77
+ # Rate limiting: Check if already called in this session
78
+ if self.context and self.context.get("product_search_called", False):
79
+ return "Error: ProductSearch can only be called once per user request to save API costs. Use the results from the previous search or web search tool."
80
+
81
+ api_key = os.getenv("SEARCH_API_KEY")
82
+ if not api_key:
83
+ raise ValueError("SEARCH_API_KEY is not set. Add it to your .env to use ProductSearch.")
84
+
85
+ # Build request parameters
86
+ params = {
87
+ "engine": "google_shopping",
88
+ "api_key": api_key,
89
+ "q": self.query,
90
+ "num": self.num_results,
91
+ "page": self.page
92
+ }
93
+
94
+ # Add optional parameters
95
+ if self.location:
96
+ params["location"] = self.location
97
+
98
+ if self.country:
99
+ params["gl"] = self.country
100
+
101
+ if self.language:
102
+ params["hl"] = self.language
103
+
104
+ if self.sort_by and self.sort_by != "relevance":
105
+ params["sort_by"] = self.sort_by
106
+
107
+ if self.price_min is not None:
108
+ params["price_min"] = str(self.price_min)
109
+
110
+ if self.price_max is not None:
111
+ params["price_max"] = str(self.price_max)
112
+
113
+ if self.condition:
114
+ params["condition"] = self.condition
115
+
116
+ # Make API request
117
+ response = requests.get(
118
+ "https://www.searchapi.io/api/v1/search",
119
+ params=params,
120
+ timeout=30
121
+ )
122
+
123
+ if response.status_code != 200:
124
+ return f"Error: API returned status {response.status_code}: {response.text}"
125
+
126
+ data = response.json()
127
+
128
+ # Check for API errors
129
+ if "error" in data:
130
+ return f"Error from API: {data['error']}"
131
+
132
+ # Extract and format results
133
+ shopping_results = data.get("shopping_results", [])
134
+ shopping_ads = data.get("shopping_ads", [])
135
+
136
+ # Combine results (ads first, then organic)
137
+ all_products = []
138
+
139
+ # Process shopping ads
140
+ for ad in shopping_ads[:5]: # Limit ads to 5
141
+ all_products.append({
142
+ "type": "sponsored",
143
+ "title": ad.get("title"),
144
+ "price": ad.get("price"),
145
+ "extracted_price": ad.get("extracted_price"),
146
+ "original_price": ad.get("original_price"),
147
+ "seller": ad.get("seller"),
148
+ "rating": ad.get("rating"),
149
+ "reviews": ad.get("reviews"),
150
+ "condition": ad.get("condition"),
151
+ "delivery": ad.get("delivery"),
152
+ "link": ad.get("link"),
153
+ "image": ad.get("image")
154
+ })
155
+
156
+ # Process organic shopping results
157
+ for result in shopping_results:
158
+ all_products.append({
159
+ "type": "organic",
160
+ "title": result.get("title"),
161
+ "price": result.get("price"),
162
+ "extracted_price": result.get("extracted_price"),
163
+ "original_price": result.get("original_price"),
164
+ "seller": result.get("seller"),
165
+ "rating": result.get("rating"),
166
+ "reviews": result.get("reviews"),
167
+ "condition": result.get("condition"),
168
+ "delivery": result.get("delivery"),
169
+ "offers": result.get("offers"),
170
+ "product_id": result.get("product_id") or result.get("prds"),
171
+ "product_link": result.get("product_link"),
172
+ "thumbnail": result.get("thumbnail")
173
+ })
174
+
175
+ # Mark as called in shared state (rate limiting)
176
+ if self.context:
177
+ self.context.set("product_search_called", True)
178
+
179
+ return json.dumps({
180
+ "query": self.query,
181
+ "location": self.location,
182
+ "sort_by": self.sort_by,
183
+ "filters": {
184
+ "price_min": self.price_min,
185
+ "price_max": self.price_max,
186
+ "condition": self.condition
187
+ },
188
+ "total_results": len(all_products),
189
+ "page": self.page,
190
+ "products": all_products
191
+ }, indent=2)
192
+
193
+ except Exception as e:
194
+ return f"Error searching products: {str(e)}"
195
+
196
+
197
+
198
+ if __name__ == "__main__":
199
+ import sys
200
+ import os
201
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
202
+
203
+ print("=" * 60)
204
+ print("ProductSearch Test Suite")
205
+ print("=" * 60)
206
+ print()
207
+
208
+ # Test 1: Basic search
209
+ print("Test 1: Basic product search")
210
+ print("-" * 60)
211
+ tool = ProductSearch(
212
+ query="wireless headphones",
213
+ num_results=5
214
+ )
215
+ result = tool.run()
216
+
217
+ try:
218
+ data = json.loads(result)
219
+ print(f"Query: {data['query']}")
220
+ print(f"Total results: {data['total_results']}")
221
+ if data['products']:
222
+ print(f"First product: {data['products'][0]['title']}")
223
+ print(f" Price: {data['products'][0]['price']}")
224
+ print(f" Seller: {data['products'][0]['seller']}")
225
+ except json.JSONDecodeError:
226
+ print(result)
227
+ print()
228
+
229
+ # Test 2: Search with price filter
230
+ print("Test 2: Search with price filter")
231
+ print("-" * 60)
232
+ tool = ProductSearch(
233
+ query="running shoes",
234
+ price_min=50,
235
+ price_max=150,
236
+ sort_by="price_low_to_high",
237
+ num_results=3
238
+ )
239
+ result = tool.run()
240
+
241
+ try:
242
+ data = json.loads(result)
243
+ print(f"Query: {data['query']}")
244
+ print(f"Filters: {data['filters']}")
245
+ print(f"Sort: {data['sort_by']}")
246
+ print(f"Results: {data['total_results']}")
247
+ except json.JSONDecodeError:
248
+ print(result)
249
+ print()
250
+
251
+ print("=" * 60)
252
+ print("Tests completed!")
253
+ print("=" * 60)
254
+