@_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,127 @@
1
+ """Search images across Unsplash, Pexels, and Pixabay."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from urllib.parse import urlencode
7
+ from urllib.request import Request, urlopen
8
+
9
+ import os
10
+ from agency_swarm.tools import BaseTool
11
+ from pydantic import Field
12
+
13
+
14
+ class ImageSearch(BaseTool):
15
+ """
16
+ Search for existing images on the internet. Use this when the user wants to find real photos, diagrams, or illustrations of something rather than generating new images.
17
+ Do not use this tool to find specific logos, icons, or other brand-specific images. It only provides generic images that are not brand-specific.
18
+ """
19
+
20
+ query: str = Field(
21
+ ...,
22
+ description="Image search query",
23
+ )
24
+ per_page: int = Field(
25
+ default=6,
26
+ description="Number of results per provider",
27
+ )
28
+ providers: list[str] | None = Field(
29
+ default=None,
30
+ description="Providers to search: unsplash, pexels, pixabay",
31
+ )
32
+
33
+ def run(self) -> str:
34
+ providers = [p.lower() for p in (self.providers or ["unsplash", "pexels", "pixabay"])]
35
+ results = []
36
+ warnings = []
37
+ if "unsplash" in providers:
38
+ key = os.getenv("UNSPLASH_ACCESS_KEY")
39
+ if not key:
40
+ warnings.append("Unsplash skipped: UNSPLASH_ACCESS_KEY not set.")
41
+ else:
42
+ url = "https://api.unsplash.com/search/photos?" + urlencode({
43
+ "query": self.query,
44
+ "per_page": self.per_page,
45
+ })
46
+ req = Request(url, headers={"Authorization": f"Client-ID {key}"})
47
+ with urlopen(req, timeout=20) as response:
48
+ data = json.loads(response.read().decode("utf-8"))
49
+ for item in data.get("results", []):
50
+ results.append({
51
+ "source": "unsplash",
52
+ "image_url": item.get("urls", {}).get("regular"),
53
+ "thumbnail_url": item.get("urls", {}).get("thumb"),
54
+ "description": item.get("description") or item.get("alt_description") or "",
55
+ "photographer": (item.get("user") or {}).get("name"),
56
+ "width": item.get("width"),
57
+ "height": item.get("height"),
58
+ "link": item.get("links", {}).get("html"),
59
+ })
60
+
61
+ if "pexels" in providers:
62
+ key = os.getenv("PEXELS_API_KEY")
63
+ if not key:
64
+ warnings.append("Pexels skipped: PEXELS_API_KEY not set.")
65
+ else:
66
+ url = "https://api.pexels.com/v1/search?" + urlencode({
67
+ "query": self.query,
68
+ "per_page": self.per_page,
69
+ })
70
+ req = Request(url, headers={"Authorization": key})
71
+ with urlopen(req, timeout=20) as response:
72
+ data = json.loads(response.read().decode("utf-8"))
73
+ for item in data.get("photos", []):
74
+ results.append({
75
+ "source": "pexels",
76
+ "image_url": (item.get("src") or {}).get("large"),
77
+ "thumbnail_url": (item.get("src") or {}).get("tiny"),
78
+ "description": item.get("alt") or "",
79
+ "photographer": item.get("photographer"),
80
+ "width": item.get("width"),
81
+ "height": item.get("height"),
82
+ "link": item.get("url"),
83
+ })
84
+
85
+ if "pixabay" in providers:
86
+ key = os.getenv("PIXABAY_API_KEY")
87
+ if not key:
88
+ warnings.append("Pixabay skipped: PIXABAY_API_KEY not set.")
89
+ else:
90
+ url = "https://pixabay.com/api/?" + urlencode({
91
+ "key": key,
92
+ "q": self.query,
93
+ "per_page": self.per_page,
94
+ })
95
+ req = Request(url)
96
+ with urlopen(req, timeout=20) as response:
97
+ data = json.loads(response.read().decode("utf-8"))
98
+ for item in data.get("hits", []):
99
+ results.append({
100
+ "source": "pixabay",
101
+ "image_url": item.get("largeImageURL"),
102
+ "thumbnail_url": item.get("previewURL"),
103
+ "description": item.get("tags") or "",
104
+ "photographer": item.get("user"),
105
+ "width": item.get("imageWidth"),
106
+ "height": item.get("imageHeight"),
107
+ "link": item.get("pageURL"),
108
+ })
109
+
110
+ if not results:
111
+ if len(warnings) == len(providers):
112
+ raise ValueError(
113
+ "No image source keys are set. Add at least one of "
114
+ "UNSPLASH_ACCESS_KEY, PEXELS_API_KEY, or PIXABAY_API_KEY to your .env to use ImageSearch."
115
+ )
116
+ return f"No images found for '{self.query}'."
117
+
118
+ return json.dumps({
119
+ "query": self.query,
120
+ "results": results,
121
+ "warnings": warnings,
122
+ }, indent=2)
123
+
124
+
125
+ if __name__ == "__main__":
126
+ tool = ImageSearch(query="abstract digital technology mesh background")
127
+ print(tool.run())
@@ -0,0 +1,393 @@
1
+ """
2
+ Insert blank slide placeholders before a position.
3
+
4
+ Uses task_brief to plan outline and execution order. Slide content is generated later via ModifySlide.
5
+ Do not use in parallel with other tools.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import re
11
+ import threading
12
+ from pathlib import Path
13
+ from typing import Literal
14
+
15
+ import os
16
+ from agency_swarm import Agent, ModelSettings, Reasoning
17
+ from agency_swarm.tools import BaseTool
18
+ from agents.extensions.models.litellm_model import LitellmModel
19
+ from pydantic import BaseModel, Field, ValidationError
20
+
21
+ from .slide_file_utils import (
22
+ apply_renames,
23
+ build_slide_name,
24
+ compute_pad_width,
25
+ get_project_dir,
26
+ list_slide_files,
27
+ )
28
+ from .slide_html_utils import ensure_full_html
29
+ from .template_registry import load_template_index
30
+
31
+
32
+ _PLANNER_MODEL_CLAUDE = "anthropic/claude-sonnet-4-6"
33
+ _PLANNER_MODEL_OAI = "gpt-5.2-codex"
34
+
35
+
36
+ class _PlanSlide(BaseModel):
37
+ page: int
38
+ title: str
39
+ content: str
40
+ template_key: str | None = None
41
+ template_name: str | None = None
42
+ template_status: Literal["existing", "new"] | None = None
43
+ depends_on: int | None = None
44
+
45
+
46
+ class _PlanResponse(BaseModel):
47
+ slides: list[_PlanSlide]
48
+
49
+
50
+ def _make_planner_agent(caller_model=None) -> Agent:
51
+ """Create a fresh, stateless agent instance for one InsertNewSlides call.
52
+
53
+ Model priority:
54
+ 1. ANTHROPIC_API_KEY in env → Claude Sonnet 4.6 (best planning quality)
55
+ 2. caller_model → inherit from the calling agent (covers /auth TUI flow)
56
+ """
57
+ anthropic_key = os.getenv("ANTHROPIC_API_KEY")
58
+ if anthropic_key:
59
+ model = LitellmModel(model=_PLANNER_MODEL_CLAUDE, api_key=anthropic_key)
60
+ else:
61
+ model = caller_model or _PLANNER_MODEL_OAI
62
+ return Agent(
63
+ name="Slide Planner",
64
+ description="Creates structured slide outline plans.",
65
+ instructions=(
66
+ "You generate JSON plans for slide creation. "
67
+ "Output must be valid JSON only, no markdown fences, no extra text."
68
+ ),
69
+ tools=[],
70
+ model=model,
71
+ model_settings=ModelSettings(
72
+ reasoning=Reasoning(effort="high", summary="auto"),
73
+ verbosity="medium",
74
+ ),
75
+ )
76
+
77
+
78
+ def _run_awaitable(awaitable):
79
+ box: dict[str, object] = {}
80
+ err: dict[str, BaseException] = {}
81
+
82
+ def _worker() -> None:
83
+ try:
84
+ box["result"] = asyncio.run(awaitable)
85
+ except BaseException as exc: # noqa: BLE001
86
+ err["error"] = exc
87
+
88
+ thread = threading.Thread(
89
+ target=_worker,
90
+ name="insert-slides-awaitable-worker",
91
+ daemon=True,
92
+ )
93
+ thread.start()
94
+ thread.join(timeout=180)
95
+ if thread.is_alive():
96
+ raise TimeoutError("InsertNewSlides planner timed out after 180s")
97
+ if "error" in err:
98
+ raise err["error"]
99
+ return box.get("result")
100
+
101
+
102
+ def _extract_json_block(text: str) -> str:
103
+ raw = (text or "").strip()
104
+ if not raw:
105
+ return ""
106
+ match = re.search(r"```(?:json)?\s*(.*?)```", raw, flags=re.IGNORECASE | re.DOTALL)
107
+ return (match.group(1) if match else raw).strip()
108
+
109
+
110
+ def _build_planner_prompt(
111
+ task_brief: str,
112
+ count: int,
113
+ insert_position: int,
114
+ existing_templates: dict[str, dict[str, str]],
115
+ ) -> str:
116
+ template_lines = []
117
+ for key, value in existing_templates.items():
118
+ name = str(value.get("name", key))
119
+ template_lines.append(f'- "{key}": "{name}"')
120
+ template_block = "\n".join(template_lines) if template_lines else "(none)"
121
+ return (
122
+ "Create a structured plan for inserting blank slides. "
123
+ "Return JSON only with shape:\n"
124
+ '{ "slides": [ { "page": int, "title": str, "content": str, "template_key": str|null, "template_name": str|null, "template_status": "existing"|"new"|null, "depends_on": int|null } ] }\n'
125
+ f"Constraints:\n- exactly {count} slides\n"
126
+ f"- pages must be contiguous from {insert_position} to {insert_position + count - 1}\n"
127
+ "- concise titles; content should describe WHAT the slide covers (topic and key points), not HOW it should look — no layout instructions, no column descriptions, no visual prescriptions\n"
128
+ "- (CRITICAL) No inline code snippets or code blocks.\n\n"
129
+ "Template assignment rules (CRITICAL):\n"
130
+ "- A template represents a LAYOUT PATTERN, not an individual slide. By default, assign each slide its own unique template_key.\n"
131
+ "- Only share a template_key across slides when the layout is genuinely identical (e.g. a repeated content card format). Reuse templates only when fitting and do not reuse them often.\n"
132
+ "- Never reuse a template just to save keys. Distinct slides deserve distinct templates.\n"
133
+ "- Never use the same template for adjacent slides.\n"
134
+ "- When multiple slides do share a template_key, only the FIRST slide gets template_status 'new'. All subsequent slides get template_status 'existing'.\n"
135
+ " Example: slides 2, 5, 9 all use layout 'two_col_content'. Slide 2: template_status 'new'. Slides 5, 9: template_status 'existing'.\n\n"
136
+ "Sequential rule:\n"
137
+ "- depends_on is null by default and should almost always stay null.\n"
138
+ "- Only set it when this slide is a direct continuation of another slide's specific content (e.g. 'Part 2 of X' that cannot be written without knowing what Part 1 said). Narrative flow, thematic progression, or being 'related to' another slide are NOT valid reasons.\n"
139
+ f"Task brief:\n{task_brief.strip()}\n\n"
140
+ f"Existing templates:\n{template_block}\n"
141
+ )
142
+
143
+
144
+ def _infer_template_key(title: str) -> str:
145
+ """Infer a stable template key from full page title text."""
146
+ raw = re.sub(r"[^a-z0-9\s]+", " ", title.lower())
147
+ words = [
148
+ w for w in raw.split() if w and w not in {"and", "the", "of", "to", "for", "in"}
149
+ ]
150
+ if not words:
151
+ return "content_default"
152
+ return "_".join(words)
153
+
154
+
155
+ def _pretty_template_name(template_key: str) -> str:
156
+ return " ".join(part.capitalize() for part in template_key.split("_"))
157
+
158
+
159
+ def _normalize_outline(
160
+ plan: _PlanResponse,
161
+ count: int,
162
+ insert_position: int,
163
+ existing_templates: dict[str, dict[str, str]],
164
+ ) -> list[dict[str, str | int]]:
165
+ existing_keys = set(existing_templates.keys())
166
+ rows: list[dict[str, str | int]] = []
167
+ for i in range(count):
168
+ page = insert_position + i
169
+ src = (
170
+ plan.slides[i]
171
+ if i < len(plan.slides)
172
+ else _PlanSlide(page=page, title=f"Slide {i + 1}", content="Content")
173
+ )
174
+ title = src.title.strip() if src.title else f"Slide {i + 1}"
175
+ content = src.content.strip() if src.content else title
176
+ key = (src.template_key or "").strip() or _infer_template_key(title)
177
+ if key in existing_keys:
178
+ status = "existing"
179
+ name = existing_templates.get(key, {}).get(
180
+ "name", _pretty_template_name(key)
181
+ )
182
+ else:
183
+ # Respect the planner's intra-batch reuse declaration: if the planner
184
+ # says "existing" for a key not in the registry, it means a previous
185
+ # slide in this batch creates the template and this slide reuses it.
186
+ status = "existing" if src.template_status == "existing" else "new"
187
+ name = (src.template_name or "").strip() or _pretty_template_name(key)
188
+ rows.append(
189
+ {
190
+ "page": page,
191
+ "title": title,
192
+ "content": content,
193
+ "template_key": key,
194
+ "template_name": name,
195
+ "template_status": status,
196
+ "depends_on": src.depends_on,
197
+ }
198
+ )
199
+ return rows
200
+
201
+
202
+ def _build_creation_steps(outline: list[dict]) -> list[list[dict]]:
203
+ """Group outline rows into parallel execution steps via DAG level assignment.
204
+
205
+ Each slide's level is the maximum level of its dependencies plus one:
206
+ - Content dependency (depends_on page P) → level ≥ level(P) + 1
207
+ - Template dependency (reuses a template created in this batch by page P)
208
+ → level ≥ level(P) + 1
209
+
210
+ Slides with no dependencies go to level 0 (step 1) regardless of their
211
+ position in the outline. All slides at the same level can run in parallel.
212
+ """
213
+ # Map template_key → page number of the first "new" creator in this batch
214
+ key_creator: dict[str, int] = {}
215
+ for row in outline:
216
+ key = row.get("template_key") or ""
217
+ if row.get("template_status") == "new" and key not in key_creator:
218
+ key_creator[key] = row["page"]
219
+
220
+ page_level: dict[int, int] = {}
221
+ for row in outline:
222
+ page = row["page"]
223
+ level = 0
224
+
225
+ dep = row.get("depends_on")
226
+ if dep is not None and dep in page_level:
227
+ level = max(level, page_level[dep] + 1)
228
+
229
+ key = row.get("template_key") or ""
230
+ if row.get("template_status") == "existing":
231
+ creator = key_creator.get(key)
232
+ if creator is not None and creator in page_level:
233
+ level = max(level, page_level[creator] + 1)
234
+
235
+ page_level[page] = level
236
+
237
+ max_level = max(page_level.values(), default=0)
238
+ return [
239
+ [row for row in outline if page_level[row["page"]] == lvl]
240
+ for lvl in range(max_level + 1)
241
+ if any(page_level[row["page"]] == lvl for row in outline)
242
+ ]
243
+
244
+
245
+ class InsertNewSlides(BaseTool):
246
+ """
247
+ Insert new slides before a specified page position.
248
+
249
+ Uses task_brief to generate an outline/plan while creating blank placeholders.
250
+ For 2+ slides, the outline includes serial vs parallel guidance for ModifySlide calls.
251
+ Do not use this tool in parallel with other tools — call it independently.
252
+ """
253
+
254
+ class ToolConfig:
255
+ one_call_at_a_time: bool = True
256
+
257
+ project_name: str = Field(
258
+ ...,
259
+ description="Name of the presentation project",
260
+ )
261
+ task_brief: str = Field(
262
+ ...,
263
+ max_length=2000,
264
+ description="Brief description of what content will be created in these new pages. Used for outline/plan generation.",
265
+ )
266
+ approximate_page_count: int = Field(
267
+ ...,
268
+ ge=1,
269
+ le=20,
270
+ description="Number of blank slide placeholders to insert (1-20). Content is added later with ModifySlide.",
271
+ )
272
+ insert_position: int = Field(
273
+ default=1,
274
+ ge=1,
275
+ description="Page number (1-based) before which to insert. insert_position=1 means at the beginning, insert_position=3 means before page 3.",
276
+ )
277
+ file_prefix: str = Field(
278
+ default="slide",
279
+ description="Prefix of the slide file names (e.g. slide_01, slide_02).",
280
+ )
281
+
282
+ def run(self):
283
+ """Insert blank placeholders and return a planning-oriented response."""
284
+ project_dir = get_project_dir(self.project_name)
285
+ project_dir.mkdir(parents=True, exist_ok=True)
286
+
287
+ slides = list_slide_files(project_dir, self.file_prefix)
288
+ pad_width = compute_pad_width(slides, extra_count=self.approximate_page_count)
289
+ insert_position = self.insert_position
290
+ n = self.approximate_page_count
291
+ existing_templates = load_template_index(project_dir)
292
+
293
+ # Rename existing slides that are at or after insert position
294
+ rename_map: dict[Path, Path] = {}
295
+ for s in slides:
296
+ if s.index >= insert_position:
297
+ new_name = build_slide_name(
298
+ self.file_prefix, s.index + n, pad_width, s.suffix
299
+ )
300
+ rename_map[s.path] = project_dir / new_name
301
+ apply_renames(rename_map)
302
+
303
+ planner = _make_planner_agent(caller_model=getattr(self._caller_agent, "model", None))
304
+ prompt = _build_planner_prompt(
305
+ self.task_brief, n, insert_position, existing_templates
306
+ )
307
+ plan_result = _run_awaitable(planner.get_response(prompt))
308
+ plan_text = _extract_json_block(
309
+ str(getattr(plan_result, "final_output", "") or "")
310
+ )
311
+ if not plan_text:
312
+ return "❌ Outline generation failed: planner returned empty output."
313
+ try:
314
+ plan_obj = _PlanResponse.model_validate(json.loads(plan_text))
315
+ except (json.JSONDecodeError, ValidationError) as exc:
316
+ return (
317
+ f"❌ Outline generation failed: planner returned invalid JSON ({exc})."
318
+ )
319
+
320
+ # Write blank slide placeholders (no content generation here)
321
+ created: list[str] = []
322
+ outline = _normalize_outline(plan_obj, n, insert_position, existing_templates)
323
+ blank_html, _ = ensure_full_html("")
324
+ for i in range(n):
325
+ idx = insert_position + i
326
+ name = build_slide_name(self.file_prefix, idx, pad_width, "")
327
+ path = project_dir / name
328
+ path.write_text(blank_html, encoding="utf-8")
329
+ created.append(name)
330
+
331
+ total_after_insert = len(slides) + n
332
+ lines = [
333
+ "Successfully generated outline",
334
+ "",
335
+ "**Summary:**",
336
+ f"- {n} new blank slide placeholder(s) created",
337
+ f"- {len({row['template_key'] for row in outline})} template key(s) planned",
338
+ f"- Insert positions: Before page {insert_position} (inserted at positions: {insert_position} to {insert_position + n - 1})",
339
+ f"- Total slides in presentation: {total_after_insert} slides",
340
+ "",
341
+ "**Slide Outline:**",
342
+ ]
343
+ for row in outline:
344
+ page = row["page"]
345
+ lines.append(f"**Page {page}:**")
346
+ lines.append(f"- Title: {row['title']}")
347
+ if row["content"] != row["title"]:
348
+ lines.append(f"- Content: {row['content']}")
349
+ lines.append(f"- Template Name: {row['template_name']}")
350
+ lines.append(f"- Template Key: {row['template_key']}")
351
+ lines.append(f"- Template Status: {row['template_status']}")
352
+ lines.append("")
353
+
354
+ steps = _build_creation_steps(outline)
355
+
356
+ lines.append("**Creation Order:**")
357
+ for step_num, step_rows in enumerate(steps, start=1):
358
+ prev = f" (after Step {step_num - 1} completes)" if step_num > 1 else ""
359
+ pages = ", ".join(str(r["page"]) for r in step_rows)
360
+ if len(step_rows) == 1:
361
+ row = step_rows[0]
362
+ dep = row.get("depends_on")
363
+ note = f" — create after slide {dep} is generated" if dep is not None else ""
364
+ lines.append(f"Step {step_num}: Create page {pages}{prev}{note}")
365
+ else:
366
+ lines.append(f"Step {step_num}: Create pages {pages} IN PARALLEL{prev}")
367
+ lines.append("These pages can be created simultaneously:")
368
+ for row in step_rows:
369
+ tag = "creates new template" if row["template_status"] == "new" else "uses existing template"
370
+ lines.append(f"- Page {row['page']}: '{row['template_key']}' ({tag})")
371
+ lines.append("")
372
+
373
+ lines.extend(
374
+ [
375
+ "",
376
+ "**Current Status:**",
377
+ f"The presentation now has {total_after_insert} page placeholders.",
378
+ "Use ModifySlide to generate slide HTML according to the creation order above.",
379
+ "Important: insert_new_slides must be called independently (not in parallel).",
380
+ ]
381
+ )
382
+ return "\n".join(lines)
383
+
384
+ if __name__ == "__main__":
385
+
386
+ agent = InsertNewSlides(
387
+ project_name="test",
388
+ task_brief="Create a presentation about the benefits of using AI",
389
+ approximate_page_count=7,
390
+ insert_position=1,
391
+ file_prefix="slide",
392
+ )
393
+ print(agent.run())
@@ -0,0 +1,112 @@
1
+ """Create and edit presentation theme CSS files."""
2
+
3
+ from agency_swarm.tools import BaseTool
4
+ from pydantic import Field
5
+
6
+ from .slide_file_utils import get_project_dir
7
+
8
+
9
+ class ManageTheme(BaseTool):
10
+ """
11
+ Create or edit the theme CSS file for a presentation project.
12
+
13
+ The theme file is saved as `_theme.css` in the project folder and defines:
14
+ - Color palette (CSS variables)
15
+ - Typography (fonts, sizes, line heights)
16
+ - Base styles for common elements
17
+ - Reusable classes (cards, labels, etc.)
18
+
19
+ Usage:
20
+ - First time: Creates new theme file
21
+ - Subsequent calls: Overwrites existing theme
22
+ """
23
+
24
+ project_name: str = Field(
25
+ ...,
26
+ description="Name of the presentation project"
27
+ )
28
+ css_content: str = Field(
29
+ ...,
30
+ description="Complete CSS content for the theme file"
31
+ )
32
+ overwrite: bool = Field(
33
+ default=False,
34
+ description=(
35
+ "If False, return an error when the theme already exists. "
36
+ "If True, overwrite the existing theme file."
37
+ ),
38
+ )
39
+
40
+ def run(self):
41
+ """Create or update theme file."""
42
+ project_dir = get_project_dir(self.project_name)
43
+ project_dir.mkdir(parents=True, exist_ok=True)
44
+
45
+ theme_path = project_dir / "_theme.css"
46
+ if theme_path.exists() and not self.overwrite:
47
+ return (
48
+ f"❌ Theme already exists: {theme_path}. "
49
+ "Set overwrite=True to replace it or add a postfix to the filename."
50
+ )
51
+ operation = "updated" if theme_path.exists() else "created"
52
+
53
+ css_content, injected = self._ensure_canvas_rules(self.css_content)
54
+
55
+ try:
56
+ theme_path.write_text(css_content, encoding='utf-8')
57
+ file_size = theme_path.stat().st_size
58
+
59
+ note = " (added base canvas rules)" if injected else ""
60
+ return f"✅ Successfully {operation} theme: {theme_path}{note}\nSize: {file_size} bytes"
61
+
62
+ except Exception as e:
63
+ return f"Error writing theme: {e}"
64
+
65
+ def _ensure_canvas_rules(self, css_content: str) -> tuple[str, bool]:
66
+ """Ensure the theme defines the required slide canvas size."""
67
+ import re
68
+
69
+ selector_pattern = r"(html\s*,\s*body|body|html)\s*\{[^}]*\}"
70
+ width_pattern = r"width\s*:\s*1280px"
71
+ height_pattern = r"height\s*:\s*720px"
72
+
73
+ has_width = False
74
+ has_height = False
75
+
76
+ for match in re.finditer(selector_pattern, css_content, flags=re.IGNORECASE | re.DOTALL):
77
+ block = match.group(0)
78
+ if re.search(width_pattern, block, flags=re.IGNORECASE):
79
+ has_width = True
80
+ if re.search(height_pattern, block, flags=re.IGNORECASE):
81
+ has_height = True
82
+
83
+ if has_width and has_height:
84
+ return css_content, False
85
+
86
+ canvas_rules = (
87
+ "html, body {\n"
88
+ " width: 1280px;\n"
89
+ " height: 720px;\n"
90
+ " margin: 0;\n"
91
+ " padding: 0;\n"
92
+ " overflow: hidden;\n"
93
+ "}\n\n"
94
+ )
95
+ return canvas_rules + css_content, True
96
+
97
+
98
+ if __name__ == "__main__":
99
+ from pathlib import Path
100
+ import sys
101
+
102
+ tools_root = Path(__file__).resolve().parents[1]
103
+ if str(tools_root) not in sys.path:
104
+ sys.path.insert(0, str(tools_root))
105
+
106
+ from tools.deck_utils import load_theme_css
107
+
108
+ tool = ManageTheme(
109
+ project_name="slides_agent_test_deck",
110
+ css_content=load_theme_css(),
111
+ )
112
+ print(tool.run())