@_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,227 @@
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 RescheduleCalendarEvent(BaseTool):
10
+ """
11
+ Reschedules an existing calendar event to a new date/time.
12
+
13
+ Supports both Google Calendar and Outlook. Can update:
14
+ - Start and end times
15
+ - Event title
16
+ - Location
17
+ - Description
18
+
19
+ Use CheckEventsForDate first to get the event_id of the event to reschedule.
20
+ """
21
+
22
+ provider: Literal["google", "outlook"] = Field(
23
+ ...,
24
+ description="Calendar provider: 'google' or 'outlook'"
25
+ )
26
+
27
+ event_id: str = Field(
28
+ ...,
29
+ description="The unique ID of the event to reschedule (from CheckEventsForDate)"
30
+ )
31
+
32
+ new_start_datetime: Optional[str] = Field(
33
+ default=None,
34
+ description="New start date/time in ISO format (e.g., '2026-01-10T14:00:00'). Required for rescheduling."
35
+ )
36
+
37
+ new_end_datetime: Optional[str] = Field(
38
+ default=None,
39
+ description="New end date/time in ISO format. If not provided, will be calculated based on original duration."
40
+ )
41
+
42
+ timezone: Optional[str] = Field(
43
+ default=None,
44
+ description="IANA timezone (e.g., 'America/New_York', 'Asia/Dubai'). Uses event's current timezone if not specified."
45
+ )
46
+
47
+ new_title: Optional[str] = Field(
48
+ default=None,
49
+ description="New title/subject for the event. Leave empty to keep current title."
50
+ )
51
+
52
+ new_location: Optional[str] = Field(
53
+ default=None,
54
+ description="New location for the event. Leave empty to keep current location."
55
+ )
56
+
57
+ new_description: Optional[str] = Field(
58
+ default=None,
59
+ description="New description for the event. Leave empty to keep current description."
60
+ )
61
+
62
+ send_updates: Literal["all", "externalOnly", "none"] = Field(
63
+ default="all",
64
+ description="Who to notify about the change: 'all', 'externalOnly', or 'none'"
65
+ )
66
+
67
+ def run(self):
68
+ try:
69
+ if self.provider == "google":
70
+ return self._reschedule_google_event(execute_composio_tool)
71
+ else:
72
+ return self._reschedule_outlook_event(execute_composio_tool)
73
+
74
+ except Exception as e:
75
+ return f"Error rescheduling event: {str(e)}"
76
+
77
+ def _reschedule_google_event(self, execute_tool) -> str:
78
+ """Reschedules a Google Calendar event."""
79
+ # Build arguments - only include fields that are being updated
80
+ arguments = {
81
+ "calendar_id": "primary",
82
+ "event_id": self.event_id
83
+ }
84
+
85
+ if self.new_start_datetime:
86
+ arguments["start_time"] = self.new_start_datetime
87
+
88
+ if self.new_end_datetime:
89
+ arguments["end_time"] = self.new_end_datetime
90
+
91
+ if self.timezone:
92
+ arguments["timezone"] = self.timezone
93
+
94
+ if self.new_title:
95
+ arguments["summary"] = self.new_title
96
+
97
+ if self.new_location:
98
+ arguments["location"] = self.new_location
99
+
100
+ if self.new_description:
101
+ arguments["description"] = self.new_description
102
+
103
+ arguments["send_updates"] = self.send_updates
104
+
105
+ # Execute the patch
106
+ result = execute_tool(
107
+ tool_name="GOOGLECALENDAR_PATCH_EVENT",
108
+ arguments=arguments,
109
+ )
110
+
111
+ if isinstance(result, dict) and result.get("error"):
112
+ return f"Error rescheduling Google Calendar event: {result.get('error')}"
113
+
114
+ data = result.get("data", {})
115
+
116
+ # Extract updated event details
117
+ start = data.get("start", {})
118
+ end = data.get("end", {})
119
+
120
+ return json.dumps({
121
+ "provider": "google",
122
+ "success": True,
123
+ "event_id": data.get("id"),
124
+ "title": data.get("summary", "(No title)"),
125
+ "new_start": start.get("dateTime") or start.get("date"),
126
+ "new_end": end.get("dateTime") or end.get("date"),
127
+ "location": data.get("location", ""),
128
+ "html_link": data.get("htmlLink", ""),
129
+ "updates_sent_to": self.send_updates
130
+ }, indent=2)
131
+
132
+ def _reschedule_outlook_event(self, execute_tool) -> str:
133
+ """Reschedules an Outlook calendar event."""
134
+ # Build arguments - only include fields that are being updated
135
+ arguments = {
136
+ "user_id": "me",
137
+ "event_id": self.event_id
138
+ }
139
+
140
+ if self.new_start_datetime:
141
+ arguments["start_datetime"] = self.new_start_datetime
142
+
143
+ if self.new_end_datetime:
144
+ arguments["end_datetime"] = self.new_end_datetime
145
+
146
+ if self.timezone:
147
+ arguments["time_zone"] = self.timezone
148
+
149
+ if self.new_title:
150
+ arguments["subject"] = self.new_title
151
+
152
+ if self.new_location:
153
+ arguments["location"] = self.new_location
154
+
155
+ if self.new_description:
156
+ arguments["body"] = {
157
+ "contentType": "Text",
158
+ "content": self.new_description
159
+ }
160
+
161
+ # Execute the update
162
+ result = execute_tool(
163
+ tool_name="OUTLOOK_UPDATE_CALENDAR_EVENT",
164
+ arguments=arguments,
165
+ )
166
+
167
+ if isinstance(result, dict) and result.get("error"):
168
+ return f"Error rescheduling Outlook event: {result.get('error')}"
169
+
170
+ data = result.get("data", {})
171
+
172
+ # Extract updated event details
173
+ start = data.get("start", {})
174
+ end = data.get("end", {})
175
+
176
+ return json.dumps({
177
+ "provider": "outlook",
178
+ "success": True,
179
+ "event_id": data.get("id"),
180
+ "title": data.get("subject", "(No title)"),
181
+ "new_start": start.get("dateTime"),
182
+ "new_end": end.get("dateTime"),
183
+ "location": data.get("location", {}).get("displayName", ""),
184
+ "web_link": data.get("webLink", "")
185
+ }, indent=2)
186
+
187
+
188
+ if __name__ == "__main__":
189
+ import sys
190
+ import os
191
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
192
+
193
+ print("=" * 60)
194
+ print("RescheduleCalendarEvent Test Suite")
195
+ print("=" * 60)
196
+ print()
197
+
198
+ # First, get an event to reschedule
199
+ from virtual_assistant.tools.CheckEventsForDate import CheckEventsForDate
200
+
201
+ print("=== Getting events for tomorrow ===")
202
+ from datetime import datetime, timedelta
203
+ tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
204
+
205
+ check_tool = CheckEventsForDate(provider="google", date=tomorrow)
206
+ result = check_tool.run()
207
+ print(result)
208
+ print()
209
+
210
+ import json
211
+ try:
212
+ data = json.loads(result)
213
+ if data.get("events"):
214
+ event = data["events"][0]
215
+ event_id = event["event_id"]
216
+ print(f"Found event: {event['title']} (ID: {event_id})")
217
+ else:
218
+ print("No events found for tomorrow to test with")
219
+ except json.JSONDecodeError:
220
+ print(f"Result: {result}")
221
+
222
+ print()
223
+ print("=" * 60)
224
+ print("Test completed!")
225
+ print("=" * 60)
226
+
227
+
@@ -0,0 +1,216 @@
1
+ from typing import Optional
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field
4
+ import json
5
+
6
+ import os
7
+
8
+
9
+ class ScholarSearch(BaseTool):
10
+ """
11
+ Searches for scholarly literature on Google Scholar.
12
+
13
+ Returns academic papers, articles, theses, books, and conference papers.
14
+ Includes links to PDFs and full-text resources when available.
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 scholarly articles (e.g., 'machine learning', 'climate change effects', 'quantum computing')"
23
+ )
24
+
25
+ year_from: Optional[int] = Field(
26
+ default=None,
27
+ description="Filter results from this year onwards (e.g., 2020)"
28
+ )
29
+
30
+ year_to: Optional[int] = Field(
31
+ default=None,
32
+ description="Filter results up to this year (e.g., 2024)"
33
+ )
34
+
35
+ num_results: int = Field(
36
+ default=10,
37
+ ge=1,
38
+ le=20,
39
+ description="Number of results to return (1-20)"
40
+ )
41
+
42
+ page: int = Field(
43
+ default=1,
44
+ ge=1,
45
+ description="Page number for pagination"
46
+ )
47
+
48
+ def run(self):
49
+ try:
50
+ import requests
51
+
52
+ # Rate limiting: Check if already called in this session
53
+ if self.context and self.context.get("scholar_search_called", False):
54
+ return "Error: ScholarSearch can only be called once per user request to save API costs. Use the results from the previous search or web search tool."
55
+
56
+ api_key = os.getenv("SEARCH_API_KEY")
57
+ if not api_key:
58
+ raise ValueError("SEARCH_API_KEY is not set. Add it to your .env to use ScholarSearch.")
59
+
60
+ # Build request parameters
61
+ params = {
62
+ "engine": "google_scholar",
63
+ "api_key": api_key,
64
+ "q": self.query,
65
+ "num": self.num_results,
66
+ "page": self.page
67
+ }
68
+
69
+ # Add year filters
70
+ if self.year_from:
71
+ params["as_ylo"] = self.year_from
72
+
73
+ if self.year_to:
74
+ params["as_yhi"] = self.year_to
75
+
76
+ # Make API request
77
+ response = requests.get(
78
+ "https://www.searchapi.io/api/v1/search",
79
+ params=params,
80
+ timeout=30
81
+ )
82
+
83
+ if response.status_code != 200:
84
+ return f"Error: API returned status {response.status_code}: {response.text}"
85
+
86
+ data = response.json()
87
+
88
+ # Check for API errors
89
+ if "error" in data:
90
+ return f"Error from API: {data['error']}"
91
+
92
+ # Extract results
93
+ organic_results = data.get("organic_results", [])
94
+ profiles = data.get("profiles", [])
95
+ search_info = data.get("search_information", {})
96
+
97
+ # Format articles
98
+ articles = []
99
+ for result in organic_results:
100
+ # Extract authors
101
+ authors = []
102
+ for author in result.get("authors", []):
103
+ authors.append(author.get("name", "Unknown"))
104
+
105
+ # Extract citation info
106
+ inline_links = result.get("inline_links", {})
107
+ cited_by = inline_links.get("cited_by", {})
108
+ versions = inline_links.get("versions", {})
109
+
110
+ # Extract resource (PDF, etc.)
111
+ resource = result.get("resource", {})
112
+
113
+ article = {
114
+ "title": result.get("title"),
115
+ "link": result.get("link"),
116
+ "publication": result.get("publication"),
117
+ "snippet": result.get("snippet"),
118
+ "authors": authors,
119
+ "citations": cited_by.get("total"),
120
+ "cites_id": cited_by.get("cites_id"),
121
+ "versions_count": versions.get("total"),
122
+ "cluster_id": versions.get("cluster_id")
123
+ }
124
+
125
+ # Add resource link (PDF, etc.) - important for reading full papers
126
+ if resource:
127
+ article["resource"] = {
128
+ "name": resource.get("name"),
129
+ "format": resource.get("format"),
130
+ "link": resource.get("link")
131
+ }
132
+
133
+ # Add related links
134
+ if inline_links.get("related_articles_link"):
135
+ article["related_articles_link"] = inline_links.get("related_articles_link")
136
+
137
+ articles.append(article)
138
+
139
+ # Format author profiles if any
140
+ author_profiles = []
141
+ for profile in profiles:
142
+ author_profiles.append({
143
+ "name": profile.get("name"),
144
+ "affiliations": profile.get("affiliations"),
145
+ "email_domain": profile.get("email"),
146
+ "total_citations": profile.get("cited_by", {}).get("total"),
147
+ "profile_link": profile.get("link"),
148
+ "author_id": profile.get("author_id")
149
+ })
150
+
151
+ # Mark as called in shared state (rate limiting)
152
+ if self.context:
153
+ self.context.set("scholar_search_called", True)
154
+
155
+ result = {
156
+ "query": self.query,
157
+ "filters": {
158
+ "year_from": self.year_from,
159
+ "year_to": self.year_to
160
+ },
161
+ "total_results": search_info.get("total_results"),
162
+ "page": self.page,
163
+ "articles_count": len(articles),
164
+ "articles": articles
165
+ }
166
+
167
+ if author_profiles:
168
+ result["author_profiles"] = author_profiles
169
+
170
+ return json.dumps(result, indent=2)
171
+
172
+ except Exception as e:
173
+ return f"Error searching scholar: {str(e)}"
174
+
175
+
176
+
177
+ if __name__ == "__main__":
178
+ import sys
179
+ import os
180
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
181
+
182
+ print("=" * 60)
183
+ print("ScholarSearch Test Suite")
184
+ print("=" * 60)
185
+ print()
186
+
187
+ # Test 1: Basic search
188
+ print("Test 1: Basic scholarly search")
189
+ print("-" * 60)
190
+ tool = ScholarSearch(
191
+ query="transformer architecture deep learning",
192
+ num_results=5
193
+ )
194
+ result = tool.run()
195
+
196
+ try:
197
+ data = json.loads(result)
198
+ print(f"Query: {data['query']}")
199
+ print(f"Total results: {data.get('total_results', 'N/A')}")
200
+ print(f"Articles returned: {data['articles_count']}")
201
+ print()
202
+
203
+ for i, article in enumerate(data['articles'][:3], 1):
204
+ print(f"{i}. {article['title']}")
205
+ print(f" Authors: {', '.join(article['authors'][:3])}...")
206
+ print(f" Citations: {article.get('citations', 'N/A')}")
207
+ if article.get('resource'):
208
+ print(f" PDF: {article['resource'].get('link', 'N/A')}")
209
+ print()
210
+ except json.JSONDecodeError:
211
+ print(result)
212
+
213
+ print("=" * 60)
214
+ print("Tests completed!")
215
+ print("=" * 60)
216
+
@@ -0,0 +1,101 @@
1
+ from typing import Literal
2
+ from agency_swarm.tools import BaseTool
3
+ from pydantic import Field, field_validator
4
+ import json
5
+
6
+ from helpers import execute_composio_tool
7
+
8
+
9
+ class SendDraft(BaseTool):
10
+ """
11
+ Sends an existing email draft by its ID.
12
+
13
+ Use this after creating a draft with DraftEmail and getting user approval.
14
+ The draft must have at least one recipient (to, cc, or bcc) to be sent.
15
+ """
16
+
17
+ provider: Literal["gmail", "outlook"] = Field(
18
+ ...,
19
+ description="Email provider: 'gmail' or 'outlook'"
20
+ )
21
+
22
+ draft_id: str = Field(
23
+ ...,
24
+ description="The draft ID to send (obtained from DraftEmail tool)"
25
+ )
26
+
27
+ @field_validator("draft_id")
28
+ @classmethod
29
+ def validate_draft_id(cls, v: str) -> str:
30
+ if "noop" in v.lower():
31
+ raise ValueError("This tool should used to send an existing EMAIL draft. REVIEW YOUR INSTRUCTIONS AND USE THE APPROPRIATE TOOLS TO COMPLETE YOUR TASKS.")
32
+ return v
33
+
34
+ def run(self):
35
+ try:
36
+ if self.provider == "gmail":
37
+ return self._send_gmail_draft(execute_composio_tool)
38
+ else:
39
+ return self._send_outlook_draft(execute_composio_tool)
40
+
41
+ except Exception as e:
42
+ return f"Error sending draft: {str(e)}"
43
+
44
+ def _send_gmail_draft(self, execute_tool) -> str:
45
+ """Sends a Gmail draft."""
46
+ result = execute_tool(
47
+ tool_name="GMAIL_SEND_DRAFT",
48
+ arguments={"user_id": "me", "draft_id": self.draft_id},
49
+ )
50
+
51
+ if isinstance(result, dict) and result.get("error"):
52
+ return f"Error sending Gmail draft: {result.get('error')}"
53
+
54
+ data = result.get("data", {})
55
+
56
+ return json.dumps({
57
+ "provider": "gmail",
58
+ "success": True,
59
+ "message": "Email sent successfully",
60
+ "message_id": data.get("id"),
61
+ "thread_id": data.get("threadId"),
62
+ "labels": data.get("labelIds", [])
63
+ }, indent=2)
64
+
65
+ def _send_outlook_draft(self, execute_tool) -> str:
66
+ """Sends an Outlook draft."""
67
+ result = execute_tool(
68
+ tool_name="OUTLOOK_SEND_DRAFT",
69
+ arguments={"user_id": "me", "message_id": self.draft_id},
70
+ )
71
+
72
+ if isinstance(result, dict) and result.get("error"):
73
+ return f"Error sending Outlook draft: {result.get('error')}"
74
+
75
+ # Outlook send returns HTTP 202 with no body
76
+ return json.dumps({
77
+ "provider": "outlook",
78
+ "success": True,
79
+ "message": "Email sent successfully",
80
+ "draft_id": self.draft_id
81
+ }, indent=2)
82
+
83
+
84
+ if __name__ == "__main__":
85
+ import sys
86
+ import os
87
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
88
+
89
+ print("=" * 60)
90
+ print("SendDraft Test Suite")
91
+ print("=" * 60)
92
+ print()
93
+ print("To test SendDraft, first create a draft using DraftEmail,")
94
+ print("then use the draft_id to send it.")
95
+ print()
96
+ print("Example:")
97
+ print(" tool = SendDraft(provider='gmail', draft_id='r12345...')")
98
+ print(" result = tool.run()")
99
+ print()
100
+ print("=" * 60)
101
+
@@ -0,0 +1,148 @@
1
+ from typing import 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 SendSlackMessage(BaseTool):
10
+ """
11
+ Sends a message to a Slack channel or DM. Supports threaded replies.
12
+ Use channel ID or name. For replies, provide the parent message timestamp.
13
+ """
14
+
15
+ channel: str = Field(
16
+ ...,
17
+ description="Channel ID or name (e.g., 'C06NX4Q1ACE', '#general', or '@username')"
18
+ )
19
+
20
+ text: str = Field(
21
+ ...,
22
+ description="Message text to send. Use Slack formatting."
23
+ )
24
+
25
+ thread_ts: Optional[str] = Field(
26
+ default=None,
27
+ description="Parent message timestamp to reply in thread. Leave empty for new message."
28
+ )
29
+
30
+ def run(self):
31
+ try:
32
+ # Resolve channel
33
+ channel_id = self._resolve_channel(execute_composio_tool, self.channel)
34
+ if channel_id.startswith("Error"):
35
+ return channel_id
36
+
37
+ # Send message
38
+ args = {
39
+ "channel": channel_id,
40
+ "text": self.text
41
+ }
42
+
43
+ if self.thread_ts:
44
+ args["thread_ts"] = self.thread_ts
45
+
46
+ result = execute_composio_tool(
47
+ tool_name="SLACK_SEND_MESSAGE",
48
+ arguments=args,
49
+ )
50
+
51
+ if result.get("error"):
52
+ return f"Error sending message: {result.get('error')}"
53
+
54
+ data = result.get("data", {})
55
+ msg = data.get("message", {})
56
+
57
+ return json.dumps({
58
+ "success": True,
59
+ "channel_id": channel_id,
60
+ "ts": msg.get("ts"),
61
+ "thread_ts": self.thread_ts,
62
+ "permalink": self._build_permalink(data, channel_id, msg.get("ts"))
63
+ }, indent=2)
64
+
65
+ except Exception as e:
66
+ return f"Error: {str(e)}"
67
+
68
+ def _resolve_channel(self, execute_tool, channel: str) -> str:
69
+ """Resolves channel/user name to ID."""
70
+ # Already an ID
71
+ if channel.startswith(("C", "D", "G")):
72
+ return channel
73
+
74
+ # Handle @username for DMs
75
+ if channel.startswith("@"):
76
+ return self._find_user_dm(execute_tool, channel[1:])
77
+
78
+ # Remove # prefix and find channel
79
+ name = channel.lstrip("#")
80
+
81
+ result = execute_tool(
82
+ tool_name="SLACK_FIND_CHANNELS",
83
+ arguments={"query": name},
84
+ )
85
+
86
+ if result.get("error"):
87
+ return f"Error: Could not find channel '{name}'"
88
+
89
+ channels = result.get("data", {}).get("channels", [])
90
+ for ch in channels:
91
+ if ch.get("name") == name:
92
+ return ch.get("id")
93
+
94
+ return f"Error: Channel '{name}' not found"
95
+
96
+ def _find_user_dm(self, execute_tool, username: str) -> str:
97
+ """Finds DM channel for a user."""
98
+ # Find user
99
+ result = execute_tool(
100
+ tool_name="SLACK_FIND_USERS",
101
+ arguments={"query": username},
102
+ )
103
+
104
+ if result.get("error"):
105
+ return f"Error: Could not find user '{username}'"
106
+
107
+ members = result.get("data", {}).get("members", [])
108
+ target_user = None
109
+
110
+ for member in members:
111
+ name = member.get("name", "").lower()
112
+ display = member.get("profile", {}).get("display_name", "").lower()
113
+ real = member.get("profile", {}).get("real_name", "").lower()
114
+
115
+ if username.lower() in [name, display, real]:
116
+ target_user = member.get("id")
117
+ break
118
+
119
+ if not target_user:
120
+ return f"Error: User '{username}' not found"
121
+
122
+ # Find existing DM or it will be created when sending
123
+ # For now, return a marker that we need to send to user
124
+ # Slack's SEND_MESSAGE can accept user IDs for DMs
125
+ return target_user
126
+
127
+ def _build_permalink(self, data: dict, channel_id: str, ts: str) -> str:
128
+ """Builds message permalink if available."""
129
+ # Some responses include permalink directly
130
+ if "permalink" in data:
131
+ return data["permalink"]
132
+
133
+ # Otherwise return empty - would need workspace URL
134
+ return ""
135
+
136
+
137
+ if __name__ == "__main__":
138
+ import sys
139
+ import os
140
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
141
+
142
+ print("SendSlackMessage Test")
143
+ print("-" * 40)
144
+ print("Skipping actual send to avoid spam")
145
+ print("Example usage:")
146
+ print(' SendSlackMessage(channel="#random", text="Hello!")')
147
+ print(' SendSlackMessage(channel="C123", text="Reply", thread_ts="1234.5678")')
148
+