@_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,221 @@
1
+ """Check for content overflowing the slide canvas boundaries."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ from copy import deepcopy
8
+ from pathlib import Path
9
+ from typing import List, Optional
10
+
11
+ from agency_swarm.tools import BaseTool
12
+ from pydantic import Field
13
+
14
+ # Add slides directory to path for render_slides module
15
+ SLIDES_DIR = Path(__file__).parent.parent.parent / "slides"
16
+ sys.path.insert(0, str(SLIDES_DIR))
17
+
18
+
19
+ class CheckSlideCanvasOverflow(BaseTool):
20
+ """
21
+ Detect slides with content overflowing the original canvas boundaries.
22
+
23
+ This tool works by:
24
+ 1. Adding grey padding around each slide
25
+ 2. Rendering the padded presentation to images
26
+ 3. Inspecting the padding margins for non-grey pixels
27
+
28
+ If content extends beyond the original slide boundaries, it will appear
29
+ in the padding area and be detected as overflow.
30
+
31
+ Returns a list of failing slide indices (1-based) and paths to debug
32
+ images showing the overflow.
33
+
34
+ Requires: LibreOffice (soffice) and pdf2image Python package.
35
+ """
36
+
37
+ input_pptx: str = Field(
38
+ ...,
39
+ description="Path to the input PowerPoint file (.pptx)",
40
+ )
41
+ max_width_px: int = Field(
42
+ default=1600,
43
+ description="Maximum width in pixels for rendered images (default 1600)",
44
+ )
45
+ max_height_px: int = Field(
46
+ default=900,
47
+ description="Maximum height in pixels for rendered images (default 900)",
48
+ )
49
+ pad_px: int = Field(
50
+ default=100,
51
+ description="Padding in pixels to add on each side (default 100)",
52
+ )
53
+
54
+ def run(self) -> str:
55
+ """Check for canvas overflow and return results."""
56
+ input_path = Path(self.input_pptx)
57
+
58
+ # Validate input
59
+ if not input_path.exists():
60
+ return f"Error: Input file not found: {self.input_pptx}"
61
+ if input_path.suffix.lower() != ".pptx":
62
+ return f"Error: Input must be a PowerPoint file (.pptx), got: {input_path.suffix}"
63
+
64
+ # Check for required external tools
65
+ if not self._check_soffice():
66
+ return "Error: LibreOffice (soffice) not found. Please install LibreOffice."
67
+
68
+ try:
69
+ import numpy as np
70
+ from pdf2image import convert_from_path
71
+ from PIL import Image
72
+ from pptx import Presentation
73
+ from pptx.dml.color import RGBColor
74
+ from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
75
+ from pptx.util import Emu
76
+ except ImportError as e:
77
+ return f"Error: Missing required package: {e}"
78
+
79
+ # Import render_slides for DPI calculation
80
+ try:
81
+ import render_slides
82
+ except ImportError:
83
+ return "Error: Could not import render_slides module"
84
+
85
+ # Constants
86
+ EMU_PER_INCH = 914_400
87
+ PAD_RGB = (200, 200, 200)
88
+
89
+ def px_to_emu(px: int, dpi: int) -> Emu:
90
+ return Emu(int(px * EMU_PER_INCH // dpi))
91
+
92
+ # Calculate DPI
93
+ try:
94
+ dpi = render_slides.calc_dpi_via_ooxml(
95
+ str(input_path), self.max_width_px, self.max_height_px
96
+ )
97
+ except Exception as e:
98
+ return f"Error calculating DPI: {e}"
99
+
100
+ # Create temporary directory for work
101
+ tmpdir = tempfile.mkdtemp(prefix="overflow_check_")
102
+ enlarged_pptx = Path(tmpdir) / "enlarged.pptx"
103
+
104
+ try:
105
+ # Enlarge the deck with padding
106
+ prs = Presentation(str(input_path))
107
+ w0 = prs.slide_width
108
+ h0 = prs.slide_height
109
+ pad_emu = px_to_emu(self.pad_px, dpi)
110
+ w1 = Emu(w0 + 2 * pad_emu)
111
+ h1 = Emu(h0 + 2 * pad_emu)
112
+ prs.slide_width = w1
113
+ prs.slide_height = h1
114
+
115
+ for slide in prs.slides:
116
+ # Shift all shapes
117
+ for shp in list(slide.shapes):
118
+ shp.left = Emu(int(shp.left) + pad_emu)
119
+ shp.top = Emu(int(shp.top) + pad_emu)
120
+
121
+ # Add padding rectangles
122
+ pads = (
123
+ (Emu(0), Emu(0), pad_emu, h1), # left
124
+ (Emu(int(w1) - int(pad_emu)), Emu(0), pad_emu, h1), # right
125
+ (Emu(0), Emu(0), w1, pad_emu), # top
126
+ (Emu(0), Emu(int(h1) - int(pad_emu)), w1, pad_emu), # bottom
127
+ )
128
+
129
+ sp_tree = slide.shapes._spTree
130
+
131
+ for left, top, width, height in pads:
132
+ pad_shape = slide.shapes.add_shape(
133
+ MSO_AUTO_SHAPE_TYPE.RECTANGLE, left, top, width, height
134
+ )
135
+ pad_shape.fill.solid()
136
+ pad_shape.fill.fore_color.rgb = RGBColor(*PAD_RGB)
137
+ pad_shape.line.fill.background()
138
+ sp_tree.remove(pad_shape._element)
139
+ sp_tree.insert(2, pad_shape._element)
140
+
141
+ prs.save(str(enlarged_pptx))
142
+
143
+ # Render to images
144
+ img_paths = render_slides.rasterize(str(enlarged_pptx), str(Path(tmpdir) / "imgs"), dpi)
145
+
146
+ # Calculate padding ratios
147
+ pad_ratio_w = pad_emu / w1
148
+ pad_ratio_h = pad_emu / h1
149
+
150
+ # Inspect images for overflow
151
+ tol = max(1, round((300 - dpi) / 25)) if dpi < 300 else 0
152
+ tol = min(tol, 10)
153
+ pad_colour = np.array(PAD_RGB, dtype=np.uint8)
154
+ failures = []
155
+
156
+ for idx, img_path in enumerate(img_paths, start=1):
157
+ with Image.open(img_path) as img:
158
+ rgb = img.convert("RGB")
159
+ arr = np.asarray(rgb)
160
+
161
+ h, w, _ = arr.shape
162
+ pad_x = int(w * pad_ratio_w) - 1
163
+ pad_y = int(h * pad_ratio_h) - 1
164
+
165
+ margins = [
166
+ arr[:, :pad_x, :], # left
167
+ arr[:, w - pad_x:, :], # right
168
+ arr[:pad_y, :, :], # top
169
+ arr[h - pad_y:, :, :], # bottom
170
+ ]
171
+
172
+ def is_clean(margin):
173
+ diff = np.abs(margin.astype(np.int16) - pad_colour)
174
+ matches = np.all(diff <= tol, axis=-1)
175
+ mismatch_fraction = 1.0 - (np.count_nonzero(matches) / matches.size)
176
+ max_mismatch = 0.01 if dpi >= 300 else (0.02 if dpi >= 200 else 0.03)
177
+ return mismatch_fraction <= max_mismatch
178
+
179
+ if not all(is_clean(m) for m in margins):
180
+ failures.append((idx, img_path))
181
+
182
+ if failures:
183
+ result = f"OVERFLOW DETECTED on {len(failures)} slide(s):\n"
184
+ for idx, img_path in failures:
185
+ result += f" - Slide {idx}: {img_path}\n"
186
+ result += "\nDebug images show grey padding with overflow content visible."
187
+ return result
188
+ else:
189
+ return "No overflow detected. All slides are within canvas boundaries."
190
+
191
+ except Exception as e:
192
+ return f"Error checking overflow: {e}"
193
+
194
+ def _check_soffice(self) -> bool:
195
+ """Check if LibreOffice is available."""
196
+ try:
197
+ kwargs = {
198
+ "capture_output": True,
199
+ "timeout": 15,
200
+ "text": True,
201
+ "input": "\n",
202
+ }
203
+ if os.name == "nt":
204
+ kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
205
+
206
+ soffice_bin = "soffice.com" if os.name == "nt" else "soffice"
207
+ subprocess.run([soffice_bin, "--version"], **kwargs)
208
+ return True
209
+ except (FileNotFoundError, subprocess.TimeoutExpired):
210
+ return False
211
+
212
+
213
+ if __name__ == "__main__":
214
+ # Test with a sample file if available
215
+ test_pptx = Path(__file__).parent.parent / "files" / "test.pptx"
216
+ if test_pptx.exists():
217
+ tool = CheckSlideCanvasOverflow(input_pptx=str(test_pptx))
218
+ print(tool.run())
219
+ else:
220
+ print(f"Test file not found: {test_pptx}")
221
+ print("Tool definition is valid.")
@@ -0,0 +1,261 @@
1
+ """Create labeled montage images from a collection of images."""
2
+
3
+ import re
4
+ import tempfile
5
+ from math import ceil
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, List, Literal, Optional
8
+
9
+ from agency_swarm.tools import BaseTool
10
+ from pydantic import Field
11
+
12
+ if TYPE_CHECKING:
13
+ from PIL import Image
14
+
15
+ # Supported image extensions (same as EnsureRasterImage)
16
+ RASTER_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp"}
17
+ CONVERTIBLE_EXTS = {
18
+ ".emf", ".wmf", ".emz", ".wmz",
19
+ ".svg", ".svgz",
20
+ ".wdp", ".jxr",
21
+ ".heic", ".heif",
22
+ ".pdf", ".eps", ".ps",
23
+ }
24
+ SUPPORTED_EXTS = RASTER_EXTS | CONVERTIBLE_EXTS
25
+
26
+
27
+ class CreateImageMontage(BaseTool):
28
+ """
29
+ Create a labeled montage image from a collection of images.
30
+
31
+ Arranges images in a grid layout with optional labels (numbers or filenames).
32
+ Useful for:
33
+ - Reviewing slide images extracted from presentations
34
+ - Comparing multiple versions of assets
35
+ - Creating visual indexes of image collections
36
+
37
+ Non-raster formats (SVG, EMF, etc.) are automatically converted to PNG
38
+ before inclusion in the montage.
39
+ """
40
+
41
+ input_files: Optional[List[str]] = Field(
42
+ default=None,
43
+ description="List of image file paths (mutually exclusive with input_dir)",
44
+ )
45
+ input_dir: Optional[str] = Field(
46
+ default=None,
47
+ description="Directory containing images (mutually exclusive with input_files)",
48
+ )
49
+ output_file: str = Field(
50
+ ...,
51
+ description="Path for the output montage image",
52
+ )
53
+ num_col: int = Field(
54
+ default=5,
55
+ description="Number of columns in the grid (default 5)",
56
+ )
57
+ cell_width: int = Field(
58
+ default=400,
59
+ description="Width of each cell in pixels (default 400)",
60
+ )
61
+ cell_height: int = Field(
62
+ default=225,
63
+ description="Height of each cell in pixels (default 225)",
64
+ )
65
+ gap: int = Field(
66
+ default=16,
67
+ description="Gap between cells in pixels (default 16)",
68
+ )
69
+ label_mode: Literal["number", "filename", "none"] = Field(
70
+ default="number",
71
+ description="Label mode: 'number' (1-based index), 'filename', or 'none'",
72
+ )
73
+
74
+ def run(self) -> str:
75
+ """Create the montage and return the output path."""
76
+ try:
77
+ from PIL import Image, ImageDraw, ImageFont, ImageOps
78
+ except ImportError:
79
+ return "Error: PIL/Pillow not installed"
80
+
81
+ # Validate inputs
82
+ if self.input_files and self.input_dir:
83
+ return "Error: Specify either input_files or input_dir, not both"
84
+ if not self.input_files and not self.input_dir:
85
+ return "Error: Must specify either input_files or input_dir"
86
+
87
+ # Gather input files
88
+ if self.input_files:
89
+ input_paths = [Path(f) for f in self.input_files]
90
+ for p in input_paths:
91
+ if not p.exists():
92
+ return f"Error: File not found: {p}"
93
+ else:
94
+ input_dir = Path(self.input_dir)
95
+ if not input_dir.is_dir():
96
+ return f"Error: Directory not found: {self.input_dir}"
97
+ # Natural sort for proper ordering (slide-1, slide-2, ..., slide-10)
98
+ input_paths = sorted(
99
+ [p for p in input_dir.iterdir() if p.suffix.lower() in SUPPORTED_EXTS],
100
+ key=lambda p: self._natural_key(p.name)
101
+ )
102
+ if not input_paths:
103
+ return f"Error: No supported images found in {self.input_dir}"
104
+
105
+ # Validate parameters
106
+ if self.num_col <= 0:
107
+ return "Error: num_col must be positive"
108
+ if self.cell_width <= 0 or self.cell_height <= 0:
109
+ return "Error: cell_width and cell_height must be positive"
110
+
111
+ # Load images (converting non-raster formats as needed)
112
+ labels = [p.name for p in input_paths]
113
+ images = []
114
+ placeholder = None
115
+
116
+ with tempfile.TemporaryDirectory(prefix="montage_convert_") as tmp_dir:
117
+ for p in input_paths:
118
+ try:
119
+ img_path = self._ensure_raster(p, tmp_dir)
120
+ images.append(Image.open(img_path))
121
+ except Exception:
122
+ pass
123
+ images.append(None)
124
+
125
+ # Check we have at least one valid image
126
+ valid_count = sum(1 for img in images if img is not None)
127
+ if valid_count == 0:
128
+ return "Error: No valid images to render"
129
+
130
+ # Create placeholder for failed images
131
+ if valid_count < len(images):
132
+ placeholder = self._make_placeholder(
133
+ int(min(self.cell_width, self.cell_height) * 0.6)
134
+ )
135
+
136
+ # Calculate grid dimensions
137
+ cols = self.num_col
138
+ rows = ceil(len(images) / cols)
139
+
140
+ # Set up font
141
+ try:
142
+ font_size = max(12, min(36, int(self.cell_height * 0.12)))
143
+ font = ImageFont.truetype("arial.ttf", font_size)
144
+ except Exception:
145
+ font = ImageFont.load_default()
146
+ font_size = 12
147
+
148
+ # Calculate label height
149
+ draw_labels = self.label_mode != "none"
150
+ label_height = 0
151
+ if draw_labels:
152
+ temp_img = Image.new("RGB", (10, 10))
153
+ temp_draw = ImageDraw.Draw(temp_img)
154
+ sample = "1" if self.label_mode == "number" else "Ag"
155
+ bbox = temp_draw.textbbox((0, 0), sample, font=font)
156
+ label_height = ceil(bbox[3] - bbox[1]) + 6
157
+
158
+ row_h = self.cell_height + label_height
159
+ canvas_w = cols * self.cell_width + (cols + 1) * self.gap
160
+ canvas_h = rows * row_h + (rows + 1) * self.gap
161
+
162
+ # Create canvas
163
+ canvas = Image.new("RGB", (canvas_w, canvas_h), (242, 242, 242))
164
+ draw = ImageDraw.Draw(canvas)
165
+
166
+ # Place images
167
+ for idx, img in enumerate(images):
168
+ col = idx % cols
169
+ row = idx // cols
170
+ x0 = self.gap + col * (self.cell_width + self.gap)
171
+ y0 = self.gap + row * (row_h + self.gap)
172
+
173
+ # Prepare label
174
+ if self.label_mode == "number":
175
+ label = str(idx + 1)
176
+ elif self.label_mode == "filename":
177
+ label = labels[idx]
178
+ else:
179
+ label = ""
180
+
181
+ # Get image to display
182
+ if img is not None:
183
+ resized = ImageOps.contain(
184
+ img.convert("RGBA"),
185
+ (self.cell_width, self.cell_height),
186
+ method=Image.Resampling.LANCZOS,
187
+ )
188
+ else:
189
+ resized = placeholder
190
+
191
+ # Calculate position
192
+ paste_x = x0 + (self.cell_width - resized.width) // 2
193
+ paste_y = y0 + (self.cell_height - resized.height) // 2
194
+
195
+ # Paste image
196
+ canvas.paste(
197
+ resized,
198
+ (paste_x, paste_y),
199
+ mask=resized.split()[3] if resized.mode == "RGBA" else None,
200
+ )
201
+
202
+ # Draw border
203
+ draw.rectangle(
204
+ [paste_x - 1, paste_y - 1, paste_x + resized.width, paste_y + resized.height],
205
+ outline=(160, 160, 160),
206
+ width=1,
207
+ )
208
+
209
+ # Draw label
210
+ if draw_labels and label:
211
+ bbox = draw.textbbox((0, 0), label, font=font)
212
+ text_w = bbox[2] - bbox[0]
213
+ tx = x0 + (self.cell_width - text_w) // 2
214
+ ty = y0 + self.cell_height + 3
215
+ draw.text((tx, ty), label, font=font, fill=(0, 0, 0))
216
+
217
+ # Save output
218
+ output_path = Path(self.output_file)
219
+ output_path.parent.mkdir(parents=True, exist_ok=True)
220
+ canvas.save(str(output_path))
221
+
222
+ return f"Montage saved to: {output_path}\n{len(images)} images in {rows}x{cols} grid"
223
+
224
+ def _natural_key(self, s: str) -> list:
225
+ """Key function for natural sorting (e.g., slide2 before slide10)."""
226
+ return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", s)]
227
+
228
+ def _make_placeholder(self, size: int) -> "Image.Image":
229
+ """Create a placeholder image for failed loads."""
230
+ from PIL import Image, ImageDraw
231
+
232
+ ph = Image.new("RGBA", (size, size), (220, 220, 220, 255))
233
+ draw = ImageDraw.Draw(ph)
234
+ draw.line([(0, 0), (size - 1, size - 1)], fill=(180, 0, 0, 255), width=3)
235
+ draw.line([(size - 1, 0), (0, size - 1)], fill=(180, 0, 0, 255), width=3)
236
+ return ph
237
+
238
+ def _ensure_raster(self, path: Path, tmp_dir: str) -> str:
239
+ """Ensure the image is in a raster format, converting if needed."""
240
+ ext = path.suffix.lower()
241
+ if ext in RASTER_EXTS:
242
+ return str(path)
243
+
244
+ # Use EnsureRasterImage tool logic for conversion
245
+ from .EnsureRasterImage import EnsureRasterImage
246
+
247
+ tool = EnsureRasterImage(input_path=str(path), output_dir=tmp_dir)
248
+ result = tool.run()
249
+
250
+ if result.startswith("Error"):
251
+ raise RuntimeError(result)
252
+
253
+ # Extract the output path from the result
254
+ if result.startswith("Converted to:"):
255
+ return result.replace("Converted to:", "").strip()
256
+ return str(path)
257
+
258
+
259
+ if __name__ == "__main__":
260
+ print("CreateImageMontage tool definition is valid.")
261
+ print(f"Supported formats: {', '.join(sorted(SUPPORTED_EXTS))}")
@@ -0,0 +1,168 @@
1
+ """Create thumbnail grids from PowerPoint presentation slides."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ from pathlib import Path
8
+ from agency_swarm.tools import BaseTool
9
+ from pydantic import Field
10
+
11
+ # Add pptx/scripts to path for thumbnail module
12
+ PPTX_SCRIPTS_DIR = Path(__file__).parent.parent / "pptx" / "scripts"
13
+ sys.path.insert(0, str(PPTX_SCRIPTS_DIR))
14
+
15
+
16
+ class CreatePptxThumbnailGrid(BaseTool):
17
+ """
18
+ Create visual thumbnail grids from PowerPoint presentation slides.
19
+
20
+ Converts all slides to images and arranges them in a large, unlabeled grid layout.
21
+ Useful for:
22
+ - Template analysis: quickly understand slide layouts and design patterns
23
+ - Content review: visual overview of entire presentation
24
+ - Quality check: verify all slides are properly formatted
25
+ - Navigation reference: find specific slides by visual appearance
26
+
27
+ Grid limits by column count:
28
+ - 3 cols: max 12 slides per grid (3×4)
29
+ - 4 cols: max 20 slides per grid (4×5)
30
+ - 5 cols: max 30 slides per grid (5×6) [default]
31
+ - 6 cols: max 42 slides per grid (6×7)
32
+
33
+ For large presentations, multiple numbered grid files are created automatically.
34
+
35
+ Requires: LibreOffice (soffice) and Poppler (pdftoppm) to be installed.
36
+ """
37
+
38
+ input_pptx: str = Field(
39
+ ...,
40
+ description="Path to the input PowerPoint file (.pptx)",
41
+ )
42
+ output_prefix: str = Field(
43
+ default="thumbnails",
44
+ description="Output prefix for image files (creates prefix.jpg or prefix-N.jpg for multiple grids)",
45
+ )
46
+ cols: int = Field(
47
+ default=5,
48
+ description="Number of columns in the grid (1-6, default 5). Auto-reduces for small decks.",
49
+ )
50
+ outline_placeholders: bool = Field(
51
+ default=False,
52
+ description="If True, outline text placeholders with red borders for visibility",
53
+ )
54
+
55
+ def run(self) -> str:
56
+ """Create thumbnail grids and return list of generated files."""
57
+ input_path = Path(self.input_pptx)
58
+
59
+ # Validate input
60
+ if not input_path.exists():
61
+ return f"Error: Input file not found: {self.input_pptx}"
62
+ if input_path.suffix.lower() != ".pptx":
63
+ return f"Error: Input must be a PowerPoint file (.pptx), got: {input_path.suffix}"
64
+
65
+ # Check for required external tools
66
+ if not self._check_soffice():
67
+ return "Error: LibreOffice (soffice) not found. Please install LibreOffice."
68
+ if not self._check_pdftoppm():
69
+ return "Error: pdftoppm not found. Please install Poppler (poppler-utils)."
70
+
71
+ # Import and run thumbnail creation
72
+ from thumbnail import ( # type: ignore[import-not-found]
73
+ convert_to_images,
74
+ create_grids,
75
+ get_placeholder_regions,
76
+ )
77
+
78
+ prefix_path = Path(self.output_prefix)
79
+ if prefix_path.parent == Path("."):
80
+ prefix_path = input_path.parent / prefix_path.name
81
+ output_path = prefix_path.with_suffix(".jpg")
82
+
83
+ try:
84
+ with tempfile.TemporaryDirectory() as temp_dir:
85
+ # Get placeholder regions if outlining is enabled
86
+ placeholder_regions = None
87
+ slide_dimensions = None
88
+ if self.outline_placeholders:
89
+ placeholder_regions, slide_dimensions = get_placeholder_regions(
90
+ input_path
91
+ )
92
+
93
+ # Convert slides to images
94
+ slide_images = convert_to_images(input_path, Path(temp_dir), 100)
95
+ if not slide_images:
96
+ return "Error: No slides found in presentation"
97
+
98
+ # Validate columns after slide count is known
99
+ cols = max(1, min(6, self.cols))
100
+ cols = min(cols, len(slide_images))
101
+ if cols != self.cols:
102
+ pass
103
+
104
+ # Create grids
105
+ grid_files = create_grids(
106
+ slide_images,
107
+ cols,
108
+ 420, # thumbnail width
109
+ output_path,
110
+ placeholder_regions,
111
+ slide_dimensions,
112
+ )
113
+
114
+ return (
115
+ f"Created {len(grid_files)} thumbnail grid(s):\n"
116
+ + "\n".join(f" - {f}" for f in grid_files)
117
+ )
118
+
119
+ except Exception as e:
120
+ return f"Error creating thumbnails: {e}"
121
+
122
+ def _check_soffice(self) -> bool:
123
+ """Check if LibreOffice is available."""
124
+ try:
125
+ kwargs = {
126
+ "capture_output": True,
127
+ "timeout": 15,
128
+ "text": True,
129
+ "input": "\n",
130
+ }
131
+ if os.name == "nt":
132
+ kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
133
+
134
+ soffice_bin = "soffice.com" if os.name == "nt" else "soffice"
135
+ subprocess.run([soffice_bin, "--version"], **kwargs)
136
+ return True
137
+ except (FileNotFoundError, subprocess.TimeoutExpired):
138
+ return False
139
+
140
+ def _check_pdftoppm(self) -> bool:
141
+ """Check if pdftoppm is available."""
142
+ try:
143
+ kwargs = {
144
+ "capture_output": True,
145
+ "timeout": 5,
146
+ "stdin": subprocess.DEVNULL,
147
+ }
148
+ if os.name == "nt":
149
+ kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
150
+
151
+ subprocess.run(["pdftoppm", "-v"], **kwargs)
152
+ return True
153
+ except (FileNotFoundError, subprocess.TimeoutExpired):
154
+ return False
155
+
156
+
157
+ if __name__ == "__main__":
158
+ # Test with a sample file if available
159
+ test_pptx = Path(__file__).parent.parent / "files" / "test.pptx"
160
+ if test_pptx.exists():
161
+ tool = CreatePptxThumbnailGrid(
162
+ input_pptx=str(test_pptx),
163
+ output_prefix="/tmp/test_thumbnails",
164
+ )
165
+ print(tool.run())
166
+ else:
167
+ print(f"Test file not found: {test_pptx}")
168
+ print("Tool definition is valid.")