@_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,308 @@
1
+ import base64
2
+ import concurrent.futures
3
+ import io
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from urllib.parse import urlparse
8
+
9
+ import requests
10
+ from PIL import Image
11
+
12
+ from agency_swarm import ToolOutputImage, ToolOutputText
13
+
14
+
15
+ DEFAULT_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp")
16
+ ALL_ASPECT_RATIOS = (
17
+ "1:1",
18
+ "2:3",
19
+ "3:2",
20
+ "3:4",
21
+ "4:3",
22
+ "4:5",
23
+ "5:4",
24
+ "9:16",
25
+ "16:9",
26
+ "21:9",
27
+ )
28
+ SUPPORTED_ASPECT_RATIOS_BY_MODEL = {
29
+ "gemini-2.5-flash-image": set(ALL_ASPECT_RATIOS),
30
+ "gemini-3-pro-image-preview": set(ALL_ASPECT_RATIOS),
31
+ # OpenAI image API size options are:
32
+ # 1024x1024 (1:1), 1024x1536 (2:3), 1536x1024 (3:2)
33
+ "gpt-image-1.5": {"1:1", "2:3", "3:2"},
34
+ }
35
+ OPENAI_SIZE_BY_ASPECT_RATIO = {
36
+ "1:1": "1024x1024",
37
+ "2:3": "1024x1536",
38
+ "3:2": "1536x1024",
39
+ }
40
+
41
+ if os.path.isfile("/.dockerenv"):
42
+ MNT_DIR = Path("/app/mnt")
43
+ else:
44
+ MNT_DIR = Path(__file__).parent.parent.parent.parent / "mnt"
45
+
46
+
47
+ def get_images_dir(product_name: str) -> Path:
48
+ images_dir = MNT_DIR / product_name / "generated_images"
49
+ images_dir.mkdir(parents=True, exist_ok=True)
50
+ return images_dir
51
+
52
+
53
+ def find_image_path_from_name(images_dir: Path, image_name: str) -> Path | None:
54
+ for ext in DEFAULT_EXTENSIONS:
55
+ candidate = images_dir / f"{image_name}{ext}"
56
+ if candidate.exists():
57
+ return candidate
58
+ return None
59
+
60
+
61
+ def resolve_image_reference(product_name: str, image_ref: str) -> tuple[Image.Image, str]:
62
+ if not image_ref.strip():
63
+ raise ValueError("image reference must not be empty")
64
+
65
+ parsed = urlparse(image_ref)
66
+ if parsed.scheme in ("http", "https"):
67
+ response = requests.get(image_ref, timeout=30)
68
+ response.raise_for_status()
69
+ image = Image.open(io.BytesIO(response.content))
70
+ return image.convert("RGB"), image_ref
71
+
72
+ candidate_path = Path(image_ref).expanduser().resolve()
73
+ if candidate_path.exists():
74
+ image = Image.open(candidate_path)
75
+ return image.convert("RGB"), str(candidate_path)
76
+
77
+ images_dir = get_images_dir(product_name)
78
+ by_name = find_image_path_from_name(images_dir, image_ref)
79
+ if by_name is not None:
80
+ image = Image.open(by_name)
81
+ return image.convert("RGB"), str(by_name)
82
+
83
+ raise FileNotFoundError(
84
+ f"Could not resolve image reference '{image_ref}' as URL, path, or name in {images_dir}."
85
+ )
86
+
87
+
88
+ _IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff"}
89
+
90
+
91
+ def normalize_file_name(value: str) -> str:
92
+ """Strip a known image extension from the name, but leave other dots untouched.
93
+
94
+ "hero.png" → "hero"
95
+ "5.1_no_bg" → "5.1_no_bg" (dot is part of the name, not an extension)
96
+ """
97
+ name = value.strip()
98
+ if not name:
99
+ raise ValueError("file name must not be empty")
100
+ p = Path(name)
101
+ return p.stem if p.suffix.lower() in _IMAGE_EXTENSIONS else name
102
+
103
+
104
+ def build_variant_output_name(output_name: str, variant_index: int, total_variants: int) -> str:
105
+ raw_value = output_name.strip()
106
+ if not raw_value:
107
+ raise ValueError("output name must not be empty")
108
+ if total_variants <= 1:
109
+ return raw_value
110
+
111
+ candidate = Path(raw_value)
112
+ has_image_ext = candidate.suffix.lower() in _IMAGE_EXTENSIONS
113
+ if has_image_ext:
114
+ variant_file = f"{candidate.stem}_variant_{variant_index}{candidate.suffix}"
115
+ return str(candidate.with_name(variant_file))
116
+
117
+ return f"{raw_value}_variant_{variant_index}"
118
+
119
+
120
+ def save_image(image: Image.Image, output_name: str, images_dir: Path) -> tuple[str, str]:
121
+ raw_value = output_name.strip()
122
+ candidate = Path(raw_value).expanduser()
123
+
124
+ has_image_ext = candidate.suffix.lower() in _IMAGE_EXTENSIONS
125
+ has_path_sep = any(sep in raw_value for sep in ("/", "\\"))
126
+
127
+ if has_image_ext:
128
+ output_path = candidate if candidate.is_absolute() else images_dir / candidate
129
+ image_name = output_path.stem
130
+ elif has_path_sep:
131
+ output_path = candidate if candidate.is_absolute() else images_dir / candidate
132
+ output_path = output_path.with_suffix(".png")
133
+ image_name = output_path.stem
134
+ else:
135
+ image_name = normalize_file_name(raw_value)
136
+ output_path = images_dir / f"{image_name}.png"
137
+
138
+ output_path.parent.mkdir(parents=True, exist_ok=True)
139
+ image.save(output_path, format="PNG")
140
+ return image_name, str(output_path)
141
+
142
+
143
+ def image_to_base64_jpeg(
144
+ image: Image.Image,
145
+ max_size: int = 768,
146
+ target_bytes: int = 120_000,
147
+ min_quality: int = 45,
148
+ ) -> str:
149
+ """
150
+ Create a compact JPEG preview for multimodal outputs.
151
+
152
+ This intentionally optimizes for small payload size to reduce token costs.
153
+ """
154
+ rgb = image.convert("RGB")
155
+ width, height = rgb.size
156
+
157
+ if max(width, height) > max_size:
158
+ scale = max_size / max(width, height)
159
+ rgb = rgb.resize((int(width * scale), int(height * scale)), Image.Resampling.LANCZOS)
160
+
161
+ best_bytes: bytes | None = None
162
+ work = rgb
163
+ quality_steps = (80, 70, 62, 55, min_quality)
164
+
165
+ # Try lowering JPEG quality first.
166
+ for quality in quality_steps:
167
+ buffer = io.BytesIO()
168
+ work.save(buffer, format="JPEG", quality=quality, optimize=True, progressive=True)
169
+ candidate = buffer.getvalue()
170
+ if best_bytes is None or len(candidate) < len(best_bytes):
171
+ best_bytes = candidate
172
+ if len(candidate) <= target_bytes:
173
+ return base64.b64encode(candidate).decode("utf-8")
174
+
175
+ # If still too large, iteratively downscale and retry quality steps.
176
+ while max(work.size) > 320:
177
+ next_size = (max(1, int(work.width * 0.85)), max(1, int(work.height * 0.85)))
178
+ work = work.resize(next_size, Image.Resampling.LANCZOS)
179
+ for quality in quality_steps:
180
+ buffer = io.BytesIO()
181
+ work.save(buffer, format="JPEG", quality=quality, optimize=True, progressive=True)
182
+ candidate = buffer.getvalue()
183
+ if best_bytes is None or len(candidate) < len(best_bytes):
184
+ best_bytes = candidate
185
+ if len(candidate) <= target_bytes:
186
+ return base64.b64encode(candidate).decode("utf-8")
187
+
188
+ # Final fallback: return the smallest candidate we produced.
189
+ assert best_bytes is not None
190
+ return base64.b64encode(best_bytes).decode("utf-8")
191
+
192
+
193
+ def build_multimodal_outputs(items: list[dict[str, Any]], title: str) -> list:
194
+ lines = [f"{title}: {len(items)} image(s)"]
195
+ for item in items:
196
+ lines.append(f"- {item['image_name']}: {item['file_path']}")
197
+
198
+ outputs: list = [ToolOutputText(type="text", text="\n".join(lines))]
199
+ for item in items:
200
+ outputs.append(ToolOutputText(type="text", text=f"Path: {item['file_path']}"))
201
+ outputs.append(
202
+ ToolOutputImage(
203
+ type="image",
204
+ image_url=f"data:image/jpeg;base64,{item['preview_b64']}",
205
+ detail="auto",
206
+ )
207
+ )
208
+ return outputs
209
+
210
+
211
+ def extract_gemini_image_and_usage(response: Any) -> tuple[Image.Image | None, dict]:
212
+ image: Image.Image | None = None
213
+ usage = getattr(response, "usage_metadata", {}) or {}
214
+
215
+ if usage and not isinstance(usage, dict):
216
+ if hasattr(usage, "model_dump"):
217
+ usage = usage.model_dump()
218
+ elif hasattr(usage, "__dict__"):
219
+ usage = vars(usage)
220
+ else:
221
+ try:
222
+ usage = dict(usage)
223
+ except (TypeError, ValueError):
224
+ usage = {}
225
+
226
+ candidates = getattr(response, "candidates", None) or []
227
+ for candidate in candidates:
228
+ content = getattr(candidate, "content", None)
229
+ parts = getattr(content, "parts", None) or []
230
+ for part in parts:
231
+ inline_data = getattr(part, "inline_data", None)
232
+ if inline_data and getattr(inline_data, "data", None):
233
+ image = Image.open(io.BytesIO(inline_data.data)).convert("RGB")
234
+ return image, usage
235
+
236
+ parts = getattr(response, "parts", None) or []
237
+ for part in parts:
238
+ inline_data = getattr(part, "inline_data", None)
239
+ if inline_data and getattr(inline_data, "data", None):
240
+ image = Image.open(io.BytesIO(inline_data.data)).convert("RGB")
241
+ return image, usage
242
+
243
+ return None, usage
244
+
245
+
246
+ def extract_openai_images_and_usage(response: Any) -> tuple[list[Image.Image], dict]:
247
+ usage = getattr(response, "usage", {}) or {}
248
+ if usage and not isinstance(usage, dict):
249
+ if hasattr(usage, "model_dump"):
250
+ usage = usage.model_dump()
251
+ elif hasattr(usage, "__dict__"):
252
+ usage = vars(usage)
253
+ else:
254
+ try:
255
+ usage = dict(usage)
256
+ except (TypeError, ValueError):
257
+ usage = {}
258
+
259
+ images: list[Image.Image] = []
260
+ data = getattr(response, "data", None) or []
261
+ for item in data:
262
+ b64 = getattr(item, "b64_json", None)
263
+ if b64:
264
+ raw = base64.b64decode(b64)
265
+ images.append(Image.open(io.BytesIO(raw)).convert("RGB"))
266
+
267
+ return images, usage
268
+
269
+
270
+ def run_parallel_variants_sync(task_fn, num_variants: int) -> list:
271
+ results_by_index: dict[int, Any] = {}
272
+ with concurrent.futures.ThreadPoolExecutor(max_workers=num_variants) as executor:
273
+ future_to_index = {
274
+ executor.submit(task_fn, idx): idx
275
+ for idx in range(1, num_variants + 1)
276
+ }
277
+ for future in concurrent.futures.as_completed(future_to_index):
278
+ idx = future_to_index[future]
279
+ try:
280
+ result = future.result()
281
+ if result is not None:
282
+ results_by_index[idx] = result
283
+ except Exception:
284
+ pass
285
+
286
+ return [results_by_index[idx] for idx in sorted(results_by_index.keys())]
287
+
288
+
289
+ def validate_aspect_ratio_for_model(model: str, aspect_ratio: str) -> None:
290
+ supported = SUPPORTED_ASPECT_RATIOS_BY_MODEL.get(model)
291
+ if supported is None:
292
+ raise ValueError(f"Unsupported model: {model}")
293
+ if aspect_ratio not in supported:
294
+ raise ValueError(
295
+ f"Aspect ratio '{aspect_ratio}' is not supported by model '{model}'. "
296
+ f"Supported values: {sorted(supported)}"
297
+ )
298
+
299
+
300
+ def get_openai_size_for_aspect_ratio(aspect_ratio: str) -> str:
301
+ size = OPENAI_SIZE_BY_ASPECT_RATIO.get(aspect_ratio)
302
+ if not size:
303
+ raise ValueError(
304
+ f"Aspect ratio '{aspect_ratio}' cannot be mapped to an OpenAI image size. "
305
+ f"Supported values: {sorted(OPENAI_SIZE_BY_ASPECT_RATIO.keys())}"
306
+ )
307
+ return size
308
+
package/onboard.py ADDED
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env python3
2
+ """OpenSwarm interactive setup wizard.
3
+
4
+ Run directly: python onboard.py
5
+ Auto-launched: python run.py (when no provider key is found)
6
+ """
7
+
8
+ import getpass
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from dotenv import dotenv_values, set_key
13
+ from rich import box
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.rule import Rule
17
+ from rich.table import Table
18
+
19
+ try:
20
+ import questionary
21
+ from questionary import Choice, Style as QStyle
22
+ import questionary.prompts.common as _qc_common
23
+
24
+ # Swap filled circle → checkmark for selected state.
25
+ _qc_common.INDICATOR_SELECTED = "✓"
26
+
27
+ _HAS_QUESTIONARY = True
28
+ except ImportError:
29
+ _HAS_QUESTIONARY = False
30
+
31
+ console = Console()
32
+
33
+ ENV_PATH = Path(__file__).parent / ".env"
34
+
35
+ # ── questionary theme ─────────────────────────────────────────────────────────
36
+ _QSTYLE = None
37
+ if _HAS_QUESTIONARY:
38
+ _QSTYLE = QStyle([
39
+ ("qmark", "fg:#4fc3f7 bold"),
40
+ ("question", "bold"),
41
+ ("answer", "fg:#4fc3f7 bold"),
42
+ ("pointer", "fg:#4fc3f7 bold noreverse"),
43
+ ("highlighted", "noreverse"),
44
+ ("selected", "fg:#4fc3f7 bold noreverse"),
45
+ ("separator", "fg:#555555 noreverse"),
46
+ ("instruction", "fg:#555555 italic noreverse"),
47
+ ("text", "noreverse"),
48
+ ])
49
+
50
+ # ── provider definitions ──────────────────────────────────────────────────────
51
+ PROVIDERS = [
52
+ {
53
+ "name": "OpenAI",
54
+ "env_key": "OPENAI_API_KEY",
55
+ "default_model": "gpt-5.2",
56
+ "url": "https://platform.openai.com/api-keys",
57
+ },
58
+ {
59
+ "name": "Anthropic",
60
+ "env_key": "ANTHROPIC_API_KEY",
61
+ "default_model": "litellm/claude-sonnet-4-6",
62
+ "url": "https://console.anthropic.com/settings/keys",
63
+ },
64
+ {
65
+ "name": "Google Gemini",
66
+ "env_key": "GOOGLE_API_KEY",
67
+ "default_model": "litellm/gemini/gemini-3-flash",
68
+ "url": "https://aistudio.google.com/app/apikey",
69
+ },
70
+ ]
71
+
72
+ # ── add-on definitions ────────────────────────────────────────────────────────
73
+ # exclude_for: list of provider names that already cover this key
74
+ ADD_ONS = [
75
+ {
76
+ "id": "search",
77
+ "name": "Web Search",
78
+ "description": "Web, Scholar & product search for all agents",
79
+ "keys": [
80
+ {"env": "SEARCH_API_KEY", "label": "SearchAPI key",
81
+ "url": "https://www.searchapi.io"},
82
+ ],
83
+ "exclude_for": [],
84
+ },
85
+ {
86
+ "id": "anthropic",
87
+ "name": "Anthropic Claude — better slides quality",
88
+ "description": "Claude produces significantly better slide HTML output",
89
+ "keys": [
90
+ {"env": "ANTHROPIC_API_KEY", "label": "Anthropic API key",
91
+ "url": "https://console.anthropic.com/settings/keys"},
92
+ ],
93
+ "exclude_for": ["Anthropic"],
94
+ },
95
+ {
96
+ "id": "composio",
97
+ "name": "Composio — 10,000+ integrations",
98
+ "description": "Gmail, Slack, GitHub, HubSpot, Google Calendar and more",
99
+ "keys": [
100
+ {"env": "COMPOSIO_API_KEY", "label": "Composio API key",
101
+ "url": "https://composio.dev"},
102
+ {"env": "COMPOSIO_USER_ID", "label": "Composio user ID",
103
+ "url": "https://composio.dev"},
104
+ ],
105
+ "exclude_for": [],
106
+ },
107
+ {
108
+ "id": "google",
109
+ "name": "Google Gemini — image gen & Veo video",
110
+ "description": "Gemini image generation/editing and Veo video generation",
111
+ "keys": [
112
+ {"env": "GOOGLE_API_KEY", "label": "Google AI API key",
113
+ "url": "https://aistudio.google.com/app/apikey"},
114
+ ],
115
+ "exclude_for": ["Google Gemini"],
116
+ },
117
+ {
118
+ "id": "fal",
119
+ "name": "Fal.ai — Seedance video & background removal",
120
+ "description": "Seedance 1.5 Pro video gen, video editing, background removal",
121
+ "keys": [
122
+ {"env": "FAL_KEY", "label": "Fal.ai API key",
123
+ "url": "https://fal.ai/dashboard/keys"},
124
+ ],
125
+ "exclude_for": [],
126
+ },
127
+ {
128
+ "id": "stock",
129
+ "name": "Stock photos — Pexels / Pixabay / Unsplash",
130
+ "description": "Image search for the Slides Agent",
131
+ "keys": [
132
+ {"env": "PEXELS_API_KEY", "label": "Pexels API key",
133
+ "url": "https://www.pexels.com/api"},
134
+ {"env": "PIXABAY_API_KEY", "label": "Pixabay API key",
135
+ "url": "https://pixabay.com/api/docs"},
136
+ {"env": "UNSPLASH_ACCESS_KEY", "label": "Unsplash access key",
137
+ "url": "https://unsplash.com/developers"},
138
+ ],
139
+ "exclude_for": [],
140
+ },
141
+ ]
142
+
143
+ # ── ui helpers ────────────────────────────────────────────────────────────────
144
+
145
+ def _step(n: int, label: str) -> None:
146
+ console.print()
147
+ console.print(Rule(f"[bold]Step {n} · {label}[/bold]", style="cyan"))
148
+ console.print()
149
+
150
+
151
+ def _ask_select(message: str, choices: list) -> object:
152
+ if _HAS_QUESTIONARY:
153
+ return questionary.select(message, choices=choices, style=_QSTYLE).ask()
154
+ # plain fallback
155
+ titles = [c.title if isinstance(c, Choice) else c for c in choices]
156
+ values = [c.value if isinstance(c, Choice) else c for c in choices]
157
+ console.print(f"\n[bold]{message}[/bold]")
158
+ for i, title in enumerate(titles, 1):
159
+ console.print(f" [cyan]{i}.[/cyan] {title}")
160
+ while True:
161
+ raw = input("Enter number: ").strip()
162
+ if raw.isdigit() and 1 <= int(raw) <= len(titles):
163
+ return values[int(raw) - 1]
164
+ console.print("[red]Invalid choice, try again.[/red]")
165
+
166
+
167
+ def _ask_checkbox(message: str, choices: list) -> list:
168
+ if _HAS_QUESTIONARY:
169
+ return questionary.checkbox(message, choices=choices, style=_QSTYLE, pointer="❯").ask() or []
170
+ # plain fallback — comma-separated numbers
171
+ titles = [c.title if isinstance(c, Choice) else c for c in choices]
172
+ values = [c.value if isinstance(c, Choice) else c for c in choices]
173
+ console.print(f"\n[bold]{message}[/bold]")
174
+ console.print("[dim] Enter comma-separated numbers, or press Enter to skip[/dim]")
175
+ for i, title in enumerate(titles, 1):
176
+ console.print(f" [cyan]{i}.[/cyan] {title}")
177
+ raw = input("Selection: ").strip()
178
+ if not raw:
179
+ return []
180
+ result = []
181
+ for part in raw.split(","):
182
+ part = part.strip()
183
+ if part.isdigit() and 1 <= int(part) <= len(titles):
184
+ result.append(values[int(part) - 1])
185
+ return result
186
+
187
+
188
+ def _ask_secret(label: str, url: str) -> str:
189
+ console.print(f" [dim]Get yours at[/dim] [link={url}]{url}[/link]")
190
+ if _HAS_QUESTIONARY:
191
+ val = questionary.password(f" {label}: ", style=_QSTYLE).ask()
192
+ return (val or "").strip()
193
+ return getpass.getpass(f" {label}: ").strip()
194
+
195
+
196
+ def _ask_confirm(message: str, default: bool = True) -> bool:
197
+ if _HAS_QUESTIONARY:
198
+ return questionary.confirm(message, default=default, style=_QSTYLE).ask()
199
+ prompt = f"{message} [{'Y/n' if default else 'y/N'}]: "
200
+ raw = input(prompt).strip().lower()
201
+ return default if not raw else raw in ("y", "yes")
202
+
203
+
204
+ def _write_env(updates: dict) -> None:
205
+ if not ENV_PATH.exists():
206
+ ENV_PATH.write_text("", encoding="utf-8")
207
+ for key, value in updates.items():
208
+ if value:
209
+ set_key(str(ENV_PATH), key, value)
210
+
211
+
212
+ # ── main wizard ───────────────────────────────────────────────────────────────
213
+
214
+ def run_onboarding() -> None:
215
+ console.print()
216
+ console.print(Panel.fit(
217
+ "[bold cyan]OpenSwarm[/bold cyan] [dim]— open-source multi-agent AI team[/dim]\n"
218
+ "[dim]Let's get you set up in a few steps.[/dim]",
219
+ border_style="cyan",
220
+ padding=(1, 4),
221
+ ))
222
+
223
+ existing = dotenv_values(str(ENV_PATH)) if ENV_PATH.exists() else {}
224
+ updates: dict[str, str] = {}
225
+
226
+ # ── Step 1: provider ──────────────────────────────────────────────────────
227
+ _step(1, "AI Provider")
228
+
229
+ provider_choices = [
230
+ Choice(title=p["name"], value=p)
231
+ for p in PROVIDERS
232
+ ]
233
+ provider = _ask_select("Choose your primary AI provider:", provider_choices)
234
+
235
+ # ── Step 2: API key ───────────────────────────────────────────────────────
236
+ _step(2, "API Key")
237
+
238
+ existing_key = existing.get(provider["env_key"], "")
239
+ if existing_key:
240
+ console.print(f" [dim]{provider['env_key']} is already configured.[/dim]")
241
+ if _ask_confirm(" Update it?", default=False):
242
+ key = _ask_secret(f"{provider['name']} API key", provider["url"])
243
+ updates[provider["env_key"]] = key or existing_key
244
+ else:
245
+ updates[provider["env_key"]] = existing_key
246
+ else:
247
+ key = _ask_secret(f"{provider['name']} API key", provider["url"])
248
+ if key:
249
+ updates[provider["env_key"]] = key
250
+
251
+ updates["DEFAULT_MODEL"] = provider["default_model"]
252
+
253
+ # ── Step 3: add-ons ───────────────────────────────────────────────────────
254
+ _step(3, "Add-ons [dim](optional)[/dim]")
255
+
256
+ available = [a for a in ADD_ONS if provider["name"] not in a["exclude_for"]]
257
+ addon_choices = [
258
+ Choice(
259
+ title=(
260
+ [
261
+ ("class:text", a["name"]),
262
+ ("fg:#555555", " · "),
263
+ ("fg:#666666", a["description"]),
264
+ ]
265
+ if _HAS_QUESTIONARY
266
+ else f"{a['name']} — {a['description']}"
267
+ ),
268
+ value=a["id"],
269
+ )
270
+ for a in available
271
+ ]
272
+ selected_ids = _ask_checkbox("Select add-ons to enable:", addon_choices)
273
+ selected_addons = [a for a in available if a["id"] in selected_ids]
274
+
275
+ # ── Step 4: add-on keys ───────────────────────────────────────────────────
276
+ if selected_addons:
277
+ _step(4, "Add-on Keys")
278
+ for addon in selected_addons:
279
+ console.print(f"\n [bold]{addon['name'].split(' ')[0]}[/bold]")
280
+ for key_spec in addon["keys"]:
281
+ existing_val = existing.get(key_spec["env"], "")
282
+ if existing_val:
283
+ console.print(f" [dim]{key_spec['env']} is already configured.[/dim]")
284
+ if not _ask_confirm(" Update it?", default=False):
285
+ updates[key_spec["env"]] = existing_val
286
+ continue
287
+ val = _ask_secret(key_spec["label"], key_spec["url"])
288
+ if val:
289
+ updates[key_spec["env"]] = val
290
+
291
+ # ── write .env ────────────────────────────────────────────────────────────
292
+ _write_env(updates)
293
+
294
+ # ── summary ───────────────────────────────────────────────────────────────
295
+ console.print()
296
+ console.print(Rule("[bold green]Setup complete[/bold green]", style="green"))
297
+ console.print()
298
+
299
+ table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
300
+ table.add_column(style="dim", no_wrap=True)
301
+ table.add_column()
302
+ table.add_row("Provider", f"[cyan]{provider['name']}[/cyan]")
303
+ table.add_row("Model", f"[cyan]{provider['default_model']}[/cyan]")
304
+ table.add_row(".env", f"[cyan]{ENV_PATH}[/cyan]")
305
+ saved = [k for k, v in updates.items() if v and not k.startswith("DEFAULT_")]
306
+ if saved:
307
+ table.add_row("Keys saved", f"[cyan]{', '.join(saved)}[/cyan]")
308
+ console.print(table)
309
+
310
+ console.print()
311
+ console.print(Panel(
312
+ "[bold]python swarm.py[/bold] [dim]launch interactive terminal[/dim]\n"
313
+ "[bold]python server.py[/bold] [dim]start the API server[/dim]",
314
+ border_style="green",
315
+ padding=(0, 3),
316
+ ))
317
+ console.print()
318
+
319
+
320
+ if __name__ == "__main__":
321
+ try:
322
+ run_onboarding()
323
+ except KeyboardInterrupt:
324
+ console.print("\n\n[dim]Setup cancelled.[/dim]\n")
325
+ sys.exit(0)
@@ -0,0 +1,3 @@
1
+ from .orchestrator import create_orchestrator
2
+
3
+ __all__ = ["create_orchestrator"]